From ced28ad00611e562dab0e8a9b1365c684fcca1f7 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Sat, 23 Mar 2019 18:58:25 -0300 Subject: [PATCH 0001/1841] Better symlink support under Windows (#487) * Better symlink support under Windows * Conditional loading of ctypes wintypes module * Shortening comment line for pylint * Adding plint bypass for Python 3 --- esphome/helpers.py | 13 ---- esphome/symlink_ops.py | 164 +++++++++++++++++++++++++++++++++++++++++ esphome/writer.py | 13 ++-- 3 files changed, 171 insertions(+), 19 deletions(-) create mode 100644 esphome/symlink_ops.py diff --git a/esphome/helpers.py b/esphome/helpers.py index 4def9568f4..01b31111b9 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -140,16 +140,3 @@ def get_bool_env(var, default=False): def is_hassio(): return get_bool_env('ESPHOME_IS_HASSIO') - - -def symlink(src, dst): - if hasattr(os, 'symlink'): - os.symlink(src, dst) - else: - import ctypes - csl = ctypes.windll.kernel32.CreateSymbolicLinkW - csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32) - csl.restype = ctypes.c_ubyte - flags = 1 if os.path.isdir(src) else 0 - if csl(dst, src, flags) == 0: - raise ctypes.WinError() diff --git a/esphome/symlink_ops.py b/esphome/symlink_ops.py new file mode 100644 index 0000000000..accfbe5ff7 --- /dev/null +++ b/esphome/symlink_ops.py @@ -0,0 +1,164 @@ +import os + +if hasattr(os, 'symlink'): + def symlink(src, dst): + return os.symlink(src, dst) + + def islink(path): + return os.path.islink(path) + + def readlink(path): + return os.readlink(path) + + def unlink(path): + return os.unlink(path) +else: + import ctypes + from ctypes import wintypes + # Code taken from + # https://stackoverflow.com/questions/27972776/having-trouble-implementing-a-readlink-function + + kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) + + FILE_READ_ATTRIBUTES = 0x0080 + OPEN_EXISTING = 3 + FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 + FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 + FILE_ATTRIBUTE_REPARSE_POINT = 0x0400 + + IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003 + IO_REPARSE_TAG_SYMLINK = 0xA000000C + FSCTL_GET_REPARSE_POINT = 0x000900A8 + MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 0x4000 + + LPDWORD = ctypes.POINTER(wintypes.DWORD) + LPWIN32_FIND_DATA = ctypes.POINTER(wintypes.WIN32_FIND_DATAW) + INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value + + def IsReparseTagNameSurrogate(tag): + return bool(tag & 0x20000000) + + def _check_invalid_handle(result, func, args): + if result == INVALID_HANDLE_VALUE: + raise ctypes.WinError(ctypes.get_last_error()) + return args + + def _check_bool(result, func, args): + if not result: + raise ctypes.WinError(ctypes.get_last_error()) + return args + + kernel32.FindFirstFileW.errcheck = _check_invalid_handle + kernel32.FindFirstFileW.restype = wintypes.HANDLE + kernel32.FindFirstFileW.argtypes = ( + wintypes.LPCWSTR, # _In_ lpFileName + LPWIN32_FIND_DATA) # _Out_ lpFindFileData + + kernel32.FindClose.argtypes = ( + wintypes.HANDLE,) # _Inout_ hFindFile + + kernel32.CreateFileW.errcheck = _check_invalid_handle + kernel32.CreateFileW.restype = wintypes.HANDLE + kernel32.CreateFileW.argtypes = ( + wintypes.LPCWSTR, # _In_ lpFileName + wintypes.DWORD, # _In_ dwDesiredAccess + wintypes.DWORD, # _In_ dwShareMode + wintypes.LPVOID, # _In_opt_ lpSecurityAttributes + wintypes.DWORD, # _In_ dwCreationDisposition + wintypes.DWORD, # _In_ dwFlagsAndAttributes + wintypes.HANDLE) # _In_opt_ hTemplateFile + + kernel32.CloseHandle.argtypes = ( + wintypes.HANDLE,) # _In_ hObject + + kernel32.DeviceIoControl.errcheck = _check_bool + kernel32.DeviceIoControl.argtypes = ( + wintypes.HANDLE, # _In_ hDevice + wintypes.DWORD, # _In_ dwIoControlCode + wintypes.LPVOID, # _In_opt_ lpInBuffer + wintypes.DWORD, # _In_ nInBufferSize + wintypes.LPVOID, # _Out_opt_ lpOutBuffer + wintypes.DWORD, # _In_ nOutBufferSize + LPDWORD, # _Out_opt_ lpBytesReturned + wintypes.LPVOID) # _Inout_opt_ lpOverlapped + + class REPARSE_DATA_BUFFER(ctypes.Structure): + class ReparseData(ctypes.Union): + class LinkData(ctypes.Structure): + _fields_ = (('SubstituteNameOffset', wintypes.USHORT), + ('SubstituteNameLength', wintypes.USHORT), + ('PrintNameOffset', wintypes.USHORT), + ('PrintNameLength', wintypes.USHORT)) + + @property + def PrintName(self): + dt = wintypes.WCHAR * (self.PrintNameLength // ctypes.sizeof(wintypes.WCHAR)) + name = dt.from_address(ctypes.addressof(self.PathBuffer) + + self.PrintNameOffset).value + if name.startswith(r'\??'): + name = r'\\?' + name[3:] # NT => Windows + return name + + class SymbolicLinkData(LinkData): + _fields_ = (('Flags', wintypes.ULONG), ('PathBuffer', wintypes.BYTE * 0)) + + class MountPointData(LinkData): + _fields_ = (('PathBuffer', wintypes.BYTE * 0),) + + class GenericData(ctypes.Structure): + _fields_ = (('DataBuffer', wintypes.BYTE * 0),) + _fields_ = (('SymbolicLinkReparseBuffer', SymbolicLinkData), + ('MountPointReparseBuffer', MountPointData), + ('GenericReparseBuffer', GenericData)) + _fields_ = (('ReparseTag', wintypes.ULONG), + ('ReparseDataLength', wintypes.USHORT), + ('Reserved', wintypes.USHORT), + ('ReparseData', ReparseData)) + _anonymous_ = ('ReparseData',) + + def symlink(src, dst): + csl = ctypes.windll.kernel32.CreateSymbolicLinkW + csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32) + csl.restype = ctypes.c_ubyte + flags = 1 if os.path.isdir(src) else 0 + if csl(dst, src, flags) == 0: + error = ctypes.WinError() + # pylint: disable=no-member + if error.winerror == 1314 and error.errno == 22: + from esphome.core import EsphomeError + raise EsphomeError("Cannot create symlink from '%s' to '%s'. Try running tool \ +with elevated privileges" % (src, dst)) + raise error + + def islink(path): + if not os.path.isdir(path): + return False + data = wintypes.WIN32_FIND_DATAW() + kernel32.FindClose(kernel32.FindFirstFileW(path, ctypes.byref(data))) + if not data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT: + return False + return IsReparseTagNameSurrogate(data.dwReserved0) + + def readlink(path): + n = wintypes.DWORD() + buf = (wintypes.BYTE * MAXIMUM_REPARSE_DATA_BUFFER_SIZE)() + flags = FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS + handle = kernel32.CreateFileW(path, FILE_READ_ATTRIBUTES, 0, None, + OPEN_EXISTING, flags, None) + try: + kernel32.DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, None, 0, + buf, ctypes.sizeof(buf), ctypes.byref(n), None) + finally: + kernel32.CloseHandle(handle) + rb = REPARSE_DATA_BUFFER.from_buffer(buf) + tag = rb.ReparseTag + if tag == IO_REPARSE_TAG_SYMLINK: + return rb.SymbolicLinkReparseBuffer.PrintName + if tag == IO_REPARSE_TAG_MOUNT_POINT: + return rb.MountPointReparseBuffer.PrintName + if not IsReparseTagNameSurrogate(tag): + raise ValueError("not a link") + raise ValueError("unsupported reparse tag: %d" % tag) + + def unlink(path): + return os.rmdir(path) diff --git a/esphome/writer.py b/esphome/writer.py index 0d62372587..450b974592 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -13,7 +13,8 @@ from esphome.const import ARDUINO_VERSION_ESP32_1_0_0, ARDUINO_VERSION_ESP8266_2 CONF_LOCAL, CONF_PLATFORMIO_OPTIONS, CONF_REPOSITORY, CONF_TAG, CONF_USE_CUSTOM_CODE from esphome.core import CORE, EsphomeError from esphome.core_config import GITHUB_ARCHIVE_ZIP, LIBRARY_URI_REPO, VERSION_REGEX -from esphome.helpers import mkdir_p, run_system_command, symlink +from esphome.helpers import mkdir_p, run_system_command +from esphome.symlink_ops import symlink, islink, readlink, unlink from esphome.pins import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS from esphome.py_compat import IS_PY3, string_types from esphome.storage_json import StorageJSON, storage_path @@ -220,10 +221,10 @@ def symlink_esphome_core_version(esphome_core_version): if CORE.is_local_esphome_core_copy: src_path = CORE.relative_path(esphome_core_version[CONF_LOCAL]) do_write = True - if os.path.islink(dst_path): - old_path = os.path.join(os.readlink(dst_path), lib_path) + if islink(dst_path): + old_path = os.path.join(readlink(dst_path), lib_path) if old_path != lib_path: - os.unlink(dst_path) + unlink(dst_path) else: do_write = False if do_write: @@ -231,8 +232,8 @@ def symlink_esphome_core_version(esphome_core_version): symlink(src_path, dst_path) else: # Remove symlink when changing back from local version - if os.path.islink(dst_path): - os.unlink(dst_path) + if islink(dst_path): + unlink(dst_path) def format_ini(data): From 356554c08d78c57db9ce29bdb43255c70d6e017a Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 24 Mar 2019 18:05:13 +0100 Subject: [PATCH 0002/1841] ESP8266 SDK 2.3.0 compat (#490) --- esphome/const.py | 1 + esphome/writer.py | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/esphome/const.py b/esphome/const.py index 088750b93b..07d2c4fa9b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -433,3 +433,4 @@ ARDUINO_VERSION_ESP32_1_0_1 = 'espressif32@1.6.0' ARDUINO_VERSION_ESP8266_DEV = 'https://github.com/platformio/platform-espressif8266.git#feature' \ '/stage' ARDUINO_VERSION_ESP8266_2_5_0 = 'espressif8266@2.0.0' +ARDUINO_VERSION_ESP8266_2_3_0 = 'espressif8266@1.5.0' diff --git a/esphome/writer.py b/esphome/writer.py index 450b974592..953680e467 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -10,7 +10,8 @@ import shutil from esphome.config import iter_components from esphome.const import ARDUINO_VERSION_ESP32_1_0_0, ARDUINO_VERSION_ESP8266_2_5_0, \ ARDUINO_VERSION_ESP8266_DEV, CONF_BOARD_FLASH_MODE, CONF_BRANCH, CONF_COMMIT, CONF_ESPHOME, \ - CONF_LOCAL, CONF_PLATFORMIO_OPTIONS, CONF_REPOSITORY, CONF_TAG, CONF_USE_CUSTOM_CODE + CONF_LOCAL, CONF_PLATFORMIO_OPTIONS, CONF_REPOSITORY, CONF_TAG, CONF_USE_CUSTOM_CODE, \ + ARDUINO_VERSION_ESP8266_2_3_0 from esphome.core import CORE, EsphomeError from esphome.core_config import GITHUB_ARCHIVE_ZIP, LIBRARY_URI_REPO, VERSION_REGEX from esphome.helpers import mkdir_p, run_system_command @@ -342,13 +343,14 @@ def gather_build_flags(): '-DUSE_WIFI_SIGNAL_SENSOR', } - if CORE.is_esp8266 and CORE.board in ESP8266_FLASH_SIZES: + if CORE.is_esp8266 and CORE.board in ESP8266_FLASH_SIZES and \ + CORE.arduino_version != ARDUINO_VERSION_ESP8266_2_3_0: flash_size = ESP8266_FLASH_SIZES[CORE.board] ld_scripts = ESP8266_LD_SCRIPTS[flash_size] ld_script = None if CORE.arduino_version in ('espressif8266@1.8.0', 'espressif8266@1.7.3', - 'espressif8266@1.6.0', 'espressif8266@1.5.0'): + 'espressif8266@1.6.0'): ld_script = ld_scripts[0] elif CORE.arduino_version in (ARDUINO_VERSION_ESP8266_DEV, ARDUINO_VERSION_ESP8266_2_5_0): ld_script = ld_scripts[1] From 300d3a1f466ffdfbc0085328a929203ec4fbf874 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 30 Mar 2019 15:47:12 +0100 Subject: [PATCH 0003/1841] Upgrade ESPAsyncTCP to 1.2.0 (#497) --- esphome/components/api.py | 2 +- esphome/writer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/api.py b/esphome/components/api.py index 86e77bb4c6..544d0e418b 100644 --- a/esphome/components/api.py +++ b/esphome/components/api.py @@ -86,7 +86,7 @@ def lib_deps(config): if CORE.is_esp32: return 'AsyncTCP@1.0.3' if CORE.is_esp8266: - return 'ESPAsyncTCP@1.1.3' + return 'ESPAsyncTCP@1.2.0' raise NotImplementedError diff --git a/esphome/writer.py b/esphome/writer.py index 953680e467..9c2bc8fbd8 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -290,7 +290,7 @@ def gather_lib_deps(): lib_deps.add('AsyncTCP@1.0.1') lib_deps.add('ESPmDNS') elif CORE.is_esp8266: - lib_deps.add('ESPAsyncTCP@1.1.3') + lib_deps.add('ESPAsyncTCP@1.2.0') lib_deps.add('ESP8266mDNS') # avoid changing build flags order From e7e785fd60a652acb0099600935817c2df3fb201 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 31 Mar 2019 11:04:41 +0200 Subject: [PATCH 0004/1841] Fix dashboard wizard unicode (#494) * Fix dashboard wizard unicode Fixes https://github.com/esphome/issues/issues/169 * Fix password md5 --- esphome/dashboard/dashboard.py | 6 +++--- esphome/espota2.py | 2 +- esphome/py_compat.py | 10 ++++++++++ esphome/wizard.py | 2 +- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index a70fe2add2..6c1d9f2616 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -28,7 +28,7 @@ import tornado.websocket from esphome import const from esphome.__main__ import get_serial_ports from esphome.helpers import mkdir_p, get_bool_env, run_system_command -from esphome.py_compat import IS_PY2 +from esphome.py_compat import IS_PY2, decode_text from esphome.storage_json import EsphomeStorageJSON, StorageJSON, \ esphome_storage_path, ext_storage_path, trash_storage_path from esphome.util import shlex_quote @@ -223,8 +223,8 @@ class WizardRequestHandler(BaseHandler): def post(self): from esphome import wizard - kwargs = {k: ''.join(v) for k, v in self.request.arguments.items()} - destination = os.path.join(CONFIG_DIR, kwargs['name'] + '.yaml') + kwargs = {k: u''.join(decode_text(x) for x in v) for k, v in self.request.arguments.items()} + destination = os.path.join(CONFIG_DIR, kwargs['name'] + u'.yaml') wizard.wizard_write(path=destination, **kwargs) self.redirect('/?begin=True') diff --git a/esphome/espota2.py b/esphome/espota2.py index dbe7e94313..ac0dd33ebc 100755 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -195,7 +195,7 @@ def perform_ota(sock, password, file_handle, filename): send_check(sock, cnonce, 'auth cnonce') result_md5 = hashlib.md5() - result_md5.update(password.encode()) + result_md5.update(password.encode('utf-8')) result_md5.update(nonce.encode()) result_md5.update(cnonce.encode()) result = result_md5.hexdigest() diff --git a/esphome/py_compat.py b/esphome/py_compat.py index 16a4d27ebf..4b48aa0f88 100644 --- a/esphome/py_compat.py +++ b/esphome/py_compat.py @@ -69,3 +69,13 @@ def indexbytes(buf, i): return buf[i] else: return ord(buf[i]) + + +if IS_PY2: + def decode_text(data, encoding='utf-8', errors='strict'): + # type: (str, str, str) -> unicode + return unicode(data, encoding='utf-8', errors=errors) +else: + def decode_text(data, encoding='utf-8', errors='strict'): + # type: (bytes, str, str) -> str + return data.decode(encoding='utf-8', errors=errors) diff --git a/esphome/wizard.py b/esphome/wizard.py index a2401584a1..abc2270235 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -79,7 +79,7 @@ def wizard_write(path, **kwargs): kwargs['platform'] = 'ESP8266' if board in ESP8266_BOARD_PINS else 'ESP32' platform = kwargs['platform'] - with codecs.open(path, 'w') as f_handle: + with codecs.open(path, 'w', 'utf-8') as f_handle: f_handle.write(wizard_file(**kwargs)) storage = StorageJSON.from_wizard(name, name + '.local', platform, board) storage_path = ext_storage_path(os.path.dirname(path), os.path.basename(path)) From 41db8a12646d01577c42f7be4e092ebf4f61afb2 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 31 Mar 2019 11:04:57 +0200 Subject: [PATCH 0005/1841] Fix text sensor MQTT settings (#495) Fixes https://github.com/esphome/issues/issues/170 --- esphome/components/text_sensor/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index a4a6caec26..1aff0c1e3b 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -46,7 +46,7 @@ def setup_text_sensor_core_(text_sensor_var, config): trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs) automation.build_automations(trigger, [(std_string, 'x')], conf) - setup_mqtt_component(text_sensor_var.get_mqtt(), config) + setup_mqtt_component(text_sensor_var.Pget_mqtt(), config) def setup_text_sensor(text_sensor_obj, config): From cda9bad233beaf47f4796e4544f3b2deae70e68e Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 31 Mar 2019 12:25:44 +0200 Subject: [PATCH 0006/1841] Upgrade docker base image to 1.4.3 (#499) --- .gitlab-ci.yml | 4 ++-- docker/Dockerfile | 2 +- docker/Dockerfile.hassio | 2 +- docker/hooks/build | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0cc29416c9..f4e2fa40af 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -41,11 +41,11 @@ stages: - | if [[ "${IS_HASSIO}" == "YES" ]]; then - BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:1.4.1 + BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:1.4.3 BUILD_TO=esphome/esphome-hassio-${BUILD_ARCH} DOCKERFILE=docker/Dockerfile.hassio else - BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:1.4.1 + BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:1.4.3 if [[ "${BUILD_ARCH}" == "amd64" ]]; then BUILD_TO=esphome/esphome else diff --git a/docker/Dockerfile b/docker/Dockerfile index 2568ef37f3..0416f2496e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG BUILD_FROM=esphome/esphome-base-amd64:1.4.1 +ARG BUILD_FROM=esphome/esphome-base-amd64:1.4.3 FROM ${BUILD_FROM} COPY . . diff --git a/docker/Dockerfile.hassio b/docker/Dockerfile.hassio index 95888739bd..eaa7d9d504 100644 --- a/docker/Dockerfile.hassio +++ b/docker/Dockerfile.hassio @@ -1,4 +1,4 @@ -ARG BUILD_FROM=esphome/esphome-hassio-base-amd64:1.4.1 +ARG BUILD_FROM=esphome/esphome-hassio-base-amd64:1.4.3 FROM ${BUILD_FROM} # Copy root filesystem diff --git a/docker/hooks/build b/docker/hooks/build index 083c5c50a8..60879aaa88 100755 --- a/docker/hooks/build +++ b/docker/hooks/build @@ -16,11 +16,11 @@ echo "PWD: $PWD" if [[ ${IS_HASSIO} = "YES" ]]; then docker build \ - --build-arg "BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:1.4.1" \ + --build-arg "BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:1.4.3" \ --build-arg "BUILD_VERSION=${CACHE_TAG}" \ -t "${IMAGE_NAME}" -f ../docker/Dockerfile.hassio .. else docker build \ - --build-arg "BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:1.4.1" \ + --build-arg "BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:1.4.3" \ -t "${IMAGE_NAME}" -f ../docker/Dockerfile .. fi From ac0b095941ff156baf68eab91bfc732195ceb69d Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 31 Mar 2019 13:13:12 +0200 Subject: [PATCH 0007/1841] Bump version to v1.12.2 --- esphome/const.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/const.py b/esphome/const.py index 07d2c4fa9b..ba1cdc247d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,10 +2,10 @@ MAJOR_VERSION = 1 MINOR_VERSION = 12 -PATCH_VERSION = '1' +PATCH_VERSION = '2' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) -ESPHOME_CORE_VERSION = '1.12.1' +ESPHOME_CORE_VERSION = '1.12.2' ESP_PLATFORM_ESP32 = 'ESP32' ESP_PLATFORM_ESP8266 = 'ESP8266' From a676ff23ded4fb533f6ceb0b7e81666efdf6c60a Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 27 May 2019 21:50:36 +0200 Subject: [PATCH 0008/1841] Bump version to v1.13.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index d285105df4..45f72a19fd 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -3,7 +3,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 13 -PATCH_VERSION = '0-dev' +PATCH_VERSION = '0b1' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) From 6a17fe375e258ebe62100ead6318586946586ee9 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 27 May 2019 21:55:54 +0200 Subject: [PATCH 0009/1841] Update .gitlab-ci.yml --- .gitlab-ci.yml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 94116bcee1..e26eb902bc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -13,8 +13,7 @@ stages: image: esphome/esphome-base-amd64 stage: lint before_script: - - pip install -e . - - pip install flake8==3.6.0 pylint==1.9.4 pillow + - script/setup tags: - docker @@ -22,7 +21,7 @@ stages: image: esphome/esphome-base-amd64 stage: test before_script: - - pip install -e . + - script/setup tags: - docker variables: @@ -94,15 +93,11 @@ stages: - docker stage: deploy -flake8: +lint-python: <<: *lint script: - - flake8 esphome - -pylint: - <<: *lint - script: - - pylint esphome + - script/ci-custom.py + - script/lint-python test1: <<: *test From 53e8b3ed3e22a0c976b7eb412f505345503ad1e3 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 28 May 2019 10:23:15 +0200 Subject: [PATCH 0010/1841] Update gitlab CI script, add cpp lint --- .gitlab-ci.yml | 177 +++++++++++++++++++++-------------------- docker/Dockerfile.lint | 20 ++++- requirements_test.txt | 1 + script/lint-python | 29 ++++--- 4 files changed, 126 insertions(+), 101 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e26eb902bc..5b247e386f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,6 +3,8 @@ variables: DOCKER_DRIVER: overlay2 DOCKER_HOST: tcp://docker:2375/ + BASE_VERSION: '1.5.1' + TZ: UTC stages: - lint @@ -10,7 +12,7 @@ stages: - deploy .lint: &lint - image: esphome/esphome-base-amd64 + image: esphome/esphome-lint:latest stage: lint before_script: - script/setup @@ -18,14 +20,12 @@ stages: - docker .test: &test - image: esphome/esphome-base-amd64 + image: esphome/esphome-base-amd64:${BASE_VERSION} stage: test before_script: - script/setup tags: - docker - variables: - TZ: UTC .docker-base: &docker-base image: esphome/esphome-base-builder @@ -40,11 +40,11 @@ stages: - | if [[ "${IS_HASSIO}" == "YES" ]]; then - BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:1.5.1 + BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:${BASE_VERSION} BUILD_TO=esphome/esphome-hassio-${BUILD_ARCH} DOCKERFILE=docker/Dockerfile.hassio else - BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:1.5.1 + BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:${BASE_VERSION} if [[ "${BUILD_ARCH}" == "amd64" ]]; then BUILD_TO=esphome/esphome else @@ -93,12 +93,33 @@ stages: - docker stage: deploy -lint-python: +lint-custom: <<: *lint script: - script/ci-custom.py + +lint-python: + <<: *lint + script: - script/lint-python +lint-tidy: + <<: *lint + script: + - pio init --ide atom + - | + if ! patch -R -p0 -s -f --dry-run - - - {% 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 14116484af..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/wizard.py b/esphome/wizard.py index 7d875b7dd2..0d912e4bbf 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -49,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: @@ -83,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 @@ -97,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" diff --git a/requirements.txt b/requirements.txt index 5915c2963f..e7f69865da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 +esphome-dashboard==20210611.0 From 4b91cfb7f90f3d34de63428a1df08c30f09ab7a5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 11 Jun 2021 23:28:00 +1200 Subject: [PATCH 0900/1841] Ensure wifi is in at least station mode before starting improv (#1899) --- esphome/components/wifi/wifi_component.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index de7ffb4f09..e98754fac7 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -79,7 +79,8 @@ void WiFiComponent::setup() { } #ifdef USE_IMPROV if (esp32_improv::global_improv_component != nullptr) - esp32_improv::global_improv_component->start(); + if (this->wifi_mode_(true, {})) + esp32_improv::global_improv_component->start(); #endif this->wifi_apply_hostname_(); #if defined(ARDUINO_ARCH_ESP32) && defined(USE_MDNS) @@ -143,11 +144,11 @@ void WiFiComponent::loop() { } #ifdef USE_IMPROV - if (esp32_improv::global_improv_component != nullptr) { - if (!this->is_connected()) { - esp32_improv::global_improv_component->start(); - } - } + if (esp32_improv::global_improv_component != nullptr) + if (!this->is_connected()) + if (this->wifi_mode_(true, {})) + esp32_improv::global_improv_component->start(); + #endif if (!this->has_ap() && this->reboot_timeout_ != 0) { From 575badc6901ad0c960b8080b0ac268e2e450c285 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 12 Jun 2021 08:31:15 +1200 Subject: [PATCH 0901/1841] Move esp32_ble_server to its own component (#1898) --- CODEOWNERS | 1 + esphome/components/esp32_ble/__init__.py | 26 +--------- esphome/components/esp32_ble/ble.cpp | 11 +++++ esphome/components/esp32_ble/ble.h | 27 +++++++++-- .../components/esp32_ble/ble_advertising.cpp | 4 ++ .../components/esp32_ble/ble_advertising.h | 1 + .../components/esp32_ble_server/__init__.py | 39 +++++++++++++++ .../ble_2901.cpp | 8 ++-- .../ble_2901.h | 4 +- .../ble_2902.cpp | 8 ++-- .../ble_2902.h | 4 +- .../ble_characteristic.cpp | 30 ++++++------ .../ble_characteristic.h | 12 +++-- .../ble_descriptor.cpp | 6 +-- .../ble_descriptor.h | 8 ++-- .../ble_server.cpp | 39 ++++++--------- .../ble_server.h | 23 +++++---- .../ble_service.cpp | 6 +-- .../ble_service.h | 8 ++-- esphome/components/esp32_improv/__init__.py | 11 +++-- .../esp32_improv/esp32_improv_component.cpp | 48 ++++++++----------- .../esp32_improv/esp32_improv_component.h | 30 ++++++------ esphome/core/defines.h | 1 + tests/test5.yaml | 7 +-- 24 files changed, 204 insertions(+), 158 deletions(-) create mode 100644 esphome/components/esp32_ble_server/__init__.py rename esphome/components/{esp32_ble => esp32_ble_server}/ble_2901.cpp (67%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_2901.h (80%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_2902.cpp (51%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_2902.h (75%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_characteristic.cpp (86%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_characteristic.h (94%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_descriptor.cpp (95%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_descriptor.h (87%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_server.cpp (84%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_server.h (89%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_service.cpp (97%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_service.h (93%) diff --git a/CODEOWNERS b/CODEOWNERS index 3f1abae5df..0594a60ef6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -36,6 +36,7 @@ esphome/components/dfplayer/* @glmnet esphome/components/dht/* @OttoWinter esphome/components/ds1307/* @badbadc0ffee esphome/components/esp32_ble/* @jesserockz +esphome/components/esp32_ble_server/* @jesserockz esphome/components/esp32_improv/* @jesserockz esphome/components/exposure_notifications/* @OttoWinter esphome/components/ezo/* @ssieb diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index 6437216f69..ccf1f6cafe 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -1,30 +1,18 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_MODEL, ESP_PLATFORM_ESP32 +from esphome.const import CONF_ID, ESP_PLATFORM_ESP32 ESP_PLATFORMS = [ESP_PLATFORM_ESP32] CODEOWNERS = ["@jesserockz"] - -CONF_MANUFACTURER = "manufacturer" -CONF_SERVER = "server" +CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble") ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component) -BLEServer = esp32_ble_ns.class_("BLEServer", cg.Component) - -BLEServiceComponent = esp32_ble_ns.class_("BLEServiceComponent") CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(ESP32BLE), - cv.Optional(CONF_SERVER): cv.Schema( - { - cv.GenerateID(): cv.declare_id(BLEServer), - cv.Optional(CONF_MANUFACTURER, default="ESPHome"): cv.string, - cv.Optional(CONF_MODEL): cv.string, - } - ), } ).extend(cv.COMPONENT_SCHEMA) @@ -32,13 +20,3 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - - if CONF_SERVER in config: - conf = config[CONF_SERVER] - server = cg.new_Pvariable(conf[CONF_ID]) - await cg.register_component(server, conf) - cg.add(server.set_manufacturer(conf[CONF_MANUFACTURER])) - if CONF_MODEL in conf: - cg.add(server.set_model(conf[CONF_MODEL])) - cg.add_define("USE_ESP32_BLE_SERVER") - cg.add(var.set_server(server)) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 889c793017..18e80754df 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -1,4 +1,5 @@ #include "ble.h" + #include "esphome/core/application.h" #include "esphome/core/log.h" @@ -26,14 +27,22 @@ void ESP32BLE::setup() { return; } + this->advertising_ = new BLEAdvertising(); + + this->advertising_->set_scan_response(true); + this->advertising_->set_min_preferred_interval(0x06); + this->advertising_->start(); + ESP_LOGD(TAG, "BLE setup complete"); } void ESP32BLE::mark_failed() { Component::mark_failed(); +#ifdef USE_ESP32_BLE_SERVER if (this->server_ != nullptr) { this->server_->mark_failed(); } +#endif } bool ESP32BLE::ble_setup_() { @@ -142,7 +151,9 @@ void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gat void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { ESP_LOGV(TAG, "(BLE) gatts_event [esp_gatt_if: %d] - %d", gatts_if, event); +#ifdef USE_ESP32_BLE_SERVER this->server_->gatts_event_handler(event, gatts_if, param); +#endif } void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 27cb9a2092..149f0008a4 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -1,16 +1,21 @@ #pragma once +#include "ble_advertising.h" + #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/helpers.h" -#include "ble_server.h" #include "queue.h" +#ifdef USE_ESP32_BLE_SERVER +#include "esphome/components/esp32_ble_server/ble_server.h" +#endif + #ifdef ARDUINO_ARCH_ESP32 #include #include #include - namespace esphome { namespace esp32_ble { @@ -28,11 +33,20 @@ class ESP32BLE : public Component { float get_setup_priority() const override; void mark_failed() override; - bool has_server() { return this->server_ != nullptr; } + bool has_server() { +#ifdef USE_ESP32_BLE_SERVER + return this->server_ != nullptr; +#else + return false; +#endif + } bool has_client() { return false; } - void set_server(BLEServer *server) { this->server_ = server; } + BLEAdvertising *get_advertising() { return this->advertising_; } +#ifdef USE_ESP32_BLE_SERVER + void set_server(esp32_ble_server::BLEServer *server) { this->server_ = server; } +#endif protected: static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); @@ -44,8 +58,11 @@ class ESP32BLE : public Component { bool ble_setup_(); - BLEServer *server_{nullptr}; +#ifdef USE_ESP32_BLE_SERVER + esp32_ble_server::BLEServer *server_{nullptr}; +#endif Queue ble_events_; + BLEAdvertising *advertising_; }; extern ESP32BLE *global_ble; diff --git a/esphome/components/esp32_ble/ble_advertising.cpp b/esphome/components/esp32_ble/ble_advertising.cpp index 6162bf3a40..f215fb48a3 100644 --- a/esphome/components/esp32_ble/ble_advertising.cpp +++ b/esphome/components/esp32_ble/ble_advertising.cpp @@ -32,6 +32,10 @@ BLEAdvertising::BLEAdvertising() { } void BLEAdvertising::add_service_uuid(ESPBTUUID uuid) { this->advertising_uuids_.push_back(uuid); } +void BLEAdvertising::remove_service_uuid(ESPBTUUID uuid) { + this->advertising_uuids_.erase(std::remove(this->advertising_uuids_.begin(), this->advertising_uuids_.end(), uuid), + this->advertising_uuids_.end()); +} void BLEAdvertising::start() { int num_services = this->advertising_uuids_.size(); diff --git a/esphome/components/esp32_ble/ble_advertising.h b/esphome/components/esp32_ble/ble_advertising.h index 405edbf482..d86089f333 100644 --- a/esphome/components/esp32_ble/ble_advertising.h +++ b/esphome/components/esp32_ble/ble_advertising.h @@ -17,6 +17,7 @@ class BLEAdvertising { BLEAdvertising(); void add_service_uuid(ESPBTUUID uuid); + void remove_service_uuid(ESPBTUUID uuid); void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; } void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; } diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py new file mode 100644 index 0000000000..0dcbcadc50 --- /dev/null +++ b/esphome/components/esp32_ble_server/__init__.py @@ -0,0 +1,39 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_MODEL, ESP_PLATFORM_ESP32 +from esphome.components import esp32_ble + +AUTO_LOAD = ["esp32_ble"] +CODEOWNERS = ["@jesserockz"] +CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] +ESP_PLATFORMS = [ESP_PLATFORM_ESP32] + +CONF_MANUFACTURER = "manufacturer" +CONF_BLE_ID = "ble_id" + +esp32_ble_server_ns = cg.esphome_ns.namespace("esp32_ble_server") +BLEServer = esp32_ble_server_ns.class_("BLEServer", cg.Component) +BLEServiceComponent = esp32_ble_server_ns.class_("BLEServiceComponent") + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(BLEServer), + cv.GenerateID(CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE), + cv.Optional(CONF_MANUFACTURER, default="ESPHome"): cv.string, + cv.Optional(CONF_MODEL): cv.string, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_BLE_ID]) + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + cg.add(var.set_manufacturer(config[CONF_MANUFACTURER])) + if CONF_MODEL in config: + cg.add(var.set_model(config[CONF_MODEL])) + cg.add_define("USE_ESP32_BLE_SERVER") + + cg.add(parent.set_server(var)) diff --git a/esphome/components/esp32_ble/ble_2901.cpp b/esphome/components/esp32_ble_server/ble_2901.cpp similarity index 67% rename from esphome/components/esp32_ble/ble_2901.cpp rename to esphome/components/esp32_ble_server/ble_2901.cpp index 952d82d5f0..962ba3ffbe 100644 --- a/esphome/components/esp32_ble/ble_2901.cpp +++ b/esphome/components/esp32_ble_server/ble_2901.cpp @@ -1,18 +1,18 @@ #include "ble_2901.h" -#include "ble_uuid.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { BLE2901::BLE2901(const std::string &value) : BLE2901((uint8_t *) value.data(), value.length()) {} -BLE2901::BLE2901(const uint8_t *data, size_t length) : BLEDescriptor(ESPBTUUID::from_uint16(0x2901)) { +BLE2901::BLE2901(const uint8_t *data, size_t length) : BLEDescriptor(esp32_ble::ESPBTUUID::from_uint16(0x2901)) { this->set_value(data, length); this->permissions_ = ESP_GATT_PERM_READ; } -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_2901.h b/esphome/components/esp32_ble_server/ble_2901.h similarity index 80% rename from esphome/components/esp32_ble/ble_2901.h rename to esphome/components/esp32_ble_server/ble_2901.h index e606b6271a..3bb23ae69d 100644 --- a/esphome/components/esp32_ble/ble_2901.h +++ b/esphome/components/esp32_ble_server/ble_2901.h @@ -5,7 +5,7 @@ #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { class BLE2901 : public BLEDescriptor { public: @@ -13,7 +13,7 @@ class BLE2901 : public BLEDescriptor { BLE2901(const uint8_t *data, size_t length); }; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_2902.cpp b/esphome/components/esp32_ble_server/ble_2902.cpp similarity index 51% rename from esphome/components/esp32_ble/ble_2902.cpp rename to esphome/components/esp32_ble_server/ble_2902.cpp index 164c8f40ff..0a87b239f9 100644 --- a/esphome/components/esp32_ble/ble_2902.cpp +++ b/esphome/components/esp32_ble_server/ble_2902.cpp @@ -1,18 +1,18 @@ #include "ble_2902.h" -#include "ble_uuid.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { -BLE2902::BLE2902() : BLEDescriptor(ESPBTUUID::from_uint16(0x2902)) { +BLE2902::BLE2902() : BLEDescriptor(esp32_ble::ESPBTUUID::from_uint16(0x2902)) { this->value_.attr_len = 2; uint8_t data[2] = {0, 0}; memcpy(this->value_.attr_value, data, 2); } -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_2902.h b/esphome/components/esp32_ble_server/ble_2902.h similarity index 75% rename from esphome/components/esp32_ble/ble_2902.h rename to esphome/components/esp32_ble_server/ble_2902.h index 356a0bb107..024eec755e 100644 --- a/esphome/components/esp32_ble/ble_2902.h +++ b/esphome/components/esp32_ble_server/ble_2902.h @@ -5,14 +5,14 @@ #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { class BLE2902 : public BLEDescriptor { public: BLE2902(); }; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp similarity index 86% rename from esphome/components/esp32_ble/ble_characteristic.cpp rename to esphome/components/esp32_ble_server/ble_characteristic.cpp index 127f973146..23b2693839 100644 --- a/esphome/components/esp32_ble/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -7,9 +7,9 @@ #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { -static const char *TAG = "esp32_ble.characteristic"; +static const char *const TAG = "esp32_ble_server.characteristic"; BLECharacteristic::BLECharacteristic(const ESPBTUUID uuid, uint32_t properties) : uuid_(uuid) { this->set_value_lock_ = xSemaphoreCreateBinary(); @@ -148,39 +148,39 @@ bool BLECharacteristic::is_failed() { void BLECharacteristic::set_broadcast_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST); else - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); } void BLECharacteristic::set_indicate_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE); else - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); } void BLECharacteristic::set_notify_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY); else - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); } void BLECharacteristic::set_read_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ); else - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ); } void BLECharacteristic::set_write_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE); else - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE); } void BLECharacteristic::set_write_no_response_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); else - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); } void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, @@ -300,7 +300,7 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt } } -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_characteristic.h b/esphome/components/esp32_ble_server/ble_characteristic.h similarity index 94% rename from esphome/components/esp32_ble/ble_characteristic.h rename to esphome/components/esp32_ble_server/ble_characteristic.h index 648cfb939d..bc5033f2ae 100644 --- a/esphome/components/esp32_ble/ble_characteristic.h +++ b/esphome/components/esp32_ble_server/ble_characteristic.h @@ -1,9 +1,9 @@ #pragma once -#include - -#include "ble_uuid.h" #include "ble_descriptor.h" +#include "esphome/components/esp32_ble/ble_uuid.h" + +#include #ifdef ARDUINO_ARCH_ESP32 @@ -14,7 +14,9 @@ #include namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { + +using namespace esp32_ble; class BLEService; @@ -89,7 +91,7 @@ class BLECharacteristic { } state_{INIT}; }; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_descriptor.cpp b/esphome/components/esp32_ble_server/ble_descriptor.cpp similarity index 95% rename from esphome/components/esp32_ble/ble_descriptor.cpp rename to esphome/components/esp32_ble_server/ble_descriptor.cpp index 9738cc2fe7..a04343da3a 100644 --- a/esphome/components/esp32_ble/ble_descriptor.cpp +++ b/esphome/components/esp32_ble_server/ble_descriptor.cpp @@ -7,9 +7,9 @@ #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { -static const char *TAG = "esp32_ble.descriptor"; +static const char *const TAG = "esp32_ble_server.descriptor"; BLEDescriptor::BLEDescriptor(ESPBTUUID uuid, uint16_t max_len) { this->uuid_ = uuid; @@ -71,7 +71,7 @@ void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ } } -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_descriptor.h b/esphome/components/esp32_ble_server/ble_descriptor.h similarity index 87% rename from esphome/components/esp32_ble/ble_descriptor.h rename to esphome/components/esp32_ble_server/ble_descriptor.h index bc7b48e331..1a72cb2b54 100644 --- a/esphome/components/esp32_ble/ble_descriptor.h +++ b/esphome/components/esp32_ble_server/ble_descriptor.h @@ -1,6 +1,6 @@ #pragma once -#include "ble_uuid.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #ifdef ARDUINO_ARCH_ESP32 @@ -8,7 +8,9 @@ #include namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { + +using namespace esp32_ble; class BLECharacteristic; @@ -43,7 +45,7 @@ class BLEDescriptor { } state_{INIT}; }; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp similarity index 84% rename from esphome/components/esp32_ble/ble_server.cpp rename to esphome/components/esp32_ble_server/ble_server.cpp index 9d5be46113..db83eb6bee 100644 --- a/esphome/components/esp32_ble/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -1,5 +1,6 @@ #include "ble_server.h" -#include "ble.h" + +#include "esphome/components/esp32_ble/ble.h" #include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/core/version.h" @@ -14,11 +15,11 @@ #include namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { -static const char *TAG = "esp32_ble.server"; +static const char *const TAG = "esp32_ble_server"; -static const uint16_t device_information_service__UUID = 0x180A; +static const uint16_t DEVICE_INFORMATION_SERVICE_UUID = 0x180A; static const uint16_t MODEL_UUID = 0x2A24; static const uint16_t VERSION_UUID = 0x2A26; static const uint16_t MANUFACTURER_UUID = 0x2A29; @@ -32,8 +33,6 @@ void BLEServer::setup() { ESP_LOGD(TAG, "Setting up BLE Server..."); global_ble_server = this; - - this->advertising_ = new BLEAdvertising(); } void BLEServer::loop() { @@ -53,35 +52,27 @@ void BLEServer::loop() { } case REGISTERING: { if (this->registered_) { - this->device_information_service_ = this->create_service(device_information_service__UUID); + this->device_information_service_ = this->create_service(DEVICE_INFORMATION_SERVICE_UUID); this->create_device_characteristics_(); - this->advertising_->set_scan_response(true); - this->advertising_->set_min_preferred_interval(0x06); - this->advertising_->start(); - this->state_ = STARTING_SERVICE; } break; } case STARTING_SERVICE: { + if (!this->device_information_service_->is_created()) { + break; + } if (this->device_information_service_->is_running()) { - for (auto *component : this->service_components_) { - component->setup_service(); - } - this->state_ = SETTING_UP_COMPONENT_SERVICES; + this->state_ = RUNNING; + this->can_proceed_ = true; + ESP_LOGD(TAG, "BLE server setup successfully"); } else if (!this->device_information_service_->is_starting()) { this->device_information_service_->start(); } break; } - case SETTING_UP_COMPONENT_SERVICES: { - this->state_ = RUNNING; - this->can_proceed_ = true; - ESP_LOGD(TAG, "BLE server setup successfully"); - break; - } } } @@ -123,7 +114,7 @@ BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t n BLEService *service = new BLEService(uuid, num_handles, inst_id); this->services_.push_back(service); if (advertise) { - this->advertising_->add_service_uuid(uuid); + esp32_ble::global_ble->get_advertising()->add_service_uuid(uuid); } service->do_create(this); return service; @@ -145,7 +136,7 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga ESP_LOGD(TAG, "BLE Client disconnected"); if (this->remove_client_(param->disconnect.conn_id)) this->connected_clients_--; - this->advertising_->start(); + esp32_ble::global_ble->get_advertising()->start(); for (auto *component : this->service_components_) { component->on_client_disconnect(); } @@ -171,7 +162,7 @@ void BLEServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Server:"); } BLEServer *global_ble_server = nullptr; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h similarity index 89% rename from esphome/components/esp32_ble/ble_server.h rename to esphome/components/esp32_ble_server/ble_server.h index 5657773446..9d955dda79 100644 --- a/esphome/components/esp32_ble/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -1,15 +1,16 @@ #pragma once +#include "ble_service.h" +#include "ble_characteristic.h" + +#include "esphome/components/esp32_ble/ble_advertising.h" +#include "esphome/components/esp32_ble/ble_uuid.h" +#include "esphome/components/esp32_ble/queue.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" -#include "ble_service.h" -#include "ble_characteristic.h" -#include "ble_uuid.h" -#include "ble_advertising.h" -#include -#include "queue.h" +#include #ifdef ARDUINO_ARCH_ESP32 @@ -17,11 +18,12 @@ #include namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { + +using namespace esp32_ble; class BLEServiceComponent { public: - virtual void setup_service(); virtual void on_client_connect(){}; virtual void on_client_disconnect(){}; virtual void start(); @@ -49,7 +51,6 @@ class BLEServer : public Component { esp_gatt_if_t get_gatts_if() { return this->gatts_if_; } uint32_t get_connected_client_count() { return this->connected_clients_; } const std::map &get_clients() { return this->clients_; } - BLEAdvertising *get_advertising() { return this->advertising_; } void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); @@ -69,7 +70,6 @@ class BLEServer : public Component { optional model_; esp_gatt_if_t gatts_if_{0}; bool registered_{false}; - BLEAdvertising *advertising_; uint32_t connected_clients_{0}; std::map clients_; @@ -83,14 +83,13 @@ class BLEServer : public Component { INIT = 0x00, REGISTERING, STARTING_SERVICE, - SETTING_UP_COMPONENT_SERVICES, RUNNING, } state_{INIT}; }; extern BLEServer *global_ble_server; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_service.cpp b/esphome/components/esp32_ble_server/ble_service.cpp similarity index 97% rename from esphome/components/esp32_ble/ble_service.cpp rename to esphome/components/esp32_ble_server/ble_service.cpp index 4b85182696..b7c88a436c 100644 --- a/esphome/components/esp32_ble/ble_service.cpp +++ b/esphome/components/esp32_ble_server/ble_service.cpp @@ -5,9 +5,9 @@ #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { -static const char *TAG = "esp32_ble.service"; +static const char *const TAG = "esp32_ble_server.service"; BLEService::BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id) : uuid_(uuid), num_handles_(num_handles), inst_id_(inst_id) {} @@ -136,7 +136,7 @@ void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t g } } -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_service.h b/esphome/components/esp32_ble_server/ble_service.h similarity index 93% rename from esphome/components/esp32_ble/ble_service.h rename to esphome/components/esp32_ble_server/ble_service.h index a539631846..9fdb95bde5 100644 --- a/esphome/components/esp32_ble/ble_service.h +++ b/esphome/components/esp32_ble_server/ble_service.h @@ -1,7 +1,7 @@ #pragma once -#include "ble_uuid.h" #include "ble_characteristic.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #ifdef ARDUINO_ARCH_ESP32 @@ -12,10 +12,12 @@ #include namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { class BLEServer; +using namespace esp32_ble; + class BLEService { public: BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id); @@ -73,7 +75,7 @@ class BLEService { } running_state_{STOPPED}; }; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index 8b91e9151d..4c337055cb 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -1,12 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import binary_sensor, output, esp32_ble +from esphome.components import binary_sensor, output, esp32_ble_server from esphome.const import CONF_ID, ESP_PLATFORM_ESP32 -AUTO_LOAD = ["binary_sensor", "output", "improv"] +AUTO_LOAD = ["binary_sensor", "output", "improv", "esp32_ble_server"] CODEOWNERS = ["@jesserockz"] -DEPENDENCIES = ["esp32_ble", "wifi"] +CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] +DEPENDENCIES = ["wifi"] ESP_PLATFORMS = [ESP_PLATFORM_ESP32] CONF_AUTHORIZED_DURATION = "authorized_duration" @@ -18,7 +19,7 @@ CONF_WIFI_TIMEOUT = "wifi_timeout" esp32_improv_ns = cg.esphome_ns.namespace("esp32_improv") ESP32ImprovComponent = esp32_improv_ns.class_( - "ESP32ImprovComponent", cg.Component, esp32_ble.BLEServiceComponent + "ESP32ImprovComponent", cg.Component, esp32_ble_server.BLEServiceComponent ) @@ -33,7 +34,7 @@ def validate_none_(value): CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(ESP32ImprovComponent), - cv.GenerateID(CONF_BLE_SERVER_ID): cv.use_id(esp32_ble.BLEServer), + cv.GenerateID(CONF_BLE_SERVER_ID): cv.use_id(esp32_ble_server.BLEServer), cv.Required(CONF_AUTHORIZER): cv.Any( validate_none_, cv.use_id(binary_sensor.BinarySensor) ), diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 491f26f46e..e076936771 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -1,7 +1,9 @@ #include "esp32_improv_component.h" -#include "esphome/core/log.h" + +#include "esphome/components/esp32_ble/ble.h" +#include "esphome/components/esp32_ble_server/ble_2902.h" #include "esphome/core/application.h" -#include "esphome/components/esp32_ble/ble_2902.h" +#include "esphome/core/log.h" #ifdef ARDUINO_ARCH_ESP32 @@ -12,40 +14,39 @@ static const char *TAG = "esp32_improv.component"; ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; } -void ESP32ImprovComponent::setup_service() { - this->service_ = esp32_ble::global_ble_server->create_service(improv::SERVICE_UUID, true); +void ESP32ImprovComponent::setup() { + this->service_ = global_ble_server->create_service(improv::SERVICE_UUID, true); + this->setup_characteristics(); } void ESP32ImprovComponent::setup_characteristics() { this->status_ = this->service_->create_characteristic( - improv::STATUS_UUID, esp32_ble::BLECharacteristic::PROPERTY_READ | esp32_ble::BLECharacteristic::PROPERTY_NOTIFY); - esp32_ble::BLEDescriptor *status_descriptor = new esp32_ble::BLE2902(); + improv::STATUS_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + BLEDescriptor *status_descriptor = new BLE2902(); this->status_->add_descriptor(status_descriptor); this->error_ = this->service_->create_characteristic( - improv::ERROR_UUID, esp32_ble::BLECharacteristic::PROPERTY_READ | esp32_ble::BLECharacteristic::PROPERTY_NOTIFY); - esp32_ble::BLEDescriptor *error_descriptor = new esp32_ble::BLE2902(); + improv::ERROR_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + BLEDescriptor *error_descriptor = new BLE2902(); this->error_->add_descriptor(error_descriptor); - this->rpc_ = - this->service_->create_characteristic(improv::RPC_COMMAND_UUID, esp32_ble::BLECharacteristic::PROPERTY_WRITE); + this->rpc_ = this->service_->create_characteristic(improv::RPC_COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE); this->rpc_->on_write([this](const std::vector &data) { if (data.size() > 0) { this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end()); } }); - esp32_ble::BLEDescriptor *rpc_descriptor = new esp32_ble::BLE2902(); + BLEDescriptor *rpc_descriptor = new BLE2902(); this->rpc_->add_descriptor(rpc_descriptor); - this->rpc_response_ = - this->service_->create_characteristic(improv::RPC_RESULT_UUID, esp32_ble::BLECharacteristic::PROPERTY_READ | - esp32_ble::BLECharacteristic::PROPERTY_NOTIFY); - esp32_ble::BLEDescriptor *rpc_response_descriptor = new esp32_ble::BLE2902(); + this->rpc_response_ = this->service_->create_characteristic( + improv::RPC_RESULT_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + BLEDescriptor *rpc_response_descriptor = new BLE2902(); this->rpc_response_->add_descriptor(rpc_response_descriptor); this->capabilities_ = - this->service_->create_characteristic(improv::CAPABILITIES_UUID, esp32_ble::BLECharacteristic::PROPERTY_READ); - esp32_ble::BLEDescriptor *capabilities_descriptor = new esp32_ble::BLE2902(); + this->service_->create_characteristic(improv::CAPABILITIES_UUID, BLECharacteristic::PROPERTY_READ); + BLEDescriptor *capabilities_descriptor = new BLE2902(); this->capabilities_->add_descriptor(capabilities_descriptor); uint8_t capabilities = 0x00; if (this->status_indicator_ != nullptr) @@ -64,13 +65,9 @@ void ESP32ImprovComponent::loop() { if (this->status_indicator_ != nullptr) this->status_indicator_->turn_off(); - if (this->service_->is_created() && !this->setup_complete_) { - this->setup_characteristics(); - } - - if (this->should_start_ && this->setup_complete_) { + if (this->service_->is_created() && this->should_start_ && this->setup_complete_) { if (this->service_->is_running()) { - this->service_->get_server()->get_advertising()->start(); + esp32_ble::global_ble->get_advertising()->start(); this->set_state_(improv::STATE_AWAITING_AUTHORIZATION); this->set_error_(improv::ERROR_NONE); @@ -205,10 +202,7 @@ void ESP32ImprovComponent::stop() { }); } -float ESP32ImprovComponent::get_setup_priority() const { - // Before WiFi - return setup_priority::AFTER_BLUETOOTH; -} +float ESP32ImprovComponent::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } void ESP32ImprovComponent::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 Improv:"); diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index e9b0c72ecd..262124f983 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -1,26 +1,28 @@ #pragma once +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/esp32_ble_server/ble_server.h" +#include "esphome/components/esp32_ble_server/ble_characteristic.h" +#include "esphome/components/improv/improv.h" +#include "esphome/components/output/binary_output.h" +#include "esphome/components/wifi/wifi_component.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" -#include "esphome/components/binary_sensor/binary_sensor.h" -#include "esphome/components/esp32_ble/ble_server.h" -#include "esphome/components/esp32_ble/ble_characteristic.h" -#include "esphome/components/output/binary_output.h" -#include "esphome/components/wifi/wifi_component.h" -#include "esphome/components/improv/improv.h" #ifdef ARDUINO_ARCH_ESP32 namespace esphome { namespace esp32_improv { -class ESP32ImprovComponent : public Component, public esp32_ble::BLEServiceComponent { +using namespace esp32_ble_server; + +class ESP32ImprovComponent : public Component, public BLEServiceComponent { public: ESP32ImprovComponent(); void dump_config() override; void loop() override; - void setup_service() override; + void setup() override; void setup_characteristics(); void on_client_disconnect() override; @@ -46,12 +48,12 @@ class ESP32ImprovComponent : public Component, public esp32_ble::BLEServiceCompo std::vector incoming_data_; wifi::WiFiAP connecting_sta_; - esp32_ble::BLEService *service_; - esp32_ble::BLECharacteristic *status_; - esp32_ble::BLECharacteristic *error_; - esp32_ble::BLECharacteristic *rpc_; - esp32_ble::BLECharacteristic *rpc_response_; - esp32_ble::BLECharacteristic *capabilities_; + BLEService *service_; + BLECharacteristic *status_; + BLECharacteristic *error_; + BLECharacteristic *rpc_; + BLECharacteristic *rpc_response_; + BLECharacteristic *capabilities_; binary_sensor::BinarySensor *authorizer_{nullptr}; output::BinaryOutput *status_indicator_{nullptr}; diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 24efd0fd14..90562510b9 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -19,6 +19,7 @@ #define USE_JSON #ifdef ARDUINO_ARCH_ESP32 #define USE_ESP32_CAMERA +#define USE_ESP32_BLE_SERVER #define USE_IMPROV #endif #define USE_TIME diff --git a/tests/test5.yaml b/tests/test5.yaml index d4e19d985b..ba047721e2 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -29,9 +29,10 @@ output: id: built_in_led esp32_ble: - server: - manufacturer: "ESPHome" - model: "Test5" + +esp32_ble_server: + manufacturer: "ESPHome" + model: "Test5" esp32_improv: authorizer: io0_button From 9bb64315f3385cb8fe704349a69e1bfb43c16a98 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 11 Jun 2021 15:49:05 -0700 Subject: [PATCH 0902/1841] Add new wizard + allow installing firmware over webserial (#1887) --- esphome/dashboard/dashboard.py | 68 +- esphome/dashboard/static/css/esphome.css | 398 ------- .../materialize-stepper.min.css | 10 - .../vendor/materialize/materialize.min.css | 13 - .../static/fonts/material-icons/LICENSE | 202 ---- .../material-icons/MaterialIcons-Regular.woff | Bin 57620 -> 0 bytes .../MaterialIcons-Regular.woff2 | Bin 44300 -> 0 bytes .../static/fonts/material-icons/README.md | 12 - .../fonts/material-icons/material-icons.css | 34 - esphome/dashboard/static/images/favicon.ico | Bin 15086 -> 0 bytes esphome/dashboard/static/js/esphome.js | 1021 ----------------- esphome/dashboard/static/js/vendor/ace/ace.js | 16 - .../static/js/vendor/ace/ext-searchbox.js | 7 - .../static/js/vendor/ace/mode-yaml.js | 7 - .../static/js/vendor/ace/theme-dreamweaver.js | 7 - .../js/vendor/jquery-ui/jquery-ui.min.js | 13 - .../jquery-validate/jquery.validate.min.js | 4 - .../static/js/vendor/jquery/jquery.min.js | 2 - .../materialize-stepper.min.js | 10 - .../js/vendor/materialize/materialize.min.js | 6 - esphome/dashboard/templates/index.html | 678 ----------- esphome/dashboard/templates/login.html | 93 -- esphome/wizard.py | 56 +- requirements.txt | 1 + 24 files changed, 98 insertions(+), 2560 deletions(-) delete mode 100644 esphome/dashboard/static/css/esphome.css delete mode 100644 esphome/dashboard/static/css/vendor/materialize-stepper/materialize-stepper.min.css delete mode 100644 esphome/dashboard/static/css/vendor/materialize/materialize.min.css delete mode 100644 esphome/dashboard/static/fonts/material-icons/LICENSE delete mode 100644 esphome/dashboard/static/fonts/material-icons/MaterialIcons-Regular.woff delete mode 100644 esphome/dashboard/static/fonts/material-icons/MaterialIcons-Regular.woff2 delete mode 100644 esphome/dashboard/static/fonts/material-icons/README.md delete mode 100644 esphome/dashboard/static/fonts/material-icons/material-icons.css delete mode 100644 esphome/dashboard/static/images/favicon.ico delete mode 100644 esphome/dashboard/static/js/esphome.js delete mode 100644 esphome/dashboard/static/js/vendor/ace/ace.js delete mode 100644 esphome/dashboard/static/js/vendor/ace/ext-searchbox.js delete mode 100644 esphome/dashboard/static/js/vendor/ace/mode-yaml.js delete mode 100644 esphome/dashboard/static/js/vendor/ace/theme-dreamweaver.js delete mode 100644 esphome/dashboard/static/js/vendor/jquery-ui/jquery-ui.min.js delete mode 100644 esphome/dashboard/static/js/vendor/jquery-validate/jquery.validate.min.js delete mode 100644 esphome/dashboard/static/js/vendor/jquery/jquery.min.js delete mode 100644 esphome/dashboard/static/js/vendor/materialize-stepper/materialize-stepper.min.js delete mode 100644 esphome/dashboard/static/js/vendor/materialize/materialize.min.js delete mode 100644 esphome/dashboard/templates/index.html delete mode 100644 esphome/dashboard/templates/login.html diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 4b40237768..90061a3d4e 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -9,6 +9,7 @@ import json import logging import multiprocessing import os +import secrets import shutil import subprocess import threading @@ -44,6 +45,8 @@ from esphome.zeroconf import DashboardStatus, Zeroconf _LOGGER = logging.getLogger(__name__) +ENV_DEV = "ESPHOME_DASHBOARD_DEV" + class DashboardSettings: def __init__(self): @@ -111,6 +114,7 @@ def template_args(): docs_link = "https://next.esphome.io/" else: docs_link = "https://www.esphome.io/" + return { "version": version, "docs_link": docs_link, @@ -349,6 +353,7 @@ class SerialPortRequestHandler(BaseHandler): data.append({"port": port.path, "desc": desc}) data.append({"port": "OTA", "desc": "Over-The-Air"}) data.sort(key=lambda x: x["port"], reverse=True) + self.set_header("content-type", "application/json") self.write(json.dumps(data)) @@ -358,11 +363,15 @@ class WizardRequestHandler(BaseHandler): from esphome import wizard kwargs = { - k: "".join(x.decode() for x in v) for k, v in self.request.arguments.items() + k: "".join(x.decode() for x in v) + for k, v in self.request.arguments.items() + if k in ("name", "platform", "board", "ssid", "psk", "password") } + kwargs["ota_password"] = secrets.token_hex(16) destination = settings.rel_path(kwargs["name"] + ".yaml") wizard.wizard_write(path=destination, **kwargs) - self.redirect("./?begin=True") + self.set_status(200) + self.finish() class DownloadBinaryRequestHandler(BaseHandler): @@ -473,7 +482,7 @@ class MainRequestHandler(BaseHandler): entries = _list_dashboard_entries() self.render( - "templates/index.html", + get_template_path("index"), entries=entries, begin=begin, **template_args(), @@ -560,6 +569,7 @@ class PingRequestHandler(BaseHandler): @authenticated def get(self): PING_REQUEST.set() + self.set_header("content-type", "application/json") self.write(json.dumps(PING_RESULT)) @@ -567,6 +577,21 @@ def is_allowed(configuration): return os.path.sep not in configuration +class InfoRequestHandler(BaseHandler): + @authenticated + @bind_config + def get(self, configuration=None): + yaml_path = settings.rel_path(configuration) + all_yaml_files = settings.list_yaml_files() + + if yaml_path not in all_yaml_files: + self.set_status(404) + return + + self.set_header("content-type", "application/json") + self.write(DashboardEntry(yaml_path).storage.to_json()) + + class EditRequestHandler(BaseHandler): @authenticated @bind_config @@ -633,7 +658,7 @@ class LoginHandler(BaseHandler): def render_login_page(self, error=None): self.render( - "templates/login.html", + get_template_path("login"), error=error, hassio=settings.using_hassio_auth, has_username=bool(settings.username), @@ -694,19 +719,44 @@ class LogoutHandler(BaseHandler): _STATIC_FILE_HASHES = {} +def get_base_frontend_path(): + if ENV_DEV not in os.environ: + import esphome_dashboard + + return esphome_dashboard.where() + + static_path = os.environ[ENV_DEV] + if not static_path.endswith("/"): + static_path += "/" + + # This path can be relative, so resolve against the root or else templates don't work + return os.path.abspath(os.path.join(os.getcwd(), static_path, "esphome_dashboard")) + + +def get_template_path(template_name): + return os.path.join(get_base_frontend_path(), f"{template_name}.template.html") + + +def get_static_path(*args): + return os.path.join(get_base_frontend_path(), "static", *args) + + def get_static_file_url(name): - static_path = os.path.join(os.path.dirname(__file__), "static") + # Module imports can't deduplicate if stuff added to url + if name == "js/esphome/index.js": + return f"./static/{name}" + if name in _STATIC_FILE_HASHES: hash_ = _STATIC_FILE_HASHES[name] else: - path = os.path.join(static_path, name) + path = get_static_path(name) with open(path, "rb") as f_handle: hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8] _STATIC_FILE_HASHES[name] = hash_ return f"./static/{name}?hash={hash_}" -def make_app(debug=False): +def make_app(debug=get_bool_env(ENV_DEV)): def log_function(handler): if handler.get_status() < 400: log_method = access_log.info @@ -736,7 +786,6 @@ def make_app(debug=False): "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0" ) - static_path = os.path.join(os.path.dirname(__file__), "static") app_settings = { "debug": debug, "cookie_secret": settings.cookie_secret, @@ -758,6 +807,7 @@ def make_app(debug=False): (rel + "vscode", EsphomeVscodeHandler), (rel + "ace", EsphomeAceEditorHandler), (rel + "update-all", EsphomeUpdateAllHandler), + (rel + "info", InfoRequestHandler), (rel + "edit", EditRequestHandler), (rel + "download.bin", DownloadBinaryRequestHandler), (rel + "serial-ports", SerialPortRequestHandler), @@ -765,7 +815,7 @@ def make_app(debug=False): (rel + "delete", DeleteRequestHandler), (rel + "undo-delete", UndoDeleteRequestHandler), (rel + "wizard.html", WizardRequestHandler), - (rel + r"static/(.*)", StaticFileHandler, {"path": static_path}), + (rel + r"static/(.*)", StaticFileHandler, {"path": get_static_path()}), ], **app_settings, ) diff --git a/esphome/dashboard/static/css/esphome.css b/esphome/dashboard/static/css/esphome.css deleted file mode 100644 index 116170f95d..0000000000 --- a/esphome/dashboard/static/css/esphome.css +++ /dev/null @@ -1,398 +0,0 @@ -/* Base */ - -:root { - /* Colors */ - --primary-bg-color: #fafafa; - - --alert-standard-color: #666666; - --alert-standard-color-bg: #e6e6e6; - --alert-info-color: #00539f; - --alert-info-color-bg: #E6EEF5; - --alert-success-color: #4CAF50; - --alert-success-color-bg: #EDF7EE; - --alert-warning-color: #FF9800; - --alert-warning-color-bg: #FFF5E6; - --alert-error-color: #D93025; - --alert-error-color-bg: #FAEFEB; -} - -body { - display: flex; - min-height: 100vh; - flex-direction: column; - background-color: var(--primary-bg-color); -} - -/* Layout */ -.valign-wrapper { - position: absolute; - width:100vw; - height:100vh; -} - -.valign { - width: 100%; -} - -main { - flex: 1 0 auto; -} - -/* Alerts & Errors */ -.alert { - width: 100%; - margin: 10px auto; - padding: 10px; - border-radius: 2px; - border-left-width: 4px; - border-left-style: solid; -} - -.alert .title { - font-weight: bold; -} - -.alert .title::after { - content: "\A"; - white-space: pre; -} - -.alert.alert-error { - color: var(--alert-error-color); - border-left-color: var(--alert-error-color); - background-color: var(--alert-error-color-bg); -} - -.card.card-error, .card.status-offline { - border-top: 4px solid var(--alert-error-color); -} - -.card.status-online { - border-top: 4px solid var(--alert-success-color); -} - -.card.status-not-responding { - border-top: 4px solid var(--alert-warning-color); -} - -.card.status-unknown { - border-top: 4px solid var(--alert-standard-color); -} - -/* Login Page */ -#login-page .row.no-bottom-margin { - margin-bottom: 0 !important; -} - -#login-page .logo { - display: block; - width: auto; - max-width: 300px; - margin-left: auto; - margin-right: auto; -} - -#login-page .input-field input:focus + label { - color: #000; -} - -#login-page .input-field input:focus { - border-bottom: 1px solid #000; - box-shadow: 0 1px 0 0 #000; -} - -#login-page .input-field .prefix.active { - color: #000; -} - -#login-page .version-number { - display: block; - text-align: center; - margin-bottom: 20px;; - color:#808080; - font-size: 12px; -} - -#login-page footer { - color: #757575; - font-size: 12px; -} - -#login-page footer a { - color: #424242; -} - -#login-page footer p { - -webkit-margin-before: 0px; - margin-block-start: 0px; - -webkit-margin-after: 5px; - margin-block-end: 5px; -} - -#login-page footer p:last-child { - -webkit-margin-after: 0px; - margin-block-end: 0px; -} - -/* Dashboard */ -.logo-wrapper { - height: 64px; - height: 100%; - width: 0; - margin-left: 24px; -} - -.logo { - width: auto; - height: 48px; - margin: 8px 0; -} - -@media only screen and (max-width: 601px) { - .logo { - height: 38px; - margin: 9px 0; - } -} - -.nav-icons { - margin-right: 24px; -} - -.nav-icons i { - color: black; -} - -nav { - height: auto; - line-height: normal; -} - -.select-port-container { - margin-top: 8px; - margin-right: 10px; - width: 350px; -} - -.serial-port-select { - margin-top: 8px; - margin-right: 10px; - width: 350px; -} - -.serial-port-select .select-dropdown { - color: black; -} - -.serial-port-select .select-dropdown:focus { - border-bottom: 1px solid #607d8b !important; -} - -.serial-port-select .caret { - fill: black; -} - -.serial-port-select .dropdown-content li>span { - color: black; -} - -#nav-dropdown li a, .node-dropdown li a { - color: black; -} - -main .container { - margin-top: 20px; - margin-bottom: 20px; - width: 90%; - max-width: 1920px; -} - -#nodes .card-content { - height: calc(100% - 47px); -} - -#nodes .card-content, #nodes .card-action { - padding: 12px; -} - -#nodes .grid-1-col { - display: grid; - grid-template-columns: 1fr; -} - -#nodes .grid-2-col { - display: grid; - grid-template-columns: 1fr 1fr; - grid-column-gap: 1.5rem; -} - -#nodes .grid-3-col { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - grid-column-gap: 1.5rem; -} - -@media only screen and (max-width: 1100px) { - #nodes .grid-3-col { - grid-template-columns: 1fr 1fr; - grid-column-gap: 1.5rem; - } -} - -@media only screen and (max-width: 750px) { - #nodes .grid-2-col { - grid-template-columns: 1fr; - grid-column-gap: 0; - } - - #nodes .grid-3-col { - grid-template-columns: 1fr; - grid-column-gap: 0; - } -} - -i.node-update-avaliable { - color:#3f51b5; -} - -i.node-webserver { - color:#039be5; -} - -.node-config-path { - margin-top: -8px; - margin-bottom: 8px; - font-size: 14px; -} - -.node-card-comment { - color: #444; - font-style: italic; -} - -.card-action a, .card-dropdown-action a { - cursor: pointer; -} - -.tooltipped { - cursor: help; -} - -#js-loading-indicator { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} - -.editor { - margin-top: 0; - margin-bottom: 0; - border-radius: 3px; - height: calc(100% - 56px); -} - -.inlinecode { - box-sizing: border-box; - padding: 0.2em 0.4em; - margin: 0; - font-size: 85%; - background-color: rgba(27,31,35,0.05); - border-radius: 3px; - font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace; -} - -.log { - height: 100%; - max-height: calc(100% - 56px); - background-color: #1c1c1c; - margin-top: 0; - margin-bottom: 0; - font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; - font-size: 12px; - padding: 16px; - overflow: auto; - line-height: 1.45; - border-radius: 3px; - white-space: pre-wrap; - overflow-wrap: break-word; - color: #DDD; -} - -.log-bold { font-weight: bold; } -.log-italic { font-style: italic; } -.log-underline { text-decoration: underline; } -.log-strikethrough { text-decoration: line-through; } -.log-underline.log-strikethrough { text-decoration: underline line-through; } -.log-secret { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -.log-secret-redacted { - opacity: 0; - width: 1px; - font-size: 1px; -} -.log-fg-black { color: rgb(128,128,128); } -.log-fg-red { color: rgb(255,0,0); } -.log-fg-green { color: rgb(0,255,0); } -.log-fg-yellow { color: rgb(255,255,0); } -.log-fg-blue { color: rgb(0,0,255); } -.log-fg-magenta { color: rgb(255,0,255); } -.log-fg-cyan { color: rgb(0,255,255); } -.log-fg-white { color: rgb(187,187,187); } -.log-bg-black { background-color: rgb(0,0,0); } -.log-bg-red { background-color: rgb(255,0,0); } -.log-bg-green { background-color: rgb(0,255,0); } -.log-bg-yellow { background-color: rgb(255,255,0); } -.log-bg-blue { background-color: rgb(0,0,255); } -.log-bg-magenta { background-color: rgb(255,0,255); } -.log-bg-cyan { background-color: rgb(0,255,255); } -.log-bg-white { background-color: rgb(255,255,255); } - -ul.browser-default { - padding-left: 30px; - margin-top: 10px; - margin-bottom: 15px; -} - -ul.browser-default li { - list-style-type: initial; -} - -ul.stepper:not(.horizontal) .step.active::before, ul.stepper:not(.horizontal) .step.done::before, ul.stepper.horizontal .step.active .step-title::before, ul.stepper.horizontal .step.done .step-title::before { - background-color: #3f51b5 !important; -} - -.select-action { - width: auto !important; - height: auto !important; - white-space: nowrap; -} - -.modal { - width: 95%; - max-height: 90%; - height: 85% !important; -} - -.page-footer { - display: flex; - align-items: center; - min-height: 50px; - padding-top: 0; - color: grey; -} - -.page-footer a { - color: #afafaf; -} - -@media only screen and (max-width: 992px) { - .page-footer .left, .page-footer .right { - width: 100%; - text-align: center; - } -} diff --git a/esphome/dashboard/static/css/vendor/materialize-stepper/materialize-stepper.min.css b/esphome/dashboard/static/css/vendor/materialize-stepper/materialize-stepper.min.css deleted file mode 100644 index 390f16a011..0000000000 --- a/esphome/dashboard/static/css/vendor/materialize-stepper/materialize-stepper.min.css +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Materialize Stepper - A little plugin that implements a stepper to Materializecss framework. - * @version v3.1.0 - * @author Igor Marcossi (Kinark) . - * @link https://github.com/Kinark/Materialize-stepper - * - * Licensed under the MIT License (https://github.com/Kinark/Materialize-stepper/blob/master/LICENSE). - */ - - .card-content ul.stepper{margin:1em -24px;padding:0 24px}@media only screen and (min-width:993px){.card-content ul.stepper.horizontal{margin-left:-24px;margin-right:-24px;padding-left:24px;padding-right:24px}.card-content ul.stepper.horizontal:first-child{margin-top:-24px}.card-content ul.stepper.horizontal .step.step-content{padding-left:40px;padding-right:40px}.card-content ul.stepper.horizontal .step.step-content .step-actions{padding-left:40px;padding-right:40px}}ul.stepper{counter-reset:section;overflow-y:auto;overflow-x:hidden}ul.stepper .wait-feedback{left:0;right:0;top:0;z-index:2;position:absolute;width:100%;height:100%;text-align:center;display:flex;justify-content:center;align-items:center}ul.stepper .step{position:relative;transition:height .4s cubic-bezier(.4,0,.2,1),padding-bottom .4s cubic-bezier(.4,0,.2,1)}ul.stepper .step .step-title{margin:0 -24px;cursor:pointer;padding:15.5px 44px 24px 64px;display:block}ul.stepper .step .step-title:hover{background-color:rgba(0,0,0,.06)}ul.stepper .step .step-title::after{content:attr(data-step-label);display:block;position:absolute;font-size:12.8px;font-size:.8rem;color:#424242;font-weight:400}ul.stepper .step .step-content{position:relative;display:none;height:0;transition:height .4s cubic-bezier(.4,0,.2,1);width:inherit;overflow:visible;margin-left:41px;margin-right:24px}ul.stepper .step .step-content .step-actions{padding-top:16px;padding-bottom:4px;display:flex;justify-content:flex-start}ul.stepper .step .step-content .step-actions .btn-flat:not(:last-child),ul.stepper .step .step-content .step-actions .btn-large:not(:last-child),ul.stepper .step .step-content .step-actions .btn:not(:last-child){margin-right:5px}ul.stepper .step .step-content .row{margin-bottom:7px}ul.stepper .step::before{position:absolute;counter-increment:section;content:counter(section);height:26px;width:26px;color:#fff;background-color:#b2b2b2;border-radius:50%;text-align:center;line-height:26px;font-weight:400;transition:background-color .4s cubic-bezier(.4,0,.2,1);font-size:14px;left:1px;top:13px}ul.stepper .step.active .step-title{font-weight:500}ul.stepper .step.active .step-content{height:auto;display:block}ul.stepper .step.active::before,ul.stepper .step.done::before{background-color:#2196f3}ul.stepper .step.done::before{content:'\e5ca';font-size:16px;font-family:'Material Icons'}ul.stepper .step.wrong::before{content:'\e001';font-size:24px;font-family:'Material Icons';background-color:red}ul.stepper .step.feedbacking .step-content>:not(.wait-feedback){opacity:.1}ul.stepper .step:not(:last-of-type)::after{content:'';position:absolute;top:52px;left:13.5px;width:1px;height:40%;height:calc(100% - 52px);background-color:rgba(0,0,0,.1);transition:height .4s cubic-bezier(.4,0,.2,1)}ul.stepper .step:not(:last-of-type).active{padding-bottom:36px}ul.stepper>li:not(:last-of-type){padding-bottom:10px}@media only screen and (min-width:993px){ul.stepper.horizontal{position:relative;display:flex;justify-content:space-between;min-height:458px;overflow:hidden}ul.stepper.horizontal::before{content:'';background-color:transparent;width:100%;min-height:84px;box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);position:absolute;left:0}ul.stepper.horizontal .step{position:static;padding:0!important;width:100%;display:flex;align-items:center;height:84px}ul.stepper.horizontal .step::before{content:none}ul.stepper.horizontal .step:last-of-type{width:auto!important}ul.stepper.horizontal .step.active:not(:last-of-type)::after,ul.stepper.horizontal .step:not(:last-of-type)::after{content:'';position:static;display:inline-block;width:100%;height:1px}ul.stepper.horizontal .step .step-title{line-height:84px;height:84px;margin:0;padding:0 25px 0 65px;display:inline-block;max-width:220px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex-shrink:0}ul.stepper.horizontal .step .step-title::before{position:absolute;counter-increment:section;content:counter(section);height:26px;width:26px;color:#fff;background-color:#b2b2b2;border-radius:50%;text-align:center;line-height:26px;font-weight:400;transition:background-color .4s cubic-bezier(.4,0,.2,1);font-size:14px;left:1px;top:28.5px;left:19px}ul.stepper.horizontal .step .step-title::after{top:15px}ul.stepper.horizontal .step.active~.step .step-content{left:100%}ul.stepper.horizontal .step.active .step-content{left:0!important}ul.stepper.horizontal .step.active .step-title::before,ul.stepper.horizontal .step.done .step-title::before{background-color:#2196f3}ul.stepper.horizontal .step.done .step-title::before{content:'\e5ca';font-size:16px;font-family:'Material Icons'}ul.stepper.horizontal .step.wrong .step-title::before{content:'\e001';font-size:24px;font-family:'Material Icons';background-color:red}ul.stepper.horizontal .step .step-content{position:absolute;height:calc(100% - 84px);top:84px;display:block;left:-100%;width:100%;overflow-y:auto;overflow-x:hidden;margin:0;padding:20px 20px 76px 20px;transition:left .4s cubic-bezier(.4,0,.2,1)}ul.stepper.horizontal .step .step-content .step-actions{position:absolute;bottom:0;left:0;width:100%;padding:20px;background-color:transparent;flex-direction:row-reverse}ul.stepper.horizontal .step .step-content .step-actions .btn-flat:not(:last-child),ul.stepper.horizontal .step .step-content .step-actions .btn-large:not(:last-child),ul.stepper.horizontal .step .step-content .step-actions .btn:not(:last-child){margin-left:5px;margin-right:0}} diff --git a/esphome/dashboard/static/css/vendor/materialize/materialize.min.css b/esphome/dashboard/static/css/vendor/materialize/materialize.min.css deleted file mode 100644 index 74b1741b62..0000000000 --- a/esphome/dashboard/static/css/vendor/materialize/materialize.min.css +++ /dev/null @@ -1,13 +0,0 @@ -/*! - * Materialize v1.0.0 (http://materializecss.com) - * Copyright 2014-2017 Materialize - * MIT License (https://raw.githubusercontent.com/Dogfalo/materialize/master/LICENSE) - */ -.materialize-red{background-color:#e51c23 !important}.materialize-red-text{color:#e51c23 !important}.materialize-red.lighten-5{background-color:#fdeaeb !important}.materialize-red-text.text-lighten-5{color:#fdeaeb !important}.materialize-red.lighten-4{background-color:#f8c1c3 !important}.materialize-red-text.text-lighten-4{color:#f8c1c3 !important}.materialize-red.lighten-3{background-color:#f3989b !important}.materialize-red-text.text-lighten-3{color:#f3989b !important}.materialize-red.lighten-2{background-color:#ee6e73 !important}.materialize-red-text.text-lighten-2{color:#ee6e73 !important}.materialize-red.lighten-1{background-color:#ea454b !important}.materialize-red-text.text-lighten-1{color:#ea454b !important}.materialize-red.darken-1{background-color:#d0181e !important}.materialize-red-text.text-darken-1{color:#d0181e !important}.materialize-red.darken-2{background-color:#b9151b !important}.materialize-red-text.text-darken-2{color:#b9151b !important}.materialize-red.darken-3{background-color:#a21318 !important}.materialize-red-text.text-darken-3{color:#a21318 !important}.materialize-red.darken-4{background-color:#8b1014 !important}.materialize-red-text.text-darken-4{color:#8b1014 !important}.red{background-color:#F44336 !important}.red-text{color:#F44336 !important}.red.lighten-5{background-color:#FFEBEE !important}.red-text.text-lighten-5{color:#FFEBEE !important}.red.lighten-4{background-color:#FFCDD2 !important}.red-text.text-lighten-4{color:#FFCDD2 !important}.red.lighten-3{background-color:#EF9A9A !important}.red-text.text-lighten-3{color:#EF9A9A !important}.red.lighten-2{background-color:#E57373 !important}.red-text.text-lighten-2{color:#E57373 !important}.red.lighten-1{background-color:#EF5350 !important}.red-text.text-lighten-1{color:#EF5350 !important}.red.darken-1{background-color:#E53935 !important}.red-text.text-darken-1{color:#E53935 !important}.red.darken-2{background-color:#D32F2F !important}.red-text.text-darken-2{color:#D32F2F !important}.red.darken-3{background-color:#C62828 !important}.red-text.text-darken-3{color:#C62828 !important}.red.darken-4{background-color:#B71C1C !important}.red-text.text-darken-4{color:#B71C1C !important}.red.accent-1{background-color:#FF8A80 !important}.red-text.text-accent-1{color:#FF8A80 !important}.red.accent-2{background-color:#FF5252 !important}.red-text.text-accent-2{color:#FF5252 !important}.red.accent-3{background-color:#FF1744 !important}.red-text.text-accent-3{color:#FF1744 !important}.red.accent-4{background-color:#D50000 !important}.red-text.text-accent-4{color:#D50000 !important}.pink{background-color:#e91e63 !important}.pink-text{color:#e91e63 !important}.pink.lighten-5{background-color:#fce4ec !important}.pink-text.text-lighten-5{color:#fce4ec !important}.pink.lighten-4{background-color:#f8bbd0 !important}.pink-text.text-lighten-4{color:#f8bbd0 !important}.pink.lighten-3{background-color:#f48fb1 !important}.pink-text.text-lighten-3{color:#f48fb1 !important}.pink.lighten-2{background-color:#f06292 !important}.pink-text.text-lighten-2{color:#f06292 !important}.pink.lighten-1{background-color:#ec407a !important}.pink-text.text-lighten-1{color:#ec407a !important}.pink.darken-1{background-color:#d81b60 !important}.pink-text.text-darken-1{color:#d81b60 !important}.pink.darken-2{background-color:#c2185b !important}.pink-text.text-darken-2{color:#c2185b !important}.pink.darken-3{background-color:#ad1457 !important}.pink-text.text-darken-3{color:#ad1457 !important}.pink.darken-4{background-color:#880e4f !important}.pink-text.text-darken-4{color:#880e4f !important}.pink.accent-1{background-color:#ff80ab !important}.pink-text.text-accent-1{color:#ff80ab !important}.pink.accent-2{background-color:#ff4081 !important}.pink-text.text-accent-2{color:#ff4081 !important}.pink.accent-3{background-color:#f50057 !important}.pink-text.text-accent-3{color:#f50057 !important}.pink.accent-4{background-color:#c51162 !important}.pink-text.text-accent-4{color:#c51162 !important}.purple{background-color:#9c27b0 !important}.purple-text{color:#9c27b0 !important}.purple.lighten-5{background-color:#f3e5f5 !important}.purple-text.text-lighten-5{color:#f3e5f5 !important}.purple.lighten-4{background-color:#e1bee7 !important}.purple-text.text-lighten-4{color:#e1bee7 !important}.purple.lighten-3{background-color:#ce93d8 !important}.purple-text.text-lighten-3{color:#ce93d8 !important}.purple.lighten-2{background-color:#ba68c8 !important}.purple-text.text-lighten-2{color:#ba68c8 !important}.purple.lighten-1{background-color:#ab47bc !important}.purple-text.text-lighten-1{color:#ab47bc !important}.purple.darken-1{background-color:#8e24aa !important}.purple-text.text-darken-1{color:#8e24aa !important}.purple.darken-2{background-color:#7b1fa2 !important}.purple-text.text-darken-2{color:#7b1fa2 !important}.purple.darken-3{background-color:#6a1b9a !important}.purple-text.text-darken-3{color:#6a1b9a !important}.purple.darken-4{background-color:#4a148c !important}.purple-text.text-darken-4{color:#4a148c !important}.purple.accent-1{background-color:#ea80fc !important}.purple-text.text-accent-1{color:#ea80fc !important}.purple.accent-2{background-color:#e040fb !important}.purple-text.text-accent-2{color:#e040fb !important}.purple.accent-3{background-color:#d500f9 !important}.purple-text.text-accent-3{color:#d500f9 !important}.purple.accent-4{background-color:#a0f !important}.purple-text.text-accent-4{color:#a0f !important}.deep-purple{background-color:#673ab7 !important}.deep-purple-text{color:#673ab7 !important}.deep-purple.lighten-5{background-color:#ede7f6 !important}.deep-purple-text.text-lighten-5{color:#ede7f6 !important}.deep-purple.lighten-4{background-color:#d1c4e9 !important}.deep-purple-text.text-lighten-4{color:#d1c4e9 !important}.deep-purple.lighten-3{background-color:#b39ddb !important}.deep-purple-text.text-lighten-3{color:#b39ddb !important}.deep-purple.lighten-2{background-color:#9575cd !important}.deep-purple-text.text-lighten-2{color:#9575cd !important}.deep-purple.lighten-1{background-color:#7e57c2 !important}.deep-purple-text.text-lighten-1{color:#7e57c2 !important}.deep-purple.darken-1{background-color:#5e35b1 !important}.deep-purple-text.text-darken-1{color:#5e35b1 !important}.deep-purple.darken-2{background-color:#512da8 !important}.deep-purple-text.text-darken-2{color:#512da8 !important}.deep-purple.darken-3{background-color:#4527a0 !important}.deep-purple-text.text-darken-3{color:#4527a0 !important}.deep-purple.darken-4{background-color:#311b92 !important}.deep-purple-text.text-darken-4{color:#311b92 !important}.deep-purple.accent-1{background-color:#b388ff !important}.deep-purple-text.text-accent-1{color:#b388ff !important}.deep-purple.accent-2{background-color:#7c4dff !important}.deep-purple-text.text-accent-2{color:#7c4dff !important}.deep-purple.accent-3{background-color:#651fff !important}.deep-purple-text.text-accent-3{color:#651fff !important}.deep-purple.accent-4{background-color:#6200ea !important}.deep-purple-text.text-accent-4{color:#6200ea !important}.indigo{background-color:#3f51b5 !important}.indigo-text{color:#3f51b5 !important}.indigo.lighten-5{background-color:#e8eaf6 !important}.indigo-text.text-lighten-5{color:#e8eaf6 !important}.indigo.lighten-4{background-color:#c5cae9 !important}.indigo-text.text-lighten-4{color:#c5cae9 !important}.indigo.lighten-3{background-color:#9fa8da !important}.indigo-text.text-lighten-3{color:#9fa8da !important}.indigo.lighten-2{background-color:#7986cb !important}.indigo-text.text-lighten-2{color:#7986cb !important}.indigo.lighten-1{background-color:#5c6bc0 !important}.indigo-text.text-lighten-1{color:#5c6bc0 !important}.indigo.darken-1{background-color:#3949ab !important}.indigo-text.text-darken-1{color:#3949ab !important}.indigo.darken-2{background-color:#303f9f !important}.indigo-text.text-darken-2{color:#303f9f !important}.indigo.darken-3{background-color:#283593 !important}.indigo-text.text-darken-3{color:#283593 !important}.indigo.darken-4{background-color:#1a237e !important}.indigo-text.text-darken-4{color:#1a237e !important}.indigo.accent-1{background-color:#8c9eff !important}.indigo-text.text-accent-1{color:#8c9eff !important}.indigo.accent-2{background-color:#536dfe !important}.indigo-text.text-accent-2{color:#536dfe !important}.indigo.accent-3{background-color:#3d5afe !important}.indigo-text.text-accent-3{color:#3d5afe !important}.indigo.accent-4{background-color:#304ffe !important}.indigo-text.text-accent-4{color:#304ffe !important}.blue{background-color:#2196F3 !important}.blue-text{color:#2196F3 !important}.blue.lighten-5{background-color:#E3F2FD !important}.blue-text.text-lighten-5{color:#E3F2FD !important}.blue.lighten-4{background-color:#BBDEFB !important}.blue-text.text-lighten-4{color:#BBDEFB !important}.blue.lighten-3{background-color:#90CAF9 !important}.blue-text.text-lighten-3{color:#90CAF9 !important}.blue.lighten-2{background-color:#64B5F6 !important}.blue-text.text-lighten-2{color:#64B5F6 !important}.blue.lighten-1{background-color:#42A5F5 !important}.blue-text.text-lighten-1{color:#42A5F5 !important}.blue.darken-1{background-color:#1E88E5 !important}.blue-text.text-darken-1{color:#1E88E5 !important}.blue.darken-2{background-color:#1976D2 !important}.blue-text.text-darken-2{color:#1976D2 !important}.blue.darken-3{background-color:#1565C0 !important}.blue-text.text-darken-3{color:#1565C0 !important}.blue.darken-4{background-color:#0D47A1 !important}.blue-text.text-darken-4{color:#0D47A1 !important}.blue.accent-1{background-color:#82B1FF !important}.blue-text.text-accent-1{color:#82B1FF !important}.blue.accent-2{background-color:#448AFF !important}.blue-text.text-accent-2{color:#448AFF !important}.blue.accent-3{background-color:#2979FF !important}.blue-text.text-accent-3{color:#2979FF !important}.blue.accent-4{background-color:#2962FF !important}.blue-text.text-accent-4{color:#2962FF !important}.light-blue{background-color:#03a9f4 !important}.light-blue-text{color:#03a9f4 !important}.light-blue.lighten-5{background-color:#e1f5fe !important}.light-blue-text.text-lighten-5{color:#e1f5fe !important}.light-blue.lighten-4{background-color:#b3e5fc !important}.light-blue-text.text-lighten-4{color:#b3e5fc !important}.light-blue.lighten-3{background-color:#81d4fa !important}.light-blue-text.text-lighten-3{color:#81d4fa !important}.light-blue.lighten-2{background-color:#4fc3f7 !important}.light-blue-text.text-lighten-2{color:#4fc3f7 !important}.light-blue.lighten-1{background-color:#29b6f6 !important}.light-blue-text.text-lighten-1{color:#29b6f6 !important}.light-blue.darken-1{background-color:#039be5 !important}.light-blue-text.text-darken-1{color:#039be5 !important}.light-blue.darken-2{background-color:#0288d1 !important}.light-blue-text.text-darken-2{color:#0288d1 !important}.light-blue.darken-3{background-color:#0277bd !important}.light-blue-text.text-darken-3{color:#0277bd !important}.light-blue.darken-4{background-color:#01579b !important}.light-blue-text.text-darken-4{color:#01579b !important}.light-blue.accent-1{background-color:#80d8ff !important}.light-blue-text.text-accent-1{color:#80d8ff !important}.light-blue.accent-2{background-color:#40c4ff !important}.light-blue-text.text-accent-2{color:#40c4ff !important}.light-blue.accent-3{background-color:#00b0ff !important}.light-blue-text.text-accent-3{color:#00b0ff !important}.light-blue.accent-4{background-color:#0091ea !important}.light-blue-text.text-accent-4{color:#0091ea !important}.cyan{background-color:#00bcd4 !important}.cyan-text{color:#00bcd4 !important}.cyan.lighten-5{background-color:#e0f7fa !important}.cyan-text.text-lighten-5{color:#e0f7fa !important}.cyan.lighten-4{background-color:#b2ebf2 !important}.cyan-text.text-lighten-4{color:#b2ebf2 !important}.cyan.lighten-3{background-color:#80deea !important}.cyan-text.text-lighten-3{color:#80deea !important}.cyan.lighten-2{background-color:#4dd0e1 !important}.cyan-text.text-lighten-2{color:#4dd0e1 !important}.cyan.lighten-1{background-color:#26c6da !important}.cyan-text.text-lighten-1{color:#26c6da !important}.cyan.darken-1{background-color:#00acc1 !important}.cyan-text.text-darken-1{color:#00acc1 !important}.cyan.darken-2{background-color:#0097a7 !important}.cyan-text.text-darken-2{color:#0097a7 !important}.cyan.darken-3{background-color:#00838f !important}.cyan-text.text-darken-3{color:#00838f !important}.cyan.darken-4{background-color:#006064 !important}.cyan-text.text-darken-4{color:#006064 !important}.cyan.accent-1{background-color:#84ffff !important}.cyan-text.text-accent-1{color:#84ffff !important}.cyan.accent-2{background-color:#18ffff !important}.cyan-text.text-accent-2{color:#18ffff !important}.cyan.accent-3{background-color:#00e5ff !important}.cyan-text.text-accent-3{color:#00e5ff !important}.cyan.accent-4{background-color:#00b8d4 !important}.cyan-text.text-accent-4{color:#00b8d4 !important}.teal{background-color:#009688 !important}.teal-text{color:#009688 !important}.teal.lighten-5{background-color:#e0f2f1 !important}.teal-text.text-lighten-5{color:#e0f2f1 !important}.teal.lighten-4{background-color:#b2dfdb !important}.teal-text.text-lighten-4{color:#b2dfdb !important}.teal.lighten-3{background-color:#80cbc4 !important}.teal-text.text-lighten-3{color:#80cbc4 !important}.teal.lighten-2{background-color:#4db6ac !important}.teal-text.text-lighten-2{color:#4db6ac !important}.teal.lighten-1{background-color:#26a69a !important}.teal-text.text-lighten-1{color:#26a69a !important}.teal.darken-1{background-color:#00897b !important}.teal-text.text-darken-1{color:#00897b !important}.teal.darken-2{background-color:#00796b !important}.teal-text.text-darken-2{color:#00796b !important}.teal.darken-3{background-color:#00695c !important}.teal-text.text-darken-3{color:#00695c !important}.teal.darken-4{background-color:#004d40 !important}.teal-text.text-darken-4{color:#004d40 !important}.teal.accent-1{background-color:#a7ffeb !important}.teal-text.text-accent-1{color:#a7ffeb !important}.teal.accent-2{background-color:#64ffda !important}.teal-text.text-accent-2{color:#64ffda !important}.teal.accent-3{background-color:#1de9b6 !important}.teal-text.text-accent-3{color:#1de9b6 !important}.teal.accent-4{background-color:#00bfa5 !important}.teal-text.text-accent-4{color:#00bfa5 !important}.green{background-color:#4CAF50 !important}.green-text{color:#4CAF50 !important}.green.lighten-5{background-color:#E8F5E9 !important}.green-text.text-lighten-5{color:#E8F5E9 !important}.green.lighten-4{background-color:#C8E6C9 !important}.green-text.text-lighten-4{color:#C8E6C9 !important}.green.lighten-3{background-color:#A5D6A7 !important}.green-text.text-lighten-3{color:#A5D6A7 !important}.green.lighten-2{background-color:#81C784 !important}.green-text.text-lighten-2{color:#81C784 !important}.green.lighten-1{background-color:#66BB6A !important}.green-text.text-lighten-1{color:#66BB6A !important}.green.darken-1{background-color:#43A047 !important}.green-text.text-darken-1{color:#43A047 !important}.green.darken-2{background-color:#388E3C !important}.green-text.text-darken-2{color:#388E3C !important}.green.darken-3{background-color:#2E7D32 !important}.green-text.text-darken-3{color:#2E7D32 !important}.green.darken-4{background-color:#1B5E20 !important}.green-text.text-darken-4{color:#1B5E20 !important}.green.accent-1{background-color:#B9F6CA !important}.green-text.text-accent-1{color:#B9F6CA !important}.green.accent-2{background-color:#69F0AE !important}.green-text.text-accent-2{color:#69F0AE !important}.green.accent-3{background-color:#00E676 !important}.green-text.text-accent-3{color:#00E676 !important}.green.accent-4{background-color:#00C853 !important}.green-text.text-accent-4{color:#00C853 !important}.light-green{background-color:#8bc34a !important}.light-green-text{color:#8bc34a !important}.light-green.lighten-5{background-color:#f1f8e9 !important}.light-green-text.text-lighten-5{color:#f1f8e9 !important}.light-green.lighten-4{background-color:#dcedc8 !important}.light-green-text.text-lighten-4{color:#dcedc8 !important}.light-green.lighten-3{background-color:#c5e1a5 !important}.light-green-text.text-lighten-3{color:#c5e1a5 !important}.light-green.lighten-2{background-color:#aed581 !important}.light-green-text.text-lighten-2{color:#aed581 !important}.light-green.lighten-1{background-color:#9ccc65 !important}.light-green-text.text-lighten-1{color:#9ccc65 !important}.light-green.darken-1{background-color:#7cb342 !important}.light-green-text.text-darken-1{color:#7cb342 !important}.light-green.darken-2{background-color:#689f38 !important}.light-green-text.text-darken-2{color:#689f38 !important}.light-green.darken-3{background-color:#558b2f !important}.light-green-text.text-darken-3{color:#558b2f !important}.light-green.darken-4{background-color:#33691e !important}.light-green-text.text-darken-4{color:#33691e !important}.light-green.accent-1{background-color:#ccff90 !important}.light-green-text.text-accent-1{color:#ccff90 !important}.light-green.accent-2{background-color:#b2ff59 !important}.light-green-text.text-accent-2{color:#b2ff59 !important}.light-green.accent-3{background-color:#76ff03 !important}.light-green-text.text-accent-3{color:#76ff03 !important}.light-green.accent-4{background-color:#64dd17 !important}.light-green-text.text-accent-4{color:#64dd17 !important}.lime{background-color:#cddc39 !important}.lime-text{color:#cddc39 !important}.lime.lighten-5{background-color:#f9fbe7 !important}.lime-text.text-lighten-5{color:#f9fbe7 !important}.lime.lighten-4{background-color:#f0f4c3 !important}.lime-text.text-lighten-4{color:#f0f4c3 !important}.lime.lighten-3{background-color:#e6ee9c !important}.lime-text.text-lighten-3{color:#e6ee9c !important}.lime.lighten-2{background-color:#dce775 !important}.lime-text.text-lighten-2{color:#dce775 !important}.lime.lighten-1{background-color:#d4e157 !important}.lime-text.text-lighten-1{color:#d4e157 !important}.lime.darken-1{background-color:#c0ca33 !important}.lime-text.text-darken-1{color:#c0ca33 !important}.lime.darken-2{background-color:#afb42b !important}.lime-text.text-darken-2{color:#afb42b !important}.lime.darken-3{background-color:#9e9d24 !important}.lime-text.text-darken-3{color:#9e9d24 !important}.lime.darken-4{background-color:#827717 !important}.lime-text.text-darken-4{color:#827717 !important}.lime.accent-1{background-color:#f4ff81 !important}.lime-text.text-accent-1{color:#f4ff81 !important}.lime.accent-2{background-color:#eeff41 !important}.lime-text.text-accent-2{color:#eeff41 !important}.lime.accent-3{background-color:#c6ff00 !important}.lime-text.text-accent-3{color:#c6ff00 !important}.lime.accent-4{background-color:#aeea00 !important}.lime-text.text-accent-4{color:#aeea00 !important}.yellow{background-color:#ffeb3b !important}.yellow-text{color:#ffeb3b !important}.yellow.lighten-5{background-color:#fffde7 !important}.yellow-text.text-lighten-5{color:#fffde7 !important}.yellow.lighten-4{background-color:#fff9c4 !important}.yellow-text.text-lighten-4{color:#fff9c4 !important}.yellow.lighten-3{background-color:#fff59d !important}.yellow-text.text-lighten-3{color:#fff59d !important}.yellow.lighten-2{background-color:#fff176 !important}.yellow-text.text-lighten-2{color:#fff176 !important}.yellow.lighten-1{background-color:#ffee58 !important}.yellow-text.text-lighten-1{color:#ffee58 !important}.yellow.darken-1{background-color:#fdd835 !important}.yellow-text.text-darken-1{color:#fdd835 !important}.yellow.darken-2{background-color:#fbc02d !important}.yellow-text.text-darken-2{color:#fbc02d !important}.yellow.darken-3{background-color:#f9a825 !important}.yellow-text.text-darken-3{color:#f9a825 !important}.yellow.darken-4{background-color:#f57f17 !important}.yellow-text.text-darken-4{color:#f57f17 !important}.yellow.accent-1{background-color:#ffff8d !important}.yellow-text.text-accent-1{color:#ffff8d !important}.yellow.accent-2{background-color:#ff0 !important}.yellow-text.text-accent-2{color:#ff0 !important}.yellow.accent-3{background-color:#ffea00 !important}.yellow-text.text-accent-3{color:#ffea00 !important}.yellow.accent-4{background-color:#ffd600 !important}.yellow-text.text-accent-4{color:#ffd600 !important}.amber{background-color:#ffc107 !important}.amber-text{color:#ffc107 !important}.amber.lighten-5{background-color:#fff8e1 !important}.amber-text.text-lighten-5{color:#fff8e1 !important}.amber.lighten-4{background-color:#ffecb3 !important}.amber-text.text-lighten-4{color:#ffecb3 !important}.amber.lighten-3{background-color:#ffe082 !important}.amber-text.text-lighten-3{color:#ffe082 !important}.amber.lighten-2{background-color:#ffd54f !important}.amber-text.text-lighten-2{color:#ffd54f !important}.amber.lighten-1{background-color:#ffca28 !important}.amber-text.text-lighten-1{color:#ffca28 !important}.amber.darken-1{background-color:#ffb300 !important}.amber-text.text-darken-1{color:#ffb300 !important}.amber.darken-2{background-color:#ffa000 !important}.amber-text.text-darken-2{color:#ffa000 !important}.amber.darken-3{background-color:#ff8f00 !important}.amber-text.text-darken-3{color:#ff8f00 !important}.amber.darken-4{background-color:#ff6f00 !important}.amber-text.text-darken-4{color:#ff6f00 !important}.amber.accent-1{background-color:#ffe57f !important}.amber-text.text-accent-1{color:#ffe57f !important}.amber.accent-2{background-color:#ffd740 !important}.amber-text.text-accent-2{color:#ffd740 !important}.amber.accent-3{background-color:#ffc400 !important}.amber-text.text-accent-3{color:#ffc400 !important}.amber.accent-4{background-color:#ffab00 !important}.amber-text.text-accent-4{color:#ffab00 !important}.orange{background-color:#ff9800 !important}.orange-text{color:#ff9800 !important}.orange.lighten-5{background-color:#fff3e0 !important}.orange-text.text-lighten-5{color:#fff3e0 !important}.orange.lighten-4{background-color:#ffe0b2 !important}.orange-text.text-lighten-4{color:#ffe0b2 !important}.orange.lighten-3{background-color:#ffcc80 !important}.orange-text.text-lighten-3{color:#ffcc80 !important}.orange.lighten-2{background-color:#ffb74d !important}.orange-text.text-lighten-2{color:#ffb74d !important}.orange.lighten-1{background-color:#ffa726 !important}.orange-text.text-lighten-1{color:#ffa726 !important}.orange.darken-1{background-color:#fb8c00 !important}.orange-text.text-darken-1{color:#fb8c00 !important}.orange.darken-2{background-color:#f57c00 !important}.orange-text.text-darken-2{color:#f57c00 !important}.orange.darken-3{background-color:#ef6c00 !important}.orange-text.text-darken-3{color:#ef6c00 !important}.orange.darken-4{background-color:#e65100 !important}.orange-text.text-darken-4{color:#e65100 !important}.orange.accent-1{background-color:#ffd180 !important}.orange-text.text-accent-1{color:#ffd180 !important}.orange.accent-2{background-color:#ffab40 !important}.orange-text.text-accent-2{color:#ffab40 !important}.orange.accent-3{background-color:#ff9100 !important}.orange-text.text-accent-3{color:#ff9100 !important}.orange.accent-4{background-color:#ff6d00 !important}.orange-text.text-accent-4{color:#ff6d00 !important}.deep-orange{background-color:#ff5722 !important}.deep-orange-text{color:#ff5722 !important}.deep-orange.lighten-5{background-color:#fbe9e7 !important}.deep-orange-text.text-lighten-5{color:#fbe9e7 !important}.deep-orange.lighten-4{background-color:#ffccbc !important}.deep-orange-text.text-lighten-4{color:#ffccbc !important}.deep-orange.lighten-3{background-color:#ffab91 !important}.deep-orange-text.text-lighten-3{color:#ffab91 !important}.deep-orange.lighten-2{background-color:#ff8a65 !important}.deep-orange-text.text-lighten-2{color:#ff8a65 !important}.deep-orange.lighten-1{background-color:#ff7043 !important}.deep-orange-text.text-lighten-1{color:#ff7043 !important}.deep-orange.darken-1{background-color:#f4511e !important}.deep-orange-text.text-darken-1{color:#f4511e !important}.deep-orange.darken-2{background-color:#e64a19 !important}.deep-orange-text.text-darken-2{color:#e64a19 !important}.deep-orange.darken-3{background-color:#d84315 !important}.deep-orange-text.text-darken-3{color:#d84315 !important}.deep-orange.darken-4{background-color:#bf360c !important}.deep-orange-text.text-darken-4{color:#bf360c !important}.deep-orange.accent-1{background-color:#ff9e80 !important}.deep-orange-text.text-accent-1{color:#ff9e80 !important}.deep-orange.accent-2{background-color:#ff6e40 !important}.deep-orange-text.text-accent-2{color:#ff6e40 !important}.deep-orange.accent-3{background-color:#ff3d00 !important}.deep-orange-text.text-accent-3{color:#ff3d00 !important}.deep-orange.accent-4{background-color:#dd2c00 !important}.deep-orange-text.text-accent-4{color:#dd2c00 !important}.brown{background-color:#795548 !important}.brown-text{color:#795548 !important}.brown.lighten-5{background-color:#efebe9 !important}.brown-text.text-lighten-5{color:#efebe9 !important}.brown.lighten-4{background-color:#d7ccc8 !important}.brown-text.text-lighten-4{color:#d7ccc8 !important}.brown.lighten-3{background-color:#bcaaa4 !important}.brown-text.text-lighten-3{color:#bcaaa4 !important}.brown.lighten-2{background-color:#a1887f !important}.brown-text.text-lighten-2{color:#a1887f !important}.brown.lighten-1{background-color:#8d6e63 !important}.brown-text.text-lighten-1{color:#8d6e63 !important}.brown.darken-1{background-color:#6d4c41 !important}.brown-text.text-darken-1{color:#6d4c41 !important}.brown.darken-2{background-color:#5d4037 !important}.brown-text.text-darken-2{color:#5d4037 !important}.brown.darken-3{background-color:#4e342e !important}.brown-text.text-darken-3{color:#4e342e !important}.brown.darken-4{background-color:#3e2723 !important}.brown-text.text-darken-4{color:#3e2723 !important}.blue-grey{background-color:#607d8b !important}.blue-grey-text{color:#607d8b !important}.blue-grey.lighten-5{background-color:#eceff1 !important}.blue-grey-text.text-lighten-5{color:#eceff1 !important}.blue-grey.lighten-4{background-color:#cfd8dc !important}.blue-grey-text.text-lighten-4{color:#cfd8dc !important}.blue-grey.lighten-3{background-color:#b0bec5 !important}.blue-grey-text.text-lighten-3{color:#b0bec5 !important}.blue-grey.lighten-2{background-color:#90a4ae !important}.blue-grey-text.text-lighten-2{color:#90a4ae !important}.blue-grey.lighten-1{background-color:#78909c !important}.blue-grey-text.text-lighten-1{color:#78909c !important}.blue-grey.darken-1{background-color:#546e7a !important}.blue-grey-text.text-darken-1{color:#546e7a !important}.blue-grey.darken-2{background-color:#455a64 !important}.blue-grey-text.text-darken-2{color:#455a64 !important}.blue-grey.darken-3{background-color:#37474f !important}.blue-grey-text.text-darken-3{color:#37474f !important}.blue-grey.darken-4{background-color:#263238 !important}.blue-grey-text.text-darken-4{color:#263238 !important}.grey{background-color:#9e9e9e !important}.grey-text{color:#9e9e9e !important}.grey.lighten-5{background-color:#fafafa !important}.grey-text.text-lighten-5{color:#fafafa !important}.grey.lighten-4{background-color:#f5f5f5 !important}.grey-text.text-lighten-4{color:#f5f5f5 !important}.grey.lighten-3{background-color:#eee !important}.grey-text.text-lighten-3{color:#eee !important}.grey.lighten-2{background-color:#e0e0e0 !important}.grey-text.text-lighten-2{color:#e0e0e0 !important}.grey.lighten-1{background-color:#bdbdbd !important}.grey-text.text-lighten-1{color:#bdbdbd !important}.grey.darken-1{background-color:#757575 !important}.grey-text.text-darken-1{color:#757575 !important}.grey.darken-2{background-color:#616161 !important}.grey-text.text-darken-2{color:#616161 !important}.grey.darken-3{background-color:#424242 !important}.grey-text.text-darken-3{color:#424242 !important}.grey.darken-4{background-color:#212121 !important}.grey-text.text-darken-4{color:#212121 !important}.black{background-color:#000 !important}.black-text{color:#000 !important}.white{background-color:#fff !important}.white-text{color:#fff !important}.transparent{background-color:rgba(0,0,0,0) !important}.transparent-text{color:rgba(0,0,0,0) !important}/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:0.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace, monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace, monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:0.35em 0.75em 0.625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,*:before,*:after{-webkit-box-sizing:inherit;box-sizing:inherit}button,input,optgroup,select,textarea{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif}ul:not(.browser-default){padding-left:0;list-style-type:none}ul:not(.browser-default)>li{list-style-type:none}a{color:#039be5;text-decoration:none;-webkit-tap-highlight-color:transparent}.valign-wrapper{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.clearfix{clear:both}.z-depth-0{-webkit-box-shadow:none !important;box-shadow:none !important}.z-depth-1,nav,.card-panel,.card,.toast,.btn,.btn-large,.btn-small,.btn-floating,.dropdown-content,.collapsible,.sidenav{-webkit-box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2);box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2)}.z-depth-1-half,.btn:hover,.btn-large:hover,.btn-small:hover,.btn-floating:hover{-webkit-box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2);box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2)}.z-depth-2{-webkit-box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3);box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3)}.z-depth-3{-webkit-box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2);box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2)}.z-depth-4{-webkit-box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2);box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2)}.z-depth-5,.modal{-webkit-box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2);box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2)}.hoverable{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s}.hoverable:hover{-webkit-box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}.divider{height:1px;overflow:hidden;background-color:#e0e0e0}blockquote{margin:20px 0;padding-left:1.5rem;border-left:5px solid #ee6e73}i{line-height:inherit}i.left{float:left;margin-right:15px}i.right{float:right;margin-left:15px}i.tiny{font-size:1rem}i.small{font-size:2rem}i.medium{font-size:4rem}i.large{font-size:6rem}img.responsive-img,video.responsive-video{max-width:100%;height:auto}.pagination li{display:inline-block;border-radius:2px;text-align:center;vertical-align:top;height:30px}.pagination li a{color:#444;display:inline-block;font-size:1.2rem;padding:0 10px;line-height:30px}.pagination li.active a{color:#fff}.pagination li.active{background-color:#ee6e73}.pagination li.disabled a{cursor:default;color:#999}.pagination li i{font-size:2rem}.pagination li.pages ul li{display:inline-block;float:none}@media only screen and (max-width: 992px){.pagination{width:100%}.pagination li.prev,.pagination li.next{width:10%}.pagination li.pages{width:80%;overflow:hidden;white-space:nowrap}}.breadcrumb{font-size:18px;color:rgba(255,255,255,0.7)}.breadcrumb i,.breadcrumb [class^="mdi-"],.breadcrumb [class*="mdi-"],.breadcrumb i.material-icons{display:inline-block;float:left;font-size:24px}.breadcrumb:before{content:'\E5CC';color:rgba(255,255,255,0.7);vertical-align:top;display:inline-block;font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:25px;margin:0 10px 0 8px;-webkit-font-smoothing:antialiased}.breadcrumb:first-child:before{display:none}.breadcrumb:last-child{color:#fff}.parallax-container{position:relative;overflow:hidden;height:500px}.parallax-container .parallax{position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}.parallax-container .parallax img{opacity:0;position:absolute;left:50%;bottom:0;min-width:100%;min-height:100%;-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);-webkit-transform:translateX(-50%);transform:translateX(-50%)}.pin-top,.pin-bottom{position:relative}.pinned{position:fixed !important}ul.staggered-list li{opacity:0}.fade-in{opacity:0;-webkit-transform-origin:0 50%;transform-origin:0 50%}@media only screen and (max-width: 600px){.hide-on-small-only,.hide-on-small-and-down{display:none !important}}@media only screen and (max-width: 992px){.hide-on-med-and-down{display:none !important}}@media only screen and (min-width: 601px){.hide-on-med-and-up{display:none !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.hide-on-med-only{display:none !important}}@media only screen and (min-width: 993px){.hide-on-large-only{display:none !important}}@media only screen and (min-width: 1201px){.hide-on-extra-large-only{display:none !important}}@media only screen and (min-width: 1201px){.show-on-extra-large{display:block !important}}@media only screen and (min-width: 993px){.show-on-large{display:block !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.show-on-medium{display:block !important}}@media only screen and (max-width: 600px){.show-on-small{display:block !important}}@media only screen and (min-width: 601px){.show-on-medium-and-up{display:block !important}}@media only screen and (max-width: 992px){.show-on-medium-and-down{display:block !important}}@media only screen and (max-width: 600px){.center-on-small-only{text-align:center}}.page-footer{padding-top:20px;color:#fff;background-color:#ee6e73}.page-footer .footer-copyright{overflow:hidden;min-height:50px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;padding:10px 0px;color:rgba(255,255,255,0.8);background-color:rgba(51,51,51,0.08)}table,th,td{border:none}table{width:100%;display:table;border-collapse:collapse;border-spacing:0}table.striped tr{border-bottom:none}table.striped>tbody>tr:nth-child(odd){background-color:rgba(242,242,242,0.5)}table.striped>tbody>tr>td{border-radius:0}table.highlight>tbody>tr{-webkit-transition:background-color .25s ease;transition:background-color .25s ease}table.highlight>tbody>tr:hover{background-color:rgba(242,242,242,0.5)}table.centered thead tr th,table.centered tbody tr td{text-align:center}tr{border-bottom:1px solid rgba(0,0,0,0.12)}td,th{padding:15px 5px;display:table-cell;text-align:left;vertical-align:middle;border-radius:2px}@media only screen and (max-width: 992px){table.responsive-table{width:100%;border-collapse:collapse;border-spacing:0;display:block;position:relative}table.responsive-table td:empty:before{content:'\00a0'}table.responsive-table th,table.responsive-table td{margin:0;vertical-align:top}table.responsive-table th{text-align:left}table.responsive-table thead{display:block;float:left}table.responsive-table thead tr{display:block;padding:0 10px 0 0}table.responsive-table thead tr th::before{content:"\00a0"}table.responsive-table tbody{display:block;width:auto;position:relative;overflow-x:auto;white-space:nowrap}table.responsive-table tbody tr{display:inline-block;vertical-align:top}table.responsive-table th{display:block;text-align:right}table.responsive-table td{display:block;min-height:1.25em;text-align:left}table.responsive-table tr{border-bottom:none;padding:0 10px}table.responsive-table thead{border:0;border-right:1px solid rgba(0,0,0,0.12)}}.collection{margin:.5rem 0 1rem 0;border:1px solid #e0e0e0;border-radius:2px;overflow:hidden;position:relative}.collection .collection-item{background-color:#fff;line-height:1.5rem;padding:10px 20px;margin:0;border-bottom:1px solid #e0e0e0}.collection .collection-item.avatar{min-height:84px;padding-left:72px;position:relative}.collection .collection-item.avatar:not(.circle-clipper)>.circle,.collection .collection-item.avatar :not(.circle-clipper)>.circle{position:absolute;width:42px;height:42px;overflow:hidden;left:15px;display:inline-block;vertical-align:middle}.collection .collection-item.avatar i.circle{font-size:18px;line-height:42px;color:#fff;background-color:#999;text-align:center}.collection .collection-item.avatar .title{font-size:16px}.collection .collection-item.avatar p{margin:0}.collection .collection-item.avatar .secondary-content{position:absolute;top:16px;right:16px}.collection .collection-item:last-child{border-bottom:none}.collection .collection-item.active{background-color:#26a69a;color:#eafaf9}.collection .collection-item.active .secondary-content{color:#fff}.collection a.collection-item{display:block;-webkit-transition:.25s;transition:.25s;color:#26a69a}.collection a.collection-item:not(.active):hover{background-color:#ddd}.collection.with-header .collection-header{background-color:#fff;border-bottom:1px solid #e0e0e0;padding:10px 20px}.collection.with-header .collection-item{padding-left:30px}.collection.with-header .collection-item.avatar{padding-left:72px}.secondary-content{float:right;color:#26a69a}.collapsible .collection{margin:0;border:none}.video-container{position:relative;padding-bottom:56.25%;height:0;overflow:hidden}.video-container iframe,.video-container object,.video-container embed{position:absolute;top:0;left:0;width:100%;height:100%}.progress{position:relative;height:4px;display:block;width:100%;background-color:#acece6;border-radius:2px;margin:.5rem 0 1rem 0;overflow:hidden}.progress .determinate{position:absolute;top:0;left:0;bottom:0;background-color:#26a69a;-webkit-transition:width .3s linear;transition:width .3s linear}.progress .indeterminate{background-color:#26a69a}.progress .indeterminate:before{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite}.progress .indeterminate:after{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;-webkit-animation-delay:1.15s;animation-delay:1.15s}@-webkit-keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@-webkit-keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}@keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}.hide{display:none !important}.left-align{text-align:left}.right-align{text-align:right}.center,.center-align{text-align:center}.left{float:left !important}.right{float:right !important}.no-select,input[type=range],input[type=range]+.thumb{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.circle{border-radius:50%}.center-block{display:block;margin-left:auto;margin-right:auto}.truncate{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-padding{padding:0 !important}span.badge{min-width:3rem;padding:0 6px;margin-left:14px;text-align:center;font-size:1rem;line-height:22px;height:22px;color:#757575;float:right;-webkit-box-sizing:border-box;box-sizing:border-box}span.badge.new{font-weight:300;font-size:0.8rem;color:#fff;background-color:#26a69a;border-radius:2px}span.badge.new:after{content:" new"}span.badge[data-badge-caption]::after{content:" " attr(data-badge-caption)}nav ul a span.badge{display:inline-block;float:none;margin-left:4px;line-height:22px;height:22px;-webkit-font-smoothing:auto}.collection-item span.badge{margin-top:calc(.75rem - 11px)}.collapsible span.badge{margin-left:auto}.sidenav span.badge{margin-top:calc(24px - 11px)}table span.badge{display:inline-block;float:none;margin-left:auto}.material-icons{text-rendering:optimizeLegibility;-webkit-font-feature-settings:'liga';-moz-font-feature-settings:'liga';font-feature-settings:'liga'}.container{margin:0 auto;max-width:1280px;width:90%}@media only screen and (min-width: 601px){.container{width:85%}}@media only screen and (min-width: 993px){.container{width:70%}}.col .row{margin-left:-.75rem;margin-right:-.75rem}.section{padding-top:1rem;padding-bottom:1rem}.section.no-pad{padding:0}.section.no-pad-bot{padding-bottom:0}.section.no-pad-top{padding-top:0}.row{margin-left:auto;margin-right:auto;margin-bottom:20px}.row:after{content:"";display:table;clear:both}.row .col{float:left;-webkit-box-sizing:border-box;box-sizing:border-box;padding:0 .75rem;min-height:1px}.row .col[class*="push-"],.row .col[class*="pull-"]{position:relative}.row .col.s1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.s4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.s7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.s10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-s1{margin-left:8.3333333333%}.row .col.pull-s1{right:8.3333333333%}.row .col.push-s1{left:8.3333333333%}.row .col.offset-s2{margin-left:16.6666666667%}.row .col.pull-s2{right:16.6666666667%}.row .col.push-s2{left:16.6666666667%}.row .col.offset-s3{margin-left:25%}.row .col.pull-s3{right:25%}.row .col.push-s3{left:25%}.row .col.offset-s4{margin-left:33.3333333333%}.row .col.pull-s4{right:33.3333333333%}.row .col.push-s4{left:33.3333333333%}.row .col.offset-s5{margin-left:41.6666666667%}.row .col.pull-s5{right:41.6666666667%}.row .col.push-s5{left:41.6666666667%}.row .col.offset-s6{margin-left:50%}.row .col.pull-s6{right:50%}.row .col.push-s6{left:50%}.row .col.offset-s7{margin-left:58.3333333333%}.row .col.pull-s7{right:58.3333333333%}.row .col.push-s7{left:58.3333333333%}.row .col.offset-s8{margin-left:66.6666666667%}.row .col.pull-s8{right:66.6666666667%}.row .col.push-s8{left:66.6666666667%}.row .col.offset-s9{margin-left:75%}.row .col.pull-s9{right:75%}.row .col.push-s9{left:75%}.row .col.offset-s10{margin-left:83.3333333333%}.row .col.pull-s10{right:83.3333333333%}.row .col.push-s10{left:83.3333333333%}.row .col.offset-s11{margin-left:91.6666666667%}.row .col.pull-s11{right:91.6666666667%}.row .col.push-s11{left:91.6666666667%}.row .col.offset-s12{margin-left:100%}.row .col.pull-s12{right:100%}.row .col.push-s12{left:100%}@media only screen and (min-width: 601px){.row .col.m1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.m4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.m7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.m10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-m1{margin-left:8.3333333333%}.row .col.pull-m1{right:8.3333333333%}.row .col.push-m1{left:8.3333333333%}.row .col.offset-m2{margin-left:16.6666666667%}.row .col.pull-m2{right:16.6666666667%}.row .col.push-m2{left:16.6666666667%}.row .col.offset-m3{margin-left:25%}.row .col.pull-m3{right:25%}.row .col.push-m3{left:25%}.row .col.offset-m4{margin-left:33.3333333333%}.row .col.pull-m4{right:33.3333333333%}.row .col.push-m4{left:33.3333333333%}.row .col.offset-m5{margin-left:41.6666666667%}.row .col.pull-m5{right:41.6666666667%}.row .col.push-m5{left:41.6666666667%}.row .col.offset-m6{margin-left:50%}.row .col.pull-m6{right:50%}.row .col.push-m6{left:50%}.row .col.offset-m7{margin-left:58.3333333333%}.row .col.pull-m7{right:58.3333333333%}.row .col.push-m7{left:58.3333333333%}.row .col.offset-m8{margin-left:66.6666666667%}.row .col.pull-m8{right:66.6666666667%}.row .col.push-m8{left:66.6666666667%}.row .col.offset-m9{margin-left:75%}.row .col.pull-m9{right:75%}.row .col.push-m9{left:75%}.row .col.offset-m10{margin-left:83.3333333333%}.row .col.pull-m10{right:83.3333333333%}.row .col.push-m10{left:83.3333333333%}.row .col.offset-m11{margin-left:91.6666666667%}.row .col.pull-m11{right:91.6666666667%}.row .col.push-m11{left:91.6666666667%}.row .col.offset-m12{margin-left:100%}.row .col.pull-m12{right:100%}.row .col.push-m12{left:100%}}@media only screen and (min-width: 993px){.row .col.l1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.l4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.l7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.l10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-l1{margin-left:8.3333333333%}.row .col.pull-l1{right:8.3333333333%}.row .col.push-l1{left:8.3333333333%}.row .col.offset-l2{margin-left:16.6666666667%}.row .col.pull-l2{right:16.6666666667%}.row .col.push-l2{left:16.6666666667%}.row .col.offset-l3{margin-left:25%}.row .col.pull-l3{right:25%}.row .col.push-l3{left:25%}.row .col.offset-l4{margin-left:33.3333333333%}.row .col.pull-l4{right:33.3333333333%}.row .col.push-l4{left:33.3333333333%}.row .col.offset-l5{margin-left:41.6666666667%}.row .col.pull-l5{right:41.6666666667%}.row .col.push-l5{left:41.6666666667%}.row .col.offset-l6{margin-left:50%}.row .col.pull-l6{right:50%}.row .col.push-l6{left:50%}.row .col.offset-l7{margin-left:58.3333333333%}.row .col.pull-l7{right:58.3333333333%}.row .col.push-l7{left:58.3333333333%}.row .col.offset-l8{margin-left:66.6666666667%}.row .col.pull-l8{right:66.6666666667%}.row .col.push-l8{left:66.6666666667%}.row .col.offset-l9{margin-left:75%}.row .col.pull-l9{right:75%}.row .col.push-l9{left:75%}.row .col.offset-l10{margin-left:83.3333333333%}.row .col.pull-l10{right:83.3333333333%}.row .col.push-l10{left:83.3333333333%}.row .col.offset-l11{margin-left:91.6666666667%}.row .col.pull-l11{right:91.6666666667%}.row .col.push-l11{left:91.6666666667%}.row .col.offset-l12{margin-left:100%}.row .col.pull-l12{right:100%}.row .col.push-l12{left:100%}}@media only screen and (min-width: 1201px){.row .col.xl1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.xl4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.xl7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.xl10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-xl1{margin-left:8.3333333333%}.row .col.pull-xl1{right:8.3333333333%}.row .col.push-xl1{left:8.3333333333%}.row .col.offset-xl2{margin-left:16.6666666667%}.row .col.pull-xl2{right:16.6666666667%}.row .col.push-xl2{left:16.6666666667%}.row .col.offset-xl3{margin-left:25%}.row .col.pull-xl3{right:25%}.row .col.push-xl3{left:25%}.row .col.offset-xl4{margin-left:33.3333333333%}.row .col.pull-xl4{right:33.3333333333%}.row .col.push-xl4{left:33.3333333333%}.row .col.offset-xl5{margin-left:41.6666666667%}.row .col.pull-xl5{right:41.6666666667%}.row .col.push-xl5{left:41.6666666667%}.row .col.offset-xl6{margin-left:50%}.row .col.pull-xl6{right:50%}.row .col.push-xl6{left:50%}.row .col.offset-xl7{margin-left:58.3333333333%}.row .col.pull-xl7{right:58.3333333333%}.row .col.push-xl7{left:58.3333333333%}.row .col.offset-xl8{margin-left:66.6666666667%}.row .col.pull-xl8{right:66.6666666667%}.row .col.push-xl8{left:66.6666666667%}.row .col.offset-xl9{margin-left:75%}.row .col.pull-xl9{right:75%}.row .col.push-xl9{left:75%}.row .col.offset-xl10{margin-left:83.3333333333%}.row .col.pull-xl10{right:83.3333333333%}.row .col.push-xl10{left:83.3333333333%}.row .col.offset-xl11{margin-left:91.6666666667%}.row .col.pull-xl11{right:91.6666666667%}.row .col.push-xl11{left:91.6666666667%}.row .col.offset-xl12{margin-left:100%}.row .col.pull-xl12{right:100%}.row .col.push-xl12{left:100%}}nav{color:#fff;background-color:#ee6e73;width:100%;height:56px;line-height:56px}nav.nav-extended{height:auto}nav.nav-extended .nav-wrapper{min-height:56px;height:auto}nav.nav-extended .nav-content{position:relative;line-height:normal}nav a{color:#fff}nav i,nav [class^="mdi-"],nav [class*="mdi-"],nav i.material-icons{display:block;font-size:24px;height:56px;line-height:56px}nav .nav-wrapper{position:relative;height:100%}@media only screen and (min-width: 993px){nav a.sidenav-trigger{display:none}}nav .sidenav-trigger{float:left;position:relative;z-index:1;height:56px;margin:0 18px}nav .sidenav-trigger i{height:56px;line-height:56px}nav .brand-logo{position:absolute;color:#fff;display:inline-block;font-size:2.1rem;padding:0}nav .brand-logo.center{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}@media only screen and (max-width: 992px){nav .brand-logo{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}nav .brand-logo.left,nav .brand-logo.right{padding:0;-webkit-transform:none;transform:none}nav .brand-logo.left{left:0.5rem}nav .brand-logo.right{right:0.5rem;left:auto}}nav .brand-logo.right{right:0.5rem;padding:0}nav .brand-logo i,nav .brand-logo [class^="mdi-"],nav .brand-logo [class*="mdi-"],nav .brand-logo i.material-icons{float:left;margin-right:15px}nav .nav-title{display:inline-block;font-size:32px;padding:28px 0}nav ul{margin:0}nav ul li{-webkit-transition:background-color .3s;transition:background-color .3s;float:left;padding:0}nav ul li.active{background-color:rgba(0,0,0,0.1)}nav ul a{-webkit-transition:background-color .3s;transition:background-color .3s;font-size:1rem;color:#fff;display:block;padding:0 15px;cursor:pointer}nav ul a.btn,nav ul a.btn-large,nav ul a.btn-small,nav ul a.btn-large,nav ul a.btn-flat,nav ul a.btn-floating{margin-top:-2px;margin-left:15px;margin-right:15px}nav ul a.btn>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-small>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-flat>.material-icons,nav ul a.btn-floating>.material-icons{height:inherit;line-height:inherit}nav ul a:hover{background-color:rgba(0,0,0,0.1)}nav ul.left{float:left}nav form{height:100%}nav .input-field{margin:0;height:100%}nav .input-field input{height:100%;font-size:1.2rem;border:none;padding-left:2rem}nav .input-field input:focus,nav .input-field input[type=text]:valid,nav .input-field input[type=password]:valid,nav .input-field input[type=email]:valid,nav .input-field input[type=url]:valid,nav .input-field input[type=date]:valid{border:none;-webkit-box-shadow:none;box-shadow:none}nav .input-field label{top:0;left:0}nav .input-field label i{color:rgba(255,255,255,0.7);-webkit-transition:color .3s;transition:color .3s}nav .input-field label.active i{color:#fff}.navbar-fixed{position:relative;height:56px;z-index:997}.navbar-fixed nav{position:fixed}@media only screen and (min-width: 601px){nav.nav-extended .nav-wrapper{min-height:64px}nav,nav .nav-wrapper i,nav a.sidenav-trigger,nav a.sidenav-trigger i{height:64px;line-height:64px}.navbar-fixed{height:64px}}a{text-decoration:none}html{line-height:1.5;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-weight:normal;color:rgba(0,0,0,0.87)}@media only screen and (min-width: 0){html{font-size:14px}}@media only screen and (min-width: 992px){html{font-size:14.5px}}@media only screen and (min-width: 1200px){html{font-size:15px}}h1,h2,h3,h4,h5,h6{font-weight:400;line-height:1.3}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{font-weight:inherit}h1{font-size:4.2rem;line-height:110%;margin:2.8rem 0 1.68rem 0}h2{font-size:3.56rem;line-height:110%;margin:2.3733333333rem 0 1.424rem 0}h3{font-size:2.92rem;line-height:110%;margin:1.9466666667rem 0 1.168rem 0}h4{font-size:2.28rem;line-height:110%;margin:1.52rem 0 .912rem 0}h5{font-size:1.64rem;line-height:110%;margin:1.0933333333rem 0 .656rem 0}h6{font-size:1.15rem;line-height:110%;margin:.7666666667rem 0 .46rem 0}em{font-style:italic}strong{font-weight:500}small{font-size:75%}.light{font-weight:300}.thin{font-weight:200}@media only screen and (min-width: 360px){.flow-text{font-size:1.2rem}}@media only screen and (min-width: 390px){.flow-text{font-size:1.224rem}}@media only screen and (min-width: 420px){.flow-text{font-size:1.248rem}}@media only screen and (min-width: 450px){.flow-text{font-size:1.272rem}}@media only screen and (min-width: 480px){.flow-text{font-size:1.296rem}}@media only screen and (min-width: 510px){.flow-text{font-size:1.32rem}}@media only screen and (min-width: 540px){.flow-text{font-size:1.344rem}}@media only screen and (min-width: 570px){.flow-text{font-size:1.368rem}}@media only screen and (min-width: 600px){.flow-text{font-size:1.392rem}}@media only screen and (min-width: 630px){.flow-text{font-size:1.416rem}}@media only screen and (min-width: 660px){.flow-text{font-size:1.44rem}}@media only screen and (min-width: 690px){.flow-text{font-size:1.464rem}}@media only screen and (min-width: 720px){.flow-text{font-size:1.488rem}}@media only screen and (min-width: 750px){.flow-text{font-size:1.512rem}}@media only screen and (min-width: 780px){.flow-text{font-size:1.536rem}}@media only screen and (min-width: 810px){.flow-text{font-size:1.56rem}}@media only screen and (min-width: 840px){.flow-text{font-size:1.584rem}}@media only screen and (min-width: 870px){.flow-text{font-size:1.608rem}}@media only screen and (min-width: 900px){.flow-text{font-size:1.632rem}}@media only screen and (min-width: 930px){.flow-text{font-size:1.656rem}}@media only screen and (min-width: 960px){.flow-text{font-size:1.68rem}}@media only screen and (max-width: 360px){.flow-text{font-size:1.2rem}}.scale-transition{-webkit-transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63), -webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important}.scale-transition.scale-out{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .2s !important;transition:-webkit-transform .2s !important;transition:transform .2s !important;transition:transform .2s, -webkit-transform .2s !important}.scale-transition.scale-in{-webkit-transform:scale(1);transform:scale(1)}.card-panel{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;padding:24px;margin:.5rem 0 1rem 0;border-radius:2px;background-color:#fff}.card{position:relative;margin:.5rem 0 1rem 0;background-color:#fff;-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;border-radius:2px}.card .card-title{font-size:24px;font-weight:300}.card .card-title.activator{cursor:pointer}.card.small,.card.medium,.card.large{position:relative}.card.small .card-image,.card.medium .card-image,.card.large .card-image{max-height:60%;overflow:hidden}.card.small .card-image+.card-content,.card.medium .card-image+.card-content,.card.large .card-image+.card-content{max-height:40%}.card.small .card-content,.card.medium .card-content,.card.large .card-content{max-height:100%;overflow:hidden}.card.small .card-action,.card.medium .card-action,.card.large .card-action{position:absolute;bottom:0;left:0;right:0}.card.small{height:300px}.card.medium{height:400px}.card.large{height:500px}.card.horizontal{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.card.horizontal.small .card-image,.card.horizontal.medium .card-image,.card.horizontal.large .card-image{height:100%;max-height:none;overflow:visible}.card.horizontal.small .card-image img,.card.horizontal.medium .card-image img,.card.horizontal.large .card-image img{height:100%}.card.horizontal .card-image{max-width:50%}.card.horizontal .card-image img{border-radius:2px 0 0 2px;max-width:100%;width:auto}.card.horizontal .card-stacked{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;position:relative}.card.horizontal .card-stacked .card-content{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.card.sticky-action .card-action{z-index:2}.card.sticky-action .card-reveal{z-index:1;padding-bottom:64px}.card .card-image{position:relative}.card .card-image img{display:block;border-radius:2px 2px 0 0;position:relative;left:0;right:0;top:0;bottom:0;width:100%}.card .card-image .card-title{color:#fff;position:absolute;bottom:0;left:0;max-width:100%;padding:24px}.card .card-content{padding:24px;border-radius:0 0 2px 2px}.card .card-content p{margin:0}.card .card-content .card-title{display:block;line-height:32px;margin-bottom:8px}.card .card-content .card-title i{line-height:32px}.card .card-action{background-color:inherit;border-top:1px solid rgba(160,160,160,0.2);position:relative;padding:16px 24px}.card .card-action:last-child{border-radius:0 0 2px 2px}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating){color:#ffab40;margin-right:24px;-webkit-transition:color .3s ease;transition:color .3s ease;text-transform:uppercase}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating):hover{color:#ffd8a6}.card .card-reveal{padding:24px;position:absolute;background-color:#fff;width:100%;overflow-y:auto;left:0;top:100%;height:100%;z-index:3;display:none}.card .card-reveal .card-title{cursor:pointer;display:block}#toast-container{display:block;position:fixed;z-index:10000}@media only screen and (max-width: 600px){#toast-container{min-width:100%;bottom:0%}}@media only screen and (min-width: 601px) and (max-width: 992px){#toast-container{left:5%;bottom:7%;max-width:90%}}@media only screen and (min-width: 993px){#toast-container{top:10%;right:7%;max-width:86%}}.toast{border-radius:2px;top:35px;width:auto;margin-top:10px;position:relative;max-width:100%;height:auto;min-height:48px;line-height:1.5em;background-color:#323232;padding:10px 25px;font-size:1.1rem;font-weight:300;color:#fff;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;cursor:default}.toast .toast-action{color:#eeff41;font-weight:500;margin-right:-25px;margin-left:3rem}.toast.rounded{border-radius:24px}@media only screen and (max-width: 600px){.toast{width:100%;border-radius:0}}.tabs{position:relative;overflow-x:auto;overflow-y:hidden;height:48px;width:100%;background-color:#fff;margin:0 auto;white-space:nowrap}.tabs.tabs-transparent{background-color:transparent}.tabs.tabs-transparent .tab a,.tabs.tabs-transparent .tab.disabled a,.tabs.tabs-transparent .tab.disabled a:hover{color:rgba(255,255,255,0.7)}.tabs.tabs-transparent .tab a:hover,.tabs.tabs-transparent .tab a.active{color:#fff}.tabs.tabs-transparent .indicator{background-color:#fff}.tabs.tabs-fixed-width{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs.tabs-fixed-width .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab{display:inline-block;text-align:center;line-height:48px;height:48px;padding:0;margin:0;text-transform:uppercase}.tabs .tab a{color:rgba(238,110,115,0.7);display:block;width:100%;height:100%;padding:0 24px;font-size:14px;text-overflow:ellipsis;overflow:hidden;-webkit-transition:color .28s ease, background-color .28s ease;transition:color .28s ease, background-color .28s ease}.tabs .tab a:focus,.tabs .tab a:focus.active{background-color:rgba(246,178,181,0.2);outline:none}.tabs .tab a:hover,.tabs .tab a.active{background-color:transparent;color:#ee6e73}.tabs .tab.disabled a,.tabs .tab.disabled a:hover{color:rgba(238,110,115,0.4);cursor:default}.tabs .indicator{position:absolute;bottom:0;height:2px;background-color:#f6b2b5;will-change:left, right}@media only screen and (max-width: 992px){.tabs{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab a{padding:0 12px}}.material-tooltip{padding:10px 8px;font-size:1rem;z-index:2000;background-color:transparent;border-radius:2px;color:#fff;min-height:36px;line-height:120%;opacity:0;position:absolute;text-align:center;max-width:calc(100% - 4px);overflow:hidden;left:0;top:0;pointer-events:none;visibility:hidden;background-color:#323232}.backdrop{position:absolute;opacity:0;height:7px;width:14px;border-radius:0 0 50% 50%;background-color:#323232;z-index:-1;-webkit-transform-origin:50% 0%;transform-origin:50% 0%;visibility:hidden}.btn,.btn-large,.btn-small,.btn-flat{border:none;border-radius:2px;display:inline-block;height:36px;line-height:36px;padding:0 16px;text-transform:uppercase;vertical-align:middle;-webkit-tap-highlight-color:transparent}.btn.disabled,.disabled.btn-large,.disabled.btn-small,.btn-floating.disabled,.btn-large.disabled,.btn-small.disabled,.btn-flat.disabled,.btn:disabled,.btn-large:disabled,.btn-small:disabled,.btn-floating:disabled,.btn-large:disabled,.btn-small:disabled,.btn-flat:disabled,.btn[disabled],.btn-large[disabled],.btn-small[disabled],.btn-floating[disabled],.btn-large[disabled],.btn-small[disabled],.btn-flat[disabled]{pointer-events:none;background-color:#DFDFDF !important;-webkit-box-shadow:none;box-shadow:none;color:#9F9F9F !important;cursor:default}.btn.disabled:hover,.disabled.btn-large:hover,.disabled.btn-small:hover,.btn-floating.disabled:hover,.btn-large.disabled:hover,.btn-small.disabled:hover,.btn-flat.disabled:hover,.btn:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-floating:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-flat:disabled:hover,.btn[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-floating[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-flat[disabled]:hover{background-color:#DFDFDF !important;color:#9F9F9F !important}.btn,.btn-large,.btn-small,.btn-floating,.btn-large,.btn-small,.btn-flat{font-size:14px;outline:0}.btn i,.btn-large i,.btn-small i,.btn-floating i,.btn-large i,.btn-small i,.btn-flat i{font-size:1.3rem;line-height:inherit}.btn:focus,.btn-large:focus,.btn-small:focus,.btn-floating:focus{background-color:#1d7d74}.btn,.btn-large,.btn-small{text-decoration:none;color:#fff;background-color:#26a69a;text-align:center;letter-spacing:.5px;-webkit-transition:background-color .2s ease-out;transition:background-color .2s ease-out;cursor:pointer}.btn:hover,.btn-large:hover,.btn-small:hover{background-color:#2bbbad}.btn-floating{display:inline-block;color:#fff;position:relative;overflow:hidden;z-index:1;width:40px;height:40px;line-height:40px;padding:0;background-color:#26a69a;border-radius:50%;-webkit-transition:background-color .3s;transition:background-color .3s;cursor:pointer;vertical-align:middle}.btn-floating:hover{background-color:#26a69a}.btn-floating:before{border-radius:0}.btn-floating.btn-large{width:56px;height:56px;padding:0}.btn-floating.btn-large.halfway-fab{bottom:-28px}.btn-floating.btn-large i{line-height:56px}.btn-floating.btn-small{width:32.4px;height:32.4px}.btn-floating.btn-small.halfway-fab{bottom:-16.2px}.btn-floating.btn-small i{line-height:32.4px}.btn-floating.halfway-fab{position:absolute;right:24px;bottom:-20px}.btn-floating.halfway-fab.left{right:auto;left:24px}.btn-floating i{width:inherit;display:inline-block;text-align:center;color:#fff;font-size:1.6rem;line-height:40px}button.btn-floating{border:none}.fixed-action-btn{position:fixed;right:23px;bottom:23px;padding-top:15px;margin-bottom:0;z-index:997}.fixed-action-btn.active ul{visibility:visible}.fixed-action-btn.direction-left,.fixed-action-btn.direction-right{padding:0 0 0 15px}.fixed-action-btn.direction-left ul,.fixed-action-btn.direction-right ul{text-align:right;right:64px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);height:100%;left:auto;width:500px}.fixed-action-btn.direction-left ul li,.fixed-action-btn.direction-right ul li{display:inline-block;margin:7.5px 15px 0 0}.fixed-action-btn.direction-right{padding:0 15px 0 0}.fixed-action-btn.direction-right ul{text-align:left;direction:rtl;left:64px;right:auto}.fixed-action-btn.direction-right ul li{margin:7.5px 0 0 15px}.fixed-action-btn.direction-bottom{padding:0 0 15px 0}.fixed-action-btn.direction-bottom ul{top:64px;bottom:auto;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:reverse;-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.fixed-action-btn.direction-bottom ul li{margin:15px 0 0 0}.fixed-action-btn.toolbar{padding:0;height:56px}.fixed-action-btn.toolbar.active>a i{opacity:0}.fixed-action-btn.toolbar ul{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;top:0;bottom:0;z-index:1}.fixed-action-btn.toolbar ul li{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;display:inline-block;margin:0;height:100%;-webkit-transition:none;transition:none}.fixed-action-btn.toolbar ul li a{display:block;overflow:hidden;position:relative;width:100%;height:100%;background-color:transparent;-webkit-box-shadow:none;box-shadow:none;color:#fff;line-height:56px;z-index:1}.fixed-action-btn.toolbar ul li a i{line-height:inherit}.fixed-action-btn ul{left:0;right:0;text-align:center;position:absolute;bottom:64px;margin:0;visibility:hidden}.fixed-action-btn ul li{margin-bottom:15px}.fixed-action-btn ul a.btn-floating{opacity:0}.fixed-action-btn .fab-backdrop{position:absolute;top:0;left:0;z-index:-1;width:40px;height:40px;background-color:#26a69a;border-radius:50%;-webkit-transform:scale(0);transform:scale(0)}.btn-flat{-webkit-box-shadow:none;box-shadow:none;background-color:transparent;color:#343434;cursor:pointer;-webkit-transition:background-color .2s;transition:background-color .2s}.btn-flat:focus,.btn-flat:hover{-webkit-box-shadow:none;box-shadow:none}.btn-flat:focus{background-color:rgba(0,0,0,0.1)}.btn-flat.disabled,.btn-flat.btn-flat[disabled]{background-color:transparent !important;color:#b3b2b2 !important;cursor:default}.btn-large{height:54px;line-height:54px;font-size:15px;padding:0 28px}.btn-large i{font-size:1.6rem}.btn-small{height:32.4px;line-height:32.4px;font-size:13px}.btn-small i{font-size:1.2rem}.btn-block{display:block}.dropdown-content{background-color:#fff;margin:0;display:none;min-width:100px;overflow-y:auto;opacity:0;position:absolute;left:0;top:0;z-index:9999;-webkit-transform-origin:0 0;transform-origin:0 0}.dropdown-content:focus{outline:0}.dropdown-content li{clear:both;color:rgba(0,0,0,0.87);cursor:pointer;min-height:50px;line-height:1.5rem;width:100%;text-align:left}.dropdown-content li:hover,.dropdown-content li.active{background-color:#eee}.dropdown-content li:focus{outline:none}.dropdown-content li.divider{min-height:0;height:1px}.dropdown-content li>a,.dropdown-content li>span{font-size:16px;color:#26a69a;display:block;line-height:22px;padding:14px 16px}.dropdown-content li>span>label{top:1px;left:0;height:18px}.dropdown-content li>a>i{height:inherit;line-height:inherit;float:left;margin:0 24px 0 0;width:24px}body.keyboard-focused .dropdown-content li:focus{background-color:#dadada}.input-field.col .dropdown-content [type="checkbox"]+label{top:1px;left:0;height:18px;-webkit-transform:none;transform:none}.dropdown-trigger{cursor:pointer}/*! - * Waves v0.6.0 - * http://fian.my.id/Waves - * - * Copyright 2014 Alfiana E. Sibuea and other contributors - * Released under the MIT license - * https://github.com/fians/Waves/blob/master/LICENSE - */.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;z-index:1;-webkit-transition:.3s ease-out;transition:.3s ease-out}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:20px;height:20px;margin-top:-10px;margin-left:-10px;opacity:0;background:rgba(0,0,0,0.2);-webkit-transition:all 0.7s ease-out;transition:all 0.7s ease-out;-webkit-transition-property:opacity, -webkit-transform;transition-property:opacity, -webkit-transform;transition-property:transform, opacity;transition-property:transform, opacity, -webkit-transform;-webkit-transform:scale(0);transform:scale(0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background-color:rgba(255,255,255,0.45)}.waves-effect.waves-red .waves-ripple{background-color:rgba(244,67,54,0.7)}.waves-effect.waves-yellow .waves-ripple{background-color:rgba(255,235,59,0.7)}.waves-effect.waves-orange .waves-ripple{background-color:rgba(255,152,0,0.7)}.waves-effect.waves-purple .waves-ripple{background-color:rgba(156,39,176,0.7)}.waves-effect.waves-green .waves-ripple{background-color:rgba(76,175,80,0.7)}.waves-effect.waves-teal .waves-ripple{background-color:rgba(0,150,136,0.7)}.waves-effect input[type="button"],.waves-effect input[type="reset"],.waves-effect input[type="submit"]{border:0;font-style:normal;font-size:inherit;text-transform:inherit;background:none}.waves-effect img{position:relative;z-index:-1}.waves-notransition{-webkit-transition:none !important;transition:none !important}.waves-circle{-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle, white 100%, black 100%)}.waves-input-wrapper{border-radius:0.2em;vertical-align:bottom}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%;-webkit-mask-image:none}.waves-block{display:block}.waves-effect .waves-ripple{z-index:-1}.modal{display:none;position:fixed;left:0;right:0;background-color:#fafafa;padding:0;max-height:70%;width:55%;margin:auto;overflow-y:auto;border-radius:2px;will-change:top, opacity}.modal:focus{outline:none}@media only screen and (max-width: 992px){.modal{width:80%}}.modal h1,.modal h2,.modal h3,.modal h4{margin-top:0}.modal .modal-content{padding:24px}.modal .modal-close{cursor:pointer}.modal .modal-footer{border-radius:0 0 2px 2px;background-color:#fafafa;padding:4px 6px;height:56px;width:100%;text-align:right}.modal .modal-footer .btn,.modal .modal-footer .btn-large,.modal .modal-footer .btn-small,.modal .modal-footer .btn-flat{margin:6px 0}.modal-overlay{position:fixed;z-index:999;top:-25%;left:0;bottom:0;right:0;height:125%;width:100%;background:#000;display:none;will-change:opacity}.modal.modal-fixed-footer{padding:0;height:70%}.modal.modal-fixed-footer .modal-content{position:absolute;height:calc(100% - 56px);max-height:100%;width:100%;overflow-y:auto}.modal.modal-fixed-footer .modal-footer{border-top:1px solid rgba(0,0,0,0.1);position:absolute;bottom:0}.modal.bottom-sheet{top:auto;bottom:-100%;margin:0;width:100%;max-height:45%;border-radius:0;will-change:bottom, opacity}.collapsible{border-top:1px solid #ddd;border-right:1px solid #ddd;border-left:1px solid #ddd;margin:.5rem 0 1rem 0}.collapsible-header{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;cursor:pointer;-webkit-tap-highlight-color:transparent;line-height:1.5;padding:1rem;background-color:#fff;border-bottom:1px solid #ddd}.collapsible-header:focus{outline:0}.collapsible-header i{width:2rem;font-size:1.6rem;display:inline-block;text-align:center;margin-right:1rem}.keyboard-focused .collapsible-header:focus{background-color:#eee}.collapsible-body{display:none;border-bottom:1px solid #ddd;-webkit-box-sizing:border-box;box-sizing:border-box;padding:2rem}.sidenav .collapsible,.sidenav.fixed .collapsible{border:none;-webkit-box-shadow:none;box-shadow:none}.sidenav .collapsible li,.sidenav.fixed .collapsible li{padding:0}.sidenav .collapsible-header,.sidenav.fixed .collapsible-header{background-color:transparent;border:none;line-height:inherit;height:inherit;padding:0 16px}.sidenav .collapsible-header:hover,.sidenav.fixed .collapsible-header:hover{background-color:rgba(0,0,0,0.05)}.sidenav .collapsible-header i,.sidenav.fixed .collapsible-header i{line-height:inherit}.sidenav .collapsible-body,.sidenav.fixed .collapsible-body{border:0;background-color:#fff}.sidenav .collapsible-body li a,.sidenav.fixed .collapsible-body li a{padding:0 23.5px 0 31px}.collapsible.popout{border:none;-webkit-box-shadow:none;box-shadow:none}.collapsible.popout>li{-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);margin:0 24px;-webkit-transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.collapsible.popout>li.active{-webkit-box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);margin:16px 0}.chip{display:inline-block;height:32px;font-size:13px;font-weight:500;color:rgba(0,0,0,0.6);line-height:32px;padding:0 12px;border-radius:16px;background-color:#e4e4e4;margin-bottom:5px;margin-right:5px}.chip:focus{outline:none;background-color:#26a69a;color:#fff}.chip>img{float:left;margin:0 8px 0 -12px;height:32px;width:32px;border-radius:50%}.chip .close{cursor:pointer;float:right;font-size:16px;line-height:32px;padding-left:8px}.chips{border:none;border-bottom:1px solid #9e9e9e;-webkit-box-shadow:none;box-shadow:none;margin:0 0 8px 0;min-height:45px;outline:none;-webkit-transition:all .3s;transition:all .3s}.chips.focus{border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}.chips:hover{cursor:text}.chips .input{background:none;border:0;color:rgba(0,0,0,0.6);display:inline-block;font-size:16px;height:3rem;line-height:32px;outline:0;margin:0;padding:0 !important;width:120px !important}.chips .input:focus{border:0 !important;-webkit-box-shadow:none !important;box-shadow:none !important}.chips .autocomplete-content{margin-top:0;margin-bottom:0}.prefix ~ .chips{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.chips:empty ~ label{font-size:0.8rem;-webkit-transform:translateY(-140%);transform:translateY(-140%)}.materialboxed{display:block;cursor:-webkit-zoom-in;cursor:zoom-in;position:relative;-webkit-transition:opacity .4s;transition:opacity .4s;-webkit-backface-visibility:hidden}.materialboxed:hover:not(.active){opacity:.8}.materialboxed.active{cursor:-webkit-zoom-out;cursor:zoom-out}#materialbox-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#292929;z-index:1000;will-change:opacity}.materialbox-caption{position:fixed;display:none;color:#fff;line-height:50px;bottom:0;left:0;width:100%;text-align:center;padding:0% 15%;height:50px;z-index:1000;-webkit-font-smoothing:antialiased}select:focus{outline:1px solid #c9f3ef}button:focus{outline:none;background-color:#2ab7a9}label{font-size:.8rem;color:#9e9e9e}::-webkit-input-placeholder{color:#d1d1d1}::-moz-placeholder{color:#d1d1d1}:-ms-input-placeholder{color:#d1d1d1}::-ms-input-placeholder{color:#d1d1d1}::placeholder{color:#d1d1d1}input:not([type]),input[type=text]:not(.browser-default),input[type=password]:not(.browser-default),input[type=email]:not(.browser-default),input[type=url]:not(.browser-default),input[type=time]:not(.browser-default),input[type=date]:not(.browser-default),input[type=datetime]:not(.browser-default),input[type=datetime-local]:not(.browser-default),input[type=tel]:not(.browser-default),input[type=number]:not(.browser-default),input[type=search]:not(.browser-default),textarea.materialize-textarea{background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;border-radius:0;outline:none;height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;-webkit-box-shadow:none;box-shadow:none;-webkit-box-sizing:content-box;box-sizing:content-box;-webkit-transition:border .3s, -webkit-box-shadow .3s;transition:border .3s, -webkit-box-shadow .3s;transition:box-shadow .3s, border .3s;transition:box-shadow .3s, border .3s, -webkit-box-shadow .3s}input:not([type]):disabled,input:not([type])[readonly="readonly"],input[type=text]:not(.browser-default):disabled,input[type=text]:not(.browser-default)[readonly="readonly"],input[type=password]:not(.browser-default):disabled,input[type=password]:not(.browser-default)[readonly="readonly"],input[type=email]:not(.browser-default):disabled,input[type=email]:not(.browser-default)[readonly="readonly"],input[type=url]:not(.browser-default):disabled,input[type=url]:not(.browser-default)[readonly="readonly"],input[type=time]:not(.browser-default):disabled,input[type=time]:not(.browser-default)[readonly="readonly"],input[type=date]:not(.browser-default):disabled,input[type=date]:not(.browser-default)[readonly="readonly"],input[type=datetime]:not(.browser-default):disabled,input[type=datetime]:not(.browser-default)[readonly="readonly"],input[type=datetime-local]:not(.browser-default):disabled,input[type=datetime-local]:not(.browser-default)[readonly="readonly"],input[type=tel]:not(.browser-default):disabled,input[type=tel]:not(.browser-default)[readonly="readonly"],input[type=number]:not(.browser-default):disabled,input[type=number]:not(.browser-default)[readonly="readonly"],input[type=search]:not(.browser-default):disabled,input[type=search]:not(.browser-default)[readonly="readonly"],textarea.materialize-textarea:disabled,textarea.materialize-textarea[readonly="readonly"]{color:rgba(0,0,0,0.42);border-bottom:1px dotted rgba(0,0,0,0.42)}input:not([type]):disabled+label,input:not([type])[readonly="readonly"]+label,input[type=text]:not(.browser-default):disabled+label,input[type=text]:not(.browser-default)[readonly="readonly"]+label,input[type=password]:not(.browser-default):disabled+label,input[type=password]:not(.browser-default)[readonly="readonly"]+label,input[type=email]:not(.browser-default):disabled+label,input[type=email]:not(.browser-default)[readonly="readonly"]+label,input[type=url]:not(.browser-default):disabled+label,input[type=url]:not(.browser-default)[readonly="readonly"]+label,input[type=time]:not(.browser-default):disabled+label,input[type=time]:not(.browser-default)[readonly="readonly"]+label,input[type=date]:not(.browser-default):disabled+label,input[type=date]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime]:not(.browser-default):disabled+label,input[type=datetime]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime-local]:not(.browser-default):disabled+label,input[type=datetime-local]:not(.browser-default)[readonly="readonly"]+label,input[type=tel]:not(.browser-default):disabled+label,input[type=tel]:not(.browser-default)[readonly="readonly"]+label,input[type=number]:not(.browser-default):disabled+label,input[type=number]:not(.browser-default)[readonly="readonly"]+label,input[type=search]:not(.browser-default):disabled+label,input[type=search]:not(.browser-default)[readonly="readonly"]+label,textarea.materialize-textarea:disabled+label,textarea.materialize-textarea[readonly="readonly"]+label{color:rgba(0,0,0,0.42)}input:not([type]):focus:not([readonly]),input[type=text]:not(.browser-default):focus:not([readonly]),input[type=password]:not(.browser-default):focus:not([readonly]),input[type=email]:not(.browser-default):focus:not([readonly]),input[type=url]:not(.browser-default):focus:not([readonly]),input[type=time]:not(.browser-default):focus:not([readonly]),input[type=date]:not(.browser-default):focus:not([readonly]),input[type=datetime]:not(.browser-default):focus:not([readonly]),input[type=datetime-local]:not(.browser-default):focus:not([readonly]),input[type=tel]:not(.browser-default):focus:not([readonly]),input[type=number]:not(.browser-default):focus:not([readonly]),input[type=search]:not(.browser-default):focus:not([readonly]),textarea.materialize-textarea:focus:not([readonly]){border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}input:not([type]):focus:not([readonly])+label,input[type=text]:not(.browser-default):focus:not([readonly])+label,input[type=password]:not(.browser-default):focus:not([readonly])+label,input[type=email]:not(.browser-default):focus:not([readonly])+label,input[type=url]:not(.browser-default):focus:not([readonly])+label,input[type=time]:not(.browser-default):focus:not([readonly])+label,input[type=date]:not(.browser-default):focus:not([readonly])+label,input[type=datetime]:not(.browser-default):focus:not([readonly])+label,input[type=datetime-local]:not(.browser-default):focus:not([readonly])+label,input[type=tel]:not(.browser-default):focus:not([readonly])+label,input[type=number]:not(.browser-default):focus:not([readonly])+label,input[type=search]:not(.browser-default):focus:not([readonly])+label,textarea.materialize-textarea:focus:not([readonly])+label{color:#26a69a}input:not([type]):focus.valid ~ label,input[type=text]:not(.browser-default):focus.valid ~ label,input[type=password]:not(.browser-default):focus.valid ~ label,input[type=email]:not(.browser-default):focus.valid ~ label,input[type=url]:not(.browser-default):focus.valid ~ label,input[type=time]:not(.browser-default):focus.valid ~ label,input[type=date]:not(.browser-default):focus.valid ~ label,input[type=datetime]:not(.browser-default):focus.valid ~ label,input[type=datetime-local]:not(.browser-default):focus.valid ~ label,input[type=tel]:not(.browser-default):focus.valid ~ label,input[type=number]:not(.browser-default):focus.valid ~ label,input[type=search]:not(.browser-default):focus.valid ~ label,textarea.materialize-textarea:focus.valid ~ label{color:#4CAF50}input:not([type]):focus.invalid ~ label,input[type=text]:not(.browser-default):focus.invalid ~ label,input[type=password]:not(.browser-default):focus.invalid ~ label,input[type=email]:not(.browser-default):focus.invalid ~ label,input[type=url]:not(.browser-default):focus.invalid ~ label,input[type=time]:not(.browser-default):focus.invalid ~ label,input[type=date]:not(.browser-default):focus.invalid ~ label,input[type=datetime]:not(.browser-default):focus.invalid ~ label,input[type=datetime-local]:not(.browser-default):focus.invalid ~ label,input[type=tel]:not(.browser-default):focus.invalid ~ label,input[type=number]:not(.browser-default):focus.invalid ~ label,input[type=search]:not(.browser-default):focus.invalid ~ label,textarea.materialize-textarea:focus.invalid ~ label{color:#F44336}input:not([type]).validate+label,input[type=text]:not(.browser-default).validate+label,input[type=password]:not(.browser-default).validate+label,input[type=email]:not(.browser-default).validate+label,input[type=url]:not(.browser-default).validate+label,input[type=time]:not(.browser-default).validate+label,input[type=date]:not(.browser-default).validate+label,input[type=datetime]:not(.browser-default).validate+label,input[type=datetime-local]:not(.browser-default).validate+label,input[type=tel]:not(.browser-default).validate+label,input[type=number]:not(.browser-default).validate+label,input[type=search]:not(.browser-default).validate+label,textarea.materialize-textarea.validate+label{width:100%}input.valid:not([type]),input.valid:not([type]):focus,input.valid[type=text]:not(.browser-default),input.valid[type=text]:not(.browser-default):focus,input.valid[type=password]:not(.browser-default),input.valid[type=password]:not(.browser-default):focus,input.valid[type=email]:not(.browser-default),input.valid[type=email]:not(.browser-default):focus,input.valid[type=url]:not(.browser-default),input.valid[type=url]:not(.browser-default):focus,input.valid[type=time]:not(.browser-default),input.valid[type=time]:not(.browser-default):focus,input.valid[type=date]:not(.browser-default),input.valid[type=date]:not(.browser-default):focus,input.valid[type=datetime]:not(.browser-default),input.valid[type=datetime]:not(.browser-default):focus,input.valid[type=datetime-local]:not(.browser-default),input.valid[type=datetime-local]:not(.browser-default):focus,input.valid[type=tel]:not(.browser-default),input.valid[type=tel]:not(.browser-default):focus,input.valid[type=number]:not(.browser-default),input.valid[type=number]:not(.browser-default):focus,input.valid[type=search]:not(.browser-default),input.valid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.valid,textarea.materialize-textarea.valid:focus,.select-wrapper.valid>input.select-dropdown{border-bottom:1px solid #4CAF50;-webkit-box-shadow:0 1px 0 0 #4CAF50;box-shadow:0 1px 0 0 #4CAF50}input.invalid:not([type]),input.invalid:not([type]):focus,input.invalid[type=text]:not(.browser-default),input.invalid[type=text]:not(.browser-default):focus,input.invalid[type=password]:not(.browser-default),input.invalid[type=password]:not(.browser-default):focus,input.invalid[type=email]:not(.browser-default),input.invalid[type=email]:not(.browser-default):focus,input.invalid[type=url]:not(.browser-default),input.invalid[type=url]:not(.browser-default):focus,input.invalid[type=time]:not(.browser-default),input.invalid[type=time]:not(.browser-default):focus,input.invalid[type=date]:not(.browser-default),input.invalid[type=date]:not(.browser-default):focus,input.invalid[type=datetime]:not(.browser-default),input.invalid[type=datetime]:not(.browser-default):focus,input.invalid[type=datetime-local]:not(.browser-default),input.invalid[type=datetime-local]:not(.browser-default):focus,input.invalid[type=tel]:not(.browser-default),input.invalid[type=tel]:not(.browser-default):focus,input.invalid[type=number]:not(.browser-default),input.invalid[type=number]:not(.browser-default):focus,input.invalid[type=search]:not(.browser-default),input.invalid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.invalid,textarea.materialize-textarea.invalid:focus,.select-wrapper.invalid>input.select-dropdown,.select-wrapper.invalid>input.select-dropdown:focus{border-bottom:1px solid #F44336;-webkit-box-shadow:0 1px 0 0 #F44336;box-shadow:0 1px 0 0 #F44336}input:not([type]).valid ~ .helper-text[data-success],input:not([type]):focus.valid ~ .helper-text[data-success],input:not([type]).invalid ~ .helper-text[data-error],input:not([type]):focus.invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default).valid ~ .helper-text[data-success],input[type=text]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=text]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default).valid ~ .helper-text[data-success],input[type=password]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=password]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default).valid ~ .helper-text[data-success],input[type=email]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=email]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default).valid ~ .helper-text[data-success],input[type=url]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=url]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default).valid ~ .helper-text[data-success],input[type=time]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=time]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default).valid ~ .helper-text[data-success],input[type=date]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=date]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default).valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default).valid ~ .helper-text[data-success],input[type=number]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=number]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default).valid ~ .helper-text[data-success],input[type=search]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=search]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default):focus.invalid ~ .helper-text[data-error],textarea.materialize-textarea.valid ~ .helper-text[data-success],textarea.materialize-textarea:focus.valid ~ .helper-text[data-success],textarea.materialize-textarea.invalid ~ .helper-text[data-error],textarea.materialize-textarea:focus.invalid ~ .helper-text[data-error],.select-wrapper.valid .helper-text[data-success],.select-wrapper.invalid ~ .helper-text[data-error]{color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}input:not([type]).valid ~ .helper-text:after,input:not([type]):focus.valid ~ .helper-text:after,input[type=text]:not(.browser-default).valid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=password]:not(.browser-default).valid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=email]:not(.browser-default).valid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=url]:not(.browser-default).valid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=time]:not(.browser-default).valid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=date]:not(.browser-default).valid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=tel]:not(.browser-default).valid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=number]:not(.browser-default).valid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=search]:not(.browser-default).valid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.valid ~ .helper-text:after,textarea.materialize-textarea.valid ~ .helper-text:after,textarea.materialize-textarea:focus.valid ~ .helper-text:after,.select-wrapper.valid ~ .helper-text:after{content:attr(data-success);color:#4CAF50}input:not([type]).invalid ~ .helper-text:after,input:not([type]):focus.invalid ~ .helper-text:after,input[type=text]:not(.browser-default).invalid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=password]:not(.browser-default).invalid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=email]:not(.browser-default).invalid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=url]:not(.browser-default).invalid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=time]:not(.browser-default).invalid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=date]:not(.browser-default).invalid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=tel]:not(.browser-default).invalid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=number]:not(.browser-default).invalid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=search]:not(.browser-default).invalid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.invalid ~ .helper-text:after,textarea.materialize-textarea.invalid ~ .helper-text:after,textarea.materialize-textarea:focus.invalid ~ .helper-text:after,.select-wrapper.invalid ~ .helper-text:after{content:attr(data-error);color:#F44336}input:not([type])+label:after,input[type=text]:not(.browser-default)+label:after,input[type=password]:not(.browser-default)+label:after,input[type=email]:not(.browser-default)+label:after,input[type=url]:not(.browser-default)+label:after,input[type=time]:not(.browser-default)+label:after,input[type=date]:not(.browser-default)+label:after,input[type=datetime]:not(.browser-default)+label:after,input[type=datetime-local]:not(.browser-default)+label:after,input[type=tel]:not(.browser-default)+label:after,input[type=number]:not(.browser-default)+label:after,input[type=search]:not(.browser-default)+label:after,textarea.materialize-textarea+label:after,.select-wrapper+label:after{display:block;content:"";position:absolute;top:100%;left:0;opacity:0;-webkit-transition:.2s opacity ease-out, .2s color ease-out;transition:.2s opacity ease-out, .2s color ease-out}.input-field{position:relative;margin-top:1rem;margin-bottom:1rem}.input-field.inline{display:inline-block;vertical-align:middle;margin-left:5px}.input-field.inline input,.input-field.inline .select-dropdown{margin-bottom:1rem}.input-field.col label{left:.75rem}.input-field.col .prefix ~ label,.input-field.col .prefix ~ .validate ~ label{width:calc(100% - 3rem - 1.5rem)}.input-field>label{color:#9e9e9e;position:absolute;top:0;left:0;font-size:1rem;cursor:text;-webkit-transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:transform .2s ease-out, color .2s ease-out;transition:transform .2s ease-out, color .2s ease-out, -webkit-transform .2s ease-out;-webkit-transform-origin:0% 100%;transform-origin:0% 100%;text-align:initial;-webkit-transform:translateY(12px);transform:translateY(12px)}.input-field>label:not(.label-icon).active{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field>input[type]:-webkit-autofill:not(.browser-default):not([type="search"])+label,.input-field>input[type=date]:not(.browser-default)+label,.input-field>input[type=time]:not(.browser-default)+label{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field .helper-text{position:relative;min-height:18px;display:block;font-size:12px;color:rgba(0,0,0,0.54)}.input-field .helper-text::after{opacity:1;position:absolute;top:0;left:0}.input-field .prefix{position:absolute;width:3rem;font-size:2rem;-webkit-transition:color .2s;transition:color .2s;top:.5rem}.input-field .prefix.active{color:#26a69a}.input-field .prefix ~ input,.input-field .prefix ~ textarea,.input-field .prefix ~ label,.input-field .prefix ~ .validate ~ label,.input-field .prefix ~ .helper-text,.input-field .prefix ~ .autocomplete-content{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.input-field .prefix ~ label{margin-left:3rem}@media only screen and (max-width: 992px){.input-field .prefix ~ input{width:86%;width:calc(100% - 3rem)}}@media only screen and (max-width: 600px){.input-field .prefix ~ input{width:80%;width:calc(100% - 3rem)}}.input-field input[type=search]{display:block;line-height:inherit;-webkit-transition:.3s background-color;transition:.3s background-color}.nav-wrapper .input-field input[type=search]{height:inherit;padding-left:4rem;width:calc(100% - 4rem);border:0;-webkit-box-shadow:none;box-shadow:none}.input-field input[type=search]:focus:not(.browser-default){background-color:#fff;border:0;-webkit-box-shadow:none;box-shadow:none;color:#444}.input-field input[type=search]:focus:not(.browser-default)+label i,.input-field input[type=search]:focus:not(.browser-default) ~ .mdi-navigation-close,.input-field input[type=search]:focus:not(.browser-default) ~ .material-icons{color:#444}.input-field input[type=search]+.label-icon{-webkit-transform:none;transform:none;left:1rem}.input-field input[type=search] ~ .mdi-navigation-close,.input-field input[type=search] ~ .material-icons{position:absolute;top:0;right:1rem;color:transparent;cursor:pointer;font-size:2rem;-webkit-transition:.3s color;transition:.3s color}textarea{width:100%;height:3rem;background-color:transparent}textarea.materialize-textarea{line-height:normal;overflow-y:hidden;padding:.8rem 0 .8rem 0;resize:none;min-height:3rem;-webkit-box-sizing:border-box;box-sizing:border-box}.hiddendiv{visibility:hidden;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;padding-top:1.2rem;position:absolute;top:0;z-index:-1}.autocomplete-content li .highlight{color:#444}.autocomplete-content li img{height:40px;width:40px;margin:5px 15px}.character-counter{min-height:18px}[type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;opacity:0;pointer-events:none}[type="radio"]:not(:checked)+span,[type="radio"]:checked+span{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-transition:.28s ease;transition:.28s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="radio"]+span:before,[type="radio"]+span:after{content:'';position:absolute;left:0;top:0;margin:4px;width:16px;height:16px;z-index:0;-webkit-transition:.28s ease;transition:.28s ease}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after,[type="radio"]:checked+span:before,[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border-radius:50%}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after{border:2px solid #5a5a5a}[type="radio"]:not(:checked)+span:after{-webkit-transform:scale(0);transform:scale(0)}[type="radio"]:checked+span:before{border:2px solid transparent}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border:2px solid #26a69a}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:after{background-color:#26a69a}[type="radio"]:checked+span:after{-webkit-transform:scale(1.02);transform:scale(1.02)}[type="radio"].with-gap:checked+span:after{-webkit-transform:scale(0.5);transform:scale(0.5)}[type="radio"].tabbed:focus+span:before{-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1)}[type="radio"].with-gap:disabled:checked+span:before{border:2px solid rgba(0,0,0,0.42)}[type="radio"].with-gap:disabled:checked+span:after{border:none;background-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before,[type="radio"]:disabled:checked+span:before{background-color:transparent;border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled+span{color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before{border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:checked+span:after{background-color:rgba(0,0,0,0.42);border-color:#949494}[type="checkbox"]:not(:checked),[type="checkbox"]:checked{position:absolute;opacity:0;pointer-events:none}[type="checkbox"]+span:not(.lever){position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="checkbox"]+span:not(.lever):before,[type="checkbox"]:not(.filled-in)+span:not(.lever):after{content:'';position:absolute;top:0;left:0;width:18px;height:18px;z-index:0;border:2px solid #5a5a5a;border-radius:1px;margin-top:3px;-webkit-transition:.2s;transition:.2s}[type="checkbox"]:not(.filled-in)+span:not(.lever):after{border:0;-webkit-transform:scale(0);transform:scale(0)}[type="checkbox"]:not(:checked):disabled+span:not(.lever):before{border:none;background-color:rgba(0,0,0,0.42)}[type="checkbox"].tabbed:focus+span:not(.lever):after{-webkit-transform:scale(1);transform:scale(1);border:0;border-radius:50%;-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1);background-color:rgba(0,0,0,0.1)}[type="checkbox"]:checked+span:not(.lever):before{top:-4px;left:-5px;width:12px;height:22px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #26a69a;border-bottom:2px solid #26a69a;-webkit-transform:rotate(40deg);transform:rotate(40deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:checked:disabled+span:before{border-right:2px solid rgba(0,0,0,0.42);border-bottom:2px solid rgba(0,0,0,0.42)}[type="checkbox"]:indeterminate+span:not(.lever):before{top:-11px;left:-12px;width:10px;height:22px;border-top:none;border-left:none;border-right:2px solid #26a69a;border-bottom:none;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:indeterminate:disabled+span:not(.lever):before{border-right:2px solid rgba(0,0,0,0.42);background-color:transparent}[type="checkbox"].filled-in+span:not(.lever):after{border-radius:2px}[type="checkbox"].filled-in+span:not(.lever):before,[type="checkbox"].filled-in+span:not(.lever):after{content:'';left:0;position:absolute;-webkit-transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;z-index:1}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):before{width:0;height:0;border:3px solid transparent;left:6px;top:10px;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):after{height:20px;width:20px;background-color:transparent;border:2px solid #5a5a5a;top:0px;z-index:0}[type="checkbox"].filled-in:checked+span:not(.lever):before{top:0;left:1px;width:8px;height:13px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #fff;border-bottom:2px solid #fff;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:checked+span:not(.lever):after{top:0;width:20px;height:20px;border:2px solid #26a69a;background-color:#26a69a;z-index:0}[type="checkbox"].filled-in.tabbed:focus+span:not(.lever):after{border-radius:2px;border-color:#5a5a5a;background-color:rgba(0,0,0,0.1)}[type="checkbox"].filled-in.tabbed:checked:focus+span:not(.lever):after{border-radius:2px;background-color:#26a69a;border-color:#26a69a}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):before{background-color:transparent;border:2px solid transparent}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):after{border-color:transparent;background-color:#949494}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):before{background-color:transparent}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):after{background-color:#949494;border-color:#949494}.switch,.switch *{-webkit-tap-highlight-color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.switch label{cursor:pointer}.switch label input[type=checkbox]{opacity:0;width:0;height:0}.switch label input[type=checkbox]:checked+.lever{background-color:#84c7c1}.switch label input[type=checkbox]:checked+.lever:before,.switch label input[type=checkbox]:checked+.lever:after{left:18px}.switch label input[type=checkbox]:checked+.lever:after{background-color:#26a69a}.switch label .lever{content:"";display:inline-block;position:relative;width:36px;height:14px;background-color:rgba(0,0,0,0.38);border-radius:15px;margin-right:10px;-webkit-transition:background 0.3s ease;transition:background 0.3s ease;vertical-align:middle;margin:0 16px}.switch label .lever:before,.switch label .lever:after{content:"";position:absolute;display:inline-block;width:20px;height:20px;border-radius:50%;left:0;top:-3px;-webkit-transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease}.switch label .lever:before{background-color:rgba(38,166,154,0.15)}.switch label .lever:after{background-color:#F1F1F1;-webkit-box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12);box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)}input[type=checkbox]:checked:not(:disabled) ~ .lever:active::before,input[type=checkbox]:checked:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(38,166,154,0.15)}input[type=checkbox]:not(:disabled) ~ .lever:active:before,input[type=checkbox]:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(0,0,0,0.08)}.switch input[type=checkbox][disabled]+.lever{cursor:default;background-color:rgba(0,0,0,0.12)}.switch label input[type=checkbox][disabled]+.lever:after,.switch label input[type=checkbox][disabled]:checked+.lever:after{background-color:#949494}select{display:none}select.browser-default{display:block}select{background-color:rgba(255,255,255,0.9);width:100%;padding:5px;border:1px solid #f2f2f2;border-radius:2px;height:3rem}.select-label{position:absolute}.select-wrapper{position:relative}.select-wrapper.valid+label,.select-wrapper.invalid+label{width:100%;pointer-events:none}.select-wrapper input.select-dropdown{position:relative;cursor:pointer;background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;outline:none;height:3rem;line-height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:1}.select-wrapper input.select-dropdown:focus{border-bottom:1px solid #26a69a}.select-wrapper .caret{position:absolute;right:0;top:0;bottom:0;margin:auto 0;z-index:0;fill:rgba(0,0,0,0.87)}.select-wrapper+label{position:absolute;top:-26px;font-size:.8rem}select:disabled{color:rgba(0,0,0,0.42)}.select-wrapper.disabled+label{color:rgba(0,0,0,0.42)}.select-wrapper.disabled .caret{fill:rgba(0,0,0,0.42)}.select-wrapper input.select-dropdown:disabled{color:rgba(0,0,0,0.42);cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.select-wrapper i{color:rgba(0,0,0,0.3)}.select-dropdown li.disabled,.select-dropdown li.disabled>span,.select-dropdown li.optgroup{color:rgba(0,0,0,0.3);background-color:transparent}body.keyboard-focused .select-dropdown.dropdown-content li:focus{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li:hover{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li.selected{background-color:rgba(0,0,0,0.03)}.prefix ~ .select-wrapper{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.prefix ~ label{margin-left:3rem}.select-dropdown li img{height:40px;width:40px;margin:5px 15px;float:right}.select-dropdown li.optgroup{border-top:1px solid #eee}.select-dropdown li.optgroup.selected>span{color:rgba(0,0,0,0.7)}.select-dropdown li.optgroup>span{color:rgba(0,0,0,0.4)}.select-dropdown li.optgroup ~ li.optgroup-option{padding-left:1rem}.file-field{position:relative}.file-field .file-path-wrapper{overflow:hidden;padding-left:10px}.file-field input.file-path{width:100%}.file-field .btn,.file-field .btn-large,.file-field .btn-small{float:left;height:3rem;line-height:3rem}.file-field span{cursor:pointer}.file-field input[type=file]{position:absolute;top:0;right:0;left:0;bottom:0;width:100%;margin:0;padding:0;font-size:20px;cursor:pointer;opacity:0;filter:alpha(opacity=0)}.file-field input[type=file]::-webkit-file-upload-button{display:none}.range-field{position:relative}input[type=range],input[type=range]+.thumb{cursor:pointer}input[type=range]{position:relative;background-color:transparent;border:none;outline:none;width:100%;margin:15px 0;padding:0}input[type=range]:focus{outline:none}input[type=range]+.thumb{position:absolute;top:10px;left:0;border:none;height:0;width:0;border-radius:50%;background-color:#26a69a;margin-left:7px;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}input[type=range]+.thumb .value{display:block;width:30px;text-align:center;color:#26a69a;font-size:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}input[type=range]+.thumb.active{border-radius:50% 50% 50% 0}input[type=range]+.thumb.active .value{color:#fff;margin-left:-1px;margin-top:8px;font-size:10px}input[type=range]{-webkit-appearance:none}input[type=range]::-webkit-slider-runnable-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-webkit-slider-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;-webkit-appearance:none;background-color:#26a69a;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;margin:-5px 0 0 0}.keyboard-focused input[type=range]:focus:not(.active)::-webkit-slider-thumb{-webkit-box-shadow:0 0 0 10px rgba(38,166,154,0.26);box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]{border:1px solid white}input[type=range]::-moz-range-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-moz-focus-inner{border:0}input[type=range]::-moz-range-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;margin-top:-5px}input[type=range]:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}.keyboard-focused input[type=range]:focus:not(.active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]::-ms-track{height:3px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#777}input[type=range]::-ms-fill-upper{background:#ddd}input[type=range]::-ms-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s}.keyboard-focused input[type=range]:focus:not(.active)::-ms-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}.table-of-contents.fixed{position:fixed}.table-of-contents li{padding:2px 0}.table-of-contents a{display:inline-block;font-weight:300;color:#757575;padding-left:16px;height:1.5rem;line-height:1.5rem;letter-spacing:.4;display:inline-block}.table-of-contents a:hover{color:#a8a8a8;padding-left:15px;border-left:1px solid #ee6e73}.table-of-contents a.active{font-weight:500;padding-left:14px;border-left:2px solid #ee6e73}.sidenav{position:fixed;width:300px;left:0;top:0;margin:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);height:100%;height:calc(100% + 60px);height:-moz-calc(100%);padding-bottom:60px;background-color:#fff;z-index:999;overflow-y:auto;will-change:transform;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.right-aligned{right:0;-webkit-transform:translateX(105%);transform:translateX(105%);left:auto;-webkit-transform:translateX(100%);transform:translateX(100%)}.sidenav .collapsible{margin:0}.sidenav li{float:none;line-height:48px}.sidenav li.active{background-color:rgba(0,0,0,0.05)}.sidenav li>a{color:rgba(0,0,0,0.87);display:block;font-size:14px;font-weight:500;height:48px;line-height:48px;padding:0 32px}.sidenav li>a:hover{background-color:rgba(0,0,0,0.05)}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-flat,.sidenav li>a.btn-floating{margin:10px 15px}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-floating{color:#fff}.sidenav li>a.btn-flat{color:#343434}.sidenav li>a.btn:hover,.sidenav li>a.btn-large:hover,.sidenav li>a.btn-small:hover,.sidenav li>a.btn-large:hover{background-color:#2bbbad}.sidenav li>a.btn-floating:hover{background-color:#26a69a}.sidenav li>a>i,.sidenav li>a>[class^="mdi-"],.sidenav li>a li>a>[class*="mdi-"],.sidenav li>a>i.material-icons{float:left;height:48px;line-height:48px;margin:0 32px 0 0;width:24px;color:rgba(0,0,0,0.54)}.sidenav .divider{margin:8px 0 0 0}.sidenav .subheader{cursor:initial;pointer-events:none;color:rgba(0,0,0,0.54);font-size:14px;font-weight:500;line-height:48px}.sidenav .subheader:hover{background-color:transparent}.sidenav .user-view{position:relative;padding:32px 32px 0;margin-bottom:8px}.sidenav .user-view>a{height:auto;padding:0}.sidenav .user-view>a:hover{background-color:transparent}.sidenav .user-view .background{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1}.sidenav .user-view .circle,.sidenav .user-view .name,.sidenav .user-view .email{display:block}.sidenav .user-view .circle{height:64px;width:64px}.sidenav .user-view .name,.sidenav .user-view .email{font-size:14px;line-height:24px}.sidenav .user-view .name{margin-top:16px;font-weight:500}.sidenav .user-view .email{padding-bottom:16px;font-weight:400}.drag-target{height:100%;width:10px;position:fixed;top:0;z-index:998}.drag-target.right-aligned{right:0}.sidenav.sidenav-fixed{left:0;-webkit-transform:translateX(0);transform:translateX(0);position:fixed}.sidenav.sidenav-fixed.right-aligned{right:0;left:auto}@media only screen and (max-width: 992px){.sidenav.sidenav-fixed{-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.sidenav-fixed.right-aligned{-webkit-transform:translateX(105%);transform:translateX(105%)}.sidenav>a{padding:0 16px}.sidenav .user-view{padding:16px 16px 0}}.sidenav .collapsible-body>ul:not(.collapsible)>li.active,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active{background-color:#ee6e73}.sidenav .collapsible-body>ul:not(.collapsible)>li.active a,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active a{color:#fff}.sidenav .collapsible-body{padding:0}.sidenav-overlay{position:fixed;top:0;left:0;right:0;opacity:0;height:120vh;background-color:rgba(0,0,0,0.5);z-index:997;display:none}.preloader-wrapper{display:inline-block;position:relative;width:50px;height:50px}.preloader-wrapper.small{width:36px;height:36px}.preloader-wrapper.big{width:64px;height:64px}.preloader-wrapper.active{-webkit-animation:container-rotate 1568ms linear infinite;animation:container-rotate 1568ms linear infinite}@-webkit-keyframes container-rotate{to{-webkit-transform:rotate(360deg)}}@keyframes container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-layer{position:absolute;width:100%;height:100%;opacity:0;border-color:#26a69a}.spinner-blue,.spinner-blue-only{border-color:#4285f4}.spinner-red,.spinner-red-only{border-color:#db4437}.spinner-yellow,.spinner-yellow-only{border-color:#f4b400}.spinner-green,.spinner-green-only{border-color:#0f9d58}.active .spinner-layer.spinner-blue{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-red{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-yellow{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-green{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer,.active .spinner-layer.spinner-blue-only,.active .spinner-layer.spinner-red-only,.active .spinner-layer.spinner-yellow-only,.active .spinner-layer.spinner-green-only{opacity:1;-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg)}}@keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@-webkit-keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@-webkit-keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@-webkit-keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}@keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}.gap-patch{position:absolute;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.gap-patch .circle{width:1000%;left:-450%}.circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.circle-clipper .circle{width:200%;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent !important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0}.circle-clipper.left .circle{left:0;border-right-color:transparent !important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.circle-clipper.right .circle{left:-100%;border-left-color:transparent !important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.active .circle-clipper.left .circle{-webkit-animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .circle-clipper.right .circle{-webkit-animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes left-spin{from{-webkit-transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg)}}@keyframes left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes right-spin{from{-webkit-transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg)}}@keyframes right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}#spinnerContainer.cooldown{-webkit-animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1)}@-webkit-keyframes fade-out{from{opacity:1}to{opacity:0}}@keyframes fade-out{from{opacity:1}to{opacity:0}}.slider{position:relative;height:400px;width:100%}.slider.fullscreen{height:100%;width:100%;position:absolute;top:0;left:0;right:0;bottom:0}.slider.fullscreen ul.slides{height:100%}.slider.fullscreen ul.indicators{z-index:2;bottom:30px}.slider .slides{background-color:#9e9e9e;margin:0;height:400px}.slider .slides li{opacity:0;position:absolute;top:0;left:0;z-index:1;width:100%;height:inherit;overflow:hidden}.slider .slides li img{height:100%;width:100%;background-size:cover;background-position:center}.slider .slides li .caption{color:#fff;position:absolute;top:15%;left:15%;width:70%;opacity:0}.slider .slides li .caption p{color:#e0e0e0}.slider .slides li.active{z-index:2}.slider .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.slider .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:16px;width:16px;margin:0 12px;background-color:#e0e0e0;-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.slider .indicators .indicator-item.active{background-color:#4CAF50}.carousel{overflow:hidden;position:relative;width:100%;height:400px;-webkit-perspective:500px;perspective:500px;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;-webkit-transform-origin:0% 50%;transform-origin:0% 50%}.carousel.carousel-slider{top:0;left:0}.carousel.carousel-slider .carousel-fixed-item{position:absolute;left:0;right:0;bottom:20px;z-index:1}.carousel.carousel-slider .carousel-fixed-item.with-indicators{bottom:68px}.carousel.carousel-slider .carousel-item{width:100%;height:100%;min-height:400px;position:absolute;top:0;left:0}.carousel.carousel-slider .carousel-item h2{font-size:24px;font-weight:500;line-height:32px}.carousel.carousel-slider .carousel-item p{font-size:15px}.carousel .carousel-item{visibility:hidden;width:200px;height:200px;position:absolute;top:0;left:0}.carousel .carousel-item>img{width:100%}.carousel .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.carousel .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:8px;width:8px;margin:24px 4px;background-color:rgba(255,255,255,0.5);-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.carousel .indicators .indicator-item.active{background-color:#fff}.carousel.scrolling .carousel-item .materialboxed,.carousel .carousel-item:not(.active) .materialboxed{pointer-events:none}.tap-target-wrapper{width:800px;height:800px;position:fixed;z-index:1000;visibility:hidden;-webkit-transition:visibility 0s .3s;transition:visibility 0s .3s}.tap-target-wrapper.open{visibility:visible;-webkit-transition:visibility 0s;transition:visibility 0s}.tap-target-wrapper.open .tap-target{-webkit-transform:scale(1);transform:scale(1);opacity:.95;-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-wrapper.open .tap-target-wave::before{-webkit-transform:scale(1);transform:scale(1)}.tap-target-wrapper.open .tap-target-wave::after{visibility:visible;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;-webkit-transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s 1s;transition:opacity .3s, transform .3s, visibility 0s 1s, -webkit-transform .3s}.tap-target{position:absolute;font-size:1rem;border-radius:50%;background-color:#ee6e73;-webkit-box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);width:100%;height:100%;opacity:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-content{position:relative;display:table-cell}.tap-target-wave{position:absolute;border-radius:50%;z-index:10001}.tap-target-wave::before,.tap-target-wave::after{content:'';display:block;position:absolute;width:100%;height:100%;border-radius:50%;background-color:#ffffff}.tap-target-wave::before{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s, -webkit-transform .3s}.tap-target-wave::after{visibility:hidden;-webkit-transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s;transition:opacity .3s, transform .3s, visibility 0s, -webkit-transform .3s;z-index:-1}.tap-target-origin{top:50%;left:50%;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);z-index:10002;position:absolute !important}.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small),.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small):hover{background:none}@media only screen and (max-width: 600px){.tap-target,.tap-target-wrapper{width:600px;height:600px}}.pulse{overflow:visible;position:relative}.pulse::before{content:'';display:block;position:absolute;width:100%;height:100%;top:0;left:0;background-color:inherit;border-radius:inherit;-webkit-transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, transform .3s;transition:opacity .3s, transform .3s, -webkit-transform .3s;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;z-index:-1}@-webkit-keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}@keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}.datepicker-modal{max-width:325px;min-width:300px;max-height:none}.datepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.datepicker-controls{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;width:280px;margin:0 auto}.datepicker-controls .selects-container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.datepicker-controls .select-wrapper input{border-bottom:none;text-align:center;margin:0}.datepicker-controls .select-wrapper input:focus{border-bottom:none}.datepicker-controls .select-wrapper .caret{display:none}.datepicker-controls .select-year input{width:50px}.datepicker-controls .select-month input{width:70px}.month-prev,.month-next{margin-top:4px;cursor:pointer;background-color:transparent;border:none}.datepicker-date-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;color:#fff;padding:20px 22px;font-weight:500}.datepicker-date-display .year-text{display:block;font-size:1.5rem;line-height:25px;color:rgba(255,255,255,0.7)}.datepicker-date-display .date-text{display:block;font-size:2.8rem;line-height:47px;font-weight:500}.datepicker-calendar-container{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.datepicker-table{width:280px;font-size:1rem;margin:0 auto}.datepicker-table thead{border-bottom:none}.datepicker-table th{padding:10px 5px;text-align:center}.datepicker-table tr{border:none}.datepicker-table abbr{text-decoration:none;color:#999}.datepicker-table td{border-radius:50%;padding:0}.datepicker-table td.is-today{color:#26a69a}.datepicker-table td.is-selected{background-color:#26a69a;color:#fff}.datepicker-table td.is-outside-current-month,.datepicker-table td.is-disabled{color:rgba(0,0,0,0.3);pointer-events:none}.datepicker-day-button{background-color:transparent;border:none;line-height:38px;display:block;width:100%;border-radius:50%;padding:0 5px;cursor:pointer;color:inherit}.datepicker-day-button:focus{background-color:rgba(43,161,150,0.25)}.datepicker-footer{width:280px;margin:0 auto;padding-bottom:5px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.datepicker-cancel,.datepicker-clear,.datepicker-today,.datepicker-done{color:#26a69a;padding:0 1rem}.datepicker-clear{color:#F44336}@media only screen and (min-width: 601px){.datepicker-modal{max-width:625px}.datepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.datepicker-date-display{-webkit-box-flex:0;-webkit-flex:0 1 270px;-ms-flex:0 1 270px;flex:0 1 270px}.datepicker-controls,.datepicker-table,.datepicker-footer{width:320px}.datepicker-day-button{line-height:44px}}.timepicker-modal{max-width:325px;max-height:none}.timepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.text-primary{color:#fff}.timepicker-digital-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;padding:10px;font-weight:300}.timepicker-text-container{font-size:4rem;font-weight:bold;text-align:center;color:rgba(255,255,255,0.6);font-weight:400;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-span-hours,.timepicker-span-minutes,.timepicker-span-am-pm div{cursor:pointer}.timepicker-span-hours{margin-right:3px}.timepicker-span-minutes{margin-left:3px}.timepicker-display-am-pm{font-size:1.3rem;position:absolute;right:1rem;bottom:1rem;font-weight:400}.timepicker-analog-display{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.timepicker-plate{background-color:#eee;border-radius:50%;width:270px;height:270px;overflow:visible;position:relative;margin:auto;margin-top:25px;margin-bottom:5px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-canvas,.timepicker-dial{position:absolute;left:0;right:0;top:0;bottom:0}.timepicker-minutes{visibility:hidden}.timepicker-tick{border-radius:50%;color:rgba(0,0,0,0.87);line-height:40px;text-align:center;width:40px;height:40px;position:absolute;cursor:pointer;font-size:15px}.timepicker-tick.active,.timepicker-tick:hover{background-color:rgba(38,166,154,0.25)}.timepicker-dial{-webkit-transition:opacity 350ms, -webkit-transform 350ms;transition:opacity 350ms, -webkit-transform 350ms;transition:transform 350ms, opacity 350ms;transition:transform 350ms, opacity 350ms, -webkit-transform 350ms}.timepicker-dial-out{opacity:0}.timepicker-dial-out.timepicker-hours{-webkit-transform:scale(1.1, 1.1);transform:scale(1.1, 1.1)}.timepicker-dial-out.timepicker-minutes{-webkit-transform:scale(0.8, 0.8);transform:scale(0.8, 0.8)}.timepicker-canvas{-webkit-transition:opacity 175ms;transition:opacity 175ms}.timepicker-canvas line{stroke:#26a69a;stroke-width:4;stroke-linecap:round}.timepicker-canvas-out{opacity:0.25}.timepicker-canvas-bearing{stroke:none;fill:#26a69a}.timepicker-canvas-bg{stroke:none;fill:#26a69a}.timepicker-footer{margin:0 auto;padding:5px 1rem;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.timepicker-clear{color:#F44336}.timepicker-close{color:#26a69a}.timepicker-clear,.timepicker-close{padding:0 20px}@media only screen and (min-width: 601px){.timepicker-modal{max-width:600px}.timepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.timepicker-text-container{top:32%}.timepicker-display-am-pm{position:relative;right:auto;bottom:auto;text-align:center;margin-top:1.2rem}} diff --git a/esphome/dashboard/static/fonts/material-icons/LICENSE b/esphome/dashboard/static/fonts/material-icons/LICENSE deleted file mode 100644 index 7a4a3ea242..0000000000 --- a/esphome/dashboard/static/fonts/material-icons/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/esphome/dashboard/static/fonts/material-icons/MaterialIcons-Regular.woff b/esphome/dashboard/static/fonts/material-icons/MaterialIcons-Regular.woff deleted file mode 100644 index b648a3eea2d16b6ce783906d6b7d5f251b9eb56c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57620 zcmY&^NelVwr$(CZQHhO+t!`$=Dp;-onGnG%1YJl`q9)OmoxnxQ~!cx z7yTwvL_vxFmrDfzAms%BFq1u;FO!o|pk)96AY1*_{QHG2qyvG0ft8*u0022U001yH z001b^-7WpDiJrqRN5%B30sjv_KLEfcmTtzs92WpU*)#y4J?2lST9B!co*@9hGW4&8 z`4=pp>u1uYzvM6XUw$aRAo>Fc^vBf7(e;Ws_PPwU|4;c6vAY`D4U;s#9fGPn0SECQP7GZX@2I3WUo4pB*5bE|8|@Fm_rEMeislDJkxA(b z7tCUlVW`i$#DWbQZsJMnX?Wci4^U?JYSLP9^{854ZTD(mZmHb5Kg#0WKDy&x2*LAw zTo>W>_}n7h_S_HghvODJCnAQCPwY%2)^GlIWGK?6;jNOlF0WOptuo*kv8|j_g}1_c zE+(DP(B{zS(DhLNP{BA|<)Y%`;w0l_Q6WO2EZKL|*ys_L#EFFrpqv(C%GE%Zc>Y>~HgyL!|@;oHhHQP}pO{tpwUsv%B#6 zd!u<`WFA2+30r%fO!U*(zhn@xA;rJNv7)dPqcC&`Gkpup)6p#8t-&S%`VH#+Vw47 z1ZrYVoekY6m!+MmkfSl@=(83Jh>RM=6@_BZ@#m2@gjSQDm~M#;i*tlcAUFkg;=PQs zMJnWEk_2tyBE8hNCL`jfI6N%DY2a%&bpE?0I6k{55d>M94FoUL_axD8r2MZ;xv-@Hvaw zq9i|4u;P4|nOd?89&S@e7$fg9w5ik7{;s1p<$%{Px^pXA)ZiJ*T_`9A%ZsrKN$)%D ztOb7M#2uWj)1nwnb0-iLgR~WM*q`jEA@w~(cU<3;TcGz6UD5z$GW#O`20df8;pRVY zzoC4zzo)g|0FvRy)=K0+BCPi)KabsDwpTdF%AsoFeo@XLYf`R3tW(N(V4APa8VTqO zYaFp!PT=^&)H+bv3U5T*5vk{AeXej$R;Oewpd^)uVn0)o;zmt7lRTM9REl*{mONZN z<|S<4WFKxe0$E{t$xn2nCGWG0$W{E${W(Sw*BQ{1U**^A&8 zI$rVs&Q8tZEFBp*nancPz{--(mmK4uN7@+{1uq?=-Qk{v}Ai(*JQ<Qb) ziI9oKiR_8ziS&uliH3S=!6yBgeC6Harr>SJm)-bB1PpopT0sz{MF16qoR^V~HVCLue&LVU6e$yTtP$;v!eHTHBEyb|!?`@o*sevdTrHJeop zwT0oAcEND0l*idnVa$A8P(K0ZVSeX`ivqs>8G5=X`&lYF5ee)Be(wuIckU$q*}<;@ z4r2#7nhUhaoUJcj*VC0s$-JYm=`HaJpLeRxTzn;J_aSv6KyL2}I@N-Vcnp-x5iQOX zh|qORY8E5lSTmQTC|@~e(_QfIL@S-9IHiq1PS)wZ*$t!IY(~`< z@a6PU3WzmFyeT?es(00UuAHM@*;!`}3SHx%=v)j#UpfM9*n2$NSKt9wR?y-h;`3^0 zlYNOTiCjHHknv2F8#vP^LJ`;lRH+t>(JB&-@R!sXn&Y*hje6bmXmdd%}w>*#3>A))z4~D%XF*+~}&sYg%I=ANO zz+0?E;B}3LCnPO}qgGQ!*}YM8HpXcy0t)~RdNRI{N?XQk$esPOG6h--f1AR(K2Yziif%z`E-CQd|Vjt8W*X++>o7Rd;B-rq6B<{d^Zlfz}sJqYrNd!pa_ zv~xQf91*{23mLP% z=BlE92usq)WUw6&Ro)nNR3PVL#>GlTLTK{`kJK^8KKJLHq&ZVA4;v&*36q<~QinCH z8E8{4&WTw=(-taC8{*&Y)m>{mW;<|X=qQp<-?&t`l^B*7m*i@fXMII|Q+)w_3;ssi z%qnt_Hr$~Zm1?=m@E-RRyV`{IWmoBEdvGCKTzT8TS91N#R<1Np$x??E36qMGdv<18 z-6C$)sM&E&c*s)~p)A_WQ4HKo+H)oAY8H!rC62qL1M);9P+;YW0|eykR*VC;U+M$b ztVo>Ecpx6C5U+sWXwHg;;i@n-q2H3Oeh+`um{bho(vHgJ^=3xK-bvtgD!Q+M%U>PP zQpY9F=}<8`)-ouvWJa~Y#!7b;#NGKhR^V@_k;Io-OE|z-BG$LdgV;o>~$$`2S05D;l@z?Bzz6w^+;vkT0VL`Ae&SJ zB7L8(p|q!#^NJ=dXA143B}42VU%KTfd%-Y_rKfmqA9`_DiO*O)Ij*dIQDvIVs0itZ>oVwYF~0%fjhehYKuIl;r$d0Z{9rb$9%=i zll)UXq1#cW|ECVFNqkfDd4YUbD+D05 zKJhAu2Ew|aPfc~ZCwAyQQIaVTo!aw5f0++2`+ zfh+wx1C4~2ezj|#t5caIHkncw<$=cm+JOvG0#m%$7+%6#0!l(uf>y#n0%Jl&f=7Z$ zLQ4YeM6o70Tq0?r$v#Hbi&S>oK*JS54wtBrT`Vs1WpP4tXE5gz9&el z<)-MSY1?K(>7M;TV#DV1BQd6`oqLQz>u%LYpC1Rvxm6ceTY_XuJ75~{Ri=3s%%yL4 z6#hikAX3@&grZH&61yjBtJqUC;@0^)_q%a0ZOcqWj3q!fZc&6{W!}EwL@8JOWf7;1 zoQZNbbVuXgqUc6R3poRBwF2_1*5G{UT9_g>pDmxZ=^WXsVIr-I@^#YnJ7jA-{r=6I&hH zN#!;#6L&mW<`MItoSS0tjqbmAvUogwxJflVDmDxZ*!0wKp7%)JmTY3p!_` zuHK_rDjtS~%J(<3mhcsP630pGaY|{xrTNUfkyAR2e)g|4d9Cps5uy_j7CP@6?Ks@& zD@oo9BS^C+ub8IcqJ0ttGfTxPO*MC3*);KI7SZWza^_vsPrlMgp+5&xU}>sG!wO{^ zR|1U!mknKuS7M8-wzvmTE^0?UT`PZ#$+IFUc4!P(5pCp z7b^|QjLrMQ$J5ibz-r3ga%PbOV#S%pE>P3v!h1SancBz>cSRYh9a=?~s;+s)!5DC* zhs}NNBxPb9{(sAtkPxmn)jm0+ne-N z2lo(C_W<2mr`PV|o*5!yugWoq57fBC^<~`xOZF1oV+Rm#!ZGsuSX|=0F%UyrA$%G| zty?ztS=*)7-2(-Vb5h7{7p#o(s;ls{VtRUJRB1_!?*J5fg}XrBY(FT1<1q@kF3-Y^ zhnto$jkY<0=g>?wnXk=`bXj66^8t?xUgLvG)2^uBq_m?G_vxMFH=`a4q-<@Kqbmp| zB>9l;CEI=+e-Y0nbj@oJ-|5m&y!eb})kCwC1|#U3#rTIz7s+a~y&WitVNrTy^J0QP zwIFd`$;0bb+`Qs*0EC3WQS1V8ibwY_8okmt%#-<84>$><$U7m0&Sf-WAIODLRZMEX z6z4JIJ>naiAf+1$V0b5GQ)-z#?pw6t_le&)} zV-DC~dpZj<`;$9K@y1FXhCI1<#^4?rl&@3QgD*^iA64x0!*B$+-7#UBWae z8y+5zDNDMW@1WS~!l&nI3&`zv23(b{R@kq!TJ?G{OPeS2z68QOa^h?zb6Fm#g5F+o z)565l!C0(>i90JJxK{xo!7Z9YB%l;G^8e{zs}KkH=E%>ead@Px{N;^xTF(Aih(%-(+? zaga~hD5!tGa;2Ed?Y7$VXPHjdNo>w;!jS;vL-J0eGAf_jEREX|t+DS-aJAM>a5*}7 znxOS_w%Y_v2!zBtliWNgr))mBt4GFNwi!;Gh3WME*}6}k3xFV`x< zLD6p(sai1gKU<~W5+)pyia28fSaQrTgkHOh4BzM%63Nh#v#v?$&}`kf48&L3fT`n} zq#E?+Nb_Xm?Xz(|{OZrxw>rH#%R1G<7`Fc2_ev)>5@uLnxCqhCGGIhAxt`=o za^rrmYEHK@DluA_x=!V0@^BC3fAe}SyPQ~?ad?~UXb`nlw!Yfj+{|txbSMd7OU!U^ z31UYoXj2)e46Auaq&@O5RqM+HH=mYQ{FHa^371(K-{zS5*J4HcUZbAtFDM_a62_-6 zhtjg78Cbj7yhMLTeqNnor!6X?j?v`G^whuBA<@G&WVQfbwss6WNV-0pTo@PYS(Z53 zCa2LF9}m@0K*EJ7gjNp06~1p~Dy68fV_%EYSZFn8Gv{>>FAAwXWTt18!lvP?EY%Dj zJ{}%)BNQKEpm@w2jH8EjF{LIST~-emATQdZTNhm$@1yqG(mxH9+IGf>Oayn;ho zgr3_1dOlpex`UYIRWQ*kUV$b(>T*L78OOW=L{D2zt8r#2)vTRS+NJPn4!cD2l=Qm> zCDT3vdEa6wLRLjfiTICBfIoE$nOu4he>^|toeqZ@MbCguI=8ItwBIdT)m|eG?Oi6W z`WU%V4M`Q~4ttQ(q8WLKZu z)AEbW>s2UiCgjd}(H4BydS_(kb;>oqjG*>GE|Maax~k(xvc8e}G4&zh&cjs3^pD#^ z@PkjZ^}lIv7cOrzZHM!QMzVVPn}?c1-aE(K4e)59b(9Ah2J^b*sf$s;f?FSaq%4I8 z3a%*hEijojCk&wi*oT_EGG22(GR*KWRjiK#{>^|Cm^6fj&b4K1D;idpG`RPFgi!&PcXzh}kwqAiwc$otwH-YVRm!q#YQJ%P&Lnt={ZWph5NFkx&SH>mQ z9R0T#;KyrtihYj6#PX~5KB7cR z=?sG$Sp{=PnlU!0s;KO#GxD8*}K%1W8<)k#|ooe|xCu5dRvXaU1MaI1r2So1D)!R|?Qa!}` zxlhNyu~9KGrfH1xF|+c>b%|O~;B%B!EPI|KN`=_4Qc1Yp1==k*xOyE&NUkN5mlY&V zzh$6;NIedWNI<4KD%EZtUn4p+(tYL5Kw7C7wed;|XI9emiYee@onsC2S%OA}siLnl z!S+<^Lf(0UMLl|=aC01W2;u=7WzJ>{ zCOnJCQjx|}GGWCScuq%(aeLgQ0<^m-b0x;3!Lpct?iI=ul-&Z|^fH?u+=054X>(WL zn>NGRNDmPHi=JT2!JkQy?1(1tP+uS`hCK5cv-^~R!vpy>lmEo-_Vuz76Pagjpc2=O z8S)vwxs()yw7TDz!{?|Dp;-&H5|;V?vO8#9Mcg_)`w?WlyUHCt9hN)hQxnLf=!?t< zE6X8qqtoFLWT?@4biJW>>KM-xl#~fL_k$Z$Q*^lA4g^YIGxaqaaP{?Q2aeO>(NjxFMOT>DrUj#tD|h-~DZ z+t(`cessRx)1Ncd?Y_c+#?C6f3c5ebY$1a!M_9Mxg6KNWaP;(PFG1zj?ea>=6H#A% zFd%fbE;F_1gl@k&tzMy(jZ(brs$XX}RmE7N_rRqzwf3;!xiT)Wm_%T1r=bt2Dbym9 zDkv@Hu6sKC06mUy>~J#@xR+c!LN+T@Ipx(Zh?Bx1*1&br5(;UX!y7!eZOmBYuvi_4 zF1nMcm?9z~krDCw_86JSPu>L|B5tq9rEZc^P_81~)Cze+Y+^AlYG9dB`W$e*2&=PS zdcWqCi6MNFa;yNWi9V9Ml9b2}G&kWnF_OKStk{z*H<%VY{{6boH(=8aCKLAm5gN*t zeu5{QWszDudu;9I2BP`!bZYO}%78#G&XA3M5hBZsU2TOta=alk=9kIC-U%ev>2H`G zwQAymG3vN3mLIz&l95`39l1cts_>&+Xb?X|T_F?aXBtD7DJ@;Tk+V+WEVo*k9bz@# z37+M5pP;60!T5spyVwhD2y$Zp;yl2OKub{etR6o}-ujDm#Pl(Wj_Q^%>Bss(C|aZN zw3!88I9;>;cFcK2df{w^$}td)k#l?(&dU3{XD8=5CPU2DxX@V`E3NNYYb#}EVJ~x@ z5%F0$6Hk=+Og3eL2M0XWQik1p^l}Q(_CHg06Bisv6n-YagwuLAE)BW&(~ zY8&0+G6Yx>fbN)UsVrPj7#AY2KhbRCo>7vGCXS2@b3AkIqk^e;nS@q`S&wWC?ZG76 za5BaVGco-O%-aAm#v6jtTvZ$Us+wURw`iH9r|-CXvcZlnDsbGcc zng6y^2tPHL_U$;kT_0(ghBIq8SGr^!hA-t~lnGd4ZR8zqWIYaN-d%=+kjtZ=gqku~ z{}H2TAxs9m!+!^fhaiBy84nqU;usmE9y}HW{8mwh4Fac^pji`U zeV7w>w55Iy9zV;rii7Xt!lbCS_IW>sXasYt)Z~YpA(fIcAIZMBHbnOIOTca63;grI zhq0SOY1>+-q?3B~b4i6+BDc2x$$gn8TF=Fkt3&5j7gU!>Kii|M@z7*;p4OM_@s}lG zB)3flH@%0&bJ1)*F66<~#<4WG14QyR84(F>t zJKwUP&Pz!#tg`QyL{BW zq&#q%U5FDtB7@T!?hqtgrN+X*skIAOv;b=zZBB-ER?C=Y+FCc$9q3kuEqD zyIEA-9LCD+IH1UYh}kwjYYs2HlzEG!6@F2rlGiKC|oLYe}fe zMNTJ;f{1#%58fpE1)P?&3(K7oMNPk%V$IYxgjyJXu-ppe86kDvmI2{o^ zEMV15dI-8`$+R`4U)P4($zoo{F4nC~b#OLQTC_sygyfj>?l!QleK$e;S!t1%o*pCm=VN~xwzT+le6Qq|bE&So zAnwtuG&1RkMDZIpDfRkHp;s@sqvGRYoB8iS8WqLEw$ag{l&qbKnH(O!3Wv({tZx(9 zrVG-Fh}u!&`2mB;R|cyvJM*)x;n=-!**cN9;ew-;rIoC(ay~fUia@`{U-Sr(Nxic6 zV4+!?uwHc#lnM|i?eH8~?ehpzOPxQ~^F!dn>jtnR*b@u`>)?i+dT9yg511ZXTEk_9 z4;OQX%m{^K1@_@IiEYsN>B0wl{fq0=P2>^sk}{+`-U#B(f+NcLDzb>uk_Q;oB4*q5 z1eXenJkr(JGeUp^6c$xV;wJ^ZfKBLwHTVp+oXD4D4RJu;*dSYZ?)zFP0)>jFI5ns; z`MbmMhaJ4&%i9DLOBwcR`xZ)8YlT&Eu?m#)tLu7|MMfTQffpqmvaz%=Y`E1ZO^%rf zB^|h)Yc6*YtO0R>N_*kNd54@5&QbqB`3$ zGxc6r%uWtB(G2a(H|=GJbi%E8e)UQG2OHe4oej(3FH{(QNe$gC#%85G^mpwV2{cP+ zWYoo??vPGz|NdOn#EZND+(h6v;igqoGHaFCcrOr>ot@3Mb}a!vi_BdWF}Z>YMev9U zdQFK-yTw$t1(V!_`xhBV_7KX6&dcoRv;lRCYQ?R*BMJiOkn1xm-CL>k90M(qla^>L z7u)BGp}ZzDI#zoEd^%Iy^W1JYEW5HEUUeEBDK59j?{Ai96-ITV6O&f@dg?dhrrJb_ zTLx0aWXe*63u#&Z*o<#=K-e>24OJ^3v<;@J{kGa-BI+k6_eO^snJVy+#?&bOB0Uva z9dt5nD|p`QbJK~8x!L52ZS*Ce0xJfQW@?;tRjzo!(FMyMW%b7I*fN3lC#Ubhqk!i zBY@}MCB;}M@2vF-Gbzjo@+>|td`#wFyuaZ`g+8nDD(5;Klt#;MxCbvCbRvj9Tjam2 zv*QNjKO<;Sm&Zv}doO!Y0diJcN(7VF$6@=f3p2mgmLp`=R1lNf5{9+09AGiB3xu z9U0v^z3hM7sJ^cA4#(nPq^z-3iW+7qAcJi{dw-%NMFosfx`@mT3=|0pEASo#k9K%S zs^G`yjm+Hfj+%+#otuh9U%s!RnH)HC1-QVZ;WqfD=`AyFWB^Zv9rHVMy%o6iN2aGt zbsQ`3@O2m6)J%SKDV-;)5IupQM`&6Imt+kvqQt~`(=Q^+Ha{P~u2SZnhT4k!EszM~ zy!Rmt6>-*?KinXOMO>r!dX`=j(ML);EE`t2RWKb=a}R+b)yBKq+eo7bDg)FJu2@Hd z)_C->k4dsxo^d_r(^h9b!bKN^(jh$2Me2wZAij(4l^ErF6_uF<8inX$N*KfrkZk1P zLC7}t*nyNWX=O*><2XZwFQ>bGC1P3x&A{h8HTGUYx_PbZMD9YiN(xmKlUbq)euF;T z!sNkeD-|>ry^R$@joo5C9RP`ou0mKW^eC!Z|~_q>TqxGE^JW` zgD68I9UUEgEdygOKmmNLuHHW&7--O+A4b14Nm*vmdPwMXfIvmiFIT|9Dd1Qt737dR zM%9guE0d{fMrRlOUke^q&}wr6zifDpRYpq(Sc?Ig|1=ubkW0Du(+?`6ilBHbKWGwx zm;_>CVb5MmqTydv!}7Y~-E1#`B9b+mQ74*cwvn_vVe~i6UTeT(&FO83$w?ZG~rF^Q=s^Y5r zZA6^(srpvF$0Oi7!B?<0wwNO3lF-2R4rjEG;UC(Z+`ts6B^elHE%U~6rI6B8xp-X{%|#>F;Up=Z|NP=H>|JzW4F>e)sM6)%MxX{!K$` zCRTLHsG?zPgXFvTJ72pVyBxb3yBNC`yA(T<52yIpDyOB`Ld56^{Xgw-{dT++eGsjP zO$6e-J4SRHfTF?7b0OD;A9=jo!8no7+|gJ4qU|X-QP%F9&1hhA9rYo*K<{kN%#wvQ z#-s+2UX+}`jAt8bYoiM;;jbOL*zZcu)?EK;^zgt8kv_1EXEWB?duZ1~f>V>$n+Cm2(X^CTUf`&zZu6m_X*tPSIlDwKta>5jV!(K-cNO-mK( z8L~#4y{Xms^Vm^In@bvwObEyw_9ZGvdOBu_Vt#gH39Np)bcy~ri?!-y3xHD#wnxxD zs_oAzD1UURp(=SZMuQR-$m1uKpV*y3ErRm}zu~L*s6cS@qHpt#Qx?;MG7BYySOmYf zS{S+umlE5fNuedLuB-JMrg)>hP1)ippzz47LK4;d~#PEl@t4jljp z0HBEy)ck8t1^o5p0=WWSx`ViGs5akrg;NjF58;zHBPHll#>KbSQBw+(iJv*jXJWY7 z{?G!SSzjD&O;b4uPfT9WFpf+_?%d$v(gZxDwrLwX?zE}cQ*oXdc+Z4Y7gkg_Omn~7 zqUg*1`TJ;YnNL6XS20YHz@C^uDBIyDjdAs|iJ;Y=&i*TT_Gj~F=8N~j8@fz%2xl{o z0Zq6xSF95pOaXP@vRieiGoK8M*LJTTjK-0=qPl#w_1|@D$q$JaZLnaV`H^~4s>y-e ziB?y?1Q&LWd*ARd6pMBKzjesZNtpQn1!Vb2d8OWILSPph4iZpD+d6b&y^4*i#f#!{ z%+@uFUNYdjR+xh?vH(a&u1JzoigdDjcBz$eX8S~tY_vbw74Y%3W@N#6T(zqWs8L0) zj-F$$ms4S$`|;-Jw?6K2$Y?q8>{oCh`**UdKJD{iL{NDUL(HbC}$2sXg*i=+26DI`coUniD8kh006JaS3WX zG>I1KO=J)9n;7OG`F*;NV2xfhKId~W-U|gWJxpJ(o76IGN5Sd*bL)?VW*hz|F+5G) zDBfo8b`R_0)Gd`%J6t?JB8OK1MpduT8KDZFQc32DV#6#bL0RbXt0X|W{&J*P|~e-Ycu^>GyjV)cXW`i`}0ND5j#f3 zB{DXVVO@R?N zj$H%A-%eL^S+Vj$U0q3K%vh$#p#$w&+Q~W340=zT2RXL_N!xA|Mn*G=Byt3?Y{r^4 zzgS7Al&~hIlbfd0pw>e7Rj2oQ5e;C};OARprmNX*{Wt$&WMJLV?}9N9Hg2IbJxp*! z-`t;vr2@T4Uh+nfMX-5flgtZL)ctDz$#Mv%9C0)2CyVdL2>=^!7 zY64g&U=d9NA|I)T5mu3Cn+w>s=oZN#**S!z|p-)!@HIMB|zQA_7&R z(TnGDn#je1v%^+~;b#&bSr$z{jg z3}Z41!#>bf;|OXnuA0mjqzC*>m+2@Rxt^>6txplh;xfM-8e4*qu}rFqLm4zDxx-Sz zk4}VRZ@XXCK4=6?U2hGY#g_c&FGA<8i zgQxYOh7}rb6K6v4tQ$(S8m+C=D=)ie&O;!L<`1LTAk5W%DRIU)YB7Ru;N=D*e#g3? zr0wPFxVXdUNN8JF1!NfuByZI-50{k;Z%hn1i;-wS5rRiQZ0-pZY-S~2MHeuUo2^Yj z^d{eJlG%yg@^H~rG?Q}9n6VRS8FY7lRy+i4OM{YRV1 zxLrT&@c=S^*TmW{Y8w%ar213h2Y_}c+udPyU@9egcHDC(_31ygMa>C=*6!iq`g3BI zGkFqj>4Xjd9Dwm7dsnJ_hZF)1fD4UbaqA!KO??S$$nU)~`3eei+s2NNgh;u~;fDyu zxa=N82tjSVlJw$)w6a?OQWo->7({>5Mp2&jJg1hg&tYRA>~VnKhQEPVa9uU+jEmVE z!e2)wLfPaj$;!)FNP`UJQ$Lq5?q5;gp@nr#%SdK{>7^t2DkTP!Pq1G_v;&-G5YQl> z&lqBBbWPKpZsUsUjB;jIpF5~zc|dHC)aEGnrSZ959e(>ki!31B%+N6HaeQB_VQJ$) zYWyQm&tA`Q9(?voO%4_o>cGe++e?Hm+a7`%0nzRSd(i}H$b}6EPTKQE@CFzYsRsbV zO<-u(8f;|SEwdkdm|(b)ycAz0jVCpk*#WZwrNni$LQj5I8i)u31kOC+)C8=_7SI8z zm{9S0IUlD+h2^)IkSo0gpDg!)LJ&*>h2)^n`=X;&F~=AnxpA{=&Cz%*(KXyhsG)Cg zJz<6bt!eF?Pi-9vE&=?=HY!IO>n-smT_c@)^f7J&b(>Oamr-k2eu`*EWXTbSRQ#ZM z7^ZfOn_=}~jWCz(e?mYp)zOn0mzR~b*2%O1>i{v-D19Oder!9v#p(bFlzyEx~NR(#3&6kQe7&=O>N#+a8#GMFS^dilnJn4 zi1c4$t8A)Fs0-6%6pW>|!n#jG?2|=n`QGwX1Q@=mW@?)1ZoW%rp`KM|mpwrvJcozr zjVBHB!GofNn7JM-@U@JB*%4p^{vgCUW-gL04|Wk+#fMF|o6lLgg?RdM5#y)h>7~Oo zP$QCwbfC36|2?-qV+sO{?LOw(9AKxw^Mz;2#?X`Bs@fF`70IW;616T3O;jHK>076j zgi&_!yl(I2n~bH&cZ2W(mPN{-$yUBujL``fI*dt`cA|*HYsITX?KB`V*qPrnP!lzg z$BVLIXfd(cK2cr&5D`v}`}zoO>uulmg|$4vd^@&}pyu}>_tCiUo7UUn$U|8PxA_cQ zxl&mqo;Hd67$J&_-A3^G32blFA%Smy9#3&Zs}vc-6mH@A;dt#oJTf0d$U0tefBUi( ze2n^uX_YzV)8BSUNT2{14~iMUsNVt7BU@$>my~q`!`vTqIr4#?RAWKE5Xp34odH0= z!2ve8S}kaCX;%!mf!EYJ`kB>L>;Ze+);l+JRB7ysO3!YJXV)w&QI zg}xroV1rIv;V0Kl16=!P5N^I?y;?92q`hxuB;Bud3M|+{Ni{u@&7bo-FzSn)l zY~`^@>=K}BBQ;}Q+#XZu4(=Fn`)2m+u)!k-G_>)UdJ*78UUl(<>*P2>@BVZQV5hAo zWdV$`;yyP3TZ3{RTFtno>T&DA(sXUt+4TmfK_BXYdXVNN5I_(bXG|D1LSh^9VT;y| zCpA&nrqT^h!G~aZWlz}4#k;5_=GaNjYLL@SqR-NUh5~Zl{)Hw@HTgsK$Y98DgS&r# z7rj>}&o-u{u_3iYVfUxYv{`wdIo8er;YDxyMH zVX!28fL8)SiwiLX+HepTd@VBLGF7d<_zh#^tukHsh1-u2Ye?|!@S~rvvlbOZm;8p7 z_!SdfyIusPt5*6}RMk=Ui-?i*|lhrKy2hiCCH} z{a@(TFv_2pG+_@}jHS$RHm6yAp=!JK!LfKU&a9(#Q(Y>cnBTL=nW-^ZO0c1BH6%jK zZw3{1(BHzM5B(T|nmeLVO=*Y=+nWa>q&%LQN!wKMn0Vf5)FMS|o;K+Yr5zQ#$P5 zFg~G|Y?1Fk+3ZAhIV;!-LmP_7*dU&ibWyQ9Uk-$m(!wHBRdOY90tYPT8hK;Z@ca6@ zJ1{})hP<-4q?DDag~ja-ab^K@&~kA(pdz!`Fryzo(ZD{WdNj$ZHfJBtiiN@UrPkny zJ6cCDpFD|>U-B`ilxv1+2wOV;0vXgig#$y$gQ3>PoVA+oXIybK!Q@rU3#xoj3<)7B zOgDj;Q^M!^@b;zl1c4;sl!>DJTnlnw3*$fQ+6Vm<&Pzn_C^Jdb57e?<=#d0m6E15i z9iK1zIz@_Sma~f2t31w|4#q}!F53sc-JfDx&3kc%DeNK8@?!QTFp4@t$~g*>Hd$au z_?_Z=aec1!ZeVe^8ChBqD6XmTsXTxg#>5tIruKxle$imQ2u6155Gkkv?^5x8<%CgQ zWRml$ff*laDKm9|_n!oQ5uNe&)qFLesnj~~u@dmO3tchZ6szr|t(^UX`cNRK3<<&qNnWx&VOqIInKK3wkQr+F@BM>gLl1 z=JIi4g7!8DJ42l?txuQp1oU3_8dFjh`ksh5Sr=A#D)oO*y$>~nyptk=jLuS^RubVP zk!Sv+0+0muLTV=LWyJ!ND~@u8?3-?fX7wue?;2mEnItj1YUxvo&)fhviuaF2Eh*x$JdD-csIjW~)&=oKD=Y@5D zzWA(k@|86e<`*}GkT9?1StV&jCI6!vG@n`co_ z?y3XSG8TvQcKAHIG`4%nm|6R};Ry3Wmk=OT(ciG+uh$H!}vG-N{$SsUD>zWAl!;I-|wfQ|y-z)@~rFB28`08RtSLizn}dG1lpvbu(MM4b2fdt0Vj zMn~rDo_`bcozzlB&xZ|vzol?Ps>$i)s}&HsCRyxp*0ZfjP7MMG$XoT$dCzR!Rad(iGWZZ|i7E3C%M_4yu=Y2%y zDD6U}$xYoHzk+*+qZwr=!lY$84wBMXv5FKJC98E}ZX|&~z6&WS1_3aNa6X|};8wx& z4Amf)I!IiBKA0vDf)cV*@kH0G0{A!_=D+18Xfas>fspz;a!CHr?>!(w$Q`|@xyo33 zumRun9>55_n0bAxa{?lGnHkyH8Q%33*6KG_EDZ{0kBZMP#bW~+o6-4ThIFBV7Bo1c z`T011(VUflrkCOCzsx#3(^>-L?FEoATY{eo6yJ4-b!?rbcVUuPPb)9_MMN5l98cuO zP9Q$(@MR4^4BYsL)A|K{a(32OCjn%{MMXYx*X`|Ptxz)^tPZ(TsrrEX%R(^Jtx`&sZFOlrsKxnJH{TUwey9>m{ysJ@I z{AAACnmx3%Ji__ZCkPP`Pr!+35kncGdc#)#c;O&v0^LCIPwP5+0Zt}p6>unz?V|(g z)WFOvv8;bnzdBHBU% zNlF%UbQ7$ia7qQiBkDCK^1Kb|E4p5#9oE^{msLot;F90$9oLBIq4aptx-FA+9b3S0 zC#Y16$RCtdL>$d8Oso{ThTSH{)~N^%Nws5ffvoRZHX%bq!y6d?q45$wYRCdu(ya?SFth-rGjSg|D)B0Xn((j%D-ITWgS-J z1U^4K7Z~4)B$n~r-z#4P3;o{S3#RAUWaQh+V?X^~Ir*;_Cy>1=jm|NT%IE;V7BNUB z2QYP_Ban0ebb2ZDuf-8b5@{=K_pb7IBlRZifea|`Q}`Jvp3d!&`K7BC7CLGnQ@-xj z3z;mxu_WQLySW6%KrQMwjL0}jj z3K;?a9Z1D*$6XrJr;udlV`S#;T1>GF;sqik*6a&xSQjQjp@}DvMrt2UFTY_qef7cv zU^;Hkn5|YPH1Q>P1WlMcTuxuNu#nDBtK@v+;ABV;RTUiH)6Y$u?{l7-hzv3b+}PS8 zdQ2PJw(+>>Pz|~-MYb)svsOcIG-y5L!9+jlg7!ZUCD^H^wdnUHqGXp~9a*G~)cMp; zpdaI6%QV0vfkQIP?JL}>H>Gk}Y7(g6W1HZVoSR)Ox2uL&7&e*>l_W=47?@pNrN8!Y ze2h>NB-lcnU8S9M{0r-xXUl@kMM`^|tAKIB4_{H$m4!lWx(Nf~Af1sKV2_8_O zsH`amIy8j3wr-lm5)_$Bh;ib9E)ogl*tK5tLt_FHpotu)A}3Stj43O@qpO{cO7=HR z-mLS`)=k{)C%cA<>#7k+zNY^OTKX-DgN=hIM*~gouk5gnIjgK+ftt_7lCe7`CL{jy z6O)q@g*~(HAEF5J*}&vvAUo+_gF(=QvqCm2d~B39+mG|O<49~0<#(4_uRu5Ob$Y7G zSak_8R^xF#8a*&KC(O*4B#*!slP-z=3}1~2iKzp{MnTA&oF+V2+2(i#-F#)9GyRn% z*#s-eENNko4yKS}Wf^vbG`UE&hQu0aD`j4!?p6eYIkHH_d?JxgK1K8}JmZ-TdA(k& zGGo}|4W$_`&rD5`2i{bW^S}ev>kUma9-a|*u4nHOl^{0eVG3l|Bjxqr6yx(T-dT?) zB1E>ky`&d=W<5;AU0Wg*a$r2{xsz~sw}Nm-F-@i3CAE{mP60+BX8Z9%@9Ve@eYBoO zYI{^0G=TgjVbuZef(LHx(cB7vHhNe4Opwz~fSY$Unvgz+w<21zi0K%)tOL?8%& z>}Cc*aE3FSo*X#4lNOlS*&uG#5-aVjw6l4oR@@}{Buf~Dv!vDflnBdtC1=5sqt>!d zI)Tpjt%Iz);hp94|JLdAVgB#E>IRA+Ig;-r`#us~9nh$%uCDOn?+ttCb)r0ap4F1t z{<*pR+3ZP8b~znmd-u=jC+4S7JtOPOC%}UL?>ZB&C0HWS_-&WWp!=xI<6^rKi3B{2 zAeG{hvOA5A2;*m+l2qtzkESeKC zQ%a@#RlRtn*pP}SXr%mKIemJv_l>)s&_Qxr#|EnVImHo$T>qFT!zB8S6y|~4KuZ-n z-$Ir_$HwwtRl_2jFqc$@W`+}QWS@%eZafWT^d#9YhaMR&Ib_Er=J$vD7X7tR-*Egd z8@EJv>o67qzGUNS*!M`{)C6M>4uF(XmqghJ$x{m4r$RPjFFgtpkqWy34nRgyv8>cS z$v#PQXc+G1Ci|(pwO5Eg!FO1^@YLR$m!A8|o=-d!9gRc-!6+Mh>cY~^FMs8^hd%LV zfoNnj8s(A}lK6B%Teg&DAQd(>6FwW5nC(6j>FZc!vT_McI?a|H$_AXnr`|5JY+8B- zHs@$_*;Y<(Aj?xLldEKR+Ge*J-NwsEX(mmGQ80fJ$h8|{H^ArQ?bMvLV9%T1+!Op6xMY8r&Pxt_ z{__E88@p&&|Iut@o!zH|;lQu%&;=E)j zm?yhkV8dqThFeCFe6KQepb52Xdbx7~Cox#XsOX7M=-q# z(1?)Llq>pj=nLVIaCqd~l=>V0pj7PdVE(blz( zlUtVA@;JI#PG|`kmQ2HdS<>{;_oA9EFfb61gb|9KLnIji!W*~(cL5xS*e_&HXMuX3 z^)$@?cKW}aW~+D(r~R+OX;W52Z>*nYRoUGV{1;$tWztXnH{N%j zi(XGX?0e`T?kz@o1Y7=DKnW($$f(#fnbd%<8fK-mp=lMpuIs#S86?5&usofhnLr|+ zd+dt$F%537YZX?8uLRp%iJ|2U$OR>kTd^Xn8l^R?|6c3qz0zUo^#u=dxLHuE5f4k; z5W1%Db5u!rEJnL9>4J3+-E0_i?2+=z@`QGM?T3!!WE0wnG zDizqqyQ0kxc6EJy)6#TMlNi_FS~?l9#vu!v`s*L+zv1JR3Nw1&cFP;iS1LALMEBv- z+IPyb3Mo^pAAs6U_!V-4@LO@^vsYs!WYsmGf=y614_RoPAwSTr51>W)B_IrL^@sZU zLM#EN@M+71I7Ts-&3={jCrKDmEjC>~p)Pgq2TeMmU&s|_74k44y}}4s3ygz} z_`I|mc!dLC%eM?Iq~xeaJFTq%Tb3UOJ$OK0!eoqJDrmL@j){C$P=~y$})T;26iQh28gnQSSr0Wgtj|J&932v>DgBCO43$%EETVX@% zclut3uh$?e;^#T#@5XsEozA;;W;EcjVS&;sHEHMBRe|an+)lq?n$5}8$=7Y7zB~Df zkdx84ONHeSe#WHH)3*i3?@8P<9{egv7|e2JYGY&SqDHl;vj4{#H?t%sgeejf{lF7+ z9e-Gz_20a(G<{?3{>;=RQyJ_MLqi>iPceU z_%Yci7DI*sjUli|rLg}pNDK^vb!r-LGg`#I0oNgkXq%)}eksfOX9X5TC5aB>n5S!V zL2!oOAvYcvxF!t*pw3gnT!uyZD2;)>b5c$ywl53*HLn!=?m39=HOIiurYQK#>*c@)F3qdq@c1UQ{QUAeaJYWPt+MJ36}e z)?1%Y?nM6ePUSz0onhWHW4GS=_)GlCOOo66RwSRk4zfTZD;9a1{HW){vaL;S&bO@L z3x~g3w-iu^t6c8OHNFlQwISlePy%J;ts-fn(y$sGeTgl^W^To--&@m^C-%pNpBf$e z&yC-T&D`=5UhFummml9BOG!fAc^gEf_MR6#v?9?XT{BqtYCHZyiuJ3Q8V z=(!_D?ml|-Zl3;HI9#pOv^Vh!l>YpUH%em8a1<9UHuwybZY$wW$pbL4iniiR7mHv; za{BwxW&G|bp&%TCV*Q)*vwKs{iu#I`EB_g#Cgs-8Pbn31BYq}Le3#mm7n4x)P;JZV zH^q!>-s78O*A4j;RGWiUh}jKP!A)~n zStB{WX2kBiGj{Ncv4aO=cQ&qC7t0z^Uq$TFH+XsJ4ow|G;zdt8_K?hFi*U<08a=&}2JC?RnIh&s> zOj>#}D*&wmuGeB21vi!|x9kddne3LY$Ima#{%sU}Jtqo0XHS})8y|P~CA!Wp#iEIL z8ZJNo^|4v#ue+n@^_lkYdK4z^*0Mv1Xl&_xSEA4Te{Y?B@NYs~pX?q^5;Ylo{RveE z_F33)T`B@EN(432OGWInfRVJu)*Adou&i;Q^n)?5f@NzuL(B=UG|&Elq*Ju|O&78t zWMn_fUVfP!dc5&CQ`xJpvYU!Ukpcy84YHsjzfbZyQ9_E1VudcC+i16#3ANJJj1cf0 zp|Jl-V@=czaZ@4i=9u<{aTJDq)1Y#zlUC6bIY-GO;Gg(ObD5Q%b@eUwgfs4nh8&~K%`j(k^s6CCh1k6*r zicF{LmUQn=*q=20C5TPQVnWgicGu&N-&Vcxu`2wrKY1MXkKI_kt?{STs^k)o9)`#_ zo@5=^k>pL!DC*Z}0Oy#N`5YK1eP3 zA<8yrGN%MJ!lDgBRGQgd#;;zthMTM$&a_vJn?0DKlDM{g?Wk=O_D>Fp+9pd#W!Ehk zWa98eHWvz|EwdR0Y!?a4Q5gdZ9J}|p5(`m%0OAIBjn@Xx^xXXcZ^Cn!UFz(7wj0%V*nI)q=cXYX3P<2`WiGo77Gg5N&d z2|pWu>~9~Rib4Gu)cBf1BL50}0;$lfp$hX>fwfgrM*IOamC3v~WL4_W*Pp#6J^OLS zc-0!$X#c+E*Yi||Ju87{ne^-@8rOIg7^8jE`ciUn3UnvC4^avWJejF0@Q+SGBz0wP zWyKQxwFaSNZt|E2koI|-0UzLmOpXiZNkrZ57ytlN$pM!#IjFf9w(Tm{bBkKV#zrO* z9&zaDC|D%6&141U*J&DSl*HMItf}x@)I3(VM(5id7#UqR9wBTi3wX?{(Fz7 zI}}cgWG5ykvLlIbsN3Ti_w-HdeI91HlDE6tTgD_d8GmKrb~f*Jb@ccETg>h5?CSOP zbhz9Lj=eV|kaNB*k|Yq zAi{;Tq~Qtj=tik@1=AWGLaW{@WoVuoZ(;+b#Py4s368kM5@byl8?a+WQ3>}Ok?3eN zVt{wmU}iAP1s)3Owfn>Sdjmk){+xy??|7ze`rjeobrwjO@#V~B=h6?^0()-jsH|ZT7)(8pd=v|q~KVAJt2@lk9Whd z+g6KMD*<`h;3gagtbG}4Qq>uO{50120c@H{TV2z26Sf-c$h}v`14!4&C8kb(SKP0P z4oHzg?3E-b|AJ>ZDlLOY$2n{@Qu@&5v~bDrIA@*PN};T9EN;1N?qLR2lW1st4HNpS z^V(ZqY1VaCfqUpVc#}|K>3&M|%xiS9NT>W3{_yk-%>}q{IPj<&*B*ouYw7o88Ms%6 z)R5ROXs0#O@gH74yz^Y@Iu;H(#J0!8coZmWN|M z?BU5x-bSbvLv6l^4+SZ{@FJvS*Kg~~Oll@NW6egO-DROre0luoP80Xn04LxrkUty%>#fT{xg5~Nh;3a_CFU&9CM#^^iKs%+h^Dg6D* z+T8A`DsM+>bH8;B>xQ^(^e#l*rf@FXJyWwgAsjVK`&6_4>>f#7td4z=o(OhaiO4%% zgMUv?ZQmowJ3NmRu=)dDJwhM11^5&&aiCWVhviu&& zD?AC(^|n4NNpG5TxBisfPi3n{xmF)+n5~Hvh7R>XtceNPH)lxx_b(sYs@+;vi!i8- zyRF6Kw$`IoYxOgY=5meK)3mBtZ=3%%_{=9YyAY#xEZQwsgztq3kIw$(PeUW!t|cGg zyhW`M!|;3IX>xSjHfro~L#<6BlIBI>NvNvLxeA}WId<%a5O3UmB@ZASO6!p2=LyFK z9gM(h;wvi-Aa_S9fPdfg}7 zu3jdSAT!EqyNZ#<$Yf8lD!1&k<>iDgNJnaj=wClFi7e664|oCw(zFYc6T=^R_sGo4 zK>ivv18v`xx#20M&mOZe@~UJV4$eK)lYIveIw`aG9%|#zi8gn0H z731{y$R3xw@k;dZ8=w3jNIis=xQCEC_*#rL;`}QpI=CZFihJG^vV3W-=-^|ZbT+>A zwfo-F*?GCM+t>L>XXhJpaag9irUsFJ^<{h$_nz*IbXm<%2>qcYb7?>F^M0cg9^2>uqneP1J?jHRpdtc+Xq6>-T{P6tIPxN;G+;ZRilQtE> zYPLN{0MXq7gzkp+AYZ#T2Y9~I>bnP~FH@DJXLdE}hG7&X$nsgKe;m?94vnBdY2c9J_0e8S&8FE}VFHoPo41G8$ihHTbGQNc^ZigLfG3PXcW z?hjm`I;Z%K>6&3`8@d4mSjjX?xRE@Syr5{VAZmbU4jA2j_%~|kU8k%XWhNP5=TmNlx;x8es!h zk$0_9r~vd~E+OL!aFCLtDPf~L3Q0n{Eo{!Civ10Y(kTyIfhro9#|e3m=QNk7@jT{5 zz8Cf+J^kwHa(;Yi99Xg<=oYJSU5{6*c|KB#_DEq$3gysA>?O>stgcqBNiP8Ur%^5& zx`|ddZDTdM8Ba=-s&y+_VsZ>o%ZW%^^6eysnHjvzH_A^6h#XW)oSx?6D^AB13b_8#hKC#&S zN8KN%A^Z+Xe@d{hd0{M>yh9k}|4Fp8vF*=Dt{&xREJ@^9a&3)FJ{mx8lfU6rU1>R6 zDEeBcTn1gGxv8~bnk<*4e?4npyU!3_msF6GAXXRZkCVg8Cz!T!Vv|?Mt1IS8o}Xa) zzmGK{`i5`D(5Q>J8C3x;x5%~0>?6#vzf%{)URAI&2^pTP?&$1 zK}hpB_F!YCj=tv-#T;p&^3BqCaWOF<+H&L3v-~tNt)-c6KLe<}uQBtSlgS5_a9{68F#F@VkuGOnU(cN`Z(?{RAB+E&`H{XJufw71 z%+37$djlS)+&eV;*hI+VML8~WvTijEcyNPbE!;qECrL9uk#cx|`^)=KW6IP{PkvF=2|f1~Xo%v5skbc|=_bKP=HtfX{4}M{m-$6SR9dOtcme zNs#VbNKwW~RyT}k8bja0>`bP>R14P-CK}g5R02R9&O@%BgE|DIVNQ#Qg1`d21@feC zi2~om3el-R(nyYj6mU(jbFh*kEBJ!C|iHW+lTOO-|i- zLKo>v;*I`tVKBYin>rplHoRg<4%T7gcFg8FPyXiY8?;*ODoJN__#QqwzoTf~L0;?2 zlFnXk&hdnCt;%WG3Ksu^O~_U!ViS$8#3o{I)-+tLP4@6aY;rO-5jPE(xQx|RuFZLc z)mdJO+HZ6?oASVB`|_%}dED5GD9Ih^Ug|yu+lY9=@}L+>z@N2~+FKcGg)}`dV%W|b z(9Aq?Pno@9(-}6pWY(fH*egIGtg}$rC^Mupj4}}#qPAxk{q@saR?KUfK`E|>My$f0 zBm|m?W*CXs!HWygfeDA^Sll&~zIm5An0IN;gS#G~MdU5r^Ly2vXm456`6=2aXp zFQbI~#g{rdzKFx-)%f^${FPT`e$5uK>k0_#(JxzKP1~M+@=D+&A~8$oh7n>P8{55a zys?pAJ}|AEoY;MVY0kac_`c=*%yD;i`ncGN{ZgdK56*E{4ystQ)mBL7I-813$WAm4 zbn-wP@Um06^dJLcLOULZ;796~2DlA&R!(oNU;VwY2ghTqzpa*)_r~5h9y_tAszRO~ z^4_6gr53h%=(15V%I#0S0gTMr<{WK3P?aQ|I=o5iRWP(>v8=z`ExWH&N&xQoR2tvZ ze{B2>nzHEslwUrUW5Z*+C*sLWByngat|qcm(B3*KLi*5(MO)6#op9(-g+e0UpNV9; zW)5}7!^g$e;u>6wTHr5%S81EJW0gpTiW*(&>czUSp|(ec*gsgvbQ z{Owv(M_RS?ruOCp^1afYCtszvS+}^kfre|fsc(RzjJfUI1yb7k#cN_Q>{lUv2qT z7Uvc@AeABJUI_(MH4v&s&?o+)Sd38LE@`OU8+dE}gwI)O;XR@#lZ?Nsf_h+Y}&M6#%hz24-$~Q+;YeaXQt6nU4iux3AQ!P;FDG z6|7Ntecwtjb;YWe*xQ|?wMOz}8=rPq{n4A1S)Bk$9i8{Uk$m?D); zY76pWMO)K25&{|e5LaXX)1=cHYP&JA<<}-%O<59g;B%5h@TVs=rpV`#axFu!YFA(hZB}#i_bti zansT%JMGv^TTRl5Tr92;m={mL&KCW#$wz;2t z@lpoBUBE!FXhbq>1*qxuF6z}+=^e$Fp?;=mV z0^adO`tgraN@aWz$|%zJSt^5m`bA2GcrRY^j8b_awZ=D2;teO6qTPT8H#B1eJxBT@ zqW`mWvk7HjSus=BzeWdAw}sGBYocp&&WCdY8q8`-XbGDu{GYrIskml*w>P4cuG$hA zt~9IAfi7G$gt>|+P-=}%8Y5P7BvJkKOS~Oen3YX_Xrub@SYtjOTZx*ufKIxglK5G= zukm#@g#x2Lr!%dIYghZ3Go-dk2AJy|6XfFmE&lnNy^Wk#I+xzDCrG& z4xDvha>k&$!Y^_BrCPSdPO1%md+jyi@n5e%y*LnAt8QgN7htigR~s8xIRa&%L~;mq z42w^j-<)}>{dqBZVZE`T>x%HiqD;}&*dwk~bB=Gy7cuwdB*g_^w9(uz=Pi)X@;W)z zg#9FY^oKW}RJEd6SzkA|`HD`+gx@rqa*F>7_45%Ohk+xU`6TIg(7htHapnAZhQau1 z`_5ls|MheGR~r8hMgzTvJ?LH8FF6IfSXolJRqS>?VeHbY|Gq?BX$=#T=?#3T3})5_ zU16n2M&kMLb%`XelwZ@Qx;@Wg?HoxJA3-*#iV5Xg!*v#0>^q7BQ@6v>208)Z4e7%gc>XQy_u1hjqfKj7sY_Y4?E|mEi-|Vem3C}py?#osYZy0T2m2MENfn2r< zd7(KTOy%?Q=s>72srJURXWv*`JnOAM?<|=&e;^qAz|CgmOM&|j{?dUbBuQ>c%*C}l zEyTDI_9XWY*rZs2I9e1Fkr|f>ZN<1`9Rs0(dJeuZi}Xk4Cq~mYIQ;!V!*dC^rM-kt zzr`;sKs+j*wEI&270vR&3;RHFP1ydB?Zsws79!)j_Tl$TS5nzB$gkG()h#eDfg9+6~QmN~O@c;(2(^x?zPxWO@#tb+~v zi_O^e^z1vthp4qXg;loo10zWz%(vvF5P%*UZtQ>+t1T;&nmcdV-;#MMD;Fu!Tq!UB{dXWxE$_d0aeujZNKTN~ ztdfuqaXtldVn%b!^BA6dBWr0^1Q<5>tgd2&{hDo8h8i-lk40h36}DeP?2cbRt7)t% z*-dBd@xhmtT5;9e)8jSKEc{V=do!C)p6 z7#a*@fZWq<`GiZreng57sw=f&O=bm|Mf*y?ei$|E{RgNX+)JG)V*CZtz@Mcw%;O$Z zh$E!rUpa>D7Q`>fa$wq`mo#W5TM@neBQ*DIY*InmSeKMzg!>@NvZ`)}b3JT<5{JpGZY>dnRnuAB`v0GwW zZ1?lh>!kan2PMh2#ZYH44p@G!y`9|rdh`1%Y&kf#?b_{gx&1zC-;N#6hLNW34s~{R z-7B`e0T;Sp%R?HVTky&9@yV-P$GXmySy}z)W?UbPu$Z^&FYDy*dm{5VTtYt##aX zEA8+LB%&QctB89R<4-B11~v_BjaRtQC>;J6aV@tA_A$%MB=SfVkm<5bM6%XZm1onxL({d4 z5%P1hN|s(rj#3%rl>FY59j+iB3LT)PT7~AgVxKUWYX2)W{0mWb%iw8-Edep?_Bi@| z-GRQYJq#PA!}BRz~|9dEO zqWP9;!hrmQ@HSPt^*OtPG@#@P-2STg+f_Qc396=S`MqH4Aw+G{X>R;1O|-P?aL%Ti zGzz3`rBGb+^_!o5`sUr!GrM-pOtU)NJUDpQ!*>l1(h8)r%67l0U3mKG3&XJk=gu97 z(Qi6}5B<atzKg8^uxuwxYqs{LE+Ef#k`1z_0H=V^Z3W z=cIjW+WmwiiCk^T^v5-8spiqii~WMf^QFZvfdx?GKf{Pk%_V!I>|=0>7d_v~L{hUl zbY{sT^hY18AYm!S(S+v-t|Oa+i5WDA=srhUTd+a~m8Q&P4c~CxsNA@CQu*TVotiwD zc;H1B`?PD}UeCYB)BowfZ^F~^v#DpME6@0kUi-zsz`0S__Wop-0_Ue3&rG{*4Iq^t z6(xd!oVvw|%w|r%N!+h)W)HO_xrb7t3!|e870&rGP2>!J6TcZHzFT4yhs2RBNI$I* z50cL}HBNF~)DPKKb4dPIAjA-sbj1Ms4g-&#BK&ROHR`WokfB#~>rJAw0e_2C9^>Y( z$VbvH-AibI60@E(RM??#Gzy05V;SM6H&Mp2Vw>%DGll8@xtH5|=7 z`JrsWGs48ecVkt{tOj?bwY7+!w8J6t$OKjc{Sj)LKTK)VNaO$tM6#MyB7)^TM>j~} z8%S?~G>~l+1KC#aG*^xaA=3lTRIJkx9)FCZi_m3O#H+eaC-oxUQ{nI;9+841sfQ-z zwqlv7-$QM9lq4?|dv%)%)p_hAD);Ahs+PzJdHD<+$XU$Qw&sVr#`&w7!KBi@FNxe0 zGl{*b7FSP2?Q3DbB(%3pQ_QtE%Z$Kbiu(eeMaV6bj&KC9*VC#yLFswnxN_>DedFn# z{=WX6)0ZwWNgz}C=k;{u$L~Hmz7**03i^8b5qp!*kH1Z_3WZyE1ROtBkeS}{>4uKLkqP7Z)x zLJ)!w2e`V5Hq*MkiYK9PY`2oW(YG$ z6-riSZ?kDaJPWC6@OZW)!6Pqy(+a(GdKei=6 zuCA@s1&Kj>l+Jd1g!UY^7uSh6GksE+>{T|YP;vp>Vbv-O+6&~Hm?Da91=5T8|W8luUi&c#r0!fLc@RPl=aEgnhVmo{?>cGF&x@Tp*Lq;B`%+Va)i z+NU??_fPkn%pKgW1w@a5?^Vj)mWdE=ap$)|R{9(dWT#$ABmV_fXD^6x677G&=V)#( zVE8^w7#|KxbDvH+pMC7H#&0nbrABqIoc=$x-xgyfd!!JLal!)Ii0lG1miXL(irJ7^ zYf()bw65#ioSEzo1XV$U~orNx2I97R?WW%jf|KaaoV(c zRf799rDr*uxy+q=<_lz3ni^J8VDt^BNNld;l3jjv?^}QF=KgNk(K$FdIS@vR>gArU zfG4UR7)jg#*g1XO?#Rr@K-j8JmFm;qtdA^Ck5%2cTVAKBmujY2Q?6CNI>iT=hWZIV zQa4vm_D}`6UAh{wo}o&@&2_4(x2rR#^mI)Q^z`^G^}-MxLi z-923cBLh8d0A-hhsewq)-G}_wXQ3uHLroNl&IN^LGs9R2j6s#K-}8BS4oiojPo;C) zd8T){I^~eu>FNs0T}qelofr1|Wj4^$(>L1J(=)(ENBtg;%jNO-M|Umsy8Qj4yX1$L zB7@_L@jkc5eVUL)Q& zuHRi1T_@=45>><8_T><`0Mw~}fKaiak~_aAp`|G15=FD)K8N3>B3coeeB1JCRd9y5 z-Z=3H?IDxoeV25Aw@6lK6>DcV%=g+p&_Xn5U|jRjbDee~2!k*mJqfhU6#Zi4r_ZhZ|MDoKN#y7~6?L`yO-8^+!ihFJ)}$-lSS@uaI`f> zeLkhO)f^i>yLm*?Y$MdLL`JfPLFz$BHtZThi<`vWSH((J6`V>H@X|v=1H-Pea}%8# zBKmA=4P_u7E0q?p2Pb8wnVaItSJyUkseQB(=_Hl=p80WZ5mDcU6Ss7TKd}=NF4)AW zlD64TKn{`3^mp|Y*gZ0q*JqDh$6H{k>+pCgx7B07<|!Q#+3OGS2#vt60u#KY3xX)p zf{|P~v3v&;VfBke2G7j&<>mHHRxC=))-6*knm`g*>nzi24b5B`-b1m%&F~q?*|yeP zf2G-Bk*Qp-mv>0x(m4Aj`=({>5GD)1XK9jNL=;`zxNo*qG-Ay25VcC;ZNIEVu8L z7=Dqa%jL|(Qtp$~e~OgNTi~|bo9Mpx3HKr0I3xMl@3HR?rc9Ijmr?r#mJIViB2wod z-xla2FgP(rPt2jh6;C!pDl#6w76>^mRDNP2-5(n^j1I3OH8hlRcsmSZIOdQ&PNzq9 zw0%=0dD2ap!@iFG#bi3|l6yRWItEx{o*vniPA3=pnajzT)5W&?9^ZgCi+72(&lZva zdbz=t5u&{yhB5^kfxQg-4eeu-vB^)zCS&j90Z~kI2rd-0EL>uyVw!J*Q~1Pwi(Z9W zdn=sWWt#7YOW-VLNoxLx_!jc5WH~68U>yp{oSbv!Q|!Lku!0cVy<>+Pb>L+y2D|M> z4dsfpYf_EV@Lb#Bwm2sMF(=@0^m1e6KI}U81d%ZRD{b054p0&;aE(z-q0A_fj6$B#Vx-sNuA9((zaPAR2hyO#{JN9 zWUoP6Ub&9HJH1u%S!g;^67DI$ND#kID~7(sCtl<5H~d>ugRp1lq+s$}D?0r#L!8^q z7K)QjzMnQf-fr(8=wRCRp6kW07w)5w^x+3d9R46lXBX-C{aYi})7N2ErL#R@N=c5s z$m7$CsqiiI3ixB+V&B5(kkl(+6#SR*$DvSjq4{$Jb}AU_(~>jr4oz7 zFIZn=K8ki*C-iu!gw}pv(BoR^1SQmaY+1n;zXw4hK$~-i<1OTNwS<3~kcw*(0;`(z zVba#4Hqc`jXE7q%g=GQJ;ZpN)V zMp^Nkew2=@f@U*8$EY*YB#rl?W?Yr5bdpEkv;FlvZQ6w_d>695Q(I6&vd6|7vT=-U zbU=33jW^y9BSrpk($~l7c;to~Zu~_$zo+Q&-0JD*^xRYg@z`x1PZ2KM28YF)JOTK| z1HZrV2|;}yr{g$WP0{(>4!Mw1Q~bHWEsj zXG_EyiGB(s8$+oM&hLI!;L8J<_H7M;S}ue9v{O&$dg3*KVo#i4aQ!v744)P8S-(fR zQq;Qnpe+Zb5kiMW`&Npo0{av{Aw$(XsIGI?K81T`dqQqB-6BmqGQoRn>AXhnir~U{ z=`=Ixl#bz=z*TU1bAo0%EJ;?gxO0*VvWzxOB?#S|J z5{%`U0vPY+{80!)cJj05H0`F2bA_b~7nXM2Wbs9R2){%ron#wff+SU@Y*J0}TuNzX z`9?AxXE&c*0QrtW0Sc5VWzQ7S;0JfzB%jk(38K4XSjCa&smYErlW^f>3iEWFJEz`B zJMug=S&`onz#Fo4bSb@)nY8=A+CIVd77!=^_qG%Olf;M*uQf>k2~)`-S`BQq84&FR zHdzRW7z--RcC*mkQ^TYn0;_F5sf9p8MC6o0z3I1oK8I`NH&$E@`(W_K+b*0td-H{J ztlHD~jUGoT<>+C%X1tn0((THX)*!i?3P*$S9jt3hI`5-(=ER zW75daS6cex@*B<;{<@k-R5y8C{j1uz{ot*NWPzJRJ~#sF%`}%;=UVb-m4JFv7R@PJ z%hBw7);ijDJ<^p8UY&~aDzHz9e1A_q-_u_XbmtRFcK~?eW(B(dZNPFWSq6jZgsCM$ z269$`LI_eV@OklBM4Jlo|JjKS4=CK_$~IJQw}5!9c3{teleoYPZew%M_!a~hjzo;1 z%+OGVb6_iMgT2W8{I=SfLJ6t|E@bCLufD;Ln}dTUCd?4L`F`iZv11ot!+iVc4g8HA zRg{G|vRVPO#x!CHI&9VrG z?)jmifmnL-b&=>q2Fff#nV+-0;>gpNB*HS64yRBE4AK@)%Q7m@UXQs9zA2{0N2Wih zyZ!OO^LJnsuqt0rW0UC+Ui17)OpT?FzU~|quTxbHNbTB;9r!aHG#*nG56|Fzf01MyDfHckil>It+dL*O_N^n(J3Y%8eArEJ@ zohWf88wLi3yanay6LEiJm|MahlzaL<=It2lT6IP~-rdZ z7tnnEq^9-z8prSP=*C~okNA6?J#+bi4tJu@*MIa41B1K9-uTA6>U2Au4pfaeJkAbx zS7%qc*Om2k##B#-)6?N_db`z3k1IB$xSYGw*QBpujGvpOx3Dk6(=SN3OA^CJ1M%~= z4;Lb=OL(^S=aca+a_J?5o;d<8Mf;+rbrGS0KN4rm2~X-_9UWc$-X7TlPa0V8yGKKQ zcvRWlHyG^aj~eiOQX5cD098P$zf9>}-F|H{5>9kDGLcTFHtp}rXe_BZT}~%+Zh6q& zUVKt0!_(~>peGHwov}VG-48BVL2u{Tr0VVhomq=6aT9RE#N# z5=!w8odR+=krGe@%)w3IxF*_xlpXn<;Q6<+C!_PT3#Tt77JmauU5~}IL_BzYX>>R- zz58IksQk|G*wO`7YP>5tpLpoh?&-ywW5@p=T|XI%=MU_jj>EU-gYkrhS_%;hsaxu& zngP-ltwSIT$3%f7uK*@u)=r#$T#%Z;exGtUK6uIJd}|`M^g)N?eQ$O8E-l4Qz;fiG zaaZ^Bg$%ztwB+imh59@OEKf_pzQ#|pv$!a+M+6>#N7eF5al(t{N^q4UehXkDph5E| z>!@Hdi@IT;45CN}Ok=3&Hcf&sgVjTa{WVG2B$*SVWLuVkDr8IE+OUUXy6Chcpc{IT zjCblf9GIF0zRvYJ8cdsn|F6TY4jV&^O+;NXu7|p0V`wRPNQBLf;)2JjaGm1WpkSv~ zsugR+4cM1fiwd1!7G_)RJ8b;YEak~_ z1eGavB}?ziF2yo21&qfj)>UfA+%VR)-_FD`PY-2cU)A5~-)2zdb6@U{r={0b8dGTLF$wLNRaCPFNmRhOr1$iP5zy#*=XH zFcg*Fw~wuIb%g#HREaIa4RG|3D671oTiYB9n(CIop2DOKXm$At|vHhj~{14p?A>mkA2<%Ax z@U_kIR~a;6N%pfe62w`KFx8wm!q9>Ongk_bSqn>e6}s*r*w_I`9@n(D!R}qCMN@o?D zXAOkBkecvRZ{<-p^FwEx-q&H`h#0c?WfFfdGu%I< z4K_BG@Wu~q;5`JSVTA7+T+WXzHm>a+1@SJml+HE?X~<7f3PKHrLIr@EEVY*)hS}@P zHO1Fo9~~Tmta`DaCEciG4^cM&V<$oc{W&OSXmB(`6?r=?upE_t-Ndhrc7#*X;aK<- zvb7KFC}F;Td^{M0?ViQOXk>9QQr%YK%;Ys9Cmk~*_;@zCTi`K(I}Qe?m(cMI`@WCXz`7BXcG&&6}D*J3Z7 zjA4BOpZ|OSIB7axhnM%?l%9tl?on9KAF<@Ke@fUV96Q8Tm;i7uMX{MH8-7r3BIl%< zM;X-qeuK0MKTfHB;nNquRTR8H*SaC~g_r{Prvj(!tmlS@b9KPR!51A0VVViHWOfy+ zHWNs%WmE07NvqAWlg*<7YC2#+PF(#{D&_YnWn<&M4#@wSM7wcM_-dFbD_<2V^JTNz zszudQpzQRu2K!^O2OCBofdGnwSvFIkaNtdJKNUI*FoYiX(CQ3(I3kWO1Rv8h8{Zt2 z6(9r*(*WW?kw@7~I=zxk&oEe{C&r4!u?bC^9L?UE9c3nB{53XyC@6Q_#W88_>X3s! z#I326@o_~Tj7DKtxy3g|oc|c7ee71s;&GdfPQ~ykBza*2Wm(KD2hV0%V^b)Z^>KWWV%e)|zqpz-BAp;iA ztGQGv_o`LEzwxs)k%$S$k>br??Xck_wYF=96`M;4AeQY^4 z0a+ft$STpr&n|r?9*(n(#--?)vz6$Ri?LxSVE*F!l*!LdH#Xvdn8cdx6@(%F-?F1s#8ay>la;j^x=PoG zrV){_!yN0^FWSg8r(p`PfsLcjrp#0h10Nxm3C;xl0|v$`#y-YZ^Y1ig`310Qy%BQ# z7tQq<&ej%yxC?E2_+1wRdEn~6MkLVZ^(Jl}?8n^&ezvjl3QZvV^A&TA@C+18*UXRx z&_P3;ooP@|ZF3}2fW$4gBGd!tO=*hkGe{Il_+t4aD=JDzFQPxDUN_cCYX;MpROWER zA;nNa2FSHbEMyREN239bddOm-kW@p|Q?e*Yb0(c0YNjlErlav{#~bD{iM~F=WTx&I z=v(g_aG=Y26VOl)6Mr|Hbo)bz=T2WbeF;A71;Uj)lI-nG zh7z4FM1gg6CPH)`?{Fc8qN^kRmk*tK=+r4ltaa#ROPZB$SrN#DR;utCQS%D07K#;r z%oa2j*rTKvDVr>V^-HXiUpM&4z(p9R@!<)T={^ogwYu1=zCs9(FEScZfT_2FqyD2V zh~LsP5#stk{%&NBbzxg@vYeWv29pt=PKK~0#OR|vWU8rc;AWnU`jH^p)8TWT^o2hW zVD7(12E#pcgU$_^IR*%OQ0wk+yPprGoNnMjIy>_(HR|+@Fv>Z8<#n+Am{|m0lG3UG z91G|0*$`RX@7pTl=DPN##v&_C2wDrPr#0h1w9m~2Y$c8z#NpU-lvet~_H29TvGDAX zBJt|1O8{#t*z+~c-Hl&+JbZMPS}AV5DL?je{tzFR-~>w62q6P8qdDoYgnma%Y8O#%CAW=sm&4xP|^2rA(qjO2~nY``XzDjNT>e zF_lES7Sd}swT?l~G}#VmD!0pF5Bq#qd?UV^4_t;p@mMB;>#}bIuENEB0A%+`jwXsC zy#r>&Q7w=O7*?A_$d1cEL8MV+3eZ)hD!gBlna$OV-a)vnpDVJ;;{_&B4pSr?jH*sg z#Cqei16FvCnr6Zk)6`0Vg92{pAX=k?eX<(jQwE&nEc-9+on2wBcnL>uhe}V zsBUz1u*hxGQ=M)fo!776m!l)y9m0G~QA1iiK4amlW@c5VlS9lHL=+GI)eW^;jYjiJ zH0BM^3bNwA5zSziN!E%iF9ZFxWge;GpXdyrm&-soY=TvA2{Z)sU*a9$CAoxoyFfFG zZMR0=Z+r~vYgZ!~@ZBwDA`B$_HM;uA)m2! zi~}u;e7(x{#y=4Izz1Ug(dQ4xPfm8k!^USXhQn7_r*(b62**1nZ-|Hcq8GzQ!WHRX z8L!H=LgPA`v6cj(0A1VFqKWLuhEfau{7po!82Q&VK1)Yz*}%!hgpK0NT&6+z`TPsC z|5~w(^9^nrATt*2Ww<2ZU&edW1oOS{-+43t-8gVv=U!vYQ8T=KoS=5JSM$Q@3m={y z9-bb)#m0NZb)gypszOisVP9rIPBipd@~3leHBSdwKlyej}J!wmDaF7IRJ zo1B!E|JTI-VxwJ+U-3G|CdOG8J3t45S0&+%2{L9N`aE_pK43EDtr&c^zmug*y=i=0 zUOA{8T#@aAKPJCHj_`9%{DKagmZt`jR^S<4BpU~b1+eQg>BZjnzrUB&8&C8aMlbYZ z8-tvzxH$SwvfsiSA4cy*dD21D9T~Z-M*QISJp6vJ%7Tc^FzFUG#(k{7ktUt)oqI}$ zX<2dz$mRpBbs>XOWsd{0bmix+5*66-)cN?h-rMI1&SevOD%j)6% zXX8tPR)=cI5$NSqt}qWvj4U@r^)i3om-UtW2fW^lSN;Igxy5@ij81eP@XB!e2VUWt zogy>gP5qBPb}e`>-XOw1S({d@D~u%&}!(ccfV-*I}w zd?eB+M43qIpg?xVkk}IgMKBQ(n-r&e{(2-FrVsQqd$&F^Xp9VYcL2jRIAZV*oxxQ! zUPmg<|1Mf3-x7((Zj!oIW&JEvq_&4!-dm&8lN|2Z{mCfc^?UTyF4MTobPd$MBW}iVSjRbMr(iqn$xB?v90b!ixK~{QRmmIh-G! zBvZXup;20ch`GZvj#|wzGhBf`fg42|GxBc-J!sCJ{R`hSKUyv7Mg4b(-(1{@AvG)I z7ng}Ao%(JJDd~Y|J?i4t*nyxbTcnD|rd4Dd1>Dhb?zOS6cSrmm?Mo1ma%|2>#vxl~ z?t<$y1I2D6%I0Xc>#hFC+!)hzw;{ zVBXp@^T5*L;iNh+lGu|-45&$$KG`Tu>iSE+Sg&^y&G#HJbf5nK(k&lQlLOvF!aI;; zlYNIK8vlh2OdRU-SIRj7r(2Yl%a%-exYY0dsVu&$DS2?ji&Vp>(ti%r%RKUPzKG z(yAjk1uL)LMrFS|6mjsPhtG|M-ik=KV%^xPh?4Ac6pm4n^hbC{AjFNjXlZ~?J+!f zj4%UgtV~uQh#62>hvTxy1v>~At&nQE)JnxQCpYyft#NBE%B2pu7?Oi*V=Cn`yrcGd zSi!-vOu{-e{+YQRWmT+&_Lxv!7a`hZN%5)5Fby^>&&oI45VJp@q8j{+aD^FmwB6%` z{r8;Yrn<0fq4wvoYto~!&+y&%!@tLl=}TB^Hho3QEvr2GXw3ewM}?Ek@#q-+gh`lP zj1_4|cT^eF&AtPw4;6whtR`Z>5u~tnZAn4>}qWlkabyQ)mS%H zwJUI~1Q&PA2QVY3|5I)XrK|`))K-l(ZFN;+MQydQ4!K-~i*SXcv^M6ZfFTGhlN&aJ zVg}I0OdYZ*>pHC=z-Kevw&(5N0im6X3O-8dUs1|*NH%|Py{Exr79^%=-2;zN~OPpar=A<7wb>x~BaqRKgD~B_4D6i2DbdUGkx_IR7yN?{@ zmw|_v$}AiM+ZyQCABWuTB&h=R6zn6;0=|6eY=;hgno{;&+BJTQb`t&0fZx^l@6x27 zD)3<}9g5*yls-l2uTk1I-U9d=K$nz@)oT1v?J;54iSa)=sfXtfLl*Aeh~4mO`gb74 zA2VV%tY4Ghh;lVph3=(Dj3j2uLRW{7e&5l5?S@zl4w$rlLu_*m=xG5&q`<0T6_^X= zAuFchbJTA-$d@O@qdcPMs)KqvQs*%`g1aB32#j>M7;O-3qW*L9?musi64Gz}nT3R& zZI3#`DU~EqA}W|bz&Nu)%drB{Bo9;i`Mr(xy%YU2i9?B*{>EQ14Ov%12#|4p0z7n< zCno$eeSI_j#vd1p=s+mBn{<~0jss|AOZq%NOz<*NcYLw{rG5xw~GTRD?Yz6qchGMqBTv_Y6 zOml$fa)a!F0>bI|TMwxduP7(i2*c_SLA=uOQll(%k-jZ7ai@$5hSwK$lq9|c$!?#vZ zN=VnHFf(`NB4*`7z|$QU0m#) z>D)UxxwrG>Hr>M1tus>{F5gd$1}}{UAMf3>r+4NI-gw5AYHm=iQs1pc91M4-N`OKA z4h63O)l_b`HXN5Eh6)I74@!IadZjZX11c`<{L<-5%C;3?QY51Tz{Gg~`dHq+BCR^` z_rDwJaNYOsziy2_8j2|wv4}Dz@$tm=^{RIEhC;oat-jHTYU^v#4s|5#!Gkn9hR`lF z&2?wwLX-zLZ}c3p4G`xOX>Lu8^A!6hk0%d?hJ!=C$=6T%5@9$7cgXwMaO0m6=JJZE zRDOhCiuAa94)pdO=ymrF@Za41!m^owJFbXck5)7a%>H`qfHvCS&4|++t#m5*j(laX`$xy#}u9ZYT^_q%CD(@ti67e8`ZDY%1SR5v3^pU zyxNZ2*+YJj$cdAjNJXLmGqio96tvR9D8JEo?{ePSfxy=&mW+Fj%#OvQ$^0_Yn}={6 z>bFnMQk%?=EBJAMq# zOt^Zlr!yW7;SGnUwRmi34lc){0LC}l;~96le~e$@-#R>rUbjfAP)zVN$0jUbZLk8o zKFEM&DJVj-IvZMbcJ|mpW-2{h)av}eoSoe;&022u$l|R%HfnKRkQNDzIl%#gGv&&?GK36E}Sx)AL z@F@lNdFzDHNSVr@v8O zU$25g$hvNtqGbY~4`c!%D72}HfZa1&luPx{q3YpZ6h@nfzTHVEg*RY7#Ks{KypRhu z=Sf>!$`ebLt3p35TzAa@ccc4UrH0O)zJO7^;z_`X^mXVa1k{Olj!!8uW%6o=gUGT(adg zk_H|R>R3f99oXK=*331Ntu;1ksafX7Yp`9?bP!FLIf>SbGW$0BR4YHqE+iM+GCJ|3 zW#Gg^p`V@3h5WF6s+U!I?pR~fy^VjE_`-0E&ERF&?i>B#(c$40*XZjWKj1T($Wvu# z@qRu|pknPdMGZ}~C^FZt*ycnQdeC398kcRSL5Ihc!I%dj%!Sg3UC z@imvDUB?D|;l{&YKVXh8Y47tzJR_A%q-qXSy4>D-h~TK%R8+lL0=G=b+ht&dH2jkIRg%!kQv+O4D_xj zCND#a`2tMhc{V=Xs~SbCoZhC*<{zL9B2mODwGPl1AhMYUy%$WTSyff&S`OY{&VjEL z4m|AQlZi7wtft&UPBp+ny{YNB>7~$JS4Q`EVBKbdOKzpBPrAeb7IJG)YYv}yy9%hpLtpwVn=4-Qhnkq%DD$wD*CTaqeP zjW0hC$qWTppfBd%6;-VTy)-SN-9wmNRTw(^ly7Vnno@A(Mk9Kf9Il@q~LJn!Bq5Ofg=5o1A6=DT8!Sl7JKcr5|`8U9FunG~ozOljkX z&6i@am&_L_jQ!;oC8uSX^GOTWP(l|W8K`y@_u2Ubos^e;0^D=oGOkBXMvRR+S>O)+ z^sA>g_U_fk;Tl}J;|~4QsTS%G*URaft=F=!;X0zWA%$)DzW{VL11C(p{ZPeFIuHxF?)j zoa))-9h)#a8~>g41jGGZo&VsK1fMPiDTIIm;VWBu(JXHRCTDpAkWBJdvhKyP@qM5T z{nLlx;h7^c;Pv3stK%5HJv%xNPZ{?A^q=74H$E5{aKO`teLBqoMNTCUz1L5clRWqy zP6AEwXU;aP!XgQ)w?Oq_Wy7del_DXOcCTw|XjA2nTqzj_7*DafVd(n0VVEQV&1q;< z753A+&*I_hg>FaBzO{6Cb7h-GbzXC_mzenli}pdVu7F8!(HJY!L3QO9q2+#P6mkfYunQ zmr7)j!2ospJ{k<0ysSGY{yIqeWq$~qOtXFj<6)sM$q$@7`GEW-{mg?8UWEg;1{c26 zD0!dw^b?Xx_-2^ZNFn(119%$Ujrf^f)eNO&htz_)G|AX?m&rq$;%jb5N0JH~S z61*SWeJ;nJz$xNNlQpVUe@|;J$Z_%Re_kx@*;De;n69JeCb)O9FkV}{L^Hvy3!~ZH zS&q&52;l^fWf1z%W-T|CCiFys)%T}m-4iYq&BTkvy^F=;i?L%D?>)MgJ#c*SSZ?x; z5?n7GIXo9LP919H`8?E9vSg0gW%%WXVlNjTfjie?zf-d9LmiS7C46s*@o`U}xs(Y0 zC=?~AIVs=?5MGdE`4CkJFA!*h@UU-k(wFj0O!|hynMhf?AruP*0WfE+!xvCvAz1d8 z6m{7jkw-@4Fp6N3{xJRox3E76Yp7lcb>E4E<(=JlyQ2O|#NXAmZ(mmz@;N@yBV-G{ zLr&U7Qc&*MZTmbZBEmG^+RqWY%+KwVOH~dh&i{1luUc=E>NPS_UaJ#)5|hYYxk%UA zP8xM)N`h}{Cr6|uN{)=!=fLEL4wKNr^KEcItT=dJ!PMlRUpP=`)E6E@sx$pA9+AFp zM9t^NV~qCd$Zoi1e^5&)nGT6nEGcM8nj-BRm6Em!Zbd3bO$YCKHIk}s&NqCwlz%dq!#vtgQGM!mJ^*O~`)vTORcLSfpzTqs3N(d)imxqnQ> z4)0KG9g4kw$6}i}i?2ulk}i-vI`lEyWes|POfW$(Ty;Qb$W5TTVh;S?OOdLsDEjK` ziLPE`CwjY1%mV9AvL!oDne-`58Fyiu+&z>#D^A`xSr-ZbCz4Xd94i#Y%+R*QSf$jc z=3&yMWMRV2p|M74_w08oA7k9Gf^=x_cu zb2F!-RoXy*KieJtkGrC}qL;@Ki-Y!RLGkQ)ybx)GN-8K@A5kS*CCx$T`bWaWlJK0G z`$+7ZyYaQ7ZryzjXoCK4thPUHwv>w*_dPdz{yswz+7>a$Ml7^p86CCM>%6=C>f+++ z;=9}5Ae+i$j%PB9JG{u9<2@GSd?0Jbdz1@8yvM9c@gB>eQYlmhqp;ObiDOg1DXZ~) zqmI|g2ESvC?iTFVyE)<#*H@-OR7$9T)_ZD>%YQT5qPa=q`y3N4;6Iad&7(&*L%UV> zjmy9e!m_d6JTlr~-u~6+Vc9OPi8eb1R_#kIuQr=&$h4iST>Z*xMk5UB$?JxK9`+Ei zmOk{RAO9!e_|>B$kxWaz~#o;?~+}3eG1m;%te3^&Ji!z^d2DXx-??_GMj5H zEX_vk#B3CfTJaY`ZttSSqip5rYSyKL_=P0Z$Er{>D#x&gF4*n(s&R5(V{PAY%Jpp* zO3d{j8tg?j`ZYAX*S?X%Z@!T9sjBbKfLIAC734YWOO_*jDk4)-`P_ukE%W?nIf6^Cy@k4t?4;ss0P;q!XnHclB%8UBAHrCUf z9|VupxynswGW5V%Z*p>CI5;O-nA$yX%v!-S!!Y%S+E(p$qf%VOQ{g+qsqToddarV0 zO-f-U*R-I-PkhJF!@&dYkxoF_}3p50+Kim-gXOUb{7 z54(tu?b@OIs+JrZOPb%y6T@gEnrXtOnhJvT1W#qUvOV=AtMC_6>F-B`|k35`u-{~v&bien#-S=Fv zCHD0GNS2_Y0SnxobH`HHZ*Blb%7MBho3IS^(XsL5F#{+(6mP4M(6b&eZ2XII< zppEhg>97UxNl>BC5jpS{lMqTw+#I@819xE#_mcP%3R*8jWf$zj=l^OP^-%_yO@b6ta-oj#XuK<(;* zIZ*ZYc1OKF^$#tKF2TovEQeW&yn!)IHcggmg!jhGuX7_(qXDW@1_Ue7D15B7MMaYW zNDI43X_r)-77*QQuQbXGm^|pLl?@Pr8L)K08e6=w3P;kFE4J-H-SXB?x2%F>vW9Ad z_*HD*0d|b$qkLVlO{8!H)bN0t107uhi>VfzyFy^eZT2W}7_$~}GH+2RSu98xdnS{> zbFfBK;~()tc!3o~0oTEYiJ%n5<#wZ}kb%6LQIYI6{)v~S*o7M}u#Zv}AEwcC@8Q8r zdgv;ZcCTfxN7{m~unlXj-34{tgb|R>;cTep01}%J1VU{#!G(M)=J!WhkO4=6LH9`K zm1Q}77QqB+WuyLQp!+;L^;-y!LefJ!^GkPaG7QHjdAz~W<5Bt!^qnBnQd(6AeCeEHs zo=ZqVIU+`>KnHr-%0%l}88)WS1C0rVvI-RT3YKc{r`Qk*J_*Gopjap|WtGSgjgsW~ zN{}@kqFkIINo`7MX|;1>nIsf!*(g3S2(`ZhtM&ive$_k_>J^&f^>+JzbrrvQNob6>G~3@plJUC3 zMYMDTD9KsrWXmoF404mu2pLcx5D!ELAW>3)02>UydMd4SI{V+ z(j90XeYp;x;LCWt%u}DZ>Iqgu1>CM@m4k9EFeYiY60mh*Bp-?I9NjCYP?~48&5FGu zc^|B@@y0hHb!$K_-h47GY+s9V44u7WOrrVq$sH;p)`aAu z>6Y(uQx?5#4gQ{r)!=V!O9NC${qr@T?$Oq)y->kM(IfSc^dnC=_ur+_!Tz$`vHio= zzzL;nFlnc!+*)FR`q2FKOO!x_WbE*k5qQ7;UCX0+DrHm4*DtPKjlH)Jdv5#UD%IF~ z3bCCEY_pJK$a0d-ju_D_iMC`CZGr6^dtdaPBgJBVx%VO1;&j4p8Jj(Fk5MWb%lTOB z&~iQ*jayeFAy%|U3iFtsu)-F$foXHn3(iI;^zeH9LfOGe}Qu8)#-zh#6Mh z8eaz9kcFJmX>k!*%SaI-sZ_##Vi~H2!HUFnH1Bpvz1$Y75D~|qR_34#DKV!o-&u&Xa|KA}n~o$hbSoXb^(Gv;?wHu)Up%tt-(#Kh z4y0mJup~~!QUkqA;)(;U$E)ay+@lYrK-JMB!-=;CnjsaNbUG(vDV&WNy!URl!Twqb zS@u7kY}Nw?wHfqhpGTTWW`8L&?@Vv+mq*UT5`DqjjaxGp5;1>o*%grSa<4y@xRANk zxV6705j!&?M1rC|6+qy15}wHD+>usOK|AmY`1ZG1SSrGa(Xz-)So^$)r{dsP4atC< zWD;t%o@IRmFz5aw$suYj>``Q|@SNA&OSB~CGV8XkgVrW7`lMia*A@}j299O`HPc#~ z>R0HmjQxOSunis^4k9Ndo=+%=?^FMU=OYU>)Ar-a65oy~E8KNg%rxHvTkNinljEV~ z>?C6N5rQ*ePj2UD!EyRFWA&j&RNXW;WAklYX?wX{v>%!$Y1<_#;HT9vAz?Lerb6I* zfWN0vC88JM{U9xO`jeKCBl?z{2(5-*VG{8rtg7pZ(x@?s8b-8_c92y9MW4$ymmjrh z&P=4qBaawsYXIGBnKVO78kb)sH5)5Jwd}SPo=7HH)l_R`YmY&*)Ae`qkjVsT*jU4K zYReU75Pxv5ufqg`MM!*&DlrZB(FtAN+3R%Z(|>`x82PQ0*+0S^c+}0QT81~ONXd4@ z9*wb!@oUm!@tdD{Cicvq<9UpJdh@S68+*3R^C!+de*!Q~Z{vDHR2jaNtGcqu>n2o2 zKOa-y>~d2pmqm$1II!$! z7^brE|69-&;G50#DfjdRo~AuUHk&&06K6(g*uN6&?hbZ;{U^@+1S`_m-`|Z_NE*Yv zV5X?9wxrrtV{o$;jBZ2&+1;7U?%9KLdk^m#oSr;X z7@9dWF>z=nd(+aAV2NG z4<~eGesbEeGJ7zzIGvBj5AU6$VjtGW_e_Qo+F&R&s3k&^d&YGKyYbM>P~p(z^k8&p z>831JM*6<{57>BnASbou!z%Hs+XLsEffBon*=*-Od z_(XP>S9krp>~62_y=h@DUHj$N$L|}Wqv`a>f0$0spP&<|d(&*)$2nodogk}|IcY)K zBT057ezzU^!EJ}|m+>lGp`dRRvPb5j3FhXTVVDgaL+~>R7YT}_Lgz4?i%9V6CWX=E z?s!P4KwNydhe_)g*Pru0c&hVQ{!GHlJW_K$GO$EM|gNB86~;KLZo^l1b#@M@hrv^}PnyG>RV0>B1tbP>nh{9+c$; z!ENrfN(J~|eWOw_&3~z+*R@4wB8{}+-Z|Q(^!vsWfC5@1WT+x0i5!>D)0JPPE7v4C zVfq$%w!*am%z`J%aXd$ub>OgoJ^@YD-2Nb_B{dLvc1OZmIIJC{QdnPb5F)aspuvW_ zqtRqnGWvc^W2;n9o5U}=Rc`JUbRnA}Zuw$`g8kVfLU#&ZSQ@`NX&DBI27%o8^vG#V z{!kc6Vvb3P<-S{Xqu^#CHokZ10!VUY^djKpzXEtvR-3il}LJuYkc+HBB2vLvppP)G9@3Qrb06DqP#pZV~!H zO~b4<#18Nk)7+%#jltXDu9$@#$c&Bk^Ote{CymLl3hzd@5`IEQQY zTfOa=$8*d%wl}e_GwgKU?R3r#cAxFu)fwEINbC)Eo<8Pu9`jW3+GBYBd9Ixtj14N| zF9a7x&nn{zeBL@XKE6IW5?okY2#$3 z`FiZ@Cs%cwAVs}?I!gs7JTJyD#MbfnKRgRVj3=Cpz9Qc)$5#N=E z2jU0+M&r*e(@DB*+grb_93cq3(sT$iacypu_hqQW7?gRDDpFiuXOd7JR)fmqRe{kf zl-xxevxjmtE?Mht%Fa zi0l`N_ulgP?QnK~p${;&`}%tE##@+gJJ4N;@j5sp;-I&(NrX<$1T|`B^kt-3k@5A)o)vM5OhOq=2NVfC zBChs_k+o{97s&&M=_S)#=SAuDy3WneelR0b@EsH|>nLJhTBaFYR!A&a;A=0J7qU

wF7DI|Kx|V1sBQ9FYs>m5C)C zC^&s-;)-p5xIz9`m{?Ao6W*g!7;RwcsCU8+^e@V%X|~&{eJJdJ*dgd0ikksDOa=7~ z3X`}#w+*#}%7j1Ga7a+*LFono(N_&|d8I4|VUf%O5CEQL3WYhCZt{45YBo59;jgIV zlaD_^rk0DgQ%ufSz!?v!PKV-jMV!4ZkLGcCJ0os~;&7^r;TH~f#OI+eTs_S%P93=2 z@%OCCdX{OPaQL0BwA<0;l!sidA(yAi;ZD1pe&%(_tRKE|Il8>gL6>XL(b46AQ)jErfZzfDG~EcjEKKyQ_|x>K*4CU8#wYBq>Y9>a;~-;fj+ zFi@1B$R;-#%L>z%^UJT=5yBWe2=b05K0$58SShyGQY2Nv8EyFSV1Ao;pL3{0w- zMmsvk^lbz}QL7m9?H~-dO%vdR{XCrG>_%C3KE-7TDr55-8vH5GK6VXw-A7oFMy+y7 z<2TsiMbWR2-sbjNPPdZUqTOW0wQW?JMb1HX!FzlS=Q5%y0n`(KMiKidz$z;%#g&E6 z7Ws|<#qVnTEvBqTY%!_}>3Ld62wd5Nb$RL#@IHrP1>k)O$2IoDyDwmLi3_`96GxYT z8#+3E0|;(^z)0lIHje{|kyXSNZntZt@6wFOD3&kniXH;6f;Q_jJGXA~?j*!(+fYU& zB@XxHhXK{yQ7?jE7JTu+A-uQ&N^=EcsFj$GJ;MOWZ4JKHYpqBhbsjI2Fc1<8>s!C!1k~Z zTSzp^Azv+6#u%*nhKZEn^%|*(H{jaD)tEdLmZ>SQVowIUx`N>9*bCsA5xJ*1J~$8A+47~40|8+y`ra<9Xa^SB1wJALtc;?!S>*ip|U z{=B3c;OLgAw$7iMvyD)H5`&5#$i+sdme7I;HS`;l5vxJ>AB{z+`xlF+_fZ`skA%Rg zPdKm~x2^r$9$heiJdRD*?HwK6D_{#6`ns-bzc+fC$)`tex%COa6?_bF1sjr1e~>pW zWTr#fNyjRpo1|zXWD_zLp`@alnyFW5wk#6i02fi!ZkHk07`fpnOg1_SHj)fDy`W@N zaq<9~A**h)CLRucII&MY{BZKN+a838y{boUyDj zAK_mf=^jCxwvnGdzl03R?#L8ccW=6# zmCb>G4o`1ltf(ryU|2gEMN`uQ16BA+3k(!B{H_~x0ZKx?c(IqANBJjcPH*SCj>fvC zP4r&8C?^!U2ani3>n7>{>-86r@yV)!Mjzi)4v3g-#RsTrA^6u7W6e-3)w!X;pJA9L zZOAi7l5Dq0Q^$~%a?&Eqq;0nB?b6wh{XHMARI11N1zRG1YA>aqBE!koefjz4zx@0M z=t{M}2LOmL;jR=lvO|8Fj{o2i-p&@E$NN7?Uwo5(^faZCXA?~wf{{JAll@=-2mvLF znlv@lPGN88dNI%P`Mjx@wjs3}8}swPHo@N)<~gM&qP~rO54dkxGBOmg-`cs30bNIN z_R98*#|zd>S(GG>)Yig*N}_IV2kPB#&z6SXc>?6pCt`a63uI|R(@=WJJ~?**J%cXH z#WKebVE9=2T)p0~XUvO|!anVgC?fR$Jtc?d$j;02{HQ6=Y)AK!?m8G-cyS?ixMTdO z@mTy~e36zE!u~TcaY%<_3-JBh#^LMuCvCfjYZCT*q_8D7u0F*3l1!FI!)MK40y%n0 zr}cdEoOGo(fY(?B(311ZBL{CiI0Hk^O;U!c&h+`S-Xll6XXmGumZm_v2Y(yDWkfQV zG`^z?aT&PM!V27OF^&~6Uk z1pRn|Qx!ByEF^VoWsElv$OYKfVy`?9yYWL8#*5*{1}5Gx`Uch!d*uzWQ$PR6tA>Fl zVK9%2zG)%?t)tmW1E=pF8@vDXz{Ly16`1!O?pV3Qd-%S27AKD2`xV26-psu zF`1xugKFDXU^~%7El{L9+h8w4kBo`h0U=JjA1o%aJe;6lIB1&8H0c@G%XZj!?425_ zpR~qCv4#j$B3;WdkG9gUwQ5~l?aK8c!vAgdqw8(v#NT|M6>~lzWyzjm4ydEOT%N$^ z+yZPe_t@vgApvW1@;B|YZ7Wo~2GwY4(O6kCvDfI4#zzT<1SVpTOx8)fYwDn3uuLwf zV^!fh9ElC+YPi29!5$`nBFF^E@Pf?s;J0g}gp>a5<2rI0ipn442=deW&_TlE z)w4Jl8a|0MY+u+&NTKPA$64QBJV)p+GoD*@An7~dYTenu7=jW-?yvo@vC3-wqBzv`| zzhl)eJGwJ<$C^Psja!xwB_Z_H{&^-iLxkN;iG6lU|l0m{{2I zNv@xzjaBG9HO!WN7DTZoz9L&WyBX13rpP^z)AcaLL6g26o;cIX#qH31B=lk0O%&td5kyw~ZxnX*Rg(Nj5^K&!`KGj%=8q=n zm-jSjzk+>nUcAaaw1kt=1tkQFd1!D1r1;@j21?mGxetA{XW<5b#Dsf((ig@j3;QM@ z>=#<_B%=Y>A1L549)kjuKe~5i|B-v{IRYVHH(~O1N-47FF9cGw`pLw2qQfRgh?>51 zAV^~84yQsZ`oKK{`pOOd1LfEoMhA3da5D6rE83NP5g?Lp+jUJsN5==o53I(@w^* z#_;M&nN`|LvAMLSO-K9lI$`wdC`@K%>tPjqSB6fU3MCEjz`Y)2JJw3zsVrfDq?R;xgO8Cbr#d@*0S}K)`)&b>dw&%&)lYHd_c^T%3EoDMOZNPsS zn#(jz-1v@YzqZ_HhQwT`tzlo^*f7hD3N<$Th+ZsNT#3JIK2wpwz0A7Rdhc{sFSns* zZERz%?L5_X&Il5j4CdD{G4OPQjxb>rWFYB?((RA=oVCI>*o!vSoz0C1Gqg&sH}ii* z6lsur^#?z04i1`_FoUSkcagvT?_4-`>;i0(#pPYKXt6ZT(*d#qx13%J*;b5n7`t=^ zMpl`ON`9|cDEE8)U(QJ86TW@p>Oj)#iDVofin1r7?tG6vd&(RP7kv6Rf`Q5GtBy@AD-cnTW^xp=jgXQTJR=|Ak{qQx!C>4veXS!(u|F`mQ~Z1 zrf4FfvZ|q*x`8FaIBPw$0i1b%xNd6j$DdT!_0|KDj6fH07@X3Og_gB*S$b)`RYHkm z56s+}Ev;?Kq$NvmJMw&X8y$i57FAYWjh8*py_1PRknCAbTsWIQyKDEEVNZQEQSS33 z192}|!4!+T&Yszw%aZQMj`8K7HC9c^Fas}^&q-Q7OtK^pN{$nTHX&+_~vjF{Z($RO#7+dO6XO;30CQ)eFV>fnys5kK7-q@#MMAD*DAwt_$(tDbNY`^Q*Pm0Krc}f(C3R8EAucG*Vb3n)Xt0}P z=>=qeSzBINS*{~}52XETkFKmx3soDs}kGO_9L^mXvCX=l#0qbq{=8UF5Vj>(WVL#%W^Y z7Y=%p zw^43Va~Qlv^mh2h=xA>+6H;QMFd=1<0VU&fJ32SHJw$hVcKf@-f&OXDGp0rZ%AoA& zbaX=dEI~bf4eBv3osjO4o|4{+qW}uv!gA^w+$YO}+6oWF$$^U4>|4p=x!L4mY?Bm85v4R4^uc)PsVy)4_k6hCMPrVS%B2N#h5%9 z@bx%@&c0sd{M_;Tvhx`*BO4vmIvkF@g)v7@M+b9s`FchpxvtJ#E@!k)J$m=i(C)Ll z0|3?Ibv`e9T#4z~$7W~Zo{mm;bYk*>$%#QH8+WnAJ^SZ99q!#n_ZzZH_a!IyBM6&+ zV8FkpG?fjfM$?_1j)@y%6Z3Z+j*N^%aB5!|9qeL0?~kPC9Zq+b!x2dB?)p(@G&VXn zb?DGkXJ-~V9)yb>lD$sm==4kuL?Qzdoo-J@R#n-6I_kQ_Vlk)O4Pp9?gHEZaK?i|Ay338F_E#M>A}lZNJhO%zb8TS#=z%>3i|r5nd*aLmq( z-?-HHvZBE84)$y5HlQKdwqL781gpc6Wxz(~Bw&9VaU4zSzz))*E#TV2L8o$LhYOjJ zqlTqewHX0%@vv#VYy0!TxqL9cU#X#p)MN@u=qjX!sg;SBr39$urEGR7V}KR~8ApUe zCQIi2frfeI3NX4gxD6AWOYe~+_9=McLBjS$;hKk=!4Tb>Q=877YI7XO{AI8o4)n2p z-}}2!`qjyt>^SHv{UGVmVTshhWcc$PLDxgRUi_N%ehU?#rek(+4v4PNeDpM`+J!fb z)M%a~h2sNTQF~}e0`d}Qk;sOH0zU9&qr2=N(Ea1y-P!S_>2zQq6H$`$T8POWkpC>q z8qii{e}o{)%`~_Vg3sVM5O0ypz}E)`yP4Ay&uU}G0k3~G;{QXAU+&=iJD0wbz5-v5 z%!3*;5Tk>08zdVP;m5#Kj8o}sqFP@+b|F54wQUzsP$77h;>HGPYROH9fuLA}zbhL3 zwfmQGlyrnz2bL?F4~0}PuxZNYm@<7_HoUJtZOX@|Pru%Kb@s*^X90cv%mebV>C^Yi zSErB3`{C=idP@(Ky!#P|-P@)kKnlYyV4M7--5>Vee`?e>cukP)k=rA;Y%PE?b!0iZs=-(k4iYR;=3=s->K=!`|lb z9`+=$-#@-*kDLsmjy9OQHny;Iaj$1F<=vH?SX!F+d;R3?72?L-dO(GPfgg76(I@uq zoe1_Xrl~|#((F@5r#DFg}%Pp8p%3Qpd`A6=%RWD?2zb$iY_6Wr- zoqe2mW{qe`ova}aO3U!BW3nfNYZ}^>(FzCM3qLS5;Mzt@UufR8m}uL3tUY^^qubT( z^sx@7+u47?>Kg3|c^r&6JaBl192G9Z{d557JRLymR3)7iS>4ieaXOsOW+A)2 ztY{b-w69hn;QtK>)^!D6iT|y5+C*`>Dtf0fJLasl_t>brcAh`Bw3HejPbCr~Jv~2% z*tw-yv><2o{ne%6+&iYzsSAmbz(in;P;}ozcIT4RWz&%2s1R`SB}RHiLJ$lwKA+HL zTMNj7oXw5LgxR5IBCD(8`x+)rEHpy+AJZr;uC8JfoW_@|t2AnwPG2RQjz~@^k*pT9 zpESd9<|!ZICX%#d!6lEZ=4|DzQw6It27Jedn2NZdN9(eB+TYb5Y-R&o*+Ye?JobY?R5JvgcM<)Dy^$@}fuwZ^Tz)uqxhaiB0Dx{$hGjcG&oLIUm zxV)dS{ma3-mQKurZY6u5|HFLpj#{`Vm z0kTZrFBOq`!!e>Z)iUsAU_*ie^fl05Q*j5ZW8e^~aH7MK_hnlXw=JH{HU+pUDhhrn zJf_|d?Tqj4-5v1jV99i)qu1Bxa292Ex36cxanqDD6jWj{CD84NIKs)1Ty7*i^()w& zstUOunSmk;ft7tI6v~e5>f04q)O|k{@b?UPy=vc7SMQN7SJD@ZYw>OtW@_$OZu&<+ zBm^O)44?u+up`P+V&7ulA|x5YpJ<}_Wo@$*IhRGl6n6`WknajW-f_H^KdZ4gnWg;Z z1Nv-$v6Iog-GFn_ANvH_r%c@*<)$g`s&UH{T?gBgPeu2F?`^1ih-_5ux;-kQMyO=_ zGs|5RfmkECFAY_A$8GL?5)$OQ6Vc*ua56qV4nXE*UVsXcvN2+PYk6t zL)K6Wc;KD?vE)ZhzJRoXHV-M>l&s3JahyzsmhflMMRCAix&MR8=c;cR)8X$P_6yM` zYDMTgBv}iyimvEmZ>i}hK=m|^M4u?KRb1-@GR9h7n8Bc$uHRGK7tNZr&(TwYAcX%hr@gd5{?;@%R_=RkP1d2kg)pA zhhul?cgGKFhvRqacf}6h+DWe>mx_Bc6eoPdLOgHCYiMco9SIGwQ(NgJo>j1>Zxai_m1Bo?*cl=(5 z#NJGC=eg$tJUFij^lzEd8z{r$K3oMD*X*{Hg9lfJqls{6kEZQWjt2H5`IY2A^9pK`W(c6r&6!=CH#hzow9vYZ2bE zJwpptu!UA+fBQ{m#JzBRi~Y@6A;|WPLdri(5#Xr}y7mo9Zxm8~g-vd@C>N}M(nOV> zlO&F5&YeJWe5UcF2uXLiId$hkX<$=G$CZK4oK3f)cn3bgkv9DE7i+#bV=j5`scz;X zCLVU(r#7FmvMZs6UiYTkLu%6HaJZ7He`x;r?%U|J@#_RFbPJ&i)d7C)hCNdZ5t66& z*ayo4X?bejz9~69;PrXoBr`C*G)-qw_?7)3slE`iZd97s8WBAW6Fgs4J1Z^q$Hzmr>-w&L zy!(hS8zFCLVU@@<)7gmb1)BZX7h@B#SbQQLi=X`B$yjXD*;n9*uEgLBu8C))`4(bA zg*l?kX4$zd1F^KvI@kNmrp#2XtRsYP8GCrxK-b+mUyFF__42q}iV#&G=eOg2v9dY2 z2V}&C&dsse+YkJzW1x?sHu}=cY&=bU7p;SNE7YVODMq+KnlvdLkWL`|FUt@*5WR$Q z>S(%U3SvL2m; ztc5IveOFZvNndexcUz*=RNEfz3qkx7k2zc5~Nln5U z&QadCZ+=MAhWsJ5FBuyL=(jzwbYfyM{)_(ANw+JiS=ls61`$@U(hnuGQ{mSQM$^SbxMg<-CRN1g_Kq`v1v+i z9jcYIYk8YhKeca2v#W@tr3QnlUCDgU?$q@3$ShP39!49A{knmFVzdRCg*-Bv zLWJD2$a{dYO2!MB3=RAK&N6Ln;|6WD2nU!IYJS z!2u);^b$1&zfsvW#=;Iquk7e>^r%yQSJ2@Ic7|PwOMNEgb$EhKHVAW(C*8H?fLsm+urvU78w^eW004LaV_;-pU}69QI0+O% z1n<-)>@NtICO)nVA%tQkj`;9bi*sKEb3;O$YEv_B@8J zS8dKbe?S^_|8D)3Gz+T$X8EtzUiMO`?4?p^@f^=yr^i@;!d^zSKHw^4%vy~H) zDOinpKDF4KqfpZ(J=98wDbZDWh1g4rtP;VnkYF?S8Je6&gMA^3!s0mu_Z#zo`VUMo z)278>Q`EVsT#wd>$f`?aF6Ulp;zne0HSCV76Y=2HRl<6LI*(Lm@QKe6ZD`f;%5{gC z+K;GJ#)d65>T(}9qmkNLF>|s~eu;0P3Ux@k=JTHNC-fuN>|yhp%o+Bwff}QGV#HY4 z5@tB)>Bk9Ui8IR)$Gn0;q3^k~d;owwi6=;k>WBW5XbUkk!F zlyl#9+}BZ!O%$@qsnVcPoNWt>c^UGg1EV$hb0z9)U!8=J1T)m%&WWv#Z`aKs zz*J&-FzcDCtcxwrwq>WVTiL7ZbM_aPoh!<9gZbSy5iQ{h22Bk%iKrYZ#>wO$4L~1LIk+w-s z$&yn z`cQp`{?t&68pd#Ai}Bc$%)(|LbESFG{9^STsm`fs zsXqk41GH5E006LT+xFA7Z7bWhZQHhO+qP|Ym|cH6TH|+&jE#>SkNu99i;qd9PgG8f zPdrWP$$rVlse-8isb@fDAO?g$KVT(r2KWzF0wu5`I2+smUWal)2Gkpx0H(dOu1tIM8hS5%j=o2~ zqyI7mnXb%OW(9MZ`NZaB6}BV0hrP@G=i*!=ZXx%E&(9-#H+}|xT__=NLR(?Ba9DUP zW)qX5BQ6l{OZg;HY9kGhX3H`8h_XnXrY=_xs<*YwT3idXk=l0co?cA%^vU`uBah)2 zvyC%mL6bH+nRCqR<|nI&MO%%nA=V1(w)NevXsdR6dxSmP-erGq(m9Y5IJ2EwZf>`Z zyV`x?mGoM8+q@6H<?`64I^qUO=YnrQ^V0{|2O006LT z+qP}ne%sdBX0~nHwr$(CwG|v5AAWK~xe@LWb4DB)@y6gaD29E8&&J%w9>yugWybra zoTi2*r)j!rx9PpPlG$U{%nQtW&7UnfEu}0zi)vYHxn|8{ZEtm1M_Tt=KiCG?6x&AI zQ+pM=#V*)4**`g|I)*q#J9aysIQ`B?u97adYpLt9JFk1NJM5n8-sk@2>EMZb#(Um- z4PMH-!TZD4%cuEH`m_6+`AvS&e=krg5D9D#d<)hJ27)t!dxH-{Swc-i!$Y$|S3)1d zWy5-Sd-zGDeME^Ik9>%hjM}0^bW`+GtYWM~%pV&c+Y);hFA?t^Psf+WA1CT3+zBOd zFmXBYFIhWjND9eq$y>>{si7$)wITH=^*LQ9ZAlC1v*~}CA(?5JD?mlS07L-<7z4}z z)&iG+$G{gb7gz;s3U&j7;3#l0cpCf!m4jMAL!lr0k#G2DFa7eAEO`LjZC zVX!bt*dja^Yl%K_rg&Z|DGiiXNJpf1a&@_@oRC+_N94as6D6apP+qF7)U-NP-Kkzv z|7oSP)|yj0rM=dR>3wxV|6dS1Kv@w0007LkZQFK_*|u%lUfcFJH`}&t+qxNb>*sAX zw~g5r+xC2WzwL{+yW6krD6wPs4r0eSAP3L^m?xiHuZR!D z7vmCs27g6lBWe)ah$L~JEKLp~N%98yhpIyjrq)qm>Lp#29z?@THl{H%kzts#%xktd z+k_p;ZehdhEv_85oWr<-+)KU?--hRfVnSD8vET@=#gbxwF)kIA+Dn9VUd|_Xk=M!l zZ>9%%5${2uTHtlCV6b~|LGVZ@Tc~$vYDf!R31mvG`=H#Hc>3mFR>wUAXzL4B`>G4ry8UNrH-b4rrq>;zluNC z7k1{)08KD3UjP6B000Bc0I&cU0000000IC2009620000$04@Lk004Lae2z6z17QG0 zAMW%xE$&+3?hXy^?s@{wm~*7go5@<0wa<5cpo9Yo$SW)Zjv(N9)T^>QpKAUBUcd(b z0WVB+il`+O@M2m?Gsz=QeDlIJmt65iGre@v!+>no^iltgbK2GOJa9^_DIsOzhhUsw8 z5uAUJ9c-IkV~b|JPE5QrLpKXyk}j&N0DosT5CC`qV_;?gga6G8MhsX004PKOxB#p3 BJ$(QG diff --git a/esphome/dashboard/static/fonts/material-icons/MaterialIcons-Regular.woff2 b/esphome/dashboard/static/fonts/material-icons/MaterialIcons-Regular.woff2 deleted file mode 100644 index 9fa211252080046a23b2449dbdced6abc2b0bb34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44300 zcmV(qLaH4god-Bm<8i3y&NC1Rw>1dIum|RgzJoZ2Lrs zpu7QWyVk0GD*tRm1RDn#*n?jf3b-+JGsXb`o^K4<|9?_)Fopu#Ks7Vl-V09HrK0t1 z8~Zi}2F+TgDCMZDV{d4SjNq*5tBjvq-#O>6QvbMhde0G@=1>WT6AD?FYHu0ikega; z>#mApX-iw$(w6QH48JEw30FN{_sf5mTE?Y}D*r#_=EX+*uo1&#?f0LDsnA_;;~H3% zLxCTdVy;vtIwBs?ZoLX9$L7>X+VkW~9@$mBGp(v>Ob<@a910>RNex5OognF)o!ohs!So!2}}rZG)$IL^H=v$DKWnv|V>w-8hao zagH}G<;94Yj2XA;q^>=(%^d5(wx|WmmDKWTsi$hebmD*KGM53NIwPkx<@V<0<%C7b zQ3^@BU!oKcp8vnvoo~GfclBBJR-x#20u3VxJj}9%>0o@O93))a-xfrYnDq0!ZvFug z2s1C_1qdS{Adq{*5`qetJRqzDWxe|t4%kYf;$S)Id$m@mtr~kQIgrpbIo%ngDG9Rlp690_YS-ueT}jfMY{APPG@P%2ZPKjR9shqiV}7sVy`{ z0|v~by%6)`bN^R5>(}h9YWLPb5@~{z33et(!V?KjfUCMN+JyUgbh%bvyWiYeEilYv zi~`^ZS;_XKB%r!`_DxmpW=zm#clXua=#r zyBzKU6?hrq`2FqYh3EGz-A>NUzmpIT-6)K?&8GByd21|V|7bvg!|BpeQ1st7wQTh- zQdcdVvYfJt&avMWwy4fU>HOx+`yM_%esITg3*GE!fRiZVmevY}oC5z04;aqMhA1a; zL?6fzWl+*xE=q@(%PXC`>ngkGT$C>PuGS2 zZMmoLz0@IMc!&`)-1+7gPM72-eaBTw3Bd$mgjNV4gjN`nH#1**`<)+suX~vNnf1TB z?-~)&A|fJ6lqlsWCF0$$<@bLWLYYoFm#RV#0YwCT(`sH#fB6Slu3Fk^)pc*Gb)>IA zA-nI+4%<7Hwb-gv1XP@;u(M8*lcE1V4=X{;sOny%uTMRy_2PC! z7{p5Dv!l%*wV%8i(2MD6gJlN%4&434HC}YXtI+FlpM2Q4twt9{w4nYk-Ut6sX_!U( zf5p8!Pb^S%XdmFTu)gR}ULZPet=Kq%!{2oe>a8+P9c|k+c5U&T=RM7PKPX{+gg8WD zcvK@9+BEZA%{-(WIlKIIx9ZJzTCd^eDb97y@S?eA8A}MIL0DyBc>*xs@VLlRMZ$!V z*_w0VR}+_wyl`f46CWl~wnU<)8ZMIrq4CpItF2O_PJL~xq{TWP>h#qhIf|qKq5@Py zOf*ialDL3Mh$@ggs9p88P69INp;4&7&|YJ=&rEHqHF*oSItB5^TW5bbp6o(tNs-m%p#=hv(v3e?@xGt4L@*mnkUuN1rcwH9`shV5aEL7P2Qm0@9^aoCsw zXw0bi+yZXLdsnfDJzNC^5eL>TQI=m`1$~pl50)}o0j`}UaMwC-DDA5ZM2gtJv9`#F zEmGetQw|sTW>ag!tJvy=00=9g58EndtD<+y_eEf}SX1xjIGVj`iMKXRPy5W1U~3G^ zK4OeNuAEuF$*U%xo(=c5&?9-QZ@ScsXjc)?3YNPJJ>fl4(sS;}cGz$d$Bg)JSvi^a ziIc6L~Q{p3eaB%`>}#A@9Z*mFo8CfPSY^|77lWWN%)u*A;1STVU;>cpnu zg#4PI>d?IC=Hws;eZX{JR2G-x?XYB2chll@H7~lfYzJJf*Uer7RVb8gJ++DjE&!Kz z_LhqMui9$*((F6D+scmcfr4^bAjH$Xp|AI)_15ChduX}M3NNbF1(>g+1_CA(;B3!V-e!$D0dUfTrzVUEotZ~*77 z>|yGpeoF{UPMy^44)+;PQrG@$-5j5*y6yzAt|d*6PQpNrAcPW&z-~Uru8;d>X{2aj zbXZ3}*WZZK?O&mt_A3m6Vu!btFb(R(Z-odMIM z(19nDmri#pXLuC#A%lZqHMQG+q}94|-N&;sq;a~GPUoXiay~M}=Oa>dK0Jk0)~RTh zc$oqS%BYH^!pN`H%L`NlH*0*K$mqmhSi;1$=K|{J`-}xT*!zuo)f@*$Ri!9^HE|v? zTP4vdk5Xy}1F4tJ(GL(YvO3O3t8J~d;bUQT1&3$9Kb=Xk(a{~U{5UG?unZZUc}{gQQsqJ61_3;8oGz zvwSBh-0e7KY~}sLDgSns*y?FkAyix=GRR92d0OozDk{~fK8&zUarRT!-)PzJuIAaP zM6Z(7R7;LjRYW8z-l0?xP+|C<6`L&&hL&ADqkcPyxwG_ginOiU3u2(cUDMCBWtQNtVMIvbWf`JE}N2#&>_ zJX#qhD>w~f#fT)CcSGx13LX$S+8B;38K9WoT2s(I)941yT%WikbWo99ImmQBV ztE(#dY?UpBMvv@HP)Np)4g@^W5Ea0~LLIJs+nSY7eEL0gY}I}zJAS|0&G_W zU8kF!I2(?}NgFWyTcpJBfauVXI_%_>c)4u?!-d>pO=s~(@5Rx1A)_7DULSYbmP72$Zvs)fbSr%m**3Yt(l?H!! zu$CN_mimVx3RHE7Z=i+J)6vMAvgjO!ilJInGtnM^Fq8e0t6`KzBe1>bPDU_W$~aCR zDe*)y8pJ55dq?{KGKpcs+n0&dLm43QSt@4j)(`zog*BoqnO+?dQ7?dfS6jm_S8-Z; zeiYw@B;R-7XN+cjO5M9bji6Y5;?dE*q_e(gA7MI|LK!5dY{%FmCCN-Ci${#(~c;tbMD&yxPU;C8R}K8q zJ&wdifFbqb;e!DaOw-Y$X(xxc=ABVv|2C|f=D_{Hm+iVJb+$~05@+%B;Mt`$TRO?y z(P+~_G#kvN>9tU4Cr54RJRb*;2^FfF-{5dDXWT<}gXXGCn-TQikijC_u^yq!+8u-u z!NF(Ir3wplRSpV)zB7V#;*u^Mf&0332w=lhbRa&0@$B83+sYbK?5FQ*ok=#k=||Qm z2gZsJC(v1#rgZc z19f{^wZtKbAT59cyQ?ArtYY{P@NW2`%LCvz@%ki1M4e8xgg%6?$IIh>$`chl2kM@C z9SUic=t4ZUk39qBJfJ#&5?6jD+g|#8dZ6Qt5YH8V&6U-1>f?y#8LIUeyTc8~-(*&V z_Xch(({a1Q{u8Ocm^?=%G5R|5XsIeeWUp;ONWjEWFlCV)>JC&Rd${j;#*q@LzcmM^ z&+-gR6)90fgb(xOdH|QU9!%~QtRKMOTz*O;rOsp~w(Ye*QEH0tldl4bK7EI%UpmL5 z>|oM?RoYutouF2q8;1=#f_Kp*I0EiAutdUP>N(Edar6z<_2^itR<^RFGeq)@fAAw{ zjy4j-_!$BuvC$EqP7pkxWZ6$_Jpye`Jr$s+qb^eYfdtV7dG zCqa0s`U+IJ_r*1OUR=_oa_wd#2nmv_T##B2*ybQndTDe}mMVOqfD>LO?%23Qr=+W* zARrGSEg*=GWGs4t^*mq>*%E0-uU*(yzDfRZoT==)pNQQ&%Qy!HOIBNtk(+0kV%6i8 zW3r#wt9f*9x?2_b&cX^qQ9hgx6haH=A5jQ%kxDozvxTLGz(_SU0(_L|R8c|Wc~vIt zCBnhsc*Oy2c3sG&z}B*;_m-7L{Imu7Y88qg!s$TsNN#x$oq}{&X_S_JU#Q3zWb255 zyx6?fjw57$^Kwr8o-5i%2zV81-8A;IwGq7UKmQ7Qy-PplG13YvBF}1CwaW$#H%;D9 z|M8O|TkMDSBlX)8sCJyO!4~IBX!VzI>8b^)haoSpsi9&@tD^2Lh zjp;dMoTN7CY|BoV)KhiW9EotZuXA~1V6Z{j8MTN;_ym&(X5bPJctim|Y8yw4H=hkQ zoa+@aATev1c(O$tg?l`XTbiV?4}m$vG?mf!l+6a~vTm2rYd02+@b)Q^yx{`;GgK)f zbetX=D5(*%n*vAk-VV}CQZZDX|0t&P`fWrI?Jbq}5>#J<7)@RMp5BhoqO>1EfQ^^_ zEB0RMCVI{^M!X(U-1|)=E<5S8Q9mm_)-pJZyP+n6GW3FteIiS1~Uy`1(4k>UP4MK_f6xnc}9F!LN?3W zszgNPMSPo|C~*2T!lNOsvFxV-(csidQ9hNA;rMlgq0`~on?7nC*|hyVFqU-N{!trN zb=SKh8opbyJPiF&U80?10+Z-j&r$~Ah7aB`0{wLiE>Xu#ZyObtMcVe?7t&MiU(NMM zEvs4%^jb+kJA#Z+3p5&3K=b-a5Un-T+;7Y|#5{}!Xs_OBnDkjNvl?>%{~cC1oVtja5cJ> zvfF$UXfN6T%8n|(Q)=!EFuf(Zm7+e2Un_N4SV?6*lB2Mo3@35kY`jQh=Cu;fbd}}M z>cI*6$h2_gep`7^G-Ua8{LX*M(K95hi9VAvCvAw~Ir3q6Jn;yAV#d|vtf zKTA|RQr0~Byh1P2wE1n!vcZ0rJ@p|7Ukh8rqMXw_1|=I7$NQmWQLC%Kod8r;=+Eg# zj4603+$d62>wbpcJ2OFIpRmi(|At1y6Ch=` zWixz6#Up*Ry4F<~z6UPC4_h!Nic6jQHa}35l>Ny^r|}A0EdjuN1OF+g;!X$?)#eMf zv2i;%`g#17iyxX)ML!GlGsk9UJ@+FT;)qn#a~l*AE2rVo$s#oG8SV(9g~c&a9C8cQ z*0D$iAsICl!qIDIdGT0LLIcH&NN&Qu(O@0lS)zpiPx8P^zP0os7i7AjfP?D`N^F&H1`6~fV&Ya-zEdJ?xR%)rTtI_eQ!Y=>n{<>VB0>C`(xi1kup)<*g!{n7ztmjYOjo&h&;)MoHjZT^8w>!pEaJ3VkAbB;h# zAM~aTCUHHl))b}WX#k*Jy5x1rc1q?1Uy5lMGPoBhX!8}`2X3#nlYk_xkCM8z2lS}i z;kAxeiv=n{2(hrNm*|t3k9$s)8twAz=ea6RtFqlx@_19-I8kMY6LrfTzXlZ55HLdjAaym*Aj=%}JQ(7N zdQgnOkg$a9VUA*I+(=oQl}egbZ?PU>n$YB@yZgc6(eZ8XcwifV=~N&`r1qY_Su`!&wF9kjcN0wax&z1<&Joo z&relZLOg!Mag!nD4m~#`4S_U1@x7d%s3T@=pwBkCmg#7sEQnD$_StN0G7+1OIxLIj zL1m0wX6xFHs0$Vd4~oKheXxPioGi*qRxL-W4!?!Z$?`nl5lEBPb;9wp8wz>}<7iOG zRaXAc-`DabkCRG;_Q{A(3r_2SE_FUs-gQz_&p4)GaC0R$v; zHW#pB1a&xQY4*-=596p><>FFSBB%9o$VeRYW;wY8&`=ey_p2?^xv8h>5# ziS$0$L(h>iH1g7(Rr9!phk2T^D5!Ysv=JVFMiQhTmWT7FdoE^bg{`WrA-0?bCguCc z)+&pA%)jT$mfOQ(7gFT*egSH4h0|ZQQY9Lr!z&JT*a_Y7EBckGLe6UQe+jaEwypeu zDuDQMmNJi-z^bXy=v7d;5SP=;~;mYReD|mCa-PFO`W**hXnrDuM*9z=44a_wHrYwmCv;h zitB=~4JwR(%a+>iWj3Rle3r@5^r~TLr*-OXbErAanzU%(P|^MH<1kI7O9g=>yu%nW zgCXqo1=ZU0y`eMz83Ni9W(=;PkJ!; zhb?T9Ta3A#^SIV0afQW}M?3{Ew#k#l$v~b&yMZ9bc#O>Bq{9xS`zCZMd1F(~@;(?3 zVKk>|Y=5;cIXE;Z0^Y5HN%Y>wBOD5&_z_M9qv=fhBB=u3lP4{Ct^ottBbzSgCzIfC zfW+r2s34YTemf(+`c+S*;?6l+FEz1W< zNDp!E$-T0U0*_V&gX4 z=-L!+9~!B)F?q!>A-FPbHrH^p!MV9G_5;P*e=lDo+agKa!fn~vC5?Y^zu`r$(JO-$ zmQoWG^qR*d%$*=Tv&BJs2WD?Ymo4oE7k*`@O)B|yVQm)S$N0i9(%#t9Z9P=k&+cGD z@BL5iHsVt=*(vcvI0$Vpv=5_gbhO7lPrC={OLZJz2ze}MOC=#C$OT_G0hqXS5n!b2 znbLpsNsyBLrMJa`4z^;u07}7Unp=Vme+gOMp*qP+B74E86-sGtola0xF`6amcPREL zCW*U4I7Jj9DtX&=M84-(+av=t+jZTS_9+tx86GZ~+WSGAfm!P#Mzon3;r9ug8DG+% zO|1WI*de|r=HL1sWmLB#l6}pP^{a0(!3M|Ow^$*NgiN*&LFsP4{rKm|(g=;L?ZWSp zS$;v%5y7d(GKe40io^!jPlbIE0-@bx*u~ROUJD$@Q;E7`>~_3?#XLSs`K1k1qm># zdoR$x-ne2(rk_STcg1yAQj9e70T#Tm0yet%VBCBB<4|9pCMLfo*_YyuG>rb^T96V) zA;B6EWyyk84kglED?HAQif4q$V@c|R4eX3JnB!o!ao4=@GV2XGjfI;*rblgiZq2zK zJM3<#gfl(LTqkxh)nous7HvNtmNV=z&kBeIcP>Y+dkWk}9m9x}O&^-vlLYGfwZIlT zBFDn4o8to0Hq$BF%0Jpc!(a_^zUJ0$*{Rc{`qVl#s@u+XkzdSDNo7kYu3w`|*{9)| zWJ|+OlOrB_j2!92qR68W{;7vU4x+=e$(rLQiH@vICkPpw7Nd5}hrCnu8YbZxCD-~IWP+V_2@NeOsD;HUl1jS1$S>nc8y-M5d zq^x3o%BJCYL(@lBoOqNooY=7rJmjzw{{7wg2mkiR{^H;M@vr~ncP}31E8XHgUVQmI zz0xH&yZnkLZu8@w_qzA|5>I{NT|VKBp84M2_`!?cb834V`aGH5+4z_Bk18sl=D6NkS?9kh(F^T!w|)D@@6}#s8^LgHaVR87VGv zoiI2E&MaArAB~#P8fUrQKPsllRKMTV)ng;cEi9He8YH_KViME6C`T_rc{1&+7wao; zAY+b#0IoHEM;QdBA!im$Hv5?<>yObp=zt}E&1-X+qEc7}X@?H>IzN#umx=3V+C4bz znzd%Kh}I>@ZKWCKk-lQsL9%SghbSMU_sg^YS>q+8iQnv5dX&s{plBtaOj9CFO@Xu|?- zI^ydEBRye*MekXZpRrI6Y%_x259?fL4eAm`RGiK-hnACsKBjI$fUMmHoI%ZhW;X#D zkNl1>+lYO{TUZRB6e789#9Cw|sfE~pj_nnDNhoDgX_oVrlpqs*EP2U>o73UpfB2p! zPeA!O@UmZ-dd+qCaDW*wk$7bro*W;_bJ_e5cFQX#6J?R8#Cjj0ar#$&)?D63RpB1B7SDc7-^~ud0rNG zJg#Q4**a;xhYSf*ybNPp$MD3P``44bCs(^uie#SEinLjU38;mLnjD3(2b?%<60~j; z4krsIT{td)z1EGEc^2A8Kso;}xqx08yKGKQtEX5?ZnpFp zN$WmtXw7tMr#+_@a?APUPkCQkC%JuL*INu0@Gs}GS zz~WHW=|qzw3*eNxPY_s&oH~2=&;?vNK)71VB}~&Cm^e zkvUey1JZQbQ09`KjB7Wvp(=5G>yr@znJ*NzPHngivxy~=ecYT5!LgeW0sd%D?mKCV z7hGS#fxnb%XM}m+(VY;P2D?}>A;7&FB)-hfM@;liNfkNVk)Lmj1={Eq4fz22)WMFy zVnh1y$8BB#T3W}UCvT9HlHrT^=a)6Z15}lGFv}1dT=XWZkVy0si{*%1QZQRl4_~aj zm+h2x+z^C6Jm-_PSTs2oglg*b=)tZP(vpt!j;{nRR32-KC1M0CcByya@=0*w|Cw0tXGc(ypyyfDb&??i;x=3A&8EPcL z5)wYiMWLe=v9LK_$`nG$OZ7cA4Z(#lS2iJJEK06w`&%_D3Y@YjsS0R`XJbRL7Ck2M zH zur6XsRqqatNcGga1;{^^P5vee7SfpNAq&h~X}W;Ri;5A6O~zrANM|BMS+Im2@BP+D z%ZMYojQZl)*7$p@=x31u7TD>kSHTcX1fm$zL?TB71ZR;TBx>x$dlLQ^kn~fl?-aF! z`E8hMt$~wXyEy6RDaS(FBLG@!ng#^O84)odnPHcZ^_)!BI-*BRYOjKCP{%8YUnXL#(bEhEVjVocy0+$4giL%QWNz z#)fD@_-w19Iq3pIB84<`f3V-6S+I-Emy1vkS zed}i5k}mAseHYHBVpc%{1(;!(z37Z7N<+djmc&Afvu0nv+AjdaIOza@o&-|KB%6GS zA@rkSsrT&41-|ivJ@&?iOy&J^`8fPlo2$N{o~$1&`iq;}S-qy;hSfRd9n$|K4c}af zOF`DfED@PVX5m%q9-m^r`2Xx*=YK(+sg6<0)Ra0(9jT5`hpWR>S5ynC4^ymCHF^c)C{AK=P{n>mmEh{mh`is8199a%S zfSvFGyay|w18rzQ6B!4uGX942gqnz7i52+=tN=U}CS{NcEmW3eck3;9Mk3GH9KuP1!-`d} zx$CY=?z?ZcJuDOWGM>L&@Or#MdI7~7ctME7pOB;GAqC?f44C*QGhx0J5o3acny|+l z2S_hLbmHZ(bGiu$o)-hGjQ2Wn>h!U(O+zeeeG ziDKx%ycH?=7%cY*IOIjD1Eb_MNa5v-;KiYZx5kjc^2Yg+5;bChK7={3$*TvhCZE6y z?*5R>n^9si6CoY|O6s6l))<3=IW<1O#kc}!`5AC(WX^3(Wf&i#vP0_<6WahPQRnNH zz9#n;l&SX{N2vc(#W(M&VLSLhhmue#o-O7!X>2JaUN|B^pdN+Wmh7;qrK)r1a!t!d z%OnsWWA_40VNj`>U= z*{9D-O=LDvP0prTJVvwO+n8uGFxu1*_`1QxCC|UVTWe($8OWV-`C;tqOmJ3ct~3%S zwaUcb1o5*=qFfC-NAYB0Qx*m%&8c=iX7dXK}>+m=5jZ!RE}EoCX9FBMT*GXyiG} zy+^c&-{8TUY2`2gP{N-m(UnKtIY#18WRXM`U+*LI$a&7$m$*^S$f{&#)HcL>VuJ`q zDKEPqUPNsHBV5RVRINrM-3*^0I4~qHW@XKi^{z>UmJAK(^Jef!FDzx0{;qYKd*{Ei z**UiBlrp#v9PZ7$8to!xjNm?y z#=##A>CYm`E^Wp{dPD}vfc2P9hqDTfJjva+m;t!eKRpwvGCot!u2oUb2{n^1{3NNn z5HqtNYqoX8ZQ1FDt;FH_l~Xc^Qkm164d~i!`G#If!_k=PQyv*$mK~C*xkOWK$V+}B zorCnUWoP53UHoK_s!FL1+)?1>&fSMoVgP8BYY`x<6q+Uv?vpyPFV~}D?EK`@1|2Ts z;&V?2oWENNn+zr@D;X@@@bX)Vq@%gHT;m-xf~8l9h9_>5&_|@Tk@}qU7uIAD)IzZ&o1q-=^)TEI%%J9$*>f|0sH189)7Y>Jz zD!*4~@fIf3jABrks&;$>2nE_XOyp%P7X~=%4y;6=jr&uc)$!Wq7*n1?XPj-{-5MDg z5oCD8)sqKP+3+MpRG~h82sg6g@sKN!BFSB>3B;gsjAR$TP}IcO-%Zqt!(OX4!k)?` z-@=Ba6?hb)fqQYSzYz~BkxN?!5q7joL52-Jt#8(cdq-;B3_F3fDs8XJRqGHjR>c9U z|7v-l)LF^5Fjm<55S1Mc1N;?H#+jsPwPws3b3{cJ!Hr!+AZfu#sG_Z6hC{rCG91N+ z0yUQNuSui4@1m*?<(UzlOZJ53mW+7xvn_ln8tI0WqTzM)h*SjC*JqVPg*yYr%KQLk zJzRT6mY&L0y?cL>gDOt$HGZ~VKcct-o=uB@a>{y?u0|U=ew0-TM?+GQl?<^3Zt#0_ z7q?rBnXquJ5tY_i=Nc+^l56iEbe5>`9U+ld32*XRk+J1dfx?Y%wpqeg2{z`lSg23ex^!%#s?!GAnIq(Lw5*4Z7H^EPg4A;38F1p3J`y?kX~zJ;h>^kctt(g zvrrNZ=CyuxXIv>)rC-fngI)PqFpdxz#XP~cH-d_z@>&W@jkb``gAV3kXG=Dw=_vz9 zZ7jic4})4A!B7mDbMQqNW_;#;d3K4X^*XoPpRWl|pagH<#q)eQ6f>3?a-(E{c`L^@ zeTZJoC_Ax-cE`R)J%WN;JPVG3j=qu6?%2V>?74YwRxuGlfwYJsFx6WOK1OuW=HxIZ z!gCv{qA%KUC4<&Dr{1k$Wm@aeb97!3QQk6@v>S|xrXR=VJUDPZU?E8&JeG-MLVY_e zKJ=ilBfVh~5tBvViC%z(%+&J))`*(`v{c19;yP__*t_vFqMhg2R>?^w;F}}Mm!gcu zBmqX|gcqQ7xB^O{)Tq#rZwlmgZvJJrbp|T?!v{lN=)|ltVn?M*^q53^!-u9;Y{Tj- zvyy?zG0(c<0FR|t<=~aeDA9)GIsT`!^14{9S=KxvHlBLQM&{DLXEp%S{XqOv+ z3&?kYq6e?!aWDMkm*l~L90;MR#(?`~ag8ZHp}Rt~Vo*a7_t8#khfML8F6cCKVi|m} zx0%vHr^L{vo6HWE<1kGzft_#Bah@0h+IS8ARG#k1rb#AMvD7WO_&SjU-cWqBqGMYC zH#FWYxz)Q^Vb-lpV`}beCQQ&3=JVU z(QY<<(cxiaE%4v>o$`a8$}c}TD;}M0+h|Jx1d%TkoYp@Xz%5oj^_`cvI9DFPlAKeP z;ZC}0eD_VF94VFQp681>|0m~(C0C5Agop7Q36!t@tK$o42Uh5WR$xo<)BQMSAP@v3 zE!o^^A_aVM8FdN*oJK30!%oww1E2X&aJyzVesU_pwLMEZ$JUYE7h&qARSjfeh@6HD z_I*ysIBH~PK;H?G1WzV;j5U#vn8S2MC5%lbI^IJ$Tz^sY7(?luiIh*~} zRm8;18%=XpSC#xcUM85I>&>zcVdeQ{t`JqZk|UY~0YSpH*<54$w@;?xZaWR(2t##5 z?ST;km9Rm8$_>B-#Ol&++g+n<@d=X1o(&iG(SNq6y8fe;_Aw3uu z5?O*i+$1!Mg$x;_+3AkD-f&%WuO%X}XJI8EQxx4xAvR<|>+)eEi~VA)L}$VL&c5i; zbI4}n&~~|K4XboR>8OJN8YIazy$Z1Q0#6AVEikTKi;TTu^qZK+b2fw2`u3B4cn)`S z21dx%>I4^%-`cj`zqQy_8u(Rt8Z)Xvg@K~)ec+n6iR*i+NCuXNsZ6*)InxdXCgrq&r&U@x zHHgbWwKOuX3kBhIc#&x*B(jA`F-t+YCAqhb>}&5t^rD`JwQmE|@vj2aKD$FJoD1dZ`dF(VW+itjz$JeQo7^(R@P_JpSvJ`o)D{wmEp1IlR zb)hj(+qKnvH=(kCp-hxorT*Y#oafM#R1)RwFk}HXO$m8y$sVKp*&KhSdGg=AEEKUE z1um(aw;A=&t(jTR*q=Usqj5G0-k*M%%?I zRg!8Y+sTN?>xG!J7$ckV`1_tc9lM_OM-4!G1N7OhXypv%%DLd_M)F7b2-1vM4#$WR z)nIMS37clL-e@O4>NO%;YAX|7BM7E01D2?FBX*w1v7M-`BWwKRG_8hR6M<+OmG>i& zh+bNFDYm%WT_#t9%Jk34(PEUk!e+dYgEgTJu8Y;W(?%1zdpF$xr}j1;BFn`(sGRz~ z4$7ZSwL2Mq1M|SC_};n!ONYpgFqL#S;0HICtpT1$+m9}Z=&Ob4amp{RZHtc6t04wn z7YJW(@$|F!%yZd}mSaur{t|n02tC$VAVu!AKif<3%z38}HSBZ|K)Aru z7Le1aT%`)>$V+2Ds+FMKw~vsJ&;Mk&c^LKP&Qa)5_+oZ(v=gRw{d4e9~7gqC;o>5>LC%)%II@g0hACrYboe z>X))#ci5Kdja7A@P$EuZZE5P{O7IxwJV@7CZ>l2P@v6+yygk`<>71%glj?W>bjgDj zia}hL8*I~0`V{A%kUL71tQ+vR=h6*hF=_;X-SzZ#J8t(G^lil=fKWY|CFad6YYTk|p#z~PUi>8ZJSEEcKMTzgAb z%=|D(c8I4d%2}gb@N<}QpwnDtkeZ~PN)S}Y?l4o*ZO5`DRS7fpu|>z~CF9Swj)|+y zMjx;6?r2uw{%%(;*siEJ)n=W-;pXmVCR$9|^w3dfO7TxuA$OCOCiBlz%5{}v2n!(u ziVOt)-s+~3#KVJ1Qzxex;K{_elQ!wJCrO&2KRso-iH+370hb0qE}z+O`--3Oa|x( z*j)#W=!KI-pjP1Pqww1K5V74tt%&SuM!Z%ERhVX~LMVaWHsoSzvPgqsqI0w6bSj;r zZz+XT4yeSnqP`dUuDBGxZH-Iw5E#kXNcc+TDlqCBL37N?SzIqThjNSixD7KO6Phhv z53oUf-yTQDdHR`covILW_*5D^dqzFazS(m*GW3+?9+}rfq2&u5HXeo5)L!f*Fk_Yka%AAL;&p*AQ~$jy@wH?zO54wbo%8x^i-BH< z*mJ+_8IN}_g4R_u2>hH>xiW^;G-$@#;x!onYEg8|@Ls0&p>vEzt2^~N*ggk@$GXG(BJn1& z=XP*@7zrFr(@S`;on;e4Za%C8qJRPx93V8^<{0RJcpzPOl+K!RuZ5}03q=4ne14Vy zuAIFIbJdOaxDSd>$UjIUV)6v=pUPRBzrq-%Ua| z&2AS~m9tL6F}Xyfijs0G8nPqK6C9{=#g!#*b$M1k7^wj2rJPfFn=>%($zfiDcs;J9 z&6K@Fe6D<;_9iP-OD-XtT`6zY3?$c{9}a6}9wr5m0u~7dNwA_hIGivLwvb$BaDoMB zaE59j-H9Z<60bbE zYcVn*H`d~3+jrSLeSuA79mg^;)kv}-vvHzZ-tnxp+KPGkz~^kY^38dQQ}mzVpAfGv zz?X1r5iqu&fUk{<^DrQnBy=*fOQvr{n9LN9 zAjOD4f}j58N#?+D`UZFr3zmgI6{?nvFPL@#{=>OoV4;m(qAknxa9V8%4{*kIAf`Y! z2lq%BNabvRZfGB`Wu^5uT_r5=44biTBBPln_V>eNJ235W-}Rl@gfZG9Weog+#@T%e zb&u5U#3eM*gn0PxV@vf~J^cr#$UI1GgoE@k0pa{o5i&2?_4L|`AyB)b9s=o#>3A%8 z3Z)Kaqz{_yRI)sDjVyPXcxDsu8u!6ZQ+A2ZW-et+9a5zXG@30TTVoE)D?M#+Mn6Bk-B~xkM zx@jFEZ0oRNv~i@ES_R@!-f{p$(Rwg1!;J~u`52k;IRe^dh+lgS30B%5`wTL`t-p2bbGSGX$ zB1+;X${@sw*$q{Iq;uv0AbdzU_9&m0f*_0rgXoovy9kEfw<({7@oU;E;7O!j)jF#7 z@)*bQp{KEsEz=GItvK-n)(8P*OnQLd>PpJ(I{q9mKFIu*jR)nDl#kSFV)=lO`c9s| zLF^h?0Ri|xXG!JlP36X3NV0HxG+Yq@`N#@PP(c^t1g0Al%fjG7H5@zD(Tpk9Kyi+~ z;0v+|!6!7)m&j?Sb}0ZrkWBe`6+IHf zN485}Zm4hAtrri>28&MoEC2lHzXh`~yj;2-q+y5XKMZ6T_;=XCOvg>)&z@Tb@^LR& z$U*=5a&!A;;mS;*E$L2xMB$szLPOy_ELHv~t>4h+ULMuCS08dZYp1hvhx;p4Xh}pM zSsKQH^wClcK3XrvH=-X5$x!yyN8@?h+)PAuW^th{9BFHr7y8%=&wpFCC{Fj5XtYI^06aj$ zzan1`;>^_y)=1*DB>dWaC|O6-Itf(SfJooDW|Eg#BN+Cs6S49v4FphO5&19_G6QfJ}Uo?Ae)un^!B&l4r3j zCI2R5GITlXY{{|{R%&5sPJi>V7Ej;xC&xp^x}oz28skSFi2LVuxOucbW9x7+(_~yT zt`3a_k{q>g7|$6E|I+^V&oQi5rA4!dy!qsW6YN_|gXL7fm6nmM9|D(bx09dr>4g12 zJTVq^?RjeG;Eb%EKr~ArVXO=vYWhF;JqiaIl4y?zp0)VZ)Okd0(BW&IAuiYe7K%(A zlkgOI?QfFQ#R{p5*^-YjNao(0YR~>7r#^W*-}$=w>k>pSy8S zB`+13in3N6J5CA&TA&*Wt(somOfuw(ybe6i8TQ*$ha9v16nt&oJiH7i7|4>jnYE_9 zcV!4_gy6YXh*dLjLo(D0g7rC+>*nD9Jvaen^F&JifTmWXtH!zhg)(GSh#s#hQ(p*Y z2dIyhR}W^r3>(xN<1UgH9!KW`Y^-s9P7hR;l#TS7*y|h_7$Vb_F(Ep+BVdbUCVJtu zS))e=Lh0{!HPqLMCsx%>FtVidm7)_HoGAKeWeI2}%1s9jBasgA(}w_Rr~3vLA6{q+ zp&8RE2@Aa>&pDb<5UBz+v6*Or5pCej6GQQ8c1yO15%`U^NEi@O&d~bieFzBZC=v|+ znk2$Pq^xyR4_khMheN8(mU8r){Hi+-UQ80`R41Ceo*0(|l@N6eDxwC?@4iU7F|tRA z>c}oor4=&57YNz9YdsH3Zsw12rGeOT(E7RRsVX+1;UpXChZI*}Xm<1@8y zpYgXx_?1gLlwC8`lU%>`(s=UVF(W#40Y9TUlcbH>HSL5KlZ}Vy;cBT4kbRP?KLC}X zUfS*ZY3*3R&r0&`D9xQ0cfod( z(iOs>BLNGGySU$w#l)!~u8C(MJjVv8ps^!Wu8rgg=gcTQOa#aP_fh`KaIjhgXpl$d zJz}c3Nz>^O0|Ev~NwCa53ecOxWpaEs(%Rej?k7=&bm_bV3bt*gt*wYOJe+)rIA!KY z5MJnT`cG=$Pw5Cfm&Eua;(#S&amkVeR5**`dgrai_u+9eE76Ikk=N2%A37@J26vJw74snDcfdts?q@V8A&H?Oqf8s)0LJx=jdRr#VcaTyNu9x668<{?~i~+Kj4Jw=2GrRs`U(k!L zleTfgC4t2+z0tSnE8;Qp;ICVcAA(lzFaMyyQ%_vs`uULHBsxe1)ou|hs5q6cMBStz zux5R2nk5b*7Q%#+mNnrwFKM4`KL(6(dAp?_F{hIq;jPibe;+z7e69C-Nf$yge%Gx!Q;4oR+i6z9IO56#jYmJg~w!tXYOtAhn>- zS~j85N})+EoZrsj~8n$!+DDDJVAePvNww!1=AaL_k2Pv ziCd~QAoOL^6VYZ&vLjAs!2Ad>GWpciq>L)a9q-K`f?{iv)A$lwgtA7Fg^t3gMHkp8 zo_rj0GHzWf&4)UH9(HTMdWsP6Kr<)B-fV5P`l+;xWTmbVHgQD)t~Xd%Jfk^7m9XG; zG~I$i8WzJu0zTgf@Iu+$OhbZ4XeQNsFA-%m4U$BWWwyyeEGBoqp_yH}%<8NQ-)gCS zqLQ>B+srDU?rcQl1PJY>FiglXg5H!SH}nz>2N`NdX|6mh?NXl?Ff0VyW_ zdsP)rXV#Lb^lkcd9wBG7$*du7^k?4>YJ6Uc=~|1C^{T6hc3q5lf~I3e-s$4-m!|6h zI71nqgkIgij-CHl=OR-pqXUs|uR)D1d7Eg(Cb&iYu_^AmcYJhmYK%Vh@F4q08=pft8G&9YAcV|wiaBHc6l?^rmVX@T)B<|6>cmKOLf zhcGBj4&yf4w{1u8K`_nrgnX3WBX*x{ui|s+@nqN+(pno=?76u($(Wl9CT7r4VL=2t zs{YzB$W3iP;E(W%Gmu?Ob0>_Y{XFlZ z0lKTm64t#Ff&hZ$r}WzlGCvD!_YtIEsK29(8UG^ihwx_jrs&)MUxQLc$)G!v76Mgr zO_40r!46|^rebORQr|qkIuDa1`*xM>IHuj(sgG{|_Ff+8jpFK-mx)wR4`rMU@{ z-TEZ_g1q+}o3-WWsP~W;3uc4(!cC+}B0khoPm!l!8HuP4W(<3z&%vt0-!50B;pd@; zY7ih4z%E>5VD!-W)9^zbm+*Ew4(!zI8(8ZiwMU8-jxKY%QvG)F6DWW8zPCu|K6MpM zqNnw@M=@K&{_^Gzwb)Z8GSp*%am3gxnPH7i;BDZMLQg)bk$uk%sM$zngm9)=s~d8C zCTh50uGtAIopRtn`#zG3J)|#GgABsTyne3NQVk3H#SSB`O?x9rIe?R^U`}?d|}2o z!`pipFNdbr4xDfaL1lw;W^Hmqj_JAs)4Y6BYpCMfJ>JbM64gpmgk+It~1 zv~c!&P>U#U8jgWw#i?+FyuxOPvh0(X^(VaFan}=qxv>gWB?HQeHzn8dL)5U_mgK8| zb}!WW7uIvQ?j)MEgPJyV+TJvc#W!(ruza1@3S^ZS$O}#b z>C2in`#NyTPg*RQ;*nxDuBxJ0tD-Dt%7Uf@FsHERTB`?nMxN8BLp5QD+x!NBxI#?3 z&3Y{ol#?eP6wvj|?$ZV&^pik#Hye9qkY^^RmIz~GxgO1hgQLAe$n9L0T_j(Ac~6&} zR$IPl(9LhTHh|m-LEu!tW+13R3n6p7ApuRZRliSazh1XiR{f{xq2i=qx@0AeRo(hZ z3e!N%pYN1;Ux{~9PM9De0?N=&wrXH`CY*y0MTvUQmOVSd?y>(RGJ>JyeL@btxn*Hg$DY&;|YGl;?IA+Vu6z{6{bmriLYpTh& zA2wJIeMEMRmzp1_<%>15uXkzZ=ee)`6$#yIz>cgkdGef{pXzx5nYxW% zV3RvGWeOYvHV_SCkS+0+@ZS3`?B-AN#M7?b$xL?_uN^H1zl7}O&t=~1K?D8TUV?bT zRf6>8V-g>2H*T98y&c8w%gI!lD{JJy8C1J4ohfyQVKM5|yXsJLO2(!3x0tRjCK@fW zA0F>_$=E&{Y3@YPkRPH+F>Wj;DSRi7O zwXEip1<7`=t1OOUQ6@t8#*r5yC`RMlX%Juq;!>dF3Hpt zGtN%>p$E!KcaxKv@x14M2d{i*dT4(}0_%scN+o=DmH7)D^XON}c<`;f(AADu+2Ij3 z8{V0glW%XaZCiqW0@$2^*q@rv`ECfm9463B2amlMrK5mM9%$Fhx9OpMAMoV|-Z#;- zVO3|nS0$lkYn%RZl&+G`HIm=vFTi0V>lFec8L@?JO5=`(GEKWm(mleOMSU&@?XMGG z&y>7(j7+17KDs!|O%5HEy@IjiIfX|3SCc?0r11<3W*H;PtaIh1&PyP_{-}mOzVJ;r zgq*@`{8zFL(q!t%pH9QH**M$W8F}xB0)Wl<>C{j}we!B55Hjj;nGlff>0--%)UlnA~G!b_e2Kfo7%a8u8|?? z^~Q(;nyv&wR$auw3zQR89i>c)p*n|ux&*25vsEThVuT2LB}(cZEoyGcO~yg!abO<9 z_u7vT#eF>G&b$n*u8@WsOUZc|Sv!3Btw%&SD!=I!5w3^)=2+=RNvKZ=5PiK|wQ$tb ztHZBE{XQb5T^FZr+8L94uvFm14h|I$NTE!+@q1f@i0!!-vyh>qos!)V!n(_MFz;NC z2UWGE>o=KHE6S)#N6*dwo;VD{5*eLU1GDR4VEpOpK-iMU#h_3NcqpejT+jHzZOac5 z@(c8XDl83>9+Dd`f4mvfeb4KP@i<~>M2{22o1j#^10yYBW{iF^8XX{Ck^v3OcnOtI zqk3~Y_m@(|vsuzHp9CtwKu1&Nb2q-Vzt3XCgPzgRMfbzGG*_rP>U1Vwk5b?Js`oYf zAjmd?3D&gJex~jZauZo-FE*Nr?qW()sV&h2=Y~kLxge9U2_nS~_NFF!jHo1Q9}UZP zRB?kf9t{I%aqzrYeM^C4st=eiu7;HpWwy)hu~=1sal%Fud)(!0!=i$jSYj}61XZa% zgVu!$mAxJs+HE{&5^^I^$z7zjRk8ipGE*qLA)1&0-9W5jiC-KQIAr6T6I&5yjcwY8 zrknqn3*PIhWS{2ed&l<-Aa~@45xVm+W*gi;>=btK#Pi>j?JH3n z90h9x;HLQ+S|4S01Yt5ydrteAETBBrwkI%)lZezeiT^M{whhxt`g)4MBkNmG-~x26 z$FC8hskrOX86gW&cN0A|-J#a#etBGV@`3R?t*p+|?;Zn9wPOqWO^(6kEIF4!+y(~q zTh7*nPpmG85*gR}xGOoilAI;++>py|<4#k;-E|=x!5!5Ecs`WDB(e`)6a^KK4Z?(x zi=>iEL0nDaPHHvkdDKo->2gf|Q|v3=@IqzD3F=juZUp&!cRp;zXj9N{&f;xjveyj} z)wf6JMdRg(FHga{3vUe@FIxjgPsiUF(*9q{-7KRI488qa4 zKsEIb$Lqx-l5oeULf6CQs>$e3s*zVFG*7qfA*%YT#I05XVH2<}Z}S|3?bATTM|q;j zjddfqz>F<$X2o+?24*f7*c51GqQ=Ol^Q3XOq=u#%T|&$RYH$gt36(@WC;-5ix>2O6 z3D!)EOD)A%Z5Vd(Z=MHxG)Zvu81YV8o>l$bqyD*8qyjc!s0DpOmC7;@f|2^7PS)iu zcxZJiDm|%b%3=ItXP`QenJ+O?n*-|5CCBuTv;c?yX}4K(mPNCIEwO6f-i4s=n!PTl z5UuTiEU3HGOP;INlD}W}NH$tz`g~Xq>4Cd_;!yTZFQrd;MKcZxmS?5Z_a zsFADQQqk|KsFzp7n0{qdze7Bx+p1bzdCv)14VVdDAz`yd6VnK=)w2N>+s8N>|x$=^aH`%R*7hN3mNyco5$ zbY5)tKWOl5{>;<%0Ld>T1Detp9(b?w?w1kug(Uz5I7s=Us zNZc$xRC0tIrU&T<29ZtXBDRL%8PP%|9y;~sJxE2-sPTEsE1#uE@w|LVrDz(5@j+5w zR1e#V#4;eLCq$P(_Q}JfOz;JQ1@N4!mB4*Hz(H11v4(x~x}MkYxA5L`{{D)>Wmk1C zl?doC>`f`Kgf($NH@q!;07)dvKOv5r;pfeHqYduV@|I0HQ3zzUK9yByawTWG?LHMY zm%XBtJD)ql`1LY8}uMSt1DTI21lAtuC{@H-^Q8I3!amqt+ej#YCt_$ zbbO}E|B^5CI=#GY$_6g<@f+N|7h(PcVgle zhIgozn@ax;?LY{@UpF_DZ7R19j2rLac9;4v#B{En_)aa1Gt4SToS9^@7Fxt=VTx_l zvLnMjouF}3VQzfJUg7^_hSdC=g>|0qj{@rgZL=&2fEjg&X6}gPg^12wQ6@|}Ry@~9 z5`0$yQ;u%5+7oYRFIfYC8df1-)SA1ndA?NoMt&cuIu$kLFtgt~zL=t2Z7X({tz+6~ zkRCgfX|J``_4K!AzHt`58Y|vY?XBrk!Q_XdeY2~5jXB@2_Yqg9{E5T5zwT?6#ZyTw2 ziHen(2^$xO-}UI>a2n?F<5Kav^}>~r<(YNqUjie#UlS8}u5qT;GQBc8oH5=-ePR&jD) zq|+@cwyms-s;7^YfxMZ;I0qV<^H7=(BNvdo<*yKYW}Rz&EUVw-CaR60*49%SaphlW zxU$t5lK8K9Y)i`a`Gnr+&mjHnAs-A*smu)fn04EaQuADpZwudkQg^a;7LQi2)JLvr!l!Jr!}x(KGR6 zk|(8_7A)9)espRwGh4_NXS4Ytg}Bo|I--HY;vfS_d;>zZL>a#UGI&jZA6BrD{Y39J zY_}#Fn*Cp$iDI0~)Jw=jdON*zrq!7!)F!hHK&NAFoV!u{9Lyj0m&Nyuyg94>vvs3G z)@*aXM5FE(m2b5RzVb8|Kp43a{?|hxhZhzEB+TDW$TfNCTl;(82}hg?(Ko(^i|+zk z4%!}edeyN?Zq22=_#4s=#^2Skfu$errQXgVMczJRJDq4L{*9PbwXVb_Ts!%ippADM z*-UMb+ZPIhQLe~qlbLijpXH;uNt|S72Qssn996FY&Px|o8B>M8(XZ-|GjqVz|0wIv zcye$8>xZ-FM)nY8DWhkn`R=E%IaA6IXY2r@q*odZ&TYd8tmCVQ;r~e}b>eZZ$6Hu> zUuD>hyvo)R z@;cW6XyByP2OrK6mNtK!GEkGvg~W<~n2SVSc?UZfC(mu;2A#B!p#V1e8mjTfk?xT@}O_t zc7nEcNEq_BxBLA;sN~NtldDSM#|qtDoewK_T^>0-;x(DxqTl&npPo zGsxd9AbnlctxHAUa#}_SQT$Z{6CqQas0RX^0@=L{3N( zd^i_Tn;z~c({HB-cAkXSPIk-b&c^c}sX80Zi#-4$D5W@H z4|cPd!)Vb2ZTXqsIp<73(P*YVVozo39jAPxpwM*B@=D5~mH%qqTHDmrI6?|Muv)Q( zT;&(B>=MgbFnWAe;=%6uw}-uZ#q#o|;DA}uDZA-kKHuR+g$0}?Rx3wciE7_)+c_Z1 z^;W(zBc(k(;%x1>?nq}_+lh`rp?9-?_UZhhbvJcPWYbntZp(kfTFJ8foEk8% zJjKRTmWkBeY-)YanFWobHRqP-)Vl)X95*Mok{e{{s~ti0!=lhOw+nkXuHbnIDEWJl zgg!~|;EF?F|~Ud1XcPhGmZ_E4#a^_-l+Su$ZkB**c`hEcj3XVo1C9VsnMF{-{$Oaz|R685$kF z;x@7CZPu>n$RH{xD4aibL5k29LjraMM7**mIwU4AC@9c$Shi}pgo4`Y=6?s?8yHGK zzcUX@Ws#%KdlVTBza8xgkVUS~k6s}Q3=B{Q1OahTfrEiTIQoOV z`=3>>yZ{sZ1A%`j(NB1D8DvZL%f6UiD;RC-pBK>qV-y-{QU;P8qik5jHrW^jrBh_! zGjtRcWf9akUa8h){z1QjSJTz(^Xxc%kD#>Z%}U4>nxmG4xl|f;$H2vY zBfeWk7SotrL{`+#Vk?Fk@2@*wcYznEDGGYWZ$E`*v4}n2$qX+d5#Z%ss~FtUd#W}J z(^2>6HfEQy_uWX|2zidYtbiy({(RVmnF%FZ;FBW(@oe+wg1a^V^QH&<(@tuP;yCV< zBp(v{HUeXK4s%e*_)8oe?S96HXe1)C*nJ5>RZfQc95XX$e_9u@~zh+CHz3wSde7zZ{N|EuABWP#q)bReLAQ2`=o& zwQrpf82+YL~3idhN9O^kKVlyRi*+@ZZ~@9&K<89 ze+U*pyXkBh<9Y9%-6MQRb(L4_1r|B4%VoEBVW$&!4G#l9J{CuDb^(E*Z{G{(Y)=o2 z*(V5aR0%*9+lYDW#5N3xvG>|J%(B9zlpMyG72TviMF>SrighUb->@l0Fy`wDaHNi_ zPBKwhociG3GiP`0_Ho^3!HGEx$5n715xetcZ`hRU8+*GrO#7hQe-H*_MIm$+Gi zHCh?0(Tp%Gd&5k_^c(=Gdie=tw>zJ$2?pfZXz%*;_3O*Pf7i;7eD z;OmUe_aQ>XVeDO0$#uBm+?W4}8ET+#JLBhwwj6$39Ya+jBCX%-`_~NanH_y4)H7Ay z8tDxD>A(M_CQ`jE;h&q^3l%**;;GXCxzrT3jJj8zH))zfsp*ERk%ie=>-$XMtGkNK zuU%dY!sWi?wJiq@w5DC)Ssqb`ij-D zU%fQ_(;!PHHK)}#rzO!-{&9hIy|=w{(S2$m$QV%&fZh$e^{1Z{KmQC=S1D+_6caxf_Oxx@@E3#aA*K0|T5V;|?qkZ2ZJTvjqh!E8=2H zONVTOtHRJeRPigiq@5-l4RM4frmYPigI4~6&RQ~m^l&L%@W~XAO|7(|v zA9NO_f|r~1z-!Wc7u5kl44%6n!Ywg6LB|t~NMSCx|IGkD@CQkcQsei=(u{Of?Wt8k zeL>5l_pdEAo;Mf%5P$(ey+LcvTg>OrgJ{vp5x-mP7yI4AmObkNsUvmSTcZ@)XNY4j z!H}e~QJGuH=L2Ih_clQO{c!5;_OG6PTAaEsczz&K! zDvS2ZVG8Vh-ZN*0hx?jOn%xd?b<6(!Eo%)eErwUd-+F7jWY@`)yS|JOGp91e7`X@( z1p$42EpQQWTw8u|*yMe5vD>a27Fw>$B0o0{dQ!R`##}TwXvQ2iqlX`l4og297XA3! zMGWRKpiP!qjCm(<*l#BccZ*ESv(H24tW z{kkKN#Y_0Q*arU5aH2DKHw|v2TYHAKJ4BUPp-|laie@rxlCAh}PHT-ygF|S>Zl`w0 z|6;=ato$2_`sQXsAm9+=VG#EuZ{957!>LJ%V~*V2wsze?ce>!^?tOK2eMCkmBIB>! zxS?cOQ4bQ&Z$IB>GKZJB*<{QeUp%){{Ks4j7!eq27qDPo#2kj3aMV4qchrGwb0ENp zq9}4s5w02#bwU4^?<1QhT|bsTJ|e1OvQ)_zUwx{+Dpc|%dFq!n=tzoQU$ETdO-US1 zNGY!B4_RK@yBL;OR2}s3p0h}m7X1|U^Vd-FR2PtUV>f4#EBL8N8NyXwHY!63{f#=^ z)t0L|PRk|q74{`?+I}91C?MyW;DQ79+`*mqX37PY+PS%PwRa4wTbN}kx_pq-5TJ+< z;=?!CgJk@-m;N#j@<6a#qIL>YTkW=!&34-k^beCa3Rk#bvtEg0g96IWK+C2wI>YBY zu$H*VzQu0mEyQe=h4zv1RUAEzD}eoprTybC%j~;L(9u+vv<~bQV9lLpA;($Lzt|c*q<9Ff4g1h~b!i zEAjvODGE2{-a%i%eEPVwPd5I=(#PKtabSPoX8ry!#3A*FBHHpBMbR6yW~jH@j;Kj0 zJDsO>a7`JXo_#mfubHB3y(F{scbhYap}-IVldB*^l)Eh+FMd?~Cj=}A4&)FBCSZ2$ zuCHHXL6*#s`jO0V`F=ZTA{SFt6mJ&SGk`ET}>{?Sa-Is{&}EW$fY^*63~_zK3;U@lBw`_nSDyE zs}uL_tvjza%WLH7Q$sTa=wO{yDOypv{Ml#MM{1OsNH}1>v5N&m5u6$8Q1IL#(F!`) zkZpvtMi+{JQ>!APBc5QbDs@Ul9D)e!DLgFX)?f76J#;?@^v0k^ zjEtV~u3F`VmMxwu9(>RhS}|>-yQeXXR|cg8{6$N4JKz1~zGY)IEj5I|%(LSs;Re>4 zT!^Z)*G*%)Dk>|w9L39e;WhjAYjNu^14qCbD^zE#$oO+LXn&0RLID95Q=#fL1A^+; zs>Js;ZdZMAr;*#HZ*SJLW3)bmX|8EnZQ!`Ztx7IkO}UDlk1OZKK+m)g(WgoYLdJS; zr_FiG%3uAGLCJ?``{SG&vQwV+0D&gRgw-XPmAECBC4yujbeWgX=!S>E3~st-1PmnO zZBxtktP^Mn$z3K7<@*9BYC?73Eyw5RbFHRE9nuAtwYQfAFMVafa^~x?{vL?b#wKz@ zi>aS}`rXRGR&M2g*N8^x74P%{j&QY&-KJ3atDlnr{;4O6{#&M)4TjSugQr|RcaSIp z9On2L5s5qtiBiFcGc&Nc9P%|6u7SGs(NXs9C<}<7RGJ`B6q(!&@xsv^zaf_zryLWO z?FcW}O9A4<1e%DM3Er`Dkb{3#s(Erisrh)CL%ebQ^F|hoiI9a3hez$e$R_8=`jL_K zKD|lQ=x2b>jiNvi=2Q5j6D>ggezv|c=+AB6?S{JzW&pmM~{YdsoP8)0}o6lOdUNkuAK7wCtd2u z(ec+0mhYV(9r^EnM@D^KSWtUDYUPIV_D^L;kNW+beextIAzzY?s^^stE5QUHc{qKv zL|&_-;FQT|9(?yvgP-MU|GZpDl<~`U1(~xG?L`3!pU$TMUNs|rv?ESNmp*Ge?`UtCIz1cnm+$RHX5mqJJ`TayimjWv=!4{C)^cUPhB*Liho&0T(W zfK?B$t1b1g!oPH2e{0d|u5h+5dwq6gclYt`?#i63b=HTut!zswnlnx2jheB20?W>m zC&Dz7cBEWeRDVD6UB_g~3rp2h%2L0`sbXF|FPWFkN{W-WbpGEIk>->XtDcQc^LJE~CQbg3&E$mOh@8X%<=3(#AT8Jdenv=YXU_eI72xcZnt(2L z5n;r>F{Ii_TEV(+De;vS6^Lqkl$e%3X0-{ZFVg{iMq0~Tg zNu+$F;YD#6K#5lpp(+c?p$mfrj9r`Og(>$YmWG7333q+65} z2@dRWfUda#FOk+2xU zKzxn^H6j@QhR=#zxakqmG6IRQqnyVfdc@xg>t2+Pk|||T7G{oN1j|3itJ)R|G#_hz zhmWKMR09%b4y4r0f0aM`7@J=pj*hC=G5Px*dkj*QD$2Z=NKI+RsfdclmAWf^y${q) zDJKU9ry?V!h6X2rRq9UzrjY%Zh~F`iA61KXyOaENk1I8`#N|REasvw+Ug? zNAbO51sIj?)7R9PYxGhUvV|68B1}S!SJp^DcU~fsDN_thHAw5yyv58eCIr`a*MyxRQy+~4P(?9iCF?6jJf{xsaXN#vH$(sdqV z+NwtBHkG1XHrp6`N^!oXrX98OuH9lmU4qO)wFx{e6vXtDb;0hy{|t#B2&@}n1Zc6q z37CNT;LAcoUYhhuNI+>`;1w+3rhqhPSGu-LRuM1#XQ5%+$`?km^3$GK5gPsTPm5gv zD+3P1uJ|c7PyhEDS^&pk&M&frC5#)n0W^m={|w8rEW;tLUwcji_@P%5-gKJgWf=Pf z=c>1535f8BlT_8vZ)M>s@s>KcYnJ}FdC7`Dn`;{5imR(%R>!z~9(h&d-07bu06gXv z*1R+D>50_|4Qbmf*Hf!q$yF{*`*pc?Y8oNWXVY}o_6Qy<2w(3LbRV$by;73pUAVfN zM+~yMY|uljf)y6j(&)z1J~4b!&5P6S$^oJWdxYs_X4^zL!?>*q#4gw-wdgDH_ciTYJ2vn&d&8Cow^;TSPPkW(zoJ4XH8eUU1w zq*7l|+|~KZPvf%^T5^$^)cd2pP|X@Hspj!~9?Y#c^aRrRbhPZ+A+NOhcBLgJtEjme z+Hy(fgr~|tGLJzjxbj16EmUCQnLa+`_t&? z(Uh3^d0SFYRg;o}hWE4T6JJ2Ok|@>TdFADKs%>|-=DZq&zYr3T&%E|@bo^x{Wk zW9`Q$#cGzfzk2(NtOs?Ux2`(a}4aYQ(hIiIXCh9?LiQMND=dF!Lu=n zUQsipnZyejTLGHGN)3yMMt(9EuQWdhZ92!tJ8}KafjVqx<_uWp(_tl1GU8&>X%6f_ z0y9T)0q=c=kv;JX<*lAk!{+v{Qi&rQ0Z;=5^9&2i2hL0%Jc5V!kI-j2PSGNL%CQXU z5O_{v#RKTtPauTyol63o17q_pm!a{Ay;RlxyeIgd>$5ZpyXe+p@ZJ0{S5S0#8F*!i!3x z9UEI4xa?lT7TN@h|v^nOk z_!Wzeoc$(p2z;{$yzN_%=psVv_D36HP@ZqBRdCr|XB)PLlsPWjOZS2E1d~Bc2~Q9~ zY>{`f2rK!gxz@D+C~v|ivfwavAg+^ zqsXaObpC5@>3q6RDyd3YrKYm)re-qjsEj(AmR&CGljci%r7uf~n9oUp5R3w2Ase@s zNZ^Lqjueu2N!TwgN`eksN^-_}lx#{~`HRA*m|%{#-9RMQWa_9e<=$}rdQ$}iJw)(i zqHMuh#@UK%Sx+ z*@EmB--BkW#`vDs+rz^)22(Sl&5s)4onBkGl7S1Ta3i8xs(VOnzL5)8goi04B;m}0 zK>-Wsc8aDmES3z(jcbQcyo_As<`694AN*;^Ai_JMz@FQ}Y^YU}Y9_4I7-;sdEo8uP zT_Fo)!kL;i0Z}5~vH22rJr*pswOy*K4+xUX{@g+mB%M{NA|f@B5&u0i`$T``QjpX? z{r|93#8%Y{t|`BKik8QE^<+iOYh3!~_v66K0z-M!%n83_d1N^=k)iE5XW)W+U{~vC z8ES)*A#Vyy_U|mLfSR;law@sjRSI66yAu+kZIy!LpM^PTr5a2h&oG>RpDmrmfE2mLG|#O`%vwv0?*CA>VB$jBRSh@_~G zXv)6|h%%K*EeMN#Hbx1%t}k47v~1mx^R@J=_D|Ly`LwK3b=P+3^vbxVXELT~2YS!9 zP0M|q|F5SajUI+QB>OLiU`%(@RQ-fW^WN%_k5QoT#fn4y3teyigx`;?$cmYJYrnWa zM^heTL6AzRG0o(AH3#^}!XZWyY`ej@>+2B0TJ_e2F_DXm{s?PLAqiC&C?qnSrl~0) zCrR@Jv+Va-LhvH;T8rdjJz=Lq28vEyQy0dC5sIIe*~qX{s^uJo^wv;7`^lB|L^ma zm5q75Z@k{y`}!MR?^szGkrAM=K?mzxKTlgRF$%%#H(E=%)xQyocKAutSiTeAo!Hct ztm@9}JyqTNXkt%x=P#;$2s`tDSVW?B@js4S+{YiNi25CXI28mc1oK>&+xQEMvz5jv z5AtZIkPae2{?D&Sf5(yQ068nJk4*#s3AJ9uvaecXb@zinIemdEelzzht+71%Oj*WQ zZ{jSca*vDW=a__gj$g%8i&$iekqDDNT4)ENE z(dP~b(O2K6b*Ba!c_(s$(IOJ_XE;k#QI|ffucVYudrjTaLA`5}M#`rWv-7gkM#g{< z$GBgJTT60Sx2FCvSknDoyfqF)OJ96KPJ6{T_G02U|)b`xA8m#Rsn~exLdM;@oX@IjGC61K7=jxutXV1mf65p|>{l9FgV!UaWt3ZzuQ zvi)8$?6h>>C^A11sZT_PfS!+n-Dt5aB}5Pqhr8bp8RDTZwYJ?;YVG0iqZAh>CTm{| zkE;G+(jKuQK>}jkKnXn)6cbMfg2vRcqZDTKw(jDX70w!aLl^L#rN(5~aH?*>;=!^h zJPTzZ#LHn~#Lh&dY1+ujCMgCpafF(b(E#tsC1V=U^1n5QU>E1vMf;2cKDSElJ+b(r z4EI`{N{bA~3QRiu48HGx0DBcD9W`cacVaRWhSGDc1_sBf7atgO`8~YY&c_wkbD9G~ zTl`7Lb+@K{U3@e1>s{7YHsVc(dQR75#arxOij1$@wfTa#;15Sfe>akWBiwzx8+)75 zbtX&PXUde@x9=NH3Qk3Hb0{@9Y52bK3z?$)OxoS3RyTG_!zv+a0SQkCUTZv)<*fVO z&)pD%j`|Z18f;hWPe1WlhWo6)1Sf4Ci<}Om?MQlAoEjD_i6}$is6*oKP+LA{#OVC4gWg90XsI zBYJ%x?6+*ewNqL)#w<87RWbg8u`5+#2Hs)4=-iHC%^1M~V+`>T3TBBDrVO%@Ce>u} zrLF*=@|`r#nmH{$N)ev35!GNv2XFD$=np>>MKd)KcE)k>s932M2$!hx+*+fW+Qs6BMJ-%@Tx z$ENGlC=PTDgBWc)Xbhh<3qNDEm8D^n4BHmDHkML@RUBv@GDfAGE=j3WZzODw!<`)R z=bW|9svgtO;eI<+Te~i4FX^vW^AgL2%HsSdo3;jNwUXOvjQ_R0-M%?* zWf#V33+V`ujo*N5&kPLIBYt5*n5V+>eZ!sqxz~tu9Hpg{n2aLE|f zpeCFDCz2sN!^ePS&{ixH#X))x-xDz8;V^dEcQT}LTVr7K8RCR-lD+&h7_G}%h|BPn z-#fE|)#X{Aw|TSD6Gw`M6URp^eJ)9hMm3yMr9HliHlfW|!GL(d_N1o3U{$H~2GA>- z1O?U}*_O)2Rfgu~16;FVjim{C=|q`Q#zsp_K5w{*LBvXP_@_%bnsLUy58TyW+-wDW zl;Q4VE3EvFr9$$nVz^}s+(KvgkRzgsq9OwG+BNUd%DljtwO(BpyQ!ry_Pd7IR$mN{ z!FREZFG=|sYbY~8)|i;t7)|?o$}`gmHu3bvXiXzkdPEF1YF1Cb;+FD368YWk?;L&& zT$P^{9X#CA*x)hVbk?;y?OJUu(r*Y`TR%@X(_|Q$SsIM>dkD6h6|~|St!4x@QmfU9 zIwn#Ur5E&3GHanCQWL2c)QFDMymAhl3&g~X-d0NIoFkN2jG33yFEgfUyzp#s!u(0T zIiU(IzInV$nA>mU)X0{GyyxzoOEJuf2b{BpidOqo+A10pudnMb8LvDx4tnLcT>Bw7 z>RbGmlFH4Wj=wZ@Z0_i|XP2*I5r4n>q1rp%3!9kD@kMy!yU_Ld;B|P@ge`P2?fcq%YtOG zJZV?JeJAc+vHP!s=9=&oZ@es96Ko07Ca0&w2Ddc2GaGha)WxPh`7)LAWD=rd{_yIW zp0r>{wtWwSE>^`ZTNbF1t_*ApxKB7k@BV8~+v@!>tMi%Bo2jR--BtSkS4tA%eizHr z{%|_!6k4&X+x)c#%b)v@LXFwVlz8k> zFSTC%_0tcWR2!qs8Fm911@rTHS_9X7FWI+GB&yZ*J!{n!`T5-1RpouYsk3R@oH;#+TA~h2j6#408&*ihkIr;L~0jSSvSNt6A5WA6G0J zf(8ZP90poNVv%4CY=p%eCnr282cxVNaFNWitQ+AF!qb9Zl%|Y3k#kX7%XtJONI=qr zxcSf=;SP|}rGAcZF4se|7A0~k$8mES9wbUF!L1(beUEWq;+TPxa-4~=;1S1Iz?QyAC zB(E}wRyR-?H!=E9oN#NWxk%ZkfxJoxHZxRQH_?OW!&-2N3zblwc!b52q?woTY!912 z8gs?)5+3h1TM1s$1^fE@*wq$vFJq58tfp%NqAfrU zkbkAnO>N#>T+9_c@iU@0EzXD#MATHAVoss+%y}$t59gjcJv}pX%&IM3<-RsFM><}2 z4$mPBk=*62`tnT|W*zr%XilLmV1&o&7TD$To;hQ&c(owhn4Hc!w+EdpT23_&7HX_* z*4u#GV#IJyMP2g_-iOG@+eaP--D9|9m^C;JiQ{eFw$IxZ+Dx0iIE<{O;)@E|?CgF; z%#AU>4jUI>+rJH>!TF9Q8SRRZWq!j4nn~Vn9-y{Ck6k?NWxXI97oBzIH>W&HQ~B=1 zrgRhYv_e$O8vTBn^d@i`soIx5SK(P6*?2tjP0TynR57%m{G+oI^KAT5JRlNY`>rNf zp7Bt3<@4RfjU$Y}Fd^Ihd}ViKEFiC@rh`NtVMb?V9cD3$4`)4G+54>_eYxA-Fvre^{)m?{5IPk~0^1-;DDMp-JD`YJd3Y7oL0W+Ou-s zp_|}&i-g1TbBl4FgH~Wf6pR5vI|Z8U1ozHTa20D>gVarUowlILH44s>D^_U6DN;qi zgtwWRUXOzL?yc6SD$!+C2XAQ=U08tiiGXPaGsxPzGb0<3VJ20UDx_*s-QZ$=;vdoJ zmWLV-X1*m4iIU4QXJ{z0@Q8@Ghdrd4VpCBN?7dz+4IktNC|EzPp9A^@?`SPBIr z>=jgv^^V9$SXRN|XzFa_uRfAHGbWjCl z)pC6qI=^0#;`5~_{N>TtgB08GTZ*9T(FOWBaaTco5QHd81${tCG4@sa4Z}#CRG)#t zMq;;)HQXv#R}}eT=i^S<)Tce9ku@Cj!|0FS6BCx?irj-n{_x`-sPH=neh~4vv7`fzc@uz za7K{=cq@!R1OVMMA-eQ}0k;nCPc4d0CbHNv9}&r-*M8H^EHD^XeN)T2u+h~exMA>2 z^aRopms;OIr$@x~>zELY9I+G`Qq<_bzDFPRk^;Zf`Q(#}(PKVKs5i9MH|Bp%+1ff* zIp(mld{)1K_1{e6IlaEU`Pj^)dBMoqt|Ajg2EOsR$1&F$Y@o*i*2e>KjB|_9nBRSs zOXW)OLTy{TjBIAzZ@lie+Zo~EWud!9GSlC?3#;!g1G{1gr|$QiFe=*zPRq*OU!<9& zWMd-E4G=aC-oAbHsmlGn^6K_n(mCKEu|xmpqa(v)xX-siAAPU;8Vxz58-HwTR0giu zfOS`Owo)ahysj<5Rf0qyMwZsG|FIA}0*&QXPHvTpn8U(1_y29$I3+uZL>i1cyk<31 zl+2xsyDx3*V=MQw$t4%#nB?M%@sfFo$g|=v7AG@t7fU4cxndDjM1M-+V0Q<5;=Zl& zlyf_3P|uF+WoMSr|0;dUh^rPq`S3IrKCJ!-0B$izLAsj8nGD;caT}K8lM0`&uCB7u zM-N36u$X9{-k;{_RgXNfiiQuv4sXo!1<%LyK6e6dze&xcjM`eh&MZNIBgHEpuMd~m zR{VVZ$Futfz+|QniF&cH-|9dP&8O6yevbN7gEdunLttd>*v6j1^XBIJ_4H!HUH&7k z8T<6pg$p)1{hMlC8FW`w7BVSI{3;)=p=iK0kENH!8;VWw>5s+2Swlk8{EhqS{OPlo>~5R;(YknKK{gg4KpdQbhpCDdqeC`g)3Tf)l;i6OUe`p& zOycQ=>0DZ7!-SXXD!>Js$F{LO(Z328q7vU#2Kou`RKrwm7}fLt*bCb7&)hkRD=|k#*R@R2r zVE`EafLkIxyzU93C|vT-2G%HOc*HB(m^b_=fQ-j#1qmz>17{2jVxa~D&ar6F8X0h# z9BFvoTAwzqa|`+9Uw-NJ%kZ!lP7LBq!xD%(?S=Mt;a%4)(}1@l$V{_(@r%I)wot3Fd8BV61&t-t+Y0-VY8&Ea8v)W|SI>z#PVgW&|$ z)&cUbO`e{O`Xqodzbhgwx(CF*V=p98A27? z!dy_xz9{@6Np>DQSYF<@uw_fE@z+paem?bZ-^*YEnn3>Uu{V?3u?NFwl2#5>El(^% zd5#UF2lgftvdfQI)bb~f z+S1<6^Cr6k$YTelhc+oYqfFt7dObA_9o04 zO-1h1-J3}T#3#(x6xY{@)ICGG-G`mdc_u8a?oDoR+&a!e^gc5~bjhg7Vn3H|q&M9a zSlWDZv2|VuGNXQEEA_-yWF@@*w&A|sX*OOX3rR|8k8mvT$=Z7TOPyn5U8rv7&N}&` zK0#RB9i^E<9bR&QjiRC$=5vATHu7MP+|sk(jtnc(6@bCXmYbaRfhzb*8JZ3`~3rQ|ZFhb>bWoXqCZe7f&j`y+qpNYRKLIm^Bc*{mCV zr8MChSNIl!$Ac$0!uR2er)*QNtWT}BJCsD}6a-7cb5-_z7mhyAV|Q|0L3dR*haiuU zDTyhO9gYOlrrl&|`Ck#Ajlq>ehhQ@EJPfVb>CqjGoE4J(Z(3_lj>v}QeqX!4-uP&& zt}^kS)PdB1#vADNn(RBD(OegcCo=!QX+K5U4+{-(2HDGv#p!?hdsi{=qdv2Fo02H^ z$1KDI#Q1jx9#!TT4%V69kZ+&=tMjx$-y@yT+ut7T`YCFhJ7Y4~@t+|BZ|ua*`jK=jrQQ>24%on~_0koZU`rW>1mr3EBQYW334w=o2m2uioq5-;SS%RP+q{q^Z zqV?CfamNeW8G+HCc_BG4`2|y8!uZo_TM3DI_lDG`!Nt$dFHFxKoE4{Pr~FGxogFb9 z9b(=3FX+AiOpzD3MSK|BUMAnHK>kGolg2FhXBC5s{+5B4mzzA|_1FC)GkwdPrZ|m9 zoX%b!Irjc==7Nk556hPYWbKKTjmg4mcHGH;*HPJ5^^8{DKZm9!sXu)FkHIaJ1=yxW zb_Kt5inm>w0vG&(oj6nOW(ZTwix?)|D-ja;OJ!)BnP50Hu^U2*uF*WB>bZ34)Fme= zcL8%=Ik`kmny02_9;~ZdPEDEWsklUS2C*=nb(xWXIlT z?bZ;xy?@jC?8*(Tb@Xh`$<1#JN}QV#bF3fuL>jQ7GkO8~8s zC{w60&8*iun>u^NjcCTGl>J6FjBu@;Br8g~oPPX2i!NPkGU@9x8BBfV*QqHg+-fjb z!>Mssv713mEREh1s~7aTCp-SQIz_t6us(Lr$eMcKR7Jtz6%E33`zF>mYmzV|7eppk z9E`;b)|{wXQuR#OA!I^_!Y(28`AsGNjsy99Sc>e|N-{H@TbvQxrV017UsRFip^*6R zOv+XpSv0&Uv#wlO^HDSjGZ_8R>a66i*8yMnNdOYGp7kEBut>*x&5rAu$>$IF{u>{t z?b3k8fQGDIje?R*QHz2i;Jp9tG~Z!pRq3R`htxngtiex6PqwA`i%qpi;6wDA<^AH zNaxdqBxS7)sj2TDmhYav(6CXW+^{@j^&JS2o8cS$bjr~7r|P-x*G?4 z)t|9y>KLX(?YKQ%RpcpB`JHjj^5yVR*fyA*jyarurPbz2hGF>ce5?Ghq$l}L>(VW1 zB4eShD;bVaUa$U4Y7}lMywXC{5wStB5j(y}pGu#^jiA=3b_I?8+14I_3WiZ#=JnO1 z9{;3VUqt>V5pKG%WL|=>0Ho*W%zZxm8+2E$WUQCnTUVmHP<7I;D`}z=i$9(CKx?%9_NLT5?=Y5Rg^M(G^ z>~bZX4CHcMRlji;yTnnTS`w&3bnA^^M;~mV^}Gz^=?wDJeRUego}S5w;s;Tl)fuJk;5B&17iHYrvAtFzw|sO%PfwnY(|ZX&69Vs7K5#ITwTZypI7=^wG-?hL!}%gHyhKWqQ& zvv@t<(Y4_Fy%tMctV#6ks8SGBSAGKnj_qFfeO7Y!?&gHi=*Ljlm@XswXyWH500+lE z+S=d8^X26v>ddZIY`JIuN-Qa81;@V=kCjxE!Y#FCM}F(`KdDN7(m(9o!b~bPk&dVo zWlEGIl9Npp*f-sVv4UJ(Czjk2}p2pjX^ws&1QK9*{s-QbQi@i^``0U zongk22RX>8wFkjNZTRp+#G`BmU9##Rk?b7%VhZ=IVEs%uDxqDlra^9wmSK#S15b!& zg~wxMLj5Tkf&(CGxR^bQiC#p3MA7@;1AX4H|8h^Yczz{s?P6HMvdmL1`R2~@;JztK zzQuL>e^>=F4iKTkQp9dVM)>CM5@`=@&9+KI-hCqphY5=~;A27>dO=-!#-qz5X+r^_w>MH*9EV zj`ZJ^)_(;k49gN$q;T6Y-;1qs)i3;e41^a6T^e-sZ_;LaMad$dTX6Io?YfK-&4r+3 z@!EuX;uuSGuq>FYGq0<&O9adx04^h4g5i`Oc~Rg5m3c?d-YGa??`pRoEd8P=fV6VX zHM3UsBO@q<-^1Q?gz?(lJv7#};aRsjqZEv{P0TONB>6ek=n=LIz-ac~FOZ9u-X(b;H2t*BmM$YHhBDQ>t zKHlPm){Cy&S^wgT_1u!dp6UEYjC|ooHRQG8uI{cvjm|l@K^-T}mBy(XCSM$o8z49} zB!Q#jTvz#{sZ{i*CG9Y_s_WKkmPb@}nI)1&#a)FTt%0cVZb0hYsQay`oJ-0pD_>c( zabwX+z4yF~{H80WwQ$m&pZ~F8okBgMj&}}a4msnYO0jOkKYpg#*Tor3;x1)>tGlt( z7rWBUGgb}^a#?<7Gg9?VZ9_wXN_SJ2=*~LT?>B9JF6x?rd!+Zj!)tw8d|UbsV2aJi(m9@ z2735}Q#%f1edZ1FZfh<2-NBn~8IT*39gwY1NJ*dZyXNoyr8Y5=Z&Izhd!s&+ol|he zZY>A=^1gK?DrNcH8TpA$iaa-oh@@yIzFlltKT&ihJkZ1lOtDW*BY9+1H0ik14D?cv5~2V09Gfn=+c`pPOHFyWLVZBT4r1x2DwEZ#yrJ^ z{sRDpS*H@Pi>VCGbtz3&B|ZaoFzw#%;i73>}8!_{yV(CDNmlObGv5H4t z@#Mp_Sd$UFGjeB=CT_wVv+-$1> z@wZlvYh&oGo4^TI-xvv}yuVX@UiNRR6tO=4316&Y{Mg&t&V_4-BpF?Vks2T+I0;!u zsI{9VVzRch_IDRCEMWvBFxM+z9PG2wZsZ1Xo1*$MHfKD;)UopXGTIp9DC076^GQ~| zq!c=j@Or;f{@*2F@JPzzhyKHX=f|zOyY5GVw^@#f#Hkn>siNqziLCe6R^}M`rBZRu znt4BKB1@>r$=3xCZ$cumwUtdtnCwj9J>L<~p@}i2|r{-hEHX#xV3C zdP&UuhtvPXtgjDGazKEjIdW&EXKj#qqqFxmPnnBRBAwr|7Enc~mUu7cOs2tzXUf;Kn4}EWx2zfOwklUnPi>X0y4H={T0nJr zVz2K8Lihch{eL`Drt0>M!G;hxpnPW)2VwhsrjgsX&&XxYZx={E;?N!!AJ(3TaS2J1 zjmnmoa{2 z=<}02=uWx*&uI+%$=x$U<5o zY6pz0lX^6r7v+gHl$~M?1bzPlw6LLaW(FYz8dfsrX~D=dBJ;=yG~@a$1C2dIqL;WL zZ+ZGJ-X^9t7riw;{?B^!bfP)ppOvyGCQ3Ha53LfUsd>gF`7_V3JZCOIW;6fFGaTu7 zF?4%#mW(}?3$&b{lANx|Z-EeFEo;X6ZZ*c_F4c>=MmKW13&W&zmzlgbc-|;fm_0D- z^|kqmPHRX~D`z8tBuFp~$P}6zoU1ZIfrx&lEJr*uFZ`*3iuM%#N)gb*9+9R(*4FlNDV1kAi;@ z?(_lrfx1QHLExj}U7Vfk(8qR{Mo-Y@I+ZeaDOV|NZ_mx4B7$Fr40wCzIMdC)53=mG z*C(&L?=QC@4D@<}iQa5J_0f2Ru7(-sc|A@p82ST%sOTR*WR$ZkGl%9F@XqZd?t50Y zb=IuqADx=&Rf4CdDp-t~nC9_$;743T#pr6#F>0BvXnKORfFhZPxvRxay5RZN7yk5JD5! z7++@w1qfZcvh0&jdU>8@@4p|$s35@7*GeNL2(YIt#!fyRWZ9txfK#eKtqt#Y510Y= za0$1;Czf?_%xw!h0wX;~%jFEsV7fgGh~x(8e4~c(FaTtuZBPap%|OZL83&KnB5TV^ zxhL0fWs|rRnL)9iu=@m0kgB~Yq|(npm9r9#ki|DS7aW&vOhAPUxgGe8A+=7WAdnU} z_(y8nvJ!Ay$&mp~hDE&$_w+dv)_bFuX@I@#&VSlvN}>!px$zmdCOCFt zLfpGoG?jbLtgMT-_CvN==VyiT4DXKYx`XA|K8bg?eE9bZEhyM6{wa&hL@)me>Lz*e+j$~5+xz@QNgz_VYJ&UGEn0fP(u{kN=EDXA|= z54@WpXSDWfZe|-;{hEe`HAVIHMfnN>LJut_8gnVJt2jL+ic`~-buGRYkmzy<#yFF` z{4YEvID(Z_YQm4PC^q+?K8l*uOj0N{>PImG{Y%SRup}U%=@$G9KD38DBL-vo-$iY- zlB`b^SsQJOByn7Y42|ihU0*0X8)LOFs8V;R$?BL0TG=q?7pK5QkBM^1*w5I3ek0>D ziUKDv<>j+!wlpaAtKxTjo7bQ4(y=1f&ZM{B)0J#^YfIS#o`5|~THk$pzq*0mnG|o! zZTj|9e?s%*u}8;tCB1$0%cTwm+~ANq)aP%b5sQa!H_$~4jn#WcJCqaIa5IBG9OrR~ z(}rFc`O(%NBnv;%!{PXG@6MfLUiahJgJm%09iZ0a^777q-*CI6x%ogdIY2IHwi(HD zFevNa_Ro}=MZrax(YcZ7@r|X)nWs>&ws2p1ipG?f9S?}wSk{W z4h1RC{5~r4QB6^Jc-ZQ*K^pP5Ed@E1#f?#c<(oKy=!pl!pmHNAl@Nn&s(b;>%!26D^t+QEK zvt#j)DAnkzYpY1?s#Vt#^SHdNKN8)U^}pmbc<1K*vfjY1r3E_UG5xthgsxs;K?HvH z2LHCD6>AGC*H)C)xmfC`%!X_Nlu?)kC&JhPl*CGFCtdu6%?&M|t6L$sad>7;raUNm zXLxeNBavhM{m>;7pbn^x`dTVAN1&GN+L`Ap@Vn{gr|a*K^HG8<>IP3`=)Ag&pQ?1} zJ830R(jod!;~w7_5YR>5C|rqF$JO}EJ8uYCZPXO?H(bz=jW-^hLJpoVpEH5r2D+j3 zSM)^`k{y%L=;jY63949hk*L%JMx;wZ zV8!sH;yOV#^gXgFCE(cTw$=rQLQwGaVg`m&3oz$}pb}it6)Y#MZ$ut)_mM;Uan|Q; z3t938F?I0a47VRQc1Ns5n*jsVO-N8X%**d8jTL<-v zivS|WSkXii2lc_8updl2nl_R)ng*-GTE^*3`NMs#wEwmE^Z%6fr;9T>9!c_mCC@Am zR%}%g<$PM_;~9*r=WZ-Mz$MdCf{3&DfURHD6B8Yg*(XM2pZfn75Hl~|ugtet@^TmM zzh7N%N;qXt9OXC}S8E}ylW?rR8Z=;+8H4us3u;lNO8T$b5DqL%hC z^TY2x$gpiSy6bI))`YO6g$1F%ErAJcIG}W546}Mi0 zoEoDPoN?Ao{G1YUU_3HMXTCV>a;cc8@%PX+apkjMd0Jd}6DN35k@)#3hU(XBcGsp& zA_(eyEjM*V|8WvRt;$wiGR&$n+E-jIv&hlNeWAA;3PkR?ww;X(m9Ui6KP-vr|jhagjl0e(;u{$2!=rz1!tBH~>f?YQ&rbmD-AZ6fuTe>Q&gx^=#b z+sm`=$+1(IyS$QFsjlr?U;J@EZU8r-gxJTq@9Xf2`{6u5`i+Z(m)w>b<#elMh=guf8g0zF+W-JBEqeNcpd)Mmvq=OW*wL zqLebnS!o^>|H}$2xDK6xj!q<%jl{QZq9H@+`zkKO)kROGYUOlA2? zIzfJfDsJ%Br0LYUw7@jAw2x9Jr@yIY)OEb4@x^JYRkS-(suQ~xrKB;q zvEb%cNzGN~rUl59lB$y$$CK0FSs$pCjR^1iIB}@wm7cOG*B8C$Q?}V=KC$m z<%i3vK#u=EU--K*oB~f}Cjfr*ZiY|!cTfEwvh<*Js#4sXS3u{2>{A~sn$M0R72K0s zI8=ie-=(pm!l60v`mL)1?}Fk74?P)@_S0yx*Ft1}$PujNPeEhOtqs+|UoAO!paBmz z*n{$p_B$VZ?Ft_}lTexwO1rz%1oDary!i5l`)~&L!`;!B2Zfl!H~At2ul!5 zJtDgq!>XA@S&H=0GMf|VQoQ~R|2PtL>2&#Y+mF!JmkS7lqZ_pjoAU$dNwWS zO0&X7VwQs2n$}0Yk_JKk{XF_Lm2E1g- z=Y1U)uQPzwSV370dXs0>&JDEr2;vonwvYkBlul3`ii69q0_!e{e-?M>97SlbAw$}h zFYsJp(r}zPkg5@$##sP=NVtJHxpD=^`y*_VdTY?LV9LcfvSFi9HxV`3U@BCC$RK8d zW_R;e$^~E#Y`G9^+{!X>+}=dMj*K`=-QmMv8l3MaSe7-8&=_qt@VNx&WlZQ90BNV;w2nz>o8@6tD9MJe=-*!~dmG*n_gj{LQXkF8{(2#7 zl`Mu2K0vGu_IMVyTK6nM`|~X7t7%zw{45S^`BM>I`Au`Z^)XaGU3J#Q0JRO!Pk)1< zse0?JvmQFC3r*Kcd-b95dg!6H1ufiv<8{p2JL+eUybi6-Y;6tLguk^_$$0h1VylXhhE_c(^)D@3!>j9uBbt==Bc(c(rftQ_by<(>>?a QW8}wPUeo^@jR61v08@RD2LJ#7 diff --git a/esphome/dashboard/static/fonts/material-icons/README.md b/esphome/dashboard/static/fonts/material-icons/README.md deleted file mode 100644 index 34d980de08..0000000000 --- a/esphome/dashboard/static/fonts/material-icons/README.md +++ /dev/null @@ -1,12 +0,0 @@ -The recommended way to use the Material Icons font is by linking to the web font hosted on Google Fonts: - -```html - -``` - -Read more in our full usage guide: -http://google.github.io/material-design-icons/#icon-font-for-the-web - -Source: -https://github.com/google/material-design-icons diff --git a/esphome/dashboard/static/fonts/material-icons/material-icons.css b/esphome/dashboard/static/fonts/material-icons/material-icons.css deleted file mode 100644 index 51f2e0a0d1..0000000000 --- a/esphome/dashboard/static/fonts/material-icons/material-icons.css +++ /dev/null @@ -1,34 +0,0 @@ -@font-face { - font-family: 'Material Icons'; - font-style: normal; - font-weight: 400; - src: local('Material Icons'), - local('MaterialIcons-Regular'), - url(MaterialIcons-Regular.woff2) format('woff2'), - url(MaterialIcons-Regular.woff) format('woff'); -} - -.material-icons { - font-family: 'Material Icons'; - font-weight: normal; - font-style: normal; - font-size: 24px; /* Preferred icon size */ - display: inline-block; - line-height: 1; - text-transform: none; - letter-spacing: normal; - word-wrap: normal; - white-space: nowrap; - direction: ltr; - - /* Support for all WebKit browsers. */ - -webkit-font-smoothing: antialiased; - /* Support for Safari and Chrome. */ - text-rendering: optimizeLegibility; - - /* Support for Firefox. */ - -moz-osx-font-smoothing: grayscale; - - /* Support for IE. */ - font-feature-settings: 'liga'; -} diff --git a/esphome/dashboard/static/images/favicon.ico b/esphome/dashboard/static/images/favicon.ico deleted file mode 100644 index 88dcd7e2d1ad8943943707409de2a8eb8089924e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHO2XvHG7M_qw4IPx0P_t5`NT?e6;lL4)r58cM(m_Ckh(dsaAOsc^QR*rPNOLVq z0?LvgQVao%aE!VbMPLQXuAzm5natbod-LD%&(BOUV{kpYC-0nZ?tS;Q``-V`ePwL6 z09&A~d2<_Rgl%U7n=RUAvqeN0{_-~4*N~M5W%z_3n{5dqL?I56*hT}CYggYzihqk+ zptuE!Tj2ks1p+Mfpx=|s678-y47aB9CbQ=CYy2gUh@cB}5$zZKMJAN;g5rA%Gv{v< zb8kq`o;?HGv}qH@+)9)vq1&!==gz?~F)<;Og@lA?*~1S%9NMEt53EeuX<3I39m0kU z8zws21`Qfif--ZQ?%lfwF)d|*fq^=m5ndD`Wkw>C)TmLEy$N)!TD7W5NlD3V-MTep(}0=yjw(4h zIk$4<%9M2m_QuA>s;yhM(tiV}3D6a(4gmoHqJL6CLV|+JjfjX~8U2Bb)YMdU@!~~= zcvs->U7Z(<%R7OTdUg^@M zDeD5*kZ)o7GnyyII?*xxQGj{o8I-)!#MAGqjOnat&3@rYIlkzm54e1;Uy%)f_ZxuY zFcbLNgiOpc49i=)cCGsI%P&=0TADg@=8PMsPoGwVRc7{+x5n`#lf2HIJ7=$3w=Uab z20$lsnak(;6Z$QHqC2awALl%)0lZ?(Un|4fukCvENWxqf9_ zcYt>%u*bk&&>g@>z;_iZR;&kFp#Dy$Q}?HZ3m2-sefz4RLx(EPHC9mGuU|iP>eMM6 zpWo4ollCu2f2kAa<4XY7&F6r*2IhhGsZ*y;WzcKBw7=136~;Wj{apwO3evJon>J~+ z*^j(9fAn91vS<^m0yYBc4Qv9PShHr$s-Okx&-Q1(YTC4^)(>M|QCBNMLqm1=#*G{A zh5piinn1s$zzPE^L0P9=z!hKmkF=q$?Z0{R=6k7s6!eS-78{`M^MN;j0`-^v`{}2j zs_E0GtLoLO>-NQ(sVUOuqtdTnn<}m+GH#^)ChAZBc^>6l16~ET0$YIBfo;Ha^dHWj z9{Z2zpPrtsN|h?5brl_vCZF!&hdrX@BS((Z>Pj9a-O2i+|1#u#0f+@S-nnKFPXRp& z8^2OM>tNWDN_0f}e031{h!G>SxHnQj&bz?hfPDaM^ml-hfKT(MtO;EID^;qb zbrxNbhxYTka9q>BWy=<=e-Y;&S-+|y&tbp_16;dkM=S!a`Z|6^7f*Gd?yQet7pr?T zer5h&2tC;@p8y#KXb=4ZcW}{HKL@BVoY(#%{a@O)kn(jf^=5n@+FQq$?*xxO`kUMTF8yoQuC4q3uhHL^ zI-28n(BGH+7j2)nb?fK1v-S8j?6;pi?7z4Xa~aORC}G{4G2AMn(QGxGt$E4*t04Vl z{x1*R*~WAtbMqxjmgITUclGMkQ&CY-PM$r`&TQ7Knc}$u&r+~PYhL6SGm7yU&Um!z zm=5W+jPgc}8fh8NA0#iPVOoUSjWeb(R0PgEE?pAeesz6NhiF94#QC0DvSf(^XC#~# zUkASD+{n2dJkOjqpR1dSf}Tq-qN4V?TV=R9K*bZW9^hYl2)u(!Jd+9oDFcFpK%nK z=klUB(SJMJ2WzL@ur=Eu)3l{amn!Z@z<-URT7qVCuRn3(M9R1q?S&;=!N$(Pc`aqL zfVf8-aL%QB4EPbq0%8F= z?m=1p1)wcJw*znh$AL-!$J@sM&pqA%=sE%y0j8Y|&`H@YUlD&BG-wbC`?8{ujqY=3 z+p=iGa%LI&Pz8+XQX-3wj}L$ySgA#e7F^%8Yu>zhFlB4jta0~W%+;k}b5t;*cc#|=2#+7hudHmNAi_$U#T+mQnU`t`b)i>tz7lIN9`7cVj_S}fcu>mcL2rif3*e7Hh|TZ zu!cz;y~!oqS1R@u^Yg?rAIb^3f_Sq|uGqf)h9a8m4{4u<7|FQr;OM4fxx&*=Udkgc zdlZKG;-o&!v0uFkn>P`OYT*3;6!wFkgRccUdk4a}FKB`L^ygqtJHd0$Hh?_#Ky$&j zg3X+TJHp-IB~P$-Io8h&-0MhP;`Z&^rxq<*#B%0wAGKr04h4Sx%$YN<9yxM^{Hku< zx~1`sqFlLhyYaqZFL}J1Nk!O;$B!RZ*aP1N9|_oycR_kEif%XD z>%=<{o@-x0p62pJ2e!#8z-mCs=YDq5oH=uJ`9}Ta?p)t(@r=bIni z)6kjDM4j2$*{uInW_%mb-`z8#dfkcf{rUAT7sSqt2f{PclxMG_8*J#*az7Lub}@Q0llmJnXmMH<4jMN z%kt$O=g^@;jOI)E(*Ep!XMlyk0^kMA=c7SKTINOWM*bE znm^ya4$68PprcMfn){{y#?-ys*fphK7ojY})w2j0pr2k9(Yools8f9`WlC@_T z^MAA$eDm)=QAQb4Ia2@1ka4Z1GnYSf`0(Kh>!*WvTZa!HRy^Yup7RXPF;h}f6#E?4 zY4UulN1ihJ5#PypunDa5udBqnf3;pX)4mTuX`Ey6*5@|(bevD;wQJXoeJ;Iz{rb0f z-VHtj=XpQ&?%kX7&;_iW7s%u6?gxZ@&bJ6ypBay9E1lHa<#YY!Taz)Cqz^tr-LdA< z{+MsFKjKaH$7dFXu-XaVDAz`;B+^x=zU3 zQ~eR_)$x8r#zZM3FU^>sv_s1oJnhSH@`ejXxY(TKjJOi!50!SH&g4nWJo6H{@YXbx zOFHuYI&)fYJo99jIB&d!7t`Ol1zc04hu_rbPgJTcTrIGLW6+0w3uG;@gR%p+00Q(c7@2jz7UVFV-2F>6O4P$5ZrMFM@B}5V15c|)v8qx=A%%n4!DQW;}UmXW?$_ChX#yxC8u&cYn}fAQKvH6w<#1el_-RCuk=Cc7S`EJgn7j;N#@-y*%FX zb5AC20PMTJz!Q)C-}_wa;T+h1I(c`BIo65xaKiTC+IxrTaql=s5`zvh($60fr2v>xd0|;nm!?KKtym*MV&CIk0nXfzQFdH3#$>_Z?WXMlrrPq}TlickIzv-_M}W z?23trc?*5!Aok*KVx3QfJ&*+ZU>n}(o<=&k2V@p{T#&)}Kna{(lth0FAz(X%;T$6b z`G=y+P_$<-_7Ei*w*Xhz*m0NVDG!c~!knaYoh74_;49}~1ZDw;fpi1pDHAY`In93n DM+@@2 diff --git a/esphome/dashboard/static/js/esphome.js b/esphome/dashboard/static/js/esphome.js deleted file mode 100644 index e861f169df..0000000000 --- a/esphome/dashboard/static/js/esphome.js +++ /dev/null @@ -1,1021 +0,0 @@ -'use strict'; - -// Document Ready -$(document).ready(function () { - M.AutoInit(document.body); - nodeGrid(); - startAceWebsocket(); - fixNavbarHeight(); -}); - -// WebSocket URL Helper -const loc = window.location; -const wsLoc = new URL("./", `${loc.protocol}//${loc.host}${loc.pathname}`); -wsLoc.protocol = 'ws:'; -if (loc.protocol === "https:") { - wsLoc.protocol = 'wss:'; -} -const wsUrl = wsLoc.href; - -/** - * Fix NavBar height - */ -const fixNavbarHeight = () => { - const fixFunc = () => { - const sel = $(".select-wrapper"); - $(".navbar-fixed").css("height", (sel.position().top + sel.outerHeight()) + "px"); - } - $(window).resize(fixFunc); - fixFunc(); -} - -/** - * Dashboard Dynamic Grid - */ -const nodeGrid = () => { - const nodeCount = document.querySelectorAll("#nodes .card").length; - const nodeGrid = document.querySelector("#nodes #grid"); - - if (nodeCount <= 3) { - nodeGrid.classList.add("grid-1-col"); - } else if (nodeCount <= 6) { - nodeGrid.classList.add("grid-2-col"); - } else { - nodeGrid.classList.add("grid-3-col"); - } -} - -/** - * Online/ Offline Status Indication - */ - -let isFetchingPing = false; - -const fetchPing = () => { - if (isFetchingPing) { - return; - } - - isFetchingPing = true; - - fetch(`./ping`, { credentials: "same-origin" }).then(res => res.json()) - .then(response => { - for (let filename in response) { - let node = document.querySelector(`#nodes .card[data-filename="${filename}"]`); - - if (node === null) { - continue; - } - - let status = response[filename]; - let className; - - if (status === null) { - className = 'status-unknown'; - } else if (status === true) { - className = 'status-online'; - node.setAttribute('data-last-connected', Date.now().toString()); - } else if (node.hasAttribute('data-last-connected')) { - const attr = parseInt(node.getAttribute('data-last-connected')); - if (Date.now() - attr <= 5000) { - className = 'status-not-responding'; - } else { - className = 'status-offline'; - } - } else { - className = 'status-offline'; - } - - if (node.classList.contains(className)) { - continue; - } - - node.classList.remove('status-unknown', 'status-online', 'status-offline', 'status-not-responding'); - node.classList.add(className); - } - - isFetchingPing = false; - }); -}; -setInterval(fetchPing, 2000); -fetchPing(); - -/** - * Log Color Parsing - */ - -const initializeColorState = () => { - return { - bold: false, - italic: false, - underline: false, - strikethrough: false, - foregroundColor: false, - backgroundColor: false, - carriageReturn: false, - secret: false, - }; -}; - -const colorReplace = (pre, state, text) => { - const re = /(?:\033|\\033)(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g; - let i = 0; - - if (state.carriageReturn) { - if (text !== "\n") { - // don't remove if \r\n - pre.removeChild(pre.lastChild); - } - state.carriageReturn = false; - } - - if (text.includes("\r")) { - state.carriageReturn = true; - } - - const lineSpan = document.createElement("span"); - lineSpan.classList.add("line"); - pre.appendChild(lineSpan); - - const addSpan = (content) => { - if (content === "") - return; - - const span = document.createElement("span"); - if (state.bold) span.classList.add("log-bold"); - if (state.italic) span.classList.add("log-italic"); - if (state.underline) span.classList.add("log-underline"); - if (state.strikethrough) span.classList.add("log-strikethrough"); - if (state.secret) span.classList.add("log-secret"); - if (state.foregroundColor !== null) span.classList.add(`log-fg-${state.foregroundColor}`); - if (state.backgroundColor !== null) span.classList.add(`log-bg-${state.backgroundColor}`); - span.appendChild(document.createTextNode(content)); - lineSpan.appendChild(span); - - if (state.secret) { - const redacted = document.createElement("span"); - redacted.classList.add("log-secret-redacted"); - redacted.appendChild(document.createTextNode("[redacted]")); - lineSpan.appendChild(redacted); - } - }; - - - while (true) { - const match = re.exec(text); - if (match === null) - break; - - const j = match.index; - addSpan(text.substring(i, j)); - i = j + match[0].length; - - if (match[1] === undefined) continue; - - for (const colorCode of match[1].split(";")) { - switch (parseInt(colorCode)) { - case 0: - // reset - state.bold = false; - state.italic = false; - state.underline = false; - state.strikethrough = false; - state.foregroundColor = null; - state.backgroundColor = null; - state.secret = false; - break; - case 1: - state.bold = true; - break; - case 3: - state.italic = true; - break; - case 4: - state.underline = true; - break; - case 5: - state.secret = true; - break; - case 6: - state.secret = false; - break; - case 9: - state.strikethrough = true; - break; - case 22: - state.bold = false; - break; - case 23: - state.italic = false; - break; - case 24: - state.underline = false; - break; - case 29: - state.strikethrough = false; - break; - case 30: - state.foregroundColor = "black"; - break; - case 31: - state.foregroundColor = "red"; - break; - case 32: - state.foregroundColor = "green"; - break; - case 33: - state.foregroundColor = "yellow"; - break; - case 34: - state.foregroundColor = "blue"; - break; - case 35: - state.foregroundColor = "magenta"; - break; - case 36: - state.foregroundColor = "cyan"; - break; - case 37: - state.foregroundColor = "white"; - break; - case 39: - state.foregroundColor = null; - break; - case 41: - state.backgroundColor = "red"; - break; - case 42: - state.backgroundColor = "green"; - break; - case 43: - state.backgroundColor = "yellow"; - break; - case 44: - state.backgroundColor = "blue"; - break; - case 45: - state.backgroundColor = "magenta"; - break; - case 46: - state.backgroundColor = "cyan"; - break; - case 47: - state.backgroundColor = "white"; - break; - case 40: - case 49: - state.backgroundColor = null; - break; - } - } - } - addSpan(text.substring(i)); - if (pre.scrollTop + 56 >= (pre.scrollHeight - pre.offsetHeight)) { - // at bottom - pre.scrollTop = pre.scrollHeight; - } -}; - -/** - * Serial Port Selection - */ - -const portSelect = document.querySelector('.nav-wrapper select'); -let ports = []; - -const fetchSerialPorts = (begin = false) => { - fetch(`./serial-ports`, { credentials: "same-origin" }).then(res => res.json()) - .then(response => { - if (ports.length === response.length) { - let allEqual = true; - for (let i = 0; i < response.length; i++) { - if (ports[i].port !== response[i].port) { - allEqual = false; - break; - } - } - if (allEqual) - return; - } - const hasNewPort = response.length >= ports.length; - - ports = response; - - const inst = M.FormSelect.getInstance(portSelect); - if (inst !== undefined) { - inst.destroy(); - } - - portSelect.innerHTML = ""; - const prevSelected = getUploadPort(); - for (let i = 0; i < response.length; i++) { - const val = response[i]; - if (val.port === prevSelected) { - portSelect.innerHTML += ``; - } else { - portSelect.innerHTML += ``; - } - } - - M.FormSelect.init(portSelect, {}); - if (!begin && hasNewPort) - M.toast({ html: "Discovered new serial port." }); - }); -}; - -const getUploadPort = () => { - const inst = M.FormSelect.getInstance(portSelect); - if (inst === undefined) { - return "OTA"; - } - - inst._setSelectedStates(); - return inst.getSelectedValues()[0]; -}; -setInterval(fetchSerialPorts, 5000); -fetchSerialPorts(true); - -/** - * Log Elements - */ - -// Log Modal Class -class LogModal { - constructor({ - name, - onPrepare = (modalElement, config) => { }, - onProcessExit = (modalElement, code) => { }, - onSocketClose = (modalElement) => { }, - dismissible = true - }) { - this.modalId = `js-${name}-modal`; - this.dataAction = `${name}`; - this.wsUrl = `${wsUrl}${name}`; - this.dismissible = dismissible; - this.activeFilename = null; - - this.modalElement = document.getElementById(this.modalId); - this.nodeFilenameElement = document.querySelector(`#${this.modalId} #js-node-filename`); - this.logElement = document.querySelector(`#${this.modalId} #js-log-area`); - this.onPrepare = onPrepare; - this.onProcessExit = onProcessExit; - this.onSocketClose = onSocketClose; - } - - setup() { - const boundOnPress = this._onPress.bind(this); - document.querySelectorAll(`[data-action="${this.dataAction}"]`).forEach((button) => { - button.addEventListener('click', boundOnPress); - }); - } - - _setupModalInstance() { - this.modalInstance = M.Modal.init(this.modalElement, { - onOpenStart: this._onOpenStart.bind(this), - onCloseStart: this._onCloseStart.bind(this), - dismissible: this.dismissible - }) - } - - _onOpenStart() { - document.addEventListener('keydown', this._boundKeydown); - } - - _onCloseStart() { - document.removeEventListener('keydown', this._boundKeydown); - this.activeSocket.close(); - } - - open(event) { - this._onPress(event); - } - - _onPress(event) { - this.activeFilename = event.target.getAttribute('data-filename'); - - this._setupModalInstance(); - this.nodeFilenameElement.innerHTML = this.activeFilename; - - this.logElement.innerHTML = ""; - const colorLogState = initializeColorState(); - - this.onPrepare(this.modalElement, this.activeFilename); - - let stopped = false; - - this.modalInstance.open(); - - const socket = new WebSocket(this.wsUrl); - this.activeSocket = socket; - socket.addEventListener('message', (event) => { - const data = JSON.parse(event.data); - if (data.event === "line") { - colorReplace(this.logElement, colorLogState, data.data); - } else if (data.event === "exit") { - this.onProcessExit(this.modalElement, data.code); - stopped = true; - } - }); - - socket.addEventListener('open', () => { - const msg = JSON.stringify(this._encodeSpawnMessage(this.activeFilename)); - socket.send(msg); - }); - - socket.addEventListener('close', () => { - if (!stopped) { - this.onSocketClose(this.modalElement); - } - }); - } - - _onKeyDown(event) { - // Close on escape key - if (event.keyCode === 27) { - this.modalInstance.close(); - } - } - - _encodeSpawnMessage(filename) { - return { - type: 'spawn', - configuration: filename, - port: getUploadPort(), - }; - } -} - -// Logs Modal -const logsModal = new LogModal({ - name: "logs", - - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("[data-action='stop-logs']").innerHTML = "Stop"; - }, - - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: "Program exited successfully", - displayLength: 10000 - }); - } else { - M.toast({ - html: `Program failed with code ${code}`, - displayLength: 10000 - }); - } - modalElem.querySelector("data-action='stop-logs'").innerHTML = "Close"; - }, - - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000 - }); - } -}) - -logsModal.setup(); - -// Upload Modal -const uploadModal = new LogModal({ - name: "upload", - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("#js-upload-modal [data-action='download-binary']").classList.add('disabled'); - modalElement.querySelector("#js-upload-modal [data-action='upload']").setAttribute('data-filename', activeFilename); - modalElement.querySelector("#js-upload-modal [data-action='upload']").classList.add('disabled'); - modalElement.querySelector("#js-upload-modal [data-action='edit']").setAttribute('data-filename', activeFilename); - modalElement.querySelector("#js-upload-modal [data-action='stop-logs']").innerHTML = "Stop"; - }, - - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: "Program exited successfully", - displayLength: 10000 - }); - - modalElement.querySelector("#js-upload-modal [data-action='download-binary']").classList.remove('disabled'); - } else { - M.toast({ - html: `Program failed with code ${code}`, - displayLength: 10000 - }); - - modalElement.querySelector("#js-upload-modal [data-action='upload']").classList.add('disabled'); - modalElement.querySelector("#js-upload-modal [data-action='upload']").classList.remove('disabled'); - } - modalElement.querySelector("#js-upload-modal [data-action='stop-logs']").innerHTML = "Close"; - }, - - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000 - }); - }, - - dismissible: false -}) - -uploadModal.setup(); - -const downloadAfterUploadButton = document.querySelector("#js-upload-modal [data-action='download-binary']"); -downloadAfterUploadButton.addEventListener('click', () => { - const link = document.createElement("a"); - link.download = name; - link.href = `./download.bin?configuration=${encodeURIComponent(uploadModal.activeFilename)}`; - document.body.appendChild(link); - link.click(); - link.remove(); -}); - -// Validate Modal -const validateModal = new LogModal({ - name: 'validate', - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("#js-validate-modal [data-action='stop-logs']").innerHTML = "Stop"; - modalElement.querySelector("#js-validate-modal [data-action='edit']").setAttribute('data-filename', activeFilename); - modalElement.querySelector("#js-validate-modal [data-action='upload']").setAttribute('data-filename', activeFilename); - modalElement.querySelector("#js-validate-modal [data-action='upload']").classList.add('disabled'); - }, - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: `${validateModal.activeFilename} is valid 👍`, - displayLength: 10000, - }); - modalElement.querySelector("#js-validate-modal [data-action='upload']").classList.remove('disabled'); - } else { - M.toast({ - html: `${validateModal.activeFilename} is invalid 😕`, - displayLength: 10000, - }); - } - modalElement.querySelector("#js-validate-modal [data-action='stop-logs']").innerHTML = "Close"; - }, - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000, - }); - }, -}); - -validateModal.setup(); - -// Compile Modal -const compileModal = new LogModal({ - name: 'compile', - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("#js-compile-modal [data-action='stop-logs']").innerHTML = "Stop"; - modalElement.querySelector("#js-compile-modal [data-action='download-binary']").classList.add('disabled'); - }, - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: "Program exited successfully", - displayLength: 10000, - }); - modalElement.querySelector("#js-compile-modal [data-action='download-binary']").classList.remove('disabled'); - } else { - M.toast({ - html: `Program failed with code ${data.code}`, - displayLength: 10000, - }); - } - modalElement.querySelector("#js-compile-modal [data-action='stop-logs']").innerHTML = "Close"; - }, - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000, - }); - }, - dismissible: false, -}); - -compileModal.setup(); - -const downloadAfterCompileButton = document.querySelector("#js-compile-modal [data-action='download-binary']"); -downloadAfterCompileButton.addEventListener('click', () => { - const link = document.createElement("a"); - link.download = name; - link.href = `./download.bin?configuration=${encodeURIComponent(compileModal.activeFilename)}`; - document.body.appendChild(link); - link.click(); - link.remove(); -}); - -// Clean MQTT Modal -const cleanMqttModal = new LogModal({ - name: 'clean-mqtt', - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("#js-clean-mqtt-modal [data-action='stop-logs']").innerHTML = "Stop"; - }, - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: "Program exited successfully", - displayLength: 10000, - }); - } else { - M.toast({ - html: `Program failed with code ${code}`, - displayLength: 10000, - }); - } - modalElement.querySelector("#js-clean-mqtt-modal [data-action='stop-logs']").innerHTML = "Close"; - }, - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000, - }); - }, -}); - -cleanMqttModal.setup(); - -// Clean Build Files Modal -const cleanModal = new LogModal({ - name: 'clean', - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("#js-clean-modal [data-action='stop-logs']").innerHTML = "Stop"; - }, - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: "Program exited successfully", - displayLength: 10000, - }); - } else { - M.toast({ - html: `Program failed with code ${code}`, - displayLength: 10000, - }); - } - modalElement.querySelector("#js-clean-modal [data-action='stop-logs']").innerHTML = "Close"; - }, - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000, - }); - }, -}); - -cleanModal.setup(); - -// Update All Modal -const updateAllModal = new LogModal({ - name: 'update-all', - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("#js-update-all-modal [data-action='stop-logs']").innerHTML = "Stop"; - modalElement.querySelector("#js-update-all-modal #js-node-filename").style.visibility = "hidden"; - }, - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: "Program exited successfully", - displayLength: 10000, - }); - downloadButton.classList.remove('disabled'); - } else { - M.toast({ - html: `Program failed with code ${data.code}`, - displayLength: 10000, - }); - } - modalElement.querySelector("#js-update-all-modal [data-action='stop-logs']").innerHTML = "Close"; - }, - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000, - }); - }, - dismissible: false, -}); - -updateAllModal.setup(); - -/** - * Node Editing - */ - -let editorActiveFilename = null; -let editorActiveSecrets = false; -let editorActiveWebSocket = null; -let editorValidationScheduled = false; -let editorValidationRunning = false; - -// Setup Editor -const editorElement = document.querySelector("#js-editor-modal #js-editor-area"); -const editor = ace.edit(editorElement); - -editor.setOptions({ - highlightActiveLine: true, - showPrintMargin: true, - useSoftTabs: true, - tabSize: 2, - useWorker: false, - theme: 'ace/theme/dreamweaver', - mode: 'ace/mode/yaml' -}); - -editor.commands.addCommand({ - name: 'saveCommand', - bindKey: { win: 'Ctrl-S', mac: 'Command-S' }, - exec: function () { - saveFile(editorActiveFilename); - }, - readOnly: false -}); - -// Edit Button Listener -document.querySelectorAll("[data-action='edit']").forEach((button) => { - button.addEventListener('click', (event) => { - - editorActiveFilename = event.target.getAttribute("data-filename"); - const filenameField = document.querySelector("#js-editor-modal #js-node-filename"); - filenameField.innerHTML = editorActiveFilename; - - const saveButton = document.querySelector("#js-editor-modal [data-action='save']"); - const uploadButton = document.querySelector("#js-editor-modal [data-action='upload']"); - const closeButton = document.querySelector("#js-editor-modal [data-action='close']"); - saveButton.setAttribute('data-filename', editorActiveFilename); - uploadButton.setAttribute('data-filename', editorActiveFilename); - uploadButton.setAttribute('onClick', `saveFile("${editorActiveFilename}")`); - if (editorActiveFilename === "secrets.yaml") { - uploadButton.classList.add("disabled"); - editorActiveSecrets = true; - } else { - uploadButton.classList.remove("disabled"); - editorActiveSecrets = false; - } - closeButton.setAttribute('data-filename', editorActiveFilename); - - const loadingIndicator = document.querySelector("#js-editor-modal #js-loading-indicator"); - const editorArea = document.querySelector("#js-editor-modal #js-editor-area"); - - loadingIndicator.style.display = "block"; - editorArea.style.display = "none"; - - editor.setOption('readOnly', true); - fetch(`./edit?configuration=${editorActiveFilename}`, { credentials: "same-origin" }) - .then(res => res.text()).then(response => { - editor.setValue(response, -1); - editor.setOption('readOnly', false); - loadingIndicator.style.display = "none"; - editorArea.style.display = "block"; - }); - editor.focus(); - - const editModalElement = document.getElementById("js-editor-modal"); - const editorModal = M.Modal.init(editModalElement, { - onOpenStart: function () { - editorModalOnOpen() - }, - onCloseStart: function () { - editorModalOnClose() - }, - dismissible: false - }) - - editorModal.open(); - - }); -}); - -// Editor On Open -const editorModalOnOpen = () => { - return -} - -// Editor On Close -const editorModalOnClose = () => { - editorActiveFilename = null; -} - -// Editor WebSocket Validation -const startAceWebsocket = () => { - editorActiveWebSocket = new WebSocket(`${wsUrl}ace`); - - editorActiveWebSocket.addEventListener('message', (event) => { - const raw = JSON.parse(event.data); - if (raw.event === "line") { - const msg = JSON.parse(raw.data); - if (msg.type === "result") { - const arr = []; - - for (const v of msg.validation_errors) { - let o = { - text: v.message, - type: 'error', - row: 0, - column: 0 - }; - if (v.range != null) { - o.row = v.range.start_line; - o.column = v.range.start_col; - } - arr.push(o); - } - for (const v of msg.yaml_errors) { - arr.push({ - text: v.message, - type: 'error', - row: 0, - column: 0 - }); - } - - editor.session.setAnnotations(arr); - - if (arr.length) { - document.querySelector("#js-editor-modal [data-action='upload']").classList.add('disabled'); - } else { - document.querySelector("#js-editor-modal [data-action='upload']").classList.remove('disabled'); - } - - editorValidationRunning = false; - } else if (msg.type === "read_file") { - sendAceStdin({ - type: 'file_response', - content: editor.getValue() - }); - } - } - }) - - editorActiveWebSocket.addEventListener('open', () => { - const msg = JSON.stringify({ type: 'spawn' }); - editorActiveWebSocket.send(msg); - }); - - editorActiveWebSocket.addEventListener('close', () => { - editorActiveWebSocket = null; - setTimeout(startAceWebsocket, 5000); - }); -}; - -const sendAceStdin = (data) => { - let send = JSON.stringify({ - type: 'stdin', - data: JSON.stringify(data) + '\n', - }); - editorActiveWebSocket.send(send); -}; - -const debounce = (func, wait) => { - let timeout; - return function () { - let context = this, args = arguments; - let later = function () { - timeout = null; - func.apply(context, args); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; -}; - -editor.session.on('change', debounce(() => { - editorValidationScheduled = !editorActiveSecrets; -}, 250)); - -setInterval(() => { - if (!editorValidationScheduled || editorValidationRunning) - return; - if (editorActiveWebSocket == null) - return; - - sendAceStdin({ - type: 'validate', - file: editorActiveFilename - }); - editorValidationRunning = true; - editorValidationScheduled = false; -}, 100); - -// Save File -const saveFile = (filename) => { - const extensionRegex = new RegExp("(?:\.([^.]+))?$"); - - if (filename.match(extensionRegex)[0] !== ".yaml") { - M.toast({ - html: `❌ File ${filename} cannot be saved as it is not a YAML file!`, - displayLength: 10000 - }); - return; - } - - fetch(`./edit?configuration=${filename}`, { - credentials: "same-origin", - method: "POST", - body: editor.getValue() - }) - .then((response) => { - response.text(); - }) - .then(() => { - M.toast({ - html: `✅ Saved ${filename}`, - displayLength: 10000 - }); - }) - .catch((error) => { - M.toast({ - html: `❌ An error occured saving ${filename}`, - displayLength: 10000 - }); - }) -} - -document.querySelectorAll("[data-action='save']").forEach((btn) => { - btn.addEventListener("click", (e) => { - saveFile(editorActiveFilename); - }); -}); - -// Delete Node -document.querySelectorAll("[data-action='delete']").forEach((btn) => { - btn.addEventListener("click", (e) => { - const filename = e.target.getAttribute("data-filename"); - - fetch(`./delete?configuration=${filename}`, { - credentials: "same-origin", - method: "POST", - }) - .then((res) => { - res.text() - }) - .then(() => { - const toastHtml = `🗑️ Deleted ${filename} - `; - const toast = M.toast({ - html: toastHtml, - displayLength: 10000 - }); - const undoButton = toast.el.querySelector('.toast-action'); - - document.querySelector(`.card[data-filename="${filename}"]`).remove(); - - undoButton.addEventListener('click', () => { - fetch(`./undo-delete?configuration=${filename}`, { - credentials: "same-origin", - method: "POST", - }) - .then((res) => { - res.text() - }) - .then(() => { - window.location.reload(false); - }); - }); - }); - - }); -}); - -/** - * Wizard - */ - -const wizardTriggerElement = document.querySelector("[data-action='wizard']"); -const wizardModal = document.getElementById("js-wizard-modal"); -const wizardCloseButton = document.querySelector("[data-action='wizard-close']"); - -const wizardStepper = document.querySelector('#js-wizard-modal .stepper'); -const wizardStepperInstace = new MStepper(wizardStepper, { - firstActive: 0, - stepTitleNavigation: false, - autoFocusInput: true, - showFeedbackLoader: true, -}) - -const startWizard = () => { - M.Modal.init(wizardModal, { - dismissible: false - }).open(); -} - -document.querySelectorAll("[data-action='wizard']").forEach((btn) => { - btn.addEventListener("click", (event) => { - startWizard(); - }) -}); - -jQuery.validator.addMethod("nospaces", (value, element) => { - return value.indexOf(' ') < 0; -}, "Name cannot contain any spaces!"); - -jQuery.validator.addMethod("nounderscores", (value, element) => { - return value.indexOf('_') < 0; -}, "Name cannot contain underscores!"); - -jQuery.validator.addMethod("lowercase", (value, element) => { - return value === value.toLowerCase(); -}, "Name must be all lower case!"); diff --git a/esphome/dashboard/static/js/vendor/ace/ace.js b/esphome/dashboard/static/js/vendor/ace/ace.js deleted file mode 100644 index 0a66043d67..0000000000 --- a/esphome/dashboard/static/js/vendor/ace/ace.js +++ /dev/null @@ -1,16 +0,0 @@ -(function () { function o(n) { var i = e; n && (e[n] || (e[n] = {}), i = e[n]); if (!i.define || !i.define.packaged) t.original = i.define, i.define = t, i.define.packaged = !0; if (!i.require || !i.require.packaged) r.original = i.require, i.require = r, i.require.packaged = !0 } var ACE_NAMESPACE = "", e = function () { return this }(); !e && typeof window != "undefined" && (e = window); if (!ACE_NAMESPACE && typeof requirejs != "undefined") return; var t = function (e, n, r) { if (typeof e != "string") { t.original ? t.original.apply(this, arguments) : (console.error("dropping module because define wasn't a string."), console.trace()); return } arguments.length == 2 && (r = n), t.modules[e] || (t.payloads[e] = r, t.modules[e] = null) }; t.modules = {}, t.payloads = {}; var n = function (e, t, n) { if (typeof t == "string") { var i = s(e, t); if (i != undefined) return n && n(), i } else if (Object.prototype.toString.call(t) === "[object Array]") { var o = []; for (var u = 0, a = t.length; u < a; ++u) { var f = s(e, t[u]); if (f == undefined && r.original) return; o.push(f) } return n && n.apply(null, o) || !0 } }, r = function (e, t) { var i = n("", e, t); return i == undefined && r.original ? r.original.apply(this, arguments) : i }, i = function (e, t) { if (t.indexOf("!") !== -1) { var n = t.split("!"); return i(e, n[0]) + "!" + i(e, n[1]) } if (t.charAt(0) == ".") { var r = e.split("/").slice(0, -1).join("/"); t = r + "/" + t; while (t.indexOf(".") !== -1 && s != t) { var s = t; t = t.replace(/\/\.\//, "/").replace(/[^\/]+\/\.\.\//, "") } } return t }, s = function (e, r) { r = i(e, r); var s = t.modules[r]; if (!s) { s = t.payloads[r]; if (typeof s == "function") { var o = {}, u = { id: r, uri: "", exports: o, packaged: !0 }, a = function (e, t) { return n(r, e, t) }, f = s(a, o, u); o = f || u.exports, t.modules[r] = o, delete t.payloads[r] } s = t.modules[r] = o || s } return s }; o(ACE_NAMESPACE) })(), define("ace/lib/regexp", ["require", "exports", "module"], function (e, t, n) { "use strict"; function o(e) { return (e.global ? "g" : "") + (e.ignoreCase ? "i" : "") + (e.multiline ? "m" : "") + (e.extended ? "x" : "") + (e.sticky ? "y" : "") } function u(e, t, n) { if (Array.prototype.indexOf) return e.indexOf(t, n); for (var r = n || 0; r < e.length; r++)if (e[r] === t) return r; return -1 } var r = { exec: RegExp.prototype.exec, test: RegExp.prototype.test, match: String.prototype.match, replace: String.prototype.replace, split: String.prototype.split }, i = r.exec.call(/()??/, "")[1] === undefined, s = function () { var e = /^/g; return r.test.call(e, ""), !e.lastIndex }(); if (s && i) return; RegExp.prototype.exec = function (e) { var t = r.exec.apply(this, arguments), n, a; if (typeof e == "string" && t) { !i && t.length > 1 && u(t, "") > -1 && (a = RegExp(this.source, r.replace.call(o(this), "g", "")), r.replace.call(e.slice(t.index), a, function () { for (var e = 1; e < arguments.length - 2; e++)arguments[e] === undefined && (t[e] = undefined) })); if (this._xregexp && this._xregexp.captureNames) for (var f = 1; f < t.length; f++)n = this._xregexp.captureNames[f - 1], n && (t[n] = t[f]); !s && this.global && !t[0].length && this.lastIndex > t.index && this.lastIndex-- } return t }, s || (RegExp.prototype.test = function (e) { var t = r.exec.call(this, e); return t && this.global && !t[0].length && this.lastIndex > t.index && this.lastIndex--, !!t }) }), define("ace/lib/es5-shim", ["require", "exports", "module"], function (e, t, n) { function r() { } function w(e) { try { return Object.defineProperty(e, "sentinel", {}), "sentinel" in e } catch (t) { } } function H(e) { return e = +e, e !== e ? e = 0 : e !== 0 && e !== 1 / 0 && e !== -1 / 0 && (e = (e > 0 || -1) * Math.floor(Math.abs(e))), e } function B(e) { var t = typeof e; return e === null || t === "undefined" || t === "boolean" || t === "number" || t === "string" } function j(e) { var t, n, r; if (B(e)) return e; n = e.valueOf; if (typeof n == "function") { t = n.call(e); if (B(t)) return t } r = e.toString; if (typeof r == "function") { t = r.call(e); if (B(t)) return t } throw new TypeError } Function.prototype.bind || (Function.prototype.bind = function (t) { var n = this; if (typeof n != "function") throw new TypeError("Function.prototype.bind called on incompatible " + n); var i = u.call(arguments, 1), s = function () { if (this instanceof s) { var e = n.apply(this, i.concat(u.call(arguments))); return Object(e) === e ? e : this } return n.apply(t, i.concat(u.call(arguments))) }; return n.prototype && (r.prototype = n.prototype, s.prototype = new r, r.prototype = null), s }); var i = Function.prototype.call, s = Array.prototype, o = Object.prototype, u = s.slice, a = i.bind(o.toString), f = i.bind(o.hasOwnProperty), l, c, h, p, d; if (d = f(o, "__defineGetter__")) l = i.bind(o.__defineGetter__), c = i.bind(o.__defineSetter__), h = i.bind(o.__lookupGetter__), p = i.bind(o.__lookupSetter__); if ([1, 2].splice(0).length != 2) if (!function () { function e(e) { var t = new Array(e + 2); return t[0] = t[1] = 0, t } var t = [], n; t.splice.apply(t, e(20)), t.splice.apply(t, e(26)), n = t.length, t.splice(5, 0, "XXX"), n + 1 == t.length; if (n + 1 == t.length) return !0 }()) Array.prototype.splice = function (e, t) { var n = this.length; e > 0 ? e > n && (e = n) : e == void 0 ? e = 0 : e < 0 && (e = Math.max(n + e, 0)), e + t < n || (t = n - e); var r = this.slice(e, e + t), i = u.call(arguments, 2), s = i.length; if (e === n) s && this.push.apply(this, i); else { var o = Math.min(t, n - e), a = e + o, f = a + s - o, l = n - a, c = n - o; if (f < a) for (var h = 0; h < l; ++h)this[f + h] = this[a + h]; else if (f > a) for (h = l; h--;)this[f + h] = this[a + h]; if (s && e === c) this.length = c, this.push.apply(this, i); else { this.length = c + s; for (h = 0; h < s; ++h)this[e + h] = i[h] } } return r }; else { var v = Array.prototype.splice; Array.prototype.splice = function (e, t) { return arguments.length ? v.apply(this, [e === void 0 ? 0 : e, t === void 0 ? this.length - e : t].concat(u.call(arguments, 2))) : [] } } Array.isArray || (Array.isArray = function (t) { return a(t) == "[object Array]" }); var m = Object("a"), g = m[0] != "a" || !(0 in m); Array.prototype.forEach || (Array.prototype.forEach = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = arguments[1], s = -1, o = r.length >>> 0; if (a(t) != "[object Function]") throw new TypeError; while (++s < o) s in r && t.call(i, r[s], s, n) }), Array.prototype.map || (Array.prototype.map = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = r.length >>> 0, s = Array(i), o = arguments[1]; if (a(t) != "[object Function]") throw new TypeError(t + " is not a function"); for (var u = 0; u < i; u++)u in r && (s[u] = t.call(o, r[u], u, n)); return s }), Array.prototype.filter || (Array.prototype.filter = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = r.length >>> 0, s = [], o, u = arguments[1]; if (a(t) != "[object Function]") throw new TypeError(t + " is not a function"); for (var f = 0; f < i; f++)f in r && (o = r[f], t.call(u, o, f, n) && s.push(o)); return s }), Array.prototype.every || (Array.prototype.every = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = r.length >>> 0, s = arguments[1]; if (a(t) != "[object Function]") throw new TypeError(t + " is not a function"); for (var o = 0; o < i; o++)if (o in r && !t.call(s, r[o], o, n)) return !1; return !0 }), Array.prototype.some || (Array.prototype.some = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = r.length >>> 0, s = arguments[1]; if (a(t) != "[object Function]") throw new TypeError(t + " is not a function"); for (var o = 0; o < i; o++)if (o in r && t.call(s, r[o], o, n)) return !0; return !1 }), Array.prototype.reduce || (Array.prototype.reduce = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = r.length >>> 0; if (a(t) != "[object Function]") throw new TypeError(t + " is not a function"); if (!i && arguments.length == 1) throw new TypeError("reduce of empty array with no initial value"); var s = 0, o; if (arguments.length >= 2) o = arguments[1]; else do { if (s in r) { o = r[s++]; break } if (++s >= i) throw new TypeError("reduce of empty array with no initial value") } while (!0); for (; s < i; s++)s in r && (o = t.call(void 0, o, r[s], s, n)); return o }), Array.prototype.reduceRight || (Array.prototype.reduceRight = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = r.length >>> 0; if (a(t) != "[object Function]") throw new TypeError(t + " is not a function"); if (!i && arguments.length == 1) throw new TypeError("reduceRight of empty array with no initial value"); var s, o = i - 1; if (arguments.length >= 2) s = arguments[1]; else do { if (o in r) { s = r[o--]; break } if (--o < 0) throw new TypeError("reduceRight of empty array with no initial value") } while (!0); do o in this && (s = t.call(void 0, s, r[o], o, n)); while (o--); return s }); if (!Array.prototype.indexOf || [0, 1].indexOf(1, 2) != -1) Array.prototype.indexOf = function (t) { var n = g && a(this) == "[object String]" ? this.split("") : F(this), r = n.length >>> 0; if (!r) return -1; var i = 0; arguments.length > 1 && (i = H(arguments[1])), i = i >= 0 ? i : Math.max(0, r + i); for (; i < r; i++)if (i in n && n[i] === t) return i; return -1 }; if (!Array.prototype.lastIndexOf || [0, 1].lastIndexOf(0, -3) != -1) Array.prototype.lastIndexOf = function (t) { var n = g && a(this) == "[object String]" ? this.split("") : F(this), r = n.length >>> 0; if (!r) return -1; var i = r - 1; arguments.length > 1 && (i = Math.min(i, H(arguments[1]))), i = i >= 0 ? i : r - Math.abs(i); for (; i >= 0; i--)if (i in n && t === n[i]) return i; return -1 }; Object.getPrototypeOf || (Object.getPrototypeOf = function (t) { return t.__proto__ || (t.constructor ? t.constructor.prototype : o) }); if (!Object.getOwnPropertyDescriptor) { var y = "Object.getOwnPropertyDescriptor called on a non-object: "; Object.getOwnPropertyDescriptor = function (t, n) { if (typeof t != "object" && typeof t != "function" || t === null) throw new TypeError(y + t); if (!f(t, n)) return; var r, i, s; r = { enumerable: !0, configurable: !0 }; if (d) { var u = t.__proto__; t.__proto__ = o; var i = h(t, n), s = p(t, n); t.__proto__ = u; if (i || s) return i && (r.get = i), s && (r.set = s), r } return r.value = t[n], r } } Object.getOwnPropertyNames || (Object.getOwnPropertyNames = function (t) { return Object.keys(t) }); if (!Object.create) { var b; Object.prototype.__proto__ === null ? b = function () { return { __proto__: null } } : b = function () { var e = {}; for (var t in e) e[t] = null; return e.constructor = e.hasOwnProperty = e.propertyIsEnumerable = e.isPrototypeOf = e.toLocaleString = e.toString = e.valueOf = e.__proto__ = null, e }, Object.create = function (t, n) { var r; if (t === null) r = b(); else { if (typeof t != "object") throw new TypeError("typeof prototype[" + typeof t + "] != 'object'"); var i = function () { }; i.prototype = t, r = new i, r.__proto__ = t } return n !== void 0 && Object.defineProperties(r, n), r } } if (Object.defineProperty) { var E = w({}), S = typeof document == "undefined" || w(document.createElement("div")); if (!E || !S) var x = Object.defineProperty } if (!Object.defineProperty || x) { var T = "Property description must be an object: ", N = "Object.defineProperty called on non-object: ", C = "getters & setters can not be defined on this javascript engine"; Object.defineProperty = function (t, n, r) { if (typeof t != "object" && typeof t != "function" || t === null) throw new TypeError(N + t); if (typeof r != "object" && typeof r != "function" || r === null) throw new TypeError(T + r); if (x) try { return x.call(Object, t, n, r) } catch (i) { } if (f(r, "value")) if (d && (h(t, n) || p(t, n))) { var s = t.__proto__; t.__proto__ = o, delete t[n], t[n] = r.value, t.__proto__ = s } else t[n] = r.value; else { if (!d) throw new TypeError(C); f(r, "get") && l(t, n, r.get), f(r, "set") && c(t, n, r.set) } return t } } Object.defineProperties || (Object.defineProperties = function (t, n) { for (var r in n) f(n, r) && Object.defineProperty(t, r, n[r]); return t }), Object.seal || (Object.seal = function (t) { return t }), Object.freeze || (Object.freeze = function (t) { return t }); try { Object.freeze(function () { }) } catch (k) { Object.freeze = function (t) { return function (n) { return typeof n == "function" ? n : t(n) } }(Object.freeze) } Object.preventExtensions || (Object.preventExtensions = function (t) { return t }), Object.isSealed || (Object.isSealed = function (t) { return !1 }), Object.isFrozen || (Object.isFrozen = function (t) { return !1 }), Object.isExtensible || (Object.isExtensible = function (t) { if (Object(t) === t) throw new TypeError; var n = ""; while (f(t, n)) n += "?"; t[n] = !0; var r = f(t, n); return delete t[n], r }); if (!Object.keys) { var L = !0, A = ["toString", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "constructor"], O = A.length; for (var M in { toString: null }) L = !1; Object.keys = function I(e) { if (typeof e != "object" && typeof e != "function" || e === null) throw new TypeError("Object.keys called on a non-object"); var I = []; for (var t in e) f(e, t) && I.push(t); if (L) for (var n = 0, r = O; n < r; n++) { var i = A[n]; f(e, i) && I.push(i) } return I } } Date.now || (Date.now = function () { return (new Date).getTime() }); var _ = " \n\x0b\f\r \u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\ufeff"; if (!String.prototype.trim) { _ = "[" + _ + "]"; var D = new RegExp("^" + _ + _ + "*"), P = new RegExp(_ + _ + "*$"); String.prototype.trim = function () { return String(this).replace(D, "").replace(P, "") } } var F = function (e) { if (e == null) throw new TypeError("can't convert " + e + " to object"); return Object(e) } }), define("ace/lib/fixoldbrowsers", ["require", "exports", "module", "ace/lib/regexp", "ace/lib/es5-shim"], function (e, t, n) { "use strict"; e("./regexp"), e("./es5-shim"), typeof Element != "undefined" && !Element.prototype.remove && Object.defineProperty(Element.prototype, "remove", { enumerable: !1, writable: !0, configurable: !0, value: function () { this.parentNode && this.parentNode.removeChild(this) } }) }), define("ace/lib/useragent", ["require", "exports", "module"], function (e, t, n) { "use strict"; t.OS = { LINUX: "LINUX", MAC: "MAC", WINDOWS: "WINDOWS" }, t.getOS = function () { return t.isMac ? t.OS.MAC : t.isLinux ? t.OS.LINUX : t.OS.WINDOWS }; var r = typeof navigator == "object" ? navigator : {}, i = (/mac|win|linux/i.exec(r.platform) || ["other"])[0].toLowerCase(), s = r.userAgent || "", o = r.appName || ""; t.isWin = i == "win", t.isMac = i == "mac", t.isLinux = i == "linux", t.isIE = o == "Microsoft Internet Explorer" || o.indexOf("MSAppHost") >= 0 ? parseFloat((s.match(/(?:MSIE |Trident\/[0-9]+[\.0-9]+;.*rv:)([0-9]+[\.0-9]+)/) || [])[1]) : parseFloat((s.match(/(?:Trident\/[0-9]+[\.0-9]+;.*rv:)([0-9]+[\.0-9]+)/) || [])[1]), t.isOldIE = t.isIE && t.isIE < 9, t.isGecko = t.isMozilla = s.match(/ Gecko\/\d+/), t.isOpera = typeof opera == "object" && Object.prototype.toString.call(window.opera) == "[object Opera]", t.isWebKit = parseFloat(s.split("WebKit/")[1]) || undefined, t.isChrome = parseFloat(s.split(" Chrome/")[1]) || undefined, t.isEdge = parseFloat(s.split(" Edge/")[1]) || undefined, t.isAIR = s.indexOf("AdobeAIR") >= 0, t.isAndroid = s.indexOf("Android") >= 0, t.isChromeOS = s.indexOf(" CrOS ") >= 0, t.isIOS = /iPad|iPhone|iPod/.test(s) && !window.MSStream, t.isIOS && (t.isMac = !0), t.isMobile = t.isIOS || t.isAndroid }), define("ace/lib/dom", ["require", "exports", "module", "ace/lib/useragent"], function (e, t, n) { "use strict"; var r = e("./useragent"), i = "http://www.w3.org/1999/xhtml"; t.buildDom = function o(e, t, n) { if (typeof e == "string" && e) { var r = document.createTextNode(e); return t && t.appendChild(r), r } if (!Array.isArray(e)) return e && e.appendChild && t && t.appendChild(e), e; if (typeof e[0] != "string" || !e[0]) { var i = []; for (var s = 0; s < e.length; s++) { var u = o(e[s], t, n); u && i.push(u) } return i } var a = document.createElement(e[0]), f = e[1], l = 1; f && typeof f == "object" && !Array.isArray(f) && (l = 2); for (var s = l; s < e.length; s++)o(e[s], a, n); return l == 2 && Object.keys(f).forEach(function (e) { var t = f[e]; e === "class" ? a.className = Array.isArray(t) ? t.join(" ") : t : typeof t == "function" || e == "value" || e[0] == "$" ? a[e] = t : e === "ref" ? n && (n[t] = a) : t != null && a.setAttribute(e, t) }), t && t.appendChild(a), a }, t.getDocumentHead = function (e) { return e || (e = document), e.head || e.getElementsByTagName("head")[0] || e.documentElement }, t.createElement = function (e, t) { return document.createElementNS ? document.createElementNS(t || i, e) : document.createElement(e) }, t.removeChildren = function (e) { e.innerHTML = "" }, t.createTextNode = function (e, t) { var n = t ? t.ownerDocument : document; return n.createTextNode(e) }, t.createFragment = function (e) { var t = e ? e.ownerDocument : document; return t.createDocumentFragment() }, t.hasCssClass = function (e, t) { var n = (e.className + "").split(/\s+/g); return n.indexOf(t) !== -1 }, t.addCssClass = function (e, n) { t.hasCssClass(e, n) || (e.className += " " + n) }, t.removeCssClass = function (e, t) { var n = e.className.split(/\s+/g); for (; ;) { var r = n.indexOf(t); if (r == -1) break; n.splice(r, 1) } e.className = n.join(" ") }, t.toggleCssClass = function (e, t) { var n = e.className.split(/\s+/g), r = !0; for (; ;) { var i = n.indexOf(t); if (i == -1) break; r = !1, n.splice(i, 1) } return r && n.push(t), e.className = n.join(" "), r }, t.setCssClass = function (e, n, r) { r ? t.addCssClass(e, n) : t.removeCssClass(e, n) }, t.hasCssString = function (e, t) { var n = 0, r; t = t || document; if (r = t.querySelectorAll("style")) while (n < r.length) if (r[n++].id === e) return !0 }, t.importCssString = function (n, r, i) { var s = i; if (!i || !i.getRootNode) s = document; else { s = i.getRootNode(); if (!s || s == i) s = document } var o = s.ownerDocument || s; if (r && t.hasCssString(r, s)) return null; r && (n += "\n/*# sourceURL=ace/css/" + r + " */"); var u = t.createElement("style"); u.appendChild(o.createTextNode(n)), r && (u.id = r), s == o && (s = t.getDocumentHead(o)), s.insertBefore(u, s.firstChild) }, t.importCssStylsheet = function (e, n) { t.buildDom(["link", { rel: "stylesheet", href: e }], t.getDocumentHead(n)) }, t.scrollbarWidth = function (e) { var n = t.createElement("ace_inner"); n.style.width = "100%", n.style.minWidth = "0px", n.style.height = "200px", n.style.display = "block"; var r = t.createElement("ace_outer"), i = r.style; i.position = "absolute", i.left = "-10000px", i.overflow = "hidden", i.width = "200px", i.minWidth = "0px", i.height = "150px", i.display = "block", r.appendChild(n); var s = e.documentElement; s.appendChild(r); var o = n.offsetWidth; i.overflow = "scroll"; var u = n.offsetWidth; return o == u && (u = r.clientWidth), s.removeChild(r), o - u }, typeof document == "undefined" && (t.importCssString = function () { }), t.computedStyle = function (e, t) { return window.getComputedStyle(e, "") || {} }, t.setStyle = function (e, t, n) { e[t] !== n && (e[t] = n) }, t.HAS_CSS_ANIMATION = !1, t.HAS_CSS_TRANSFORMS = !1, t.HI_DPI = r.isWin ? typeof window != "undefined" && window.devicePixelRatio >= 1.5 : !0; if (typeof document != "undefined") { var s = document.createElement("div"); t.HI_DPI && s.style.transform !== undefined && (t.HAS_CSS_TRANSFORMS = !0), !r.isEdge && typeof s.style.animationName != "undefined" && (t.HAS_CSS_ANIMATION = !0), s = null } t.HAS_CSS_TRANSFORMS ? t.translate = function (e, t, n) { e.style.transform = "translate(" + Math.round(t) + "px, " + Math.round(n) + "px)" } : t.translate = function (e, t, n) { e.style.top = Math.round(n) + "px", e.style.left = Math.round(t) + "px" } }), define("ace/lib/oop", ["require", "exports", "module"], function (e, t, n) { "use strict"; t.inherits = function (e, t) { e.super_ = t, e.prototype = Object.create(t.prototype, { constructor: { value: e, enumerable: !1, writable: !0, configurable: !0 } }) }, t.mixin = function (e, t) { for (var n in t) e[n] = t[n]; return e }, t.implement = function (e, n) { t.mixin(e, n) } }), define("ace/lib/keys", ["require", "exports", "module", "ace/lib/oop"], function (e, t, n) { "use strict"; var r = e("./oop"), i = function () { var e = { MODIFIER_KEYS: { 16: "Shift", 17: "Ctrl", 18: "Alt", 224: "Meta", 91: "MetaLeft", 92: "MetaRight", 93: "ContextMenu" }, KEY_MODS: { ctrl: 1, alt: 2, option: 2, shift: 4, "super": 8, meta: 8, command: 8, cmd: 8, control: 1 }, FUNCTION_KEYS: { 8: "Backspace", 9: "Tab", 13: "Return", 19: "Pause", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "Print", 45: "Insert", 46: "Delete", 96: "Numpad0", 97: "Numpad1", 98: "Numpad2", 99: "Numpad3", 100: "Numpad4", 101: "Numpad5", 102: "Numpad6", 103: "Numpad7", 104: "Numpad8", 105: "Numpad9", "-13": "NumpadEnter", 112: "F1", 113: "F2", 114: "F3", 115: "F4", 116: "F5", 117: "F6", 118: "F7", 119: "F8", 120: "F9", 121: "F10", 122: "F11", 123: "F12", 144: "Numlock", 145: "Scrolllock" }, PRINTABLE_KEYS: { 32: " ", 48: "0", 49: "1", 50: "2", 51: "3", 52: "4", 53: "5", 54: "6", 55: "7", 56: "8", 57: "9", 59: ";", 61: "=", 65: "a", 66: "b", 67: "c", 68: "d", 69: "e", 70: "f", 71: "g", 72: "h", 73: "i", 74: "j", 75: "k", 76: "l", 77: "m", 78: "n", 79: "o", 80: "p", 81: "q", 82: "r", 83: "s", 84: "t", 85: "u", 86: "v", 87: "w", 88: "x", 89: "y", 90: "z", 107: "+", 109: "-", 110: ".", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", 111: "/", 106: "*" } }, t, n; for (n in e.FUNCTION_KEYS) t = e.FUNCTION_KEYS[n].toLowerCase(), e[t] = parseInt(n, 10); for (n in e.PRINTABLE_KEYS) t = e.PRINTABLE_KEYS[n].toLowerCase(), e[t] = parseInt(n, 10); return r.mixin(e, e.MODIFIER_KEYS), r.mixin(e, e.PRINTABLE_KEYS), r.mixin(e, e.FUNCTION_KEYS), e.enter = e["return"], e.escape = e.esc, e.del = e["delete"], e[173] = "-", function () { var t = ["cmd", "ctrl", "alt", "shift"]; for (var n = Math.pow(2, t.length); n--;)e.KEY_MODS[n] = t.filter(function (t) { return n & e.KEY_MODS[t] }).join("-") + "-" }(), e.KEY_MODS[0] = "", e.KEY_MODS[-1] = "input-", e }(); r.mixin(t, i), t.keyCodeToString = function (e) { var t = i[e]; return typeof t != "string" && (t = String.fromCharCode(e)), t.toLowerCase() } }), define("ace/lib/event", ["require", "exports", "module", "ace/lib/keys", "ace/lib/useragent"], function (e, t, n) { "use strict"; function a() { u = !1; try { document.createComment("").addEventListener("test", function () { }, { get passive() { u = { passive: !1 } } }) } catch (e) { } } function f() { return u == undefined && a(), u } function l(e, t, n) { this.elem = e, this.type = t, this.callback = n } function d(e, t, n) { var u = p(t); if (!i.isMac && s) { t.getModifierState && (t.getModifierState("OS") || t.getModifierState("Win")) && (u |= 8); if (s.altGr) { if ((3 & u) == 3) return; s.altGr = 0 } if (n === 18 || n === 17) { var a = "location" in t ? t.location : t.keyLocation; if (n === 17 && a === 1) s[n] == 1 && (o = t.timeStamp); else if (n === 18 && u === 3 && a === 2) { var f = t.timeStamp - o; f < 50 && (s.altGr = !0) } } } n in r.MODIFIER_KEYS && (n = -1); if (!u && n === 13) { var a = "location" in t ? t.location : t.keyLocation; if (a === 3) { e(t, u, -n); if (t.defaultPrevented) return } } if (i.isChromeOS && u & 8) { e(t, u, n); if (t.defaultPrevented) return; u &= -9 } return !!u || n in r.FUNCTION_KEYS || n in r.PRINTABLE_KEYS ? e(t, u, n) : !1 } function v() { s = Object.create(null) } var r = e("./keys"), i = e("./useragent"), s = null, o = 0, u; l.prototype.destroy = function () { h(this.elem, this.type, this.callback), this.elem = this.type = this.callback = undefined }; var c = t.addListener = function (e, t, n, r) { e.addEventListener(t, n, f()), r && r.$toDestroy.push(new l(e, t, n)) }, h = t.removeListener = function (e, t, n) { e.removeEventListener(t, n, f()) }; t.stopEvent = function (e) { return t.stopPropagation(e), t.preventDefault(e), !1 }, t.stopPropagation = function (e) { e.stopPropagation && e.stopPropagation() }, t.preventDefault = function (e) { e.preventDefault && e.preventDefault() }, t.getButton = function (e) { return e.type == "dblclick" ? 0 : e.type == "contextmenu" || i.isMac && e.ctrlKey && !e.altKey && !e.shiftKey ? 2 : e.button }, t.capture = function (e, t, n) { function r(e) { t && t(e), n && n(e), h(document, "mousemove", t), h(document, "mouseup", r), h(document, "dragstart", r) } return c(document, "mousemove", t), c(document, "mouseup", r), c(document, "dragstart", r), r }, t.addMouseWheelListener = function (e, t, n) { "onmousewheel" in e ? c(e, "mousewheel", function (e) { var n = 8; e.wheelDeltaX !== undefined ? (e.wheelX = -e.wheelDeltaX / n, e.wheelY = -e.wheelDeltaY / n) : (e.wheelX = 0, e.wheelY = -e.wheelDelta / n), t(e) }, n) : "onwheel" in e ? c(e, "wheel", function (e) { var n = .35; switch (e.deltaMode) { case e.DOM_DELTA_PIXEL: e.wheelX = e.deltaX * n || 0, e.wheelY = e.deltaY * n || 0; break; case e.DOM_DELTA_LINE: case e.DOM_DELTA_PAGE: e.wheelX = (e.deltaX || 0) * 5, e.wheelY = (e.deltaY || 0) * 5 }t(e) }, n) : c(e, "DOMMouseScroll", function (e) { e.axis && e.axis == e.HORIZONTAL_AXIS ? (e.wheelX = (e.detail || 0) * 5, e.wheelY = 0) : (e.wheelX = 0, e.wheelY = (e.detail || 0) * 5), t(e) }, n) }, t.addMultiMouseDownListener = function (e, n, r, s, o) { function p(e) { t.getButton(e) !== 0 ? u = 0 : e.detail > 1 ? (u++, u > 4 && (u = 1)) : u = 1; if (i.isIE) { var o = Math.abs(e.clientX - a) > 5 || Math.abs(e.clientY - f) > 5; if (!l || o) u = 1; l && clearTimeout(l), l = setTimeout(function () { l = null }, n[u - 1] || 600), u == 1 && (a = e.clientX, f = e.clientY) } e._clicks = u, r[s]("mousedown", e); if (u > 4) u = 0; else if (u > 1) return r[s](h[u], e) } var u = 0, a, f, l, h = { 2: "dblclick", 3: "tripleclick", 4: "quadclick" }; Array.isArray(e) || (e = [e]), e.forEach(function (e) { c(e, "mousedown", p, o) }) }; var p = function (e) { return 0 | (e.ctrlKey ? 1 : 0) | (e.altKey ? 2 : 0) | (e.shiftKey ? 4 : 0) | (e.metaKey ? 8 : 0) }; t.getModifierString = function (e) { return r.KEY_MODS[p(e)] }, t.addCommandKeyListener = function (e, n, r) { if (i.isOldGecko || i.isOpera && !("KeyboardEvent" in window)) { var o = null; c(e, "keydown", function (e) { o = e.keyCode }, r), c(e, "keypress", function (e) { return d(n, e, o) }, r) } else { var u = null; c(e, "keydown", function (e) { s[e.keyCode] = (s[e.keyCode] || 0) + 1; var t = d(n, e, e.keyCode); return u = e.defaultPrevented, t }, r), c(e, "keypress", function (e) { u && (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey) && (t.stopEvent(e), u = null) }, r), c(e, "keyup", function (e) { s[e.keyCode] = null }, r), s || (v(), c(window, "focus", v)) } }; if (typeof window == "object" && window.postMessage && !i.isOldIE) { var m = 1; t.nextTick = function (e, n) { n = n || window; var r = "zero-timeout-message-" + m++, i = function (s) { s.data == r && (t.stopPropagation(s), h(n, "message", i), e()) }; c(n, "message", i), n.postMessage(r, "*") } } t.$idleBlocked = !1, t.onIdle = function (e, n) { return setTimeout(function r() { t.$idleBlocked ? setTimeout(r, 100) : e() }, n) }, t.$idleBlockId = null, t.blockIdle = function (e) { t.$idleBlockId && clearTimeout(t.$idleBlockId), t.$idleBlocked = !0, t.$idleBlockId = setTimeout(function () { t.$idleBlocked = !1 }, e || 100) }, t.nextFrame = typeof window == "object" && (window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame), t.nextFrame ? t.nextFrame = t.nextFrame.bind(window) : t.nextFrame = function (e) { setTimeout(e, 17) } }), define("ace/range", ["require", "exports", "module"], function (e, t, n) { "use strict"; var r = function (e, t) { return e.row - t.row || e.column - t.column }, i = function (e, t, n, r) { this.start = { row: e, column: t }, this.end = { row: n, column: r } }; (function () { this.isEqual = function (e) { return this.start.row === e.start.row && this.end.row === e.end.row && this.start.column === e.start.column && this.end.column === e.end.column }, this.toString = function () { return "Range: [" + this.start.row + "/" + this.start.column + "] -> [" + this.end.row + "/" + this.end.column + "]" }, this.contains = function (e, t) { return this.compare(e, t) == 0 }, this.compareRange = function (e) { var t, n = e.end, r = e.start; return t = this.compare(n.row, n.column), t == 1 ? (t = this.compare(r.row, r.column), t == 1 ? 2 : t == 0 ? 1 : 0) : t == -1 ? -2 : (t = this.compare(r.row, r.column), t == -1 ? -1 : t == 1 ? 42 : 0) }, this.comparePoint = function (e) { return this.compare(e.row, e.column) }, this.containsRange = function (e) { return this.comparePoint(e.start) == 0 && this.comparePoint(e.end) == 0 }, this.intersects = function (e) { var t = this.compareRange(e); return t == -1 || t == 0 || t == 1 }, this.isEnd = function (e, t) { return this.end.row == e && this.end.column == t }, this.isStart = function (e, t) { return this.start.row == e && this.start.column == t }, this.setStart = function (e, t) { typeof e == "object" ? (this.start.column = e.column, this.start.row = e.row) : (this.start.row = e, this.start.column = t) }, this.setEnd = function (e, t) { typeof e == "object" ? (this.end.column = e.column, this.end.row = e.row) : (this.end.row = e, this.end.column = t) }, this.inside = function (e, t) { return this.compare(e, t) == 0 ? this.isEnd(e, t) || this.isStart(e, t) ? !1 : !0 : !1 }, this.insideStart = function (e, t) { return this.compare(e, t) == 0 ? this.isEnd(e, t) ? !1 : !0 : !1 }, this.insideEnd = function (e, t) { return this.compare(e, t) == 0 ? this.isStart(e, t) ? !1 : !0 : !1 }, this.compare = function (e, t) { return !this.isMultiLine() && e === this.start.row ? t < this.start.column ? -1 : t > this.end.column ? 1 : 0 : e < this.start.row ? -1 : e > this.end.row ? 1 : this.start.row === e ? t >= this.start.column ? 0 : -1 : this.end.row === e ? t <= this.end.column ? 0 : 1 : 0 }, this.compareStart = function (e, t) { return this.start.row == e && this.start.column == t ? -1 : this.compare(e, t) }, this.compareEnd = function (e, t) { return this.end.row == e && this.end.column == t ? 1 : this.compare(e, t) }, this.compareInside = function (e, t) { return this.end.row == e && this.end.column == t ? 1 : this.start.row == e && this.start.column == t ? -1 : this.compare(e, t) }, this.clipRows = function (e, t) { if (this.end.row > t) var n = { row: t + 1, column: 0 }; else if (this.end.row < e) var n = { row: e, column: 0 }; if (this.start.row > t) var r = { row: t + 1, column: 0 }; else if (this.start.row < e) var r = { row: e, column: 0 }; return i.fromPoints(r || this.start, n || this.end) }, this.extend = function (e, t) { var n = this.compare(e, t); if (n == 0) return this; if (n == -1) var r = { row: e, column: t }; else var s = { row: e, column: t }; return i.fromPoints(r || this.start, s || this.end) }, this.isEmpty = function () { return this.start.row === this.end.row && this.start.column === this.end.column }, this.isMultiLine = function () { return this.start.row !== this.end.row }, this.clone = function () { return i.fromPoints(this.start, this.end) }, this.collapseRows = function () { return this.end.column == 0 ? new i(this.start.row, 0, Math.max(this.start.row, this.end.row - 1), 0) : new i(this.start.row, 0, this.end.row, 0) }, this.toScreenRange = function (e) { var t = e.documentToScreenPosition(this.start), n = e.documentToScreenPosition(this.end); return new i(t.row, t.column, n.row, n.column) }, this.moveBy = function (e, t) { this.start.row += e, this.start.column += t, this.end.row += e, this.end.column += t } }).call(i.prototype), i.fromPoints = function (e, t) { return new i(e.row, e.column, t.row, t.column) }, i.comparePoints = r, i.comparePoints = function (e, t) { return e.row - t.row || e.column - t.column }, t.Range = i }), define("ace/lib/lang", ["require", "exports", "module"], function (e, t, n) { "use strict"; t.last = function (e) { return e[e.length - 1] }, t.stringReverse = function (e) { return e.split("").reverse().join("") }, t.stringRepeat = function (e, t) { var n = ""; while (t > 0) { t & 1 && (n += e); if (t >>= 1) e += e } return n }; var r = /^\s\s*/, i = /\s\s*$/; t.stringTrimLeft = function (e) { return e.replace(r, "") }, t.stringTrimRight = function (e) { return e.replace(i, "") }, t.copyObject = function (e) { var t = {}; for (var n in e) t[n] = e[n]; return t }, t.copyArray = function (e) { var t = []; for (var n = 0, r = e.length; n < r; n++)e[n] && typeof e[n] == "object" ? t[n] = this.copyObject(e[n]) : t[n] = e[n]; return t }, t.deepCopy = function s(e) { if (typeof e != "object" || !e) return e; var t; if (Array.isArray(e)) { t = []; for (var n = 0; n < e.length; n++)t[n] = s(e[n]); return t } if (Object.prototype.toString.call(e) !== "[object Object]") return e; t = {}; for (var n in e) t[n] = s(e[n]); return t }, t.arrayToMap = function (e) { var t = {}; for (var n = 0; n < e.length; n++)t[e[n]] = 1; return t }, t.createMap = function (e) { var t = Object.create(null); for (var n in e) t[n] = e[n]; return t }, t.arrayRemove = function (e, t) { for (var n = 0; n <= e.length; n++)t === e[n] && e.splice(n, 1) }, t.escapeRegExp = function (e) { return e.replace(/([.*+?^${}()|[\]\/\\])/g, "\\$1") }, t.escapeHTML = function (e) { return ("" + e).replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/ Date.now() - 50 ? !0 : r = !1 }, cancel: function () { r = Date.now() } } }), define("ace/keyboard/textinput", ["require", "exports", "module", "ace/lib/event", "ace/lib/useragent", "ace/lib/dom", "ace/lib/lang", "ace/clipboard", "ace/lib/keys"], function (e, t, n) { "use strict"; var r = e("../lib/event"), i = e("../lib/useragent"), s = e("../lib/dom"), o = e("../lib/lang"), u = e("../clipboard"), a = i.isChrome < 18, f = i.isIE, l = i.isChrome > 63, c = 400, h = e("../lib/keys"), p = h.KEY_MODS, d = i.isIOS, v = d ? /\s/ : /\n/, m = i.isMobile, g = function (e, t) { function X() { x = !0, n.blur(), n.focus(), x = !1 } function $(e) { e.keyCode == 27 && n.value.length < n.selectionStart && (b || (T = n.value), N = C = -1, O()), V() } function K() { clearTimeout(J), J = setTimeout(function () { E && (n.style.cssText = E, E = ""), t.renderer.$isMousePressed = !1, t.renderer.$keepTextAreaAtCursor && t.renderer.$moveTextAreaToCursor() }, 0) } function G(e, t, n) { var r = null, i = !1; n.addEventListener("keydown", function (e) { r && clearTimeout(r), i = !0 }, !0), n.addEventListener("keyup", function (e) { r = setTimeout(function () { i = !1 }, 100) }, !0); var s = function (e) { if (document.activeElement !== n) return; if (i || b || t.$mouseHandler.isMousePressed) return; if (g) return; var r = n.selectionStart, s = n.selectionEnd, o = null, u = 0; if (r == 0) o = h.up; else if (r == 1) o = h.home; else if (s > C && T[s] == "\n") o = h.end; else if (r < N && T[r - 1] == " ") o = h.left, u = p.option; else if (r < N || r == N && C != N && r == s) o = h.left; else if (s > C && T.slice(0, s).split("\n").length > 2) o = h.down; else if (s > C && T[s - 1] == " ") o = h.right, u = p.option; else if (s > C || s == C && C != N && r == s) o = h.right; r !== s && (u |= p.shift); if (o) { var a = t.onCommandKey({}, u, o); if (!a && t.commands) { o = h.keyCodeToString(o); var f = t.commands.findKeyCommand(u, o); f && t.execCommand(f) } N = r, C = s, O("") } }; document.addEventListener("selectionchange", s), t.on("destroy", function () { document.removeEventListener("selectionchange", s) }) } var n = s.createElement("textarea"); n.className = "ace_text-input", n.setAttribute("wrap", "off"), n.setAttribute("autocorrect", "off"), n.setAttribute("autocapitalize", "off"), n.setAttribute("spellcheck", !1), n.style.opacity = "0", e.insertBefore(n, e.firstChild); var g = !1, y = !1, b = !1, w = !1, E = ""; m || (n.style.fontSize = "1px"); var S = !1, x = !1, T = "", N = 0, C = 0, k = 0; try { var L = document.activeElement === n } catch (A) { } r.addListener(n, "blur", function (e) { if (x) return; t.onBlur(e), L = !1 }, t), r.addListener(n, "focus", function (e) { if (x) return; L = !0; if (i.isEdge) try { if (!document.hasFocus()) return } catch (e) { } t.onFocus(e), i.isEdge ? setTimeout(O) : O() }, t), this.$focusScroll = !1, this.focus = function () { if (E || l || this.$focusScroll == "browser") return n.focus({ preventScroll: !0 }); var e = n.style.top; n.style.position = "fixed", n.style.top = "0px"; try { var t = n.getBoundingClientRect().top != 0 } catch (r) { return } var i = []; if (t) { var s = n.parentElement; while (s && s.nodeType == 1) i.push(s), s.setAttribute("ace_nocontext", !0), !s.parentElement && s.getRootNode ? s = s.getRootNode().host : s = s.parentElement } n.focus({ preventScroll: !0 }), t && i.forEach(function (e) { e.removeAttribute("ace_nocontext") }), setTimeout(function () { n.style.position = "", n.style.top == "0px" && (n.style.top = e) }, 0) }, this.blur = function () { n.blur() }, this.isFocused = function () { return L }, t.on("beforeEndOperation", function () { var e = t.curOp, r = e && e.command && e.command.name; if (r == "insertstring") return; var i = r && (e.docChanged || e.selectionChanged); b && i && (T = n.value = "", W()), O() }); var O = d ? function (e) { if (!L || g && !e || w) return; e || (e = ""); var r = "\n ab" + e + "cde fg\n"; r != n.value && (n.value = T = r); var i = 4, s = 4 + (e.length || (t.selection.isEmpty() ? 0 : 1)); (N != i || C != s) && n.setSelectionRange(i, s), N = i, C = s } : function () { if (b || w) return; if (!L && !P) return; b = !0; var e = 0, r = 0, i = ""; if (t.session) { var s = t.selection, o = s.getRange(), u = s.cursor.row; e = o.start.column, r = o.end.column, i = t.session.getLine(u); if (o.start.row != u) { var a = t.session.getLine(u - 1); e = o.start.row < u - 1 ? 0 : e, r += a.length + 1, i = a + "\n" + i } else if (o.end.row != u) { var f = t.session.getLine(u + 1); r = o.end.row > u + 1 ? f.length : r, r += i.length + 1, i = i + "\n" + f } else m && u > 0 && (i = "\n" + i, r += 1, e += 1); i.length > c && (e < c && r < c ? i = i.slice(0, c) : (i = "\n", e = 0, r = 1)) } var l = i + "\n\n"; l != T && (n.value = T = l, N = C = l.length), P && (N = n.selectionStart, C = n.selectionEnd); if (C != r || N != e || n.selectionEnd != C) try { n.setSelectionRange(e, r), N = e, C = r } catch (h) { } b = !1 }; this.resetSelection = O, L && t.onFocus(); var M = function (e) { return e.selectionStart === 0 && e.selectionEnd >= T.length && e.value === T && T && e.selectionEnd !== C }, _ = function (e) { if (b) return; g ? g = !1 : M(n) ? (t.selectAll(), O()) : m && n.selectionStart != N && O() }, D = null; this.setInputHandler = function (e) { D = e }, this.getInputHandler = function () { return D }; var P = !1, H = function (e, r) { P && (P = !1); if (y) return O(), e && t.onPaste(e), y = !1, ""; var i = n.selectionStart, s = n.selectionEnd, o = N, u = T.length - C, a = e, f = e.length - i, l = e.length - s, c = 0; while (o > 0 && T[c] == e[c]) c++, o--; a = a.slice(c), c = 1; while (u > 0 && T.length - c > N - 1 && T[T.length - c] == e[e.length - c]) c++, u--; f -= c - 1, l -= c - 1; var h = a.length - c + 1; return h < 0 && (o = -h, h = 0), a = a.slice(0, h), !r && !a && !f && !o && !u && !l ? "" : (w = !0, a && !o && !u && !f && !l || S ? t.onTextInput(a) : t.onTextInput(a, { extendLeft: o, extendRight: u, restoreStart: f, restoreEnd: l }), w = !1, T = e, N = i, C = s, k = l, a) }, B = function (e) { if (b) return z(); if (e && e.inputType) { if (e.inputType == "historyUndo") return t.execCommand("undo"); if (e.inputType == "historyRedo") return t.execCommand("redo") } var r = n.value, i = H(r, !0); (r.length > c + 100 || v.test(i) || m && N < 1 && N == C) && O() }, j = function (e, t, n) { var r = e.clipboardData || window.clipboardData; if (!r || a) return; var i = f || n ? "Text" : "text/plain"; try { return t ? r.setData(i, t) !== !1 : r.getData(i) } catch (e) { if (!n) return j(e, t, !0) } }, F = function (e, i) { var s = t.getCopyText(); if (!s) return r.preventDefault(e); j(e, s) ? (d && (O(s), g = s, setTimeout(function () { g = !1 }, 10)), i ? t.onCut() : t.onCopy(), r.preventDefault(e)) : (g = !0, n.value = s, n.select(), setTimeout(function () { g = !1, O(), i ? t.onCut() : t.onCopy() })) }, I = function (e) { F(e, !0) }, q = function (e) { F(e, !1) }, R = function (e) { var s = j(e); if (u.pasteCancelled()) return; typeof s == "string" ? (s && t.onPaste(s, e), i.isIE && setTimeout(O), r.preventDefault(e)) : (n.value = "", y = !0) }; r.addCommandKeyListener(n, t.onCommandKey.bind(t), t), r.addListener(n, "select", _, t), r.addListener(n, "input", B, t), r.addListener(n, "cut", I, t), r.addListener(n, "copy", q, t), r.addListener(n, "paste", R, t), (!("oncut" in n) || !("oncopy" in n) || !("onpaste" in n)) && r.addListener(e, "keydown", function (e) { if (i.isMac && !e.metaKey || !e.ctrlKey) return; switch (e.keyCode) { case 67: q(e); break; case 86: R(e); break; case 88: I(e) } }, t); var U = function (e) { if (b || !t.onCompositionStart || t.$readOnly) return; b = {}; if (S) return; e.data && (b.useTextareaForIME = !1), setTimeout(z, 0), t._signal("compositionStart"), t.on("mousedown", X); var r = t.getSelectionRange(); r.end.row = r.start.row, r.end.column = r.start.column, b.markerRange = r, b.selectionStart = N, t.onCompositionStart(b), b.useTextareaForIME ? (T = n.value = "", N = 0, C = 0) : (n.msGetInputContext && (b.context = n.msGetInputContext()), n.getInputContext && (b.context = n.getInputContext())) }, z = function () { if (!b || !t.onCompositionUpdate || t.$readOnly) return; if (S) return X(); if (b.useTextareaForIME) t.onCompositionUpdate(n.value); else { var e = n.value; H(e), b.markerRange && (b.context && (b.markerRange.start.column = b.selectionStart = b.context.compositionStartOffset), b.markerRange.end.column = b.markerRange.start.column + C - b.selectionStart + k) } }, W = function (e) { if (!t.onCompositionEnd || t.$readOnly) return; b = !1, t.onCompositionEnd(), t.off("mousedown", X), e && B() }, V = o.delayedCall(z, 50).schedule.bind(null, null); r.addListener(n, "compositionstart", U, t), r.addListener(n, "compositionupdate", z, t), r.addListener(n, "keyup", $, t), r.addListener(n, "keydown", V, t), r.addListener(n, "compositionend", W, t), this.getElement = function () { return n }, this.setCommandMode = function (e) { S = e, n.readOnly = !1 }, this.setReadOnly = function (e) { S || (n.readOnly = e) }, this.setCopyWithEmptySelection = function (e) { }, this.onContextMenu = function (e) { P = !0, O(), t._emit("nativecontextmenu", { target: t, domEvent: e }), this.moveToMouse(e, !0) }, this.moveToMouse = function (e, o) { E || (E = n.style.cssText), n.style.cssText = (o ? "z-index:100000;" : "") + (i.isIE ? "opacity:0.1;" : "") + "text-indent: -" + (N + C) * t.renderer.characterWidth * .5 + "px;"; var u = t.container.getBoundingClientRect(), a = s.computedStyle(t.container), f = u.top + (parseInt(a.borderTopWidth) || 0), l = u.left + (parseInt(u.borderLeftWidth) || 0), c = u.bottom - f - n.clientHeight - 2, h = function (e) { s.translate(n, e.clientX - l - 2, Math.min(e.clientY - f - 2, c)) }; h(e); if (e.type != "mousedown") return; t.renderer.$isMousePressed = !0, clearTimeout(J), i.isWin && r.capture(t.container, h, K) }, this.onContextMenuClose = K; var J, Q = function (e) { t.textInput.onContextMenu(e), K() }; r.addListener(n, "mouseup", Q, t), r.addListener(n, "mousedown", function (e) { e.preventDefault(), K() }, t), r.addListener(t.renderer.scroller, "contextmenu", Q, t), r.addListener(n, "contextmenu", Q, t), d && G(e, t, n) }; t.TextInput = g, t.$setUserAgentForTests = function (e, t) { m = e, d = t } }), define("ace/mouse/default_handlers", ["require", "exports", "module", "ace/lib/useragent"], function (e, t, n) { "use strict"; function o(e) { e.$clickSelection = null; var t = e.editor; t.setDefaultHandler("mousedown", this.onMouseDown.bind(e)), t.setDefaultHandler("dblclick", this.onDoubleClick.bind(e)), t.setDefaultHandler("tripleclick", this.onTripleClick.bind(e)), t.setDefaultHandler("quadclick", this.onQuadClick.bind(e)), t.setDefaultHandler("mousewheel", this.onMouseWheel.bind(e)); var n = ["select", "startSelect", "selectEnd", "selectAllEnd", "selectByWordsEnd", "selectByLinesEnd", "dragWait", "dragWaitEnd", "focusWait"]; n.forEach(function (t) { e[t] = this[t] }, this), e.selectByLines = this.extendSelectionBy.bind(e, "getLineRange"), e.selectByWords = this.extendSelectionBy.bind(e, "getWordRange") } function u(e, t, n, r) { return Math.sqrt(Math.pow(n - e, 2) + Math.pow(r - t, 2)) } function a(e, t) { if (e.start.row == e.end.row) var n = 2 * t.column - e.start.column - e.end.column; else if (e.start.row == e.end.row - 1 && !e.start.column && !e.end.column) var n = t.column - 4; else var n = 2 * t.row - e.start.row - e.end.row; return n < 0 ? { cursor: e.start, anchor: e.end } : { cursor: e.end, anchor: e.start } } var r = e("../lib/useragent"), i = 0, s = 550; (function () { this.onMouseDown = function (e) { var t = e.inSelection(), n = e.getDocumentPosition(); this.mousedownEvent = e; var i = this.editor, s = e.getButton(); if (s !== 0) { var o = i.getSelectionRange(), u = o.isEmpty(); (u || s == 1) && i.selection.moveToPosition(n), s == 2 && (i.textInput.onContextMenu(e.domEvent), r.isMozilla || e.preventDefault()); return } this.mousedownEvent.time = Date.now(); if (t && !i.isFocused()) { i.focus(); if (this.$focusTimeout && !this.$clickSelection && !i.inMultiSelectMode) { this.setState("focusWait"), this.captureMouse(e); return } } return this.captureMouse(e), this.startSelect(n, e.domEvent._clicks > 1), e.preventDefault() }, this.startSelect = function (e, t) { e = e || this.editor.renderer.screenToTextCoordinates(this.x, this.y); var n = this.editor; if (!this.mousedownEvent) return; this.mousedownEvent.getShiftKey() ? n.selection.selectToPosition(e) : t || n.selection.moveToPosition(e), t || this.select(), n.renderer.scroller.setCapture && n.renderer.scroller.setCapture(), n.setStyle("ace_selecting"), this.setState("select") }, this.select = function () { var e, t = this.editor, n = t.renderer.screenToTextCoordinates(this.x, this.y); if (this.$clickSelection) { var r = this.$clickSelection.comparePoint(n); if (r == -1) e = this.$clickSelection.end; else if (r == 1) e = this.$clickSelection.start; else { var i = a(this.$clickSelection, n); n = i.cursor, e = i.anchor } t.selection.setSelectionAnchor(e.row, e.column) } t.selection.selectToPosition(n), t.renderer.scrollCursorIntoView() }, this.extendSelectionBy = function (e) { var t, n = this.editor, r = n.renderer.screenToTextCoordinates(this.x, this.y), i = n.selection[e](r.row, r.column); if (this.$clickSelection) { var s = this.$clickSelection.comparePoint(i.start), o = this.$clickSelection.comparePoint(i.end); if (s == -1 && o <= 0) { t = this.$clickSelection.end; if (i.end.row != r.row || i.end.column != r.column) r = i.start } else if (o == 1 && s >= 0) { t = this.$clickSelection.start; if (i.start.row != r.row || i.start.column != r.column) r = i.end } else if (s == -1 && o == 1) r = i.end, t = i.start; else { var u = a(this.$clickSelection, r); r = u.cursor, t = u.anchor } n.selection.setSelectionAnchor(t.row, t.column) } n.selection.selectToPosition(r), n.renderer.scrollCursorIntoView() }, this.selectEnd = this.selectAllEnd = this.selectByWordsEnd = this.selectByLinesEnd = function () { this.$clickSelection = null, this.editor.unsetStyle("ace_selecting"), this.editor.renderer.scroller.releaseCapture && this.editor.renderer.scroller.releaseCapture() }, this.focusWait = function () { var e = u(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y), t = Date.now(); (e > i || t - this.mousedownEvent.time > this.$focusTimeout) && this.startSelect(this.mousedownEvent.getDocumentPosition()) }, this.onDoubleClick = function (e) { var t = e.getDocumentPosition(), n = this.editor, r = n.session, i = r.getBracketRange(t); i ? (i.isEmpty() && (i.start.column--, i.end.column++), this.setState("select")) : (i = n.selection.getWordRange(t.row, t.column), this.setState("selectByWords")), this.$clickSelection = i, this.select() }, this.onTripleClick = function (e) { var t = e.getDocumentPosition(), n = this.editor; this.setState("selectByLines"); var r = n.getSelectionRange(); r.isMultiLine() && r.contains(t.row, t.column) ? (this.$clickSelection = n.selection.getLineRange(r.start.row), this.$clickSelection.end = n.selection.getLineRange(r.end.row).end) : this.$clickSelection = n.selection.getLineRange(t.row), this.select() }, this.onQuadClick = function (e) { var t = this.editor; t.selectAll(), this.$clickSelection = t.getSelectionRange(), this.setState("selectAll") }, this.onMouseWheel = function (e) { if (e.getAccelKey()) return; e.getShiftKey() && e.wheelY && !e.wheelX && (e.wheelX = e.wheelY, e.wheelY = 0); var t = this.editor; this.$lastScroll || (this.$lastScroll = { t: 0, vx: 0, vy: 0, allowed: 0 }); var n = this.$lastScroll, r = e.domEvent.timeStamp, i = r - n.t, o = i ? e.wheelX / i : n.vx, u = i ? e.wheelY / i : n.vy; i < s && (o = (o + n.vx) / 2, u = (u + n.vy) / 2); var a = Math.abs(o / u), f = !1; a >= 1 && t.renderer.isScrollableBy(e.wheelX * e.speed, 0) && (f = !0), a <= 1 && t.renderer.isScrollableBy(0, e.wheelY * e.speed) && (f = !0); if (f) n.allowed = r; else if (r - n.allowed < s) { var l = Math.abs(o) <= 1.5 * Math.abs(n.vx) && Math.abs(u) <= 1.5 * Math.abs(n.vy); l ? (f = !0, n.allowed = r) : n.allowed = 0 } n.t = r, n.vx = o, n.vy = u; if (f) return t.renderer.scrollBy(e.wheelX * e.speed, e.wheelY * e.speed), e.stop() } }).call(o.prototype), t.DefaultHandlers = o }), define("ace/tooltip", ["require", "exports", "module", "ace/lib/oop", "ace/lib/dom"], function (e, t, n) { "use strict"; function s(e) { this.isOpen = !1, this.$element = null, this.$parentNode = e } var r = e("./lib/oop"), i = e("./lib/dom"); (function () { this.$init = function () { return this.$element = i.createElement("div"), this.$element.className = "ace_tooltip", this.$element.style.display = "none", this.$parentNode.appendChild(this.$element), this.$element }, this.getElement = function () { return this.$element || this.$init() }, this.setText = function (e) { this.getElement().textContent = e }, this.setHtml = function (e) { this.getElement().innerHTML = e }, this.setPosition = function (e, t) { this.getElement().style.left = e + "px", this.getElement().style.top = t + "px" }, this.setClassName = function (e) { i.addCssClass(this.getElement(), e) }, this.show = function (e, t, n) { e != null && this.setText(e), t != null && n != null && this.setPosition(t, n), this.isOpen || (this.getElement().style.display = "block", this.isOpen = !0) }, this.hide = function () { this.isOpen && (this.getElement().style.display = "none", this.isOpen = !1) }, this.getHeight = function () { return this.getElement().offsetHeight }, this.getWidth = function () { return this.getElement().offsetWidth }, this.destroy = function () { this.isOpen = !1, this.$element && this.$element.parentNode && this.$element.parentNode.removeChild(this.$element) } }).call(s.prototype), t.Tooltip = s }), define("ace/mouse/default_gutter_handler", ["require", "exports", "module", "ace/lib/dom", "ace/lib/oop", "ace/lib/event", "ace/tooltip"], function (e, t, n) { "use strict"; function u(e) { function l() { var r = u.getDocumentPosition().row, s = n.$annotations[r]; if (!s) return c(); var o = t.session.getLength(); if (r == o) { var a = t.renderer.pixelToScreenCoordinates(0, u.y).row, l = u.$pos; if (a > t.session.documentToScreenRow(l.row, l.column)) return c() } if (f == s) return; f = s.text.join("
"), i.setHtml(f), i.show(), t._signal("showGutterTooltip", i), t.on("mousewheel", c); if (e.$tooltipFollowsMouse) h(u); else { var p = u.domEvent.target, d = p.getBoundingClientRect(), v = i.getElement().style; v.left = d.right + "px", v.top = d.bottom + "px" } } function c() { o && (o = clearTimeout(o)), f && (i.hide(), f = null, t._signal("hideGutterTooltip", i), t.off("mousewheel", c)) } function h(e) { i.setPosition(e.x, e.y) } var t = e.editor, n = t.renderer.$gutterLayer, i = new a(t.container); e.editor.setDefaultHandler("guttermousedown", function (r) { if (!t.isFocused() || r.getButton() != 0) return; var i = n.getRegion(r); if (i == "foldWidgets") return; var s = r.getDocumentPosition().row, o = t.session.selection; if (r.getShiftKey()) o.selectTo(s, 0); else { if (r.domEvent.detail == 2) return t.selectAll(), r.preventDefault(); e.$clickSelection = t.selection.getLineRange(s) } return e.setState("selectByLines"), e.captureMouse(r), r.preventDefault() }); var o, u, f; e.editor.setDefaultHandler("guttermousemove", function (t) { var n = t.domEvent.target || t.domEvent.srcElement; if (r.hasCssClass(n, "ace_fold-widget")) return c(); f && e.$tooltipFollowsMouse && h(t), u = t; if (o) return; o = setTimeout(function () { o = null, u && !e.isMousePressed ? l() : c() }, 50) }), s.addListener(t.renderer.$gutter, "mouseout", function (e) { u = null; if (!f || o) return; o = setTimeout(function () { o = null, c() }, 50) }, t), t.on("changeSession", c) } function a(e) { o.call(this, e) } var r = e("../lib/dom"), i = e("../lib/oop"), s = e("../lib/event"), o = e("../tooltip").Tooltip; i.inherits(a, o), function () { this.setPosition = function (e, t) { var n = window.innerWidth || document.documentElement.clientWidth, r = window.innerHeight || document.documentElement.clientHeight, i = this.getWidth(), s = this.getHeight(); e += 15, t += 15, e + i > n && (e -= e + i - n), t + s > r && (t -= 20 + s), o.prototype.setPosition.call(this, e, t) } }.call(a.prototype), t.GutterHandler = u }), define("ace/mouse/mouse_event", ["require", "exports", "module", "ace/lib/event", "ace/lib/useragent"], function (e, t, n) { "use strict"; var r = e("../lib/event"), i = e("../lib/useragent"), s = t.MouseEvent = function (e, t) { this.domEvent = e, this.editor = t, this.x = this.clientX = e.clientX, this.y = this.clientY = e.clientY, this.$pos = null, this.$inSelection = null, this.propagationStopped = !1, this.defaultPrevented = !1 }; (function () { this.stopPropagation = function () { r.stopPropagation(this.domEvent), this.propagationStopped = !0 }, this.preventDefault = function () { r.preventDefault(this.domEvent), this.defaultPrevented = !0 }, this.stop = function () { this.stopPropagation(), this.preventDefault() }, this.getDocumentPosition = function () { return this.$pos ? this.$pos : (this.$pos = this.editor.renderer.screenToTextCoordinates(this.clientX, this.clientY), this.$pos) }, this.inSelection = function () { if (this.$inSelection !== null) return this.$inSelection; var e = this.editor, t = e.getSelectionRange(); if (t.isEmpty()) this.$inSelection = !1; else { var n = this.getDocumentPosition(); this.$inSelection = t.contains(n.row, n.column) } return this.$inSelection }, this.getButton = function () { return r.getButton(this.domEvent) }, this.getShiftKey = function () { return this.domEvent.shiftKey }, this.getAccelKey = i.isMac ? function () { return this.domEvent.metaKey } : function () { return this.domEvent.ctrlKey } }).call(s.prototype) }), define("ace/mouse/dragdrop_handler", ["require", "exports", "module", "ace/lib/dom", "ace/lib/event", "ace/lib/useragent"], function (e, t, n) { "use strict"; function f(e) { function T(e, n) { var r = Date.now(), i = !n || e.row != n.row, s = !n || e.column != n.column; if (!S || i || s) t.moveCursorToPosition(e), S = r, x = { x: p, y: d }; else { var o = l(x.x, x.y, p, d); o > a ? S = null : r - S >= u && (t.renderer.scrollCursorIntoView(), S = null) } } function N(e, n) { var r = Date.now(), i = t.renderer.layerConfig.lineHeight, s = t.renderer.layerConfig.characterWidth, u = t.renderer.scroller.getBoundingClientRect(), a = { x: { left: p - u.left, right: u.right - p }, y: { top: d - u.top, bottom: u.bottom - d } }, f = Math.min(a.x.left, a.x.right), l = Math.min(a.y.top, a.y.bottom), c = { row: e.row, column: e.column }; f / s <= 2 && (c.column += a.x.left < a.x.right ? -3 : 2), l / i <= 1 && (c.row += a.y.top < a.y.bottom ? -1 : 1); var h = e.row != c.row, v = e.column != c.column, m = !n || e.row != n.row; h || v && !m ? E ? r - E >= o && t.renderer.scrollCursorIntoView(c) : E = r : E = null } function C() { var e = g; g = t.renderer.screenToTextCoordinates(p, d), T(g, e), N(g, e) } function k() { m = t.selection.toOrientedRange(), h = t.session.addMarker(m, "ace_selection", t.getSelectionStyle()), t.clearSelection(), t.isFocused() && t.renderer.$cursorLayer.setBlinking(!1), clearInterval(v), C(), v = setInterval(C, 20), y = 0, i.addListener(document, "mousemove", O) } function L() { clearInterval(v), t.session.removeMarker(h), h = null, t.selection.fromOrientedRange(m), t.isFocused() && !w && t.$resetCursorStyle(), m = null, g = null, y = 0, E = null, S = null, i.removeListener(document, "mousemove", O) } function O() { A == null && (A = setTimeout(function () { A != null && h && L() }, 20)) } function M(e) { var t = e.types; return !t || Array.prototype.some.call(t, function (e) { return e == "text/plain" || e == "Text" }) } function _(e) { var t = ["copy", "copymove", "all", "uninitialized"], n = ["move", "copymove", "linkmove", "all", "uninitialized"], r = s.isMac ? e.altKey : e.ctrlKey, i = "uninitialized"; try { i = e.dataTransfer.effectAllowed.toLowerCase() } catch (e) { } var o = "none"; return r && t.indexOf(i) >= 0 ? o = "copy" : n.indexOf(i) >= 0 ? o = "move" : t.indexOf(i) >= 0 && (o = "copy"), o } var t = e.editor, n = r.createElement("img"); n.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==", s.isOpera && (n.style.cssText = "width:1px;height:1px;position:fixed;top:0;left:0;z-index:2147483647;opacity:0;"); var f = ["dragWait", "dragWaitEnd", "startDrag", "dragReadyEnd", "onMouseDrag"]; f.forEach(function (t) { e[t] = this[t] }, this), t.on("mousedown", this.onMouseDown.bind(e)); var c = t.container, h, p, d, v, m, g, y = 0, b, w, E, S, x; this.onDragStart = function (e) { if (this.cancelDrag || !c.draggable) { var r = this; return setTimeout(function () { r.startSelect(), r.captureMouse(e) }, 0), e.preventDefault() } m = t.getSelectionRange(); var i = e.dataTransfer; i.effectAllowed = t.getReadOnly() ? "copy" : "copyMove", s.isOpera && (t.container.appendChild(n), n.scrollTop = 0), i.setDragImage && i.setDragImage(n, 0, 0), s.isOpera && t.container.removeChild(n), i.clearData(), i.setData("Text", t.session.getTextRange()), w = !0, this.setState("drag") }, this.onDragEnd = function (e) { c.draggable = !1, w = !1, this.setState(null); if (!t.getReadOnly()) { var n = e.dataTransfer.dropEffect; !b && n == "move" && t.session.remove(t.getSelectionRange()), t.$resetCursorStyle() } this.editor.unsetStyle("ace_dragging"), this.editor.renderer.setCursorStyle("") }, this.onDragEnter = function (e) { if (t.getReadOnly() || !M(e.dataTransfer)) return; return p = e.clientX, d = e.clientY, h || k(), y++, e.dataTransfer.dropEffect = b = _(e), i.preventDefault(e) }, this.onDragOver = function (e) { if (t.getReadOnly() || !M(e.dataTransfer)) return; return p = e.clientX, d = e.clientY, h || (k(), y++), A !== null && (A = null), e.dataTransfer.dropEffect = b = _(e), i.preventDefault(e) }, this.onDragLeave = function (e) { y--; if (y <= 0 && h) return L(), b = null, i.preventDefault(e) }, this.onDrop = function (e) { if (!g) return; var n = e.dataTransfer; if (w) switch (b) { case "move": m.contains(g.row, g.column) ? m = { start: g, end: g } : m = t.moveText(m, g); break; case "copy": m = t.moveText(m, g, !0) } else { var r = n.getData("Text"); m = { start: g, end: t.session.insert(g, r) }, t.focus(), b = null } return L(), i.preventDefault(e) }, i.addListener(c, "dragstart", this.onDragStart.bind(e), t), i.addListener(c, "dragend", this.onDragEnd.bind(e), t), i.addListener(c, "dragenter", this.onDragEnter.bind(e), t), i.addListener(c, "dragover", this.onDragOver.bind(e), t), i.addListener(c, "dragleave", this.onDragLeave.bind(e), t), i.addListener(c, "drop", this.onDrop.bind(e), t); var A = null } function l(e, t, n, r) { return Math.sqrt(Math.pow(n - e, 2) + Math.pow(r - t, 2)) } var r = e("../lib/dom"), i = e("../lib/event"), s = e("../lib/useragent"), o = 200, u = 200, a = 5; (function () { this.dragWait = function () { var e = Date.now() - this.mousedownEvent.time; e > this.editor.getDragDelay() && this.startDrag() }, this.dragWaitEnd = function () { var e = this.editor.container; e.draggable = !1, this.startSelect(this.mousedownEvent.getDocumentPosition()), this.selectEnd() }, this.dragReadyEnd = function (e) { this.editor.$resetCursorStyle(), this.editor.unsetStyle("ace_dragging"), this.editor.renderer.setCursorStyle(""), this.dragWaitEnd() }, this.startDrag = function () { this.cancelDrag = !1; var e = this.editor, t = e.container; t.draggable = !0, e.renderer.$cursorLayer.setBlinking(!1), e.setStyle("ace_dragging"); var n = s.isWin ? "default" : "move"; e.renderer.setCursorStyle(n), this.setState("dragReady") }, this.onMouseDrag = function (e) { var t = this.editor.container; if (s.isIE && this.state == "dragReady") { var n = l(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y); n > 3 && t.dragDrop() } if (this.state === "dragWait") { var n = l(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y); n > 0 && (t.draggable = !1, this.startSelect(this.mousedownEvent.getDocumentPosition())) } }, this.onMouseDown = function (e) { if (!this.$dragEnabled) return; this.mousedownEvent = e; var t = this.editor, n = e.inSelection(), r = e.getButton(), i = e.domEvent.detail || 1; if (i === 1 && r === 0 && n) { if (e.editor.inMultiSelectMode && (e.getAccelKey() || e.getShiftKey())) return; this.mousedownEvent.time = Date.now(); var o = e.domEvent.target || e.domEvent.srcElement; "unselectable" in o && (o.unselectable = "on"); if (t.getDragDelay()) { if (s.isWebKit) { this.cancelDrag = !0; var u = t.container; u.draggable = !0 } this.setState("dragWait") } else this.startDrag(); this.captureMouse(e, this.onMouseDrag.bind(this)), e.defaultPrevented = !0 } } }).call(f.prototype), t.DragdropHandler = f }), define("ace/mouse/touch_handler", ["require", "exports", "module", "ace/mouse/mouse_event", "ace/lib/event", "ace/lib/dom"], function (e, t, n) { "use strict"; var r = e("./mouse_event").MouseEvent, i = e("../lib/event"), s = e("../lib/dom"); t.addTouchListeners = function (e, t) { function b() { var e = window.navigator && window.navigator.clipboard, r = !1, i = function () { var n = t.getCopyText(), i = t.session.getUndoManager().hasUndo(); y.replaceChild(s.buildDom(r ? ["span", !n && ["span", { "class": "ace_mobile-button", action: "selectall" }, "Select All"], n && ["span", { "class": "ace_mobile-button", action: "copy" }, "Copy"], n && ["span", { "class": "ace_mobile-button", action: "cut" }, "Cut"], e && ["span", { "class": "ace_mobile-button", action: "paste" }, "Paste"], i && ["span", { "class": "ace_mobile-button", action: "undo" }, "Undo"], ["span", { "class": "ace_mobile-button", action: "find" }, "Find"], ["span", { "class": "ace_mobile-button", action: "openCommandPallete" }, "Pallete"]] : ["span"]), y.firstChild) }, o = function (n) { var s = n.target.getAttribute("action"); if (s == "more" || !r) return r = !r, i(); if (s == "paste") e.readText().then(function (e) { t.execCommand(s, e) }); else if (s) { if (s == "cut" || s == "copy") e ? e.writeText(t.getCopyText()) : document.execCommand("copy"); t.execCommand(s) } y.firstChild.style.display = "none", r = !1, s != "openCommandPallete" && t.focus() }; y = s.buildDom(["div", { "class": "ace_mobile-menu", ontouchstart: function (e) { n = "menu", e.stopPropagation(), e.preventDefault(), t.textInput.focus() }, ontouchend: function (e) { e.stopPropagation(), e.preventDefault(), o(e) }, onclick: o }, ["span"], ["span", { "class": "ace_mobile-button", action: "more" }, "..."]], t.container) } function w() { y || b(); var e = t.selection.cursor, n = t.renderer.textToScreenCoordinates(e.row, e.column), r = t.container.getBoundingClientRect(); y.style.top = n.pageY - r.top - 3 + "px", y.style.right = "10px", y.style.display = "", y.firstChild.style.display = "none", t.on("input", E) } function E(e) { y && (y.style.display = "none"), t.off("input", E) } function S() { l = null, clearTimeout(l); var e = t.selection.getRange(), r = e.contains(p.row, p.column); if (e.isEmpty() || !r) t.selection.moveToPosition(p), t.selection.selectWord(); n = "wait", w() } function x() { l = null, clearTimeout(l), t.selection.moveToPosition(p); var e = d >= 2 ? t.selection.getLineRange(p.row) : t.session.getBracketRange(p); e && !e.isEmpty() ? t.selection.setRange(e) : t.selection.selectWord(), n = "wait" } function T() { h += 60, c = setInterval(function () { h-- <= 0 && (clearInterval(c), c = null), Math.abs(v) < .01 && (v = 0), Math.abs(m) < .01 && (m = 0), h < 20 && (v = .9 * v), h < 20 && (m = .9 * m); var e = t.session.getScrollTop(); t.renderer.scrollBy(10 * v, 10 * m), e == t.session.getScrollTop() && (h = 0) }, 10) } var n = "scroll", o, u, a, f, l, c, h = 0, p, d = 0, v = 0, m = 0, g, y; i.addListener(e, "contextmenu", function (e) { if (!g) return; var n = t.textInput.getElement(); n.focus() }, t), i.addListener(e, "touchstart", function (e) { var i = e.touches; if (l || i.length > 1) { clearTimeout(l), l = null, a = -1, n = "zoom"; return } g = t.$mouseHandler.isMousePressed = !0; var s = t.renderer.layerConfig.lineHeight, c = t.renderer.layerConfig.lineHeight, y = e.timeStamp; f = y; var b = i[0], w = b.clientX, E = b.clientY; Math.abs(o - w) + Math.abs(u - E) > s && (a = -1), o = e.clientX = w, u = e.clientY = E, v = m = 0; var T = new r(e, t); p = T.getDocumentPosition(); if (y - a < 500 && i.length == 1 && !h) d++, e.preventDefault(), e.button = 0, x(); else { d = 0; var N = t.selection.cursor, C = t.selection.isEmpty() ? N : t.selection.anchor, k = t.renderer.$cursorLayer.getPixelPosition(N, !0), L = t.renderer.$cursorLayer.getPixelPosition(C, !0), A = t.renderer.scroller.getBoundingClientRect(), O = function (e, t) { return e /= c, t = t / s - .75, e * e + t * t }; if (e.clientX < A.left) { n = "zoom"; return } var M = O(e.clientX - A.left - k.left, e.clientY - A.top - k.top), _ = O(e.clientX - A.left - L.left, e.clientY - A.top - L.top); M < 3.5 && _ < 3.5 && (n = M > _ ? "cursor" : "anchor"), _ < 3.5 ? n = "anchor" : M < 3.5 ? n = "cursor" : n = "scroll", l = setTimeout(S, 450) } a = y }, t), i.addListener(e, "touchend", function (e) { g = t.$mouseHandler.isMousePressed = !1, c && clearInterval(c), n == "zoom" ? (n = "", h = 0) : l ? (t.selection.moveToPosition(p), h = 0, w()) : n == "scroll" ? (T(), E()) : w(), clearTimeout(l), l = null }, t), i.addListener(e, "touchmove", function (e) { l && (clearTimeout(l), l = null); var i = e.touches; if (i.length > 1 || n == "zoom") return; var s = i[0], a = o - s.clientX, c = u - s.clientY; if (n == "wait") { if (!(a * a + c * c > 4)) return e.preventDefault(); n = "cursor" } o = s.clientX, u = s.clientY, e.clientX = s.clientX, e.clientY = s.clientY; var h = e.timeStamp, p = h - f; f = h; if (n == "scroll") { var d = new r(e, t); d.speed = 1, d.wheelX = a, d.wheelY = c, 10 * Math.abs(a) < Math.abs(c) && (a = 0), 10 * Math.abs(c) < Math.abs(a) && (c = 0), p != 0 && (v = a / p, m = c / p), t._emit("mousewheel", d), d.propagationStopped || (v = m = 0) } else { var g = new r(e, t), y = g.getDocumentPosition(); n == "cursor" ? t.selection.moveCursorToPosition(y) : n == "anchor" && t.selection.setSelectionAnchor(y.row, y.column), t.renderer.scrollCursorIntoView(y), e.preventDefault() } }, t) } }), define("ace/lib/net", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { "use strict"; var r = e("./dom"); t.get = function (e, t) { var n = new XMLHttpRequest; n.open("GET", e, !0), n.onreadystatechange = function () { n.readyState === 4 && t(n.responseText) }, n.send(null) }, t.loadScript = function (e, t) { var n = r.getDocumentHead(), i = document.createElement("script"); i.src = e, n.appendChild(i), i.onload = i.onreadystatechange = function (e, n) { if (n || !i.readyState || i.readyState == "loaded" || i.readyState == "complete") i = i.onload = i.onreadystatechange = null, n || t() } }, t.qualifyURL = function (e) { var t = document.createElement("a"); return t.href = e, t.href } }), define("ace/lib/event_emitter", ["require", "exports", "module"], function (e, t, n) { "use strict"; var r = {}, i = function () { this.propagationStopped = !0 }, s = function () { this.defaultPrevented = !0 }; r._emit = r._dispatchEvent = function (e, t) { this._eventRegistry || (this._eventRegistry = {}), this._defaultHandlers || (this._defaultHandlers = {}); var n = this._eventRegistry[e] || [], r = this._defaultHandlers[e]; if (!n.length && !r) return; if (typeof t != "object" || !t) t = {}; t.type || (t.type = e), t.stopPropagation || (t.stopPropagation = i), t.preventDefault || (t.preventDefault = s), n = n.slice(); for (var o = 0; o < n.length; o++) { n[o](t, this); if (t.propagationStopped) break } if (r && !t.defaultPrevented) return r(t, this) }, r._signal = function (e, t) { var n = (this._eventRegistry || {})[e]; if (!n) return; n = n.slice(); for (var r = 0; r < n.length; r++)n[r](t, this) }, r.once = function (e, t) { var n = this; this.on(e, function r() { n.off(e, r), t.apply(null, arguments) }); if (!t) return new Promise(function (e) { t = e }) }, r.setDefaultHandler = function (e, t) { var n = this._defaultHandlers; n || (n = this._defaultHandlers = { _disabled_: {} }); if (n[e]) { var r = n[e], i = n._disabled_[e]; i || (n._disabled_[e] = i = []), i.push(r); var s = i.indexOf(t); s != -1 && i.splice(s, 1) } n[e] = t }, r.removeDefaultHandler = function (e, t) { var n = this._defaultHandlers; if (!n) return; var r = n._disabled_[e]; if (n[e] == t) r && this.setDefaultHandler(e, r.pop()); else if (r) { var i = r.indexOf(t); i != -1 && r.splice(i, 1) } }, r.on = r.addEventListener = function (e, t, n) { this._eventRegistry = this._eventRegistry || {}; var r = this._eventRegistry[e]; return r || (r = this._eventRegistry[e] = []), r.indexOf(t) == -1 && r[n ? "unshift" : "push"](t), t }, r.off = r.removeListener = r.removeEventListener = function (e, t) { this._eventRegistry = this._eventRegistry || {}; var n = this._eventRegistry[e]; if (!n) return; var r = n.indexOf(t); r !== -1 && n.splice(r, 1) }, r.removeAllListeners = function (e) { e || (this._eventRegistry = this._defaultHandlers = undefined), this._eventRegistry && (this._eventRegistry[e] = undefined), this._defaultHandlers && (this._defaultHandlers[e] = undefined) }, t.EventEmitter = r }), define("ace/lib/app_config", ["require", "exports", "module", "ace/lib/oop", "ace/lib/event_emitter"], function (e, t, n) { "no use strict"; function o(e) { typeof console != "undefined" && console.warn && console.warn.apply(console, arguments) } function u(e, t) { var n = new Error(e); n.data = t, typeof console == "object" && console.error && console.error(n), setTimeout(function () { throw n }) } var r = e("./oop"), i = e("./event_emitter").EventEmitter, s = { setOptions: function (e) { Object.keys(e).forEach(function (t) { this.setOption(t, e[t]) }, this) }, getOptions: function (e) { var t = {}; if (!e) { var n = this.$options; e = Object.keys(n).filter(function (e) { return !n[e].hidden }) } else Array.isArray(e) || (t = e, e = Object.keys(t)); return e.forEach(function (e) { t[e] = this.getOption(e) }, this), t }, setOption: function (e, t) { if (this["$" + e] === t) return; var n = this.$options[e]; if (!n) return o('misspelled option "' + e + '"'); if (n.forwardTo) return this[n.forwardTo] && this[n.forwardTo].setOption(e, t); n.handlesSet || (this["$" + e] = t), n && n.set && n.set.call(this, t) }, getOption: function (e) { var t = this.$options[e]; return t ? t.forwardTo ? this[t.forwardTo] && this[t.forwardTo].getOption(e) : t && t.get ? t.get.call(this) : this["$" + e] : o('misspelled option "' + e + '"') } }, a = function () { this.$defaultOptions = {} }; (function () { r.implement(this, i), this.defineOptions = function (e, t, n) { return e.$options || (this.$defaultOptions[t] = e.$options = {}), Object.keys(n).forEach(function (t) { var r = n[t]; typeof r == "string" && (r = { forwardTo: r }), r.name || (r.name = t), e.$options[r.name] = r, "initialValue" in r && (e["$" + r.name] = r.initialValue) }), r.implement(e, s), this }, this.resetOptions = function (e) { Object.keys(e.$options).forEach(function (t) { var n = e.$options[t]; "value" in n && e.setOption(t, n.value) }) }, this.setDefaultValue = function (e, t, n) { if (!e) { for (e in this.$defaultOptions) if (this.$defaultOptions[e][t]) break; if (!this.$defaultOptions[e][t]) return !1 } var r = this.$defaultOptions[e] || (this.$defaultOptions[e] = {}); r[t] && (r.forwardTo ? this.setDefaultValue(r.forwardTo, t, n) : r[t].value = n) }, this.setDefaultValues = function (e, t) { Object.keys(t).forEach(function (n) { this.setDefaultValue(e, n, t[n]) }, this) }, this.warn = o, this.reportError = u }).call(a.prototype), t.AppConfig = a }), define("ace/config", ["require", "exports", "module", "ace/lib/lang", "ace/lib/oop", "ace/lib/net", "ace/lib/app_config"], function (e, t, n) { "no use strict"; function l(r) { if (!u || !u.document) return; a.packaged = r || e.packaged || n.packaged || u.define && define.packaged; var i = {}, s = "", o = document.currentScript || document._currentScript, f = o && o.ownerDocument || document, l = f.getElementsByTagName("script"); for (var h = 0; h < l.length; h++) { var p = l[h], d = p.src || p.getAttribute("src"); if (!d) continue; var v = p.attributes; for (var m = 0, g = v.length; m < g; m++) { var y = v[m]; y.name.indexOf("data-ace-") === 0 && (i[c(y.name.replace(/^data-ace-/, ""))] = y.value) } var b = d.match(/^(.*)\/ace(\-\w+)?\.js(\?|$)/); b && (s = b[1]) } s && (i.base = i.base || s, i.packaged = !0), i.basePath = i.base, i.workerPath = i.workerPath || i.base, i.modePath = i.modePath || i.base, i.themePath = i.themePath || i.base, delete i.base; for (var w in i) typeof i[w] != "undefined" && t.set(w, i[w]) } function c(e) { return e.replace(/-(.)/g, function (e, t) { return t.toUpperCase() }) } var r = e("./lib/lang"), i = e("./lib/oop"), s = e("./lib/net"), o = e("./lib/app_config").AppConfig; n.exports = t = new o; var u = function () { return this || typeof window != "undefined" && window }(), a = { packaged: !1, workerPath: null, modePath: null, themePath: null, basePath: "", suffix: ".js", $moduleUrls: {}, loadWorkerFromBlob: !0, sharedPopups: !1 }; t.get = function (e) { if (!a.hasOwnProperty(e)) throw new Error("Unknown config key: " + e); return a[e] }, t.set = function (e, t) { if (a.hasOwnProperty(e)) a[e] = t; else if (this.setDefaultValue("", e, t) == 0) throw new Error("Unknown config key: " + e) }, t.all = function () { return r.copyObject(a) }, t.$modes = {}, t.moduleUrl = function (e, t) { if (a.$moduleUrls[e]) return a.$moduleUrls[e]; var n = e.split("/"); t = t || n[n.length - 2] || ""; var r = t == "snippets" ? "/" : "-", i = n[n.length - 1]; if (t == "worker" && r == "-") { var s = new RegExp("^" + t + "[\\-_]|[\\-_]" + t + "$", "g"); i = i.replace(s, "") } (!i || i == t) && n.length > 1 && (i = n[n.length - 2]); var o = a[t + "Path"]; return o == null ? o = a.basePath : r == "/" && (t = r = ""), o && o.slice(-1) != "/" && (o += "/"), o + t + r + i + this.get("suffix") }, t.setModuleUrl = function (e, t) { return a.$moduleUrls[e] = t }, t.$loading = {}, t.loadModule = function (n, r) { var i, o; Array.isArray(n) && (o = n[0], n = n[1]); try { i = e(n) } catch (u) { } if (i && !t.$loading[n]) return r && r(i); t.$loading[n] || (t.$loading[n] = []), t.$loading[n].push(r); if (t.$loading[n].length > 1) return; var a = function () { e([n], function (e) { t._emit("load.module", { name: n, module: e }); var r = t.$loading[n]; t.$loading[n] = null, r.forEach(function (t) { t && t(e) }) }) }; if (!t.get("packaged")) return a(); s.loadScript(t.moduleUrl(n, o), a), f() }; var f = function () { !a.basePath && !a.workerPath && !a.modePath && !a.themePath && !Object.keys(a.$moduleUrls).length && (console.error("Unable to infer path to ace from script src,", "use ace.config.set('basePath', 'path') to enable dynamic loading of modes and themes", "or with webpack use ace/webpack-resolver"), f = function () { }) }; t.init = l, t.version = "1.4.10" }), define("ace/mouse/mouse_handler", ["require", "exports", "module", "ace/lib/event", "ace/lib/useragent", "ace/mouse/default_handlers", "ace/mouse/default_gutter_handler", "ace/mouse/mouse_event", "ace/mouse/dragdrop_handler", "ace/mouse/touch_handler", "ace/config"], function (e, t, n) { "use strict"; var r = e("../lib/event"), i = e("../lib/useragent"), s = e("./default_handlers").DefaultHandlers, o = e("./default_gutter_handler").GutterHandler, u = e("./mouse_event").MouseEvent, a = e("./dragdrop_handler").DragdropHandler, f = e("./touch_handler").addTouchListeners, l = e("../config"), c = function (e) { var t = this; this.editor = e, new s(this), new o(this), new a(this); var n = function (t) { var n = !document.hasFocus || !document.hasFocus() || !e.isFocused() && document.activeElement == (e.textInput && e.textInput.getElement()); n && window.focus(), e.focus() }, u = e.renderer.getMouseEventTarget(); r.addListener(u, "click", this.onMouseEvent.bind(this, "click"), e), r.addListener(u, "mousemove", this.onMouseMove.bind(this, "mousemove"), e), r.addMultiMouseDownListener([u, e.renderer.scrollBarV && e.renderer.scrollBarV.inner, e.renderer.scrollBarH && e.renderer.scrollBarH.inner, e.textInput && e.textInput.getElement()].filter(Boolean), [400, 300, 250], this, "onMouseEvent", e), r.addMouseWheelListener(e.container, this.onMouseWheel.bind(this, "mousewheel"), e), f(e.container, e); var l = e.renderer.$gutter; r.addListener(l, "mousedown", this.onMouseEvent.bind(this, "guttermousedown"), e), r.addListener(l, "click", this.onMouseEvent.bind(this, "gutterclick"), e), r.addListener(l, "dblclick", this.onMouseEvent.bind(this, "gutterdblclick"), e), r.addListener(l, "mousemove", this.onMouseEvent.bind(this, "guttermousemove"), e), r.addListener(u, "mousedown", n, e), r.addListener(l, "mousedown", n, e), i.isIE && e.renderer.scrollBarV && (r.addListener(e.renderer.scrollBarV.element, "mousedown", n, e), r.addListener(e.renderer.scrollBarH.element, "mousedown", n, e)), e.on("mousemove", function (n) { if (t.state || t.$dragDelay || !t.$dragEnabled) return; var r = e.renderer.screenToTextCoordinates(n.x, n.y), i = e.session.selection.getRange(), s = e.renderer; !i.isEmpty() && i.insideStart(r.row, r.column) ? s.setCursorStyle("default") : s.setCursorStyle("") }, e) }; (function () { this.onMouseEvent = function (e, t) { this.editor._emit(e, new u(t, this.editor)) }, this.onMouseMove = function (e, t) { var n = this.editor._eventRegistry && this.editor._eventRegistry.mousemove; if (!n || !n.length) return; this.editor._emit(e, new u(t, this.editor)) }, this.onMouseWheel = function (e, t) { var n = new u(t, this.editor); n.speed = this.$scrollSpeed * 2, n.wheelX = t.wheelX, n.wheelY = t.wheelY, this.editor._emit(e, n) }, this.setState = function (e) { this.state = e }, this.captureMouse = function (e, t) { this.x = e.x, this.y = e.y, this.isMousePressed = !0; var n = this.editor, s = this.editor.renderer; s.$isMousePressed = !0; var o = this, a = function (e) { if (!e) return; if (i.isWebKit && !e.which && o.releaseMouse) return o.releaseMouse(); o.x = e.clientX, o.y = e.clientY, t && t(e), o.mouseEvent = new u(e, o.editor), o.$mouseMoved = !0 }, f = function (e) { n.off("beforeEndOperation", c), clearInterval(h), l(), o[o.state + "End"] && o[o.state + "End"](e), o.state = "", o.isMousePressed = s.$isMousePressed = !1, s.$keepTextAreaAtCursor && s.$moveTextAreaToCursor(), o.$onCaptureMouseMove = o.releaseMouse = null, e && o.onMouseEvent("mouseup", e), n.endOperation() }, l = function () { o[o.state] && o[o.state](), o.$mouseMoved = !1 }; if (i.isOldIE && e.domEvent.type == "dblclick") return setTimeout(function () { f(e) }); var c = function (e) { if (!o.releaseMouse) return; n.curOp.command.name && n.curOp.selectionChanged && (o[o.state + "End"] && o[o.state + "End"](), o.state = "", o.releaseMouse()) }; n.on("beforeEndOperation", c), n.startOperation({ command: { name: "mouse" } }), o.$onCaptureMouseMove = a, o.releaseMouse = r.capture(this.editor.container, a, f); var h = setInterval(l, 20) }, this.releaseMouse = null, this.cancelContextMenu = function () { var e = function (t) { if (t && t.domEvent && t.domEvent.type != "contextmenu") return; this.editor.off("nativecontextmenu", e), t && t.domEvent && r.stopEvent(t.domEvent) }.bind(this); setTimeout(e, 10), this.editor.on("nativecontextmenu", e) } }).call(c.prototype), l.defineOptions(c.prototype, "mouseHandler", { scrollSpeed: { initialValue: 2 }, dragDelay: { initialValue: i.isMac ? 150 : 0 }, dragEnabled: { initialValue: !0 }, focusTimeout: { initialValue: 0 }, tooltipFollowsMouse: { initialValue: !0 } }), t.MouseHandler = c }), define("ace/mouse/fold_handler", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { "use strict"; function i(e) { e.on("click", function (t) { var n = t.getDocumentPosition(), i = e.session, s = i.getFoldAt(n.row, n.column, 1); s && (t.getAccelKey() ? i.removeFold(s) : i.expandFold(s), t.stop()); var o = t.domEvent && t.domEvent.target; o && r.hasCssClass(o, "ace_inline_button") && r.hasCssClass(o, "ace_toggle_wrap") && (i.setOption("wrap", !i.getUseWrapMode()), e.renderer.scrollCursorIntoView()) }), e.on("gutterclick", function (t) { var n = e.renderer.$gutterLayer.getRegion(t); if (n == "foldWidgets") { var r = t.getDocumentPosition().row, i = e.session; i.foldWidgets && i.foldWidgets[r] && e.session.onFoldWidgetClick(r, t), e.isFocused() || e.focus(), t.stop() } }), e.on("gutterdblclick", function (t) { var n = e.renderer.$gutterLayer.getRegion(t); if (n == "foldWidgets") { var r = t.getDocumentPosition().row, i = e.session, s = i.getParentFoldRangeData(r, !0), o = s.range || s.firstRange; if (o) { r = o.start.row; var u = i.getFoldAt(r, i.getLine(r).length, 1); u ? i.removeFold(u) : (i.addFold("...", o), e.renderer.scrollCursorIntoView({ row: o.start.row, column: 0 })) } t.stop() } }) } var r = e("../lib/dom"); t.FoldHandler = i }), define("ace/keyboard/keybinding", ["require", "exports", "module", "ace/lib/keys", "ace/lib/event"], function (e, t, n) { "use strict"; var r = e("../lib/keys"), i = e("../lib/event"), s = function (e) { this.$editor = e, this.$data = { editor: e }, this.$handlers = [], this.setDefaultHandler(e.commands) }; (function () { this.setDefaultHandler = function (e) { this.removeKeyboardHandler(this.$defaultHandler), this.$defaultHandler = e, this.addKeyboardHandler(e, 0) }, this.setKeyboardHandler = function (e) { var t = this.$handlers; if (t[t.length - 1] == e) return; while (t[t.length - 1] && t[t.length - 1] != this.$defaultHandler) this.removeKeyboardHandler(t[t.length - 1]); this.addKeyboardHandler(e, 1) }, this.addKeyboardHandler = function (e, t) { if (!e) return; typeof e == "function" && !e.handleKeyboard && (e.handleKeyboard = e); var n = this.$handlers.indexOf(e); n != -1 && this.$handlers.splice(n, 1), t == undefined ? this.$handlers.push(e) : this.$handlers.splice(t, 0, e), n == -1 && e.attach && e.attach(this.$editor) }, this.removeKeyboardHandler = function (e) { var t = this.$handlers.indexOf(e); return t == -1 ? !1 : (this.$handlers.splice(t, 1), e.detach && e.detach(this.$editor), !0) }, this.getKeyboardHandler = function () { return this.$handlers[this.$handlers.length - 1] }, this.getStatusText = function () { var e = this.$data, t = e.editor; return this.$handlers.map(function (n) { return n.getStatusText && n.getStatusText(t, e) || "" }).filter(Boolean).join(" ") }, this.$callKeyboardHandlers = function (e, t, n, r) { var s, o = !1, u = this.$editor.commands; for (var a = this.$handlers.length; a--;) { s = this.$handlers[a].handleKeyboard(this.$data, e, t, n, r); if (!s || !s.command) continue; s.command == "null" ? o = !0 : o = u.exec(s.command, this.$editor, s.args, r), o && r && e != -1 && s.passEvent != 1 && s.command.passEvent != 1 && i.stopEvent(r); if (o) break } return !o && e == -1 && (s = { command: "insertstring" }, o = u.exec("insertstring", this.$editor, t)), o && this.$editor._signal && this.$editor._signal("keyboardActivity", s), o }, this.onCommandKey = function (e, t, n) { var i = r.keyCodeToString(n); return this.$callKeyboardHandlers(t, i, n, e) }, this.onTextInput = function (e) { return this.$callKeyboardHandlers(-1, e) } }).call(s.prototype), t.KeyBinding = s }), define("ace/lib/bidiutil", ["require", "exports", "module"], function (e, t, n) { "use strict"; function F(e, t, n, r) { var i = s ? d : p, c = null, h = null, v = null, m = 0, g = null, y = null, b = -1, w = null, E = null, T = []; if (!r) for (w = 0, r = []; w < n; w++)r[w] = R(e[w]); o = s, u = !1, a = !1, f = !1, l = !1; for (E = 0; E < n; E++) { c = m, T[E] = h = q(e, r, T, E), m = i[c][h], g = m & 240, m &= 15, t[E] = v = i[m][5]; if (g > 0) if (g == 16) { for (w = b; w < E; w++)t[w] = 1; b = -1 } else b = -1; y = i[m][6]; if (y) b == -1 && (b = E); else if (b > -1) { for (w = b; w < E; w++)t[w] = v; b = -1 } r[E] == S && (t[E] = 0), o |= v } if (l) for (w = 0; w < n; w++)if (r[w] == x) { t[w] = s; for (var C = w - 1; C >= 0; C--) { if (r[C] != N) break; t[C] = s } } } function I(e, t, n) { if (o < e) return; if (e == 1 && s == m && !f) { n.reverse(); return } var r = n.length, i = 0, u, a, l, c; while (i < r) { if (t[i] >= e) { u = i + 1; while (u < r && t[u] >= e) u++; for (a = i, l = u - 1; a < l; a++, l--)c = n[a], n[a] = n[l], n[l] = c; i = u } i++ } } function q(e, t, n, r) { var i = t[r], o, c, h, p; switch (i) { case g: case y: u = !1; case E: case w: return i; case b: return u ? w : b; case T: return u = !0, a = !0, y; case N: return E; case C: if (r < 1 || r + 1 >= t.length || (o = n[r - 1]) != b && o != w || (c = t[r + 1]) != b && c != w) return E; return u && (c = w), c == o ? c : E; case k: o = r > 0 ? n[r - 1] : S; if (o == b && r + 1 < t.length && t[r + 1] == b) return b; return E; case L: if (r > 0 && n[r - 1] == b) return b; if (u) return E; p = r + 1, h = t.length; while (p < h && t[p] == L) p++; if (p < h && t[p] == b) return b; return E; case A: h = t.length, p = r + 1; while (p < h && t[p] == A) p++; if (p < h) { var d = e[r], v = d >= 1425 && d <= 2303 || d == 64286; o = t[p]; if (v && (o == y || o == T)) return y } if (r < 1 || (o = t[r - 1]) == S) return E; return n[r - 1]; case S: return u = !1, f = !0, s; case x: return l = !0, E; case O: case M: case D: case P: case _: u = !1; case H: return E } } function R(e) { var t = e.charCodeAt(0), n = t >> 8; return n == 0 ? t > 191 ? g : B[t] : n == 5 ? /[\u0591-\u05f4]/.test(e) ? y : g : n == 6 ? /[\u0610-\u061a\u064b-\u065f\u06d6-\u06e4\u06e7-\u06ed]/.test(e) ? A : /[\u0660-\u0669\u066b-\u066c]/.test(e) ? w : t == 1642 ? L : /[\u06f0-\u06f9]/.test(e) ? b : T : n == 32 && t <= 8287 ? j[t & 255] : n == 254 ? t >= 65136 ? T : E : E } function U(e) { return e >= "\u064b" && e <= "\u0655" } var r = ["\u0621", "\u0641"], i = ["\u063a", "\u064a"], s = 0, o = 0, u = !1, a = !1, f = !1, l = !1, c = !1, h = !1, p = [[0, 3, 0, 1, 0, 0, 0], [0, 3, 0, 1, 2, 2, 0], [0, 3, 0, 17, 2, 0, 1], [0, 3, 5, 5, 4, 1, 0], [0, 3, 21, 21, 4, 0, 1], [0, 3, 5, 5, 4, 2, 0]], d = [[2, 0, 1, 1, 0, 1, 0], [2, 0, 1, 1, 0, 2, 0], [2, 0, 2, 1, 3, 2, 0], [2, 0, 2, 33, 3, 1, 1]], v = 0, m = 1, g = 0, y = 1, b = 2, w = 3, E = 4, S = 5, x = 6, T = 7, N = 8, C = 9, k = 10, L = 11, A = 12, O = 13, M = 14, _ = 15, D = 16, P = 17, H = 18, B = [H, H, H, H, H, H, H, H, H, x, S, x, N, S, H, H, H, H, H, H, H, H, H, H, H, H, H, H, S, S, S, x, N, E, E, L, L, L, E, E, E, E, E, k, C, k, C, C, b, b, b, b, b, b, b, b, b, b, C, E, E, E, E, E, E, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, E, E, E, E, E, E, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, E, E, E, E, H, H, H, H, H, H, S, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, C, E, L, L, L, L, E, E, E, E, g, E, E, H, E, E, L, L, b, b, E, g, E, E, E, b, g, E, E, E, E, E], j = [N, N, N, N, N, N, N, N, N, N, N, H, H, H, g, y, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, N, S, O, M, _, D, P, C, L, L, L, L, L, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, C, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, N]; t.L = g, t.R = y, t.EN = b, t.ON_R = 3, t.AN = 4, t.R_H = 5, t.B = 6, t.RLE = 7, t.DOT = "\u00b7", t.doBidiReorder = function (e, n, r) { if (e.length < 2) return {}; var i = e.split(""), o = new Array(i.length), u = new Array(i.length), a = []; s = r ? m : v, F(i, a, i.length, n); for (var f = 0; f < o.length; o[f] = f, f++); I(2, a, o), I(1, a, o); for (var f = 0; f < o.length - 1; f++)n[f] === w ? a[f] = t.AN : a[f] === y && (n[f] > T && n[f] < O || n[f] === E || n[f] === H) ? a[f] = t.ON_R : f > 0 && i[f - 1] === "\u0644" && /\u0622|\u0623|\u0625|\u0627/.test(i[f]) && (a[f - 1] = a[f] = t.R_H, f++); i[i.length - 1] === t.DOT && (a[i.length - 1] = t.B), i[0] === "\u202b" && (a[0] = t.RLE); for (var f = 0; f < o.length; f++)u[f] = a[o[f]]; return { logicalFromVisual: o, bidiLevels: u } }, t.hasBidiCharacters = function (e, t) { var n = !1; for (var r = 0; r < e.length; r++)t[r] = R(e.charAt(r)), !n && (t[r] == y || t[r] == T || t[r] == w) && (n = !0); return n }, t.getVisualFromLogicalIdx = function (e, t) { for (var n = 0; n < t.logicalFromVisual.length; n++)if (t.logicalFromVisual[n] == e) return n; return 0 } }), define("ace/bidihandler", ["require", "exports", "module", "ace/lib/bidiutil", "ace/lib/lang"], function (e, t, n) { "use strict"; var r = e("./lib/bidiutil"), i = e("./lib/lang"), s = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac\u202B]/, o = function (e) { this.session = e, this.bidiMap = {}, this.currentRow = null, this.bidiUtil = r, this.charWidths = [], this.EOL = "\u00ac", this.showInvisibles = !0, this.isRtlDir = !1, this.$isRtl = !1, this.line = "", this.wrapIndent = 0, this.EOF = "\u00b6", this.RLE = "\u202b", this.contentWidth = 0, this.fontMetrics = null, this.rtlLineOffset = 0, this.wrapOffset = 0, this.isMoveLeftOperation = !1, this.seenBidi = s.test(e.getValue()) }; (function () { this.isBidiRow = function (e, t, n) { return this.seenBidi ? (e !== this.currentRow && (this.currentRow = e, this.updateRowLine(t, n), this.updateBidiMap()), this.bidiMap.bidiLevels) : !1 }, this.onChange = function (e) { this.seenBidi ? this.currentRow = null : e.action == "insert" && s.test(e.lines.join("\n")) && (this.seenBidi = !0, this.currentRow = null) }, this.getDocumentRow = function () { var e = 0, t = this.session.$screenRowCache; if (t.length) { var n = this.session.$getRowCacheIndex(t, this.currentRow); n >= 0 && (e = this.session.$docRowCache[n]) } return e }, this.getSplitIndex = function () { var e = 0, t = this.session.$screenRowCache; if (t.length) { var n, r = this.session.$getRowCacheIndex(t, this.currentRow); while (this.currentRow - e > 0) { n = this.session.$getRowCacheIndex(t, this.currentRow - e - 1); if (n !== r) break; r = n, e++ } } else e = this.currentRow; return e }, this.updateRowLine = function (e, t) { e === undefined && (e = this.getDocumentRow()); var n = e === this.session.getLength() - 1, s = n ? this.EOF : this.EOL; this.wrapIndent = 0, this.line = this.session.getLine(e), this.isRtlDir = this.$isRtl || this.line.charAt(0) === this.RLE; if (this.session.$useWrapMode) { var o = this.session.$wrapData[e]; o && (t === undefined && (t = this.getSplitIndex()), t > 0 && o.length ? (this.wrapIndent = o.indent, this.wrapOffset = this.wrapIndent * this.charWidths[r.L], this.line = t < o.length ? this.line.substring(o[t - 1], o[t]) : this.line.substring(o[o.length - 1])) : this.line = this.line.substring(0, o[t])), t == o.length && (this.line += this.showInvisibles ? s : r.DOT) } else this.line += this.showInvisibles ? s : r.DOT; var u = this.session, a = 0, f; this.line = this.line.replace(/\t|[\u1100-\u2029, \u202F-\uFFE6]/g, function (e, t) { return e === " " || u.isFullWidth(e.charCodeAt(0)) ? (f = e === " " ? u.getScreenTabSize(t + a) : 2, a += f - 1, i.stringRepeat(r.DOT, f)) : e }), this.isRtlDir && (this.fontMetrics.$main.textContent = this.line.charAt(this.line.length - 1) == r.DOT ? this.line.substr(0, this.line.length - 1) : this.line, this.rtlLineOffset = this.contentWidth - this.fontMetrics.$main.getBoundingClientRect().width) }, this.updateBidiMap = function () { var e = []; r.hasBidiCharacters(this.line, e) || this.isRtlDir ? this.bidiMap = r.doBidiReorder(this.line, e, this.isRtlDir) : this.bidiMap = {} }, this.markAsDirty = function () { this.currentRow = null }, this.updateCharacterWidths = function (e) { if (this.characterWidth === e.$characterSize.width) return; this.fontMetrics = e; var t = this.characterWidth = e.$characterSize.width, n = e.$measureCharWidth("\u05d4"); this.charWidths[r.L] = this.charWidths[r.EN] = this.charWidths[r.ON_R] = t, this.charWidths[r.R] = this.charWidths[r.AN] = n, this.charWidths[r.R_H] = n * .45, this.charWidths[r.B] = this.charWidths[r.RLE] = 0, this.currentRow = null }, this.setShowInvisibles = function (e) { this.showInvisibles = e, this.currentRow = null }, this.setEolChar = function (e) { this.EOL = e }, this.setContentWidth = function (e) { this.contentWidth = e }, this.isRtlLine = function (e) { return this.$isRtl ? !0 : e != undefined ? this.session.getLine(e).charAt(0) == this.RLE : this.isRtlDir }, this.setRtlDirection = function (e, t) { var n = e.getCursorPosition(); for (var r = e.selection.getSelectionAnchor().row; r <= n.row; r++)!t && e.session.getLine(r).charAt(0) === e.session.$bidiHandler.RLE ? e.session.doc.removeInLine(r, 0, 1) : t && e.session.getLine(r).charAt(0) !== e.session.$bidiHandler.RLE && e.session.doc.insert({ column: 0, row: r }, e.session.$bidiHandler.RLE) }, this.getPosLeft = function (e) { e -= this.wrapIndent; var t = this.line.charAt(0) === this.RLE ? 1 : 0, n = e > t ? this.session.getOverwrite() ? e : e - 1 : t, i = r.getVisualFromLogicalIdx(n, this.bidiMap), s = this.bidiMap.bidiLevels, o = 0; !this.session.getOverwrite() && e <= t && s[i] % 2 !== 0 && i++; for (var u = 0; u < i; u++)o += this.charWidths[s[u]]; return !this.session.getOverwrite() && e > t && s[i] % 2 === 0 && (o += this.charWidths[s[i]]), this.wrapIndent && (o += this.isRtlDir ? -1 * this.wrapOffset : this.wrapOffset), this.isRtlDir && (o += this.rtlLineOffset), o }, this.getSelections = function (e, t) { var n = this.bidiMap, r = n.bidiLevels, i, s = [], o = 0, u = Math.min(e, t) - this.wrapIndent, a = Math.max(e, t) - this.wrapIndent, f = !1, l = !1, c = 0; this.wrapIndent && (o += this.isRtlDir ? -1 * this.wrapOffset : this.wrapOffset); for (var h, p = 0; p < r.length; p++)h = n.logicalFromVisual[p], i = r[p], f = h >= u && h < a, f && !l ? c = o : !f && l && s.push({ left: c, width: o - c }), o += this.charWidths[i], l = f; f && p === r.length && s.push({ left: c, width: o - c }); if (this.isRtlDir) for (var d = 0; d < s.length; d++)s[d].left += this.rtlLineOffset; return s }, this.offsetToCol = function (e) { this.isRtlDir && (e -= this.rtlLineOffset); var t = 0, e = Math.max(e, 0), n = 0, r = 0, i = this.bidiMap.bidiLevels, s = this.charWidths[i[r]]; this.wrapIndent && (e -= this.isRtlDir ? -1 * this.wrapOffset : this.wrapOffset); while (e > n + s / 2) { n += s; if (r === i.length - 1) { s = 0; break } s = this.charWidths[i[++r]] } return r > 0 && i[r - 1] % 2 !== 0 && i[r] % 2 === 0 ? (e < n && r--, t = this.bidiMap.logicalFromVisual[r]) : r > 0 && i[r - 1] % 2 === 0 && i[r] % 2 !== 0 ? t = 1 + (e > n ? this.bidiMap.logicalFromVisual[r] : this.bidiMap.logicalFromVisual[r - 1]) : this.isRtlDir && r === i.length - 1 && s === 0 && i[r - 1] % 2 === 0 || !this.isRtlDir && r === 0 && i[r] % 2 !== 0 ? t = 1 + this.bidiMap.logicalFromVisual[r] : (r > 0 && i[r - 1] % 2 !== 0 && s !== 0 && r--, t = this.bidiMap.logicalFromVisual[r]), t === 0 && this.isRtlDir && t++, t + this.wrapIndent } }).call(o.prototype), t.BidiHandler = o }), define("ace/selection", ["require", "exports", "module", "ace/lib/oop", "ace/lib/lang", "ace/lib/event_emitter", "ace/range"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./lib/lang"), s = e("./lib/event_emitter").EventEmitter, o = e("./range").Range, u = function (e) { this.session = e, this.doc = e.getDocument(), this.clearSelection(), this.cursor = this.lead = this.doc.createAnchor(0, 0), this.anchor = this.doc.createAnchor(0, 0), this.$silent = !1; var t = this; this.cursor.on("change", function (e) { t.$cursorChanged = !0, t.$silent || t._emit("changeCursor"), !t.$isEmpty && !t.$silent && t._emit("changeSelection"), !t.$keepDesiredColumnOnChange && e.old.column != e.value.column && (t.$desiredColumn = null) }), this.anchor.on("change", function () { t.$anchorChanged = !0, !t.$isEmpty && !t.$silent && t._emit("changeSelection") }) }; (function () { r.implement(this, s), this.isEmpty = function () { return this.$isEmpty || this.anchor.row == this.lead.row && this.anchor.column == this.lead.column }, this.isMultiLine = function () { return !this.$isEmpty && this.anchor.row != this.cursor.row }, this.getCursor = function () { return this.lead.getPosition() }, this.setSelectionAnchor = function (e, t) { this.$isEmpty = !1, this.anchor.setPosition(e, t) }, this.getAnchor = this.getSelectionAnchor = function () { return this.$isEmpty ? this.getSelectionLead() : this.anchor.getPosition() }, this.getSelectionLead = function () { return this.lead.getPosition() }, this.isBackwards = function () { var e = this.anchor, t = this.lead; return e.row > t.row || e.row == t.row && e.column > t.column }, this.getRange = function () { var e = this.anchor, t = this.lead; return this.$isEmpty ? o.fromPoints(t, t) : this.isBackwards() ? o.fromPoints(t, e) : o.fromPoints(e, t) }, this.clearSelection = function () { this.$isEmpty || (this.$isEmpty = !0, this._emit("changeSelection")) }, this.selectAll = function () { this.$setSelection(0, 0, Number.MAX_VALUE, Number.MAX_VALUE) }, this.setRange = this.setSelectionRange = function (e, t) { var n = t ? e.end : e.start, r = t ? e.start : e.end; this.$setSelection(n.row, n.column, r.row, r.column) }, this.$setSelection = function (e, t, n, r) { if (this.$silent) return; var i = this.$isEmpty, s = this.inMultiSelectMode; this.$silent = !0, this.$cursorChanged = this.$anchorChanged = !1, this.anchor.setPosition(e, t), this.cursor.setPosition(n, r), this.$isEmpty = !o.comparePoints(this.anchor, this.cursor), this.$silent = !1, this.$cursorChanged && this._emit("changeCursor"), (this.$cursorChanged || this.$anchorChanged || i != this.$isEmpty || s) && this._emit("changeSelection") }, this.$moveSelection = function (e) { var t = this.lead; this.$isEmpty && this.setSelectionAnchor(t.row, t.column), e.call(this) }, this.selectTo = function (e, t) { this.$moveSelection(function () { this.moveCursorTo(e, t) }) }, this.selectToPosition = function (e) { this.$moveSelection(function () { this.moveCursorToPosition(e) }) }, this.moveTo = function (e, t) { this.clearSelection(), this.moveCursorTo(e, t) }, this.moveToPosition = function (e) { this.clearSelection(), this.moveCursorToPosition(e) }, this.selectUp = function () { this.$moveSelection(this.moveCursorUp) }, this.selectDown = function () { this.$moveSelection(this.moveCursorDown) }, this.selectRight = function () { this.$moveSelection(this.moveCursorRight) }, this.selectLeft = function () { this.$moveSelection(this.moveCursorLeft) }, this.selectLineStart = function () { this.$moveSelection(this.moveCursorLineStart) }, this.selectLineEnd = function () { this.$moveSelection(this.moveCursorLineEnd) }, this.selectFileEnd = function () { this.$moveSelection(this.moveCursorFileEnd) }, this.selectFileStart = function () { this.$moveSelection(this.moveCursorFileStart) }, this.selectWordRight = function () { this.$moveSelection(this.moveCursorWordRight) }, this.selectWordLeft = function () { this.$moveSelection(this.moveCursorWordLeft) }, this.getWordRange = function (e, t) { if (typeof t == "undefined") { var n = e || this.lead; e = n.row, t = n.column } return this.session.getWordRange(e, t) }, this.selectWord = function () { this.setSelectionRange(this.getWordRange()) }, this.selectAWord = function () { var e = this.getCursor(), t = this.session.getAWordRange(e.row, e.column); this.setSelectionRange(t) }, this.getLineRange = function (e, t) { var n = typeof e == "number" ? e : this.lead.row, r, i = this.session.getFoldLine(n); return i ? (n = i.start.row, r = i.end.row) : r = n, t === !0 ? new o(n, 0, r, this.session.getLine(r).length) : new o(n, 0, r + 1, 0) }, this.selectLine = function () { this.setSelectionRange(this.getLineRange()) }, this.moveCursorUp = function () { this.moveCursorBy(-1, 0) }, this.moveCursorDown = function () { this.moveCursorBy(1, 0) }, this.wouldMoveIntoSoftTab = function (e, t, n) { var r = e.column, i = e.column + t; return n < 0 && (r = e.column - t, i = e.column), this.session.isTabStop(e) && this.doc.getLine(e.row).slice(r, i).split(" ").length - 1 == t }, this.moveCursorLeft = function () { var e = this.lead.getPosition(), t; if (t = this.session.getFoldAt(e.row, e.column, -1)) this.moveCursorTo(t.start.row, t.start.column); else if (e.column === 0) e.row > 0 && this.moveCursorTo(e.row - 1, this.doc.getLine(e.row - 1).length); else { var n = this.session.getTabSize(); this.wouldMoveIntoSoftTab(e, n, -1) && !this.session.getNavigateWithinSoftTabs() ? this.moveCursorBy(0, -n) : this.moveCursorBy(0, -1) } }, this.moveCursorRight = function () { var e = this.lead.getPosition(), t; if (t = this.session.getFoldAt(e.row, e.column, 1)) this.moveCursorTo(t.end.row, t.end.column); else if (this.lead.column == this.doc.getLine(this.lead.row).length) this.lead.row < this.doc.getLength() - 1 && this.moveCursorTo(this.lead.row + 1, 0); else { var n = this.session.getTabSize(), e = this.lead; this.wouldMoveIntoSoftTab(e, n, 1) && !this.session.getNavigateWithinSoftTabs() ? this.moveCursorBy(0, n) : this.moveCursorBy(0, 1) } }, this.moveCursorLineStart = function () { var e = this.lead.row, t = this.lead.column, n = this.session.documentToScreenRow(e, t), r = this.session.screenToDocumentPosition(n, 0), i = this.session.getDisplayLine(e, null, r.row, r.column), s = i.match(/^\s*/); s[0].length != t && !this.session.$useEmacsStyleLineStart && (r.column += s[0].length), this.moveCursorToPosition(r) }, this.moveCursorLineEnd = function () { var e = this.lead, t = this.session.getDocumentLastRowColumnPosition(e.row, e.column); if (this.lead.column == t.column) { var n = this.session.getLine(t.row); if (t.column == n.length) { var r = n.search(/\s+$/); r > 0 && (t.column = r) } } this.moveCursorTo(t.row, t.column) }, this.moveCursorFileEnd = function () { var e = this.doc.getLength() - 1, t = this.doc.getLine(e).length; this.moveCursorTo(e, t) }, this.moveCursorFileStart = function () { this.moveCursorTo(0, 0) }, this.moveCursorLongWordRight = function () { var e = this.lead.row, t = this.lead.column, n = this.doc.getLine(e), r = n.substring(t); this.session.nonTokenRe.lastIndex = 0, this.session.tokenRe.lastIndex = 0; var i = this.session.getFoldAt(e, t, 1); if (i) { this.moveCursorTo(i.end.row, i.end.column); return } this.session.nonTokenRe.exec(r) && (t += this.session.nonTokenRe.lastIndex, this.session.nonTokenRe.lastIndex = 0, r = n.substring(t)); if (t >= n.length) { this.moveCursorTo(e, n.length), this.moveCursorRight(), e < this.doc.getLength() - 1 && this.moveCursorWordRight(); return } this.session.tokenRe.exec(r) && (t += this.session.tokenRe.lastIndex, this.session.tokenRe.lastIndex = 0), this.moveCursorTo(e, t) }, this.moveCursorLongWordLeft = function () { var e = this.lead.row, t = this.lead.column, n; if (n = this.session.getFoldAt(e, t, -1)) { this.moveCursorTo(n.start.row, n.start.column); return } var r = this.session.getFoldStringAt(e, t, -1); r == null && (r = this.doc.getLine(e).substring(0, t)); var s = i.stringReverse(r); this.session.nonTokenRe.lastIndex = 0, this.session.tokenRe.lastIndex = 0, this.session.nonTokenRe.exec(s) && (t -= this.session.nonTokenRe.lastIndex, s = s.slice(this.session.nonTokenRe.lastIndex), this.session.nonTokenRe.lastIndex = 0); if (t <= 0) { this.moveCursorTo(e, 0), this.moveCursorLeft(), e > 0 && this.moveCursorWordLeft(); return } this.session.tokenRe.exec(s) && (t -= this.session.tokenRe.lastIndex, this.session.tokenRe.lastIndex = 0), this.moveCursorTo(e, t) }, this.$shortWordEndIndex = function (e) { var t = 0, n, r = /\s/, i = this.session.tokenRe; i.lastIndex = 0; if (this.session.tokenRe.exec(e)) t = this.session.tokenRe.lastIndex; else { while ((n = e[t]) && r.test(n)) t++; if (t < 1) { i.lastIndex = 0; while ((n = e[t]) && !i.test(n)) { i.lastIndex = 0, t++; if (r.test(n)) { if (t > 2) { t--; break } while ((n = e[t]) && r.test(n)) t++; if (t > 2) break } } } } return i.lastIndex = 0, t }, this.moveCursorShortWordRight = function () { var e = this.lead.row, t = this.lead.column, n = this.doc.getLine(e), r = n.substring(t), i = this.session.getFoldAt(e, t, 1); if (i) return this.moveCursorTo(i.end.row, i.end.column); if (t == n.length) { var s = this.doc.getLength(); do e++, r = this.doc.getLine(e); while (e < s && /^\s*$/.test(r)); /^\s+/.test(r) || (r = ""), t = 0 } var o = this.$shortWordEndIndex(r); this.moveCursorTo(e, t + o) }, this.moveCursorShortWordLeft = function () { var e = this.lead.row, t = this.lead.column, n; if (n = this.session.getFoldAt(e, t, -1)) return this.moveCursorTo(n.start.row, n.start.column); var r = this.session.getLine(e).substring(0, t); if (t === 0) { do e--, r = this.doc.getLine(e); while (e > 0 && /^\s*$/.test(r)); t = r.length, /\s+$/.test(r) || (r = "") } var s = i.stringReverse(r), o = this.$shortWordEndIndex(s); return this.moveCursorTo(e, t - o) }, this.moveCursorWordRight = function () { this.session.$selectLongWords ? this.moveCursorLongWordRight() : this.moveCursorShortWordRight() }, this.moveCursorWordLeft = function () { this.session.$selectLongWords ? this.moveCursorLongWordLeft() : this.moveCursorShortWordLeft() }, this.moveCursorBy = function (e, t) { var n = this.session.documentToScreenPosition(this.lead.row, this.lead.column), r; t === 0 && (e !== 0 && (this.session.$bidiHandler.isBidiRow(n.row, this.lead.row) ? (r = this.session.$bidiHandler.getPosLeft(n.column), n.column = Math.round(r / this.session.$bidiHandler.charWidths[0])) : r = n.column * this.session.$bidiHandler.charWidths[0]), this.$desiredColumn ? n.column = this.$desiredColumn : this.$desiredColumn = n.column); if (e != 0 && this.session.lineWidgets && this.session.lineWidgets[this.lead.row]) { var i = this.session.lineWidgets[this.lead.row]; e < 0 ? e -= i.rowsAbove || 0 : e > 0 && (e += i.rowCount - (i.rowsAbove || 0)) } var s = this.session.screenToDocumentPosition(n.row + e, n.column, r); e !== 0 && t === 0 && s.row === this.lead.row && s.column === this.lead.column, this.moveCursorTo(s.row, s.column + t, t === 0) }, this.moveCursorToPosition = function (e) { this.moveCursorTo(e.row, e.column) }, this.moveCursorTo = function (e, t, n) { var r = this.session.getFoldAt(e, t, 1); r && (e = r.start.row, t = r.start.column), this.$keepDesiredColumnOnChange = !0; var i = this.session.getLine(e); /[\uDC00-\uDFFF]/.test(i.charAt(t)) && i.charAt(t - 1) && (this.lead.row == e && this.lead.column == t + 1 ? t -= 1 : t += 1), this.lead.setPosition(e, t), this.$keepDesiredColumnOnChange = !1, n || (this.$desiredColumn = null) }, this.moveCursorToScreen = function (e, t, n) { var r = this.session.screenToDocumentPosition(e, t); this.moveCursorTo(r.row, r.column, n) }, this.detach = function () { this.lead.detach(), this.anchor.detach(), this.session = this.doc = null }, this.fromOrientedRange = function (e) { this.setSelectionRange(e, e.cursor == e.start), this.$desiredColumn = e.desiredColumn || this.$desiredColumn }, this.toOrientedRange = function (e) { var t = this.getRange(); return e ? (e.start.column = t.start.column, e.start.row = t.start.row, e.end.column = t.end.column, e.end.row = t.end.row) : e = t, e.cursor = this.isBackwards() ? e.start : e.end, e.desiredColumn = this.$desiredColumn, e }, this.getRangeOfMovements = function (e) { var t = this.getCursor(); try { e(this); var n = this.getCursor(); return o.fromPoints(t, n) } catch (r) { return o.fromPoints(t, t) } finally { this.moveCursorToPosition(t) } }, this.toJSON = function () { if (this.rangeCount) var e = this.ranges.map(function (e) { var t = e.clone(); return t.isBackwards = e.cursor == e.start, t }); else { var e = this.getRange(); e.isBackwards = this.isBackwards() } return e }, this.fromJSON = function (e) { if (e.start == undefined) { if (this.rangeList && e.length > 1) { this.toSingleRange(e[0]); for (var t = e.length; t--;) { var n = o.fromPoints(e[t].start, e[t].end); e[t].isBackwards && (n.cursor = n.start), this.addRange(n, !0) } return } e = e[0] } this.rangeList && this.toSingleRange(e), this.setSelectionRange(e, e.isBackwards) }, this.isEqual = function (e) { if ((e.length || this.rangeCount) && e.length != this.rangeCount) return !1; if (!e.length || !this.ranges) return this.getRange().isEqual(e); for (var t = this.ranges.length; t--;)if (!this.ranges[t].isEqual(e[t])) return !1; return !0 } }).call(u.prototype), t.Selection = u }), define("ace/tokenizer", ["require", "exports", "module", "ace/config"], function (e, t, n) { "use strict"; var r = e("./config"), i = 2e3, s = function (e) { this.states = e, this.regExps = {}, this.matchMappings = {}; for (var t in this.states) { var n = this.states[t], r = [], i = 0, s = this.matchMappings[t] = { defaultToken: "text" }, o = "g", u = []; for (var a = 0; a < n.length; a++) { var f = n[a]; f.defaultToken && (s.defaultToken = f.defaultToken), f.caseInsensitive && (o = "gi"); if (f.regex == null) continue; f.regex instanceof RegExp && (f.regex = f.regex.toString().slice(1, -1)); var l = f.regex, c = (new RegExp("(?:(" + l + ")|(.))")).exec("a").length - 2; Array.isArray(f.token) ? f.token.length == 1 || c == 1 ? f.token = f.token[0] : c - 1 != f.token.length ? (this.reportError("number of classes and regexp groups doesn't match", { rule: f, groupCount: c - 1 }), f.token = f.token[0]) : (f.tokenArray = f.token, f.token = null, f.onMatch = this.$arrayTokens) : typeof f.token == "function" && !f.onMatch && (c > 1 ? f.onMatch = this.$applyToken : f.onMatch = f.token), c > 1 && (/\\\d/.test(f.regex) ? l = f.regex.replace(/\\([0-9]+)/g, function (e, t) { return "\\" + (parseInt(t, 10) + i + 1) }) : (c = 1, l = this.removeCapturingGroups(f.regex)), !f.splitRegex && typeof f.token != "string" && u.push(f)), s[i] = a, i += c, r.push(l), f.onMatch || (f.onMatch = null) } r.length || (s[0] = 0, r.push("$")), u.forEach(function (e) { e.splitRegex = this.createSplitterRegexp(e.regex, o) }, this), this.regExps[t] = new RegExp("(" + r.join(")|(") + ")|($)", o) } }; (function () { this.$setMaxTokenCount = function (e) { i = e | 0 }, this.$applyToken = function (e) { var t = this.splitRegex.exec(e).slice(1), n = this.token.apply(this, t); if (typeof n == "string") return [{ type: n, value: e }]; var r = []; for (var i = 0, s = n.length; i < s; i++)t[i] && (r[r.length] = { type: n[i], value: t[i] }); return r }, this.$arrayTokens = function (e) { if (!e) return []; var t = this.splitRegex.exec(e); if (!t) return "text"; var n = [], r = this.tokenArray; for (var i = 0, s = r.length; i < s; i++)t[i + 1] && (n[n.length] = { type: r[i], value: t[i + 1] }); return n }, this.removeCapturingGroups = function (e) { var t = e.replace(/\\.|\[(?:\\.|[^\\\]])*|\(\?[:=!]|(\()/g, function (e, t) { return t ? "(?:" : e }); return t }, this.createSplitterRegexp = function (e, t) { if (e.indexOf("(?=") != -1) { var n = 0, r = !1, i = {}; e.replace(/(\\.)|(\((?:\?[=!])?)|(\))|([\[\]])/g, function (e, t, s, o, u, a) { return r ? r = u != "]" : u ? r = !0 : o ? (n == i.stack && (i.end = a + 1, i.stack = -1), n--) : s && (n++, s.length != 1 && (i.stack = n, i.start = a)), e }), i.end != null && /^\)*$/.test(e.substr(i.end)) && (e = e.substring(0, i.start) + e.substr(i.end)) } return e.charAt(0) != "^" && (e = "^" + e), e.charAt(e.length - 1) != "$" && (e += "$"), new RegExp(e, (t || "").replace("g", "")) }, this.getLineTokens = function (e, t) { if (t && typeof t != "string") { var n = t.slice(0); t = n[0], t === "#tmp" && (n.shift(), t = n.shift()) } else var n = []; var r = t || "start", s = this.states[r]; s || (r = "start", s = this.states[r]); var o = this.matchMappings[r], u = this.regExps[r]; u.lastIndex = 0; var a, f = [], l = 0, c = 0, h = { type: null, value: "" }; while (a = u.exec(e)) { var p = o.defaultToken, d = null, v = a[0], m = u.lastIndex; if (m - v.length > l) { var g = e.substring(l, m - v.length); h.type == p ? h.value += g : (h.type && f.push(h), h = { type: p, value: g }) } for (var y = 0; y < a.length - 2; y++) { if (a[y + 1] === undefined) continue; d = s[o[y]], d.onMatch ? p = d.onMatch(v, r, n, e) : p = d.token, d.next && (typeof d.next == "string" ? r = d.next : r = d.next(r, n), s = this.states[r], s || (this.reportError("state doesn't exist", r), r = "start", s = this.states[r]), o = this.matchMappings[r], l = m, u = this.regExps[r], u.lastIndex = m), d.consumeLineEnd && (l = m); break } if (v) if (typeof p == "string") !!d && d.merge === !1 || h.type !== p ? (h.type && f.push(h), h = { type: p, value: v }) : h.value += v; else if (p) { h.type && f.push(h), h = { type: null, value: "" }; for (var y = 0; y < p.length; y++)f.push(p[y]) } if (l == e.length) break; l = m; if (c++ > i) { c > 2 * e.length && this.reportError("infinite loop with in ace tokenizer", { startState: t, line: e }); while (l < e.length) h.type && f.push(h), h = { value: e.substring(l, l += 500), type: "overflow" }; r = "start", n = []; break } } return h.type && f.push(h), n.length > 1 && n[0] !== r && n.unshift("#tmp", r), { tokens: f, state: n.length ? n : r } }, this.reportError = r.reportError }).call(s.prototype), t.Tokenizer = s }), define("ace/mode/text_highlight_rules", ["require", "exports", "module", "ace/lib/lang"], function (e, t, n) { "use strict"; var r = e("../lib/lang"), i = function () { this.$rules = { start: [{ token: "empty_line", regex: "^$" }, { defaultToken: "text" }] } }; (function () { this.addRules = function (e, t) { if (!t) { for (var n in e) this.$rules[n] = e[n]; return } for (var n in e) { var r = e[n]; for (var i = 0; i < r.length; i++) { var s = r[i]; if (s.next || s.onMatch) typeof s.next == "string" && s.next.indexOf(t) !== 0 && (s.next = t + s.next), s.nextState && s.nextState.indexOf(t) !== 0 && (s.nextState = t + s.nextState) } this.$rules[t + n] = r } }, this.getRules = function () { return this.$rules }, this.embedRules = function (e, t, n, i, s) { var o = typeof e == "function" ? (new e).getRules() : e; if (i) for (var u = 0; u < i.length; u++)i[u] = t + i[u]; else { i = []; for (var a in o) i.push(t + a) } this.addRules(o, t); if (n) { var f = Array.prototype[s ? "push" : "unshift"]; for (var u = 0; u < i.length; u++)f.apply(this.$rules[i[u]], r.deepCopy(n)) } this.$embeds || (this.$embeds = []), this.$embeds.push(t) }, this.getEmbeds = function () { return this.$embeds }; var e = function (e, t) { return (e != "start" || t.length) && t.unshift(this.nextState, e), this.nextState }, t = function (e, t) { return t.shift(), t.shift() || "start" }; this.normalizeRules = function () { function i(s) { var o = r[s]; o.processed = !0; for (var u = 0; u < o.length; u++) { var a = o[u], f = null; Array.isArray(a) && (f = a, a = {}), !a.regex && a.start && (a.regex = a.start, a.next || (a.next = []), a.next.push({ defaultToken: a.token }, { token: a.token + ".end", regex: a.end || a.start, next: "pop" }), a.token = a.token + ".start", a.push = !0); var l = a.next || a.push; if (l && Array.isArray(l)) { var c = a.stateName; c || (c = a.token, typeof c != "string" && (c = c[0] || ""), r[c] && (c += n++)), r[c] = l, a.next = c, i(c) } else l == "pop" && (a.next = t); a.push && (a.nextState = a.next || a.push, a.next = e, delete a.push); if (a.rules) for (var h in a.rules) r[h] ? r[h].push && r[h].push.apply(r[h], a.rules[h]) : r[h] = a.rules[h]; var p = typeof a == "string" ? a : a.include; p && (Array.isArray(p) ? f = p.map(function (e) { return r[e] }) : f = r[p]); if (f) { var d = [u, 1].concat(f); a.noEscape && (d = d.filter(function (e) { return !e.next })), o.splice.apply(o, d), u-- } a.keywordMap && (a.token = this.createKeywordMapper(a.keywordMap, a.defaultToken || "text", a.caseInsensitive), delete a.defaultToken) } } var n = 0, r = this.$rules; Object.keys(r).forEach(i, this) }, this.createKeywordMapper = function (e, t, n, r) { var i = Object.create(null); return Object.keys(e).forEach(function (t) { var s = e[t]; n && (s = s.toLowerCase()); var o = s.split(r || "|"); for (var u = o.length; u--;)i[o[u]] = t }), Object.getPrototypeOf(i) && (i.__proto__ = null), this.$keywordList = Object.keys(i), e = null, n ? function (e) { return i[e.toLowerCase()] || t } : function (e) { return i[e] || t } }, this.getKeywords = function () { return this.$keywords } }).call(i.prototype), t.TextHighlightRules = i }), define("ace/mode/behaviour", ["require", "exports", "module"], function (e, t, n) { "use strict"; var r = function () { this.$behaviours = {} }; (function () { this.add = function (e, t, n) { switch (undefined) { case this.$behaviours: this.$behaviours = {}; case this.$behaviours[e]: this.$behaviours[e] = {} }this.$behaviours[e][t] = n }, this.addBehaviours = function (e) { for (var t in e) for (var n in e[t]) this.add(t, n, e[t][n]) }, this.remove = function (e) { this.$behaviours && this.$behaviours[e] && delete this.$behaviours[e] }, this.inherit = function (e, t) { if (typeof e == "function") var n = (new e).getBehaviours(t); else var n = e.getBehaviours(t); this.addBehaviours(n) }, this.getBehaviours = function (e) { if (!e) return this.$behaviours; var t = {}; for (var n = 0; n < e.length; n++)this.$behaviours[e[n]] && (t[e[n]] = this.$behaviours[e[n]]); return t } }).call(r.prototype), t.Behaviour = r }), define("ace/token_iterator", ["require", "exports", "module", "ace/range"], function (e, t, n) { "use strict"; var r = e("./range").Range, i = function (e, t, n) { this.$session = e, this.$row = t, this.$rowTokens = e.getTokens(t); var r = e.getTokenAt(t, n); this.$tokenIndex = r ? r.index : -1 }; (function () { this.stepBackward = function () { this.$tokenIndex -= 1; while (this.$tokenIndex < 0) { this.$row -= 1; if (this.$row < 0) return this.$row = 0, null; this.$rowTokens = this.$session.getTokens(this.$row), this.$tokenIndex = this.$rowTokens.length - 1 } return this.$rowTokens[this.$tokenIndex] }, this.stepForward = function () { this.$tokenIndex += 1; var e; while (this.$tokenIndex >= this.$rowTokens.length) { this.$row += 1, e || (e = this.$session.getLength()); if (this.$row >= e) return this.$row = e - 1, null; this.$rowTokens = this.$session.getTokens(this.$row), this.$tokenIndex = 0 } return this.$rowTokens[this.$tokenIndex] }, this.getCurrentToken = function () { return this.$rowTokens[this.$tokenIndex] }, this.getCurrentTokenRow = function () { return this.$row }, this.getCurrentTokenColumn = function () { var e = this.$rowTokens, t = this.$tokenIndex, n = e[t].start; if (n !== undefined) return n; n = 0; while (t > 0) t -= 1, n += e[t].value.length; return n }, this.getCurrentTokenPosition = function () { return { row: this.$row, column: this.getCurrentTokenColumn() } }, this.getCurrentTokenRange = function () { var e = this.$rowTokens[this.$tokenIndex], t = this.getCurrentTokenColumn(); return new r(this.$row, t, this.$row, t + e.value.length) } }).call(i.prototype), t.TokenIterator = i }), define("ace/mode/behaviour/cstyle", ["require", "exports", "module", "ace/lib/oop", "ace/mode/behaviour", "ace/token_iterator", "ace/lib/lang"], function (e, t, n) { "use strict"; var r = e("../../lib/oop"), i = e("../behaviour").Behaviour, s = e("../../token_iterator").TokenIterator, o = e("../../lib/lang"), u = ["text", "paren.rparen", "rparen", "paren", "punctuation.operator"], a = ["text", "paren.rparen", "rparen", "paren", "punctuation.operator", "comment"], f, l = {}, c = { '"': '"', "'": "'" }, h = function (e) { var t = -1; e.multiSelect && (t = e.selection.index, l.rangeCount != e.multiSelect.rangeCount && (l = { rangeCount: e.multiSelect.rangeCount })); if (l[t]) return f = l[t]; f = l[t] = { autoInsertedBrackets: 0, autoInsertedRow: -1, autoInsertedLineEnd: "", maybeInsertedBrackets: 0, maybeInsertedRow: -1, maybeInsertedLineStart: "", maybeInsertedLineEnd: "" } }, p = function (e, t, n, r) { var i = e.end.row - e.start.row; return { text: n + t + r, selection: [0, e.start.column + 1, i, e.end.column + (i ? 0 : 1)] } }, d = function (e) { this.add("braces", "insertion", function (t, n, r, i, s) { var u = r.getCursorPosition(), a = i.doc.getLine(u.row); if (s == "{") { h(r); var l = r.getSelectionRange(), c = i.doc.getTextRange(l); if (c !== "" && c !== "{" && r.getWrapBehavioursEnabled()) return p(l, c, "{", "}"); if (d.isSaneInsertion(r, i)) return /[\]\}\)]/.test(a[u.column]) || r.inMultiSelectMode || e && e.braces ? (d.recordAutoInsert(r, i, "}"), { text: "{}", selection: [1, 1] }) : (d.recordMaybeInsert(r, i, "{"), { text: "{", selection: [1, 1] }) } else if (s == "}") { h(r); var v = a.substring(u.column, u.column + 1); if (v == "}") { var m = i.$findOpeningBracket("}", { column: u.column + 1, row: u.row }); if (m !== null && d.isAutoInsertedClosing(u, a, s)) return d.popAutoInsertedClosing(), { text: "", selection: [1, 1] } } } else { if (s == "\n" || s == "\r\n") { h(r); var g = ""; d.isMaybeInsertedClosing(u, a) && (g = o.stringRepeat("}", f.maybeInsertedBrackets), d.clearMaybeInsertedClosing()); var v = a.substring(u.column, u.column + 1); if (v === "}") { var y = i.findMatchingBracket({ row: u.row, column: u.column + 1 }, "}"); if (!y) return null; var b = this.$getIndent(i.getLine(y.row)) } else { if (!g) { d.clearMaybeInsertedClosing(); return } var b = this.$getIndent(a) } var w = b + i.getTabString(); return { text: "\n" + w + "\n" + b + g, selection: [1, w.length, 1, w.length] } } d.clearMaybeInsertedClosing() } }), this.add("braces", "deletion", function (e, t, n, r, i) { var s = r.doc.getTextRange(i); if (!i.isMultiLine() && s == "{") { h(n); var o = r.doc.getLine(i.start.row), u = o.substring(i.end.column, i.end.column + 1); if (u == "}") return i.end.column++, i; f.maybeInsertedBrackets-- } }), this.add("parens", "insertion", function (e, t, n, r, i) { if (i == "(") { h(n); var s = n.getSelectionRange(), o = r.doc.getTextRange(s); if (o !== "" && n.getWrapBehavioursEnabled()) return p(s, o, "(", ")"); if (d.isSaneInsertion(n, r)) return d.recordAutoInsert(n, r, ")"), { text: "()", selection: [1, 1] } } else if (i == ")") { h(n); var u = n.getCursorPosition(), a = r.doc.getLine(u.row), f = a.substring(u.column, u.column + 1); if (f == ")") { var l = r.$findOpeningBracket(")", { column: u.column + 1, row: u.row }); if (l !== null && d.isAutoInsertedClosing(u, a, i)) return d.popAutoInsertedClosing(), { text: "", selection: [1, 1] } } } }), this.add("parens", "deletion", function (e, t, n, r, i) { var s = r.doc.getTextRange(i); if (!i.isMultiLine() && s == "(") { h(n); var o = r.doc.getLine(i.start.row), u = o.substring(i.start.column + 1, i.start.column + 2); if (u == ")") return i.end.column++, i } }), this.add("brackets", "insertion", function (e, t, n, r, i) { if (i == "[") { h(n); var s = n.getSelectionRange(), o = r.doc.getTextRange(s); if (o !== "" && n.getWrapBehavioursEnabled()) return p(s, o, "[", "]"); if (d.isSaneInsertion(n, r)) return d.recordAutoInsert(n, r, "]"), { text: "[]", selection: [1, 1] } } else if (i == "]") { h(n); var u = n.getCursorPosition(), a = r.doc.getLine(u.row), f = a.substring(u.column, u.column + 1); if (f == "]") { var l = r.$findOpeningBracket("]", { column: u.column + 1, row: u.row }); if (l !== null && d.isAutoInsertedClosing(u, a, i)) return d.popAutoInsertedClosing(), { text: "", selection: [1, 1] } } } }), this.add("brackets", "deletion", function (e, t, n, r, i) { var s = r.doc.getTextRange(i); if (!i.isMultiLine() && s == "[") { h(n); var o = r.doc.getLine(i.start.row), u = o.substring(i.start.column + 1, i.start.column + 2); if (u == "]") return i.end.column++, i } }), this.add("string_dquotes", "insertion", function (e, t, n, r, i) { var s = r.$mode.$quotes || c; if (i.length == 1 && s[i]) { if (this.lineCommentStart && this.lineCommentStart.indexOf(i) != -1) return; h(n); var o = i, u = n.getSelectionRange(), a = r.doc.getTextRange(u); if (a !== "" && (a.length != 1 || !s[a]) && n.getWrapBehavioursEnabled()) return p(u, a, o, o); if (!a) { var f = n.getCursorPosition(), l = r.doc.getLine(f.row), d = l.substring(f.column - 1, f.column), v = l.substring(f.column, f.column + 1), m = r.getTokenAt(f.row, f.column), g = r.getTokenAt(f.row, f.column + 1); if (d == "\\" && m && /escape/.test(m.type)) return null; var y = m && /string|escape/.test(m.type), b = !g || /string|escape/.test(g.type), w; if (v == o) w = y !== b, w && /string\.end/.test(g.type) && (w = !1); else { if (y && !b) return null; if (y && b) return null; var E = r.$mode.tokenRe; E.lastIndex = 0; var S = E.test(d); E.lastIndex = 0; var x = E.test(d); if (S || x) return null; if (v && !/[\s;,.})\]\\]/.test(v)) return null; var T = l[f.column - 2]; if (!(d != o || T != o && !E.test(T))) return null; w = !0 } return { text: w ? o + o : "", selection: [1, 1] } } } }), this.add("string_dquotes", "deletion", function (e, t, n, r, i) { var s = r.$mode.$quotes || c, o = r.doc.getTextRange(i); if (!i.isMultiLine() && s.hasOwnProperty(o)) { h(n); var u = r.doc.getLine(i.start.row), a = u.substring(i.start.column + 1, i.start.column + 2); if (a == o) return i.end.column++, i } }) }; d.isSaneInsertion = function (e, t) { var n = e.getCursorPosition(), r = new s(t, n.row, n.column); if (!this.$matchTokenType(r.getCurrentToken() || "text", u)) { if (/[)}\]]/.test(e.session.getLine(n.row)[n.column])) return !0; var i = new s(t, n.row, n.column + 1); if (!this.$matchTokenType(i.getCurrentToken() || "text", u)) return !1 } return r.stepForward(), r.getCurrentTokenRow() !== n.row || this.$matchTokenType(r.getCurrentToken() || "text", a) }, d.$matchTokenType = function (e, t) { return t.indexOf(e.type || e) > -1 }, d.recordAutoInsert = function (e, t, n) { var r = e.getCursorPosition(), i = t.doc.getLine(r.row); this.isAutoInsertedClosing(r, i, f.autoInsertedLineEnd[0]) || (f.autoInsertedBrackets = 0), f.autoInsertedRow = r.row, f.autoInsertedLineEnd = n + i.substr(r.column), f.autoInsertedBrackets++ }, d.recordMaybeInsert = function (e, t, n) { var r = e.getCursorPosition(), i = t.doc.getLine(r.row); this.isMaybeInsertedClosing(r, i) || (f.maybeInsertedBrackets = 0), f.maybeInsertedRow = r.row, f.maybeInsertedLineStart = i.substr(0, r.column) + n, f.maybeInsertedLineEnd = i.substr(r.column), f.maybeInsertedBrackets++ }, d.isAutoInsertedClosing = function (e, t, n) { return f.autoInsertedBrackets > 0 && e.row === f.autoInsertedRow && n === f.autoInsertedLineEnd[0] && t.substr(e.column) === f.autoInsertedLineEnd }, d.isMaybeInsertedClosing = function (e, t) { return f.maybeInsertedBrackets > 0 && e.row === f.maybeInsertedRow && t.substr(e.column) === f.maybeInsertedLineEnd && t.substr(0, e.column) == f.maybeInsertedLineStart }, d.popAutoInsertedClosing = function () { f.autoInsertedLineEnd = f.autoInsertedLineEnd.substr(1), f.autoInsertedBrackets-- }, d.clearMaybeInsertedClosing = function () { f && (f.maybeInsertedBrackets = 0, f.maybeInsertedRow = -1) }, r.inherits(d, i), t.CstyleBehaviour = d }), define("ace/unicode", ["require", "exports", "module"], function (e, t, n) { "use strict"; var r = [48, 9, 8, 25, 5, 0, 2, 25, 48, 0, 11, 0, 5, 0, 6, 22, 2, 30, 2, 457, 5, 11, 15, 4, 8, 0, 2, 0, 18, 116, 2, 1, 3, 3, 9, 0, 2, 2, 2, 0, 2, 19, 2, 82, 2, 138, 2, 4, 3, 155, 12, 37, 3, 0, 8, 38, 10, 44, 2, 0, 2, 1, 2, 1, 2, 0, 9, 26, 6, 2, 30, 10, 7, 61, 2, 9, 5, 101, 2, 7, 3, 9, 2, 18, 3, 0, 17, 58, 3, 100, 15, 53, 5, 0, 6, 45, 211, 57, 3, 18, 2, 5, 3, 11, 3, 9, 2, 1, 7, 6, 2, 2, 2, 7, 3, 1, 3, 21, 2, 6, 2, 0, 4, 3, 3, 8, 3, 1, 3, 3, 9, 0, 5, 1, 2, 4, 3, 11, 16, 2, 2, 5, 5, 1, 3, 21, 2, 6, 2, 1, 2, 1, 2, 1, 3, 0, 2, 4, 5, 1, 3, 2, 4, 0, 8, 3, 2, 0, 8, 15, 12, 2, 2, 8, 2, 2, 2, 21, 2, 6, 2, 1, 2, 4, 3, 9, 2, 2, 2, 2, 3, 0, 16, 3, 3, 9, 18, 2, 2, 7, 3, 1, 3, 21, 2, 6, 2, 1, 2, 4, 3, 8, 3, 1, 3, 2, 9, 1, 5, 1, 2, 4, 3, 9, 2, 0, 17, 1, 2, 5, 4, 2, 2, 3, 4, 1, 2, 0, 2, 1, 4, 1, 4, 2, 4, 11, 5, 4, 4, 2, 2, 3, 3, 0, 7, 0, 15, 9, 18, 2, 2, 7, 2, 2, 2, 22, 2, 9, 2, 4, 4, 7, 2, 2, 2, 3, 8, 1, 2, 1, 7, 3, 3, 9, 19, 1, 2, 7, 2, 2, 2, 22, 2, 9, 2, 4, 3, 8, 2, 2, 2, 3, 8, 1, 8, 0, 2, 3, 3, 9, 19, 1, 2, 7, 2, 2, 2, 22, 2, 15, 4, 7, 2, 2, 2, 3, 10, 0, 9, 3, 3, 9, 11, 5, 3, 1, 2, 17, 4, 23, 2, 8, 2, 0, 3, 6, 4, 0, 5, 5, 2, 0, 2, 7, 19, 1, 14, 57, 6, 14, 2, 9, 40, 1, 2, 0, 3, 1, 2, 0, 3, 0, 7, 3, 2, 6, 2, 2, 2, 0, 2, 0, 3, 1, 2, 12, 2, 2, 3, 4, 2, 0, 2, 5, 3, 9, 3, 1, 35, 0, 24, 1, 7, 9, 12, 0, 2, 0, 2, 0, 5, 9, 2, 35, 5, 19, 2, 5, 5, 7, 2, 35, 10, 0, 58, 73, 7, 77, 3, 37, 11, 42, 2, 0, 4, 328, 2, 3, 3, 6, 2, 0, 2, 3, 3, 40, 2, 3, 3, 32, 2, 3, 3, 6, 2, 0, 2, 3, 3, 14, 2, 56, 2, 3, 3, 66, 5, 0, 33, 15, 17, 84, 13, 619, 3, 16, 2, 25, 6, 74, 22, 12, 2, 6, 12, 20, 12, 19, 13, 12, 2, 2, 2, 1, 13, 51, 3, 29, 4, 0, 5, 1, 3, 9, 34, 2, 3, 9, 7, 87, 9, 42, 6, 69, 11, 28, 4, 11, 5, 11, 11, 39, 3, 4, 12, 43, 5, 25, 7, 10, 38, 27, 5, 62, 2, 28, 3, 10, 7, 9, 14, 0, 89, 75, 5, 9, 18, 8, 13, 42, 4, 11, 71, 55, 9, 9, 4, 48, 83, 2, 2, 30, 14, 230, 23, 280, 3, 5, 3, 37, 3, 5, 3, 7, 2, 0, 2, 0, 2, 0, 2, 30, 3, 52, 2, 6, 2, 0, 4, 2, 2, 6, 4, 3, 3, 5, 5, 12, 6, 2, 2, 6, 67, 1, 20, 0, 29, 0, 14, 0, 17, 4, 60, 12, 5, 0, 4, 11, 18, 0, 5, 0, 3, 9, 2, 0, 4, 4, 7, 0, 2, 0, 2, 0, 2, 3, 2, 10, 3, 3, 6, 4, 5, 0, 53, 1, 2684, 46, 2, 46, 2, 132, 7, 6, 15, 37, 11, 53, 10, 0, 17, 22, 10, 6, 2, 6, 2, 6, 2, 6, 2, 6, 2, 6, 2, 6, 2, 6, 2, 31, 48, 0, 470, 1, 36, 5, 2, 4, 6, 1, 5, 85, 3, 1, 3, 2, 2, 89, 2, 3, 6, 40, 4, 93, 18, 23, 57, 15, 513, 6581, 75, 20939, 53, 1164, 68, 45, 3, 268, 4, 27, 21, 31, 3, 13, 13, 1, 2, 24, 9, 69, 11, 1, 38, 8, 3, 102, 3, 1, 111, 44, 25, 51, 13, 68, 12, 9, 7, 23, 4, 0, 5, 45, 3, 35, 13, 28, 4, 64, 15, 10, 39, 54, 10, 13, 3, 9, 7, 22, 4, 1, 5, 66, 25, 2, 227, 42, 2, 1, 3, 9, 7, 11171, 13, 22, 5, 48, 8453, 301, 3, 61, 3, 105, 39, 6, 13, 4, 6, 11, 2, 12, 2, 4, 2, 0, 2, 1, 2, 1, 2, 107, 34, 362, 19, 63, 3, 53, 41, 11, 5, 15, 17, 6, 13, 1, 25, 2, 33, 4, 2, 134, 20, 9, 8, 25, 5, 0, 2, 25, 12, 88, 4, 5, 3, 5, 3, 5, 3, 2], i = 0, s = []; for (var o = 0; o < r.length; o += 2)s.push(i += r[o]), r[o + 1] && s.push(45, i += r[o + 1]); t.wordChars = String.fromCharCode.apply(null, s) }), define("ace/mode/text", ["require", "exports", "module", "ace/config", "ace/tokenizer", "ace/mode/text_highlight_rules", "ace/mode/behaviour/cstyle", "ace/unicode", "ace/lib/lang", "ace/token_iterator", "ace/range"], function (e, t, n) { "use strict"; var r = e("../config"), i = e("../tokenizer").Tokenizer, s = e("./text_highlight_rules").TextHighlightRules, o = e("./behaviour/cstyle").CstyleBehaviour, u = e("../unicode"), a = e("../lib/lang"), f = e("../token_iterator").TokenIterator, l = e("../range").Range, c = function () { this.HighlightRules = s }; (function () { this.$defaultBehaviour = new o, this.tokenRe = new RegExp("^[" + u.wordChars + "\\$_]+", "g"), this.nonTokenRe = new RegExp("^(?:[^" + u.wordChars + "\\$_]|\\s])+", "g"), this.getTokenizer = function () { return this.$tokenizer || (this.$highlightRules = this.$highlightRules || new this.HighlightRules(this.$highlightRuleConfig), this.$tokenizer = new i(this.$highlightRules.getRules())), this.$tokenizer }, this.lineCommentStart = "", this.blockComment = "", this.toggleCommentLines = function (e, t, n, r) { function w(e) { for (var t = n; t <= r; t++)e(i.getLine(t), t) } var i = t.doc, s = !0, o = !0, u = Infinity, f = t.getTabSize(), l = !1; if (!this.lineCommentStart) { if (!this.blockComment) return !1; var c = this.blockComment.start, h = this.blockComment.end, p = new RegExp("^(\\s*)(?:" + a.escapeRegExp(c) + ")"), d = new RegExp("(?:" + a.escapeRegExp(h) + ")\\s*$"), v = function (e, t) { if (g(e, t)) return; if (!s || /\S/.test(e)) i.insertInLine({ row: t, column: e.length }, h), i.insertInLine({ row: t, column: u }, c) }, m = function (e, t) { var n; (n = e.match(d)) && i.removeInLine(t, e.length - n[0].length, e.length), (n = e.match(p)) && i.removeInLine(t, n[1].length, n[0].length) }, g = function (e, n) { if (p.test(e)) return !0; var r = t.getTokens(n); for (var i = 0; i < r.length; i++)if (r[i].type === "comment") return !0 } } else { if (Array.isArray(this.lineCommentStart)) var p = this.lineCommentStart.map(a.escapeRegExp).join("|"), c = this.lineCommentStart[0]; else var p = a.escapeRegExp(this.lineCommentStart), c = this.lineCommentStart; p = new RegExp("^(\\s*)(?:" + p + ") ?"), l = t.getUseSoftTabs(); var m = function (e, t) { var n = e.match(p); if (!n) return; var r = n[1].length, s = n[0].length; !b(e, r, s) && n[0][s - 1] == " " && s--, i.removeInLine(t, r, s) }, y = c + " ", v = function (e, t) { if (!s || /\S/.test(e)) b(e, u, u) ? i.insertInLine({ row: t, column: u }, y) : i.insertInLine({ row: t, column: u }, c) }, g = function (e, t) { return p.test(e) }, b = function (e, t, n) { var r = 0; while (t-- && e.charAt(t) == " ") r++; if (r % f != 0) return !1; var r = 0; while (e.charAt(n++) == " ") r++; return f > 2 ? r % f != f - 1 : r % f == 0 } } var E = Infinity; w(function (e, t) { var n = e.search(/\S/); n !== -1 ? (n < u && (u = n), o && !g(e, t) && (o = !1)) : E > e.length && (E = e.length) }), u == Infinity && (u = E, s = !1, o = !1), l && u % f != 0 && (u = Math.floor(u / f) * f), w(o ? m : v) }, this.toggleBlockComment = function (e, t, n, r) { var i = this.blockComment; if (!i) return; !i.start && i[0] && (i = i[0]); var s = new f(t, r.row, r.column), o = s.getCurrentToken(), u = t.selection, a = t.selection.toOrientedRange(), c, h; if (o && /comment/.test(o.type)) { var p, d; while (o && /comment/.test(o.type)) { var v = o.value.indexOf(i.start); if (v != -1) { var m = s.getCurrentTokenRow(), g = s.getCurrentTokenColumn() + v; p = new l(m, g, m, g + i.start.length); break } o = s.stepBackward() } var s = new f(t, r.row, r.column), o = s.getCurrentToken(); while (o && /comment/.test(o.type)) { var v = o.value.indexOf(i.end); if (v != -1) { var m = s.getCurrentTokenRow(), g = s.getCurrentTokenColumn() + v; d = new l(m, g, m, g + i.end.length); break } o = s.stepForward() } d && t.remove(d), p && (t.remove(p), c = p.start.row, h = -i.start.length) } else h = i.start.length, c = n.start.row, t.insert(n.end, i.end), t.insert(n.start, i.start); a.start.row == c && (a.start.column += h), a.end.row == c && (a.end.column += h), t.selection.fromOrientedRange(a) }, this.getNextLineIndent = function (e, t, n) { return this.$getIndent(t) }, this.checkOutdent = function (e, t, n) { return !1 }, this.autoOutdent = function (e, t, n) { }, this.$getIndent = function (e) { return e.match(/^\s*/)[0] }, this.createWorker = function (e) { return null }, this.createModeDelegates = function (e) { this.$embeds = [], this.$modes = {}; for (var t in e) if (e[t]) { var n = e[t], i = n.prototype.$id, s = r.$modes[i]; s || (r.$modes[i] = s = new n), r.$modes[t] || (r.$modes[t] = s), this.$embeds.push(t), this.$modes[t] = s } var o = ["toggleBlockComment", "toggleCommentLines", "getNextLineIndent", "checkOutdent", "autoOutdent", "transformAction", "getCompletions"]; for (var t = 0; t < o.length; t++)(function (e) { var n = o[t], r = e[n]; e[o[t]] = function () { return this.$delegator(n, arguments, r) } })(this) }, this.$delegator = function (e, t, n) { var r = t[0] || "start"; if (typeof r != "string") { if (Array.isArray(r[2])) { var i = r[2][r[2].length - 1], s = this.$modes[i]; if (s) return s[e].apply(s, [r[1]].concat([].slice.call(t, 1))) } r = r[0] || "start" } for (var o = 0; o < this.$embeds.length; o++) { if (!this.$modes[this.$embeds[o]]) continue; var u = r.split(this.$embeds[o]); if (!u[0] && u[1]) { t[0] = u[1]; var s = this.$modes[this.$embeds[o]]; return s[e].apply(s, t) } } var a = n.apply(this, t); return n ? a : undefined }, this.transformAction = function (e, t, n, r, i) { if (this.$behaviour) { var s = this.$behaviour.getBehaviours(); for (var o in s) if (s[o][t]) { var u = s[o][t].apply(this, arguments); if (u) return u } } }, this.getKeywords = function (e) { if (!this.completionKeywords) { var t = this.$tokenizer.rules, n = []; for (var r in t) { var i = t[r]; for (var s = 0, o = i.length; s < o; s++)if (typeof i[s].token == "string") /keyword|support|storage/.test(i[s].token) && n.push(i[s].regex); else if (typeof i[s].token == "object") for (var u = 0, a = i[s].token.length; u < a; u++)if (/keyword|support|storage/.test(i[s].token[u])) { var r = i[s].regex.match(/\(.+?\)/g)[u]; n.push(r.substr(1, r.length - 2)) } } this.completionKeywords = n } return e ? n.concat(this.$keywordList || []) : this.$keywordList }, this.$createKeywordList = function () { return this.$highlightRules || this.getTokenizer(), this.$keywordList = this.$highlightRules.$keywordList || [] }, this.getCompletions = function (e, t, n, r) { var i = this.$keywordList || this.$createKeywordList(); return i.map(function (e) { return { name: e, value: e, score: 0, meta: "keyword" } }) }, this.$id = "ace/mode/text" }).call(c.prototype), t.Mode = c }), define("ace/apply_delta", ["require", "exports", "module"], function (e, t, n) { "use strict"; function r(e, t) { throw console.log("Invalid Delta:", e), "Invalid Delta: " + t } function i(e, t) { return t.row >= 0 && t.row < e.length && t.column >= 0 && t.column <= e[t.row].length } function s(e, t) { t.action != "insert" && t.action != "remove" && r(t, "delta.action must be 'insert' or 'remove'"), t.lines instanceof Array || r(t, "delta.lines must be an Array"), (!t.start || !t.end) && r(t, "delta.start/end must be an present"); var n = t.start; i(e, t.start) || r(t, "delta.start must be contained in document"); var s = t.end; t.action == "remove" && !i(e, s) && r(t, "delta.end must contained in document for 'remove' actions"); var o = s.row - n.row, u = s.column - (o == 0 ? n.column : 0); (o != t.lines.length - 1 || t.lines[o].length != u) && r(t, "delta.range must match delta lines") } t.applyDelta = function (e, t, n) { var r = t.start.row, i = t.start.column, s = e[r] || ""; switch (t.action) { case "insert": var o = t.lines; if (o.length === 1) e[r] = s.substring(0, i) + t.lines[0] + s.substring(i); else { var u = [r, 1].concat(t.lines); e.splice.apply(e, u), e[r] = s.substring(0, i) + e[r], e[r + t.lines.length - 1] += s.substring(i) } break; case "remove": var a = t.end.column, f = t.end.row; r === f ? e[r] = s.substring(0, i) + s.substring(a) : e.splice(r, f - r + 1, s.substring(0, i) + e[f].substring(a)) } } }), define("ace/anchor", ["require", "exports", "module", "ace/lib/oop", "ace/lib/event_emitter"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./lib/event_emitter").EventEmitter, s = t.Anchor = function (e, t, n) { this.$onChange = this.onChange.bind(this), this.attach(e), typeof n == "undefined" ? this.setPosition(t.row, t.column) : this.setPosition(t, n) }; (function () { function e(e, t, n) { var r = n ? e.column <= t.column : e.column < t.column; return e.row < t.row || e.row == t.row && r } function t(t, n, r) { var i = t.action == "insert", s = (i ? 1 : -1) * (t.end.row - t.start.row), o = (i ? 1 : -1) * (t.end.column - t.start.column), u = t.start, a = i ? u : t.end; return e(n, u, r) ? { row: n.row, column: n.column } : e(a, n, !r) ? { row: n.row + s, column: n.column + (n.row == a.row ? o : 0) } : { row: u.row, column: u.column } } r.implement(this, i), this.getPosition = function () { return this.$clipPositionToDocument(this.row, this.column) }, this.getDocument = function () { return this.document }, this.$insertRight = !1, this.onChange = function (e) { if (e.start.row == e.end.row && e.start.row != this.row) return; if (e.start.row > this.row) return; var n = t(e, { row: this.row, column: this.column }, this.$insertRight); this.setPosition(n.row, n.column, !0) }, this.setPosition = function (e, t, n) { var r; n ? r = { row: e, column: t } : r = this.$clipPositionToDocument(e, t); if (this.row == r.row && this.column == r.column) return; var i = { row: this.row, column: this.column }; this.row = r.row, this.column = r.column, this._signal("change", { old: i, value: r }) }, this.detach = function () { this.document.off("change", this.$onChange) }, this.attach = function (e) { this.document = e || this.document, this.document.on("change", this.$onChange) }, this.$clipPositionToDocument = function (e, t) { var n = {}; return e >= this.document.getLength() ? (n.row = Math.max(0, this.document.getLength() - 1), n.column = this.document.getLine(n.row).length) : e < 0 ? (n.row = 0, n.column = 0) : (n.row = e, n.column = Math.min(this.document.getLine(n.row).length, Math.max(0, t))), t < 0 && (n.column = 0), n } }).call(s.prototype) }), define("ace/document", ["require", "exports", "module", "ace/lib/oop", "ace/apply_delta", "ace/lib/event_emitter", "ace/range", "ace/anchor"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./apply_delta").applyDelta, s = e("./lib/event_emitter").EventEmitter, o = e("./range").Range, u = e("./anchor").Anchor, a = function (e) { this.$lines = [""], e.length === 0 ? this.$lines = [""] : Array.isArray(e) ? this.insertMergedLines({ row: 0, column: 0 }, e) : this.insert({ row: 0, column: 0 }, e) }; (function () { r.implement(this, s), this.setValue = function (e) { var t = this.getLength() - 1; this.remove(new o(0, 0, t, this.getLine(t).length)), this.insert({ row: 0, column: 0 }, e) }, this.getValue = function () { return this.getAllLines().join(this.getNewLineCharacter()) }, this.createAnchor = function (e, t) { return new u(this, e, t) }, "aaa".split(/a/).length === 0 ? this.$split = function (e) { return e.replace(/\r\n|\r/g, "\n").split("\n") } : this.$split = function (e) { return e.split(/\r\n|\r|\n/) }, this.$detectNewLine = function (e) { var t = e.match(/^.*?(\r\n|\r|\n)/m); this.$autoNewLine = t ? t[1] : "\n", this._signal("changeNewLineMode") }, this.getNewLineCharacter = function () { switch (this.$newLineMode) { case "windows": return "\r\n"; case "unix": return "\n"; default: return this.$autoNewLine || "\n" } }, this.$autoNewLine = "", this.$newLineMode = "auto", this.setNewLineMode = function (e) { if (this.$newLineMode === e) return; this.$newLineMode = e, this._signal("changeNewLineMode") }, this.getNewLineMode = function () { return this.$newLineMode }, this.isNewLine = function (e) { return e == "\r\n" || e == "\r" || e == "\n" }, this.getLine = function (e) { return this.$lines[e] || "" }, this.getLines = function (e, t) { return this.$lines.slice(e, t + 1) }, this.getAllLines = function () { return this.getLines(0, this.getLength()) }, this.getLength = function () { return this.$lines.length }, this.getTextRange = function (e) { return this.getLinesForRange(e).join(this.getNewLineCharacter()) }, this.getLinesForRange = function (e) { var t; if (e.start.row === e.end.row) t = [this.getLine(e.start.row).substring(e.start.column, e.end.column)]; else { t = this.getLines(e.start.row, e.end.row), t[0] = (t[0] || "").substring(e.start.column); var n = t.length - 1; e.end.row - e.start.row == n && (t[n] = t[n].substring(0, e.end.column)) } return t }, this.insertLines = function (e, t) { return console.warn("Use of document.insertLines is deprecated. Use the insertFullLines method instead."), this.insertFullLines(e, t) }, this.removeLines = function (e, t) { return console.warn("Use of document.removeLines is deprecated. Use the removeFullLines method instead."), this.removeFullLines(e, t) }, this.insertNewLine = function (e) { return console.warn("Use of document.insertNewLine is deprecated. Use insertMergedLines(position, ['', '']) instead."), this.insertMergedLines(e, ["", ""]) }, this.insert = function (e, t) { return this.getLength() <= 1 && this.$detectNewLine(t), this.insertMergedLines(e, this.$split(t)) }, this.insertInLine = function (e, t) { var n = this.clippedPos(e.row, e.column), r = this.pos(e.row, e.column + t.length); return this.applyDelta({ start: n, end: r, action: "insert", lines: [t] }, !0), this.clonePos(r) }, this.clippedPos = function (e, t) { var n = this.getLength(); e === undefined ? e = n : e < 0 ? e = 0 : e >= n && (e = n - 1, t = undefined); var r = this.getLine(e); return t == undefined && (t = r.length), t = Math.min(Math.max(t, 0), r.length), { row: e, column: t } }, this.clonePos = function (e) { return { row: e.row, column: e.column } }, this.pos = function (e, t) { return { row: e, column: t } }, this.$clipPosition = function (e) { var t = this.getLength(); return e.row >= t ? (e.row = Math.max(0, t - 1), e.column = this.getLine(t - 1).length) : (e.row = Math.max(0, e.row), e.column = Math.min(Math.max(e.column, 0), this.getLine(e.row).length)), e }, this.insertFullLines = function (e, t) { e = Math.min(Math.max(e, 0), this.getLength()); var n = 0; e < this.getLength() ? (t = t.concat([""]), n = 0) : (t = [""].concat(t), e--, n = this.$lines[e].length), this.insertMergedLines({ row: e, column: n }, t) }, this.insertMergedLines = function (e, t) { var n = this.clippedPos(e.row, e.column), r = { row: n.row + t.length - 1, column: (t.length == 1 ? n.column : 0) + t[t.length - 1].length }; return this.applyDelta({ start: n, end: r, action: "insert", lines: t }), this.clonePos(r) }, this.remove = function (e) { var t = this.clippedPos(e.start.row, e.start.column), n = this.clippedPos(e.end.row, e.end.column); return this.applyDelta({ start: t, end: n, action: "remove", lines: this.getLinesForRange({ start: t, end: n }) }), this.clonePos(t) }, this.removeInLine = function (e, t, n) { var r = this.clippedPos(e, t), i = this.clippedPos(e, n); return this.applyDelta({ start: r, end: i, action: "remove", lines: this.getLinesForRange({ start: r, end: i }) }, !0), this.clonePos(r) }, this.removeFullLines = function (e, t) { e = Math.min(Math.max(0, e), this.getLength() - 1), t = Math.min(Math.max(0, t), this.getLength() - 1); var n = t == this.getLength() - 1 && e > 0, r = t < this.getLength() - 1, i = n ? e - 1 : e, s = n ? this.getLine(i).length : 0, u = r ? t + 1 : t, a = r ? 0 : this.getLine(u).length, f = new o(i, s, u, a), l = this.$lines.slice(e, t + 1); return this.applyDelta({ start: f.start, end: f.end, action: "remove", lines: this.getLinesForRange(f) }), l }, this.removeNewLine = function (e) { e < this.getLength() - 1 && e >= 0 && this.applyDelta({ start: this.pos(e, this.getLine(e).length), end: this.pos(e + 1, 0), action: "remove", lines: ["", ""] }) }, this.replace = function (e, t) { e instanceof o || (e = o.fromPoints(e.start, e.end)); if (t.length === 0 && e.isEmpty()) return e.start; if (t == this.getTextRange(e)) return e.end; this.remove(e); var n; return t ? n = this.insert(e.start, t) : n = e.start, n }, this.applyDeltas = function (e) { for (var t = 0; t < e.length; t++)this.applyDelta(e[t]) }, this.revertDeltas = function (e) { for (var t = e.length - 1; t >= 0; t--)this.revertDelta(e[t]) }, this.applyDelta = function (e, t) { var n = e.action == "insert"; if (n ? e.lines.length <= 1 && !e.lines[0] : !o.comparePoints(e.start, e.end)) return; n && e.lines.length > 2e4 ? this.$splitAndapplyLargeDelta(e, 2e4) : (i(this.$lines, e, t), this._signal("change", e)) }, this.$safeApplyDelta = function (e) { var t = this.$lines.length; (e.action == "remove" && e.start.row < t && e.end.row < t || e.action == "insert" && e.start.row <= t) && this.applyDelta(e) }, this.$splitAndapplyLargeDelta = function (e, t) { var n = e.lines, r = n.length - t + 1, i = e.start.row, s = e.start.column; for (var o = 0, u = 0; o < r; o = u) { u += t - 1; var a = n.slice(o, u); a.push(""), this.applyDelta({ start: this.pos(i + o, s), end: this.pos(i + u, s = 0), action: e.action, lines: a }, !0) } e.lines = n.slice(o), e.start.row = i + o, e.start.column = s, this.applyDelta(e, !0) }, this.revertDelta = function (e) { this.$safeApplyDelta({ start: this.clonePos(e.start), end: this.clonePos(e.end), action: e.action == "insert" ? "remove" : "insert", lines: e.lines.slice() }) }, this.indexToPosition = function (e, t) { var n = this.$lines || this.getAllLines(), r = this.getNewLineCharacter().length; for (var i = t || 0, s = n.length; i < s; i++) { e -= n[i].length + r; if (e < 0) return { row: i, column: e + n[i].length + r } } return { row: s - 1, column: e + n[s - 1].length + r } }, this.positionToIndex = function (e, t) { var n = this.$lines || this.getAllLines(), r = this.getNewLineCharacter().length, i = 0, s = Math.min(e.row, n.length); for (var o = t || 0; o < s; ++o)i += n[o].length + r; return i + e.column } }).call(a.prototype), t.Document = a }), define("ace/background_tokenizer", ["require", "exports", "module", "ace/lib/oop", "ace/lib/event_emitter"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./lib/event_emitter").EventEmitter, s = function (e, t) { this.running = !1, this.lines = [], this.states = [], this.currentLine = 0, this.tokenizer = e; var n = this; this.$worker = function () { if (!n.running) return; var e = new Date, t = n.currentLine, r = -1, i = n.doc, s = t; while (n.lines[t]) t++; var o = i.getLength(), u = 0; n.running = !1; while (t < o) { n.$tokenizeRow(t), r = t; do t++; while (n.lines[t]); u++; if (u % 5 === 0 && new Date - e > 20) { n.running = setTimeout(n.$worker, 20); break } } n.currentLine = t, r == -1 && (r = t), s <= r && n.fireUpdateEvent(s, r) } }; (function () { r.implement(this, i), this.setTokenizer = function (e) { this.tokenizer = e, this.lines = [], this.states = [], this.start(0) }, this.setDocument = function (e) { this.doc = e, this.lines = [], this.states = [], this.stop() }, this.fireUpdateEvent = function (e, t) { var n = { first: e, last: t }; this._signal("update", { data: n }) }, this.start = function (e) { this.currentLine = Math.min(e || 0, this.currentLine, this.doc.getLength()), this.lines.splice(this.currentLine, this.lines.length), this.states.splice(this.currentLine, this.states.length), this.stop(), this.running = setTimeout(this.$worker, 700) }, this.scheduleStart = function () { this.running || (this.running = setTimeout(this.$worker, 700)) }, this.$updateOnChange = function (e) { var t = e.start.row, n = e.end.row - t; if (n === 0) this.lines[t] = null; else if (e.action == "remove") this.lines.splice(t, n + 1, null), this.states.splice(t, n + 1, null); else { var r = Array(n + 1); r.unshift(t, 1), this.lines.splice.apply(this.lines, r), this.states.splice.apply(this.states, r) } this.currentLine = Math.min(t, this.currentLine, this.doc.getLength()), this.stop() }, this.stop = function () { this.running && clearTimeout(this.running), this.running = !1 }, this.getTokens = function (e) { return this.lines[e] || this.$tokenizeRow(e) }, this.getState = function (e) { return this.currentLine == e && this.$tokenizeRow(e), this.states[e] || "start" }, this.$tokenizeRow = function (e) { var t = this.doc.getLine(e), n = this.states[e - 1], r = this.tokenizer.getLineTokens(t, n, e); return this.states[e] + "" != r.state + "" ? (this.states[e] = r.state, this.lines[e + 1] = null, this.currentLine > e + 1 && (this.currentLine = e + 1)) : this.currentLine == e && (this.currentLine = e + 1), this.lines[e] = r.tokens } }).call(s.prototype), t.BackgroundTokenizer = s }), define("ace/search_highlight", ["require", "exports", "module", "ace/lib/lang", "ace/lib/oop", "ace/range"], function (e, t, n) { "use strict"; var r = e("./lib/lang"), i = e("./lib/oop"), s = e("./range").Range, o = function (e, t, n) { this.setRegexp(e), this.clazz = t, this.type = n || "text" }; (function () { this.MAX_RANGES = 500, this.setRegexp = function (e) { if (this.regExp + "" == e + "") return; this.regExp = e, this.cache = [] }, this.update = function (e, t, n, i) { if (!this.regExp) return; var o = i.firstRow, u = i.lastRow; for (var a = o; a <= u; a++) { var f = this.cache[a]; f == null && (f = r.getMatchOffsets(n.getLine(a), this.regExp), f.length > this.MAX_RANGES && (f = f.slice(0, this.MAX_RANGES)), f = f.map(function (e) { return new s(a, e.offset, a, e.offset + e.length) }), this.cache[a] = f.length ? f : ""); for (var l = f.length; l--;)t.drawSingleLineMarker(e, f[l].toScreenRange(n), this.clazz, i) } } }).call(o.prototype), t.SearchHighlight = o }), define("ace/edit_session/fold_line", ["require", "exports", "module", "ace/range"], function (e, t, n) { "use strict"; function i(e, t) { this.foldData = e, Array.isArray(t) ? this.folds = t : t = this.folds = [t]; var n = t[t.length - 1]; this.range = new r(t[0].start.row, t[0].start.column, n.end.row, n.end.column), this.start = this.range.start, this.end = this.range.end, this.folds.forEach(function (e) { e.setFoldLine(this) }, this) } var r = e("../range").Range; (function () { this.shiftRow = function (e) { this.start.row += e, this.end.row += e, this.folds.forEach(function (t) { t.start.row += e, t.end.row += e }) }, this.addFold = function (e) { if (e.sameRow) { if (e.start.row < this.startRow || e.endRow > this.endRow) throw new Error("Can't add a fold to this FoldLine as it has no connection"); this.folds.push(e), this.folds.sort(function (e, t) { return -e.range.compareEnd(t.start.row, t.start.column) }), this.range.compareEnd(e.start.row, e.start.column) > 0 ? (this.end.row = e.end.row, this.end.column = e.end.column) : this.range.compareStart(e.end.row, e.end.column) < 0 && (this.start.row = e.start.row, this.start.column = e.start.column) } else if (e.start.row == this.end.row) this.folds.push(e), this.end.row = e.end.row, this.end.column = e.end.column; else { if (e.end.row != this.start.row) throw new Error("Trying to add fold to FoldRow that doesn't have a matching row"); this.folds.unshift(e), this.start.row = e.start.row, this.start.column = e.start.column } e.foldLine = this }, this.containsRow = function (e) { return e >= this.start.row && e <= this.end.row }, this.walk = function (e, t, n) { var r = 0, i = this.folds, s, o, u, a = !0; t == null && (t = this.end.row, n = this.end.column); for (var f = 0; f < i.length; f++) { s = i[f], o = s.range.compareStart(t, n); if (o == -1) { e(null, t, n, r, a); return } u = e(null, s.start.row, s.start.column, r, a), u = !u && e(s.placeholder, s.start.row, s.start.column, r); if (u || o === 0) return; a = !s.sameRow, r = s.end.column } e(null, t, n, r, a) }, this.getNextFoldTo = function (e, t) { var n, r; for (var i = 0; i < this.folds.length; i++) { n = this.folds[i], r = n.range.compareEnd(e, t); if (r == -1) return { fold: n, kind: "after" }; if (r === 0) return { fold: n, kind: "inside" } } return null }, this.addRemoveChars = function (e, t, n) { var r = this.getNextFoldTo(e, t), i, s; if (r) { i = r.fold; if (r.kind == "inside" && i.start.column != t && i.start.row != e) window.console && window.console.log(e, t, i); else if (i.start.row == e) { s = this.folds; var o = s.indexOf(i); o === 0 && (this.start.column += n); for (o; o < s.length; o++) { i = s[o], i.start.column += n; if (!i.sameRow) return; i.end.column += n } this.end.column += n } } }, this.split = function (e, t) { var n = this.getNextFoldTo(e, t); if (!n || n.kind == "inside") return null; var r = n.fold, s = this.folds, o = this.foldData, u = s.indexOf(r), a = s[u - 1]; this.end.row = a.end.row, this.end.column = a.end.column, s = s.splice(u, s.length - u); var f = new i(o, s); return o.splice(o.indexOf(this) + 1, 0, f), f }, this.merge = function (e) { var t = e.folds; for (var n = 0; n < t.length; n++)this.addFold(t[n]); var r = this.foldData; r.splice(r.indexOf(e), 1) }, this.toString = function () { var e = [this.range.toString() + ": ["]; return this.folds.forEach(function (t) { e.push(" " + t.toString()) }), e.push("]"), e.join("\n") }, this.idxToPosition = function (e) { var t = 0; for (var n = 0; n < this.folds.length; n++) { var r = this.folds[n]; e -= r.start.column - t; if (e < 0) return { row: r.start.row, column: r.start.column + e }; e -= r.placeholder.length; if (e < 0) return r.start; t = r.end.column } return { row: this.end.row, column: this.end.column + e } } }).call(i.prototype), t.FoldLine = i }), define("ace/range_list", ["require", "exports", "module", "ace/range"], function (e, t, n) { "use strict"; var r = e("./range").Range, i = r.comparePoints, s = function () { this.ranges = [], this.$bias = 1 }; (function () { this.comparePoints = i, this.pointIndex = function (e, t, n) { var r = this.ranges; for (var s = n || 0; s < r.length; s++) { var o = r[s], u = i(e, o.end); if (u > 0) continue; var a = i(e, o.start); return u === 0 ? t && a !== 0 ? -s - 2 : s : a > 0 || a === 0 && !t ? s : -s - 1 } return -s - 1 }, this.add = function (e) { var t = !e.isEmpty(), n = this.pointIndex(e.start, t); n < 0 && (n = -n - 1); var r = this.pointIndex(e.end, t, n); return r < 0 ? r = -r - 1 : r++, this.ranges.splice(n, r - n, e) }, this.addList = function (e) { var t = []; for (var n = e.length; n--;)t.push.apply(t, this.add(e[n])); return t }, this.substractPoint = function (e) { var t = this.pointIndex(e); if (t >= 0) return this.ranges.splice(t, 1) }, this.merge = function () { var e = [], t = this.ranges; t = t.sort(function (e, t) { return i(e.start, t.start) }); var n = t[0], r; for (var s = 1; s < t.length; s++) { r = n, n = t[s]; var o = i(r.end, n.start); if (o < 0) continue; if (o == 0 && !r.isEmpty() && !n.isEmpty()) continue; i(r.end, n.end) < 0 && (r.end.row = n.end.row, r.end.column = n.end.column), t.splice(s, 1), e.push(n), n = r, s-- } return this.ranges = t, e }, this.contains = function (e, t) { return this.pointIndex({ row: e, column: t }) >= 0 }, this.containsPoint = function (e) { return this.pointIndex(e) >= 0 }, this.rangeAtPoint = function (e) { var t = this.pointIndex(e); if (t >= 0) return this.ranges[t] }, this.clipRows = function (e, t) { var n = this.ranges; if (n[0].start.row > t || n[n.length - 1].start.row < e) return []; var r = this.pointIndex({ row: e, column: 0 }); r < 0 && (r = -r - 1); var i = this.pointIndex({ row: t, column: 0 }, r); i < 0 && (i = -i - 1); var s = []; for (var o = r; o < i; o++)s.push(n[o]); return s }, this.removeAll = function () { return this.ranges.splice(0, this.ranges.length) }, this.attach = function (e) { this.session && this.detach(), this.session = e, this.onChange = this.$onChange.bind(this), this.session.on("change", this.onChange) }, this.detach = function () { if (!this.session) return; this.session.removeListener("change", this.onChange), this.session = null }, this.$onChange = function (e) { var t = e.start, n = e.end, r = t.row, i = n.row, s = this.ranges; for (var o = 0, u = s.length; o < u; o++) { var a = s[o]; if (a.end.row >= r) break } if (e.action == "insert") { var f = i - r, l = -t.column + n.column; for (; o < u; o++) { var a = s[o]; if (a.start.row > r) break; a.start.row == r && a.start.column >= t.column && (a.start.column == t.column && this.$bias <= 0 || (a.start.column += l, a.start.row += f)); if (a.end.row == r && a.end.column >= t.column) { if (a.end.column == t.column && this.$bias < 0) continue; a.end.column == t.column && l > 0 && o < u - 1 && a.end.column > a.start.column && a.end.column == s[o + 1].start.column && (a.end.column -= l), a.end.column += l, a.end.row += f } } } else { var f = r - i, l = t.column - n.column; for (; o < u; o++) { var a = s[o]; if (a.start.row > i) break; if (a.end.row < i && (r < a.end.row || r == a.end.row && t.column < a.end.column)) a.end.row = r, a.end.column = t.column; else if (a.end.row == i) if (a.end.column <= n.column) { if (f || a.end.column > t.column) a.end.column = t.column, a.end.row = t.row } else a.end.column += l, a.end.row += f; else a.end.row > i && (a.end.row += f); if (a.start.row < i && (r < a.start.row || r == a.start.row && t.column < a.start.column)) a.start.row = r, a.start.column = t.column; else if (a.start.row == i) if (a.start.column <= n.column) { if (f || a.start.column > t.column) a.start.column = t.column, a.start.row = t.row } else a.start.column += l, a.start.row += f; else a.start.row > i && (a.start.row += f) } } if (f != 0 && o < u) for (; o < u; o++) { var a = s[o]; a.start.row += f, a.end.row += f } } }).call(s.prototype), t.RangeList = s }), define("ace/edit_session/fold", ["require", "exports", "module", "ace/range_list", "ace/lib/oop"], function (e, t, n) { "use strict"; function o(e, t) { e.row -= t.row, e.row == 0 && (e.column -= t.column) } function u(e, t) { o(e.start, t), o(e.end, t) } function a(e, t) { e.row == 0 && (e.column += t.column), e.row += t.row } function f(e, t) { a(e.start, t), a(e.end, t) } var r = e("../range_list").RangeList, i = e("../lib/oop"), s = t.Fold = function (e, t) { this.foldLine = null, this.placeholder = t, this.range = e, this.start = e.start, this.end = e.end, this.sameRow = e.start.row == e.end.row, this.subFolds = this.ranges = [] }; i.inherits(s, r), function () { this.toString = function () { return '"' + this.placeholder + '" ' + this.range.toString() }, this.setFoldLine = function (e) { this.foldLine = e, this.subFolds.forEach(function (t) { t.setFoldLine(e) }) }, this.clone = function () { var e = this.range.clone(), t = new s(e, this.placeholder); return this.subFolds.forEach(function (e) { t.subFolds.push(e.clone()) }), t.collapseChildren = this.collapseChildren, t }, this.addSubFold = function (e) { if (this.range.isEqual(e)) return; u(e, this.start); var t = e.start.row, n = e.start.column; for (var r = 0, i = -1; r < this.subFolds.length; r++) { i = this.subFolds[r].range.compare(t, n); if (i != 1) break } var s = this.subFolds[r], o = 0; if (i == 0) { if (s.range.containsRange(e)) return s.addSubFold(e); o = 1 } var t = e.range.end.row, n = e.range.end.column; for (var a = r, i = -1; a < this.subFolds.length; a++) { i = this.subFolds[a].range.compare(t, n); if (i != 1) break } i == 0 && a++; var f = this.subFolds.splice(r, a - r, e), l = i == 0 ? f.length - 1 : f.length; for (var c = o; c < l; c++)e.addSubFold(f[c]); return e.setFoldLine(this.foldLine), e }, this.restoreRange = function (e) { return f(e, this.start) } }.call(s.prototype) }), define("ace/edit_session/folding", ["require", "exports", "module", "ace/range", "ace/edit_session/fold_line", "ace/edit_session/fold", "ace/token_iterator"], function (e, t, n) { "use strict"; function u() { this.getFoldAt = function (e, t, n) { var r = this.getFoldLine(e); if (!r) return null; var i = r.folds; for (var s = 0; s < i.length; s++) { var o = i[s].range; if (o.contains(e, t)) { if (n == 1 && o.isEnd(e, t) && !o.isEmpty()) continue; if (n == -1 && o.isStart(e, t) && !o.isEmpty()) continue; return i[s] } } }, this.getFoldsInRange = function (e) { var t = e.start, n = e.end, r = this.$foldData, i = []; t.column += 1, n.column -= 1; for (var s = 0; s < r.length; s++) { var o = r[s].range.compareRange(e); if (o == 2) continue; if (o == -2) break; var u = r[s].folds; for (var a = 0; a < u.length; a++) { var f = u[a]; o = f.range.compareRange(e); if (o == -2) break; if (o == 2) continue; if (o == 42) break; i.push(f) } } return t.column -= 1, n.column += 1, i }, this.getFoldsInRangeList = function (e) { if (Array.isArray(e)) { var t = []; e.forEach(function (e) { t = t.concat(this.getFoldsInRange(e)) }, this) } else var t = this.getFoldsInRange(e); return t }, this.getAllFolds = function () { var e = [], t = this.$foldData; for (var n = 0; n < t.length; n++)for (var r = 0; r < t[n].folds.length; r++)e.push(t[n].folds[r]); return e }, this.getFoldStringAt = function (e, t, n, r) { r = r || this.getFoldLine(e); if (!r) return null; var i = { end: { column: 0 } }, s, o; for (var u = 0; u < r.folds.length; u++) { o = r.folds[u]; var a = o.range.compareEnd(e, t); if (a == -1) { s = this.getLine(o.start.row).substring(i.end.column, o.start.column); break } if (a === 0) return null; i = o } return s || (s = this.getLine(o.start.row).substring(i.end.column)), n == -1 ? s.substring(0, t - i.end.column) : n == 1 ? s.substring(t - i.end.column) : s }, this.getFoldLine = function (e, t) { var n = this.$foldData, r = 0; t && (r = n.indexOf(t)), r == -1 && (r = 0); for (r; r < n.length; r++) { var i = n[r]; if (i.start.row <= e && i.end.row >= e) return i; if (i.end.row > e) return null } return null }, this.getNextFoldLine = function (e, t) { var n = this.$foldData, r = 0; t && (r = n.indexOf(t)), r == -1 && (r = 0); for (r; r < n.length; r++) { var i = n[r]; if (i.end.row >= e) return i } return null }, this.getFoldedRowCount = function (e, t) { var n = this.$foldData, r = t - e + 1; for (var i = 0; i < n.length; i++) { var s = n[i], o = s.end.row, u = s.start.row; if (o >= t) { u < t && (u >= e ? r -= t - u : r = 0); break } o >= e && (u >= e ? r -= o - u : r -= o - e + 1) } return r }, this.$addFoldLine = function (e) { return this.$foldData.push(e), this.$foldData.sort(function (e, t) { return e.start.row - t.start.row }), e }, this.addFold = function (e, t) { var n = this.$foldData, r = !1, o; e instanceof s ? o = e : (o = new s(t, e), o.collapseChildren = t.collapseChildren), this.$clipRangeToDocument(o.range); var u = o.start.row, a = o.start.column, f = o.end.row, l = o.end.column, c = this.getFoldAt(u, a, 1), h = this.getFoldAt(f, l, -1); if (c && h == c) return c.addSubFold(o); c && !c.range.isStart(u, a) && this.removeFold(c), h && !h.range.isEnd(f, l) && this.removeFold(h); var p = this.getFoldsInRange(o.range); p.length > 0 && (this.removeFolds(p), p.forEach(function (e) { o.addSubFold(e) })); for (var d = 0; d < n.length; d++) { var v = n[d]; if (f == v.start.row) { v.addFold(o), r = !0; break } if (u == v.end.row) { v.addFold(o), r = !0; if (!o.sameRow) { var m = n[d + 1]; if (m && m.start.row == f) { v.merge(m); break } } break } if (f <= v.start.row) break } return r || (v = this.$addFoldLine(new i(this.$foldData, o))), this.$useWrapMode ? this.$updateWrapData(v.start.row, v.start.row) : this.$updateRowLengthCache(v.start.row, v.start.row), this.$modified = !0, this._signal("changeFold", { data: o, action: "add" }), o }, this.addFolds = function (e) { e.forEach(function (e) { this.addFold(e) }, this) }, this.removeFold = function (e) { var t = e.foldLine, n = t.start.row, r = t.end.row, i = this.$foldData, s = t.folds; if (s.length == 1) i.splice(i.indexOf(t), 1); else if (t.range.isEnd(e.end.row, e.end.column)) s.pop(), t.end.row = s[s.length - 1].end.row, t.end.column = s[s.length - 1].end.column; else if (t.range.isStart(e.start.row, e.start.column)) s.shift(), t.start.row = s[0].start.row, t.start.column = s[0].start.column; else if (e.sameRow) s.splice(s.indexOf(e), 1); else { var o = t.split(e.start.row, e.start.column); s = o.folds, s.shift(), o.start.row = s[0].start.row, o.start.column = s[0].start.column } this.$updating || (this.$useWrapMode ? this.$updateWrapData(n, r) : this.$updateRowLengthCache(n, r)), this.$modified = !0, this._signal("changeFold", { data: e, action: "remove" }) }, this.removeFolds = function (e) { var t = []; for (var n = 0; n < e.length; n++)t.push(e[n]); t.forEach(function (e) { this.removeFold(e) }, this), this.$modified = !0 }, this.expandFold = function (e) { this.removeFold(e), e.subFolds.forEach(function (t) { e.restoreRange(t), this.addFold(t) }, this), e.collapseChildren > 0 && this.foldAll(e.start.row + 1, e.end.row, e.collapseChildren - 1), e.subFolds = [] }, this.expandFolds = function (e) { e.forEach(function (e) { this.expandFold(e) }, this) }, this.unfold = function (e, t) { var n, i; e == null ? (n = new r(0, 0, this.getLength(), 0), t = !0) : typeof e == "number" ? n = new r(e, 0, e, this.getLine(e).length) : "row" in e ? n = r.fromPoints(e, e) : n = e, i = this.getFoldsInRangeList(n); if (t) this.removeFolds(i); else { var s = i; while (s.length) this.expandFolds(s), s = this.getFoldsInRangeList(n) } if (i.length) return i }, this.isRowFolded = function (e, t) { return !!this.getFoldLine(e, t) }, this.getRowFoldEnd = function (e, t) { var n = this.getFoldLine(e, t); return n ? n.end.row : e }, this.getRowFoldStart = function (e, t) { var n = this.getFoldLine(e, t); return n ? n.start.row : e }, this.getFoldDisplayLine = function (e, t, n, r, i) { r == null && (r = e.start.row), i == null && (i = 0), t == null && (t = e.end.row), n == null && (n = this.getLine(t).length); var s = this.doc, o = ""; return e.walk(function (e, t, n, u) { if (t < r) return; if (t == r) { if (n < i) return; u = Math.max(i, u) } e != null ? o += e : o += s.getLine(t).substring(u, n) }, t, n), o }, this.getDisplayLine = function (e, t, n, r) { var i = this.getFoldLine(e); if (!i) { var s; return s = this.doc.getLine(e), s.substring(r || 0, t || s.length) } return this.getFoldDisplayLine(i, e, t, n, r) }, this.$cloneFoldData = function () { var e = []; return e = this.$foldData.map(function (t) { var n = t.folds.map(function (e) { return e.clone() }); return new i(e, n) }), e }, this.toggleFold = function (e) { var t = this.selection, n = t.getRange(), r, i; if (n.isEmpty()) { var s = n.start; r = this.getFoldAt(s.row, s.column); if (r) { this.expandFold(r); return } (i = this.findMatchingBracket(s)) ? n.comparePoint(i) == 1 ? n.end = i : (n.start = i, n.start.column++, n.end.column--) : (i = this.findMatchingBracket({ row: s.row, column: s.column + 1 })) ? (n.comparePoint(i) == 1 ? n.end = i : n.start = i, n.start.column++) : n = this.getCommentFoldRange(s.row, s.column) || n } else { var o = this.getFoldsInRange(n); if (e && o.length) { this.expandFolds(o); return } o.length == 1 && (r = o[0]) } r || (r = this.getFoldAt(n.start.row, n.start.column)); if (r && r.range.toString() == n.toString()) { this.expandFold(r); return } var u = "..."; if (!n.isMultiLine()) { u = this.getTextRange(n); if (u.length < 4) return; u = u.trim().substring(0, 2) + ".." } this.addFold(u, n) }, this.getCommentFoldRange = function (e, t, n) { var i = new o(this, e, t), s = i.getCurrentToken(), u = s.type; if (s && /^comment|string/.test(u)) { u = u.match(/comment|string/)[0], u == "comment" && (u += "|doc-start"); var a = new RegExp(u), f = new r; if (n != 1) { do s = i.stepBackward(); while (s && a.test(s.type)); i.stepForward() } f.start.row = i.getCurrentTokenRow(), f.start.column = i.getCurrentTokenColumn() + 2, i = new o(this, e, t); if (n != -1) { var l = -1; do { s = i.stepForward(); if (l == -1) { var c = this.getState(i.$row); a.test(c) || (l = i.$row) } else if (i.$row > l) break } while (s && a.test(s.type)); s = i.stepBackward() } else s = i.getCurrentToken(); return f.end.row = i.getCurrentTokenRow(), f.end.column = i.getCurrentTokenColumn() + s.value.length - 2, f } }, this.foldAll = function (e, t, n) { n == undefined && (n = 1e5); var r = this.foldWidgets; if (!r) return; t = t || this.getLength(), e = e || 0; for (var i = e; i < t; i++) { r[i] == null && (r[i] = this.getFoldWidget(i)); if (r[i] != "start") continue; var s = this.getFoldWidgetRange(i); if (s && s.isMultiLine() && s.end.row <= t && s.start.row >= e) { i = s.end.row; try { var o = this.addFold("...", s); o && (o.collapseChildren = n) } catch (u) { } } } }, this.$foldStyles = { manual: 1, markbegin: 1, markbeginend: 1 }, this.$foldStyle = "markbegin", this.setFoldStyle = function (e) { if (!this.$foldStyles[e]) throw new Error("invalid fold style: " + e + "[" + Object.keys(this.$foldStyles).join(", ") + "]"); if (this.$foldStyle == e) return; this.$foldStyle = e, e == "manual" && this.unfold(); var t = this.$foldMode; this.$setFolding(null), this.$setFolding(t) }, this.$setFolding = function (e) { if (this.$foldMode == e) return; this.$foldMode = e, this.off("change", this.$updateFoldWidgets), this.off("tokenizerUpdate", this.$tokenizerUpdateFoldWidgets), this._signal("changeAnnotation"); if (!e || this.$foldStyle == "manual") { this.foldWidgets = null; return } this.foldWidgets = [], this.getFoldWidget = e.getFoldWidget.bind(e, this, this.$foldStyle), this.getFoldWidgetRange = e.getFoldWidgetRange.bind(e, this, this.$foldStyle), this.$updateFoldWidgets = this.updateFoldWidgets.bind(this), this.$tokenizerUpdateFoldWidgets = this.tokenizerUpdateFoldWidgets.bind(this), this.on("change", this.$updateFoldWidgets), this.on("tokenizerUpdate", this.$tokenizerUpdateFoldWidgets) }, this.getParentFoldRangeData = function (e, t) { var n = this.foldWidgets; if (!n || t && n[e]) return {}; var r = e - 1, i; while (r >= 0) { var s = n[r]; s == null && (s = n[r] = this.getFoldWidget(r)); if (s == "start") { var o = this.getFoldWidgetRange(r); i || (i = o); if (o && o.end.row >= e) break } r-- } return { range: r !== -1 && o, firstRange: i } }, this.onFoldWidgetClick = function (e, t) { t = t.domEvent; var n = { children: t.shiftKey, all: t.ctrlKey || t.metaKey, siblings: t.altKey }, r = this.$toggleFoldWidget(e, n); if (!r) { var i = t.target || t.srcElement; i && /ace_fold-widget/.test(i.className) && (i.className += " ace_invalid") } }, this.$toggleFoldWidget = function (e, t) { if (!this.getFoldWidget) return; var n = this.getFoldWidget(e), r = this.getLine(e), i = n === "end" ? -1 : 1, s = this.getFoldAt(e, i === -1 ? 0 : r.length, i); if (s) return t.children || t.all ? this.removeFold(s) : this.expandFold(s), s; var o = this.getFoldWidgetRange(e, !0); if (o && !o.isMultiLine()) { s = this.getFoldAt(o.start.row, o.start.column, 1); if (s && o.isEqual(s.range)) return this.removeFold(s), s } if (t.siblings) { var u = this.getParentFoldRangeData(e); if (u.range) var a = u.range.start.row + 1, f = u.range.end.row; this.foldAll(a, f, t.all ? 1e4 : 0) } else t.children ? (f = o ? o.end.row : this.getLength(), this.foldAll(e + 1, f, t.all ? 1e4 : 0)) : o && (t.all && (o.collapseChildren = 1e4), this.addFold("...", o)); return o }, this.toggleFoldWidget = function (e) { var t = this.selection.getCursor().row; t = this.getRowFoldStart(t); var n = this.$toggleFoldWidget(t, {}); if (n) return; var r = this.getParentFoldRangeData(t, !0); n = r.range || r.firstRange; if (n) { t = n.start.row; var i = this.getFoldAt(t, this.getLine(t).length, 1); i ? this.removeFold(i) : this.addFold("...", n) } }, this.updateFoldWidgets = function (e) { var t = e.start.row, n = e.end.row - t; if (n === 0) this.foldWidgets[t] = null; else if (e.action == "remove") this.foldWidgets.splice(t, n + 1, null); else { var r = Array(n + 1); r.unshift(t, 1), this.foldWidgets.splice.apply(this.foldWidgets, r) } }, this.tokenizerUpdateFoldWidgets = function (e) { var t = e.data; t.first != t.last && this.foldWidgets.length > t.first && this.foldWidgets.splice(t.first, this.foldWidgets.length) } } var r = e("../range").Range, i = e("./fold_line").FoldLine, s = e("./fold").Fold, o = e("../token_iterator").TokenIterator; t.Folding = u }), define("ace/edit_session/bracket_match", ["require", "exports", "module", "ace/token_iterator", "ace/range"], function (e, t, n) { "use strict"; function s() { this.findMatchingBracket = function (e, t) { if (e.column == 0) return null; var n = t || this.getLine(e.row).charAt(e.column - 1); if (n == "") return null; var r = n.match(/([\(\[\{])|([\)\]\}])/); return r ? r[1] ? this.$findClosingBracket(r[1], e) : this.$findOpeningBracket(r[2], e) : null }, this.getBracketRange = function (e) { var t = this.getLine(e.row), n = !0, r, s = t.charAt(e.column - 1), o = s && s.match(/([\(\[\{])|([\)\]\}])/); o || (s = t.charAt(e.column), e = { row: e.row, column: e.column + 1 }, o = s && s.match(/([\(\[\{])|([\)\]\}])/), n = !1); if (!o) return null; if (o[1]) { var u = this.$findClosingBracket(o[1], e); if (!u) return null; r = i.fromPoints(e, u), n || (r.end.column++, r.start.column--), r.cursor = r.end } else { var u = this.$findOpeningBracket(o[2], e); if (!u) return null; r = i.fromPoints(u, e), n || (r.start.column++, r.end.column--), r.cursor = r.start } return r }, this.getMatchingBracketRanges = function (e) { var t = this.getLine(e.row), n = t.charAt(e.column - 1), r = n && n.match(/([\(\[\{])|([\)\]\}])/); r || (n = t.charAt(e.column), e = { row: e.row, column: e.column + 1 }, r = n && n.match(/([\(\[\{])|([\)\]\}])/)); if (!r) return null; var s = new i(e.row, e.column - 1, e.row, e.column), o = r[1] ? this.$findClosingBracket(r[1], e) : this.$findOpeningBracket(r[2], e); if (!o) return [s]; var u = new i(o.row, o.column, o.row, o.column + 1); return [s, u] }, this.$brackets = { ")": "(", "(": ")", "]": "[", "[": "]", "{": "}", "}": "{", "<": ">", ">": "<" }, this.$findOpeningBracket = function (e, t, n) { var i = this.$brackets[e], s = 1, o = new r(this, t.row, t.column), u = o.getCurrentToken(); u || (u = o.stepForward()); if (!u) return; n || (n = new RegExp("(\\.?" + u.type.replace(".", "\\.").replace("rparen", ".paren").replace(/\b(?:end)\b/, "(?:start|begin|end)") + ")+")); var a = t.column - o.getCurrentTokenColumn() - 2, f = u.value; for (; ;) { while (a >= 0) { var l = f.charAt(a); if (l == i) { s -= 1; if (s == 0) return { row: o.getCurrentTokenRow(), column: a + o.getCurrentTokenColumn() } } else l == e && (s += 1); a -= 1 } do u = o.stepBackward(); while (u && !n.test(u.type)); if (u == null) break; f = u.value, a = f.length - 1 } return null }, this.$findClosingBracket = function (e, t, n) { var i = this.$brackets[e], s = 1, o = new r(this, t.row, t.column), u = o.getCurrentToken(); u || (u = o.stepForward()); if (!u) return; n || (n = new RegExp("(\\.?" + u.type.replace(".", "\\.").replace("lparen", ".paren").replace(/\b(?:start|begin)\b/, "(?:start|begin|end)") + ")+")); var a = t.column - o.getCurrentTokenColumn(); for (; ;) { var f = u.value, l = f.length; while (a < l) { var c = f.charAt(a); if (c == i) { s -= 1; if (s == 0) return { row: o.getCurrentTokenRow(), column: a + o.getCurrentTokenColumn() } } else c == e && (s += 1); a += 1 } do u = o.stepForward(); while (u && !n.test(u.type)); if (u == null) break; a = 0 } return null } } var r = e("../token_iterator").TokenIterator, i = e("../range").Range; t.BracketMatch = s }), define("ace/edit_session", ["require", "exports", "module", "ace/lib/oop", "ace/lib/lang", "ace/bidihandler", "ace/config", "ace/lib/event_emitter", "ace/selection", "ace/mode/text", "ace/range", "ace/document", "ace/background_tokenizer", "ace/search_highlight", "ace/edit_session/folding", "ace/edit_session/bracket_match"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./lib/lang"), s = e("./bidihandler").BidiHandler, o = e("./config"), u = e("./lib/event_emitter").EventEmitter, a = e("./selection").Selection, f = e("./mode/text").Mode, l = e("./range").Range, c = e("./document").Document, h = e("./background_tokenizer").BackgroundTokenizer, p = e("./search_highlight").SearchHighlight, d = function (e, t) { this.$breakpoints = [], this.$decorations = [], this.$frontMarkers = {}, this.$backMarkers = {}, this.$markerId = 1, this.$undoSelect = !0, this.$foldData = [], this.id = "session" + ++d.$uid, this.$foldData.toString = function () { return this.join("\n") }, this.on("changeFold", this.onChangeFold.bind(this)), this.$onChange = this.onChange.bind(this); if (typeof e != "object" || !e.getLine) e = new c(e); this.setDocument(e), this.selection = new a(this), this.$bidiHandler = new s(this), o.resetOptions(this), this.setMode(t), o._signal("session", this) }; d.$uid = 0, function () { function m(e) { return e < 4352 ? !1 : e >= 4352 && e <= 4447 || e >= 4515 && e <= 4519 || e >= 4602 && e <= 4607 || e >= 9001 && e <= 9002 || e >= 11904 && e <= 11929 || e >= 11931 && e <= 12019 || e >= 12032 && e <= 12245 || e >= 12272 && e <= 12283 || e >= 12288 && e <= 12350 || e >= 12353 && e <= 12438 || e >= 12441 && e <= 12543 || e >= 12549 && e <= 12589 || e >= 12593 && e <= 12686 || e >= 12688 && e <= 12730 || e >= 12736 && e <= 12771 || e >= 12784 && e <= 12830 || e >= 12832 && e <= 12871 || e >= 12880 && e <= 13054 || e >= 13056 && e <= 19903 || e >= 19968 && e <= 42124 || e >= 42128 && e <= 42182 || e >= 43360 && e <= 43388 || e >= 44032 && e <= 55203 || e >= 55216 && e <= 55238 || e >= 55243 && e <= 55291 || e >= 63744 && e <= 64255 || e >= 65040 && e <= 65049 || e >= 65072 && e <= 65106 || e >= 65108 && e <= 65126 || e >= 65128 && e <= 65131 || e >= 65281 && e <= 65376 || e >= 65504 && e <= 65510 } r.implement(this, u), this.setDocument = function (e) { this.doc && this.doc.removeListener("change", this.$onChange), this.doc = e, e.on("change", this.$onChange), this.bgTokenizer && this.bgTokenizer.setDocument(this.getDocument()), this.resetCaches() }, this.getDocument = function () { return this.doc }, this.$resetRowCache = function (e) { if (!e) { this.$docRowCache = [], this.$screenRowCache = []; return } var t = this.$docRowCache.length, n = this.$getRowCacheIndex(this.$docRowCache, e) + 1; t > n && (this.$docRowCache.splice(n, t), this.$screenRowCache.splice(n, t)) }, this.$getRowCacheIndex = function (e, t) { var n = 0, r = e.length - 1; while (n <= r) { var i = n + r >> 1, s = e[i]; if (t > s) n = i + 1; else { if (!(t < s)) return i; r = i - 1 } } return n - 1 }, this.resetCaches = function () { this.$modified = !0, this.$wrapData = [], this.$rowLengthCache = [], this.$resetRowCache(0), this.bgTokenizer && this.bgTokenizer.start(0) }, this.onChangeFold = function (e) { var t = e.data; this.$resetRowCache(t.start.row) }, this.onChange = function (e) { this.$modified = !0, this.$bidiHandler.onChange(e), this.$resetRowCache(e.start.row); var t = this.$updateInternalDataOnChange(e); !this.$fromUndo && this.$undoManager && (t && t.length && (this.$undoManager.add({ action: "removeFolds", folds: t }, this.mergeUndoDeltas), this.mergeUndoDeltas = !0), this.$undoManager.add(e, this.mergeUndoDeltas), this.mergeUndoDeltas = !0, this.$informUndoManager.schedule()), this.bgTokenizer && this.bgTokenizer.$updateOnChange(e), this._signal("change", e) }, this.setValue = function (e) { this.doc.setValue(e), this.selection.moveTo(0, 0), this.$resetRowCache(0), this.setUndoManager(this.$undoManager), this.getUndoManager().reset() }, this.getValue = this.toString = function () { return this.doc.getValue() }, this.getSelection = function () { return this.selection }, this.getState = function (e) { return this.bgTokenizer.getState(e) }, this.getTokens = function (e) { return this.bgTokenizer.getTokens(e) }, this.getTokenAt = function (e, t) { var n = this.bgTokenizer.getTokens(e), r, i = 0; if (t == null) { var s = n.length - 1; i = this.getLine(e).length } else for (var s = 0; s < n.length; s++) { i += n[s].value.length; if (i >= t) break } return r = n[s], r ? (r.index = s, r.start = i - r.value.length, r) : null }, this.setUndoManager = function (e) { this.$undoManager = e, this.$informUndoManager && this.$informUndoManager.cancel(); if (e) { var t = this; e.addSession(this), this.$syncInformUndoManager = function () { t.$informUndoManager.cancel(), t.mergeUndoDeltas = !1 }, this.$informUndoManager = i.delayedCall(this.$syncInformUndoManager) } else this.$syncInformUndoManager = function () { } }, this.markUndoGroup = function () { this.$syncInformUndoManager && this.$syncInformUndoManager() }, this.$defaultUndoManager = { undo: function () { }, redo: function () { }, hasUndo: function () { }, hasRedo: function () { }, reset: function () { }, add: function () { }, addSelection: function () { }, startNewGroup: function () { }, addSession: function () { } }, this.getUndoManager = function () { return this.$undoManager || this.$defaultUndoManager }, this.getTabString = function () { return this.getUseSoftTabs() ? i.stringRepeat(" ", this.getTabSize()) : " " }, this.setUseSoftTabs = function (e) { this.setOption("useSoftTabs", e) }, this.getUseSoftTabs = function () { return this.$useSoftTabs && !this.$mode.$indentWithTabs }, this.setTabSize = function (e) { this.setOption("tabSize", e) }, this.getTabSize = function () { return this.$tabSize }, this.isTabStop = function (e) { return this.$useSoftTabs && e.column % this.$tabSize === 0 }, this.setNavigateWithinSoftTabs = function (e) { this.setOption("navigateWithinSoftTabs", e) }, this.getNavigateWithinSoftTabs = function () { return this.$navigateWithinSoftTabs }, this.$overwrite = !1, this.setOverwrite = function (e) { this.setOption("overwrite", e) }, this.getOverwrite = function () { return this.$overwrite }, this.toggleOverwrite = function () { this.setOverwrite(!this.$overwrite) }, this.addGutterDecoration = function (e, t) { this.$decorations[e] || (this.$decorations[e] = ""), this.$decorations[e] += " " + t, this._signal("changeBreakpoint", {}) }, this.removeGutterDecoration = function (e, t) { this.$decorations[e] = (this.$decorations[e] || "").replace(" " + t, ""), this._signal("changeBreakpoint", {}) }, this.getBreakpoints = function () { return this.$breakpoints }, this.setBreakpoints = function (e) { this.$breakpoints = []; for (var t = 0; t < e.length; t++)this.$breakpoints[e[t]] = "ace_breakpoint"; this._signal("changeBreakpoint", {}) }, this.clearBreakpoints = function () { this.$breakpoints = [], this._signal("changeBreakpoint", {}) }, this.setBreakpoint = function (e, t) { t === undefined && (t = "ace_breakpoint"), t ? this.$breakpoints[e] = t : delete this.$breakpoints[e], this._signal("changeBreakpoint", {}) }, this.clearBreakpoint = function (e) { delete this.$breakpoints[e], this._signal("changeBreakpoint", {}) }, this.addMarker = function (e, t, n, r) { var i = this.$markerId++, s = { range: e, type: n || "line", renderer: typeof n == "function" ? n : null, clazz: t, inFront: !!r, id: i }; return r ? (this.$frontMarkers[i] = s, this._signal("changeFrontMarker")) : (this.$backMarkers[i] = s, this._signal("changeBackMarker")), i }, this.addDynamicMarker = function (e, t) { if (!e.update) return; var n = this.$markerId++; return e.id = n, e.inFront = !!t, t ? (this.$frontMarkers[n] = e, this._signal("changeFrontMarker")) : (this.$backMarkers[n] = e, this._signal("changeBackMarker")), e }, this.removeMarker = function (e) { var t = this.$frontMarkers[e] || this.$backMarkers[e]; if (!t) return; var n = t.inFront ? this.$frontMarkers : this.$backMarkers; delete n[e], this._signal(t.inFront ? "changeFrontMarker" : "changeBackMarker") }, this.getMarkers = function (e) { return e ? this.$frontMarkers : this.$backMarkers }, this.highlight = function (e) { if (!this.$searchHighlight) { var t = new p(null, "ace_selected-word", "text"); this.$searchHighlight = this.addDynamicMarker(t) } this.$searchHighlight.setRegexp(e) }, this.highlightLines = function (e, t, n, r) { typeof t != "number" && (n = t, t = e), n || (n = "ace_step"); var i = new l(e, 0, t, Infinity); return i.id = this.addMarker(i, n, "fullLine", r), i }, this.setAnnotations = function (e) { this.$annotations = e, this._signal("changeAnnotation", {}) }, this.getAnnotations = function () { return this.$annotations || [] }, this.clearAnnotations = function () { this.setAnnotations([]) }, this.$detectNewLine = function (e) { var t = e.match(/^.*?(\r?\n)/m); t ? this.$autoNewLine = t[1] : this.$autoNewLine = "\n" }, this.getWordRange = function (e, t) { var n = this.getLine(e), r = !1; t > 0 && (r = !!n.charAt(t - 1).match(this.tokenRe)), r || (r = !!n.charAt(t).match(this.tokenRe)); if (r) var i = this.tokenRe; else if (/^\s+$/.test(n.slice(t - 1, t + 1))) var i = /\s/; else var i = this.nonTokenRe; var s = t; if (s > 0) { do s--; while (s >= 0 && n.charAt(s).match(i)); s++ } var o = t; while (o < n.length && n.charAt(o).match(i)) o++; return new l(e, s, e, o) }, this.getAWordRange = function (e, t) { var n = this.getWordRange(e, t), r = this.getLine(n.end.row); while (r.charAt(n.end.column).match(/[ \t]/)) n.end.column += 1; return n }, this.setNewLineMode = function (e) { this.doc.setNewLineMode(e) }, this.getNewLineMode = function () { return this.doc.getNewLineMode() }, this.setUseWorker = function (e) { this.setOption("useWorker", e) }, this.getUseWorker = function () { return this.$useWorker }, this.onReloadTokenizer = function (e) { var t = e.data; this.bgTokenizer.start(t.first), this._signal("tokenizerUpdate", e) }, this.$modes = o.$modes, this.$mode = null, this.$modeId = null, this.setMode = function (e, t) { if (e && typeof e == "object") { if (e.getTokenizer) return this.$onChangeMode(e); var n = e, r = n.path } else r = e || "ace/mode/text"; this.$modes["ace/mode/text"] || (this.$modes["ace/mode/text"] = new f); if (this.$modes[r] && !n) { this.$onChangeMode(this.$modes[r]), t && t(); return } this.$modeId = r, o.loadModule(["mode", r], function (e) { if (this.$modeId !== r) return t && t(); this.$modes[r] && !n ? this.$onChangeMode(this.$modes[r]) : e && e.Mode && (e = new e.Mode(n), n || (this.$modes[r] = e, e.$id = r), this.$onChangeMode(e)), t && t() }.bind(this)), this.$mode || this.$onChangeMode(this.$modes["ace/mode/text"], !0) }, this.$onChangeMode = function (e, t) { t || (this.$modeId = e.$id); if (this.$mode === e) return; var n = this.$mode; this.$mode = e, this.$stopWorker(), this.$useWorker && this.$startWorker(); var r = e.getTokenizer(); if (r.on !== undefined) { var i = this.onReloadTokenizer.bind(this); r.on("update", i) } if (!this.bgTokenizer) { this.bgTokenizer = new h(r); var s = this; this.bgTokenizer.on("update", function (e) { s._signal("tokenizerUpdate", e) }) } else this.bgTokenizer.setTokenizer(r); this.bgTokenizer.setDocument(this.getDocument()), this.tokenRe = e.tokenRe, this.nonTokenRe = e.nonTokenRe, t || (e.attachToSession && e.attachToSession(this), this.$options.wrapMethod.set.call(this, this.$wrapMethod), this.$setFolding(e.foldingRules), this.bgTokenizer.start(0), this._emit("changeMode", { oldMode: n, mode: e })) }, this.$stopWorker = function () { this.$worker && (this.$worker.terminate(), this.$worker = null) }, this.$startWorker = function () { try { this.$worker = this.$mode.createWorker(this) } catch (e) { o.warn("Could not load worker", e), this.$worker = null } }, this.getMode = function () { return this.$mode }, this.$scrollTop = 0, this.setScrollTop = function (e) { if (this.$scrollTop === e || isNaN(e)) return; this.$scrollTop = e, this._signal("changeScrollTop", e) }, this.getScrollTop = function () { return this.$scrollTop }, this.$scrollLeft = 0, this.setScrollLeft = function (e) { if (this.$scrollLeft === e || isNaN(e)) return; this.$scrollLeft = e, this._signal("changeScrollLeft", e) }, this.getScrollLeft = function () { return this.$scrollLeft }, this.getScreenWidth = function () { return this.$computeWidth(), this.lineWidgets ? Math.max(this.getLineWidgetMaxWidth(), this.screenWidth) : this.screenWidth }, this.getLineWidgetMaxWidth = function () { if (this.lineWidgetsWidth != null) return this.lineWidgetsWidth; var e = 0; return this.lineWidgets.forEach(function (t) { t && t.screenWidth > e && (e = t.screenWidth) }), this.lineWidgetWidth = e }, this.$computeWidth = function (e) { if (this.$modified || e) { this.$modified = !1; if (this.$useWrapMode) return this.screenWidth = this.$wrapLimit; var t = this.doc.getAllLines(), n = this.$rowLengthCache, r = 0, i = 0, s = this.$foldData[i], o = s ? s.start.row : Infinity, u = t.length; for (var a = 0; a < u; a++) { if (a > o) { a = s.end.row + 1; if (a >= u) break; s = this.$foldData[i++], o = s ? s.start.row : Infinity } n[a] == null && (n[a] = this.$getStringScreenWidth(t[a])[0]), n[a] > r && (r = n[a]) } this.screenWidth = r } }, this.getLine = function (e) { return this.doc.getLine(e) }, this.getLines = function (e, t) { return this.doc.getLines(e, t) }, this.getLength = function () { return this.doc.getLength() }, this.getTextRange = function (e) { return this.doc.getTextRange(e || this.selection.getRange()) }, this.insert = function (e, t) { return this.doc.insert(e, t) }, this.remove = function (e) { return this.doc.remove(e) }, this.removeFullLines = function (e, t) { return this.doc.removeFullLines(e, t) }, this.undoChanges = function (e, t) { if (!e.length) return; this.$fromUndo = !0; for (var n = e.length - 1; n != -1; n--) { var r = e[n]; r.action == "insert" || r.action == "remove" ? this.doc.revertDelta(r) : r.folds && this.addFolds(r.folds) } !t && this.$undoSelect && (e.selectionBefore ? this.selection.fromJSON(e.selectionBefore) : this.selection.setRange(this.$getUndoSelection(e, !0))), this.$fromUndo = !1 }, this.redoChanges = function (e, t) { if (!e.length) return; this.$fromUndo = !0; for (var n = 0; n < e.length; n++) { var r = e[n]; (r.action == "insert" || r.action == "remove") && this.doc.$safeApplyDelta(r) } !t && this.$undoSelect && (e.selectionAfter ? this.selection.fromJSON(e.selectionAfter) : this.selection.setRange(this.$getUndoSelection(e, !1))), this.$fromUndo = !1 }, this.setUndoSelect = function (e) { this.$undoSelect = e }, this.$getUndoSelection = function (e, t) { function n(e) { return t ? e.action !== "insert" : e.action === "insert" } var r, i; for (var s = 0; s < e.length; s++) { var o = e[s]; if (!o.start) continue; if (!r) { n(o) ? r = l.fromPoints(o.start, o.end) : r = l.fromPoints(o.start, o.start); continue } n(o) ? (i = o.start, r.compare(i.row, i.column) == -1 && r.setStart(i), i = o.end, r.compare(i.row, i.column) == 1 && r.setEnd(i)) : (i = o.start, r.compare(i.row, i.column) == -1 && (r = l.fromPoints(o.start, o.start))) } return r }, this.replace = function (e, t) { return this.doc.replace(e, t) }, this.moveText = function (e, t, n) { var r = this.getTextRange(e), i = this.getFoldsInRange(e), s = l.fromPoints(t, t); if (!n) { this.remove(e); var o = e.start.row - e.end.row, u = o ? -e.end.column : e.start.column - e.end.column; u && (s.start.row == e.end.row && s.start.column > e.end.column && (s.start.column += u), s.end.row == e.end.row && s.end.column > e.end.column && (s.end.column += u)), o && s.start.row >= e.end.row && (s.start.row += o, s.end.row += o) } s.end = this.insert(s.start, r); if (i.length) { var a = e.start, f = s.start, o = f.row - a.row, u = f.column - a.column; this.addFolds(i.map(function (e) { return e = e.clone(), e.start.row == a.row && (e.start.column += u), e.end.row == a.row && (e.end.column += u), e.start.row += o, e.end.row += o, e })) } return s }, this.indentRows = function (e, t, n) { n = n.replace(/\t/g, this.getTabString()); for (var r = e; r <= t; r++)this.doc.insertInLine({ row: r, column: 0 }, n) }, this.outdentRows = function (e) { var t = e.collapseRows(), n = new l(0, 0, 0, 0), r = this.getTabSize(); for (var i = t.start.row; i <= t.end.row; ++i) { var s = this.getLine(i); n.start.row = i, n.end.row = i; for (var o = 0; o < r; ++o)if (s.charAt(o) != " ") break; o < r && s.charAt(o) == " " ? (n.start.column = o, n.end.column = o + 1) : (n.start.column = 0, n.end.column = o), this.remove(n) } }, this.$moveLines = function (e, t, n) { e = this.getRowFoldStart(e), t = this.getRowFoldEnd(t); if (n < 0) { var r = this.getRowFoldStart(e + n); if (r < 0) return 0; var i = r - e } else if (n > 0) { var r = this.getRowFoldEnd(t + n); if (r > this.doc.getLength() - 1) return 0; var i = r - t } else { e = this.$clipRowToDocument(e), t = this.$clipRowToDocument(t); var i = t - e + 1 } var s = new l(e, 0, t, Number.MAX_VALUE), o = this.getFoldsInRange(s).map(function (e) { return e = e.clone(), e.start.row += i, e.end.row += i, e }), u = n == 0 ? this.doc.getLines(e, t) : this.doc.removeFullLines(e, t); return this.doc.insertFullLines(e + i, u), o.length && this.addFolds(o), i }, this.moveLinesUp = function (e, t) { return this.$moveLines(e, t, -1) }, this.moveLinesDown = function (e, t) { return this.$moveLines(e, t, 1) }, this.duplicateLines = function (e, t) { return this.$moveLines(e, t, 0) }, this.$clipRowToDocument = function (e) { return Math.max(0, Math.min(e, this.doc.getLength() - 1)) }, this.$clipColumnToRow = function (e, t) { return t < 0 ? 0 : Math.min(this.doc.getLine(e).length, t) }, this.$clipPositionToDocument = function (e, t) { t = Math.max(0, t); if (e < 0) e = 0, t = 0; else { var n = this.doc.getLength(); e >= n ? (e = n - 1, t = this.doc.getLine(n - 1).length) : t = Math.min(this.doc.getLine(e).length, t) } return { row: e, column: t } }, this.$clipRangeToDocument = function (e) { e.start.row < 0 ? (e.start.row = 0, e.start.column = 0) : e.start.column = this.$clipColumnToRow(e.start.row, e.start.column); var t = this.doc.getLength() - 1; return e.end.row > t ? (e.end.row = t, e.end.column = this.doc.getLine(t).length) : e.end.column = this.$clipColumnToRow(e.end.row, e.end.column), e }, this.$wrapLimit = 80, this.$useWrapMode = !1, this.$wrapLimitRange = { min: null, max: null }, this.setUseWrapMode = function (e) { if (e != this.$useWrapMode) { this.$useWrapMode = e, this.$modified = !0, this.$resetRowCache(0); if (e) { var t = this.getLength(); this.$wrapData = Array(t), this.$updateWrapData(0, t - 1) } this._signal("changeWrapMode") } }, this.getUseWrapMode = function () { return this.$useWrapMode }, this.setWrapLimitRange = function (e, t) { if (this.$wrapLimitRange.min !== e || this.$wrapLimitRange.max !== t) this.$wrapLimitRange = { min: e, max: t }, this.$modified = !0, this.$bidiHandler.markAsDirty(), this.$useWrapMode && this._signal("changeWrapMode") }, this.adjustWrapLimit = function (e, t) { var n = this.$wrapLimitRange; n.max < 0 && (n = { min: t, max: t }); var r = this.$constrainWrapLimit(e, n.min, n.max); return r != this.$wrapLimit && r > 1 ? (this.$wrapLimit = r, this.$modified = !0, this.$useWrapMode && (this.$updateWrapData(0, this.getLength() - 1), this.$resetRowCache(0), this._signal("changeWrapLimit")), !0) : !1 }, this.$constrainWrapLimit = function (e, t, n) { return t && (e = Math.max(t, e)), n && (e = Math.min(n, e)), e }, this.getWrapLimit = function () { return this.$wrapLimit }, this.setWrapLimit = function (e) { this.setWrapLimitRange(e, e) }, this.getWrapLimitRange = function () { return { min: this.$wrapLimitRange.min, max: this.$wrapLimitRange.max } }, this.$updateInternalDataOnChange = function (e) { var t = this.$useWrapMode, n = e.action, r = e.start, i = e.end, s = r.row, o = i.row, u = o - s, a = null; this.$updating = !0; if (u != 0) if (n === "remove") { this[t ? "$wrapData" : "$rowLengthCache"].splice(s, u); var f = this.$foldData; a = this.getFoldsInRange(e), this.removeFolds(a); var l = this.getFoldLine(i.row), c = 0; if (l) { l.addRemoveChars(i.row, i.column, r.column - i.column), l.shiftRow(-u); var h = this.getFoldLine(s); h && h !== l && (h.merge(l), l = h), c = f.indexOf(l) + 1 } for (c; c < f.length; c++) { var l = f[c]; l.start.row >= i.row && l.shiftRow(-u) } o = s } else { var p = Array(u); p.unshift(s, 0); var d = t ? this.$wrapData : this.$rowLengthCache; d.splice.apply(d, p); var f = this.$foldData, l = this.getFoldLine(s), c = 0; if (l) { var v = l.range.compareInside(r.row, r.column); v == 0 ? (l = l.split(r.row, r.column), l && (l.shiftRow(u), l.addRemoveChars(o, 0, i.column - r.column))) : v == -1 && (l.addRemoveChars(s, 0, i.column - r.column), l.shiftRow(u)), c = f.indexOf(l) + 1 } for (c; c < f.length; c++) { var l = f[c]; l.start.row >= s && l.shiftRow(u) } } else { u = Math.abs(e.start.column - e.end.column), n === "remove" && (a = this.getFoldsInRange(e), this.removeFolds(a), u = -u); var l = this.getFoldLine(s); l && l.addRemoveChars(s, r.column, u) } return t && this.$wrapData.length != this.doc.getLength() && console.error("doc.getLength() and $wrapData.length have to be the same!"), this.$updating = !1, t ? this.$updateWrapData(s, o) : this.$updateRowLengthCache(s, o), a }, this.$updateRowLengthCache = function (e, t, n) { this.$rowLengthCache[e] = null, this.$rowLengthCache[t] = null }, this.$updateWrapData = function (e, t) { var r = this.doc.getAllLines(), i = this.getTabSize(), o = this.$wrapData, u = this.$wrapLimit, a, f, l = e; t = Math.min(t, r.length - 1); while (l <= t) f = this.getFoldLine(l, f), f ? (a = [], f.walk(function (e, t, i, o) { var u; if (e != null) { u = this.$getDisplayTokens(e, a.length), u[0] = n; for (var f = 1; f < u.length; f++)u[f] = s } else u = this.$getDisplayTokens(r[t].substring(o, i), a.length); a = a.concat(u) }.bind(this), f.end.row, r[f.end.row].length + 1), o[f.start.row] = this.$computeWrapSplits(a, u, i), l = f.end.row + 1) : (a = this.$getDisplayTokens(r[l]), o[l] = this.$computeWrapSplits(a, u, i), l++) }; var e = 1, t = 2, n = 3, s = 4, a = 9, c = 10, d = 11, v = 12; this.$computeWrapSplits = function (e, r, i) { function g() { var t = 0; if (m === 0) return t; if (p) for (var n = 0; n < e.length; n++) { var r = e[n]; if (r == c) t += 1; else { if (r != d) { if (r == v) continue; break } t += i } } return h && p !== !1 && (t += i), Math.min(t, m) } function y(t) { var n = t - f; for (var r = f; r < t; r++) { var i = e[r]; if (i === 12 || i === 2) n -= 1 } o.length || (b = g(), o.indent = b), l += n, o.push(l), f = t } if (e.length == 0) return []; var o = [], u = e.length, f = 0, l = 0, h = this.$wrapAsCode, p = this.$indentedSoftWrap, m = r <= Math.max(2 * i, 8) || p === !1 ? 0 : Math.floor(r / 2), b = 0; while (u - f > r - b) { var w = f + r - b; if (e[w - 1] >= c && e[w] >= c) { y(w); continue } if (e[w] == n || e[w] == s) { for (w; w != f - 1; w--)if (e[w] == n) break; if (w > f) { y(w); continue } w = f + r; for (w; w < e.length; w++)if (e[w] != s) break; if (w == e.length) break; y(w); continue } var E = Math.max(w - (r - (r >> 2)), f - 1); while (w > E && e[w] < n) w--; if (h) { while (w > E && e[w] < n) w--; while (w > E && e[w] == a) w-- } else while (w > E && e[w] < c) w--; if (w > E) { y(++w); continue } w = f + r, e[w] == t && w--, y(w - b) } return o }, this.$getDisplayTokens = function (n, r) { var i = [], s; r = r || 0; for (var o = 0; o < n.length; o++) { var u = n.charCodeAt(o); if (u == 9) { s = this.getScreenTabSize(i.length + r), i.push(d); for (var f = 1; f < s; f++)i.push(v) } else u == 32 ? i.push(c) : u > 39 && u < 48 || u > 57 && u < 64 ? i.push(a) : u >= 4352 && m(u) ? i.push(e, t) : i.push(e) } return i }, this.$getStringScreenWidth = function (e, t, n) { if (t == 0) return [0, 0]; t == null && (t = Infinity), n = n || 0; var r, i; for (i = 0; i < e.length; i++) { r = e.charCodeAt(i), r == 9 ? n += this.getScreenTabSize(n) : r >= 4352 && m(r) ? n += 2 : n += 1; if (n > t) break } return [n, i] }, this.lineWidgets = null, this.getRowLength = function (e) { var t = 1; return this.lineWidgets && (t += this.lineWidgets[e] && this.lineWidgets[e].rowCount || 0), !this.$useWrapMode || !this.$wrapData[e] ? t : this.$wrapData[e].length + t }, this.getRowLineCount = function (e) { return !this.$useWrapMode || !this.$wrapData[e] ? 1 : this.$wrapData[e].length + 1 }, this.getRowWrapIndent = function (e) { if (this.$useWrapMode) { var t = this.screenToDocumentPosition(e, Number.MAX_VALUE), n = this.$wrapData[t.row]; return n.length && n[0] < t.column ? n.indent : 0 } return 0 }, this.getScreenLastRowColumn = function (e) { var t = this.screenToDocumentPosition(e, Number.MAX_VALUE); return this.documentToScreenColumn(t.row, t.column) }, this.getDocumentLastRowColumn = function (e, t) { var n = this.documentToScreenRow(e, t); return this.getScreenLastRowColumn(n) }, this.getDocumentLastRowColumnPosition = function (e, t) { var n = this.documentToScreenRow(e, t); return this.screenToDocumentPosition(n, Number.MAX_VALUE / 10) }, this.getRowSplitData = function (e) { return this.$useWrapMode ? this.$wrapData[e] : undefined }, this.getScreenTabSize = function (e) { return this.$tabSize - (e % this.$tabSize | 0) }, this.screenToDocumentRow = function (e, t) { return this.screenToDocumentPosition(e, t).row }, this.screenToDocumentColumn = function (e, t) { return this.screenToDocumentPosition(e, t).column }, this.screenToDocumentPosition = function (e, t, n) { if (e < 0) return { row: 0, column: 0 }; var r, i = 0, s = 0, o, u = 0, a = 0, f = this.$screenRowCache, l = this.$getRowCacheIndex(f, e), c = f.length; if (c && l >= 0) var u = f[l], i = this.$docRowCache[l], h = e > f[c - 1]; else var h = !c; var p = this.getLength() - 1, d = this.getNextFoldLine(i), v = d ? d.start.row : Infinity; while (u <= e) { a = this.getRowLength(i); if (u + a > e || i >= p) break; u += a, i++, i > v && (i = d.end.row + 1, d = this.getNextFoldLine(i, d), v = d ? d.start.row : Infinity), h && (this.$docRowCache.push(i), this.$screenRowCache.push(u)) } if (d && d.start.row <= i) r = this.getFoldDisplayLine(d), i = d.start.row; else { if (u + a <= e || i > p) return { row: p, column: this.getLine(p).length }; r = this.getLine(i), d = null } var m = 0, g = Math.floor(e - u); if (this.$useWrapMode) { var y = this.$wrapData[i]; y && (o = y[g], g > 0 && y.length && (m = y.indent, s = y[g - 1] || y[y.length - 1], r = r.substring(s))) } return n !== undefined && this.$bidiHandler.isBidiRow(u + g, i, g) && (t = this.$bidiHandler.offsetToCol(n)), s += this.$getStringScreenWidth(r, t - m)[1], this.$useWrapMode && s >= o && (s = o - 1), d ? d.idxToPosition(s) : { row: i, column: s } }, this.documentToScreenPosition = function (e, t) { if (typeof t == "undefined") var n = this.$clipPositionToDocument(e.row, e.column); else n = this.$clipPositionToDocument(e, t); e = n.row, t = n.column; var r = 0, i = null, s = null; s = this.getFoldAt(e, t, 1), s && (e = s.start.row, t = s.start.column); var o, u = 0, a = this.$docRowCache, f = this.$getRowCacheIndex(a, e), l = a.length; if (l && f >= 0) var u = a[f], r = this.$screenRowCache[f], c = e > a[l - 1]; else var c = !l; var h = this.getNextFoldLine(u), p = h ? h.start.row : Infinity; while (u < e) { if (u >= p) { o = h.end.row + 1; if (o > e) break; h = this.getNextFoldLine(o, h), p = h ? h.start.row : Infinity } else o = u + 1; r += this.getRowLength(u), u = o, c && (this.$docRowCache.push(u), this.$screenRowCache.push(r)) } var d = ""; h && u >= p ? (d = this.getFoldDisplayLine(h, e, t), i = h.start.row) : (d = this.getLine(e).substring(0, t), i = e); var v = 0; if (this.$useWrapMode) { var m = this.$wrapData[i]; if (m) { var g = 0; while (d.length >= m[g]) r++, g++; d = d.substring(m[g - 1] || 0, d.length), v = g > 0 ? m.indent : 0 } } return this.lineWidgets && this.lineWidgets[u] && this.lineWidgets[u].rowsAbove && (r += this.lineWidgets[u].rowsAbove), { row: r, column: v + this.$getStringScreenWidth(d)[0] } }, this.documentToScreenColumn = function (e, t) { return this.documentToScreenPosition(e, t).column }, this.documentToScreenRow = function (e, t) { return this.documentToScreenPosition(e, t).row }, this.getScreenLength = function () { var e = 0, t = null; if (!this.$useWrapMode) { e = this.getLength(); var n = this.$foldData; for (var r = 0; r < n.length; r++)t = n[r], e -= t.end.row - t.start.row } else { var i = this.$wrapData.length, s = 0, r = 0, t = this.$foldData[r++], o = t ? t.start.row : Infinity; while (s < i) { var u = this.$wrapData[s]; e += u ? u.length + 1 : 1, s++, s > o && (s = t.end.row + 1, t = this.$foldData[r++], o = t ? t.start.row : Infinity) } } return this.lineWidgets && (e += this.$getWidgetScreenLength()), e }, this.$setFontMetrics = function (e) { if (!this.$enableVarChar) return; this.$getStringScreenWidth = function (t, n, r) { if (n === 0) return [0, 0]; n || (n = Infinity), r = r || 0; var i, s; for (s = 0; s < t.length; s++) { i = t.charAt(s), i === " " ? r += this.getScreenTabSize(r) : r += e.getCharacterWidth(i); if (r > n) break } return [r, s] } }, this.destroy = function () { this.bgTokenizer && (this.bgTokenizer.setDocument(null), this.bgTokenizer = null), this.$stopWorker(), this.removeAllListeners(), this.selection.detach() }, this.isFullWidth = m }.call(d.prototype), e("./edit_session/folding").Folding.call(d.prototype), e("./edit_session/bracket_match").BracketMatch.call(d.prototype), o.defineOptions(d.prototype, "session", { wrap: { set: function (e) { !e || e == "off" ? e = !1 : e == "free" ? e = !0 : e == "printMargin" ? e = -1 : typeof e == "string" && (e = parseInt(e, 10) || !1); if (this.$wrap == e) return; this.$wrap = e; if (!e) this.setUseWrapMode(!1); else { var t = typeof e == "number" ? e : null; this.setWrapLimitRange(t, t), this.setUseWrapMode(!0) } }, get: function () { return this.getUseWrapMode() ? this.$wrap == -1 ? "printMargin" : this.getWrapLimitRange().min ? this.$wrap : "free" : "off" }, handlesSet: !0 }, wrapMethod: { set: function (e) { e = e == "auto" ? this.$mode.type != "text" : e != "text", e != this.$wrapAsCode && (this.$wrapAsCode = e, this.$useWrapMode && (this.$useWrapMode = !1, this.setUseWrapMode(!0))) }, initialValue: "auto" }, indentedSoftWrap: { set: function () { this.$useWrapMode && (this.$useWrapMode = !1, this.setUseWrapMode(!0)) }, initialValue: !0 }, firstLineNumber: { set: function () { this._signal("changeBreakpoint") }, initialValue: 1 }, useWorker: { set: function (e) { this.$useWorker = e, this.$stopWorker(), e && this.$startWorker() }, initialValue: !0 }, useSoftTabs: { initialValue: !0 }, tabSize: { set: function (e) { e = parseInt(e), e > 0 && this.$tabSize !== e && (this.$modified = !0, this.$rowLengthCache = [], this.$tabSize = e, this._signal("changeTabSize")) }, initialValue: 4, handlesSet: !0 }, navigateWithinSoftTabs: { initialValue: !1 }, foldStyle: { set: function (e) { this.setFoldStyle(e) }, handlesSet: !0 }, overwrite: { set: function (e) { this._signal("changeOverwrite") }, initialValue: !1 }, newLineMode: { set: function (e) { this.doc.setNewLineMode(e) }, get: function () { return this.doc.getNewLineMode() }, handlesSet: !0 }, mode: { set: function (e) { this.setMode(e) }, get: function () { return this.$modeId }, handlesSet: !0 } }), t.EditSession = d }), define("ace/search", ["require", "exports", "module", "ace/lib/lang", "ace/lib/oop", "ace/range"], function (e, t, n) { "use strict"; function u(e, t) { function n(e) { return /\w/.test(e) || t.regExp ? "\\b" : "" } return n(e[0]) + e + n(e[e.length - 1]) } var r = e("./lib/lang"), i = e("./lib/oop"), s = e("./range").Range, o = function () { this.$options = {} }; (function () { this.set = function (e) { return i.mixin(this.$options, e), this }, this.getOptions = function () { return r.copyObject(this.$options) }, this.setOptions = function (e) { this.$options = e }, this.find = function (e) { var t = this.$options, n = this.$matchIterator(e, t); if (!n) return !1; var r = null; return n.forEach(function (e, n, i, o) { return r = new s(e, n, i, o), n == o && t.start && t.start.start && t.skipCurrent != 0 && r.isEqual(t.start) ? (r = null, !1) : !0 }), r }, this.findAll = function (e) { var t = this.$options; if (!t.needle) return []; this.$assembleRegExp(t); var n = t.range, i = n ? e.getLines(n.start.row, n.end.row) : e.doc.getAllLines(), o = [], u = t.re; if (t.$isMultiLine) { var a = u.length, f = i.length - a, l; e: for (var c = u.offset || 0; c <= f; c++) { for (var h = 0; h < a; h++)if (i[c + h].search(u[h]) == -1) continue e; var p = i[c], d = i[c + a - 1], v = p.length - p.match(u[0])[0].length, m = d.match(u[a - 1])[0].length; if (l && l.end.row === c && l.end.column > v) continue; o.push(l = new s(c, v, c + a - 1, m)), a > 2 && (c = c + a - 2) } } else for (var g = 0; g < i.length; g++) { var y = r.getMatchOffsets(i[g], u); for (var h = 0; h < y.length; h++) { var b = y[h]; o.push(new s(g, b.offset, g, b.offset + b.length)) } } if (n) { var w = n.start.column, E = n.start.column, g = 0, h = o.length - 1; while (g < h && o[g].start.column < w && o[g].start.row == n.start.row) g++; while (g < h && o[h].end.column > E && o[h].end.row == n.end.row) h--; o = o.slice(g, h + 1); for (g = 0, h = o.length; g < h; g++)o[g].start.row += n.start.row, o[g].end.row += n.start.row } return o }, this.replace = function (e, t) { var n = this.$options, r = this.$assembleRegExp(n); if (n.$isMultiLine) return t; if (!r) return; var i = r.exec(e); if (!i || i[0].length != e.length) return null; t = e.replace(r, t); if (n.preserveCase) { t = t.split(""); for (var s = Math.min(e.length, e.length); s--;) { var o = e[s]; o && o.toLowerCase() != o ? t[s] = t[s].toUpperCase() : t[s] = t[s].toLowerCase() } t = t.join("") } return t }, this.$assembleRegExp = function (e, t) { if (e.needle instanceof RegExp) return e.re = e.needle; var n = e.needle; if (!e.needle) return e.re = !1; e.regExp || (n = r.escapeRegExp(n)), e.wholeWord && (n = u(n, e)); var i = e.caseSensitive ? "gm" : "gmi"; e.$isMultiLine = !t && /[\n\r]/.test(n); if (e.$isMultiLine) return e.re = this.$assembleMultilineRegExp(n, i); try { var s = new RegExp(n, i) } catch (o) { s = !1 } return e.re = s }, this.$assembleMultilineRegExp = function (e, t) { var n = e.replace(/\r\n|\r|\n/g, "$\n^").split("\n"), r = []; for (var i = 0; i < n.length; i++)try { r.push(new RegExp(n[i], t)) } catch (s) { return !1 } return r }, this.$matchIterator = function (e, t) { var n = this.$assembleRegExp(t); if (!n) return !1; var r = t.backwards == 1, i = t.skipCurrent != 0, s = t.range, o = t.start; o || (o = s ? s[r ? "end" : "start"] : e.selection.getRange()), o.start && (o = o[i != r ? "end" : "start"]); var u = s ? s.start.row : 0, a = s ? s.end.row : e.getLength() - 1; if (r) var f = function (e) { var n = o.row; if (c(n, o.column, e)) return; for (n--; n >= u; n--)if (c(n, Number.MAX_VALUE, e)) return; if (t.wrap == 0) return; for (n = a, u = o.row; n >= u; n--)if (c(n, Number.MAX_VALUE, e)) return }; else var f = function (e) { var n = o.row; if (c(n, o.column, e)) return; for (n += 1; n <= a; n++)if (c(n, 0, e)) return; if (t.wrap == 0) return; for (n = u, a = o.row; n <= a; n++)if (c(n, 0, e)) return }; if (t.$isMultiLine) var l = n.length, c = function (t, i, s) { var o = r ? t - l + 1 : t; if (o < 0) return; var u = e.getLine(o), a = u.search(n[0]); if (!r && a < i || a === -1) return; for (var f = 1; f < l; f++) { u = e.getLine(o + f); if (u.search(n[f]) == -1) return } var c = u.match(n[l - 1])[0].length; if (r && c > i) return; if (s(o, a, o + l - 1, c)) return !0 }; else if (r) var c = function (t, r, i) { var s = e.getLine(t), o = [], u, a = 0; n.lastIndex = 0; while (u = n.exec(s)) { var f = u[0].length; a = u.index; if (!f) { if (a >= s.length) break; n.lastIndex = a += 1 } if (u.index + f > r) break; o.push(u.index, f) } for (var l = o.length - 1; l >= 0; l -= 2) { var c = o[l - 1], f = o[l]; if (i(t, c, t, c + f)) return !0 } }; else var c = function (t, r, i) { var s = e.getLine(t), o, u; n.lastIndex = r; while (u = n.exec(s)) { var a = u[0].length; o = u.index; if (i(t, o, t, o + a)) return !0; if (!a) { n.lastIndex = o += 1; if (o >= s.length) return !1 } } }; return { forEach: f } } }).call(o.prototype), t.Search = o }), define("ace/keyboard/hash_handler", ["require", "exports", "module", "ace/lib/keys", "ace/lib/useragent"], function (e, t, n) { "use strict"; function o(e, t) { this.platform = t || (i.isMac ? "mac" : "win"), this.commands = {}, this.commandKeyBinding = {}, this.addCommands(e), this.$singleCommand = !0 } function u(e, t) { o.call(this, e, t), this.$singleCommand = !1 } var r = e("../lib/keys"), i = e("../lib/useragent"), s = r.KEY_MODS; u.prototype = o.prototype, function () { function e(e) { return typeof e == "object" && e.bindKey && e.bindKey.position || (e.isDefault ? -100 : 0) } this.addCommand = function (e) { this.commands[e.name] && this.removeCommand(e), this.commands[e.name] = e, e.bindKey && this._buildKeyHash(e) }, this.removeCommand = function (e, t) { var n = e && (typeof e == "string" ? e : e.name); e = this.commands[n], t || delete this.commands[n]; var r = this.commandKeyBinding; for (var i in r) { var s = r[i]; if (s == e) delete r[i]; else if (Array.isArray(s)) { var o = s.indexOf(e); o != -1 && (s.splice(o, 1), s.length == 1 && (r[i] = s[0])) } } }, this.bindKey = function (e, t, n) { typeof e == "object" && e && (n == undefined && (n = e.position), e = e[this.platform]); if (!e) return; if (typeof t == "function") return this.addCommand({ exec: t, bindKey: e, name: t.name || e }); e.split("|").forEach(function (e) { var r = ""; if (e.indexOf(" ") != -1) { var i = e.split(/\s+/); e = i.pop(), i.forEach(function (e) { var t = this.parseKeys(e), n = s[t.hashId] + t.key; r += (r ? " " : "") + n, this._addCommandToBinding(r, "chainKeys") }, this), r += " " } var o = this.parseKeys(e), u = s[o.hashId] + o.key; this._addCommandToBinding(r + u, t, n) }, this) }, this._addCommandToBinding = function (t, n, r) { var i = this.commandKeyBinding, s; if (!n) delete i[t]; else if (!i[t] || this.$singleCommand) i[t] = n; else { Array.isArray(i[t]) ? (s = i[t].indexOf(n)) != -1 && i[t].splice(s, 1) : i[t] = [i[t]], typeof r != "number" && (r = e(n)); var o = i[t]; for (s = 0; s < o.length; s++) { var u = o[s], a = e(u); if (a > r) break } o.splice(s, 0, n) } }, this.addCommands = function (e) { e && Object.keys(e).forEach(function (t) { var n = e[t]; if (!n) return; if (typeof n == "string") return this.bindKey(n, t); typeof n == "function" && (n = { exec: n }); if (typeof n != "object") return; n.name || (n.name = t), this.addCommand(n) }, this) }, this.removeCommands = function (e) { Object.keys(e).forEach(function (t) { this.removeCommand(e[t]) }, this) }, this.bindKeys = function (e) { Object.keys(e).forEach(function (t) { this.bindKey(t, e[t]) }, this) }, this._buildKeyHash = function (e) { this.bindKey(e.bindKey, e) }, this.parseKeys = function (e) { var t = e.toLowerCase().split(/[\-\+]([\-\+])?/).filter(function (e) { return e }), n = t.pop(), i = r[n]; if (r.FUNCTION_KEYS[i]) n = r.FUNCTION_KEYS[i].toLowerCase(); else { if (!t.length) return { key: n, hashId: -1 }; if (t.length == 1 && t[0] == "shift") return { key: n.toUpperCase(), hashId: -1 } } var s = 0; for (var o = t.length; o--;) { var u = r.KEY_MODS[t[o]]; if (u == null) return typeof console != "undefined" && console.error("invalid modifier " + t[o] + " in " + e), !1; s |= u } return { key: n, hashId: s } }, this.findKeyCommand = function (t, n) { var r = s[t] + n; return this.commandKeyBinding[r] }, this.handleKeyboard = function (e, t, n, r) { if (r < 0) return; var i = s[t] + n, o = this.commandKeyBinding[i]; e.$keyChain && (e.$keyChain += " " + i, o = this.commandKeyBinding[e.$keyChain] || o); if (o) if (o == "chainKeys" || o[o.length - 1] == "chainKeys") return e.$keyChain = e.$keyChain || i, { command: "null" }; if (e.$keyChain) if (!!t && t != 4 || n.length != 1) { if (t == -1 || r > 0) e.$keyChain = "" } else e.$keyChain = e.$keyChain.slice(0, -i.length - 1); return { command: o } }, this.getStatusText = function (e, t) { return t.$keyChain || "" } }.call(o.prototype), t.HashHandler = o, t.MultiHashHandler = u }), define("ace/commands/command_manager", ["require", "exports", "module", "ace/lib/oop", "ace/keyboard/hash_handler", "ace/lib/event_emitter"], function (e, t, n) { "use strict"; var r = e("../lib/oop"), i = e("../keyboard/hash_handler").MultiHashHandler, s = e("../lib/event_emitter").EventEmitter, o = function (e, t) { i.call(this, t, e), this.byName = this.commands, this.setDefaultHandler("exec", function (e) { return e.command.exec(e.editor, e.args || {}) }) }; r.inherits(o, i), function () { r.implement(this, s), this.exec = function (e, t, n) { if (Array.isArray(e)) { for (var r = e.length; r--;)if (this.exec(e[r], t, n)) return !0; return !1 } typeof e == "string" && (e = this.commands[e]); if (!e) return !1; if (t && t.$readOnly && !e.readOnly) return !1; if (this.$checkCommandState != 0 && e.isAvailable && !e.isAvailable(t)) return !1; var i = { editor: t, command: e, args: n }; return i.returnValue = this._emit("exec", i), this._signal("afterExec", i), i.returnValue === !1 ? !1 : !0 }, this.toggleRecording = function (e) { if (this.$inReplay) return; return e && e._emit("changeStatus"), this.recording ? (this.macro.pop(), this.off("exec", this.$addCommandToMacro), this.macro.length || (this.macro = this.oldMacro), this.recording = !1) : (this.$addCommandToMacro || (this.$addCommandToMacro = function (e) { this.macro.push([e.command, e.args]) }.bind(this)), this.oldMacro = this.macro, this.macro = [], this.on("exec", this.$addCommandToMacro), this.recording = !0) }, this.replay = function (e) { if (this.$inReplay || !this.macro) return; if (this.recording) return this.toggleRecording(e); try { this.$inReplay = !0, this.macro.forEach(function (t) { typeof t == "string" ? this.exec(t, e) : this.exec(t[0], e, t[1]) }, this) } finally { this.$inReplay = !1 } }, this.trimMacro = function (e) { return e.map(function (e) { return typeof e[0] != "string" && (e[0] = e[0].name), e[1] || (e = e[0]), e }) } }.call(o.prototype), t.CommandManager = o }), define("ace/commands/default_commands", ["require", "exports", "module", "ace/lib/lang", "ace/config", "ace/range"], function (e, t, n) { "use strict"; function o(e, t) { return { win: e, mac: t } } var r = e("../lib/lang"), i = e("../config"), s = e("../range").Range; t.commands = [{ name: "showSettingsMenu", bindKey: o("Ctrl-,", "Command-,"), exec: function (e) { i.loadModule("ace/ext/settings_menu", function (t) { t.init(e), e.showSettingsMenu() }) }, readOnly: !0 }, { name: "goToNextError", bindKey: o("Alt-E", "F4"), exec: function (e) { i.loadModule("./ext/error_marker", function (t) { t.showErrorMarker(e, 1) }) }, scrollIntoView: "animate", readOnly: !0 }, { name: "goToPreviousError", bindKey: o("Alt-Shift-E", "Shift-F4"), exec: function (e) { i.loadModule("./ext/error_marker", function (t) { t.showErrorMarker(e, -1) }) }, scrollIntoView: "animate", readOnly: !0 }, { name: "selectall", description: "Select all", bindKey: o("Ctrl-A", "Command-A"), exec: function (e) { e.selectAll() }, readOnly: !0 }, { name: "centerselection", description: "Center selection", bindKey: o(null, "Ctrl-L"), exec: function (e) { e.centerSelection() }, readOnly: !0 }, { name: "gotoline", description: "Go to line...", bindKey: o("Ctrl-L", "Command-L"), exec: function (e, t) { typeof t == "number" && !isNaN(t) && e.gotoLine(t), e.prompt({ $type: "gotoLine" }) }, readOnly: !0 }, { name: "fold", bindKey: o("Alt-L|Ctrl-F1", "Command-Alt-L|Command-F1"), exec: function (e) { e.session.toggleFold(!1) }, multiSelectAction: "forEach", scrollIntoView: "center", readOnly: !0 }, { name: "unfold", bindKey: o("Alt-Shift-L|Ctrl-Shift-F1", "Command-Alt-Shift-L|Command-Shift-F1"), exec: function (e) { e.session.toggleFold(!0) }, multiSelectAction: "forEach", scrollIntoView: "center", readOnly: !0 }, { name: "toggleFoldWidget", bindKey: o("F2", "F2"), exec: function (e) { e.session.toggleFoldWidget() }, multiSelectAction: "forEach", scrollIntoView: "center", readOnly: !0 }, { name: "toggleParentFoldWidget", bindKey: o("Alt-F2", "Alt-F2"), exec: function (e) { e.session.toggleFoldWidget(!0) }, multiSelectAction: "forEach", scrollIntoView: "center", readOnly: !0 }, { name: "foldall", description: "Fold all", bindKey: o(null, "Ctrl-Command-Option-0"), exec: function (e) { e.session.foldAll() }, scrollIntoView: "center", readOnly: !0 }, { name: "foldOther", description: "Fold other", bindKey: o("Alt-0", "Command-Option-0"), exec: function (e) { e.session.foldAll(), e.session.unfold(e.selection.getAllRanges()) }, scrollIntoView: "center", readOnly: !0 }, { name: "unfoldall", description: "Unfold all", bindKey: o("Alt-Shift-0", "Command-Option-Shift-0"), exec: function (e) { e.session.unfold() }, scrollIntoView: "center", readOnly: !0 }, { name: "findnext", description: "Find next", bindKey: o("Ctrl-K", "Command-G"), exec: function (e) { e.findNext() }, multiSelectAction: "forEach", scrollIntoView: "center", readOnly: !0 }, { name: "findprevious", description: "Find previous", bindKey: o("Ctrl-Shift-K", "Command-Shift-G"), exec: function (e) { e.findPrevious() }, multiSelectAction: "forEach", scrollIntoView: "center", readOnly: !0 }, { name: "selectOrFindNext", description: "Select or find next", bindKey: o("Alt-K", "Ctrl-G"), exec: function (e) { e.selection.isEmpty() ? e.selection.selectWord() : e.findNext() }, readOnly: !0 }, { name: "selectOrFindPrevious", description: "Select or find previous", bindKey: o("Alt-Shift-K", "Ctrl-Shift-G"), exec: function (e) { e.selection.isEmpty() ? e.selection.selectWord() : e.findPrevious() }, readOnly: !0 }, { name: "find", description: "Find", bindKey: o("Ctrl-F", "Command-F"), exec: function (e) { i.loadModule("ace/ext/searchbox", function (t) { t.Search(e) }) }, readOnly: !0 }, { name: "overwrite", description: "Overwrite", bindKey: "Insert", exec: function (e) { e.toggleOverwrite() }, readOnly: !0 }, { name: "selecttostart", description: "Select to start", bindKey: o("Ctrl-Shift-Home", "Command-Shift-Home|Command-Shift-Up"), exec: function (e) { e.getSelection().selectFileStart() }, multiSelectAction: "forEach", readOnly: !0, scrollIntoView: "animate", aceCommandGroup: "fileJump" }, { name: "gotostart", description: "Go to start", bindKey: o("Ctrl-Home", "Command-Home|Command-Up"), exec: function (e) { e.navigateFileStart() }, multiSelectAction: "forEach", readOnly: !0, scrollIntoView: "animate", aceCommandGroup: "fileJump" }, { name: "selectup", description: "Select up", bindKey: o("Shift-Up", "Shift-Up|Ctrl-Shift-P"), exec: function (e) { e.getSelection().selectUp() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "golineup", description: "Go line up", bindKey: o("Up", "Up|Ctrl-P"), exec: function (e, t) { e.navigateUp(t.times) }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selecttoend", description: "Select to end", bindKey: o("Ctrl-Shift-End", "Command-Shift-End|Command-Shift-Down"), exec: function (e) { e.getSelection().selectFileEnd() }, multiSelectAction: "forEach", readOnly: !0, scrollIntoView: "animate", aceCommandGroup: "fileJump" }, { name: "gotoend", description: "Go to end", bindKey: o("Ctrl-End", "Command-End|Command-Down"), exec: function (e) { e.navigateFileEnd() }, multiSelectAction: "forEach", readOnly: !0, scrollIntoView: "animate", aceCommandGroup: "fileJump" }, { name: "selectdown", description: "Select down", bindKey: o("Shift-Down", "Shift-Down|Ctrl-Shift-N"), exec: function (e) { e.getSelection().selectDown() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "golinedown", description: "Go line down", bindKey: o("Down", "Down|Ctrl-N"), exec: function (e, t) { e.navigateDown(t.times) }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selectwordleft", description: "Select word left", bindKey: o("Ctrl-Shift-Left", "Option-Shift-Left"), exec: function (e) { e.getSelection().selectWordLeft() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "gotowordleft", description: "Go to word left", bindKey: o("Ctrl-Left", "Option-Left"), exec: function (e) { e.navigateWordLeft() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selecttolinestart", description: "Select to line start", bindKey: o("Alt-Shift-Left", "Command-Shift-Left|Ctrl-Shift-A"), exec: function (e) { e.getSelection().selectLineStart() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "gotolinestart", description: "Go to line start", bindKey: o("Alt-Left|Home", "Command-Left|Home|Ctrl-A"), exec: function (e) { e.navigateLineStart() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selectleft", description: "Select left", bindKey: o("Shift-Left", "Shift-Left|Ctrl-Shift-B"), exec: function (e) { e.getSelection().selectLeft() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "gotoleft", description: "Go to left", bindKey: o("Left", "Left|Ctrl-B"), exec: function (e, t) { e.navigateLeft(t.times) }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selectwordright", description: "Select word right", bindKey: o("Ctrl-Shift-Right", "Option-Shift-Right"), exec: function (e) { e.getSelection().selectWordRight() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "gotowordright", description: "Go to word right", bindKey: o("Ctrl-Right", "Option-Right"), exec: function (e) { e.navigateWordRight() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selecttolineend", description: "Select to line end", bindKey: o("Alt-Shift-Right", "Command-Shift-Right|Shift-End|Ctrl-Shift-E"), exec: function (e) { e.getSelection().selectLineEnd() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "gotolineend", description: "Go to line end", bindKey: o("Alt-Right|End", "Command-Right|End|Ctrl-E"), exec: function (e) { e.navigateLineEnd() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selectright", description: "Select right", bindKey: o("Shift-Right", "Shift-Right"), exec: function (e) { e.getSelection().selectRight() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "gotoright", description: "Go to right", bindKey: o("Right", "Right|Ctrl-F"), exec: function (e, t) { e.navigateRight(t.times) }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selectpagedown", description: "Select page down", bindKey: "Shift-PageDown", exec: function (e) { e.selectPageDown() }, readOnly: !0 }, { name: "pagedown", description: "Page down", bindKey: o(null, "Option-PageDown"), exec: function (e) { e.scrollPageDown() }, readOnly: !0 }, { name: "gotopagedown", description: "Go to page down", bindKey: o("PageDown", "PageDown|Ctrl-V"), exec: function (e) { e.gotoPageDown() }, readOnly: !0 }, { name: "selectpageup", description: "Select page up", bindKey: "Shift-PageUp", exec: function (e) { e.selectPageUp() }, readOnly: !0 }, { name: "pageup", description: "Page up", bindKey: o(null, "Option-PageUp"), exec: function (e) { e.scrollPageUp() }, readOnly: !0 }, { name: "gotopageup", description: "Go to page up", bindKey: "PageUp", exec: function (e) { e.gotoPageUp() }, readOnly: !0 }, { name: "scrollup", description: "Scroll up", bindKey: o("Ctrl-Up", null), exec: function (e) { e.renderer.scrollBy(0, -2 * e.renderer.layerConfig.lineHeight) }, readOnly: !0 }, { name: "scrolldown", description: "Scroll down", bindKey: o("Ctrl-Down", null), exec: function (e) { e.renderer.scrollBy(0, 2 * e.renderer.layerConfig.lineHeight) }, readOnly: !0 }, { name: "selectlinestart", description: "Select line start", bindKey: "Shift-Home", exec: function (e) { e.getSelection().selectLineStart() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selectlineend", description: "Select line end", bindKey: "Shift-End", exec: function (e) { e.getSelection().selectLineEnd() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "togglerecording", description: "Toggle recording", bindKey: o("Ctrl-Alt-E", "Command-Option-E"), exec: function (e) { e.commands.toggleRecording(e) }, readOnly: !0 }, { name: "replaymacro", description: "Replay macro", bindKey: o("Ctrl-Shift-E", "Command-Shift-E"), exec: function (e) { e.commands.replay(e) }, readOnly: !0 }, { name: "jumptomatching", description: "Jump to matching", bindKey: o("Ctrl-\\|Ctrl-P", "Command-\\"), exec: function (e) { e.jumpToMatching() }, multiSelectAction: "forEach", scrollIntoView: "animate", readOnly: !0 }, { name: "selecttomatching", description: "Select to matching", bindKey: o("Ctrl-Shift-\\|Ctrl-Shift-P", "Command-Shift-\\"), exec: function (e) { e.jumpToMatching(!0) }, multiSelectAction: "forEach", scrollIntoView: "animate", readOnly: !0 }, { name: "expandToMatching", description: "Expand to matching", bindKey: o("Ctrl-Shift-M", "Ctrl-Shift-M"), exec: function (e) { e.jumpToMatching(!0, !0) }, multiSelectAction: "forEach", scrollIntoView: "animate", readOnly: !0 }, { name: "passKeysToBrowser", description: "Pass keys to browser", bindKey: o(null, null), exec: function () { }, passEvent: !0, readOnly: !0 }, { name: "copy", description: "Copy", exec: function (e) { }, readOnly: !0 }, { name: "cut", description: "Cut", exec: function (e) { var t = e.$copyWithEmptySelection && e.selection.isEmpty(), n = t ? e.selection.getLineRange() : e.selection.getRange(); e._emit("cut", n), n.isEmpty() || e.session.remove(n), e.clearSelection() }, scrollIntoView: "cursor", multiSelectAction: "forEach" }, { name: "paste", description: "Paste", exec: function (e, t) { e.$handlePaste(t) }, scrollIntoView: "cursor" }, { name: "removeline", description: "Remove line", bindKey: o("Ctrl-D", "Command-D"), exec: function (e) { e.removeLines() }, scrollIntoView: "cursor", multiSelectAction: "forEachLine" }, { name: "duplicateSelection", description: "Duplicate selection", bindKey: o("Ctrl-Shift-D", "Command-Shift-D"), exec: function (e) { e.duplicateSelection() }, scrollIntoView: "cursor", multiSelectAction: "forEach" }, { name: "sortlines", description: "Sort lines", bindKey: o("Ctrl-Alt-S", "Command-Alt-S"), exec: function (e) { e.sortLines() }, scrollIntoView: "selection", multiSelectAction: "forEachLine" }, { name: "togglecomment", description: "Toggle comment", bindKey: o("Ctrl-/", "Command-/"), exec: function (e) { e.toggleCommentLines() }, multiSelectAction: "forEachLine", scrollIntoView: "selectionPart" }, { name: "toggleBlockComment", description: "Toggle block comment", bindKey: o("Ctrl-Shift-/", "Command-Shift-/"), exec: function (e) { e.toggleBlockComment() }, multiSelectAction: "forEach", scrollIntoView: "selectionPart" }, { name: "modifyNumberUp", description: "Modify number up", bindKey: o("Ctrl-Shift-Up", "Alt-Shift-Up"), exec: function (e) { e.modifyNumber(1) }, scrollIntoView: "cursor", multiSelectAction: "forEach" }, { name: "modifyNumberDown", description: "Modify number down", bindKey: o("Ctrl-Shift-Down", "Alt-Shift-Down"), exec: function (e) { e.modifyNumber(-1) }, scrollIntoView: "cursor", multiSelectAction: "forEach" }, { name: "replace", description: "Replace", bindKey: o("Ctrl-H", "Command-Option-F"), exec: function (e) { i.loadModule("ace/ext/searchbox", function (t) { t.Search(e, !0) }) } }, { name: "undo", description: "Undo", bindKey: o("Ctrl-Z", "Command-Z"), exec: function (e) { e.undo() } }, { name: "redo", description: "Redo", bindKey: o("Ctrl-Shift-Z|Ctrl-Y", "Command-Shift-Z|Command-Y"), exec: function (e) { e.redo() } }, { name: "copylinesup", description: "Copy lines up", bindKey: o("Alt-Shift-Up", "Command-Option-Up"), exec: function (e) { e.copyLinesUp() }, scrollIntoView: "cursor" }, { name: "movelinesup", description: "Move lines up", bindKey: o("Alt-Up", "Option-Up"), exec: function (e) { e.moveLinesUp() }, scrollIntoView: "cursor" }, { name: "copylinesdown", description: "Copy lines down", bindKey: o("Alt-Shift-Down", "Command-Option-Down"), exec: function (e) { e.copyLinesDown() }, scrollIntoView: "cursor" }, { name: "movelinesdown", description: "Move lines down", bindKey: o("Alt-Down", "Option-Down"), exec: function (e) { e.moveLinesDown() }, scrollIntoView: "cursor" }, { name: "del", description: "Delete", bindKey: o("Delete", "Delete|Ctrl-D|Shift-Delete"), exec: function (e) { e.remove("right") }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "backspace", description: "Backspace", bindKey: o("Shift-Backspace|Backspace", "Ctrl-Backspace|Shift-Backspace|Backspace|Ctrl-H"), exec: function (e) { e.remove("left") }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "cut_or_delete", description: "Cut or delete", bindKey: o("Shift-Delete", null), exec: function (e) { if (!e.selection.isEmpty()) return !1; e.remove("left") }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "removetolinestart", description: "Remove to line start", bindKey: o("Alt-Backspace", "Command-Backspace"), exec: function (e) { e.removeToLineStart() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "removetolineend", description: "Remove to line end", bindKey: o("Alt-Delete", "Ctrl-K|Command-Delete"), exec: function (e) { e.removeToLineEnd() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "removetolinestarthard", description: "Remove to line start hard", bindKey: o("Ctrl-Shift-Backspace", null), exec: function (e) { var t = e.selection.getRange(); t.start.column = 0, e.session.remove(t) }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "removetolineendhard", description: "Remove to line end hard", bindKey: o("Ctrl-Shift-Delete", null), exec: function (e) { var t = e.selection.getRange(); t.end.column = Number.MAX_VALUE, e.session.remove(t) }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "removewordleft", description: "Remove word left", bindKey: o("Ctrl-Backspace", "Alt-Backspace|Ctrl-Alt-Backspace"), exec: function (e) { e.removeWordLeft() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "removewordright", description: "Remove word right", bindKey: o("Ctrl-Delete", "Alt-Delete"), exec: function (e) { e.removeWordRight() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "outdent", description: "Outdent", bindKey: o("Shift-Tab", "Shift-Tab"), exec: function (e) { e.blockOutdent() }, multiSelectAction: "forEach", scrollIntoView: "selectionPart" }, { name: "indent", description: "Indent", bindKey: o("Tab", "Tab"), exec: function (e) { e.indent() }, multiSelectAction: "forEach", scrollIntoView: "selectionPart" }, { name: "blockoutdent", description: "Block outdent", bindKey: o("Ctrl-[", "Ctrl-["), exec: function (e) { e.blockOutdent() }, multiSelectAction: "forEachLine", scrollIntoView: "selectionPart" }, { name: "blockindent", description: "Block indent", bindKey: o("Ctrl-]", "Ctrl-]"), exec: function (e) { e.blockIndent() }, multiSelectAction: "forEachLine", scrollIntoView: "selectionPart" }, { name: "insertstring", description: "Insert string", exec: function (e, t) { e.insert(t) }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "inserttext", description: "Insert text", exec: function (e, t) { e.insert(r.stringRepeat(t.text || "", t.times || 1)) }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "splitline", description: "Split line", bindKey: o(null, "Ctrl-O"), exec: function (e) { e.splitLine() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "transposeletters", description: "Transpose letters", bindKey: o("Alt-Shift-X", "Ctrl-T"), exec: function (e) { e.transposeLetters() }, multiSelectAction: function (e) { e.transposeSelections(1) }, scrollIntoView: "cursor" }, { name: "touppercase", description: "To uppercase", bindKey: o("Ctrl-U", "Ctrl-U"), exec: function (e) { e.toUpperCase() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "tolowercase", description: "To lowercase", bindKey: o("Ctrl-Shift-U", "Ctrl-Shift-U"), exec: function (e) { e.toLowerCase() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "autoindent", description: "Auto Indent", bindKey: o(null, null), exec: function (e) { e.autoIndent() }, multiSelectAction: "forEachLine", scrollIntoView: "animate" }, { name: "expandtoline", description: "Expand to line", bindKey: o("Ctrl-Shift-L", "Command-Shift-L"), exec: function (e) { var t = e.selection.getRange(); t.start.column = t.end.column = 0, t.end.row++, e.selection.setRange(t, !1) }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "joinlines", description: "Join lines", bindKey: o(null, null), exec: function (e) { var t = e.selection.isBackwards(), n = t ? e.selection.getSelectionLead() : e.selection.getSelectionAnchor(), i = t ? e.selection.getSelectionAnchor() : e.selection.getSelectionLead(), o = e.session.doc.getLine(n.row).length, u = e.session.doc.getTextRange(e.selection.getRange()), a = u.replace(/\n\s*/, " ").length, f = e.session.doc.getLine(n.row); for (var l = n.row + 1; l <= i.row + 1; l++) { var c = r.stringTrimLeft(r.stringTrimRight(e.session.doc.getLine(l))); c.length !== 0 && (c = " " + c), f += c } i.row + 1 < e.session.doc.getLength() - 1 && (f += e.session.doc.getNewLineCharacter()), e.clearSelection(), e.session.doc.replace(new s(n.row, 0, i.row + 2, 0), f), a > 0 ? (e.selection.moveCursorTo(n.row, n.column), e.selection.selectTo(n.row, n.column + a)) : (o = e.session.doc.getLine(n.row).length > o ? o + 1 : o, e.selection.moveCursorTo(n.row, o)) }, multiSelectAction: "forEach", readOnly: !0 }, { name: "invertSelection", description: "Invert selection", bindKey: o(null, null), exec: function (e) { var t = e.session.doc.getLength() - 1, n = e.session.doc.getLine(t).length, r = e.selection.rangeList.ranges, i = []; r.length < 1 && (r = [e.selection.getRange()]); for (var o = 0; o < r.length; o++)o == r.length - 1 && (r[o].end.row !== t || r[o].end.column !== n) && i.push(new s(r[o].end.row, r[o].end.column, t, n)), o === 0 ? (r[o].start.row !== 0 || r[o].start.column !== 0) && i.push(new s(0, 0, r[o].start.row, r[o].start.column)) : i.push(new s(r[o - 1].end.row, r[o - 1].end.column, r[o].start.row, r[o].start.column)); e.exitMultiSelectMode(), e.clearSelection(); for (var o = 0; o < i.length; o++)e.selection.addRange(i[o], !1) }, readOnly: !0, scrollIntoView: "none" }, { name: "addLineAfter", exec: function (e) { e.selection.clearSelection(), e.navigateLineEnd(), e.insert("\n") }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "addLineBefore", exec: function (e) { e.selection.clearSelection(); var t = e.getCursorPosition(); e.selection.moveTo(t.row - 1, Number.MAX_VALUE), e.insert("\n"), t.row === 0 && e.navigateUp() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "openCommandPallete", description: "Open command pallete", bindKey: o("F1", "F1"), exec: function (e) { e.prompt({ $type: "commands" }) }, readOnly: !0 }, { name: "modeSelect", description: "Change language mode...", bindKey: o(null, null), exec: function (e) { e.prompt({ $type: "modes" }) }, readOnly: !0 }] }), define("ace/editor", ["require", "exports", "module", "ace/lib/fixoldbrowsers", "ace/lib/oop", "ace/lib/dom", "ace/lib/lang", "ace/lib/useragent", "ace/keyboard/textinput", "ace/mouse/mouse_handler", "ace/mouse/fold_handler", "ace/keyboard/keybinding", "ace/edit_session", "ace/search", "ace/range", "ace/lib/event_emitter", "ace/commands/command_manager", "ace/commands/default_commands", "ace/config", "ace/token_iterator", "ace/clipboard"], function (e, t, n) { "use strict"; e("./lib/fixoldbrowsers"); var r = e("./lib/oop"), i = e("./lib/dom"), s = e("./lib/lang"), o = e("./lib/useragent"), u = e("./keyboard/textinput").TextInput, a = e("./mouse/mouse_handler").MouseHandler, f = e("./mouse/fold_handler").FoldHandler, l = e("./keyboard/keybinding").KeyBinding, c = e("./edit_session").EditSession, h = e("./search").Search, p = e("./range").Range, d = e("./lib/event_emitter").EventEmitter, v = e("./commands/command_manager").CommandManager, m = e("./commands/default_commands").commands, g = e("./config"), y = e("./token_iterator").TokenIterator, b = e("./clipboard"), w = function (e, t, n) { this.$toDestroy = []; var r = e.getContainerElement(); this.container = r, this.renderer = e, this.id = "editor" + ++w.$uid, this.commands = new v(o.isMac ? "mac" : "win", m), typeof document == "object" && (this.textInput = new u(e.getTextAreaContainer(), this), this.renderer.textarea = this.textInput.getElement(), this.$mouseHandler = new a(this), new f(this)), this.keyBinding = new l(this), this.$search = (new h).set({ wrap: !0 }), this.$historyTracker = this.$historyTracker.bind(this), this.commands.on("exec", this.$historyTracker), this.$initOperationListeners(), this._$emitInputEvent = s.delayedCall(function () { this._signal("input", {}), this.session && this.session.bgTokenizer && this.session.bgTokenizer.scheduleStart() }.bind(this)), this.on("change", function (e, t) { t._$emitInputEvent.schedule(31) }), this.setSession(t || n && n.session || new c("")), g.resetOptions(this), n && this.setOptions(n), g._signal("editor", this) }; w.$uid = 0, function () { r.implement(this, d), this.$initOperationListeners = function () { this.commands.on("exec", this.startOperation.bind(this), !0), this.commands.on("afterExec", this.endOperation.bind(this), !0), this.$opResetTimer = s.delayedCall(this.endOperation.bind(this, !0)), this.on("change", function () { this.curOp || (this.startOperation(), this.curOp.selectionBefore = this.$lastSel), this.curOp.docChanged = !0 }.bind(this), !0), this.on("changeSelection", function () { this.curOp || (this.startOperation(), this.curOp.selectionBefore = this.$lastSel), this.curOp.selectionChanged = !0 }.bind(this), !0) }, this.curOp = null, this.prevOp = {}, this.startOperation = function (e) { if (this.curOp) { if (!e || this.curOp.command) return; this.prevOp = this.curOp } e || (this.previousCommand = null, e = {}), this.$opResetTimer.schedule(), this.curOp = this.session.curOp = { command: e.command || {}, args: e.args, scrollTop: this.renderer.scrollTop }, this.curOp.selectionBefore = this.selection.toJSON() }, this.endOperation = function (e) { if (this.curOp && this.session) { if (e && e.returnValue === !1 || !this.session) return this.curOp = null; if (e == 1 && this.curOp.command && this.curOp.command.name == "mouse") return; this._signal("beforeEndOperation"); if (!this.curOp) return; var t = this.curOp.command, n = t && t.scrollIntoView; if (n) { switch (n) { case "center-animate": n = "animate"; case "center": this.renderer.scrollCursorIntoView(null, .5); break; case "animate": case "cursor": this.renderer.scrollCursorIntoView(); break; case "selectionPart": var r = this.selection.getRange(), i = this.renderer.layerConfig; (r.start.row >= i.lastRow || r.end.row <= i.firstRow) && this.renderer.scrollSelectionIntoView(this.selection.anchor, this.selection.lead); break; default: }n == "animate" && this.renderer.animateScrolling(this.curOp.scrollTop) } var s = this.selection.toJSON(); this.curOp.selectionAfter = s, this.$lastSel = this.selection.toJSON(), this.session.getUndoManager().addSelection(s), this.prevOp = this.curOp, this.curOp = null } }, this.$mergeableCommands = ["backspace", "del", "insertstring"], this.$historyTracker = function (e) { if (!this.$mergeUndoDeltas) return; var t = this.prevOp, n = this.$mergeableCommands, r = t.command && e.command.name == t.command.name; if (e.command.name == "insertstring") { var i = e.args; this.mergeNextCommand === undefined && (this.mergeNextCommand = !0), r = r && this.mergeNextCommand && (!/\s/.test(i) || /\s/.test(t.args)), this.mergeNextCommand = !0 } else r = r && n.indexOf(e.command.name) !== -1; this.$mergeUndoDeltas != "always" && Date.now() - this.sequenceStartTime > 2e3 && (r = !1), r ? this.session.mergeUndoDeltas = !0 : n.indexOf(e.command.name) !== -1 && (this.sequenceStartTime = Date.now()) }, this.setKeyboardHandler = function (e, t) { if (e && typeof e == "string" && e != "ace") { this.$keybindingId = e; var n = this; g.loadModule(["keybinding", e], function (r) { n.$keybindingId == e && n.keyBinding.setKeyboardHandler(r && r.handler), t && t() }) } else this.$keybindingId = null, this.keyBinding.setKeyboardHandler(e), t && t() }, this.getKeyboardHandler = function () { return this.keyBinding.getKeyboardHandler() }, this.setSession = function (e) { if (this.session == e) return; this.curOp && this.endOperation(), this.curOp = {}; var t = this.session; if (t) { this.session.off("change", this.$onDocumentChange), this.session.off("changeMode", this.$onChangeMode), this.session.off("tokenizerUpdate", this.$onTokenizerUpdate), this.session.off("changeTabSize", this.$onChangeTabSize), this.session.off("changeWrapLimit", this.$onChangeWrapLimit), this.session.off("changeWrapMode", this.$onChangeWrapMode), this.session.off("changeFold", this.$onChangeFold), this.session.off("changeFrontMarker", this.$onChangeFrontMarker), this.session.off("changeBackMarker", this.$onChangeBackMarker), this.session.off("changeBreakpoint", this.$onChangeBreakpoint), this.session.off("changeAnnotation", this.$onChangeAnnotation), this.session.off("changeOverwrite", this.$onCursorChange), this.session.off("changeScrollTop", this.$onScrollTopChange), this.session.off("changeScrollLeft", this.$onScrollLeftChange); var n = this.session.getSelection(); n.off("changeCursor", this.$onCursorChange), n.off("changeSelection", this.$onSelectionChange) } this.session = e, e ? (this.$onDocumentChange = this.onDocumentChange.bind(this), e.on("change", this.$onDocumentChange), this.renderer.setSession(e), this.$onChangeMode = this.onChangeMode.bind(this), e.on("changeMode", this.$onChangeMode), this.$onTokenizerUpdate = this.onTokenizerUpdate.bind(this), e.on("tokenizerUpdate", this.$onTokenizerUpdate), this.$onChangeTabSize = this.renderer.onChangeTabSize.bind(this.renderer), e.on("changeTabSize", this.$onChangeTabSize), this.$onChangeWrapLimit = this.onChangeWrapLimit.bind(this), e.on("changeWrapLimit", this.$onChangeWrapLimit), this.$onChangeWrapMode = this.onChangeWrapMode.bind(this), e.on("changeWrapMode", this.$onChangeWrapMode), this.$onChangeFold = this.onChangeFold.bind(this), e.on("changeFold", this.$onChangeFold), this.$onChangeFrontMarker = this.onChangeFrontMarker.bind(this), this.session.on("changeFrontMarker", this.$onChangeFrontMarker), this.$onChangeBackMarker = this.onChangeBackMarker.bind(this), this.session.on("changeBackMarker", this.$onChangeBackMarker), this.$onChangeBreakpoint = this.onChangeBreakpoint.bind(this), this.session.on("changeBreakpoint", this.$onChangeBreakpoint), this.$onChangeAnnotation = this.onChangeAnnotation.bind(this), this.session.on("changeAnnotation", this.$onChangeAnnotation), this.$onCursorChange = this.onCursorChange.bind(this), this.session.on("changeOverwrite", this.$onCursorChange), this.$onScrollTopChange = this.onScrollTopChange.bind(this), this.session.on("changeScrollTop", this.$onScrollTopChange), this.$onScrollLeftChange = this.onScrollLeftChange.bind(this), this.session.on("changeScrollLeft", this.$onScrollLeftChange), this.selection = e.getSelection(), this.selection.on("changeCursor", this.$onCursorChange), this.$onSelectionChange = this.onSelectionChange.bind(this), this.selection.on("changeSelection", this.$onSelectionChange), this.onChangeMode(), this.onCursorChange(), this.onScrollTopChange(), this.onScrollLeftChange(), this.onSelectionChange(), this.onChangeFrontMarker(), this.onChangeBackMarker(), this.onChangeBreakpoint(), this.onChangeAnnotation(), this.session.getUseWrapMode() && this.renderer.adjustWrapLimit(), this.renderer.updateFull()) : (this.selection = null, this.renderer.setSession(e)), this._signal("changeSession", { session: e, oldSession: t }), this.curOp = null, t && t._signal("changeEditor", { oldEditor: this }), e && e._signal("changeEditor", { editor: this }), e && e.bgTokenizer && e.bgTokenizer.scheduleStart() }, this.getSession = function () { return this.session }, this.setValue = function (e, t) { return this.session.doc.setValue(e), t ? t == 1 ? this.navigateFileEnd() : t == -1 && this.navigateFileStart() : this.selectAll(), e }, this.getValue = function () { return this.session.getValue() }, this.getSelection = function () { return this.selection }, this.resize = function (e) { this.renderer.onResize(e) }, this.setTheme = function (e, t) { this.renderer.setTheme(e, t) }, this.getTheme = function () { return this.renderer.getTheme() }, this.setStyle = function (e) { this.renderer.setStyle(e) }, this.unsetStyle = function (e) { this.renderer.unsetStyle(e) }, this.getFontSize = function () { return this.getOption("fontSize") || i.computedStyle(this.container).fontSize }, this.setFontSize = function (e) { this.setOption("fontSize", e) }, this.$highlightBrackets = function () { if (this.$highlightPending) return; var e = this; this.$highlightPending = !0, setTimeout(function () { e.$highlightPending = !1; var t = e.session; if (!t || !t.bgTokenizer) return; t.$bracketHighlight && (t.$bracketHighlight.markerIds.forEach(function (e) { t.removeMarker(e) }), t.$bracketHighlight = null); var n = t.getMatchingBracketRanges(e.getCursorPosition()); !n && t.$mode.getMatching && (n = t.$mode.getMatching(e.session)); if (!n) return; var r = "ace_bracket"; Array.isArray(n) ? n.length == 1 && (r = "ace_error_bracket") : n = [n], n.length == 2 && (p.comparePoints(n[0].end, n[1].start) == 0 ? n = [p.fromPoints(n[0].start, n[1].end)] : p.comparePoints(n[0].start, n[1].end) == 0 && (n = [p.fromPoints(n[1].start, n[0].end)])), t.$bracketHighlight = { ranges: n, markerIds: n.map(function (e) { return t.addMarker(e, r, "text") }) } }, 50) }, this.$highlightTags = function () { if (this.$highlightTagPending) return; var e = this; this.$highlightTagPending = !0, setTimeout(function () { e.$highlightTagPending = !1; var t = e.session; if (!t || !t.bgTokenizer) return; var n = e.getCursorPosition(), r = new y(e.session, n.row, n.column), i = r.getCurrentToken(); if (!i || !/\b(?:tag-open|tag-name)/.test(i.type)) { t.removeMarker(t.$tagHighlight), t.$tagHighlight = null; return } if (i.type.indexOf("tag-open") != -1) { i = r.stepForward(); if (!i) return } var s = i.value, o = 0, u = r.stepBackward(); if (u.value == "<") { do u = i, i = r.stepForward(), i && i.value === s && i.type.indexOf("tag-name") !== -1 && (u.value === "<" ? o++ : u.value === "= 0) } else { do i = u, u = r.stepBackward(), i && i.value === s && i.type.indexOf("tag-name") !== -1 && (u.value === "<" ? o++ : u.value === " 1) && (t = !1) } if (e.$highlightLineMarker && !t) e.removeMarker(e.$highlightLineMarker.id), e.$highlightLineMarker = null; else if (!e.$highlightLineMarker && t) { var n = new p(t.row, t.column, t.row, Infinity); n.id = e.addMarker(n, "ace_active-line", "screenLine"), e.$highlightLineMarker = n } else t && (e.$highlightLineMarker.start.row = t.row, e.$highlightLineMarker.end.row = t.row, e.$highlightLineMarker.start.column = t.column, e._signal("changeBackMarker")) }, this.onSelectionChange = function (e) { var t = this.session; t.$selectionMarker && t.removeMarker(t.$selectionMarker), t.$selectionMarker = null; if (!this.selection.isEmpty()) { var n = this.selection.getRange(), r = this.getSelectionStyle(); t.$selectionMarker = t.addMarker(n, "ace_selection", r) } else this.$updateHighlightActiveLine(); var i = this.$highlightSelectedWord && this.$getSelectionHighLightRegexp(); this.session.highlight(i), this._signal("changeSelection") }, this.$getSelectionHighLightRegexp = function () { var e = this.session, t = this.getSelectionRange(); if (t.isEmpty() || t.isMultiLine()) return; var n = t.start.column, r = t.end.column, i = e.getLine(t.start.row), s = i.substring(n, r); if (s.length > 5e3 || !/[\w\d]/.test(s)) return; var o = this.$search.$assembleRegExp({ wholeWord: !0, caseSensitive: !0, needle: s }), u = i.substring(n - 1, r + 1); if (!o.test(u)) return; return o }, this.onChangeFrontMarker = function () { this.renderer.updateFrontMarkers() }, this.onChangeBackMarker = function () { this.renderer.updateBackMarkers() }, this.onChangeBreakpoint = function () { this.renderer.updateBreakpoints() }, this.onChangeAnnotation = function () { this.renderer.setAnnotations(this.session.getAnnotations()) }, this.onChangeMode = function (e) { this.renderer.updateText(), this._emit("changeMode", e) }, this.onChangeWrapLimit = function () { this.renderer.updateFull() }, this.onChangeWrapMode = function () { this.renderer.onResize(!0) }, this.onChangeFold = function () { this.$updateHighlightActiveLine(), this.renderer.updateFull() }, this.getSelectedText = function () { return this.session.getTextRange(this.getSelectionRange()) }, this.getCopyText = function () { var e = this.getSelectedText(), t = this.session.doc.getNewLineCharacter(), n = !1; if (!e && this.$copyWithEmptySelection) { n = !0; var r = this.selection.getAllRanges(); for (var i = 0; i < r.length; i++) { var s = r[i]; if (i && r[i - 1].start.row == s.start.row) continue; e += this.session.getLine(s.start.row) + t } } var o = { text: e }; return this._signal("copy", o), b.lineMode = n ? o.text : "", o.text }, this.onCopy = function () { this.commands.exec("copy", this) }, this.onCut = function () { this.commands.exec("cut", this) }, this.onPaste = function (e, t) { var n = { text: e, event: t }; this.commands.exec("paste", this, n) }, this.$handlePaste = function (e) { typeof e == "string" && (e = { text: e }), this._signal("paste", e); var t = e.text, n = t == b.lineMode, r = this.session; if (!this.inMultiSelectMode || this.inVirtualSelectionMode) n ? r.insert({ row: this.selection.lead.row, column: 0 }, t) : this.insert(t); else if (n) this.selection.rangeList.ranges.forEach(function (e) { r.insert({ row: e.start.row, column: 0 }, t) }); else { var i = t.split(/\r\n|\r|\n/), s = this.selection.rangeList.ranges, o = i.length == 2 && (!i[0] || !i[1]); if (i.length != s.length || o) return this.commands.exec("insertstring", this, t); for (var u = s.length; u--;) { var a = s[u]; a.isEmpty() || r.remove(a), r.insert(a.start, i[u]) } } }, this.execCommand = function (e, t) { return this.commands.exec(e, this, t) }, this.insert = function (e, t) { var n = this.session, r = n.getMode(), i = this.getCursorPosition(); if (this.getBehavioursEnabled() && !t) { var s = r.transformAction(n.getState(i.row), "insertion", this, n, e); s && (e !== s.text && (this.inVirtualSelectionMode || (this.session.mergeUndoDeltas = !1, this.mergeNextCommand = !1)), e = s.text) } e == " " && (e = this.session.getTabString()); if (!this.selection.isEmpty()) { var o = this.getSelectionRange(); i = this.session.remove(o), this.clearSelection() } else if (this.session.getOverwrite() && e.indexOf("\n") == -1) { var o = new p.fromPoints(i, i); o.end.column += e.length, this.session.remove(o) } if (e == "\n" || e == "\r\n") { var u = n.getLine(i.row); if (i.column > u.search(/\S|$/)) { var a = u.substr(i.column).search(/\S|$/); n.doc.removeInLine(i.row, i.column, i.column + a) } } this.clearSelection(); var f = i.column, l = n.getState(i.row), u = n.getLine(i.row), c = r.checkOutdent(l, u, e); n.insert(i, e), s && s.selection && (s.selection.length == 2 ? this.selection.setSelectionRange(new p(i.row, f + s.selection[0], i.row, f + s.selection[1])) : this.selection.setSelectionRange(new p(i.row + s.selection[0], s.selection[1], i.row + s.selection[2], s.selection[3]))); if (this.$enableAutoIndent) { if (n.getDocument().isNewLine(e)) { var h = r.getNextLineIndent(l, u.slice(0, i.column), n.getTabString()); n.insert({ row: i.row + 1, column: 0 }, h) } c && r.autoOutdent(l, n, i.row) } }, this.autoIndent = function () { var e = this.session, t = e.getMode(), n, r; if (this.selection.isEmpty()) n = 0, r = e.doc.getLength() - 1; else { var i = this.getSelectionRange(); n = i.start.row, r = i.end.row } var s = "", o = "", u = "", a, f, l, c = e.getTabString(); for (var h = n; h <= r; h++)h > 0 && (s = e.getState(h - 1), o = e.getLine(h - 1), u = t.getNextLineIndent(s, o, c)), a = e.getLine(h), f = t.$getIndent(a), u !== f && (f.length > 0 && (l = new p(h, 0, h, f.length), e.remove(l)), u.length > 0 && e.insert({ row: h, column: 0 }, u)), t.autoOutdent(s, e, h) }, this.onTextInput = function (e, t) { if (!t) return this.keyBinding.onTextInput(e); this.startOperation({ command: { name: "insertstring" } }); var n = this.applyComposition.bind(this, e, t); this.selection.rangeCount ? this.forEachSelection(n) : n(), this.endOperation() }, this.applyComposition = function (e, t) { if (t.extendLeft || t.extendRight) { var n = this.selection.getRange(); n.start.column -= t.extendLeft, n.end.column += t.extendRight, n.start.column < 0 && (n.start.row--, n.start.column += this.session.getLine(n.start.row).length + 1), this.selection.setRange(n), !e && !n.isEmpty() && this.remove() } (e || !this.selection.isEmpty()) && this.insert(e, !0); if (t.restoreStart || t.restoreEnd) { var n = this.selection.getRange(); n.start.column -= t.restoreStart, n.end.column -= t.restoreEnd, this.selection.setRange(n) } }, this.onCommandKey = function (e, t, n) { return this.keyBinding.onCommandKey(e, t, n) }, this.setOverwrite = function (e) { this.session.setOverwrite(e) }, this.getOverwrite = function () { return this.session.getOverwrite() }, this.toggleOverwrite = function () { this.session.toggleOverwrite() }, this.setScrollSpeed = function (e) { this.setOption("scrollSpeed", e) }, this.getScrollSpeed = function () { return this.getOption("scrollSpeed") }, this.setDragDelay = function (e) { this.setOption("dragDelay", e) }, this.getDragDelay = function () { return this.getOption("dragDelay") }, this.setSelectionStyle = function (e) { this.setOption("selectionStyle", e) }, this.getSelectionStyle = function () { return this.getOption("selectionStyle") }, this.setHighlightActiveLine = function (e) { this.setOption("highlightActiveLine", e) }, this.getHighlightActiveLine = function () { return this.getOption("highlightActiveLine") }, this.setHighlightGutterLine = function (e) { this.setOption("highlightGutterLine", e) }, this.getHighlightGutterLine = function () { return this.getOption("highlightGutterLine") }, this.setHighlightSelectedWord = function (e) { this.setOption("highlightSelectedWord", e) }, this.getHighlightSelectedWord = function () { return this.$highlightSelectedWord }, this.setAnimatedScroll = function (e) { this.renderer.setAnimatedScroll(e) }, this.getAnimatedScroll = function () { return this.renderer.getAnimatedScroll() }, this.setShowInvisibles = function (e) { this.renderer.setShowInvisibles(e) }, this.getShowInvisibles = function () { return this.renderer.getShowInvisibles() }, this.setDisplayIndentGuides = function (e) { this.renderer.setDisplayIndentGuides(e) }, this.getDisplayIndentGuides = function () { return this.renderer.getDisplayIndentGuides() }, this.setShowPrintMargin = function (e) { this.renderer.setShowPrintMargin(e) }, this.getShowPrintMargin = function () { return this.renderer.getShowPrintMargin() }, this.setPrintMarginColumn = function (e) { this.renderer.setPrintMarginColumn(e) }, this.getPrintMarginColumn = function () { return this.renderer.getPrintMarginColumn() }, this.setReadOnly = function (e) { this.setOption("readOnly", e) }, this.getReadOnly = function () { return this.getOption("readOnly") }, this.setBehavioursEnabled = function (e) { this.setOption("behavioursEnabled", e) }, this.getBehavioursEnabled = function () { return this.getOption("behavioursEnabled") }, this.setWrapBehavioursEnabled = function (e) { this.setOption("wrapBehavioursEnabled", e) }, this.getWrapBehavioursEnabled = function () { return this.getOption("wrapBehavioursEnabled") }, this.setShowFoldWidgets = function (e) { this.setOption("showFoldWidgets", e) }, this.getShowFoldWidgets = function () { return this.getOption("showFoldWidgets") }, this.setFadeFoldWidgets = function (e) { this.setOption("fadeFoldWidgets", e) }, this.getFadeFoldWidgets = function () { return this.getOption("fadeFoldWidgets") }, this.remove = function (e) { this.selection.isEmpty() && (e == "left" ? this.selection.selectLeft() : this.selection.selectRight()); var t = this.getSelectionRange(); if (this.getBehavioursEnabled()) { var n = this.session, r = n.getState(t.start.row), i = n.getMode().transformAction(r, "deletion", this, n, t); if (t.end.column === 0) { var s = n.getTextRange(t); if (s[s.length - 1] == "\n") { var o = n.getLine(t.end.row); /^\s+$/.test(o) && (t.end.column = o.length) } } i && (t = i) } this.session.remove(t), this.clearSelection() }, this.removeWordRight = function () { this.selection.isEmpty() && this.selection.selectWordRight(), this.session.remove(this.getSelectionRange()), this.clearSelection() }, this.removeWordLeft = function () { this.selection.isEmpty() && this.selection.selectWordLeft(), this.session.remove(this.getSelectionRange()), this.clearSelection() }, this.removeToLineStart = function () { this.selection.isEmpty() && this.selection.selectLineStart(), this.selection.isEmpty() && this.selection.selectLeft(), this.session.remove(this.getSelectionRange()), this.clearSelection() }, this.removeToLineEnd = function () { this.selection.isEmpty() && this.selection.selectLineEnd(); var e = this.getSelectionRange(); e.start.column == e.end.column && e.start.row == e.end.row && (e.end.column = 0, e.end.row++), this.session.remove(e), this.clearSelection() }, this.splitLine = function () { this.selection.isEmpty() || (this.session.remove(this.getSelectionRange()), this.clearSelection()); var e = this.getCursorPosition(); this.insert("\n"), this.moveCursorToPosition(e) }, this.transposeLetters = function () { if (!this.selection.isEmpty()) return; var e = this.getCursorPosition(), t = e.column; if (t === 0) return; var n = this.session.getLine(e.row), r, i; t < n.length ? (r = n.charAt(t) + n.charAt(t - 1), i = new p(e.row, t - 1, e.row, t + 1)) : (r = n.charAt(t - 1) + n.charAt(t - 2), i = new p(e.row, t - 2, e.row, t)), this.session.replace(i, r), this.session.selection.moveToPosition(i.end) }, this.toLowerCase = function () { var e = this.getSelectionRange(); this.selection.isEmpty() && this.selection.selectWord(); var t = this.getSelectionRange(), n = this.session.getTextRange(t); this.session.replace(t, n.toLowerCase()), this.selection.setSelectionRange(e) }, this.toUpperCase = function () { var e = this.getSelectionRange(); this.selection.isEmpty() && this.selection.selectWord(); var t = this.getSelectionRange(), n = this.session.getTextRange(t); this.session.replace(t, n.toUpperCase()), this.selection.setSelectionRange(e) }, this.indent = function () { var e = this.session, t = this.getSelectionRange(); if (t.start.row < t.end.row) { var n = this.$getSelectedRows(); e.indentRows(n.first, n.last, " "); return } if (t.start.column < t.end.column) { var r = e.getTextRange(t); if (!/^\s+$/.test(r)) { var n = this.$getSelectedRows(); e.indentRows(n.first, n.last, " "); return } } var i = e.getLine(t.start.row), o = t.start, u = e.getTabSize(), a = e.documentToScreenColumn(o.row, o.column); if (this.session.getUseSoftTabs()) var f = u - a % u, l = s.stringRepeat(" ", f); else { var f = a % u; while (i[t.start.column - 1] == " " && f) t.start.column--, f--; this.selection.setSelectionRange(t), l = " " } return this.insert(l) }, this.blockIndent = function () { var e = this.$getSelectedRows(); this.session.indentRows(e.first, e.last, " ") }, this.blockOutdent = function () { var e = this.session.getSelection(); this.session.outdentRows(e.getRange()) }, this.sortLines = function () { var e = this.$getSelectedRows(), t = this.session, n = []; for (var r = e.first; r <= e.last; r++)n.push(t.getLine(r)); n.sort(function (e, t) { return e.toLowerCase() < t.toLowerCase() ? -1 : e.toLowerCase() > t.toLowerCase() ? 1 : 0 }); var i = new p(0, 0, 0, 0); for (var r = e.first; r <= e.last; r++) { var s = t.getLine(r); i.start.row = r, i.end.row = r, i.end.column = s.length, t.replace(i, n[r - e.first]) } }, this.toggleCommentLines = function () { var e = this.session.getState(this.getCursorPosition().row), t = this.$getSelectedRows(); this.session.getMode().toggleCommentLines(e, this.session, t.first, t.last) }, this.toggleBlockComment = function () { var e = this.getCursorPosition(), t = this.session.getState(e.row), n = this.getSelectionRange(); this.session.getMode().toggleBlockComment(t, this.session, n, e) }, this.getNumberAt = function (e, t) { var n = /[\-]?[0-9]+(?:\.[0-9]+)?/g; n.lastIndex = 0; var r = this.session.getLine(e); while (n.lastIndex < t) { var i = n.exec(r); if (i.index <= t && i.index + i[0].length >= t) { var s = { value: i[0], start: i.index, end: i.index + i[0].length }; return s } } return null }, this.modifyNumber = function (e) { var t = this.selection.getCursor().row, n = this.selection.getCursor().column, r = new p(t, n - 1, t, n), i = this.session.getTextRange(r); if (!isNaN(parseFloat(i)) && isFinite(i)) { var s = this.getNumberAt(t, n); if (s) { var o = s.value.indexOf(".") >= 0 ? s.start + s.value.indexOf(".") + 1 : s.end, u = s.start + s.value.length - o, a = parseFloat(s.value); a *= Math.pow(10, u), o !== s.end && n < o ? e *= Math.pow(10, s.end - n - 1) : e *= Math.pow(10, s.end - n), a += e, a /= Math.pow(10, u); var f = a.toFixed(u), l = new p(t, s.start, t, s.end); this.session.replace(l, f), this.moveCursorTo(t, Math.max(s.start + 1, n + f.length - s.value.length)) } } else this.toggleWord() }, this.$toggleWordPairs = [["first", "last"], ["true", "false"], ["yes", "no"], ["width", "height"], ["top", "bottom"], ["right", "left"], ["on", "off"], ["x", "y"], ["get", "set"], ["max", "min"], ["horizontal", "vertical"], ["show", "hide"], ["add", "remove"], ["up", "down"], ["before", "after"], ["even", "odd"], ["in", "out"], ["inside", "outside"], ["next", "previous"], ["increase", "decrease"], ["attach", "detach"], ["&&", "||"], ["==", "!="]], this.toggleWord = function () { var e = this.selection.getCursor().row, t = this.selection.getCursor().column; this.selection.selectWord(); var n = this.getSelectedText(), r = this.selection.getWordRange().start.column, i = n.replace(/([a-z]+|[A-Z]+)(?=[A-Z_]|$)/g, "$1 ").split(/\s/), o = t - r - 1; o < 0 && (o = 0); var u = 0, a = 0, f = this; n.match(/[A-Za-z0-9_]+/) && i.forEach(function (t, i) { a = u + t.length, o >= u && o <= a && (n = t, f.selection.clearSelection(), f.moveCursorTo(e, u + r), f.selection.selectTo(e, a + r)), u = a }); var l = this.$toggleWordPairs, c; for (var h = 0; h < l.length; h++) { var p = l[h]; for (var d = 0; d <= 1; d++) { var v = +!d, m = n.match(new RegExp("^\\s?_?(" + s.escapeRegExp(p[d]) + ")\\s?$", "i")); if (m) { var g = n.match(new RegExp("([_]|^|\\s)(" + s.escapeRegExp(m[1]) + ")($|\\s)", "g")); g && (c = n.replace(new RegExp(s.escapeRegExp(p[d]), "i"), function (e) { var t = p[v]; return e.toUpperCase() == e ? t = t.toUpperCase() : e.charAt(0).toUpperCase() == e.charAt(0) && (t = t.substr(0, 0) + p[v].charAt(0).toUpperCase() + t.substr(1)), t }), this.insert(c), c = "") } } } }, this.removeLines = function () { var e = this.$getSelectedRows(); this.session.removeFullLines(e.first, e.last), this.clearSelection() }, this.duplicateSelection = function () { var e = this.selection, t = this.session, n = e.getRange(), r = e.isBackwards(); if (n.isEmpty()) { var i = n.start.row; t.duplicateLines(i, i) } else { var s = r ? n.start : n.end, o = t.insert(s, t.getTextRange(n), !1); n.start = s, n.end = o, e.setSelectionRange(n, r) } }, this.moveLinesDown = function () { this.$moveLines(1, !1) }, this.moveLinesUp = function () { this.$moveLines(-1, !1) }, this.moveText = function (e, t, n) { return this.session.moveText(e, t, n) }, this.copyLinesUp = function () { this.$moveLines(-1, !0) }, this.copyLinesDown = function () { this.$moveLines(1, !0) }, this.$moveLines = function (e, t) { var n, r, i = this.selection; if (!i.inMultiSelectMode || this.inVirtualSelectionMode) { var s = i.toOrientedRange(); n = this.$getSelectedRows(s), r = this.session.$moveLines(n.first, n.last, t ? 0 : e), t && e == -1 && (r = 0), s.moveBy(r, 0), i.fromOrientedRange(s) } else { var o = i.rangeList.ranges; i.rangeList.detach(this.session), this.inVirtualSelectionMode = !0; var u = 0, a = 0, f = o.length; for (var l = 0; l < f; l++) { var c = l; o[l].moveBy(u, 0), n = this.$getSelectedRows(o[l]); var h = n.first, p = n.last; while (++l < f) { a && o[l].moveBy(a, 0); var d = this.$getSelectedRows(o[l]); if (t && d.first != p) break; if (!t && d.first > p + 1) break; p = d.last } l--, u = this.session.$moveLines(h, p, t ? 0 : e), t && e == -1 && (c = l + 1); while (c <= l) o[c].moveBy(u, 0), c++; t || (u = 0), a += u } i.fromOrientedRange(i.ranges[0]), i.rangeList.attach(this.session), this.inVirtualSelectionMode = !1 } }, this.$getSelectedRows = function (e) { return e = (e || this.getSelectionRange()).collapseRows(), { first: this.session.getRowFoldStart(e.start.row), last: this.session.getRowFoldEnd(e.end.row) } }, this.onCompositionStart = function (e) { this.renderer.showComposition(e) }, this.onCompositionUpdate = function (e) { this.renderer.setCompositionText(e) }, this.onCompositionEnd = function () { this.renderer.hideComposition() }, this.getFirstVisibleRow = function () { return this.renderer.getFirstVisibleRow() }, this.getLastVisibleRow = function () { return this.renderer.getLastVisibleRow() }, this.isRowVisible = function (e) { return e >= this.getFirstVisibleRow() && e <= this.getLastVisibleRow() }, this.isRowFullyVisible = function (e) { return e >= this.renderer.getFirstFullyVisibleRow() && e <= this.renderer.getLastFullyVisibleRow() }, this.$getVisibleRowCount = function () { return this.renderer.getScrollBottomRow() - this.renderer.getScrollTopRow() + 1 }, this.$moveByPage = function (e, t) { var n = this.renderer, r = this.renderer.layerConfig, i = e * Math.floor(r.height / r.lineHeight); t === !0 ? this.selection.$moveSelection(function () { this.moveCursorBy(i, 0) }) : t === !1 && (this.selection.moveCursorBy(i, 0), this.selection.clearSelection()); var s = n.scrollTop; n.scrollBy(0, i * r.lineHeight), t != null && n.scrollCursorIntoView(null, .5), n.animateScrolling(s) }, this.selectPageDown = function () { this.$moveByPage(1, !0) }, this.selectPageUp = function () { this.$moveByPage(-1, !0) }, this.gotoPageDown = function () { this.$moveByPage(1, !1) }, this.gotoPageUp = function () { this.$moveByPage(-1, !1) }, this.scrollPageDown = function () { this.$moveByPage(1) }, this.scrollPageUp = function () { this.$moveByPage(-1) }, this.scrollToRow = function (e) { this.renderer.scrollToRow(e) }, this.scrollToLine = function (e, t, n, r) { this.renderer.scrollToLine(e, t, n, r) }, this.centerSelection = function () { var e = this.getSelectionRange(), t = { row: Math.floor(e.start.row + (e.end.row - e.start.row) / 2), column: Math.floor(e.start.column + (e.end.column - e.start.column) / 2) }; this.renderer.alignCursor(t, .5) }, this.getCursorPosition = function () { return this.selection.getCursor() }, this.getCursorPositionScreen = function () { return this.session.documentToScreenPosition(this.getCursorPosition()) }, this.getSelectionRange = function () { return this.selection.getRange() }, this.selectAll = function () { this.selection.selectAll() }, this.clearSelection = function () { this.selection.clearSelection() }, this.moveCursorTo = function (e, t) { this.selection.moveCursorTo(e, t) }, this.moveCursorToPosition = function (e) { this.selection.moveCursorToPosition(e) }, this.jumpToMatching = function (e, t) { var n = this.getCursorPosition(), r = new y(this.session, n.row, n.column), i = r.getCurrentToken(), s = i || r.stepForward(); if (!s) return; var o, u = !1, a = {}, f = n.column - s.start, l, c = { ")": "(", "(": "(", "]": "[", "[": "[", "{": "{", "}": "{" }; do { if (s.value.match(/[{}()\[\]]/g)) for (; f < s.value.length && !u; f++) { if (!c[s.value[f]]) continue; l = c[s.value[f]] + "." + s.type.replace("rparen", "lparen"), isNaN(a[l]) && (a[l] = 0); switch (s.value[f]) { case "(": case "[": case "{": a[l]++; break; case ")": case "]": case "}": a[l]--, a[l] === -1 && (o = "bracket", u = !0) } } else s.type.indexOf("tag-name") !== -1 && (isNaN(a[s.value]) && (a[s.value] = 0), i.value === "<" ? a[s.value]++ : i.value === "= 0; --s)this.$tryReplace(n[s], e) && r++; return this.selection.setSelectionRange(i), r }, this.$tryReplace = function (e, t) { var n = this.session.getTextRange(e); return t = this.$search.replace(n, t), t !== null ? (e.end = this.session.replace(e, t), e) : null }, this.getLastSearchOptions = function () { return this.$search.getOptions() }, this.find = function (e, t, n) { t || (t = {}), typeof e == "string" || e instanceof RegExp ? t.needle = e : typeof e == "object" && r.mixin(t, e); var i = this.selection.getRange(); t.needle == null && (e = this.session.getTextRange(i) || this.$search.$options.needle, e || (i = this.session.getWordRange(i.start.row, i.start.column), e = this.session.getTextRange(i)), this.$search.set({ needle: e })), this.$search.set(t), t.start || this.$search.set({ start: i }); var s = this.$search.find(this.session); if (t.preventScroll) return s; if (s) return this.revealRange(s, n), s; t.backwards ? i.start = i.end : i.end = i.start, this.selection.setRange(i) }, this.findNext = function (e, t) { this.find({ skipCurrent: !0, backwards: !1 }, e, t) }, this.findPrevious = function (e, t) { this.find(e, { skipCurrent: !0, backwards: !0 }, t) }, this.revealRange = function (e, t) { this.session.unfold(e), this.selection.setSelectionRange(e); var n = this.renderer.scrollTop; this.renderer.scrollSelectionIntoView(e.start, e.end, .5), t !== !1 && this.renderer.animateScrolling(n) }, this.undo = function () { this.session.getUndoManager().undo(this.session), this.renderer.scrollCursorIntoView(null, .5) }, this.redo = function () { this.session.getUndoManager().redo(this.session), this.renderer.scrollCursorIntoView(null, .5) }, this.destroy = function () { this.$toDestroy && (this.$toDestroy.forEach(function (e) { e.destroy() }), this.$toDestroy = null), this.renderer.destroy(), this._signal("destroy", this), this.session && this.session.destroy(), this._$emitInputEvent && this._$emitInputEvent.cancel(), this.removeAllListeners() }, this.setAutoScrollEditorIntoView = function (e) { if (!e) return; var t, n = this, r = !1; this.$scrollAnchor || (this.$scrollAnchor = document.createElement("div")); var i = this.$scrollAnchor; i.style.cssText = "position:absolute", this.container.insertBefore(i, this.container.firstChild); var s = this.on("changeSelection", function () { r = !0 }), o = this.renderer.on("beforeRender", function () { r && (t = n.renderer.container.getBoundingClientRect()) }), u = this.renderer.on("afterRender", function () { if (r && t && (n.isFocused() || n.searchBox && n.searchBox.isFocused())) { var e = n.renderer, s = e.$cursorLayer.$pixelPos, o = e.layerConfig, u = s.top - o.offset; s.top >= 0 && u + t.top < 0 ? r = !0 : s.top < o.height && s.top + t.top + o.lineHeight > window.innerHeight ? r = !1 : r = null, r != null && (i.style.top = u + "px", i.style.left = s.left + "px", i.style.height = o.lineHeight + "px", i.scrollIntoView(r)), r = t = null } }); this.setAutoScrollEditorIntoView = function (e) { if (e) return; delete this.setAutoScrollEditorIntoView, this.off("changeSelection", s), this.renderer.off("afterRender", u), this.renderer.off("beforeRender", o) } }, this.$resetCursorStyle = function () { var e = this.$cursorStyle || "ace", t = this.renderer.$cursorLayer; if (!t) return; t.setSmoothBlinking(/smooth/.test(e)), t.isBlinking = !this.$readOnly && e != "wide", i.setCssClass(t.element, "ace_slim-cursors", /slim/.test(e)) }, this.prompt = function (e, t, n) { var r = this; g.loadModule("./ext/prompt", function (i) { i.prompt(r, e, t, n) }) } }.call(w.prototype), g.defineOptions(w.prototype, "editor", { selectionStyle: { set: function (e) { this.onSelectionChange(), this._signal("changeSelectionStyle", { data: e }) }, initialValue: "line" }, highlightActiveLine: { set: function () { this.$updateHighlightActiveLine() }, initialValue: !0 }, highlightSelectedWord: { set: function (e) { this.$onSelectionChange() }, initialValue: !0 }, readOnly: { set: function (e) { this.textInput.setReadOnly(e), this.$resetCursorStyle() }, initialValue: !1 }, copyWithEmptySelection: { set: function (e) { this.textInput.setCopyWithEmptySelection(e) }, initialValue: !1 }, cursorStyle: { set: function (e) { this.$resetCursorStyle() }, values: ["ace", "slim", "smooth", "wide"], initialValue: "ace" }, mergeUndoDeltas: { values: [!1, !0, "always"], initialValue: !0 }, behavioursEnabled: { initialValue: !0 }, wrapBehavioursEnabled: { initialValue: !0 }, enableAutoIndent: { initialValue: !0 }, autoScrollEditorIntoView: { set: function (e) { this.setAutoScrollEditorIntoView(e) } }, keyboardHandler: { set: function (e) { this.setKeyboardHandler(e) }, get: function () { return this.$keybindingId }, handlesSet: !0 }, value: { set: function (e) { this.session.setValue(e) }, get: function () { return this.getValue() }, handlesSet: !0, hidden: !0 }, session: { set: function (e) { this.setSession(e) }, get: function () { return this.session }, handlesSet: !0, hidden: !0 }, showLineNumbers: { set: function (e) { this.renderer.$gutterLayer.setShowLineNumbers(e), this.renderer.$loop.schedule(this.renderer.CHANGE_GUTTER), e && this.$relativeLineNumbers ? E.attach(this) : E.detach(this) }, initialValue: !0 }, relativeLineNumbers: { set: function (e) { this.$showLineNumbers && e ? E.attach(this) : E.detach(this) } }, placeholder: { set: function (e) { this.$updatePlaceholder || (this.$updatePlaceholder = function () { var e = this.session && (this.renderer.$composition || this.getValue()); if (e && this.renderer.placeholderNode) this.renderer.off("afterRender", this.$updatePlaceholder), i.removeCssClass(this.container, "ace_hasPlaceholder"), this.renderer.placeholderNode.remove(), this.renderer.placeholderNode = null; else if (!e && !this.renderer.placeholderNode) { this.renderer.on("afterRender", this.$updatePlaceholder), i.addCssClass(this.container, "ace_hasPlaceholder"); var t = i.createElement("div"); t.className = "ace_placeholder", t.textContent = this.$placeholder || "", this.renderer.placeholderNode = t, this.renderer.content.appendChild(this.renderer.placeholderNode) } else !e && this.renderer.placeholderNode && (this.renderer.placeholderNode.textContent = this.$placeholder || "") }.bind(this), this.on("input", this.$updatePlaceholder)), this.$updatePlaceholder() } }, hScrollBarAlwaysVisible: "renderer", vScrollBarAlwaysVisible: "renderer", highlightGutterLine: "renderer", animatedScroll: "renderer", showInvisibles: "renderer", showPrintMargin: "renderer", printMarginColumn: "renderer", printMargin: "renderer", fadeFoldWidgets: "renderer", showFoldWidgets: "renderer", displayIndentGuides: "renderer", showGutter: "renderer", fontSize: "renderer", fontFamily: "renderer", maxLines: "renderer", minLines: "renderer", scrollPastEnd: "renderer", fixedWidthGutter: "renderer", theme: "renderer", hasCssTransforms: "renderer", maxPixelHeight: "renderer", useTextareaForIME: "renderer", scrollSpeed: "$mouseHandler", dragDelay: "$mouseHandler", dragEnabled: "$mouseHandler", focusTimeout: "$mouseHandler", tooltipFollowsMouse: "$mouseHandler", firstLineNumber: "session", overwrite: "session", newLineMode: "session", useWorker: "session", useSoftTabs: "session", navigateWithinSoftTabs: "session", tabSize: "session", wrap: "session", indentedSoftWrap: "session", foldStyle: "session", mode: "session" }); var E = { getText: function (e, t) { return (Math.abs(e.selection.lead.row - t) || t + 1 + (t < 9 ? "\u00b7" : "")) + "" }, getWidth: function (e, t, n) { return Math.max(t.toString().length, (n.lastRow + 1).toString().length, 2) * n.characterWidth }, update: function (e, t) { t.renderer.$loop.schedule(t.renderer.CHANGE_GUTTER) }, attach: function (e) { e.renderer.$gutterLayer.$renderer = this, e.on("changeSelection", this.update), this.update(null, e) }, detach: function (e) { e.renderer.$gutterLayer.$renderer == this && (e.renderer.$gutterLayer.$renderer = null), e.off("changeSelection", this.update), this.update(null, e) } }; t.Editor = w }), define("ace/undomanager", ["require", "exports", "module", "ace/range"], function (e, t, n) { "use strict"; function i(e, t) { for (var n = t; n--;) { var r = e[n]; if (r && !r[0].ignore) { while (n < t - 1) { var i = d(e[n], e[n + 1]); e[n] = i[0], e[n + 1] = i[1], n++ } return !0 } } } function a(e) { var t = e.action == "insert", n = e.start, r = e.end, i = (r.row - n.row) * (t ? 1 : -1), s = (r.column - n.column) * (t ? 1 : -1); t && (r = n); for (var o in this.marks) { var a = this.marks[o], f = u(a, n); if (f < 0) continue; if (f === 0 && t) { if (a.bias != 1) { a.bias == -1; continue } f = 1 } var l = t ? f : u(a, r); if (l > 0) { a.row += i, a.column += a.row == r.row ? s : 0; continue } !t && l <= 0 && (a.row = n.row, a.column = n.column, l === 0 && (a.bias = 1)) } } function f(e) { return { row: e.row, column: e.column } } function l(e) { return { start: f(e.start), end: f(e.end), action: e.action, lines: e.lines.slice() } } function c(e) { e = e || this; if (Array.isArray(e)) return e.map(c).join("\n"); var t = ""; e.action ? (t = e.action == "insert" ? "+" : "-", t += "[" + e.lines + "]") : e.value && (Array.isArray(e.value) ? t = e.value.map(h).join("\n") : t = h(e.value)), e.start && (t += h(e)); if (e.id || e.rev) t += " (" + (e.id || e.rev) + ")"; return t } function h(e) { return e.start.row + ":" + e.start.column + "=>" + e.end.row + ":" + e.end.column } function p(e, t) { var n = e.action == "insert", r = t.action == "insert"; if (n && r) if (o(t.start, e.end) >= 0) m(t, e, -1); else { if (!(o(t.start, e.start) <= 0)) return null; m(e, t, 1) } else if (n && !r) if (o(t.start, e.end) >= 0) m(t, e, -1); else { if (!(o(t.end, e.start) <= 0)) return null; m(e, t, -1) } else if (!n && r) if (o(t.start, e.start) >= 0) m(t, e, 1); else { if (!(o(t.start, e.start) <= 0)) return null; m(e, t, 1) } else if (!n && !r) if (o(t.start, e.start) >= 0) m(t, e, 1); else { if (!(o(t.end, e.start) <= 0)) return null; m(e, t, -1) } return [t, e] } function d(e, t) { for (var n = e.length; n--;)for (var r = 0; r < t.length; r++)if (!p(e[n], t[r])) { while (n < e.length) { while (r--) p(t[r], e[n]); r = t.length, n++ } return [e, t] } return e.selectionBefore = t.selectionBefore = e.selectionAfter = t.selectionAfter = null, [t, e] } function v(e, t) { var n = e.action == "insert", r = t.action == "insert"; if (n && r) o(e.start, t.start) < 0 ? m(t, e, 1) : m(e, t, 1); else if (n && !r) o(e.start, t.end) >= 0 ? m(e, t, -1) : o(e.start, t.start) <= 0 ? m(t, e, 1) : (m(e, s.fromPoints(t.start, e.start), -1), m(t, e, 1)); else if (!n && r) o(t.start, e.end) >= 0 ? m(t, e, -1) : o(t.start, e.start) <= 0 ? m(e, t, 1) : (m(t, s.fromPoints(e.start, t.start), -1), m(e, t, 1)); else if (!n && !r) if (o(t.start, e.end) >= 0) m(t, e, -1); else { if (!(o(t.end, e.start) <= 0)) { var i, u; return o(e.start, t.start) < 0 && (i = e, e = y(e, t.start)), o(e.end, t.end) > 0 && (u = y(e, t.end)), g(t.end, e.start, e.end, -1), u && !i && (e.lines = u.lines, e.start = u.start, e.end = u.end, u = e), [t, i, u].filter(Boolean) } m(e, t, -1) } return [t, e] } function m(e, t, n) { g(e.start, t.start, t.end, n), g(e.end, t.start, t.end, n) } function g(e, t, n, r) { e.row == (r == 1 ? t : n).row && (e.column += r * (n.column - t.column)), e.row += r * (n.row - t.row) } function y(e, t) { var n = e.lines, r = e.end; e.end = f(t); var i = e.end.row - e.start.row, s = n.splice(i, n.length), o = i ? t.column : t.column - e.start.column; n.push(s[0].substring(0, o)), s[0] = s[0].substr(o); var u = { start: f(t), end: r, lines: s, action: e.action }; return u } function b(e, t) { t = l(t); for (var n = e.length; n--;) { var r = e[n]; for (var i = 0; i < r.length; i++) { var s = r[i], o = v(s, t); t = o[0], o.length != 2 && (o[2] ? (r.splice(i + 1, 1, o[1], o[2]), i++) : o[1] || (r.splice(i, 1), i--)) } r.length || e.splice(n, 1) } return e } function w(e, t) { for (var n = 0; n < t.length; n++) { var r = t[n]; for (var i = 0; i < r.length; i++)b(e, r[i]) } } var r = function () { this.$maxRev = 0, this.$fromUndo = !1, this.reset() }; (function () { this.addSession = function (e) { this.$session = e }, this.add = function (e, t, n) { if (this.$fromUndo) return; if (e == this.$lastDelta) return; this.$keepRedoStack || (this.$redoStack.length = 0); if (t === !1 || !this.lastDeltas) this.lastDeltas = [], this.$undoStack.push(this.lastDeltas), e.id = this.$rev = ++this.$maxRev; if (e.action == "remove" || e.action == "insert") this.$lastDelta = e; this.lastDeltas.push(e) }, this.addSelection = function (e, t) { this.selections.push({ value: e, rev: t || this.$rev }) }, this.startNewGroup = function () { return this.lastDeltas = null, this.$rev }, this.markIgnored = function (e, t) { t == null && (t = this.$rev + 1); var n = this.$undoStack; for (var r = n.length; r--;) { var i = n[r][0]; if (i.id <= e) break; i.id < t && (i.ignore = !0) } this.lastDeltas = null }, this.getSelection = function (e, t) { var n = this.selections; for (var r = n.length; r--;) { var i = n[r]; if (i.rev < e) return t && (i = n[r + 1]), i } }, this.getRevision = function () { return this.$rev }, this.getDeltas = function (e, t) { t == null && (t = this.$rev + 1); var n = this.$undoStack, r = null, i = 0; for (var s = n.length; s--;) { var o = n[s][0]; o.id < t && !r && (r = s + 1); if (o.id <= e) { i = s + 1; break } } return n.slice(i, r) }, this.getChangedRanges = function (e, t) { t == null && (t = this.$rev + 1) }, this.getChangedLines = function (e, t) { t == null && (t = this.$rev + 1) }, this.undo = function (e, t) { this.lastDeltas = null; var n = this.$undoStack; if (!i(n, n.length)) return; e || (e = this.$session), this.$redoStackBaseRev !== this.$rev && this.$redoStack.length && (this.$redoStack = []), this.$fromUndo = !0; var r = n.pop(), s = null; return r && (s = e.undoChanges(r, t), this.$redoStack.push(r), this.$syncRev()), this.$fromUndo = !1, s }, this.redo = function (e, t) { this.lastDeltas = null, e || (e = this.$session), this.$fromUndo = !0; if (this.$redoStackBaseRev != this.$rev) { var n = this.getDeltas(this.$redoStackBaseRev, this.$rev + 1); w(this.$redoStack, n), this.$redoStackBaseRev = this.$rev, this.$redoStack.forEach(function (e) { e[0].id = ++this.$maxRev }, this) } var r = this.$redoStack.pop(), i = null; return r && (i = e.redoChanges(r, t), this.$undoStack.push(r), this.$syncRev()), this.$fromUndo = !1, i }, this.$syncRev = function () { var e = this.$undoStack, t = e[e.length - 1], n = t && t[0].id || 0; this.$redoStackBaseRev = n, this.$rev = n }, this.reset = function () { this.lastDeltas = null, this.$lastDelta = null, this.$undoStack = [], this.$redoStack = [], this.$rev = 0, this.mark = 0, this.$redoStackBaseRev = this.$rev, this.selections = [] }, this.canUndo = function () { return this.$undoStack.length > 0 }, this.canRedo = function () { return this.$redoStack.length > 0 }, this.bookmark = function (e) { e == undefined && (e = this.$rev), this.mark = e }, this.isAtBookmark = function () { return this.$rev === this.mark }, this.toJSON = function () { }, this.fromJSON = function () { }, this.hasUndo = this.canUndo, this.hasRedo = this.canRedo, this.isClean = this.isAtBookmark, this.markClean = this.bookmark, this.$prettyPrint = function (e) { return e ? c(e) : c(this.$undoStack) + "\n---\n" + c(this.$redoStack) } }).call(r.prototype); var s = e("./range").Range, o = s.comparePoints, u = s.comparePoints; t.UndoManager = r }), define("ace/layer/lines", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { "use strict"; var r = e("../lib/dom"), i = function (e, t) { this.element = e, this.canvasHeight = t || 5e5, this.element.style.height = this.canvasHeight * 2 + "px", this.cells = [], this.cellCache = [], this.$offsetCoefficient = 0 }; (function () { this.moveContainer = function (e) { r.translate(this.element, 0, -(e.firstRowScreen * e.lineHeight % this.canvasHeight) - e.offset * this.$offsetCoefficient) }, this.pageChanged = function (e, t) { return Math.floor(e.firstRowScreen * e.lineHeight / this.canvasHeight) !== Math.floor(t.firstRowScreen * t.lineHeight / this.canvasHeight) }, this.computeLineTop = function (e, t, n) { var r = t.firstRowScreen * t.lineHeight, i = Math.floor(r / this.canvasHeight), s = n.documentToScreenRow(e, 0) * t.lineHeight; return s - i * this.canvasHeight }, this.computeLineHeight = function (e, t, n) { return t.lineHeight * n.getRowLineCount(e) }, this.getLength = function () { return this.cells.length }, this.get = function (e) { return this.cells[e] }, this.shift = function () { this.$cacheCell(this.cells.shift()) }, this.pop = function () { this.$cacheCell(this.cells.pop()) }, this.push = function (e) { if (Array.isArray(e)) { this.cells.push.apply(this.cells, e); var t = r.createFragment(this.element); for (var n = 0; n < e.length; n++)t.appendChild(e[n].element); this.element.appendChild(t) } else this.cells.push(e), this.element.appendChild(e.element) }, this.unshift = function (e) { if (Array.isArray(e)) { this.cells.unshift.apply(this.cells, e); var t = r.createFragment(this.element); for (var n = 0; n < e.length; n++)t.appendChild(e[n].element); this.element.firstChild ? this.element.insertBefore(t, this.element.firstChild) : this.element.appendChild(t) } else this.cells.unshift(e), this.element.insertAdjacentElement("afterbegin", e.element) }, this.last = function () { return this.cells.length ? this.cells[this.cells.length - 1] : null }, this.$cacheCell = function (e) { if (!e) return; e.element.remove(), this.cellCache.push(e) }, this.createCell = function (e, t, n, i) { var s = this.cellCache.pop(); if (!s) { var o = r.createElement("div"); i && i(o), this.element.appendChild(o), s = { element: o, text: "", row: e } } return s.row = e, s } }).call(i.prototype), t.Lines = i }), define("ace/layer/gutter", ["require", "exports", "module", "ace/lib/dom", "ace/lib/oop", "ace/lib/lang", "ace/lib/event_emitter", "ace/layer/lines"], function (e, t, n) { "use strict"; function f(e) { var t = document.createTextNode(""); e.appendChild(t); var n = r.createElement("span"); return e.appendChild(n), e } var r = e("../lib/dom"), i = e("../lib/oop"), s = e("../lib/lang"), o = e("../lib/event_emitter").EventEmitter, u = e("./lines").Lines, a = function (e) { this.element = r.createElement("div"), this.element.className = "ace_layer ace_gutter-layer", e.appendChild(this.element), this.setShowFoldWidgets(this.$showFoldWidgets), this.gutterWidth = 0, this.$annotations = [], this.$updateAnnotations = this.$updateAnnotations.bind(this), this.$lines = new u(this.element), this.$lines.$offsetCoefficient = 1 }; (function () { i.implement(this, o), this.setSession = function (e) { this.session && this.session.off("change", this.$updateAnnotations), this.session = e, e && e.on("change", this.$updateAnnotations) }, this.addGutterDecoration = function (e, t) { window.console && console.warn && console.warn("deprecated use session.addGutterDecoration"), this.session.addGutterDecoration(e, t) }, this.removeGutterDecoration = function (e, t) { window.console && console.warn && console.warn("deprecated use session.removeGutterDecoration"), this.session.removeGutterDecoration(e, t) }, this.setAnnotations = function (e) { this.$annotations = []; for (var t = 0; t < e.length; t++) { var n = e[t], r = n.row, i = this.$annotations[r]; i || (i = this.$annotations[r] = { text: [] }); var o = n.text; o = o ? s.escapeHTML(o) : n.html || "", i.text.indexOf(o) === -1 && i.text.push(o); var u = n.type; u == "error" ? i.className = " ace_error" : u == "warning" && i.className != " ace_error" ? i.className = " ace_warning" : u == "info" && !i.className && (i.className = " ace_info") } }, this.$updateAnnotations = function (e) { if (!this.$annotations.length) return; var t = e.start.row, n = e.end.row - t; if (n !== 0) if (e.action == "remove") this.$annotations.splice(t, n + 1, null); else { var r = new Array(n + 1); r.unshift(t, 1), this.$annotations.splice.apply(this.$annotations, r) } }, this.update = function (e) { this.config = e; var t = this.session, n = e.firstRow, r = Math.min(e.lastRow + e.gutterOffset, t.getLength() - 1); this.oldLastRow = r, this.config = e, this.$lines.moveContainer(e), this.$updateCursorRow(); var i = t.getNextFoldLine(n), s = i ? i.start.row : Infinity, o = null, u = -1, a = n; for (; ;) { a > s && (a = i.end.row + 1, i = t.getNextFoldLine(a, i), s = i ? i.start.row : Infinity); if (a > r) { while (this.$lines.getLength() > u + 1) this.$lines.pop(); break } o = this.$lines.get(++u), o ? o.row = a : (o = this.$lines.createCell(a, e, this.session, f), this.$lines.push(o)), this.$renderCell(o, e, i, a), a++ } this._signal("afterRender"), this.$updateGutterWidth(e) }, this.$updateGutterWidth = function (e) { var t = this.session, n = t.gutterRenderer || this.$renderer, r = t.$firstLineNumber, i = this.$lines.last() ? this.$lines.last().text : ""; if (this.$fixedWidth || t.$useWrapMode) i = t.getLength() + r - 1; var s = n ? n.getWidth(t, i, e) : i.toString().length * e.characterWidth, o = this.$padding || this.$computePadding(); s += o.left + o.right, s !== this.gutterWidth && !isNaN(s) && (this.gutterWidth = s, this.element.parentNode.style.width = this.element.style.width = Math.ceil(this.gutterWidth) + "px", this._signal("changeGutterWidth", s)) }, this.$updateCursorRow = function () { if (!this.$highlightGutterLine) return; var e = this.session.selection.getCursor(); if (this.$cursorRow === e.row) return; this.$cursorRow = e.row }, this.updateLineHighlight = function () { if (!this.$highlightGutterLine) return; var e = this.session.selection.cursor.row; this.$cursorRow = e; if (this.$cursorCell && this.$cursorCell.row == e) return; this.$cursorCell && (this.$cursorCell.element.className = this.$cursorCell.element.className.replace("ace_gutter-active-line ", "")); var t = this.$lines.cells; this.$cursorCell = null; for (var n = 0; n < t.length; n++) { var r = t[n]; if (r.row >= this.$cursorRow) { if (r.row > this.$cursorRow) { var i = this.session.getFoldLine(this.$cursorRow); if (!(n > 0 && i && i.start.row == t[n - 1].row)) break; r = t[n - 1] } r.element.className = "ace_gutter-active-line " + r.element.className, this.$cursorCell = r; break } } }, this.scrollLines = function (e) { var t = this.config; this.config = e, this.$updateCursorRow(); if (this.$lines.pageChanged(t, e)) return this.update(e); this.$lines.moveContainer(e); var n = Math.min(e.lastRow + e.gutterOffset, this.session.getLength() - 1), r = this.oldLastRow; this.oldLastRow = n; if (!t || r < e.firstRow) return this.update(e); if (n < t.firstRow) return this.update(e); if (t.firstRow < e.firstRow) for (var i = this.session.getFoldedRowCount(t.firstRow, e.firstRow - 1); i > 0; i--)this.$lines.shift(); if (r > n) for (var i = this.session.getFoldedRowCount(n + 1, r); i > 0; i--)this.$lines.pop(); e.firstRow < t.firstRow && this.$lines.unshift(this.$renderLines(e, e.firstRow, t.firstRow - 1)), n > r && this.$lines.push(this.$renderLines(e, r + 1, n)), this.updateLineHighlight(), this._signal("afterRender"), this.$updateGutterWidth(e) }, this.$renderLines = function (e, t, n) { var r = [], i = t, s = this.session.getNextFoldLine(i), o = s ? s.start.row : Infinity; for (; ;) { i > o && (i = s.end.row + 1, s = this.session.getNextFoldLine(i, s), o = s ? s.start.row : Infinity); if (i > n) break; var u = this.$lines.createCell(i, e, this.session, f); this.$renderCell(u, e, s, i), r.push(u), i++ } return r }, this.$renderCell = function (e, t, n, i) { var s = e.element, o = this.session, u = s.childNodes[0], a = s.childNodes[1], f = o.$firstLineNumber, l = o.$breakpoints, c = o.$decorations, h = o.gutterRenderer || this.$renderer, p = this.$showFoldWidgets && o.foldWidgets, d = n ? n.start.row : Number.MAX_VALUE, v = "ace_gutter-cell "; this.$highlightGutterLine && (i == this.$cursorRow || n && i < this.$cursorRow && i >= d && this.$cursorRow <= n.end.row) && (v += "ace_gutter-active-line ", this.$cursorCell != e && (this.$cursorCell && (this.$cursorCell.element.className = this.$cursorCell.element.className.replace("ace_gutter-active-line ", "")), this.$cursorCell = e)), l[i] && (v += l[i]), c[i] && (v += c[i]), this.$annotations[i] && (v += this.$annotations[i].className), s.className != v && (s.className = v); if (p) { var m = p[i]; m == null && (m = p[i] = o.getFoldWidget(i)) } if (m) { var v = "ace_fold-widget ace_" + m; m == "start" && i == d && i < n.end.row ? v += " ace_closed" : v += " ace_open", a.className != v && (a.className = v); var g = t.lineHeight + "px"; r.setStyle(a.style, "height", g), r.setStyle(a.style, "display", "inline-block") } else a && r.setStyle(a.style, "display", "none"); var y = (h ? h.getText(o, i) : i + f).toString(); return y !== u.data && (u.data = y), r.setStyle(e.element.style, "height", this.$lines.computeLineHeight(i, t, o) + "px"), r.setStyle(e.element.style, "top", this.$lines.computeLineTop(i, t, o) + "px"), e.text = y, e }, this.$fixedWidth = !1, this.$highlightGutterLine = !0, this.$renderer = "", this.setHighlightGutterLine = function (e) { this.$highlightGutterLine = e }, this.$showLineNumbers = !0, this.$renderer = "", this.setShowLineNumbers = function (e) { this.$renderer = !e && { getWidth: function () { return 0 }, getText: function () { return "" } } }, this.getShowLineNumbers = function () { return this.$showLineNumbers }, this.$showFoldWidgets = !0, this.setShowFoldWidgets = function (e) { e ? r.addCssClass(this.element, "ace_folding-enabled") : r.removeCssClass(this.element, "ace_folding-enabled"), this.$showFoldWidgets = e, this.$padding = null }, this.getShowFoldWidgets = function () { return this.$showFoldWidgets }, this.$computePadding = function () { if (!this.element.firstChild) return { left: 0, right: 0 }; var e = r.computedStyle(this.element.firstChild); return this.$padding = {}, this.$padding.left = (parseInt(e.borderLeftWidth) || 0) + (parseInt(e.paddingLeft) || 0) + 1, this.$padding.right = (parseInt(e.borderRightWidth) || 0) + (parseInt(e.paddingRight) || 0), this.$padding }, this.getRegion = function (e) { var t = this.$padding || this.$computePadding(), n = this.element.getBoundingClientRect(); if (e.x < t.left + n.left) return "markers"; if (this.$showFoldWidgets && e.x > n.right - t.right) return "foldWidgets" } }).call(a.prototype), t.Gutter = a }), define("ace/layer/marker", ["require", "exports", "module", "ace/range", "ace/lib/dom"], function (e, t, n) { "use strict"; var r = e("../range").Range, i = e("../lib/dom"), s = function (e) { this.element = i.createElement("div"), this.element.className = "ace_layer ace_marker-layer", e.appendChild(this.element) }; (function () { function e(e, t, n, r) { return (e ? 1 : 0) | (t ? 2 : 0) | (n ? 4 : 0) | (r ? 8 : 0) } this.$padding = 0, this.setPadding = function (e) { this.$padding = e }, this.setSession = function (e) { this.session = e }, this.setMarkers = function (e) { this.markers = e }, this.elt = function (e, t) { var n = this.i != -1 && this.element.childNodes[this.i]; n ? this.i++ : (n = document.createElement("div"), this.element.appendChild(n), this.i = -1), n.style.cssText = t, n.className = e }, this.update = function (e) { if (!e) return; this.config = e, this.i = 0; var t; for (var n in this.markers) { var r = this.markers[n]; if (!r.range) { r.update(t, this, this.session, e); continue } var i = r.range.clipRows(e.firstRow, e.lastRow); if (i.isEmpty()) continue; i = i.toScreenRange(this.session); if (r.renderer) { var s = this.$getTop(i.start.row, e), o = this.$padding + i.start.column * e.characterWidth; r.renderer(t, i, o, s, e) } else r.type == "fullLine" ? this.drawFullLineMarker(t, i, r.clazz, e) : r.type == "screenLine" ? this.drawScreenLineMarker(t, i, r.clazz, e) : i.isMultiLine() ? r.type == "text" ? this.drawTextMarker(t, i, r.clazz, e) : this.drawMultiLineMarker(t, i, r.clazz, e) : this.drawSingleLineMarker(t, i, r.clazz + " ace_start" + " ace_br15", e) } if (this.i != -1) while (this.i < this.element.childElementCount) this.element.removeChild(this.element.lastChild) }, this.$getTop = function (e, t) { return (e - t.firstRowScreen) * t.lineHeight }, this.drawTextMarker = function (t, n, i, s, o) { var u = this.session, a = n.start.row, f = n.end.row, l = a, c = 0, h = 0, p = u.getScreenLastRowColumn(l), d = new r(l, n.start.column, l, h); for (; l <= f; l++)d.start.row = d.end.row = l, d.start.column = l == a ? n.start.column : u.getRowWrapIndent(l), d.end.column = p, c = h, h = p, p = l + 1 < f ? u.getScreenLastRowColumn(l + 1) : l == f ? 0 : n.end.column, this.drawSingleLineMarker(t, d, i + (l == a ? " ace_start" : "") + " ace_br" + e(l == a || l == a + 1 && n.start.column, c < h, h > p, l == f), s, l == f ? 0 : 1, o) }, this.drawMultiLineMarker = function (e, t, n, r, i) { var s = this.$padding, o = r.lineHeight, u = this.$getTop(t.start.row, r), a = s + t.start.column * r.characterWidth; i = i || ""; if (this.session.$bidiHandler.isBidiRow(t.start.row)) { var f = t.clone(); f.end.row = f.start.row, f.end.column = this.session.getLine(f.start.row).length, this.drawBidiSingleLineMarker(e, f, n + " ace_br1 ace_start", r, null, i) } else this.elt(n + " ace_br1 ace_start", "height:" + o + "px;" + "right:0;" + "top:" + u + "px;left:" + a + "px;" + (i || "")); if (this.session.$bidiHandler.isBidiRow(t.end.row)) { var f = t.clone(); f.start.row = f.end.row, f.start.column = 0, this.drawBidiSingleLineMarker(e, f, n + " ace_br12", r, null, i) } else { u = this.$getTop(t.end.row, r); var l = t.end.column * r.characterWidth; this.elt(n + " ace_br12", "height:" + o + "px;" + "width:" + l + "px;" + "top:" + u + "px;" + "left:" + s + "px;" + (i || "")) } o = (t.end.row - t.start.row - 1) * r.lineHeight; if (o <= 0) return; u = this.$getTop(t.start.row + 1, r); var c = (t.start.column ? 1 : 0) | (t.end.column ? 0 : 8); this.elt(n + (c ? " ace_br" + c : ""), "height:" + o + "px;" + "right:0;" + "top:" + u + "px;" + "left:" + s + "px;" + (i || "")) }, this.drawSingleLineMarker = function (e, t, n, r, i, s) { if (this.session.$bidiHandler.isBidiRow(t.start.row)) return this.drawBidiSingleLineMarker(e, t, n, r, i, s); var o = r.lineHeight, u = (t.end.column + (i || 0) - t.start.column) * r.characterWidth, a = this.$getTop(t.start.row, r), f = this.$padding + t.start.column * r.characterWidth; this.elt(n, "height:" + o + "px;" + "width:" + u + "px;" + "top:" + a + "px;" + "left:" + f + "px;" + (s || "")) }, this.drawBidiSingleLineMarker = function (e, t, n, r, i, s) { var o = r.lineHeight, u = this.$getTop(t.start.row, r), a = this.$padding, f = this.session.$bidiHandler.getSelections(t.start.column, t.end.column); f.forEach(function (e) { this.elt(n, "height:" + o + "px;" + "width:" + e.width + (i || 0) + "px;" + "top:" + u + "px;" + "left:" + (a + e.left) + "px;" + (s || "")) }, this) }, this.drawFullLineMarker = function (e, t, n, r, i) { var s = this.$getTop(t.start.row, r), o = r.lineHeight; t.start.row != t.end.row && (o += this.$getTop(t.end.row, r) - s), this.elt(n, "height:" + o + "px;" + "top:" + s + "px;" + "left:0;right:0;" + (i || "")) }, this.drawScreenLineMarker = function (e, t, n, r, i) { var s = this.$getTop(t.start.row, r), o = r.lineHeight; this.elt(n, "height:" + o + "px;" + "top:" + s + "px;" + "left:0;right:0;" + (i || "")) } }).call(s.prototype), t.Marker = s }), define("ace/layer/text", ["require", "exports", "module", "ace/lib/oop", "ace/lib/dom", "ace/lib/lang", "ace/layer/lines", "ace/lib/event_emitter"], function (e, t, n) { "use strict"; var r = e("../lib/oop"), i = e("../lib/dom"), s = e("../lib/lang"), o = e("./lines").Lines, u = e("../lib/event_emitter").EventEmitter, a = function (e) { this.dom = i, this.element = this.dom.createElement("div"), this.element.className = "ace_layer ace_text-layer", e.appendChild(this.element), this.$updateEolChar = this.$updateEolChar.bind(this), this.$lines = new o(this.element) }; (function () { r.implement(this, u), this.EOF_CHAR = "\u00b6", this.EOL_CHAR_LF = "\u00ac", this.EOL_CHAR_CRLF = "\u00a4", this.EOL_CHAR = this.EOL_CHAR_LF, this.TAB_CHAR = "\u2014", this.SPACE_CHAR = "\u00b7", this.$padding = 0, this.MAX_LINE_LENGTH = 1e4, this.$updateEolChar = function () { var e = this.session.doc, t = e.getNewLineCharacter() == "\n" && e.getNewLineMode() != "windows", n = t ? this.EOL_CHAR_LF : this.EOL_CHAR_CRLF; if (this.EOL_CHAR != n) return this.EOL_CHAR = n, !0 }, this.setPadding = function (e) { this.$padding = e, this.element.style.margin = "0 " + e + "px" }, this.getLineHeight = function () { return this.$fontMetrics.$characterSize.height || 0 }, this.getCharacterWidth = function () { return this.$fontMetrics.$characterSize.width || 0 }, this.$setFontMetrics = function (e) { this.$fontMetrics = e, this.$fontMetrics.on("changeCharacterSize", function (e) { this._signal("changeCharacterSize", e) }.bind(this)), this.$pollSizeChanges() }, this.checkForSizeChanges = function () { this.$fontMetrics.checkForSizeChanges() }, this.$pollSizeChanges = function () { return this.$pollSizeChangesTimer = this.$fontMetrics.$pollSizeChanges() }, this.setSession = function (e) { this.session = e, e && this.$computeTabString() }, this.showInvisibles = !1, this.showSpaces = !1, this.showTabs = !1, this.showEOL = !1, this.setShowInvisibles = function (e) { return this.showInvisibles == e ? !1 : (this.showInvisibles = e, typeof e == "string" ? (this.showSpaces = /tab/i.test(e), this.showTabs = /space/i.test(e), this.showEOL = /eol/i.test(e)) : this.showSpaces = this.showTabs = this.showEOL = e, this.$computeTabString(), !0) }, this.displayIndentGuides = !0, this.setDisplayIndentGuides = function (e) { return this.displayIndentGuides == e ? !1 : (this.displayIndentGuides = e, this.$computeTabString(), !0) }, this.$tabStrings = [], this.onChangeTabSize = this.$computeTabString = function () { var e = this.session.getTabSize(); this.tabSize = e; var t = this.$tabStrings = [0]; for (var n = 1; n < e + 1; n++)if (this.showTabs) { var r = this.dom.createElement("span"); r.className = "ace_invisible ace_invisible_tab", r.textContent = s.stringRepeat(this.TAB_CHAR, n), t.push(r) } else t.push(this.dom.createTextNode(s.stringRepeat(" ", n), this.element)); if (this.displayIndentGuides) { this.$indentGuideRe = /\s\S| \t|\t |\s$/; var i = "ace_indent-guide", o = this.showSpaces ? " ace_invisible ace_invisible_space" : "", u = this.showSpaces ? s.stringRepeat(this.SPACE_CHAR, this.tabSize) : s.stringRepeat(" ", this.tabSize), a = this.showTabs ? " ace_invisible ace_invisible_tab" : "", f = this.showTabs ? s.stringRepeat(this.TAB_CHAR, this.tabSize) : u, r = this.dom.createElement("span"); r.className = i + o, r.textContent = u, this.$tabStrings[" "] = r; var r = this.dom.createElement("span"); r.className = i + a, r.textContent = f, this.$tabStrings[" "] = r } }, this.updateLines = function (e, t, n) { if (this.config.lastRow != e.lastRow || this.config.firstRow != e.firstRow) return this.update(e); this.config = e; var r = Math.max(t, e.firstRow), i = Math.min(n, e.lastRow), s = this.element.childNodes, o = 0; for (var u = e.firstRow; u < r; u++) { var a = this.session.getFoldLine(u); if (a) { if (a.containsRow(r)) { r = a.start.row; break } u = a.end.row } o++ } var f = !1, u = r, a = this.session.getNextFoldLine(u), l = a ? a.start.row : Infinity; for (; ;) { u > l && (u = a.end.row + 1, a = this.session.getNextFoldLine(u, a), l = a ? a.start.row : Infinity); if (u > i) break; var c = s[o++]; if (c) { this.dom.removeChildren(c), this.$renderLine(c, u, u == l ? a : !1), f && (c.style.top = this.$lines.computeLineTop(u, e, this.session) + "px"); var h = e.lineHeight * this.session.getRowLength(u) + "px"; c.style.height != h && (f = !0, c.style.height = h) } u++ } if (f) while (o < this.$lines.cells.length) { var p = this.$lines.cells[o++]; p.element.style.top = this.$lines.computeLineTop(p.row, e, this.session) + "px" } }, this.scrollLines = function (e) { var t = this.config; this.config = e; if (this.$lines.pageChanged(t, e)) return this.update(e); this.$lines.moveContainer(e); var n = e.lastRow, r = t ? t.lastRow : -1; if (!t || r < e.firstRow) return this.update(e); if (n < t.firstRow) return this.update(e); if (!t || t.lastRow < e.firstRow) return this.update(e); if (e.lastRow < t.firstRow) return this.update(e); if (t.firstRow < e.firstRow) for (var i = this.session.getFoldedRowCount(t.firstRow, e.firstRow - 1); i > 0; i--)this.$lines.shift(); if (t.lastRow > e.lastRow) for (var i = this.session.getFoldedRowCount(e.lastRow + 1, t.lastRow); i > 0; i--)this.$lines.pop(); e.firstRow < t.firstRow && this.$lines.unshift(this.$renderLinesFragment(e, e.firstRow, t.firstRow - 1)), e.lastRow > t.lastRow && this.$lines.push(this.$renderLinesFragment(e, t.lastRow + 1, e.lastRow)) }, this.$renderLinesFragment = function (e, t, n) { var r = [], s = t, o = this.session.getNextFoldLine(s), u = o ? o.start.row : Infinity; for (; ;) { s > u && (s = o.end.row + 1, o = this.session.getNextFoldLine(s, o), u = o ? o.start.row : Infinity); if (s > n) break; var a = this.$lines.createCell(s, e, this.session), f = a.element; this.dom.removeChildren(f), i.setStyle(f.style, "height", this.$lines.computeLineHeight(s, e, this.session) + "px"), i.setStyle(f.style, "top", this.$lines.computeLineTop(s, e, this.session) + "px"), this.$renderLine(f, s, s == u ? o : !1), this.$useLineGroups() ? f.className = "ace_line_group" : f.className = "ace_line", r.push(a), s++ } return r }, this.update = function (e) { this.$lines.moveContainer(e), this.config = e; var t = e.firstRow, n = e.lastRow, r = this.$lines; while (r.getLength()) r.pop(); r.push(this.$renderLinesFragment(e, t, n)) }, this.$textToken = { text: !0, rparen: !0, lparen: !0 }, this.$renderToken = function (e, t, n, r) { var i = this, o = /(\t)|( +)|([\x00-\x1f\x80-\xa0\xad\u1680\u180E\u2000-\u200f\u2028\u2029\u202F\u205F\uFEFF\uFFF9-\uFFFC]+)|(\u3000)|([\u1100-\u115F\u11A3-\u11A7\u11FA-\u11FF\u2329-\u232A\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3001-\u303E\u3041-\u3096\u3099-\u30FF\u3105-\u312D\u3131-\u318E\u3190-\u31BA\u31C0-\u31E3\u31F0-\u321E\u3220-\u3247\u3250-\u32FE\u3300-\u4DBF\u4E00-\uA48C\uA490-\uA4C6\uA960-\uA97C\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFF01-\uFF60\uFFE0-\uFFE6]|[\uD800-\uDBFF][\uDC00-\uDFFF])/g, u = this.dom.createFragment(this.element), a, f = 0; while (a = o.exec(r)) { var l = a[1], c = a[2], h = a[3], p = a[4], d = a[5]; if (!i.showSpaces && c) continue; var v = f != a.index ? r.slice(f, a.index) : ""; f = a.index + a[0].length, v && u.appendChild(this.dom.createTextNode(v, this.element)); if (l) { var m = i.session.getScreenTabSize(t + a.index); u.appendChild(i.$tabStrings[m].cloneNode(!0)), t += m - 1 } else if (c) if (i.showSpaces) { var g = this.dom.createElement("span"); g.className = "ace_invisible ace_invisible_space", g.textContent = s.stringRepeat(i.SPACE_CHAR, c.length), u.appendChild(g) } else u.appendChild(this.com.createTextNode(c, this.element)); else if (h) { var g = this.dom.createElement("span"); g.className = "ace_invisible ace_invisible_space ace_invalid", g.textContent = s.stringRepeat(i.SPACE_CHAR, h.length), u.appendChild(g) } else if (p) { t += 1; var g = this.dom.createElement("span"); g.style.width = i.config.characterWidth * 2 + "px", g.className = i.showSpaces ? "ace_cjk ace_invisible ace_invisible_space" : "ace_cjk", g.textContent = i.showSpaces ? i.SPACE_CHAR : p, u.appendChild(g) } else if (d) { t += 1; var g = this.dom.createElement("span"); g.style.width = i.config.characterWidth * 2 + "px", g.className = "ace_cjk", g.textContent = d, u.appendChild(g) } } u.appendChild(this.dom.createTextNode(f ? r.slice(f) : r, this.element)); if (!this.$textToken[n.type]) { var y = "ace_" + n.type.replace(/\./g, " ace_"), g = this.dom.createElement("span"); n.type == "fold" && (g.style.width = n.value.length * this.config.characterWidth + "px"), g.className = y, g.appendChild(u), e.appendChild(g) } else e.appendChild(u); return t + r.length }, this.renderIndentGuide = function (e, t, n) { var r = t.search(this.$indentGuideRe); if (r <= 0 || r >= n) return t; if (t[0] == " ") { r -= r % this.tabSize; var i = r / this.tabSize; for (var s = 0; s < i; s++)e.appendChild(this.$tabStrings[" "].cloneNode(!0)); return t.substr(r) } if (t[0] == " ") { for (var s = 0; s < r; s++)e.appendChild(this.$tabStrings[" "].cloneNode(!0)); return t.substr(r) } return t }, this.$createLineElement = function (e) { var t = this.dom.createElement("div"); return t.className = "ace_line", t.style.height = this.config.lineHeight + "px", t }, this.$renderWrappedLine = function (e, t, n) { var r = 0, i = 0, o = n[0], u = 0, a = this.$createLineElement(); e.appendChild(a); for (var f = 0; f < t.length; f++) { var l = t[f], c = l.value; if (f == 0 && this.displayIndentGuides) { r = c.length, c = this.renderIndentGuide(a, c, o); if (!c) continue; r -= c.length } if (r + c.length < o) u = this.$renderToken(a, u, l, c), r += c.length; else { while (r + c.length >= o) u = this.$renderToken(a, u, l, c.substring(0, o - r)), c = c.substring(o - r), r = o, a = this.$createLineElement(), e.appendChild(a), a.appendChild(this.dom.createTextNode(s.stringRepeat("\u00a0", n.indent), this.element)), i++, u = 0, o = n[i] || Number.MAX_VALUE; c.length != 0 && (r += c.length, u = this.$renderToken(a, u, l, c)) } } n[n.length - 1] > this.MAX_LINE_LENGTH && this.$renderOverflowMessage(a, u, null, "", !0) }, this.$renderSimpleLine = function (e, t) { var n = 0, r = t[0], i = r.value; this.displayIndentGuides && (i = this.renderIndentGuide(e, i)), i && (n = this.$renderToken(e, n, r, i)); for (var s = 1; s < t.length; s++) { r = t[s], i = r.value; if (n + i.length > this.MAX_LINE_LENGTH) return this.$renderOverflowMessage(e, n, r, i); n = this.$renderToken(e, n, r, i) } }, this.$renderOverflowMessage = function (e, t, n, r, i) { n && this.$renderToken(e, t, n, r.slice(0, this.MAX_LINE_LENGTH - t)); var s = this.dom.createElement("span"); s.className = "ace_inline_button ace_keyword ace_toggle_wrap", s.textContent = i ? "" : "", e.appendChild(s) }, this.$renderLine = function (e, t, n) { !n && n != 0 && (n = this.session.getFoldLine(t)); if (n) var r = this.$getFoldLineTokens(t, n); else var r = this.session.getTokens(t); var i = e; if (r.length) { var s = this.session.getRowSplitData(t); if (s && s.length) { this.$renderWrappedLine(e, r, s); var i = e.lastChild } else { var i = e; this.$useLineGroups() && (i = this.$createLineElement(), e.appendChild(i)), this.$renderSimpleLine(i, r) } } else this.$useLineGroups() && (i = this.$createLineElement(), e.appendChild(i)); if (this.showEOL && i) { n && (t = n.end.row); var o = this.dom.createElement("span"); o.className = "ace_invisible ace_invisible_eol", o.textContent = t == this.session.getLength() - 1 ? this.EOF_CHAR : this.EOL_CHAR, i.appendChild(o) } }, this.$getFoldLineTokens = function (e, t) { function i(e, t, n) { var i = 0, s = 0; while (s + e[i].value.length < t) { s += e[i].value.length, i++; if (i == e.length) return } if (s != t) { var o = e[i].value.substring(t - s); o.length > n - t && (o = o.substring(0, n - t)), r.push({ type: e[i].type, value: o }), s = t + o.length, i += 1 } while (s < n && i < e.length) { var o = e[i].value; o.length + s > n ? r.push({ type: e[i].type, value: o.substring(0, n - s) }) : r.push(e[i]), s += o.length, i += 1 } } var n = this.session, r = [], s = n.getTokens(e); return t.walk(function (e, t, o, u, a) { e != null ? r.push({ type: "fold", value: e }) : (a && (s = n.getTokens(t)), s.length && i(s, u, o)) }, t.end.row, this.session.getLine(t.end.row).length), r }, this.$useLineGroups = function () { return this.session.getUseWrapMode() }, this.destroy = function () { } }).call(a.prototype), t.Text = a }), define("ace/layer/cursor", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { "use strict"; var r = e("../lib/dom"), i = function (e) { this.element = r.createElement("div"), this.element.className = "ace_layer ace_cursor-layer", e.appendChild(this.element), this.isVisible = !1, this.isBlinking = !0, this.blinkInterval = 1e3, this.smoothBlinking = !1, this.cursors = [], this.cursor = this.addCursor(), r.addCssClass(this.element, "ace_hidden-cursors"), this.$updateCursors = this.$updateOpacity.bind(this) }; (function () { this.$updateOpacity = function (e) { var t = this.cursors; for (var n = t.length; n--;)r.setStyle(t[n].style, "opacity", e ? "" : "0") }, this.$startCssAnimation = function () { var e = this.cursors; for (var t = e.length; t--;)e[t].style.animationDuration = this.blinkInterval + "ms"; setTimeout(function () { r.addCssClass(this.element, "ace_animate-blinking") }.bind(this)) }, this.$stopCssAnimation = function () { r.removeCssClass(this.element, "ace_animate-blinking") }, this.$padding = 0, this.setPadding = function (e) { this.$padding = e }, this.setSession = function (e) { this.session = e }, this.setBlinking = function (e) { e != this.isBlinking && (this.isBlinking = e, this.restartTimer()) }, this.setBlinkInterval = function (e) { e != this.blinkInterval && (this.blinkInterval = e, this.restartTimer()) }, this.setSmoothBlinking = function (e) { e != this.smoothBlinking && (this.smoothBlinking = e, r.setCssClass(this.element, "ace_smooth-blinking", e), this.$updateCursors(!0), this.restartTimer()) }, this.addCursor = function () { var e = r.createElement("div"); return e.className = "ace_cursor", this.element.appendChild(e), this.cursors.push(e), e }, this.removeCursor = function () { if (this.cursors.length > 1) { var e = this.cursors.pop(); return e.parentNode.removeChild(e), e } }, this.hideCursor = function () { this.isVisible = !1, r.addCssClass(this.element, "ace_hidden-cursors"), this.restartTimer() }, this.showCursor = function () { this.isVisible = !0, r.removeCssClass(this.element, "ace_hidden-cursors"), this.restartTimer() }, this.restartTimer = function () { var e = this.$updateCursors; clearInterval(this.intervalId), clearTimeout(this.timeoutId), this.$stopCssAnimation(), this.smoothBlinking && r.removeCssClass(this.element, "ace_smooth-blinking"), e(!0); if (!this.isBlinking || !this.blinkInterval || !this.isVisible) { this.$stopCssAnimation(); return } this.smoothBlinking && setTimeout(function () { r.addCssClass(this.element, "ace_smooth-blinking") }.bind(this)); if (r.HAS_CSS_ANIMATION) this.$startCssAnimation(); else { var t = function () { this.timeoutId = setTimeout(function () { e(!1) }, .6 * this.blinkInterval) }.bind(this); this.intervalId = setInterval(function () { e(!0), t() }, this.blinkInterval), t() } }, this.getPixelPosition = function (e, t) { if (!this.config || !this.session) return { left: 0, top: 0 }; e || (e = this.session.selection.getCursor()); var n = this.session.documentToScreenPosition(e), r = this.$padding + (this.session.$bidiHandler.isBidiRow(n.row, e.row) ? this.session.$bidiHandler.getPosLeft(n.column) : n.column * this.config.characterWidth), i = (n.row - (t ? this.config.firstRowScreen : 0)) * this.config.lineHeight; return { left: r, top: i } }, this.isCursorInView = function (e, t) { return e.top >= 0 && e.top < t.maxHeight }, this.update = function (e) { this.config = e; var t = this.session.$selectionMarkers, n = 0, i = 0; if (t === undefined || t.length === 0) t = [{ cursor: null }]; for (var n = 0, s = t.length; n < s; n++) { var o = this.getPixelPosition(t[n].cursor, !0); if ((o.top > e.height + e.offset || o.top < 0) && n > 1) continue; var u = this.cursors[i++] || this.addCursor(), a = u.style; this.drawCursor ? this.drawCursor(u, o, e, t[n], this.session) : this.isCursorInView(o, e) ? (r.setStyle(a, "display", "block"), r.translate(u, o.left, o.top), r.setStyle(a, "width", Math.round(e.characterWidth) + "px"), r.setStyle(a, "height", e.lineHeight + "px")) : r.setStyle(a, "display", "none") } while (this.cursors.length > i) this.removeCursor(); var f = this.session.getOverwrite(); this.$setOverwrite(f), this.$pixelPos = o, this.restartTimer() }, this.drawCursor = null, this.$setOverwrite = function (e) { e != this.overwrite && (this.overwrite = e, e ? r.addCssClass(this.element, "ace_overwrite-cursors") : r.removeCssClass(this.element, "ace_overwrite-cursors")) }, this.destroy = function () { clearInterval(this.intervalId), clearTimeout(this.timeoutId) } }).call(i.prototype), t.Cursor = i }), define("ace/scrollbar", ["require", "exports", "module", "ace/lib/oop", "ace/lib/dom", "ace/lib/event", "ace/lib/event_emitter"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./lib/dom"), s = e("./lib/event"), o = e("./lib/event_emitter").EventEmitter, u = 32768, a = function (e) { this.element = i.createElement("div"), this.element.className = "ace_scrollbar ace_scrollbar" + this.classSuffix, this.inner = i.createElement("div"), this.inner.className = "ace_scrollbar-inner", this.inner.textContent = "\u00a0", this.element.appendChild(this.inner), e.appendChild(this.element), this.setVisible(!1), this.skipEvent = !1, s.addListener(this.element, "scroll", this.onScroll.bind(this)), s.addListener(this.element, "mousedown", s.preventDefault) }; (function () { r.implement(this, o), this.setVisible = function (e) { this.element.style.display = e ? "" : "none", this.isVisible = e, this.coeff = 1 } }).call(a.prototype); var f = function (e, t) { a.call(this, e), this.scrollTop = 0, this.scrollHeight = 0, t.$scrollbarWidth = this.width = i.scrollbarWidth(e.ownerDocument), this.inner.style.width = this.element.style.width = (this.width || 15) + 5 + "px", this.$minWidth = 0 }; r.inherits(f, a), function () { this.classSuffix = "-v", this.onScroll = function () { if (!this.skipEvent) { this.scrollTop = this.element.scrollTop; if (this.coeff != 1) { var e = this.element.clientHeight / this.scrollHeight; this.scrollTop = this.scrollTop * (1 - e) / (this.coeff - e) } this._emit("scroll", { data: this.scrollTop }) } this.skipEvent = !1 }, this.getWidth = function () { return Math.max(this.isVisible ? this.width : 0, this.$minWidth || 0) }, this.setHeight = function (e) { this.element.style.height = e + "px" }, this.setInnerHeight = this.setScrollHeight = function (e) { this.scrollHeight = e, e > u ? (this.coeff = u / e, e = u) : this.coeff != 1 && (this.coeff = 1), this.inner.style.height = e + "px" }, this.setScrollTop = function (e) { this.scrollTop != e && (this.skipEvent = !0, this.scrollTop = e, this.element.scrollTop = e * this.coeff) } }.call(f.prototype); var l = function (e, t) { a.call(this, e), this.scrollLeft = 0, this.height = t.$scrollbarWidth, this.inner.style.height = this.element.style.height = (this.height || 15) + 5 + "px" }; r.inherits(l, a), function () { this.classSuffix = "-h", this.onScroll = function () { this.skipEvent || (this.scrollLeft = this.element.scrollLeft, this._emit("scroll", { data: this.scrollLeft })), this.skipEvent = !1 }, this.getHeight = function () { return this.isVisible ? this.height : 0 }, this.setWidth = function (e) { this.element.style.width = e + "px" }, this.setInnerWidth = function (e) { this.inner.style.width = e + "px" }, this.setScrollWidth = function (e) { this.inner.style.width = e + "px" }, this.setScrollLeft = function (e) { this.scrollLeft != e && (this.skipEvent = !0, this.scrollLeft = this.element.scrollLeft = e) } }.call(l.prototype), t.ScrollBar = f, t.ScrollBarV = f, t.ScrollBarH = l, t.VScrollBar = f, t.HScrollBar = l }), define("ace/renderloop", ["require", "exports", "module", "ace/lib/event"], function (e, t, n) { "use strict"; var r = e("./lib/event"), i = function (e, t) { this.onRender = e, this.pending = !1, this.changes = 0, this.$recursionLimit = 2, this.window = t || window; var n = this; this._flush = function (e) { n.pending = !1; var t = n.changes; t && (r.blockIdle(100), n.changes = 0, n.onRender(t)); if (n.changes) { if (n.$recursionLimit-- < 0) return; n.schedule() } else n.$recursionLimit = 2 } }; (function () { this.schedule = function (e) { this.changes = this.changes | e, this.changes && !this.pending && (r.nextFrame(this._flush), this.pending = !0) }, this.clear = function (e) { var t = this.changes; return this.changes = 0, t } }).call(i.prototype), t.RenderLoop = i }), define("ace/layer/font_metrics", ["require", "exports", "module", "ace/lib/oop", "ace/lib/dom", "ace/lib/lang", "ace/lib/event", "ace/lib/useragent", "ace/lib/event_emitter"], function (e, t, n) { var r = e("../lib/oop"), i = e("../lib/dom"), s = e("../lib/lang"), o = e("../lib/event"), u = e("../lib/useragent"), a = e("../lib/event_emitter").EventEmitter, f = 256, l = typeof ResizeObserver == "function", c = 200, h = t.FontMetrics = function (e) { this.el = i.createElement("div"), this.$setMeasureNodeStyles(this.el.style, !0), this.$main = i.createElement("div"), this.$setMeasureNodeStyles(this.$main.style), this.$measureNode = i.createElement("div"), this.$setMeasureNodeStyles(this.$measureNode.style), this.el.appendChild(this.$main), this.el.appendChild(this.$measureNode), e.appendChild(this.el), this.$measureNode.textContent = s.stringRepeat("X", f), this.$characterSize = { width: 0, height: 0 }, l ? this.$addObserver() : this.checkForSizeChanges() }; (function () { r.implement(this, a), this.$characterSize = { width: 0, height: 0 }, this.$setMeasureNodeStyles = function (e, t) { e.width = e.height = "auto", e.left = e.top = "0px", e.visibility = "hidden", e.position = "absolute", e.whiteSpace = "pre", u.isIE < 8 ? e["font-family"] = "inherit" : e.font = "inherit", e.overflow = t ? "hidden" : "visible" }, this.checkForSizeChanges = function (e) { e === undefined && (e = this.$measureSizes()); if (e && (this.$characterSize.width !== e.width || this.$characterSize.height !== e.height)) { this.$measureNode.style.fontWeight = "bold"; var t = this.$measureSizes(); this.$measureNode.style.fontWeight = "", this.$characterSize = e, this.charSizes = Object.create(null), this.allowBoldFonts = t && t.width === e.width && t.height === e.height, this._emit("changeCharacterSize", { data: e }) } }, this.$addObserver = function () { var e = this; this.$observer = new window.ResizeObserver(function (t) { e.checkForSizeChanges() }), this.$observer.observe(this.$measureNode) }, this.$pollSizeChanges = function () { if (this.$pollSizeChangesTimer || this.$observer) return this.$pollSizeChangesTimer; var e = this; return this.$pollSizeChangesTimer = o.onIdle(function t() { e.checkForSizeChanges(), o.onIdle(t, 500) }, 500) }, this.setPolling = function (e) { e ? this.$pollSizeChanges() : this.$pollSizeChangesTimer && (clearInterval(this.$pollSizeChangesTimer), this.$pollSizeChangesTimer = 0) }, this.$measureSizes = function (e) { var t = { height: (e || this.$measureNode).clientHeight, width: (e || this.$measureNode).clientWidth / f }; return t.width === 0 || t.height === 0 ? null : t }, this.$measureCharWidth = function (e) { this.$main.textContent = s.stringRepeat(e, f); var t = this.$main.getBoundingClientRect(); return t.width / f }, this.getCharacterWidth = function (e) { var t = this.charSizes[e]; return t === undefined && (t = this.charSizes[e] = this.$measureCharWidth(e) / this.$characterSize.width), t }, this.destroy = function () { clearInterval(this.$pollSizeChangesTimer), this.$observer && this.$observer.disconnect(), this.el && this.el.parentNode && this.el.parentNode.removeChild(this.el) }, this.$getZoom = function e(t) { return t ? (window.getComputedStyle(t).zoom || 1) * e(t.parentElement) : 1 }, this.$initTransformMeasureNodes = function () { var e = function (e, t) { return ["div", { style: "position: absolute;top:" + e + "px;left:" + t + "px;" }] }; this.els = i.buildDom([e(0, 0), e(c, 0), e(0, c), e(c, c)], this.el) }, this.transformCoordinates = function (e, t) { function r(e, t, n) { var r = e[1] * t[0] - e[0] * t[1]; return [(-t[1] * n[0] + t[0] * n[1]) / r, (+e[1] * n[0] - e[0] * n[1]) / r] } function i(e, t) { return [e[0] - t[0], e[1] - t[1]] } function s(e, t) { return [e[0] + t[0], e[1] + t[1]] } function o(e, t) { return [e * t[0], e * t[1]] } function u(e) { var t = e.getBoundingClientRect(); return [t.left, t.top] } if (e) { var n = this.$getZoom(this.el); e = o(1 / n, e) } this.els || this.$initTransformMeasureNodes(); var a = u(this.els[0]), f = u(this.els[1]), l = u(this.els[2]), h = u(this.els[3]), p = r(i(h, f), i(h, l), i(s(f, l), s(h, a))), d = o(1 + p[0], i(f, a)), v = o(1 + p[1], i(l, a)); if (t) { var m = t, g = p[0] * m[0] / c + p[1] * m[1] / c + 1, y = s(o(m[0], d), o(m[1], v)); return s(o(1 / g / c, y), a) } var b = i(e, a), w = r(i(d, o(p[0], b)), i(v, o(p[1], b)), b); return o(c, w) } }).call(h.prototype) }), define("ace/virtual_renderer", ["require", "exports", "module", "ace/lib/oop", "ace/lib/dom", "ace/config", "ace/layer/gutter", "ace/layer/marker", "ace/layer/text", "ace/layer/cursor", "ace/scrollbar", "ace/scrollbar", "ace/renderloop", "ace/layer/font_metrics", "ace/lib/event_emitter", "ace/lib/useragent"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./lib/dom"), s = e("./config"), o = e("./layer/gutter").Gutter, u = e("./layer/marker").Marker, a = e("./layer/text").Text, f = e("./layer/cursor").Cursor, l = e("./scrollbar").HScrollBar, c = e("./scrollbar").VScrollBar, h = e("./renderloop").RenderLoop, p = e("./layer/font_metrics").FontMetrics, d = e("./lib/event_emitter").EventEmitter, v = '.ace_br1 {border-top-left-radius : 3px;}.ace_br2 {border-top-right-radius : 3px;}.ace_br3 {border-top-left-radius : 3px; border-top-right-radius: 3px;}.ace_br4 {border-bottom-right-radius: 3px;}.ace_br5 {border-top-left-radius : 3px; border-bottom-right-radius: 3px;}.ace_br6 {border-top-right-radius : 3px; border-bottom-right-radius: 3px;}.ace_br7 {border-top-left-radius : 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px;}.ace_br8 {border-bottom-left-radius : 3px;}.ace_br9 {border-top-left-radius : 3px; border-bottom-left-radius: 3px;}.ace_br10{border-top-right-radius : 3px; border-bottom-left-radius: 3px;}.ace_br11{border-top-left-radius : 3px; border-top-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_br12{border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_br13{border-top-left-radius : 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_br14{border-top-right-radius : 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_br15{border-top-left-radius : 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_editor {position: relative;overflow: hidden;padding: 0;font: 12px/normal \'Monaco\', \'Menlo\', \'Ubuntu Mono\', \'Consolas\', \'source-code-pro\', monospace;direction: ltr;text-align: left;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);}.ace_scroller {position: absolute;overflow: hidden;top: 0;bottom: 0;background-color: inherit;-ms-user-select: none;-moz-user-select: none;-webkit-user-select: none;user-select: none;cursor: text;}.ace_content {position: absolute;box-sizing: border-box;min-width: 100%;contain: style size layout;font-variant-ligatures: no-common-ligatures;}.ace_dragging .ace_scroller:before{position: absolute;top: 0;left: 0;right: 0;bottom: 0;content: \'\';background: rgba(250, 250, 250, 0.01);z-index: 1000;}.ace_dragging.ace_dark .ace_scroller:before{background: rgba(0, 0, 0, 0.01);}.ace_selecting, .ace_selecting * {cursor: text !important;}.ace_gutter {position: absolute;overflow : hidden;width: auto;top: 0;bottom: 0;left: 0;cursor: default;z-index: 4;-ms-user-select: none;-moz-user-select: none;-webkit-user-select: none;user-select: none;contain: style size layout;}.ace_gutter-active-line {position: absolute;left: 0;right: 0;}.ace_scroller.ace_scroll-left {box-shadow: 17px 0 16px -16px rgba(0, 0, 0, 0.4) inset;}.ace_gutter-cell {position: absolute;top: 0;left: 0;right: 0;padding-left: 19px;padding-right: 6px;background-repeat: no-repeat;}.ace_gutter-cell.ace_error {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABOFBMVEX/////////QRswFAb/Ui4wFAYwFAYwFAaWGAfDRymzOSH/PxswFAb/SiUwFAYwFAbUPRvjQiDllog5HhHdRybsTi3/Tyv9Tir+Syj/UC3////XurebMBIwFAb/RSHbPx/gUzfdwL3kzMivKBAwFAbbvbnhPx66NhowFAYwFAaZJg8wFAaxKBDZurf/RB6mMxb/SCMwFAYwFAbxQB3+RB4wFAb/Qhy4Oh+4QifbNRcwFAYwFAYwFAb/QRzdNhgwFAYwFAbav7v/Uy7oaE68MBK5LxLewr/r2NXewLswFAaxJw4wFAbkPRy2PyYwFAaxKhLm1tMwFAazPiQwFAaUGAb/QBrfOx3bvrv/VC/maE4wFAbRPBq6MRO8Qynew8Dp2tjfwb0wFAbx6eju5+by6uns4uH9/f36+vr/GkHjAAAAYnRSTlMAGt+64rnWu/bo8eAA4InH3+DwoN7j4eLi4xP99Nfg4+b+/u9B/eDs1MD1mO7+4PHg2MXa347g7vDizMLN4eG+Pv7i5evs/v79yu7S3/DV7/498Yv24eH+4ufQ3Ozu/v7+y13sRqwAAADLSURBVHjaZc/XDsFgGIBhtDrshlitmk2IrbHFqL2pvXf/+78DPokj7+Fz9qpU/9UXJIlhmPaTaQ6QPaz0mm+5gwkgovcV6GZzd5JtCQwgsxoHOvJO15kleRLAnMgHFIESUEPmawB9ngmelTtipwwfASilxOLyiV5UVUyVAfbG0cCPHig+GBkzAENHS0AstVF6bacZIOzgLmxsHbt2OecNgJC83JERmePUYq8ARGkJx6XtFsdddBQgZE2nPR6CICZhawjA4Fb/chv+399kfR+MMMDGOQAAAABJRU5ErkJggg==");background-repeat: no-repeat;background-position: 2px center;}.ace_gutter-cell.ace_warning {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAmVBMVEX///8AAAD///8AAAAAAABPSzb/5sAAAAB/blH/73z/ulkAAAAAAAD85pkAAAAAAAACAgP/vGz/rkDerGbGrV7/pkQICAf////e0IsAAAD/oED/qTvhrnUAAAD/yHD/njcAAADuv2r/nz//oTj/p064oGf/zHAAAAA9Nir/tFIAAAD/tlTiuWf/tkIAAACynXEAAAAAAAAtIRW7zBpBAAAAM3RSTlMAABR1m7RXO8Ln31Z36zT+neXe5OzooRDfn+TZ4p3h2hTf4t3k3ucyrN1K5+Xaks52Sfs9CXgrAAAAjklEQVR42o3PbQ+CIBQFYEwboPhSYgoYunIqqLn6/z8uYdH8Vmdnu9vz4WwXgN/xTPRD2+sgOcZjsge/whXZgUaYYvT8QnuJaUrjrHUQreGczuEafQCO/SJTufTbroWsPgsllVhq3wJEk2jUSzX3CUEDJC84707djRc5MTAQxoLgupWRwW6UB5fS++NV8AbOZgnsC7BpEAAAAABJRU5ErkJggg==");background-position: 2px center;}.ace_gutter-cell.ace_info {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAJ0Uk5TAAB2k804AAAAPklEQVQY02NgIB68QuO3tiLznjAwpKTgNyDbMegwisCHZUETUZV0ZqOquBpXj2rtnpSJT1AEnnRmL2OgGgAAIKkRQap2htgAAAAASUVORK5CYII=");background-position: 2px center;}.ace_dark .ace_gutter-cell.ace_info {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAChoaGAgIAqKiq+vr6tra1ZWVmUlJSbm5s8PDxubm56enrdgzg3AAAAAXRSTlMAQObYZgAAAClJREFUeNpjYMAPdsMYHegyJZFQBlsUlMFVCWUYKkAZMxZAGdxlDMQBAG+TBP4B6RyJAAAAAElFTkSuQmCC");}.ace_scrollbar {contain: strict;position: absolute;right: 0;bottom: 0;z-index: 6;}.ace_scrollbar-inner {position: absolute;cursor: text;left: 0;top: 0;}.ace_scrollbar-v{overflow-x: hidden;overflow-y: scroll;top: 0;}.ace_scrollbar-h {overflow-x: scroll;overflow-y: hidden;left: 0;}.ace_print-margin {position: absolute;height: 100%;}.ace_text-input {position: absolute;z-index: 0;width: 0.5em;height: 1em;opacity: 0;background: transparent;-moz-appearance: none;appearance: none;border: none;resize: none;outline: none;overflow: hidden;font: inherit;padding: 0 1px;margin: 0 -1px;contain: strict;-ms-user-select: text;-moz-user-select: text;-webkit-user-select: text;user-select: text;white-space: pre!important;}.ace_text-input.ace_composition {background: transparent;color: inherit;z-index: 1000;opacity: 1;}.ace_composition_placeholder { color: transparent }.ace_composition_marker { border-bottom: 1px solid;position: absolute;border-radius: 0;margin-top: 1px;}[ace_nocontext=true] {transform: none!important;filter: none!important;clip-path: none!important;mask : none!important;contain: none!important;perspective: none!important;mix-blend-mode: initial!important;z-index: auto;}.ace_layer {z-index: 1;position: absolute;overflow: hidden;word-wrap: normal;white-space: pre;height: 100%;width: 100%;box-sizing: border-box;pointer-events: none;}.ace_gutter-layer {position: relative;width: auto;text-align: right;pointer-events: auto;height: 1000000px;contain: style size layout;}.ace_text-layer {font: inherit !important;position: absolute;height: 1000000px;width: 1000000px;contain: style size layout;}.ace_text-layer > .ace_line, .ace_text-layer > .ace_line_group {contain: style size layout;position: absolute;top: 0;left: 0;right: 0;}.ace_hidpi .ace_text-layer,.ace_hidpi .ace_gutter-layer,.ace_hidpi .ace_content,.ace_hidpi .ace_gutter {contain: strict;will-change: transform;}.ace_hidpi .ace_text-layer > .ace_line, .ace_hidpi .ace_text-layer > .ace_line_group {contain: strict;}.ace_cjk {display: inline-block;text-align: center;}.ace_cursor-layer {z-index: 4;}.ace_cursor {z-index: 4;position: absolute;box-sizing: border-box;border-left: 2px solid;transform: translatez(0);}.ace_multiselect .ace_cursor {border-left-width: 1px;}.ace_slim-cursors .ace_cursor {border-left-width: 1px;}.ace_overwrite-cursors .ace_cursor {border-left-width: 0;border-bottom: 1px solid;}.ace_hidden-cursors .ace_cursor {opacity: 0.2;}.ace_hasPlaceholder .ace_hidden-cursors .ace_cursor {opacity: 0;}.ace_smooth-blinking .ace_cursor {transition: opacity 0.18s;}.ace_animate-blinking .ace_cursor {animation-duration: 1000ms;animation-timing-function: step-end;animation-name: blink-ace-animate;animation-iteration-count: infinite;}.ace_animate-blinking.ace_smooth-blinking .ace_cursor {animation-duration: 1000ms;animation-timing-function: ease-in-out;animation-name: blink-ace-animate-smooth;}@keyframes blink-ace-animate {from, to { opacity: 1; }60% { opacity: 0; }}@keyframes blink-ace-animate-smooth {from, to { opacity: 1; }45% { opacity: 1; }60% { opacity: 0; }85% { opacity: 0; }}.ace_marker-layer .ace_step, .ace_marker-layer .ace_stack {position: absolute;z-index: 3;}.ace_marker-layer .ace_selection {position: absolute;z-index: 5;}.ace_marker-layer .ace_bracket {position: absolute;z-index: 6;}.ace_marker-layer .ace_error_bracket {position: absolute;border-bottom: 1px solid #DE5555;border-radius: 0;}.ace_marker-layer .ace_active-line {position: absolute;z-index: 2;}.ace_marker-layer .ace_selected-word {position: absolute;z-index: 4;box-sizing: border-box;}.ace_line .ace_fold {box-sizing: border-box;display: inline-block;height: 11px;margin-top: -2px;vertical-align: middle;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAJCAYAAADU6McMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAJpJREFUeNpi/P//PwOlgAXGYGRklAVSokD8GmjwY1wasKljQpYACtpCFeADcHVQfQyMQAwzwAZI3wJKvCLkfKBaMSClBlR7BOQikCFGQEErIH0VqkabiGCAqwUadAzZJRxQr/0gwiXIal8zQQPnNVTgJ1TdawL0T5gBIP1MUJNhBv2HKoQHHjqNrA4WO4zY0glyNKLT2KIfIMAAQsdgGiXvgnYAAAAASUVORK5CYII="),url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAA3CAYAAADNNiA5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACJJREFUeNpi+P//fxgTAwPDBxDxD078RSX+YeEyDFMCIMAAI3INmXiwf2YAAAAASUVORK5CYII=");background-repeat: no-repeat, repeat-x;background-position: center center, top left;color: transparent;border: 1px solid black;border-radius: 2px;cursor: pointer;pointer-events: auto;}.ace_dark .ace_fold {}.ace_fold:hover{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAJCAYAAADU6McMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAJpJREFUeNpi/P//PwOlgAXGYGRklAVSokD8GmjwY1wasKljQpYACtpCFeADcHVQfQyMQAwzwAZI3wJKvCLkfKBaMSClBlR7BOQikCFGQEErIH0VqkabiGCAqwUadAzZJRxQr/0gwiXIal8zQQPnNVTgJ1TdawL0T5gBIP1MUJNhBv2HKoQHHjqNrA4WO4zY0glyNKLT2KIfIMAAQsdgGiXvgnYAAAAASUVORK5CYII="),url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAA3CAYAAADNNiA5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACBJREFUeNpi+P//fz4TAwPDZxDxD5X4i5fLMEwJgAADAEPVDbjNw87ZAAAAAElFTkSuQmCC");}.ace_tooltip {background-color: #FFF;background-image: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.1));border: 1px solid gray;border-radius: 1px;box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);color: black;max-width: 100%;padding: 3px 4px;position: fixed;z-index: 999999;box-sizing: border-box;cursor: default;white-space: pre;word-wrap: break-word;line-height: normal;font-style: normal;font-weight: normal;letter-spacing: normal;pointer-events: none;}.ace_folding-enabled > .ace_gutter-cell {padding-right: 13px;}.ace_fold-widget {box-sizing: border-box;margin: 0 -12px 0 1px;display: none;width: 11px;vertical-align: top;background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAANElEQVR42mWKsQ0AMAzC8ixLlrzQjzmBiEjp0A6WwBCSPgKAXoLkqSot7nN3yMwR7pZ32NzpKkVoDBUxKAAAAABJRU5ErkJggg==");background-repeat: no-repeat;background-position: center;border-radius: 3px;border: 1px solid transparent;cursor: pointer;}.ace_folding-enabled .ace_fold-widget {display: inline-block; }.ace_fold-widget.ace_end {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAANElEQVR42m3HwQkAMAhD0YzsRchFKI7sAikeWkrxwScEB0nh5e7KTPWimZki4tYfVbX+MNl4pyZXejUO1QAAAABJRU5ErkJggg==");}.ace_fold-widget.ace_closed {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAGCAYAAAAG5SQMAAAAOUlEQVR42jXKwQkAMAgDwKwqKD4EwQ26sSOkVWjgIIHAzPiCgaqiqnJHZnKICBERHN194O5b9vbLuAVRL+l0YWnZAAAAAElFTkSuQmCCXA==");}.ace_fold-widget:hover {border: 1px solid rgba(0, 0, 0, 0.3);background-color: rgba(255, 255, 255, 0.2);box-shadow: 0 1px 1px rgba(255, 255, 255, 0.7);}.ace_fold-widget:active {border: 1px solid rgba(0, 0, 0, 0.4);background-color: rgba(0, 0, 0, 0.05);box-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);}.ace_dark .ace_fold-widget {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHklEQVQIW2P4//8/AzoGEQ7oGCaLLAhWiSwB146BAQCSTPYocqT0AAAAAElFTkSuQmCC");}.ace_dark .ace_fold-widget.ace_end {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAH0lEQVQIW2P4//8/AxQ7wNjIAjDMgC4AxjCVKBirIAAF0kz2rlhxpAAAAABJRU5ErkJggg==");}.ace_dark .ace_fold-widget.ace_closed {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAHElEQVQIW2P4//+/AxAzgDADlOOAznHAKgPWAwARji8UIDTfQQAAAABJRU5ErkJggg==");}.ace_dark .ace_fold-widget:hover {box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);background-color: rgba(255, 255, 255, 0.1);}.ace_dark .ace_fold-widget:active {box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);}.ace_inline_button {border: 1px solid lightgray;display: inline-block;margin: -1px 8px;padding: 0 5px;pointer-events: auto;cursor: pointer;}.ace_inline_button:hover {border-color: gray;background: rgba(200,200,200,0.2);display: inline-block;pointer-events: auto;}.ace_fold-widget.ace_invalid {background-color: #FFB4B4;border-color: #DE5555;}.ace_fade-fold-widgets .ace_fold-widget {transition: opacity 0.4s ease 0.05s;opacity: 0;}.ace_fade-fold-widgets:hover .ace_fold-widget {transition: opacity 0.05s ease 0.05s;opacity:1;}.ace_underline {text-decoration: underline;}.ace_bold {font-weight: bold;}.ace_nobold .ace_bold {font-weight: normal;}.ace_italic {font-style: italic;}.ace_error-marker {background-color: rgba(255, 0, 0,0.2);position: absolute;z-index: 9;}.ace_highlight-marker {background-color: rgba(255, 255, 0,0.2);position: absolute;z-index: 8;}.ace_mobile-menu {position: absolute;line-height: 1.5;border-radius: 4px;-ms-user-select: none;-moz-user-select: none;-webkit-user-select: none;user-select: none;background: white;box-shadow: 1px 3px 2px grey;border: 1px solid #dcdcdc;color: black;}.ace_dark > .ace_mobile-menu {background: #333;color: #ccc;box-shadow: 1px 3px 2px grey;border: 1px solid #444;}.ace_mobile-button {padding: 2px;cursor: pointer;overflow: hidden;}.ace_mobile-button:hover {background-color: #eee;opacity:1;}.ace_mobile-button:active {background-color: #ddd;}.ace_placeholder {font-family: arial;transform: scale(0.9);transform-origin: left;white-space: pre;opacity: 0.7;margin: 0 10px;}', m = e("./lib/useragent"), g = m.isIE; i.importCssString(v, "ace_editor.css"); var y = function (e, t) { var n = this; this.container = e || i.createElement("div"), i.addCssClass(this.container, "ace_editor"), i.HI_DPI && i.addCssClass(this.container, "ace_hidpi"), this.setTheme(t), this.$gutter = i.createElement("div"), this.$gutter.className = "ace_gutter", this.container.appendChild(this.$gutter), this.$gutter.setAttribute("aria-hidden", !0), this.scroller = i.createElement("div"), this.scroller.className = "ace_scroller", this.container.appendChild(this.scroller), this.content = i.createElement("div"), this.content.className = "ace_content", this.scroller.appendChild(this.content), this.$gutterLayer = new o(this.$gutter), this.$gutterLayer.on("changeGutterWidth", this.onGutterResize.bind(this)), this.$markerBack = new u(this.content); var r = this.$textLayer = new a(this.content); this.canvas = r.element, this.$markerFront = new u(this.content), this.$cursorLayer = new f(this.content), this.$horizScroll = !1, this.$vScroll = !1, this.scrollBar = this.scrollBarV = new c(this.container, this), this.scrollBarH = new l(this.container, this), this.scrollBarV.on("scroll", function (e) { n.$scrollAnimation || n.session.setScrollTop(e.data - n.scrollMargin.top) }), this.scrollBarH.on("scroll", function (e) { n.$scrollAnimation || n.session.setScrollLeft(e.data - n.scrollMargin.left) }), this.scrollTop = 0, this.scrollLeft = 0, this.cursorPos = { row: 0, column: 0 }, this.$fontMetrics = new p(this.container), this.$textLayer.$setFontMetrics(this.$fontMetrics), this.$textLayer.on("changeCharacterSize", function (e) { n.updateCharacterSize(), n.onResize(!0, n.gutterWidth, n.$size.width, n.$size.height), n._signal("changeCharacterSize", e) }), this.$size = { width: 0, height: 0, scrollerHeight: 0, scrollerWidth: 0, $dirty: !0 }, this.layerConfig = { width: 1, padding: 0, firstRow: 0, firstRowScreen: 0, lastRow: 0, lineHeight: 0, characterWidth: 0, minHeight: 1, maxHeight: 1, offset: 0, height: 1, gutterOffset: 1 }, this.scrollMargin = { left: 0, right: 0, top: 0, bottom: 0, v: 0, h: 0 }, this.margin = { left: 0, right: 0, top: 0, bottom: 0, v: 0, h: 0 }, this.$keepTextAreaAtCursor = !m.isIOS, this.$loop = new h(this.$renderChanges.bind(this), this.container.ownerDocument.defaultView), this.$loop.schedule(this.CHANGE_FULL), this.updateCharacterSize(), this.setPadding(4), s.resetOptions(this), s._signal("renderer", this) }; (function () { this.CHANGE_CURSOR = 1, this.CHANGE_MARKER = 2, this.CHANGE_GUTTER = 4, this.CHANGE_SCROLL = 8, this.CHANGE_LINES = 16, this.CHANGE_TEXT = 32, this.CHANGE_SIZE = 64, this.CHANGE_MARKER_BACK = 128, this.CHANGE_MARKER_FRONT = 256, this.CHANGE_FULL = 512, this.CHANGE_H_SCROLL = 1024, r.implement(this, d), this.updateCharacterSize = function () { this.$textLayer.allowBoldFonts != this.$allowBoldFonts && (this.$allowBoldFonts = this.$textLayer.allowBoldFonts, this.setStyle("ace_nobold", !this.$allowBoldFonts)), this.layerConfig.characterWidth = this.characterWidth = this.$textLayer.getCharacterWidth(), this.layerConfig.lineHeight = this.lineHeight = this.$textLayer.getLineHeight(), this.$updatePrintMargin(), i.setStyle(this.scroller.style, "line-height", this.lineHeight + "px") }, this.setSession = function (e) { this.session && this.session.doc.off("changeNewLineMode", this.onChangeNewLineMode), this.session = e, e && this.scrollMargin.top && e.getScrollTop() <= 0 && e.setScrollTop(-this.scrollMargin.top), this.$cursorLayer.setSession(e), this.$markerBack.setSession(e), this.$markerFront.setSession(e), this.$gutterLayer.setSession(e), this.$textLayer.setSession(e); if (!e) return; this.$loop.schedule(this.CHANGE_FULL), this.session.$setFontMetrics(this.$fontMetrics), this.scrollBarH.scrollLeft = this.scrollBarV.scrollTop = null, this.onChangeNewLineMode = this.onChangeNewLineMode.bind(this), this.onChangeNewLineMode(), this.session.doc.on("changeNewLineMode", this.onChangeNewLineMode) }, this.updateLines = function (e, t, n) { t === undefined && (t = Infinity), this.$changedLines ? (this.$changedLines.firstRow > e && (this.$changedLines.firstRow = e), this.$changedLines.lastRow < t && (this.$changedLines.lastRow = t)) : this.$changedLines = { firstRow: e, lastRow: t }; if (this.$changedLines.lastRow < this.layerConfig.firstRow) { if (!n) return; this.$changedLines.lastRow = this.layerConfig.lastRow } if (this.$changedLines.firstRow > this.layerConfig.lastRow) return; this.$loop.schedule(this.CHANGE_LINES) }, this.onChangeNewLineMode = function () { this.$loop.schedule(this.CHANGE_TEXT), this.$textLayer.$updateEolChar(), this.session.$bidiHandler.setEolChar(this.$textLayer.EOL_CHAR) }, this.onChangeTabSize = function () { this.$loop.schedule(this.CHANGE_TEXT | this.CHANGE_MARKER), this.$textLayer.onChangeTabSize() }, this.updateText = function () { this.$loop.schedule(this.CHANGE_TEXT) }, this.updateFull = function (e) { e ? this.$renderChanges(this.CHANGE_FULL, !0) : this.$loop.schedule(this.CHANGE_FULL) }, this.updateFontSize = function () { this.$textLayer.checkForSizeChanges() }, this.$changes = 0, this.$updateSizeAsync = function () { this.$loop.pending ? this.$size.$dirty = !0 : this.onResize() }, this.onResize = function (e, t, n, r) { if (this.resizing > 2) return; this.resizing > 0 ? this.resizing++ : this.resizing = e ? 1 : 0; var i = this.container; r || (r = i.clientHeight || i.scrollHeight), n || (n = i.clientWidth || i.scrollWidth); var s = this.$updateCachedSize(e, t, n, r); if (!this.$size.scrollerHeight || !n && !r) return this.resizing = 0; e && (this.$gutterLayer.$padding = null), e ? this.$renderChanges(s | this.$changes, !0) : this.$loop.schedule(s | this.$changes), this.resizing && (this.resizing = 0), this.scrollBarV.scrollLeft = this.scrollBarV.scrollTop = null }, this.$updateCachedSize = function (e, t, n, r) { r -= this.$extraHeight || 0; var s = 0, o = this.$size, u = { width: o.width, height: o.height, scrollerHeight: o.scrollerHeight, scrollerWidth: o.scrollerWidth }; r && (e || o.height != r) && (o.height = r, s |= this.CHANGE_SIZE, o.scrollerHeight = o.height, this.$horizScroll && (o.scrollerHeight -= this.scrollBarH.getHeight()), this.scrollBarV.element.style.bottom = this.scrollBarH.getHeight() + "px", s |= this.CHANGE_SCROLL); if (n && (e || o.width != n)) { s |= this.CHANGE_SIZE, o.width = n, t == null && (t = this.$showGutter ? this.$gutter.offsetWidth : 0), this.gutterWidth = t, i.setStyle(this.scrollBarH.element.style, "left", t + "px"), i.setStyle(this.scroller.style, "left", t + this.margin.left + "px"), o.scrollerWidth = Math.max(0, n - t - this.scrollBarV.getWidth() - this.margin.h), i.setStyle(this.$gutter.style, "left", this.margin.left + "px"); var a = this.scrollBarV.getWidth() + "px"; i.setStyle(this.scrollBarH.element.style, "right", a), i.setStyle(this.scroller.style, "right", a), i.setStyle(this.scroller.style, "bottom", this.scrollBarH.getHeight()); if (this.session && this.session.getUseWrapMode() && this.adjustWrapLimit() || e) s |= this.CHANGE_FULL } return o.$dirty = !n || !r, s && this._signal("resize", u), s }, this.onGutterResize = function (e) { var t = this.$showGutter ? e : 0; t != this.gutterWidth && (this.$changes |= this.$updateCachedSize(!0, t, this.$size.width, this.$size.height)), this.session.getUseWrapMode() && this.adjustWrapLimit() ? this.$loop.schedule(this.CHANGE_FULL) : this.$size.$dirty ? this.$loop.schedule(this.CHANGE_FULL) : this.$computeLayerConfig() }, this.adjustWrapLimit = function () { var e = this.$size.scrollerWidth - this.$padding * 2, t = Math.floor(e / this.characterWidth); return this.session.adjustWrapLimit(t, this.$showPrintMargin && this.$printMarginColumn) }, this.setAnimatedScroll = function (e) { this.setOption("animatedScroll", e) }, this.getAnimatedScroll = function () { return this.$animatedScroll }, this.setShowInvisibles = function (e) { this.setOption("showInvisibles", e), this.session.$bidiHandler.setShowInvisibles(e) }, this.getShowInvisibles = function () { return this.getOption("showInvisibles") }, this.getDisplayIndentGuides = function () { return this.getOption("displayIndentGuides") }, this.setDisplayIndentGuides = function (e) { this.setOption("displayIndentGuides", e) }, this.setShowPrintMargin = function (e) { this.setOption("showPrintMargin", e) }, this.getShowPrintMargin = function () { return this.getOption("showPrintMargin") }, this.setPrintMarginColumn = function (e) { this.setOption("printMarginColumn", e) }, this.getPrintMarginColumn = function () { return this.getOption("printMarginColumn") }, this.getShowGutter = function () { return this.getOption("showGutter") }, this.setShowGutter = function (e) { return this.setOption("showGutter", e) }, this.getFadeFoldWidgets = function () { return this.getOption("fadeFoldWidgets") }, this.setFadeFoldWidgets = function (e) { this.setOption("fadeFoldWidgets", e) }, this.setHighlightGutterLine = function (e) { this.setOption("highlightGutterLine", e) }, this.getHighlightGutterLine = function () { return this.getOption("highlightGutterLine") }, this.$updatePrintMargin = function () { if (!this.$showPrintMargin && !this.$printMarginEl) return; if (!this.$printMarginEl) { var e = i.createElement("div"); e.className = "ace_layer ace_print-margin-layer", this.$printMarginEl = i.createElement("div"), this.$printMarginEl.className = "ace_print-margin", e.appendChild(this.$printMarginEl), this.content.insertBefore(e, this.content.firstChild) } var t = this.$printMarginEl.style; t.left = Math.round(this.characterWidth * this.$printMarginColumn + this.$padding) + "px", t.visibility = this.$showPrintMargin ? "visible" : "hidden", this.session && this.session.$wrap == -1 && this.adjustWrapLimit() }, this.getContainerElement = function () { return this.container }, this.getMouseEventTarget = function () { return this.scroller }, this.getTextAreaContainer = function () { return this.container }, this.$moveTextAreaToCursor = function () { if (this.$isMousePressed) return; var e = this.textarea.style, t = this.$composition; if (!this.$keepTextAreaAtCursor && !t) { i.translate(this.textarea, -100, 0); return } var n = this.$cursorLayer.$pixelPos; if (!n) return; t && t.markerRange && (n = this.$cursorLayer.getPixelPosition(t.markerRange.start, !0)); var r = this.layerConfig, s = n.top, o = n.left; s -= r.offset; var u = t && t.useTextareaForIME ? this.lineHeight : g ? 0 : 1; if (s < 0 || s > r.height - u) { i.translate(this.textarea, 0, 0); return } var a = 1, f = this.$size.height - u; if (!t) s += this.lineHeight; else if (t.useTextareaForIME) { var l = this.textarea.value; a = this.characterWidth * this.session.$getStringScreenWidth(l)[0] } else s += this.lineHeight + 2; o -= this.scrollLeft, o > this.$size.scrollerWidth - a && (o = this.$size.scrollerWidth - a), o += this.gutterWidth + this.margin.left, i.setStyle(e, "height", u + "px"), i.setStyle(e, "width", a + "px"), i.translate(this.textarea, Math.min(o, this.$size.scrollerWidth - a), Math.min(s, f)) }, this.getFirstVisibleRow = function () { return this.layerConfig.firstRow }, this.getFirstFullyVisibleRow = function () { return this.layerConfig.firstRow + (this.layerConfig.offset === 0 ? 0 : 1) }, this.getLastFullyVisibleRow = function () { var e = this.layerConfig, t = e.lastRow, n = this.session.documentToScreenRow(t, 0) * e.lineHeight; return n - this.session.getScrollTop() > e.height - e.lineHeight ? t - 1 : t }, this.getLastVisibleRow = function () { return this.layerConfig.lastRow }, this.$padding = null, this.setPadding = function (e) { this.$padding = e, this.$textLayer.setPadding(e), this.$cursorLayer.setPadding(e), this.$markerFront.setPadding(e), this.$markerBack.setPadding(e), this.$loop.schedule(this.CHANGE_FULL), this.$updatePrintMargin() }, this.setScrollMargin = function (e, t, n, r) { var i = this.scrollMargin; i.top = e | 0, i.bottom = t | 0, i.right = r | 0, i.left = n | 0, i.v = i.top + i.bottom, i.h = i.left + i.right, i.top && this.scrollTop <= 0 && this.session && this.session.setScrollTop(-i.top), this.updateFull() }, this.setMargin = function (e, t, n, r) { var i = this.margin; i.top = e | 0, i.bottom = t | 0, i.right = r | 0, i.left = n | 0, i.v = i.top + i.bottom, i.h = i.left + i.right, this.$updateCachedSize(!0, this.gutterWidth, this.$size.width, this.$size.height), this.updateFull() }, this.getHScrollBarAlwaysVisible = function () { return this.$hScrollBarAlwaysVisible }, this.setHScrollBarAlwaysVisible = function (e) { this.setOption("hScrollBarAlwaysVisible", e) }, this.getVScrollBarAlwaysVisible = function () { return this.$vScrollBarAlwaysVisible }, this.setVScrollBarAlwaysVisible = function (e) { this.setOption("vScrollBarAlwaysVisible", e) }, this.$updateScrollBarV = function () { var e = this.layerConfig.maxHeight, t = this.$size.scrollerHeight; !this.$maxLines && this.$scrollPastEnd && (e -= (t - this.lineHeight) * this.$scrollPastEnd, this.scrollTop > e - t && (e = this.scrollTop + t, this.scrollBarV.scrollTop = null)), this.scrollBarV.setScrollHeight(e + this.scrollMargin.v), this.scrollBarV.setScrollTop(this.scrollTop + this.scrollMargin.top) }, this.$updateScrollBarH = function () { this.scrollBarH.setScrollWidth(this.layerConfig.width + 2 * this.$padding + this.scrollMargin.h), this.scrollBarH.setScrollLeft(this.scrollLeft + this.scrollMargin.left) }, this.$frozen = !1, this.freeze = function () { this.$frozen = !0 }, this.unfreeze = function () { this.$frozen = !1 }, this.$renderChanges = function (e, t) { this.$changes && (e |= this.$changes, this.$changes = 0); if (!this.session || !this.container.offsetWidth || this.$frozen || !e && !t) { this.$changes |= e; return } if (this.$size.$dirty) return this.$changes |= e, this.onResize(!0); this.lineHeight || this.$textLayer.checkForSizeChanges(), this._signal("beforeRender", e), this.session && this.session.$bidiHandler && this.session.$bidiHandler.updateCharacterWidths(this.$fontMetrics); var n = this.layerConfig; if (e & this.CHANGE_FULL || e & this.CHANGE_SIZE || e & this.CHANGE_TEXT || e & this.CHANGE_LINES || e & this.CHANGE_SCROLL || e & this.CHANGE_H_SCROLL) { e |= this.$computeLayerConfig() | this.$loop.clear(); if (n.firstRow != this.layerConfig.firstRow && n.firstRowScreen == this.layerConfig.firstRowScreen) { var r = this.scrollTop + (n.firstRow - this.layerConfig.firstRow) * this.lineHeight; r > 0 && (this.scrollTop = r, e |= this.CHANGE_SCROLL, e |= this.$computeLayerConfig() | this.$loop.clear()) } n = this.layerConfig, this.$updateScrollBarV(), e & this.CHANGE_H_SCROLL && this.$updateScrollBarH(), i.translate(this.content, -this.scrollLeft, -n.offset); var s = n.width + 2 * this.$padding + "px", o = n.minHeight + "px"; i.setStyle(this.content.style, "width", s), i.setStyle(this.content.style, "height", o) } e & this.CHANGE_H_SCROLL && (i.translate(this.content, -this.scrollLeft, -n.offset), this.scroller.className = this.scrollLeft <= 0 ? "ace_scroller" : "ace_scroller ace_scroll-left"); if (e & this.CHANGE_FULL) { this.$changedLines = null, this.$textLayer.update(n), this.$showGutter && this.$gutterLayer.update(n), this.$markerBack.update(n), this.$markerFront.update(n), this.$cursorLayer.update(n), this.$moveTextAreaToCursor(), this._signal("afterRender", e); return } if (e & this.CHANGE_SCROLL) { this.$changedLines = null, e & this.CHANGE_TEXT || e & this.CHANGE_LINES ? this.$textLayer.update(n) : this.$textLayer.scrollLines(n), this.$showGutter && (e & this.CHANGE_GUTTER || e & this.CHANGE_LINES ? this.$gutterLayer.update(n) : this.$gutterLayer.scrollLines(n)), this.$markerBack.update(n), this.$markerFront.update(n), this.$cursorLayer.update(n), this.$moveTextAreaToCursor(), this._signal("afterRender", e); return } e & this.CHANGE_TEXT ? (this.$changedLines = null, this.$textLayer.update(n), this.$showGutter && this.$gutterLayer.update(n)) : e & this.CHANGE_LINES ? (this.$updateLines() || e & this.CHANGE_GUTTER && this.$showGutter) && this.$gutterLayer.update(n) : e & this.CHANGE_TEXT || e & this.CHANGE_GUTTER ? this.$showGutter && this.$gutterLayer.update(n) : e & this.CHANGE_CURSOR && this.$highlightGutterLine && this.$gutterLayer.updateLineHighlight(n), e & this.CHANGE_CURSOR && (this.$cursorLayer.update(n), this.$moveTextAreaToCursor()), e & (this.CHANGE_MARKER | this.CHANGE_MARKER_FRONT) && this.$markerFront.update(n), e & (this.CHANGE_MARKER | this.CHANGE_MARKER_BACK) && this.$markerBack.update(n), this._signal("afterRender", e) }, this.$autosize = function () { var e = this.session.getScreenLength() * this.lineHeight, t = this.$maxLines * this.lineHeight, n = Math.min(t, Math.max((this.$minLines || 1) * this.lineHeight, e)) + this.scrollMargin.v + (this.$extraHeight || 0); this.$horizScroll && (n += this.scrollBarH.getHeight()), this.$maxPixelHeight && n > this.$maxPixelHeight && (n = this.$maxPixelHeight); var r = n <= 2 * this.lineHeight, i = !r && e > t; if (n != this.desiredHeight || this.$size.height != this.desiredHeight || i != this.$vScroll) { i != this.$vScroll && (this.$vScroll = i, this.scrollBarV.setVisible(i)); var s = this.container.clientWidth; this.container.style.height = n + "px", this.$updateCachedSize(!0, this.$gutterWidth, s, n), this.desiredHeight = n, this._signal("autosize") } }, this.$computeLayerConfig = function () { var e = this.session, t = this.$size, n = t.height <= 2 * this.lineHeight, r = this.session.getScreenLength(), i = r * this.lineHeight, s = this.$getLongestLine(), o = !n && (this.$hScrollBarAlwaysVisible || t.scrollerWidth - s - 2 * this.$padding < 0), u = this.$horizScroll !== o; u && (this.$horizScroll = o, this.scrollBarH.setVisible(o)); var a = this.$vScroll; this.$maxLines && this.lineHeight > 1 && this.$autosize(); var f = t.scrollerHeight + this.lineHeight, l = !this.$maxLines && this.$scrollPastEnd ? (t.scrollerHeight - this.lineHeight) * this.$scrollPastEnd : 0; i += l; var c = this.scrollMargin; this.session.setScrollTop(Math.max(-c.top, Math.min(this.scrollTop, i - t.scrollerHeight + c.bottom))), this.session.setScrollLeft(Math.max(-c.left, Math.min(this.scrollLeft, s + 2 * this.$padding - t.scrollerWidth + c.right))); var h = !n && (this.$vScrollBarAlwaysVisible || t.scrollerHeight - i + l < 0 || this.scrollTop > c.top), p = a !== h; p && (this.$vScroll = h, this.scrollBarV.setVisible(h)); var d = this.scrollTop % this.lineHeight, v = Math.ceil(f / this.lineHeight) - 1, m = Math.max(0, Math.round((this.scrollTop - d) / this.lineHeight)), g = m + v, y, b, w = this.lineHeight; m = e.screenToDocumentRow(m, 0); var E = e.getFoldLine(m); E && (m = E.start.row), y = e.documentToScreenRow(m, 0), b = e.getRowLength(m) * w, g = Math.min(e.screenToDocumentRow(g, 0), e.getLength() - 1), f = t.scrollerHeight + e.getRowLength(g) * w + b, d = this.scrollTop - y * w; var S = 0; if (this.layerConfig.width != s || u) S = this.CHANGE_H_SCROLL; if (u || p) S |= this.$updateCachedSize(!0, this.gutterWidth, t.width, t.height), this._signal("scrollbarVisibilityChanged"), p && (s = this.$getLongestLine()); return this.layerConfig = { width: s, padding: this.$padding, firstRow: m, firstRowScreen: y, lastRow: g, lineHeight: w, characterWidth: this.characterWidth, minHeight: f, maxHeight: i, offset: d, gutterOffset: w ? Math.max(0, Math.ceil((d + t.height - t.scrollerHeight) / w)) : 0, height: this.$size.scrollerHeight }, this.session.$bidiHandler && this.session.$bidiHandler.setContentWidth(s - this.$padding), S }, this.$updateLines = function () { if (!this.$changedLines) return; var e = this.$changedLines.firstRow, t = this.$changedLines.lastRow; this.$changedLines = null; var n = this.layerConfig; if (e > n.lastRow + 1) return; if (t < n.firstRow) return; if (t === Infinity) { this.$showGutter && this.$gutterLayer.update(n), this.$textLayer.update(n); return } return this.$textLayer.updateLines(n, e, t), !0 }, this.$getLongestLine = function () { var e = this.session.getScreenWidth(); return this.showInvisibles && !this.session.$useWrapMode && (e += 1), this.$textLayer && e > this.$textLayer.MAX_LINE_LENGTH && (e = this.$textLayer.MAX_LINE_LENGTH + 30), Math.max(this.$size.scrollerWidth - 2 * this.$padding, Math.round(e * this.characterWidth)) }, this.updateFrontMarkers = function () { this.$markerFront.setMarkers(this.session.getMarkers(!0)), this.$loop.schedule(this.CHANGE_MARKER_FRONT) }, this.updateBackMarkers = function () { this.$markerBack.setMarkers(this.session.getMarkers()), this.$loop.schedule(this.CHANGE_MARKER_BACK) }, this.addGutterDecoration = function (e, t) { this.$gutterLayer.addGutterDecoration(e, t) }, this.removeGutterDecoration = function (e, t) { this.$gutterLayer.removeGutterDecoration(e, t) }, this.updateBreakpoints = function (e) { this.$loop.schedule(this.CHANGE_GUTTER) }, this.setAnnotations = function (e) { this.$gutterLayer.setAnnotations(e), this.$loop.schedule(this.CHANGE_GUTTER) }, this.updateCursor = function () { this.$loop.schedule(this.CHANGE_CURSOR) }, this.hideCursor = function () { this.$cursorLayer.hideCursor() }, this.showCursor = function () { this.$cursorLayer.showCursor() }, this.scrollSelectionIntoView = function (e, t, n) { this.scrollCursorIntoView(e, n), this.scrollCursorIntoView(t, n) }, this.scrollCursorIntoView = function (e, t, n) { if (this.$size.scrollerHeight === 0) return; var r = this.$cursorLayer.getPixelPosition(e), i = r.left, s = r.top, o = n && n.top || 0, u = n && n.bottom || 0, a = this.$scrollAnimation ? this.session.getScrollTop() : this.scrollTop; a + o > s ? (t && a + o > s + this.lineHeight && (s -= t * this.$size.scrollerHeight), s === 0 && (s = -this.scrollMargin.top), this.session.setScrollTop(s)) : a + this.$size.scrollerHeight - u < s + this.lineHeight && (t && a + this.$size.scrollerHeight - u < s - this.lineHeight && (s += t * this.$size.scrollerHeight), this.session.setScrollTop(s + this.lineHeight + u - this.$size.scrollerHeight)); var f = this.scrollLeft; f > i ? (i < this.$padding + 2 * this.layerConfig.characterWidth && (i = -this.scrollMargin.left), this.session.setScrollLeft(i)) : f + this.$size.scrollerWidth < i + this.characterWidth ? this.session.setScrollLeft(Math.round(i + this.characterWidth - this.$size.scrollerWidth)) : f <= this.$padding && i - f < this.characterWidth && this.session.setScrollLeft(0) }, this.getScrollTop = function () { return this.session.getScrollTop() }, this.getScrollLeft = function () { return this.session.getScrollLeft() }, this.getScrollTopRow = function () { return this.scrollTop / this.lineHeight }, this.getScrollBottomRow = function () { return Math.max(0, Math.floor((this.scrollTop + this.$size.scrollerHeight) / this.lineHeight) - 1) }, this.scrollToRow = function (e) { this.session.setScrollTop(e * this.lineHeight) }, this.alignCursor = function (e, t) { typeof e == "number" && (e = { row: e, column: 0 }); var n = this.$cursorLayer.getPixelPosition(e), r = this.$size.scrollerHeight - this.lineHeight, i = n.top - r * (t || 0); return this.session.setScrollTop(i), i }, this.STEPS = 8, this.$calcSteps = function (e, t) { var n = 0, r = this.STEPS, i = [], s = function (e, t, n) { return n * (Math.pow(e - 1, 3) + 1) + t }; for (n = 0; n < r; ++n)i.push(s(n / this.STEPS, e, t - e)); return i }, this.scrollToLine = function (e, t, n, r) { var i = this.$cursorLayer.getPixelPosition({ row: e, column: 0 }), s = i.top; t && (s -= this.$size.scrollerHeight / 2); var o = this.scrollTop; this.session.setScrollTop(s), n !== !1 && this.animateScrolling(o, r) }, this.animateScrolling = function (e, t) { var n = this.scrollTop; if (!this.$animatedScroll) return; var r = this; if (e == n) return; if (this.$scrollAnimation) { var i = this.$scrollAnimation.steps; if (i.length) { e = i[0]; if (e == n) return } } var s = r.$calcSteps(e, n); this.$scrollAnimation = { from: e, to: n, steps: s }, clearInterval(this.$timer), r.session.setScrollTop(s.shift()), r.session.$scrollTop = n, this.$timer = setInterval(function () { if (!r.session) return clearInterval(r.$timer); s.length ? (r.session.setScrollTop(s.shift()), r.session.$scrollTop = n) : n != null ? (r.session.$scrollTop = -1, r.session.setScrollTop(n), n = null) : (r.$timer = clearInterval(r.$timer), r.$scrollAnimation = null, t && t()) }, 10) }, this.scrollToY = function (e) { this.scrollTop !== e && (this.$loop.schedule(this.CHANGE_SCROLL), this.scrollTop = e) }, this.scrollToX = function (e) { this.scrollLeft !== e && (this.scrollLeft = e), this.$loop.schedule(this.CHANGE_H_SCROLL) }, this.scrollTo = function (e, t) { this.session.setScrollTop(t), this.session.setScrollLeft(t) }, this.scrollBy = function (e, t) { t && this.session.setScrollTop(this.session.getScrollTop() + t), e && this.session.setScrollLeft(this.session.getScrollLeft() + e) }, this.isScrollableBy = function (e, t) { if (t < 0 && this.session.getScrollTop() >= 1 - this.scrollMargin.top) return !0; if (t > 0 && this.session.getScrollTop() + this.$size.scrollerHeight - this.layerConfig.maxHeight < -1 + this.scrollMargin.bottom) return !0; if (e < 0 && this.session.getScrollLeft() >= 1 - this.scrollMargin.left) return !0; if (e > 0 && this.session.getScrollLeft() + this.$size.scrollerWidth - this.layerConfig.width < -1 + this.scrollMargin.right) return !0 }, this.pixelToScreenCoordinates = function (e, t) { var n; if (this.$hasCssTransforms) { n = { top: 0, left: 0 }; var r = this.$fontMetrics.transformCoordinates([e, t]); e = r[1] - this.gutterWidth - this.margin.left, t = r[0] } else n = this.scroller.getBoundingClientRect(); var i = e + this.scrollLeft - n.left - this.$padding, s = i / this.characterWidth, o = Math.floor((t + this.scrollTop - n.top) / this.lineHeight), u = this.$blockCursor ? Math.floor(s) : Math.round(s); return { row: o, column: u, side: s - u > 0 ? 1 : -1, offsetX: i } }, this.screenToTextCoordinates = function (e, t) { var n; if (this.$hasCssTransforms) { n = { top: 0, left: 0 }; var r = this.$fontMetrics.transformCoordinates([e, t]); e = r[1] - this.gutterWidth - this.margin.left, t = r[0] } else n = this.scroller.getBoundingClientRect(); var i = e + this.scrollLeft - n.left - this.$padding, s = i / this.characterWidth, o = this.$blockCursor ? Math.floor(s) : Math.round(s), u = Math.floor((t + this.scrollTop - n.top) / this.lineHeight); return this.session.screenToDocumentPosition(u, Math.max(o, 0), i) }, this.textToScreenCoordinates = function (e, t) { var n = this.scroller.getBoundingClientRect(), r = this.session.documentToScreenPosition(e, t), i = this.$padding + (this.session.$bidiHandler.isBidiRow(r.row, e) ? this.session.$bidiHandler.getPosLeft(r.column) : Math.round(r.column * this.characterWidth)), s = r.row * this.lineHeight; return { pageX: n.left + i - this.scrollLeft, pageY: n.top + s - this.scrollTop } }, this.visualizeFocus = function () { i.addCssClass(this.container, "ace_focus") }, this.visualizeBlur = function () { i.removeCssClass(this.container, "ace_focus") }, this.showComposition = function (e) { this.$composition = e, e.cssText || (e.cssText = this.textarea.style.cssText), e.useTextareaForIME == undefined && (e.useTextareaForIME = this.$useTextareaForIME), this.$useTextareaForIME ? (i.addCssClass(this.textarea, "ace_composition"), this.textarea.style.cssText = "", this.$moveTextAreaToCursor(), this.$cursorLayer.element.style.display = "none") : e.markerId = this.session.addMarker(e.markerRange, "ace_composition_marker", "text") }, this.setCompositionText = function (e) { var t = this.session.selection.cursor; this.addToken(e, "composition_placeholder", t.row, t.column), this.$moveTextAreaToCursor() }, this.hideComposition = function () { if (!this.$composition) return; this.$composition.markerId && this.session.removeMarker(this.$composition.markerId), i.removeCssClass(this.textarea, "ace_composition"), this.textarea.style.cssText = this.$composition.cssText; var e = this.session.selection.cursor; this.removeExtraToken(e.row, e.column), this.$composition = null, this.$cursorLayer.element.style.display = "" }, this.addToken = function (e, t, n, r) { var i = this.session; i.bgTokenizer.lines[n] = null; var s = { type: t, value: e }, o = i.getTokens(n); if (r == null) o.push(s); else { var u = 0; for (var a = 0; a < o.length; a++) { var f = o[a]; u += f.value.length; if (r <= u) { var l = f.value.length - (u - r), c = f.value.slice(0, l), h = f.value.slice(l); o.splice(a, 1, { type: f.type, value: c }, s, { type: f.type, value: h }); break } } } this.updateLines(n, n) }, this.removeExtraToken = function (e, t) { this.updateLines(e, e) }, this.setTheme = function (e, t) { function o(r) { if (n.$themeId != e) return t && t(); if (!r || !r.cssClass) throw new Error("couldn't load module " + e + " or it didn't call define"); r.$id && (n.$themeId = r.$id), i.importCssString(r.cssText, r.cssClass, n.container), n.theme && i.removeCssClass(n.container, n.theme.cssClass); var s = "padding" in r ? r.padding : "padding" in (n.theme || {}) ? 4 : n.$padding; n.$padding && s != n.$padding && n.setPadding(s), n.$theme = r.cssClass, n.theme = r, i.addCssClass(n.container, r.cssClass), i.setCssClass(n.container, "ace_dark", r.isDark), n.$size && (n.$size.width = 0, n.$updateSizeAsync()), n._dispatchEvent("themeLoaded", { theme: r }), t && t() } var n = this; this.$themeId = e, n._dispatchEvent("themeChange", { theme: e }); if (!e || typeof e == "string") { var r = e || this.$options.theme.initialValue; s.loadModule(["theme", r], o) } else o(e) }, this.getTheme = function () { return this.$themeId }, this.setStyle = function (e, t) { i.setCssClass(this.container, e, t !== !1) }, this.unsetStyle = function (e) { i.removeCssClass(this.container, e) }, this.setCursorStyle = function (e) { i.setStyle(this.scroller.style, "cursor", e) }, this.setMouseCursor = function (e) { i.setStyle(this.scroller.style, "cursor", e) }, this.attachToShadowRoot = function () { i.importCssString(v, "ace_editor.css", this.container) }, this.destroy = function () { this.freeze(), this.$fontMetrics.destroy(), this.$cursorLayer.destroy(), this.removeAllListeners(), this.container.textContent = "" } }).call(y.prototype), s.defineOptions(y.prototype, "renderer", { animatedScroll: { initialValue: !1 }, showInvisibles: { set: function (e) { this.$textLayer.setShowInvisibles(e) && this.$loop.schedule(this.CHANGE_TEXT) }, initialValue: !1 }, showPrintMargin: { set: function () { this.$updatePrintMargin() }, initialValue: !0 }, printMarginColumn: { set: function () { this.$updatePrintMargin() }, initialValue: 80 }, printMargin: { set: function (e) { typeof e == "number" && (this.$printMarginColumn = e), this.$showPrintMargin = !!e, this.$updatePrintMargin() }, get: function () { return this.$showPrintMargin && this.$printMarginColumn } }, showGutter: { set: function (e) { this.$gutter.style.display = e ? "block" : "none", this.$loop.schedule(this.CHANGE_FULL), this.onGutterResize() }, initialValue: !0 }, fadeFoldWidgets: { set: function (e) { i.setCssClass(this.$gutter, "ace_fade-fold-widgets", e) }, initialValue: !1 }, showFoldWidgets: { set: function (e) { this.$gutterLayer.setShowFoldWidgets(e), this.$loop.schedule(this.CHANGE_GUTTER) }, initialValue: !0 }, displayIndentGuides: { set: function (e) { this.$textLayer.setDisplayIndentGuides(e) && this.$loop.schedule(this.CHANGE_TEXT) }, initialValue: !0 }, highlightGutterLine: { set: function (e) { this.$gutterLayer.setHighlightGutterLine(e), this.$loop.schedule(this.CHANGE_GUTTER) }, initialValue: !0 }, hScrollBarAlwaysVisible: { set: function (e) { (!this.$hScrollBarAlwaysVisible || !this.$horizScroll) && this.$loop.schedule(this.CHANGE_SCROLL) }, initialValue: !1 }, vScrollBarAlwaysVisible: { set: function (e) { (!this.$vScrollBarAlwaysVisible || !this.$vScroll) && this.$loop.schedule(this.CHANGE_SCROLL) }, initialValue: !1 }, fontSize: { set: function (e) { typeof e == "number" && (e += "px"), this.container.style.fontSize = e, this.updateFontSize() }, initialValue: 12 }, fontFamily: { set: function (e) { this.container.style.fontFamily = e, this.updateFontSize() } }, maxLines: { set: function (e) { this.updateFull() } }, minLines: { set: function (e) { this.$minLines < 562949953421311 || (this.$minLines = 0), this.updateFull() } }, maxPixelHeight: { set: function (e) { this.updateFull() }, initialValue: 0 }, scrollPastEnd: { set: function (e) { e = +e || 0; if (this.$scrollPastEnd == e) return; this.$scrollPastEnd = e, this.$loop.schedule(this.CHANGE_SCROLL) }, initialValue: 0, handlesSet: !0 }, fixedWidthGutter: { set: function (e) { this.$gutterLayer.$fixedWidth = !!e, this.$loop.schedule(this.CHANGE_GUTTER) } }, theme: { set: function (e) { this.setTheme(e) }, get: function () { return this.$themeId || this.theme }, initialValue: "./theme/textmate", handlesSet: !0 }, hasCssTransforms: {}, useTextareaForIME: { initialValue: !m.isMobile && !m.isIE } }), t.VirtualRenderer = y }), define("ace/worker/worker_client", ["require", "exports", "module", "ace/lib/oop", "ace/lib/net", "ace/lib/event_emitter", "ace/config"], function (e, t, n) { "use strict"; function u(e) { var t = "importScripts('" + i.qualifyURL(e) + "');"; try { return new Blob([t], { type: "application/javascript" }) } catch (n) { var r = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder, s = new r; return s.append(t), s.getBlob("application/javascript") } } function a(e) { if (typeof Worker == "undefined") return { postMessage: function () { }, terminate: function () { } }; if (o.get("loadWorkerFromBlob")) { var t = u(e), n = window.URL || window.webkitURL, r = n.createObjectURL(t); return new Worker(r) } return new Worker(e) } var r = e("../lib/oop"), i = e("../lib/net"), s = e("../lib/event_emitter").EventEmitter, o = e("../config"), f = function (e) { e.postMessage || (e = this.$createWorkerFromOldConfig.apply(this, arguments)), this.$worker = e, this.$sendDeltaQueue = this.$sendDeltaQueue.bind(this), this.changeListener = this.changeListener.bind(this), this.onMessage = this.onMessage.bind(this), this.callbackId = 1, this.callbacks = {}, this.$worker.onmessage = this.onMessage }; (function () { r.implement(this, s), this.$createWorkerFromOldConfig = function (t, n, r, i, s) { e.nameToUrl && !e.toUrl && (e.toUrl = e.nameToUrl); if (o.get("packaged") || !e.toUrl) i = i || o.moduleUrl(n, "worker"); else { var u = this.$normalizePath; i = i || u(e.toUrl("ace/worker/worker.js", null, "_")); var f = {}; t.forEach(function (t) { f[t] = u(e.toUrl(t, null, "_").replace(/(\.js)?(\?.*)?$/, "")) }) } return this.$worker = a(i), s && this.send("importScripts", s), this.$worker.postMessage({ init: !0, tlns: f, module: n, classname: r }), this.$worker }, this.onMessage = function (e) { var t = e.data; switch (t.type) { case "event": this._signal(t.name, { data: t.data }); break; case "call": var n = this.callbacks[t.id]; n && (n(t.data), delete this.callbacks[t.id]); break; case "error": this.reportError(t.data); break; case "log": window.console && console.log && console.log.apply(console, t.data) } }, this.reportError = function (e) { window.console && console.error && console.error(e) }, this.$normalizePath = function (e) { return i.qualifyURL(e) }, this.terminate = function () { this._signal("terminate", {}), this.deltaQueue = null, this.$worker.terminate(), this.$worker = null, this.$doc && this.$doc.off("change", this.changeListener), this.$doc = null }, this.send = function (e, t) { this.$worker.postMessage({ command: e, args: t }) }, this.call = function (e, t, n) { if (n) { var r = this.callbackId++; this.callbacks[r] = n, t.push(r) } this.send(e, t) }, this.emit = function (e, t) { try { t.data && t.data.err && (t.data.err = { message: t.data.err.message, stack: t.data.err.stack, code: t.data.err.code }), this.$worker.postMessage({ event: e, data: { data: t.data } }) } catch (n) { console.error(n.stack) } }, this.attachToDocument = function (e) { this.$doc && this.terminate(), this.$doc = e, this.call("setValue", [e.getValue()]), e.on("change", this.changeListener) }, this.changeListener = function (e) { this.deltaQueue || (this.deltaQueue = [], setTimeout(this.$sendDeltaQueue, 0)), e.action == "insert" ? this.deltaQueue.push(e.start, e.lines) : this.deltaQueue.push(e.start, e.end) }, this.$sendDeltaQueue = function () { var e = this.deltaQueue; if (!e) return; this.deltaQueue = null, e.length > 50 && e.length > this.$doc.getLength() >> 1 ? this.call("setValue", [this.$doc.getValue()]) : this.emit("change", { data: e }) } }).call(f.prototype); var l = function (e, t, n) { var r = null, i = !1, u = Object.create(s), a = [], l = new f({ messageBuffer: a, terminate: function () { }, postMessage: function (e) { a.push(e); if (!r) return; i ? setTimeout(c) : c() } }); l.setEmitSync = function (e) { i = e }; var c = function () { var e = a.shift(); e.command ? r[e.command].apply(r, e.args) : e.event && u._signal(e.event, e.data) }; return u.postMessage = function (e) { l.onMessage({ data: e }) }, u.callback = function (e, t) { this.postMessage({ type: "call", id: t, data: e }) }, u.emit = function (e, t) { this.postMessage({ type: "event", name: e, data: t }) }, o.loadModule(["worker", t], function (e) { r = new e[n](u); while (a.length) c() }), l }; t.UIWorkerClient = l, t.WorkerClient = f, t.createWorker = a }), define("ace/placeholder", ["require", "exports", "module", "ace/range", "ace/lib/event_emitter", "ace/lib/oop"], function (e, t, n) { "use strict"; var r = e("./range").Range, i = e("./lib/event_emitter").EventEmitter, s = e("./lib/oop"), o = function (e, t, n, r, i, s) { var o = this; this.length = t, this.session = e, this.doc = e.getDocument(), this.mainClass = i, this.othersClass = s, this.$onUpdate = this.onUpdate.bind(this), this.doc.on("change", this.$onUpdate), this.$others = r, this.$onCursorChange = function () { setTimeout(function () { o.onCursorChange() }) }, this.$pos = n; var u = e.getUndoManager().$undoStack || e.getUndoManager().$undostack || { length: -1 }; this.$undoStackDepth = u.length, this.setup(), e.selection.on("changeCursor", this.$onCursorChange) }; (function () { s.implement(this, i), this.setup = function () { var e = this, t = this.doc, n = this.session; this.selectionBefore = n.selection.toJSON(), n.selection.inMultiSelectMode && n.selection.toSingleRange(), this.pos = t.createAnchor(this.$pos.row, this.$pos.column); var i = this.pos; i.$insertRight = !0, i.detach(), i.markerId = n.addMarker(new r(i.row, i.column, i.row, i.column + this.length), this.mainClass, null, !1), this.others = [], this.$others.forEach(function (n) { var r = t.createAnchor(n.row, n.column); r.$insertRight = !0, r.detach(), e.others.push(r) }), n.setUndoSelect(!1) }, this.showOtherMarkers = function () { if (this.othersActive) return; var e = this.session, t = this; this.othersActive = !0, this.others.forEach(function (n) { n.markerId = e.addMarker(new r(n.row, n.column, n.row, n.column + t.length), t.othersClass, null, !1) }) }, this.hideOtherMarkers = function () { if (!this.othersActive) return; this.othersActive = !1; for (var e = 0; e < this.others.length; e++)this.session.removeMarker(this.others[e].markerId) }, this.onUpdate = function (e) { if (this.$updating) return this.updateAnchors(e); var t = e; if (t.start.row !== t.end.row) return; if (t.start.row !== this.pos.row) return; this.$updating = !0; var n = e.action === "insert" ? t.end.column - t.start.column : t.start.column - t.end.column, i = t.start.column >= this.pos.column && t.start.column <= this.pos.column + this.length + 1, s = t.start.column - this.pos.column; this.updateAnchors(e), i && (this.length += n); if (i && !this.session.$fromUndo) if (e.action === "insert") for (var o = this.others.length - 1; o >= 0; o--) { var u = this.others[o], a = { row: u.row, column: u.column + s }; this.doc.insertMergedLines(a, e.lines) } else if (e.action === "remove") for (var o = this.others.length - 1; o >= 0; o--) { var u = this.others[o], a = { row: u.row, column: u.column + s }; this.doc.remove(new r(a.row, a.column, a.row, a.column - n)) } this.$updating = !1, this.updateMarkers() }, this.updateAnchors = function (e) { this.pos.onChange(e); for (var t = this.others.length; t--;)this.others[t].onChange(e); this.updateMarkers() }, this.updateMarkers = function () { if (this.$updating) return; var e = this, t = this.session, n = function (n, i) { t.removeMarker(n.markerId), n.markerId = t.addMarker(new r(n.row, n.column, n.row, n.column + e.length), i, null, !1) }; n(this.pos, this.mainClass); for (var i = this.others.length; i--;)n(this.others[i], this.othersClass) }, this.onCursorChange = function (e) { if (this.$updating || !this.session) return; var t = this.session.selection.getCursor(); t.row === this.pos.row && t.column >= this.pos.column && t.column <= this.pos.column + this.length ? (this.showOtherMarkers(), this._emit("cursorEnter", e)) : (this.hideOtherMarkers(), this._emit("cursorLeave", e)) }, this.detach = function () { this.session.removeMarker(this.pos && this.pos.markerId), this.hideOtherMarkers(), this.doc.off("change", this.$onUpdate), this.session.selection.off("changeCursor", this.$onCursorChange), this.session.setUndoSelect(!0), this.session = null }, this.cancel = function () { if (this.$undoStackDepth === -1) return; var e = this.session.getUndoManager(), t = (e.$undoStack || e.$undostack).length - this.$undoStackDepth; for (var n = 0; n < t; n++)e.undo(this.session, !0); this.selectionBefore && this.session.selection.fromJSON(this.selectionBefore) } }).call(o.prototype), t.PlaceHolder = o }), define("ace/mouse/multi_select_handler", ["require", "exports", "module", "ace/lib/event", "ace/lib/useragent"], function (e, t, n) { function s(e, t) { return e.row == t.row && e.column == t.column } function o(e) { var t = e.domEvent, n = t.altKey, o = t.shiftKey, u = t.ctrlKey, a = e.getAccelKey(), f = e.getButton(); u && i.isMac && (f = t.button); if (e.editor.inMultiSelectMode && f == 2) { e.editor.textInput.onContextMenu(e.domEvent); return } if (!u && !n && !a) { f === 0 && e.editor.inMultiSelectMode && e.editor.exitMultiSelectMode(); return } if (f !== 0) return; var l = e.editor, c = l.selection, h = l.inMultiSelectMode, p = e.getDocumentPosition(), d = c.getCursor(), v = e.inSelection() || c.isEmpty() && s(p, d), m = e.x, g = e.y, y = function (e) { m = e.clientX, g = e.clientY }, b = l.session, w = l.renderer.pixelToScreenCoordinates(m, g), E = w, S; if (l.$mouseHandler.$enableJumpToDef) u && n || a && n ? S = o ? "block" : "add" : n && l.$blockSelectEnabled && (S = "block"); else if (a && !n) { S = "add"; if (!h && o) return } else n && l.$blockSelectEnabled && (S = "block"); S && i.isMac && t.ctrlKey && l.$mouseHandler.cancelContextMenu(); if (S == "add") { if (!h && v) return; if (!h) { var x = c.toOrientedRange(); l.addSelectionMarker(x) } var T = c.rangeList.rangeAtPoint(p); l.inVirtualSelectionMode = !0, o && (T = null, x = c.ranges[0] || x, l.removeSelectionMarker(x)), l.once("mouseup", function () { var e = c.toOrientedRange(); T && e.isEmpty() && s(T.cursor, e.cursor) ? c.substractPoint(e.cursor) : (o ? c.substractPoint(x.cursor) : x && (l.removeSelectionMarker(x), c.addRange(x)), c.addRange(e)), l.inVirtualSelectionMode = !1 }) } else if (S == "block") { e.stop(), l.inVirtualSelectionMode = !0; var N, C = [], k = function () { var e = l.renderer.pixelToScreenCoordinates(m, g), t = b.screenToDocumentPosition(e.row, e.column, e.offsetX); if (s(E, e) && s(t, c.lead)) return; E = e, l.selection.moveToPosition(t), l.renderer.scrollCursorIntoView(), l.removeSelectionMarkers(C), C = c.rectangularRangeBlock(E, w), l.$mouseHandler.$clickSelection && C.length == 1 && C[0].isEmpty() && (C[0] = l.$mouseHandler.$clickSelection.clone()), C.forEach(l.addSelectionMarker, l), l.updateSelectionMarkers() }; h && !a ? c.toSingleRange() : !h && a && (N = c.toOrientedRange(), l.addSelectionMarker(N)), o ? w = b.documentToScreenPosition(c.lead) : c.moveToPosition(p), E = { row: -1, column: -1 }; var L = function (e) { k(), clearInterval(O), l.removeSelectionMarkers(C), C.length || (C = [c.toOrientedRange()]), N && (l.removeSelectionMarker(N), c.toSingleRange(N)); for (var t = 0; t < C.length; t++)c.addRange(C[t]); l.inVirtualSelectionMode = !1, l.$mouseHandler.$clickSelection = null }, A = k; r.capture(l.container, y, L); var O = setInterval(function () { A() }, 20); return e.preventDefault() } } var r = e("../lib/event"), i = e("../lib/useragent"); t.onMouseDown = o }), define("ace/commands/multi_select_commands", ["require", "exports", "module", "ace/keyboard/hash_handler"], function (e, t, n) { t.defaultCommands = [{ name: "addCursorAbove", description: "Add cursor above", exec: function (e) { e.selectMoreLines(-1) }, bindKey: { win: "Ctrl-Alt-Up", mac: "Ctrl-Alt-Up" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "addCursorBelow", description: "Add cursor below", exec: function (e) { e.selectMoreLines(1) }, bindKey: { win: "Ctrl-Alt-Down", mac: "Ctrl-Alt-Down" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "addCursorAboveSkipCurrent", description: "Add cursor above (skip current)", exec: function (e) { e.selectMoreLines(-1, !0) }, bindKey: { win: "Ctrl-Alt-Shift-Up", mac: "Ctrl-Alt-Shift-Up" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "addCursorBelowSkipCurrent", description: "Add cursor below (skip current)", exec: function (e) { e.selectMoreLines(1, !0) }, bindKey: { win: "Ctrl-Alt-Shift-Down", mac: "Ctrl-Alt-Shift-Down" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "selectMoreBefore", description: "Select more before", exec: function (e) { e.selectMore(-1) }, bindKey: { win: "Ctrl-Alt-Left", mac: "Ctrl-Alt-Left" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "selectMoreAfter", description: "Select more after", exec: function (e) { e.selectMore(1) }, bindKey: { win: "Ctrl-Alt-Right", mac: "Ctrl-Alt-Right" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "selectNextBefore", description: "Select next before", exec: function (e) { e.selectMore(-1, !0) }, bindKey: { win: "Ctrl-Alt-Shift-Left", mac: "Ctrl-Alt-Shift-Left" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "selectNextAfter", description: "Select next after", exec: function (e) { e.selectMore(1, !0) }, bindKey: { win: "Ctrl-Alt-Shift-Right", mac: "Ctrl-Alt-Shift-Right" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "toggleSplitSelectionIntoLines", description: "Split into lines", exec: function (e) { e.multiSelect.rangeCount > 1 ? e.multiSelect.joinSelections() : e.multiSelect.splitIntoLines() }, bindKey: { win: "Ctrl-Alt-L", mac: "Ctrl-Alt-L" }, readOnly: !0 }, { name: "splitSelectionIntoLines", description: "Split into lines", exec: function (e) { e.multiSelect.splitIntoLines() }, readOnly: !0 }, { name: "alignCursors", description: "Align cursors", exec: function (e) { e.alignCursors() }, bindKey: { win: "Ctrl-Alt-A", mac: "Ctrl-Alt-A" }, scrollIntoView: "cursor" }, { name: "findAll", description: "Find all", exec: function (e) { e.findAll() }, bindKey: { win: "Ctrl-Alt-K", mac: "Ctrl-Alt-G" }, scrollIntoView: "cursor", readOnly: !0 }], t.multiSelectCommands = [{ name: "singleSelection", description: "Single selection", bindKey: "esc", exec: function (e) { e.exitMultiSelectMode() }, scrollIntoView: "cursor", readOnly: !0, isAvailable: function (e) { return e && e.inMultiSelectMode } }]; var r = e("../keyboard/hash_handler").HashHandler; t.keyboardHandler = new r(t.multiSelectCommands) }), define("ace/multi_select", ["require", "exports", "module", "ace/range_list", "ace/range", "ace/selection", "ace/mouse/multi_select_handler", "ace/lib/event", "ace/lib/lang", "ace/commands/multi_select_commands", "ace/search", "ace/edit_session", "ace/editor", "ace/config"], function (e, t, n) { function h(e, t, n) { return c.$options.wrap = !0, c.$options.needle = t, c.$options.backwards = n == -1, c.find(e) } function v(e, t) { return e.row == t.row && e.column == t.column } function m(e) { if (e.$multiselectOnSessionChange) return; e.$onAddRange = e.$onAddRange.bind(e), e.$onRemoveRange = e.$onRemoveRange.bind(e), e.$onMultiSelect = e.$onMultiSelect.bind(e), e.$onSingleSelect = e.$onSingleSelect.bind(e), e.$multiselectOnSessionChange = t.onSessionChange.bind(e), e.$checkMultiselectChange = e.$checkMultiselectChange.bind(e), e.$multiselectOnSessionChange(e), e.on("changeSession", e.$multiselectOnSessionChange), e.on("mousedown", o), e.commands.addCommands(f.defaultCommands), g(e) } function g(e) { function r(t) { n && (e.renderer.setMouseCursor(""), n = !1) } if (!e.textInput) return; var t = e.textInput.getElement(), n = !1; u.addListener(t, "keydown", function (t) { var i = t.keyCode == 18 && !(t.ctrlKey || t.shiftKey || t.metaKey); e.$blockSelectEnabled && i ? n || (e.renderer.setMouseCursor("crosshair"), n = !0) : n && r() }, e), u.addListener(t, "keyup", r, e), u.addListener(t, "blur", r, e) } var r = e("./range_list").RangeList, i = e("./range").Range, s = e("./selection").Selection, o = e("./mouse/multi_select_handler").onMouseDown, u = e("./lib/event"), a = e("./lib/lang"), f = e("./commands/multi_select_commands"); t.commands = f.defaultCommands.concat(f.multiSelectCommands); var l = e("./search").Search, c = new l, p = e("./edit_session").EditSession; (function () { this.getSelectionMarkers = function () { return this.$selectionMarkers } }).call(p.prototype), function () { this.ranges = null, this.rangeList = null, this.addRange = function (e, t) { if (!e) return; if (!this.inMultiSelectMode && this.rangeCount === 0) { var n = this.toOrientedRange(); this.rangeList.add(n), this.rangeList.add(e); if (this.rangeList.ranges.length != 2) return this.rangeList.removeAll(), t || this.fromOrientedRange(e); this.rangeList.removeAll(), this.rangeList.add(n), this.$onAddRange(n) } e.cursor || (e.cursor = e.end); var r = this.rangeList.add(e); return this.$onAddRange(e), r.length && this.$onRemoveRange(r), this.rangeCount > 1 && !this.inMultiSelectMode && (this._signal("multiSelect"), this.inMultiSelectMode = !0, this.session.$undoSelect = !1, this.rangeList.attach(this.session)), t || this.fromOrientedRange(e) }, this.toSingleRange = function (e) { e = e || this.ranges[0]; var t = this.rangeList.removeAll(); t.length && this.$onRemoveRange(t), e && this.fromOrientedRange(e) }, this.substractPoint = function (e) { var t = this.rangeList.substractPoint(e); if (t) return this.$onRemoveRange(t), t[0] }, this.mergeOverlappingRanges = function () { var e = this.rangeList.merge(); e.length && this.$onRemoveRange(e) }, this.$onAddRange = function (e) { this.rangeCount = this.rangeList.ranges.length, this.ranges.unshift(e), this._signal("addRange", { range: e }) }, this.$onRemoveRange = function (e) { this.rangeCount = this.rangeList.ranges.length; if (this.rangeCount == 1 && this.inMultiSelectMode) { var t = this.rangeList.ranges.pop(); e.push(t), this.rangeCount = 0 } for (var n = e.length; n--;) { var r = this.ranges.indexOf(e[n]); this.ranges.splice(r, 1) } this._signal("removeRange", { ranges: e }), this.rangeCount === 0 && this.inMultiSelectMode && (this.inMultiSelectMode = !1, this._signal("singleSelect"), this.session.$undoSelect = !0, this.rangeList.detach(this.session)), t = t || this.ranges[0], t && !t.isEqual(this.getRange()) && this.fromOrientedRange(t) }, this.$initRangeList = function () { if (this.rangeList) return; this.rangeList = new r, this.ranges = [], this.rangeCount = 0 }, this.getAllRanges = function () { return this.rangeCount ? this.rangeList.ranges.concat() : [this.getRange()] }, this.splitIntoLines = function () { var e = this.ranges.length ? this.ranges : [this.getRange()], t = []; for (var n = 0; n < e.length; n++) { var r = e[n], s = r.start.row, o = r.end.row; if (s === o) t.push(r.clone()); else { t.push(new i(s, r.start.column, s, this.session.getLine(s).length)); while (++s < o) t.push(this.getLineRange(s, !0)); t.push(new i(o, 0, o, r.end.column)) } n == 0 && !this.isBackwards() && (t = t.reverse()) } this.toSingleRange(); for (var n = t.length; n--;)this.addRange(t[n]) }, this.joinSelections = function () { var e = this.rangeList.ranges, t = e[e.length - 1], n = i.fromPoints(e[0].start, t.end); this.toSingleRange(), this.setSelectionRange(n, t.cursor == t.start) }, this.toggleBlockSelection = function () { if (this.rangeCount > 1) { var e = this.rangeList.ranges, t = e[e.length - 1], n = i.fromPoints(e[0].start, t.end); this.toSingleRange(), this.setSelectionRange(n, t.cursor == t.start) } else { var r = this.session.documentToScreenPosition(this.cursor), s = this.session.documentToScreenPosition(this.anchor), o = this.rectangularRangeBlock(r, s); o.forEach(this.addRange, this) } }, this.rectangularRangeBlock = function (e, t, n) { var r = [], s = e.column < t.column; if (s) var o = e.column, u = t.column, a = e.offsetX, f = t.offsetX; else var o = t.column, u = e.column, a = t.offsetX, f = e.offsetX; var l = e.row < t.row; if (l) var c = e.row, h = t.row; else var c = t.row, h = e.row; o < 0 && (o = 0), c < 0 && (c = 0), c == h && (n = !0); var p; for (var d = c; d <= h; d++) { var m = i.fromPoints(this.session.screenToDocumentPosition(d, o, a), this.session.screenToDocumentPosition(d, u, f)); if (m.isEmpty()) { if (p && v(m.end, p)) break; p = m.end } m.cursor = s ? m.start : m.end, r.push(m) } l && r.reverse(); if (!n) { var g = r.length - 1; while (r[g].isEmpty() && g > 0) g--; if (g > 0) { var y = 0; while (r[y].isEmpty()) y++ } for (var b = g; b >= y; b--)r[b].isEmpty() && r.splice(b, 1) } return r } }.call(s.prototype); var d = e("./editor").Editor; (function () { this.updateSelectionMarkers = function () { this.renderer.updateCursor(), this.renderer.updateBackMarkers() }, this.addSelectionMarker = function (e) { e.cursor || (e.cursor = e.end); var t = this.getSelectionStyle(); return e.marker = this.session.addMarker(e, "ace_selection", t), this.session.$selectionMarkers.push(e), this.session.selectionMarkerCount = this.session.$selectionMarkers.length, e }, this.removeSelectionMarker = function (e) { if (!e.marker) return; this.session.removeMarker(e.marker); var t = this.session.$selectionMarkers.indexOf(e); t != -1 && this.session.$selectionMarkers.splice(t, 1), this.session.selectionMarkerCount = this.session.$selectionMarkers.length }, this.removeSelectionMarkers = function (e) { var t = this.session.$selectionMarkers; for (var n = e.length; n--;) { var r = e[n]; if (!r.marker) continue; this.session.removeMarker(r.marker); var i = t.indexOf(r); i != -1 && t.splice(i, 1) } this.session.selectionMarkerCount = t.length }, this.$onAddRange = function (e) { this.addSelectionMarker(e.range), this.renderer.updateCursor(), this.renderer.updateBackMarkers() }, this.$onRemoveRange = function (e) { this.removeSelectionMarkers(e.ranges), this.renderer.updateCursor(), this.renderer.updateBackMarkers() }, this.$onMultiSelect = function (e) { if (this.inMultiSelectMode) return; this.inMultiSelectMode = !0, this.setStyle("ace_multiselect"), this.keyBinding.addKeyboardHandler(f.keyboardHandler), this.commands.setDefaultHandler("exec", this.$onMultiSelectExec), this.renderer.updateCursor(), this.renderer.updateBackMarkers() }, this.$onSingleSelect = function (e) { if (this.session.multiSelect.inVirtualMode) return; this.inMultiSelectMode = !1, this.unsetStyle("ace_multiselect"), this.keyBinding.removeKeyboardHandler(f.keyboardHandler), this.commands.removeDefaultHandler("exec", this.$onMultiSelectExec), this.renderer.updateCursor(), this.renderer.updateBackMarkers(), this._emit("changeSelection") }, this.$onMultiSelectExec = function (e) { var t = e.command, n = e.editor; if (!n.multiSelect) return; if (!t.multiSelectAction) { var r = t.exec(n, e.args || {}); n.multiSelect.addRange(n.multiSelect.toOrientedRange()), n.multiSelect.mergeOverlappingRanges() } else t.multiSelectAction == "forEach" ? r = n.forEachSelection(t, e.args) : t.multiSelectAction == "forEachLine" ? r = n.forEachSelection(t, e.args, !0) : t.multiSelectAction == "single" ? (n.exitMultiSelectMode(), r = t.exec(n, e.args || {})) : r = t.multiSelectAction(n, e.args || {}); return r }, this.forEachSelection = function (e, t, n) { if (this.inVirtualSelectionMode) return; var r = n && n.keepOrder, i = n == 1 || n && n.$byLines, o = this.session, u = this.selection, a = u.rangeList, f = (r ? u : a).ranges, l; if (!f.length) return e.exec ? e.exec(this, t || {}) : e(this, t || {}); var c = u._eventRegistry; u._eventRegistry = {}; var h = new s(o); this.inVirtualSelectionMode = !0; for (var p = f.length; p--;) { if (i) while (p > 0 && f[p].start.row == f[p - 1].end.row) p--; h.fromOrientedRange(f[p]), h.index = p, this.selection = o.selection = h; var d = e.exec ? e.exec(this, t || {}) : e(this, t || {}); !l && d !== undefined && (l = d), h.toOrientedRange(f[p]) } h.detach(), this.selection = o.selection = u, this.inVirtualSelectionMode = !1, u._eventRegistry = c, u.mergeOverlappingRanges(), u.ranges[0] && u.fromOrientedRange(u.ranges[0]); var v = this.renderer.$scrollAnimation; return this.onCursorChange(), this.onSelectionChange(), v && v.from == v.to && this.renderer.animateScrolling(v.from), l }, this.exitMultiSelectMode = function () { if (!this.inMultiSelectMode || this.inVirtualSelectionMode) return; this.multiSelect.toSingleRange() }, this.getSelectedText = function () { var e = ""; if (this.inMultiSelectMode && !this.inVirtualSelectionMode) { var t = this.multiSelect.rangeList.ranges, n = []; for (var r = 0; r < t.length; r++)n.push(this.session.getTextRange(t[r])); var i = this.session.getDocument().getNewLineCharacter(); e = n.join(i), e.length == (n.length - 1) * i.length && (e = "") } else this.selection.isEmpty() || (e = this.session.getTextRange(this.getSelectionRange())); return e }, this.$checkMultiselectChange = function (e, t) { if (this.inMultiSelectMode && !this.inVirtualSelectionMode) { var n = this.multiSelect.ranges[0]; if (this.multiSelect.isEmpty() && t == this.multiSelect.anchor) return; var r = t == this.multiSelect.anchor ? n.cursor == n.start ? n.end : n.start : n.cursor; r.row != t.row || this.session.$clipPositionToDocument(r.row, r.column).column != t.column ? this.multiSelect.toSingleRange(this.multiSelect.toOrientedRange()) : this.multiSelect.mergeOverlappingRanges() } }, this.findAll = function (e, t, n) { t = t || {}, t.needle = e || t.needle; if (t.needle == undefined) { var r = this.selection.isEmpty() ? this.selection.getWordRange() : this.selection.getRange(); t.needle = this.session.getTextRange(r) } this.$search.set(t); var i = this.$search.findAll(this.session); if (!i.length) return 0; var s = this.multiSelect; n || s.toSingleRange(i[0]); for (var o = i.length; o--;)s.addRange(i[o], !0); return r && s.rangeList.rangeAtPoint(r.start) && s.addRange(r, !0), i.length }, this.selectMoreLines = function (e, t) { var n = this.selection.toOrientedRange(), r = n.cursor == n.end, s = this.session.documentToScreenPosition(n.cursor); this.selection.$desiredColumn && (s.column = this.selection.$desiredColumn); var o = this.session.screenToDocumentPosition(s.row + e, s.column); if (!n.isEmpty()) var u = this.session.documentToScreenPosition(r ? n.end : n.start), a = this.session.screenToDocumentPosition(u.row + e, u.column); else var a = o; if (r) { var f = i.fromPoints(o, a); f.cursor = f.start } else { var f = i.fromPoints(a, o); f.cursor = f.end } f.desiredColumn = s.column; if (!this.selection.inMultiSelectMode) this.selection.addRange(n); else if (t) var l = n.cursor; this.selection.addRange(f), l && this.selection.substractPoint(l) }, this.transposeSelections = function (e) { var t = this.session, n = t.multiSelect, r = n.ranges; for (var i = r.length; i--;) { var s = r[i]; if (s.isEmpty()) { var o = t.getWordRange(s.start.row, s.start.column); s.start.row = o.start.row, s.start.column = o.start.column, s.end.row = o.end.row, s.end.column = o.end.column } } n.mergeOverlappingRanges(); var u = []; for (var i = r.length; i--;) { var s = r[i]; u.unshift(t.getTextRange(s)) } e < 0 ? u.unshift(u.pop()) : u.push(u.shift()); for (var i = r.length; i--;) { var s = r[i], o = s.clone(); t.replace(s, u[i]), s.start.row = o.start.row, s.start.column = o.start.column } n.fromOrientedRange(n.ranges[0]) }, this.selectMore = function (e, t, n) { var r = this.session, i = r.multiSelect, s = i.toOrientedRange(); if (s.isEmpty()) { s = r.getWordRange(s.start.row, s.start.column), s.cursor = e == -1 ? s.start : s.end, this.multiSelect.addRange(s); if (n) return } var o = r.getTextRange(s), u = h(r, o, e); u && (u.cursor = e == -1 ? u.start : u.end, this.session.unfold(u), this.multiSelect.addRange(u), this.renderer.scrollCursorIntoView(null, .5)), t && this.multiSelect.substractPoint(s.cursor) }, this.alignCursors = function () { var e = this.session, t = e.multiSelect, n = t.ranges, r = -1, s = n.filter(function (e) { if (e.cursor.row == r) return !0; r = e.cursor.row }); if (!n.length || s.length == n.length - 1) { var o = this.selection.getRange(), u = o.start.row, f = o.end.row, l = u == f; if (l) { var c = this.session.getLength(), h; do h = this.session.getLine(f); while (/[=:]/.test(h) && ++f < c); do h = this.session.getLine(u); while (/[=:]/.test(h) && --u > 0); u < 0 && (u = 0), f >= c && (f = c - 1) } var p = this.session.removeFullLines(u, f); p = this.$reAlignText(p, l), this.session.insert({ row: u, column: 0 }, p.join("\n") + "\n"), l || (o.start.column = 0, o.end.column = p[p.length - 1].length), this.selection.setRange(o) } else { s.forEach(function (e) { t.substractPoint(e.cursor) }); var d = 0, v = Infinity, m = n.map(function (t) { var n = t.cursor, r = e.getLine(n.row), i = r.substr(n.column).search(/\S/g); return i == -1 && (i = 0), n.column > d && (d = n.column), i < v && (v = i), i }); n.forEach(function (t, n) { var r = t.cursor, s = d - r.column, o = m[n] - v; s > o ? e.insert(r, a.stringRepeat(" ", s - o)) : e.remove(new i(r.row, r.column, r.row, r.column - s + o)), t.start.column = t.end.column = d, t.start.row = t.end.row = r.row, t.cursor = t.end }), t.fromOrientedRange(n[0]), this.renderer.updateCursor(), this.renderer.updateBackMarkers() } }, this.$reAlignText = function (e, t) { function u(e) { return a.stringRepeat(" ", e) } function f(e) { return e[2] ? u(i) + e[2] + u(s - e[2].length + o) + e[4].replace(/^([=:])\s+/, "$1 ") : e[0] } function l(e) { return e[2] ? u(i + s - e[2].length) + e[2] + u(o) + e[4].replace(/^([=:])\s+/, "$1 ") : e[0] } function c(e) { return e[2] ? u(i) + e[2] + u(o) + e[4].replace(/^([=:])\s+/, "$1 ") : e[0] } var n = !0, r = !0, i, s, o; return e.map(function (e) { var t = e.match(/(\s*)(.*?)(\s*)([=:].*)/); return t ? i == null ? (i = t[1].length, s = t[2].length, o = t[3].length, t) : (i + s + o != t[1].length + t[2].length + t[3].length && (r = !1), i != t[1].length && (n = !1), i > t[1].length && (i = t[1].length), s < t[2].length && (s = t[2].length), o > t[3].length && (o = t[3].length), t) : [e] }).map(t ? f : n ? r ? l : f : c) } }).call(d.prototype), t.onSessionChange = function (e) { var t = e.session; t && !t.multiSelect && (t.$selectionMarkers = [], t.selection.$initRangeList(), t.multiSelect = t.selection), this.multiSelect = t && t.multiSelect; var n = e.oldSession; n && (n.multiSelect.off("addRange", this.$onAddRange), n.multiSelect.off("removeRange", this.$onRemoveRange), n.multiSelect.off("multiSelect", this.$onMultiSelect), n.multiSelect.off("singleSelect", this.$onSingleSelect), n.multiSelect.lead.off("change", this.$checkMultiselectChange), n.multiSelect.anchor.off("change", this.$checkMultiselectChange)), t && (t.multiSelect.on("addRange", this.$onAddRange), t.multiSelect.on("removeRange", this.$onRemoveRange), t.multiSelect.on("multiSelect", this.$onMultiSelect), t.multiSelect.on("singleSelect", this.$onSingleSelect), t.multiSelect.lead.on("change", this.$checkMultiselectChange), t.multiSelect.anchor.on("change", this.$checkMultiselectChange)), t && this.inMultiSelectMode != t.selection.inMultiSelectMode && (t.selection.inMultiSelectMode ? this.$onMultiSelect() : this.$onSingleSelect()) }, t.MultiSelect = m, e("./config").defineOptions(d.prototype, "editor", { enableMultiselect: { set: function (e) { m(this), e ? (this.on("changeSession", this.$multiselectOnSessionChange), this.on("mousedown", o)) : (this.off("changeSession", this.$multiselectOnSessionChange), this.off("mousedown", o)) }, value: !0 }, enableBlockSelect: { set: function (e) { this.$blockSelectEnabled = e }, value: !0 } }) }), define("ace/mode/folding/fold_mode", ["require", "exports", "module", "ace/range"], function (e, t, n) { "use strict"; var r = e("../../range").Range, i = t.FoldMode = function () { }; (function () { this.foldingStartMarker = null, this.foldingStopMarker = null, this.getFoldWidget = function (e, t, n) { var r = e.getLine(n); return this.foldingStartMarker.test(r) ? "start" : t == "markbeginend" && this.foldingStopMarker && this.foldingStopMarker.test(r) ? "end" : "" }, this.getFoldWidgetRange = function (e, t, n) { return null }, this.indentationBlock = function (e, t, n) { var i = /\S/, s = e.getLine(t), o = s.search(i); if (o == -1) return; var u = n || s.length, a = e.getLength(), f = t, l = t; while (++t < a) { var c = e.getLine(t).search(i); if (c == -1) continue; if (c <= o) { var h = e.getTokenAt(t, 0); if (!h || h.type !== "string") break } l = t } if (l > f) { var p = e.getLine(l).length; return new r(f, u, l, p) } }, this.openingBracketBlock = function (e, t, n, i, s) { var o = { row: n, column: i + 1 }, u = e.$findClosingBracket(t, o, s); if (!u) return; var a = e.foldWidgets[u.row]; return a == null && (a = e.getFoldWidget(u.row)), a == "start" && u.row > o.row && (u.row--, u.column = e.getLine(u.row).length), r.fromPoints(o, u) }, this.closingBracketBlock = function (e, t, n, i, s) { var o = { row: n, column: i }, u = e.$findOpeningBracket(t, o); if (!u) return; return u.column++, o.column--, r.fromPoints(u, o) } }).call(i.prototype) }), define("ace/theme/textmate", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { "use strict"; t.isDark = !1, t.cssClass = "ace-tm", t.cssText = '.ace-tm .ace_gutter {background: #f0f0f0;color: #333;}.ace-tm .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-tm .ace_fold {background-color: #6B72E6;}.ace-tm {background-color: #FFFFFF;color: black;}.ace-tm .ace_cursor {color: black;}.ace-tm .ace_invisible {color: rgb(191, 191, 191);}.ace-tm .ace_storage,.ace-tm .ace_keyword {color: blue;}.ace-tm .ace_constant {color: rgb(197, 6, 11);}.ace-tm .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-tm .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-tm .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-tm .ace_invalid {background-color: rgba(255, 0, 0, 0.1);color: red;}.ace-tm .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-tm .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-tm .ace_support.ace_type,.ace-tm .ace_support.ace_class {color: rgb(109, 121, 222);}.ace-tm .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-tm .ace_string {color: rgb(3, 106, 7);}.ace-tm .ace_comment {color: rgb(76, 136, 107);}.ace-tm .ace_comment.ace_doc {color: rgb(0, 102, 255);}.ace-tm .ace_comment.ace_doc.ace_tag {color: rgb(128, 159, 191);}.ace-tm .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-tm .ace_variable {color: rgb(49, 132, 149);}.ace-tm .ace_xml-pe {color: rgb(104, 104, 91);}.ace-tm .ace_entity.ace_name.ace_function {color: #0000A2;}.ace-tm .ace_heading {color: rgb(12, 7, 255);}.ace-tm .ace_list {color:rgb(185, 6, 144);}.ace-tm .ace_meta.ace_tag {color:rgb(0, 22, 142);}.ace-tm .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-tm .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-tm.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px white;}.ace-tm .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-tm .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-tm .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-tm .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-tm .ace_gutter-active-line {background-color : #dcdcdc;}.ace-tm .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-tm .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}', t.$id = "ace/theme/textmate"; var r = e("../lib/dom"); r.importCssString(t.cssText, t.cssClass) }), define("ace/line_widgets", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { "use strict"; function i(e) { this.session = e, this.session.widgetManager = this, this.session.getRowLength = this.getRowLength, this.session.$getWidgetScreenLength = this.$getWidgetScreenLength, this.updateOnChange = this.updateOnChange.bind(this), this.renderWidgets = this.renderWidgets.bind(this), this.measureWidgets = this.measureWidgets.bind(this), this.session._changedWidgets = [], this.$onChangeEditor = this.$onChangeEditor.bind(this), this.session.on("change", this.updateOnChange), this.session.on("changeFold", this.updateOnFold), this.session.on("changeEditor", this.$onChangeEditor) } var r = e("./lib/dom"); (function () { this.getRowLength = function (e) { var t; return this.lineWidgets ? t = this.lineWidgets[e] && this.lineWidgets[e].rowCount || 0 : t = 0, !this.$useWrapMode || !this.$wrapData[e] ? 1 + t : this.$wrapData[e].length + 1 + t }, this.$getWidgetScreenLength = function () { var e = 0; return this.lineWidgets.forEach(function (t) { t && t.rowCount && !t.hidden && (e += t.rowCount) }), e }, this.$onChangeEditor = function (e) { this.attach(e.editor) }, this.attach = function (e) { e && e.widgetManager && e.widgetManager != this && e.widgetManager.detach(); if (this.editor == e) return; this.detach(), this.editor = e, e && (e.widgetManager = this, e.renderer.on("beforeRender", this.measureWidgets), e.renderer.on("afterRender", this.renderWidgets)) }, this.detach = function (e) { var t = this.editor; if (!t) return; this.editor = null, t.widgetManager = null, t.renderer.off("beforeRender", this.measureWidgets), t.renderer.off("afterRender", this.renderWidgets); var n = this.session.lineWidgets; n && n.forEach(function (e) { e && e.el && e.el.parentNode && (e._inDocument = !1, e.el.parentNode.removeChild(e.el)) }) }, this.updateOnFold = function (e, t) { var n = t.lineWidgets; if (!n || !e.action) return; var r = e.data, i = r.start.row, s = r.end.row, o = e.action == "add"; for (var u = i + 1; u < s; u++)n[u] && (n[u].hidden = o); n[s] && (o ? n[i] ? n[s].hidden = o : n[i] = n[s] : (n[i] == n[s] && (n[i] = undefined), n[s].hidden = o)) }, this.updateOnChange = function (e) { var t = this.session.lineWidgets; if (!t) return; var n = e.start.row, r = e.end.row - n; if (r !== 0) if (e.action == "remove") { var i = t.splice(n + 1, r); !t[n] && i[i.length - 1] && (t[n] = i.pop()), i.forEach(function (e) { e && this.removeLineWidget(e) }, this), this.$updateRows() } else { var s = new Array(r); t[n] && t[n].column != null && e.start.column > t[n].column && n++, s.unshift(n, 0), t.splice.apply(t, s), this.$updateRows() } }, this.$updateRows = function () { var e = this.session.lineWidgets; if (!e) return; var t = !0; e.forEach(function (e, n) { if (e) { t = !1, e.row = n; while (e.$oldWidget) e.$oldWidget.row = n, e = e.$oldWidget } }), t && (this.session.lineWidgets = null) }, this.$registerLineWidget = function (e) { this.session.lineWidgets || (this.session.lineWidgets = new Array(this.session.getLength())); var t = this.session.lineWidgets[e.row]; return t && (e.$oldWidget = t, t.el && t.el.parentNode && (t.el.parentNode.removeChild(t.el), t._inDocument = !1)), this.session.lineWidgets[e.row] = e, e }, this.addLineWidget = function (e) { this.$registerLineWidget(e), e.session = this.session; if (!this.editor) return e; var t = this.editor.renderer; e.html && !e.el && (e.el = r.createElement("div"), e.el.innerHTML = e.html), e.el && (r.addCssClass(e.el, "ace_lineWidgetContainer"), e.el.style.position = "absolute", e.el.style.zIndex = 5, t.container.appendChild(e.el), e._inDocument = !0, e.coverGutter || (e.el.style.zIndex = 3), e.pixelHeight == null && (e.pixelHeight = e.el.offsetHeight)), e.rowCount == null && (e.rowCount = e.pixelHeight / t.layerConfig.lineHeight); var n = this.session.getFoldAt(e.row, 0); e.$fold = n; if (n) { var i = this.session.lineWidgets; e.row == n.end.row && !i[n.start.row] ? i[n.start.row] = e : e.hidden = !0 } return this.session._emit("changeFold", { data: { start: { row: e.row } } }), this.$updateRows(), this.renderWidgets(null, t), this.onWidgetChanged(e), e }, this.removeLineWidget = function (e) { e._inDocument = !1, e.session = null, e.el && e.el.parentNode && e.el.parentNode.removeChild(e.el); if (e.editor && e.editor.destroy) try { e.editor.destroy() } catch (t) { } if (this.session.lineWidgets) { var n = this.session.lineWidgets[e.row]; if (n == e) this.session.lineWidgets[e.row] = e.$oldWidget, e.$oldWidget && this.onWidgetChanged(e.$oldWidget); else while (n) { if (n.$oldWidget == e) { n.$oldWidget = e.$oldWidget; break } n = n.$oldWidget } } this.session._emit("changeFold", { data: { start: { row: e.row } } }), this.$updateRows() }, this.getWidgetsAtRow = function (e) { var t = this.session.lineWidgets, n = t && t[e], r = []; while (n) r.push(n), n = n.$oldWidget; return r }, this.onWidgetChanged = function (e) { this.session._changedWidgets.push(e), this.editor && this.editor.renderer.updateFull() }, this.measureWidgets = function (e, t) { var n = this.session._changedWidgets, r = t.layerConfig; if (!n || !n.length) return; var i = Infinity; for (var s = 0; s < n.length; s++) { var o = n[s]; if (!o || !o.el) continue; if (o.session != this.session) continue; if (!o._inDocument) { if (this.session.lineWidgets[o.row] != o) continue; o._inDocument = !0, t.container.appendChild(o.el) } o.h = o.el.offsetHeight, o.fixedWidth || (o.w = o.el.offsetWidth, o.screenWidth = Math.ceil(o.w / r.characterWidth)); var u = o.h / r.lineHeight; o.coverLine && (u -= this.session.getRowLineCount(o.row), u < 0 && (u = 0)), o.rowCount != u && (o.rowCount = u, o.row < i && (i = o.row)) } i != Infinity && (this.session._emit("changeFold", { data: { start: { row: i } } }), this.session.lineWidgetWidth = null), this.session._changedWidgets = [] }, this.renderWidgets = function (e, t) { var n = t.layerConfig, r = this.session.lineWidgets; if (!r) return; var i = Math.min(this.firstRow, n.firstRow), s = Math.max(this.lastRow, n.lastRow, r.length); while (i > 0 && !r[i]) i--; this.firstRow = n.firstRow, this.lastRow = n.lastRow, t.$cursorLayer.config = n; for (var o = i; o <= s; o++) { var u = r[o]; if (!u || !u.el) continue; if (u.hidden) { u.el.style.top = -100 - (u.pixelHeight || 0) + "px"; continue } u._inDocument || (u._inDocument = !0, t.container.appendChild(u.el)); var a = t.$cursorLayer.getPixelPosition({ row: o, column: 0 }, !0).top; u.coverLine || (a += n.lineHeight * this.session.getRowLineCount(u.row)), u.el.style.top = a - n.offset + "px"; var f = u.coverGutter ? 0 : t.gutterWidth; u.fixedWidth || (f -= t.scrollLeft), u.el.style.left = f + "px", u.fullWidth && u.screenWidth && (u.el.style.minWidth = n.width + 2 * n.padding + "px"), u.fixedWidth ? u.el.style.right = t.scrollBar.getWidth() + "px" : u.el.style.right = "" } } }).call(i.prototype), t.LineWidgets = i }), define("ace/ext/error_marker", ["require", "exports", "module", "ace/line_widgets", "ace/lib/dom", "ace/range"], function (e, t, n) { "use strict"; function o(e, t, n) { var r = 0, i = e.length - 1; while (r <= i) { var s = r + i >> 1, o = n(t, e[s]); if (o > 0) r = s + 1; else { if (!(o < 0)) return s; i = s - 1 } } return -(r + 1) } function u(e, t, n) { var r = e.getAnnotations().sort(s.comparePoints); if (!r.length) return; var i = o(r, { row: t, column: -1 }, s.comparePoints); i < 0 && (i = -i - 1), i >= r.length ? i = n > 0 ? 0 : r.length - 1 : i === 0 && n < 0 && (i = r.length - 1); var u = r[i]; if (!u || !n) return; if (u.row === t) { do u = r[i += n]; while (u && u.row === t); if (!u) return r.slice() } var a = []; t = u.row; do a[n < 0 ? "unshift" : "push"](u), u = r[i += n]; while (u && u.row == t); return a.length && a } var r = e("../line_widgets").LineWidgets, i = e("../lib/dom"), s = e("../range").Range; t.showErrorMarker = function (e, t) { var n = e.session; n.widgetManager || (n.widgetManager = new r(n), n.widgetManager.attach(e)); var s = e.getCursorPosition(), o = s.row, a = n.widgetManager.getWidgetsAtRow(o).filter(function (e) { return e.type == "errorMarker" })[0]; a ? a.destroy() : o -= t; var f = u(n, o, t), l; if (f) { var c = f[0]; s.column = (c.pos && typeof c.column != "number" ? c.pos.sc : c.column) || 0, s.row = c.row, l = e.renderer.$gutterLayer.$annotations[s.row] } else { if (a) return; l = { text: ["Looks good!"], className: "ace_ok" } } e.session.unfold(s.row), e.selection.moveToPosition(s); var h = { row: s.row, fixedWidth: !0, coverGutter: !0, el: i.createElement("div"), type: "errorMarker" }, p = h.el.appendChild(i.createElement("div")), d = h.el.appendChild(i.createElement("div")); d.className = "error_widget_arrow " + l.className; var v = e.renderer.$cursorLayer.getPixelPosition(s).left; d.style.left = v + e.renderer.gutterWidth - 5 + "px", h.el.className = "error_widget_wrapper", p.className = "error_widget " + l.className, p.innerHTML = l.text.join("
"), p.appendChild(i.createElement("div")); var m = function (e, t, n) { if (t === 0 && (n === "esc" || n === "return")) return h.destroy(), { command: "null" } }; h.destroy = function () { if (e.$mouseHandler.isMousePressed) return; e.keyBinding.removeKeyboardHandler(m), n.widgetManager.removeLineWidget(h), e.off("changeSelection", h.destroy), e.off("changeSession", h.destroy), e.off("mouseup", h.destroy), e.off("change", h.destroy) }, e.keyBinding.addKeyboardHandler(m), e.on("changeSelection", h.destroy), e.on("changeSession", h.destroy), e.on("mouseup", h.destroy), e.on("change", h.destroy), e.session.widgetManager.addLineWidget(h), h.el.onmousedown = e.focus.bind(e), e.renderer.scrollCursorIntoView(null, .5, { bottom: h.el.offsetHeight }) }, i.importCssString(" .error_widget_wrapper { background: inherit; color: inherit; border:none } .error_widget { border-top: solid 2px; border-bottom: solid 2px; margin: 5px 0; padding: 10px 40px; white-space: pre-wrap; } .error_widget.ace_error, .error_widget_arrow.ace_error{ border-color: #ff5a5a } .error_widget.ace_warning, .error_widget_arrow.ace_warning{ border-color: #F1D817 } .error_widget.ace_info, .error_widget_arrow.ace_info{ border-color: #5a5a5a } .error_widget.ace_ok, .error_widget_arrow.ace_ok{ border-color: #5aaa5a } .error_widget_arrow { position: absolute; border: solid 5px; border-top-color: transparent!important; border-right-color: transparent!important; border-left-color: transparent!important; top: -5px; }", "") }), define("ace/ace", ["require", "exports", "module", "ace/lib/fixoldbrowsers", "ace/lib/dom", "ace/lib/event", "ace/range", "ace/editor", "ace/edit_session", "ace/undomanager", "ace/virtual_renderer", "ace/worker/worker_client", "ace/keyboard/hash_handler", "ace/placeholder", "ace/multi_select", "ace/mode/folding/fold_mode", "ace/theme/textmate", "ace/ext/error_marker", "ace/config"], function (e, t, n) { "use strict"; e("./lib/fixoldbrowsers"); var r = e("./lib/dom"), i = e("./lib/event"), s = e("./range").Range, o = e("./editor").Editor, u = e("./edit_session").EditSession, a = e("./undomanager").UndoManager, f = e("./virtual_renderer").VirtualRenderer; e("./worker/worker_client"), e("./keyboard/hash_handler"), e("./placeholder"), e("./multi_select"), e("./mode/folding/fold_mode"), e("./theme/textmate"), e("./ext/error_marker"), t.config = e("./config"), t.require = e, typeof define == "function" && (t.define = define), t.edit = function (e, n) { if (typeof e == "string") { var s = e; e = document.getElementById(s); if (!e) throw new Error("ace.edit can't find div #" + s) } if (e && e.env && e.env.editor instanceof o) return e.env.editor; var u = ""; if (e && /input|textarea/i.test(e.tagName)) { var a = e; u = a.value, e = r.createElement("pre"), a.parentNode.replaceChild(e, a) } else e && (u = e.textContent, e.innerHTML = ""); var l = t.createEditSession(u), c = new o(new f(e), l, n), h = { document: l, editor: c, onResize: c.resize.bind(c, null) }; return a && (h.textarea = a), i.addListener(window, "resize", h.onResize), c.on("destroy", function () { i.removeListener(window, "resize", h.onResize), h.editor.container.env = null }), c.container.env = c.env = h, c }, t.createEditSession = function (e, t) { var n = new u(e, t); return n.setUndoManager(new a), n }, t.Range = s, t.Editor = o, t.EditSession = u, t.UndoManager = a, t.VirtualRenderer = f, t.version = t.config.version }); (function () { - window.require(["ace/ace"], function (a) { - if (a) { - a.config.init(true); - a.define = window.define; - } - if (!window.ace) - window.ace = a; - for (var key in a) if (a.hasOwnProperty(key)) - window.ace[key] = a[key]; - window.ace["default"] = window.ace; - if (typeof module == "object" && typeof exports == "object" && module) { - module.exports = window.ace; - } - }); -})(); diff --git a/esphome/dashboard/static/js/vendor/ace/ext-searchbox.js b/esphome/dashboard/static/js/vendor/ace/ext-searchbox.js deleted file mode 100644 index cf28c3b012..0000000000 --- a/esphome/dashboard/static/js/vendor/ace/ext-searchbox.js +++ /dev/null @@ -1,7 +0,0 @@ -define("ace/ext/searchbox", ["require", "exports", "module", "ace/lib/dom", "ace/lib/lang", "ace/lib/event", "ace/keyboard/hash_handler", "ace/lib/keys"], function (e, t, n) { "use strict"; var r = e("../lib/dom"), i = e("../lib/lang"), s = e("../lib/event"), o = '.ace_search {background-color: #ddd;color: #666;border: 1px solid #cbcbcb;border-top: 0 none;overflow: hidden;margin: 0;padding: 4px 6px 0 4px;position: absolute;top: 0;z-index: 99;white-space: normal;}.ace_search.left {border-left: 0 none;border-radius: 0px 0px 5px 0px;left: 0;}.ace_search.right {border-radius: 0px 0px 0px 5px;border-right: 0 none;right: 0;}.ace_search_form, .ace_replace_form {margin: 0 20px 4px 0;overflow: hidden;line-height: 1.9;}.ace_replace_form {margin-right: 0;}.ace_search_form.ace_nomatch {outline: 1px solid red;}.ace_search_field {border-radius: 3px 0 0 3px;background-color: white;color: black;border: 1px solid #cbcbcb;border-right: 0 none;outline: 0;padding: 0;font-size: inherit;margin: 0;line-height: inherit;padding: 0 6px;min-width: 17em;vertical-align: top;min-height: 1.8em;box-sizing: content-box;}.ace_searchbtn {border: 1px solid #cbcbcb;line-height: inherit;display: inline-block;padding: 0 6px;background: #fff;border-right: 0 none;border-left: 1px solid #dcdcdc;cursor: pointer;margin: 0;position: relative;color: #666;}.ace_searchbtn:last-child {border-radius: 0 3px 3px 0;border-right: 1px solid #cbcbcb;}.ace_searchbtn:disabled {background: none;cursor: default;}.ace_searchbtn:hover {background-color: #eef1f6;}.ace_searchbtn.prev, .ace_searchbtn.next {padding: 0px 0.7em}.ace_searchbtn.prev:after, .ace_searchbtn.next:after {content: "";border: solid 2px #888;width: 0.5em;height: 0.5em;border-width: 2px 0 0 2px;display:inline-block;transform: rotate(-45deg);}.ace_searchbtn.next:after {border-width: 0 2px 2px 0 ;}.ace_searchbtn_close {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAcCAYAAABRVo5BAAAAZ0lEQVR42u2SUQrAMAhDvazn8OjZBilCkYVVxiis8H4CT0VrAJb4WHT3C5xU2a2IQZXJjiQIRMdkEoJ5Q2yMqpfDIo+XY4k6h+YXOyKqTIj5REaxloNAd0xiKmAtsTHqW8sR2W5f7gCu5nWFUpVjZwAAAABJRU5ErkJggg==) no-repeat 50% 0;border-radius: 50%;border: 0 none;color: #656565;cursor: pointer;font: 16px/16px Arial;padding: 0;height: 14px;width: 14px;top: 9px;right: 7px;position: absolute;}.ace_searchbtn_close:hover {background-color: #656565;background-position: 50% 100%;color: white;}.ace_button {margin-left: 2px;cursor: pointer;-webkit-user-select: none;-moz-user-select: none;-o-user-select: none;-ms-user-select: none;user-select: none;overflow: hidden;opacity: 0.7;border: 1px solid rgba(100,100,100,0.23);padding: 1px;box-sizing: border-box!important;color: black;}.ace_button:hover {background-color: #eee;opacity:1;}.ace_button:active {background-color: #ddd;}.ace_button.checked {border-color: #3399ff;opacity:1;}.ace_search_options{margin-bottom: 3px;text-align: right;-webkit-user-select: none;-moz-user-select: none;-o-user-select: none;-ms-user-select: none;user-select: none;clear: both;}.ace_search_counter {float: left;font-family: arial;padding: 0 8px;}', u = e("../keyboard/hash_handler").HashHandler, a = e("../lib/keys"), f = 999; r.importCssString(o, "ace_searchbox"); var l = function (e, t, n) { var i = r.createElement("div"); r.buildDom(["div", { "class": "ace_search right" }, ["span", { action: "hide", "class": "ace_searchbtn_close" }], ["div", { "class": "ace_search_form" }, ["input", { "class": "ace_search_field", placeholder: "Search for", spellcheck: "false" }], ["span", { action: "findPrev", "class": "ace_searchbtn prev" }, "\u200b"], ["span", { action: "findNext", "class": "ace_searchbtn next" }, "\u200b"], ["span", { action: "findAll", "class": "ace_searchbtn", title: "Alt-Enter" }, "All"]], ["div", { "class": "ace_replace_form" }, ["input", { "class": "ace_search_field", placeholder: "Replace with", spellcheck: "false" }], ["span", { action: "replaceAndFindNext", "class": "ace_searchbtn" }, "Replace"], ["span", { action: "replaceAll", "class": "ace_searchbtn" }, "All"]], ["div", { "class": "ace_search_options" }, ["span", { action: "toggleReplace", "class": "ace_button", title: "Toggle Replace mode", style: "float:left;margin-top:-2px;padding:0 5px;" }, "+"], ["span", { "class": "ace_search_counter" }], ["span", { action: "toggleRegexpMode", "class": "ace_button", title: "RegExp Search" }, ".*"], ["span", { action: "toggleCaseSensitive", "class": "ace_button", title: "CaseSensitive Search" }, "Aa"], ["span", { action: "toggleWholeWords", "class": "ace_button", title: "Whole Word Search" }, "\\b"], ["span", { action: "searchInSelection", "class": "ace_button", title: "Search In Selection" }, "S"]]], i), this.element = i.firstChild, this.setSession = this.setSession.bind(this), this.$init(), this.setEditor(e), r.importCssString(o, "ace_searchbox", e.container) }; (function () { this.setEditor = function (e) { e.searchBox = this, e.renderer.scroller.appendChild(this.element), this.editor = e }, this.setSession = function (e) { this.searchRange = null, this.$syncOptions(!0) }, this.$initElements = function (e) { this.searchBox = e.querySelector(".ace_search_form"), this.replaceBox = e.querySelector(".ace_replace_form"), this.searchOption = e.querySelector("[action=searchInSelection]"), this.replaceOption = e.querySelector("[action=toggleReplace]"), this.regExpOption = e.querySelector("[action=toggleRegexpMode]"), this.caseSensitiveOption = e.querySelector("[action=toggleCaseSensitive]"), this.wholeWordOption = e.querySelector("[action=toggleWholeWords]"), this.searchInput = this.searchBox.querySelector(".ace_search_field"), this.replaceInput = this.replaceBox.querySelector(".ace_search_field"), this.searchCounter = e.querySelector(".ace_search_counter") }, this.$init = function () { var e = this.element; this.$initElements(e); var t = this; s.addListener(e, "mousedown", function (e) { setTimeout(function () { t.activeInput.focus() }, 0), s.stopPropagation(e) }), s.addListener(e, "click", function (e) { var n = e.target || e.srcElement, r = n.getAttribute("action"); r && t[r] ? t[r]() : t.$searchBarKb.commands[r] && t.$searchBarKb.commands[r].exec(t), s.stopPropagation(e) }), s.addCommandKeyListener(e, function (e, n, r) { var i = a.keyCodeToString(r), o = t.$searchBarKb.findKeyCommand(n, i); o && o.exec && (o.exec(t), s.stopEvent(e)) }), this.$onChange = i.delayedCall(function () { t.find(!1, !1) }), s.addListener(this.searchInput, "input", function () { t.$onChange.schedule(20) }), s.addListener(this.searchInput, "focus", function () { t.activeInput = t.searchInput, t.searchInput.value && t.highlight() }), s.addListener(this.replaceInput, "focus", function () { t.activeInput = t.replaceInput, t.searchInput.value && t.highlight() }) }, this.$closeSearchBarKb = new u([{ bindKey: "Esc", name: "closeSearchBar", exec: function (e) { e.searchBox.hide() } }]), this.$searchBarKb = new u, this.$searchBarKb.bindKeys({ "Ctrl-f|Command-f": function (e) { var t = e.isReplace = !e.isReplace; e.replaceBox.style.display = t ? "" : "none", e.replaceOption.checked = !1, e.$syncOptions(), e.searchInput.focus() }, "Ctrl-H|Command-Option-F": function (e) { if (e.editor.getReadOnly()) return; e.replaceOption.checked = !0, e.$syncOptions(), e.replaceInput.focus() }, "Ctrl-G|Command-G": function (e) { e.findNext() }, "Ctrl-Shift-G|Command-Shift-G": function (e) { e.findPrev() }, esc: function (e) { setTimeout(function () { e.hide() }) }, Return: function (e) { e.activeInput == e.replaceInput && e.replace(), e.findNext() }, "Shift-Return": function (e) { e.activeInput == e.replaceInput && e.replace(), e.findPrev() }, "Alt-Return": function (e) { e.activeInput == e.replaceInput && e.replaceAll(), e.findAll() }, Tab: function (e) { (e.activeInput == e.replaceInput ? e.searchInput : e.replaceInput).focus() } }), this.$searchBarKb.addCommands([{ name: "toggleRegexpMode", bindKey: { win: "Alt-R|Alt-/", mac: "Ctrl-Alt-R|Ctrl-Alt-/" }, exec: function (e) { e.regExpOption.checked = !e.regExpOption.checked, e.$syncOptions() } }, { name: "toggleCaseSensitive", bindKey: { win: "Alt-C|Alt-I", mac: "Ctrl-Alt-R|Ctrl-Alt-I" }, exec: function (e) { e.caseSensitiveOption.checked = !e.caseSensitiveOption.checked, e.$syncOptions() } }, { name: "toggleWholeWords", bindKey: { win: "Alt-B|Alt-W", mac: "Ctrl-Alt-B|Ctrl-Alt-W" }, exec: function (e) { e.wholeWordOption.checked = !e.wholeWordOption.checked, e.$syncOptions() } }, { name: "toggleReplace", exec: function (e) { e.replaceOption.checked = !e.replaceOption.checked, e.$syncOptions() } }, { name: "searchInSelection", exec: function (e) { e.searchOption.checked = !e.searchRange, e.setSearchRange(e.searchOption.checked && e.editor.getSelectionRange()), e.$syncOptions() } }]), this.setSearchRange = function (e) { this.searchRange = e, e ? this.searchRangeMarker = this.editor.session.addMarker(e, "ace_active-line") : this.searchRangeMarker && (this.editor.session.removeMarker(this.searchRangeMarker), this.searchRangeMarker = null) }, this.$syncOptions = function (e) { r.setCssClass(this.replaceOption, "checked", this.searchRange), r.setCssClass(this.searchOption, "checked", this.searchOption.checked), this.replaceOption.textContent = this.replaceOption.checked ? "-" : "+", r.setCssClass(this.regExpOption, "checked", this.regExpOption.checked), r.setCssClass(this.wholeWordOption, "checked", this.wholeWordOption.checked), r.setCssClass(this.caseSensitiveOption, "checked", this.caseSensitiveOption.checked); var t = this.editor.getReadOnly(); this.replaceOption.style.display = t ? "none" : "", this.replaceBox.style.display = this.replaceOption.checked && !t ? "" : "none", this.find(!1, !1, e) }, this.highlight = function (e) { this.editor.session.highlight(e || this.editor.$search.$options.re), this.editor.renderer.updateBackMarkers() }, this.find = function (e, t, n) { var i = this.editor.find(this.searchInput.value, { skipCurrent: e, backwards: t, wrap: !0, regExp: this.regExpOption.checked, caseSensitive: this.caseSensitiveOption.checked, wholeWord: this.wholeWordOption.checked, preventScroll: n, range: this.searchRange }), s = !i && this.searchInput.value; r.setCssClass(this.searchBox, "ace_nomatch", s), this.editor._emit("findSearchBox", { match: !s }), this.highlight(), this.updateCounter() }, this.updateCounter = function () { var e = this.editor, t = e.$search.$options.re, n = 0, r = 0; if (t) { var i = this.searchRange ? e.session.getTextRange(this.searchRange) : e.getValue(), s = e.session.doc.positionToIndex(e.selection.anchor); this.searchRange && (s -= e.session.doc.positionToIndex(this.searchRange.start)); var o = t.lastIndex = 0, u; while (u = t.exec(i)) { n++, o = u.index, o <= s && r++; if (n > f) break; if (!u[0]) { t.lastIndex = o += 1; if (o >= i.length) break } } } this.searchCounter.textContent = r + " of " + (n > f ? f + "+" : n) }, this.findNext = function () { this.find(!0, !1) }, this.findPrev = function () { this.find(!0, !0) }, this.findAll = function () { var e = this.editor.findAll(this.searchInput.value, { regExp: this.regExpOption.checked, caseSensitive: this.caseSensitiveOption.checked, wholeWord: this.wholeWordOption.checked }), t = !e && this.searchInput.value; r.setCssClass(this.searchBox, "ace_nomatch", t), this.editor._emit("findSearchBox", { match: !t }), this.highlight(), this.hide() }, this.replace = function () { this.editor.getReadOnly() || this.editor.replace(this.replaceInput.value) }, this.replaceAndFindNext = function () { this.editor.getReadOnly() || (this.editor.replace(this.replaceInput.value), this.findNext()) }, this.replaceAll = function () { this.editor.getReadOnly() || this.editor.replaceAll(this.replaceInput.value) }, this.hide = function () { this.active = !1, this.setSearchRange(null), this.editor.off("changeSession", this.setSession), this.element.style.display = "none", this.editor.keyBinding.removeKeyboardHandler(this.$closeSearchBarKb), this.editor.focus() }, this.show = function (e, t) { this.active = !0, this.editor.on("changeSession", this.setSession), this.element.style.display = "", this.replaceOption.checked = t, e && (this.searchInput.value = e), this.searchInput.focus(), this.searchInput.select(), this.editor.keyBinding.addKeyboardHandler(this.$closeSearchBarKb), this.$syncOptions(!0) }, this.isFocused = function () { var e = document.activeElement; return e == this.searchInput || e == this.replaceInput } }).call(l.prototype), t.SearchBox = l, t.Search = function (e, t) { var n = e.searchBox || new l(e); n.show(e.session.getTextRange(), t) } }); (function () { - window.require(["ace/ext/searchbox"], function (m) { - if (typeof module == "object" && typeof exports == "object" && module) { - module.exports = m; - } - }); -})(); diff --git a/esphome/dashboard/static/js/vendor/ace/mode-yaml.js b/esphome/dashboard/static/js/vendor/ace/mode-yaml.js deleted file mode 100644 index 81cf343aa5..0000000000 --- a/esphome/dashboard/static/js/vendor/ace/mode-yaml.js +++ /dev/null @@ -1,7 +0,0 @@ -define("ace/mode/yaml_highlight_rules", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text_highlight_rules"], function (e, t, n) { "use strict"; var r = e("../lib/oop"), i = e("./text_highlight_rules").TextHighlightRules, s = function () { this.$rules = { start: [{ token: "comment", regex: "#.*$" }, { token: "list.markup", regex: /^(?:-{3}|\.{3})\s*(?=#|$)/ }, { token: "list.markup", regex: /^\s*[\-?](?:$|\s)/ }, { token: "constant", regex: "!![\\w//]+" }, { token: "constant.language", regex: "[&\\*][a-zA-Z0-9-_]+" }, { token: ["meta.tag", "keyword"], regex: /^(\s*\w.*?)(:(?=\s|$))/ }, { token: ["meta.tag", "keyword"], regex: /(\w+?)(\s*:(?=\s|$))/ }, { token: "keyword.operator", regex: "<<\\w*:\\w*" }, { token: "keyword.operator", regex: "-\\s*(?=[{])" }, { token: "string", regex: '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' }, { token: "string", regex: /[|>][-+\d]*(?:$|\s+(?:$|#))/, onMatch: function (e, t, n, r) { r = r.replace(/ #.*/, ""); var i = /^ *((:\s*)?-(\s*[^|>])?)?/.exec(r)[0].replace(/\S\s*$/, "").length, s = parseInt(/\d+[\s+-]*$/.exec(r)); return s ? (i += s - 1, this.next = "mlString") : this.next = "mlStringPre", n.length ? (n[0] = this.next, n[1] = i) : (n.push(this.next), n.push(i)), this.token }, next: "mlString" }, { token: "string", regex: "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']" }, { token: "constant.numeric", regex: /(\b|[+\-\.])[\d_]+(?:(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)(?=[^\d-\w]|$)/ }, { token: "constant.numeric", regex: /[+\-]?\.inf\b|NaN\b|0x[\dA-Fa-f_]+|0b[10_]+/ }, { token: "constant.language.boolean", regex: "\\b(?:true|false|TRUE|FALSE|True|False|yes|no)\\b" }, { token: "paren.lparen", regex: "[[({]" }, { token: "paren.rparen", regex: "[\\])}]" }, { token: "text", regex: /[^\s,:\[\]\{\}]+/ }], mlStringPre: [{ token: "indent", regex: /^ *$/ }, { token: "indent", regex: /^ */, onMatch: function (e, t, n) { var r = n[1]; return r >= e.length ? (this.next = "start", n.shift(), n.shift()) : (n[1] = e.length - 1, this.next = n[0] = "mlString"), this.token }, next: "mlString" }, { defaultToken: "string" }], mlString: [{ token: "indent", regex: /^ *$/ }, { token: "indent", regex: /^ */, onMatch: function (e, t, n) { var r = n[1]; return r >= e.length ? (this.next = "start", n.splice(0)) : this.next = "mlString", this.token }, next: "mlString" }, { token: "string", regex: ".+" }] }, this.normalizeRules() }; r.inherits(s, i), t.YamlHighlightRules = s }), define("ace/mode/matching_brace_outdent", ["require", "exports", "module", "ace/range"], function (e, t, n) { "use strict"; var r = e("../range").Range, i = function () { }; (function () { this.checkOutdent = function (e, t) { return /^\s+$/.test(e) ? /^\s*\}/.test(t) : !1 }, this.autoOutdent = function (e, t) { var n = e.getLine(t), i = n.match(/^(\s*\})/); if (!i) return 0; var s = i[1].length, o = e.findMatchingBracket({ row: t, column: s }); if (!o || o.row == t) return 0; var u = this.$getIndent(e.getLine(o.row)); e.replace(new r(t, 0, t, s - 1), u) }, this.$getIndent = function (e) { return e.match(/^\s*/)[0] } }).call(i.prototype), t.MatchingBraceOutdent = i }), define("ace/mode/folding/coffee", ["require", "exports", "module", "ace/lib/oop", "ace/mode/folding/fold_mode", "ace/range"], function (e, t, n) { "use strict"; var r = e("../../lib/oop"), i = e("./fold_mode").FoldMode, s = e("../../range").Range, o = t.FoldMode = function () { }; r.inherits(o, i), function () { this.getFoldWidgetRange = function (e, t, n) { var r = this.indentationBlock(e, n); if (r) return r; var i = /\S/, o = e.getLine(n), u = o.search(i); if (u == -1 || o[u] != "#") return; var a = o.length, f = e.getLength(), l = n, c = n; while (++n < f) { o = e.getLine(n); var h = o.search(i); if (h == -1) continue; if (o[h] != "#") break; c = n } if (c > l) { var p = e.getLine(c).length; return new s(l, a, c, p) } }, this.getFoldWidget = function (e, t, n) { var r = e.getLine(n), i = r.search(/\S/), s = e.getLine(n + 1), o = e.getLine(n - 1), u = o.search(/\S/), a = s.search(/\S/); if (i == -1) return e.foldWidgets[n - 1] = u != -1 && u < a ? "start" : "", ""; if (u == -1) { if (i == a && r[i] == "#" && s[i] == "#") return e.foldWidgets[n - 1] = "", e.foldWidgets[n + 1] = "", "start" } else if (u == i && r[i] == "#" && o[i] == "#" && e.getLine(n - 2).search(/\S/) == -1) return e.foldWidgets[n - 1] = "start", e.foldWidgets[n + 1] = "", ""; return u != -1 && u < i ? e.foldWidgets[n - 1] = "start" : e.foldWidgets[n - 1] = "", i < a ? "start" : "" } }.call(o.prototype) }), define("ace/mode/yaml", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text", "ace/mode/yaml_highlight_rules", "ace/mode/matching_brace_outdent", "ace/mode/folding/coffee"], function (e, t, n) { "use strict"; var r = e("../lib/oop"), i = e("./text").Mode, s = e("./yaml_highlight_rules").YamlHighlightRules, o = e("./matching_brace_outdent").MatchingBraceOutdent, u = e("./folding/coffee").FoldMode, a = function () { this.HighlightRules = s, this.$outdent = new o, this.foldingRules = new u, this.$behaviour = this.$defaultBehaviour }; r.inherits(a, i), function () { this.lineCommentStart = ["#"], this.getNextLineIndent = function (e, t, n) { var r = this.$getIndent(t); if (e == "start") { var i = t.match(/^.*[\{\(\[]\s*$/); i && (r += n) } return r }, this.checkOutdent = function (e, t, n) { return this.$outdent.checkOutdent(t, n) }, this.autoOutdent = function (e, t, n) { this.$outdent.autoOutdent(t, n) }, this.$id = "ace/mode/yaml" }.call(a.prototype), t.Mode = a }); (function () { - window.require(["ace/mode/yaml"], function (m) { - if (typeof module == "object" && typeof exports == "object" && module) { - module.exports = m; - } - }); -})(); diff --git a/esphome/dashboard/static/js/vendor/ace/theme-dreamweaver.js b/esphome/dashboard/static/js/vendor/ace/theme-dreamweaver.js deleted file mode 100644 index 2335f6d773..0000000000 --- a/esphome/dashboard/static/js/vendor/ace/theme-dreamweaver.js +++ /dev/null @@ -1,7 +0,0 @@ -define("ace/theme/dreamweaver", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { t.isDark = !1, t.cssClass = "ace-dreamweaver", t.cssText = '.ace-dreamweaver .ace_gutter {background: #e8e8e8;color: #333;}.ace-dreamweaver .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-dreamweaver {background-color: #FFFFFF;color: black;}.ace-dreamweaver .ace_fold {background-color: #757AD8;}.ace-dreamweaver .ace_cursor {color: black;}.ace-dreamweaver .ace_invisible {color: rgb(191, 191, 191);}.ace-dreamweaver .ace_storage,.ace-dreamweaver .ace_keyword {color: blue;}.ace-dreamweaver .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-dreamweaver .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-dreamweaver .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-dreamweaver .ace_invalid {background-color: rgb(153, 0, 0);color: white;}.ace-dreamweaver .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-dreamweaver .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-dreamweaver .ace_support.ace_type,.ace-dreamweaver .ace_support.ace_class {color: #009;}.ace-dreamweaver .ace_support.ace_php_tag {color: #f00;}.ace-dreamweaver .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-dreamweaver .ace_string {color: #00F;}.ace-dreamweaver .ace_comment {color: rgb(76, 136, 107);}.ace-dreamweaver .ace_comment.ace_doc {color: rgb(0, 102, 255);}.ace-dreamweaver .ace_comment.ace_doc.ace_tag {color: rgb(128, 159, 191);}.ace-dreamweaver .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-dreamweaver .ace_variable {color: #06F}.ace-dreamweaver .ace_xml-pe {color: rgb(104, 104, 91);}.ace-dreamweaver .ace_entity.ace_name.ace_function {color: #00F;}.ace-dreamweaver .ace_heading {color: rgb(12, 7, 255);}.ace-dreamweaver .ace_list {color:rgb(185, 6, 144);}.ace-dreamweaver .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-dreamweaver .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-dreamweaver .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-dreamweaver .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-dreamweaver .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-dreamweaver .ace_gutter-active-line {background-color : #DCDCDC;}.ace-dreamweaver .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-dreamweaver .ace_meta.ace_tag {color:#009;}.ace-dreamweaver .ace_meta.ace_tag.ace_anchor {color:#060;}.ace-dreamweaver .ace_meta.ace_tag.ace_form {color:#F90;}.ace-dreamweaver .ace_meta.ace_tag.ace_image {color:#909;}.ace-dreamweaver .ace_meta.ace_tag.ace_script {color:#900;}.ace-dreamweaver .ace_meta.ace_tag.ace_style {color:#909;}.ace-dreamweaver .ace_meta.ace_tag.ace_table {color:#099;}.ace-dreamweaver .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-dreamweaver .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}'; var r = e("../lib/dom"); r.importCssString(t.cssText, t.cssClass) }); (function () { - window.require(["ace/theme/dreamweaver"], function (m) { - if (typeof module == "object" && typeof exports == "object" && module) { - module.exports = m; - } - }); -})(); diff --git a/esphome/dashboard/static/js/vendor/jquery-ui/jquery-ui.min.js b/esphome/dashboard/static/js/vendor/jquery-ui/jquery-ui.min.js deleted file mode 100644 index 862a649869..0000000000 --- a/esphome/dashboard/static/js/vendor/jquery-ui/jquery-ui.min.js +++ /dev/null @@ -1,13 +0,0 @@ -/*! jQuery UI - v1.12.1 - 2016-09-14 -* http://jqueryui.com -* Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-1-7.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js -* Copyright jQuery Foundation and other contributors; Licensed MIT */ - -(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){function e(t){for(var e=t.css("visibility");"inherit"===e;)t=t.parent(),e=t.css("visibility");return"hidden"!==e}function i(t){for(var e,i;t.length&&t[0]!==document;){if(e=t.css("position"),("absolute"===e||"relative"===e||"fixed"===e)&&(i=parseInt(t.css("zIndex"),10),!isNaN(i)&&0!==i))return i;t=t.parent()}return 0}function s(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},t.extend(this._defaults,this.regional[""]),this.regional.en=t.extend(!0,{},this.regional[""]),this.regional["en-US"]=t.extend(!0,{},this.regional.en),this.dpDiv=n(t("

"))}function n(e){var i="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return e.on("mouseout",i,function(){t(this).removeClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).removeClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).removeClass("ui-datepicker-next-hover")}).on("mouseover",i,o)}function o(){t.datepicker._isDisabledDatepicker(m.inline?m.dpDiv.parent()[0]:m.input[0])||(t(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),t(this).addClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).addClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).addClass("ui-datepicker-next-hover"))}function a(e,i){t.extend(e,i);for(var s in i)null==i[s]&&(e[s]=i[s]);return e}function r(t){return function(){var e=this.element.val();t.apply(this,arguments),this._refresh(),e!==this.element.val()&&this._trigger("change")}}t.ui=t.ui||{},t.ui.version="1.12.1";var h=0,l=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,n,o;for(o=0;null!=(n=i[o]);o++)try{s=t._data(n,"events"),s&&s.remove&&t(n).triggerHandler("remove")}catch(a){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var n,o,a,r={},h=e.split(".")[0];e=e.split(".")[1];var l=h+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][l.toLowerCase()]=function(e){return!!t.data(e,l)},t[h]=t[h]||{},n=t[h][e],o=t[h][e]=function(t,e){return this._createWidget?(arguments.length&&this._createWidget(t,e),void 0):new o(t,e)},t.extend(o,n,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),a=new i,a.options=t.widget.extend({},a.options),t.each(s,function(e,s){return t.isFunction(s)?(r[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function n(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,o=this._superApply;return this._super=t,this._superApply=n,e=s.apply(this,arguments),this._super=i,this._superApply=o,e}}(),void 0):(r[e]=s,void 0)}),o.prototype=t.widget.extend(a,{widgetEventPrefix:n?a.widgetEventPrefix||e:e},r,{constructor:o,namespace:h,widgetName:e,widgetFullName:l}),n?(t.each(n._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete n._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o),o},t.widget.extend=function(e){for(var i,s,n=l.call(arguments,1),o=0,a=n.length;a>o;o++)for(i in n[o])s=n[o][i],n[o].hasOwnProperty(i)&&void 0!==s&&(e[i]=t.isPlainObject(s)?t.isPlainObject(e[i])?t.widget.extend({},e[i],s):t.widget.extend({},s):s);return e},t.widget.bridge=function(e,i){var s=i.prototype.widgetFullName||e;t.fn[e]=function(n){var o="string"==typeof n,a=l.call(arguments,1),r=this;return o?this.length||"instance"!==n?this.each(function(){var i,o=t.data(this,s);return"instance"===n?(r=o,!1):o?t.isFunction(o[n])&&"_"!==n.charAt(0)?(i=o[n].apply(o,a),i!==o&&void 0!==i?(r=i&&i.jquery?r.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+n+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; "+"attempted to call method '"+n+"'")}):r=void 0:(a.length&&(n=t.widget.extend.apply(null,[n].concat(a))),this.each(function(){var e=t.data(this,s);e?(e.option(n||{}),e._init&&e._init()):t.data(this,s,new i(n,this))})),r}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{classes:{},disabled:!1,create:null},_createWidget:function(e,i){i=t(i||this.defaultElement||this)[0],this.element=t(i),this.uuid=h++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},i!==this&&(t.data(i,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===i&&this.destroy()}}),this.document=t(i.style?i.ownerDocument:i.document||i),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},s=e.split("."),e=s.shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){var a,r;for(r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof t||null===t,o={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(s,a){function r(){return e||o.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+o.eventNamespace,c=h[2];c?n.on(l,c,r):i.on(l,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,function(){function e(t,e,i){return[parseFloat(t[0])*(u.test(t[0])?e/100:1),parseFloat(t[1])*(u.test(t[1])?i/100:1)]}function i(e,i){return parseInt(t.css(e,i),10)||0}function s(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}var n,o=Math.max,a=Math.abs,r=/left|center|right/,h=/top|center|bottom/,l=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,u=/%$/,d=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==n)return n;var e,i,s=t("
"),o=s.children()[0];return t("body").append(s),e=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,e===i&&(i=s[0].clientWidth),s.remove(),n=e-i},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.widthi?"left":e>0?"right":"center",vertical:0>r?"top":s>0?"bottom":"middle"};l>p&&p>a(e+i)&&(u.horizontal="center"),c>f&&f>a(s+r)&&(u.vertical="middle"),u.important=o(a(e),a(i))>o(a(s),a(r))?"horizontal":"vertical",n.using.call(this,t,u)}),h.offset(t.extend(D,{using:r}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,h=n-r,l=r+e.collisionWidth-a-n;e.collisionWidth>a?h>0&&0>=l?(i=t.left+h+e.collisionWidth-a-n,t.left+=h-i):t.left=l>0&&0>=h?n:h>l?n+a-e.collisionWidth:n:h>0?t.left+=h:l>0?t.left-=l:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,h=n-r,l=r+e.collisionHeight-a-n;e.collisionHeight>a?h>0&&0>=l?(i=t.top+h+e.collisionHeight-a-n,t.top+=h-i):t.top=l>0&&0>=h?n:h>l?n+a-e.collisionHeight:n:h>0?t.top+=h:l>0?t.top-=l:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,o=n.offset.left+n.scrollLeft,r=n.width,h=n.isWindow?n.scrollLeft:n.offset.left,l=t.left-e.collisionPosition.marginLeft,c=l-h,u=l+e.collisionWidth-r-h,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-r-o,(0>i||a(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-h,(s>0||u>a(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,o=n.offset.top+n.scrollTop,r=n.height,h=n.isWindow?n.scrollTop:n.offset.top,l=t.top-e.collisionPosition.marginTop,c=l-h,u=l+e.collisionHeight-r-h,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-r-o,(0>s||a(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-h,(i>0||u>a(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}}}(),t.ui.position,t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,s){return!!t.data(e,s[3])}}),t.fn.extend({disableSelection:function(){var t="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.on(t+".ui-disableSelection",function(t){t.preventDefault()})}}(),enableSelection:function(){return this.off(".ui-disableSelection")}});var c="ui-effects-",u="ui-effects-style",d="ui-effects-animated",p=t;t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:t>s.max?s.max:t)}function s(i){var s=l(),n=s._rgba=[];return i=i.toLowerCase(),f(h,function(t,o){var a,r=o.re.exec(i),h=r&&o.parse(r),l=o.space||"rgba";return h?(a=s[l](h),s[c[l].cache]=a[c[l].cache],n=s._rgba=a._rgba,!1):e}),n.length?("0,0,0,0"===n.join()&&t.extend(n,o.transparent),s):o[i]}function n(t,e,i){return i=(i+1)%1,1>6*i?t+6*(e-t)*i:1>2*i?e:2>3*i?t+6*(e-t)*(2/3-i):t}var o,a="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,h=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],l=t.Color=function(e,i,s,n){return new t.Color.fn.parse(e,i,s,n)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},d=l.support={},p=t("

")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(c,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),l.fn=t.extend(l.prototype,{parse:function(n,a,r,h){if(n===e)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=t(n).css(a),a=e);var u=this,d=t.type(n),p=this._rgba=[];return a!==e&&(n=[n,a,r,h],d="array"),"string"===d?this.parse(s(n)||o._default):"array"===d?(f(c.rgba.props,function(t,e){p[e.idx]=i(n[e.idx],e)}),this):"object"===d?(n instanceof l?f(c,function(t,e){n[e.cache]&&(u[e.cache]=n[e.cache].slice())}):f(c,function(e,s){var o=s.cache;f(s.props,function(t,e){if(!u[o]&&s.to){if("alpha"===t||null==n[t])return;u[o]=s.to(u._rgba)}u[o][e.idx]=i(n[t],e,!0)}),u[o]&&0>t.inArray(null,u[o].slice(0,3))&&(u[o][3]=1,s.from&&(u._rgba=s.from(u[o])))}),this):e},is:function(t){var i=l(t),s=!0,n=this;return f(c,function(t,o){var a,r=i[o.cache];return r&&(a=n[o.cache]||o.to&&o.to(n._rgba)||[],f(o.props,function(t,i){return null!=r[i.idx]?s=r[i.idx]===a[i.idx]:e})),s}),s},_space:function(){var t=[],e=this;return f(c,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=l(t),n=s._space(),o=c[n],a=0===this.alpha()?l("transparent"):this,r=a[o.cache]||o.to(a._rgba),h=r.slice();return s=s[o.cache],f(o.props,function(t,n){var o=n.idx,a=r[o],l=s[o],c=u[n.type]||{};null!==l&&(null===a?h[o]=l:(c.mod&&(l-a>c.mod/2?a+=c.mod:a-l>c.mod/2&&(a-=c.mod)),h[o]=i((l-a)*e+a,n)))}),this[n](h)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=l(e)._rgba;return l(t.map(i,function(t,e){return(1-s)*n[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&3>e&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),l.fn.parse.prototype=l.fn,c.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,n=t[1]/255,o=t[2]/255,a=t[3],r=Math.max(s,n,o),h=Math.min(s,n,o),l=r-h,c=r+h,u=.5*c;return e=h===r?0:s===r?60*(n-o)/l+360:n===r?60*(o-s)/l+120:60*(s-n)/l+240,i=0===l?0:.5>=u?l/c:l/(2-c),[Math.round(e)%360,i,u,null==a?1:a]},c.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],o=t[3],a=.5>=s?s*(1+i):s+i-s*i,r=2*s-a;return[Math.round(255*n(r,a,e+1/3)),Math.round(255*n(r,a,e)),Math.round(255*n(r,a,e-1/3)),o]},f(c,function(s,n){var o=n.props,a=n.cache,h=n.to,c=n.from;l.fn[s]=function(s){if(h&&!this[a]&&(this[a]=h(this._rgba)),s===e)return this[a].slice();var n,r=t.type(s),u="array"===r||"object"===r?s:arguments,d=this[a].slice();return f(o,function(t,e){var s=u["object"===r?t:e.idx];null==s&&(s=d[e.idx]),d[e.idx]=i(s,e)}),c?(n=l(c(d)),n[a]=d,n):l(d)},f(o,function(e,i){l.fn[e]||(l.fn[e]=function(n){var o,a=t.type(n),h="alpha"===e?this._hsla?"hsla":"rgba":s,l=this[h](),c=l[i.idx];return"undefined"===a?c:("function"===a&&(n=n.call(this,c),a=t.type(n)),null==n&&i.empty?this:("string"===a&&(o=r.exec(n),o&&(n=c+parseFloat(o[2])*("+"===o[1]?1:-1))),l[i.idx]=n,this[h](l)))})})}),l.hook=function(e){var i=e.split(" ");f(i,function(e,i){t.cssHooks[i]={set:function(e,n){var o,a,r="";if("transparent"!==n&&("string"!==t.type(n)||(o=s(n)))){if(n=l(o||n),!d.rgba&&1!==n._rgba[3]){for(a="backgroundColor"===i?e.parentNode:e;(""===r||"transparent"===r)&&a&&a.style;)try{r=t.css(a,"backgroundColor"),a=a.parentNode}catch(h){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{e.style[i]=n}catch(h){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=l(e.elem,i),e.end=l(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},l.hook(a),t.cssHooks.borderColor={expand:function(t){var e={};return f(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},o=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(p),function(){function e(e){var i,s,n=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,o={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(o[t.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(o[i]=n[i]);return o}function i(e,i){var s,o,a={};for(s in i)o=i[s],e[s]!==o&&(n[s]||(t.fx.step[s]||!isNaN(parseFloat(o)))&&(a[s]=o));return a}var s=["add","remove","toggle"],n={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(p.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(n,o,a,r){var h=t.speed(o,a,r);return this.queue(function(){var o,a=t(this),r=a.attr("class")||"",l=h.children?a.find("*").addBack():a;l=l.map(function(){var i=t(this);return{el:i,start:e(this)}}),o=function(){t.each(s,function(t,e){n[e]&&a[e+"Class"](n[e])})},o(),l=l.map(function(){return this.end=e(this.el[0]),this.diff=i(this.start,this.end),this}),a.attr("class",r),l=l.map(function(){var e=this,i=t.Deferred(),s=t.extend({},h,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,l.get()).done(function(){o(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),h.complete.call(a[0])})})},t.fn.extend({addClass:function(e){return function(i,s,n,o){return s?t.effects.animateClass.call(this,{add:i},s,n,o):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,n,o){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,n,o):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(e){return function(i,s,n,o,a){return"boolean"==typeof s||void 0===s?n?t.effects.animateClass.call(this,s?{add:i}:{remove:i},n,o,a):e.apply(this,arguments):t.effects.animateClass.call(this,{toggle:i},s,n,o)}}(t.fn.toggleClass),switchClass:function(e,i,s,n,o){return t.effects.animateClass.call(this,{add:i,remove:e},s,n,o)}})}(),function(){function e(e,i,s,n){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(n=s,s=i,i={}),t.isFunction(s)&&(n=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=n||i.complete,e}function i(e){return!e||"number"==typeof e||t.fx.speeds[e]?!0:"string"!=typeof e||t.effects.effect[e]?t.isFunction(e)?!0:"object"!=typeof e||e.effect?!1:!0:!0}function s(t,e){var i=e.outerWidth(),s=e.outerHeight(),n=/^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/,o=n.exec(t)||["",0,i,s,0];return{top:parseFloat(o[1])||0,right:"auto"===o[2]?i:parseFloat(o[2]),bottom:"auto"===o[3]?s:parseFloat(o[3]),left:parseFloat(o[4])||0}}t.expr&&t.expr.filters&&t.expr.filters.animated&&(t.expr.filters.animated=function(e){return function(i){return!!t(i).data(d)||e(i)}}(t.expr.filters.animated)),t.uiBackCompat!==!1&&t.extend(t.effects,{save:function(t,e){for(var i=0,s=e.length;s>i;i++)null!==e[i]&&t.data(c+e[i],t[0].style[e[i]])},restore:function(t,e){for(var i,s=0,n=e.length;n>s;s++)null!==e[s]&&(i=t.data(c+e[s]),t.css(e[s],i))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),"float":e.css("float")},s=t("

").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},o=document.activeElement;try{o.id}catch(a){o=document.body}return e.wrap(s),(e[0]===o||t.contains(e[0],o))&&t(o).trigger("focus"),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).trigger("focus")),e}}),t.extend(t.effects,{version:"1.12.1",define:function(e,i,s){return s||(s=i,i="effect"),t.effects.effect[e]=s,t.effects.effect[e].mode=i,s},scaledDimensions:function(t,e,i){if(0===e)return{height:0,width:0,outerHeight:0,outerWidth:0};var s="horizontal"!==i?(e||100)/100:1,n="vertical"!==i?(e||100)/100:1;return{height:t.height()*n,width:t.width()*s,outerHeight:t.outerHeight()*n,outerWidth:t.outerWidth()*s}},clipToBox:function(t){return{width:t.clip.right-t.clip.left,height:t.clip.bottom-t.clip.top,left:t.clip.left,top:t.clip.top}},unshift:function(t,e,i){var s=t.queue();e>1&&s.splice.apply(s,[1,0].concat(s.splice(e,i))),t.dequeue()},saveStyle:function(t){t.data(u,t[0].style.cssText)},restoreStyle:function(t){t[0].style.cssText=t.data(u)||"",t.removeData(u)},mode:function(t,e){var i=t.is(":hidden");return"toggle"===e&&(e=i?"show":"hide"),(i?"hide"===e:"show"===e)&&(e="none"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createPlaceholder:function(e){var i,s=e.css("position"),n=e.position();return e.css({marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()),/^(static|relative)/.test(s)&&(s="absolute",i=t("<"+e[0].nodeName+">").insertAfter(e).css({display:/^(inline|ruby)/.test(e.css("display"))?"inline-block":"block",visibility:"hidden",marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight"),"float":e.css("float")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).addClass("ui-effects-placeholder"),e.data(c+"placeholder",i)),e.css({position:s,left:n.left,top:n.top}),i},removePlaceholder:function(t){var e=c+"placeholder",i=t.data(e);i&&(i.remove(),t.removeData(e))},cleanUp:function(e){t.effects.restoreStyle(e),t.effects.removePlaceholder(e)},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var o=e.cssUnit(i);o[0]>0&&(n[i]=o[0]*s+o[1])}),n}}),t.fn.extend({effect:function(){function i(e){function i(){r.removeData(d),t.effects.cleanUp(r),"hide"===s.mode&&r.hide(),a()}function a(){t.isFunction(h)&&h.call(r[0]),t.isFunction(e)&&e()}var r=t(this);s.mode=c.shift(),t.uiBackCompat===!1||o?"none"===s.mode?(r[l](),a()):n.call(r[0],s,i):(r.is(":hidden")?"hide"===l:"show"===l)?(r[l](),a()):n.call(r[0],s,a)}var s=e.apply(this,arguments),n=t.effects.effect[s.effect],o=n.mode,a=s.queue,r=a||"fx",h=s.complete,l=s.mode,c=[],u=function(e){var i=t(this),s=t.effects.mode(i,l)||o;i.data(d,!0),c.push(s),o&&("show"===s||s===o&&"hide"===s)&&i.show(),o&&"none"===s||t.effects.saveStyle(i),t.isFunction(e)&&e()};return t.fx.off||!n?l?this[l](s.duration,h):this.each(function(){h&&h.call(this)}):a===!1?this.each(u).each(i):this.queue(r,u).queue(r,i)},show:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="show",this.effect.call(this,n) -}}(t.fn.show),hide:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="hide",this.effect.call(this,n)}}(t.fn.hide),toggle:function(t){return function(s){if(i(s)||"boolean"==typeof s)return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s},cssClip:function(t){return t?this.css("clip","rect("+t.top+"px "+t.right+"px "+t.bottom+"px "+t.left+"px)"):s(this.css("clip"),this)},transfer:function(e,i){var s=t(this),n=t(e.to),o="fixed"===n.css("position"),a=t("body"),r=o?a.scrollTop():0,h=o?a.scrollLeft():0,l=n.offset(),c={top:l.top-r,left:l.left-h,height:n.innerHeight(),width:n.innerWidth()},u=s.offset(),d=t("
").appendTo("body").addClass(e.className).css({top:u.top-r,left:u.left-h,height:s.innerHeight(),width:s.innerWidth(),position:o?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){d.remove(),t.isFunction(i)&&i()})}}),t.fx.step.clip=function(e){e.clipInit||(e.start=t(e.elem).cssClip(),"string"==typeof e.end&&(e.end=s(e.end,e.elem)),e.clipInit=!0),t(e.elem).cssClip({top:e.pos*(e.end.top-e.start.top)+e.start.top,right:e.pos*(e.end.right-e.start.right)+e.start.right,bottom:e.pos*(e.end.bottom-e.start.bottom)+e.start.bottom,left:e.pos*(e.end.left-e.start.left)+e.start.left})}}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}();var f=t.effects;t.effects.define("blind","hide",function(e,i){var s={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},n=t(this),o=e.direction||"up",a=n.cssClip(),r={clip:t.extend({},a)},h=t.effects.createPlaceholder(n);r.clip[s[o][0]]=r.clip[s[o][1]],"show"===e.mode&&(n.cssClip(r.clip),h&&h.css(t.effects.clipToBox(r)),r.clip=a),h&&h.animate(t.effects.clipToBox(r),e.duration,e.easing),n.animate(r,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("bounce",function(e,i){var s,n,o,a=t(this),r=e.mode,h="hide"===r,l="show"===r,c=e.direction||"up",u=e.distance,d=e.times||5,p=2*d+(l||h?1:0),f=e.duration/p,g=e.easing,m="up"===c||"down"===c?"top":"left",_="up"===c||"left"===c,v=0,b=a.queue().length;for(t.effects.createPlaceholder(a),o=a.css(m),u||(u=a["top"===m?"outerHeight":"outerWidth"]()/3),l&&(n={opacity:1},n[m]=o,a.css("opacity",0).css(m,_?2*-u:2*u).animate(n,f,g)),h&&(u/=Math.pow(2,d-1)),n={},n[m]=o;d>v;v++)s={},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g).animate(n,f,g),u=h?2*u:u/2;h&&(s={opacity:0},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g)),a.queue(i),t.effects.unshift(a,b,p+1)}),t.effects.define("clip","hide",function(e,i){var s,n={},o=t(this),a=e.direction||"vertical",r="both"===a,h=r||"horizontal"===a,l=r||"vertical"===a;s=o.cssClip(),n.clip={top:l?(s.bottom-s.top)/2:s.top,right:h?(s.right-s.left)/2:s.right,bottom:l?(s.bottom-s.top)/2:s.bottom,left:h?(s.right-s.left)/2:s.left},t.effects.createPlaceholder(o),"show"===e.mode&&(o.cssClip(n.clip),n.clip=s),o.animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("drop","hide",function(e,i){var s,n=t(this),o=e.mode,a="show"===o,r=e.direction||"left",h="up"===r||"down"===r?"top":"left",l="up"===r||"left"===r?"-=":"+=",c="+="===l?"-=":"+=",u={opacity:0};t.effects.createPlaceholder(n),s=e.distance||n["top"===h?"outerHeight":"outerWidth"](!0)/2,u[h]=l+s,a&&(n.css(u),u[h]=c+s,u.opacity=1),n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("explode","hide",function(e,i){function s(){b.push(this),b.length===u*d&&n()}function n(){p.css({visibility:"visible"}),t(b).remove(),i()}var o,a,r,h,l,c,u=e.pieces?Math.round(Math.sqrt(e.pieces)):3,d=u,p=t(this),f=e.mode,g="show"===f,m=p.show().css("visibility","hidden").offset(),_=Math.ceil(p.outerWidth()/d),v=Math.ceil(p.outerHeight()/u),b=[];for(o=0;u>o;o++)for(h=m.top+o*v,c=o-(u-1)/2,a=0;d>a;a++)r=m.left+a*_,l=a-(d-1)/2,p.clone().appendTo("body").wrap("
").css({position:"absolute",visibility:"visible",left:-a*_,top:-o*v}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:_,height:v,left:r+(g?l*_:0),top:h+(g?c*v:0),opacity:g?0:1}).animate({left:r+(g?0:l*_),top:h+(g?0:c*v),opacity:g?1:0},e.duration||500,e.easing,s)}),t.effects.define("fade","toggle",function(e,i){var s="show"===e.mode;t(this).css("opacity",s?0:1).animate({opacity:s?1:0},{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("fold","hide",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=e.size||15,h=/([0-9]+)%/.exec(r),l=!!e.horizFirst,c=l?["right","bottom"]:["bottom","right"],u=e.duration/2,d=t.effects.createPlaceholder(s),p=s.cssClip(),f={clip:t.extend({},p)},g={clip:t.extend({},p)},m=[p[c[0]],p[c[1]]],_=s.queue().length;h&&(r=parseInt(h[1],10)/100*m[a?0:1]),f.clip[c[0]]=r,g.clip[c[0]]=r,g.clip[c[1]]=0,o&&(s.cssClip(g.clip),d&&d.css(t.effects.clipToBox(g)),g.clip=p),s.queue(function(i){d&&d.animate(t.effects.clipToBox(f),u,e.easing).animate(t.effects.clipToBox(g),u,e.easing),i()}).animate(f,u,e.easing).animate(g,u,e.easing).queue(i),t.effects.unshift(s,_,4)}),t.effects.define("highlight","show",function(e,i){var s=t(this),n={backgroundColor:s.css("backgroundColor")};"hide"===e.mode&&(n.opacity=0),t.effects.saveStyle(s),s.css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("size",function(e,i){var s,n,o,a=t(this),r=["fontSize"],h=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],l=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],c=e.mode,u="effect"!==c,d=e.scale||"both",p=e.origin||["middle","center"],f=a.css("position"),g=a.position(),m=t.effects.scaledDimensions(a),_=e.from||m,v=e.to||t.effects.scaledDimensions(a,0);t.effects.createPlaceholder(a),"show"===c&&(o=_,_=v,v=o),n={from:{y:_.height/m.height,x:_.width/m.width},to:{y:v.height/m.height,x:v.width/m.width}},("box"===d||"both"===d)&&(n.from.y!==n.to.y&&(_=t.effects.setTransition(a,h,n.from.y,_),v=t.effects.setTransition(a,h,n.to.y,v)),n.from.x!==n.to.x&&(_=t.effects.setTransition(a,l,n.from.x,_),v=t.effects.setTransition(a,l,n.to.x,v))),("content"===d||"both"===d)&&n.from.y!==n.to.y&&(_=t.effects.setTransition(a,r,n.from.y,_),v=t.effects.setTransition(a,r,n.to.y,v)),p&&(s=t.effects.getBaseline(p,m),_.top=(m.outerHeight-_.outerHeight)*s.y+g.top,_.left=(m.outerWidth-_.outerWidth)*s.x+g.left,v.top=(m.outerHeight-v.outerHeight)*s.y+g.top,v.left=(m.outerWidth-v.outerWidth)*s.x+g.left),a.css(_),("content"===d||"both"===d)&&(h=h.concat(["marginTop","marginBottom"]).concat(r),l=l.concat(["marginLeft","marginRight"]),a.find("*[width]").each(function(){var i=t(this),s=t.effects.scaledDimensions(i),o={height:s.height*n.from.y,width:s.width*n.from.x,outerHeight:s.outerHeight*n.from.y,outerWidth:s.outerWidth*n.from.x},a={height:s.height*n.to.y,width:s.width*n.to.x,outerHeight:s.height*n.to.y,outerWidth:s.width*n.to.x};n.from.y!==n.to.y&&(o=t.effects.setTransition(i,h,n.from.y,o),a=t.effects.setTransition(i,h,n.to.y,a)),n.from.x!==n.to.x&&(o=t.effects.setTransition(i,l,n.from.x,o),a=t.effects.setTransition(i,l,n.to.x,a)),u&&t.effects.saveStyle(i),i.css(o),i.animate(a,e.duration,e.easing,function(){u&&t.effects.restoreStyle(i)})})),a.animate(v,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){var e=a.offset();0===v.opacity&&a.css("opacity",_.opacity),u||(a.css("position","static"===f?"relative":f).offset(e),t.effects.saveStyle(a)),i()}})}),t.effects.define("scale",function(e,i){var s=t(this),n=e.mode,o=parseInt(e.percent,10)||(0===parseInt(e.percent,10)?0:"effect"!==n?0:100),a=t.extend(!0,{from:t.effects.scaledDimensions(s),to:t.effects.scaledDimensions(s,o,e.direction||"both"),origin:e.origin||["middle","center"]},e);e.fade&&(a.from.opacity=1,a.to.opacity=0),t.effects.effect.size.call(this,a,i)}),t.effects.define("puff","hide",function(e,i){var s=t.extend(!0,{},e,{fade:!0,percent:parseInt(e.percent,10)||150});t.effects.effect.scale.call(this,s,i)}),t.effects.define("pulsate","show",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=o||a,h=2*(e.times||5)+(r?1:0),l=e.duration/h,c=0,u=1,d=s.queue().length;for((o||!s.is(":visible"))&&(s.css("opacity",0).show(),c=1);h>u;u++)s.animate({opacity:c},l,e.easing),c=1-c;s.animate({opacity:c},l,e.easing),s.queue(i),t.effects.unshift(s,d,h+1)}),t.effects.define("shake",function(e,i){var s=1,n=t(this),o=e.direction||"left",a=e.distance||20,r=e.times||3,h=2*r+1,l=Math.round(e.duration/h),c="up"===o||"down"===o?"top":"left",u="up"===o||"left"===o,d={},p={},f={},g=n.queue().length;for(t.effects.createPlaceholder(n),d[c]=(u?"-=":"+=")+a,p[c]=(u?"+=":"-=")+2*a,f[c]=(u?"-=":"+=")+2*a,n.animate(d,l,e.easing);r>s;s++)n.animate(p,l,e.easing).animate(f,l,e.easing);n.animate(p,l,e.easing).animate(d,l/2,e.easing).queue(i),t.effects.unshift(n,g,h+1)}),t.effects.define("slide","show",function(e,i){var s,n,o=t(this),a={up:["bottom","top"],down:["top","bottom"],left:["right","left"],right:["left","right"]},r=e.mode,h=e.direction||"left",l="up"===h||"down"===h?"top":"left",c="up"===h||"left"===h,u=e.distance||o["top"===l?"outerHeight":"outerWidth"](!0),d={};t.effects.createPlaceholder(o),s=o.cssClip(),n=o.position()[l],d[l]=(c?-1:1)*u+n,d.clip=o.cssClip(),d.clip[a[h][1]]=d.clip[a[h][0]],"show"===r&&(o.cssClip(d.clip),o.css(l,d[l]),d.clip=s,d[l]=n),o.animate(d,{queue:!1,duration:e.duration,easing:e.easing,complete:i})});var f;t.uiBackCompat!==!1&&(f=t.effects.define("transfer",function(e,i){t(this).transfer(e,i)})),t.ui.focusable=function(i,s){var n,o,a,r,h,l=i.nodeName.toLowerCase();return"area"===l?(n=i.parentNode,o=n.name,i.href&&o&&"map"===n.nodeName.toLowerCase()?(a=t("img[usemap='#"+o+"']"),a.length>0&&a.is(":visible")):!1):(/^(input|select|textarea|button|object)$/.test(l)?(r=!i.disabled,r&&(h=t(i).closest("fieldset")[0],h&&(r=!h.disabled))):r="a"===l?i.href||s:s,r&&t(i).is(":visible")&&e(t(i)))},t.extend(t.expr[":"],{focusable:function(e){return t.ui.focusable(e,null!=t.attr(e,"tabindex"))}}),t.ui.focusable,t.fn.form=function(){return"string"==typeof this[0].form?this.closest("form"):t(this[0].form)},t.ui.formResetMixin={_formResetHandler:function(){var e=t(this);setTimeout(function(){var i=e.data("ui-form-reset-instances");t.each(i,function(){this.refresh()})})},_bindFormResetHandler:function(){if(this.form=this.element.form(),this.form.length){var t=this.form.data("ui-form-reset-instances")||[];t.length||this.form.on("reset.ui-form-reset",this._formResetHandler),t.push(this),this.form.data("ui-form-reset-instances",t)}},_unbindFormResetHandler:function(){if(this.form.length){var e=this.form.data("ui-form-reset-instances");e.splice(t.inArray(this,e),1),e.length?this.form.data("ui-form-reset-instances",e):this.form.removeData("ui-form-reset-instances").off("reset.ui-form-reset")}}},"1.7"===t.fn.jquery.substring(0,3)&&(t.each(["Width","Height"],function(e,i){function s(e,i,s,o){return t.each(n,function(){i-=parseFloat(t.css(e,"padding"+this))||0,s&&(i-=parseFloat(t.css(e,"border"+this+"Width"))||0),o&&(i-=parseFloat(t.css(e,"margin"+this))||0)}),i}var n="Width"===i?["Left","Right"]:["Top","Bottom"],o=i.toLowerCase(),a={innerWidth:t.fn.innerWidth,innerHeight:t.fn.innerHeight,outerWidth:t.fn.outerWidth,outerHeight:t.fn.outerHeight};t.fn["inner"+i]=function(e){return void 0===e?a["inner"+i].call(this):this.each(function(){t(this).css(o,s(this,e)+"px")})},t.fn["outer"+i]=function(e,n){return"number"!=typeof e?a["outer"+i].call(this,e):this.each(function(){t(this).css(o,s(this,e,!0,n)+"px")})}}),t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.ui.keyCode={BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38},t.ui.escapeSelector=function(){var t=/([!"#$%&'()*+,.\/:;<=>?@[\]^`{|}~])/g;return function(e){return e.replace(t,"\\$1")}}(),t.fn.labels=function(){var e,i,s,n,o;return this[0].labels&&this[0].labels.length?this.pushStack(this[0].labels):(n=this.eq(0).parents("label"),s=this.attr("id"),s&&(e=this.eq(0).parents().last(),o=e.add(e.length?e.siblings():this.siblings()),i="label[for='"+t.ui.escapeSelector(s)+"']",n=n.add(o.find(i).addBack(i))),this.pushStack(n))},t.fn.scrollParent=function(e){var i=this.css("position"),s="absolute"===i,n=e?/(auto|scroll|hidden)/:/(auto|scroll)/,o=this.parents().filter(function(){var e=t(this);return s&&"static"===e.css("position")?!1:n.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&o.length?o:t(this[0].ownerDocument||document)},t.extend(t.expr[":"],{tabbable:function(e){var i=t.attr(e,"tabindex"),s=null!=i;return(!s||i>=0)&&t.ui.focusable(e,s)}}),t.fn.extend({uniqueId:function(){var t=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++t)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&t(this).removeAttr("id")})}}),t.widget("ui.accordion",{version:"1.12.1",options:{active:0,animate:{},classes:{"ui-accordion-header":"ui-corner-top","ui-accordion-header-collapsed":"ui-corner-all","ui-accordion-content":"ui-corner-bottom"},collapsible:!1,event:"click",header:"> li > :first-child, > :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var e=this.options;this.prevShow=this.prevHide=t(),this._addClass("ui-accordion","ui-widget ui-helper-reset"),this.element.attr("role","tablist"),e.collapsible||e.active!==!1&&null!=e.active||(e.active=0),this._processPanels(),0>e.active&&(e.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():t()}},_createIcons:function(){var e,i,s=this.options.icons;s&&(e=t(""),this._addClass(e,"ui-accordion-header-icon","ui-icon "+s.header),e.prependTo(this.headers),i=this.active.children(".ui-accordion-header-icon"),this._removeClass(i,s.header)._addClass(i,null,s.activeHeader)._addClass(this.headers,"ui-accordion-icons"))},_destroyIcons:function(){this._removeClass(this.headers,"ui-accordion-icons"),this.headers.children(".ui-accordion-header-icon").remove()},_destroy:function(){var t;this.element.removeAttr("role"),this.headers.removeAttr("role aria-expanded aria-selected aria-controls tabIndex").removeUniqueId(),this._destroyIcons(),t=this.headers.next().css("display","").removeAttr("role aria-hidden aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&t.css("height","")},_setOption:function(t,e){return"active"===t?(this._activate(e),void 0):("event"===t&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(e)),this._super(t,e),"collapsible"!==t||e||this.options.active!==!1||this._activate(0),"icons"===t&&(this._destroyIcons(),e&&this._createIcons()),void 0)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t),this._toggleClass(this.headers.add(this.headers.next()),null,"ui-state-disabled",!!t)},_keydown:function(e){if(!e.altKey&&!e.ctrlKey){var i=t.ui.keyCode,s=this.headers.length,n=this.headers.index(e.target),o=!1;switch(e.keyCode){case i.RIGHT:case i.DOWN:o=this.headers[(n+1)%s];break;case i.LEFT:case i.UP:o=this.headers[(n-1+s)%s];break;case i.SPACE:case i.ENTER:this._eventHandler(e);break;case i.HOME:o=this.headers[0];break;case i.END:o=this.headers[s-1]}o&&(t(e.target).attr("tabIndex",-1),t(o).attr("tabIndex",0),t(o).trigger("focus"),e.preventDefault())}},_panelKeyDown:function(e){e.keyCode===t.ui.keyCode.UP&&e.ctrlKey&&t(e.currentTarget).prev().trigger("focus")},refresh:function(){var e=this.options;this._processPanels(),e.active===!1&&e.collapsible===!0||!this.headers.length?(e.active=!1,this.active=t()):e.active===!1?this._activate(0):this.active.length&&!t.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(e.active=!1,this.active=t()):this._activate(Math.max(0,e.active-1)):e.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var t=this.headers,e=this.panels;this.headers=this.element.find(this.options.header),this._addClass(this.headers,"ui-accordion-header ui-accordion-header-collapsed","ui-state-default"),this.panels=this.headers.next().filter(":not(.ui-accordion-content-active)").hide(),this._addClass(this.panels,"ui-accordion-content","ui-helper-reset ui-widget-content"),e&&(this._off(t.not(this.headers)),this._off(e.not(this.panels)))},_refresh:function(){var e,i=this.options,s=i.heightStyle,n=this.element.parent();this.active=this._findActive(i.active),this._addClass(this.active,"ui-accordion-header-active","ui-state-active")._removeClass(this.active,"ui-accordion-header-collapsed"),this._addClass(this.active.next(),"ui-accordion-content-active"),this.active.next().show(),this.headers.attr("role","tab").each(function(){var e=t(this),i=e.uniqueId().attr("id"),s=e.next(),n=s.uniqueId().attr("id");e.attr("aria-controls",n),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(i.event),"fill"===s?(e=n.height(),this.element.siblings(":visible").each(function(){var i=t(this),s=i.css("position");"absolute"!==s&&"fixed"!==s&&(e-=i.outerHeight(!0))}),this.headers.each(function(){e-=t(this).outerHeight(!0)}),this.headers.next().each(function(){t(this).height(Math.max(0,e-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===s&&(e=0,this.headers.next().each(function(){var i=t(this).is(":visible");i||t(this).show(),e=Math.max(e,t(this).css("height","").height()),i||t(this).hide()}).height(e))},_activate:function(e){var i=this._findActive(e)[0];i!==this.active[0]&&(i=i||this.active[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return"number"==typeof e?this.headers.eq(e):t()},_setupEvents:function(e){var i={keydown:"_keydown"};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(e){var i,s,n=this.options,o=this.active,a=t(e.currentTarget),r=a[0]===o[0],h=r&&n.collapsible,l=h?t():a.next(),c=o.next(),u={oldHeader:o,oldPanel:c,newHeader:h?t():a,newPanel:l};e.preventDefault(),r&&!n.collapsible||this._trigger("beforeActivate",e,u)===!1||(n.active=h?!1:this.headers.index(a),this.active=r?t():a,this._toggle(u),this._removeClass(o,"ui-accordion-header-active","ui-state-active"),n.icons&&(i=o.children(".ui-accordion-header-icon"),this._removeClass(i,null,n.icons.activeHeader)._addClass(i,null,n.icons.header)),r||(this._removeClass(a,"ui-accordion-header-collapsed")._addClass(a,"ui-accordion-header-active","ui-state-active"),n.icons&&(s=a.children(".ui-accordion-header-icon"),this._removeClass(s,null,n.icons.header)._addClass(s,null,n.icons.activeHeader)),this._addClass(a.next(),"ui-accordion-content-active")))},_toggle:function(e){var i=e.newPanel,s=this.prevShow.length?this.prevShow:e.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=i,this.prevHide=s,this.options.animate?this._animate(i,s,e):(s.hide(),i.show(),this._toggleComplete(e)),s.attr({"aria-hidden":"true"}),s.prev().attr({"aria-selected":"false","aria-expanded":"false"}),i.length&&s.length?s.prev().attr({tabIndex:-1,"aria-expanded":"false"}):i.length&&this.headers.filter(function(){return 0===parseInt(t(this).attr("tabIndex"),10)}).attr("tabIndex",-1),i.attr("aria-hidden","false").prev().attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_animate:function(t,e,i){var s,n,o,a=this,r=0,h=t.css("box-sizing"),l=t.length&&(!e.length||t.index()",delay:300,options:{icons:{submenu:"ui-icon-caret-1-e"},items:"> *",menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().attr({role:this.options.role,tabIndex:0}),this._addClass("ui-menu","ui-widget ui-widget-content"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault()},"click .ui-menu-item":function(e){var i=t(e.target),s=t(t.ui.safeActiveElement(this.document[0]));!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&s.closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){if(!this.previousFilter){var i=t(e.target).closest(".ui-menu-item"),s=t(e.currentTarget);i[0]===s[0]&&(this._removeClass(s.siblings().children(".ui-state-active"),null,"ui-state-active"),this.focus(e,s))}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.find(this.options.items).eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){var i=!t.contains(this.element[0],t.ui.safeActiveElement(this.document[0]));i&&this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t),this.mouseHandled=!1}})},_destroy:function(){var e=this.element.find(".ui-menu-item").removeAttr("role aria-disabled"),i=e.children(".ui-menu-item-wrapper").removeUniqueId().removeAttr("tabIndex role aria-haspopup");this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeAttr("role aria-labelledby aria-expanded aria-hidden aria-disabled tabIndex").removeUniqueId().show(),i.children().each(function(){var e=t(this);e.data("ui-menu-submenu-caret")&&e.remove()})},_keydown:function(e){var i,s,n,o,a=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:a=!1,s=this.previousFilter||"",o=!1,n=e.keyCode>=96&&105>=e.keyCode?""+(e.keyCode-96):String.fromCharCode(e.keyCode),clearTimeout(this.filterTimer),n===s?o=!0:n=s+n,i=this._filterMenuItems(n),i=o&&-1!==i.index(this.active.next())?this.active.nextAll(".ui-menu-item"):i,i.length||(n=String.fromCharCode(e.keyCode),i=this._filterMenuItems(n)),i.length?(this.focus(e,i),this.previousFilter=n,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}a&&e.preventDefault()},_activate:function(t){this.active&&!this.active.is(".ui-state-disabled")&&(this.active.children("[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i,s,n,o,a=this,r=this.options.icons.submenu,h=this.element.find(this.options.menus);this._toggleClass("ui-menu-icons",null,!!this.element.find(".ui-icon").length),s=h.filter(":not(.ui-menu)").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),i=e.prev(),s=t("").data("ui-menu-submenu-caret",!0);a._addClass(s,"ui-menu-icon","ui-icon "+r),i.attr("aria-haspopup","true").prepend(s),e.attr("aria-labelledby",i.attr("id"))}),this._addClass(s,"ui-menu","ui-widget ui-widget-content ui-front"),e=h.add(this.element),i=e.find(this.options.items),i.not(".ui-menu-item").each(function(){var e=t(this);a._isDivider(e)&&a._addClass(e,"ui-menu-divider","ui-widget-content")}),n=i.not(".ui-menu-item, .ui-menu-divider"),o=n.children().not(".ui-menu").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),this._addClass(n,"ui-menu-item")._addClass(o,"ui-menu-item-wrapper"),i.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){if("icons"===t){var i=this.element.find(".ui-menu-icon");this._removeClass(i,null,this.options.icons.submenu)._addClass(i,null,e.submenu)}this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t+""),this._toggleClass(null,"ui-state-disabled",!!t)},focus:function(t,e){var i,s,n;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children(".ui-menu-item-wrapper"),this._addClass(s,null,"ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),n=this.active.parent().closest(".ui-menu-item").children(".ui-menu-item-wrapper"),this._addClass(n,null,"ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,o,a,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,o=this.activeMenu.scrollTop(),a=this.activeMenu.height(),r=e.outerHeight(),0>n?this.activeMenu.scrollTop(o+n):n+r>a&&this.activeMenu.scrollTop(o+n-a+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this._removeClass(this.active.children(".ui-menu-item-wrapper"),null,"ui-state-active"),this._trigger("blur",t,{item:this.active}),this.active=null)},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this._removeClass(s.find(".ui-state-active"),null,"ui-state-active"),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false")},_closeOnDocumentClick:function(e){return!t(e.target).closest(".ui-menu").length},_isDivider:function(t){return!/[^\-\u2014\u2013\s]/.test(t.text())},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.find(this.options.items)[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items)[this.active?"last":"first"]())),void 0):(this.next(e),void 0)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items).first())),void 0):(this.next(e),void 0)},_hasScroll:function(){return this.element.outerHeight()",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var e,i,s,n=this.element[0].nodeName.toLowerCase(),o="textarea"===n,a="input"===n; -this.isMultiLine=o||!a&&this._isContentEditable(this.element),this.valueMethod=this.element[o||a?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return e=!0,s=!0,i=!0,void 0;e=!1,s=!1,i=!1;var o=t.ui.keyCode;switch(n.keyCode){case o.PAGE_UP:e=!0,this._move("previousPage",n);break;case o.PAGE_DOWN:e=!0,this._move("nextPage",n);break;case o.UP:e=!0,this._keyEvent("previous",n);break;case o.DOWN:e=!0,this._keyEvent("next",n);break;case o.ENTER:this.menu.active&&(e=!0,n.preventDefault(),this.menu.select(n));break;case o.TAB:this.menu.active&&this.menu.select(n);break;case o.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(e)return e=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),void 0;if(!i){var n=t.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(t){return s?(s=!1,t.preventDefault(),void 0):(this._searchTimeout(t),void 0)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(clearTimeout(this.searching),this.close(t),this._change(t),void 0)}}),this._initSource(),this.menu=t("'),this.$slides.each(function(t,e){var i=s('
  • ');n.$indicators.append(i[0])}),this.$el.append(this.$indicators[0]),this.$indicators=this.$indicators.children("li.indicator-item"))}},{key:"_removeIndicators",value:function(){this.$el.find("ul.indicators").remove()}},{key:"set",value:function(t){var e=this;if(t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.activeIndex!=t){this.$active=this.$slides.eq(this.activeIndex);var i=this.$active.find(".caption");this.$active.removeClass("active"),o({targets:this.$active[0],opacity:0,duration:this.options.duration,easing:"easeOutQuad",complete:function(){e.$slides.not(".active").each(function(t){o({targets:t,opacity:0,translateX:0,translateY:0,duration:0,easing:"easeOutQuad"})})}}),this._animateCaptionIn(i[0],this.options.duration),this.options.indicators&&(this.$indicators.eq(this.activeIndex).removeClass("active"),this.$indicators.eq(t).addClass("active")),o({targets:this.$slides.eq(t)[0],opacity:1,duration:this.options.duration,easing:"easeOutQuad"}),o({targets:this.$slides.eq(t).find(".caption")[0],opacity:1,translateX:0,translateY:0,duration:this.options.duration,delay:this.options.duration,easing:"easeOutQuad"}),this.$slides.eq(t).addClass("active"),this.activeIndex=t,this.start()}}},{key:"pause",value:function(){clearInterval(this.interval)}},{key:"start",value:function(){clearInterval(this.interval),this.interval=setInterval(this._handleIntervalBound,this.options.duration+this.options.interval)}},{key:"next",value:function(){var t=this.activeIndex+1;t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.set(t)}},{key:"prev",value:function(){var t=this.activeIndex-1;t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.set(t)}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Slider}},{key:"defaults",get:function(){return e}}]),n}();M.Slider=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"slider","M_Slider")}(cash,M.anime),function(n,s){n(document).on("click",".card",function(t){if(n(this).children(".card-reveal").length){var i=n(t.target).closest(".card");void 0===i.data("initialOverflow")&&i.data("initialOverflow",void 0===i.css("overflow")?"":i.css("overflow"));var e=n(this).find(".card-reveal");n(t.target).is(n(".card-reveal .card-title"))||n(t.target).is(n(".card-reveal .card-title i"))?s({targets:e[0],translateY:0,duration:225,easing:"easeInOutQuad",complete:function(t){var e=t.animatables[0].target;n(e).css({display:"none"}),i.css("overflow",i.data("initialOverflow"))}}):(n(t.target).is(n(".card .activator"))||n(t.target).is(n(".card .activator i")))&&(i.css("overflow","hidden"),e.css({display:"block"}),s({targets:e[0],translateY:"-100%",duration:300,easing:"easeInOutQuad"}))}})}(cash,M.anime),function(h){"use strict";var e={data:[],placeholder:"",secondaryPlaceholder:"",autocompleteOptions:{},limit:1/0,onChipAdd:null,onChipSelect:null,onChipDelete:null},t=function(t){function l(t,e){_classCallCheck(this,l);var i=_possibleConstructorReturn(this,(l.__proto__||Object.getPrototypeOf(l)).call(this,l,t,e));return(i.el.M_Chips=i).options=h.extend({},l.defaults,e),i.$el.addClass("chips input-field"),i.chipsData=[],i.$chips=h(),i._setupInput(),i.hasAutocomplete=0"),this.$el.append(this.$input)),this.$input.addClass("input")}},{key:"_setupLabel",value:function(){this.$label=this.$el.find("label"),this.$label.length&&this.$label.setAttribute("for",this.$input.attr("id"))}},{key:"_setPlaceholder",value:function(){void 0!==this.chipsData&&!this.chipsData.length&&this.options.placeholder?h(this.$input).prop("placeholder",this.options.placeholder):(void 0===this.chipsData||this.chipsData.length)&&this.options.secondaryPlaceholder&&h(this.$input).prop("placeholder",this.options.secondaryPlaceholder)}},{key:"_isValid",value:function(t){if(t.hasOwnProperty("tag")&&""!==t.tag){for(var e=!1,i=0;i=this.options.limit)){var e=this._renderChip(t);this.$chips.add(e),this.chipsData.push(t),h(this.$input).before(e),this._setPlaceholder(),"function"==typeof this.options.onChipAdd&&this.options.onChipAdd.call(this,this.$el,e)}}},{key:"deleteChip",value:function(t){var e=this.$chips.eq(t);this.$chips.eq(t).remove(),this.$chips=this.$chips.filter(function(t){return 0<=h(t).index()}),this.chipsData.splice(t,1),this._setPlaceholder(),"function"==typeof this.options.onChipDelete&&this.options.onChipDelete.call(this,this.$el,e[0])}},{key:"selectChip",value:function(t){var e=this.$chips.eq(t);(this._selectedChip=e)[0].focus(),"function"==typeof this.options.onChipSelect&&this.options.onChipSelect.call(this,this.$el,e[0])}}],[{key:"init",value:function(t,e){return _get(l.__proto__||Object.getPrototypeOf(l),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Chips}},{key:"_handleChipsKeydown",value:function(t){l._keydown=!0;var e=h(t.target).closest(".chips"),i=t.target&&e.length;if(!h(t.target).is("input, textarea")&&i){var n=e[0].M_Chips;if(8===t.keyCode||46===t.keyCode){t.preventDefault();var s=n.chipsData.length;if(n._selectedChip){var o=n._selectedChip.index();n.deleteChip(o),n._selectedChip=null,s=Math.max(o-1,0)}n.chipsData.length&&n.selectChip(s)}else if(37===t.keyCode){if(n._selectedChip){var a=n._selectedChip.index()-1;if(a<0)return;n.selectChip(a)}}else if(39===t.keyCode&&n._selectedChip){var r=n._selectedChip.index()+1;r>=n.chipsData.length?n.$input[0].focus():n.selectChip(r)}}}},{key:"_handleChipsKeyup",value:function(t){l._keydown=!1}},{key:"_handleChipsBlur",value:function(t){l._keydown||(h(t.target).closest(".chips")[0].M_Chips._selectedChip=null)}},{key:"defaults",get:function(){return e}}]),l}();t._keydown=!1,M.Chips=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"chips","M_Chips"),h(document).ready(function(){h(document.body).on("click",".chip .close",function(){var t=h(this).closest(".chips");t.length&&t[0].M_Chips||h(this).closest(".chip").remove()})})}(cash),function(s){"use strict";var e={top:0,bottom:1/0,offset:0,onPositionChange:null},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_Pushpin=i).options=s.extend({},n.defaults,e),i.originalOffset=i.el.offsetTop,n._pushpins.push(i),i._setupEventHandlers(),i._updatePosition(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this.el.style.top=null,this._removePinClasses(),this._removeEventHandlers();var t=n._pushpins.indexOf(this);n._pushpins.splice(t,1)}},{key:"_setupEventHandlers",value:function(){document.addEventListener("scroll",n._updateElements)}},{key:"_removeEventHandlers",value:function(){document.removeEventListener("scroll",n._updateElements)}},{key:"_updatePosition",value:function(){var t=M.getDocumentScrollTop()+this.options.offset;this.options.top<=t&&this.options.bottom>=t&&!this.el.classList.contains("pinned")&&(this._removePinClasses(),this.el.style.top=this.options.offset+"px",this.el.classList.add("pinned"),"function"==typeof this.options.onPositionChange&&this.options.onPositionChange.call(this,"pinned")),tthis.options.bottom&&!this.el.classList.contains("pin-bottom")&&(this._removePinClasses(),this.el.classList.add("pin-bottom"),this.el.style.top=this.options.bottom-this.originalOffset+"px","function"==typeof this.options.onPositionChange&&this.options.onPositionChange.call(this,"pin-bottom"))}},{key:"_removePinClasses",value:function(){this.el.classList.remove("pin-top"),this.el.classList.remove("pinned"),this.el.classList.remove("pin-bottom")}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Pushpin}},{key:"_updateElements",value:function(){for(var t in n._pushpins){n._pushpins[t]._updatePosition()}}},{key:"defaults",get:function(){return e}}]),n}();t._pushpins=[],M.Pushpin=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"pushpin","M_Pushpin")}(cash),function(r,s){"use strict";var e={direction:"top",hoverEnabled:!0,toolbarEnabled:!1};r.fn.reverse=[].reverse;var t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_FloatingActionButton=i).options=r.extend({},n.defaults,e),i.isOpen=!1,i.$anchor=i.$el.children("a").first(),i.$menu=i.$el.children("ul").first(),i.$floatingBtns=i.$el.find("ul .btn-floating"),i.$floatingBtnsReverse=i.$el.find("ul .btn-floating").reverse(),i.offsetY=0,i.offsetX=0,i.$el.addClass("direction-"+i.options.direction),"top"===i.options.direction?i.offsetY=40:"right"===i.options.direction?i.offsetX=-40:"bottom"===i.options.direction?i.offsetY=-40:i.offsetX=40,i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.M_FloatingActionButton=void 0}},{key:"_setupEventHandlers",value:function(){this._handleFABClickBound=this._handleFABClick.bind(this),this._handleOpenBound=this.open.bind(this),this._handleCloseBound=this.close.bind(this),this.options.hoverEnabled&&!this.options.toolbarEnabled?(this.el.addEventListener("mouseenter",this._handleOpenBound),this.el.addEventListener("mouseleave",this._handleCloseBound)):this.el.addEventListener("click",this._handleFABClickBound)}},{key:"_removeEventHandlers",value:function(){this.options.hoverEnabled&&!this.options.toolbarEnabled?(this.el.removeEventListener("mouseenter",this._handleOpenBound),this.el.removeEventListener("mouseleave",this._handleCloseBound)):this.el.removeEventListener("click",this._handleFABClickBound)}},{key:"_handleFABClick",value:function(){this.isOpen?this.close():this.open()}},{key:"_handleDocumentClick",value:function(t){r(t.target).closest(this.$menu).length||this.close()}},{key:"open",value:function(){this.isOpen||(this.options.toolbarEnabled?this._animateInToolbar():this._animateInFAB(),this.isOpen=!0)}},{key:"close",value:function(){this.isOpen&&(this.options.toolbarEnabled?(window.removeEventListener("scroll",this._handleCloseBound,!0),document.body.removeEventListener("click",this._handleDocumentClickBound,!0),this._animateOutToolbar()):this._animateOutFAB(),this.isOpen=!1)}},{key:"_animateInFAB",value:function(){var e=this;this.$el.addClass("active");var i=0;this.$floatingBtnsReverse.each(function(t){s({targets:t,opacity:1,scale:[.4,1],translateY:[e.offsetY,0],translateX:[e.offsetX,0],duration:275,delay:i,easing:"easeInOutQuad"}),i+=40})}},{key:"_animateOutFAB",value:function(){var e=this;this.$floatingBtnsReverse.each(function(t){s.remove(t),s({targets:t,opacity:0,scale:.4,translateY:e.offsetY,translateX:e.offsetX,duration:175,easing:"easeOutQuad",complete:function(){e.$el.removeClass("active")}})})}},{key:"_animateInToolbar",value:function(){var t,e=this,i=window.innerWidth,n=window.innerHeight,s=this.el.getBoundingClientRect(),o=r('
    '),a=this.$anchor.css("background-color");this.$anchor.append(o),this.offsetX=s.left-i/2+s.width/2,this.offsetY=n-s.bottom,t=i/o[0].clientWidth,this.btnBottom=s.bottom,this.btnLeft=s.left,this.btnWidth=s.width,this.$el.addClass("active"),this.$el.css({"text-align":"center",width:"100%",bottom:0,left:0,transform:"translateX("+this.offsetX+"px)",transition:"none"}),this.$anchor.css({transform:"translateY("+-this.offsetY+"px)",transition:"none"}),o.css({"background-color":a}),setTimeout(function(){e.$el.css({transform:"",transition:"transform .2s cubic-bezier(0.550, 0.085, 0.680, 0.530), background-color 0s linear .2s"}),e.$anchor.css({overflow:"visible",transform:"",transition:"transform .2s"}),setTimeout(function(){e.$el.css({overflow:"hidden","background-color":a}),o.css({transform:"scale("+t+")",transition:"transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)"}),e.$menu.children("li").children("a").css({opacity:1}),e._handleDocumentClickBound=e._handleDocumentClick.bind(e),window.addEventListener("scroll",e._handleCloseBound,!0),document.body.addEventListener("click",e._handleDocumentClickBound,!0)},100)},0)}},{key:"_animateOutToolbar",value:function(){var t=this,e=window.innerWidth,i=window.innerHeight,n=this.$el.find(".fab-backdrop"),s=this.$anchor.css("background-color");this.offsetX=this.btnLeft-e/2+this.btnWidth/2,this.offsetY=i-this.btnBottom,this.$el.removeClass("active"),this.$el.css({"background-color":"transparent",transition:"none"}),this.$anchor.css({transition:"none"}),n.css({transform:"scale(0)","background-color":s}),this.$menu.children("li").children("a").css({opacity:""}),setTimeout(function(){n.remove(),t.$el.css({"text-align":"",width:"",bottom:"",left:"",overflow:"","background-color":"",transform:"translate3d("+-t.offsetX+"px,0,0)"}),t.$anchor.css({overflow:"",transform:"translate3d(0,"+t.offsetY+"px,0)"}),setTimeout(function(){t.$el.css({transform:"translate3d(0,0,0)",transition:"transform .2s"}),t.$anchor.css({transform:"translate3d(0,0,0)",transition:"transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)"})},20)},200)}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_FloatingActionButton}},{key:"defaults",get:function(){return e}}]),n}();M.FloatingActionButton=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"floatingActionButton","M_FloatingActionButton")}(cash,M.anime),function(g){"use strict";var e={autoClose:!1,format:"mmm dd, yyyy",parse:null,defaultDate:null,setDefaultDate:!1,disableWeekends:!1,disableDayFn:null,firstDay:0,minDate:null,maxDate:null,yearRange:10,minYear:0,maxYear:9999,minMonth:void 0,maxMonth:void 0,startRange:null,endRange:null,isRTL:!1,showMonthAfterYear:!1,showDaysInNextAndPreviousMonths:!1,container:null,showClearBtn:!1,i18n:{cancel:"Cancel",clear:"Clear",done:"Ok",previousMonth:"‹",nextMonth:"›",months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],weekdays:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],weekdaysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],weekdaysAbbrev:["S","M","T","W","T","F","S"]},events:[],onSelect:null,onOpen:null,onClose:null,onDraw:null},t=function(t){function B(t,e){_classCallCheck(this,B);var i=_possibleConstructorReturn(this,(B.__proto__||Object.getPrototypeOf(B)).call(this,B,t,e));(i.el.M_Datepicker=i).options=g.extend({},B.defaults,e),e&&e.hasOwnProperty("i18n")&&"object"==typeof e.i18n&&(i.options.i18n=g.extend({},B.defaults.i18n,e.i18n)),i.options.minDate&&i.options.minDate.setHours(0,0,0,0),i.options.maxDate&&i.options.maxDate.setHours(0,0,0,0),i.id=M.guid(),i._setupVariables(),i._insertHTMLIntoDOM(),i._setupModal(),i._setupEventHandlers(),i.options.defaultDate||(i.options.defaultDate=new Date(Date.parse(i.el.value)));var n=i.options.defaultDate;return B._isDate(n)?i.options.setDefaultDate?(i.setDate(n,!0),i.setInputValue()):i.gotoDate(n):i.gotoDate(new Date),i.isOpen=!1,i}return _inherits(B,Component),_createClass(B,[{key:"destroy",value:function(){this._removeEventHandlers(),this.modal.destroy(),g(this.modalEl).remove(),this.destroySelects(),this.el.M_Datepicker=void 0}},{key:"destroySelects",value:function(){var t=this.calendarEl.querySelector(".orig-select-year");t&&M.FormSelect.getInstance(t).destroy();var e=this.calendarEl.querySelector(".orig-select-month");e&&M.FormSelect.getInstance(e).destroy()}},{key:"_insertHTMLIntoDOM",value:function(){this.options.showClearBtn&&(g(this.clearBtn).css({visibility:""}),this.clearBtn.innerHTML=this.options.i18n.clear),this.doneBtn.innerHTML=this.options.i18n.done,this.cancelBtn.innerHTML=this.options.i18n.cancel,this.options.container?this.$modalEl.appendTo(this.options.container):this.$modalEl.insertBefore(this.el)}},{key:"_setupModal",value:function(){var t=this;this.modalEl.id="modal-"+this.id,this.modal=M.Modal.init(this.modalEl,{onCloseEnd:function(){t.isOpen=!1}})}},{key:"toString",value:function(t){var e=this;return t=t||this.options.format,B._isDate(this.date)?t.split(/(d{1,4}|m{1,4}|y{4}|yy|!.)/g).map(function(t){return e.formats[t]?e.formats[t]():t}).join(""):""}},{key:"setDate",value:function(t,e){if(!t)return this.date=null,this._renderDateDisplay(),this.draw();if("string"==typeof t&&(t=new Date(Date.parse(t))),B._isDate(t)){var i=this.options.minDate,n=this.options.maxDate;B._isDate(i)&&tn.maxDate||n.disableWeekends&&B._isWeekend(y)||n.disableDayFn&&n.disableDayFn(y),isEmpty:C,isStartRange:x,isEndRange:L,isInRange:T,showDaysInNextAndPreviousMonths:n.showDaysInNextAndPreviousMonths};l.push(this.renderDay($)),7==++_&&(r.push(this.renderRow(l,n.isRTL,m)),_=0,m=!(l=[]))}return this.renderTable(n,r,i)}},{key:"renderDay",value:function(t){var e=[],i="false";if(t.isEmpty){if(!t.showDaysInNextAndPreviousMonths)return'';e.push("is-outside-current-month"),e.push("is-selection-disabled")}return t.isDisabled&&e.push("is-disabled"),t.isToday&&e.push("is-today"),t.isSelected&&(e.push("is-selected"),i="true"),t.hasEvent&&e.push("has-event"),t.isInRange&&e.push("is-inrange"),t.isStartRange&&e.push("is-startrange"),t.isEndRange&&e.push("is-endrange"),'"}},{key:"renderRow",value:function(t,e,i){return''+(e?t.reverse():t).join("")+""}},{key:"renderTable",value:function(t,e,i){return'
    '+this.renderHead(t)+this.renderBody(e)+"
    "}},{key:"renderHead",value:function(t){var e=void 0,i=[];for(e=0;e<7;e++)i.push(''+this.renderDayName(t,e,!0)+"");return""+(t.isRTL?i.reverse():i).join("")+""}},{key:"renderBody",value:function(t){return""+t.join("")+""}},{key:"renderTitle",value:function(t,e,i,n,s,o){var a,r,l=void 0,h=void 0,d=void 0,u=this.options,c=i===u.minYear,p=i===u.maxYear,v='
    ',f=!0,m=!0;for(d=[],l=0;l<12;l++)d.push('");for(a='",g.isArray(u.yearRange)?(l=u.yearRange[0],h=u.yearRange[1]+1):(l=i-u.yearRange,h=1+i+u.yearRange),d=[];l=u.minYear&&d.push('");r='";v+='',v+='
    ',u.showMonthAfterYear?v+=r+a:v+=a+r,v+="
    ",c&&(0===n||u.minMonth>=n)&&(f=!1),p&&(11===n||u.maxMonth<=n)&&(m=!1);return(v+='')+"
    "}},{key:"draw",value:function(t){if(this.isOpen||t){var e,i=this.options,n=i.minYear,s=i.maxYear,o=i.minMonth,a=i.maxMonth,r="";this._y<=n&&(this._y=n,!isNaN(o)&&this._m=s&&(this._y=s,!isNaN(a)&&this._m>a&&(this._m=a)),e="datepicker-title-"+Math.random().toString(36).replace(/[^a-z]+/g,"").substr(0,2);for(var l=0;l<1;l++)this._renderDateDisplay(),r+=this.renderTitle(this,l,this.calendars[l].year,this.calendars[l].month,this.calendars[0].year,e)+this.render(this.calendars[l].year,this.calendars[l].month,e);this.destroySelects(),this.calendarEl.innerHTML=r;var h=this.calendarEl.querySelector(".orig-select-year"),d=this.calendarEl.querySelector(".orig-select-month");M.FormSelect.init(h,{classes:"select-year",dropdownOptions:{container:document.body,constrainWidth:!1}}),M.FormSelect.init(d,{classes:"select-month",dropdownOptions:{container:document.body,constrainWidth:!1}}),h.addEventListener("change",this._handleYearChange.bind(this)),d.addEventListener("change",this._handleMonthChange.bind(this)),"function"==typeof this.options.onDraw&&this.options.onDraw(this)}}},{key:"_setupEventHandlers",value:function(){this._handleInputKeydownBound=this._handleInputKeydown.bind(this),this._handleInputClickBound=this._handleInputClick.bind(this),this._handleInputChangeBound=this._handleInputChange.bind(this),this._handleCalendarClickBound=this._handleCalendarClick.bind(this),this._finishSelectionBound=this._finishSelection.bind(this),this._handleMonthChange=this._handleMonthChange.bind(this),this._closeBound=this.close.bind(this),this.el.addEventListener("click",this._handleInputClickBound),this.el.addEventListener("keydown",this._handleInputKeydownBound),this.el.addEventListener("change",this._handleInputChangeBound),this.calendarEl.addEventListener("click",this._handleCalendarClickBound),this.doneBtn.addEventListener("click",this._finishSelectionBound),this.cancelBtn.addEventListener("click",this._closeBound),this.options.showClearBtn&&(this._handleClearClickBound=this._handleClearClick.bind(this),this.clearBtn.addEventListener("click",this._handleClearClickBound))}},{key:"_setupVariables",value:function(){var e=this;this.$modalEl=g(B._template),this.modalEl=this.$modalEl[0],this.calendarEl=this.modalEl.querySelector(".datepicker-calendar"),this.yearTextEl=this.modalEl.querySelector(".year-text"),this.dateTextEl=this.modalEl.querySelector(".date-text"),this.options.showClearBtn&&(this.clearBtn=this.modalEl.querySelector(".datepicker-clear")),this.doneBtn=this.modalEl.querySelector(".datepicker-done"),this.cancelBtn=this.modalEl.querySelector(".datepicker-cancel"),this.formats={d:function(){return e.date.getDate()},dd:function(){var t=e.date.getDate();return(t<10?"0":"")+t},ddd:function(){return e.options.i18n.weekdaysShort[e.date.getDay()]},dddd:function(){return e.options.i18n.weekdays[e.date.getDay()]},m:function(){return e.date.getMonth()+1},mm:function(){var t=e.date.getMonth()+1;return(t<10?"0":"")+t},mmm:function(){return e.options.i18n.monthsShort[e.date.getMonth()]},mmmm:function(){return e.options.i18n.months[e.date.getMonth()]},yy:function(){return(""+e.date.getFullYear()).slice(2)},yyyy:function(){return e.date.getFullYear()}}}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleInputClickBound),this.el.removeEventListener("keydown",this._handleInputKeydownBound),this.el.removeEventListener("change",this._handleInputChangeBound),this.calendarEl.removeEventListener("click",this._handleCalendarClickBound)}},{key:"_handleInputClick",value:function(){this.open()}},{key:"_handleInputKeydown",value:function(t){t.which===M.keys.ENTER&&(t.preventDefault(),this.open())}},{key:"_handleCalendarClick",value:function(t){if(this.isOpen){var e=g(t.target);e.hasClass("is-disabled")||(!e.hasClass("datepicker-day-button")||e.hasClass("is-empty")||e.parent().hasClass("is-disabled")?e.closest(".month-prev").length?this.prevMonth():e.closest(".month-next").length&&this.nextMonth():(this.setDate(new Date(t.target.getAttribute("data-year"),t.target.getAttribute("data-month"),t.target.getAttribute("data-day"))),this.options.autoClose&&this._finishSelection()))}}},{key:"_handleClearClick",value:function(){this.date=null,this.setInputValue(),this.close()}},{key:"_handleMonthChange",value:function(t){this.gotoMonth(t.target.value)}},{key:"_handleYearChange",value:function(t){this.gotoYear(t.target.value)}},{key:"gotoMonth",value:function(t){isNaN(t)||(this.calendars[0].month=parseInt(t,10),this.adjustCalendars())}},{key:"gotoYear",value:function(t){isNaN(t)||(this.calendars[0].year=parseInt(t,10),this.adjustCalendars())}},{key:"_handleInputChange",value:function(t){var e=void 0;t.firedBy!==this&&(e=this.options.parse?this.options.parse(this.el.value,this.options.format):new Date(Date.parse(this.el.value)),B._isDate(e)&&this.setDate(e))}},{key:"renderDayName",value:function(t,e,i){for(e+=t.firstDay;7<=e;)e-=7;return i?t.i18n.weekdaysAbbrev[e]:t.i18n.weekdays[e]}},{key:"_finishSelection",value:function(){this.setInputValue(),this.close()}},{key:"open",value:function(){if(!this.isOpen)return this.isOpen=!0,"function"==typeof this.options.onOpen&&this.options.onOpen.call(this),this.draw(),this.modal.open(),this}},{key:"close",value:function(){if(this.isOpen)return this.isOpen=!1,"function"==typeof this.options.onClose&&this.options.onClose.call(this),this.modal.close(),this}}],[{key:"init",value:function(t,e){return _get(B.__proto__||Object.getPrototypeOf(B),"init",this).call(this,this,t,e)}},{key:"_isDate",value:function(t){return/Date/.test(Object.prototype.toString.call(t))&&!isNaN(t.getTime())}},{key:"_isWeekend",value:function(t){var e=t.getDay();return 0===e||6===e}},{key:"_setToStartOfDay",value:function(t){B._isDate(t)&&t.setHours(0,0,0,0)}},{key:"_getDaysInMonth",value:function(t,e){return[31,B._isLeapYear(t)?29:28,31,30,31,30,31,31,30,31,30,31][e]}},{key:"_isLeapYear",value:function(t){return t%4==0&&t%100!=0||t%400==0}},{key:"_compareDates",value:function(t,e){return t.getTime()===e.getTime()}},{key:"_setToStartOfDay",value:function(t){B._isDate(t)&&t.setHours(0,0,0,0)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Datepicker}},{key:"defaults",get:function(){return e}}]),B}();t._template=['"].join(""),M.Datepicker=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"datepicker","M_Datepicker")}(cash),function(h){"use strict";var e={dialRadius:135,outerRadius:105,innerRadius:70,tickRadius:20,duration:350,container:null,defaultTime:"now",fromNow:0,showClearBtn:!1,i18n:{cancel:"Cancel",clear:"Clear",done:"Ok"},autoClose:!1,twelveHour:!0,vibrate:!0,onOpenStart:null,onOpenEnd:null,onCloseStart:null,onCloseEnd:null,onSelect:null},t=function(t){function f(t,e){_classCallCheck(this,f);var i=_possibleConstructorReturn(this,(f.__proto__||Object.getPrototypeOf(f)).call(this,f,t,e));return(i.el.M_Timepicker=i).options=h.extend({},f.defaults,e),i.id=M.guid(),i._insertHTMLIntoDOM(),i._setupModal(),i._setupVariables(),i._setupEventHandlers(),i._clockSetup(),i._pickerSetup(),i}return _inherits(f,Component),_createClass(f,[{key:"destroy",value:function(){this._removeEventHandlers(),this.modal.destroy(),h(this.modalEl).remove(),this.el.M_Timepicker=void 0}},{key:"_setupEventHandlers",value:function(){this._handleInputKeydownBound=this._handleInputKeydown.bind(this),this._handleInputClickBound=this._handleInputClick.bind(this),this._handleClockClickStartBound=this._handleClockClickStart.bind(this),this._handleDocumentClickMoveBound=this._handleDocumentClickMove.bind(this),this._handleDocumentClickEndBound=this._handleDocumentClickEnd.bind(this),this.el.addEventListener("click",this._handleInputClickBound),this.el.addEventListener("keydown",this._handleInputKeydownBound),this.plate.addEventListener("mousedown",this._handleClockClickStartBound),this.plate.addEventListener("touchstart",this._handleClockClickStartBound),h(this.spanHours).on("click",this.showView.bind(this,"hours")),h(this.spanMinutes).on("click",this.showView.bind(this,"minutes"))}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleInputClickBound),this.el.removeEventListener("keydown",this._handleInputKeydownBound)}},{key:"_handleInputClick",value:function(){this.open()}},{key:"_handleInputKeydown",value:function(t){t.which===M.keys.ENTER&&(t.preventDefault(),this.open())}},{key:"_handleClockClickStart",value:function(t){t.preventDefault();var e=this.plate.getBoundingClientRect(),i=e.left,n=e.top;this.x0=i+this.options.dialRadius,this.y0=n+this.options.dialRadius,this.moved=!1;var s=f._Pos(t);this.dx=s.x-this.x0,this.dy=s.y-this.y0,this.setHand(this.dx,this.dy,!1),document.addEventListener("mousemove",this._handleDocumentClickMoveBound),document.addEventListener("touchmove",this._handleDocumentClickMoveBound),document.addEventListener("mouseup",this._handleDocumentClickEndBound),document.addEventListener("touchend",this._handleDocumentClickEndBound)}},{key:"_handleDocumentClickMove",value:function(t){t.preventDefault();var e=f._Pos(t),i=e.x-this.x0,n=e.y-this.y0;this.moved=!0,this.setHand(i,n,!1,!0)}},{key:"_handleDocumentClickEnd",value:function(t){var e=this;t.preventDefault(),document.removeEventListener("mouseup",this._handleDocumentClickEndBound),document.removeEventListener("touchend",this._handleDocumentClickEndBound);var i=f._Pos(t),n=i.x-this.x0,s=i.y-this.y0;this.moved&&n===this.dx&&s===this.dy&&this.setHand(n,s),"hours"===this.currentView?this.showView("minutes",this.options.duration/2):this.options.autoClose&&(h(this.minutesView).addClass("timepicker-dial-out"),setTimeout(function(){e.done()},this.options.duration/2)),"function"==typeof this.options.onSelect&&this.options.onSelect.call(this,this.hours,this.minutes),document.removeEventListener("mousemove",this._handleDocumentClickMoveBound),document.removeEventListener("touchmove",this._handleDocumentClickMoveBound)}},{key:"_insertHTMLIntoDOM",value:function(){this.$modalEl=h(f._template),this.modalEl=this.$modalEl[0],this.modalEl.id="modal-"+this.id;var t=document.querySelector(this.options.container);this.options.container&&t?this.$modalEl.appendTo(t):this.$modalEl.insertBefore(this.el)}},{key:"_setupModal",value:function(){var t=this;this.modal=M.Modal.init(this.modalEl,{onOpenStart:this.options.onOpenStart,onOpenEnd:this.options.onOpenEnd,onCloseStart:this.options.onCloseStart,onCloseEnd:function(){"function"==typeof t.options.onCloseEnd&&t.options.onCloseEnd.call(t),t.isOpen=!1}})}},{key:"_setupVariables",value:function(){this.currentView="hours",this.vibrate=navigator.vibrate?"vibrate":navigator.webkitVibrate?"webkitVibrate":null,this._canvas=this.modalEl.querySelector(".timepicker-canvas"),this.plate=this.modalEl.querySelector(".timepicker-plate"),this.hoursView=this.modalEl.querySelector(".timepicker-hours"),this.minutesView=this.modalEl.querySelector(".timepicker-minutes"),this.spanHours=this.modalEl.querySelector(".timepicker-span-hours"),this.spanMinutes=this.modalEl.querySelector(".timepicker-span-minutes"),this.spanAmPm=this.modalEl.querySelector(".timepicker-span-am-pm"),this.footer=this.modalEl.querySelector(".timepicker-footer"),this.amOrPm="PM"}},{key:"_pickerSetup",value:function(){var t=h('").appendTo(this.footer).on("click",this.clear.bind(this));this.options.showClearBtn&&t.css({visibility:""});var e=h('
    ');h('").appendTo(e).on("click",this.close.bind(this)),h('").appendTo(e).on("click",this.done.bind(this)),e.appendTo(this.footer)}},{key:"_clockSetup",value:function(){this.options.twelveHour&&(this.$amBtn=h('
    AM
    '),this.$pmBtn=h('
    PM
    '),this.$amBtn.on("click",this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm),this.$pmBtn.on("click",this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm)),this._buildHoursView(),this._buildMinutesView(),this._buildSVGClock()}},{key:"_buildSVGClock",value:function(){var t=this.options.dialRadius,e=this.options.tickRadius,i=2*t,n=f._createSVGEl("svg");n.setAttribute("class","timepicker-svg"),n.setAttribute("width",i),n.setAttribute("height",i);var s=f._createSVGEl("g");s.setAttribute("transform","translate("+t+","+t+")");var o=f._createSVGEl("circle");o.setAttribute("class","timepicker-canvas-bearing"),o.setAttribute("cx",0),o.setAttribute("cy",0),o.setAttribute("r",4);var a=f._createSVGEl("line");a.setAttribute("x1",0),a.setAttribute("y1",0);var r=f._createSVGEl("circle");r.setAttribute("class","timepicker-canvas-bg"),r.setAttribute("r",e),s.appendChild(a),s.appendChild(r),s.appendChild(o),n.appendChild(s),this._canvas.appendChild(n),this.hand=a,this.bg=r,this.bearing=o,this.g=s}},{key:"_buildHoursView",value:function(){var t=h('
    ');if(this.options.twelveHour)for(var e=1;e<13;e+=1){var i=t.clone(),n=e/6*Math.PI,s=this.options.outerRadius;i.css({left:this.options.dialRadius+Math.sin(n)*s-this.options.tickRadius+"px",top:this.options.dialRadius-Math.cos(n)*s-this.options.tickRadius+"px"}),i.html(0===e?"00":e),this.hoursView.appendChild(i[0])}else for(var o=0;o<24;o+=1){var a=t.clone(),r=o/6*Math.PI,l=0
    '),e=0;e<60;e+=5){var i=t.clone(),n=e/30*Math.PI;i.css({left:this.options.dialRadius+Math.sin(n)*this.options.outerRadius-this.options.tickRadius+"px",top:this.options.dialRadius-Math.cos(n)*this.options.outerRadius-this.options.tickRadius+"px"}),i.html(f._addLeadingZero(e)),this.minutesView.appendChild(i[0])}}},{key:"_handleAmPmClick",value:function(t){var e=h(t.target);this.amOrPm=e.hasClass("am-btn")?"AM":"PM",this._updateAmPmView()}},{key:"_updateAmPmView",value:function(){this.options.twelveHour&&(this.$amBtn.toggleClass("text-primary","AM"===this.amOrPm),this.$pmBtn.toggleClass("text-primary","PM"===this.amOrPm))}},{key:"_updateTimeFromInput",value:function(){var t=((this.el.value||this.options.defaultTime||"")+"").split(":");if(this.options.twelveHour&&void 0!==t[1]&&(0','",""].join(""),M.Timepicker=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"timepicker","M_Timepicker")}(cash),function(s){"use strict";var e={},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_CharacterCounter=i).options=s.extend({},n.defaults,e),i.isInvalid=!1,i.isValidLength=!1,i._setupCounter(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.CharacterCounter=void 0,this._removeCounter()}},{key:"_setupEventHandlers",value:function(){this._handleUpdateCounterBound=this.updateCounter.bind(this),this.el.addEventListener("focus",this._handleUpdateCounterBound,!0),this.el.addEventListener("input",this._handleUpdateCounterBound,!0)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("focus",this._handleUpdateCounterBound,!0),this.el.removeEventListener("input",this._handleUpdateCounterBound,!0)}},{key:"_setupCounter",value:function(){this.counterEl=document.createElement("span"),s(this.counterEl).addClass("character-counter").css({float:"right","font-size":"12px",height:1}),this.$el.parent().append(this.counterEl)}},{key:"_removeCounter",value:function(){s(this.counterEl).remove()}},{key:"updateCounter",value:function(){var t=+this.$el.attr("data-length"),e=this.el.value.length;this.isValidLength=e<=t;var i=e;t&&(i+="/"+t,this._validateInput()),s(this.counterEl).html(i)}},{key:"_validateInput",value:function(){this.isValidLength&&this.isInvalid?(this.isInvalid=!1,this.$el.removeClass("invalid")):this.isValidLength||this.isInvalid||(this.isInvalid=!0,this.$el.removeClass("valid"),this.$el.addClass("invalid"))}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_CharacterCounter}},{key:"defaults",get:function(){return e}}]),n}();M.CharacterCounter=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"characterCounter","M_CharacterCounter")}(cash),function(b){"use strict";var e={duration:200,dist:-100,shift:0,padding:0,numVisible:5,fullWidth:!1,indicators:!1,noWrap:!1,onCycleTo:null},t=function(t){function i(t,e){_classCallCheck(this,i);var n=_possibleConstructorReturn(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,i,t,e));return(n.el.M_Carousel=n).options=b.extend({},i.defaults,e),n.hasMultipleSlides=1'),n.$el.find(".carousel-item").each(function(t,e){if(n.images.push(t),n.showIndicators){var i=b('
  • ');0===e&&i[0].classList.add("active"),n.$indicators.append(i)}}),n.showIndicators&&n.$el.append(n.$indicators),n.count=n.images.length,n.options.numVisible=Math.min(n.count,n.options.numVisible),n.xform="transform",["webkit","Moz","O","ms"].every(function(t){var e=t+"Transform";return void 0===document.body.style[e]||(n.xform=e,!1)}),n._setupEventHandlers(),n._scroll(n.offset),n}return _inherits(i,Component),_createClass(i,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.M_Carousel=void 0}},{key:"_setupEventHandlers",value:function(){var i=this;this._handleCarouselTapBound=this._handleCarouselTap.bind(this),this._handleCarouselDragBound=this._handleCarouselDrag.bind(this),this._handleCarouselReleaseBound=this._handleCarouselRelease.bind(this),this._handleCarouselClickBound=this._handleCarouselClick.bind(this),void 0!==window.ontouchstart&&(this.el.addEventListener("touchstart",this._handleCarouselTapBound),this.el.addEventListener("touchmove",this._handleCarouselDragBound),this.el.addEventListener("touchend",this._handleCarouselReleaseBound)),this.el.addEventListener("mousedown",this._handleCarouselTapBound),this.el.addEventListener("mousemove",this._handleCarouselDragBound),this.el.addEventListener("mouseup",this._handleCarouselReleaseBound),this.el.addEventListener("mouseleave",this._handleCarouselReleaseBound),this.el.addEventListener("click",this._handleCarouselClickBound),this.showIndicators&&this.$indicators&&(this._handleIndicatorClickBound=this._handleIndicatorClick.bind(this),this.$indicators.find(".indicator-item").each(function(t,e){t.addEventListener("click",i._handleIndicatorClickBound)}));var t=M.throttle(this._handleResize,200);this._handleThrottledResizeBound=t.bind(this),window.addEventListener("resize",this._handleThrottledResizeBound)}},{key:"_removeEventHandlers",value:function(){var i=this;void 0!==window.ontouchstart&&(this.el.removeEventListener("touchstart",this._handleCarouselTapBound),this.el.removeEventListener("touchmove",this._handleCarouselDragBound),this.el.removeEventListener("touchend",this._handleCarouselReleaseBound)),this.el.removeEventListener("mousedown",this._handleCarouselTapBound),this.el.removeEventListener("mousemove",this._handleCarouselDragBound),this.el.removeEventListener("mouseup",this._handleCarouselReleaseBound),this.el.removeEventListener("mouseleave",this._handleCarouselReleaseBound),this.el.removeEventListener("click",this._handleCarouselClickBound),this.showIndicators&&this.$indicators&&this.$indicators.find(".indicator-item").each(function(t,e){t.removeEventListener("click",i._handleIndicatorClickBound)}),window.removeEventListener("resize",this._handleThrottledResizeBound)}},{key:"_handleCarouselTap",value:function(t){"mousedown"===t.type&&b(t.target).is("img")&&t.preventDefault(),this.pressed=!0,this.dragged=!1,this.verticalDragged=!1,this.reference=this._xpos(t),this.referenceY=this._ypos(t),this.velocity=this.amplitude=0,this.frame=this.offset,this.timestamp=Date.now(),clearInterval(this.ticker),this.ticker=setInterval(this._trackBound,100)}},{key:"_handleCarouselDrag",value:function(t){var e=void 0,i=void 0,n=void 0;if(this.pressed)if(e=this._xpos(t),i=this._ypos(t),n=this.reference-e,Math.abs(this.referenceY-i)<30&&!this.verticalDragged)(2=this.dim*(this.count-1)?this.target=this.dim*(this.count-1):this.target<0&&(this.target=0)),this.amplitude=this.target-this.offset,this.timestamp=Date.now(),requestAnimationFrame(this._autoScrollBound),this.dragged&&(t.preventDefault(),t.stopPropagation()),!1}},{key:"_handleCarouselClick",value:function(t){if(this.dragged)return t.preventDefault(),t.stopPropagation(),!1;if(!this.options.fullWidth){var e=b(t.target).closest(".carousel-item").index();0!==this._wrap(this.center)-e&&(t.preventDefault(),t.stopPropagation()),this._cycleTo(e)}}},{key:"_handleIndicatorClick",value:function(t){t.stopPropagation();var e=b(t.target).closest(".indicator-item");e.length&&this._cycleTo(e.index())}},{key:"_handleResize",value:function(t){this.options.fullWidth?(this.itemWidth=this.$el.find(".carousel-item").first().innerWidth(),this.imageHeight=this.$el.find(".carousel-item.active").height(),this.dim=2*this.itemWidth+this.options.padding,this.offset=2*this.center*this.itemWidth,this.target=this.offset,this._setCarouselHeight(!0)):this._scroll()}},{key:"_setCarouselHeight",value:function(t){var i=this,e=this.$el.find(".carousel-item.active").length?this.$el.find(".carousel-item.active").first():this.$el.find(".carousel-item").first(),n=e.find("img").first();if(n.length)if(n[0].complete){var s=n.height();if(0=this.count?t%this.count:t<0?this._wrap(this.count+t%this.count):t}},{key:"_track",value:function(){var t,e,i,n;e=(t=Date.now())-this.timestamp,this.timestamp=t,i=this.offset-this.frame,this.frame=this.offset,n=1e3*i/(1+e),this.velocity=.8*n+.2*this.velocity}},{key:"_autoScroll",value:function(){var t=void 0,e=void 0;this.amplitude&&(t=Date.now()-this.timestamp,2<(e=this.amplitude*Math.exp(-t/this.options.duration))||e<-2?(this._scroll(this.target-e),requestAnimationFrame(this._autoScrollBound)):this._scroll(this.target))}},{key:"_scroll",value:function(t){var e=this;this.$el.hasClass("scrolling")||this.el.classList.add("scrolling"),null!=this.scrollingTimeout&&window.clearTimeout(this.scrollingTimeout),this.scrollingTimeout=window.setTimeout(function(){e.$el.removeClass("scrolling")},this.options.duration);var i,n,s,o,a=void 0,r=void 0,l=void 0,h=void 0,d=void 0,u=void 0,c=this.center,p=1/this.options.numVisible;if(this.offset="number"==typeof t?t:this.offset,this.center=Math.floor((this.offset+this.dim/2)/this.dim),o=-(s=(n=this.offset-this.center*this.dim)<0?1:-1)*n*2/this.dim,i=this.count>>1,this.options.fullWidth?(l="translateX(0)",u=1):(l="translateX("+(this.el.clientWidth-this.itemWidth)/2+"px) ",l+="translateY("+(this.el.clientHeight-this.itemHeight)/2+"px)",u=1-p*o),this.showIndicators){var v=this.center%this.count,f=this.$indicators.find(".indicator-item.active");f.index()!==v&&(f.removeClass("active"),this.$indicators.find(".indicator-item").eq(v)[0].classList.add("active"))}if(!this.noWrap||0<=this.center&&this.center=this.count||e<0){if(this.noWrap)return;e=this._wrap(e)}this._cycleTo(e)}},{key:"prev",value:function(t){(void 0===t||isNaN(t))&&(t=1);var e=this.center-t;if(e>=this.count||e<0){if(this.noWrap)return;e=this._wrap(e)}this._cycleTo(e)}},{key:"set",value:function(t,e){if((void 0===t||isNaN(t))&&(t=0),t>this.count||t<0){if(this.noWrap)return;t=this._wrap(t)}this._cycleTo(t,e)}}],[{key:"init",value:function(t,e){return _get(i.__proto__||Object.getPrototypeOf(i),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Carousel}},{key:"defaults",get:function(){return e}}]),i}();M.Carousel=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"carousel","M_Carousel")}(cash),function(S){"use strict";var e={onOpen:void 0,onClose:void 0},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_TapTarget=i).options=S.extend({},n.defaults,e),i.isOpen=!1,i.$origin=S("#"+i.$el.attr("data-target")),i._setup(),i._calculatePositioning(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.TapTarget=void 0}},{key:"_setupEventHandlers",value:function(){this._handleDocumentClickBound=this._handleDocumentClick.bind(this),this._handleTargetClickBound=this._handleTargetClick.bind(this),this._handleOriginClickBound=this._handleOriginClick.bind(this),this.el.addEventListener("click",this._handleTargetClickBound),this.originEl.addEventListener("click",this._handleOriginClickBound);var t=M.throttle(this._handleResize,200);this._handleThrottledResizeBound=t.bind(this),window.addEventListener("resize",this._handleThrottledResizeBound)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleTargetClickBound),this.originEl.removeEventListener("click",this._handleOriginClickBound),window.removeEventListener("resize",this._handleThrottledResizeBound)}},{key:"_handleTargetClick",value:function(t){this.open()}},{key:"_handleOriginClick",value:function(t){this.close()}},{key:"_handleResize",value:function(t){this._calculatePositioning()}},{key:"_handleDocumentClick",value:function(t){S(t.target).closest(".tap-target-wrapper").length||(this.close(),t.preventDefault(),t.stopPropagation())}},{key:"_setup",value:function(){this.wrapper=this.$el.parent()[0],this.waveEl=S(this.wrapper).find(".tap-target-wave")[0],this.originEl=S(this.wrapper).find(".tap-target-origin")[0],this.contentEl=this.$el.find(".tap-target-content")[0],S(this.wrapper).hasClass(".tap-target-wrapper")||(this.wrapper=document.createElement("div"),this.wrapper.classList.add("tap-target-wrapper"),this.$el.before(S(this.wrapper)),this.wrapper.append(this.el)),this.contentEl||(this.contentEl=document.createElement("div"),this.contentEl.classList.add("tap-target-content"),this.$el.append(this.contentEl)),this.waveEl||(this.waveEl=document.createElement("div"),this.waveEl.classList.add("tap-target-wave"),this.originEl||(this.originEl=this.$origin.clone(!0,!0),this.originEl.addClass("tap-target-origin"),this.originEl.removeAttr("id"),this.originEl.removeAttr("style"),this.originEl=this.originEl[0],this.waveEl.append(this.originEl)),this.wrapper.append(this.waveEl))}},{key:"_calculatePositioning",value:function(){var t="fixed"===this.$origin.css("position");if(!t)for(var e=this.$origin.parents(),i=0;i'+t.getAttribute("label")+"")[0]),i.each(function(t){var e=n._appendOptionWithIcon(n.$el,t,"optgroup-option");n._addOptionToValueDict(t,e)})}}),this.$el.after(this.dropdownOptions),this.input=document.createElement("input"),d(this.input).addClass("select-dropdown dropdown-trigger"),this.input.setAttribute("type","text"),this.input.setAttribute("readonly","true"),this.input.setAttribute("data-target",this.dropdownOptions.id),this.el.disabled&&d(this.input).prop("disabled","true"),this.$el.before(this.input),this._setValueToInput();var t=d('');if(this.$el.before(t[0]),!this.el.disabled){var e=d.extend({},this.options.dropdownOptions);e.onOpenEnd=function(t){var e=d(n.dropdownOptions).find(".selected").first();if(e.length&&(M.keyDown=!0,n.dropdown.focusedIndex=e.index(),n.dropdown._focusFocusedItem(),M.keyDown=!1,n.dropdown.isScrollable)){var i=e[0].getBoundingClientRect().top-n.dropdownOptions.getBoundingClientRect().top;i-=n.dropdownOptions.clientHeight/2,n.dropdownOptions.scrollTop=i}},this.isMultiple&&(e.closeOnClick=!1),this.dropdown=M.Dropdown.init(this.input,e)}this._setSelectedStates()}},{key:"_addOptionToValueDict",value:function(t,e){var i=Object.keys(this._valueDict).length,n=this.dropdownOptions.id+i,s={};e.id=n,s.el=t,s.optionEl=e,this._valueDict[n]=s}},{key:"_removeDropdown",value:function(){d(this.wrapper).find(".caret").remove(),d(this.input).remove(),d(this.dropdownOptions).remove(),d(this.wrapper).before(this.$el),d(this.wrapper).remove()}},{key:"_appendOptionWithIcon",value:function(t,e,i){var n=e.disabled?"disabled ":"",s="optgroup-option"===i?"optgroup-option ":"",o=this.isMultiple?'":e.innerHTML,a=d("
  • "),r=d("");r.html(o),a.addClass(n+" "+s),a.append(r);var l=e.getAttribute("data-icon");if(l){var h=d('');a.prepend(h)}return d(this.dropdownOptions).append(a[0]),a[0]}},{key:"_toggleEntryFromArray",value:function(t){var e=!this._keysSelected.hasOwnProperty(t),i=d(this._valueDict[t].optionEl);return e?this._keysSelected[t]=!0:delete this._keysSelected[t],i.toggleClass("selected",e),i.find('input[type="checkbox"]').prop("checked",e),i.prop("selected",e),e}},{key:"_setValueToInput",value:function(){var i=[];if(this.$el.find("option").each(function(t){if(d(t).prop("selected")){var e=d(t).text();i.push(e)}}),!i.length){var t=this.$el.find("option:disabled").eq(0);t.length&&""===t[0].value&&i.push(t.text())}this.input.value=i.join(", ")}},{key:"_setSelectedStates",value:function(){for(var t in this._keysSelected={},this._valueDict){var e=this._valueDict[t],i=d(e.el).prop("selected");d(e.optionEl).find('input[type="checkbox"]').prop("checked",i),i?(this._activateOption(d(this.dropdownOptions),d(e.optionEl)),this._keysSelected[t]=!0):d(e.optionEl).removeClass("selected")}}},{key:"_activateOption",value:function(t,e){e&&(this.isMultiple||t.find("li.selected").removeClass("selected"),d(e).addClass("selected"))}},{key:"getSelectedValues",value:function(){var t=[];for(var e in this._keysSelected)t.push(this._valueDict[e].el.value);return t}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_FormSelect}},{key:"defaults",get:function(){return e}}]),n}();M.FormSelect=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"formSelect","M_FormSelect")}(cash),function(s,e){"use strict";var i={},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_Range=i).options=s.extend({},n.defaults,e),i._mousedown=!1,i._setupThumb(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this._removeThumb(),this.el.M_Range=void 0}},{key:"_setupEventHandlers",value:function(){this._handleRangeChangeBound=this._handleRangeChange.bind(this),this._handleRangeMousedownTouchstartBound=this._handleRangeMousedownTouchstart.bind(this),this._handleRangeInputMousemoveTouchmoveBound=this._handleRangeInputMousemoveTouchmove.bind(this),this._handleRangeMouseupTouchendBound=this._handleRangeMouseupTouchend.bind(this),this._handleRangeBlurMouseoutTouchleaveBound=this._handleRangeBlurMouseoutTouchleave.bind(this),this.el.addEventListener("change",this._handleRangeChangeBound),this.el.addEventListener("mousedown",this._handleRangeMousedownTouchstartBound),this.el.addEventListener("touchstart",this._handleRangeMousedownTouchstartBound),this.el.addEventListener("input",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("mousemove",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("touchmove",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("mouseup",this._handleRangeMouseupTouchendBound),this.el.addEventListener("touchend",this._handleRangeMouseupTouchendBound),this.el.addEventListener("blur",this._handleRangeBlurMouseoutTouchleaveBound),this.el.addEventListener("mouseout",this._handleRangeBlurMouseoutTouchleaveBound),this.el.addEventListener("touchleave",this._handleRangeBlurMouseoutTouchleaveBound)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("change",this._handleRangeChangeBound),this.el.removeEventListener("mousedown",this._handleRangeMousedownTouchstartBound),this.el.removeEventListener("touchstart",this._handleRangeMousedownTouchstartBound),this.el.removeEventListener("input",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("mousemove",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("touchmove",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("mouseup",this._handleRangeMouseupTouchendBound),this.el.removeEventListener("touchend",this._handleRangeMouseupTouchendBound),this.el.removeEventListener("blur",this._handleRangeBlurMouseoutTouchleaveBound),this.el.removeEventListener("mouseout",this._handleRangeBlurMouseoutTouchleaveBound),this.el.removeEventListener("touchleave",this._handleRangeBlurMouseoutTouchleaveBound)}},{key:"_handleRangeChange",value:function(){s(this.value).html(this.$el.val()),s(this.thumb).hasClass("active")||this._showRangeBubble();var t=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",t+"px")}},{key:"_handleRangeMousedownTouchstart",value:function(t){if(s(this.value).html(this.$el.val()),this._mousedown=!0,this.$el.addClass("active"),s(this.thumb).hasClass("active")||this._showRangeBubble(),"input"!==t.type){var e=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",e+"px")}}},{key:"_handleRangeInputMousemoveTouchmove",value:function(){if(this._mousedown){s(this.thumb).hasClass("active")||this._showRangeBubble();var t=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",t+"px"),s(this.value).html(this.$el.val())}}},{key:"_handleRangeMouseupTouchend",value:function(){this._mousedown=!1,this.$el.removeClass("active")}},{key:"_handleRangeBlurMouseoutTouchleave",value:function(){if(!this._mousedown){var t=7+parseInt(this.$el.css("padding-left"))+"px";s(this.thumb).hasClass("active")&&(e.remove(this.thumb),e({targets:this.thumb,height:0,width:0,top:10,easing:"easeOutQuad",marginLeft:t,duration:100})),s(this.thumb).removeClass("active")}}},{key:"_setupThumb",value:function(){this.thumb=document.createElement("span"),this.value=document.createElement("span"),s(this.thumb).addClass("thumb"),s(this.value).addClass("value"),s(this.thumb).append(this.value),this.$el.after(this.thumb)}},{key:"_removeThumb",value:function(){s(this.thumb).remove()}},{key:"_showRangeBubble",value:function(){var t=-7+parseInt(s(this.thumb).parent().css("padding-left"))+"px";e.remove(this.thumb),e({targets:this.thumb,height:30,width:30,top:-30,marginLeft:t,duration:300,easing:"easeOutQuint"})}},{key:"_calcRangeOffset",value:function(){var t=this.$el.width()-15,e=parseFloat(this.$el.attr("max"))||100,i=parseFloat(this.$el.attr("min"))||0;return(parseFloat(this.$el.val())-i)/(e-i)*t}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Range}},{key:"defaults",get:function(){return i}}]),n}();M.Range=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"range","M_Range"),t.init(s("input[type=range]"))}(cash,M.anime); diff --git a/esphome/dashboard/templates/index.html b/esphome/dashboard/templates/index.html deleted file mode 100644 index b10901f5d4..0000000000 --- a/esphome/dashboard/templates/index.html +++ /dev/null @@ -1,678 +0,0 @@ - - - - - - - Dashboard - ESPHome - - - - - - - - - - - - - - - {% if streamer_mode %} - - {% end %} - - - - - - -
    -
    - -
    - - {% for i, entry in enumerate(entries) %} -
    -
    - - {{ escape(entry.name) }} - - more_vert - - {% if 'web_server' in entry.loaded_integrations %} - launch - {% end %} - - {% if entry.update_available %} - system_update - {% end %} - - -
    - Filename: - - {{ escape(entry.filename) }} - -
    - - {% if entry.comment %} -
    - {{ escape(entry.comment) }} -
    - {% end %} - -
    -
    - Edit - Validate - Upload - Logs -
    - -
    - {% end %} - -
    - -
    - - {% if len(entries) == 0 %} -
    -
    Welcome to ESPHome
    -

    It looks like you don't yet have any Nodes configured.

    -

    Click on the pulsating button at the bottom right of the page to add a Node.

    -
    - - - {% end %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - {% 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 14116484af..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/wizard.py b/esphome/wizard.py index 7d875b7dd2..0d912e4bbf 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -49,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: @@ -83,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 @@ -97,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" diff --git a/requirements.txt b/requirements.txt index 5915c2963f..e7f69865da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 +esphome-dashboard==20210611.0 From f599c36272c448192ddfad0eb1b68c9b04dd2d8a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 12 Jun 2021 11:03:09 +1200 Subject: [PATCH 0903/1841] Bump version to v1.19.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 30df9abe17..f7ad39b95d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b2" +PATCH_VERSION = "0b3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 75b524ddc470ef083fc1dc3032492361d080df2d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 12 Jun 2021 11:07:43 +1200 Subject: [PATCH 0904/1841] Fix formatting from cherry-pick conflict --- .../esp32_ble_server/ble_characteristic.cpp | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index 23b2693839..62775ab05f 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -148,39 +148,39 @@ bool BLECharacteristic::is_failed() { void BLECharacteristic::set_broadcast_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST); else - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); } void BLECharacteristic::set_indicate_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE); else - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); } void BLECharacteristic::set_notify_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY); else - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); } void BLECharacteristic::set_read_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ); else - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ); } void BLECharacteristic::set_write_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE); else - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE); } void BLECharacteristic::set_write_no_response_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); else - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); } void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, From 0efc1f06f2538cb5caf78ccd666e2171adc97c8d Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 14 Jun 2021 18:01:56 +0200 Subject: [PATCH 0905/1841] Split files in light component (#1893) --- .../components/light/addressable_light.cpp | 170 ---- esphome/components/light/addressable_light.h | 263 +------ .../components/light/esp_color_correction.cpp | 26 + .../components/light/esp_color_correction.h | 78 ++ esphome/components/light/esp_color_view.h | 110 +++ esphome/components/light/esp_hsv_color.cpp | 74 ++ esphome/components/light/esp_hsv_color.h | 36 + esphome/components/light/esp_range_view.cpp | 96 +++ esphome/components/light/esp_range_view.h | 75 ++ esphome/components/light/light_call.cpp | 522 +++++++++++++ esphome/components/light/light_call.h | 160 ++++ esphome/components/light/light_state.cpp | 737 +++--------------- esphome/components/light/light_state.h | 216 +---- esphome/core/optional.h | 2 + 14 files changed, 1334 insertions(+), 1231 deletions(-) create mode 100644 esphome/components/light/esp_color_correction.cpp create mode 100644 esphome/components/light/esp_color_correction.h create mode 100644 esphome/components/light/esp_color_view.h create mode 100644 esphome/components/light/esp_hsv_color.cpp create mode 100644 esphome/components/light/esp_hsv_color.h create mode 100644 esphome/components/light/esp_range_view.cpp create mode 100644 esphome/components/light/esp_range_view.h create mode 100644 esphome/components/light/light_call.cpp create mode 100644 esphome/components/light/light_call.h diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index ecc48e32b8..e3ab9596d9 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -6,158 +6,6 @@ namespace light { static const char *const TAG = "light.addressable"; -Color ESPHSVColor::to_rgb() const { - // based on FastLED's hsv rainbow to rgb - const uint8_t hue = this->hue; - const uint8_t sat = this->saturation; - const uint8_t val = this->value; - // upper 3 hue bits are for branch selection, lower 5 are for values - const uint8_t offset8 = (hue & 0x1F) << 3; // 0..248 - // third of the offset, 255/3 = 85 (actually only up to 82; 164) - const uint8_t third = esp_scale8(offset8, 85); - const uint8_t two_thirds = esp_scale8(offset8, 170); - Color rgb(255, 255, 255, 0); - switch (hue >> 5) { - case 0b000: - rgb.r = 255 - third; - rgb.g = third; - rgb.b = 0; - break; - case 0b001: - rgb.r = 171; - rgb.g = 85 + third; - rgb.b = 0; - break; - case 0b010: - rgb.r = 171 - two_thirds; - rgb.g = 170 + third; - rgb.b = 0; - break; - case 0b011: - rgb.r = 0; - rgb.g = 255 - third; - rgb.b = third; - break; - case 0b100: - rgb.r = 0; - rgb.g = 171 - two_thirds; - rgb.b = 85 + two_thirds; - break; - case 0b101: - rgb.r = third; - rgb.g = 0; - rgb.b = 255 - third; - break; - case 0b110: - rgb.r = 85 + third; - rgb.g = 0; - rgb.b = 171 - third; - break; - case 0b111: - rgb.r = 170 + third; - rgb.g = 0; - rgb.b = 85 - third; - break; - default: - break; - } - // low saturation -> add uniform color to orig. hue - // high saturation -> use hue directly - // scales with square of saturation - // (r,g,b) = (r,g,b) * sat + (1 - sat)^2 - rgb *= sat; - const uint8_t desat = 255 - sat; - rgb += esp_scale8(desat, desat); - // (r,g,b) = (r,g,b) * val - rgb *= val; - return rgb; -} - -void ESPRangeView::set(const Color &color) { - for (int32_t i = this->begin_; i < this->end_; i++) { - (*this->parent_)[i] = color; - } -} -ESPColorView ESPRangeView::operator[](int32_t index) const { - index = interpret_index(index, this->size()) + this->begin_; - return (*this->parent_)[index]; -} -ESPRangeIterator ESPRangeView::begin() { return {*this, this->begin_}; } -ESPRangeIterator ESPRangeView::end() { return {*this, this->end_}; } -void ESPRangeView::set_red(uint8_t red) { - for (auto c : *this) - c.set_red(red); -} -void ESPRangeView::set_green(uint8_t green) { - for (auto c : *this) - c.set_green(green); -} -void ESPRangeView::set_blue(uint8_t blue) { - for (auto c : *this) - c.set_blue(blue); -} -void ESPRangeView::set_white(uint8_t white) { - for (auto c : *this) - c.set_white(white); -} -void ESPRangeView::set_effect_data(uint8_t effect_data) { - for (auto c : *this) - c.set_effect_data(effect_data); -} -void ESPRangeView::fade_to_white(uint8_t amnt) { - for (auto c : *this) - c.fade_to_white(amnt); -} -void ESPRangeView::fade_to_black(uint8_t amnt) { - for (auto c : *this) - c.fade_to_black(amnt); -} -void ESPRangeView::lighten(uint8_t delta) { - for (auto c : *this) - c.lighten(delta); -} -void ESPRangeView::darken(uint8_t delta) { - for (auto c : *this) - c.darken(delta); -} -ESPRangeView &ESPRangeView::operator=(const ESPRangeView &rhs) { // NOLINT - // If size doesn't match, error (todo warning) - if (rhs.size() != this->size()) - return *this; - - if (this->parent_ != rhs.parent_) { - for (int32_t i = 0; i < this->size(); i++) - (*this)[i].set(rhs[i].get()); - return *this; - } - - // If both equal, already done - if (rhs.begin_ == this->begin_) - return *this; - - if (rhs.begin_ > this->begin_) { - // Copy from left - for (int32_t i = 0; i < this->size(); i++) { - (*this)[i].set(rhs[i].get()); - } - } else { - // Copy from right - for (int32_t i = this->size() - 1; i >= 0; i--) { - (*this)[i].set(rhs[i].get()); - } - } - - return *this; -} - -ESPColorView ESPRangeIterator::operator*() const { return this->range_.parent_->get(this->i_); } - -int32_t HOT interpret_index(int32_t index, int32_t size) { - if (index < 0) - return size + index; - return index; -} - void AddressableLight::call_setup() { this->setup(); @@ -254,23 +102,5 @@ void AddressableLight::write_state(LightState *state) { this->schedule_show(); } -void ESPColorCorrection::calculate_gamma_table(float gamma) { - for (uint16_t i = 0; i < 256; i++) { - // corrected = val ^ gamma - auto corrected = static_cast(roundf(255.0f * gamma_correct(i / 255.0f, gamma))); - this->gamma_table_[i] = corrected; - } - if (gamma == 0.0f) { - for (uint16_t i = 0; i < 256; i++) - this->gamma_reverse_table_[i] = i; - return; - } - for (uint16_t i = 0; i < 256; i++) { - // val = corrected ^ (1/gamma) - auto uncorrected = static_cast(roundf(255.0f * powf(i / 255.0f, 1.0f / gamma))); - this->gamma_reverse_table_[i] = uncorrected; - } -} - } // namespace light } // namespace esphome diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index 39bd905c65..460cb88935 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -3,6 +3,9 @@ #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/color.h" +#include "esp_color_correction.h" +#include "esp_color_view.h" +#include "esp_range_view.h" #include "light_output.h" #include "light_state.h" @@ -15,266 +18,6 @@ namespace light { using ESPColor = Color; -struct ESPHSVColor { - union { - struct { - union { - uint8_t hue; - uint8_t h; - }; - union { - uint8_t saturation; - uint8_t s; - }; - union { - uint8_t value; - uint8_t v; - }; - }; - uint8_t raw[3]; - }; - inline ESPHSVColor() ALWAYS_INLINE : h(0), s(0), v(0) { // NOLINT - } - inline ESPHSVColor(uint8_t hue, uint8_t saturation, uint8_t value) ALWAYS_INLINE : hue(hue), - saturation(saturation), - value(value) {} - Color to_rgb() const; -}; - -class ESPColorCorrection { - public: - ESPColorCorrection() : max_brightness_(255, 255, 255, 255) {} - void set_max_brightness(const Color &max_brightness) { this->max_brightness_ = max_brightness; } - void set_local_brightness(uint8_t local_brightness) { this->local_brightness_ = local_brightness; } - void calculate_gamma_table(float gamma); - inline Color color_correct(Color color) const ALWAYS_INLINE { - // corrected = (uncorrected * max_brightness * local_brightness) ^ gamma - return Color(this->color_correct_red(color.red), this->color_correct_green(color.green), - this->color_correct_blue(color.blue), this->color_correct_white(color.white)); - } - inline uint8_t color_correct_red(uint8_t red) const ALWAYS_INLINE { - uint8_t res = esp_scale8(esp_scale8(red, this->max_brightness_.red), this->local_brightness_); - return this->gamma_table_[res]; - } - inline uint8_t color_correct_green(uint8_t green) const ALWAYS_INLINE { - uint8_t res = esp_scale8(esp_scale8(green, this->max_brightness_.green), this->local_brightness_); - return this->gamma_table_[res]; - } - inline uint8_t color_correct_blue(uint8_t blue) const ALWAYS_INLINE { - uint8_t res = esp_scale8(esp_scale8(blue, this->max_brightness_.blue), this->local_brightness_); - return this->gamma_table_[res]; - } - inline uint8_t color_correct_white(uint8_t white) const ALWAYS_INLINE { - // do not scale white value with brightness - uint8_t res = esp_scale8(white, this->max_brightness_.white); - return this->gamma_table_[res]; - } - inline Color color_uncorrect(Color color) const ALWAYS_INLINE { - // uncorrected = corrected^(1/gamma) / (max_brightness * local_brightness) - return Color(this->color_uncorrect_red(color.red), this->color_uncorrect_green(color.green), - this->color_uncorrect_blue(color.blue), this->color_uncorrect_white(color.white)); - } - inline uint8_t color_uncorrect_red(uint8_t red) const ALWAYS_INLINE { - if (this->max_brightness_.red == 0 || this->local_brightness_ == 0) - return 0; - uint16_t uncorrected = this->gamma_reverse_table_[red] * 255UL; - uint8_t res = ((uncorrected / this->max_brightness_.red) * 255UL) / this->local_brightness_; - return res; - } - inline uint8_t color_uncorrect_green(uint8_t green) const ALWAYS_INLINE { - if (this->max_brightness_.green == 0 || this->local_brightness_ == 0) - return 0; - uint16_t uncorrected = this->gamma_reverse_table_[green] * 255UL; - uint8_t res = ((uncorrected / this->max_brightness_.green) * 255UL) / this->local_brightness_; - return res; - } - inline uint8_t color_uncorrect_blue(uint8_t blue) const ALWAYS_INLINE { - if (this->max_brightness_.blue == 0 || this->local_brightness_ == 0) - return 0; - uint16_t uncorrected = this->gamma_reverse_table_[blue] * 255UL; - uint8_t res = ((uncorrected / this->max_brightness_.blue) * 255UL) / this->local_brightness_; - return res; - } - inline uint8_t color_uncorrect_white(uint8_t white) const ALWAYS_INLINE { - if (this->max_brightness_.white == 0) - return 0; - uint16_t uncorrected = this->gamma_reverse_table_[white] * 255UL; - uint8_t res = uncorrected / this->max_brightness_.white; - return res; - } - - protected: - uint8_t gamma_table_[256]; - uint8_t gamma_reverse_table_[256]; - Color max_brightness_; - uint8_t local_brightness_{255}; -}; - -class ESPColorSettable { - public: - virtual void set(const Color &color) = 0; - virtual void set_red(uint8_t red) = 0; - virtual void set_green(uint8_t green) = 0; - virtual void set_blue(uint8_t blue) = 0; - virtual void set_white(uint8_t white) = 0; - virtual void set_effect_data(uint8_t effect_data) = 0; - virtual void fade_to_white(uint8_t amnt) = 0; - virtual void fade_to_black(uint8_t amnt) = 0; - virtual void lighten(uint8_t delta) = 0; - virtual void darken(uint8_t delta) = 0; - void set(const ESPHSVColor &color) { this->set_hsv(color); } - void set_hsv(const ESPHSVColor &color) { - Color rgb = color.to_rgb(); - this->set_rgb(rgb.r, rgb.g, rgb.b); - } - void set_rgb(uint8_t red, uint8_t green, uint8_t blue) { - this->set_red(red); - this->set_green(green); - this->set_blue(blue); - } - void set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) { - this->set_rgb(red, green, blue); - this->set_white(white); - } -}; - -class ESPColorView : public ESPColorSettable { - public: - ESPColorView(uint8_t *red, uint8_t *green, uint8_t *blue, uint8_t *white, uint8_t *effect_data, - const ESPColorCorrection *color_correction) - : red_(red), - green_(green), - blue_(blue), - white_(white), - effect_data_(effect_data), - color_correction_(color_correction) {} - ESPColorView &operator=(const Color &rhs) { - this->set(rhs); - return *this; - } - ESPColorView &operator=(const ESPHSVColor &rhs) { - this->set_hsv(rhs); - return *this; - } - void set(const Color &color) override { this->set_rgbw(color.r, color.g, color.b, color.w); } - void set_red(uint8_t red) override { *this->red_ = this->color_correction_->color_correct_red(red); } - void set_green(uint8_t green) override { *this->green_ = this->color_correction_->color_correct_green(green); } - void set_blue(uint8_t blue) override { *this->blue_ = this->color_correction_->color_correct_blue(blue); } - void set_white(uint8_t white) override { - if (this->white_ == nullptr) - return; - *this->white_ = this->color_correction_->color_correct_white(white); - } - void set_effect_data(uint8_t effect_data) override { - if (this->effect_data_ == nullptr) - return; - *this->effect_data_ = effect_data; - } - void fade_to_white(uint8_t amnt) override { this->set(this->get().fade_to_white(amnt)); } - void fade_to_black(uint8_t amnt) override { this->set(this->get().fade_to_black(amnt)); } - void lighten(uint8_t delta) override { this->set(this->get().lighten(delta)); } - void darken(uint8_t delta) override { this->set(this->get().darken(delta)); } - Color get() const { return Color(this->get_red(), this->get_green(), this->get_blue(), this->get_white()); } - uint8_t get_red() const { return this->color_correction_->color_uncorrect_red(*this->red_); } - uint8_t get_red_raw() const { return *this->red_; } - uint8_t get_green() const { return this->color_correction_->color_uncorrect_green(*this->green_); } - uint8_t get_green_raw() const { return *this->green_; } - uint8_t get_blue() const { return this->color_correction_->color_uncorrect_blue(*this->blue_); } - uint8_t get_blue_raw() const { return *this->blue_; } - uint8_t get_white() const { - if (this->white_ == nullptr) - return 0; - return this->color_correction_->color_uncorrect_white(*this->white_); - } - uint8_t get_white_raw() const { - if (this->white_ == nullptr) - return 0; - return *this->white_; - } - uint8_t get_effect_data() const { - if (this->effect_data_ == nullptr) - return 0; - return *this->effect_data_; - } - void raw_set_color_correction(const ESPColorCorrection *color_correction) { - this->color_correction_ = color_correction; - } - - protected: - uint8_t *const red_; - uint8_t *const green_; - uint8_t *const blue_; - uint8_t *const white_; - uint8_t *const effect_data_; - const ESPColorCorrection *color_correction_; -}; - -class AddressableLight; - -int32_t interpret_index(int32_t index, int32_t size); - -class ESPRangeIterator; - -class ESPRangeView : public ESPColorSettable { - public: - ESPRangeView(AddressableLight *parent, int32_t begin, int32_t an_end) : parent_(parent), begin_(begin), end_(an_end) { - if (this->end_ < this->begin_) { - this->end_ = this->begin_; - } - } - - ESPColorView operator[](int32_t index) const; - ESPRangeIterator begin(); - ESPRangeIterator end(); - - void set(const Color &color) override; - ESPRangeView &operator=(const Color &rhs) { - this->set(rhs); - return *this; - } - ESPRangeView &operator=(const ESPColorView &rhs) { - this->set(rhs.get()); - return *this; - } - ESPRangeView &operator=(const ESPHSVColor &rhs) { - this->set_hsv(rhs); - return *this; - } - ESPRangeView &operator=(const ESPRangeView &rhs); - void set_red(uint8_t red) override; - void set_green(uint8_t green) override; - void set_blue(uint8_t blue) override; - void set_white(uint8_t white) override; - void set_effect_data(uint8_t effect_data) override; - void fade_to_white(uint8_t amnt) override; - void fade_to_black(uint8_t amnt) override; - void lighten(uint8_t delta) override; - void darken(uint8_t delta) override; - int32_t size() const { return this->end_ - this->begin_; } - - protected: - friend ESPRangeIterator; - - AddressableLight *parent_; - int32_t begin_; - int32_t end_; -}; - -class ESPRangeIterator { - public: - ESPRangeIterator(const ESPRangeView &range, int32_t i) : range_(range), i_(i) {} - ESPRangeIterator operator++() { - this->i_++; - return *this; - } - bool operator!=(const ESPRangeIterator &other) const { return this->i_ != other.i_; } - ESPColorView operator*() const; - - protected: - ESPRangeView range_; - int32_t i_; -}; - class AddressableLight : public LightOutput, public Component { public: virtual int32_t size() const = 0; diff --git a/esphome/components/light/esp_color_correction.cpp b/esphome/components/light/esp_color_correction.cpp new file mode 100644 index 0000000000..19a2af3da1 --- /dev/null +++ b/esphome/components/light/esp_color_correction.cpp @@ -0,0 +1,26 @@ +#include "esp_color_correction.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace light { + +void ESPColorCorrection::calculate_gamma_table(float gamma) { + for (uint16_t i = 0; i < 256; i++) { + // corrected = val ^ gamma + auto corrected = static_cast(roundf(255.0f * gamma_correct(i / 255.0f, gamma))); + this->gamma_table_[i] = corrected; + } + if (gamma == 0.0f) { + for (uint16_t i = 0; i < 256; i++) + this->gamma_reverse_table_[i] = i; + return; + } + for (uint16_t i = 0; i < 256; i++) { + // val = corrected ^ (1/gamma) + auto uncorrected = static_cast(roundf(255.0f * powf(i / 255.0f, 1.0f / gamma))); + this->gamma_reverse_table_[i] = uncorrected; + } +} + +} // namespace light +} // namespace esphome diff --git a/esphome/components/light/esp_color_correction.h b/esphome/components/light/esp_color_correction.h new file mode 100644 index 0000000000..32fee8c8ea --- /dev/null +++ b/esphome/components/light/esp_color_correction.h @@ -0,0 +1,78 @@ +#pragma once + +#include "esphome/core/color.h" + +namespace esphome { +namespace light { + +class ESPColorCorrection { + public: + ESPColorCorrection() : max_brightness_(255, 255, 255, 255) {} + void set_max_brightness(const Color &max_brightness) { this->max_brightness_ = max_brightness; } + void set_local_brightness(uint8_t local_brightness) { this->local_brightness_ = local_brightness; } + void calculate_gamma_table(float gamma); + inline Color color_correct(Color color) const ALWAYS_INLINE { + // corrected = (uncorrected * max_brightness * local_brightness) ^ gamma + return Color(this->color_correct_red(color.red), this->color_correct_green(color.green), + this->color_correct_blue(color.blue), this->color_correct_white(color.white)); + } + inline uint8_t color_correct_red(uint8_t red) const ALWAYS_INLINE { + uint8_t res = esp_scale8(esp_scale8(red, this->max_brightness_.red), this->local_brightness_); + return this->gamma_table_[res]; + } + inline uint8_t color_correct_green(uint8_t green) const ALWAYS_INLINE { + uint8_t res = esp_scale8(esp_scale8(green, this->max_brightness_.green), this->local_brightness_); + return this->gamma_table_[res]; + } + inline uint8_t color_correct_blue(uint8_t blue) const ALWAYS_INLINE { + uint8_t res = esp_scale8(esp_scale8(blue, this->max_brightness_.blue), this->local_brightness_); + return this->gamma_table_[res]; + } + inline uint8_t color_correct_white(uint8_t white) const ALWAYS_INLINE { + // do not scale white value with brightness + uint8_t res = esp_scale8(white, this->max_brightness_.white); + return this->gamma_table_[res]; + } + inline Color color_uncorrect(Color color) const ALWAYS_INLINE { + // uncorrected = corrected^(1/gamma) / (max_brightness * local_brightness) + return Color(this->color_uncorrect_red(color.red), this->color_uncorrect_green(color.green), + this->color_uncorrect_blue(color.blue), this->color_uncorrect_white(color.white)); + } + inline uint8_t color_uncorrect_red(uint8_t red) const ALWAYS_INLINE { + if (this->max_brightness_.red == 0 || this->local_brightness_ == 0) + return 0; + uint16_t uncorrected = this->gamma_reverse_table_[red] * 255UL; + uint8_t res = ((uncorrected / this->max_brightness_.red) * 255UL) / this->local_brightness_; + return res; + } + inline uint8_t color_uncorrect_green(uint8_t green) const ALWAYS_INLINE { + if (this->max_brightness_.green == 0 || this->local_brightness_ == 0) + return 0; + uint16_t uncorrected = this->gamma_reverse_table_[green] * 255UL; + uint8_t res = ((uncorrected / this->max_brightness_.green) * 255UL) / this->local_brightness_; + return res; + } + inline uint8_t color_uncorrect_blue(uint8_t blue) const ALWAYS_INLINE { + if (this->max_brightness_.blue == 0 || this->local_brightness_ == 0) + return 0; + uint16_t uncorrected = this->gamma_reverse_table_[blue] * 255UL; + uint8_t res = ((uncorrected / this->max_brightness_.blue) * 255UL) / this->local_brightness_; + return res; + } + inline uint8_t color_uncorrect_white(uint8_t white) const ALWAYS_INLINE { + if (this->max_brightness_.white == 0) + return 0; + uint16_t uncorrected = this->gamma_reverse_table_[white] * 255UL; + uint8_t res = uncorrected / this->max_brightness_.white; + return res; + } + + protected: + uint8_t gamma_table_[256]; + uint8_t gamma_reverse_table_[256]; + Color max_brightness_; + uint8_t local_brightness_{255}; +}; + +} // namespace light +} // namespace esphome diff --git a/esphome/components/light/esp_color_view.h b/esphome/components/light/esp_color_view.h new file mode 100644 index 0000000000..35117e7dd8 --- /dev/null +++ b/esphome/components/light/esp_color_view.h @@ -0,0 +1,110 @@ +#pragma once + +#include "esphome/core/color.h" +#include "esp_hsv_color.h" +#include "esp_color_correction.h" + +namespace esphome { +namespace light { + +class ESPColorSettable { + public: + virtual void set(const Color &color) = 0; + virtual void set_red(uint8_t red) = 0; + virtual void set_green(uint8_t green) = 0; + virtual void set_blue(uint8_t blue) = 0; + virtual void set_white(uint8_t white) = 0; + virtual void set_effect_data(uint8_t effect_data) = 0; + virtual void fade_to_white(uint8_t amnt) = 0; + virtual void fade_to_black(uint8_t amnt) = 0; + virtual void lighten(uint8_t delta) = 0; + virtual void darken(uint8_t delta) = 0; + void set(const ESPHSVColor &color) { this->set_hsv(color); } + void set_hsv(const ESPHSVColor &color) { + Color rgb = color.to_rgb(); + this->set_rgb(rgb.r, rgb.g, rgb.b); + } + void set_rgb(uint8_t red, uint8_t green, uint8_t blue) { + this->set_red(red); + this->set_green(green); + this->set_blue(blue); + } + void set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) { + this->set_rgb(red, green, blue); + this->set_white(white); + } +}; + +class ESPColorView : public ESPColorSettable { + public: + ESPColorView(uint8_t *red, uint8_t *green, uint8_t *blue, uint8_t *white, uint8_t *effect_data, + const ESPColorCorrection *color_correction) + : red_(red), + green_(green), + blue_(blue), + white_(white), + effect_data_(effect_data), + color_correction_(color_correction) {} + ESPColorView &operator=(const Color &rhs) { + this->set(rhs); + return *this; + } + ESPColorView &operator=(const ESPHSVColor &rhs) { + this->set_hsv(rhs); + return *this; + } + void set(const Color &color) override { this->set_rgbw(color.r, color.g, color.b, color.w); } + void set_red(uint8_t red) override { *this->red_ = this->color_correction_->color_correct_red(red); } + void set_green(uint8_t green) override { *this->green_ = this->color_correction_->color_correct_green(green); } + void set_blue(uint8_t blue) override { *this->blue_ = this->color_correction_->color_correct_blue(blue); } + void set_white(uint8_t white) override { + if (this->white_ == nullptr) + return; + *this->white_ = this->color_correction_->color_correct_white(white); + } + void set_effect_data(uint8_t effect_data) override { + if (this->effect_data_ == nullptr) + return; + *this->effect_data_ = effect_data; + } + void fade_to_white(uint8_t amnt) override { this->set(this->get().fade_to_white(amnt)); } + void fade_to_black(uint8_t amnt) override { this->set(this->get().fade_to_black(amnt)); } + void lighten(uint8_t delta) override { this->set(this->get().lighten(delta)); } + void darken(uint8_t delta) override { this->set(this->get().darken(delta)); } + Color get() const { return Color(this->get_red(), this->get_green(), this->get_blue(), this->get_white()); } + uint8_t get_red() const { return this->color_correction_->color_uncorrect_red(*this->red_); } + uint8_t get_red_raw() const { return *this->red_; } + uint8_t get_green() const { return this->color_correction_->color_uncorrect_green(*this->green_); } + uint8_t get_green_raw() const { return *this->green_; } + uint8_t get_blue() const { return this->color_correction_->color_uncorrect_blue(*this->blue_); } + uint8_t get_blue_raw() const { return *this->blue_; } + uint8_t get_white() const { + if (this->white_ == nullptr) + return 0; + return this->color_correction_->color_uncorrect_white(*this->white_); + } + uint8_t get_white_raw() const { + if (this->white_ == nullptr) + return 0; + return *this->white_; + } + uint8_t get_effect_data() const { + if (this->effect_data_ == nullptr) + return 0; + return *this->effect_data_; + } + void raw_set_color_correction(const ESPColorCorrection *color_correction) { + this->color_correction_ = color_correction; + } + + protected: + uint8_t *const red_; + uint8_t *const green_; + uint8_t *const blue_; + uint8_t *const white_; + uint8_t *const effect_data_; + const ESPColorCorrection *color_correction_; +}; + +} // namespace light +} // namespace esphome diff --git a/esphome/components/light/esp_hsv_color.cpp b/esphome/components/light/esp_hsv_color.cpp new file mode 100644 index 0000000000..450c2e11ce --- /dev/null +++ b/esphome/components/light/esp_hsv_color.cpp @@ -0,0 +1,74 @@ +#include "esp_hsv_color.h" + +namespace esphome { +namespace light { + +Color ESPHSVColor::to_rgb() const { + // based on FastLED's hsv rainbow to rgb + const uint8_t hue = this->hue; + const uint8_t sat = this->saturation; + const uint8_t val = this->value; + // upper 3 hue bits are for branch selection, lower 5 are for values + const uint8_t offset8 = (hue & 0x1F) << 3; // 0..248 + // third of the offset, 255/3 = 85 (actually only up to 82; 164) + const uint8_t third = esp_scale8(offset8, 85); + const uint8_t two_thirds = esp_scale8(offset8, 170); + Color rgb(255, 255, 255, 0); + switch (hue >> 5) { + case 0b000: + rgb.r = 255 - third; + rgb.g = third; + rgb.b = 0; + break; + case 0b001: + rgb.r = 171; + rgb.g = 85 + third; + rgb.b = 0; + break; + case 0b010: + rgb.r = 171 - two_thirds; + rgb.g = 170 + third; + rgb.b = 0; + break; + case 0b011: + rgb.r = 0; + rgb.g = 255 - third; + rgb.b = third; + break; + case 0b100: + rgb.r = 0; + rgb.g = 171 - two_thirds; + rgb.b = 85 + two_thirds; + break; + case 0b101: + rgb.r = third; + rgb.g = 0; + rgb.b = 255 - third; + break; + case 0b110: + rgb.r = 85 + third; + rgb.g = 0; + rgb.b = 171 - third; + break; + case 0b111: + rgb.r = 170 + third; + rgb.g = 0; + rgb.b = 85 - third; + break; + default: + break; + } + // low saturation -> add uniform color to orig. hue + // high saturation -> use hue directly + // scales with square of saturation + // (r,g,b) = (r,g,b) * sat + (1 - sat)^2 + rgb *= sat; + const uint8_t desat = 255 - sat; + rgb += esp_scale8(desat, desat); + // (r,g,b) = (r,g,b) * val + rgb *= val; + return rgb; +} + +} // namespace light +} // namespace esphome diff --git a/esphome/components/light/esp_hsv_color.h b/esphome/components/light/esp_hsv_color.h new file mode 100644 index 0000000000..e0aa388875 --- /dev/null +++ b/esphome/components/light/esp_hsv_color.h @@ -0,0 +1,36 @@ +#pragma once + +#include "esphome/core/helpers.h" +#include "esphome/core/color.h" + +namespace esphome { +namespace light { + +struct ESPHSVColor { + union { + struct { + union { + uint8_t hue; + uint8_t h; + }; + union { + uint8_t saturation; + uint8_t s; + }; + union { + uint8_t value; + uint8_t v; + }; + }; + uint8_t raw[3]; + }; + inline ESPHSVColor() ALWAYS_INLINE : h(0), s(0), v(0) { // NOLINT + } + inline ESPHSVColor(uint8_t hue, uint8_t saturation, uint8_t value) ALWAYS_INLINE : hue(hue), + saturation(saturation), + value(value) {} + Color to_rgb() const; +}; + +} // namespace light +} // namespace esphome diff --git a/esphome/components/light/esp_range_view.cpp b/esphome/components/light/esp_range_view.cpp new file mode 100644 index 0000000000..e1f0a507bd --- /dev/null +++ b/esphome/components/light/esp_range_view.cpp @@ -0,0 +1,96 @@ +#include "esp_range_view.h" +#include "addressable_light.h" + +namespace esphome { +namespace light { + +int32_t HOT interpret_index(int32_t index, int32_t size) { + if (index < 0) + return size + index; + return index; +} + +ESPColorView ESPRangeView::operator[](int32_t index) const { + index = interpret_index(index, this->size()) + this->begin_; + return (*this->parent_)[index]; +} +ESPRangeIterator ESPRangeView::begin() { return {*this, this->begin_}; } +ESPRangeIterator ESPRangeView::end() { return {*this, this->end_}; } + +void ESPRangeView::set(const Color &color) { + for (int32_t i = this->begin_; i < this->end_; i++) { + (*this->parent_)[i] = color; + } +} + +void ESPRangeView::set_red(uint8_t red) { + for (auto c : *this) + c.set_red(red); +} +void ESPRangeView::set_green(uint8_t green) { + for (auto c : *this) + c.set_green(green); +} +void ESPRangeView::set_blue(uint8_t blue) { + for (auto c : *this) + c.set_blue(blue); +} +void ESPRangeView::set_white(uint8_t white) { + for (auto c : *this) + c.set_white(white); +} +void ESPRangeView::set_effect_data(uint8_t effect_data) { + for (auto c : *this) + c.set_effect_data(effect_data); +} + +void ESPRangeView::fade_to_white(uint8_t amnt) { + for (auto c : *this) + c.fade_to_white(amnt); +} +void ESPRangeView::fade_to_black(uint8_t amnt) { + for (auto c : *this) + c.fade_to_black(amnt); +} +void ESPRangeView::lighten(uint8_t delta) { + for (auto c : *this) + c.lighten(delta); +} +void ESPRangeView::darken(uint8_t delta) { + for (auto c : *this) + c.darken(delta); +} +ESPRangeView &ESPRangeView::operator=(const ESPRangeView &rhs) { // NOLINT + // If size doesn't match, error (todo warning) + if (rhs.size() != this->size()) + return *this; + + if (this->parent_ != rhs.parent_) { + for (int32_t i = 0; i < this->size(); i++) + (*this)[i].set(rhs[i].get()); + return *this; + } + + // If both equal, already done + if (rhs.begin_ == this->begin_) + return *this; + + if (rhs.begin_ > this->begin_) { + // Copy from left + for (int32_t i = 0; i < this->size(); i++) { + (*this)[i].set(rhs[i].get()); + } + } else { + // Copy from right + for (int32_t i = this->size() - 1; i >= 0; i--) { + (*this)[i].set(rhs[i].get()); + } + } + + return *this; +} + +ESPColorView ESPRangeIterator::operator*() const { return this->range_.parent_->get(this->i_); } + +} // namespace light +} // namespace esphome diff --git a/esphome/components/light/esp_range_view.h b/esphome/components/light/esp_range_view.h new file mode 100644 index 0000000000..f2cc347176 --- /dev/null +++ b/esphome/components/light/esp_range_view.h @@ -0,0 +1,75 @@ +#pragma once + +#include "esp_color_view.h" +#include "esp_hsv_color.h" + +namespace esphome { +namespace light { + +int32_t interpret_index(int32_t index, int32_t size); + +class AddressableLight; +class ESPRangeIterator; + +class ESPRangeView : public ESPColorSettable { + public: + ESPRangeView(AddressableLight *parent, int32_t begin, int32_t end) + : parent_(parent), begin_(begin), end_(end < begin ? begin : end) {} + + int32_t size() const { return this->end_ - this->begin_; } + ESPColorView operator[](int32_t index) const; + ESPRangeIterator begin(); + ESPRangeIterator end(); + + void set(const Color &color) override; + void set(const ESPHSVColor &color) { this->set(color.to_rgb()); } + void set_red(uint8_t red) override; + void set_green(uint8_t green) override; + void set_blue(uint8_t blue) override; + void set_white(uint8_t white) override; + void set_effect_data(uint8_t effect_data) override; + + void fade_to_white(uint8_t amnt) override; + void fade_to_black(uint8_t amnt) override; + void lighten(uint8_t delta) override; + void darken(uint8_t delta) override; + + ESPRangeView &operator=(const Color &rhs) { + this->set(rhs); + return *this; + } + ESPRangeView &operator=(const ESPColorView &rhs) { + this->set(rhs.get()); + return *this; + } + ESPRangeView &operator=(const ESPHSVColor &rhs) { + this->set_hsv(rhs); + return *this; + } + ESPRangeView &operator=(const ESPRangeView &rhs); + + protected: + friend ESPRangeIterator; + + AddressableLight *parent_; + int32_t begin_; + int32_t end_; +}; + +class ESPRangeIterator { + public: + ESPRangeIterator(const ESPRangeView &range, int32_t i) : range_(range), i_(i) {} + ESPRangeIterator operator++() { + this->i_++; + return *this; + } + bool operator!=(const ESPRangeIterator &other) const { return this->i_ != other.i_; } + ESPColorView operator*() const; + + protected: + ESPRangeView range_; + int32_t i_; +}; + +} // namespace light +} // namespace esphome diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp new file mode 100644 index 0000000000..359da51de9 --- /dev/null +++ b/esphome/components/light/light_call.cpp @@ -0,0 +1,522 @@ +#include "light_call.h" +#include "light_state.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace light { + +static const char *TAG = "light"; + +#ifdef USE_JSON +LightCall &LightCall::parse_color_json(JsonObject &root) { + if (root.containsKey("state")) { + auto val = parse_on_off(root["state"]); + switch (val) { + case PARSE_ON: + this->set_state(true); + break; + case PARSE_OFF: + this->set_state(false); + break; + case PARSE_TOGGLE: + this->set_state(!this->parent_->remote_values.is_on()); + break; + case PARSE_NONE: + break; + } + } + + if (root.containsKey("brightness")) { + this->set_brightness(float(root["brightness"]) / 255.0f); + } + + if (root.containsKey("color")) { + JsonObject &color = root["color"]; + if (color.containsKey("r")) { + this->set_red(float(color["r"]) / 255.0f); + } + if (color.containsKey("g")) { + this->set_green(float(color["g"]) / 255.0f); + } + if (color.containsKey("b")) { + this->set_blue(float(color["b"]) / 255.0f); + } + } + + if (root.containsKey("white_value")) { + this->set_white(float(root["white_value"]) / 255.0f); + } + + if (root.containsKey("color_temp")) { + this->set_color_temperature(float(root["color_temp"])); + } + + return *this; +} +LightCall &LightCall::parse_json(JsonObject &root) { + this->parse_color_json(root); + + if (root.containsKey("flash")) { + auto length = uint32_t(float(root["flash"]) * 1000); + this->set_flash_length(length); + } + + if (root.containsKey("transition")) { + auto length = uint32_t(float(root["transition"]) * 1000); + this->set_transition_length(length); + } + + if (root.containsKey("effect")) { + const char *effect = root["effect"]; + this->set_effect(effect); + } + + return *this; +} +#endif + +void LightCall::perform() { + // use remote values for fallback + const char *name = this->parent_->get_name().c_str(); + if (this->publish_) { + ESP_LOGD(TAG, "'%s' Setting:", name); + } + + LightColorValues v = this->validate_(); + + if (this->publish_) { + // Only print state when it's being changed + bool current_state = this->parent_->remote_values.is_on(); + if (this->state_.value_or(current_state) != current_state) { + ESP_LOGD(TAG, " State: %s", ONOFF(v.is_on())); + } + + if (this->brightness_.has_value()) { + ESP_LOGD(TAG, " Brightness: %.0f%%", v.get_brightness() * 100.0f); + } + + if (this->color_temperature_.has_value()) { + ESP_LOGD(TAG, " Color Temperature: %.1f mireds", v.get_color_temperature()); + } + + if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { + ESP_LOGD(TAG, " Red=%.0f%%, Green=%.0f%%, Blue=%.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f, + v.get_blue() * 100.0f); + } + if (this->white_.has_value()) { + ESP_LOGD(TAG, " White Value: %.0f%%", v.get_white() * 100.0f); + } + } + + if (this->has_flash_()) { + // FLASH + if (this->publish_) { + ESP_LOGD(TAG, " Flash Length: %.1fs", *this->flash_length_ / 1e3f); + } + + this->parent_->start_flash_(v, *this->flash_length_); + } else if (this->has_transition_()) { + // TRANSITION + if (this->publish_) { + ESP_LOGD(TAG, " Transition Length: %.1fs", *this->transition_length_ / 1e3f); + } + + // Special case: Transition and effect can be set when turning off + if (this->has_effect_()) { + if (this->publish_) { + ESP_LOGD(TAG, " Effect: 'None'"); + } + this->parent_->stop_effect_(); + } + + this->parent_->start_transition_(v, *this->transition_length_); + + } else if (this->has_effect_()) { + // EFFECT + auto effect = this->effect_; + const char *effect_s; + if (effect == 0) + effect_s = "None"; + else + effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str(); + + if (this->publish_) { + ESP_LOGD(TAG, " Effect: '%s'", effect_s); + } + + this->parent_->start_effect_(*this->effect_); + + // Also set light color values when starting an effect + // For example to turn off the light + this->parent_->set_immediately_(v, true); + } else { + // INSTANT CHANGE + this->parent_->set_immediately_(v, this->publish_); + } + + if (!this->has_transition_()) { + this->parent_->target_state_reached_callback_.call(); + } + if (this->publish_) { + this->parent_->publish_state(); + } + if (this->save_) { + this->parent_->save_remote_values_(); + } +} + +LightColorValues LightCall::validate_() { + // use remote values for fallback + auto *name = this->parent_->get_name().c_str(); + auto traits = this->parent_->get_traits(); + + // Brightness exists check + if (this->brightness_.has_value() && !traits.get_supports_brightness()) { + ESP_LOGW(TAG, "'%s' - This light does not support setting brightness!", name); + this->brightness_.reset(); + } + + // Transition length possible check + if (this->transition_length_.has_value() && *this->transition_length_ != 0 && !traits.get_supports_brightness()) { + ESP_LOGW(TAG, "'%s' - This light does not support transitions!", name); + this->transition_length_.reset(); + } + + // RGB exists check + if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { + if (!traits.get_supports_rgb()) { + ESP_LOGW(TAG, "'%s' - This light does not support setting RGB color!", name); + this->red_.reset(); + this->green_.reset(); + this->blue_.reset(); + } + } + + // White value exists check + if (this->white_.has_value() && !traits.get_supports_rgb_white_value()) { + ESP_LOGW(TAG, "'%s' - This light does not support setting white value!", name); + this->white_.reset(); + } + + // Color temperature exists check + if (this->color_temperature_.has_value() && !traits.get_supports_color_temperature()) { + ESP_LOGW(TAG, "'%s' - This light does not support setting color temperature!", name); + this->color_temperature_.reset(); + } + + // If white channel is specified, set RGB to white color (when interlock is enabled) + if (this->white_.has_value()) { + if (traits.get_supports_color_interlock()) { + if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) { + this->red_ = optional(1.0f); + this->green_ = optional(1.0f); + this->blue_ = optional(1.0f); + } + // make white values binary aka 0.0f or 1.0f... this allows brightness to do its job + if (*this->white_ > 0.0f) { + this->white_ = optional(1.0f); + } else { + this->white_ = optional(0.0f); + } + } + } + // If only a color channel is specified, set white channel to 100% for white, otherwise 0% (when interlock is enabled) + else if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { + if (traits.get_supports_color_interlock()) { + if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f) { + this->white_ = optional(1.0f); + } else { + this->white_ = optional(0.0f); + } + } + } + // If only a color temperature is specified, change to white light + else if (this->color_temperature_.has_value()) { + this->red_ = optional(1.0f); + this->green_ = optional(1.0f); + this->blue_ = optional(1.0f); + + // if setting color temperature from color (i.e. switching to white light), set White to 100% + auto cv = this->parent_->remote_values; + bool was_color = cv.get_red() != 1.0f || cv.get_blue() != 1.0f || cv.get_green() != 1.0f; + if (traits.get_supports_color_interlock() || was_color) { + this->white_ = optional(1.0f); + } + } + +#define VALIDATE_RANGE_(name_, upper_name) \ + if (name_##_.has_value()) { \ + auto val = *name_##_; \ + if (val < 0.0f || val > 1.0f) { \ + ESP_LOGW(TAG, "'%s' - %s value %.2f is out of range [0.0 - 1.0]!", name, upper_name, val); \ + name_##_ = clamp(val, 0.0f, 1.0f); \ + } \ + } +#define VALIDATE_RANGE(name, upper_name) VALIDATE_RANGE_(name, upper_name) + + // Range checks + VALIDATE_RANGE(brightness, "Brightness") + VALIDATE_RANGE(red, "Red") + VALIDATE_RANGE(green, "Green") + VALIDATE_RANGE(blue, "Blue") + VALIDATE_RANGE(white, "White") + + auto v = this->parent_->remote_values; + if (this->state_.has_value()) + v.set_state(*this->state_); + if (this->brightness_.has_value()) + v.set_brightness(*this->brightness_); + + if (this->red_.has_value()) + v.set_red(*this->red_); + if (this->green_.has_value()) + v.set_green(*this->green_); + if (this->blue_.has_value()) + v.set_blue(*this->blue_); + if (this->white_.has_value()) + v.set_white(*this->white_); + + if (this->color_temperature_.has_value()) + v.set_color_temperature(*this->color_temperature_); + + v.normalize_color(traits); + + // Flash length check + if (this->has_flash_() && *this->flash_length_ == 0) { + ESP_LOGW(TAG, "'%s' - Flash length must be greater than zero!", name); + this->flash_length_.reset(); + } + + // validate transition length/flash length/effect not used at the same time + bool supports_transition = traits.get_supports_brightness(); + + // If effect is already active, remove effect start + if (this->has_effect_() && *this->effect_ == this->parent_->active_effect_index_) { + this->effect_.reset(); + } + + // validate effect index + if (this->has_effect_() && *this->effect_ > this->parent_->effects_.size()) { + ESP_LOGW(TAG, "'%s' Invalid effect index %u", name, *this->effect_); + this->effect_.reset(); + } + + if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) { + ESP_LOGW(TAG, "'%s' - Effect cannot be used together with transition/flash!", name); + this->transition_length_.reset(); + this->flash_length_.reset(); + } + + if (this->has_flash_() && this->has_transition_()) { + ESP_LOGW(TAG, "'%s' - Flash cannot be used together with transition!", name); + this->transition_length_.reset(); + } + + if (!this->has_transition_() && !this->has_flash_() && (!this->has_effect_() || *this->effect_ == 0) && + supports_transition) { + // nothing specified and light supports transitions, set default transition length + this->transition_length_ = this->parent_->default_transition_length_; + } + + if (this->transition_length_.value_or(0) == 0) { + // 0 transition is interpreted as no transition (instant change) + this->transition_length_.reset(); + } + + if (this->has_transition_() && !supports_transition) { + ESP_LOGW(TAG, "'%s' - Light does not support transitions!", name); + this->transition_length_.reset(); + } + + // If not a flash and turning the light off, then disable the light + // Do not use light color values directly, so that effects can set 0% brightness + // Reason: When user turns off the light in frontend, the effect should also stop + if (!this->has_flash_() && !this->state_.value_or(v.is_on())) { + if (this->has_effect_()) { + ESP_LOGW(TAG, "'%s' - Cannot start an effect when turning off!", name); + this->effect_.reset(); + } else if (this->parent_->active_effect_index_ != 0) { + // Auto turn off effect + this->effect_ = 0; + } + } + + // Disable saving for flashes + if (this->has_flash_()) + this->save_ = false; + + return v; +} +LightCall &LightCall::set_effect(const std::string &effect) { + if (strcasecmp(effect.c_str(), "none") == 0) { + this->set_effect(0); + return *this; + } + + bool found = false; + for (uint32_t i = 0; i < this->parent_->effects_.size(); i++) { + LightEffect *e = this->parent_->effects_[i]; + + if (strcasecmp(effect.c_str(), e->get_name().c_str()) == 0) { + this->set_effect(i + 1); + found = true; + break; + } + } + if (!found) { + ESP_LOGW(TAG, "'%s' - No such effect '%s'", this->parent_->get_name().c_str(), effect.c_str()); + } + return *this; +} +LightCall &LightCall::from_light_color_values(const LightColorValues &values) { + this->set_state(values.is_on()); + this->set_brightness_if_supported(values.get_brightness()); + this->set_red_if_supported(values.get_red()); + this->set_green_if_supported(values.get_green()); + this->set_blue_if_supported(values.get_blue()); + this->set_white_if_supported(values.get_white()); + this->set_color_temperature_if_supported(values.get_color_temperature()); + return *this; +} +LightCall &LightCall::set_transition_length_if_supported(uint32_t transition_length) { + if (this->parent_->get_traits().get_supports_brightness()) + this->set_transition_length(transition_length); + return *this; +} +LightCall &LightCall::set_brightness_if_supported(float brightness) { + if (this->parent_->get_traits().get_supports_brightness()) + this->set_brightness(brightness); + return *this; +} +LightCall &LightCall::set_red_if_supported(float red) { + if (this->parent_->get_traits().get_supports_rgb()) + this->set_red(red); + return *this; +} +LightCall &LightCall::set_green_if_supported(float green) { + if (this->parent_->get_traits().get_supports_rgb()) + this->set_green(green); + return *this; +} +LightCall &LightCall::set_blue_if_supported(float blue) { + if (this->parent_->get_traits().get_supports_rgb()) + this->set_blue(blue); + return *this; +} +LightCall &LightCall::set_white_if_supported(float white) { + if (this->parent_->get_traits().get_supports_rgb_white_value()) + this->set_white(white); + return *this; +} +LightCall &LightCall::set_color_temperature_if_supported(float color_temperature) { + if (this->parent_->get_traits().get_supports_color_temperature()) + this->set_color_temperature(color_temperature); + return *this; +} +LightCall &LightCall::set_state(optional state) { + this->state_ = state; + return *this; +} +LightCall &LightCall::set_state(bool state) { + this->state_ = state; + return *this; +} +LightCall &LightCall::set_transition_length(optional transition_length) { + this->transition_length_ = transition_length; + return *this; +} +LightCall &LightCall::set_transition_length(uint32_t transition_length) { + this->transition_length_ = transition_length; + return *this; +} +LightCall &LightCall::set_flash_length(optional flash_length) { + this->flash_length_ = flash_length; + return *this; +} +LightCall &LightCall::set_flash_length(uint32_t flash_length) { + this->flash_length_ = flash_length; + return *this; +} +LightCall &LightCall::set_brightness(optional brightness) { + this->brightness_ = brightness; + return *this; +} +LightCall &LightCall::set_brightness(float brightness) { + this->brightness_ = brightness; + return *this; +} +LightCall &LightCall::set_red(optional red) { + this->red_ = red; + return *this; +} +LightCall &LightCall::set_red(float red) { + this->red_ = red; + return *this; +} +LightCall &LightCall::set_green(optional green) { + this->green_ = green; + return *this; +} +LightCall &LightCall::set_green(float green) { + this->green_ = green; + return *this; +} +LightCall &LightCall::set_blue(optional blue) { + this->blue_ = blue; + return *this; +} +LightCall &LightCall::set_blue(float blue) { + this->blue_ = blue; + return *this; +} +LightCall &LightCall::set_white(optional white) { + this->white_ = white; + return *this; +} +LightCall &LightCall::set_white(float white) { + this->white_ = white; + return *this; +} +LightCall &LightCall::set_color_temperature(optional color_temperature) { + this->color_temperature_ = color_temperature; + return *this; +} +LightCall &LightCall::set_color_temperature(float color_temperature) { + this->color_temperature_ = color_temperature; + return *this; +} +LightCall &LightCall::set_effect(optional effect) { + if (effect.has_value()) + this->set_effect(*effect); + return *this; +} +LightCall &LightCall::set_effect(uint32_t effect_number) { + this->effect_ = effect_number; + return *this; +} +LightCall &LightCall::set_effect(optional effect_number) { + this->effect_ = effect_number; + return *this; +} +LightCall &LightCall::set_publish(bool publish) { + this->publish_ = publish; + return *this; +} +LightCall &LightCall::set_save(bool save) { + this->save_ = save; + return *this; +} +LightCall &LightCall::set_rgb(float red, float green, float blue) { + this->set_red(red); + this->set_green(green); + this->set_blue(blue); + return *this; +} +LightCall &LightCall::set_rgbw(float red, float green, float blue, float white) { + this->set_rgb(red, green, blue); + this->set_white(white); + return *this; +} + +} // namespace light +} // namespace esphome diff --git a/esphome/components/light/light_call.h b/esphome/components/light/light_call.h new file mode 100644 index 0000000000..becea50004 --- /dev/null +++ b/esphome/components/light/light_call.h @@ -0,0 +1,160 @@ +#pragma once + +#include "esphome/core/optional.h" +#include "light_color_values.h" + +namespace esphome { +namespace light { + +class LightState; + +/** This class represents a requested change in a light state. + */ +class LightCall { + public: + explicit LightCall(LightState *parent) : parent_(parent) {} + + /// Set the binary ON/OFF state of the light. + LightCall &set_state(optional state); + /// Set the binary ON/OFF state of the light. + LightCall &set_state(bool state); + /** Set the transition length of this call in milliseconds. + * + * This argument is ignored for starting flashes and effects. + * + * Defaults to the default transition length defined in the light configuration. + */ + LightCall &set_transition_length(optional transition_length); + /** Set the transition length of this call in milliseconds. + * + * This argument is ignored for starting flashes and effects. + * + * Defaults to the default transition length defined in the light configuration. + */ + LightCall &set_transition_length(uint32_t transition_length); + /// Set the transition length property if the light supports transitions. + LightCall &set_transition_length_if_supported(uint32_t transition_length); + /// Start and set the flash length of this call in milliseconds. + LightCall &set_flash_length(optional flash_length); + /// Start and set the flash length of this call in milliseconds. + LightCall &set_flash_length(uint32_t flash_length); + /// Set the target brightness of the light from 0.0 (fully off) to 1.0 (fully on) + LightCall &set_brightness(optional brightness); + /// Set the target brightness of the light from 0.0 (fully off) to 1.0 (fully on) + LightCall &set_brightness(float brightness); + /// Set the brightness property if the light supports brightness. + LightCall &set_brightness_if_supported(float brightness); + /** Set the red RGB value of the light from 0.0 to 1.0. + * + * Note that this only controls the color of the light, not its brightness. + */ + LightCall &set_red(optional red); + /** Set the red RGB value of the light from 0.0 to 1.0. + * + * Note that this only controls the color of the light, not its brightness. + */ + LightCall &set_red(float red); + /// Set the red property if the light supports RGB. + LightCall &set_red_if_supported(float red); + /** Set the green RGB value of the light from 0.0 to 1.0. + * + * Note that this only controls the color of the light, not its brightness. + */ + LightCall &set_green(optional green); + /** Set the green RGB value of the light from 0.0 to 1.0. + * + * Note that this only controls the color of the light, not its brightness. + */ + LightCall &set_green(float green); + /// Set the green property if the light supports RGB. + LightCall &set_green_if_supported(float green); + /** Set the blue RGB value of the light from 0.0 to 1.0. + * + * Note that this only controls the color of the light, not its brightness. + */ + LightCall &set_blue(optional blue); + /** Set the blue RGB value of the light from 0.0 to 1.0. + * + * Note that this only controls the color of the light, not its brightness. + */ + LightCall &set_blue(float blue); + /// Set the blue property if the light supports RGB. + LightCall &set_blue_if_supported(float blue); + /// Set the white value value of the light from 0.0 to 1.0 for RGBW[W] lights. + LightCall &set_white(optional white); + /// Set the white value value of the light from 0.0 to 1.0 for RGBW[W] lights. + LightCall &set_white(float white); + /// Set the white property if the light supports RGB. + LightCall &set_white_if_supported(float white); + /// Set the color temperature of the light in mireds for CWWW or RGBWW lights. + LightCall &set_color_temperature(optional color_temperature); + /// Set the color temperature of the light in mireds for CWWW or RGBWW lights. + LightCall &set_color_temperature(float color_temperature); + /// Set the color_temperature property if the light supports color temperature. + LightCall &set_color_temperature_if_supported(float color_temperature); + /// Set the effect of the light by its name. + LightCall &set_effect(optional effect); + /// Set the effect of the light by its name. + LightCall &set_effect(const std::string &effect); + /// Set the effect of the light by its internal index number (only for internal use). + LightCall &set_effect(uint32_t effect_number); + LightCall &set_effect(optional effect_number); + /// Set whether this light call should trigger a publish state. + LightCall &set_publish(bool publish); + /// Set whether this light call should trigger a save state to recover them at startup.. + LightCall &set_save(bool save); + + /** Set the RGB color of the light by RGB values. + * + * Please note that this only changes the color of the light, not the brightness. + * + * @param red The red color value from 0.0 to 1.0. + * @param green The green color value from 0.0 to 1.0. + * @param blue The blue color value from 0.0 to 1.0. + * @return The light call for chaining setters. + */ + LightCall &set_rgb(float red, float green, float blue); + /** Set the RGBW color of the light by RGB values. + * + * Please note that this only changes the color of the light, not the brightness. + * + * @param red The red color value from 0.0 to 1.0. + * @param green The green color value from 0.0 to 1.0. + * @param blue The blue color value from 0.0 to 1.0. + * @param white The white color value from 0.0 to 1.0. + * @return The light call for chaining setters. + */ + LightCall &set_rgbw(float red, float green, float blue, float white); +#ifdef USE_JSON + LightCall &parse_color_json(JsonObject &root); + LightCall &parse_json(JsonObject &root); +#endif + LightCall &from_light_color_values(const LightColorValues &values); + + void perform(); + + protected: + /// Validate all properties and return the target light color values. + LightColorValues validate_(); + + bool has_transition_() { return this->transition_length_.has_value(); } + bool has_flash_() { return this->flash_length_.has_value(); } + bool has_effect_() { return this->effect_.has_value(); } + + LightState *parent_; + optional state_; + optional transition_length_; + optional flash_length_; + optional brightness_; + optional red_; + optional green_; + optional blue_; + optional white_; + optional color_temperature_; + optional effect_; + bool publish_{true}; + bool save_{true}; +}; + +} // namespace light +} // namespace esphome diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 177dbb8d4e..69d147b6f3 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -7,80 +7,13 @@ namespace light { static const char *const TAG = "light"; -void LightState::start_transition_(const LightColorValues &target, uint32_t length) { - this->transformer_ = make_unique(millis(), length, this->current_values, target); - this->remote_values = this->transformer_->get_remote_values(); -} - -void LightState::start_flash_(const LightColorValues &target, uint32_t length) { - LightColorValues end_colors = this->current_values; - // If starting a flash if one is already happening, set end values to end values of current flash - // Hacky but works - if (this->transformer_ != nullptr) - end_colors = this->transformer_->get_end_values(); - this->transformer_ = make_unique(millis(), length, end_colors, target); - this->remote_values = this->transformer_->get_remote_values(); -} - LightState::LightState(const std::string &name, LightOutput *output) : Nameable(name), output_(output) {} -void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) { - this->transformer_ = nullptr; - this->current_values = target; - if (set_remote_values) { - this->remote_values = target; - } - this->next_write_ = true; -} - -LightColorValues LightState::get_current_values() { return this->current_values; } - -void LightState::publish_state() { - this->remote_values_callback_.call(); - this->next_write_ = true; -} - -LightColorValues LightState::get_remote_values() { return this->remote_values; } - -std::string LightState::get_effect_name() { - if (this->active_effect_index_ > 0) - return this->effects_[this->active_effect_index_ - 1]->get_name(); - else - return "None"; -} - -void LightState::start_effect_(uint32_t effect_index) { - this->stop_effect_(); - if (effect_index == 0) - return; - - this->active_effect_index_ = effect_index; - auto *effect = this->get_active_effect_(); - effect->start_internal(); -} - -bool LightState::supports_effects() { return !this->effects_.empty(); } -void LightState::set_transformer_(std::unique_ptr transformer) { - this->transformer_ = std::move(transformer); -} -void LightState::stop_effect_() { - auto *effect = this->get_active_effect_(); - if (effect != nullptr) { - effect->stop(); - } - this->active_effect_index_ = 0; -} - -void LightState::set_default_transition_length(uint32_t default_transition_length) { - this->default_transition_length_ = default_transition_length; -} -#ifdef USE_JSON -void LightState::dump_json(JsonObject &root) { - if (this->supports_effects()) - root["effect"] = this->get_effect_name(); - this->remote_values.dump_json(root, this->output_->get_traits()); -} -#endif +LightTraits LightState::get_traits() { return this->output_->get_traits(); } +LightCall LightState::turn_on() { return this->make_call().set_state(true); } +LightCall LightState::turn_off() { return this->make_call().set_state(false); } +LightCall LightState::toggle() { return this->make_call().set_state(!this->remote_values.is_on()); } +LightCall LightState::make_call() { return LightCall(this); } struct LightStateRTCState { bool state{false}; @@ -144,6 +77,17 @@ void LightState::setup() { } call.perform(); } +void LightState::dump_config() { + ESP_LOGCONFIG(TAG, "Light '%s'", this->get_name().c_str()); + if (this->get_traits().get_supports_brightness()) { + ESP_LOGCONFIG(TAG, " Default Transition Length: %.1fs", this->default_transition_length_ / 1e3f); + ESP_LOGCONFIG(TAG, " Gamma Correct: %.2f", this->gamma_correct_); + } + if (this->get_traits().get_supports_color_temperature()) { + ESP_LOGCONFIG(TAG, " Min Mireds: %.1f", this->get_traits().get_min_mireds()); + ESP_LOGCONFIG(TAG, " Max Mireds: %.1f", this->get_traits().get_max_mireds()); + } +} void LightState::loop() { // Apply effect (if any) auto *effect = this->get_active_effect_(); @@ -171,7 +115,47 @@ void LightState::loop() { this->next_write_ = false; } } -LightTraits LightState::get_traits() { return this->output_->get_traits(); } + +float LightState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; } +uint32_t LightState::hash_base() { return 1114400283; } + +LightColorValues LightState::get_current_values() { return this->current_values; } +LightColorValues LightState::get_remote_values() { return this->remote_values; } + +void LightState::publish_state() { + this->remote_values_callback_.call(); + this->next_write_ = true; +} + +LightOutput *LightState::get_output() const { return this->output_; } +std::string LightState::get_effect_name() { + if (this->active_effect_index_ > 0) + return this->effects_[this->active_effect_index_ - 1]->get_name(); + else + return "None"; +} + +void LightState::add_new_remote_values_callback(std::function &&send_callback) { + this->remote_values_callback_.add(std::move(send_callback)); +} +void LightState::add_new_target_state_reached_callback(std::function &&send_callback) { + this->target_state_reached_callback_.add(std::move(send_callback)); +} + +#ifdef USE_JSON +void LightState::dump_json(JsonObject &root) { + if (this->supports_effects()) + root["effect"] = this->get_effect_name(); + this->remote_values.dump_json(root, this->output_->get_traits()); +} +#endif + +void LightState::set_default_transition_length(uint32_t default_transition_length) { + this->default_transition_length_ = default_transition_length; +} +void LightState::set_gamma_correct(float gamma_correct) { this->gamma_correct_ = gamma_correct; } +void LightState::set_restore_mode(LightRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } +bool LightState::supports_effects() { return !this->effects_.empty(); } const std::vector &LightState::get_effects() const { return this->effects_; } void LightState::add_effects(const std::vector &effects) { this->effects_.reserve(this->effects_.size() + effects.size()); @@ -179,551 +163,7 @@ void LightState::add_effects(const std::vector &effects) { this->effects_.push_back(effect); } } -LightCall LightState::turn_on() { return this->make_call().set_state(true); } -LightCall LightState::turn_off() { return this->make_call().set_state(false); } -LightCall LightState::toggle() { return this->make_call().set_state(!this->remote_values.is_on()); } -LightCall LightState::make_call() { return LightCall(this); } -uint32_t LightState::hash_base() { return 1114400283; } -void LightState::dump_config() { - ESP_LOGCONFIG(TAG, "Light '%s'", this->get_name().c_str()); - if (this->get_traits().get_supports_brightness()) { - ESP_LOGCONFIG(TAG, " Default Transition Length: %.1fs", this->default_transition_length_ / 1e3f); - ESP_LOGCONFIG(TAG, " Gamma Correct: %.2f", this->gamma_correct_); - } - if (this->get_traits().get_supports_color_temperature()) { - ESP_LOGCONFIG(TAG, " Min Mireds: %.1f", this->get_traits().get_min_mireds()); - ESP_LOGCONFIG(TAG, " Max Mireds: %.1f", this->get_traits().get_max_mireds()); - } -} -#ifdef USE_MQTT_LIGHT -MQTTJSONLightComponent *LightState::get_mqtt() const { return this->mqtt_; } -void LightState::set_mqtt(MQTTJSONLightComponent *mqtt) { this->mqtt_ = mqtt; } -#endif -#ifdef USE_JSON -LightCall &LightCall::parse_color_json(JsonObject &root) { - if (root.containsKey("state")) { - auto val = parse_on_off(root["state"]); - switch (val) { - case PARSE_ON: - this->set_state(true); - break; - case PARSE_OFF: - this->set_state(false); - break; - case PARSE_TOGGLE: - this->set_state(!this->parent_->remote_values.is_on()); - break; - case PARSE_NONE: - break; - } - } - - if (root.containsKey("brightness")) { - this->set_brightness(float(root["brightness"]) / 255.0f); - } - - if (root.containsKey("color")) { - JsonObject &color = root["color"]; - if (color.containsKey("r")) { - this->set_red(float(color["r"]) / 255.0f); - } - if (color.containsKey("g")) { - this->set_green(float(color["g"]) / 255.0f); - } - if (color.containsKey("b")) { - this->set_blue(float(color["b"]) / 255.0f); - } - } - - if (root.containsKey("white_value")) { - this->set_white(float(root["white_value"]) / 255.0f); - } - - if (root.containsKey("color_temp")) { - this->set_color_temperature(float(root["color_temp"])); - } - - return *this; -} -LightCall &LightCall::parse_json(JsonObject &root) { - this->parse_color_json(root); - - if (root.containsKey("flash")) { - auto length = uint32_t(float(root["flash"]) * 1000); - this->set_flash_length(length); - } - - if (root.containsKey("transition")) { - auto length = uint32_t(float(root["transition"]) * 1000); - this->set_transition_length(length); - } - - if (root.containsKey("effect")) { - const char *effect = root["effect"]; - this->set_effect(effect); - } - - return *this; -} -#endif - -void LightCall::perform() { - // use remote values for fallback - const char *name = this->parent_->get_name().c_str(); - if (this->publish_) { - ESP_LOGD(TAG, "'%s' Setting:", name); - } - - LightColorValues v = this->validate_(); - - if (this->publish_) { - // Only print state when it's being changed - bool current_state = this->parent_->remote_values.is_on(); - if (this->state_.value_or(current_state) != current_state) { - ESP_LOGD(TAG, " State: %s", ONOFF(v.is_on())); - } - - if (this->brightness_.has_value()) { - ESP_LOGD(TAG, " Brightness: %.0f%%", v.get_brightness() * 100.0f); - } - - if (this->color_temperature_.has_value()) { - ESP_LOGD(TAG, " Color Temperature: %.1f mireds", v.get_color_temperature()); - } - - if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { - ESP_LOGD(TAG, " Red=%.0f%%, Green=%.0f%%, Blue=%.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f, - v.get_blue() * 100.0f); - } - if (this->white_.has_value()) { - ESP_LOGD(TAG, " White Value: %.0f%%", v.get_white() * 100.0f); - } - } - - if (this->has_flash_()) { - // FLASH - if (this->publish_) { - ESP_LOGD(TAG, " Flash Length: %.1fs", *this->flash_length_ / 1e3f); - } - - this->parent_->start_flash_(v, *this->flash_length_); - } else if (this->has_transition_()) { - // TRANSITION - if (this->publish_) { - ESP_LOGD(TAG, " Transition Length: %.1fs", *this->transition_length_ / 1e3f); - } - - // Special case: Transition and effect can be set when turning off - if (this->has_effect_()) { - if (this->publish_) { - ESP_LOGD(TAG, " Effect: 'None'"); - } - this->parent_->stop_effect_(); - } - - this->parent_->start_transition_(v, *this->transition_length_); - - } else if (this->has_effect_()) { - // EFFECT - auto effect = this->effect_; - const char *effect_s; - if (effect == 0) - effect_s = "None"; - else - effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str(); - - if (this->publish_) { - ESP_LOGD(TAG, " Effect: '%s'", effect_s); - } - - this->parent_->start_effect_(*this->effect_); - - // Also set light color values when starting an effect - // For example to turn off the light - this->parent_->set_immediately_(v, true); - } else { - // INSTANT CHANGE - this->parent_->set_immediately_(v, this->publish_); - } - - if (!this->has_transition_()) { - this->parent_->target_state_reached_callback_.call(); - } - if (this->publish_) { - this->parent_->publish_state(); - } - - if (this->save_) { - LightStateRTCState saved; - saved.state = v.is_on(); - saved.brightness = v.get_brightness(); - saved.red = v.get_red(); - saved.green = v.get_green(); - saved.blue = v.get_blue(); - saved.white = v.get_white(); - saved.color_temp = v.get_color_temperature(); - saved.effect = this->parent_->active_effect_index_; - this->parent_->rtc_.save(&saved); - } -} - -LightColorValues LightCall::validate_() { - // use remote values for fallback - auto *name = this->parent_->get_name().c_str(); - auto traits = this->parent_->get_traits(); - - // Brightness exists check - if (this->brightness_.has_value() && !traits.get_supports_brightness()) { - ESP_LOGW(TAG, "'%s' - This light does not support setting brightness!", name); - this->brightness_.reset(); - } - - // Transition length possible check - if (this->transition_length_.has_value() && *this->transition_length_ != 0 && !traits.get_supports_brightness()) { - ESP_LOGW(TAG, "'%s' - This light does not support transitions!", name); - this->transition_length_.reset(); - } - - // RGB exists check - if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { - if (!traits.get_supports_rgb()) { - ESP_LOGW(TAG, "'%s' - This light does not support setting RGB color!", name); - this->red_.reset(); - this->green_.reset(); - this->blue_.reset(); - } - } - - // White value exists check - if (this->white_.has_value() && !traits.get_supports_rgb_white_value()) { - ESP_LOGW(TAG, "'%s' - This light does not support setting white value!", name); - this->white_.reset(); - } - - // Color temperature exists check - if (this->color_temperature_.has_value() && !traits.get_supports_color_temperature()) { - ESP_LOGW(TAG, "'%s' - This light does not support setting color temperature!", name); - this->color_temperature_.reset(); - } - - // If white channel is specified, set RGB to white color (when interlock is enabled) - if (this->white_.has_value()) { - if (traits.get_supports_color_interlock()) { - if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) { - this->red_ = optional(1.0f); - this->green_ = optional(1.0f); - this->blue_ = optional(1.0f); - } - // make white values binary aka 0.0f or 1.0f... this allows brightness to do its job - if (*this->white_ > 0.0f) { - this->white_ = optional(1.0f); - } else { - this->white_ = optional(0.0f); - } - } - } - // If only a color channel is specified, set white channel to 100% for white, otherwise 0% (when interlock is enabled) - else if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { - if (traits.get_supports_color_interlock()) { - if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f) { - this->white_ = optional(1.0f); - } else { - this->white_ = optional(0.0f); - } - } - } - // If only a color temperature is specified, change to white light - else if (this->color_temperature_.has_value()) { - this->red_ = optional(1.0f); - this->green_ = optional(1.0f); - this->blue_ = optional(1.0f); - - // if setting color temperature from color (i.e. switching to white light), set White to 100% - auto cv = this->parent_->remote_values; - bool was_color = cv.get_red() != 1.0f || cv.get_blue() != 1.0f || cv.get_green() != 1.0f; - if (traits.get_supports_color_interlock() || was_color) { - this->white_ = optional(1.0f); - } - } - -#define VALIDATE_RANGE_(name_, upper_name) \ - if (name_##_.has_value()) { \ - auto val = *name_##_; \ - if (val < 0.0f || val > 1.0f) { \ - ESP_LOGW(TAG, "'%s' - %s value %.2f is out of range [0.0 - 1.0]!", name, upper_name, val); \ - name_##_ = clamp(val, 0.0f, 1.0f); \ - } \ - } -#define VALIDATE_RANGE(name, upper_name) VALIDATE_RANGE_(name, upper_name) - - // Range checks - VALIDATE_RANGE(brightness, "Brightness") - VALIDATE_RANGE(red, "Red") - VALIDATE_RANGE(green, "Green") - VALIDATE_RANGE(blue, "Blue") - VALIDATE_RANGE(white, "White") - - auto v = this->parent_->remote_values; - if (this->state_.has_value()) - v.set_state(*this->state_); - if (this->brightness_.has_value()) - v.set_brightness(*this->brightness_); - - if (this->red_.has_value()) - v.set_red(*this->red_); - if (this->green_.has_value()) - v.set_green(*this->green_); - if (this->blue_.has_value()) - v.set_blue(*this->blue_); - if (this->white_.has_value()) - v.set_white(*this->white_); - - if (this->color_temperature_.has_value()) - v.set_color_temperature(*this->color_temperature_); - - v.normalize_color(traits); - - // Flash length check - if (this->has_flash_() && *this->flash_length_ == 0) { - ESP_LOGW(TAG, "'%s' - Flash length must be greater than zero!", name); - this->flash_length_.reset(); - } - - // validate transition length/flash length/effect not used at the same time - bool supports_transition = traits.get_supports_brightness(); - - // If effect is already active, remove effect start - if (this->has_effect_() && *this->effect_ == this->parent_->active_effect_index_) { - this->effect_.reset(); - } - - // validate effect index - if (this->has_effect_() && *this->effect_ > this->parent_->effects_.size()) { - ESP_LOGW(TAG, "'%s' Invalid effect index %u", name, *this->effect_); - this->effect_.reset(); - } - - if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) { - ESP_LOGW(TAG, "'%s' - Effect cannot be used together with transition/flash!", name); - this->transition_length_.reset(); - this->flash_length_.reset(); - } - - if (this->has_flash_() && this->has_transition_()) { - ESP_LOGW(TAG, "'%s' - Flash cannot be used together with transition!", name); - this->transition_length_.reset(); - } - - if (!this->has_transition_() && !this->has_flash_() && (!this->has_effect_() || *this->effect_ == 0) && - supports_transition) { - // nothing specified and light supports transitions, set default transition length - this->transition_length_ = this->parent_->default_transition_length_; - } - - if (this->transition_length_.value_or(0) == 0) { - // 0 transition is interpreted as no transition (instant change) - this->transition_length_.reset(); - } - - if (this->has_transition_() && !supports_transition) { - ESP_LOGW(TAG, "'%s' - Light does not support transitions!", name); - this->transition_length_.reset(); - } - - // If not a flash and turning the light off, then disable the light - // Do not use light color values directly, so that effects can set 0% brightness - // Reason: When user turns off the light in frontend, the effect should also stop - if (!this->has_flash_() && !this->state_.value_or(v.is_on())) { - if (this->has_effect_()) { - ESP_LOGW(TAG, "'%s' - Cannot start an effect when turning off!", name); - this->effect_.reset(); - } else if (this->parent_->active_effect_index_ != 0) { - // Auto turn off effect - this->effect_ = 0; - } - } - - // Disable saving for flashes - if (this->has_flash_()) - this->save_ = false; - - return v; -} -LightCall &LightCall::set_effect(const std::string &effect) { - if (strcasecmp(effect.c_str(), "none") == 0) { - this->set_effect(0); - return *this; - } - - bool found = false; - for (uint32_t i = 0; i < this->parent_->effects_.size(); i++) { - LightEffect *e = this->parent_->effects_[i]; - - if (strcasecmp(effect.c_str(), e->get_name().c_str()) == 0) { - this->set_effect(i + 1); - found = true; - break; - } - } - if (!found) { - ESP_LOGW(TAG, "'%s' - No such effect '%s'", this->parent_->get_name().c_str(), effect.c_str()); - } - return *this; -} -LightCall &LightCall::from_light_color_values(const LightColorValues &values) { - this->set_state(values.is_on()); - this->set_brightness_if_supported(values.get_brightness()); - this->set_red_if_supported(values.get_red()); - this->set_green_if_supported(values.get_green()); - this->set_blue_if_supported(values.get_blue()); - this->set_white_if_supported(values.get_white()); - this->set_color_temperature_if_supported(values.get_color_temperature()); - return *this; -} -LightCall &LightCall::set_transition_length_if_supported(uint32_t transition_length) { - if (this->parent_->get_traits().get_supports_brightness()) - this->set_transition_length(transition_length); - return *this; -} -LightCall &LightCall::set_brightness_if_supported(float brightness) { - if (this->parent_->get_traits().get_supports_brightness()) - this->set_brightness(brightness); - return *this; -} -LightCall &LightCall::set_red_if_supported(float red) { - if (this->parent_->get_traits().get_supports_rgb()) - this->set_red(red); - return *this; -} -LightCall &LightCall::set_green_if_supported(float green) { - if (this->parent_->get_traits().get_supports_rgb()) - this->set_green(green); - return *this; -} -LightCall &LightCall::set_blue_if_supported(float blue) { - if (this->parent_->get_traits().get_supports_rgb()) - this->set_blue(blue); - return *this; -} -LightCall &LightCall::set_white_if_supported(float white) { - if (this->parent_->get_traits().get_supports_rgb_white_value()) - this->set_white(white); - return *this; -} -LightCall &LightCall::set_color_temperature_if_supported(float color_temperature) { - if (this->parent_->get_traits().get_supports_color_temperature()) - this->set_color_temperature(color_temperature); - return *this; -} -LightCall &LightCall::set_state(optional state) { - this->state_ = state; - return *this; -} -LightCall &LightCall::set_state(bool state) { - this->state_ = state; - return *this; -} -LightCall &LightCall::set_transition_length(optional transition_length) { - this->transition_length_ = transition_length; - return *this; -} -LightCall &LightCall::set_transition_length(uint32_t transition_length) { - this->transition_length_ = transition_length; - return *this; -} -LightCall &LightCall::set_flash_length(optional flash_length) { - this->flash_length_ = flash_length; - return *this; -} -LightCall &LightCall::set_flash_length(uint32_t flash_length) { - this->flash_length_ = flash_length; - return *this; -} -LightCall &LightCall::set_brightness(optional brightness) { - this->brightness_ = brightness; - return *this; -} -LightCall &LightCall::set_brightness(float brightness) { - this->brightness_ = brightness; - return *this; -} -LightCall &LightCall::set_red(optional red) { - this->red_ = red; - return *this; -} -LightCall &LightCall::set_red(float red) { - this->red_ = red; - return *this; -} -LightCall &LightCall::set_green(optional green) { - this->green_ = green; - return *this; -} -LightCall &LightCall::set_green(float green) { - this->green_ = green; - return *this; -} -LightCall &LightCall::set_blue(optional blue) { - this->blue_ = blue; - return *this; -} -LightCall &LightCall::set_blue(float blue) { - this->blue_ = blue; - return *this; -} -LightCall &LightCall::set_white(optional white) { - this->white_ = white; - return *this; -} -LightCall &LightCall::set_white(float white) { - this->white_ = white; - return *this; -} -LightCall &LightCall::set_color_temperature(optional color_temperature) { - this->color_temperature_ = color_temperature; - return *this; -} -LightCall &LightCall::set_color_temperature(float color_temperature) { - this->color_temperature_ = color_temperature; - return *this; -} -LightCall &LightCall::set_effect(optional effect) { - if (effect.has_value()) - this->set_effect(*effect); - return *this; -} -LightCall &LightCall::set_effect(uint32_t effect_number) { - this->effect_ = effect_number; - return *this; -} -LightCall &LightCall::set_effect(optional effect_number) { - this->effect_ = effect_number; - return *this; -} -LightCall &LightCall::set_publish(bool publish) { - this->publish_ = publish; - return *this; -} -LightCall &LightCall::set_save(bool save) { - this->save_ = save; - return *this; -} -LightCall &LightCall::set_rgb(float red, float green, float blue) { - this->set_red(red); - this->set_green(green); - this->set_blue(blue); - return *this; -} -LightCall &LightCall::set_rgbw(float red, float green, float blue, float white) { - this->set_rgb(red, green, blue); - this->set_white(white); - return *this; -} - -float LightState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; } -LightOutput *LightState::get_output() const { return this->output_; } -void LightState::set_gamma_correct(float gamma_correct) { this->gamma_correct_ = gamma_correct; } void LightState::current_values_as_binary(bool *binary) { this->current_values.as_binary(binary); } void LightState::current_values_as_brightness(float *brightness) { this->current_values.as_brightness(brightness, this->gamma_correct_); @@ -748,19 +188,70 @@ void LightState::current_values_as_cwww(float *cold_white, float *warm_white, bo this->current_values.as_cwww(traits.get_min_mireds(), traits.get_max_mireds(), cold_white, warm_white, this->gamma_correct_, constant_brightness); } -void LightState::add_new_remote_values_callback(std::function &&send_callback) { - this->remote_values_callback_.add(std::move(send_callback)); -} -void LightState::add_new_target_state_reached_callback(std::function &&send_callback) { - this->target_state_reached_callback_.add(std::move(send_callback)); -} +void LightState::start_effect_(uint32_t effect_index) { + this->stop_effect_(); + if (effect_index == 0) + return; + + this->active_effect_index_ = effect_index; + auto *effect = this->get_active_effect_(); + effect->start_internal(); +} LightEffect *LightState::get_active_effect_() { if (this->active_effect_index_ == 0) return nullptr; else return this->effects_[this->active_effect_index_ - 1]; } +void LightState::stop_effect_() { + auto *effect = this->get_active_effect_(); + if (effect != nullptr) { + effect->stop(); + } + this->active_effect_index_ = 0; +} + +void LightState::start_transition_(const LightColorValues &target, uint32_t length) { + this->transformer_ = make_unique(millis(), length, this->current_values, target); + this->remote_values = this->transformer_->get_remote_values(); +} + +void LightState::start_flash_(const LightColorValues &target, uint32_t length) { + LightColorValues end_colors = this->current_values; + // If starting a flash if one is already happening, set end values to end values of current flash + // Hacky but works + if (this->transformer_ != nullptr) + end_colors = this->transformer_->get_end_values(); + this->transformer_ = make_unique(millis(), length, end_colors, target); + this->remote_values = this->transformer_->get_remote_values(); +} + +void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) { + this->transformer_ = nullptr; + this->current_values = target; + if (set_remote_values) { + this->remote_values = target; + } + this->next_write_ = true; +} + +void LightState::set_transformer_(std::unique_ptr transformer) { + this->transformer_ = std::move(transformer); +} + +void LightState::save_remote_values_() { + LightStateRTCState saved; + saved.state = this->remote_values.is_on(); + saved.brightness = this->remote_values.get_brightness(); + saved.red = this->remote_values.get_red(); + saved.green = this->remote_values.get_green(); + saved.blue = this->remote_values.get_blue(); + saved.white = this->remote_values.get_white(); + saved.color_temp = this->remote_values.get_color_temperature(); + saved.effect = this->active_effect_index_; + this->rtc_.save(&saved); +} } // namespace light } // namespace esphome diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index 10bda2d17b..5f3441daf7 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -5,161 +5,15 @@ #include "esphome/core/preferences.h" #include "light_effect.h" #include "light_color_values.h" +#include "light_call.h" #include "light_traits.h" #include "light_transformer.h" namespace esphome { namespace light { -class LightState; class LightOutput; -class LightCall { - public: - explicit LightCall(LightState *parent) : parent_(parent) {} - - /// Set the binary ON/OFF state of the light. - LightCall &set_state(optional state); - /// Set the binary ON/OFF state of the light. - LightCall &set_state(bool state); - /** Set the transition length of this call in milliseconds. - * - * This argument is ignored for starting flashes and effects. - * - * Defaults to the default transition length defined in the light configuration. - */ - LightCall &set_transition_length(optional transition_length); - /** Set the transition length of this call in milliseconds. - * - * This argument is ignored for starting flashes and effects. - * - * Defaults to the default transition length defined in the light configuration. - */ - LightCall &set_transition_length(uint32_t transition_length); - /// Set the transition length property if the light supports transitions. - LightCall &set_transition_length_if_supported(uint32_t transition_length); - /// Start and set the flash length of this call in milliseconds. - LightCall &set_flash_length(optional flash_length); - /// Start and set the flash length of this call in milliseconds. - LightCall &set_flash_length(uint32_t flash_length); - /// Set the target brightness of the light from 0.0 (fully off) to 1.0 (fully on) - LightCall &set_brightness(optional brightness); - /// Set the target brightness of the light from 0.0 (fully off) to 1.0 (fully on) - LightCall &set_brightness(float brightness); - /// Set the brightness property if the light supports brightness. - LightCall &set_brightness_if_supported(float brightness); - /** Set the red RGB value of the light from 0.0 to 1.0. - * - * Note that this only controls the color of the light, not its brightness. - */ - LightCall &set_red(optional red); - /** Set the red RGB value of the light from 0.0 to 1.0. - * - * Note that this only controls the color of the light, not its brightness. - */ - LightCall &set_red(float red); - /// Set the red property if the light supports RGB. - LightCall &set_red_if_supported(float red); - /** Set the green RGB value of the light from 0.0 to 1.0. - * - * Note that this only controls the color of the light, not its brightness. - */ - LightCall &set_green(optional green); - /** Set the green RGB value of the light from 0.0 to 1.0. - * - * Note that this only controls the color of the light, not its brightness. - */ - LightCall &set_green(float green); - /// Set the green property if the light supports RGB. - LightCall &set_green_if_supported(float green); - /** Set the blue RGB value of the light from 0.0 to 1.0. - * - * Note that this only controls the color of the light, not its brightness. - */ - LightCall &set_blue(optional blue); - /** Set the blue RGB value of the light from 0.0 to 1.0. - * - * Note that this only controls the color of the light, not its brightness. - */ - LightCall &set_blue(float blue); - /// Set the blue property if the light supports RGB. - LightCall &set_blue_if_supported(float blue); - /// Set the white value value of the light from 0.0 to 1.0 for RGBW[W] lights. - LightCall &set_white(optional white); - /// Set the white value value of the light from 0.0 to 1.0 for RGBW[W] lights. - LightCall &set_white(float white); - /// Set the white property if the light supports RGB. - LightCall &set_white_if_supported(float white); - /// Set the color temperature of the light in mireds for CWWW or RGBWW lights. - LightCall &set_color_temperature(optional color_temperature); - /// Set the color temperature of the light in mireds for CWWW or RGBWW lights. - LightCall &set_color_temperature(float color_temperature); - /// Set the color_temperature property if the light supports color temperature. - LightCall &set_color_temperature_if_supported(float color_temperature); - /// Set the effect of the light by its name. - LightCall &set_effect(optional effect); - /// Set the effect of the light by its name. - LightCall &set_effect(const std::string &effect); - /// Set the effect of the light by its internal index number (only for internal use). - LightCall &set_effect(uint32_t effect_number); - LightCall &set_effect(optional effect_number); - /// Set whether this light call should trigger a publish state. - LightCall &set_publish(bool publish); - /// Set whether this light call should trigger a save state to recover them at startup.. - LightCall &set_save(bool save); - - /** Set the RGB color of the light by RGB values. - * - * Please note that this only changes the color of the light, not the brightness. - * - * @param red The red color value from 0.0 to 1.0. - * @param green The green color value from 0.0 to 1.0. - * @param blue The blue color value from 0.0 to 1.0. - * @return The light call for chaining setters. - */ - LightCall &set_rgb(float red, float green, float blue); - /** Set the RGBW color of the light by RGB values. - * - * Please note that this only changes the color of the light, not the brightness. - * - * @param red The red color value from 0.0 to 1.0. - * @param green The green color value from 0.0 to 1.0. - * @param blue The blue color value from 0.0 to 1.0. - * @param white The white color value from 0.0 to 1.0. - * @return The light call for chaining setters. - */ - LightCall &set_rgbw(float red, float green, float blue, float white); -#ifdef USE_JSON - LightCall &parse_color_json(JsonObject &root); - LightCall &parse_json(JsonObject &root); -#endif - LightCall &from_light_color_values(const LightColorValues &values); - - void perform(); - - protected: - /// Validate all properties and return the target light color values. - LightColorValues validate_(); - - bool has_transition_() { return this->transition_length_.has_value(); } - bool has_flash_() { return this->flash_length_.has_value(); } - bool has_effect_() { return this->effect_.has_value(); } - - LightState *parent_; - optional state_; - optional transition_length_; - optional flash_length_; - optional brightness_; - optional red_; - optional green_; - optional blue_; - optional white_; - optional color_temperature_; - optional effect_; - bool publish_{true}; - bool save_{true}; -}; - enum LightRestoreMode { LIGHT_RESTORE_DEFAULT_OFF, LIGHT_RESTORE_DEFAULT_ON, @@ -204,14 +58,6 @@ class LightState : public Nameable, public Component { */ LightColorValues current_values; - /// Deprecated method to access current_values. - ESPDEPRECATED("get_current_values() is deprecated, please use .current_values instead.") - LightColorValues get_current_values(); - - /// Deprecated method to access remote_values. - ESPDEPRECATED("get_remote_values() is deprecated, please use .remote_values instead.") - LightColorValues get_remote_values(); - /** The remote color values reported to the frontend. * * These are different from the "current" values: For example transitions will @@ -222,6 +68,14 @@ class LightState : public Nameable, public Component { */ LightColorValues remote_values; + /// Deprecated method to access current_values. + ESPDEPRECATED("get_current_values() is deprecated, please use .current_values instead.") + LightColorValues get_current_values(); + + /// Deprecated method to access remote_values. + ESPDEPRECATED("get_remote_values() is deprecated, please use .remote_values instead.") + LightColorValues get_remote_values(); + /// Publish the currently active state to the frontend. void publish_state(); @@ -231,29 +85,22 @@ class LightState : public Nameable, public Component { /// Return the name of the current effect, or if no effect is active "None". std::string get_effect_name(); - /** This lets front-end components subscribe to light change events. - * - * This is different from add_new_current_values_callback in that it only sends events for start - * and end values. For example, with transitions it will only send a single callback whereas - * the callback passed in add_new_current_values_callback will be called every loop() cycle when - * a transition is active - * - * Note the callback should get the output values through get_remote_values(). + /** + * This lets front-end components subscribe to light change events. This callback is called once + * when the remote color values are changed. * * @param send_callback The callback. */ void add_new_remote_values_callback(std::function &&send_callback); /** - * The callback is called once the state of current_values and remote_values are equal + * The callback is called once the state of current_values and remote_values are equal (when the + * transition is finished). * * @param send_callback */ void add_new_target_state_reached_callback(std::function &&send_callback); - /// Return whether the light has any effects that meet the trait requirements. - bool supports_effects(); - #ifdef USE_JSON /// Dump the state of this light as JSON. void dump_json(JsonObject &root); @@ -265,10 +112,17 @@ class LightState : public Nameable, public Component { /// Set the gamma correction factor void set_gamma_correct(float gamma_correct); float get_gamma_correct() const { return this->gamma_correct_; } - void set_restore_mode(LightRestoreMode restore_mode) { restore_mode_ = restore_mode; } + /// Set the restore mode of this light + void set_restore_mode(LightRestoreMode restore_mode); + + /// Return whether the light has any effects that meet the trait requirements. + bool supports_effects(); + + /// Get all effects for this light state. const std::vector &get_effects() const; + /// Add effects for this light state. void add_effects(const std::vector &effects); void current_values_as_binary(bool *binary); @@ -293,6 +147,8 @@ class LightState : public Nameable, public Component { /// Internal method to start an effect with the given index void start_effect_(uint32_t effect_index); + /// Internal method to get the currently active effect + LightEffect *get_active_effect_(); /// Internal method to stop the current effect (if one is active). void stop_effect_(); /// Internal method to start a transition to the target color with the given length. @@ -307,18 +163,21 @@ class LightState : public Nameable, public Component { /// Internal method to start a transformer. void set_transformer_(std::unique_ptr transformer); - LightEffect *get_active_effect_(); + /// Internal method to save the current remote_values to the preferences + void save_remote_values_(); - /// Object used to store the persisted values of the light. - ESPPreferenceObject rtc_; - /// Restore mode of the light. - LightRestoreMode restore_mode_; - /// Default transition length for all transitions in ms. - uint32_t default_transition_length_{}; + /// Store the output to allow effects to have more access. + LightOutput *output_; /// Value for storing the index of the currently active effect. 0 if no effect is active uint32_t active_effect_index_{}; /// The currently active transformer for this light (transition/flash). std::unique_ptr transformer_{nullptr}; + /// Whether the light value should be written in the next cycle. + bool next_write_{true}; + + /// Object used to store the persisted values of the light. + ESPPreferenceObject rtc_; + /** Callback to call when new values for the frontend are available. * * "Remote values" are light color values that are reported to the frontend and have a lower @@ -333,11 +192,12 @@ class LightState : public Nameable, public Component { */ CallbackManager target_state_reached_callback_{}; - LightOutput *output_; ///< Store the output to allow effects to have more access. - /// Whether the light value should be written in the next cycle. - bool next_write_{true}; + /// Default transition length for all transitions in ms. + uint32_t default_transition_length_{}; /// Gamma correction factor for the light. float gamma_correct_{}; + /// Restore mode of the light. + LightRestoreMode restore_mode_; /// List of effects for this light. std::vector effects_; }; diff --git a/esphome/core/optional.h b/esphome/core/optional.h index 7ace7b122a..5b96781e63 100644 --- a/esphome/core/optional.h +++ b/esphome/core/optional.h @@ -16,6 +16,8 @@ // // Modified by Otto Winter on 18.05.18 +#include + namespace esphome { // type for nullopt From 93f8ee7e60cc8b16651dbce4c74fabb1e2686115 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 14 Jun 2021 23:12:06 +0200 Subject: [PATCH 0906/1841] Fix non-const global (#1907) --- esphome/components/light/light_call.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 359da51de9..3fbe921aa1 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -5,7 +5,7 @@ namespace esphome { namespace light { -static const char *TAG = "light"; +static const char *const TAG = "light"; #ifdef USE_JSON LightCall &LightCall::parse_color_json(JsonObject &root) { From c4110436810dfc11da9735b0eddd2097603a5664 Mon Sep 17 00:00:00 2001 From: dentra Date: Tue, 15 Jun 2021 01:45:22 +0300 Subject: [PATCH 0907/1841] Adds support cpp to vscode (#1828) Co-authored-by: Stefan Agner --- .devcontainer/devcontainer.json | 37 +++++++++++++++++++++++++----- .editorconfig | 10 ++++++-- docker/Dockerfile.dev | 14 +---------- script/bump-docker-base-version.py | 5 ---- script/devcontainer-post-create | 18 +++++++++++++++ 5 files changed, 58 insertions(+), 26 deletions(-) create mode 100755 script/devcontainer-post-create diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8f7d751437..0e6fe19e0e 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": "/bin/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/.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/docker/Dockerfile.dev b/docker/Dockerfile.dev index ebcf14d1bc..27eaa18da9 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -1,13 +1 @@ -FROM esphome/esphome-base-amd64:3.4.0 - -COPY . . - -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - python3-wheel \ - net-tools \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /workspaces -ENV SHELL /bin/bash +FROM esphome/esphome-lint:1.1 diff --git a/script/bump-docker-base-version.py b/script/bump-docker-base-version.py index 765a330ce4..f5f4399fd2 100755 --- a/script/bump-docker-base-version.py +++ b/script/bump-docker-base-version.py @@ -28,11 +28,6 @@ def write_version(version: str): r"ARG BUILD_FROM=esphome/esphome-base-amd64:.*", f"ARG BUILD_FROM=esphome/esphome-base-amd64:{version}", ) - sub( - "docker/Dockerfile.dev", - r"FROM esphome/esphome-base-amd64:.*", - f"FROM esphome/esphome-base-amd64:{version}", - ) sub( "docker/Dockerfile.lint", r"FROM esphome/esphome-lint-base:.*", diff --git a/script/devcontainer-post-create b/script/devcontainer-post-create new file mode 100755 index 0000000000..f4e5ce0d5a --- /dev/null +++ b/script/devcontainer-post-create @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e +# set -x + +mkdir -p config +pip3 install -e . + +cpp_json=.vscode/c_cpp_properties.json +if [ ! -f $cpp_json ]; then + echo "Initializing PlatformIO..." + pio init --ide vscode --silent + sed -i "/\\/workspaces\/esphome\/include/d" $cpp_json +else + echo "Cpp environment already configured. To reconfigure it you could run one the following commands:" + echo " pio init --ide vscode -e livingroom8266" + echo " pio init --ide vscode -e livingroom32" +fi From da7eb9ac907bd5f47ef21c93c8c7c08c79edfac4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 15 Jun 2021 11:05:10 +1200 Subject: [PATCH 0908/1841] Allow no networks or AP to be set. (#1908) Co-authored-by: Paulus Schoutsen --- esphome/components/wifi/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index c45e179bc4..851c5f1b90 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -157,9 +157,7 @@ def _validate(config): config[CONF_NETWORKS] = cv.ensure_list(WIFI_NETWORK_STA)(network) if (CONF_NETWORKS not in config) and (CONF_AP not in config): - raise cv.Invalid( - "Please specify at least an SSID or an Access Point " "to create." - ) + config[CONF_NETWORKS] = [] if config.get(CONF_FAST_CONNECT, False): networks = config.get(CONF_NETWORKS, []) From 86710ed4835ea84de714927634b6f78e6e0a88b5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 15 Jun 2021 13:16:43 +1200 Subject: [PATCH 0909/1841] Validate that either networks, ap, or improv is set up (#1910) --- esphome/components/wifi/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 851c5f1b90..a701aa37e5 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -137,6 +137,18 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend( ) +def validate(config, item_config): + if ( + (CONF_NETWORKS in item_config) + and (item_config[CONF_NETWORKS] == []) + and (CONF_AP not in item_config) + ): + if "esp32_improv" not in config: + raise ValueError( + "Please specify at least an SSID or an Access Point to create." + ) + + def _validate(config): if CONF_PASSWORD in config and CONF_SSID not in config: raise cv.Invalid("Cannot have WiFi password without SSID!") From 92bbedfa5aef0b44cae8b256460f8a1a28fa0904 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 15 Jun 2021 08:48:18 +0200 Subject: [PATCH 0910/1841] Fix #1908 mutating input parameter --- esphome/components/wifi/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index a701aa37e5..13d698d245 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -169,6 +169,7 @@ def _validate(config): config[CONF_NETWORKS] = cv.ensure_list(WIFI_NETWORK_STA)(network) if (CONF_NETWORKS not in config) and (CONF_AP not in config): + config = config.copy() config[CONF_NETWORKS] = [] if config.get(CONF_FAST_CONNECT, False): From a80f9ed3367a297d709f095ed9324aa47a93617d Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Tue, 15 Jun 2021 08:50:58 +0200 Subject: [PATCH 0911/1841] Support ESP8266 Arduino 3.0.0 (#1897) Co-authored-by: Otto Winter --- esphome/components/sensor/filter.cpp | 2 +- esphome/components/wifi/wifi_component_esp8266.cpp | 14 ++++++++++++++ esphome/const.py | 1 + esphome/core/helpers.cpp | 2 +- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index a2058f7d90..bbe47b43ec 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -237,7 +237,7 @@ optional FilterOutValueFilter::new_value(float value) { return value; } else { int8_t accuracy = this->parent_->get_accuracy_decimals(); - float accuracy_mult = pow10f(accuracy); + float accuracy_mult = powf(10.0f, accuracy); float rounded_filter_out = roundf(accuracy_mult * this->value_to_filter_out_); float rounded_value = roundf(accuracy_mult * value); if (rounded_filter_out == rounded_value) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 0425f58c28..2f6c32aec6 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -10,6 +10,10 @@ #include #endif +#ifdef WIFI_IS_OFF_AT_BOOT // Identifies ESP8266 Arduino 3.0.0 +#define ARDUINO_ESP8266_RELEASE_3 +#endif + extern "C" { #include "lwip/err.h" #include "lwip/dns.h" @@ -18,6 +22,12 @@ extern "C" { #if LWIP_IPV6 #include "lwip/netif.h" // struct netif #endif +#ifdef ARDUINO_ESP8266_RELEASE_3 +#include "LwipDhcpServer.h" +#define wifi_softap_set_dhcps_lease(lease) dhcpSoftAP.set_dhcps_lease(lease) +#define wifi_softap_set_dhcps_lease_time(time) dhcpSoftAP.set_dhcps_lease_time(time) +#define wifi_softap_set_dhcps_offer_option(offer, mode) dhcpSoftAP.set_dhcps_offer_option(offer, mode) +#endif } #include "esphome/core/helpers.h" @@ -649,6 +659,10 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { return false; } +#ifdef ARDUINO_ESP8266_RELEASE_3 + dhcpSoftAP.begin(&info); +#endif + struct dhcps_lease lease {}; IPAddress start_address = info.ip.addr; start_address[3] += 99; diff --git a/esphome/const.py b/esphome/const.py index b50b60a204..bb20c606ce 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -28,6 +28,7 @@ ARDUINO_VERSION_ESP32 = { # See also https://github.com/platformio/platform-espressif8266/releases ARDUINO_VERSION_ESP8266 = { "dev": "https://github.com/platformio/platform-espressif8266.git", + "3.0.0": "platformio/espressif8266@3.0.0", "2.7.4": "platformio/espressif8266@2.6.2", "2.7.3": "platformio/espressif8266@2.6.1", "2.7.2": "platformio/espressif8266@2.6.0", diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index f78dcf3183..194ab08af3 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -105,7 +105,7 @@ std::string truncate_string(const std::string &s, size_t length) { } std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { - auto multiplier = float(pow10(accuracy_decimals)); + auto multiplier = float(powf(10.0f, accuracy_decimals)); float value_rounded = roundf(value * multiplier) / multiplier; char tmp[32]; // should be enough, but we should maybe improve this at some point. dtostrf(value_rounded, 0, uint8_t(std::max(0, int(accuracy_decimals))), tmp); From d781f3a11bed164be3b782216ca0f7a7edc462ca Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 15 Jun 2021 01:18:27 -0700 Subject: [PATCH 0912/1841] Bump frontend to 20210614.0 (#1912) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e7f69865da..6ae20849da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210611.0 +esphome-dashboard==20210614.1 From 424c34225f831882d313d203da30792d6aab7d31 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 15 Jun 2021 10:19:21 +0200 Subject: [PATCH 0913/1841] Run script/setup in devcontainer instead of pip install (#1913) --- script/devcontainer-post-create | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/devcontainer-post-create b/script/devcontainer-post-create index f4e5ce0d5a..4b4dc5b6c9 100755 --- a/script/devcontainer-post-create +++ b/script/devcontainer-post-create @@ -4,7 +4,7 @@ set -e # set -x mkdir -p config -pip3 install -e . +script/setup cpp_json=.vscode/c_cpp_properties.json if [ ! -f $cpp_json ]; then From 24ba9eba460658a1e60a0dd6030e91c530e4c28c Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Tue, 15 Jun 2021 05:20:24 -0300 Subject: [PATCH 0914/1841] fixes compatibility with esphome cfg vscode (#1911) --- esphome/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 9af08a8f21..c7b2151728 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -504,6 +504,7 @@ def parse_args(argv): "version", "clean", "dashboard", + "vscode", ], ) @@ -686,7 +687,7 @@ def run_esphome(argv): CORE.dashboard = args.dashboard setup_log(args.verbose, args.quiet) - if args.deprecated_argv_suggestion is not None: + if args.deprecated_argv_suggestion is not None and args.command != "vscode": _LOGGER.warning( "Calling ESPHome with the configuration before the command is deprecated " "and will be removed in the future. " From d8264166842a71af0c4d1a95894658b96b4bdb06 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 15 Jun 2021 11:05:10 +1200 Subject: [PATCH 0915/1841] Allow no networks or AP to be set. (#1908) Co-authored-by: Paulus Schoutsen --- esphome/components/wifi/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index c45e179bc4..851c5f1b90 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -157,9 +157,7 @@ def _validate(config): config[CONF_NETWORKS] = cv.ensure_list(WIFI_NETWORK_STA)(network) if (CONF_NETWORKS not in config) and (CONF_AP not in config): - raise cv.Invalid( - "Please specify at least an SSID or an Access Point " "to create." - ) + config[CONF_NETWORKS] = [] if config.get(CONF_FAST_CONNECT, False): networks = config.get(CONF_NETWORKS, []) From 106f0d611f1bcfa14b9da79e753ecf7441dab88a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 15 Jun 2021 13:16:43 +1200 Subject: [PATCH 0916/1841] Validate that either networks, ap, or improv is set up (#1910) --- esphome/components/wifi/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 851c5f1b90..a701aa37e5 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -137,6 +137,18 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend( ) +def validate(config, item_config): + if ( + (CONF_NETWORKS in item_config) + and (item_config[CONF_NETWORKS] == []) + and (CONF_AP not in item_config) + ): + if "esp32_improv" not in config: + raise ValueError( + "Please specify at least an SSID or an Access Point to create." + ) + + def _validate(config): if CONF_PASSWORD in config and CONF_SSID not in config: raise cv.Invalid("Cannot have WiFi password without SSID!") From 744ca1af7c47f047f1414c31effe769079471e2b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 15 Jun 2021 01:18:27 -0700 Subject: [PATCH 0917/1841] Bump frontend to 20210614.0 (#1912) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e7f69865da..6ae20849da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210611.0 +esphome-dashboard==20210614.1 From 429caccefad7b747131c7511e7378c9ded829a12 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Tue, 15 Jun 2021 05:20:24 -0300 Subject: [PATCH 0918/1841] fixes compatibility with esphome cfg vscode (#1911) --- esphome/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 9af08a8f21..c7b2151728 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -504,6 +504,7 @@ def parse_args(argv): "version", "clean", "dashboard", + "vscode", ], ) @@ -686,7 +687,7 @@ def run_esphome(argv): CORE.dashboard = args.dashboard setup_log(args.verbose, args.quiet) - if args.deprecated_argv_suggestion is not None: + if args.deprecated_argv_suggestion is not None and args.command != "vscode": _LOGGER.warning( "Calling ESPHome with the configuration before the command is deprecated " "and will be removed in the future. " From e8bdbc45a9f4abd976dd4fadb3f916b7160c49c9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 15 Jun 2021 20:26:53 +1200 Subject: [PATCH 0919/1841] Bump version to v1.19.0b4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f7ad39b95d..a17c847953 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b3" +PATCH_VERSION = "0b4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 25b116048c7d7d22b82f30b8234d7efe6b035b1e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 15 Jun 2021 14:04:17 -0700 Subject: [PATCH 0920/1841] Bump dashboard to 20210615.0 (#1918) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6ae20849da..33436fb1b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210614.1 +esphome-dashboard==20210615.0 From 28635124f9a6d57161ed328e9b1f57124eccbc01 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 15 Jun 2021 14:04:17 -0700 Subject: [PATCH 0921/1841] Bump dashboard to 20210615.0 (#1918) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6ae20849da..33436fb1b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210614.1 +esphome-dashboard==20210615.0 From 69e6cf2c0c887ed76abb56eb9a1925ffa9b2517b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Jun 2021 09:06:50 +1200 Subject: [PATCH 0922/1841] Bump version to v1.19.0b5 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a17c847953..b9a22d2613 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b4" +PATCH_VERSION = "0b5" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 5591832b5024399a8dbb087655d3e51292dd9b87 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Jun 2021 13:49:06 +1200 Subject: [PATCH 0923/1841] Shorten the ble name to prevent crash with long device names (#1920) --- esphome/components/esp32_ble/ble.cpp | 11 ++++++++++- esphome/core/application.h | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 756affaead..a54ee48b30 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -91,7 +91,16 @@ bool ESP32BLE::ble_setup_() { } } - err = esp_ble_gap_set_device_name(App.get_name().c_str()); + std::string name = App.get_name(); + if (name.length() > 20) { + if (App.is_name_add_mac_suffix_enabled()) { + name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address + } else { + name = name.substr(0, 20); + } + } + + err = esp_ble_gap_set_device_name(name.c_str()); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gap_set_device_name failed: %d", err); return false; diff --git a/esphome/core/application.h b/esphome/core/application.h index c674210ac0..774f6e3aa8 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -38,6 +38,7 @@ namespace esphome { class Application { public: void pre_setup(const std::string &name, const char *compilation_time, bool name_add_mac_suffix) { + this->name_add_mac_suffix_ = name_add_mac_suffix; if (name_add_mac_suffix) { this->name_ = name + "-" + get_mac_address().substr(6); } else { @@ -97,6 +98,8 @@ class Application { /// Get the name of this Application set by set_name(). const std::string &get_name() const { return this->name_; } + bool is_name_add_mac_suffix_enabled() const { return this->name_add_mac_suffix_; } + const std::string &get_compilation_time() const { return this->compilation_time_; } /** Set the target interval with which to run the loop() calls. @@ -245,6 +248,7 @@ class Application { std::string name_; std::string compilation_time_; + bool name_add_mac_suffix_; uint32_t last_loop_{0}; uint32_t loop_interval_{16}; int dump_config_at_{-1}; From a5383fd2088d2622b30cfa23e45733b936e20059 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Jun 2021 13:49:06 +1200 Subject: [PATCH 0924/1841] Shorten the ble name to prevent crash with long device names (#1920) --- esphome/components/esp32_ble/ble.cpp | 11 ++++++++++- esphome/core/application.h | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 18e80754df..d245cb9e4d 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -91,7 +91,16 @@ bool ESP32BLE::ble_setup_() { } } - err = esp_ble_gap_set_device_name(App.get_name().c_str()); + std::string name = App.get_name(); + if (name.length() > 20) { + if (App.is_name_add_mac_suffix_enabled()) { + name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address + } else { + name = name.substr(0, 20); + } + } + + err = esp_ble_gap_set_device_name(name.c_str()); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gap_set_device_name failed: %d", err); return false; diff --git a/esphome/core/application.h b/esphome/core/application.h index aeda245161..97c99bd4f9 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -38,6 +38,7 @@ namespace esphome { class Application { public: void pre_setup(const std::string &name, const char *compilation_time, bool name_add_mac_suffix) { + this->name_add_mac_suffix_ = name_add_mac_suffix; if (name_add_mac_suffix) { this->name_ = name + "-" + get_mac_address().substr(6); } else { @@ -97,6 +98,8 @@ class Application { /// Get the name of this Application set by set_name(). const std::string &get_name() const { return this->name_; } + bool is_name_add_mac_suffix_enabled() const { return this->name_add_mac_suffix_; } + const std::string &get_compilation_time() const { return this->compilation_time_; } /** Set the target interval with which to run the loop() calls. @@ -245,6 +248,7 @@ class Application { std::string name_; std::string compilation_time_; + bool name_add_mac_suffix_; uint32_t last_loop_{0}; uint32_t loop_interval_{16}; int dump_config_at_{-1}; From fff3645901b760b10c6ed29f470a149b9963db87 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Jun 2021 13:51:31 +1200 Subject: [PATCH 0925/1841] Bump version to v1.19.0b6 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index b9a22d2613..abaf55dece 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b5" +PATCH_VERSION = "0b6" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 970563e07b997f2b6c7c5d5edc7a36a23be65af3 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Jun 2021 21:00:51 +1200 Subject: [PATCH 0926/1841] Bump version to v1.19.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index abaf55dece..3492858240 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b6" +PATCH_VERSION = "0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 607c3ae65151dd791a02c92d92eac08ffff2baaa Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 16 Jun 2021 19:39:04 +0200 Subject: [PATCH 0927/1841] Fix update-all from dashboard (#1924) --- esphome/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index c7b2151728..48f8bea083 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -394,7 +394,7 @@ def command_update_all(args): import click success = {} - files = list_yaml_files(args.configuration) + files = list_yaml_files(args.configuration[0]) twidth = 60 def print_bar(middle_text): @@ -408,7 +408,7 @@ def command_update_all(args): print("-" * twidth) print() rc = run_external_process( - "esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA" + "esphome", "--dashboard", "run", "--no-logs", "--device", "OTA", f ) if rc == 0: print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f)) From f763daa577cad745c98e51803e5dcf69d16a4ac4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 16 Jun 2021 19:39:04 +0200 Subject: [PATCH 0928/1841] Fix update-all from dashboard (#1924) --- esphome/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index c7b2151728..48f8bea083 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -394,7 +394,7 @@ def command_update_all(args): import click success = {} - files = list_yaml_files(args.configuration) + files = list_yaml_files(args.configuration[0]) twidth = 60 def print_bar(middle_text): @@ -408,7 +408,7 @@ def command_update_all(args): print("-" * twidth) print() rc = run_external_process( - "esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA" + "esphome", "--dashboard", "run", "--no-logs", "--device", "OTA", f ) if rc == 0: print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f)) From f310ca1b748ca52b845ba04f6cd32bc794396c21 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 17 Jun 2021 05:40:55 +1200 Subject: [PATCH 0929/1841] Bump version to v1.19.0b7 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index abaf55dece..53acf3d84e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b6" +PATCH_VERSION = "0b7" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From e5929225eb936957317a1a4a43004909031130b3 Mon Sep 17 00:00:00 2001 From: Barry Loong Date: Thu, 17 Jun 2021 17:39:59 +0800 Subject: [PATCH 0930/1841] Fix typo in test3.yaml (#1928) --- tests/test3.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test3.yaml b/tests/test3.yaml index af5398b604..c709539309 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1097,27 +1097,27 @@ fingerprint_grow: new_password: 0xA65B9840 on_finger_scan_matched: - homeassistant.event: - event: esphome.${devicename}_fingerprint_grow_finger_scan_matched + 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.${devicename}_fingerprint_grow_finger_scan_unmatched + event: esphome.${device_name}_fingerprint_grow_finger_scan_unmatched on_enrollment_scan: - homeassistant.event: - event: esphome.${devicename}_fingerprint_grow_enrollment_scan + 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.${devicename}_fingerprint_grow_node_enrollment_done + event: esphome.${device_name}_fingerprint_grow_node_enrollment_done data: finger_id: !lambda 'return finger_id;' on_enrollment_failed: - homeassistant.event: - event: esphome.${devicename}_fingerprint_grow_enrollment_failed + event: esphome.${device_name}_fingerprint_grow_enrollment_failed data: finger_id: !lambda 'return finger_id;' uart_id: uart6 From ef1e91d838f3222587d8d55fe0e372fc901f878c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 17 Jun 2021 12:35:54 -0700 Subject: [PATCH 0931/1841] Update dashboard to 20210617.0 (#1930) --- esphome/dashboard/dashboard.py | 28 +++++++++++++--------------- requirements.txt | 2 +- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 90061a3d4e..00b12199c0 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -716,9 +716,6 @@ class LogoutHandler(BaseHandler): self.redirect("./login") -_STATIC_FILE_HASHES = {} - - def get_base_frontend_path(): if ENV_DEV not in os.environ: import esphome_dashboard @@ -741,19 +738,23 @@ def get_static_path(*args): return os.path.join(get_base_frontend_path(), "static", *args) +@functools.lru_cache(maxsize=None) def get_static_file_url(name): + base = f"./static/{name}" + + if ENV_DEV in os.environ: + return base + # Module imports can't deduplicate if stuff added to url if name == "js/esphome/index.js": - return f"./static/{name}" + import esphome_dashboard - if name in _STATIC_FILE_HASHES: - hash_ = _STATIC_FILE_HASHES[name] - else: - path = get_static_path(name) - with open(path, "rb") as f_handle: - hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8] - _STATIC_FILE_HASHES[name] = hash_ - return f"./static/{name}?hash={hash_}" + return base.replace("index.js", esphome_dashboard.entrypoint()) + + path = get_static_path(name) + with open(path, "rb") as f_handle: + hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8] + return f"{base}?hash={hash_}" def make_app(debug=get_bool_env(ENV_DEV)): @@ -820,9 +821,6 @@ def make_app(debug=get_bool_env(ENV_DEV)): **app_settings, ) - if debug: - _STATIC_FILE_HASHES.clear() - return app diff --git a/requirements.txt b/requirements.txt index 33436fb1b1..f02d65e518 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210615.0 +esphome-dashboard==20210617.1 From c19b3ecd4387dd7b235bcdb1acbbac37f73df387 Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Thu, 17 Jun 2021 23:39:13 +0400 Subject: [PATCH 0932/1841] Fix: midea_ac: fixed query status frame (#1922) --- esphome/components/midea_ac/midea_frame.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp index 3d210d89f0..4041ca7923 100644 --- a/esphome/components/midea_ac/midea_frame.cpp +++ b/esphome/components/midea_ac/midea_frame.cpp @@ -8,9 +8,9 @@ const std::string MIDEA_SILENT_FAN_MODE = "silent"; const std::string MIDEA_TURBO_FAN_MODE = "turbo"; const std::string MIDEA_FREEZE_PROTECTION_PRESET = "freeze protection"; -const uint8_t QueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x00, - 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x68}; +const uint8_t QueryFrame::INIT[] = {0xAA, 0x21, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x61, + 0x00, 0xFF, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE3, 0xA8}; const uint8_t PowerQueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x21, 0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, From 2419bc367820b985d153d7ffdc304949cc242adb Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 17 Jun 2021 21:54:14 +0200 Subject: [PATCH 0933/1841] Improve config final validation (#1917) --- esphome/components/cse7766/sensor.py | 9 +- esphome/components/dfplayer/__init__.py | 9 +- esphome/components/gps/__init__.py | 5 +- esphome/components/http_request/__init__.py | 68 +++++++-------- esphome/components/rc522_spi/__init__.py | 9 +- esphome/components/rtttl/__init__.py | 24 +++-- esphome/components/sim800l/__init__.py | 7 +- esphome/components/spi/__init__.py | 28 ++++-- esphome/components/uart/__init__.py | 97 +++++++++++++-------- esphome/components/wifi/__init__.py | 22 ++--- esphome/config.py | 76 ++++++++++------ esphome/config_validation.py | 8 +- esphome/core/__init__.py | 10 +-- esphome/cpp_helpers.py | 3 +- esphome/final_validate.py | 57 ++++++++++++ esphome/loader.py | 10 ++- esphome/storage_json.py | 5 +- esphome/types.py | 18 ++++ 18 files changed, 303 insertions(+), 162 deletions(-) create mode 100644 esphome/final_validate.py create mode 100644 esphome/types.py diff --git a/esphome/components/cse7766/sensor.py b/esphome/components/cse7766/sensor.py index 98cf4da96d..4ccb346efd 100644 --- a/esphome/components/cse7766/sensor.py +++ b/esphome/components/cse7766/sensor.py @@ -45,6 +45,9 @@ CONFIG_SCHEMA = ( .extend(cv.polling_component_schema("60s")) .extend(uart.UART_DEVICE_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "cse7766", baud_rate=4800, require_rx=True +) async def to_code(config): @@ -64,9 +67,3 @@ async def to_code(config): conf = config[CONF_POWER] sens = await sensor.new_sensor(conf) cg.add(var.set_power_sensor(sens)) - - -def validate(config, item_config): - uart.validate_device( - "cse7766", config, item_config, baud_rate=4800, require_tx=False - ) diff --git a/esphome/components/dfplayer/__init__.py b/esphome/components/dfplayer/__init__.py index 6af83888ab..3cdfc8ab85 100644 --- a/esphome/components/dfplayer/__init__.py +++ b/esphome/components/dfplayer/__init__.py @@ -68,6 +68,9 @@ CONFIG_SCHEMA = cv.All( } ).extend(uart.UART_DEVICE_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "dfplayer", baud_rate=9600, require_tx=True +) async def to_code(config): @@ -80,12 +83,6 @@ async def to_code(config): await automation.build_automation(trigger, [], conf) -def validate(config, item_config): - uart.validate_device( - "dfplayer", config, item_config, baud_rate=9600, require_rx=False - ) - - @automation.register_action( "dfplayer.play_next", NextAction, diff --git a/esphome/components/gps/__init__.py b/esphome/components/gps/__init__.py index de3eae1115..2867aa7325 100644 --- a/esphome/components/gps/__init__.py +++ b/esphome/components/gps/__init__.py @@ -62,6 +62,7 @@ CONFIG_SCHEMA = ( .extend(cv.polling_component_schema("20s")) .extend(uart.UART_DEVICE_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema("gps", require_rx=True) async def to_code(config): @@ -95,7 +96,3 @@ async def to_code(config): # https://platformio.org/lib/show/1655/TinyGPSPlus cg.add_library("1655", "1.0.2") # TinyGPSPlus, has name conflict - - -def validate(config, item_config): - uart.validate_device("gps", config, item_config, require_tx=False) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index 650602150a..f4475060df 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -2,6 +2,7 @@ import urllib.parse as urlparse import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import automation from esphome.const import ( CONF_ID, @@ -14,7 +15,6 @@ from esphome.const import ( CONF_URL, ) from esphome.core import CORE, Lambda -from esphome.core.config import PLATFORMIO_ESP8266_LUT DEPENDENCIES = ["network"] AUTO_LOAD = ["json"] @@ -36,29 +36,6 @@ CONF_VERIFY_SSL = "verify_ssl" CONF_ON_RESPONSE = "on_response" -def validate_framework(config): - if CORE.is_esp32: - return config - - version = "RECOMMENDED" - if CONF_ARDUINO_VERSION in CORE.raw_config[CONF_ESPHOME]: - version = CORE.raw_config[CONF_ESPHOME][CONF_ARDUINO_VERSION] - - if version in ["LATEST", "DEV"]: - return config - - framework = ( - PLATFORMIO_ESP8266_LUT[version] - if version in PLATFORMIO_ESP8266_LUT - else version - ) - if framework < ARDUINO_VERSION_ESP8266["2.5.1"]: - raise cv.Invalid( - "This component is not supported on arduino framework version below 2.5.1" - ) - return config - - def validate_url(value): value = cv.string(value) try: @@ -92,19 +69,36 @@ def validate_secure_url(config): return config -CONFIG_SCHEMA = ( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(HttpRequestComponent), - cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string, - cv.Optional( - CONF_TIMEOUT, default="5s" - ): cv.positive_time_period_milliseconds, - } - ) - .add_extra(validate_framework) - .extend(cv.COMPONENT_SCHEMA) -) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(HttpRequestComponent), + cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string, + cv.Optional(CONF_TIMEOUT, default="5s"): cv.positive_time_period_milliseconds, + } +).extend(cv.COMPONENT_SCHEMA) + + +def validate_framework(config): + if CORE.is_esp32: + return + + # only for ESP8266 + path = [CONF_ESPHOME, CONF_ARDUINO_VERSION] + version: str = fv.full_config.get().get_config_for_path(path) + + reverse_map = {v: k for k, v in ARDUINO_VERSION_ESP8266.items()} + framework_version = reverse_map.get(version) + if framework_version is None or framework_version == "dev": + return + + if framework_version < "2.5.1": + raise cv.Invalid( + "This component is not supported on arduino framework version below 2.5.1", + path=[cv.ROOT_CONFIG_PATH] + path, + ) + + +FINAL_VALIDATE_SCHEMA = cv.Schema(validate_framework) async def to_code(config): diff --git a/esphome/components/rc522_spi/__init__.py b/esphome/components/rc522_spi/__init__.py index 68b1e64145..77b0a99662 100644 --- a/esphome/components/rc522_spi/__init__.py +++ b/esphome/components/rc522_spi/__init__.py @@ -19,13 +19,12 @@ CONFIG_SCHEMA = cv.All( ).extend(spi.spi_device_schema(cs_pin_required=True)) ) +FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( + "rc522_spi", require_miso=True, require_mosi=True +) + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await rc522.setup_rc522(var, config) await spi.register_spi_device(var, config) - - -def validate(config, item_config): - # validate given SPI hub is suitable for rc522_spi, it needs both miso and mosi - spi.validate_device("rc522_spi", config, item_config, True, True) diff --git a/esphome/components/rtttl/__init__.py b/esphome/components/rtttl/__init__.py index 7f860fe3d7..e9453896ac 100644 --- a/esphome/components/rtttl/__init__.py +++ b/esphome/components/rtttl/__init__.py @@ -1,6 +1,7 @@ import logging import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import automation from esphome.components.output import FloatOutput from esphome.const import CONF_ID, CONF_OUTPUT, CONF_PLATFORM, CONF_TRIGGER_ID @@ -36,12 +37,8 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -def validate(config, item_config): - # Not adding this to FloatOutput as this is the only component which needs `update_frequency` - - parent_config = config.get_config_by_id(item_config[CONF_OUTPUT]) - platform = parent_config[CONF_PLATFORM] - +def validate_parent_output_config(value): + platform = value.get(CONF_PLATFORM) PWM_GOOD = ["esp8266_pwm", "ledc"] PWM_BAD = [ "ac_dimmer ", @@ -55,14 +52,25 @@ def validate(config, item_config): ] if platform in PWM_BAD: - raise ValueError(f"Component rtttl cannot use {platform} as output component") + raise cv.Invalid(f"Component rtttl cannot use {platform} as output component") if platform not in PWM_GOOD: _LOGGER.warning( - "Component rtttl is not known to work with the selected output type. Make sure this output supports custom frequency output method." + "Component rtttl is not known to work with the selected output type. " + "Make sure this output supports custom frequency output method." ) +FINAL_VALIDATE_SCHEMA = cv.Schema( + { + cv.Required(CONF_OUTPUT): fv.id_declaration_match_schema( + validate_parent_output_config + ) + }, + extra=cv.ALLOW_EXTRA, +) + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/sim800l/__init__.py b/esphome/components/sim800l/__init__.py index 40c011a769..0887b8640f 100644 --- a/esphome/components/sim800l/__init__.py +++ b/esphome/components/sim800l/__init__.py @@ -40,6 +40,9 @@ CONFIG_SCHEMA = cv.All( .extend(cv.polling_component_schema("5s")) .extend(uart.UART_DEVICE_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "sim800l", baud_rate=9600, require_tx=True, require_rx=True +) async def to_code(config): @@ -54,10 +57,6 @@ async def to_code(config): ) -def validate(config, item_config): - uart.validate_device("sim800l", config, item_config, baud_rate=9600) - - SIM800L_SEND_SMS_SCHEMA = cv.Schema( { cv.GenerateID(): cv.use_id(Sim800LComponent), diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index e6e073c4a4..803a45814c 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import pins from esphome.const import ( CONF_CLK_PIN, @@ -69,9 +70,24 @@ async def register_spi_device(var, config): cg.add(var.set_cs_pin(pin)) -def validate_device(name, config, item_config, require_mosi, require_miso): - spi_config = config.get_config_by_id(item_config[CONF_SPI_ID]) - if require_mosi and CONF_MISO_PIN not in spi_config: - raise ValueError(f"Component {name} requires parent spi to declare miso_pin") - if require_miso and CONF_MOSI_PIN not in spi_config: - raise ValueError(f"Component {name} requires parent spi to declare mosi_pin") +def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: bool): + hub_schema = {} + if require_miso: + hub_schema[ + cv.Required( + CONF_MISO_PIN, + msg=f"Component {name} requires this spi bus to declare a miso_pin", + ) + ] = cv.valid + if require_mosi: + hub_schema[ + cv.Required( + CONF_MOSI_PIN, + msg=f"Component {name} requires this spi bus to declare a mosi_pin", + ) + ] = cv.valid + + return cv.Schema( + {cv.Required(CONF_SPI_ID): fv.id_declaration_match_schema(hub_schema)}, + extra=cv.ALLOW_EXTRA, + ) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index aaed333e34..d2fcac2cb6 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -1,5 +1,8 @@ +from typing import Optional + import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import pins, automation from esphome.const import ( CONF_BAUD_RATE, @@ -92,42 +95,6 @@ async def to_code(config): cg.add(var.set_parity(config[CONF_PARITY])) -def validate_device( - name, config, item_config, baud_rate=None, require_tx=True, require_rx=True -): - if not hasattr(config, "uart_devices"): - config.uart_devices = {} - devices = config.uart_devices - - uart_config = config.get_config_by_id(item_config[CONF_UART_ID]) - - uart_id = uart_config[CONF_ID] - device = devices.setdefault(uart_id, {}) - - if require_tx: - if CONF_TX_PIN not in uart_config: - raise ValueError(f"Component {name} requires parent uart to declare tx_pin") - if CONF_TX_PIN in device: - raise ValueError( - f"Component {name} cannot use the same uart.{CONF_TX_PIN} as component {device[CONF_TX_PIN]} is already using it" - ) - device[CONF_TX_PIN] = name - - if require_rx: - if CONF_RX_PIN not in uart_config: - raise ValueError(f"Component {name} requires parent uart to declare rx_pin") - if CONF_RX_PIN in device: - raise ValueError( - f"Component {name} cannot use the same uart.{CONF_RX_PIN} as component {device[CONF_RX_PIN]} is already using it" - ) - device[CONF_RX_PIN] = name - - if baud_rate and uart_config[CONF_BAUD_RATE] != baud_rate: - raise ValueError( - f"Component {name} requires parent uart baud rate be {baud_rate}" - ) - - # A schema to use for all UART devices, all UART integrations must extend this! UART_DEVICE_SCHEMA = cv.Schema( { @@ -135,6 +102,64 @@ UART_DEVICE_SCHEMA = cv.Schema( } ) +KEY_UART_DEVICES = "uart_devices" + + +def final_validate_device_schema( + name: str, + *, + baud_rate: Optional[int] = None, + require_tx: bool = False, + require_rx: bool = False, +): + def validate_baud_rate(value): + if value != baud_rate: + raise cv.Invalid( + f"Component {name} required baud rate {baud_rate} for the uart bus" + ) + return value + + def validate_pin(opt, device): + def validator(value): + if opt in device: + raise cv.Invalid( + f"The uart {opt} is used both by {name} and {device[opt]}, " + f"but can only be used by one. Please create a new uart bus for {name}." + ) + device[opt] = name + return value + + return validator + + def validate_hub(hub_config): + hub_schema = {} + uart_id = hub_config[CONF_ID] + devices = fv.full_config.get().data.setdefault(KEY_UART_DEVICES, {}) + device = devices.setdefault(uart_id, {}) + + if require_tx: + hub_schema[ + cv.Required( + CONF_TX_PIN, + msg=f"Component {name} requires this uart bus to declare a tx_pin", + ) + ] = validate_pin(CONF_TX_PIN, device) + if require_rx: + hub_schema[ + cv.Required( + CONF_RX_PIN, + msg=f"Component {name} requires this uart bus to declare a rx_pin", + ) + ] = validate_pin(CONF_RX_PIN, device) + if baud_rate is not None: + hub_schema[cv.Required(CONF_BAUD_RATE)] = validate_baud_rate + return cv.Schema(hub_schema, extra=cv.ALLOW_EXTRA)(hub_config) + + return cv.Schema( + {cv.Required(CONF_UART_ID): fv.id_declaration_match_schema(validate_hub)}, + extra=cv.ALLOW_EXTRA, + ) + async def register_uart_device(var, config): """Register a UART device, setting up all the internal values. diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 13d698d245..54307388d6 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import automation from esphome.automation import Condition from esphome.components.network import add_mdns_library @@ -137,16 +138,17 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend( ) -def validate(config, item_config): - if ( - (CONF_NETWORKS in item_config) - and (item_config[CONF_NETWORKS] == []) - and (CONF_AP not in item_config) - ): - if "esp32_improv" not in config: - raise ValueError( - "Please specify at least an SSID or an Access Point to create." - ) +def final_validate(config): + has_sta = bool(config.get(CONF_NETWORKS, True)) + has_ap = CONF_AP in config + has_improv = "esp32_improv" in fv.full_config.get() + if (not has_sta) and (not has_ap) and (not has_improv): + raise cv.Invalid( + "Please specify at least an SSID or an Access Point to create." + ) + + +FINAL_VALIDATE_SCHEMA = cv.Schema(final_validate) def _validate(config): diff --git a/esphome/config.py b/esphome/config.py index fcd2fac90f..93413a009c 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -21,11 +21,13 @@ from esphome.helpers import indent from esphome.util import safe_print, OrderedDict from typing import List, Optional, Tuple, Union -from esphome.core import ConfigType from esphome.loader import get_component, get_platform, ComponentManifest from esphome.yaml_util import is_secret, ESPHomeDataBase, ESPForceValue from esphome.voluptuous_schema import ExtraKeysInvalid from esphome.log import color, Fore +import esphome.final_validate as fv +import esphome.config_validation as cv +from esphome.types import ConfigType, ConfigPathType, ConfigFragmentType _LOGGER = logging.getLogger(__name__) @@ -54,7 +56,7 @@ def _path_begins_with(path, other): # type: (ConfigPath, ConfigPath) -> bool return path[: len(other)] == other -class Config(OrderedDict): +class Config(OrderedDict, fv.FinalValidateConfig): def __init__(self): super().__init__() # A list of voluptuous errors @@ -65,6 +67,7 @@ class Config(OrderedDict): self.output_paths = [] # type: List[Tuple[ConfigPath, str]] # A list of components ids with the config path self.declare_ids = [] # type: List[Tuple[core.ID, ConfigPath]] + self._data = {} def add_error(self, error): # type: (vol.Invalid) -> None @@ -72,6 +75,12 @@ class Config(OrderedDict): for err in error.errors: self.add_error(err) return + if cv.ROOT_CONFIG_PATH in error.path: + # Root value means that the path before the root should be ignored + last_root = max( + i for i, v in enumerate(error.path) if v is cv.ROOT_CONFIG_PATH + ) + error.path = error.path[last_root + 1 :] self.errors.append(error) @contextmanager @@ -140,13 +149,16 @@ class Config(OrderedDict): return doc_range - def get_nested_item(self, path): - # type: (ConfigPath) -> ConfigType + def get_nested_item( + self, path: ConfigPathType, raise_error: bool = False + ) -> ConfigFragmentType: data = self for item_index in path: try: data = data[item_index] except (KeyError, IndexError, TypeError): + if raise_error: + raise return {} return data @@ -163,11 +175,20 @@ class Config(OrderedDict): part.append(item_index) return part - def get_config_by_id(self, id): + def get_path_for_id(self, id: core.ID): + """Return the config fragment where the given ID is declared.""" for declared_id, path in self.declare_ids: if declared_id.id == str(id): - return self.get_nested_item(path[:-1]) - return None + return path + raise KeyError(f"ID {id} not found in configuration") + + def get_config_for_path(self, path: ConfigPathType) -> ConfigFragmentType: + return self.get_nested_item(path, raise_error=True) + + @property + def data(self): + """Return temporary data used by final validation functions.""" + return self._data def iter_ids(config, path=None): @@ -189,23 +210,22 @@ def do_id_pass(result): # type: (Config) -> None from esphome.cpp_generator import MockObjClass from esphome.cpp_types import Component - declare_ids = result.declare_ids # type: List[Tuple[core.ID, ConfigPath]] searching_ids = [] # type: List[Tuple[core.ID, ConfigPath]] for id, path in iter_ids(result): if id.is_declaration: if id.id is not None: # Look for duplicate definitions - match = next((v for v in declare_ids if v[0].id == id.id), None) + match = next((v for v in result.declare_ids if v[0].id == id.id), None) if match is not None: opath = "->".join(str(v) for v in match[1]) result.add_str_error(f"ID {id.id} redefined! Check {opath}", path) continue - declare_ids.append((id, path)) + result.declare_ids.append((id, path)) else: searching_ids.append((id, path)) # Resolve default ids after manual IDs - for id, _ in declare_ids: - id.resolve([v[0].id for v in declare_ids]) + for id, _ in result.declare_ids: + id.resolve([v[0].id for v in result.declare_ids]) if isinstance(id.type, MockObjClass) and id.type.inherits_from(Component): CORE.component_ids.add(id.id) @@ -213,7 +233,7 @@ def do_id_pass(result): # type: (Config) -> None for id, path in searching_ids: if id.id is not None: # manually declared - match = next((v[0] for v in declare_ids if v[0].id == id.id), None) + match = next((v[0] for v in result.declare_ids if v[0].id == id.id), None) if match is None or not match.is_manual: # No declared ID with this name import difflib @@ -224,7 +244,7 @@ def do_id_pass(result): # type: (Config) -> None ) # Find candidates matches = difflib.get_close_matches( - id.id, [v[0].id for v in declare_ids if v[0].is_manual] + id.id, [v[0].id for v in result.declare_ids if v[0].is_manual] ) if matches: matches_s = ", ".join(f'"{x}"' for x in matches) @@ -245,7 +265,7 @@ def do_id_pass(result): # type: (Config) -> None if id.id is None and id.type is not None: matches = [] - for v in declare_ids: + for v in result.declare_ids: if v[0] is None or not isinstance(v[0].type, MockObjClass): continue inherits = v[0].type.inherits_from(id.type) @@ -278,8 +298,6 @@ def do_id_pass(result): # type: (Config) -> None def recursive_check_replaceme(value): - import esphome.config_validation as cv - if isinstance(value, list): return cv.Schema([recursive_check_replaceme])(value) if isinstance(value, dict): @@ -558,14 +576,16 @@ def validate_config(config, command_line_substitutions): # 7. Final validation if not result.errors: # Inter - components validation - for path, conf, comp in validate_queue: - if comp.config_schema is None: + token = fv.full_config.set(result) + + for path, _, comp in validate_queue: + if comp.final_validate_schema is None: continue - if callable(comp.validate): - try: - comp.validate(result, result.get_nested_item(path)) - except ValueError as err: - result.add_str_error(err, path) + conf = result.get_nested_item(path) + with result.catch_error(path): + comp.final_validate_schema(conf) + + fv.full_config.reset(token) return result @@ -621,8 +641,12 @@ def _format_vol_invalid(ex, config): ) elif "extra keys not allowed" in str(ex): message += "[{}] is an invalid option for [{}].".format(ex.path[-1], paren) - elif "required key not provided" in str(ex): - message += "'{}' is a required option for [{}].".format(ex.path[-1], paren) + elif isinstance(ex, vol.RequiredFieldInvalid): + if ex.msg == "required key not provided": + message += "'{}' is a required option for [{}].".format(ex.path[-1], paren) + else: + # Required has set a custom error message + message += ex.msg else: message += humanize_error(config, ex) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 2cdb6b0b76..7292cc3af5 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -75,6 +75,9 @@ Inclusive = vol.Inclusive ALLOW_EXTRA = vol.ALLOW_EXTRA UNDEFINED = vol.UNDEFINED RequiredFieldInvalid = vol.RequiredFieldInvalid +# this sentinel object can be placed in an 'Invalid' path to say +# the rest of the error path is relative to the root config path +ROOT_CONFIG_PATH = object() RESERVED_IDS = [ # C++ keywords http://en.cppreference.com/w/cpp/keyword @@ -218,8 +221,8 @@ class Required(vol.Required): - *not* the `config.get(CONF_)` syntax. """ - def __init__(self, key): - super().__init__(key) + def __init__(self, key, msg=None): + super().__init__(key, msg=msg) def check_not_templatable(value): @@ -1073,6 +1076,7 @@ def invalid(message): def valid(value): + """A validator that is always valid and returns the value as-is.""" return value diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 1841dfd8be..df98e1b150 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -2,7 +2,7 @@ import logging import math import os import re -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple from esphome.const import ( CONF_ARDUINO_VERSION, @@ -23,6 +23,7 @@ from esphome.util import OrderedDict if TYPE_CHECKING: from ..cpp_generator import MockObj, MockObjClass, Statement + from ..types import ConfigType _LOGGER = logging.getLogger(__name__) @@ -462,9 +463,9 @@ class EsphomeCore: # The board that's used (for example nodemcuv2) self.board: Optional[str] = None # The full raw configuration - self.raw_config: Optional[ConfigType] = None + self.raw_config: Optional["ConfigType"] = None # The validated configuration, this is None until the config has been validated - self.config: Optional[ConfigType] = None + self.config: Optional["ConfigType"] = None # The pending tasks in the task queue (mostly for C++ generation) # This is a priority queue (with heapq) # Each item is a tuple of form: (-priority, unique number, task) @@ -752,6 +753,3 @@ class EnumValue: CORE = EsphomeCore() - -ConfigType = Dict[str, Any] -CoreType = EsphomeCore diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 1c52f38e50..1d66eabf6c 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -8,7 +8,8 @@ from esphome.const import ( ) # pylint: disable=unused-import -from esphome.core import coroutine, ID, CORE, ConfigType +from esphome.core import coroutine, ID, CORE +from esphome.types import ConfigType from esphome.cpp_generator import RawExpression, add, get_variable from esphome.cpp_types import App, GPIOPin from esphome.util import Registry, RegistryEntry diff --git a/esphome/final_validate.py b/esphome/final_validate.py new file mode 100644 index 0000000000..50fdbaf3f4 --- /dev/null +++ b/esphome/final_validate.py @@ -0,0 +1,57 @@ +from abc import ABC, abstractmethod, abstractproperty +from typing import Dict, Any +import contextvars + +from esphome.types import ConfigFragmentType, ID, ConfigPathType +import esphome.config_validation as cv + + +class FinalValidateConfig(ABC): + @abstractproperty + 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 diff --git a/esphome/loader.py b/esphome/loader.py index d9d407d787..f74fc6367d 100644 --- a/esphome/loader.py +++ b/esphome/loader.py @@ -12,6 +12,7 @@ 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__) @@ -81,8 +82,13 @@ class ComponentManifest: return getattr(self.module, "CODEOWNERS", []) @property - def validate(self): - return getattr(self.module, "validate", None) + 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]: 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] From dca1c0f1601edca9ed4d08fc9d787f8003606dd7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 18 Jun 2021 11:48:40 +1200 Subject: [PATCH 0934/1841] Replace CLIMATE_MODE_AUTO with CLIMATE_MODE_HEAT_COOL in most cases (#1933) --- esphome/components/bang_bang/bang_bang_climate.cpp | 6 +++--- esphome/components/climate/climate.h | 2 +- esphome/components/climate/climate_traits.cpp | 5 +++++ esphome/components/climate/climate_traits.h | 2 ++ esphome/components/climate_ir/climate_ir.cpp | 2 +- esphome/components/climate_ir_lg/climate_ir_lg.cpp | 12 ++++++------ esphome/components/coolix/coolix.cpp | 8 ++++---- esphome/components/daikin/daikin.cpp | 6 +++--- .../components/fujitsu_general/fujitsu_general.cpp | 4 ++-- esphome/components/hitachi_ac344/hitachi_ac344.cpp | 4 ++-- esphome/components/midea_ac/midea_climate.cpp | 2 +- esphome/components/midea_ac/midea_frame.cpp | 4 ++-- esphome/components/mitsubishi/mitsubishi.cpp | 2 +- esphome/components/pid/pid_climate.cpp | 10 +++++----- esphome/components/tcl112/tcl112.cpp | 4 ++-- esphome/components/thermostat/climate.py | 8 ++++---- .../components/thermostat/thermostat_climate.cpp | 13 +++++++++---- esphome/components/thermostat/thermostat_climate.h | 2 ++ esphome/components/toshiba/toshiba.cpp | 4 ++-- esphome/components/tuya/climate/tuya_climate.cpp | 2 +- esphome/components/whirlpool/whirlpool.cpp | 4 ++-- esphome/components/yashima/yashima.cpp | 4 ++-- 22 files changed, 62 insertions(+), 48 deletions(-) diff --git a/esphome/components/bang_bang/bang_bang_climate.cpp b/esphome/components/bang_bang/bang_bang_climate.cpp index ca361c7012..e3e833ce2a 100644 --- a/esphome/components/bang_bang/bang_bang_climate.cpp +++ b/esphome/components/bang_bang/bang_bang_climate.cpp @@ -21,7 +21,7 @@ void BangBangClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles those for us - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; this->change_away_(false); } } @@ -41,7 +41,7 @@ void BangBangClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits BangBangClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_cool_mode(this->supports_cool_); traits.set_supports_heat_mode(this->supports_heat_); traits.set_supports_two_point_target_temperature(true); @@ -50,7 +50,7 @@ climate::ClimateTraits BangBangClimate::traits() { return traits; } void BangBangClimate::compute_state_() { - if (this->mode != climate::CLIMATE_MODE_AUTO) { + if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) { // in non-auto mode, switch directly to appropriate action // - HEAT mode -> HEATING action // - COOL mode -> COOLING action diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 3ac9270341..b688ac8efb 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -159,7 +159,7 @@ struct ClimateDeviceRestoreState { * * The entire state of the climate device is encoded in public properties of the base class (current_temperature, * mode etc). These are read-only for the user and rw for integrations. The reason these are public - * is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_AUTO) ...` + * is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_HEAT_COOL) ...` */ class Climate : public Nameable { public: diff --git a/esphome/components/climate/climate_traits.cpp b/esphome/components/climate/climate_traits.cpp index eda4722fcb..774ada785f 100644 --- a/esphome/components/climate/climate_traits.cpp +++ b/esphome/components/climate/climate_traits.cpp @@ -8,6 +8,8 @@ bool ClimateTraits::supports_mode(ClimateMode mode) const { switch (mode) { case CLIMATE_MODE_OFF: return true; + case CLIMATE_MODE_HEAT_COOL: + return this->supports_heat_cool_mode_; case CLIMATE_MODE_AUTO: return this->supports_auto_mode_; case CLIMATE_MODE_COOL: @@ -31,6 +33,9 @@ void ClimateTraits::set_supports_two_point_target_temperature(bool supports_two_ supports_two_point_target_temperature_ = supports_two_point_target_temperature; } void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_auto_mode_ = supports_auto_mode; } +void ClimateTraits::set_supports_heat_cool_mode(bool supports_heat_cool_mode) { + supports_heat_cool_mode_ = supports_heat_cool_mode; +} void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; } void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; } void ClimateTraits::set_supports_fan_only_mode(bool supports_fan_only_mode) { diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index f0a48ca308..f8e6f87306 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -46,6 +46,7 @@ class ClimateTraits { bool get_supports_two_point_target_temperature() const; void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature); void set_supports_auto_mode(bool supports_auto_mode); + void set_supports_heat_cool_mode(bool supports_heat_cool_mode); void set_supports_cool_mode(bool supports_cool_mode); void set_supports_heat_mode(bool supports_heat_mode); void set_supports_fan_only_mode(bool supports_fan_only_mode); @@ -100,6 +101,7 @@ class ClimateTraits { bool supports_current_temperature_{false}; bool supports_two_point_target_temperature_{false}; bool supports_auto_mode_{false}; + bool supports_heat_cool_mode_{false}; bool supports_cool_mode_{false}; bool supports_heat_mode_{false}; bool supports_fan_only_mode_{false}; diff --git a/esphome/components/climate_ir/climate_ir.cpp b/esphome/components/climate_ir/climate_ir.cpp index 3c9d118736..5b16ed7fae 100644 --- a/esphome/components/climate_ir/climate_ir.cpp +++ b/esphome/components/climate_ir/climate_ir.cpp @@ -9,7 +9,7 @@ static const char *const TAG = "climate_ir"; climate::ClimateTraits ClimateIR::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->sensor_ != nullptr); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_cool_mode(this->supports_cool_); traits.set_supports_heat_mode(this->supports_heat_); traits.set_supports_dry_mode(this->supports_dry_); diff --git a/esphome/components/climate_ir_lg/climate_ir_lg.cpp b/esphome/components/climate_ir_lg/climate_ir_lg.cpp index 892560db8d..4c721d5e64 100644 --- a/esphome/components/climate_ir_lg/climate_ir_lg.cpp +++ b/esphome/components/climate_ir_lg/climate_ir_lg.cpp @@ -39,7 +39,7 @@ void LgIrClimate::transmit_state() { send_swing_cmd_ = false; remote_state |= COMMAND_SWING; } else { - if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_AUTO) { + if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_HEAT_COOL) { remote_state |= COMMAND_ON_AI; } else if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode != climate::CLIMATE_MODE_OFF) { remote_state |= COMMAND_ON; @@ -52,7 +52,7 @@ void LgIrClimate::transmit_state() { case climate::CLIMATE_MODE_HEAT: remote_state |= COMMAND_HEAT; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state |= COMMAND_AUTO; break; case climate::CLIMATE_MODE_DRY: @@ -89,7 +89,7 @@ void LgIrClimate::transmit_state() { } } - if (this->mode == climate::CLIMATE_MODE_AUTO) { + if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) { this->fan_mode = climate::CLIMATE_FAN_AUTO; // remote_state |= FAN_MODE_AUTO_DRY; } @@ -128,7 +128,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { if ((remote_state & COMMAND_MASK) == COMMAND_ON) { this->mode = climate::CLIMATE_MODE_COOL; } else if ((remote_state & COMMAND_MASK) == COMMAND_ON_AI) { - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; } if ((remote_state & COMMAND_MASK) == COMMAND_OFF) { @@ -138,7 +138,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF; } else { if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) this->mode = climate::CLIMATE_MODE_DRY; else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) { @@ -152,7 +152,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15; // Fan Speed - if (this->mode == climate::CLIMATE_MODE_AUTO) { + if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) { this->fan_mode = climate::CLIMATE_FAN_AUTO; } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT || this->mode == climate::CLIMATE_MODE_DRY) { diff --git a/esphome/components/coolix/coolix.cpp b/esphome/components/coolix/coolix.cpp index b28cee9245..653d8344e0 100644 --- a/esphome/components/coolix/coolix.cpp +++ b/esphome/components/coolix/coolix.cpp @@ -70,7 +70,7 @@ void CoolixClimate::transmit_state() { case climate::CLIMATE_MODE_HEAT: remote_state |= COOLIX_HEAT; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state |= COOLIX_AUTO; break; case climate::CLIMATE_MODE_FAN_ONLY: @@ -89,7 +89,7 @@ void CoolixClimate::transmit_state() { } else { remote_state |= COOLIX_FAN_TEMP_CODE; } - if (this->mode == climate::CLIMATE_MODE_AUTO || this->mode == climate::CLIMATE_MODE_DRY) { + if (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_DRY) { this->fan_mode = climate::CLIMATE_FAN_AUTO; remote_state |= COOLIX_FAN_MODE_AUTO_DRY; } else { @@ -197,7 +197,7 @@ bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) { if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT) this->mode = climate::CLIMATE_MODE_HEAT; else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO) - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) { if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY) this->mode = climate::CLIMATE_MODE_DRY; @@ -207,7 +207,7 @@ bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) { this->mode = climate::CLIMATE_MODE_COOL; // Fan Speed - if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_AUTO || + if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_DRY) this->fan_mode = climate::CLIMATE_FAN_AUTO; else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN) diff --git a/esphome/components/daikin/daikin.cpp b/esphome/components/daikin/daikin.cpp index b426b85183..40734203da 100644 --- a/esphome/components/daikin/daikin.cpp +++ b/esphome/components/daikin/daikin.cpp @@ -77,7 +77,7 @@ uint8_t DaikinClimate::operation_mode_() { case climate::CLIMATE_MODE_HEAT: operating_mode |= DAIKIN_MODE_HEAT; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: operating_mode |= DAIKIN_MODE_AUTO; break; case climate::CLIMATE_MODE_FAN_ONLY: @@ -131,7 +131,7 @@ uint8_t DaikinClimate::temperature_() { switch (this->mode) { case climate::CLIMATE_MODE_FAN_ONLY: return 0x32; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: case climate::CLIMATE_MODE_DRY: return 0xc0; default: @@ -160,7 +160,7 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) { this->mode = climate::CLIMATE_MODE_HEAT; break; case DAIKIN_MODE_AUTO: - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; case DAIKIN_MODE_FAN: this->mode = climate::CLIMATE_MODE_FAN_ONLY; diff --git a/esphome/components/fujitsu_general/fujitsu_general.cpp b/esphome/components/fujitsu_general/fujitsu_general.cpp index 892f9cd726..6dd569fa21 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.cpp +++ b/esphome/components/fujitsu_general/fujitsu_general.cpp @@ -133,7 +133,7 @@ void FujitsuGeneralClimate::transmit_state() { case climate::CLIMATE_MODE_FAN_ONLY: SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_FAN); break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: default: SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_AUTO); break; @@ -344,7 +344,7 @@ bool FujitsuGeneralClimate::on_receive(remote_base::RemoteReceiveData data) { case FUJITSU_GENERAL_MODE_AUTO: default: // TODO: CLIMATE_MODE_10C is missing from esphome - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; } diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.cpp b/esphome/components/hitachi_ac344/hitachi_ac344.cpp index 35b3d17358..1e5bca1396 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.cpp +++ b/esphome/components/hitachi_ac344/hitachi_ac344.cpp @@ -155,7 +155,7 @@ void HitachiClimate::transmit_state() { case climate::CLIMATE_MODE_HEAT: set_mode_(HITACHI_AC344_MODE_HEAT); break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: set_mode_(HITACHI_AC344_MODE_AUTO); break; case climate::CLIMATE_MODE_FAN_ONLY: @@ -251,7 +251,7 @@ bool HitachiClimate::parse_mode_(const uint8_t remote_state[]) { this->mode = climate::CLIMATE_MODE_HEAT; break; case HITACHI_AC344_MODE_AUTO: - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; case HITACHI_AC344_MODE_FAN: this->mode = climate::CLIMATE_MODE_FAN_ONLY; diff --git a/esphome/components/midea_ac/midea_climate.cpp b/esphome/components/midea_ac/midea_climate.cpp index 1efe6c93f8..001d5edb53 100644 --- a/esphome/components/midea_ac/midea_climate.cpp +++ b/esphome/components/midea_ac/midea_climate.cpp @@ -167,7 +167,7 @@ climate::ClimateTraits MideaAC::traits() { traits.set_visual_min_temperature(17); traits.set_visual_max_temperature(30); traits.set_visual_temperature_step(0.5); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_cool_mode(true); traits.set_supports_dry_mode(true); traits.set_supports_heat_mode(true); diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp index 4041ca7923..098beedf7a 100644 --- a/esphome/components/midea_ac/midea_frame.cpp +++ b/esphome/components/midea_ac/midea_frame.cpp @@ -44,7 +44,7 @@ climate::ClimateMode PropertiesFrame::get_mode() const { return climate::CLIMATE_MODE_OFF; switch (this->pbuf_[12] >> 5) { case MIDEA_MODE_AUTO: - return climate::CLIMATE_MODE_AUTO; + return climate::CLIMATE_MODE_HEAT_COOL; case MIDEA_MODE_COOL: return climate::CLIMATE_MODE_COOL; case MIDEA_MODE_DRY: @@ -61,7 +61,7 @@ climate::ClimateMode PropertiesFrame::get_mode() const { void PropertiesFrame::set_mode(climate::ClimateMode mode) { uint8_t m; switch (mode) { - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: m = MIDEA_MODE_AUTO; break; case climate::CLIMATE_MODE_COOL: diff --git a/esphome/components/mitsubishi/mitsubishi.cpp b/esphome/components/mitsubishi/mitsubishi.cpp index d0f07e42dc..0b4b9be1eb 100644 --- a/esphome/components/mitsubishi/mitsubishi.cpp +++ b/esphome/components/mitsubishi/mitsubishi.cpp @@ -33,7 +33,7 @@ void MitsubishiClimate::transmit_state() { case climate::CLIMATE_MODE_HEAT: remote_state[6] = MITSUBISHI_HEAT; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state[6] = MITSUBISHI_AUTO; break; case climate::CLIMATE_MODE_OFF: diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index 4c32d92576..1601f1a0fe 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -20,7 +20,7 @@ void PIDClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles those for us - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; this->target_temperature = this->default_target_temperature_; } } @@ -31,7 +31,7 @@ void PIDClimate::control(const climate::ClimateCall &call) { this->target_temperature = *call.get_target_temperature(); // If switching to non-auto mode, set output immediately - if (this->mode != climate::CLIMATE_MODE_AUTO) + if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) this->handle_non_auto_mode_(); this->publish_state(); @@ -39,7 +39,7 @@ void PIDClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits PIDClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_two_point_target_temperature(false); traits.set_supports_cool_mode(this->supports_cool_()); traits.set_supports_heat_mode(this->supports_heat_()); @@ -121,14 +121,14 @@ void PIDClimate::update_pid_() { // keep autotuner instance so that subsequent dump_configs will print the long result message. } else { value = res.output; - if (mode != climate::CLIMATE_MODE_AUTO) { + if (mode != climate::CLIMATE_MODE_HEAT_COOL) { ESP_LOGW(TAG, "For PID autotuner you need to set AUTO (also called heat/cool) mode!"); } } } } - if (this->mode != climate::CLIMATE_MODE_AUTO) { + if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) { this->handle_non_auto_mode_(); } else { this->write_output_(value); diff --git a/esphome/components/tcl112/tcl112.cpp b/esphome/components/tcl112/tcl112.cpp index da44b3c827..5b938ba0c3 100644 --- a/esphome/components/tcl112/tcl112.cpp +++ b/esphome/components/tcl112/tcl112.cpp @@ -47,7 +47,7 @@ void Tcl112Climate::transmit_state() { // Set mode switch (this->mode) { - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state[6] &= 0xF0; remote_state[6] |= TCL112_AUTO; break; @@ -204,7 +204,7 @@ bool Tcl112Climate::on_receive(remote_base::RemoteReceiveData data) { this->mode = climate::CLIMATE_MODE_FAN_ONLY; break; case TCL112_AUTO: - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; } } diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index 4a371ec165..07a94fd184 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -227,7 +227,7 @@ async def to_code(config): await cg.register_component(var, config) await climate.register_climate(var, config) - auto_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config + heat_cool_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config two_points_available = CONF_HEAT_ACTION in config and ( CONF_COOL_ACTION in config or CONF_FAN_ONLY_ACTION in config ) @@ -258,10 +258,10 @@ async def to_code(config): var.get_idle_action_trigger(), [], config[CONF_IDLE_ACTION] ) - if auto_mode_available is True: - cg.add(var.set_supports_auto(True)) + if heat_cool_mode_available is True: + cg.add(var.set_supports_heat_cool(True)) else: - cg.add(var.set_supports_auto(False)) + cg.add(var.set_supports_heat_cool(False)) if CONF_COOL_ACTION in config: await automation.build_automation( diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 6154f293dc..3fdb4efa7b 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -21,7 +21,7 @@ void ThermostatClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles temps for us - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; this->change_away_(false); } // refresh the climate action based on the restored settings @@ -79,6 +79,7 @@ climate::ClimateTraits ThermostatClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); traits.set_supports_auto_mode(this->supports_auto_); + traits.set_supports_heat_cool_mode(this->supports_heat_cool_); traits.set_supports_cool_mode(this->supports_cool_); traits.set_supports_dry_mode(this->supports_dry_); traits.set_supports_fan_only_mode(this->supports_fan_only_); @@ -130,7 +131,7 @@ climate::ClimateAction ThermostatClimate::compute_action_() { case climate::CLIMATE_MODE_OFF: target_action = climate::CLIMATE_ACTION_OFF; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: case climate::CLIMATE_MODE_COOL: case climate::CLIMATE_MODE_HEAT: if (this->supports_cool_) { @@ -321,7 +322,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) { case climate::CLIMATE_MODE_OFF: trig = this->off_mode_trigger_; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: // trig = this->auto_mode_trigger_; break; case climate::CLIMATE_MODE_COOL: @@ -339,7 +340,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) { default: // we cannot report an invalid mode back to HA (even if it asked for one) // and must assume some valid value - mode = climate::CLIMATE_MODE_AUTO; + mode = climate::CLIMATE_MODE_HEAT_COOL; // trig = this->auto_mode_trigger_; } assert(trig != nullptr); @@ -434,6 +435,9 @@ ThermostatClimate::ThermostatClimate() swing_mode_vertical_trigger_(new Trigger<>()) {} void ThermostatClimate::set_hysteresis(float hysteresis) { this->hysteresis_ = hysteresis; } void ThermostatClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } +void ThermostatClimate::set_supports_heat_cool(bool supports_heat_cool) { + this->supports_heat_cool_ = supports_heat_cool; +} void ThermostatClimate::set_supports_auto(bool supports_auto) { this->supports_auto_ = supports_auto; } void ThermostatClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } void ThermostatClimate::set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; } @@ -521,6 +525,7 @@ void ThermostatClimate::dump_config() { } ESP_LOGCONFIG(TAG, " Hysteresis: %.1f°C", this->hysteresis_); ESP_LOGCONFIG(TAG, " Supports AUTO: %s", YESNO(this->supports_auto_)); + ESP_LOGCONFIG(TAG, " Supports HEAT/COOL: %s", YESNO(this->supports_heat_cool_)); ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_)); ESP_LOGCONFIG(TAG, " Supports DRY: %s", YESNO(this->supports_dry_)); ESP_LOGCONFIG(TAG, " Supports FAN_ONLY: %s", YESNO(this->supports_fan_only_)); diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 86a1007efa..3fd482da53 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -29,6 +29,7 @@ class ThermostatClimate : public climate::Climate, public Component { void set_hysteresis(float hysteresis); void set_sensor(sensor::Sensor *sensor); void set_supports_auto(bool supports_auto); + void set_supports_heat_cool(bool supports_heat_cool); void set_supports_cool(bool supports_cool); void set_supports_dry(bool supports_dry); void set_supports_fan_only(bool supports_fan_only); @@ -113,6 +114,7 @@ class ThermostatClimate : public climate::Climate, public Component { /// A false value for any given attribute means that the controller has no such action /// (for example a thermostat, where only heating and not-heating is possible). bool supports_auto_{false}; + bool supports_heat_cool_{false}; bool supports_cool_{false}; bool supports_dry_{false}; bool supports_fan_only_{false}; diff --git a/esphome/components/toshiba/toshiba.cpp b/esphome/components/toshiba/toshiba.cpp index 4f5b7d8537..c08ae898b5 100644 --- a/esphome/components/toshiba/toshiba.cpp +++ b/esphome/components/toshiba/toshiba.cpp @@ -80,7 +80,7 @@ void ToshibaClimate::transmit_state() { mode = TOSHIBA_MODE_COOL; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: default: mode = TOSHIBA_MODE_AUTO; } @@ -190,7 +190,7 @@ bool ToshibaClimate::on_receive(remote_base::RemoteReceiveData data) { case TOSHIBA_MODE_AUTO: default: /* Note: Dry and Fan-only modes are reported as Auto, as they are not supported yet */ - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; } /* Get the target temperature */ diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index 66fdcbb472..2b6ce833de 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -13,7 +13,7 @@ void TuyaClimate::setup() { this->mode = climate::CLIMATE_MODE_OFF; if (datapoint.value_bool) { if (this->supports_heat_ && this->supports_cool_) { - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; } else if (this->supports_heat_) { this->mode = climate::CLIMATE_MODE_HEAT; } else if (this->supports_cool_) { diff --git a/esphome/components/whirlpool/whirlpool.cpp b/esphome/components/whirlpool/whirlpool.cpp index 07296b6fa5..d705b42a8c 100644 --- a/esphome/components/whirlpool/whirlpool.cpp +++ b/esphome/components/whirlpool/whirlpool.cpp @@ -48,7 +48,7 @@ void WhirlpoolClimate::transmit_state() { this->powered_on_assumed = powered_on; } switch (this->mode) { - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: // set fan auto // set temp auto temp // set sleep false @@ -239,7 +239,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) { this->mode = climate::CLIMATE_MODE_FAN_ONLY; break; case WHIRLPOOL_AUTO: - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; } } diff --git a/esphome/components/yashima/yashima.cpp b/esphome/components/yashima/yashima.cpp index 2615d7a353..bda27b62fb 100644 --- a/esphome/components/yashima/yashima.cpp +++ b/esphome/components/yashima/yashima.cpp @@ -82,7 +82,7 @@ const uint32_t YASHIMA_CARRIER_FREQUENCY = 38000; climate::ClimateTraits YashimaClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->sensor_ != nullptr); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_cool_mode(this->supports_cool_); traits.set_supports_heat_mode(this->supports_heat_); traits.set_supports_two_point_target_temperature(false); @@ -139,7 +139,7 @@ void YashimaClimate::transmit_state_() { // Set mode switch (this->mode) { - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state[0] |= YASHIMA_MODE_AUTO_BYTE0; remote_state[5] |= YASHIMA_MODE_AUTO_BYTE5; break; From 066c1022d0fb7915569c957dbeb1c8c7e3517968 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 17 Jun 2021 12:35:54 -0700 Subject: [PATCH 0935/1841] Update dashboard to 20210617.0 (#1930) --- esphome/dashboard/dashboard.py | 28 +++++++++++++--------------- requirements.txt | 2 +- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 90061a3d4e..00b12199c0 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -716,9 +716,6 @@ class LogoutHandler(BaseHandler): self.redirect("./login") -_STATIC_FILE_HASHES = {} - - def get_base_frontend_path(): if ENV_DEV not in os.environ: import esphome_dashboard @@ -741,19 +738,23 @@ def get_static_path(*args): return os.path.join(get_base_frontend_path(), "static", *args) +@functools.lru_cache(maxsize=None) def get_static_file_url(name): + base = f"./static/{name}" + + if ENV_DEV in os.environ: + return base + # Module imports can't deduplicate if stuff added to url if name == "js/esphome/index.js": - return f"./static/{name}" + import esphome_dashboard - if name in _STATIC_FILE_HASHES: - hash_ = _STATIC_FILE_HASHES[name] - else: - path = get_static_path(name) - with open(path, "rb") as f_handle: - hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8] - _STATIC_FILE_HASHES[name] = hash_ - return f"./static/{name}?hash={hash_}" + return base.replace("index.js", esphome_dashboard.entrypoint()) + + path = get_static_path(name) + with open(path, "rb") as f_handle: + hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8] + return f"{base}?hash={hash_}" def make_app(debug=get_bool_env(ENV_DEV)): @@ -820,9 +821,6 @@ def make_app(debug=get_bool_env(ENV_DEV)): **app_settings, ) - if debug: - _STATIC_FILE_HASHES.clear() - return app diff --git a/requirements.txt b/requirements.txt index 33436fb1b1..f02d65e518 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210615.0 +esphome-dashboard==20210617.1 From a045d001bf6101a63f4ef815449ec94c5b0fdf1e Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Thu, 17 Jun 2021 23:39:13 +0400 Subject: [PATCH 0936/1841] Fix: midea_ac: fixed query status frame (#1922) --- esphome/components/midea_ac/midea_frame.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp index e90155bad3..a29345035b 100644 --- a/esphome/components/midea_ac/midea_frame.cpp +++ b/esphome/components/midea_ac/midea_frame.cpp @@ -8,9 +8,9 @@ const std::string MIDEA_SILENT_FAN_MODE = "silent"; const std::string MIDEA_TURBO_FAN_MODE = "turbo"; const std::string MIDEA_FREEZE_PROTECTION_PRESET = "freeze protection"; -const uint8_t QueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x00, - 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x68}; +const uint8_t QueryFrame::INIT[] = {0xAA, 0x21, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x61, + 0x00, 0xFF, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE3, 0xA8}; const uint8_t PowerQueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x21, 0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, From f1dcf0f0b8266b5e8f12a069bd254479d189e36f Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 17 Jun 2021 21:54:14 +0200 Subject: [PATCH 0937/1841] Improve config final validation (#1917) --- esphome/components/cse7766/sensor.py | 9 +- esphome/components/dfplayer/__init__.py | 9 +- esphome/components/gps/__init__.py | 5 +- esphome/components/http_request/__init__.py | 68 +++++++-------- esphome/components/rc522_spi/__init__.py | 9 +- esphome/components/rtttl/__init__.py | 24 +++-- esphome/components/sim800l/__init__.py | 7 +- esphome/components/spi/__init__.py | 28 ++++-- esphome/components/uart/__init__.py | 97 +++++++++++++-------- esphome/components/wifi/__init__.py | 22 ++--- esphome/config.py | 76 ++++++++++------ esphome/config_validation.py | 8 +- esphome/core/__init__.py | 10 +-- esphome/cpp_helpers.py | 3 +- esphome/final_validate.py | 57 ++++++++++++ esphome/loader.py | 10 ++- esphome/storage_json.py | 5 +- esphome/types.py | 18 ++++ 18 files changed, 303 insertions(+), 162 deletions(-) create mode 100644 esphome/final_validate.py create mode 100644 esphome/types.py diff --git a/esphome/components/cse7766/sensor.py b/esphome/components/cse7766/sensor.py index 98cf4da96d..4ccb346efd 100644 --- a/esphome/components/cse7766/sensor.py +++ b/esphome/components/cse7766/sensor.py @@ -45,6 +45,9 @@ CONFIG_SCHEMA = ( .extend(cv.polling_component_schema("60s")) .extend(uart.UART_DEVICE_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "cse7766", baud_rate=4800, require_rx=True +) async def to_code(config): @@ -64,9 +67,3 @@ async def to_code(config): conf = config[CONF_POWER] sens = await sensor.new_sensor(conf) cg.add(var.set_power_sensor(sens)) - - -def validate(config, item_config): - uart.validate_device( - "cse7766", config, item_config, baud_rate=4800, require_tx=False - ) diff --git a/esphome/components/dfplayer/__init__.py b/esphome/components/dfplayer/__init__.py index 6af83888ab..3cdfc8ab85 100644 --- a/esphome/components/dfplayer/__init__.py +++ b/esphome/components/dfplayer/__init__.py @@ -68,6 +68,9 @@ CONFIG_SCHEMA = cv.All( } ).extend(uart.UART_DEVICE_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "dfplayer", baud_rate=9600, require_tx=True +) async def to_code(config): @@ -80,12 +83,6 @@ async def to_code(config): await automation.build_automation(trigger, [], conf) -def validate(config, item_config): - uart.validate_device( - "dfplayer", config, item_config, baud_rate=9600, require_rx=False - ) - - @automation.register_action( "dfplayer.play_next", NextAction, diff --git a/esphome/components/gps/__init__.py b/esphome/components/gps/__init__.py index de3eae1115..2867aa7325 100644 --- a/esphome/components/gps/__init__.py +++ b/esphome/components/gps/__init__.py @@ -62,6 +62,7 @@ CONFIG_SCHEMA = ( .extend(cv.polling_component_schema("20s")) .extend(uart.UART_DEVICE_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema("gps", require_rx=True) async def to_code(config): @@ -95,7 +96,3 @@ async def to_code(config): # https://platformio.org/lib/show/1655/TinyGPSPlus cg.add_library("1655", "1.0.2") # TinyGPSPlus, has name conflict - - -def validate(config, item_config): - uart.validate_device("gps", config, item_config, require_tx=False) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index 650602150a..f4475060df 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -2,6 +2,7 @@ import urllib.parse as urlparse import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import automation from esphome.const import ( CONF_ID, @@ -14,7 +15,6 @@ from esphome.const import ( CONF_URL, ) from esphome.core import CORE, Lambda -from esphome.core.config import PLATFORMIO_ESP8266_LUT DEPENDENCIES = ["network"] AUTO_LOAD = ["json"] @@ -36,29 +36,6 @@ CONF_VERIFY_SSL = "verify_ssl" CONF_ON_RESPONSE = "on_response" -def validate_framework(config): - if CORE.is_esp32: - return config - - version = "RECOMMENDED" - if CONF_ARDUINO_VERSION in CORE.raw_config[CONF_ESPHOME]: - version = CORE.raw_config[CONF_ESPHOME][CONF_ARDUINO_VERSION] - - if version in ["LATEST", "DEV"]: - return config - - framework = ( - PLATFORMIO_ESP8266_LUT[version] - if version in PLATFORMIO_ESP8266_LUT - else version - ) - if framework < ARDUINO_VERSION_ESP8266["2.5.1"]: - raise cv.Invalid( - "This component is not supported on arduino framework version below 2.5.1" - ) - return config - - def validate_url(value): value = cv.string(value) try: @@ -92,19 +69,36 @@ def validate_secure_url(config): return config -CONFIG_SCHEMA = ( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(HttpRequestComponent), - cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string, - cv.Optional( - CONF_TIMEOUT, default="5s" - ): cv.positive_time_period_milliseconds, - } - ) - .add_extra(validate_framework) - .extend(cv.COMPONENT_SCHEMA) -) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(HttpRequestComponent), + cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string, + cv.Optional(CONF_TIMEOUT, default="5s"): cv.positive_time_period_milliseconds, + } +).extend(cv.COMPONENT_SCHEMA) + + +def validate_framework(config): + if CORE.is_esp32: + return + + # only for ESP8266 + path = [CONF_ESPHOME, CONF_ARDUINO_VERSION] + version: str = fv.full_config.get().get_config_for_path(path) + + reverse_map = {v: k for k, v in ARDUINO_VERSION_ESP8266.items()} + framework_version = reverse_map.get(version) + if framework_version is None or framework_version == "dev": + return + + if framework_version < "2.5.1": + raise cv.Invalid( + "This component is not supported on arduino framework version below 2.5.1", + path=[cv.ROOT_CONFIG_PATH] + path, + ) + + +FINAL_VALIDATE_SCHEMA = cv.Schema(validate_framework) async def to_code(config): diff --git a/esphome/components/rc522_spi/__init__.py b/esphome/components/rc522_spi/__init__.py index 68b1e64145..77b0a99662 100644 --- a/esphome/components/rc522_spi/__init__.py +++ b/esphome/components/rc522_spi/__init__.py @@ -19,13 +19,12 @@ CONFIG_SCHEMA = cv.All( ).extend(spi.spi_device_schema(cs_pin_required=True)) ) +FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( + "rc522_spi", require_miso=True, require_mosi=True +) + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await rc522.setup_rc522(var, config) await spi.register_spi_device(var, config) - - -def validate(config, item_config): - # validate given SPI hub is suitable for rc522_spi, it needs both miso and mosi - spi.validate_device("rc522_spi", config, item_config, True, True) diff --git a/esphome/components/rtttl/__init__.py b/esphome/components/rtttl/__init__.py index 7f860fe3d7..e9453896ac 100644 --- a/esphome/components/rtttl/__init__.py +++ b/esphome/components/rtttl/__init__.py @@ -1,6 +1,7 @@ import logging import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import automation from esphome.components.output import FloatOutput from esphome.const import CONF_ID, CONF_OUTPUT, CONF_PLATFORM, CONF_TRIGGER_ID @@ -36,12 +37,8 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -def validate(config, item_config): - # Not adding this to FloatOutput as this is the only component which needs `update_frequency` - - parent_config = config.get_config_by_id(item_config[CONF_OUTPUT]) - platform = parent_config[CONF_PLATFORM] - +def validate_parent_output_config(value): + platform = value.get(CONF_PLATFORM) PWM_GOOD = ["esp8266_pwm", "ledc"] PWM_BAD = [ "ac_dimmer ", @@ -55,14 +52,25 @@ def validate(config, item_config): ] if platform in PWM_BAD: - raise ValueError(f"Component rtttl cannot use {platform} as output component") + raise cv.Invalid(f"Component rtttl cannot use {platform} as output component") if platform not in PWM_GOOD: _LOGGER.warning( - "Component rtttl is not known to work with the selected output type. Make sure this output supports custom frequency output method." + "Component rtttl is not known to work with the selected output type. " + "Make sure this output supports custom frequency output method." ) +FINAL_VALIDATE_SCHEMA = cv.Schema( + { + cv.Required(CONF_OUTPUT): fv.id_declaration_match_schema( + validate_parent_output_config + ) + }, + extra=cv.ALLOW_EXTRA, +) + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/sim800l/__init__.py b/esphome/components/sim800l/__init__.py index 40c011a769..0887b8640f 100644 --- a/esphome/components/sim800l/__init__.py +++ b/esphome/components/sim800l/__init__.py @@ -40,6 +40,9 @@ CONFIG_SCHEMA = cv.All( .extend(cv.polling_component_schema("5s")) .extend(uart.UART_DEVICE_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "sim800l", baud_rate=9600, require_tx=True, require_rx=True +) async def to_code(config): @@ -54,10 +57,6 @@ async def to_code(config): ) -def validate(config, item_config): - uart.validate_device("sim800l", config, item_config, baud_rate=9600) - - SIM800L_SEND_SMS_SCHEMA = cv.Schema( { cv.GenerateID(): cv.use_id(Sim800LComponent), diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index e6e073c4a4..803a45814c 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import pins from esphome.const import ( CONF_CLK_PIN, @@ -69,9 +70,24 @@ async def register_spi_device(var, config): cg.add(var.set_cs_pin(pin)) -def validate_device(name, config, item_config, require_mosi, require_miso): - spi_config = config.get_config_by_id(item_config[CONF_SPI_ID]) - if require_mosi and CONF_MISO_PIN not in spi_config: - raise ValueError(f"Component {name} requires parent spi to declare miso_pin") - if require_miso and CONF_MOSI_PIN not in spi_config: - raise ValueError(f"Component {name} requires parent spi to declare mosi_pin") +def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: bool): + hub_schema = {} + if require_miso: + hub_schema[ + cv.Required( + CONF_MISO_PIN, + msg=f"Component {name} requires this spi bus to declare a miso_pin", + ) + ] = cv.valid + if require_mosi: + hub_schema[ + cv.Required( + CONF_MOSI_PIN, + msg=f"Component {name} requires this spi bus to declare a mosi_pin", + ) + ] = cv.valid + + return cv.Schema( + {cv.Required(CONF_SPI_ID): fv.id_declaration_match_schema(hub_schema)}, + extra=cv.ALLOW_EXTRA, + ) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index aaed333e34..d2fcac2cb6 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -1,5 +1,8 @@ +from typing import Optional + import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import pins, automation from esphome.const import ( CONF_BAUD_RATE, @@ -92,42 +95,6 @@ async def to_code(config): cg.add(var.set_parity(config[CONF_PARITY])) -def validate_device( - name, config, item_config, baud_rate=None, require_tx=True, require_rx=True -): - if not hasattr(config, "uart_devices"): - config.uart_devices = {} - devices = config.uart_devices - - uart_config = config.get_config_by_id(item_config[CONF_UART_ID]) - - uart_id = uart_config[CONF_ID] - device = devices.setdefault(uart_id, {}) - - if require_tx: - if CONF_TX_PIN not in uart_config: - raise ValueError(f"Component {name} requires parent uart to declare tx_pin") - if CONF_TX_PIN in device: - raise ValueError( - f"Component {name} cannot use the same uart.{CONF_TX_PIN} as component {device[CONF_TX_PIN]} is already using it" - ) - device[CONF_TX_PIN] = name - - if require_rx: - if CONF_RX_PIN not in uart_config: - raise ValueError(f"Component {name} requires parent uart to declare rx_pin") - if CONF_RX_PIN in device: - raise ValueError( - f"Component {name} cannot use the same uart.{CONF_RX_PIN} as component {device[CONF_RX_PIN]} is already using it" - ) - device[CONF_RX_PIN] = name - - if baud_rate and uart_config[CONF_BAUD_RATE] != baud_rate: - raise ValueError( - f"Component {name} requires parent uart baud rate be {baud_rate}" - ) - - # A schema to use for all UART devices, all UART integrations must extend this! UART_DEVICE_SCHEMA = cv.Schema( { @@ -135,6 +102,64 @@ UART_DEVICE_SCHEMA = cv.Schema( } ) +KEY_UART_DEVICES = "uart_devices" + + +def final_validate_device_schema( + name: str, + *, + baud_rate: Optional[int] = None, + require_tx: bool = False, + require_rx: bool = False, +): + def validate_baud_rate(value): + if value != baud_rate: + raise cv.Invalid( + f"Component {name} required baud rate {baud_rate} for the uart bus" + ) + return value + + def validate_pin(opt, device): + def validator(value): + if opt in device: + raise cv.Invalid( + f"The uart {opt} is used both by {name} and {device[opt]}, " + f"but can only be used by one. Please create a new uart bus for {name}." + ) + device[opt] = name + return value + + return validator + + def validate_hub(hub_config): + hub_schema = {} + uart_id = hub_config[CONF_ID] + devices = fv.full_config.get().data.setdefault(KEY_UART_DEVICES, {}) + device = devices.setdefault(uart_id, {}) + + if require_tx: + hub_schema[ + cv.Required( + CONF_TX_PIN, + msg=f"Component {name} requires this uart bus to declare a tx_pin", + ) + ] = validate_pin(CONF_TX_PIN, device) + if require_rx: + hub_schema[ + cv.Required( + CONF_RX_PIN, + msg=f"Component {name} requires this uart bus to declare a rx_pin", + ) + ] = validate_pin(CONF_RX_PIN, device) + if baud_rate is not None: + hub_schema[cv.Required(CONF_BAUD_RATE)] = validate_baud_rate + return cv.Schema(hub_schema, extra=cv.ALLOW_EXTRA)(hub_config) + + return cv.Schema( + {cv.Required(CONF_UART_ID): fv.id_declaration_match_schema(validate_hub)}, + extra=cv.ALLOW_EXTRA, + ) + async def register_uart_device(var, config): """Register a UART device, setting up all the internal values. diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index a701aa37e5..fa28eaffd4 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import automation from esphome.automation import Condition from esphome.components.network import add_mdns_library @@ -137,16 +138,17 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend( ) -def validate(config, item_config): - if ( - (CONF_NETWORKS in item_config) - and (item_config[CONF_NETWORKS] == []) - and (CONF_AP not in item_config) - ): - if "esp32_improv" not in config: - raise ValueError( - "Please specify at least an SSID or an Access Point to create." - ) +def final_validate(config): + has_sta = bool(config.get(CONF_NETWORKS, True)) + has_ap = CONF_AP in config + has_improv = "esp32_improv" in fv.full_config.get() + if (not has_sta) and (not has_ap) and (not has_improv): + raise cv.Invalid( + "Please specify at least an SSID or an Access Point to create." + ) + + +FINAL_VALIDATE_SCHEMA = cv.Schema(final_validate) def _validate(config): diff --git a/esphome/config.py b/esphome/config.py index fcd2fac90f..93413a009c 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -21,11 +21,13 @@ from esphome.helpers import indent from esphome.util import safe_print, OrderedDict from typing import List, Optional, Tuple, Union -from esphome.core import ConfigType from esphome.loader import get_component, get_platform, ComponentManifest from esphome.yaml_util import is_secret, ESPHomeDataBase, ESPForceValue from esphome.voluptuous_schema import ExtraKeysInvalid from esphome.log import color, Fore +import esphome.final_validate as fv +import esphome.config_validation as cv +from esphome.types import ConfigType, ConfigPathType, ConfigFragmentType _LOGGER = logging.getLogger(__name__) @@ -54,7 +56,7 @@ def _path_begins_with(path, other): # type: (ConfigPath, ConfigPath) -> bool return path[: len(other)] == other -class Config(OrderedDict): +class Config(OrderedDict, fv.FinalValidateConfig): def __init__(self): super().__init__() # A list of voluptuous errors @@ -65,6 +67,7 @@ class Config(OrderedDict): self.output_paths = [] # type: List[Tuple[ConfigPath, str]] # A list of components ids with the config path self.declare_ids = [] # type: List[Tuple[core.ID, ConfigPath]] + self._data = {} def add_error(self, error): # type: (vol.Invalid) -> None @@ -72,6 +75,12 @@ class Config(OrderedDict): for err in error.errors: self.add_error(err) return + if cv.ROOT_CONFIG_PATH in error.path: + # Root value means that the path before the root should be ignored + last_root = max( + i for i, v in enumerate(error.path) if v is cv.ROOT_CONFIG_PATH + ) + error.path = error.path[last_root + 1 :] self.errors.append(error) @contextmanager @@ -140,13 +149,16 @@ class Config(OrderedDict): return doc_range - def get_nested_item(self, path): - # type: (ConfigPath) -> ConfigType + def get_nested_item( + self, path: ConfigPathType, raise_error: bool = False + ) -> ConfigFragmentType: data = self for item_index in path: try: data = data[item_index] except (KeyError, IndexError, TypeError): + if raise_error: + raise return {} return data @@ -163,11 +175,20 @@ class Config(OrderedDict): part.append(item_index) return part - def get_config_by_id(self, id): + def get_path_for_id(self, id: core.ID): + """Return the config fragment where the given ID is declared.""" for declared_id, path in self.declare_ids: if declared_id.id == str(id): - return self.get_nested_item(path[:-1]) - return None + return path + raise KeyError(f"ID {id} not found in configuration") + + def get_config_for_path(self, path: ConfigPathType) -> ConfigFragmentType: + return self.get_nested_item(path, raise_error=True) + + @property + def data(self): + """Return temporary data used by final validation functions.""" + return self._data def iter_ids(config, path=None): @@ -189,23 +210,22 @@ def do_id_pass(result): # type: (Config) -> None from esphome.cpp_generator import MockObjClass from esphome.cpp_types import Component - declare_ids = result.declare_ids # type: List[Tuple[core.ID, ConfigPath]] searching_ids = [] # type: List[Tuple[core.ID, ConfigPath]] for id, path in iter_ids(result): if id.is_declaration: if id.id is not None: # Look for duplicate definitions - match = next((v for v in declare_ids if v[0].id == id.id), None) + match = next((v for v in result.declare_ids if v[0].id == id.id), None) if match is not None: opath = "->".join(str(v) for v in match[1]) result.add_str_error(f"ID {id.id} redefined! Check {opath}", path) continue - declare_ids.append((id, path)) + result.declare_ids.append((id, path)) else: searching_ids.append((id, path)) # Resolve default ids after manual IDs - for id, _ in declare_ids: - id.resolve([v[0].id for v in declare_ids]) + for id, _ in result.declare_ids: + id.resolve([v[0].id for v in result.declare_ids]) if isinstance(id.type, MockObjClass) and id.type.inherits_from(Component): CORE.component_ids.add(id.id) @@ -213,7 +233,7 @@ def do_id_pass(result): # type: (Config) -> None for id, path in searching_ids: if id.id is not None: # manually declared - match = next((v[0] for v in declare_ids if v[0].id == id.id), None) + match = next((v[0] for v in result.declare_ids if v[0].id == id.id), None) if match is None or not match.is_manual: # No declared ID with this name import difflib @@ -224,7 +244,7 @@ def do_id_pass(result): # type: (Config) -> None ) # Find candidates matches = difflib.get_close_matches( - id.id, [v[0].id for v in declare_ids if v[0].is_manual] + id.id, [v[0].id for v in result.declare_ids if v[0].is_manual] ) if matches: matches_s = ", ".join(f'"{x}"' for x in matches) @@ -245,7 +265,7 @@ def do_id_pass(result): # type: (Config) -> None if id.id is None and id.type is not None: matches = [] - for v in declare_ids: + for v in result.declare_ids: if v[0] is None or not isinstance(v[0].type, MockObjClass): continue inherits = v[0].type.inherits_from(id.type) @@ -278,8 +298,6 @@ def do_id_pass(result): # type: (Config) -> None def recursive_check_replaceme(value): - import esphome.config_validation as cv - if isinstance(value, list): return cv.Schema([recursive_check_replaceme])(value) if isinstance(value, dict): @@ -558,14 +576,16 @@ def validate_config(config, command_line_substitutions): # 7. Final validation if not result.errors: # Inter - components validation - for path, conf, comp in validate_queue: - if comp.config_schema is None: + token = fv.full_config.set(result) + + for path, _, comp in validate_queue: + if comp.final_validate_schema is None: continue - if callable(comp.validate): - try: - comp.validate(result, result.get_nested_item(path)) - except ValueError as err: - result.add_str_error(err, path) + conf = result.get_nested_item(path) + with result.catch_error(path): + comp.final_validate_schema(conf) + + fv.full_config.reset(token) return result @@ -621,8 +641,12 @@ def _format_vol_invalid(ex, config): ) elif "extra keys not allowed" in str(ex): message += "[{}] is an invalid option for [{}].".format(ex.path[-1], paren) - elif "required key not provided" in str(ex): - message += "'{}' is a required option for [{}].".format(ex.path[-1], paren) + elif isinstance(ex, vol.RequiredFieldInvalid): + if ex.msg == "required key not provided": + message += "'{}' is a required option for [{}].".format(ex.path[-1], paren) + else: + # Required has set a custom error message + message += ex.msg else: message += humanize_error(config, ex) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 2cdb6b0b76..7292cc3af5 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -75,6 +75,9 @@ Inclusive = vol.Inclusive ALLOW_EXTRA = vol.ALLOW_EXTRA UNDEFINED = vol.UNDEFINED RequiredFieldInvalid = vol.RequiredFieldInvalid +# this sentinel object can be placed in an 'Invalid' path to say +# the rest of the error path is relative to the root config path +ROOT_CONFIG_PATH = object() RESERVED_IDS = [ # C++ keywords http://en.cppreference.com/w/cpp/keyword @@ -218,8 +221,8 @@ class Required(vol.Required): - *not* the `config.get(CONF_)` syntax. """ - def __init__(self, key): - super().__init__(key) + def __init__(self, key, msg=None): + super().__init__(key, msg=msg) def check_not_templatable(value): @@ -1073,6 +1076,7 @@ def invalid(message): def valid(value): + """A validator that is always valid and returns the value as-is.""" return value diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 1841dfd8be..df98e1b150 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -2,7 +2,7 @@ import logging import math import os import re -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple from esphome.const import ( CONF_ARDUINO_VERSION, @@ -23,6 +23,7 @@ from esphome.util import OrderedDict if TYPE_CHECKING: from ..cpp_generator import MockObj, MockObjClass, Statement + from ..types import ConfigType _LOGGER = logging.getLogger(__name__) @@ -462,9 +463,9 @@ class EsphomeCore: # The board that's used (for example nodemcuv2) self.board: Optional[str] = None # The full raw configuration - self.raw_config: Optional[ConfigType] = None + self.raw_config: Optional["ConfigType"] = None # The validated configuration, this is None until the config has been validated - self.config: Optional[ConfigType] = None + self.config: Optional["ConfigType"] = None # The pending tasks in the task queue (mostly for C++ generation) # This is a priority queue (with heapq) # Each item is a tuple of form: (-priority, unique number, task) @@ -752,6 +753,3 @@ class EnumValue: CORE = EsphomeCore() - -ConfigType = Dict[str, Any] -CoreType = EsphomeCore diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 1c52f38e50..1d66eabf6c 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -8,7 +8,8 @@ from esphome.const import ( ) # pylint: disable=unused-import -from esphome.core import coroutine, ID, CORE, ConfigType +from esphome.core import coroutine, ID, CORE +from esphome.types import ConfigType from esphome.cpp_generator import RawExpression, add, get_variable from esphome.cpp_types import App, GPIOPin from esphome.util import Registry, RegistryEntry diff --git a/esphome/final_validate.py b/esphome/final_validate.py new file mode 100644 index 0000000000..50fdbaf3f4 --- /dev/null +++ b/esphome/final_validate.py @@ -0,0 +1,57 @@ +from abc import ABC, abstractmethod, abstractproperty +from typing import Dict, Any +import contextvars + +from esphome.types import ConfigFragmentType, ID, ConfigPathType +import esphome.config_validation as cv + + +class FinalValidateConfig(ABC): + @abstractproperty + 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 diff --git a/esphome/loader.py b/esphome/loader.py index d9d407d787..f74fc6367d 100644 --- a/esphome/loader.py +++ b/esphome/loader.py @@ -12,6 +12,7 @@ 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__) @@ -81,8 +82,13 @@ class ComponentManifest: return getattr(self.module, "CODEOWNERS", []) @property - def validate(self): - return getattr(self.module, "validate", None) + 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]: 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] From dd875e7529ef57abbed6c1ad549226eef28864a5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 18 Jun 2021 11:48:40 +1200 Subject: [PATCH 0938/1841] Replace CLIMATE_MODE_AUTO with CLIMATE_MODE_HEAT_COOL in most cases (#1933) --- esphome/components/bang_bang/bang_bang_climate.cpp | 6 +++--- esphome/components/climate/climate.h | 2 +- esphome/components/climate/climate_traits.cpp | 5 +++++ esphome/components/climate/climate_traits.h | 2 ++ esphome/components/climate_ir/climate_ir.cpp | 2 +- esphome/components/climate_ir_lg/climate_ir_lg.cpp | 12 ++++++------ esphome/components/coolix/coolix.cpp | 8 ++++---- esphome/components/daikin/daikin.cpp | 6 +++--- .../components/fujitsu_general/fujitsu_general.cpp | 4 ++-- esphome/components/hitachi_ac344/hitachi_ac344.cpp | 4 ++-- esphome/components/midea_ac/midea_climate.cpp | 2 +- esphome/components/midea_ac/midea_frame.cpp | 4 ++-- esphome/components/mitsubishi/mitsubishi.cpp | 2 +- esphome/components/pid/pid_climate.cpp | 10 +++++----- esphome/components/tcl112/tcl112.cpp | 4 ++-- esphome/components/thermostat/climate.py | 8 ++++---- .../components/thermostat/thermostat_climate.cpp | 13 +++++++++---- esphome/components/thermostat/thermostat_climate.h | 2 ++ esphome/components/toshiba/toshiba.cpp | 4 ++-- esphome/components/tuya/climate/tuya_climate.cpp | 2 +- esphome/components/whirlpool/whirlpool.cpp | 4 ++-- esphome/components/yashima/yashima.cpp | 4 ++-- 22 files changed, 62 insertions(+), 48 deletions(-) diff --git a/esphome/components/bang_bang/bang_bang_climate.cpp b/esphome/components/bang_bang/bang_bang_climate.cpp index 45d5174390..ea1442755d 100644 --- a/esphome/components/bang_bang/bang_bang_climate.cpp +++ b/esphome/components/bang_bang/bang_bang_climate.cpp @@ -21,7 +21,7 @@ void BangBangClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles those for us - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; this->change_away_(false); } } @@ -41,7 +41,7 @@ void BangBangClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits BangBangClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_cool_mode(this->supports_cool_); traits.set_supports_heat_mode(this->supports_heat_); traits.set_supports_two_point_target_temperature(true); @@ -50,7 +50,7 @@ climate::ClimateTraits BangBangClimate::traits() { return traits; } void BangBangClimate::compute_state_() { - if (this->mode != climate::CLIMATE_MODE_AUTO) { + if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) { // in non-auto mode, switch directly to appropriate action // - HEAT mode -> HEATING action // - COOL mode -> COOLING action diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index cd69469692..3aa29be2fb 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -159,7 +159,7 @@ struct ClimateDeviceRestoreState { * * The entire state of the climate device is encoded in public properties of the base class (current_temperature, * mode etc). These are read-only for the user and rw for integrations. The reason these are public - * is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_AUTO) ...` + * is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_HEAT_COOL) ...` */ class Climate : public Nameable { public: diff --git a/esphome/components/climate/climate_traits.cpp b/esphome/components/climate/climate_traits.cpp index eda4722fcb..774ada785f 100644 --- a/esphome/components/climate/climate_traits.cpp +++ b/esphome/components/climate/climate_traits.cpp @@ -8,6 +8,8 @@ bool ClimateTraits::supports_mode(ClimateMode mode) const { switch (mode) { case CLIMATE_MODE_OFF: return true; + case CLIMATE_MODE_HEAT_COOL: + return this->supports_heat_cool_mode_; case CLIMATE_MODE_AUTO: return this->supports_auto_mode_; case CLIMATE_MODE_COOL: @@ -31,6 +33,9 @@ void ClimateTraits::set_supports_two_point_target_temperature(bool supports_two_ supports_two_point_target_temperature_ = supports_two_point_target_temperature; } void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_auto_mode_ = supports_auto_mode; } +void ClimateTraits::set_supports_heat_cool_mode(bool supports_heat_cool_mode) { + supports_heat_cool_mode_ = supports_heat_cool_mode; +} void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; } void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; } void ClimateTraits::set_supports_fan_only_mode(bool supports_fan_only_mode) { diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index f0a48ca308..f8e6f87306 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -46,6 +46,7 @@ class ClimateTraits { bool get_supports_two_point_target_temperature() const; void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature); void set_supports_auto_mode(bool supports_auto_mode); + void set_supports_heat_cool_mode(bool supports_heat_cool_mode); void set_supports_cool_mode(bool supports_cool_mode); void set_supports_heat_mode(bool supports_heat_mode); void set_supports_fan_only_mode(bool supports_fan_only_mode); @@ -100,6 +101,7 @@ class ClimateTraits { bool supports_current_temperature_{false}; bool supports_two_point_target_temperature_{false}; bool supports_auto_mode_{false}; + bool supports_heat_cool_mode_{false}; bool supports_cool_mode_{false}; bool supports_heat_mode_{false}; bool supports_fan_only_mode_{false}; diff --git a/esphome/components/climate_ir/climate_ir.cpp b/esphome/components/climate_ir/climate_ir.cpp index 8f06ff2214..f88b2174ee 100644 --- a/esphome/components/climate_ir/climate_ir.cpp +++ b/esphome/components/climate_ir/climate_ir.cpp @@ -9,7 +9,7 @@ static const char *TAG = "climate_ir"; climate::ClimateTraits ClimateIR::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->sensor_ != nullptr); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_cool_mode(this->supports_cool_); traits.set_supports_heat_mode(this->supports_heat_); traits.set_supports_dry_mode(this->supports_dry_); diff --git a/esphome/components/climate_ir_lg/climate_ir_lg.cpp b/esphome/components/climate_ir_lg/climate_ir_lg.cpp index 983d33c0b1..d675883fcf 100644 --- a/esphome/components/climate_ir_lg/climate_ir_lg.cpp +++ b/esphome/components/climate_ir_lg/climate_ir_lg.cpp @@ -39,7 +39,7 @@ void LgIrClimate::transmit_state() { send_swing_cmd_ = false; remote_state |= COMMAND_SWING; } else { - if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_AUTO) { + if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_HEAT_COOL) { remote_state |= COMMAND_ON_AI; } else if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode != climate::CLIMATE_MODE_OFF) { remote_state |= COMMAND_ON; @@ -52,7 +52,7 @@ void LgIrClimate::transmit_state() { case climate::CLIMATE_MODE_HEAT: remote_state |= COMMAND_HEAT; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state |= COMMAND_AUTO; break; case climate::CLIMATE_MODE_DRY: @@ -89,7 +89,7 @@ void LgIrClimate::transmit_state() { } } - if (this->mode == climate::CLIMATE_MODE_AUTO) { + if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) { this->fan_mode = climate::CLIMATE_FAN_AUTO; // remote_state |= FAN_MODE_AUTO_DRY; } @@ -128,7 +128,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { if ((remote_state & COMMAND_MASK) == COMMAND_ON) { this->mode = climate::CLIMATE_MODE_COOL; } else if ((remote_state & COMMAND_MASK) == COMMAND_ON_AI) { - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; } if ((remote_state & COMMAND_MASK) == COMMAND_OFF) { @@ -138,7 +138,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF; } else { if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) this->mode = climate::CLIMATE_MODE_DRY; else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) { @@ -152,7 +152,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15; // Fan Speed - if (this->mode == climate::CLIMATE_MODE_AUTO) { + if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) { this->fan_mode = climate::CLIMATE_FAN_AUTO; } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT || this->mode == climate::CLIMATE_MODE_DRY) { diff --git a/esphome/components/coolix/coolix.cpp b/esphome/components/coolix/coolix.cpp index e50521a348..012744b3e9 100644 --- a/esphome/components/coolix/coolix.cpp +++ b/esphome/components/coolix/coolix.cpp @@ -70,7 +70,7 @@ void CoolixClimate::transmit_state() { case climate::CLIMATE_MODE_HEAT: remote_state |= COOLIX_HEAT; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state |= COOLIX_AUTO; break; case climate::CLIMATE_MODE_FAN_ONLY: @@ -89,7 +89,7 @@ void CoolixClimate::transmit_state() { } else { remote_state |= COOLIX_FAN_TEMP_CODE; } - if (this->mode == climate::CLIMATE_MODE_AUTO || this->mode == climate::CLIMATE_MODE_DRY) { + if (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_DRY) { this->fan_mode = climate::CLIMATE_FAN_AUTO; remote_state |= COOLIX_FAN_MODE_AUTO_DRY; } else { @@ -197,7 +197,7 @@ bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) { if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT) this->mode = climate::CLIMATE_MODE_HEAT; else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO) - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) { if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY) this->mode = climate::CLIMATE_MODE_DRY; @@ -207,7 +207,7 @@ bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) { this->mode = climate::CLIMATE_MODE_COOL; // Fan Speed - if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_AUTO || + if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_DRY) this->fan_mode = climate::CLIMATE_FAN_AUTO; else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN) diff --git a/esphome/components/daikin/daikin.cpp b/esphome/components/daikin/daikin.cpp index e0ffd46387..dca3bffbac 100644 --- a/esphome/components/daikin/daikin.cpp +++ b/esphome/components/daikin/daikin.cpp @@ -77,7 +77,7 @@ uint8_t DaikinClimate::operation_mode_() { case climate::CLIMATE_MODE_HEAT: operating_mode |= DAIKIN_MODE_HEAT; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: operating_mode |= DAIKIN_MODE_AUTO; break; case climate::CLIMATE_MODE_FAN_ONLY: @@ -131,7 +131,7 @@ uint8_t DaikinClimate::temperature_() { switch (this->mode) { case climate::CLIMATE_MODE_FAN_ONLY: return 0x32; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: case climate::CLIMATE_MODE_DRY: return 0xc0; default: @@ -160,7 +160,7 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) { this->mode = climate::CLIMATE_MODE_HEAT; break; case DAIKIN_MODE_AUTO: - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; case DAIKIN_MODE_FAN: this->mode = climate::CLIMATE_MODE_FAN_ONLY; diff --git a/esphome/components/fujitsu_general/fujitsu_general.cpp b/esphome/components/fujitsu_general/fujitsu_general.cpp index 8671f38e8e..2e93a98e52 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.cpp +++ b/esphome/components/fujitsu_general/fujitsu_general.cpp @@ -132,7 +132,7 @@ void FujitsuGeneralClimate::transmit_state() { case climate::CLIMATE_MODE_FAN_ONLY: SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_FAN); break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: default: SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_AUTO); break; @@ -343,7 +343,7 @@ bool FujitsuGeneralClimate::on_receive(remote_base::RemoteReceiveData data) { case FUJITSU_GENERAL_MODE_AUTO: default: // TODO: CLIMATE_MODE_10C is missing from esphome - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; } diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.cpp b/esphome/components/hitachi_ac344/hitachi_ac344.cpp index b2798b608a..86f82d8bbb 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.cpp +++ b/esphome/components/hitachi_ac344/hitachi_ac344.cpp @@ -155,7 +155,7 @@ void HitachiClimate::transmit_state() { case climate::CLIMATE_MODE_HEAT: set_mode_(HITACHI_AC344_MODE_HEAT); break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: set_mode_(HITACHI_AC344_MODE_AUTO); break; case climate::CLIMATE_MODE_FAN_ONLY: @@ -251,7 +251,7 @@ bool HitachiClimate::parse_mode_(const uint8_t remote_state[]) { this->mode = climate::CLIMATE_MODE_HEAT; break; case HITACHI_AC344_MODE_AUTO: - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; case HITACHI_AC344_MODE_FAN: this->mode = climate::CLIMATE_MODE_FAN_ONLY; diff --git a/esphome/components/midea_ac/midea_climate.cpp b/esphome/components/midea_ac/midea_climate.cpp index f98cf74ac1..481a6da54d 100644 --- a/esphome/components/midea_ac/midea_climate.cpp +++ b/esphome/components/midea_ac/midea_climate.cpp @@ -167,7 +167,7 @@ climate::ClimateTraits MideaAC::traits() { traits.set_visual_min_temperature(17); traits.set_visual_max_temperature(30); traits.set_visual_temperature_step(0.5); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_cool_mode(true); traits.set_supports_dry_mode(true); traits.set_supports_heat_mode(true); diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp index a29345035b..0a09e86de7 100644 --- a/esphome/components/midea_ac/midea_frame.cpp +++ b/esphome/components/midea_ac/midea_frame.cpp @@ -44,7 +44,7 @@ climate::ClimateMode PropertiesFrame::get_mode() const { return climate::CLIMATE_MODE_OFF; switch (this->pbuf_[12] >> 5) { case MIDEA_MODE_AUTO: - return climate::CLIMATE_MODE_AUTO; + return climate::CLIMATE_MODE_HEAT_COOL; case MIDEA_MODE_COOL: return climate::CLIMATE_MODE_COOL; case MIDEA_MODE_DRY: @@ -61,7 +61,7 @@ climate::ClimateMode PropertiesFrame::get_mode() const { void PropertiesFrame::set_mode(climate::ClimateMode mode) { uint8_t m; switch (mode) { - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: m = MIDEA_MODE_AUTO; break; case climate::CLIMATE_MODE_COOL: diff --git a/esphome/components/mitsubishi/mitsubishi.cpp b/esphome/components/mitsubishi/mitsubishi.cpp index dbc70af75c..c9d0ce842e 100644 --- a/esphome/components/mitsubishi/mitsubishi.cpp +++ b/esphome/components/mitsubishi/mitsubishi.cpp @@ -33,7 +33,7 @@ void MitsubishiClimate::transmit_state() { case climate::CLIMATE_MODE_HEAT: remote_state[6] = MITSUBISHI_HEAT; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state[6] = MITSUBISHI_AUTO; break; case climate::CLIMATE_MODE_OFF: diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index 24fb0ec905..0423ab27fc 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -20,7 +20,7 @@ void PIDClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles those for us - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; this->target_temperature = this->default_target_temperature_; } } @@ -31,7 +31,7 @@ void PIDClimate::control(const climate::ClimateCall &call) { this->target_temperature = *call.get_target_temperature(); // If switching to non-auto mode, set output immediately - if (this->mode != climate::CLIMATE_MODE_AUTO) + if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) this->handle_non_auto_mode_(); this->publish_state(); @@ -39,7 +39,7 @@ void PIDClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits PIDClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_two_point_target_temperature(false); traits.set_supports_cool_mode(this->supports_cool_()); traits.set_supports_heat_mode(this->supports_heat_()); @@ -121,14 +121,14 @@ void PIDClimate::update_pid_() { // keep autotuner instance so that subsequent dump_configs will print the long result message. } else { value = res.output; - if (mode != climate::CLIMATE_MODE_AUTO) { + if (mode != climate::CLIMATE_MODE_HEAT_COOL) { ESP_LOGW(TAG, "For PID autotuner you need to set AUTO (also called heat/cool) mode!"); } } } } - if (this->mode != climate::CLIMATE_MODE_AUTO) { + if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) { this->handle_non_auto_mode_(); } else { this->write_output_(value); diff --git a/esphome/components/tcl112/tcl112.cpp b/esphome/components/tcl112/tcl112.cpp index 91cec27094..6921bbd3c0 100644 --- a/esphome/components/tcl112/tcl112.cpp +++ b/esphome/components/tcl112/tcl112.cpp @@ -47,7 +47,7 @@ void Tcl112Climate::transmit_state() { // Set mode switch (this->mode) { - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state[6] &= 0xF0; remote_state[6] |= TCL112_AUTO; break; @@ -204,7 +204,7 @@ bool Tcl112Climate::on_receive(remote_base::RemoteReceiveData data) { this->mode = climate::CLIMATE_MODE_FAN_ONLY; break; case TCL112_AUTO: - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; } } diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index 4a371ec165..07a94fd184 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -227,7 +227,7 @@ async def to_code(config): await cg.register_component(var, config) await climate.register_climate(var, config) - auto_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config + heat_cool_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config two_points_available = CONF_HEAT_ACTION in config and ( CONF_COOL_ACTION in config or CONF_FAN_ONLY_ACTION in config ) @@ -258,10 +258,10 @@ async def to_code(config): var.get_idle_action_trigger(), [], config[CONF_IDLE_ACTION] ) - if auto_mode_available is True: - cg.add(var.set_supports_auto(True)) + if heat_cool_mode_available is True: + cg.add(var.set_supports_heat_cool(True)) else: - cg.add(var.set_supports_auto(False)) + cg.add(var.set_supports_heat_cool(False)) if CONF_COOL_ACTION in config: await automation.build_automation( diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 3bab0e85fd..a96c702473 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -21,7 +21,7 @@ void ThermostatClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles temps for us - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; this->change_away_(false); } // refresh the climate action based on the restored settings @@ -79,6 +79,7 @@ climate::ClimateTraits ThermostatClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); traits.set_supports_auto_mode(this->supports_auto_); + traits.set_supports_heat_cool_mode(this->supports_heat_cool_); traits.set_supports_cool_mode(this->supports_cool_); traits.set_supports_dry_mode(this->supports_dry_); traits.set_supports_fan_only_mode(this->supports_fan_only_); @@ -130,7 +131,7 @@ climate::ClimateAction ThermostatClimate::compute_action_() { case climate::CLIMATE_MODE_OFF: target_action = climate::CLIMATE_ACTION_OFF; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: case climate::CLIMATE_MODE_COOL: case climate::CLIMATE_MODE_HEAT: if (this->supports_cool_) { @@ -321,7 +322,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) { case climate::CLIMATE_MODE_OFF: trig = this->off_mode_trigger_; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: // trig = this->auto_mode_trigger_; break; case climate::CLIMATE_MODE_COOL: @@ -339,7 +340,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) { default: // we cannot report an invalid mode back to HA (even if it asked for one) // and must assume some valid value - mode = climate::CLIMATE_MODE_AUTO; + mode = climate::CLIMATE_MODE_HEAT_COOL; // trig = this->auto_mode_trigger_; } assert(trig != nullptr); @@ -434,6 +435,9 @@ ThermostatClimate::ThermostatClimate() swing_mode_vertical_trigger_(new Trigger<>()) {} void ThermostatClimate::set_hysteresis(float hysteresis) { this->hysteresis_ = hysteresis; } void ThermostatClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } +void ThermostatClimate::set_supports_heat_cool(bool supports_heat_cool) { + this->supports_heat_cool_ = supports_heat_cool; +} void ThermostatClimate::set_supports_auto(bool supports_auto) { this->supports_auto_ = supports_auto; } void ThermostatClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } void ThermostatClimate::set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; } @@ -521,6 +525,7 @@ void ThermostatClimate::dump_config() { } ESP_LOGCONFIG(TAG, " Hysteresis: %.1f°C", this->hysteresis_); ESP_LOGCONFIG(TAG, " Supports AUTO: %s", YESNO(this->supports_auto_)); + ESP_LOGCONFIG(TAG, " Supports HEAT/COOL: %s", YESNO(this->supports_heat_cool_)); ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_)); ESP_LOGCONFIG(TAG, " Supports DRY: %s", YESNO(this->supports_dry_)); ESP_LOGCONFIG(TAG, " Supports FAN_ONLY: %s", YESNO(this->supports_fan_only_)); diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 86a1007efa..3fd482da53 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -29,6 +29,7 @@ class ThermostatClimate : public climate::Climate, public Component { void set_hysteresis(float hysteresis); void set_sensor(sensor::Sensor *sensor); void set_supports_auto(bool supports_auto); + void set_supports_heat_cool(bool supports_heat_cool); void set_supports_cool(bool supports_cool); void set_supports_dry(bool supports_dry); void set_supports_fan_only(bool supports_fan_only); @@ -113,6 +114,7 @@ class ThermostatClimate : public climate::Climate, public Component { /// A false value for any given attribute means that the controller has no such action /// (for example a thermostat, where only heating and not-heating is possible). bool supports_auto_{false}; + bool supports_heat_cool_{false}; bool supports_cool_{false}; bool supports_dry_{false}; bool supports_fan_only_{false}; diff --git a/esphome/components/toshiba/toshiba.cpp b/esphome/components/toshiba/toshiba.cpp index 33e2831dd3..b932516edf 100644 --- a/esphome/components/toshiba/toshiba.cpp +++ b/esphome/components/toshiba/toshiba.cpp @@ -80,7 +80,7 @@ void ToshibaClimate::transmit_state() { mode = TOSHIBA_MODE_COOL; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: default: mode = TOSHIBA_MODE_AUTO; } @@ -190,7 +190,7 @@ bool ToshibaClimate::on_receive(remote_base::RemoteReceiveData data) { case TOSHIBA_MODE_AUTO: default: /* Note: Dry and Fan-only modes are reported as Auto, as they are not supported yet */ - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; } /* Get the target temperature */ diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index 962d18a391..9c9cf9f3e7 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -13,7 +13,7 @@ void TuyaClimate::setup() { this->mode = climate::CLIMATE_MODE_OFF; if (datapoint.value_bool) { if (this->supports_heat_ && this->supports_cool_) { - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; } else if (this->supports_heat_) { this->mode = climate::CLIMATE_MODE_HEAT; } else if (this->supports_cool_) { diff --git a/esphome/components/whirlpool/whirlpool.cpp b/esphome/components/whirlpool/whirlpool.cpp index e7c93246f2..5a1e025a38 100644 --- a/esphome/components/whirlpool/whirlpool.cpp +++ b/esphome/components/whirlpool/whirlpool.cpp @@ -48,7 +48,7 @@ void WhirlpoolClimate::transmit_state() { this->powered_on_assumed = powered_on; } switch (this->mode) { - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: // set fan auto // set temp auto temp // set sleep false @@ -239,7 +239,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) { this->mode = climate::CLIMATE_MODE_FAN_ONLY; break; case WHIRLPOOL_AUTO: - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; } } diff --git a/esphome/components/yashima/yashima.cpp b/esphome/components/yashima/yashima.cpp index e3c0a33127..e53e5fcccc 100644 --- a/esphome/components/yashima/yashima.cpp +++ b/esphome/components/yashima/yashima.cpp @@ -82,7 +82,7 @@ const uint32_t YASHIMA_CARRIER_FREQUENCY = 38000; climate::ClimateTraits YashimaClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->sensor_ != nullptr); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_cool_mode(this->supports_cool_); traits.set_supports_heat_mode(this->supports_heat_); traits.set_supports_two_point_target_temperature(false); @@ -139,7 +139,7 @@ void YashimaClimate::transmit_state_() { // Set mode switch (this->mode) { - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state[0] |= YASHIMA_MODE_AUTO_BYTE0; remote_state[5] |= YASHIMA_MODE_AUTO_BYTE5; break; From 01904a0f107c92d55a51fba93f6c31a91b5259eb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 18 Jun 2021 11:52:02 +1200 Subject: [PATCH 0939/1841] Bump version to v1.19.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 3492858240..733931ce09 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0" +PATCH_VERSION = "1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From f9a31c1abb406190d88dfc908e4778fdd3615a88 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 18 Jun 2021 03:49:25 +0200 Subject: [PATCH 0940/1841] Fix error print in script/helpers.py (#1935) --- script/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/helpers.py b/script/helpers.py index 5b1b7ba918..1a4402aa1d 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -40,7 +40,7 @@ def build_all_include(): def build_compile_commands(): gcc_flags_json = os.path.join(root_path, ".gcc-flags.json") if not os.path.isfile(gcc_flags_json): - print("Could not find {} file which is required for clang-tidy.") + print("Could not find {} file which is required for clang-tidy.".format(gcc_flags_json)) print( 'Please run "pio init --ide atom" in the root esphome folder to generate that file.' ) From 04d926af3957ac418a8a252ef9a205c2a7ec345c Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Thu, 17 Jun 2021 20:54:46 -0500 Subject: [PATCH 0941/1841] Add variable bit width for Samsung protocol (#1927) --- esphome/codegen.py | 1 + esphome/components/remote_base/__init__.py | 8 +++++-- .../remote_base/samsung_protocol.cpp | 23 +++++++++++-------- .../components/remote_base/samsung_protocol.h | 9 +++++--- esphome/config_validation.py | 2 ++ esphome/cpp_types.py | 1 + 6 files changed, 30 insertions(+), 14 deletions(-) diff --git a/esphome/codegen.py b/esphome/codegen.py index 8361faeb81..c05cc5efca 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -60,6 +60,7 @@ from esphome.cpp_types import ( # noqa uint8, uint16, uint32, + uint64, int32, const_char_ptr, NAN, diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 76e4b51bde..9416c2220d 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -866,7 +866,8 @@ def rc_switch_dumper(var, config): ) = declare_protocol("Samsung") SAMSUNG_SCHEMA = cv.Schema( { - cv.Required(CONF_DATA): cv.hex_uint32_t, + cv.Required(CONF_DATA): cv.hex_uint64_t, + cv.Optional(CONF_NBITS, default=32): cv.int_range(32, 64), } ) @@ -878,6 +879,7 @@ def samsung_binary_sensor(var, config): cg.StructInitializer( SamsungData, ("data", config[CONF_DATA]), + ("nbits", config[CONF_NBITS]), ) ) ) @@ -895,8 +897,10 @@ def samsung_dumper(var, config): @register_action("samsung", SamsungAction, SAMSUNG_SCHEMA) async def samsung_action(var, config, args): - template_ = await cg.templatable(config[CONF_DATA], args, cg.uint32) + template_ = await cg.templatable(config[CONF_DATA], args, cg.uint64) cg.add(var.set_data(template_)) + template_ = await cg.templatable(config[CONF_NBITS], args, cg.uint8) + cg.add(var.set_nbits(template_)) # Samsung36 diff --git a/esphome/components/remote_base/samsung_protocol.cpp b/esphome/components/remote_base/samsung_protocol.cpp index 5062c46126..0f2605d865 100644 --- a/esphome/components/remote_base/samsung_protocol.cpp +++ b/esphome/components/remote_base/samsung_protocol.cpp @@ -6,7 +6,6 @@ namespace remote_base { static const char *const TAG = "remote.samsung"; -static const uint8_t NBITS = 32; static const uint32_t HEADER_HIGH_US = 4500; static const uint32_t HEADER_LOW_US = 4500; static const uint32_t BIT_HIGH_US = 560; @@ -17,12 +16,12 @@ static const uint32_t FOOTER_LOW_US = 560; void SamsungProtocol::encode(RemoteTransmitData *dst, const SamsungData &data) { dst->set_carrier_frequency(38000); - dst->reserve(4 + NBITS * 2u); + dst->reserve(4 + data.nbits * 2u); dst->item(HEADER_HIGH_US, HEADER_LOW_US); - for (uint32_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) { - if (data.data & mask) + for (uint8_t bit = data.nbits; bit > 0; bit--) { + if ((data.data >> (bit - 1)) & 1) dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); else dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); @@ -33,16 +32,20 @@ void SamsungProtocol::encode(RemoteTransmitData *dst, const SamsungData &data) { optional SamsungProtocol::decode(RemoteReceiveData src) { SamsungData out{ .data = 0, + .nbits = 0, }; if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US)) return {}; - for (uint8_t i = 0; i < NBITS; i++) { - out.data <<= 1UL; + for (out.nbits = 0; out.nbits < 64; out.nbits++) { if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { - out.data |= 1UL; + out.data = (out.data << 1) | 1; } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { - out.data |= 0UL; + out.data = (out.data << 1) | 0; + } else if (out.nbits >= 31) { + if (!src.expect_mark(FOOTER_HIGH_US)) + return {}; + return out; } else { return {}; } @@ -52,7 +55,9 @@ optional SamsungProtocol::decode(RemoteReceiveData src) { return {}; return out; } -void SamsungProtocol::dump(const SamsungData &data) { ESP_LOGD(TAG, "Received Samsung: data=0x%08X", data.data); } +void SamsungProtocol::dump(const SamsungData &data) { + ESP_LOGD(TAG, "Received Samsung: data=0x%" PRIX64 ", nbits=%d", data.data, data.nbits); +} } // namespace remote_base } // namespace esphome diff --git a/esphome/components/remote_base/samsung_protocol.h b/esphome/components/remote_base/samsung_protocol.h index f7a54788e5..41434f2889 100644 --- a/esphome/components/remote_base/samsung_protocol.h +++ b/esphome/components/remote_base/samsung_protocol.h @@ -7,9 +7,10 @@ namespace esphome { namespace remote_base { struct SamsungData { - uint32_t data; + uint64_t data; + uint8_t nbits; - bool operator==(const SamsungData &rhs) const { return data == rhs.data; } + bool operator==(const SamsungData &rhs) const { return data == rhs.data && nbits == rhs.nbits; } }; class SamsungProtocol : public RemoteProtocol { @@ -23,11 +24,13 @@ DECLARE_REMOTE_PROTOCOL(Samsung) template class SamsungAction : public RemoteTransmitterActionBase { public: - TEMPLATABLE_VALUE(uint32_t, data) + TEMPLATABLE_VALUE(uint64_t, data) + TEMPLATABLE_VALUE(uint8_t, nbits) void encode(RemoteTransmitData *dst, Ts... x) override { SamsungData data{}; data.data = this->data_.value(x...); + data.nbits = this->nbits_.value(x...); SamsungProtocol().encode(dst, data); } }; diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 7292cc3af5..aad147dbc9 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1018,9 +1018,11 @@ def requires_component(comp): uint8_t = int_range(min=0, max=255) uint16_t = int_range(min=0, max=65535) uint32_t = int_range(min=0, max=4294967295) +uint64_t = int_range(min=0, max=18446744073709551615) hex_uint8_t = hex_int_range(min=0, max=255) hex_uint16_t = hex_int_range(min=0, max=65535) hex_uint32_t = hex_int_range(min=0, max=4294967295) +hex_uint64_t = hex_int_range(min=0, max=18446744073709551615) i2c_address = hex_uint8_t diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index 3036249a03..ee606ec7b3 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -13,6 +13,7 @@ std_vector = std_ns.class_("vector") uint8 = global_ns.namespace("uint8_t") uint16 = global_ns.namespace("uint16_t") uint32 = global_ns.namespace("uint32_t") +uint64 = global_ns.namespace("uint64_t") int32 = global_ns.namespace("int32_t") const_char_ptr = global_ns.namespace("const char *") NAN = global_ns.namespace("NAN") From 439566454715198c63f7bed154c75bba12dcce4b Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Thu, 17 Jun 2021 21:58:39 -0500 Subject: [PATCH 0942/1841] Don't send Tuya commands while currently receiving a message (#1886) Co-authored-by: Chris Nussbaum --- esphome/components/tuya/tuya.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 540a925879..2c06a79ce3 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -333,7 +333,7 @@ void Tuya::send_raw_command_(TuyaCommand command) { void Tuya::process_command_queue_() { uint32_t delay = millis() - this->last_command_timestamp_; // Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly - if (delay > COMMAND_DELAY && !command_queue_.empty()) { + if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty()) { this->send_raw_command_(command_queue_.front()); this->command_queue_.erase(command_queue_.begin()); } From 4891cfef56f46a64aff7b9d370040d5f883db6ae Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 18 Jun 2021 15:50:56 +1200 Subject: [PATCH 0943/1841] Add data sizes to tuya log message (#1938) --- esphome/components/tuya/tuya.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 2c06a79ce3..d9a0a9932a 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -232,7 +232,7 @@ void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) { const uint8_t *data = buffer + 4; size_t data_len = len - 4; if (data_size != data_len) { - ESP_LOGW(TAG, "Datapoint %u is not expected size", datapoint.id); + ESP_LOGW(TAG, "Datapoint %u is not expected size (%zu != %zu)", datapoint.id, data_size, data_len); return; } datapoint.len = data_len; From c5eba21ff6f94eefe199800534f425e0e7d39b93 Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Mon, 21 Jun 2021 00:59:12 +0400 Subject: [PATCH 0944/1841] Fix midea_ac query frame (#1940) --- esphome/components/midea_ac/midea_frame.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp index 098beedf7a..5f09f4314f 100644 --- a/esphome/components/midea_ac/midea_frame.cpp +++ b/esphome/components/midea_ac/midea_frame.cpp @@ -8,9 +8,9 @@ const std::string MIDEA_SILENT_FAN_MODE = "silent"; const std::string MIDEA_TURBO_FAN_MODE = "turbo"; const std::string MIDEA_FREEZE_PROTECTION_PRESET = "freeze protection"; -const uint8_t QueryFrame::INIT[] = {0xAA, 0x21, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x61, - 0x00, 0xFF, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE3, 0xA8}; +const uint8_t QueryFrame::INIT[] = {0xAA, 0x21, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x81, + 0x00, 0xFF, 0x03, 0xFF, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x37, 0x31}; const uint8_t PowerQueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x21, 0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, From 40a5005d94b34356bf3bbc45ba397a2c8aceb30e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Jun 2021 09:00:16 +1200 Subject: [PATCH 0945/1841] Allow wifi setup to proceed when there is no sta or ap (#1931) --- esphome/components/wifi/wifi_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 5e02307598..0c3a3054f8 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -587,7 +587,7 @@ void WiFiComponent::retry_connect() { } bool WiFiComponent::can_proceed() { - if (this->has_ap() && !this->has_sta()) { + if (!this->has_sta()) { return true; } return this->is_connected(); From b6011b93532676600d138483a710aa9f0e35f11f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Jun 2021 14:17:38 +1200 Subject: [PATCH 0946/1841] Fix bad climate control enum (#1942) --- esphome/components/climate/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 5a4492216e..88991ff795 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -36,7 +36,7 @@ ClimateTraits = climate_ns.class_("ClimateTraits") ClimateMode = climate_ns.enum("ClimateMode") CLIMATE_MODES = { "OFF": ClimateMode.CLIMATE_MODE_OFF, - "HEAT_COOL": ClimateMode.CLIMATE_HEAT_COOL, + "HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL, "COOL": ClimateMode.CLIMATE_MODE_COOL, "HEAT": ClimateMode.CLIMATE_MODE_HEAT, "DRY": ClimateMode.CLIMATE_MODE_DRY, From 4fa959ba45bb6b81729ac0644d780b559bea5016 Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Thu, 17 Jun 2021 21:58:39 -0500 Subject: [PATCH 0947/1841] Don't send Tuya commands while currently receiving a message (#1886) Co-authored-by: Chris Nussbaum --- esphome/components/tuya/tuya.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 3f09db068d..d1f8b8c9e2 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -333,7 +333,7 @@ void Tuya::send_raw_command_(TuyaCommand command) { void Tuya::process_command_queue_() { uint32_t delay = millis() - this->last_command_timestamp_; // Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly - if (delay > COMMAND_DELAY && !command_queue_.empty()) { + if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty()) { this->send_raw_command_(command_queue_.front()); this->command_queue_.erase(command_queue_.begin()); } From 8aec092ab62a9c6d4ba436a51071144006e29b39 Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Mon, 21 Jun 2021 00:59:12 +0400 Subject: [PATCH 0948/1841] Fix midea_ac query frame (#1940) --- esphome/components/midea_ac/midea_frame.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp index 0a09e86de7..a624400411 100644 --- a/esphome/components/midea_ac/midea_frame.cpp +++ b/esphome/components/midea_ac/midea_frame.cpp @@ -8,9 +8,9 @@ const std::string MIDEA_SILENT_FAN_MODE = "silent"; const std::string MIDEA_TURBO_FAN_MODE = "turbo"; const std::string MIDEA_FREEZE_PROTECTION_PRESET = "freeze protection"; -const uint8_t QueryFrame::INIT[] = {0xAA, 0x21, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x61, - 0x00, 0xFF, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE3, 0xA8}; +const uint8_t QueryFrame::INIT[] = {0xAA, 0x21, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x81, + 0x00, 0xFF, 0x03, 0xFF, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x37, 0x31}; const uint8_t PowerQueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x21, 0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, From d73a44c50435a108bdb64586dc46c719dd2f6406 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Jun 2021 09:00:16 +1200 Subject: [PATCH 0949/1841] Allow wifi setup to proceed when there is no sta or ap (#1931) --- esphome/components/wifi/wifi_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index e98754fac7..7d6cb1347e 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -587,7 +587,7 @@ void WiFiComponent::retry_connect() { } bool WiFiComponent::can_proceed() { - if (this->has_ap() && !this->has_sta()) { + if (!this->has_sta()) { return true; } return this->is_connected(); From 969834e0375432b39df2dc32bbf0ce6ddeeb4a28 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Jun 2021 14:17:38 +1200 Subject: [PATCH 0950/1841] Fix bad climate control enum (#1942) --- esphome/components/climate/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 5a4492216e..88991ff795 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -36,7 +36,7 @@ ClimateTraits = climate_ns.class_("ClimateTraits") ClimateMode = climate_ns.enum("ClimateMode") CLIMATE_MODES = { "OFF": ClimateMode.CLIMATE_MODE_OFF, - "HEAT_COOL": ClimateMode.CLIMATE_HEAT_COOL, + "HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL, "COOL": ClimateMode.CLIMATE_MODE_COOL, "HEAT": ClimateMode.CLIMATE_MODE_HEAT, "DRY": ClimateMode.CLIMATE_MODE_DRY, From 2a5def10e74f6f831e9d784411348db8378c8bc2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Jun 2021 14:40:05 +1200 Subject: [PATCH 0951/1841] Bump version to v1.19.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 733931ce09..35959caaff 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "1" +PATCH_VERSION = "2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From b8a7741c617dff13da85eba60d71a3fbb0708f5b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Jun 2021 21:27:35 +1200 Subject: [PATCH 0952/1841] Update generation script to add const (#1945) --- script/api_protobuf/api_protobuf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 97cc95e556..7f9067cd22 100644 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -821,7 +821,7 @@ cpp += """\ namespace esphome { namespace api { -static const char *TAG = "api.service"; +static const char *const TAG = "api.service"; """ From 871c0ee2a5c8d47626c00a23cfdf03938ad144c1 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 21 Jun 2021 21:17:01 +0200 Subject: [PATCH 0953/1841] Rework climate traits (#1941) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/api/api.proto | 16 +- esphome/components/api/api_connection.cpp | 52 ++--- esphome/components/api/api_pb2.cpp | 40 ++-- esphome/components/api/api_pb2.h | 12 +- .../bang_bang/bang_bang_climate.cpp | 23 +- esphome/components/climate/automation.h | 4 +- esphome/components/climate/climate.cpp | 74 +++--- esphome/components/climate/climate.h | 8 +- esphome/components/climate/climate_mode.cpp | 4 +- esphome/components/climate/climate_mode.h | 9 +- esphome/components/climate/climate_traits.cpp | 203 ---------------- esphome/components/climate/climate_traits.h | 219 +++++++++++------- esphome/components/climate_ir/climate_ir.cpp | 65 +----- esphome/components/climate_ir/climate_ir.h | 9 +- esphome/components/daikin/daikin.h | 11 +- .../components/hitachi_ac344/hitachi_ac344.h | 9 +- esphome/components/midea_ac/midea_climate.cpp | 48 ++-- esphome/components/midea_ac/midea_climate.h | 8 +- esphome/components/mqtt/mqtt_climate.cpp | 14 +- esphome/components/pid/pid_climate.cpp | 10 +- .../thermostat/thermostat_climate.cpp | 73 ++++-- .../components/tuya/climate/tuya_climate.cpp | 6 +- esphome/components/yashima/yashima.cpp | 11 +- script/api_protobuf/api_protobuf.py | 1 + 24 files changed, 387 insertions(+), 542 deletions(-) mode change 100644 => 100755 script/api_protobuf/api_protobuf.py diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index bdb94b3d9b..87a7cf4749 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -710,11 +710,11 @@ enum ClimateAction { CLIMATE_ACTION_FAN = 6; } enum ClimatePreset { - CLIMATE_PRESET_ECO = 0; + CLIMATE_PRESET_HOME = 0; CLIMATE_PRESET_AWAY = 1; CLIMATE_PRESET_BOOST = 2; CLIMATE_PRESET_COMFORT = 3; - CLIMATE_PRESET_HOME = 4; + CLIMATE_PRESET_ECO = 4; CLIMATE_PRESET_SLEEP = 5; CLIMATE_PRESET_ACTIVITY = 6; } @@ -734,7 +734,9 @@ message ListEntitiesClimateResponse { float visual_min_temperature = 8; float visual_max_temperature = 9; float visual_temperature_step = 10; - bool supports_away = 11; + // for older peer versions - in new system this + // is if CLIMATE_PRESET_AWAY exists is supported_presets + bool legacy_supports_away = 11; bool supports_action = 12; repeated ClimateFanMode supported_fan_modes = 13; repeated ClimateSwingMode supported_swing_modes = 14; @@ -754,7 +756,8 @@ message ClimateStateResponse { float target_temperature = 4; float target_temperature_low = 5; float target_temperature_high = 6; - bool away = 7; + // For older peers, equal to preset == CLIMATE_PRESET_AWAY + bool legacy_away = 7; ClimateAction action = 8; ClimateFanMode fan_mode = 9; ClimateSwingMode swing_mode = 10; @@ -777,8 +780,9 @@ message ClimateCommandRequest { float target_temperature_low = 7; bool has_target_temperature_high = 8; float target_temperature_high = 9; - bool has_away = 10; - bool away = 11; + // legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset + bool has_legacy_away = 10; + bool legacy_away = 11; bool has_fan_mode = 12; ClimateFanMode fan_mode = 13; bool has_swing_mode = 14; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 5576ace33b..89c0dde24a 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -475,14 +475,14 @@ bool APIConnection::send_climate_state(climate::Climate *climate) { } else { resp.target_temperature = climate->target_temperature; } - if (traits.get_supports_away()) - resp.away = climate->away; if (traits.get_supports_fan_modes() && climate->fan_mode.has_value()) resp.fan_mode = static_cast(climate->fan_mode.value()); if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value()) resp.custom_fan_mode = climate->custom_fan_mode.value(); - if (traits.get_supports_presets() && climate->preset.has_value()) + if (traits.get_supports_presets() && climate->preset.has_value()) { resp.preset = static_cast(climate->preset.value()); + resp.legacy_away = resp.preset == enums::CLIMATE_PRESET_AWAY; + } if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) resp.custom_preset = climate->custom_preset.value(); if (traits.get_supports_swing_modes()) @@ -498,40 +498,26 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { msg.unique_id = get_default_unique_id("climate", climate); msg.supports_current_temperature = traits.get_supports_current_temperature(); msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); - for (auto mode : - {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT, - climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_HEAT_COOL}) { - if (traits.supports_mode(mode)) - msg.supported_modes.push_back(static_cast(mode)); - } + + for (auto mode : traits.get_supported_modes()) + msg.supported_modes.push_back(static_cast(mode)); + msg.visual_min_temperature = traits.get_visual_min_temperature(); msg.visual_max_temperature = traits.get_visual_max_temperature(); msg.visual_temperature_step = traits.get_visual_temperature_step(); - msg.supports_away = traits.get_supports_away(); + msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY); msg.supports_action = traits.get_supports_action(); - for (auto fan_mode : {climate::CLIMATE_FAN_ON, climate::CLIMATE_FAN_OFF, climate::CLIMATE_FAN_AUTO, - climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH, - climate::CLIMATE_FAN_MIDDLE, climate::CLIMATE_FAN_FOCUS, climate::CLIMATE_FAN_DIFFUSE}) { - if (traits.supports_fan_mode(fan_mode)) - msg.supported_fan_modes.push_back(static_cast(fan_mode)); - } - for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) { + + for (auto fan_mode : traits.get_supported_fan_modes()) + msg.supported_fan_modes.push_back(static_cast(fan_mode)); + for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) msg.supported_custom_fan_modes.push_back(custom_fan_mode); - } - for (auto preset : {climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_AWAY, climate::CLIMATE_PRESET_BOOST, - climate::CLIMATE_PRESET_COMFORT, climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_SLEEP, - climate::CLIMATE_PRESET_ACTIVITY}) { - if (traits.supports_preset(preset)) - msg.supported_presets.push_back(static_cast(preset)); - } - for (auto const &custom_preset : traits.get_supported_custom_presets()) { + for (auto preset : traits.get_supported_presets()) + msg.supported_presets.push_back(static_cast(preset)); + for (auto const &custom_preset : traits.get_supported_custom_presets()) msg.supported_custom_presets.push_back(custom_preset); - } - for (auto swing_mode : {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL, - climate::CLIMATE_SWING_HORIZONTAL}) { - if (traits.supports_swing_mode(swing_mode)) - msg.supported_swing_modes.push_back(static_cast(swing_mode)); - } + for (auto swing_mode : traits.get_supported_swing_modes()) + msg.supported_swing_modes.push_back(static_cast(swing_mode)); return this->send_list_entities_climate_response(msg); } void APIConnection::climate_command(const ClimateCommandRequest &msg) { @@ -548,8 +534,8 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { call.set_target_temperature_low(msg.target_temperature_low); if (msg.has_target_temperature_high) call.set_target_temperature_high(msg.target_temperature_high); - if (msg.has_away) - call.set_away(msg.away); + if (msg.has_legacy_away) + call.set_preset(msg.legacy_away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME); if (msg.has_fan_mode) call.set_fan_mode(static_cast(msg.fan_mode)); if (msg.has_custom_fan_mode) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index a9e9d64bc1..1e023f3988 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -192,16 +192,16 @@ template<> const char *proto_enum_to_string(enums::Climate } template<> const char *proto_enum_to_string(enums::ClimatePreset value) { switch (value) { - case enums::CLIMATE_PRESET_ECO: - return "CLIMATE_PRESET_ECO"; + case enums::CLIMATE_PRESET_HOME: + return "CLIMATE_PRESET_HOME"; case enums::CLIMATE_PRESET_AWAY: return "CLIMATE_PRESET_AWAY"; case enums::CLIMATE_PRESET_BOOST: return "CLIMATE_PRESET_BOOST"; case enums::CLIMATE_PRESET_COMFORT: return "CLIMATE_PRESET_COMFORT"; - case enums::CLIMATE_PRESET_HOME: - return "CLIMATE_PRESET_HOME"; + case enums::CLIMATE_PRESET_ECO: + return "CLIMATE_PRESET_ECO"; case enums::CLIMATE_PRESET_SLEEP: return "CLIMATE_PRESET_SLEEP"; case enums::CLIMATE_PRESET_ACTIVITY: @@ -2672,7 +2672,7 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v return true; } case 11: { - this->supports_away = value.as_bool(); + this->legacy_supports_away = value.as_bool(); return true; } case 12: { @@ -2756,7 +2756,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(8, this->visual_min_temperature); buffer.encode_float(9, this->visual_max_temperature); buffer.encode_float(10, this->visual_temperature_step); - buffer.encode_bool(11, this->supports_away); + buffer.encode_bool(11, this->legacy_supports_away); buffer.encode_bool(12, this->supports_action); for (auto &it : this->supported_fan_modes) { buffer.encode_enum(13, it, true); @@ -2823,8 +2823,8 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" supports_away: "); - out.append(YESNO(this->supports_away)); + out.append(" legacy_supports_away: "); + out.append(YESNO(this->legacy_supports_away)); out.append("\n"); out.append(" supports_action: "); @@ -2869,7 +2869,7 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { return true; } case 7: { - this->away = value.as_bool(); + this->legacy_away = value.as_bool(); return true; } case 8: { @@ -2939,7 +2939,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(4, this->target_temperature); buffer.encode_float(5, this->target_temperature_low); buffer.encode_float(6, this->target_temperature_high); - buffer.encode_bool(7, this->away); + buffer.encode_bool(7, this->legacy_away); buffer.encode_enum(8, this->action); buffer.encode_enum(9, this->fan_mode); buffer.encode_enum(10, this->swing_mode); @@ -2979,8 +2979,8 @@ void ClimateStateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" away: "); - out.append(YESNO(this->away)); + out.append(" legacy_away: "); + out.append(YESNO(this->legacy_away)); out.append("\n"); out.append(" action: "); @@ -3031,11 +3031,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) return true; } case 10: { - this->has_away = value.as_bool(); + this->has_legacy_away = value.as_bool(); return true; } case 11: { - this->away = value.as_bool(); + this->legacy_away = value.as_bool(); return true; } case 12: { @@ -3120,8 +3120,8 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(7, this->target_temperature_low); buffer.encode_bool(8, this->has_target_temperature_high); buffer.encode_float(9, this->target_temperature_high); - buffer.encode_bool(10, this->has_away); - buffer.encode_bool(11, this->away); + buffer.encode_bool(10, this->has_legacy_away); + buffer.encode_bool(11, this->legacy_away); buffer.encode_bool(12, this->has_fan_mode); buffer.encode_enum(13, this->fan_mode); buffer.encode_bool(14, this->has_swing_mode); @@ -3176,12 +3176,12 @@ void ClimateCommandRequest::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" has_away: "); - out.append(YESNO(this->has_away)); + out.append(" has_legacy_away: "); + out.append(YESNO(this->has_legacy_away)); out.append("\n"); - out.append(" away: "); - out.append(YESNO(this->away)); + out.append(" legacy_away: "); + out.append(YESNO(this->legacy_away)); out.append("\n"); out.append(" has_fan_mode: "); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 04d5834572..7f37c0b94b 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -90,11 +90,11 @@ enum ClimateAction : uint32_t { CLIMATE_ACTION_FAN = 6, }; enum ClimatePreset : uint32_t { - CLIMATE_PRESET_ECO = 0, + CLIMATE_PRESET_HOME = 0, CLIMATE_PRESET_AWAY = 1, CLIMATE_PRESET_BOOST = 2, CLIMATE_PRESET_COMFORT = 3, - CLIMATE_PRESET_HOME = 4, + CLIMATE_PRESET_ECO = 4, CLIMATE_PRESET_SLEEP = 5, CLIMATE_PRESET_ACTIVITY = 6, }; @@ -709,7 +709,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { float visual_min_temperature{0.0f}; float visual_max_temperature{0.0f}; float visual_temperature_step{0.0f}; - bool supports_away{false}; + bool legacy_supports_away{false}; bool supports_action{false}; std::vector supported_fan_modes{}; std::vector supported_swing_modes{}; @@ -732,7 +732,7 @@ class ClimateStateResponse : public ProtoMessage { float target_temperature{0.0f}; float target_temperature_low{0.0f}; float target_temperature_high{0.0f}; - bool away{false}; + bool legacy_away{false}; enums::ClimateAction action{}; enums::ClimateFanMode fan_mode{}; enums::ClimateSwingMode swing_mode{}; @@ -758,8 +758,8 @@ class ClimateCommandRequest : public ProtoMessage { float target_temperature_low{0.0f}; bool has_target_temperature_high{false}; float target_temperature_high{0.0f}; - bool has_away{false}; - bool away{false}; + bool has_legacy_away{false}; + bool legacy_away{false}; bool has_fan_mode{false}; enums::ClimateFanMode fan_mode{}; bool has_swing_mode{false}; diff --git a/esphome/components/bang_bang/bang_bang_climate.cpp b/esphome/components/bang_bang/bang_bang_climate.cpp index e3e833ce2a..c043f6b7de 100644 --- a/esphome/components/bang_bang/bang_bang_climate.cpp +++ b/esphome/components/bang_bang/bang_bang_climate.cpp @@ -32,8 +32,8 @@ void BangBangClimate::control(const climate::ClimateCall &call) { this->target_temperature_low = *call.get_target_temperature_low(); if (call.get_target_temperature_high().has_value()) this->target_temperature_high = *call.get_target_temperature_high(); - if (call.get_away().has_value()) - this->change_away_(*call.get_away()); + if (call.get_preset().has_value()) + this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY); this->compute_state_(); this->publish_state(); @@ -41,11 +41,20 @@ void BangBangClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits BangBangClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_heat_cool_mode(true); - traits.set_supports_cool_mode(this->supports_cool_); - traits.set_supports_heat_mode(this->supports_heat_); + traits.set_supported_modes({ + climate::CLIMATE_MODE_OFF, + climate::CLIMATE_MODE_HEAT_COOL, + }); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); traits.set_supports_two_point_target_temperature(true); - traits.set_supports_away(this->supports_away_); + if (supports_away_) + traits.set_supported_presets({ + climate::CLIMATE_PRESET_HOME, + climate::CLIMATE_PRESET_AWAY, + }); traits.set_supports_action(true); return traits; } @@ -140,7 +149,7 @@ void BangBangClimate::change_away_(bool away) { this->target_temperature_low = this->away_config_.default_temperature_low; this->target_temperature_high = this->away_config_.default_temperature_high; } - this->away = away; + this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME; } void BangBangClimate::set_normal_config(const BangBangClimateTargetTempConfig &normal_config) { this->normal_config_ = normal_config; diff --git a/esphome/components/climate/automation.h b/esphome/components/climate/automation.h index b0b71cb7d7..49a87027f2 100644 --- a/esphome/components/climate/automation.h +++ b/esphome/components/climate/automation.h @@ -27,7 +27,9 @@ template class ControlAction : public Action { call.set_target_temperature(this->target_temperature_.optional_value(x...)); call.set_target_temperature_low(this->target_temperature_low_.optional_value(x...)); call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...)); - call.set_away(this->away_.optional_value(x...)); + if (away_.has_value()) { + call.set_preset(away_.value(x...) ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME); + } call.set_fan_mode(this->fan_mode_.optional_value(x...)); call.set_fan_mode(this->custom_fan_mode_.optional_value(x...)); call.set_preset(this->preset_.optional_value(x...)); diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 49ed8d922c..07347e4eee 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -43,9 +43,6 @@ void ClimateCall::perform() { if (this->target_temperature_high_.has_value()) { ESP_LOGD(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_); } - if (this->away_.has_value()) { - ESP_LOGD(TAG, " Away Mode: %s", ONOFF(*this->away_)); - } this->parent_->control(*this); } void ClimateCall::validate_() { @@ -125,12 +122,6 @@ void ClimateCall::validate_() { this->target_temperature_high_.reset(); } } - if (this->away_.has_value()) { - if (!traits.get_supports_away()) { - ESP_LOGW(TAG, " Cannot set away mode for this device!"); - this->away_.reset(); - } - } } ClimateCall &ClimateCall::set_mode(ClimateMode mode) { this->mode_ = mode; @@ -181,8 +172,7 @@ ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) { } else if (str_equals_case_insensitive(fan_mode, "DIFFUSE")) { this->set_fan_mode(CLIMATE_FAN_DIFFUSE); } else { - auto custom_fan_modes = this->parent_->get_traits().get_supported_custom_fan_modes(); - if (std::find(custom_fan_modes.begin(), custom_fan_modes.end(), fan_mode) != custom_fan_modes.end()) { + if (this->parent_->get_traits().supports_custom_fan_mode(fan_mode)) { this->custom_fan_mode_ = fan_mode; this->fan_mode_.reset(); } else { @@ -218,8 +208,7 @@ ClimateCall &ClimateCall::set_preset(const std::string &preset) { } else if (str_equals_case_insensitive(preset, "ACTIVITY")) { this->set_preset(CLIMATE_PRESET_ACTIVITY); } else { - auto custom_presets = this->parent_->get_traits().get_supported_custom_presets(); - if (std::find(custom_presets.begin(), custom_presets.end(), preset) != custom_presets.end()) { + if (this->parent_->get_traits().supports_custom_preset(preset)) { this->custom_preset_ = preset; this->preset_.reset(); } else { @@ -269,18 +258,23 @@ const optional &ClimateCall::get_mode() const { return this->mode_; const optional &ClimateCall::get_target_temperature() const { return this->target_temperature_; } const optional &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; } const optional &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; } -const optional &ClimateCall::get_away() const { return this->away_; } +optional ClimateCall::get_away() const { + if (!this->preset_.has_value()) + return {}; + return *this->preset_ == ClimatePreset::CLIMATE_PRESET_AWAY; +} const optional &ClimateCall::get_fan_mode() const { return this->fan_mode_; } const optional &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; } const optional &ClimateCall::get_preset() const { return this->preset_; } const optional &ClimateCall::get_custom_preset() const { return this->custom_preset_; } const optional &ClimateCall::get_swing_mode() const { return this->swing_mode_; } ClimateCall &ClimateCall::set_away(bool away) { - this->away_ = away; + this->preset_ = away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME; return *this; } ClimateCall &ClimateCall::set_away(optional away) { - this->away_ = away; + if (away.has_value()) + this->preset_ = *away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME; return *this; } ClimateCall &ClimateCall::set_target_temperature_high(optional target_temperature_high) { @@ -338,20 +332,17 @@ void Climate::save_state_() { } else { state.target_temperature = this->target_temperature; } - if (traits.get_supports_away()) { - state.away = this->away; - } if (traits.get_supports_fan_modes() && fan_mode.has_value()) { state.uses_custom_fan_mode = false; state.fan_mode = this->fan_mode.value(); } if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) { state.uses_custom_fan_mode = true; - auto &custom_fan_modes = traits.get_supported_custom_fan_modes(); - auto it = std::find(custom_fan_modes.begin(), custom_fan_modes.end(), this->custom_fan_mode.value()); - // only set custom fan mode if value exists, otherwise leave it as is - if (it != custom_fan_modes.cend()) { - state.custom_fan_mode = std::distance(custom_fan_modes.begin(), it); + const auto &supported = traits.get_supported_custom_fan_modes(); + std::vector vec{supported.begin(), supported.end()}; + auto it = std::find(vec.begin(), vec.end(), custom_fan_mode); + if (it != vec.end()) { + state.custom_fan_mode = std::distance(vec.begin(), it); } } if (traits.get_supports_presets() && preset.has_value()) { @@ -360,11 +351,12 @@ void Climate::save_state_() { } if (!traits.get_supported_custom_presets().empty() && custom_preset.has_value()) { state.uses_custom_preset = true; - auto custom_presets = traits.get_supported_custom_presets(); - auto it = std::find(custom_presets.begin(), custom_presets.end(), this->custom_preset.value()); + const auto &supported = traits.get_supported_custom_presets(); + std::vector vec{supported.begin(), supported.end()}; + auto it = std::find(vec.begin(), vec.end(), custom_preset); // only set custom preset if value exists, otherwise leave it as is - if (it != custom_presets.cend()) { - state.custom_preset = std::distance(custom_presets.begin(), it); + if (it != vec.cend()) { + state.custom_preset = std::distance(vec.begin(), it); } } if (traits.get_supports_swing_modes()) { @@ -405,9 +397,6 @@ void Climate::publish_state() { } else { ESP_LOGD(TAG, " Target Temperature: %.2f°C", this->target_temperature); } - if (traits.get_supports_away()) { - ESP_LOGD(TAG, " Away: %s", ONOFF(this->away)); - } // Send state to frontend this->state_callback_.call(); @@ -453,9 +442,6 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) { } else { call.set_target_temperature(this->target_temperature); } - if (traits.get_supports_away()) { - call.set_away(this->away); - } if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) { call.set_fan_mode(this->fan_mode); } @@ -476,23 +462,27 @@ void ClimateDeviceRestoreState::apply(Climate *climate) { } else { climate->target_temperature = this->target_temperature; } - if (traits.get_supports_away()) { - climate->away = this->away; - } if (traits.get_supports_fan_modes() && !this->uses_custom_fan_mode) { climate->fan_mode = this->fan_mode; } if (!traits.get_supported_custom_fan_modes().empty() && this->uses_custom_fan_mode) { - climate->custom_fan_mode = traits.get_supported_custom_fan_modes()[this->custom_fan_mode]; + // std::set has consistent order (lexicographic for strings), so this is ok + const auto &modes = traits.get_supported_custom_fan_modes(); + std::vector modes_vec{modes.begin(), modes.end()}; + if (custom_fan_mode < modes_vec.size()) { + climate->custom_fan_mode = modes_vec[this->custom_fan_mode]; + } } if (traits.get_supports_presets() && !this->uses_custom_preset) { climate->preset = this->preset; } - if (!traits.get_supported_custom_presets().empty() && this->uses_custom_preset) { - climate->custom_preset = traits.get_supported_custom_presets()[this->custom_preset]; - } if (!traits.get_supported_custom_presets().empty() && uses_custom_preset) { - climate->custom_preset = traits.get_supported_custom_presets()[this->preset]; + // std::set has consistent order (lexicographic for strings), so this is ok + const auto &presets = traits.get_supported_custom_presets(); + std::vector presets_vec{presets.begin(), presets.end()}; + if (custom_preset < presets_vec.size()) { + climate->custom_preset = presets_vec[this->custom_preset]; + } } if (traits.get_supports_swing_modes()) { climate->swing_mode = this->swing_mode; diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index b688ac8efb..ed5c5069b7 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -63,7 +63,9 @@ class ClimateCall { * For climate devices with two point target temperature control */ ClimateCall &set_target_temperature_high(optional target_temperature_high); + ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead") ClimateCall &set_away(bool away); + ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead") ClimateCall &set_away(optional away); /// Set the fan mode of the climate device. ClimateCall &set_fan_mode(ClimateFanMode fan_mode); @@ -94,7 +96,8 @@ class ClimateCall { const optional &get_target_temperature() const; const optional &get_target_temperature_low() const; const optional &get_target_temperature_high() const; - const optional &get_away() const; + ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead") + optional get_away() const; const optional &get_fan_mode() const; const optional &get_swing_mode() const; const optional &get_custom_fan_mode() const; @@ -109,7 +112,6 @@ class ClimateCall { optional target_temperature_; optional target_temperature_low_; optional target_temperature_high_; - optional away_; optional fan_mode_; optional swing_mode_; optional custom_fan_mode_; @@ -120,7 +122,6 @@ class ClimateCall { /// Struct used to save the state of the climate device in restore memory. struct ClimateDeviceRestoreState { ClimateMode mode; - bool away; bool uses_custom_fan_mode{false}; union { ClimateFanMode fan_mode; @@ -191,6 +192,7 @@ class Climate : public Nameable { * Away allows climate devices to have two different target temperature configs: * one for normal mode and one for away mode. */ + ESPDEPRECATED("away is deprecated, use preset instead") bool away{false}; /// The active fan mode of the climate device. diff --git a/esphome/components/climate/climate_mode.cpp b/esphome/components/climate/climate_mode.cpp index 4540208a3f..099074a887 100644 --- a/esphome/components/climate/climate_mode.cpp +++ b/esphome/components/climate/climate_mode.cpp @@ -84,6 +84,8 @@ const char *climate_swing_mode_to_string(ClimateSwingMode swing_mode) { const char *climate_preset_to_string(ClimatePreset preset) { switch (preset) { + case climate::CLIMATE_PRESET_HOME: + return "HOME"; case climate::CLIMATE_PRESET_ECO: return "ECO"; case climate::CLIMATE_PRESET_AWAY: @@ -92,8 +94,6 @@ const char *climate_preset_to_string(ClimatePreset preset) { return "BOOST"; case climate::CLIMATE_PRESET_COMFORT: return "COMFORT"; - case climate::CLIMATE_PRESET_HOME: - return "HOME"; case climate::CLIMATE_PRESET_SLEEP: return "SLEEP"; case climate::CLIMATE_PRESET_ACTIVITY: diff --git a/esphome/components/climate/climate_mode.h b/esphome/components/climate/climate_mode.h index e129fca91d..7afa2dae55 100644 --- a/esphome/components/climate/climate_mode.h +++ b/esphome/components/climate/climate_mode.h @@ -39,7 +39,6 @@ enum ClimateAction : uint8_t { CLIMATE_ACTION_FAN = 6, }; -/// Enum for all modes a climate fan can be in enum ClimateFanMode : uint8_t { /// The fan mode is set to On CLIMATE_FAN_ON = 0, @@ -75,16 +74,16 @@ enum ClimateSwingMode : uint8_t { /// Enum for all modes a climate swing can be in enum ClimatePreset : uint8_t { - /// Preset is set to ECO - CLIMATE_PRESET_ECO = 0, + /// Preset is set to HOME + CLIMATE_PRESET_HOME = 0, /// Preset is set to AWAY CLIMATE_PRESET_AWAY = 1, /// Preset is set to BOOST CLIMATE_PRESET_BOOST = 2, /// Preset is set to COMFORT CLIMATE_PRESET_COMFORT = 3, - /// Preset is set to HOME - CLIMATE_PRESET_HOME = 4, + /// Preset is set to ECO + CLIMATE_PRESET_ECO = 4, /// Preset is set to SLEEP CLIMATE_PRESET_SLEEP = 5, /// Preset is set to ACTIVITY diff --git a/esphome/components/climate/climate_traits.cpp b/esphome/components/climate/climate_traits.cpp index 774ada785f..c871552360 100644 --- a/esphome/components/climate/climate_traits.cpp +++ b/esphome/components/climate/climate_traits.cpp @@ -4,55 +4,6 @@ namespace esphome { namespace climate { -bool ClimateTraits::supports_mode(ClimateMode mode) const { - switch (mode) { - case CLIMATE_MODE_OFF: - return true; - case CLIMATE_MODE_HEAT_COOL: - return this->supports_heat_cool_mode_; - case CLIMATE_MODE_AUTO: - return this->supports_auto_mode_; - case CLIMATE_MODE_COOL: - return this->supports_cool_mode_; - case CLIMATE_MODE_HEAT: - return this->supports_heat_mode_; - case CLIMATE_MODE_FAN_ONLY: - return this->supports_fan_only_mode_; - case CLIMATE_MODE_DRY: - return this->supports_dry_mode_; - default: - return false; - } -} -bool ClimateTraits::get_supports_current_temperature() const { return supports_current_temperature_; } -void ClimateTraits::set_supports_current_temperature(bool supports_current_temperature) { - supports_current_temperature_ = supports_current_temperature; -} -bool ClimateTraits::get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; } -void ClimateTraits::set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) { - supports_two_point_target_temperature_ = supports_two_point_target_temperature; -} -void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_auto_mode_ = supports_auto_mode; } -void ClimateTraits::set_supports_heat_cool_mode(bool supports_heat_cool_mode) { - supports_heat_cool_mode_ = supports_heat_cool_mode; -} -void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; } -void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; } -void ClimateTraits::set_supports_fan_only_mode(bool supports_fan_only_mode) { - supports_fan_only_mode_ = supports_fan_only_mode; -} -void ClimateTraits::set_supports_dry_mode(bool supports_dry_mode) { supports_dry_mode_ = supports_dry_mode; } -void ClimateTraits::set_supports_away(bool supports_away) { supports_away_ = supports_away; } -void ClimateTraits::set_supports_action(bool supports_action) { supports_action_ = supports_action; } -float ClimateTraits::get_visual_min_temperature() const { return visual_min_temperature_; } -void ClimateTraits::set_visual_min_temperature(float visual_min_temperature) { - visual_min_temperature_ = visual_min_temperature; -} -float ClimateTraits::get_visual_max_temperature() const { return visual_max_temperature_; } -void ClimateTraits::set_visual_max_temperature(float visual_max_temperature) { - visual_max_temperature_ = visual_max_temperature; -} -float ClimateTraits::get_visual_temperature_step() const { return visual_temperature_step_; } int8_t ClimateTraits::get_temperature_accuracy_decimals() const { // use printf %g to find number of digits based on temperature step char buf[32]; @@ -64,160 +15,6 @@ int8_t ClimateTraits::get_temperature_accuracy_decimals() const { return str.length() - dot_pos - 1; } -void ClimateTraits::set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; } -bool ClimateTraits::get_supports_away() const { return supports_away_; } -bool ClimateTraits::get_supports_action() const { return supports_action_; } -void ClimateTraits::set_supports_fan_mode_on(bool supports_fan_mode_on) { - this->supports_fan_mode_on_ = supports_fan_mode_on; -} -void ClimateTraits::set_supports_fan_mode_off(bool supports_fan_mode_off) { - this->supports_fan_mode_off_ = supports_fan_mode_off; -} -void ClimateTraits::set_supports_fan_mode_auto(bool supports_fan_mode_auto) { - this->supports_fan_mode_auto_ = supports_fan_mode_auto; -} -void ClimateTraits::set_supports_fan_mode_low(bool supports_fan_mode_low) { - this->supports_fan_mode_low_ = supports_fan_mode_low; -} -void ClimateTraits::set_supports_fan_mode_medium(bool supports_fan_mode_medium) { - this->supports_fan_mode_medium_ = supports_fan_mode_medium; -} -void ClimateTraits::set_supports_fan_mode_high(bool supports_fan_mode_high) { - this->supports_fan_mode_high_ = supports_fan_mode_high; -} -void ClimateTraits::set_supports_fan_mode_middle(bool supports_fan_mode_middle) { - this->supports_fan_mode_middle_ = supports_fan_mode_middle; -} -void ClimateTraits::set_supports_fan_mode_focus(bool supports_fan_mode_focus) { - this->supports_fan_mode_focus_ = supports_fan_mode_focus; -} -void ClimateTraits::set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse) { - this->supports_fan_mode_diffuse_ = supports_fan_mode_diffuse; -} -bool ClimateTraits::supports_fan_mode(ClimateFanMode fan_mode) const { - switch (fan_mode) { - case climate::CLIMATE_FAN_ON: - return this->supports_fan_mode_on_; - case climate::CLIMATE_FAN_OFF: - return this->supports_fan_mode_off_; - case climate::CLIMATE_FAN_AUTO: - return this->supports_fan_mode_auto_; - case climate::CLIMATE_FAN_LOW: - return this->supports_fan_mode_low_; - case climate::CLIMATE_FAN_MEDIUM: - return this->supports_fan_mode_medium_; - case climate::CLIMATE_FAN_HIGH: - return this->supports_fan_mode_high_; - case climate::CLIMATE_FAN_MIDDLE: - return this->supports_fan_mode_middle_; - case climate::CLIMATE_FAN_FOCUS: - return this->supports_fan_mode_focus_; - case climate::CLIMATE_FAN_DIFFUSE: - return this->supports_fan_mode_diffuse_; - default: - return false; - } -} -bool ClimateTraits::get_supports_fan_modes() const { - return this->supports_fan_mode_on_ || this->supports_fan_mode_off_ || this->supports_fan_mode_auto_ || - this->supports_fan_mode_low_ || this->supports_fan_mode_medium_ || this->supports_fan_mode_high_ || - this->supports_fan_mode_middle_ || this->supports_fan_mode_focus_ || this->supports_fan_mode_diffuse_; -} -void ClimateTraits::set_supported_custom_fan_modes(std::vector &supported_custom_fan_modes) { - this->supported_custom_fan_modes_ = supported_custom_fan_modes; -} -const std::vector ClimateTraits::get_supported_custom_fan_modes() const { - return this->supported_custom_fan_modes_; -} -bool ClimateTraits::supports_custom_fan_mode(std::string &custom_fan_mode) const { - return std::count(this->supported_custom_fan_modes_.begin(), this->supported_custom_fan_modes_.end(), - custom_fan_mode); -} -bool ClimateTraits::supports_preset(ClimatePreset preset) const { - switch (preset) { - case climate::CLIMATE_PRESET_ECO: - return this->supports_preset_eco_; - case climate::CLIMATE_PRESET_AWAY: - return this->supports_preset_away_; - case climate::CLIMATE_PRESET_BOOST: - return this->supports_preset_boost_; - case climate::CLIMATE_PRESET_COMFORT: - return this->supports_preset_comfort_; - case climate::CLIMATE_PRESET_HOME: - return this->supports_preset_home_; - case climate::CLIMATE_PRESET_SLEEP: - return this->supports_preset_sleep_; - case climate::CLIMATE_PRESET_ACTIVITY: - return this->supports_preset_activity_; - default: - return false; - } -} -void ClimateTraits::set_supports_preset_eco(bool supports_preset_eco) { - this->supports_preset_eco_ = supports_preset_eco; -} -void ClimateTraits::set_supports_preset_away(bool supports_preset_away) { - this->supports_preset_away_ = supports_preset_away; -} -void ClimateTraits::set_supports_preset_boost(bool supports_preset_boost) { - this->supports_preset_boost_ = supports_preset_boost; -} -void ClimateTraits::set_supports_preset_comfort(bool supports_preset_comfort) { - this->supports_preset_comfort_ = supports_preset_comfort; -} -void ClimateTraits::set_supports_preset_home(bool supports_preset_home) { - this->supports_preset_home_ = supports_preset_home; -} -void ClimateTraits::set_supports_preset_sleep(bool supports_preset_sleep) { - this->supports_preset_sleep_ = supports_preset_sleep; -} -void ClimateTraits::set_supports_preset_activity(bool supports_preset_activity) { - this->supports_preset_activity_ = supports_preset_activity; -} -bool ClimateTraits::get_supports_presets() const { - return this->supports_preset_eco_ || this->supports_preset_away_ || this->supports_preset_boost_ || - this->supports_preset_comfort_ || this->supports_preset_home_ || this->supports_preset_sleep_ || - this->supports_preset_activity_; -} -void ClimateTraits::set_supported_custom_presets(std::vector &supported_custom_presets) { - this->supported_custom_presets_ = supported_custom_presets; -} -const std::vector ClimateTraits::get_supported_custom_presets() const { - return this->supported_custom_presets_; -} -bool ClimateTraits::supports_custom_preset(std::string &custom_preset) const { - return std::count(this->supported_custom_presets_.begin(), this->supported_custom_presets_.end(), custom_preset); -} -void ClimateTraits::set_supports_swing_mode_off(bool supports_swing_mode_off) { - this->supports_swing_mode_off_ = supports_swing_mode_off; -} -void ClimateTraits::set_supports_swing_mode_both(bool supports_swing_mode_both) { - this->supports_swing_mode_both_ = supports_swing_mode_both; -} -void ClimateTraits::set_supports_swing_mode_vertical(bool supports_swing_mode_vertical) { - this->supports_swing_mode_vertical_ = supports_swing_mode_vertical; -} -void ClimateTraits::set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal) { - this->supports_swing_mode_horizontal_ = supports_swing_mode_horizontal; -} -bool ClimateTraits::supports_swing_mode(ClimateSwingMode swing_mode) const { - switch (swing_mode) { - case climate::CLIMATE_SWING_OFF: - return this->supports_swing_mode_off_; - case climate::CLIMATE_SWING_BOTH: - return this->supports_swing_mode_both_; - case climate::CLIMATE_SWING_VERTICAL: - return this->supports_swing_mode_vertical_; - case climate::CLIMATE_SWING_HORIZONTAL: - return this->supports_swing_mode_horizontal_; - default: - return false; - } -} -bool ClimateTraits::get_supports_swing_modes() const { - return this->supports_swing_mode_off_ || this->supports_swing_mode_both_ || supports_swing_mode_vertical_ || - supports_swing_mode_horizontal_; -} } // namespace climate } // namespace esphome diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index f8e6f87306..b86a0c7774 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -2,6 +2,7 @@ #include "esphome/core/helpers.h" #include "climate_mode.h" +#include namespace esphome { namespace climate { @@ -24,8 +25,6 @@ namespace climate { * - heat mode (increases current temperature) * - dry mode (removes humidity from air) * - fan mode (only turns on fan) - * - supports away - away mode means that the climate device supports two different - * target temperature settings: one target temp setting for "away" mode and one for non-away mode. * - supports action - if the climate device supports reporting the active * current action of the device with the action property. * - supports fan modes - optionally, if it has a fan which can be configured in different ways: @@ -41,95 +40,147 @@ namespace climate { */ class ClimateTraits { public: - bool get_supports_current_temperature() const; - void set_supports_current_temperature(bool supports_current_temperature); - bool get_supports_two_point_target_temperature() const; - void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature); - void set_supports_auto_mode(bool supports_auto_mode); - void set_supports_heat_cool_mode(bool supports_heat_cool_mode); - void set_supports_cool_mode(bool supports_cool_mode); - void set_supports_heat_mode(bool supports_heat_mode); - void set_supports_fan_only_mode(bool supports_fan_only_mode); - void set_supports_dry_mode(bool supports_dry_mode); - void set_supports_away(bool supports_away); - bool get_supports_away() const; - void set_supports_action(bool supports_action); - bool get_supports_action() const; - bool supports_mode(ClimateMode mode) const; - void set_supports_fan_mode_on(bool supports_fan_mode_on); - void set_supports_fan_mode_off(bool supports_fan_mode_off); - void set_supports_fan_mode_auto(bool supports_fan_mode_auto); - void set_supports_fan_mode_low(bool supports_fan_mode_low); - void set_supports_fan_mode_medium(bool supports_fan_mode_medium); - void set_supports_fan_mode_high(bool supports_fan_mode_high); - void set_supports_fan_mode_middle(bool supports_fan_mode_middle); - void set_supports_fan_mode_focus(bool supports_fan_mode_focus); - void set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse); - bool supports_fan_mode(ClimateFanMode fan_mode) const; - bool get_supports_fan_modes() const; - void set_supported_custom_fan_modes(std::vector &supported_custom_fan_modes); - const std::vector get_supported_custom_fan_modes() const; - bool supports_custom_fan_mode(std::string &custom_fan_mode) const; - bool supports_preset(ClimatePreset preset) const; - void set_supports_preset_eco(bool supports_preset_eco); - void set_supports_preset_away(bool supports_preset_away); - void set_supports_preset_boost(bool supports_preset_boost); - void set_supports_preset_comfort(bool supports_preset_comfort); - void set_supports_preset_home(bool supports_preset_home); - void set_supports_preset_sleep(bool supports_preset_sleep); - void set_supports_preset_activity(bool supports_preset_activity); - bool get_supports_presets() const; - void set_supported_custom_presets(std::vector &supported_custom_presets); - const std::vector get_supported_custom_presets() const; - bool supports_custom_preset(std::string &custom_preset) const; - void set_supports_swing_mode_off(bool supports_swing_mode_off); - void set_supports_swing_mode_both(bool supports_swing_mode_both); - void set_supports_swing_mode_vertical(bool supports_swing_mode_vertical); - void set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal); - bool supports_swing_mode(ClimateSwingMode swing_mode) const; - bool get_supports_swing_modes() const; + bool get_supports_current_temperature() const { return supports_current_temperature_; } + void set_supports_current_temperature(bool supports_current_temperature) { + supports_current_temperature_ = supports_current_temperature; + } + bool get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; } + void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) { + supports_two_point_target_temperature_ = supports_two_point_target_temperature; + } + void set_supported_modes(std::set modes) { supported_modes_ = std::move(modes); } + void add_supported_mode(ClimateMode mode) { supported_modes_.insert(mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_auto_mode(bool supports_auto_mode) { set_mode_support_(CLIMATE_MODE_AUTO, supports_auto_mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_cool_mode(bool supports_cool_mode) { set_mode_support_(CLIMATE_MODE_COOL, supports_cool_mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_heat_mode(bool supports_heat_mode) { set_mode_support_(CLIMATE_MODE_HEAT, supports_heat_mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_heat_cool_mode(bool supported) { set_mode_support_(CLIMATE_MODE_HEAT_COOL, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_fan_only_mode(bool supports_fan_only_mode) { + set_mode_support_(CLIMATE_MODE_FAN_ONLY, supports_fan_only_mode); + } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); } + bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); } + const std::set get_supported_modes() const { return supported_modes_; } - float get_visual_min_temperature() const; - void set_visual_min_temperature(float visual_min_temperature); - float get_visual_max_temperature() const; - void set_visual_max_temperature(float visual_max_temperature); - float get_visual_temperature_step() const; + void set_supports_action(bool supports_action) { supports_action_ = supports_action; } + bool get_supports_action() const { return supports_action_; } + + void set_supported_fan_modes(std::set modes) { supported_fan_modes_ = std::move(modes); } + void add_supported_fan_mode(ClimateFanMode mode) { supported_fan_modes_.insert(mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_on(bool supported) { set_fan_mode_support_(CLIMATE_FAN_ON, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_off(bool supported) { set_fan_mode_support_(CLIMATE_FAN_OFF, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_auto(bool supported) { set_fan_mode_support_(CLIMATE_FAN_AUTO, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_low(bool supported) { set_fan_mode_support_(CLIMATE_FAN_LOW, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_medium(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MEDIUM, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_high(bool supported) { set_fan_mode_support_(CLIMATE_FAN_HIGH, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_middle(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MIDDLE, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_focus(bool supported) { set_fan_mode_support_(CLIMATE_FAN_FOCUS, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); } + bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); } + bool get_supports_fan_modes() const { return !supported_fan_modes_.empty(); } + const std::set get_supported_fan_modes() const { return supported_fan_modes_; } + + void set_supported_custom_fan_modes(std::set supported_custom_fan_modes) { + supported_custom_fan_modes_ = std::move(supported_custom_fan_modes); + } + const std::set &get_supported_custom_fan_modes() const { return supported_custom_fan_modes_; } + bool supports_custom_fan_mode(const std::string &custom_fan_mode) const { + return supported_custom_fan_modes_.count(custom_fan_mode); + } + + void set_supported_presets(std::set presets) { supported_presets_ = std::move(presets); } + void add_supported_preset(ClimatePreset preset) { supported_presets_.insert(preset); } + bool supports_preset(ClimatePreset preset) const { return supported_presets_.count(preset); } + bool get_supports_presets() const { return !supported_presets_.empty(); } + const std::set &get_supported_presets() const { return supported_presets_; } + + void set_supported_custom_presets(std::set supported_custom_presets) { + supported_custom_presets_ = std::move(supported_custom_presets); + } + const std::set &get_supported_custom_presets() const { return supported_custom_presets_; } + bool supports_custom_preset(const std::string &custom_preset) const { + return supported_custom_presets_.count(custom_preset); + } + ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead") + void set_supports_away(bool supports) { + if (supports) { + supported_presets_.insert(CLIMATE_PRESET_AWAY); + supported_presets_.insert(CLIMATE_PRESET_HOME); + } + } + ESPDEPRECATED("This method is deprecated, use supports_preset() instead") + bool get_supports_away() const { return supports_preset(CLIMATE_PRESET_AWAY); } + + void set_supported_swing_modes(std::set modes) { supported_swing_modes_ = std::move(modes); } + void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_swing_mode_off(bool supported) { set_swing_mode_support_(CLIMATE_SWING_OFF, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_swing_mode_both(bool supported) { set_swing_mode_support_(CLIMATE_SWING_BOTH, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_swing_mode_vertical(bool supported) { set_swing_mode_support_(CLIMATE_SWING_VERTICAL, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_swing_mode_horizontal(bool supported) { + set_swing_mode_support_(CLIMATE_SWING_HORIZONTAL, supported); + } + bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); } + bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); } + const std::set get_supported_swing_modes() { return supported_swing_modes_; } + + float get_visual_min_temperature() const { return visual_min_temperature_; } + void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; } + float get_visual_max_temperature() const { return visual_max_temperature_; } + void set_visual_max_temperature(float visual_max_temperature) { visual_max_temperature_ = visual_max_temperature; } + float get_visual_temperature_step() const { return visual_temperature_step_; } int8_t get_temperature_accuracy_decimals() const; - void set_visual_temperature_step(float temperature_step); + void set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; } protected: + void set_mode_support_(climate::ClimateMode mode, bool supported) { + if (supported) { + supported_modes_.insert(mode); + } else { + supported_modes_.erase(mode); + } + } + void set_fan_mode_support_(climate::ClimateFanMode mode, bool supported) { + if (supported) { + supported_fan_modes_.insert(mode); + } else { + supported_fan_modes_.erase(mode); + } + } + void set_swing_mode_support_(climate::ClimateSwingMode mode, bool supported) { + if (supported) { + supported_swing_modes_.insert(mode); + } else { + supported_swing_modes_.erase(mode); + } + } + bool supports_current_temperature_{false}; bool supports_two_point_target_temperature_{false}; - bool supports_auto_mode_{false}; - bool supports_heat_cool_mode_{false}; - bool supports_cool_mode_{false}; - bool supports_heat_mode_{false}; - bool supports_fan_only_mode_{false}; - bool supports_dry_mode_{false}; - bool supports_away_{false}; + std::set supported_modes_ = {climate::CLIMATE_MODE_OFF}; bool supports_action_{false}; - bool supports_fan_mode_on_{false}; - bool supports_fan_mode_off_{false}; - bool supports_fan_mode_auto_{false}; - bool supports_fan_mode_low_{false}; - bool supports_fan_mode_medium_{false}; - bool supports_fan_mode_high_{false}; - bool supports_fan_mode_middle_{false}; - bool supports_fan_mode_focus_{false}; - bool supports_fan_mode_diffuse_{false}; - bool supports_swing_mode_off_{false}; - bool supports_swing_mode_both_{false}; - bool supports_swing_mode_vertical_{false}; - bool supports_swing_mode_horizontal_{false}; - bool supports_preset_eco_{false}; - bool supports_preset_away_{false}; - bool supports_preset_boost_{false}; - bool supports_preset_comfort_{false}; - bool supports_preset_home_{false}; - bool supports_preset_sleep_{false}; - bool supports_preset_activity_{false}; - std::vector supported_custom_fan_modes_; - std::vector supported_custom_presets_; + std::set supported_fan_modes_; + std::set supported_swing_modes_; + std::set supported_presets_; + std::set supported_custom_fan_modes_; + std::set supported_custom_presets_; float visual_min_temperature_{10}; float visual_max_temperature_{30}; diff --git a/esphome/components/climate_ir/climate_ir.cpp b/esphome/components/climate_ir/climate_ir.cpp index 5b16ed7fae..8d5ae6053d 100644 --- a/esphome/components/climate_ir/climate_ir.cpp +++ b/esphome/components/climate_ir/climate_ir.cpp @@ -9,63 +9,22 @@ static const char *const TAG = "climate_ir"; climate::ClimateTraits ClimateIR::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->sensor_ != nullptr); - traits.set_supports_heat_cool_mode(true); - traits.set_supports_cool_mode(this->supports_cool_); - traits.set_supports_heat_mode(this->supports_heat_); - traits.set_supports_dry_mode(this->supports_dry_); - traits.set_supports_fan_only_mode(this->supports_fan_only_); + traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL}); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + if (supports_dry_) + traits.add_supported_mode(climate::CLIMATE_MODE_DRY); + if (supports_fan_only_) + traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY); + traits.set_supports_two_point_target_temperature(false); - traits.set_supports_away(false); traits.set_visual_min_temperature(this->minimum_temperature_); traits.set_visual_max_temperature(this->maximum_temperature_); traits.set_visual_temperature_step(this->temperature_step_); - for (auto fan_mode : this->fan_modes_) { - switch (fan_mode) { - case climate::CLIMATE_FAN_AUTO: - traits.set_supports_fan_mode_auto(true); - break; - case climate::CLIMATE_FAN_DIFFUSE: - traits.set_supports_fan_mode_diffuse(true); - break; - case climate::CLIMATE_FAN_FOCUS: - traits.set_supports_fan_mode_focus(true); - break; - case climate::CLIMATE_FAN_HIGH: - traits.set_supports_fan_mode_high(true); - break; - case climate::CLIMATE_FAN_LOW: - traits.set_supports_fan_mode_low(true); - break; - case climate::CLIMATE_FAN_MEDIUM: - traits.set_supports_fan_mode_medium(true); - break; - case climate::CLIMATE_FAN_MIDDLE: - traits.set_supports_fan_mode_middle(true); - break; - case climate::CLIMATE_FAN_OFF: - traits.set_supports_fan_mode_off(true); - break; - case climate::CLIMATE_FAN_ON: - traits.set_supports_fan_mode_on(true); - break; - } - } - for (auto swing_mode : this->swing_modes_) { - switch (swing_mode) { - case climate::CLIMATE_SWING_OFF: - traits.set_supports_swing_mode_off(true); - break; - case climate::CLIMATE_SWING_BOTH: - traits.set_supports_swing_mode_both(true); - break; - case climate::CLIMATE_SWING_VERTICAL: - traits.set_supports_swing_mode_vertical(true); - break; - case climate::CLIMATE_SWING_HORIZONTAL: - traits.set_supports_swing_mode_horizontal(true); - break; - } - } + traits.set_supported_fan_modes(fan_modes_); + traits.set_supported_swing_modes(swing_modes_); return traits; } diff --git a/esphome/components/climate_ir/climate_ir.h b/esphome/components/climate_ir/climate_ir.h index 0914f730cf..677021da29 100644 --- a/esphome/components/climate_ir/climate_ir.h +++ b/esphome/components/climate_ir/climate_ir.h @@ -21,9 +21,8 @@ namespace climate_ir { class ClimateIR : public climate::Climate, public Component, public remote_base::RemoteReceiverListener { public: ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f, - bool supports_dry = false, bool supports_fan_only = false, - std::vector fan_modes = {}, - std::vector swing_modes = {}) { + bool supports_dry = false, bool supports_fan_only = false, std::set fan_modes = {}, + std::set swing_modes = {}) { this->minimum_temperature_ = minimum_temperature; this->maximum_temperature_ = maximum_temperature; this->temperature_step_ = temperature_step; @@ -60,8 +59,8 @@ class ClimateIR : public climate::Climate, public Component, public remote_base: bool supports_heat_{true}; bool supports_dry_{false}; bool supports_fan_only_{false}; - std::vector fan_modes_ = {}; - std::vector swing_modes_ = {}; + std::set fan_modes_ = {}; + std::set swing_modes_ = {}; remote_transmitter::RemoteTransmitterComponent *transmitter_; sensor::Sensor *sensor_{nullptr}; diff --git a/esphome/components/daikin/daikin.h b/esphome/components/daikin/daikin.h index c0a472bce7..b4ac309de9 100644 --- a/esphome/components/daikin/daikin.h +++ b/esphome/components/daikin/daikin.h @@ -43,12 +43,11 @@ const uint8_t DAIKIN_STATE_FRAME_SIZE = 19; class DaikinClimate : public climate_ir::ClimateIR { public: DaikinClimate() - : climate_ir::ClimateIR( - DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true, - std::vector{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, - climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}, - std::vector{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, - climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {} + : climate_ir::ClimateIR(DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, + climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {} protected: // Transmit via IR the state of this climate controller. diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.h b/esphome/components/hitachi_ac344/hitachi_ac344.h index 3126ef0493..c34f033d92 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.h +++ b/esphome/components/hitachi_ac344/hitachi_ac344.h @@ -79,11 +79,10 @@ const uint16_t HITACHI_AC344_BITS = HITACHI_AC344_STATE_LENGTH * 8; class HitachiClimate : public climate_ir::ClimateIR { public: HitachiClimate() - : climate_ir::ClimateIR( - HITACHI_AC344_TEMP_MIN, HITACHI_AC344_TEMP_MAX, 1.0F, true, true, - std::vector{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, - climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}, - std::vector{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL}) {} + : climate_ir::ClimateIR(HITACHI_AC344_TEMP_MIN, HITACHI_AC344_TEMP_MAX, 1.0F, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL}) {} protected: uint8_t remote_state_[HITACHI_AC344_STATE_LENGTH]{0x01, 0x10, 0x00, 0x40, 0x00, 0xFF, 0x00, 0xCC, 0x00, 0x00, 0x00, diff --git a/esphome/components/midea_ac/midea_climate.cpp b/esphome/components/midea_ac/midea_climate.cpp index 001d5edb53..9fe5df7de3 100644 --- a/esphome/components/midea_ac/midea_climate.cpp +++ b/esphome/components/midea_ac/midea_climate.cpp @@ -167,24 +167,38 @@ climate::ClimateTraits MideaAC::traits() { traits.set_visual_min_temperature(17); traits.set_visual_max_temperature(30); traits.set_visual_temperature_step(0.5); - traits.set_supports_heat_cool_mode(true); - traits.set_supports_cool_mode(true); - traits.set_supports_dry_mode(true); - traits.set_supports_heat_mode(true); - traits.set_supports_fan_only_mode(true); - traits.set_supports_fan_mode_auto(true); - traits.set_supports_fan_mode_low(true); - traits.set_supports_fan_mode_medium(true); - traits.set_supports_fan_mode_high(true); + traits.set_supported_modes({ + climate::CLIMATE_MODE_OFF, + climate::CLIMATE_MODE_HEAT_COOL, + climate::CLIMATE_MODE_COOL, + climate::CLIMATE_MODE_DRY, + climate::CLIMATE_MODE_HEAT, + climate::CLIMATE_MODE_FAN_ONLY, + }); + traits.set_supported_fan_modes({ + climate::CLIMATE_FAN_AUTO, + climate::CLIMATE_FAN_LOW, + climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH, + }); traits.set_supported_custom_fan_modes(this->traits_custom_fan_modes_); - traits.set_supports_swing_mode_off(true); - traits.set_supports_swing_mode_vertical(true); - traits.set_supports_swing_mode_horizontal(this->traits_swing_horizontal_); - traits.set_supports_swing_mode_both(this->traits_swing_both_); - traits.set_supports_preset_home(true); - traits.set_supports_preset_eco(this->traits_preset_eco_); - traits.set_supports_preset_sleep(this->traits_preset_sleep_); - traits.set_supports_preset_boost(this->traits_preset_boost_); + traits.set_supported_swing_modes({ + climate::CLIMATE_SWING_OFF, + climate::CLIMATE_SWING_VERTICAL, + }); + if (traits_swing_horizontal_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_HORIZONTAL); + if (traits_swing_both_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH); + traits.set_supported_presets({ + climate::CLIMATE_PRESET_HOME, + }); + if (traits_preset_eco_) + traits.add_supported_preset(climate::CLIMATE_PRESET_ECO); + if (traits_preset_sleep_) + traits.add_supported_preset(climate::CLIMATE_PRESET_SLEEP); + if (traits_preset_boost_) + traits.add_supported_preset(climate::CLIMATE_PRESET_BOOST); traits.set_supported_custom_presets(this->traits_custom_presets_); traits.set_supports_current_temperature(true); return traits; diff --git a/esphome/components/midea_ac/midea_climate.h b/esphome/components/midea_ac/midea_climate.h index 1d21c4cab2..3cdf42f52e 100644 --- a/esphome/components/midea_ac/midea_climate.h +++ b/esphome/components/midea_ac/midea_climate.h @@ -28,10 +28,10 @@ class MideaAC : public midea_dongle::MideaAppliance, public climate::Climate, pu void set_preset_sleep(bool state) { this->traits_preset_sleep_ = state; } void set_preset_boost(bool state) { this->traits_preset_boost_ = state; } bool allow_preset(climate::ClimatePreset preset) const; - void set_custom_fan_modes(std::vector custom_fan_modes) { + void set_custom_fan_modes(std::set custom_fan_modes) { this->traits_custom_fan_modes_ = std::move(custom_fan_modes); } - void set_custom_presets(std::vector custom_presets) { + void set_custom_presets(std::set custom_presets) { this->traits_custom_presets_ = std::move(custom_presets); } bool allow_custom_preset(const std::string &custom_preset) const; @@ -57,8 +57,8 @@ class MideaAC : public midea_dongle::MideaAppliance, public climate::Climate, pu bool traits_preset_eco_{false}; bool traits_preset_sleep_{false}; bool traits_preset_boost_{false}; - std::vector traits_custom_fan_modes_{{}}; - std::vector traits_custom_presets_{{}}; + std::set traits_custom_fan_modes_{{}}; + std::set traits_custom_presets_{{}}; }; } // namespace midea_ac diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 164ba16faf..0934922bc6 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -61,7 +61,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC // temp_step root["temp_step"] = traits.get_visual_temperature_step(); - if (traits.get_supports_away()) { + if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { // away_mode_command_topic root["away_mode_cmd_t"] = this->get_away_command_topic(); // away_mode_state_topic @@ -164,19 +164,19 @@ void MQTTClimateComponent::setup() { }); } - if (traits.get_supports_away()) { + if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { this->subscribe(this->get_away_command_topic(), [this](const std::string &topic, const std::string &payload) { auto onoff = parse_on_off(payload.c_str()); auto call = this->device_->make_call(); switch (onoff) { case PARSE_ON: - call.set_away(true); + call.set_preset(CLIMATE_PRESET_AWAY); break; case PARSE_OFF: - call.set_away(false); + call.set_preset(CLIMATE_PRESET_HOME); break; case PARSE_TOGGLE: - call.set_away(!this->device_->away); + call.set_preset(this->device_->preset == CLIMATE_PRESET_AWAY ? CLIMATE_PRESET_HOME : CLIMATE_PRESET_AWAY); break; case PARSE_NONE: default: @@ -259,8 +259,8 @@ bool MQTTClimateComponent::publish_state_() { success = false; } - if (traits.get_supports_away()) { - std::string payload = ONOFF(this->device_->away); + if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { + std::string payload = ONOFF(this->device_->preset == CLIMATE_PRESET_AWAY); if (!this->publish(this->get_away_state_topic(), payload)) success = false; } diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index 1601f1a0fe..8a61361fb8 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -39,10 +39,14 @@ void PIDClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits PIDClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_heat_cool_mode(true); traits.set_supports_two_point_target_temperature(false); - traits.set_supports_cool_mode(this->supports_cool_()); - traits.set_supports_heat_mode(this->supports_heat_()); + + traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL}); + if (supports_cool_()) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_heat_()) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + traits.set_supports_action(true); return traits; } diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 3fdb4efa7b..305db66f16 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -50,12 +50,13 @@ void ThermostatClimate::control(const climate::ClimateCall &call) { this->target_temperature_low = *call.get_target_temperature_low(); if (call.get_target_temperature_high().has_value()) this->target_temperature_high = *call.get_target_temperature_high(); - if (call.get_away().has_value()) { + if (call.get_preset().has_value()) { // setup_complete_ blocks modifying/resetting the temps immediately after boot if (this->setup_complete_) { - this->change_away_(*call.get_away()); + this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY); } else { - this->away = *call.get_away(); + this->preset = *call.get_preset(); + ; } } // set point validation @@ -78,27 +79,51 @@ void ThermostatClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits ThermostatClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_auto_mode(this->supports_auto_); - traits.set_supports_heat_cool_mode(this->supports_heat_cool_); - traits.set_supports_cool_mode(this->supports_cool_); - traits.set_supports_dry_mode(this->supports_dry_); - traits.set_supports_fan_only_mode(this->supports_fan_only_); - traits.set_supports_heat_mode(this->supports_heat_); - traits.set_supports_fan_mode_on(this->supports_fan_mode_on_); - traits.set_supports_fan_mode_off(this->supports_fan_mode_off_); - traits.set_supports_fan_mode_auto(this->supports_fan_mode_auto_); - traits.set_supports_fan_mode_low(this->supports_fan_mode_low_); - traits.set_supports_fan_mode_medium(this->supports_fan_mode_medium_); - traits.set_supports_fan_mode_high(this->supports_fan_mode_high_); - traits.set_supports_fan_mode_middle(this->supports_fan_mode_middle_); - traits.set_supports_fan_mode_focus(this->supports_fan_mode_focus_); - traits.set_supports_fan_mode_diffuse(this->supports_fan_mode_diffuse_); - traits.set_supports_swing_mode_both(this->supports_swing_mode_both_); - traits.set_supports_swing_mode_horizontal(this->supports_swing_mode_horizontal_); - traits.set_supports_swing_mode_off(this->supports_swing_mode_off_); - traits.set_supports_swing_mode_vertical(this->supports_swing_mode_vertical_); + if (supports_auto_) + traits.add_supported_mode(climate::CLIMATE_MODE_AUTO); + if (supports_heat_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_dry_) + traits.add_supported_mode(climate::CLIMATE_MODE_DRY); + if (supports_fan_only_) + traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + + if (supports_fan_mode_on_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_ON); + if (supports_fan_mode_off_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_OFF); + if (supports_fan_mode_auto_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_AUTO); + if (supports_fan_mode_low_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_LOW); + if (supports_fan_mode_medium_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_MEDIUM); + if (supports_fan_mode_high_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_HIGH); + if (supports_fan_mode_middle_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_MIDDLE); + if (supports_fan_mode_focus_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_FOCUS); + if (supports_fan_mode_diffuse_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_DIFFUSE); + + if (supports_swing_mode_both_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH); + if (supports_swing_mode_horizontal_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_HORIZONTAL); + if (supports_swing_mode_off_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_OFF); + if (supports_swing_mode_vertical_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL); + + if (supports_away_) + traits.set_supported_presets({climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_AWAY}); + traits.set_supports_two_point_target_temperature(this->supports_two_points_); - traits.set_supports_away(this->supports_away_); traits.set_supports_action(true); return traits; } @@ -399,7 +424,7 @@ void ThermostatClimate::change_away_(bool away) { } else this->target_temperature = this->away_config_.default_temperature; } - this->away = away; + this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME; } void ThermostatClimate::set_normal_config(const ThermostatClimateTargetTempConfig &normal_config) { this->normal_config_ = normal_config; diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index 2b6ce833de..5f214d3bfe 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -68,8 +68,10 @@ void TuyaClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits TuyaClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->current_temperature_id_.has_value()); - traits.set_supports_heat_mode(this->supports_heat_); - traits.set_supports_cool_mode(this->supports_cool_); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); traits.set_supports_action(true); return traits; } diff --git a/esphome/components/yashima/yashima.cpp b/esphome/components/yashima/yashima.cpp index bda27b62fb..8d588127b0 100644 --- a/esphome/components/yashima/yashima.cpp +++ b/esphome/components/yashima/yashima.cpp @@ -82,11 +82,14 @@ const uint32_t YASHIMA_CARRIER_FREQUENCY = 38000; climate::ClimateTraits YashimaClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->sensor_ != nullptr); - traits.set_supports_heat_cool_mode(true); - traits.set_supports_cool_mode(this->supports_cool_); - traits.set_supports_heat_mode(this->supports_heat_); + + traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL}); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + traits.set_supports_two_point_target_temperature(false); - traits.set_supports_away(false); traits.set_visual_min_temperature(YASHIMA_TEMP_MIN); traits.set_visual_max_temperature(YASHIMA_TEMP_MAX); traits.set_visual_temperature_step(1); diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py old mode 100644 new mode 100755 index 7f9067cd22..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. From c811141a4f6238333ac42031d6d8fa80c946eb7c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 21 Jun 2021 22:02:18 +0200 Subject: [PATCH 0954/1841] API raise minor version for climate changes (#1947) --- esphome/components/api/api_connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 89c0dde24a..00a8539112 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -615,7 +615,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { HelloResponse resp; resp.api_version_major = 1; - resp.api_version_minor = 4; + resp.api_version_minor = 5; resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; this->connection_state_ = ConnectionState::CONNECTED; return resp; From 027e0de48ed356b3ce32654697e6a166aca73668 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 21 Jun 2021 21:17:46 -0700 Subject: [PATCH 0955/1841] Bump dashboard to 20210621.0 (#1946) --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f02d65e518..0711451955 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,5 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210617.1 +esphome-dashboard==20210621.0 + From 3dfff2930a28cc061da8f6c852b46dc931675c96 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Tue, 22 Jun 2021 10:07:14 +0200 Subject: [PATCH 0956/1841] Improve DHT read timings (#1901) Make sure that the initial rising edge is properly detected even if timing is somewhat off. Set MCU start signal to 1ms for AM2302. --- esphome/components/dht/dht.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index fd51a976b7..4a5c418e0a 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -94,11 +94,17 @@ bool HOT ICACHE_RAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, delayMicroseconds(40); } else if (this->model_ == DHT_MODEL_DHT22_TYPE2) { delayMicroseconds(2000); + } else if (this->model_ == DHT_MODEL_AM2302) { + delayMicroseconds(1000); } else { delayMicroseconds(800); } this->pin_->pin_mode(INPUT_PULLUP); - delayMicroseconds(40); + + // Host pull up 20-40us then DHT response 80us + // Start waiting for initial rising edge at the center when we + // expect the DHT response (30us+40us) + delayMicroseconds(70); uint8_t bit = 7; uint8_t byte = 0; @@ -116,8 +122,6 @@ bool HOT ICACHE_RAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, break; } } - if (error_code != 0) - break; start_time = micros(); uint32_t end_time = start_time; @@ -132,8 +136,6 @@ bool HOT ICACHE_RAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, break; } } - if (error_code != 0) - break; if (i < 0) continue; From bfca3f242a4bc5535899f23074bfd0e8770d22aa Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 22 Jun 2021 10:53:10 +0200 Subject: [PATCH 0957/1841] Disallow power_save_mode NONE if used together with BLE (#1950) --- esphome/components/http_request/__init__.py | 19 ++++------- esphome/components/wifi/__init__.py | 36 ++++++++++++++++++++- esphome/final_validate.py | 24 ++++++++++++++ tests/test1.yaml | 2 +- 4 files changed, 66 insertions(+), 15 deletions(-) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index f4475060df..7dffdae27f 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -7,10 +7,7 @@ from esphome import automation from esphome.const import ( CONF_ID, CONF_TIMEOUT, - CONF_ESPHOME, CONF_METHOD, - CONF_ARDUINO_VERSION, - ARDUINO_VERSION_ESP8266, CONF_TRIGGER_ID, CONF_URL, ) @@ -78,23 +75,19 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -def validate_framework(config): - if CORE.is_esp32: +def validate_framework(value): + if not CORE.is_esp8266: + # only for ESP8266 return - # only for ESP8266 - path = [CONF_ESPHOME, CONF_ARDUINO_VERSION] - version: str = fv.full_config.get().get_config_for_path(path) - - reverse_map = {v: k for k, v in ARDUINO_VERSION_ESP8266.items()} - framework_version = reverse_map.get(version) + framework_version = fv.get_arduino_framework_version() if framework_version is None or framework_version == "dev": return if framework_version < "2.5.1": raise cv.Invalid( - "This component is not supported on arduino framework version below 2.5.1", - path=[cv.ROOT_CONFIG_PATH] + path, + "This component is not supported on arduino framework version below 2.5.1, ", + "please check esphome->arduino_version", ) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 54307388d6..7a1a01bcc4 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -148,7 +148,41 @@ def final_validate(config): ) -FINAL_VALIDATE_SCHEMA = cv.Schema(final_validate) +def final_validate_power_esp32_ble(value): + if not CORE.is_esp32: + return + if value != "NONE": + # WiFi should be in modem sleep (!=NONE) with BLE coexistence + # https://docs.espressif.com/projects/esp-idf/en/v3.3.5/api-guides/wifi.html#station-sleep + return + framework_version = fv.get_arduino_framework_version() + if framework_version not in (None, "dev") and framework_version < "1.0.5": + # Only frameworks 1.0.5+ impacted + return + full = fv.full_config.get() + for conflicting in [ + "esp32_ble", + "esp32_ble_beacon", + "esp32_ble_server", + "esp32_ble_tracker", + ]: + if conflicting in full: + raise cv.Invalid( + f"power_save_mode NONE is incompatible with {conflicting}. " + f"Please remove the power save mode. See also " + f"https://github.com/esphome/issues/issues/2141#issuecomment-865688582" + ) + + +FINAL_VALIDATE_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_POWER_SAVE_MODE): final_validate_power_esp32_ble, + }, + extra=cv.ALLOW_EXTRA, + ), + final_validate, +) def _validate(config): diff --git a/esphome/final_validate.py b/esphome/final_validate.py index 50fdbaf3f4..47071b5391 100644 --- a/esphome/final_validate.py +++ b/esphome/final_validate.py @@ -4,6 +4,13 @@ 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): @@ -55,3 +62,20 @@ def id_declaration_match_schema(schema): 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/tests/test1.yaml b/tests/test1.yaml index 29df5857d3..08e1d63534 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -80,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 From 32f2da77f8431e7e13ab3e44cc62634a225a13c6 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Tue, 22 Jun 2021 16:37:05 +0200 Subject: [PATCH 0958/1841] More VSCode devcontainer improvements (#1934) --- .devcontainer/devcontainer.json | 2 +- .dockerignore | 4 ++++ .vscode/tasks.json | 35 ++++++++++++++++++++++++++------- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0e6fe19e0e..3904962d7c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -32,7 +32,7 @@ "editor.formatOnSave": true, "editor.formatOnType": true, "files.trimTrailingWhitespace": true, - "terminal.integrated.defaultProfile.linux": "/bin/bash", + "terminal.integrated.defaultProfile.linux": "bash", "yaml.customTags": [ "!secret scalar", "!lambda scalar", 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/.vscode/tasks.json b/.vscode/tasks.json index 513228722a..8c55646f28 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,11 +1,32 @@ { - "version": "2.0.0", - "tasks": [ + "version": "2.0.0", + "tasks": [ + { + "label": "run", + "type": "shell", + "command": "python3 -m esphome dashboard config/", + "problemMatcher": [] + }, + { + "label": "clang-tidy", + "type": "shell", + "command": "test -f .gcc-flags.json || pio init --silent --ide atom; ./script/clang-tidy", + "problemMatcher": [ { - "label": "run", - "type": "shell", - "command": "python3 -m esphome dashboard config/", - "problemMatcher": [] + "owner": "clang-tidy", + "fileLocation": "absolute", + "pattern": [ + { + "regexp": "^(.*):(\\d+):(\\d+):\\s+(error):\\s+(.*) \\[([a-z0-9,\\-]+)\\]\\s*$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + ] } - ] + ] + } + ] } From 61ebc629f6bbd26e6f63253cb2f496e95834a7c4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 23 Jun 2021 17:15:11 +1200 Subject: [PATCH 0959/1841] Bump esphome-dashboard to 20210622.0 (#1955) --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0711451955..12500d57b7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,5 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210621.0 - +esphome-dashboard==20210622.0 From 89dfa5ea82d1cbb61cb80385e1c307153f50aea4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 23 Jun 2021 17:15:11 +1200 Subject: [PATCH 0960/1841] Bump esphome-dashboard to 20210622.0 (#1955) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f02d65e518..12500d57b7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210617.1 +esphome-dashboard==20210622.0 From 150114d774b36f6c7af56b187aa8b2a8624a5297 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 23 Jun 2021 19:39:37 +1200 Subject: [PATCH 0961/1841] Bump version to v1.19.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 35959caaff..de33ebc2de 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "2" +PATCH_VERSION = "3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From d0859a7d33accdad7fbc54dbcee50be2c7fd2a04 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 23 Jun 2021 20:25:19 +0200 Subject: [PATCH 0962/1841] Add climate preset NONE again (#1951) --- esphome/components/api/api.proto | 15 ++++++----- esphome/components/api/api_pb2.cpp | 2 ++ esphome/components/api/api_pb2.h | 15 ++++++----- esphome/components/climate/climate_mode.cpp | 2 ++ esphome/components/climate/climate_mode.h | 30 +++++++++++---------- 5 files changed, 36 insertions(+), 28 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 87a7cf4749..a5bd9aec6d 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -710,13 +710,14 @@ enum ClimateAction { CLIMATE_ACTION_FAN = 6; } enum ClimatePreset { - CLIMATE_PRESET_HOME = 0; - CLIMATE_PRESET_AWAY = 1; - CLIMATE_PRESET_BOOST = 2; - CLIMATE_PRESET_COMFORT = 3; - CLIMATE_PRESET_ECO = 4; - CLIMATE_PRESET_SLEEP = 5; - CLIMATE_PRESET_ACTIVITY = 6; + CLIMATE_PRESET_NONE = 0; + CLIMATE_PRESET_HOME = 1; + CLIMATE_PRESET_AWAY = 2; + CLIMATE_PRESET_BOOST = 3; + CLIMATE_PRESET_COMFORT = 4; + CLIMATE_PRESET_ECO = 5; + CLIMATE_PRESET_SLEEP = 6; + CLIMATE_PRESET_ACTIVITY = 7; } message ListEntitiesClimateResponse { option (id) = 46; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 1e023f3988..e53ac2019a 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -192,6 +192,8 @@ template<> const char *proto_enum_to_string(enums::Climate } template<> const char *proto_enum_to_string(enums::ClimatePreset value) { switch (value) { + case enums::CLIMATE_PRESET_NONE: + return "CLIMATE_PRESET_NONE"; case enums::CLIMATE_PRESET_HOME: return "CLIMATE_PRESET_HOME"; case enums::CLIMATE_PRESET_AWAY: diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 7f37c0b94b..956cecdeb9 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -90,13 +90,14 @@ enum ClimateAction : uint32_t { CLIMATE_ACTION_FAN = 6, }; enum ClimatePreset : uint32_t { - CLIMATE_PRESET_HOME = 0, - CLIMATE_PRESET_AWAY = 1, - CLIMATE_PRESET_BOOST = 2, - CLIMATE_PRESET_COMFORT = 3, - CLIMATE_PRESET_ECO = 4, - CLIMATE_PRESET_SLEEP = 5, - CLIMATE_PRESET_ACTIVITY = 6, + CLIMATE_PRESET_NONE = 0, + CLIMATE_PRESET_HOME = 1, + CLIMATE_PRESET_AWAY = 2, + CLIMATE_PRESET_BOOST = 3, + CLIMATE_PRESET_COMFORT = 4, + CLIMATE_PRESET_ECO = 5, + CLIMATE_PRESET_SLEEP = 6, + CLIMATE_PRESET_ACTIVITY = 7, }; } // namespace enums diff --git a/esphome/components/climate/climate_mode.cpp b/esphome/components/climate/climate_mode.cpp index 099074a887..7a626942eb 100644 --- a/esphome/components/climate/climate_mode.cpp +++ b/esphome/components/climate/climate_mode.cpp @@ -84,6 +84,8 @@ const char *climate_swing_mode_to_string(ClimateSwingMode swing_mode) { const char *climate_preset_to_string(ClimatePreset preset) { switch (preset) { + case climate::CLIMATE_PRESET_NONE: + return "NONE"; case climate::CLIMATE_PRESET_HOME: return "HOME"; case climate::CLIMATE_PRESET_ECO: diff --git a/esphome/components/climate/climate_mode.h b/esphome/components/climate/climate_mode.h index 7afa2dae55..07fbf32b26 100644 --- a/esphome/components/climate/climate_mode.h +++ b/esphome/components/climate/climate_mode.h @@ -74,20 +74,22 @@ enum ClimateSwingMode : uint8_t { /// Enum for all modes a climate swing can be in enum ClimatePreset : uint8_t { - /// Preset is set to HOME - CLIMATE_PRESET_HOME = 0, - /// Preset is set to AWAY - CLIMATE_PRESET_AWAY = 1, - /// Preset is set to BOOST - CLIMATE_PRESET_BOOST = 2, - /// Preset is set to COMFORT - CLIMATE_PRESET_COMFORT = 3, - /// Preset is set to ECO - CLIMATE_PRESET_ECO = 4, - /// Preset is set to SLEEP - CLIMATE_PRESET_SLEEP = 5, - /// Preset is set to ACTIVITY - CLIMATE_PRESET_ACTIVITY = 6, + /// No preset is active + CLIMATE_PRESET_NONE = 0, + /// Device is in home preset + CLIMATE_PRESET_HOME = 1, + /// Device is in away preset + CLIMATE_PRESET_AWAY = 2, + /// Device is in boost preset + CLIMATE_PRESET_BOOST = 3, + /// Device is in comfort preset + CLIMATE_PRESET_COMFORT = 4, + /// Device is running an energy-saving preset + CLIMATE_PRESET_ECO = 5, + /// Device is prepared for sleep + CLIMATE_PRESET_SLEEP = 6, + /// Device is reacting to activity (e.g., movement sensors) + CLIMATE_PRESET_ACTIVITY = 7, }; /// Convert the given ClimateMode to a human-readable string. From 2cb3015a2814be21ee8f537793d89eb40bb186f5 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 23 Jun 2021 20:27:08 +0200 Subject: [PATCH 0963/1841] Compat argv parsing improvements (#1952) --- esphome/__main__.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 48f8bea083..232652db9f 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -514,14 +514,26 @@ def parse_args(argv): compat_parser.error = _raise - try: - result, unparsed = compat_parser.parse_known_args(argv[1:]) - last_option = len(argv) - len(unparsed) - 1 - len(result.configuration) - argv = argv[0:last_option] + [result.command] + result.configuration + unparsed - deprecated_argv_suggestion = argv - except argparse.ArgumentError: - # This is not an old-style command line, so we don't have to do anything. - deprecated_argv_suggestion = None + deprecated_argv_suggestion = None + + if ["dashboard", "config"] == argv[1:3]: + # this is most likely meant in new-style arg format. do not try compat parsing + pass + else: + try: + result, unparsed = compat_parser.parse_known_args(argv[1:]) + last_option = len(argv) - len(unparsed) - 1 - len(result.configuration) + unparsed = [ + "--device" if arg in ("--upload-port", "--serial-port") else arg + for arg in unparsed + ] + argv = ( + argv[0:last_option] + [result.command] + result.configuration + unparsed + ) + deprecated_argv_suggestion = argv + except argparse.ArgumentError: + # This is not an old-style command line, so we don't have to do anything. + pass # And continue on with regular parsing parser = argparse.ArgumentParser( From 7051f897bc86ebea7cfb8eb6c401847b9c6bcbc6 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 24 Jun 2021 00:07:27 +0200 Subject: [PATCH 0964/1841] Validate color temperature values for RGBWW/CWWW lights (#1957) Check if the color temperature of the cold white channel is colder (less) than the warm white channel. --- esphome/components/cwww/light.py | 21 +++++++++++--------- esphome/components/light/__init__.py | 14 ++++++++++++++ esphome/components/rgbww/light.py | 29 +++++++++++++++------------- 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/esphome/components/cwww/light.py b/esphome/components/cwww/light.py index 674c48d219..1a027a86b4 100644 --- a/esphome/components/cwww/light.py +++ b/esphome/components/cwww/light.py @@ -14,15 +14,18 @@ CWWWLightOutput = cwww_ns.class_("CWWWLightOutput", light.LightOutput) CONF_CONSTANT_BRIGHTNESS = "constant_brightness" -CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend( - { - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(CWWWLightOutput), - cv.Required(CONF_COLD_WHITE): cv.use_id(output.FloatOutput), - cv.Required(CONF_WARM_WHITE): cv.use_id(output.FloatOutput), - cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, - cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, - cv.Optional(CONF_CONSTANT_BRIGHTNESS, default=False): cv.boolean, - } +CONFIG_SCHEMA = cv.All( + light.RGB_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(CWWWLightOutput), + cv.Required(CONF_COLD_WHITE): cv.use_id(output.FloatOutput), + cv.Required(CONF_WARM_WHITE): cv.use_id(output.FloatOutput), + cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, + cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, + cv.Optional(CONF_CONSTANT_BRIGHTNESS, default=False): cv.boolean, + } + ), + light.validate_color_temperature_channels, ) diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index 276bf8073d..119ff3703c 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -16,6 +16,8 @@ from esphome.const import ( CONF_ON_TURN_OFF, CONF_ON_TURN_ON, CONF_TRIGGER_ID, + CONF_COLD_WHITE_COLOR_TEMPERATURE, + CONF_WARM_WHITE_COLOR_TEMPERATURE, ) from esphome.core import coroutine_with_priority from .automation import light_control_to_code # noqa @@ -104,6 +106,18 @@ ADDRESSABLE_LIGHT_SCHEMA = RGB_LIGHT_SCHEMA.extend( ) +def validate_color_temperature_channels(value): + if ( + value[CONF_COLD_WHITE_COLOR_TEMPERATURE] + >= value[CONF_WARM_WHITE_COLOR_TEMPERATURE] + ): + raise cv.Invalid( + "Color temperature of the cold white channel must be colder than that of the warm white channel.", + path=[CONF_COLD_WHITE_COLOR_TEMPERATURE], + ) + return value + + async def setup_light_core_(light_var, output_var, config): cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE])) if CONF_INTERNAL in config: diff --git a/esphome/components/rgbww/light.py b/esphome/components/rgbww/light.py index d152fbc6db..41cba1f7a1 100644 --- a/esphome/components/rgbww/light.py +++ b/esphome/components/rgbww/light.py @@ -18,19 +18,22 @@ RGBWWLightOutput = rgbww_ns.class_("RGBWWLightOutput", light.LightOutput) CONF_CONSTANT_BRIGHTNESS = "constant_brightness" CONF_COLOR_INTERLOCK = "color_interlock" -CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend( - { - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(RGBWWLightOutput), - cv.Required(CONF_RED): cv.use_id(output.FloatOutput), - cv.Required(CONF_GREEN): cv.use_id(output.FloatOutput), - cv.Required(CONF_BLUE): cv.use_id(output.FloatOutput), - cv.Required(CONF_COLD_WHITE): cv.use_id(output.FloatOutput), - cv.Required(CONF_WARM_WHITE): cv.use_id(output.FloatOutput), - cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, - cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, - cv.Optional(CONF_CONSTANT_BRIGHTNESS, default=False): cv.boolean, - cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean, - } +CONFIG_SCHEMA = cv.All( + light.RGB_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(RGBWWLightOutput), + cv.Required(CONF_RED): cv.use_id(output.FloatOutput), + cv.Required(CONF_GREEN): cv.use_id(output.FloatOutput), + cv.Required(CONF_BLUE): cv.use_id(output.FloatOutput), + cv.Required(CONF_COLD_WHITE): cv.use_id(output.FloatOutput), + cv.Required(CONF_WARM_WHITE): cv.use_id(output.FloatOutput), + cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, + cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, + cv.Optional(CONF_CONSTANT_BRIGHTNESS, default=False): cv.boolean, + cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean, + } + ), + light.validate_color_temperature_channels, ) From 5fca4809216554321222c668f85b11b091ee3802 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 24 Jun 2021 12:38:59 +1200 Subject: [PATCH 0965/1841] Bump dashboard to 20210623.0 (#1958) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 12500d57b7..cc9059f0d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210622.0 +esphome-dashboard==20210623.0 From d4eb0f16550db1cda3573cee2ff76eda9aaa4d36 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 21 Jun 2021 21:17:01 +0200 Subject: [PATCH 0966/1841] Rework climate traits (#1941) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/api/api.proto | 16 +- esphome/components/api/api_connection.cpp | 52 ++--- esphome/components/api/api_pb2.cpp | 40 ++-- esphome/components/api/api_pb2.h | 12 +- .../bang_bang/bang_bang_climate.cpp | 23 +- esphome/components/climate/automation.h | 4 +- esphome/components/climate/climate.cpp | 74 +++--- esphome/components/climate/climate.h | 8 +- esphome/components/climate/climate_mode.cpp | 4 +- esphome/components/climate/climate_mode.h | 9 +- esphome/components/climate/climate_traits.cpp | 203 ---------------- esphome/components/climate/climate_traits.h | 219 +++++++++++------- esphome/components/climate_ir/climate_ir.cpp | 65 +----- esphome/components/climate_ir/climate_ir.h | 9 +- esphome/components/daikin/daikin.h | 11 +- .../components/hitachi_ac344/hitachi_ac344.h | 9 +- esphome/components/midea_ac/midea_climate.cpp | 48 ++-- esphome/components/midea_ac/midea_climate.h | 18 +- esphome/components/mqtt/mqtt_climate.cpp | 14 +- esphome/components/pid/pid_climate.cpp | 10 +- .../thermostat/thermostat_climate.cpp | 73 ++++-- .../components/tuya/climate/tuya_climate.cpp | 6 +- esphome/components/yashima/yashima.cpp | 11 +- script/api_protobuf/api_protobuf.py | 1 + 24 files changed, 393 insertions(+), 546 deletions(-) mode change 100644 => 100755 script/api_protobuf/api_protobuf.py diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index bdb94b3d9b..87a7cf4749 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -710,11 +710,11 @@ enum ClimateAction { CLIMATE_ACTION_FAN = 6; } enum ClimatePreset { - CLIMATE_PRESET_ECO = 0; + CLIMATE_PRESET_HOME = 0; CLIMATE_PRESET_AWAY = 1; CLIMATE_PRESET_BOOST = 2; CLIMATE_PRESET_COMFORT = 3; - CLIMATE_PRESET_HOME = 4; + CLIMATE_PRESET_ECO = 4; CLIMATE_PRESET_SLEEP = 5; CLIMATE_PRESET_ACTIVITY = 6; } @@ -734,7 +734,9 @@ message ListEntitiesClimateResponse { float visual_min_temperature = 8; float visual_max_temperature = 9; float visual_temperature_step = 10; - bool supports_away = 11; + // for older peer versions - in new system this + // is if CLIMATE_PRESET_AWAY exists is supported_presets + bool legacy_supports_away = 11; bool supports_action = 12; repeated ClimateFanMode supported_fan_modes = 13; repeated ClimateSwingMode supported_swing_modes = 14; @@ -754,7 +756,8 @@ message ClimateStateResponse { float target_temperature = 4; float target_temperature_low = 5; float target_temperature_high = 6; - bool away = 7; + // For older peers, equal to preset == CLIMATE_PRESET_AWAY + bool legacy_away = 7; ClimateAction action = 8; ClimateFanMode fan_mode = 9; ClimateSwingMode swing_mode = 10; @@ -777,8 +780,9 @@ message ClimateCommandRequest { float target_temperature_low = 7; bool has_target_temperature_high = 8; float target_temperature_high = 9; - bool has_away = 10; - bool away = 11; + // legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset + bool has_legacy_away = 10; + bool legacy_away = 11; bool has_fan_mode = 12; ClimateFanMode fan_mode = 13; bool has_swing_mode = 14; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 175c3c4487..68187f259e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -475,14 +475,14 @@ bool APIConnection::send_climate_state(climate::Climate *climate) { } else { resp.target_temperature = climate->target_temperature; } - if (traits.get_supports_away()) - resp.away = climate->away; if (traits.get_supports_fan_modes() && climate->fan_mode.has_value()) resp.fan_mode = static_cast(climate->fan_mode.value()); if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value()) resp.custom_fan_mode = climate->custom_fan_mode.value(); - if (traits.get_supports_presets() && climate->preset.has_value()) + if (traits.get_supports_presets() && climate->preset.has_value()) { resp.preset = static_cast(climate->preset.value()); + resp.legacy_away = resp.preset == enums::CLIMATE_PRESET_AWAY; + } if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) resp.custom_preset = climate->custom_preset.value(); if (traits.get_supports_swing_modes()) @@ -498,40 +498,26 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { msg.unique_id = get_default_unique_id("climate", climate); msg.supports_current_temperature = traits.get_supports_current_temperature(); msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); - for (auto mode : - {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT, - climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_HEAT_COOL}) { - if (traits.supports_mode(mode)) - msg.supported_modes.push_back(static_cast(mode)); - } + + for (auto mode : traits.get_supported_modes()) + msg.supported_modes.push_back(static_cast(mode)); + msg.visual_min_temperature = traits.get_visual_min_temperature(); msg.visual_max_temperature = traits.get_visual_max_temperature(); msg.visual_temperature_step = traits.get_visual_temperature_step(); - msg.supports_away = traits.get_supports_away(); + msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY); msg.supports_action = traits.get_supports_action(); - for (auto fan_mode : {climate::CLIMATE_FAN_ON, climate::CLIMATE_FAN_OFF, climate::CLIMATE_FAN_AUTO, - climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH, - climate::CLIMATE_FAN_MIDDLE, climate::CLIMATE_FAN_FOCUS, climate::CLIMATE_FAN_DIFFUSE}) { - if (traits.supports_fan_mode(fan_mode)) - msg.supported_fan_modes.push_back(static_cast(fan_mode)); - } - for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) { + + for (auto fan_mode : traits.get_supported_fan_modes()) + msg.supported_fan_modes.push_back(static_cast(fan_mode)); + for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) msg.supported_custom_fan_modes.push_back(custom_fan_mode); - } - for (auto preset : {climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_AWAY, climate::CLIMATE_PRESET_BOOST, - climate::CLIMATE_PRESET_COMFORT, climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_SLEEP, - climate::CLIMATE_PRESET_ACTIVITY}) { - if (traits.supports_preset(preset)) - msg.supported_presets.push_back(static_cast(preset)); - } - for (auto const &custom_preset : traits.get_supported_custom_presets()) { + for (auto preset : traits.get_supported_presets()) + msg.supported_presets.push_back(static_cast(preset)); + for (auto const &custom_preset : traits.get_supported_custom_presets()) msg.supported_custom_presets.push_back(custom_preset); - } - for (auto swing_mode : {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL, - climate::CLIMATE_SWING_HORIZONTAL}) { - if (traits.supports_swing_mode(swing_mode)) - msg.supported_swing_modes.push_back(static_cast(swing_mode)); - } + for (auto swing_mode : traits.get_supported_swing_modes()) + msg.supported_swing_modes.push_back(static_cast(swing_mode)); return this->send_list_entities_climate_response(msg); } void APIConnection::climate_command(const ClimateCommandRequest &msg) { @@ -548,8 +534,8 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { call.set_target_temperature_low(msg.target_temperature_low); if (msg.has_target_temperature_high) call.set_target_temperature_high(msg.target_temperature_high); - if (msg.has_away) - call.set_away(msg.away); + if (msg.has_legacy_away) + call.set_preset(msg.legacy_away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME); if (msg.has_fan_mode) call.set_fan_mode(static_cast(msg.fan_mode)); if (msg.has_custom_fan_mode) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index a9e9d64bc1..1e023f3988 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -192,16 +192,16 @@ template<> const char *proto_enum_to_string(enums::Climate } template<> const char *proto_enum_to_string(enums::ClimatePreset value) { switch (value) { - case enums::CLIMATE_PRESET_ECO: - return "CLIMATE_PRESET_ECO"; + case enums::CLIMATE_PRESET_HOME: + return "CLIMATE_PRESET_HOME"; case enums::CLIMATE_PRESET_AWAY: return "CLIMATE_PRESET_AWAY"; case enums::CLIMATE_PRESET_BOOST: return "CLIMATE_PRESET_BOOST"; case enums::CLIMATE_PRESET_COMFORT: return "CLIMATE_PRESET_COMFORT"; - case enums::CLIMATE_PRESET_HOME: - return "CLIMATE_PRESET_HOME"; + case enums::CLIMATE_PRESET_ECO: + return "CLIMATE_PRESET_ECO"; case enums::CLIMATE_PRESET_SLEEP: return "CLIMATE_PRESET_SLEEP"; case enums::CLIMATE_PRESET_ACTIVITY: @@ -2672,7 +2672,7 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v return true; } case 11: { - this->supports_away = value.as_bool(); + this->legacy_supports_away = value.as_bool(); return true; } case 12: { @@ -2756,7 +2756,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(8, this->visual_min_temperature); buffer.encode_float(9, this->visual_max_temperature); buffer.encode_float(10, this->visual_temperature_step); - buffer.encode_bool(11, this->supports_away); + buffer.encode_bool(11, this->legacy_supports_away); buffer.encode_bool(12, this->supports_action); for (auto &it : this->supported_fan_modes) { buffer.encode_enum(13, it, true); @@ -2823,8 +2823,8 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" supports_away: "); - out.append(YESNO(this->supports_away)); + out.append(" legacy_supports_away: "); + out.append(YESNO(this->legacy_supports_away)); out.append("\n"); out.append(" supports_action: "); @@ -2869,7 +2869,7 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { return true; } case 7: { - this->away = value.as_bool(); + this->legacy_away = value.as_bool(); return true; } case 8: { @@ -2939,7 +2939,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(4, this->target_temperature); buffer.encode_float(5, this->target_temperature_low); buffer.encode_float(6, this->target_temperature_high); - buffer.encode_bool(7, this->away); + buffer.encode_bool(7, this->legacy_away); buffer.encode_enum(8, this->action); buffer.encode_enum(9, this->fan_mode); buffer.encode_enum(10, this->swing_mode); @@ -2979,8 +2979,8 @@ void ClimateStateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" away: "); - out.append(YESNO(this->away)); + out.append(" legacy_away: "); + out.append(YESNO(this->legacy_away)); out.append("\n"); out.append(" action: "); @@ -3031,11 +3031,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) return true; } case 10: { - this->has_away = value.as_bool(); + this->has_legacy_away = value.as_bool(); return true; } case 11: { - this->away = value.as_bool(); + this->legacy_away = value.as_bool(); return true; } case 12: { @@ -3120,8 +3120,8 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(7, this->target_temperature_low); buffer.encode_bool(8, this->has_target_temperature_high); buffer.encode_float(9, this->target_temperature_high); - buffer.encode_bool(10, this->has_away); - buffer.encode_bool(11, this->away); + buffer.encode_bool(10, this->has_legacy_away); + buffer.encode_bool(11, this->legacy_away); buffer.encode_bool(12, this->has_fan_mode); buffer.encode_enum(13, this->fan_mode); buffer.encode_bool(14, this->has_swing_mode); @@ -3176,12 +3176,12 @@ void ClimateCommandRequest::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" has_away: "); - out.append(YESNO(this->has_away)); + out.append(" has_legacy_away: "); + out.append(YESNO(this->has_legacy_away)); out.append("\n"); - out.append(" away: "); - out.append(YESNO(this->away)); + out.append(" legacy_away: "); + out.append(YESNO(this->legacy_away)); out.append("\n"); out.append(" has_fan_mode: "); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 04d5834572..7f37c0b94b 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -90,11 +90,11 @@ enum ClimateAction : uint32_t { CLIMATE_ACTION_FAN = 6, }; enum ClimatePreset : uint32_t { - CLIMATE_PRESET_ECO = 0, + CLIMATE_PRESET_HOME = 0, CLIMATE_PRESET_AWAY = 1, CLIMATE_PRESET_BOOST = 2, CLIMATE_PRESET_COMFORT = 3, - CLIMATE_PRESET_HOME = 4, + CLIMATE_PRESET_ECO = 4, CLIMATE_PRESET_SLEEP = 5, CLIMATE_PRESET_ACTIVITY = 6, }; @@ -709,7 +709,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { float visual_min_temperature{0.0f}; float visual_max_temperature{0.0f}; float visual_temperature_step{0.0f}; - bool supports_away{false}; + bool legacy_supports_away{false}; bool supports_action{false}; std::vector supported_fan_modes{}; std::vector supported_swing_modes{}; @@ -732,7 +732,7 @@ class ClimateStateResponse : public ProtoMessage { float target_temperature{0.0f}; float target_temperature_low{0.0f}; float target_temperature_high{0.0f}; - bool away{false}; + bool legacy_away{false}; enums::ClimateAction action{}; enums::ClimateFanMode fan_mode{}; enums::ClimateSwingMode swing_mode{}; @@ -758,8 +758,8 @@ class ClimateCommandRequest : public ProtoMessage { float target_temperature_low{0.0f}; bool has_target_temperature_high{false}; float target_temperature_high{0.0f}; - bool has_away{false}; - bool away{false}; + bool has_legacy_away{false}; + bool legacy_away{false}; bool has_fan_mode{false}; enums::ClimateFanMode fan_mode{}; bool has_swing_mode{false}; diff --git a/esphome/components/bang_bang/bang_bang_climate.cpp b/esphome/components/bang_bang/bang_bang_climate.cpp index ea1442755d..c915d69981 100644 --- a/esphome/components/bang_bang/bang_bang_climate.cpp +++ b/esphome/components/bang_bang/bang_bang_climate.cpp @@ -32,8 +32,8 @@ void BangBangClimate::control(const climate::ClimateCall &call) { this->target_temperature_low = *call.get_target_temperature_low(); if (call.get_target_temperature_high().has_value()) this->target_temperature_high = *call.get_target_temperature_high(); - if (call.get_away().has_value()) - this->change_away_(*call.get_away()); + if (call.get_preset().has_value()) + this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY); this->compute_state_(); this->publish_state(); @@ -41,11 +41,20 @@ void BangBangClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits BangBangClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_heat_cool_mode(true); - traits.set_supports_cool_mode(this->supports_cool_); - traits.set_supports_heat_mode(this->supports_heat_); + traits.set_supported_modes({ + climate::CLIMATE_MODE_OFF, + climate::CLIMATE_MODE_HEAT_COOL, + }); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); traits.set_supports_two_point_target_temperature(true); - traits.set_supports_away(this->supports_away_); + if (supports_away_) + traits.set_supported_presets({ + climate::CLIMATE_PRESET_HOME, + climate::CLIMATE_PRESET_AWAY, + }); traits.set_supports_action(true); return traits; } @@ -140,7 +149,7 @@ void BangBangClimate::change_away_(bool away) { this->target_temperature_low = this->away_config_.default_temperature_low; this->target_temperature_high = this->away_config_.default_temperature_high; } - this->away = away; + this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME; } void BangBangClimate::set_normal_config(const BangBangClimateTargetTempConfig &normal_config) { this->normal_config_ = normal_config; diff --git a/esphome/components/climate/automation.h b/esphome/components/climate/automation.h index b0b71cb7d7..49a87027f2 100644 --- a/esphome/components/climate/automation.h +++ b/esphome/components/climate/automation.h @@ -27,7 +27,9 @@ template class ControlAction : public Action { call.set_target_temperature(this->target_temperature_.optional_value(x...)); call.set_target_temperature_low(this->target_temperature_low_.optional_value(x...)); call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...)); - call.set_away(this->away_.optional_value(x...)); + if (away_.has_value()) { + call.set_preset(away_.value(x...) ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME); + } call.set_fan_mode(this->fan_mode_.optional_value(x...)); call.set_fan_mode(this->custom_fan_mode_.optional_value(x...)); call.set_preset(this->preset_.optional_value(x...)); diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index c047d96cdb..5369449839 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -43,9 +43,6 @@ void ClimateCall::perform() { if (this->target_temperature_high_.has_value()) { ESP_LOGD(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_); } - if (this->away_.has_value()) { - ESP_LOGD(TAG, " Away Mode: %s", ONOFF(*this->away_)); - } this->parent_->control(*this); } void ClimateCall::validate_() { @@ -125,12 +122,6 @@ void ClimateCall::validate_() { this->target_temperature_high_.reset(); } } - if (this->away_.has_value()) { - if (!traits.get_supports_away()) { - ESP_LOGW(TAG, " Cannot set away mode for this device!"); - this->away_.reset(); - } - } } ClimateCall &ClimateCall::set_mode(ClimateMode mode) { this->mode_ = mode; @@ -181,8 +172,7 @@ ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) { } else if (str_equals_case_insensitive(fan_mode, "DIFFUSE")) { this->set_fan_mode(CLIMATE_FAN_DIFFUSE); } else { - auto custom_fan_modes = this->parent_->get_traits().get_supported_custom_fan_modes(); - if (std::find(custom_fan_modes.begin(), custom_fan_modes.end(), fan_mode) != custom_fan_modes.end()) { + if (this->parent_->get_traits().supports_custom_fan_mode(fan_mode)) { this->custom_fan_mode_ = fan_mode; this->fan_mode_.reset(); } else { @@ -218,8 +208,7 @@ ClimateCall &ClimateCall::set_preset(const std::string &preset) { } else if (str_equals_case_insensitive(preset, "ACTIVITY")) { this->set_preset(CLIMATE_PRESET_ACTIVITY); } else { - auto custom_presets = this->parent_->get_traits().get_supported_custom_presets(); - if (std::find(custom_presets.begin(), custom_presets.end(), preset) != custom_presets.end()) { + if (this->parent_->get_traits().supports_custom_preset(preset)) { this->custom_preset_ = preset; this->preset_.reset(); } else { @@ -269,18 +258,23 @@ const optional &ClimateCall::get_mode() const { return this->mode_; const optional &ClimateCall::get_target_temperature() const { return this->target_temperature_; } const optional &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; } const optional &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; } -const optional &ClimateCall::get_away() const { return this->away_; } +optional ClimateCall::get_away() const { + if (!this->preset_.has_value()) + return {}; + return *this->preset_ == ClimatePreset::CLIMATE_PRESET_AWAY; +} const optional &ClimateCall::get_fan_mode() const { return this->fan_mode_; } const optional &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; } const optional &ClimateCall::get_preset() const { return this->preset_; } const optional &ClimateCall::get_custom_preset() const { return this->custom_preset_; } const optional &ClimateCall::get_swing_mode() const { return this->swing_mode_; } ClimateCall &ClimateCall::set_away(bool away) { - this->away_ = away; + this->preset_ = away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME; return *this; } ClimateCall &ClimateCall::set_away(optional away) { - this->away_ = away; + if (away.has_value()) + this->preset_ = *away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME; return *this; } ClimateCall &ClimateCall::set_target_temperature_high(optional target_temperature_high) { @@ -338,20 +332,17 @@ void Climate::save_state_() { } else { state.target_temperature = this->target_temperature; } - if (traits.get_supports_away()) { - state.away = this->away; - } if (traits.get_supports_fan_modes() && fan_mode.has_value()) { state.uses_custom_fan_mode = false; state.fan_mode = this->fan_mode.value(); } if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) { state.uses_custom_fan_mode = true; - auto &custom_fan_modes = traits.get_supported_custom_fan_modes(); - auto it = std::find(custom_fan_modes.begin(), custom_fan_modes.end(), this->custom_fan_mode.value()); - // only set custom fan mode if value exists, otherwise leave it as is - if (it != custom_fan_modes.cend()) { - state.custom_fan_mode = std::distance(custom_fan_modes.begin(), it); + const auto &supported = traits.get_supported_custom_fan_modes(); + std::vector vec{supported.begin(), supported.end()}; + auto it = std::find(vec.begin(), vec.end(), custom_fan_mode); + if (it != vec.end()) { + state.custom_fan_mode = std::distance(vec.begin(), it); } } if (traits.get_supports_presets() && preset.has_value()) { @@ -360,11 +351,12 @@ void Climate::save_state_() { } if (!traits.get_supported_custom_presets().empty() && custom_preset.has_value()) { state.uses_custom_preset = true; - auto custom_presets = traits.get_supported_custom_presets(); - auto it = std::find(custom_presets.begin(), custom_presets.end(), this->custom_preset.value()); + const auto &supported = traits.get_supported_custom_presets(); + std::vector vec{supported.begin(), supported.end()}; + auto it = std::find(vec.begin(), vec.end(), custom_preset); // only set custom preset if value exists, otherwise leave it as is - if (it != custom_presets.cend()) { - state.custom_preset = std::distance(custom_presets.begin(), it); + if (it != vec.cend()) { + state.custom_preset = std::distance(vec.begin(), it); } } if (traits.get_supports_swing_modes()) { @@ -405,9 +397,6 @@ void Climate::publish_state() { } else { ESP_LOGD(TAG, " Target Temperature: %.2f°C", this->target_temperature); } - if (traits.get_supports_away()) { - ESP_LOGD(TAG, " Away: %s", ONOFF(this->away)); - } // Send state to frontend this->state_callback_.call(); @@ -453,9 +442,6 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) { } else { call.set_target_temperature(this->target_temperature); } - if (traits.get_supports_away()) { - call.set_away(this->away); - } if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) { call.set_fan_mode(this->fan_mode); } @@ -476,23 +462,27 @@ void ClimateDeviceRestoreState::apply(Climate *climate) { } else { climate->target_temperature = this->target_temperature; } - if (traits.get_supports_away()) { - climate->away = this->away; - } if (traits.get_supports_fan_modes() && !this->uses_custom_fan_mode) { climate->fan_mode = this->fan_mode; } if (!traits.get_supported_custom_fan_modes().empty() && this->uses_custom_fan_mode) { - climate->custom_fan_mode = traits.get_supported_custom_fan_modes()[this->custom_fan_mode]; + // std::set has consistent order (lexicographic for strings), so this is ok + const auto &modes = traits.get_supported_custom_fan_modes(); + std::vector modes_vec{modes.begin(), modes.end()}; + if (custom_fan_mode < modes_vec.size()) { + climate->custom_fan_mode = modes_vec[this->custom_fan_mode]; + } } if (traits.get_supports_presets() && !this->uses_custom_preset) { climate->preset = this->preset; } - if (!traits.get_supported_custom_presets().empty() && this->uses_custom_preset) { - climate->custom_preset = traits.get_supported_custom_presets()[this->custom_preset]; - } if (!traits.get_supported_custom_presets().empty() && uses_custom_preset) { - climate->custom_preset = traits.get_supported_custom_presets()[this->preset]; + // std::set has consistent order (lexicographic for strings), so this is ok + const auto &presets = traits.get_supported_custom_presets(); + std::vector presets_vec{presets.begin(), presets.end()}; + if (custom_preset < presets_vec.size()) { + climate->custom_preset = presets_vec[this->custom_preset]; + } } if (traits.get_supports_swing_modes()) { climate->swing_mode = this->swing_mode; diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 3aa29be2fb..4b12c98dc7 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -63,7 +63,9 @@ class ClimateCall { * For climate devices with two point target temperature control */ ClimateCall &set_target_temperature_high(optional target_temperature_high); + ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead") ClimateCall &set_away(bool away); + ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead") ClimateCall &set_away(optional away); /// Set the fan mode of the climate device. ClimateCall &set_fan_mode(ClimateFanMode fan_mode); @@ -94,7 +96,8 @@ class ClimateCall { const optional &get_target_temperature() const; const optional &get_target_temperature_low() const; const optional &get_target_temperature_high() const; - const optional &get_away() const; + ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead") + optional get_away() const; const optional &get_fan_mode() const; const optional &get_swing_mode() const; const optional &get_custom_fan_mode() const; @@ -109,7 +112,6 @@ class ClimateCall { optional target_temperature_; optional target_temperature_low_; optional target_temperature_high_; - optional away_; optional fan_mode_; optional swing_mode_; optional custom_fan_mode_; @@ -120,7 +122,6 @@ class ClimateCall { /// Struct used to save the state of the climate device in restore memory. struct ClimateDeviceRestoreState { ClimateMode mode; - bool away; bool uses_custom_fan_mode{false}; union { ClimateFanMode fan_mode; @@ -191,6 +192,7 @@ class Climate : public Nameable { * Away allows climate devices to have two different target temperature configs: * one for normal mode and one for away mode. */ + ESPDEPRECATED("away is deprecated, use preset instead") bool away{false}; /// The active fan mode of the climate device. diff --git a/esphome/components/climate/climate_mode.cpp b/esphome/components/climate/climate_mode.cpp index 4540208a3f..099074a887 100644 --- a/esphome/components/climate/climate_mode.cpp +++ b/esphome/components/climate/climate_mode.cpp @@ -84,6 +84,8 @@ const char *climate_swing_mode_to_string(ClimateSwingMode swing_mode) { const char *climate_preset_to_string(ClimatePreset preset) { switch (preset) { + case climate::CLIMATE_PRESET_HOME: + return "HOME"; case climate::CLIMATE_PRESET_ECO: return "ECO"; case climate::CLIMATE_PRESET_AWAY: @@ -92,8 +94,6 @@ const char *climate_preset_to_string(ClimatePreset preset) { return "BOOST"; case climate::CLIMATE_PRESET_COMFORT: return "COMFORT"; - case climate::CLIMATE_PRESET_HOME: - return "HOME"; case climate::CLIMATE_PRESET_SLEEP: return "SLEEP"; case climate::CLIMATE_PRESET_ACTIVITY: diff --git a/esphome/components/climate/climate_mode.h b/esphome/components/climate/climate_mode.h index e129fca91d..7afa2dae55 100644 --- a/esphome/components/climate/climate_mode.h +++ b/esphome/components/climate/climate_mode.h @@ -39,7 +39,6 @@ enum ClimateAction : uint8_t { CLIMATE_ACTION_FAN = 6, }; -/// Enum for all modes a climate fan can be in enum ClimateFanMode : uint8_t { /// The fan mode is set to On CLIMATE_FAN_ON = 0, @@ -75,16 +74,16 @@ enum ClimateSwingMode : uint8_t { /// Enum for all modes a climate swing can be in enum ClimatePreset : uint8_t { - /// Preset is set to ECO - CLIMATE_PRESET_ECO = 0, + /// Preset is set to HOME + CLIMATE_PRESET_HOME = 0, /// Preset is set to AWAY CLIMATE_PRESET_AWAY = 1, /// Preset is set to BOOST CLIMATE_PRESET_BOOST = 2, /// Preset is set to COMFORT CLIMATE_PRESET_COMFORT = 3, - /// Preset is set to HOME - CLIMATE_PRESET_HOME = 4, + /// Preset is set to ECO + CLIMATE_PRESET_ECO = 4, /// Preset is set to SLEEP CLIMATE_PRESET_SLEEP = 5, /// Preset is set to ACTIVITY diff --git a/esphome/components/climate/climate_traits.cpp b/esphome/components/climate/climate_traits.cpp index 774ada785f..c871552360 100644 --- a/esphome/components/climate/climate_traits.cpp +++ b/esphome/components/climate/climate_traits.cpp @@ -4,55 +4,6 @@ namespace esphome { namespace climate { -bool ClimateTraits::supports_mode(ClimateMode mode) const { - switch (mode) { - case CLIMATE_MODE_OFF: - return true; - case CLIMATE_MODE_HEAT_COOL: - return this->supports_heat_cool_mode_; - case CLIMATE_MODE_AUTO: - return this->supports_auto_mode_; - case CLIMATE_MODE_COOL: - return this->supports_cool_mode_; - case CLIMATE_MODE_HEAT: - return this->supports_heat_mode_; - case CLIMATE_MODE_FAN_ONLY: - return this->supports_fan_only_mode_; - case CLIMATE_MODE_DRY: - return this->supports_dry_mode_; - default: - return false; - } -} -bool ClimateTraits::get_supports_current_temperature() const { return supports_current_temperature_; } -void ClimateTraits::set_supports_current_temperature(bool supports_current_temperature) { - supports_current_temperature_ = supports_current_temperature; -} -bool ClimateTraits::get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; } -void ClimateTraits::set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) { - supports_two_point_target_temperature_ = supports_two_point_target_temperature; -} -void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_auto_mode_ = supports_auto_mode; } -void ClimateTraits::set_supports_heat_cool_mode(bool supports_heat_cool_mode) { - supports_heat_cool_mode_ = supports_heat_cool_mode; -} -void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; } -void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; } -void ClimateTraits::set_supports_fan_only_mode(bool supports_fan_only_mode) { - supports_fan_only_mode_ = supports_fan_only_mode; -} -void ClimateTraits::set_supports_dry_mode(bool supports_dry_mode) { supports_dry_mode_ = supports_dry_mode; } -void ClimateTraits::set_supports_away(bool supports_away) { supports_away_ = supports_away; } -void ClimateTraits::set_supports_action(bool supports_action) { supports_action_ = supports_action; } -float ClimateTraits::get_visual_min_temperature() const { return visual_min_temperature_; } -void ClimateTraits::set_visual_min_temperature(float visual_min_temperature) { - visual_min_temperature_ = visual_min_temperature; -} -float ClimateTraits::get_visual_max_temperature() const { return visual_max_temperature_; } -void ClimateTraits::set_visual_max_temperature(float visual_max_temperature) { - visual_max_temperature_ = visual_max_temperature; -} -float ClimateTraits::get_visual_temperature_step() const { return visual_temperature_step_; } int8_t ClimateTraits::get_temperature_accuracy_decimals() const { // use printf %g to find number of digits based on temperature step char buf[32]; @@ -64,160 +15,6 @@ int8_t ClimateTraits::get_temperature_accuracy_decimals() const { return str.length() - dot_pos - 1; } -void ClimateTraits::set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; } -bool ClimateTraits::get_supports_away() const { return supports_away_; } -bool ClimateTraits::get_supports_action() const { return supports_action_; } -void ClimateTraits::set_supports_fan_mode_on(bool supports_fan_mode_on) { - this->supports_fan_mode_on_ = supports_fan_mode_on; -} -void ClimateTraits::set_supports_fan_mode_off(bool supports_fan_mode_off) { - this->supports_fan_mode_off_ = supports_fan_mode_off; -} -void ClimateTraits::set_supports_fan_mode_auto(bool supports_fan_mode_auto) { - this->supports_fan_mode_auto_ = supports_fan_mode_auto; -} -void ClimateTraits::set_supports_fan_mode_low(bool supports_fan_mode_low) { - this->supports_fan_mode_low_ = supports_fan_mode_low; -} -void ClimateTraits::set_supports_fan_mode_medium(bool supports_fan_mode_medium) { - this->supports_fan_mode_medium_ = supports_fan_mode_medium; -} -void ClimateTraits::set_supports_fan_mode_high(bool supports_fan_mode_high) { - this->supports_fan_mode_high_ = supports_fan_mode_high; -} -void ClimateTraits::set_supports_fan_mode_middle(bool supports_fan_mode_middle) { - this->supports_fan_mode_middle_ = supports_fan_mode_middle; -} -void ClimateTraits::set_supports_fan_mode_focus(bool supports_fan_mode_focus) { - this->supports_fan_mode_focus_ = supports_fan_mode_focus; -} -void ClimateTraits::set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse) { - this->supports_fan_mode_diffuse_ = supports_fan_mode_diffuse; -} -bool ClimateTraits::supports_fan_mode(ClimateFanMode fan_mode) const { - switch (fan_mode) { - case climate::CLIMATE_FAN_ON: - return this->supports_fan_mode_on_; - case climate::CLIMATE_FAN_OFF: - return this->supports_fan_mode_off_; - case climate::CLIMATE_FAN_AUTO: - return this->supports_fan_mode_auto_; - case climate::CLIMATE_FAN_LOW: - return this->supports_fan_mode_low_; - case climate::CLIMATE_FAN_MEDIUM: - return this->supports_fan_mode_medium_; - case climate::CLIMATE_FAN_HIGH: - return this->supports_fan_mode_high_; - case climate::CLIMATE_FAN_MIDDLE: - return this->supports_fan_mode_middle_; - case climate::CLIMATE_FAN_FOCUS: - return this->supports_fan_mode_focus_; - case climate::CLIMATE_FAN_DIFFUSE: - return this->supports_fan_mode_diffuse_; - default: - return false; - } -} -bool ClimateTraits::get_supports_fan_modes() const { - return this->supports_fan_mode_on_ || this->supports_fan_mode_off_ || this->supports_fan_mode_auto_ || - this->supports_fan_mode_low_ || this->supports_fan_mode_medium_ || this->supports_fan_mode_high_ || - this->supports_fan_mode_middle_ || this->supports_fan_mode_focus_ || this->supports_fan_mode_diffuse_; -} -void ClimateTraits::set_supported_custom_fan_modes(std::vector &supported_custom_fan_modes) { - this->supported_custom_fan_modes_ = supported_custom_fan_modes; -} -const std::vector ClimateTraits::get_supported_custom_fan_modes() const { - return this->supported_custom_fan_modes_; -} -bool ClimateTraits::supports_custom_fan_mode(std::string &custom_fan_mode) const { - return std::count(this->supported_custom_fan_modes_.begin(), this->supported_custom_fan_modes_.end(), - custom_fan_mode); -} -bool ClimateTraits::supports_preset(ClimatePreset preset) const { - switch (preset) { - case climate::CLIMATE_PRESET_ECO: - return this->supports_preset_eco_; - case climate::CLIMATE_PRESET_AWAY: - return this->supports_preset_away_; - case climate::CLIMATE_PRESET_BOOST: - return this->supports_preset_boost_; - case climate::CLIMATE_PRESET_COMFORT: - return this->supports_preset_comfort_; - case climate::CLIMATE_PRESET_HOME: - return this->supports_preset_home_; - case climate::CLIMATE_PRESET_SLEEP: - return this->supports_preset_sleep_; - case climate::CLIMATE_PRESET_ACTIVITY: - return this->supports_preset_activity_; - default: - return false; - } -} -void ClimateTraits::set_supports_preset_eco(bool supports_preset_eco) { - this->supports_preset_eco_ = supports_preset_eco; -} -void ClimateTraits::set_supports_preset_away(bool supports_preset_away) { - this->supports_preset_away_ = supports_preset_away; -} -void ClimateTraits::set_supports_preset_boost(bool supports_preset_boost) { - this->supports_preset_boost_ = supports_preset_boost; -} -void ClimateTraits::set_supports_preset_comfort(bool supports_preset_comfort) { - this->supports_preset_comfort_ = supports_preset_comfort; -} -void ClimateTraits::set_supports_preset_home(bool supports_preset_home) { - this->supports_preset_home_ = supports_preset_home; -} -void ClimateTraits::set_supports_preset_sleep(bool supports_preset_sleep) { - this->supports_preset_sleep_ = supports_preset_sleep; -} -void ClimateTraits::set_supports_preset_activity(bool supports_preset_activity) { - this->supports_preset_activity_ = supports_preset_activity; -} -bool ClimateTraits::get_supports_presets() const { - return this->supports_preset_eco_ || this->supports_preset_away_ || this->supports_preset_boost_ || - this->supports_preset_comfort_ || this->supports_preset_home_ || this->supports_preset_sleep_ || - this->supports_preset_activity_; -} -void ClimateTraits::set_supported_custom_presets(std::vector &supported_custom_presets) { - this->supported_custom_presets_ = supported_custom_presets; -} -const std::vector ClimateTraits::get_supported_custom_presets() const { - return this->supported_custom_presets_; -} -bool ClimateTraits::supports_custom_preset(std::string &custom_preset) const { - return std::count(this->supported_custom_presets_.begin(), this->supported_custom_presets_.end(), custom_preset); -} -void ClimateTraits::set_supports_swing_mode_off(bool supports_swing_mode_off) { - this->supports_swing_mode_off_ = supports_swing_mode_off; -} -void ClimateTraits::set_supports_swing_mode_both(bool supports_swing_mode_both) { - this->supports_swing_mode_both_ = supports_swing_mode_both; -} -void ClimateTraits::set_supports_swing_mode_vertical(bool supports_swing_mode_vertical) { - this->supports_swing_mode_vertical_ = supports_swing_mode_vertical; -} -void ClimateTraits::set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal) { - this->supports_swing_mode_horizontal_ = supports_swing_mode_horizontal; -} -bool ClimateTraits::supports_swing_mode(ClimateSwingMode swing_mode) const { - switch (swing_mode) { - case climate::CLIMATE_SWING_OFF: - return this->supports_swing_mode_off_; - case climate::CLIMATE_SWING_BOTH: - return this->supports_swing_mode_both_; - case climate::CLIMATE_SWING_VERTICAL: - return this->supports_swing_mode_vertical_; - case climate::CLIMATE_SWING_HORIZONTAL: - return this->supports_swing_mode_horizontal_; - default: - return false; - } -} -bool ClimateTraits::get_supports_swing_modes() const { - return this->supports_swing_mode_off_ || this->supports_swing_mode_both_ || supports_swing_mode_vertical_ || - supports_swing_mode_horizontal_; -} } // namespace climate } // namespace esphome diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index f8e6f87306..b86a0c7774 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -2,6 +2,7 @@ #include "esphome/core/helpers.h" #include "climate_mode.h" +#include namespace esphome { namespace climate { @@ -24,8 +25,6 @@ namespace climate { * - heat mode (increases current temperature) * - dry mode (removes humidity from air) * - fan mode (only turns on fan) - * - supports away - away mode means that the climate device supports two different - * target temperature settings: one target temp setting for "away" mode and one for non-away mode. * - supports action - if the climate device supports reporting the active * current action of the device with the action property. * - supports fan modes - optionally, if it has a fan which can be configured in different ways: @@ -41,95 +40,147 @@ namespace climate { */ class ClimateTraits { public: - bool get_supports_current_temperature() const; - void set_supports_current_temperature(bool supports_current_temperature); - bool get_supports_two_point_target_temperature() const; - void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature); - void set_supports_auto_mode(bool supports_auto_mode); - void set_supports_heat_cool_mode(bool supports_heat_cool_mode); - void set_supports_cool_mode(bool supports_cool_mode); - void set_supports_heat_mode(bool supports_heat_mode); - void set_supports_fan_only_mode(bool supports_fan_only_mode); - void set_supports_dry_mode(bool supports_dry_mode); - void set_supports_away(bool supports_away); - bool get_supports_away() const; - void set_supports_action(bool supports_action); - bool get_supports_action() const; - bool supports_mode(ClimateMode mode) const; - void set_supports_fan_mode_on(bool supports_fan_mode_on); - void set_supports_fan_mode_off(bool supports_fan_mode_off); - void set_supports_fan_mode_auto(bool supports_fan_mode_auto); - void set_supports_fan_mode_low(bool supports_fan_mode_low); - void set_supports_fan_mode_medium(bool supports_fan_mode_medium); - void set_supports_fan_mode_high(bool supports_fan_mode_high); - void set_supports_fan_mode_middle(bool supports_fan_mode_middle); - void set_supports_fan_mode_focus(bool supports_fan_mode_focus); - void set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse); - bool supports_fan_mode(ClimateFanMode fan_mode) const; - bool get_supports_fan_modes() const; - void set_supported_custom_fan_modes(std::vector &supported_custom_fan_modes); - const std::vector get_supported_custom_fan_modes() const; - bool supports_custom_fan_mode(std::string &custom_fan_mode) const; - bool supports_preset(ClimatePreset preset) const; - void set_supports_preset_eco(bool supports_preset_eco); - void set_supports_preset_away(bool supports_preset_away); - void set_supports_preset_boost(bool supports_preset_boost); - void set_supports_preset_comfort(bool supports_preset_comfort); - void set_supports_preset_home(bool supports_preset_home); - void set_supports_preset_sleep(bool supports_preset_sleep); - void set_supports_preset_activity(bool supports_preset_activity); - bool get_supports_presets() const; - void set_supported_custom_presets(std::vector &supported_custom_presets); - const std::vector get_supported_custom_presets() const; - bool supports_custom_preset(std::string &custom_preset) const; - void set_supports_swing_mode_off(bool supports_swing_mode_off); - void set_supports_swing_mode_both(bool supports_swing_mode_both); - void set_supports_swing_mode_vertical(bool supports_swing_mode_vertical); - void set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal); - bool supports_swing_mode(ClimateSwingMode swing_mode) const; - bool get_supports_swing_modes() const; + bool get_supports_current_temperature() const { return supports_current_temperature_; } + void set_supports_current_temperature(bool supports_current_temperature) { + supports_current_temperature_ = supports_current_temperature; + } + bool get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; } + void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) { + supports_two_point_target_temperature_ = supports_two_point_target_temperature; + } + void set_supported_modes(std::set modes) { supported_modes_ = std::move(modes); } + void add_supported_mode(ClimateMode mode) { supported_modes_.insert(mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_auto_mode(bool supports_auto_mode) { set_mode_support_(CLIMATE_MODE_AUTO, supports_auto_mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_cool_mode(bool supports_cool_mode) { set_mode_support_(CLIMATE_MODE_COOL, supports_cool_mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_heat_mode(bool supports_heat_mode) { set_mode_support_(CLIMATE_MODE_HEAT, supports_heat_mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_heat_cool_mode(bool supported) { set_mode_support_(CLIMATE_MODE_HEAT_COOL, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_fan_only_mode(bool supports_fan_only_mode) { + set_mode_support_(CLIMATE_MODE_FAN_ONLY, supports_fan_only_mode); + } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); } + bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); } + const std::set get_supported_modes() const { return supported_modes_; } - float get_visual_min_temperature() const; - void set_visual_min_temperature(float visual_min_temperature); - float get_visual_max_temperature() const; - void set_visual_max_temperature(float visual_max_temperature); - float get_visual_temperature_step() const; + void set_supports_action(bool supports_action) { supports_action_ = supports_action; } + bool get_supports_action() const { return supports_action_; } + + void set_supported_fan_modes(std::set modes) { supported_fan_modes_ = std::move(modes); } + void add_supported_fan_mode(ClimateFanMode mode) { supported_fan_modes_.insert(mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_on(bool supported) { set_fan_mode_support_(CLIMATE_FAN_ON, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_off(bool supported) { set_fan_mode_support_(CLIMATE_FAN_OFF, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_auto(bool supported) { set_fan_mode_support_(CLIMATE_FAN_AUTO, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_low(bool supported) { set_fan_mode_support_(CLIMATE_FAN_LOW, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_medium(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MEDIUM, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_high(bool supported) { set_fan_mode_support_(CLIMATE_FAN_HIGH, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_middle(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MIDDLE, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_focus(bool supported) { set_fan_mode_support_(CLIMATE_FAN_FOCUS, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); } + bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); } + bool get_supports_fan_modes() const { return !supported_fan_modes_.empty(); } + const std::set get_supported_fan_modes() const { return supported_fan_modes_; } + + void set_supported_custom_fan_modes(std::set supported_custom_fan_modes) { + supported_custom_fan_modes_ = std::move(supported_custom_fan_modes); + } + const std::set &get_supported_custom_fan_modes() const { return supported_custom_fan_modes_; } + bool supports_custom_fan_mode(const std::string &custom_fan_mode) const { + return supported_custom_fan_modes_.count(custom_fan_mode); + } + + void set_supported_presets(std::set presets) { supported_presets_ = std::move(presets); } + void add_supported_preset(ClimatePreset preset) { supported_presets_.insert(preset); } + bool supports_preset(ClimatePreset preset) const { return supported_presets_.count(preset); } + bool get_supports_presets() const { return !supported_presets_.empty(); } + const std::set &get_supported_presets() const { return supported_presets_; } + + void set_supported_custom_presets(std::set supported_custom_presets) { + supported_custom_presets_ = std::move(supported_custom_presets); + } + const std::set &get_supported_custom_presets() const { return supported_custom_presets_; } + bool supports_custom_preset(const std::string &custom_preset) const { + return supported_custom_presets_.count(custom_preset); + } + ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead") + void set_supports_away(bool supports) { + if (supports) { + supported_presets_.insert(CLIMATE_PRESET_AWAY); + supported_presets_.insert(CLIMATE_PRESET_HOME); + } + } + ESPDEPRECATED("This method is deprecated, use supports_preset() instead") + bool get_supports_away() const { return supports_preset(CLIMATE_PRESET_AWAY); } + + void set_supported_swing_modes(std::set modes) { supported_swing_modes_ = std::move(modes); } + void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_swing_mode_off(bool supported) { set_swing_mode_support_(CLIMATE_SWING_OFF, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_swing_mode_both(bool supported) { set_swing_mode_support_(CLIMATE_SWING_BOTH, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_swing_mode_vertical(bool supported) { set_swing_mode_support_(CLIMATE_SWING_VERTICAL, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_swing_mode_horizontal(bool supported) { + set_swing_mode_support_(CLIMATE_SWING_HORIZONTAL, supported); + } + bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); } + bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); } + const std::set get_supported_swing_modes() { return supported_swing_modes_; } + + float get_visual_min_temperature() const { return visual_min_temperature_; } + void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; } + float get_visual_max_temperature() const { return visual_max_temperature_; } + void set_visual_max_temperature(float visual_max_temperature) { visual_max_temperature_ = visual_max_temperature; } + float get_visual_temperature_step() const { return visual_temperature_step_; } int8_t get_temperature_accuracy_decimals() const; - void set_visual_temperature_step(float temperature_step); + void set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; } protected: + void set_mode_support_(climate::ClimateMode mode, bool supported) { + if (supported) { + supported_modes_.insert(mode); + } else { + supported_modes_.erase(mode); + } + } + void set_fan_mode_support_(climate::ClimateFanMode mode, bool supported) { + if (supported) { + supported_fan_modes_.insert(mode); + } else { + supported_fan_modes_.erase(mode); + } + } + void set_swing_mode_support_(climate::ClimateSwingMode mode, bool supported) { + if (supported) { + supported_swing_modes_.insert(mode); + } else { + supported_swing_modes_.erase(mode); + } + } + bool supports_current_temperature_{false}; bool supports_two_point_target_temperature_{false}; - bool supports_auto_mode_{false}; - bool supports_heat_cool_mode_{false}; - bool supports_cool_mode_{false}; - bool supports_heat_mode_{false}; - bool supports_fan_only_mode_{false}; - bool supports_dry_mode_{false}; - bool supports_away_{false}; + std::set supported_modes_ = {climate::CLIMATE_MODE_OFF}; bool supports_action_{false}; - bool supports_fan_mode_on_{false}; - bool supports_fan_mode_off_{false}; - bool supports_fan_mode_auto_{false}; - bool supports_fan_mode_low_{false}; - bool supports_fan_mode_medium_{false}; - bool supports_fan_mode_high_{false}; - bool supports_fan_mode_middle_{false}; - bool supports_fan_mode_focus_{false}; - bool supports_fan_mode_diffuse_{false}; - bool supports_swing_mode_off_{false}; - bool supports_swing_mode_both_{false}; - bool supports_swing_mode_vertical_{false}; - bool supports_swing_mode_horizontal_{false}; - bool supports_preset_eco_{false}; - bool supports_preset_away_{false}; - bool supports_preset_boost_{false}; - bool supports_preset_comfort_{false}; - bool supports_preset_home_{false}; - bool supports_preset_sleep_{false}; - bool supports_preset_activity_{false}; - std::vector supported_custom_fan_modes_; - std::vector supported_custom_presets_; + std::set supported_fan_modes_; + std::set supported_swing_modes_; + std::set supported_presets_; + std::set supported_custom_fan_modes_; + std::set supported_custom_presets_; float visual_min_temperature_{10}; float visual_max_temperature_{30}; diff --git a/esphome/components/climate_ir/climate_ir.cpp b/esphome/components/climate_ir/climate_ir.cpp index f88b2174ee..16a8e78414 100644 --- a/esphome/components/climate_ir/climate_ir.cpp +++ b/esphome/components/climate_ir/climate_ir.cpp @@ -9,63 +9,22 @@ static const char *TAG = "climate_ir"; climate::ClimateTraits ClimateIR::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->sensor_ != nullptr); - traits.set_supports_heat_cool_mode(true); - traits.set_supports_cool_mode(this->supports_cool_); - traits.set_supports_heat_mode(this->supports_heat_); - traits.set_supports_dry_mode(this->supports_dry_); - traits.set_supports_fan_only_mode(this->supports_fan_only_); + traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL}); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + if (supports_dry_) + traits.add_supported_mode(climate::CLIMATE_MODE_DRY); + if (supports_fan_only_) + traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY); + traits.set_supports_two_point_target_temperature(false); - traits.set_supports_away(false); traits.set_visual_min_temperature(this->minimum_temperature_); traits.set_visual_max_temperature(this->maximum_temperature_); traits.set_visual_temperature_step(this->temperature_step_); - for (auto fan_mode : this->fan_modes_) { - switch (fan_mode) { - case climate::CLIMATE_FAN_AUTO: - traits.set_supports_fan_mode_auto(true); - break; - case climate::CLIMATE_FAN_DIFFUSE: - traits.set_supports_fan_mode_diffuse(true); - break; - case climate::CLIMATE_FAN_FOCUS: - traits.set_supports_fan_mode_focus(true); - break; - case climate::CLIMATE_FAN_HIGH: - traits.set_supports_fan_mode_high(true); - break; - case climate::CLIMATE_FAN_LOW: - traits.set_supports_fan_mode_low(true); - break; - case climate::CLIMATE_FAN_MEDIUM: - traits.set_supports_fan_mode_medium(true); - break; - case climate::CLIMATE_FAN_MIDDLE: - traits.set_supports_fan_mode_middle(true); - break; - case climate::CLIMATE_FAN_OFF: - traits.set_supports_fan_mode_off(true); - break; - case climate::CLIMATE_FAN_ON: - traits.set_supports_fan_mode_on(true); - break; - } - } - for (auto swing_mode : this->swing_modes_) { - switch (swing_mode) { - case climate::CLIMATE_SWING_OFF: - traits.set_supports_swing_mode_off(true); - break; - case climate::CLIMATE_SWING_BOTH: - traits.set_supports_swing_mode_both(true); - break; - case climate::CLIMATE_SWING_VERTICAL: - traits.set_supports_swing_mode_vertical(true); - break; - case climate::CLIMATE_SWING_HORIZONTAL: - traits.set_supports_swing_mode_horizontal(true); - break; - } - } + traits.set_supported_fan_modes(fan_modes_); + traits.set_supported_swing_modes(swing_modes_); return traits; } diff --git a/esphome/components/climate_ir/climate_ir.h b/esphome/components/climate_ir/climate_ir.h index 7a69b19786..ff04fa4e2f 100644 --- a/esphome/components/climate_ir/climate_ir.h +++ b/esphome/components/climate_ir/climate_ir.h @@ -19,9 +19,8 @@ namespace climate_ir { class ClimateIR : public climate::Climate, public Component, public remote_base::RemoteReceiverListener { public: ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f, - bool supports_dry = false, bool supports_fan_only = false, - std::vector fan_modes = {}, - std::vector swing_modes = {}) { + bool supports_dry = false, bool supports_fan_only = false, std::set fan_modes = {}, + std::set swing_modes = {}) { this->minimum_temperature_ = minimum_temperature; this->maximum_temperature_ = maximum_temperature; this->temperature_step_ = temperature_step; @@ -58,8 +57,8 @@ class ClimateIR : public climate::Climate, public Component, public remote_base: bool supports_heat_{true}; bool supports_dry_{false}; bool supports_fan_only_{false}; - std::vector fan_modes_ = {}; - std::vector swing_modes_ = {}; + std::set fan_modes_ = {}; + std::set swing_modes_ = {}; remote_transmitter::RemoteTransmitterComponent *transmitter_; sensor::Sensor *sensor_{nullptr}; diff --git a/esphome/components/daikin/daikin.h b/esphome/components/daikin/daikin.h index c0a472bce7..b4ac309de9 100644 --- a/esphome/components/daikin/daikin.h +++ b/esphome/components/daikin/daikin.h @@ -43,12 +43,11 @@ const uint8_t DAIKIN_STATE_FRAME_SIZE = 19; class DaikinClimate : public climate_ir::ClimateIR { public: DaikinClimate() - : climate_ir::ClimateIR( - DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true, - std::vector{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, - climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}, - std::vector{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, - climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {} + : climate_ir::ClimateIR(DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, + climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {} protected: // Transmit via IR the state of this climate controller. diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.h b/esphome/components/hitachi_ac344/hitachi_ac344.h index 9e850d9b53..1d0719f11b 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.h +++ b/esphome/components/hitachi_ac344/hitachi_ac344.h @@ -79,11 +79,10 @@ const uint16_t HITACHI_AC344_BITS = HITACHI_AC344_STATE_LENGTH * 8; class HitachiClimate : public climate_ir::ClimateIR { public: HitachiClimate() - : climate_ir::ClimateIR( - HITACHI_AC344_TEMP_MIN, HITACHI_AC344_TEMP_MAX, 1.0F, true, true, - std::vector{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, - climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}, - std::vector{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL}) {} + : climate_ir::ClimateIR(HITACHI_AC344_TEMP_MIN, HITACHI_AC344_TEMP_MAX, 1.0F, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL}) {} protected: uint8_t remote_state_[HITACHI_AC344_STATE_LENGTH]{0x01, 0x10, 0x00, 0x40, 0x00, 0xFF, 0x00, 0xCC, 0x00, 0x00, 0x00, diff --git a/esphome/components/midea_ac/midea_climate.cpp b/esphome/components/midea_ac/midea_climate.cpp index 481a6da54d..b3b196aad2 100644 --- a/esphome/components/midea_ac/midea_climate.cpp +++ b/esphome/components/midea_ac/midea_climate.cpp @@ -167,24 +167,38 @@ climate::ClimateTraits MideaAC::traits() { traits.set_visual_min_temperature(17); traits.set_visual_max_temperature(30); traits.set_visual_temperature_step(0.5); - traits.set_supports_heat_cool_mode(true); - traits.set_supports_cool_mode(true); - traits.set_supports_dry_mode(true); - traits.set_supports_heat_mode(true); - traits.set_supports_fan_only_mode(true); - traits.set_supports_fan_mode_auto(true); - traits.set_supports_fan_mode_low(true); - traits.set_supports_fan_mode_medium(true); - traits.set_supports_fan_mode_high(true); + traits.set_supported_modes({ + climate::CLIMATE_MODE_OFF, + climate::CLIMATE_MODE_HEAT_COOL, + climate::CLIMATE_MODE_COOL, + climate::CLIMATE_MODE_DRY, + climate::CLIMATE_MODE_HEAT, + climate::CLIMATE_MODE_FAN_ONLY, + }); + traits.set_supported_fan_modes({ + climate::CLIMATE_FAN_AUTO, + climate::CLIMATE_FAN_LOW, + climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH, + }); traits.set_supported_custom_fan_modes(this->traits_custom_fan_modes_); - traits.set_supports_swing_mode_off(true); - traits.set_supports_swing_mode_vertical(true); - traits.set_supports_swing_mode_horizontal(this->traits_swing_horizontal_); - traits.set_supports_swing_mode_both(this->traits_swing_both_); - traits.set_supports_preset_home(true); - traits.set_supports_preset_eco(this->traits_preset_eco_); - traits.set_supports_preset_sleep(this->traits_preset_sleep_); - traits.set_supports_preset_boost(this->traits_preset_boost_); + traits.set_supported_swing_modes({ + climate::CLIMATE_SWING_OFF, + climate::CLIMATE_SWING_VERTICAL, + }); + if (traits_swing_horizontal_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_HORIZONTAL); + if (traits_swing_both_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH); + traits.set_supported_presets({ + climate::CLIMATE_PRESET_HOME, + }); + if (traits_preset_eco_) + traits.add_supported_preset(climate::CLIMATE_PRESET_ECO); + if (traits_preset_sleep_) + traits.add_supported_preset(climate::CLIMATE_PRESET_SLEEP); + if (traits_preset_boost_) + traits.add_supported_preset(climate::CLIMATE_PRESET_BOOST); traits.set_supported_custom_presets(this->traits_custom_presets_); traits.set_supports_current_temperature(true); return traits; diff --git a/esphome/components/midea_ac/midea_climate.h b/esphome/components/midea_ac/midea_climate.h index 0a63312961..d5f29529df 100644 --- a/esphome/components/midea_ac/midea_climate.h +++ b/esphome/components/midea_ac/midea_climate.h @@ -1,9 +1,9 @@ #pragma once -#include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/components/midea_dongle/midea_dongle.h" #include "esphome/components/climate/climate.h" +#include "esphome/components/midea_dongle/midea_dongle.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" #include "midea_frame.h" namespace esphome { @@ -26,10 +26,12 @@ class MideaAC : public midea_dongle::MideaAppliance, public climate::Climate, pu void set_preset_sleep(bool state) { this->traits_preset_sleep_ = state; } void set_preset_boost(bool state) { this->traits_preset_boost_ = state; } bool allow_preset(climate::ClimatePreset preset) const; - void set_custom_fan_modes(std::vector custom_fan_modes) { - this->traits_custom_fan_modes_ = custom_fan_modes; + void set_custom_fan_modes(std::set custom_fan_modes) { + this->traits_custom_fan_modes_ = std::move(custom_fan_modes); + } + void set_custom_presets(std::set custom_presets) { + this->traits_custom_presets_ = std::move(custom_presets); } - void set_custom_presets(std::vector custom_presets) { this->traits_custom_presets_ = custom_presets; } bool allow_custom_preset(const std::string &custom_preset) const; protected: @@ -53,8 +55,8 @@ class MideaAC : public midea_dongle::MideaAppliance, public climate::Climate, pu bool traits_preset_eco_{false}; bool traits_preset_sleep_{false}; bool traits_preset_boost_{false}; - std::vector traits_custom_fan_modes_{{}}; - std::vector traits_custom_presets_{{}}; + std::set traits_custom_fan_modes_{{}}; + std::set traits_custom_presets_{{}}; }; } // namespace midea_ac diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 47923dc924..ed3193ae97 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -61,7 +61,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC // temp_step root["temp_step"] = traits.get_visual_temperature_step(); - if (traits.get_supports_away()) { + if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { // away_mode_command_topic root["away_mode_cmd_t"] = this->get_away_command_topic(); // away_mode_state_topic @@ -164,19 +164,19 @@ void MQTTClimateComponent::setup() { }); } - if (traits.get_supports_away()) { + if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { this->subscribe(this->get_away_command_topic(), [this](const std::string &topic, const std::string &payload) { auto onoff = parse_on_off(payload.c_str()); auto call = this->device_->make_call(); switch (onoff) { case PARSE_ON: - call.set_away(true); + call.set_preset(CLIMATE_PRESET_AWAY); break; case PARSE_OFF: - call.set_away(false); + call.set_preset(CLIMATE_PRESET_HOME); break; case PARSE_TOGGLE: - call.set_away(!this->device_->away); + call.set_preset(this->device_->preset == CLIMATE_PRESET_AWAY ? CLIMATE_PRESET_HOME : CLIMATE_PRESET_AWAY); break; case PARSE_NONE: default: @@ -259,8 +259,8 @@ bool MQTTClimateComponent::publish_state_() { success = false; } - if (traits.get_supports_away()) { - std::string payload = ONOFF(this->device_->away); + if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { + std::string payload = ONOFF(this->device_->preset == CLIMATE_PRESET_AWAY); if (!this->publish(this->get_away_state_topic(), payload)) success = false; } diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index 0423ab27fc..b4660feb32 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -39,10 +39,14 @@ void PIDClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits PIDClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_heat_cool_mode(true); traits.set_supports_two_point_target_temperature(false); - traits.set_supports_cool_mode(this->supports_cool_()); - traits.set_supports_heat_mode(this->supports_heat_()); + + traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL}); + if (supports_cool_()) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_heat_()) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + traits.set_supports_action(true); return traits; } diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index a96c702473..65dd398197 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -50,12 +50,13 @@ void ThermostatClimate::control(const climate::ClimateCall &call) { this->target_temperature_low = *call.get_target_temperature_low(); if (call.get_target_temperature_high().has_value()) this->target_temperature_high = *call.get_target_temperature_high(); - if (call.get_away().has_value()) { + if (call.get_preset().has_value()) { // setup_complete_ blocks modifying/resetting the temps immediately after boot if (this->setup_complete_) { - this->change_away_(*call.get_away()); + this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY); } else { - this->away = *call.get_away(); + this->preset = *call.get_preset(); + ; } } // set point validation @@ -78,27 +79,51 @@ void ThermostatClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits ThermostatClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_auto_mode(this->supports_auto_); - traits.set_supports_heat_cool_mode(this->supports_heat_cool_); - traits.set_supports_cool_mode(this->supports_cool_); - traits.set_supports_dry_mode(this->supports_dry_); - traits.set_supports_fan_only_mode(this->supports_fan_only_); - traits.set_supports_heat_mode(this->supports_heat_); - traits.set_supports_fan_mode_on(this->supports_fan_mode_on_); - traits.set_supports_fan_mode_off(this->supports_fan_mode_off_); - traits.set_supports_fan_mode_auto(this->supports_fan_mode_auto_); - traits.set_supports_fan_mode_low(this->supports_fan_mode_low_); - traits.set_supports_fan_mode_medium(this->supports_fan_mode_medium_); - traits.set_supports_fan_mode_high(this->supports_fan_mode_high_); - traits.set_supports_fan_mode_middle(this->supports_fan_mode_middle_); - traits.set_supports_fan_mode_focus(this->supports_fan_mode_focus_); - traits.set_supports_fan_mode_diffuse(this->supports_fan_mode_diffuse_); - traits.set_supports_swing_mode_both(this->supports_swing_mode_both_); - traits.set_supports_swing_mode_horizontal(this->supports_swing_mode_horizontal_); - traits.set_supports_swing_mode_off(this->supports_swing_mode_off_); - traits.set_supports_swing_mode_vertical(this->supports_swing_mode_vertical_); + if (supports_auto_) + traits.add_supported_mode(climate::CLIMATE_MODE_AUTO); + if (supports_heat_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_dry_) + traits.add_supported_mode(climate::CLIMATE_MODE_DRY); + if (supports_fan_only_) + traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + + if (supports_fan_mode_on_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_ON); + if (supports_fan_mode_off_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_OFF); + if (supports_fan_mode_auto_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_AUTO); + if (supports_fan_mode_low_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_LOW); + if (supports_fan_mode_medium_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_MEDIUM); + if (supports_fan_mode_high_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_HIGH); + if (supports_fan_mode_middle_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_MIDDLE); + if (supports_fan_mode_focus_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_FOCUS); + if (supports_fan_mode_diffuse_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_DIFFUSE); + + if (supports_swing_mode_both_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH); + if (supports_swing_mode_horizontal_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_HORIZONTAL); + if (supports_swing_mode_off_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_OFF); + if (supports_swing_mode_vertical_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL); + + if (supports_away_) + traits.set_supported_presets({climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_AWAY}); + traits.set_supports_two_point_target_temperature(this->supports_two_points_); - traits.set_supports_away(this->supports_away_); traits.set_supports_action(true); return traits; } @@ -399,7 +424,7 @@ void ThermostatClimate::change_away_(bool away) { } else this->target_temperature = this->away_config_.default_temperature; } - this->away = away; + this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME; } void ThermostatClimate::set_normal_config(const ThermostatClimateTargetTempConfig &normal_config) { this->normal_config_ = normal_config; diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index 9c9cf9f3e7..fbd25ee03a 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -68,8 +68,10 @@ void TuyaClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits TuyaClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->current_temperature_id_.has_value()); - traits.set_supports_heat_mode(this->supports_heat_); - traits.set_supports_cool_mode(this->supports_cool_); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); traits.set_supports_action(true); return traits; } diff --git a/esphome/components/yashima/yashima.cpp b/esphome/components/yashima/yashima.cpp index e53e5fcccc..1505f4ead2 100644 --- a/esphome/components/yashima/yashima.cpp +++ b/esphome/components/yashima/yashima.cpp @@ -82,11 +82,14 @@ const uint32_t YASHIMA_CARRIER_FREQUENCY = 38000; climate::ClimateTraits YashimaClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->sensor_ != nullptr); - traits.set_supports_heat_cool_mode(true); - traits.set_supports_cool_mode(this->supports_cool_); - traits.set_supports_heat_mode(this->supports_heat_); + + traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL}); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + traits.set_supports_two_point_target_temperature(false); - traits.set_supports_away(false); traits.set_visual_min_temperature(YASHIMA_TEMP_MIN); traits.set_visual_max_temperature(YASHIMA_TEMP_MAX); traits.set_visual_temperature_step(1); diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py old mode 100644 new mode 100755 index 97cc95e556..05ad94a69d --- 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. From e3f36c033ec486fe8e1182c62dc1f28adb8d6dcf Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 21 Jun 2021 22:02:18 +0200 Subject: [PATCH 0967/1841] API raise minor version for climate changes (#1947) --- esphome/components/api/api_connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 68187f259e..a04dc0630b 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -615,7 +615,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { HelloResponse resp; resp.api_version_major = 1; - resp.api_version_minor = 4; + resp.api_version_minor = 5; resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; this->connection_state_ = ConnectionState::CONNECTED; return resp; From 6009c7edb40a6ee2315687c056dd6170d3d877bc Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 22 Jun 2021 10:53:10 +0200 Subject: [PATCH 0968/1841] Disallow power_save_mode NONE if used together with BLE (#1950) --- esphome/components/http_request/__init__.py | 19 ++++------- esphome/components/wifi/__init__.py | 36 ++++++++++++++++++++- esphome/final_validate.py | 24 ++++++++++++++ tests/test1.yaml | 2 +- 4 files changed, 66 insertions(+), 15 deletions(-) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index f4475060df..7dffdae27f 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -7,10 +7,7 @@ from esphome import automation from esphome.const import ( CONF_ID, CONF_TIMEOUT, - CONF_ESPHOME, CONF_METHOD, - CONF_ARDUINO_VERSION, - ARDUINO_VERSION_ESP8266, CONF_TRIGGER_ID, CONF_URL, ) @@ -78,23 +75,19 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -def validate_framework(config): - if CORE.is_esp32: +def validate_framework(value): + if not CORE.is_esp8266: + # only for ESP8266 return - # only for ESP8266 - path = [CONF_ESPHOME, CONF_ARDUINO_VERSION] - version: str = fv.full_config.get().get_config_for_path(path) - - reverse_map = {v: k for k, v in ARDUINO_VERSION_ESP8266.items()} - framework_version = reverse_map.get(version) + framework_version = fv.get_arduino_framework_version() if framework_version is None or framework_version == "dev": return if framework_version < "2.5.1": raise cv.Invalid( - "This component is not supported on arduino framework version below 2.5.1", - path=[cv.ROOT_CONFIG_PATH] + path, + "This component is not supported on arduino framework version below 2.5.1, ", + "please check esphome->arduino_version", ) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index fa28eaffd4..5a81d6a8f5 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -148,7 +148,41 @@ def final_validate(config): ) -FINAL_VALIDATE_SCHEMA = cv.Schema(final_validate) +def final_validate_power_esp32_ble(value): + if not CORE.is_esp32: + return + if value != "NONE": + # WiFi should be in modem sleep (!=NONE) with BLE coexistence + # https://docs.espressif.com/projects/esp-idf/en/v3.3.5/api-guides/wifi.html#station-sleep + return + framework_version = fv.get_arduino_framework_version() + if framework_version not in (None, "dev") and framework_version < "1.0.5": + # Only frameworks 1.0.5+ impacted + return + full = fv.full_config.get() + for conflicting in [ + "esp32_ble", + "esp32_ble_beacon", + "esp32_ble_server", + "esp32_ble_tracker", + ]: + if conflicting in full: + raise cv.Invalid( + f"power_save_mode NONE is incompatible with {conflicting}. " + f"Please remove the power save mode. See also " + f"https://github.com/esphome/issues/issues/2141#issuecomment-865688582" + ) + + +FINAL_VALIDATE_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_POWER_SAVE_MODE): final_validate_power_esp32_ble, + }, + extra=cv.ALLOW_EXTRA, + ), + final_validate, +) def _validate(config): diff --git a/esphome/final_validate.py b/esphome/final_validate.py index 50fdbaf3f4..47071b5391 100644 --- a/esphome/final_validate.py +++ b/esphome/final_validate.py @@ -4,6 +4,13 @@ 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): @@ -55,3 +62,20 @@ def id_declaration_match_schema(schema): 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/tests/test1.yaml b/tests/test1.yaml index 29df5857d3..08e1d63534 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -80,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 From 1d8c170f48a59a2604a7c8c022603162a7f22fb6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 23 Jun 2021 20:25:19 +0200 Subject: [PATCH 0969/1841] Add climate preset NONE again (#1951) --- esphome/components/api/api.proto | 15 ++++++----- esphome/components/api/api_pb2.cpp | 2 ++ esphome/components/api/api_pb2.h | 15 ++++++----- esphome/components/climate/climate_mode.cpp | 2 ++ esphome/components/climate/climate_mode.h | 30 +++++++++++---------- 5 files changed, 36 insertions(+), 28 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 87a7cf4749..a5bd9aec6d 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -710,13 +710,14 @@ enum ClimateAction { CLIMATE_ACTION_FAN = 6; } enum ClimatePreset { - CLIMATE_PRESET_HOME = 0; - CLIMATE_PRESET_AWAY = 1; - CLIMATE_PRESET_BOOST = 2; - CLIMATE_PRESET_COMFORT = 3; - CLIMATE_PRESET_ECO = 4; - CLIMATE_PRESET_SLEEP = 5; - CLIMATE_PRESET_ACTIVITY = 6; + CLIMATE_PRESET_NONE = 0; + CLIMATE_PRESET_HOME = 1; + CLIMATE_PRESET_AWAY = 2; + CLIMATE_PRESET_BOOST = 3; + CLIMATE_PRESET_COMFORT = 4; + CLIMATE_PRESET_ECO = 5; + CLIMATE_PRESET_SLEEP = 6; + CLIMATE_PRESET_ACTIVITY = 7; } message ListEntitiesClimateResponse { option (id) = 46; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 1e023f3988..e53ac2019a 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -192,6 +192,8 @@ template<> const char *proto_enum_to_string(enums::Climate } template<> const char *proto_enum_to_string(enums::ClimatePreset value) { switch (value) { + case enums::CLIMATE_PRESET_NONE: + return "CLIMATE_PRESET_NONE"; case enums::CLIMATE_PRESET_HOME: return "CLIMATE_PRESET_HOME"; case enums::CLIMATE_PRESET_AWAY: diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 7f37c0b94b..956cecdeb9 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -90,13 +90,14 @@ enum ClimateAction : uint32_t { CLIMATE_ACTION_FAN = 6, }; enum ClimatePreset : uint32_t { - CLIMATE_PRESET_HOME = 0, - CLIMATE_PRESET_AWAY = 1, - CLIMATE_PRESET_BOOST = 2, - CLIMATE_PRESET_COMFORT = 3, - CLIMATE_PRESET_ECO = 4, - CLIMATE_PRESET_SLEEP = 5, - CLIMATE_PRESET_ACTIVITY = 6, + CLIMATE_PRESET_NONE = 0, + CLIMATE_PRESET_HOME = 1, + CLIMATE_PRESET_AWAY = 2, + CLIMATE_PRESET_BOOST = 3, + CLIMATE_PRESET_COMFORT = 4, + CLIMATE_PRESET_ECO = 5, + CLIMATE_PRESET_SLEEP = 6, + CLIMATE_PRESET_ACTIVITY = 7, }; } // namespace enums diff --git a/esphome/components/climate/climate_mode.cpp b/esphome/components/climate/climate_mode.cpp index 099074a887..7a626942eb 100644 --- a/esphome/components/climate/climate_mode.cpp +++ b/esphome/components/climate/climate_mode.cpp @@ -84,6 +84,8 @@ const char *climate_swing_mode_to_string(ClimateSwingMode swing_mode) { const char *climate_preset_to_string(ClimatePreset preset) { switch (preset) { + case climate::CLIMATE_PRESET_NONE: + return "NONE"; case climate::CLIMATE_PRESET_HOME: return "HOME"; case climate::CLIMATE_PRESET_ECO: diff --git a/esphome/components/climate/climate_mode.h b/esphome/components/climate/climate_mode.h index 7afa2dae55..07fbf32b26 100644 --- a/esphome/components/climate/climate_mode.h +++ b/esphome/components/climate/climate_mode.h @@ -74,20 +74,22 @@ enum ClimateSwingMode : uint8_t { /// Enum for all modes a climate swing can be in enum ClimatePreset : uint8_t { - /// Preset is set to HOME - CLIMATE_PRESET_HOME = 0, - /// Preset is set to AWAY - CLIMATE_PRESET_AWAY = 1, - /// Preset is set to BOOST - CLIMATE_PRESET_BOOST = 2, - /// Preset is set to COMFORT - CLIMATE_PRESET_COMFORT = 3, - /// Preset is set to ECO - CLIMATE_PRESET_ECO = 4, - /// Preset is set to SLEEP - CLIMATE_PRESET_SLEEP = 5, - /// Preset is set to ACTIVITY - CLIMATE_PRESET_ACTIVITY = 6, + /// No preset is active + CLIMATE_PRESET_NONE = 0, + /// Device is in home preset + CLIMATE_PRESET_HOME = 1, + /// Device is in away preset + CLIMATE_PRESET_AWAY = 2, + /// Device is in boost preset + CLIMATE_PRESET_BOOST = 3, + /// Device is in comfort preset + CLIMATE_PRESET_COMFORT = 4, + /// Device is running an energy-saving preset + CLIMATE_PRESET_ECO = 5, + /// Device is prepared for sleep + CLIMATE_PRESET_SLEEP = 6, + /// Device is reacting to activity (e.g., movement sensors) + CLIMATE_PRESET_ACTIVITY = 7, }; /// Convert the given ClimateMode to a human-readable string. From 2bf70d7d003ebd857f2ca57d583c5b2ad86099af Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 23 Jun 2021 20:27:08 +0200 Subject: [PATCH 0970/1841] Compat argv parsing improvements (#1952) --- esphome/__main__.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 48f8bea083..232652db9f 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -514,14 +514,26 @@ def parse_args(argv): compat_parser.error = _raise - try: - result, unparsed = compat_parser.parse_known_args(argv[1:]) - last_option = len(argv) - len(unparsed) - 1 - len(result.configuration) - argv = argv[0:last_option] + [result.command] + result.configuration + unparsed - deprecated_argv_suggestion = argv - except argparse.ArgumentError: - # This is not an old-style command line, so we don't have to do anything. - deprecated_argv_suggestion = None + deprecated_argv_suggestion = None + + if ["dashboard", "config"] == argv[1:3]: + # this is most likely meant in new-style arg format. do not try compat parsing + pass + else: + try: + result, unparsed = compat_parser.parse_known_args(argv[1:]) + last_option = len(argv) - len(unparsed) - 1 - len(result.configuration) + unparsed = [ + "--device" if arg in ("--upload-port", "--serial-port") else arg + for arg in unparsed + ] + argv = ( + argv[0:last_option] + [result.command] + result.configuration + unparsed + ) + deprecated_argv_suggestion = argv + except argparse.ArgumentError: + # This is not an old-style command line, so we don't have to do anything. + pass # And continue on with regular parsing parser = argparse.ArgumentParser( From 96721f305f7e8c0e514ec993d35b70869249b33a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 24 Jun 2021 12:38:59 +1200 Subject: [PATCH 0971/1841] Bump dashboard to 20210623.0 (#1958) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 12500d57b7..cc9059f0d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210622.0 +esphome-dashboard==20210623.0 From 8600620305b12319a8b1c1ff74ef9faa60b087cc Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 24 Jun 2021 12:49:45 +1200 Subject: [PATCH 0972/1841] Bump version to v1.19.4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index de33ebc2de..02d0491cfe 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "3" +PATCH_VERSION = "4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 3b940b1c04ebabf520d1494a36c719e5ab27caad Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 25 Jun 2021 07:09:07 +1200 Subject: [PATCH 0973/1841] Set is_valid to true straight away when min_length is 0 (#1960) --- esphome/components/binary_sensor/automation.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/binary_sensor/automation.cpp b/esphome/components/binary_sensor/automation.cpp index a333b33397..ce082aafb3 100644 --- a/esphome/components/binary_sensor/automation.cpp +++ b/esphome/components/binary_sensor/automation.cpp @@ -80,6 +80,10 @@ void binary_sensor::MultiClickTrigger::schedule_cooldown_() { this->cancel_timeout("is_not_valid"); } void binary_sensor::MultiClickTrigger::schedule_is_valid_(uint32_t min_length) { + if (min_length == 0) { + this->is_valid_ = true; + return; + } this->is_valid_ = false; this->set_timeout("is_valid", min_length, [this]() { ESP_LOGV(TAG, "Multi Click: You can now %s the button.", this->parent_->state ? "RELEASE" : "PRESS"); From 964ab654974ebb03ebd08949259fe4844537cd0c Mon Sep 17 00:00:00 2001 From: bazuchan Date: Mon, 28 Jun 2021 22:26:30 +0300 Subject: [PATCH 0974/1841] Climate component for Ballu air conditioners with remote model YKR-K/002E (#1939) --- CODEOWNERS | 1 + esphome/components/ballu/__init__.py | 0 esphome/components/ballu/ballu.cpp | 239 +++++++++++++++++++++++++++ esphome/components/ballu/ballu.h | 31 ++++ esphome/components/ballu/climate.py | 21 +++ 5 files changed, 292 insertions(+) create mode 100644 esphome/components/ballu/__init__.py create mode 100644 esphome/components/ballu/ballu.cpp create mode 100644 esphome/components/ballu/ballu.h create mode 100644 esphome/components/ballu/climate.py diff --git a/CODEOWNERS b/CODEOWNERS index 0594a60ef6..f242cc6d9d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -19,6 +19,7 @@ esphome/components/api/* @OttoWinter esphome/components/async_tcp/* @OttoWinter esphome/components/atc_mithermometer/* @ahpohl esphome/components/b_parasite/* @rbaron +esphome/components/ballu/* @bazuchan esphome/components/bang_bang/* @OttoWinter esphome/components/binary_sensor/* @esphome/core esphome/components/ble_client/* @buxtronix diff --git a/esphome/components/ballu/__init__.py b/esphome/components/ballu/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/ballu/ballu.cpp b/esphome/components/ballu/ballu.cpp new file mode 100644 index 0000000000..e2703a79fb --- /dev/null +++ b/esphome/components/ballu/ballu.cpp @@ -0,0 +1,239 @@ +#include "ballu.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ballu { + +static const char *const TAG = "ballu.climate"; + +const uint16_t BALLU_HEADER_MARK = 9000; +const uint16_t BALLU_HEADER_SPACE = 4500; +const uint16_t BALLU_BIT_MARK = 575; +const uint16_t BALLU_ONE_SPACE = 1675; +const uint16_t BALLU_ZERO_SPACE = 550; + +const uint32_t BALLU_CARRIER_FREQUENCY = 38000; + +const uint8_t BALLU_STATE_LENGTH = 13; + +const uint8_t BALLU_AUTO = 0; +const uint8_t BALLU_COOL = 0x20; +const uint8_t BALLU_DRY = 0x40; +const uint8_t BALLU_HEAT = 0x80; +const uint8_t BALLU_FAN = 0xc0; + +const uint8_t BALLU_FAN_AUTO = 0xa0; +const uint8_t BALLU_FAN_HIGH = 0x20; +const uint8_t BALLU_FAN_MED = 0x40; +const uint8_t BALLU_FAN_LOW = 0x60; + +const uint8_t BALLU_SWING_VER = 0x07; +const uint8_t BALLU_SWING_HOR = 0xe0; +const uint8_t BALLU_POWER = 0x20; + +void BalluClimate::transmit_state() { + uint8_t remote_state[BALLU_STATE_LENGTH] = {0}; + + auto temp = (uint8_t) roundf(clamp(this->target_temperature, YKR_K_002E_TEMP_MIN, YKR_K_002E_TEMP_MAX)); + auto swing_ver = + ((this->swing_mode == climate::CLIMATE_SWING_VERTICAL) || (this->swing_mode == climate::CLIMATE_SWING_BOTH)); + auto swing_hor = + ((this->swing_mode == climate::CLIMATE_SWING_HORIZONTAL) || (this->swing_mode == climate::CLIMATE_SWING_BOTH)); + + remote_state[0] = 0xc3; + remote_state[1] = ((temp - 8) << 3) | (swing_ver ? 0 : BALLU_SWING_VER); + remote_state[2] = swing_hor ? 0 : BALLU_SWING_HOR; + remote_state[9] = (this->mode == climate::CLIMATE_MODE_OFF) ? 0 : BALLU_POWER; + remote_state[11] = 0x1e; + + // Fan speed + switch (this->fan_mode.value()) { + case climate::CLIMATE_FAN_HIGH: + remote_state[4] |= BALLU_FAN_HIGH; + break; + case climate::CLIMATE_FAN_MEDIUM: + remote_state[4] |= BALLU_FAN_MED; + break; + case climate::CLIMATE_FAN_LOW: + remote_state[4] |= BALLU_FAN_LOW; + break; + case climate::CLIMATE_FAN_AUTO: + remote_state[4] |= BALLU_FAN_AUTO; + break; + default: + break; + } + + // Mode + switch (this->mode) { + case climate::CLIMATE_MODE_AUTO: + remote_state[6] |= BALLU_AUTO; + break; + case climate::CLIMATE_MODE_HEAT: + remote_state[6] |= BALLU_HEAT; + break; + case climate::CLIMATE_MODE_COOL: + remote_state[6] |= BALLU_COOL; + break; + case climate::CLIMATE_MODE_DRY: + remote_state[6] |= BALLU_DRY; + break; + case climate::CLIMATE_MODE_FAN_ONLY: + remote_state[6] |= BALLU_FAN; + break; + case climate::CLIMATE_MODE_OFF: + remote_state[6] |= BALLU_AUTO; + default: + break; + } + + // Checksum + for (uint8_t i = 0; i < BALLU_STATE_LENGTH - 1; i++) + remote_state[12] += remote_state[i]; + + ESP_LOGV(TAG, "Sending: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", remote_state[0], + remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5], remote_state[6], + remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11], remote_state[12]); + + // Send code + auto transmit = this->transmitter_->transmit(); + auto data = transmit.get_data(); + + data->set_carrier_frequency(38000); + + // Header + data->mark(BALLU_HEADER_MARK); + data->space(BALLU_HEADER_SPACE); + // Data + for (uint8_t i : remote_state) { + for (uint8_t j = 0; j < 8; j++) { + data->mark(BALLU_BIT_MARK); + bool bit = i & (1 << j); + data->space(bit ? BALLU_ONE_SPACE : BALLU_ZERO_SPACE); + } + } + // Footer + data->mark(BALLU_BIT_MARK); + + transmit.perform(); +} + +bool BalluClimate::on_receive(remote_base::RemoteReceiveData data) { + // Validate header + if (!data.expect_item(BALLU_HEADER_MARK, BALLU_HEADER_SPACE)) { + ESP_LOGV(TAG, "Header fail"); + return false; + } + + uint8_t remote_state[BALLU_STATE_LENGTH] = {0}; + // Read all bytes. + for (int i = 0; i < BALLU_STATE_LENGTH; i++) { + // Read bit + for (int j = 0; j < 8; j++) { + if (data.expect_item(BALLU_BIT_MARK, BALLU_ONE_SPACE)) + remote_state[i] |= 1 << j; + + else if (!data.expect_item(BALLU_BIT_MARK, BALLU_ZERO_SPACE)) { + ESP_LOGV(TAG, "Byte %d bit %d fail", i, j); + return false; + } + } + + ESP_LOGVV(TAG, "Byte %d %02X", i, remote_state[i]); + } + // Validate footer + if (!data.expect_mark(BALLU_BIT_MARK)) { + ESP_LOGV(TAG, "Footer fail"); + return false; + } + + uint8_t checksum = 0; + // Calculate checksum and compare with signal value. + for (uint8_t i = 0; i < BALLU_STATE_LENGTH - 1; i++) + checksum += remote_state[i]; + + if (checksum != remote_state[BALLU_STATE_LENGTH - 1]) { + ESP_LOGVV(TAG, "Checksum fail"); + return false; + } + + ESP_LOGV(TAG, "Received: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", remote_state[0], + remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5], remote_state[6], + remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11], remote_state[12]); + + // verify header remote code + if (remote_state[0] != 0xc3) + return false; + + // powr on/off button + ESP_LOGV(TAG, "Power: %02X", (remote_state[9] & BALLU_POWER)); + + if ((remote_state[9] & BALLU_POWER) != BALLU_POWER) { + this->mode = climate::CLIMATE_MODE_OFF; + } else { + auto mode = remote_state[6] & 0xe0; + ESP_LOGV(TAG, "Mode: %02X", mode); + switch (mode) { + case BALLU_HEAT: + this->mode = climate::CLIMATE_MODE_HEAT; + break; + case BALLU_COOL: + this->mode = climate::CLIMATE_MODE_COOL; + break; + case BALLU_DRY: + this->mode = climate::CLIMATE_MODE_DRY; + break; + case BALLU_FAN: + this->mode = climate::CLIMATE_MODE_FAN_ONLY; + break; + case BALLU_AUTO: + this->mode = climate::CLIMATE_MODE_AUTO; + break; + } + } + + // Set received temp + int temp = remote_state[1] & 0xf8; + ESP_LOGVV(TAG, "Temperature Raw: %02X", temp); + temp = ((uint8_t) temp >> 3) + 8; + ESP_LOGVV(TAG, "Temperature Climate: %u", temp); + this->target_temperature = temp; + + // Set received fan speed + auto fan = remote_state[4] & 0xe0; + ESP_LOGVV(TAG, "Fan: %02X", fan); + switch (fan) { + case BALLU_FAN_HIGH: + this->fan_mode = climate::CLIMATE_FAN_HIGH; + break; + case BALLU_FAN_MED: + this->fan_mode = climate::CLIMATE_FAN_MEDIUM; + break; + case BALLU_FAN_LOW: + this->fan_mode = climate::CLIMATE_FAN_LOW; + break; + case BALLU_FAN_AUTO: + default: + this->fan_mode = climate::CLIMATE_FAN_AUTO; + break; + } + + // Set received swing status + ESP_LOGVV(TAG, "Swing status: %02X %02X", remote_state[1] & BALLU_SWING_VER, remote_state[2] & BALLU_SWING_HOR); + if (((remote_state[1] & BALLU_SWING_VER) != BALLU_SWING_VER) && + ((remote_state[2] & BALLU_SWING_HOR) != BALLU_SWING_HOR)) { + this->swing_mode = climate::CLIMATE_SWING_BOTH; + } else if ((remote_state[1] & BALLU_SWING_VER) != BALLU_SWING_VER) { + this->swing_mode = climate::CLIMATE_SWING_VERTICAL; + } else if ((remote_state[2] & BALLU_SWING_HOR) != BALLU_SWING_HOR) { + this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; + } else { + this->swing_mode = climate::CLIMATE_SWING_OFF; + } + + this->publish_state(); + return true; +} + +} // namespace ballu +} // namespace esphome diff --git a/esphome/components/ballu/ballu.h b/esphome/components/ballu/ballu.h new file mode 100644 index 0000000000..80a4699cfb --- /dev/null +++ b/esphome/components/ballu/ballu.h @@ -0,0 +1,31 @@ +#pragma once + +#include "esphome/components/climate_ir/climate_ir.h" + +namespace esphome { +namespace ballu { + +// Support for Ballu air conditioners with YKR-K/002E remote + +// Temperature +const float YKR_K_002E_TEMP_MIN = 16.0; +const float YKR_K_002E_TEMP_MAX = 32.0; + +class BalluClimate : public climate_ir::ClimateIR { + public: + BalluClimate() + : climate_ir::ClimateIR(YKR_K_002E_TEMP_MIN, YKR_K_002E_TEMP_MAX, 1.0f, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, + climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {} + + protected: + /// Transmit via IR the state of this climate controller. + void transmit_state() override; + /// Handle received IR Buffer + bool on_receive(remote_base::RemoteReceiveData data) override; +}; + +} // namespace ballu +} // namespace esphome diff --git a/esphome/components/ballu/climate.py b/esphome/components/ballu/climate.py new file mode 100644 index 0000000000..82e9fead1e --- /dev/null +++ b/esphome/components/ballu/climate.py @@ -0,0 +1,21 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate_ir +from esphome.const import CONF_ID + +AUTO_LOAD = ["climate_ir"] +CODEOWNERS = ["@bazuchan"] + +ballu_ns = cg.esphome_ns.namespace("ballu") +BalluClimate = ballu_ns.class_("BalluClimate", climate_ir.ClimateIR) + +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(BalluClimate), + } +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await climate_ir.register_climate_ir(var, config) From d604321f37d33645fb7801411c6b1987d701da64 Mon Sep 17 00:00:00 2001 From: Frederik Gladhorn Date: Wed, 30 Jun 2021 20:36:48 +0200 Subject: [PATCH 0975/1841] Simplify initializing glyph_data (#1970) Make it easier to read the initialization with zeros, no loop required. --- esphome/components/font/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 11bbedd80b..16a7d8a612 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -110,7 +110,7 @@ async def to_code(config): _, (offset_x, offset_y) = font.font.getsize(glyph) width, height = mask.size width8 = ((width + 7) // 8) * 8 - glyph_data = [0 for _ in range(height * width8 // 8)] # noqa: F812 + glyph_data = [0] * (height * width8 // 8) for y in range(height): for x in range(width): if not mask.getpixel((x, y)): From 36861595f1aafb920adb05b5df2a8e8a1cc7ff78 Mon Sep 17 00:00:00 2001 From: definitio <37266727+definitio@users.noreply.github.com> Date: Thu, 1 Jul 2021 16:36:01 +0300 Subject: [PATCH 0976/1841] Add device_class support for MQTT integration (#1832) --- esphome/components/mqtt/mqtt_sensor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index 07c7fdc00d..c106a95902 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -46,6 +46,9 @@ void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } std::string MQTTSensorComponent::friendly_name() const { return this->sensor_->get_name(); } void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { + if (!this->sensor_->get_device_class().empty()) + root["device_class"] = this->sensor_->get_device_class(); + if (!this->sensor_->get_unit_of_measurement().empty()) root["unit_of_measurement"] = this->sensor_->get_unit_of_measurement(); From d5278351da993ad24f9e7523a0d69f75457ea0dd Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 2 Jul 2021 15:42:36 +0200 Subject: [PATCH 0977/1841] Rename master branch to release (#1976) --- .github/workflows/ci-docker.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- .pre-commit-config.yaml | 2 +- README.md | 2 +- esphome/core/config.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index b5f8b7b0e0..91ec88aeb3 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -3,7 +3,7 @@ name: CI for docker images # Only run when docker paths change on: push: - branches: [dev, beta, master] + branches: [dev, beta, release] paths: - 'docker/**' - '.github/workflows/**' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a27cbe67b0..121b3f1339 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: push: # On dev branch release-dev already performs CI checks # On other branches the `pull_request` trigger will be used - branches: [beta, master] + branches: [beta, release] pull_request: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9523a2164c..383f2878e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -307,4 +307,4 @@ jobs: -X POST \ -H "Accept: application/vnd.github.v3+json" \ https://api.github.com/repos/esphome/hassio/actions/workflows/bump-version.yml/dispatches \ - -d "{\"ref\":\"master\",\"inputs\":{\"version\":\"$TAG\"}}" + -d "{\"ref\":\"main\",\"inputs\":{\"version\":\"$TAG\"}}" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1e56aa17cf..a821c21fa7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,5 +23,5 @@ repos: - id: no-commit-to-branch args: - --branch=dev - - --branch=master + - --branch=release - --branch=beta diff --git a/README.md b/README.md index f21e748d40..bb6fb37d3a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ESPHome [![Build Status](https://travis-ci.org/esphome/esphome.svg?branch=master)](https://travis-ci.org/esphome/esphome) [![Discord Chat](https://img.shields.io/discord/429907082951524364.svg)](https://discord.gg/KhAMKrd) [![GitHub release](https://img.shields.io/github/release/esphome/esphome.svg)](https://GitHub.com/esphome/esphome/releases/) +# ESPHome [![Discord Chat](https://img.shields.io/discord/429907082951524364.svg)](https://discord.gg/KhAMKrd) [![GitHub release](https://img.shields.io/github/release/esphome/esphome.svg)](https://GitHub.com/esphome/esphome/releases/) [![ESPHome Logo](https://esphome.io/_images/logo-text.png)](https://esphome.io/) diff --git a/esphome/core/config.py b/esphome/core/config.py index fd4b7088cc..f55acc1892 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -77,7 +77,7 @@ PLATFORMIO_ESP8266_LUT = { # recommended version as otherwise a bunch of devices could be bricked # * The docker images need to be updated to ship the new recommended version, in order not # to DDoS platformio servers. - # Update this file: https://github.com/esphome/esphome-docker-base/blob/master/platformio.ini + # Update this file: https://github.com/esphome/esphome-docker-base/blob/main/platformio.ini "RECOMMENDED": ARDUINO_VERSION_ESP8266["2.7.4"], "LATEST": "espressif8266", "DEV": ARDUINO_VERSION_ESP8266["dev"], From 9e400a785700285e837d50c9072b33a80507098a Mon Sep 17 00:00:00 2001 From: Trevor North Date: Sun, 4 Jul 2021 12:47:22 +0100 Subject: [PATCH 0978/1841] Fix tuya fan speed send (#1978) --- esphome/components/tuya/fan/tuya_fan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp index 56efcf2d77..ed92713e52 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -80,7 +80,7 @@ void TuyaFan::write_state() { } if (this->speed_id_.has_value()) { ESP_LOGV(TAG, "Setting speed: %d", this->fan_->speed); - this->parent_->set_datapoint_value(*this->speed_id_, this->fan_->speed); + this->parent_->set_datapoint_value(*this->speed_id_, this->fan_->speed - 1); } } From 86ac7f3a59b0eca2fa70db6894ad7e38976a7d6b Mon Sep 17 00:00:00 2001 From: Paul Doidge Date: Sun, 4 Jul 2021 21:39:18 +0200 Subject: [PATCH 0979/1841] Time Based Cover: Fixed apparent race condition on ESP32 chips (#1984) --- esphome/components/time_based/time_based_cover.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/time_based/time_based_cover.cpp b/esphome/components/time_based/time_based_cover.cpp index 066004f014..60a33aa82a 100644 --- a/esphome/components/time_based/time_based_cover.cpp +++ b/esphome/components/time_based/time_based_cover.cpp @@ -115,13 +115,13 @@ void TimeBasedCover::start_direction_(CoverOperation dir) { this->current_operation = dir; - this->stop_prev_trigger_(); - trig->trigger(); - this->prev_command_trigger_ = trig; - const uint32_t now = millis(); this->start_dir_time_ = now; this->last_recompute_time_ = now; + + this->stop_prev_trigger_(); + trig->trigger(); + this->prev_command_trigger_ = trig; } void TimeBasedCover::recompute_position_() { if (this->current_operation == COVER_OPERATION_IDLE) From 4c4099966ad39d8ffe21e97bc2d24700aa8c8ffd Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 5 Jul 2021 00:04:18 +0200 Subject: [PATCH 0980/1841] Fix invalid escape sequences in regex (#1814) --- esphome/components/logger/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 2b571b817b..8d79c96f63 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -205,8 +205,7 @@ def maybe_simple_message(schema): def validate_printf(value): # https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python - # pylint: disable=anomalous-backslash-in-string - cfmt = """\ + cfmt = r""" ( # start of capture group 1 % # literal "%" (?:[-+0 #]{0,5}) # optional flags From 8ca34f7098f6a54a0c01ead55ca403dee4d3a3be Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Mon, 5 Jul 2021 00:10:59 +0200 Subject: [PATCH 0981/1841] Bump hypothesis from 5.21.0 to 5.49.0 (#1753) --- requirements_test.txt | 2 +- tests/unit_tests/strategies.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_test.txt b/requirements_test.txt index 15593a8e12..9f7224a86d 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -10,4 +10,4 @@ pytest-cov==2.11.1 pytest-mock==3.5.1 pytest-asyncio==0.14.0 asyncmock==0.4.2 -hypothesis==5.21.0 +hypothesis==5.49.0 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. From 52d19fa43dd5e9ee7e09102aa726aa7bcfb56606 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jul 2021 10:27:13 +1200 Subject: [PATCH 0982/1841] Bump pytest-mock from 3.5.1 to 3.6.1 (#1754) Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 3.5.1 to 3.6.1. - [Release notes](https://github.com/pytest-dev/pytest-mock/releases) - [Changelog](https://github.com/pytest-dev/pytest-mock/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-mock/compare/v3.5.1...v3.6.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 9f7224a86d..26755921cd 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -7,7 +7,7 @@ pre-commit # Unit tests pytest==6.2.4 pytest-cov==2.11.1 -pytest-mock==3.5.1 +pytest-mock==3.6.1 pytest-asyncio==0.14.0 asyncmock==0.4.2 hypothesis==5.49.0 From d31040f5d8023a81a1011fe2b55e63992724d917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Panella?= Date: Sun, 4 Jul 2021 18:09:09 -0500 Subject: [PATCH 0983/1841] hlw8012: fix constants for BL0937 (#1973) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/hlw8012/hlw8012.cpp | 34 +++++++++++++++++--------- esphome/components/hlw8012/hlw8012.h | 12 +++++++++ esphome/components/hlw8012/sensor.py | 11 +++++++++ tests/test1.yaml | 1 + 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/esphome/components/hlw8012/hlw8012.cpp b/esphome/components/hlw8012/hlw8012.cpp index 356dbd0bf4..79c25a45b0 100644 --- a/esphome/components/hlw8012/hlw8012.cpp +++ b/esphome/components/hlw8012/hlw8012.cpp @@ -6,15 +6,32 @@ namespace hlw8012 { static const char *const TAG = "hlw8012"; +// valid for HLW8012 and CSE7759 static const uint32_t HLW8012_CLOCK_FREQUENCY = 3579000; -static const float HLW8012_REFERENCE_VOLTAGE = 2.43f; void HLW8012Component::setup() { + float reference_voltage = 0; ESP_LOGCONFIG(TAG, "Setting up HLW8012..."); this->sel_pin_->setup(); this->sel_pin_->digital_write(this->current_mode_); this->cf_store_.pulse_counter_setup(this->cf_pin_); this->cf1_store_.pulse_counter_setup(this->cf1_pin_); + + // Initialize multipliers + if (this->sensor_model_ == HLW8012_SENSOR_MODEL_BL0937) { + reference_voltage = 1.218f; + this->power_multiplier_ = + reference_voltage * reference_voltage * this->voltage_divider_ / this->current_resistor_ / 1721506.0f; + this->current_multiplier_ = reference_voltage / this->current_resistor_ / 94638.0f; + this->voltage_multiplier_ = reference_voltage * this->voltage_divider_ / 15397.0f; + } else { + // HLW8012 and CSE7759 have same reference specs + reference_voltage = 2.43f; + this->power_multiplier_ = reference_voltage * reference_voltage * this->voltage_divider_ / this->current_resistor_ * + 64.0f / 24.0f / HLW8012_CLOCK_FREQUENCY; + this->current_multiplier_ = reference_voltage / this->current_resistor_ * 512.0f / 24.0f / HLW8012_CLOCK_FREQUENCY; + this->voltage_multiplier_ = reference_voltage * this->voltage_divider_ * 256.0f / HLW8012_CLOCK_FREQUENCY; + } } void HLW8012Component::dump_config() { ESP_LOGCONFIG(TAG, "HLW8012:"); @@ -49,25 +66,18 @@ void HLW8012Component::update() { return; } - const float v_ref_squared = HLW8012_REFERENCE_VOLTAGE * HLW8012_REFERENCE_VOLTAGE; - const float power_multiplier_micros = - 64000000.0f * v_ref_squared * this->voltage_divider_ / this->current_resistor_ / 24.0f / HLW8012_CLOCK_FREQUENCY; - float power = cf_hz * power_multiplier_micros / 1000000.0f; + float power = cf_hz * this->power_multiplier_; if (this->change_mode_at_ != 0) { // Only read cf1 after one cycle. Apparently it's quite unstable after being changed. if (this->current_mode_) { - const float current_multiplier_micros = - 512000000.0f * HLW8012_REFERENCE_VOLTAGE / this->current_resistor_ / 24.0f / HLW8012_CLOCK_FREQUENCY; - float current = cf1_hz * current_multiplier_micros / 1000000.0f; + float current = cf1_hz * this->current_multiplier_; ESP_LOGD(TAG, "Got power=%.1fW, current=%.1fA", power, current); if (this->current_sensor_ != nullptr) { this->current_sensor_->publish_state(current); } } else { - const float voltage_multiplier_micros = - 256000000.0f * HLW8012_REFERENCE_VOLTAGE * this->voltage_divider_ / HLW8012_CLOCK_FREQUENCY; - float voltage = cf1_hz * voltage_multiplier_micros / 1000000.0f; + float voltage = cf1_hz * this->voltage_multiplier_; ESP_LOGD(TAG, "Got power=%.1fW, voltage=%.1fV", power, voltage); if (this->voltage_sensor_ != nullptr) { this->voltage_sensor_->publish_state(voltage); @@ -81,7 +91,7 @@ void HLW8012Component::update() { if (this->energy_sensor_ != nullptr) { cf_total_pulses_ += raw_cf; - float energy = cf_total_pulses_ * power_multiplier_micros / 3600 / 1000000.0f; + float energy = cf_total_pulses_ * this->power_multiplier_ / 3600; this->energy_sensor_->publish_state(energy); } diff --git a/esphome/components/hlw8012/hlw8012.h b/esphome/components/hlw8012/hlw8012.h index af1f2e9a8c..52fb03c020 100644 --- a/esphome/components/hlw8012/hlw8012.h +++ b/esphome/components/hlw8012/hlw8012.h @@ -10,6 +10,12 @@ namespace hlw8012 { enum HLW8012InitialMode { HLW8012_INITIAL_MODE_CURRENT = 0, HLW8012_INITIAL_MODE_VOLTAGE }; +enum HLW8012SensorModels { + HLW8012_SENSOR_MODEL_HLW8012 = 0, + HLW8012_SENSOR_MODEL_CSE7759, + HLW8012_SENSOR_MODEL_BL0937 +}; + class HLW8012Component : public PollingComponent { public: void setup() override; @@ -20,6 +26,7 @@ class HLW8012Component : public PollingComponent { void set_initial_mode(HLW8012InitialMode initial_mode) { current_mode_ = initial_mode == HLW8012_INITIAL_MODE_CURRENT; } + void set_sensor_model(HLW8012SensorModels sensor_model) { sensor_model_ = sensor_model; } void set_change_mode_every(uint32_t change_mode_every) { change_mode_every_ = change_mode_every; } void set_current_resistor(float current_resistor) { current_resistor_ = current_resistor; } void set_voltage_divider(float voltage_divider) { voltage_divider_ = voltage_divider; } @@ -38,6 +45,7 @@ class HLW8012Component : public PollingComponent { uint32_t change_mode_every_{8}; float current_resistor_{0.001}; float voltage_divider_{2351}; + HLW8012SensorModels sensor_model_{HLW8012_SENSOR_MODEL_HLW8012}; uint64_t cf_total_pulses_{0}; GPIOPin *sel_pin_; GPIOPin *cf_pin_; @@ -48,6 +56,10 @@ class HLW8012Component : public PollingComponent { sensor::Sensor *current_sensor_{nullptr}; sensor::Sensor *power_sensor_{nullptr}; sensor::Sensor *energy_sensor_{nullptr}; + + float voltage_multiplier_{0.0f}; + float current_multiplier_{0.0f}; + float power_multiplier_{0.0f}; }; } // namespace hlw8012 diff --git a/esphome/components/hlw8012/sensor.py b/esphome/components/hlw8012/sensor.py index 6454a9fcc9..e24e995eba 100644 --- a/esphome/components/hlw8012/sensor.py +++ b/esphome/components/hlw8012/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( CONF_POWER, CONF_ENERGY, CONF_SEL_PIN, + CONF_MODEL, CONF_VOLTAGE, CONF_VOLTAGE_DIVIDER, DEVICE_CLASS_CURRENT, @@ -31,11 +32,19 @@ AUTO_LOAD = ["pulse_counter"] hlw8012_ns = cg.esphome_ns.namespace("hlw8012") HLW8012Component = hlw8012_ns.class_("HLW8012Component", cg.PollingComponent) HLW8012InitialMode = hlw8012_ns.enum("HLW8012InitialMode") +HLW8012SensorModels = hlw8012_ns.enum("HLW8012SensorModels") + INITIAL_MODES = { CONF_CURRENT: HLW8012InitialMode.HLW8012_INITIAL_MODE_CURRENT, CONF_VOLTAGE: HLW8012InitialMode.HLW8012_INITIAL_MODE_VOLTAGE, } +MODELS = { + "HLW8012": HLW8012SensorModels.HLW8012_SENSOR_MODEL_HLW8012, + "CSE7759": HLW8012SensorModels.HLW8012_SENSOR_MODEL_CSE7759, + "BL0937": HLW8012SensorModels.HLW8012_SENSOR_MODEL_BL0937, +} + CONF_CF1_PIN = "cf1_pin" CONF_CF_PIN = "cf_pin" CONFIG_SCHEMA = cv.Schema( @@ -62,6 +71,7 @@ CONFIG_SCHEMA = cv.Schema( ), cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance, cv.Optional(CONF_VOLTAGE_DIVIDER, default=2351): cv.positive_float, + cv.Optional(CONF_MODEL, default="HLW8012"): cv.enum(MODELS, upper=True), cv.Optional(CONF_CHANGE_MODE_EVERY, default=8): cv.All( cv.uint32_t, cv.Range(min=1) ), @@ -99,3 +109,4 @@ async def to_code(config): cg.add(var.set_voltage_divider(config[CONF_VOLTAGE_DIVIDER])) cg.add(var.set_change_mode_every(config[CONF_CHANGE_MODE_EVERY])) cg.add(var.set_initial_mode(INITIAL_MODES[config[CONF_INITIAL_MODE]])) + cg.add(var.set_sensor_model(config[CONF_MODEL])) diff --git a/tests/test1.yaml b/tests/test1.yaml index 08e1d63534..78dc40cf8e 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -514,6 +514,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' From ab31117bf329c388cd4e641c0e10ff2b8a63d291 Mon Sep 17 00:00:00 2001 From: buxtronix Date: Mon, 5 Jul 2021 09:59:12 +1000 Subject: [PATCH 0984/1841] Anova ble component (#1752) Co-authored-by: Ben Buxton Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/anova/__init__.py | 0 esphome/components/anova/anova.cpp | 140 ++++++++++++++++++++++++ esphome/components/anova/anova.h | 50 +++++++++ esphome/components/anova/anova_base.cpp | 119 ++++++++++++++++++++ esphome/components/anova/anova_base.h | 79 +++++++++++++ esphome/components/anova/climate.py | 25 +++++ tests/test1.yaml | 20 ++++ 8 files changed, 434 insertions(+) create mode 100644 esphome/components/anova/__init__.py create mode 100644 esphome/components/anova/anova.cpp create mode 100644 esphome/components/anova/anova.h create mode 100644 esphome/components/anova/anova_base.cpp create mode 100644 esphome/components/anova/anova_base.h create mode 100644 esphome/components/anova/climate.py diff --git a/CODEOWNERS b/CODEOWNERS index f242cc6d9d..a1c7bf9bfd 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -15,6 +15,7 @@ esphome/components/ac_dimmer/* @glmnet esphome/components/adc/* @esphome/core esphome/components/addressable_light/* @justfalter esphome/components/animation/* @syndlex +esphome/components/anova/* @buxtronix esphome/components/api/* @OttoWinter esphome/components/async_tcp/* @OttoWinter esphome/components/atc_mithermometer/* @ahpohl diff --git a/esphome/components/anova/__init__.py b/esphome/components/anova/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp new file mode 100644 index 0000000000..c4b08ca6b5 --- /dev/null +++ b/esphome/components/anova/anova.cpp @@ -0,0 +1,140 @@ +#include "anova.h" +#include "esphome/core/log.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace anova { + +static const char *TAG = "anova"; + +using namespace esphome::climate; + +void Anova::dump_config() { LOG_CLIMATE("", "Anova BLE Cooker", this); } + +void Anova::setup() { + this->codec_ = new AnovaCodec(); + this->current_request_ = 0; +} + +void Anova::loop() {} + +void Anova::control(const ClimateCall &call) { + if (call.get_mode().has_value()) { + ClimateMode mode = *call.get_mode(); + AnovaPacket *pkt; + switch (mode) { + case climate::CLIMATE_MODE_OFF: + pkt = this->codec_->get_stop_request(); + break; + case climate::CLIMATE_MODE_HEAT: + pkt = this->codec_->get_start_request(); + break; + default: + ESP_LOGW(TAG, "Unsupported mode: %d", mode); + return; + } + auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, + pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + } + if (call.get_target_temperature().has_value()) { + auto pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature()); + auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, + pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + } +} + +void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { + switch (event) { + case ESP_GATTC_DISCONNECT_EVT: { + this->current_temperature = NAN; + this->target_temperature = NAN; + this->publish_state(); + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: { + auto chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID); + if (chr == nullptr) { + ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str()); + break; + } + this->char_handle_ = chr->handle; + + auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle); + if (status) { + ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status); + } + break; + } + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + this->node_state = espbt::ClientState::Established; + this->current_request_ = 0; + this->update(); + break; + } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.handle != this->char_handle_) + break; + this->codec_->decode(param->notify.value, param->notify.value_len); + if (this->codec_->has_target_temp()) { + this->target_temperature = this->codec_->target_temp_; + } + if (this->codec_->has_current_temp()) { + this->current_temperature = this->codec_->current_temp_; + } + if (this->codec_->has_running()) { + this->mode = this->codec_->running_ ? climate::CLIMATE_MODE_HEAT : climate::CLIMATE_MODE_OFF; + } + this->publish_state(); + + if (this->current_request_ > 0) { + AnovaPacket *pkt = nullptr; + switch (this->current_request_++) { + case 1: + pkt = this->codec_->get_read_target_temp_request(); + break; + case 2: + pkt = this->codec_->get_read_current_temp_request(); + break; + default: + this->current_request_ = 0; + break; + } + if (pkt != nullptr) { + auto status = + esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length, + pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), + status); + } + } + break; + } + default: + break; + } +} + +void Anova::update() { + if (this->node_state != espbt::ClientState::Established) + return; + + if (this->current_request_ == 0) { + auto pkt = this->codec_->get_read_device_status_request(); + auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, + pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + this->current_request_++; + } +} + +} // namespace anova +} // namespace esphome + +#endif diff --git a/esphome/components/anova/anova.h b/esphome/components/anova/anova.h new file mode 100644 index 0000000000..63d03cb329 --- /dev/null +++ b/esphome/components/anova/anova.h @@ -0,0 +1,50 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/ble_client/ble_client.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/climate/climate.h" +#include "anova_base.h" + +#ifdef ARDUINO_ARCH_ESP32 + +#include + +namespace esphome { +namespace anova { + +namespace espbt = esphome::esp32_ble_tracker; + +static const uint16_t ANOVA_SERVICE_UUID = 0xFFE0; +static const uint16_t ANOVA_CHARACTERISTIC_UUID = 0xFFE1; + +class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode, public PollingComponent { + public: + void setup() override; + void loop() override; + void update() override; + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + climate::ClimateTraits traits() { + auto traits = climate::ClimateTraits(); + traits.set_supports_current_temperature(true); + traits.set_supports_heat_mode(true); + traits.set_visual_min_temperature(25.0); + traits.set_visual_max_temperature(100.0); + traits.set_visual_temperature_step(0.1); + return traits; + } + + protected: + AnovaCodec *codec_; + void control(const climate::ClimateCall &call) override; + uint16_t char_handle_; + uint8_t current_request_; +}; + +} // namespace anova +} // namespace esphome + +#endif diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp new file mode 100644 index 0000000000..8cbc481643 --- /dev/null +++ b/esphome/components/anova/anova_base.cpp @@ -0,0 +1,119 @@ +#include "anova_base.h" + +namespace esphome { +namespace anova { + +AnovaPacket *AnovaCodec::clean_packet_() { + this->packet_.length = strlen((char *) this->packet_.data); + this->packet_.data[this->packet_.length] = '\0'; + ESP_LOGV("anova", "SendPkt: %s\n", this->packet_.data); + return &this->packet_; +} + +AnovaPacket *AnovaCodec::get_read_device_status_request() { + this->current_query_ = READ_DEVICE_STATUS; + sprintf((char *) this->packet_.data, "%s", CMD_READ_DEVICE_STATUS); + return this->clean_packet_(); +} + +AnovaPacket *AnovaCodec::get_read_target_temp_request() { + this->current_query_ = READ_TARGET_TEMPERATURE; + sprintf((char *) this->packet_.data, "%s", CMD_READ_TARGET_TEMP); + return this->clean_packet_(); +} + +AnovaPacket *AnovaCodec::get_read_current_temp_request() { + this->current_query_ = READ_CURRENT_TEMPERATURE; + sprintf((char *) this->packet_.data, "%s", CMD_READ_CURRENT_TEMP); + return this->clean_packet_(); +} + +AnovaPacket *AnovaCodec::get_read_unit_request() { + this->current_query_ = READ_UNIT; + sprintf((char *) this->packet_.data, "%s", CMD_READ_UNIT); + return this->clean_packet_(); +} + +AnovaPacket *AnovaCodec::get_read_data_request() { + this->current_query_ = READ_DATA; + sprintf((char *) this->packet_.data, "%s", CMD_READ_DATA); + return this->clean_packet_(); +} + +AnovaPacket *AnovaCodec::get_set_target_temp_request(float temperature) { + this->current_query_ = SET_TARGET_TEMPERATURE; + sprintf((char *) this->packet_.data, CMD_SET_TARGET_TEMP, temperature); + return this->clean_packet_(); +} + +AnovaPacket *AnovaCodec::get_set_unit_request(char unit) { + this->current_query_ = SET_UNIT; + sprintf((char *) this->packet_.data, CMD_SET_TEMP_UNIT, unit); + return this->clean_packet_(); +} + +AnovaPacket *AnovaCodec::get_start_request() { + this->current_query_ = START; + sprintf((char *) this->packet_.data, CMD_START); + return this->clean_packet_(); +} + +AnovaPacket *AnovaCodec::get_stop_request() { + this->current_query_ = STOP; + sprintf((char *) this->packet_.data, CMD_STOP); + return this->clean_packet_(); +} + +void AnovaCodec::decode(const uint8_t *data, uint16_t length) { + memset(this->buf_, 0, 32); + strncpy(this->buf_, (char *) data, length); + ESP_LOGV("anova", "Received: %s\n", this->buf_); + this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false; + switch (this->current_query_) { + case READ_DEVICE_STATUS: { + if (!strncmp(this->buf_, "stopped", 7)) { + this->has_running_ = true; + this->running_ = false; + } + if (!strncmp(this->buf_, "running", 7)) { + this->has_running_ = true; + this->running_ = true; + } + break; + } + case START: { + if (!strncmp(this->buf_, "start", 5)) { + this->has_running_ = true; + this->running_ = true; + } + break; + } + case STOP: { + if (!strncmp(this->buf_, "stop", 4)) { + this->has_running_ = true; + this->running_ = false; + } + break; + } + case READ_TARGET_TEMPERATURE: { + this->target_temp_ = strtof(this->buf_, nullptr); + this->has_target_temp_ = true; + break; + } + case SET_TARGET_TEMPERATURE: { + this->target_temp_ = strtof(this->buf_, nullptr); + this->has_target_temp_ = true; + break; + } + case READ_CURRENT_TEMPERATURE: { + this->current_temp_ = strtof(this->buf_, nullptr); + this->has_current_temp_ = true; + break; + } + default: + break; + } +} + +} // namespace anova +} // namespace esphome diff --git a/esphome/components/anova/anova_base.h b/esphome/components/anova/anova_base.h new file mode 100644 index 0000000000..e94fe619a6 --- /dev/null +++ b/esphome/components/anova/anova_base.h @@ -0,0 +1,79 @@ +#pragma once + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace anova { + +enum CurrentQuery { + NONE, + READ_DEVICE_STATUS, + READ_TARGET_TEMPERATURE, + READ_CURRENT_TEMPERATURE, + READ_DATA, + READ_UNIT, + SET_TARGET_TEMPERATURE, + SET_UNIT, + START, + STOP, +}; + +struct AnovaPacket { + uint16_t length; + uint8_t data[24]; +}; + +#define CMD_READ_DEVICE_STATUS "status\r" +#define CMD_READ_TARGET_TEMP "read set temp\r" +#define CMD_READ_CURRENT_TEMP "read temp\r" +#define CMD_READ_UNIT "read unit\r" +#define CMD_READ_DATA "read data\r" +#define CMD_SET_TARGET_TEMP "set temp %.1f\r" +#define CMD_SET_TEMP_UNIT "set unit %c\r" + +#define CMD_START "start\r" +#define CMD_STOP "stop\r" + +class AnovaCodec { + public: + AnovaPacket *get_read_device_status_request(); + AnovaPacket *get_read_target_temp_request(); + AnovaPacket *get_read_current_temp_request(); + AnovaPacket *get_read_data_request(); + AnovaPacket *get_read_unit_request(); + + AnovaPacket *get_set_target_temp_request(float temperature); + AnovaPacket *get_set_unit_request(char unit); + + AnovaPacket *get_start_request(); + AnovaPacket *get_stop_request(); + + void decode(const uint8_t *data, uint16_t length); + bool has_target_temp() { return this->has_target_temp_; } + bool has_current_temp() { return this->has_current_temp_; } + bool has_unit() { return this->has_unit_; } + bool has_running() { return this->has_running_; } + + union { + float target_temp_; + float current_temp_; + char unit_; + bool running_; + }; + + protected: + AnovaPacket *clean_packet_(); + AnovaPacket packet_; + + bool has_target_temp_; + bool has_current_temp_; + bool has_unit_; + bool has_running_; + char buf_[32]; + + CurrentQuery current_query_; +}; + +} // namespace anova +} // namespace esphome diff --git a/esphome/components/anova/climate.py b/esphome/components/anova/climate.py new file mode 100644 index 0000000000..ab1c9045d8 --- /dev/null +++ b/esphome/components/anova/climate.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate, ble_client +from esphome.const import CONF_ID + +CODEOWNERS = ["@buxtronix"] +DEPENDENCIES = ["ble_client"] + +anova_ns = cg.esphome_ns.namespace("anova") +Anova = anova_ns.class_( + "Anova", climate.Climate, ble_client.BLEClientNode, cg.PollingComponent +) + +CONFIG_SCHEMA = ( + climate.CLIMATE_SCHEMA.extend({cv.GenerateID(): cv.declare_id(Anova)}) + .extend(ble_client.BLE_CLIENT_SCHEMA) + .extend(cv.polling_component_schema("60s")) +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield climate.register_climate(var, config) + yield ble_client.register_ble_node(var, config) diff --git a/tests/test1.yaml b/tests/test1.yaml index 78dc40cf8e..1f817f0dab 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1526,6 +1526,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 From 79b9d0579d3a02c88afd57115400ec7fa000288f Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Mon, 5 Jul 2021 11:22:43 +1000 Subject: [PATCH 0985/1841] Add stepper.set_acceleration and stepper.set_deceleration to stepper component (#1977) --- esphome/components/stepper/__init__.py | 40 +++++++++++++++++++++++++- esphome/components/stepper/stepper.h | 30 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/esphome/components/stepper/__init__.py b/esphome/components/stepper/__init__.py index 41b1db7df2..54f6aa4205 100644 --- a/esphome/components/stepper/__init__.py +++ b/esphome/components/stepper/__init__.py @@ -21,6 +21,8 @@ Stepper = stepper_ns.class_("Stepper") SetTargetAction = stepper_ns.class_("SetTargetAction", automation.Action) ReportPositionAction = stepper_ns.class_("ReportPositionAction", automation.Action) SetSpeedAction = stepper_ns.class_("SetSpeedAction", automation.Action) +SetAccelerationAction = stepper_ns.class_("SetAccelerationAction", automation.Action) +SetDecelerationAction = stepper_ns.class_("SetDecelerationAction", automation.Action) def validate_acceleration(value): @@ -138,11 +140,47 @@ async def stepper_report_position_to_code(config, action_id, template_arg, args) async def stepper_set_speed_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) - template_ = await cg.templatable(config[CONF_SPEED], args, cg.int32) + template_ = await cg.templatable(config[CONF_SPEED], args, cg.float_) cg.add(var.set_speed(template_)) return var +@automation.register_action( + "stepper.set_acceleration", + SetAccelerationAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(Stepper), + cv.Required(CONF_ACCELERATION): cv.templatable(validate_acceleration), + } + ), +) +async def stepper_set_acceleration_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_ACCELERATION], args, cg.float_) + cg.add(var.set_acceleration(template_)) + return var + + +@automation.register_action( + "stepper.set_deceleration", + SetDecelerationAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(Stepper), + cv.Required(CONF_DECELERATION): cv.templatable(validate_acceleration), + } + ), +) +async def stepper_set_deceleration_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_DECELERATION], args, cg.float_) + cg.add(var.set_deceleration(template_)) + return var + + @coroutine_with_priority(100.0) async def to_code(config): cg.add_global(stepper_ns.using) diff --git a/esphome/components/stepper/stepper.h b/esphome/components/stepper/stepper.h index 33777dce83..560362e4d0 100644 --- a/esphome/components/stepper/stepper.h +++ b/esphome/components/stepper/stepper.h @@ -77,5 +77,35 @@ template class SetSpeedAction : public Action { Stepper *parent_; }; +template class SetAccelerationAction : public Action { + public: + explicit SetAccelerationAction(Stepper *parent) : parent_(parent) {} + + TEMPLATABLE_VALUE(float, acceleration); + + void play(Ts... x) override { + float acceleration = this->acceleration_.value(x...); + this->parent_->set_acceleration(acceleration); + } + + protected: + Stepper *parent_; +}; + +template class SetDecelerationAction : public Action { + public: + explicit SetDecelerationAction(Stepper *parent) : parent_(parent) {} + + TEMPLATABLE_VALUE(float, deceleration); + + void play(Ts... x) override { + float deceleration = this->deceleration_.value(x...); + this->parent_->set_deceleration(deceleration); + } + + protected: + Stepper *parent_; +}; + } // namespace stepper } // namespace esphome From 062cedc200e300800a6dbb2fe43380fdf2b3181b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 6 Jul 2021 10:15:29 +1200 Subject: [PATCH 0986/1841] remote_receiver: use config parent receiver for registering dumpers (#1980) --- esphome/components/remote_base/__init__.py | 6 +++--- esphome/components/remote_receiver/__init__.py | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 9416c2220d..7c27c1b736 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -176,7 +176,9 @@ validate_binary_sensor = cv.validate_registry_entry( TRIGGER_REGISTRY = SimpleRegistry() DUMPER_REGISTRY = Registry( { - cv.GenerateID(CONF_RECEIVER_ID): cv.use_id(RemoteReceiverBase), + cv.Optional(CONF_RECEIVER_ID): cv.invalid( + "This has been removed in ESPHome 1.20.0 and the dumper attaches directly to the parent receiver." + ), } ) @@ -228,8 +230,6 @@ async def build_dumpers(config): dumpers = [] for conf in config: dumper = await cg.build_registry_entry(DUMPER_REGISTRY, conf) - receiver = await cg.get_variable(conf[CONF_RECEIVER_ID]) - cg.add(receiver.register_dumper(dumper)) dumpers.append(dumper) return dumpers diff --git a/esphome/components/remote_receiver/__init__.py b/esphome/components/remote_receiver/__init__.py index fed52803cb..7158368ed8 100644 --- a/esphome/components/remote_receiver/__init__.py +++ b/esphome/components/remote_receiver/__init__.py @@ -54,7 +54,10 @@ async def to_code(config): else: var = cg.new_Pvariable(config[CONF_ID], pin) - await remote_base.build_dumpers(config[CONF_DUMP]) + dumpers = await remote_base.build_dumpers(config[CONF_DUMP]) + for dumper in dumpers: + cg.add(var.register_dumper(dumper)) + await remote_base.build_triggers(config) await cg.register_component(var, config) From fd4b7d4588b38e0ff9d4e16d48545f5719186e7d Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 6 Jul 2021 00:17:36 +0200 Subject: [PATCH 0987/1841] Don't try compat parsing for "esphome version" (#1966) --- esphome/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 232652db9f..a7a3836b69 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -516,7 +516,7 @@ def parse_args(argv): deprecated_argv_suggestion = None - if ["dashboard", "config"] == argv[1:3]: + if ["dashboard", "config"] == argv[1:3] or ["version"] == argv[1:2]: # this is most likely meant in new-style arg format. do not try compat parsing pass else: From f9797825ad03d2e692290ef4f337a58cad47fd62 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 8 Jul 2021 11:37:47 +0200 Subject: [PATCH 0988/1841] Change color model to fix white channel issues (#1895) --- esphome/components/api/api.proto | 3 + esphome/components/api/api_connection.cpp | 3 + esphome/components/api/api_pb2.cpp | 29 +++++ esphome/components/api/api_pb2.h | 3 + .../components/light/addressable_light.cpp | 8 +- esphome/components/light/automation.h | 12 +- esphome/components/light/automation.py | 5 + esphome/components/light/effects.py | 4 + .../components/light/esp_color_correction.h | 7 +- esphome/components/light/light_call.cpp | 91 +++++++++++---- esphome/components/light/light_call.h | 7 ++ esphome/components/light/light_color_values.h | 110 ++++++++++-------- esphome/const.py | 1 + 13 files changed, 200 insertions(+), 83 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index a5bd9aec6d..8034c980a4 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -378,6 +378,7 @@ message LightStateResponse { fixed32 key = 1; bool state = 2; float brightness = 3; + float color_brightness = 10; float red = 4; float green = 5; float blue = 6; @@ -396,6 +397,8 @@ message LightCommandRequest { bool state = 3; bool has_brightness = 4; float brightness = 5; + bool has_color_brightness = 20; + float color_brightness = 21; bool has_rgb = 6; float red = 7; float green = 8; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 00a8539112..c36d36a159 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -308,6 +308,7 @@ bool APIConnection::send_light_state(light::LightState *light) { if (traits.get_supports_brightness()) resp.brightness = values.get_brightness(); if (traits.get_supports_rgb()) { + resp.color_brightness = values.get_color_brightness(); resp.red = values.get_red(); resp.green = values.get_green(); resp.blue = values.get_blue(); @@ -352,6 +353,8 @@ void APIConnection::light_command(const LightCommandRequest &msg) { call.set_state(msg.state); if (msg.has_brightness) call.set_brightness(msg.brightness); + if (msg.has_color_brightness) + call.set_color_brightness(msg.color_brightness); if (msg.has_rgb) { call.set_red(msg.red); call.set_green(msg.green); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index e53ac2019a..83ebdd8b68 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1263,6 +1263,10 @@ bool LightStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { this->brightness = value.as_float(); return true; } + case 10: { + this->color_brightness = value.as_float(); + return true; + } case 4: { this->red = value.as_float(); return true; @@ -1291,6 +1295,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_bool(2, this->state); buffer.encode_float(3, this->brightness); + buffer.encode_float(10, this->color_brightness); buffer.encode_float(4, this->red); buffer.encode_float(5, this->green); buffer.encode_float(6, this->blue); @@ -1315,6 +1320,11 @@ void LightStateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); + out.append(" color_brightness: "); + sprintf(buffer, "%g", this->color_brightness); + out.append(buffer); + out.append("\n"); + out.append(" red: "); sprintf(buffer, "%g", this->red); out.append(buffer); @@ -1359,6 +1369,10 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { this->has_brightness = value.as_bool(); return true; } + case 20: { + this->has_color_brightness = value.as_bool(); + return true; + } case 6: { this->has_rgb = value.as_bool(); return true; @@ -1415,6 +1429,10 @@ bool LightCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { this->brightness = value.as_float(); return true; } + case 21: { + this->color_brightness = value.as_float(); + return true; + } case 7: { this->red = value.as_float(); return true; @@ -1445,6 +1463,8 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(3, this->state); buffer.encode_bool(4, this->has_brightness); buffer.encode_float(5, this->brightness); + buffer.encode_bool(20, this->has_color_brightness); + buffer.encode_float(21, this->color_brightness); buffer.encode_bool(6, this->has_rgb); buffer.encode_float(7, this->red); buffer.encode_float(8, this->green); @@ -1485,6 +1505,15 @@ void LightCommandRequest::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); + out.append(" has_color_brightness: "); + out.append(YESNO(this->has_color_brightness)); + out.append("\n"); + + out.append(" color_brightness: "); + sprintf(buffer, "%g", this->color_brightness); + out.append(buffer); + out.append("\n"); + out.append(" has_rgb: "); out.append(YESNO(this->has_rgb)); out.append("\n"); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 956cecdeb9..6873e0d54c 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -371,6 +371,7 @@ class LightStateResponse : public ProtoMessage { uint32_t key{0}; bool state{false}; float brightness{0.0f}; + float color_brightness{0.0f}; float red{0.0f}; float green{0.0f}; float blue{0.0f}; @@ -392,6 +393,8 @@ class LightCommandRequest : public ProtoMessage { bool state{false}; bool has_brightness{false}; float brightness{0.0f}; + bool has_color_brightness{false}; + float color_brightness{0.0f}; bool has_rgb{false}; float red{0.0f}; float green{0.0f}; diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index e3ab9596d9..ea24736c63 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -25,10 +25,10 @@ void AddressableLight::call_setup() { } Color esp_color_from_light_color_values(LightColorValues val) { - auto r = static_cast(roundf(val.get_red() * 255.0f)); - auto g = static_cast(roundf(val.get_green() * 255.0f)); - auto b = static_cast(roundf(val.get_blue() * 255.0f)); - auto w = static_cast(roundf(val.get_white() * val.get_state() * 255.0f)); + auto r = static_cast(roundf(val.get_color_brightness() * val.get_red() * 255.0f)); + auto g = static_cast(roundf(val.get_color_brightness() * val.get_green() * 255.0f)); + auto b = static_cast(roundf(val.get_color_brightness() * val.get_blue() * 255.0f)); + auto w = static_cast(roundf(val.get_white() * 255.0f)); return Color(r, g, b, w); } diff --git a/esphome/components/light/automation.h b/esphome/components/light/automation.h index d1fb2a0bcb..e99d5c58bd 100644 --- a/esphome/components/light/automation.h +++ b/esphome/components/light/automation.h @@ -31,6 +31,7 @@ template class LightControlAction : public Action { TEMPLATABLE_VALUE(uint32_t, transition_length) TEMPLATABLE_VALUE(uint32_t, flash_length) TEMPLATABLE_VALUE(float, brightness) + TEMPLATABLE_VALUE(float, color_brightness) TEMPLATABLE_VALUE(float, red) TEMPLATABLE_VALUE(float, green) TEMPLATABLE_VALUE(float, blue) @@ -42,6 +43,7 @@ template class LightControlAction : public Action { auto call = this->parent_->make_call(); call.set_state(this->state_.optional_value(x...)); call.set_brightness(this->brightness_.optional_value(x...)); + call.set_color_brightness(this->color_brightness_.optional_value(x...)); call.set_red(this->red_.optional_value(x...)); call.set_green(this->green_.optional_value(x...)); call.set_blue(this->blue_.optional_value(x...)); @@ -139,6 +141,7 @@ template class AddressableSet : public Action { TEMPLATABLE_VALUE(int32_t, range_from) TEMPLATABLE_VALUE(int32_t, range_to) + TEMPLATABLE_VALUE(uint8_t, color_brightness) TEMPLATABLE_VALUE(uint8_t, red) TEMPLATABLE_VALUE(uint8_t, green) TEMPLATABLE_VALUE(uint8_t, blue) @@ -148,13 +151,16 @@ template class AddressableSet : public Action { auto *out = (AddressableLight *) this->parent_->get_output(); int32_t range_from = this->range_from_.value_or(x..., 0); int32_t range_to = this->range_to_.value_or(x..., out->size() - 1) + 1; + uint8_t remote_color_brightness = + static_cast(roundf(this->parent_->remote_values.get_color_brightness() * 255.0f)); + uint8_t color_brightness = this->color_brightness_.value_or(x..., remote_color_brightness); auto range = out->range(range_from, range_to); if (this->red_.has_value()) - range.set_red(this->red_.value(x...)); + range.set_red(esp_scale8(this->red_.value(x...), color_brightness)); if (this->green_.has_value()) - range.set_green(this->green_.value(x...)); + range.set_green(esp_scale8(this->green_.value(x...), color_brightness)); if (this->blue_.has_value()) - range.set_blue(this->blue_.value(x...)); + range.set_blue(esp_scale8(this->blue_.value(x...), color_brightness)); if (this->white_.has_value()) range.set_white(this->white_.value(x...)); out->schedule_show(); diff --git a/esphome/components/light/automation.py b/esphome/components/light/automation.py index 3fb3126f14..dd1148131a 100644 --- a/esphome/components/light/automation.py +++ b/esphome/components/light/automation.py @@ -8,6 +8,7 @@ from esphome.const import ( CONF_FLASH_LENGTH, CONF_EFFECT, CONF_BRIGHTNESS, + CONF_COLOR_BRIGHTNESS, CONF_RED, CONF_GREEN, CONF_BLUE, @@ -63,6 +64,7 @@ LIGHT_CONTROL_ACTION_SCHEMA = cv.Schema( ), cv.Exclusive(CONF_EFFECT, "transformer"): cv.templatable(cv.string), cv.Optional(CONF_BRIGHTNESS): cv.templatable(cv.percentage), + cv.Optional(CONF_COLOR_BRIGHTNESS): cv.templatable(cv.percentage), cv.Optional(CONF_RED): cv.templatable(cv.percentage), cv.Optional(CONF_GREEN): cv.templatable(cv.percentage), cv.Optional(CONF_BLUE): cv.templatable(cv.percentage), @@ -114,6 +116,9 @@ async def light_control_to_code(config, action_id, template_arg, args): if CONF_BRIGHTNESS in config: template_ = await cg.templatable(config[CONF_BRIGHTNESS], args, float) cg.add(var.set_brightness(template_)) + if CONF_COLOR_BRIGHTNESS in config: + template_ = await cg.templatable(config[CONF_COLOR_BRIGHTNESS], args, float) + cg.add(var.set_color_brightness(template_)) if CONF_RED in config: template_ = await cg.templatable(config[CONF_RED], args, float) cg.add(var.set_red(template_)) diff --git a/esphome/components/light/effects.py b/esphome/components/light/effects.py index c213de0ae6..f6ce812c34 100644 --- a/esphome/components/light/effects.py +++ b/esphome/components/light/effects.py @@ -12,6 +12,7 @@ from esphome.const import ( CONF_STATE, CONF_DURATION, CONF_BRIGHTNESS, + CONF_COLOR_BRIGHTNESS, CONF_RED, CONF_GREEN, CONF_BLUE, @@ -211,6 +212,7 @@ async def random_effect_to_code(config, effect_id): { cv.Optional(CONF_STATE, default=True): cv.boolean, cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, + cv.Optional(CONF_COLOR_BRIGHTNESS, default=1.0): cv.percentage, cv.Optional(CONF_RED, default=1.0): cv.percentage, cv.Optional(CONF_GREEN, default=1.0): cv.percentage, cv.Optional(CONF_BLUE, default=1.0): cv.percentage, @@ -223,6 +225,7 @@ async def random_effect_to_code(config, effect_id): cv.has_at_least_one_key( CONF_STATE, CONF_BRIGHTNESS, + CONF_COLOR_BRIGHTNESS, CONF_RED, CONF_GREEN, CONF_BLUE, @@ -245,6 +248,7 @@ async def strobe_effect_to_code(config, effect_id): LightColorValues( color[CONF_STATE], color[CONF_BRIGHTNESS], + color[CONF_COLOR_BRIGHTNESS], color[CONF_RED], color[CONF_GREEN], color[CONF_BLUE], diff --git a/esphome/components/light/esp_color_correction.h b/esphome/components/light/esp_color_correction.h index 32fee8c8ea..8788246cfc 100644 --- a/esphome/components/light/esp_color_correction.h +++ b/esphome/components/light/esp_color_correction.h @@ -29,8 +29,7 @@ class ESPColorCorrection { return this->gamma_table_[res]; } inline uint8_t color_correct_white(uint8_t white) const ALWAYS_INLINE { - // do not scale white value with brightness - uint8_t res = esp_scale8(white, this->max_brightness_.white); + uint8_t res = esp_scale8(esp_scale8(white, this->max_brightness_.white), this->local_brightness_); return this->gamma_table_[res]; } inline Color color_uncorrect(Color color) const ALWAYS_INLINE { @@ -60,10 +59,10 @@ class ESPColorCorrection { return res; } inline uint8_t color_uncorrect_white(uint8_t white) const ALWAYS_INLINE { - if (this->max_brightness_.white == 0) + if (this->max_brightness_.white == 0 || this->local_brightness_ == 0) return 0; uint16_t uncorrected = this->gamma_reverse_table_[white] * 255UL; - uint8_t res = uncorrected / this->max_brightness_.white; + uint8_t res = ((uncorrected / this->max_brightness_.white) * 255UL) / this->local_brightness_; return res; } diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 3fbe921aa1..557d001321 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -43,6 +43,10 @@ LightCall &LightCall::parse_color_json(JsonObject &root) { } } + if (root.containsKey("color_brightness")) { + this->set_color_brightness(float(root["color_brightness"]) / 255.0f); + } + if (root.containsKey("white_value")) { this->set_white(float(root["white_value"]) / 255.0f); } @@ -182,6 +186,12 @@ LightColorValues LightCall::validate_() { this->transition_length_.reset(); } + // Color brightness exists check + if (this->color_brightness_.has_value() && !traits.get_supports_rgb()) { + ESP_LOGW(TAG, "'%s' - This light does not support setting RGB brightness!", name); + this->color_brightness_.reset(); + } + // RGB exists check if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { if (!traits.get_supports_rgb()) { @@ -204,34 +214,48 @@ LightColorValues LightCall::validate_() { this->color_temperature_.reset(); } - // If white channel is specified, set RGB to white color (when interlock is enabled) - if (this->white_.has_value()) { - if (traits.get_supports_color_interlock()) { - if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) { - this->red_ = optional(1.0f); - this->green_ = optional(1.0f); - this->blue_ = optional(1.0f); - } - // make white values binary aka 0.0f or 1.0f... this allows brightness to do its job - if (*this->white_ > 0.0f) { - this->white_ = optional(1.0f); - } else { - this->white_ = optional(0.0f); - } - } - } - // If only a color channel is specified, set white channel to 100% for white, otherwise 0% (when interlock is enabled) - else if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { - if (traits.get_supports_color_interlock()) { - if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f) { - this->white_ = optional(1.0f); - } else { - this->white_ = optional(0.0f); - } + // Set color brightness to 100% if currently zero and a color is set. This is both for compatibility with older + // clients that don't know about color brightness, and it's intuitive UX anyway: if I set a color, it should show up. + if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { + if (!this->color_brightness_.has_value() && this->parent_->remote_values.get_color_brightness() == 0.0f) + this->color_brightness_ = optional(1.0f); + } + + // Handle interaction between RGB and white for color interlock + if (traits.get_supports_color_interlock()) { + // Find out which channel (white or color) the user wanted to enable + bool output_white = this->white_.has_value() && *this->white_ > 0.0f; + bool output_color = (this->color_brightness_.has_value() && *this->color_brightness_ > 0.0f) || + this->red_.has_value() || this->green_.has_value() || this->blue_.has_value(); + + // Interpret setting the color to white as setting the white channel. + if (output_color && *this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f) { + output_white = true; + output_color = false; + + if (!this->white_.has_value()) + this->white_ = optional(this->color_brightness_.value_or(1.0f)); + } + + // Ensure either the white value or the color brightness is always zero. + if (output_white && output_color) { + ESP_LOGW(TAG, "'%s' - Cannot enable color and white channel simultaneously with interlock!", name); + // For compatibility with historic behaviour, prefer white channel in this case. + this->color_brightness_ = optional(0.0f); + } else if (output_white) { + this->color_brightness_ = optional(0.0f); + } else if (output_color) { + this->white_ = optional(0.0f); } } + // If only a color temperature is specified, change to white light - else if (this->color_temperature_.has_value()) { + if (this->color_temperature_.has_value() && !this->white_.has_value() && !this->red_.has_value() && + !this->green_.has_value() && !this->blue_.has_value()) { + // Disable color LEDs explicitly if not already set + if (traits.get_supports_rgb() && !this->color_brightness_.has_value()) + this->color_brightness_ = optional(0.0f); + this->red_ = optional(1.0f); this->green_ = optional(1.0f); this->blue_ = optional(1.0f); @@ -256,6 +280,7 @@ LightColorValues LightCall::validate_() { // Range checks VALIDATE_RANGE(brightness, "Brightness") + VALIDATE_RANGE(color_brightness, "Color brightness") VALIDATE_RANGE(red, "Red") VALIDATE_RANGE(green, "Green") VALIDATE_RANGE(blue, "Blue") @@ -267,6 +292,8 @@ LightColorValues LightCall::validate_() { if (this->brightness_.has_value()) v.set_brightness(*this->brightness_); + if (this->color_brightness_.has_value()) + v.set_color_brightness(*this->color_brightness_); if (this->red_.has_value()) v.set_red(*this->red_); if (this->green_.has_value()) @@ -371,6 +398,7 @@ LightCall &LightCall::set_effect(const std::string &effect) { LightCall &LightCall::from_light_color_values(const LightColorValues &values) { this->set_state(values.is_on()); this->set_brightness_if_supported(values.get_brightness()); + this->set_color_brightness_if_supported(values.get_color_brightness()); this->set_red_if_supported(values.get_red()); this->set_green_if_supported(values.get_green()); this->set_blue_if_supported(values.get_blue()); @@ -388,6 +416,11 @@ LightCall &LightCall::set_brightness_if_supported(float brightness) { this->set_brightness(brightness); return *this; } +LightCall &LightCall::set_color_brightness_if_supported(float brightness) { + if (this->parent_->get_traits().get_supports_rgb_white_value()) + this->set_brightness(brightness); + return *this; +} LightCall &LightCall::set_red_if_supported(float red) { if (this->parent_->get_traits().get_supports_rgb()) this->set_red(red); @@ -445,6 +478,14 @@ LightCall &LightCall::set_brightness(float brightness) { this->brightness_ = brightness; return *this; } +LightCall &LightCall::set_color_brightness(optional brightness) { + this->color_brightness_ = brightness; + return *this; +} +LightCall &LightCall::set_color_brightness(float brightness) { + this->color_brightness_ = brightness; + return *this; +} LightCall &LightCall::set_red(optional red) { this->red_ = red; return *this; diff --git a/esphome/components/light/light_call.h b/esphome/components/light/light_call.h index becea50004..c63b63bc54 100644 --- a/esphome/components/light/light_call.h +++ b/esphome/components/light/light_call.h @@ -44,6 +44,12 @@ class LightCall { LightCall &set_brightness(float brightness); /// Set the brightness property if the light supports brightness. LightCall &set_brightness_if_supported(float brightness); + /// Set the color brightness of the light from 0.0 (no color) to 1.0 (fully on) + LightCall &set_color_brightness(optional brightness); + /// Set the color brightness of the light from 0.0 (no color) to 1.0 (fully on) + LightCall &set_color_brightness(float brightness); + /// Set the color brightness property if the light supports RGBW. + LightCall &set_color_brightness_if_supported(float brightness); /** Set the red RGB value of the light from 0.0 to 1.0. * * Note that this only controls the color of the light, not its brightness. @@ -146,6 +152,7 @@ class LightCall { optional transition_length_; optional flash_length_; optional brightness_; + optional color_brightness_; optional red_; optional green_; optional blue_; diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index cdd05ae7b7..4b6ca9e576 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -13,17 +13,24 @@ namespace light { /** This class represents the color state for a light object. * - * All values in this class are represented using floats in the range from 0.0 (off) to 1.0 (on). - * Not all values have to be populated though, for example a simple monochromatic light only needs - * to access the state and brightness attributes. + * All values in this class (except color temperature) are represented using floats in the range + * from 0.0 (off) to 1.0 (on). Please note that all values are automatically clamped to this range. * - * Please note all float values are automatically clamped. + * This class has the following properties: + * - state: Whether the light should be on/off. Represented as a float for transitions. Used for + * all lights. + * - brightness: The master brightness of the light, applied to all channels. Used for all lights + * with brightness control. + * - color_brightness: The brightness of the color channels of the light. Used for RGB, RGBW and + * RGBWW lights. + * - red, green, blue: The RGB values of the current color. They are normalized, so at least one of + * them is always 1.0. + * - white: The brightness of the white channel of the light. Used for RGBW and RGBWW lights. + * - color_temperature: The color temperature of the white channel in mireds. Used for RGBWW and + * CWWW lights. * - * state - Whether the light should be on/off. Represented as a float for transitions. - * brightness - The brightness of the light. - * red, green, blue - RGB values. - * white - The white value for RGBW lights. - * color_temperature - Temperature of the white value, range from 0.0 (cold) to 1.0 (warm) + * For lights with a color interlock (RGB lights and white light cannot be on at the same time), a + * valid state has always either color_brightness or white (or both) set to zero. */ class LightColorValues { public: @@ -31,16 +38,18 @@ class LightColorValues { LightColorValues() : state_(0.0f), brightness_(1.0f), + color_brightness_(1.0f), red_(1.0f), green_(1.0f), blue_(1.0f), white_(1.0f), color_temperature_{1.0f} {} - LightColorValues(float state, float brightness, float red, float green, float blue, float white, - float color_temperature = 1.0f) { + LightColorValues(float state, float brightness, float color_brightness, float red, float green, float blue, + float white, float color_temperature = 1.0f) { this->set_state(state); this->set_brightness(brightness); + this->set_color_brightness(color_brightness); this->set_red(red); this->set_green(green); this->set_blue(blue); @@ -48,38 +57,46 @@ class LightColorValues { this->set_color_temperature(color_temperature); } - LightColorValues(bool state, float brightness, float red, float green, float blue, float white, - float color_temperature = 1.0f) - : LightColorValues(state ? 1.0f : 0.0f, brightness, red, green, blue, white, color_temperature) {} + LightColorValues(bool state, float brightness, float color_brightness, float red, float green, float blue, + float white, float color_temperature = 1.0f) + : LightColorValues(state ? 1.0f : 0.0f, brightness, color_brightness, red, green, blue, white, + color_temperature) {} /// Create light color values from a binary true/false state. - static LightColorValues from_binary(bool state) { return {state, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; } + static LightColorValues from_binary(bool state) { return {state, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; } /// Create light color values from a monochromatic brightness state. static LightColorValues from_monochromatic(float brightness) { if (brightness == 0.0f) - return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; + return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; else - return {1.0f, brightness, 1.0f, 1.0f, 1.0f, 1.0f}; + return {1.0f, brightness, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; } /// Create light color values from an RGB state. static LightColorValues from_rgb(float r, float g, float b) { float brightness = std::max(r, std::max(g, b)); if (brightness == 0.0f) { - return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; + return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; } else { - return {1.0f, brightness, r / brightness, g / brightness, b / brightness, 1.0f}; + return {1.0f, brightness, 1.0f, r / brightness, g / brightness, b / brightness, 1.0f}; } } /// Create light color values from an RGBW state. static LightColorValues from_rgbw(float r, float g, float b, float w) { - float brightness = std::max(r, std::max(g, std::max(b, w))); - if (brightness == 0.0f) { - return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; + float color_brightness = std::max(r, std::max(g, b)); + float master_brightness = std::max(color_brightness, w); + if (master_brightness == 0.0f) { + return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; } else { - return {1.0f, brightness, r / brightness, g / brightness, b / brightness, w / brightness}; + return {1.0f, + master_brightness, + color_brightness / master_brightness, + r / color_brightness, + g / color_brightness, + b / color_brightness, + w / master_brightness}; } } @@ -97,6 +114,7 @@ class LightColorValues { LightColorValues v; v.set_state(esphome::lerp(completion, start.get_state(), end.get_state())); v.set_brightness(esphome::lerp(completion, start.get_brightness(), end.get_brightness())); + v.set_color_brightness(esphome::lerp(completion, start.get_color_brightness(), end.get_color_brightness())); v.set_red(esphome::lerp(completion, start.get_red(), end.get_red())); v.set_green(esphome::lerp(completion, start.get_green(), end.get_green())); v.set_blue(esphome::lerp(completion, start.get_blue(), end.get_blue())); @@ -121,8 +139,10 @@ class LightColorValues { color["g"] = uint8_t(this->get_green() * 255); color["b"] = uint8_t(this->get_blue() * 255); } - if (traits.get_supports_rgb_white_value()) + if (traits.get_supports_rgb_white_value()) { + root["color_brightness"] = uint8_t(this->get_color_brightness() * 255); root["white_value"] = uint8_t(this->get_white() * 255); + } if (traits.get_supports_color_temperature()) root["color_temp"] = uint32_t(this->get_color_temperature()); } @@ -131,21 +151,15 @@ class LightColorValues { /** Normalize the color (RGB/W) component. * * Divides all color attributes by the maximum attribute, so effectively set at least one attribute to 1. - * For example: r=0.3, g=0.5, b=0.4 => r=0.6, g=1.0, b=0.8 + * For example: r=0.3, g=0.5, b=0.4 => r=0.6, g=1.0, b=0.8. + * + * Note that this does NOT retain the brightness information from the color attributes. * * @param traits Used for determining which attributes to consider. */ void normalize_color(const LightTraits &traits) { if (traits.get_supports_rgb()) { float max_value = fmaxf(this->get_red(), fmaxf(this->get_green(), this->get_blue())); - if (traits.get_supports_rgb_white_value()) { - max_value = fmaxf(max_value, this->get_white()); - if (max_value == 0.0f) { - this->set_white(1.0f); - } else { - this->set_white(this->get_white() / max_value); - } - } if (max_value == 0.0f) { this->set_red(1.0f); this->set_green(1.0f); @@ -158,15 +172,10 @@ class LightColorValues { } if (traits.get_supports_brightness() && this->get_brightness() == 0.0f) { - if (traits.get_supports_rgb_white_value()) { - // 0% brightness for RGBW[W] means no RGB channel, but white channel on. - // do nothing - } else { - // 0% brightness means off - this->set_state(false); - // reset brightness to 100% - this->set_brightness(1.0f); - } + // 0% brightness means off + this->set_state(false); + // reset brightness to 100% + this->set_brightness(1.0f); } } @@ -180,9 +189,9 @@ class LightColorValues { /// Convert these light color values to an RGB representation and write them to red, green, blue. void as_rgb(float *red, float *green, float *blue, float gamma = 0, bool color_interlock = false) const { - float brightness = this->state_ * this->brightness_; - if (color_interlock) { - brightness = brightness * (1.0f - this->white_); + float brightness = this->state_ * this->brightness_ * this->color_brightness_; + if (color_interlock && this->white_ > 0.0f) { + brightness = 0; } *red = gamma_correct(brightness * this->red_, gamma); *green = gamma_correct(brightness * this->green_, gamma); @@ -232,8 +241,9 @@ class LightColorValues { /// Compare this LightColorValues to rhs, return true if and only if all attributes match. bool operator==(const LightColorValues &rhs) const { - return state_ == rhs.state_ && brightness_ == rhs.brightness_ && red_ == rhs.red_ && green_ == rhs.green_ && - blue_ == rhs.blue_ && white_ == rhs.white_ && color_temperature_ == rhs.color_temperature_; + return state_ == rhs.state_ && brightness_ == rhs.brightness_ && color_brightness_ == rhs.color_brightness_ && + red_ == rhs.red_ && green_ == rhs.green_ && blue_ == rhs.blue_ && white_ == rhs.white_ && + color_temperature_ == rhs.color_temperature_; } bool operator!=(const LightColorValues &rhs) const { return !(rhs == *this); } @@ -251,6 +261,11 @@ class LightColorValues { /// Set the brightness property of these light color values. In range 0.0 to 1.0 void set_brightness(float brightness) { this->brightness_ = clamp(brightness, 0.0f, 1.0f); } + /// Get the color brightness property of these light color values. In range 0.0 to 1.0 + float get_color_brightness() const { return this->color_brightness_; } + /// Set the color brightness property of these light color values. In range 0.0 to 1.0 + void set_color_brightness(float brightness) { this->color_brightness_ = clamp(brightness, 0.0f, 1.0f); } + /// Get the red property of these light color values. In range 0.0 to 1.0 float get_red() const { return this->red_; } /// Set the red property of these light color values. In range 0.0 to 1.0 @@ -281,6 +296,7 @@ class LightColorValues { protected: float state_; ///< ON / OFF, float for transition float brightness_; + float color_brightness_; float red_; float green_; float blue_; diff --git a/esphome/const.py b/esphome/const.py index bb20c606ce..9b7490878d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -121,6 +121,7 @@ CONF_CODE = "code" CONF_COLD_WHITE = "cold_white" CONF_COLD_WHITE_COLOR_TEMPERATURE = "cold_white_color_temperature" CONF_COLOR = "color" +CONF_COLOR_BRIGHTNESS = "color_brightness" CONF_COLOR_CORRECT = "color_correct" CONF_COLOR_TEMPERATURE = "color_temperature" CONF_COLORS = "colors" From be61b38a2cb82312c67adec67ee821817555dfa2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 9 Jul 2021 00:39:37 +1200 Subject: [PATCH 0989/1841] Allow WiFi AP to use device name (#1990) --- esphome/components/wifi/wifi_component.cpp | 19 +++++++++++++++++-- esphome/components/wifi/wifi_component.h | 1 + 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 0c3a3054f8..e99cd0e1b1 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -164,7 +164,7 @@ void WiFiComponent::loop() { WiFiComponent::WiFiComponent() { global_wifi_component = this; } -bool WiFiComponent::has_ap() const { return !this->ap_.get_ssid().empty(); } +bool WiFiComponent::has_ap() const { return this->has_ap_; } bool WiFiComponent::has_sta() const { return !this->sta_.empty(); } void WiFiComponent::set_fast_connect(bool fast_connect) { this->fast_connect_ = fast_connect; } IPAddress WiFiComponent::get_ip_address() { @@ -187,6 +187,18 @@ void WiFiComponent::setup_ap_config_() { if (this->ap_setup_) return; + if (this->ap_.get_ssid().empty()) { + std::string name = App.get_name(); + if (name.length() > 32) { + if (App.is_name_add_mac_suffix_enabled()) { + name.erase(name.begin() + 25, name.end() - 7); // Remove characters between 25 and the mac address + } else { + name = name.substr(0, 32); + } + } + this->ap_.set_ssid(name); + } + ESP_LOGCONFIG(TAG, "Setting up AP..."); ESP_LOGCONFIG(TAG, " AP SSID: '%s'", this->ap_.get_ssid().c_str()); @@ -212,7 +224,10 @@ void WiFiComponent::setup_ap_config_() { float WiFiComponent::get_loop_priority() const { return 10.0f; // before other loop components } -void WiFiComponent::set_ap(const WiFiAP &ap) { this->ap_ = ap; } +void WiFiComponent::set_ap(const WiFiAP &ap) { + this->ap_ = ap; + this->has_ap_ = true; +} void WiFiComponent::add_sta(const WiFiAP &ap) { this->sta_.push_back(ap); } void WiFiComponent::set_sta(const WiFiAP &ap) { this->clear_sta(); diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index d690a35420..f698e09d93 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -282,6 +282,7 @@ class WiFiComponent : public Component { WiFiAP selected_ap_; bool fast_connect_{false}; + bool has_ap_{false}; WiFiAP ap_; WiFiComponentState state_{WIFI_COMPONENT_STATE_OFF}; uint32_t action_started_; From 294ba1fca7a572d0bc2df3a5bc71b32e71d8d960 Mon Sep 17 00:00:00 2001 From: Michael Gorven Date: Fri, 9 Jul 2021 14:25:27 -0700 Subject: [PATCH 0990/1841] Support custom fan modes in mqtt_climate (#1989) Co-authored-by: Michael Gorven --- esphome/components/climate/climate_traits.h | 2 +- esphome/components/mqtt/mqtt_climate.cpp | 65 +++++++++++---------- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index b86a0c7774..8f9e716e1d 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -91,7 +91,7 @@ class ClimateTraits { ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); } bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); } - bool get_supports_fan_modes() const { return !supported_fan_modes_.empty(); } + bool get_supports_fan_modes() const { return !supported_fan_modes_.empty() || !supported_custom_fan_modes_.empty(); } const std::set get_supported_fan_modes() const { return supported_fan_modes_; } void set_supported_custom_fan_modes(std::set supported_custom_fan_modes) { diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 0934922bc6..ab8354e66c 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -97,6 +97,8 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC fan_modes.add("focus"); if (traits.supports_fan_mode(CLIMATE_FAN_DIFFUSE)) fan_modes.add("diffuse"); + for (const auto &fan_mode : traits.get_supported_custom_fan_modes()) + fan_modes.add(fan_mode); } if (traits.get_supports_swing_modes()) { @@ -291,36 +293,39 @@ bool MQTTClimateComponent::publish_state_() { } if (traits.get_supports_fan_modes()) { - const char *payload = ""; - switch (this->device_->fan_mode.value()) { - case CLIMATE_FAN_ON: - payload = "on"; - break; - case CLIMATE_FAN_OFF: - payload = "off"; - break; - case CLIMATE_FAN_AUTO: - payload = "auto"; - break; - case CLIMATE_FAN_LOW: - payload = "low"; - break; - case CLIMATE_FAN_MEDIUM: - payload = "medium"; - break; - case CLIMATE_FAN_HIGH: - payload = "high"; - break; - case CLIMATE_FAN_MIDDLE: - payload = "middle"; - break; - case CLIMATE_FAN_FOCUS: - payload = "focus"; - break; - case CLIMATE_FAN_DIFFUSE: - payload = "diffuse"; - break; - } + std::string payload; + if (this->device_->fan_mode.has_value()) + switch (this->device_->fan_mode.value()) { + case CLIMATE_FAN_ON: + payload = "on"; + break; + case CLIMATE_FAN_OFF: + payload = "off"; + break; + case CLIMATE_FAN_AUTO: + payload = "auto"; + break; + case CLIMATE_FAN_LOW: + payload = "low"; + break; + case CLIMATE_FAN_MEDIUM: + payload = "medium"; + break; + case CLIMATE_FAN_HIGH: + payload = "high"; + break; + case CLIMATE_FAN_MIDDLE: + payload = "middle"; + break; + case CLIMATE_FAN_FOCUS: + payload = "focus"; + break; + case CLIMATE_FAN_DIFFUSE: + payload = "diffuse"; + break; + } + if (this->device_->custom_fan_mode.has_value()) + payload = this->device_->custom_fan_mode.value(); if (!this->publish(this->get_fan_mode_state_topic(), payload)) success = false; } From 4f88c2489bb462a6ad36809e50e1175a4a7f1f45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jul 2021 23:27:08 +0200 Subject: [PATCH 0991/1841] Bump protobuf from 3.17.0 to 3.17.3 (#1986) Bumps [protobuf](https://github.com/protocolbuffers/protobuf) from 3.17.0 to 3.17.3. - [Release notes](https://github.com/protocolbuffers/protobuf/releases) - [Changelog](https://github.com/protocolbuffers/protobuf/blob/master/generate_changelog.py) - [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.17.0...v3.17.3) --- updated-dependencies: - dependency-name: protobuf dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index cc9059f0d7..c3562f492d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ PyYAML==5.4.1 paho-mqtt==1.5.1 colorama==0.4.4 tornado==6.1 -protobuf==3.17.0 +protobuf==3.17.3 tzlocal==2.1 pytz==2021.1 pyserial==3.5 From 99f497c3b82e5a5196a3a429191d414184a85440 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jul 2021 23:28:28 +0200 Subject: [PATCH 0992/1841] Bump pytest-cov from 2.11.1 to 2.12.1 (#1855) Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 2.11.1 to 2.12.1. - [Release notes](https://github.com/pytest-dev/pytest-cov/releases) - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v2.11.1...v2.12.1) --- updated-dependencies: - dependency-name: pytest-cov dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 26755921cd..6eb22ac43f 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -6,7 +6,7 @@ pre-commit # Unit tests pytest==6.2.4 -pytest-cov==2.11.1 +pytest-cov==2.12.1 pytest-mock==3.6.1 pytest-asyncio==0.14.0 asyncmock==0.4.2 From b62c47fedee7e4da540bc6e13188c02ec770d3e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jul 2021 23:28:37 +0200 Subject: [PATCH 0993/1841] Bump pytest-asyncio from 0.14.0 to 0.15.1 (#1793) Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.14.0 to 0.15.1. - [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) - [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.14.0...v0.15.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 6eb22ac43f..38717f3f77 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -8,6 +8,6 @@ pre-commit pytest==6.2.4 pytest-cov==2.12.1 pytest-mock==3.6.1 -pytest-asyncio==0.14.0 +pytest-asyncio==0.15.1 asyncmock==0.4.2 hypothesis==5.49.0 From 7ae611256aef1c44bb9cfb0ea690f91b8fddcd27 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 10 Jul 2021 11:23:04 +0200 Subject: [PATCH 0994/1841] Improve climate mode code docs (#1995) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/climate/climate_mode.h | 25 +++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/esphome/components/climate/climate_mode.h b/esphome/components/climate/climate_mode.h index 07fbf32b26..476cf5bd84 100644 --- a/esphome/components/climate/climate_mode.h +++ b/esphome/components/climate/climate_mode.h @@ -7,19 +7,22 @@ namespace climate { /// Enum for all modes a climate device can be in. enum ClimateMode : uint8_t { - /// The climate device is off (not in auto, heat or cool mode) + /// The climate device is off CLIMATE_MODE_OFF = 0, - /// The climate device is set to automatically change the heating/cooling cycle + /// The climate device is set to heat/cool to reach the target temperature. CLIMATE_MODE_HEAT_COOL = 1, - /// The climate device is manually set to cool mode (not in auto mode!) + /// The climate device is set to cool to reach the target temperature CLIMATE_MODE_COOL = 2, - /// The climate device is manually set to heat mode (not in auto mode!) + /// The climate device is set to heat to reach the target temperature CLIMATE_MODE_HEAT = 3, - /// The climate device is manually set to fan only mode + /// The climate device only has the fan enabled, no heating or cooling is taking place CLIMATE_MODE_FAN_ONLY = 4, - /// The climate device is manually set to dry mode + /// The climate device is set to dry/humidity mode CLIMATE_MODE_DRY = 5, - /// The climate device is manually set to heat-cool mode + /** The climate device is adjusting the temperatre dynamically. + * For example, the target temperature can be adjusted based on a schedule, or learned behavior. + * The target temperature can't be adjusted when in this mode. + */ CLIMATE_MODE_AUTO = 6 }; @@ -27,15 +30,15 @@ enum ClimateMode : uint8_t { enum ClimateAction : uint8_t { /// The climate device is off (inactive or no power) CLIMATE_ACTION_OFF = 0, - /// The climate device is actively cooling (usually in cool or auto mode) + /// The climate device is actively cooling CLIMATE_ACTION_COOLING = 2, - /// The climate device is actively heating (usually in heat or auto mode) + /// The climate device is actively heating CLIMATE_ACTION_HEATING = 3, /// The climate device is idle (monitoring climate but no action needed) CLIMATE_ACTION_IDLE = 4, - /// The climate device is drying (either mode DRY or AUTO) + /// The climate device is drying CLIMATE_ACTION_DRYING = 5, - /// The climate device is in fan only mode (either mode FAN_ONLY or AUTO) + /// The climate device is in fan only mode CLIMATE_ACTION_FAN = 6, }; From cdbc146e5d032773f8f46dc3994ae2d60f5e2e44 Mon Sep 17 00:00:00 2001 From: carstenschroeder Date: Sat, 10 Jul 2021 11:37:55 +0200 Subject: [PATCH 0995/1841] Climate modes COOL and HEAT are auto modes (#1994) --- esphome/components/pid/pid_climate.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index 8a61361fb8..dac4426698 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -20,7 +20,12 @@ void PIDClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles those for us - this->mode = climate::CLIMATE_MODE_HEAT_COOL; + if (supports_heat_() && supports_cool_()) + this->mode = climate::CLIMATE_MODE_HEAT_COOL; + else if (supports_cool_()) + this->mode = climate::CLIMATE_MODE_COOL; + else if (supports_heat_()) + this->mode = climate::CLIMATE_MODE_HEAT; this->target_temperature = this->default_target_temperature_; } } @@ -41,11 +46,13 @@ climate::ClimateTraits PIDClimate::traits() { traits.set_supports_current_temperature(true); traits.set_supports_two_point_target_temperature(false); - traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL}); + traits.set_supported_modes({climate::CLIMATE_MODE_OFF}); if (supports_cool_()) traits.add_supported_mode(climate::CLIMATE_MODE_COOL); if (supports_heat_()) traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + if (supports_heat_() && supports_cool_()) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL); traits.set_supports_action(true); return traits; @@ -93,13 +100,8 @@ void PIDClimate::write_output_(float value) { } void PIDClimate::handle_non_auto_mode_() { // in non-auto mode, switch directly to appropriate action - // - HEAT mode / COOL mode -> Output at ±100% // - OFF mode -> Output at 0% - if (this->mode == climate::CLIMATE_MODE_HEAT) { - this->write_output_(1.0); - } else if (this->mode == climate::CLIMATE_MODE_COOL) { - this->write_output_(-1.0); - } else if (this->mode == climate::CLIMATE_MODE_OFF) { + if (this->mode == climate::CLIMATE_MODE_OFF) { this->write_output_(0.0); } else { assert(false); @@ -132,7 +134,7 @@ void PIDClimate::update_pid_() { } } - if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) { + if (this->mode == climate::CLIMATE_MODE_OFF) { this->handle_non_auto_mode_(); } else { this->write_output_(value); From 623570a117857180816e9dbb2e426aadba56fbd1 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sat, 10 Jul 2021 21:52:19 +0200 Subject: [PATCH 0996/1841] Add state callback to ota component (#1816) Co-authored-by: Maurice Makaay Co-authored-by: Guillermo Ruffino --- esphome/components/ota/__init__.py | 65 ++++++++++++++++++++++ esphome/components/ota/automation.h | 71 ++++++++++++++++++++++++ esphome/components/ota/ota_component.cpp | 20 ++++++- esphome/components/ota/ota_component.h | 11 ++++ tests/test1.yaml | 18 ++++++ 5 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 esphome/components/ota/automation.h diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 7ee7ef47ca..75641ad399 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -1,6 +1,7 @@ from esphome.cpp_generator import RawExpression import esphome.codegen as cg import esphome.config_validation as cv +from esphome import automation from esphome.const import ( CONF_ID, CONF_NUM_ATTEMPTS, @@ -8,14 +9,29 @@ from esphome.const import ( CONF_PORT, CONF_REBOOT_TIMEOUT, CONF_SAFE_MODE, + CONF_TRIGGER_ID, ) from esphome.core import CORE, coroutine_with_priority CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] +CONF_ON_STATE_CHANGE = "on_state_change" +CONF_ON_BEGIN = "on_begin" +CONF_ON_PROGRESS = "on_progress" +CONF_ON_END = "on_end" +CONF_ON_ERROR = "on_error" + ota_ns = cg.esphome_ns.namespace("ota") +OTAState = ota_ns.enum("OTAState") OTAComponent = ota_ns.class_("OTAComponent", cg.Component) +OTAStateChangeTrigger = ota_ns.class_( + "OTAStateChangeTrigger", automation.Trigger.template() +) +OTAStartTrigger = ota_ns.class_("OTAStartTrigger", automation.Trigger.template()) +OTAProgressTrigger = ota_ns.class_("OTAProgressTrigger", automation.Trigger.template()) +OTAEndTrigger = ota_ns.class_("OTAEndTrigger", automation.Trigger.template()) +OTAErrorTrigger = ota_ns.class_("OTAErrorTrigger", automation.Trigger.template()) CONFIG_SCHEMA = cv.Schema( { @@ -27,6 +43,31 @@ CONFIG_SCHEMA = cv.Schema( CONF_REBOOT_TIMEOUT, default="5min" ): cv.positive_time_period_milliseconds, cv.Optional(CONF_NUM_ATTEMPTS, default="10"): cv.positive_not_null_int, + cv.Optional(CONF_ON_STATE_CHANGE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OTAStateChangeTrigger), + } + ), + cv.Optional(CONF_ON_BEGIN): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OTAStartTrigger), + } + ), + cv.Optional(CONF_ON_ERROR): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OTAErrorTrigger), + } + ), + cv.Optional(CONF_ON_PROGRESS): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OTAProgressTrigger), + } + ), + cv.Optional(CONF_ON_END): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OTAEndTrigger), + } + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -49,3 +90,27 @@ async def to_code(config): cg.add_library("Update", None) elif CORE.is_esp32: cg.add_library("Hash", None) + + use_state_callback = False + for conf in config.get(CONF_ON_STATE_CHANGE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(OTAState, "state")], conf) + use_state_callback = True + for conf in config.get(CONF_ON_BEGIN, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + use_state_callback = True + for conf in config.get(CONF_ON_PROGRESS, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(float, "x")], conf) + use_state_callback = True + for conf in config.get(CONF_ON_END, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + use_state_callback = True + for conf in config.get(CONF_ON_ERROR, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(int, "x")], conf) + use_state_callback = True + if use_state_callback: + cg.add_define("USE_OTA_STATE_CALLBACK") diff --git a/esphome/components/ota/automation.h b/esphome/components/ota/automation.h new file mode 100644 index 0000000000..6c8aca3705 --- /dev/null +++ b/esphome/components/ota/automation.h @@ -0,0 +1,71 @@ +#pragma once + +#include "esphome/core/defines.h" +#ifdef USE_OTA_STATE_CALLBACK + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/ota/ota_component.h" + +namespace esphome { +namespace ota { + +class OTAStateChangeTrigger : public Trigger { + public: + explicit OTAStateChangeTrigger(OTAComponent *parent) { + parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { + if (!parent->is_failed()) { + return trigger(state); + } + }); + } +}; + +class OTAStartTrigger : public Trigger<> { + public: + explicit OTAStartTrigger(OTAComponent *parent) { + parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { + if (state == OTA_STARTED && !parent->is_failed()) { + trigger(); + } + }); + } +}; + +class OTAProgressTrigger : public Trigger { + public: + explicit OTAProgressTrigger(OTAComponent *parent) { + parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { + if (state == OTA_IN_PROGRESS && !parent->is_failed()) { + trigger(progress); + } + }); + } +}; + +class OTAEndTrigger : public Trigger<> { + public: + explicit OTAEndTrigger(OTAComponent *parent) { + parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { + if (state == OTA_COMPLETED && !parent->is_failed()) { + trigger(); + } + }); + } +}; + +class OTAErrorTrigger : public Trigger { + public: + explicit OTAErrorTrigger(OTAComponent *parent) { + parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { + if (state == OTA_ERROR && !parent->is_failed()) { + trigger(error); + } + }); + } +}; + +} // namespace ota +} // namespace esphome + +#endif // USE_OTA_STATE_CALLBACK diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 5302b7bc24..71f8101704 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -1,7 +1,6 @@ #include "ota_component.h" #include "esphome/core/log.h" -#include "esphome/core/helpers.h" #include "esphome/core/application.h" #include "esphome/core/util.h" @@ -25,6 +24,7 @@ void OTAComponent::setup() { this->dump_config(); } + void OTAComponent::dump_config() { ESP_LOGCONFIG(TAG, "Over-The-Air Updates:"); ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->port_); @@ -71,6 +71,9 @@ void OTAComponent::handle_() { ESP_LOGD(TAG, "Starting OTA Update from %s...", this->client_.remoteIP().toString().c_str()); this->status_set_warning(); +#ifdef USE_OTA_STATE_CALLBACK + this->state_callback_.call(OTA_STARTED, 0.0f, 0); +#endif if (!this->wait_receive_(buf, 5)) { ESP_LOGW(TAG, "Reading magic bytes failed!"); @@ -241,6 +244,9 @@ void OTAComponent::handle_() { last_progress = now; float percentage = (total * 100.0f) / ota_size; ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage); +#ifdef USE_OTA_STATE_CALLBACK + this->state_callback_.call(OTA_IN_PROGRESS, percentage, 0); +#endif // slow down OTA update to avoid getting killed by task watchdog (task_wdt) delay(10); } @@ -268,6 +274,9 @@ void OTAComponent::handle_() { delay(10); ESP_LOGI(TAG, "OTA update finished!"); this->status_clear_warning(); +#ifdef USE_OTA_STATE_CALLBACK + this->state_callback_.call(OTA_COMPLETED, 100.0f, 0); +#endif delay(100); // NOLINT App.safe_reboot(); @@ -296,6 +305,9 @@ error: #endif this->status_momentary_error("onerror", 5000); +#ifdef USE_OTA_STATE_CALLBACK + this->state_callback_.call(OTA_ERROR, 0.0f, static_cast(error_code)); +#endif #ifdef ARDUINO_ARCH_ESP8266 global_preferences.prevent_write(false); @@ -400,5 +412,11 @@ void OTAComponent::on_safe_shutdown() { this->clean_rtc(); } +#ifdef USE_OTA_STATE_CALLBACK +void OTAComponent::add_on_state_callback(std::function &&callback) { + this->state_callback_.add(std::move(callback)); +} +#endif + } // namespace ota } // namespace esphome diff --git a/esphome/components/ota/ota_component.h b/esphome/components/ota/ota_component.h index f16725e324..8b5830295e 100644 --- a/esphome/components/ota/ota_component.h +++ b/esphome/components/ota/ota_component.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/preferences.h" +#include "esphome/core/helpers.h" #include #include @@ -32,6 +33,8 @@ enum OTAResponseTypes { OTA_RESPONSE_ERROR_UNKNOWN = 255, }; +enum OTAState { OTA_COMPLETED = 0, OTA_STARTED, OTA_IN_PROGRESS, OTA_ERROR }; + /// OTAComponent provides a simple way to integrate Over-the-Air updates into your app using ArduinoOTA. class OTAComponent : public Component { public: @@ -49,6 +52,10 @@ class OTAComponent : public Component { bool should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time); +#ifdef USE_OTA_STATE_CALLBACK + void add_on_state_callback(std::function &&callback); +#endif + // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) void setup() override; @@ -82,6 +89,10 @@ class OTAComponent : public Component { uint32_t safe_mode_rtc_value_; uint8_t safe_mode_num_attempts_; ESPPreferenceObject rtc_; + +#ifdef USE_OTA_STATE_CALLBACK + CallbackManager state_callback_{}; +#endif }; } // namespace ota diff --git a/tests/test1.yaml b/tests/test1.yaml index 1f817f0dab..1c522a23d4 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -197,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 From 1f5c79bd1714d6eebec67a5d1b9585fcf7fab48f Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 11 Jul 2021 06:51:24 +0200 Subject: [PATCH 0997/1841] Fix deprecation message for old climate swing mode methods (#2003) --- esphome/components/climate/climate_traits.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index 8f9e716e1d..fbd6f158e6 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -127,13 +127,13 @@ class ClimateTraits { void set_supported_swing_modes(std::set modes) { supported_swing_modes_ = std::move(modes); } void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); } - ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead") void set_supports_swing_mode_off(bool supported) { set_swing_mode_support_(CLIMATE_SWING_OFF, supported); } - ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead") void set_supports_swing_mode_both(bool supported) { set_swing_mode_support_(CLIMATE_SWING_BOTH, supported); } - ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead") void set_supports_swing_mode_vertical(bool supported) { set_swing_mode_support_(CLIMATE_SWING_VERTICAL, supported); } - ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead") void set_supports_swing_mode_horizontal(bool supported) { set_swing_mode_support_(CLIMATE_SWING_HORIZONTAL, supported); } From dd37a4e04c8e1220f934de89a97c326381d0fdda Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 13 Jul 2021 07:20:12 +1200 Subject: [PATCH 0998/1841] Add Number entities (from Home Assistant) (#1971) Co-authored-by: Oxan van Leeuwen --- CODEOWNERS | 1 + esphome/components/api/api.proto | 39 ++++ esphome/components/api/api_connection.cpp | 36 ++++ esphome/components/api/api_connection.h | 5 + esphome/components/api/api_pb2.cpp | 173 ++++++++++++++++++ esphome/components/api/api_pb2.h | 39 ++++ esphome/components/api/api_pb2_service.cpp | 36 ++++ esphome/components/api/api_pb2_service.h | 15 ++ esphome/components/api/api_server.cpp | 9 + esphome/components/api/api_server.h | 3 + esphome/components/api/list_entities.cpp | 4 + esphome/components/api/list_entities.h | 3 + esphome/components/api/subscribe_state.cpp | 5 + esphome/components/api/subscribe_state.h | 3 + esphome/components/api/util.cpp | 15 ++ esphome/components/api/util.h | 6 + esphome/components/mqtt/__init__.py | 1 + esphome/components/mqtt/mqtt_number.cpp | 61 ++++++ esphome/components/mqtt/mqtt_number.h | 46 +++++ esphome/components/number/__init__.py | 154 ++++++++++++++++ esphome/components/number/automation.cpp | 47 +++++ esphome/components/number/automation.h | 76 ++++++++ esphome/components/number/number.cpp | 76 ++++++++ esphome/components/number/number.h | 112 ++++++++++++ .../components/template/number/__init__.py | 63 +++++++ .../template/number/template_number.cpp | 39 ++++ .../template/number/template_number.h | 30 +++ esphome/components/web_server/web_server.cpp | 49 +++++ esphome/components/web_server/web_server.h | 9 + esphome/const.py | 1 + esphome/core/application.h | 19 ++ esphome/core/controller.cpp | 6 + esphome/core/controller.h | 6 + esphome/core/defines.h | 1 + script/ci-custom.py | 1 + tests/test5.yaml | 17 ++ 36 files changed, 1206 insertions(+) create mode 100644 esphome/components/mqtt/mqtt_number.cpp create mode 100644 esphome/components/mqtt/mqtt_number.h create mode 100644 esphome/components/number/__init__.py create mode 100644 esphome/components/number/automation.cpp create mode 100644 esphome/components/number/automation.h create mode 100644 esphome/components/number/number.cpp create mode 100644 esphome/components/number/number.h create mode 100644 esphome/components/template/number/__init__.py create mode 100644 esphome/components/template/number/template_number.cpp create mode 100644 esphome/components/template/number/template_number.h diff --git a/CODEOWNERS b/CODEOWNERS index a1c7bf9bfd..2a57e5d81a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -73,6 +73,7 @@ esphome/components/midea_dongle/* @dudanov esphome/components/mitsubishi/* @RubyBailey esphome/components/network/* @esphome/core esphome/components/nfc/* @jesserockz +esphome/components/number/* @esphome/core esphome/components/ota/* @esphome/core esphome/components/output/* @esphome/core esphome/components/pid/* @OttoWinter diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 8034c980a4..40be1fd0db 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -38,6 +38,7 @@ service APIConnection { rpc switch_command (SwitchCommandRequest) returns (void) {} rpc camera_image (CameraImageRequest) returns (void) {} rpc climate_command (ClimateCommandRequest) returns (void) {} + rpc number_command (NumberCommandRequest) returns (void) {} } @@ -798,3 +799,41 @@ message ClimateCommandRequest { bool has_custom_preset = 20; string custom_preset = 21; } + +// ==================== NUMBER ==================== +message ListEntitiesNumberResponse { + option (id) = 49; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_NUMBER"; + + string object_id = 1; + fixed32 key = 2; + string name = 3; + string unique_id = 4; + + string icon = 5; + float min_value = 6; + float max_value = 7; + float step = 8; +} +message NumberStateResponse { + option (id) = 50; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_NUMBER"; + option (no_delay) = true; + + fixed32 key = 1; + float state = 2; + // If the number does not have a valid state yet. + // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller + bool missing_state = 3; +} +message NumberCommandRequest { + option (id) = 51; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_NUMBER"; + option (no_delay) = true; + + fixed32 key = 1; + float state = 2; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index c36d36a159..8c76583fc7 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -553,6 +553,42 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { } #endif +#ifdef USE_NUMBER +bool APIConnection::send_number_state(number::Number *number, float state) { + if (!this->state_subscription_) + return false; + + NumberStateResponse resp{}; + resp.key = number->get_object_id_hash(); + resp.state = state; + resp.missing_state = !number->has_state(); + return this->send_number_state_response(resp); +} +bool APIConnection::send_number_info(number::Number *number) { + ListEntitiesNumberResponse msg; + msg.key = number->get_object_id_hash(); + msg.object_id = number->get_object_id(); + msg.name = number->get_name(); + msg.unique_id = get_default_unique_id("number", number); + msg.icon = number->get_icon(); + + msg.min_value = number->get_min_value(); + msg.max_value = number->get_max_value(); + msg.step = number->get_step(); + + return this->send_list_entities_number_response(msg); +} +void APIConnection::number_command(const NumberCommandRequest &msg) { + number::Number *number = App.get_number_by_key(msg.key); + if (number == nullptr) + return; + + auto call = number->make_call(); + call.set_value(msg.state); + call.perform(); +} +#endif + #ifdef USE_ESP32_CAMERA void APIConnection::send_camera_state(std::shared_ptr image) { if (!this->state_subscription_) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 3e91ead52c..1d7fc48563 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -62,6 +62,11 @@ class APIConnection : public APIServerConnection { bool send_climate_state(climate::Climate *climate); bool send_climate_info(climate::Climate *climate); void climate_command(const ClimateCommandRequest &msg) override; +#endif +#ifdef USE_NUMBER + bool send_number_state(number::Number *number, float state); + bool send_number_info(number::Number *number); + void number_command(const NumberCommandRequest &msg) override; #endif bool send_log_message(int level, const char *tag, const char *line); void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 83ebdd8b68..c3cfc8cd76 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -3256,6 +3256,179 @@ void ClimateCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: { + this->object_id = value.as_string(); + return true; + } + case 3: { + this->name = value.as_string(); + return true; + } + case 4: { + this->unique_id = value.as_string(); + return true; + } + case 5: { + this->icon = value.as_string(); + return true; + } + default: + return false; + } +} +bool ListEntitiesNumberResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 2: { + this->key = value.as_fixed32(); + return true; + } + case 6: { + this->min_value = value.as_float(); + return true; + } + case 7: { + this->max_value = value.as_float(); + return true; + } + case 8: { + this->step = value.as_float(); + return true; + } + default: + return false; + } +} +void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->object_id); + buffer.encode_fixed32(2, this->key); + buffer.encode_string(3, this->name); + buffer.encode_string(4, this->unique_id); + buffer.encode_string(5, this->icon); + buffer.encode_float(6, this->min_value); + buffer.encode_float(7, this->max_value); + buffer.encode_float(8, this->step); +} +void ListEntitiesNumberResponse::dump_to(std::string &out) const { + char buffer[64]; + out.append("ListEntitiesNumberResponse {\n"); + out.append(" object_id: "); + out.append("'").append(this->object_id).append("'"); + out.append("\n"); + + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" name: "); + out.append("'").append(this->name).append("'"); + out.append("\n"); + + out.append(" unique_id: "); + out.append("'").append(this->unique_id).append("'"); + out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); + + out.append(" min_value: "); + sprintf(buffer, "%g", this->min_value); + out.append(buffer); + out.append("\n"); + + out.append(" max_value: "); + sprintf(buffer, "%g", this->max_value); + out.append(buffer); + out.append("\n"); + + out.append(" step: "); + sprintf(buffer, "%g", this->step); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +bool NumberStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 3: { + this->missing_state = value.as_bool(); + return true; + } + default: + return false; + } +} +bool NumberStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: { + this->key = value.as_fixed32(); + return true; + } + case 2: { + this->state = value.as_float(); + return true; + } + default: + return false; + } +} +void NumberStateResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_fixed32(1, this->key); + buffer.encode_float(2, this->state); + buffer.encode_bool(3, this->missing_state); +} +void NumberStateResponse::dump_to(std::string &out) const { + char buffer[64]; + out.append("NumberStateResponse {\n"); + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" state: "); + sprintf(buffer, "%g", this->state); + out.append(buffer); + out.append("\n"); + + out.append(" missing_state: "); + out.append(YESNO(this->missing_state)); + out.append("\n"); + out.append("}"); +} +bool NumberCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: { + this->key = value.as_fixed32(); + return true; + } + case 2: { + this->state = value.as_float(); + return true; + } + default: + return false; + } +} +void NumberCommandRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_fixed32(1, this->key); + buffer.encode_float(2, this->state); +} +void NumberCommandRequest::dump_to(std::string &out) const { + char buffer[64]; + out.append("NumberCommandRequest {\n"); + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" state: "); + sprintf(buffer, "%g", this->state); + out.append(buffer); + out.append("\n"); + out.append("}"); +} } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 6873e0d54c..e3bb1d9106 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -782,6 +782,45 @@ class ClimateCommandRequest : public ProtoMessage { bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; +class ListEntitiesNumberResponse : public ProtoMessage { + public: + std::string object_id{}; + uint32_t key{0}; + std::string name{}; + std::string unique_id{}; + std::string icon{}; + float min_value{0.0f}; + float max_value{0.0f}; + float step{0.0f}; + void encode(ProtoWriteBuffer buffer) const override; + void dump_to(std::string &out) const override; + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; +}; +class NumberStateResponse : public ProtoMessage { + public: + uint32_t key{0}; + float state{0.0f}; + bool missing_state{false}; + void encode(ProtoWriteBuffer buffer) const override; + void dump_to(std::string &out) const override; + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class NumberCommandRequest : public ProtoMessage { + public: + uint32_t key{0}; + float state{0.0f}; + void encode(ProtoWriteBuffer buffer) const override; + void dump_to(std::string &out) const override; + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; +}; } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 4fade19787..440a5d0ab3 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -184,6 +184,20 @@ bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResp #endif #ifdef USE_CLIMATE #endif +#ifdef USE_NUMBER +bool APIServerConnectionBase::send_list_entities_number_response(const ListEntitiesNumberResponse &msg) { + ESP_LOGVV(TAG, "send_list_entities_number_response: %s", msg.dump().c_str()); + return this->send_message_(msg, 49); +} +#endif +#ifdef USE_NUMBER +bool APIServerConnectionBase::send_number_state_response(const NumberStateResponse &msg) { + ESP_LOGVV(TAG, "send_number_state_response: %s", msg.dump().c_str()); + return this->send_message_(msg, 50); +} +#endif +#ifdef USE_NUMBER +#endif bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { switch (msg_type) { case 1: { @@ -349,6 +363,15 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, msg.decode(msg_data, msg_size); ESP_LOGVV(TAG, "on_climate_command_request: %s", msg.dump().c_str()); this->on_climate_command_request(msg); +#endif + break; + } + case 51: { +#ifdef USE_NUMBER + NumberCommandRequest msg; + msg.decode(msg_data, msg_size); + ESP_LOGVV(TAG, "on_number_command_request: %s", msg.dump().c_str()); + this->on_number_command_request(msg); #endif break; } @@ -547,6 +570,19 @@ void APIServerConnection::on_climate_command_request(const ClimateCommandRequest this->climate_command(msg); } #endif +#ifdef USE_NUMBER +void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->number_command(msg); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index afbe39e314..398c10a811 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -111,6 +111,15 @@ class APIServerConnectionBase : public ProtoService { #endif #ifdef USE_CLIMATE virtual void on_climate_command_request(const ClimateCommandRequest &value){}; +#endif +#ifdef USE_NUMBER + bool send_list_entities_number_response(const ListEntitiesNumberResponse &msg); +#endif +#ifdef USE_NUMBER + bool send_number_state_response(const NumberStateResponse &msg); +#endif +#ifdef USE_NUMBER + virtual void on_number_command_request(const NumberCommandRequest &value){}; #endif protected: bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; @@ -147,6 +156,9 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_CLIMATE virtual void climate_command(const ClimateCommandRequest &msg) = 0; +#endif +#ifdef USE_NUMBER + virtual void number_command(const NumberCommandRequest &msg) = 0; #endif protected: void on_hello_request(const HelloRequest &msg) override; @@ -179,6 +191,9 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_CLIMATE void on_climate_command_request(const ClimateCommandRequest &msg) override; #endif +#ifdef USE_NUMBER + void on_number_command_request(const NumberCommandRequest &msg) override; +#endif }; } // namespace api diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 1373533ae2..7434030565 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -197,6 +197,15 @@ void APIServer::on_climate_update(climate::Climate *obj) { } #endif +#ifdef USE_NUMBER +void APIServer::on_number_update(number::Number *obj, float state) { + if (obj->is_internal()) + return; + for (auto *c : this->clients_) + c->send_number_state(obj, state); +} +#endif + float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; } void APIServer::set_port(uint16_t port) { this->port_ = port; } APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index eb6c91d01c..add22e121e 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -60,6 +60,9 @@ class APIServer : public Component, public Controller { #endif #ifdef USE_CLIMATE void on_climate_update(climate::Climate *obj) override; +#endif +#ifdef USE_NUMBER + void on_number_update(number::Number *obj, float state) override; #endif void send_homeassistant_service_call(const HomeassistantServiceResponse &call); void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index d4245136ae..8897758073 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -51,5 +51,9 @@ bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) { bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_info(climate); } #endif +#ifdef USE_NUMBER +bool ListEntitiesIterator::on_number(number::Number *number) { return this->client_->send_number_info(number); } +#endif + } // namespace api } // namespace esphome diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index 6b10a72fdf..c55ba5089e 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -39,6 +39,9 @@ class ListEntitiesIterator : public ComponentIterator { #endif #ifdef USE_CLIMATE bool on_climate(climate::Climate *climate) override; +#endif +#ifdef USE_NUMBER + bool on_number(number::Number *number) override; #endif bool on_end() override; diff --git a/esphome/components/api/subscribe_state.cpp b/esphome/components/api/subscribe_state.cpp index 2612a852d3..25aa7c8b31 100644 --- a/esphome/components/api/subscribe_state.cpp +++ b/esphome/components/api/subscribe_state.cpp @@ -37,6 +37,11 @@ bool InitialStateIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) #ifdef USE_CLIMATE bool InitialStateIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_state(climate); } #endif +#ifdef USE_NUMBER +bool InitialStateIterator::on_number(number::Number *number) { + return this->client_->send_number_state(number, number->state); +} +#endif InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client) : ComponentIterator(server), client_(client) {} diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index 51b9c695e4..f03322ac4a 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -36,6 +36,9 @@ class InitialStateIterator : public ComponentIterator { #endif #ifdef USE_CLIMATE bool on_climate(climate::Climate *climate) override; +#endif +#ifdef USE_NUMBER + bool on_number(number::Number *number) override; #endif protected: APIConnection *client_; diff --git a/esphome/components/api/util.cpp b/esphome/components/api/util.cpp index f929db5d6a..6e05d49b74 100644 --- a/esphome/components/api/util.cpp +++ b/esphome/components/api/util.cpp @@ -167,6 +167,21 @@ void ComponentIterator::advance() { } } break; +#endif +#ifdef USE_NUMBER + case IteratorState::NUMBER: + if (this->at_ >= App.get_numbers().size()) { + advance_platform = true; + } else { + auto *number = App.get_numbers()[this->at_]; + if (number->is_internal()) { + success = true; + break; + } else { + success = this->on_number(number); + } + } + break; #endif case IteratorState::MAX: if (this->on_end()) { diff --git a/esphome/components/api/util.h b/esphome/components/api/util.h index 5a29a48cbe..f8b248056b 100644 --- a/esphome/components/api/util.h +++ b/esphome/components/api/util.h @@ -47,6 +47,9 @@ class ComponentIterator { #endif #ifdef USE_CLIMATE virtual bool on_climate(climate::Climate *climate) = 0; +#endif +#ifdef USE_NUMBER + virtual bool on_number(number::Number *number) = 0; #endif virtual bool on_end(); @@ -81,6 +84,9 @@ class ComponentIterator { #endif #ifdef USE_CLIMATE CLIMATE, +#endif +#ifdef USE_NUMBER + NUMBER, #endif MAX, } state_{IteratorState::NONE}; diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 906c570b17..3559fce046 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -91,6 +91,7 @@ MQTTJSONLightComponent = mqtt_ns.class_("MQTTJSONLightComponent", MQTTComponent) MQTTSensorComponent = mqtt_ns.class_("MQTTSensorComponent", MQTTComponent) MQTTSwitchComponent = mqtt_ns.class_("MQTTSwitchComponent", MQTTComponent) MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent) +MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent) def validate_config(value): diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp new file mode 100644 index 0000000000..bb67a225fd --- /dev/null +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -0,0 +1,61 @@ +#include "mqtt_number.h" +#include "esphome/core/log.h" + +#ifdef USE_NUMBER + +#ifdef USE_DEEP_SLEEP +#include "esphome/components/deep_sleep/deep_sleep_component.h" +#endif + +namespace esphome { +namespace mqtt { + +static const char *const TAG = "mqtt.number"; + +using namespace esphome::number; + +MQTTNumberComponent::MQTTNumberComponent(Number *number) : MQTTComponent(), number_(number) {} + +void MQTTNumberComponent::setup() { + this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) { + auto val = parse_float(state); + if (!val.has_value()) { + ESP_LOGE(TAG, "Can't convert '%s' to number!", state.c_str()); + return; + } + auto call = this->number_->make_call(); + call.set_value(*val); + call.perform(); + }); + this->number_->add_on_state_callback([this](float state) { this->publish_state(state); }); +} + +void MQTTNumberComponent::dump_config() { + ESP_LOGCONFIG(TAG, "MQTT Number '%s':", this->number_->get_name().c_str()); + LOG_MQTT_COMPONENT(true, false) +} + +std::string MQTTNumberComponent::component_type() const { return "number"; } + +std::string MQTTNumberComponent::friendly_name() const { return this->number_->get_name(); } +void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { + if (!this->number_->get_icon().empty()) + root["icon"] = this->number_->get_icon(); +} +bool MQTTNumberComponent::send_initial_state() { + if (this->number_->has_state()) { + return this->publish_state(this->number_->state); + } else { + return true; + } +} +bool MQTTNumberComponent::is_internal() { return this->number_->is_internal(); } +bool MQTTNumberComponent::publish_state(float value) { + int8_t accuracy = this->number_->get_accuracy_decimals(); + return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy)); +} + +} // namespace mqtt +} // namespace esphome + +#endif diff --git a/esphome/components/mqtt/mqtt_number.h b/esphome/components/mqtt/mqtt_number.h new file mode 100644 index 0000000000..f44de91435 --- /dev/null +++ b/esphome/components/mqtt/mqtt_number.h @@ -0,0 +1,46 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_NUMBER + +#include "esphome/components/number/number.h" +#include "mqtt_component.h" + +namespace esphome { +namespace mqtt { + +class MQTTNumberComponent : public mqtt::MQTTComponent { + public: + /** Construct this MQTTNumberComponent instance with the provided friendly_name and number + * + * @param number The number. + */ + explicit MQTTNumberComponent(number::Number *number); + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + /// Override setup. + void setup() override; + void dump_config() override; + + void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; + + bool send_initial_state() override; + bool is_internal() override; + + bool publish_state(float value); + + protected: + /// Override for MQTTComponent, returns "number". + std::string component_type() const override; + + std::string friendly_name() const override; + + number::Number *number_; +}; + +} // namespace mqtt +} // namespace esphome + +#endif diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py new file mode 100644 index 0000000000..ed33931d8b --- /dev/null +++ b/esphome/components/number/__init__.py @@ -0,0 +1,154 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.components import mqtt +from esphome.const import ( + CONF_ABOVE, + CONF_BELOW, + CONF_ICON, + CONF_ID, + CONF_INTERNAL, + CONF_ON_VALUE, + CONF_ON_VALUE_RANGE, + CONF_TRIGGER_ID, + CONF_NAME, + CONF_MQTT_ID, + CONF_VALUE, + ICON_EMPTY, +) +from esphome.core import CORE, coroutine_with_priority + +CODEOWNERS = ["@esphome/core"] +IS_PLATFORM_COMPONENT = True + +number_ns = cg.esphome_ns.namespace("number") +Number = number_ns.class_("Number", cg.Nameable) +NumberPtr = Number.operator("ptr") + +# Triggers +NumberStateTrigger = number_ns.class_( + "NumberStateTrigger", automation.Trigger.template(cg.float_) +) +ValueRangeTrigger = number_ns.class_( + "ValueRangeTrigger", automation.Trigger.template(cg.float_), cg.Component +) + +# Actions +NumberSetAction = number_ns.class_("NumberSetAction", automation.Action) + +# Conditions +NumberInRangeCondition = number_ns.class_( + "NumberInRangeCondition", automation.Condition +) + +icon = cv.icon + + +NUMBER_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent), + cv.GenerateID(): cv.declare_id(Number), + cv.Optional(CONF_ICON, default=ICON_EMPTY): icon, + cv.Optional(CONF_ON_VALUE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(NumberStateTrigger), + } + ), + cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger), + cv.Optional(CONF_ABOVE): cv.float_, + cv.Optional(CONF_BELOW): cv.float_, + }, + cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), + ), + } +) + + +async def setup_number_core_(var, config): + cg.add(var.set_name(config[CONF_NAME])) + if CONF_INTERNAL in config: + cg.add(var.set_internal(config[CONF_INTERNAL])) + + cg.add(var.set_icon(config[CONF_ICON])) + + for conf in config.get(CONF_ON_VALUE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(float, "x")], conf) + for conf in config.get(CONF_ON_VALUE_RANGE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await cg.register_component(trigger, conf) + if CONF_ABOVE in conf: + template_ = await cg.templatable(conf[CONF_ABOVE], [(float, "x")], float) + cg.add(trigger.set_min(template_)) + if CONF_BELOW in conf: + template_ = await cg.templatable(conf[CONF_BELOW], [(float, "x")], float) + cg.add(trigger.set_max(template_)) + await automation.build_automation(trigger, [(float, "x")], conf) + + if CONF_MQTT_ID in config: + mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) + await mqtt.register_mqtt_component(mqtt_, config) + + +async def register_number(var, config): + if not CORE.has_id(config[CONF_ID]): + var = cg.Pvariable(config[CONF_ID], var) + cg.add(cg.App.register_number(var)) + await setup_number_core_(var, config) + + +async def new_number(config): + var = cg.new_Pvariable(config[CONF_ID]) + await register_number(var, config) + return var + + +NUMBER_IN_RANGE_CONDITION_SCHEMA = cv.All( + { + cv.Required(CONF_ID): cv.use_id(Number), + cv.Optional(CONF_ABOVE): cv.float_, + cv.Optional(CONF_BELOW): cv.float_, + }, + cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), +) + + +@automation.register_condition( + "number.in_range", NumberInRangeCondition, NUMBER_IN_RANGE_CONDITION_SCHEMA +) +async def number_in_range_to_code(config, condition_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(condition_id, template_arg, paren) + + if CONF_ABOVE in config: + cg.add(var.set_min(config[CONF_ABOVE])) + if CONF_BELOW in config: + cg.add(var.set_max(config[CONF_BELOW])) + + return var + + +@coroutine_with_priority(40.0) +async def to_code(config): + cg.add_define("USE_NUMBER") + cg.add_global(number_ns.using) + + +@automation.register_action( + "number.set", + NumberSetAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(Number), + cv.Required(CONF_VALUE): cv.templatable(cv.float_), + } + ), +) +async def number_set_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_VALUE], args, float) + cg.add(var.set_value(template_)) + return var diff --git a/esphome/components/number/automation.cpp b/esphome/components/number/automation.cpp new file mode 100644 index 0000000000..a0b169427f --- /dev/null +++ b/esphome/components/number/automation.cpp @@ -0,0 +1,47 @@ +#include "automation.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace number { + +static const char *const TAG = "number.automation"; + +void ValueRangeTrigger::setup() { + this->rtc_ = global_preferences.make_preference(this->parent_->get_object_id_hash()); + bool initial_state; + if (this->rtc_.load(&initial_state)) { + this->previous_in_range_ = initial_state; + } + + this->parent_->add_on_state_callback([this](float state) { this->on_state_(state); }); +} +float ValueRangeTrigger::get_setup_priority() const { return setup_priority::HARDWARE; } + +void ValueRangeTrigger::on_state_(float state) { + if (isnan(state)) + return; + + float local_min = this->min_.value(state); + float local_max = this->max_.value(state); + + bool in_range; + if (isnan(local_min) && isnan(local_max)) { + in_range = this->previous_in_range_; + } else if (isnan(local_min)) { + in_range = state <= local_max; + } else if (isnan(local_max)) { + in_range = state >= local_min; + } else { + in_range = local_min <= state && state <= local_max; + } + + if (in_range != this->previous_in_range_ && in_range) { + this->trigger(state); + } + + this->previous_in_range_ = in_range; + this->rtc_.save(&in_range); +} + +} // namespace number +} // namespace esphome diff --git a/esphome/components/number/automation.h b/esphome/components/number/automation.h new file mode 100644 index 0000000000..9e812f8c49 --- /dev/null +++ b/esphome/components/number/automation.h @@ -0,0 +1,76 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace number { + +class NumberStateTrigger : public Trigger { + public: + explicit NumberStateTrigger(Number *parent) { + parent->add_on_state_callback([this](float value) { this->trigger(value); }); + } +}; + +template class NumberSetAction : public Action { + public: + NumberSetAction(Number *number) : number_(number) {} + TEMPLATABLE_VALUE(float, value) + + void play(Ts... x) override { + auto call = this->number_->make_call(); + call.set_value(this->value_.value(x...)); + call.perform(); + } + + protected: + Number *number_; +}; + +class ValueRangeTrigger : public Trigger, public Component { + public: + explicit ValueRangeTrigger(Number *parent) : parent_(parent) {} + + template void set_min(V min) { this->min_ = min; } + template void set_max(V max) { this->max_ = max; } + + void setup() override; + float get_setup_priority() const override; + + protected: + void on_state_(float state); + + Number *parent_; + ESPPreferenceObject rtc_; + bool previous_in_range_{false}; + TemplatableValue min_{NAN}; + TemplatableValue max_{NAN}; +}; + +template class NumberInRangeCondition : public Condition { + public: + NumberInRangeCondition(Number *parent) : parent_(parent) {} + + void set_min(float min) { this->min_ = min; } + void set_max(float max) { this->max_ = max; } + bool check(Ts... x) override { + const float state = this->parent_->state; + if (isnan(this->min_)) { + return state <= this->max_; + } else if (isnan(this->max_)) { + return state >= this->min_; + } else { + return this->min_ <= state && state <= this->max_; + } + } + + protected: + Number *parent_; + float min_{NAN}; + float max_{NAN}; +}; + +} // namespace number +} // namespace esphome diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp new file mode 100644 index 0000000000..eaee5d4e69 --- /dev/null +++ b/esphome/components/number/number.cpp @@ -0,0 +1,76 @@ +#include "number.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace number { + +static const char *const TAG = "number"; + +void NumberCall::perform() { + ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); + if (this->value_.has_value()) { + auto value = *this->value_; + uint8_t accuracy = this->parent_->get_accuracy_decimals(); + float min_value = this->parent_->get_min_value(); + if (value < min_value) { + ESP_LOGW(TAG, " Value %s must not be less than minimum %s", value_accuracy_to_string(value, accuracy).c_str(), + value_accuracy_to_string(min_value, accuracy).c_str()); + this->value_.reset(); + return; + } + float max_value = this->parent_->get_max_value(); + if (value > max_value) { + ESP_LOGW(TAG, " Value %s must not be larger than maximum %s", value_accuracy_to_string(value, accuracy).c_str(), + value_accuracy_to_string(max_value, accuracy).c_str()); + this->value_.reset(); + return; + } + ESP_LOGD(TAG, " Value: %s", value_accuracy_to_string(*this->value_, accuracy).c_str()); + this->parent_->set(*this->value_); + } +} + +NumberCall &NumberCall::set_value(float value) { + this->value_ = value; + return *this; +} + +const optional &NumberCall::get_value() const { return this->value_; } + +NumberCall Number::make_call() { return NumberCall(this); } + +void Number::publish_state(float state) { + this->has_state_ = true; + this->state = state; + ESP_LOGD(TAG, "'%s': Sending state %.5f", this->get_name().c_str(), state); + this->state_callback_.call(state); +} + +uint32_t Number::update_interval() { return 0; } +Number::Number(const std::string &name) : Nameable(name), state(NAN) {} +Number::Number() : Number("") {} + +void Number::add_on_state_callback(std::function &&callback) { + this->state_callback_.add(std::move(callback)); +} +void Number::set_icon(const std::string &icon) { this->icon_ = icon; } +std::string Number::get_icon() { return *this->icon_; } +int8_t Number::get_accuracy_decimals() { + // use printf %g to find number of digits based on step + char buf[32]; + sprintf(buf, "%.5g", this->step_); + std::string str{buf}; + size_t dot_pos = str.find('.'); + if (dot_pos == std::string::npos) + return 0; + + return str.length() - dot_pos - 1; +} +float Number::get_state() const { return this->state; } + +bool Number::has_state() const { return this->has_state_; } + +uint32_t Number::hash_base() { return 2282307003UL; } + +} // namespace number +} // namespace esphome diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h new file mode 100644 index 0000000000..4fe9692a6b --- /dev/null +++ b/esphome/components/number/number.h @@ -0,0 +1,112 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace number { + +#define LOG_NUMBER(prefix, type, obj) \ + if ((obj) != nullptr) { \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ + if (!(obj)->get_icon().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ + } \ + } + +class Number; + +class NumberCall { + public: + explicit NumberCall(Number *parent) : parent_(parent) {} + NumberCall &set_value(float value); + void perform(); + + const optional &get_value() const; + + protected: + Number *const parent_; + optional value_; +}; + +/** Base-class for all numbers. + * + * A number can use publish_state to send out a new value. + */ +class Number : public Nameable { + public: + explicit Number(); + explicit Number(const std::string &name); + + /** Manually set the icon of this number. By default the number's default defined by icon() is used. + * + * @param icon The icon, for example "mdi:flash". "" to disable. + */ + void set_icon(const std::string &icon); + /// Get the Home Assistant Icon. Uses the manual override if specified or the default value instead. + std::string get_icon(); + + /// Getter-syntax for .state. + float get_state() const; + + /// Get the accuracy in decimals. Based on the step value. + int8_t get_accuracy_decimals(); + + /** Publish the current state to the front-end. + */ + void publish_state(float state); + + NumberCall make_call(); + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + /// Add a callback that will be called every time the state changes. + void add_on_state_callback(std::function &&callback); + + /** This member variable stores the last state. + * + * On startup, when no state is available yet, this is NAN (not-a-number) and the validity + * can be checked using has_state(). + * + * This is exposed through a member variable for ease of use in esphome lambdas. + */ + float state; + + /// Return whether this number has gotten a full state yet. + bool has_state() const; + + /// Return with which interval the number is polled. Return 0 for non-polling mode. + virtual uint32_t update_interval(); + + void set_min_value(float min_value) { this->min_value_ = min_value; } + void set_max_value(float max_value) { this->max_value_ = max_value; } + void set_step(float step) { this->step_ = step; } + + float get_min_value() const { return this->min_value_; } + float get_max_value() const { return this->max_value_; } + float get_step() const { return this->step_; } + + protected: + friend class NumberCall; + + /** Set the value of the number, this is a virtual method that each number integration must implement. + * + * This method is called by the NumberCall. + * + * @param value The value as validated by the NumberCall. + */ + virtual void set(float value) = 0; + + uint32_t hash_base() override; + + CallbackManager state_callback_; + /// Override the icon advertised to Home Assistant, otherwise number's icon will be used. + optional icon_; + bool has_state_{false}; + float step_{1.0}; + float min_value_{0}; + float max_value_{100}; +}; + +} // namespace number +} // namespace esphome diff --git a/esphome/components/template/number/__init__.py b/esphome/components/template/number/__init__.py new file mode 100644 index 0000000000..cf70a48c4d --- /dev/null +++ b/esphome/components/template/number/__init__.py @@ -0,0 +1,63 @@ +from esphome import automation +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import number +from esphome.const import ( + CONF_ID, + CONF_LAMBDA, + CONF_MAX_VALUE, + CONF_MIN_VALUE, + CONF_OPTIMISTIC, + CONF_STEP, +) +from .. import template_ns + +TemplateNumber = template_ns.class_( + "TemplateNumber", number.Number, cg.PollingComponent +) + +CONF_SET_ACTION = "set_action" + + +def validate_min_max(config): + if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]: + raise cv.Invalid("max_value must be greater than min_value") + return config + + +CONFIG_SCHEMA = cv.All( + number.NUMBER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TemplateNumber), + cv.Required(CONF_MAX_VALUE): cv.float_, + cv.Required(CONF_MIN_VALUE): cv.float_, + cv.Required(CONF_STEP): cv.positive_float, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), + } + ).extend(cv.polling_component_schema("60s")), + validate_min_max, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await number.register_number(var, config) + + if CONF_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_LAMBDA], [], return_type=cg.optional.template(float) + ) + cg.add(var.set_template(template_)) + if CONF_SET_ACTION in config: + await automation.build_automation( + var.get_set_trigger(), [(float, "x")], config[CONF_SET_ACTION] + ) + + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + + cg.add(var.set_min_value(config[CONF_MIN_VALUE])) + cg.add(var.set_max_value(config[CONF_MAX_VALUE])) + cg.add(var.set_step(config[CONF_STEP])) diff --git a/esphome/components/template/number/template_number.cpp b/esphome/components/template/number/template_number.cpp new file mode 100644 index 0000000000..69c5d62684 --- /dev/null +++ b/esphome/components/template/number/template_number.cpp @@ -0,0 +1,39 @@ +#include "template_number.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace template_ { + +static const char *const TAG = "template.number"; + +TemplateNumber::TemplateNumber() : set_trigger_(new Trigger()) {} + +void TemplateNumber::update() { + if (!this->f_.has_value()) + return; + + auto val = (*this->f_)(); + if (val.has_value()) { + this->publish_state(*val); + } +} + +void TemplateNumber::set(float value) { + this->set_trigger_->trigger(value); + + if (this->optimistic_) + this->publish_state(value); +} +float TemplateNumber::get_setup_priority() const { return setup_priority::HARDWARE; } +void TemplateNumber::set_template(std::function()> &&f) { this->f_ = f; } +void TemplateNumber::dump_config() { + LOG_NUMBER("", "Template Number", this); + ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); + LOG_UPDATE_INTERVAL(this); +} + +void TemplateNumber::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } +Trigger *TemplateNumber::get_set_trigger() const { return this->set_trigger_; }; + +} // namespace template_ +} // namespace esphome diff --git a/esphome/components/template/number/template_number.h b/esphome/components/template/number/template_number.h new file mode 100644 index 0000000000..4c633e3b53 --- /dev/null +++ b/esphome/components/template/number/template_number.h @@ -0,0 +1,30 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace template_ { + +class TemplateNumber : public number::Number, public PollingComponent { + public: + TemplateNumber(); + void set_template(std::function()> &&f); + + void update() override; + void dump_config() override; + float get_setup_priority() const override; + + Trigger *get_set_trigger() const; + void set_optimistic(bool optimistic); + + protected: + void set(float value) override; + bool optimistic_{false}; + Trigger *set_trigger_; + optional()>> f_; +}; + +} // namespace template_ +} // namespace esphome diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 7a6d877d8f..57eef7a946 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -119,6 +119,12 @@ void WebServer::setup() { if (!obj->is_internal()) client->send(this->cover_json(obj).c_str(), "state"); #endif + +#ifdef USE_NUMBER + for (auto *obj : App.get_numbers()) + if (!obj->is_internal()) + client->send(this->number_json(obj, obj->state).c_str(), "state"); +#endif }); #ifdef USE_LOGGER @@ -196,6 +202,11 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { write_row(stream, obj, "cover", ""); #endif +#ifdef USE_NUMBER + for (auto *obj : App.get_numbers()) + write_row(stream, obj, "number", ""); +#endif + stream->print(F("

    See ESPHome Web API for " "REST API documentation.

    " "

    OTA Update

    events_.send(this->number_json(obj, state).c_str(), "state"); +} +void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) { + for (auto *obj : App.get_numbers()) { + if (obj->is_internal()) + continue; + if (obj->get_object_id() != match.id) + continue; + std::string data = this->number_json(obj, obj->state); + request->send(200, "text/json", data.c_str()); + return; + } + request->send(404); +} +std::string WebServer::number_json(number::Number *obj, float value) { + return json::build_json([obj, value](JsonObject &root) { + root["id"] = "number-" + obj->get_object_id(); + std::string state = value_accuracy_to_string(value, obj->get_accuracy_decimals()); + root["state"] = state; + root["value"] = value; + }); +} +#endif + bool WebServer::canHandle(AsyncWebServerRequest *request) { if (request->url() == "/") return true; @@ -636,6 +673,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return true; #endif +#ifdef USE_NUMBER + if (request->method() == HTTP_GET && match.domain == "number") + return true; +#endif + return false; } void WebServer::handleRequest(AsyncWebServerRequest *request) { @@ -711,6 +753,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { return; } #endif + +#ifdef USE_NUMBER + if (match.domain == "number") { + this->handle_number_request(request, match); + return; + } +#endif } bool WebServer::isRequestHandlerTrivial() { return false; } diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 89e23b7071..4789c6e1c0 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -154,6 +154,15 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { std::string cover_json(cover::Cover *obj); #endif +#ifdef USE_NUMBER + void on_number_update(number::Number *obj, float state) override; + /// Handle a number request under '/number/'. + void handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match); + + /// Dump the number state with its value as a JSON string. + std::string number_json(number::Number *obj, float value); +#endif + /// Override the web handler's canHandle method. bool canHandle(AsyncWebServerRequest *request) override; /// Override the web handler's handleRequest method. diff --git a/esphome/const.py b/esphome/const.py index 9b7490878d..e82b66ee37 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -553,6 +553,7 @@ CONF_STATE_CLASS = "state_class" CONF_STATE_TOPIC = "state_topic" CONF_STATIC_IP = "static_ip" CONF_STATUS = "status" +CONF_STEP = "step" CONF_STEP_MODE = "step_mode" CONF_STEP_PIN = "step_pin" CONF_STOP = "stop" diff --git a/esphome/core/application.h b/esphome/core/application.h index 774f6e3aa8..e065552a74 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -32,6 +32,9 @@ #ifdef USE_COVER #include "esphome/components/cover/cover.h" #endif +#ifdef USE_NUMBER +#include "esphome/components/number/number.h" +#endif namespace esphome { @@ -82,6 +85,10 @@ class Application { void register_light(light::LightState *light) { this->lights_.push_back(light); } #endif +#ifdef USE_NUMBER + void register_number(number::Number *number) { this->numbers_.push_back(number); } +#endif + /// Register the component in this Application instance. template C *register_component(C *c) { static_assert(std::is_base_of::value, "Only Component subclasses can be registered"); @@ -208,6 +215,15 @@ class Application { return nullptr; } #endif +#ifdef USE_NUMBER + const std::vector &get_numbers() { return this->numbers_; } + number::Number *get_number_by_key(uint32_t key, bool include_internal = false) { + for (auto *obj : this->numbers_) + if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) + return obj; + return nullptr; + } +#endif Scheduler scheduler; @@ -245,6 +261,9 @@ class Application { #ifdef USE_LIGHT std::vector lights_{}; #endif +#ifdef USE_NUMBER + std::vector numbers_{}; +#endif std::string name_; std::string compilation_time_; diff --git a/esphome/core/controller.cpp b/esphome/core/controller.cpp index f3d10b23ff..305fe93532 100644 --- a/esphome/core/controller.cpp +++ b/esphome/core/controller.cpp @@ -53,6 +53,12 @@ void Controller::setup_controller() { obj->add_on_state_callback([this, obj]() { this->on_climate_update(obj); }); } #endif +#ifdef USE_NUMBER + for (auto *obj : App.get_numbers()) { + if (!obj->is_internal()) + obj->add_on_state_callback([this, obj](float state) { this->on_number_update(obj, state); }); + } +#endif } } // namespace esphome diff --git a/esphome/core/controller.h b/esphome/core/controller.h index 0e94a43c4c..746658075f 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -25,6 +25,9 @@ #ifdef USE_CLIMATE #include "esphome/components/climate/climate.h" #endif +#ifdef USE_NUMBER +#include "esphome/components/number/number.h" +#endif namespace esphome { @@ -55,6 +58,9 @@ class Controller { #ifdef USE_CLIMATE virtual void on_climate_update(climate::Climate *obj){}; #endif +#ifdef USE_NUMBER + virtual void on_number_update(number::Number *obj, float state){}; +#endif }; } // namespace esphome diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 90562510b9..cac03fc703 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -13,6 +13,7 @@ #define USE_COVER #define USE_LIGHT #define USE_CLIMATE +#define USE_NUMBER #define USE_MQTT #define USE_POWER_SUPPLY #define USE_HOMEASSISTANT_TIME diff --git a/script/ci-custom.py b/script/ci-custom.py index 4ec7c664a4..02f193c6e0 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -559,6 +559,7 @@ def lint_inclusive_language(fname, match): "esphome/components/display/display_buffer.h", "esphome/components/i2c/i2c.h", "esphome/components/mqtt/mqtt_component.h", + "esphome/components/number/number.h", "esphome/components/output/binary_output.h", "esphome/components/output/float_output.h", "esphome/components/sensor/sensor.h", diff --git a/tests/test5.yaml b/tests/test5.yaml index ba047721e2..35225402a3 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -38,3 +38,20 @@ 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 From d77c3abdc05ed4d4414ace1dfd825dea06bb1cb2 Mon Sep 17 00:00:00 2001 From: monkeyclass <30174727+monkeyclass@users.noreply.github.com> Date: Mon, 12 Jul 2021 21:37:34 +0200 Subject: [PATCH 0999/1841] Fixed lolin32 lite key (#2001) Co-authored-by: monkeyclass --- esphome/pins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/pins.py b/esphome/pins.py index fef77f3946..6356ae9bd0 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -538,7 +538,7 @@ ESP32_BOARD_PINS = { "iotbusio": {}, "iotbusproteus": {}, "lolin32": {"LED": 5}, - "lolin32-lite": {"LED": 22}, + "lolin32_lite": {"LED": 22}, "lolin_d32": {"LED": 5, "_VBAT": 35}, "lolin_d32_pro": {"LED": 5, "_VBAT": 35}, "lopy": { From cc9f0b3f47e5310aac38cb7d90c89e320d8c25af Mon Sep 17 00:00:00 2001 From: Mikko Tervala Date: Mon, 12 Jul 2021 23:55:53 +0300 Subject: [PATCH 1000/1841] Add support for IBS-TH1 External Sensor (#1983) --- .../inkbird_ibsth1_mini.cpp | 30 +++++++++++++++++-- .../inkbird_ibsth1_mini/inkbird_ibsth1_mini.h | 2 ++ .../components/inkbird_ibsth1_mini/sensor.py | 12 ++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp index 03bc4f9f92..45be7c1acf 100644 --- a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp @@ -11,6 +11,7 @@ static const char *const TAG = "inkbird_ibsth1_mini"; void InkbirdIBSTH1_MINI::dump_config() { ESP_LOGCONFIG(TAG, "Inkbird IBS TH1 MINI"); LOG_SENSOR(" ", "Temperature", this->temperature_); + LOG_SENSOR(" ", "External Temperature", this->external_temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); } @@ -54,7 +55,7 @@ bool InkbirdIBSTH1_MINI::parse_device(const esp32_ble_tracker::ESPBTDevice &devi ESP_LOGVV(TAG, "parse_device(): manufacturer data element length is expected to be of length 7"); return false; } - if ((mnfData.data[2] != 0) || (mnfData.data[6] != 8)) { + if (mnfData.data[6] != 8) { ESP_LOGVV(TAG, "parse_device(): unexpected data"); return false; } @@ -63,13 +64,36 @@ bool InkbirdIBSTH1_MINI::parse_device(const esp32_ble_tracker::ESPBTDevice &devi // data[5] is a battery level // data[0] and data[1] is humidity * 100 (in pct) // uuid is a temperature * 100 (in Celcius) + // when data[2] == 0 temperature is from internal sensor (IBS-TH1 or IBS-TH1 Mini) + // when data[2] == 1 temperature is from external sensor (IBS-TH1 only) + + // Create empty variables to pass automatic checks + auto temperature = NAN; + auto external_temperature = NAN; + + // Read bluetooth data into variable + auto measured_temperature = mnfData.uuid.get_uuid().uuid.uuid16 / 100.0f; + + // Set temperature or external_temperature based on which sensor is in use + if (mnfData.data[2] == 0) { + temperature = measured_temperature; + } else if (mnfData.data[2] == 1) { + external_temperature = measured_temperature; + } else { + ESP_LOGVV(TAG, "parse_device(): unknown sensor type"); + return false; + } + auto battery_level = mnfData.data[5]; - auto temperature = mnfData.uuid.get_uuid().uuid.uuid16 / 100.0f; auto humidity = ((mnfData.data[1] << 8) + mnfData.data[0]) / 100.0f; - if (this->temperature_ != nullptr) { + // Send temperature only if the value is set + if (!isnan(temperature) && this->temperature_ != nullptr) { this->temperature_->publish_state(temperature); } + if (!isnan(external_temperature) && this->external_temperature_ != nullptr) { + this->external_temperature_->publish_state(external_temperature); + } if (this->humidity_ != nullptr) { this->humidity_->publish_state(humidity); } diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h index 38e72dad17..c3a9f7062d 100644 --- a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h @@ -18,12 +18,14 @@ class InkbirdIBSTH1_MINI : public Component, public esp32_ble_tracker::ESPBTDevi void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } + void set_external_temperature(sensor::Sensor *external_temperature) { external_temperature_ = external_temperature; } void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } protected: uint64_t address_; sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *external_temperature_{nullptr}; sensor::Sensor *humidity_{nullptr}; sensor::Sensor *battery_level_{nullptr}; }; diff --git a/esphome/components/inkbird_ibsth1_mini/sensor.py b/esphome/components/inkbird_ibsth1_mini/sensor.py index 044e7fe67d..aaaaddb890 100644 --- a/esphome/components/inkbird_ibsth1_mini/sensor.py +++ b/esphome/components/inkbird_ibsth1_mini/sensor.py @@ -19,6 +19,8 @@ from esphome.const import ( CODEOWNERS = ["@fkirill"] DEPENDENCIES = ["esp32_ble_tracker"] +CONF_EXTERNAL_TEMPERATURE = "external_temperature" + inkbird_ibsth1_mini_ns = cg.esphome_ns.namespace("inkbird_ibsth1_mini") InkbirdUBSTH1_MINI = inkbird_ibsth1_mini_ns.class_( "InkbirdIBSTH1_MINI", esp32_ble_tracker.ESPBTDeviceListener, cg.Component @@ -36,6 +38,13 @@ CONFIG_SCHEMA = ( DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_EXTERNAL_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, + ICON_EMPTY, + 1, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( UNIT_PERCENT, ICON_EMPTY, @@ -67,6 +76,9 @@ async def to_code(config): if CONF_TEMPERATURE in config: sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) cg.add(var.set_temperature(sens)) + if CONF_EXTERNAL_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_EXTERNAL_TEMPERATURE]) + cg.add(var.set_external_temperature(sens)) if CONF_HUMIDITY in config: sens = await sensor.new_sensor(config[CONF_HUMIDITY]) cg.add(var.set_humidity(sens)) From 551e9c6111d5f0cd797778bccafc878f8d81875b Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 12 Jul 2021 22:56:55 +0200 Subject: [PATCH 1001/1841] Bang bang climate new mode meanings (#1996) --- .../components/bang_bang/bang_bang_climate.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/esphome/components/bang_bang/bang_bang_climate.cpp b/esphome/components/bang_bang/bang_bang_climate.cpp index c043f6b7de..4b41684707 100644 --- a/esphome/components/bang_bang/bang_bang_climate.cpp +++ b/esphome/components/bang_bang/bang_bang_climate.cpp @@ -21,7 +21,12 @@ void BangBangClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles those for us - this->mode = climate::CLIMATE_MODE_HEAT_COOL; + if (supports_cool_ && supports_heat_) + this->mode = climate::CLIMATE_MODE_HEAT_COOL; + else if (supports_cool_) + this->mode = climate::CLIMATE_MODE_COOL; + else if (supports_heat_) + this->mode = climate::CLIMATE_MODE_HEAT; this->change_away_(false); } } @@ -43,12 +48,13 @@ climate::ClimateTraits BangBangClimate::traits() { traits.set_supports_current_temperature(true); traits.set_supported_modes({ climate::CLIMATE_MODE_OFF, - climate::CLIMATE_MODE_HEAT_COOL, }); if (supports_cool_) traits.add_supported_mode(climate::CLIMATE_MODE_COOL); if (supports_heat_) traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + if (supports_cool_ && supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL); traits.set_supports_two_point_target_temperature(true); if (supports_away_) traits.set_supported_presets({ @@ -59,12 +65,8 @@ climate::ClimateTraits BangBangClimate::traits() { return traits; } void BangBangClimate::compute_state_() { - if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) { - // in non-auto mode, switch directly to appropriate action - // - HEAT mode -> HEATING action - // - COOL mode -> COOLING action - // - OFF mode -> OFF action (not IDLE!) - this->switch_to_action_(static_cast(this->mode)); + if (this->mode == climate::CLIMATE_MODE_OFF) { + this->switch_to_action_(climate::CLIMATE_ACTION_OFF); return; } if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || isnan(this->target_temperature_high)) { From 7dd16df846989ddba21b5927912c8ea8d4545634 Mon Sep 17 00:00:00 2001 From: Huub Eikens Date: Mon, 12 Jul 2021 23:21:54 +0200 Subject: [PATCH 1002/1841] Sgp30 sensor improvements (#1510) Co-authored-by: Umberto73 Co-authored-by: Guillermo Ruffino --- esphome/components/ccs811/sensor.py | 5 +-- esphome/components/sgp30/sensor.py | 28 +++++++++++-- esphome/components/sgp30/sgp30.cpp | 65 +++++++++++++++++++++++++++-- esphome/components/sgp30/sgp30.h | 16 +++++++ esphome/const.py | 2 + 5 files changed, 105 insertions(+), 11 deletions(-) diff --git a/esphome/components/ccs811/sensor.py b/esphome/components/ccs811/sensor.py index 4c4f8802d4..4e81d6ac10 100644 --- a/esphome/components/ccs811/sensor.py +++ b/esphome/components/ccs811/sensor.py @@ -8,6 +8,8 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, UNIT_PARTS_PER_BILLION, + CONF_BASELINE, + CONF_ECO2, CONF_TEMPERATURE, CONF_TVOC, CONF_HUMIDITY, @@ -21,9 +23,6 @@ CCS811Component = ccs811_ns.class_( "CCS811Component", cg.PollingComponent, i2c.I2CDevice ) -CONF_ECO2 = "eco2" -CONF_BASELINE = "baseline" - CONFIG_SCHEMA = ( cv.Schema( { diff --git a/esphome/components/sgp30/sensor.py b/esphome/components/sgp30/sensor.py index f393627eda..7a3e870f6d 100644 --- a/esphome/components/sgp30/sensor.py +++ b/esphome/components/sgp30/sensor.py @@ -3,13 +3,16 @@ import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( CONF_ID, + CONF_BASELINE, DEVICE_CLASS_EMPTY, + CONF_ECO2, + CONF_TVOC, ICON_RADIATOR, STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, UNIT_PARTS_PER_BILLION, + UNIT_EMPTY, ICON_MOLECULE_CO2, - CONF_TVOC, ) DEPENDENCIES = ["i2c"] @@ -17,10 +20,9 @@ DEPENDENCIES = ["i2c"] sgp30_ns = cg.esphome_ns.namespace("sgp30") SGP30Component = sgp30_ns.class_("SGP30Component", cg.PollingComponent, i2c.I2CDevice) -CONF_ECO2 = "eco2" -CONF_BASELINE = "baseline" CONF_ECO2_BASELINE = "eco2_baseline" CONF_TVOC_BASELINE = "tvoc_baseline" +CONF_STORE_BASELINE = "store_baseline" CONF_UPTIME = "uptime" CONF_COMPENSATION = "compensation" CONF_HUMIDITY_SOURCE = "humidity_source" @@ -44,6 +46,13 @@ CONFIG_SCHEMA = ( DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_ECO2_BASELINE): sensor.sensor_schema( + UNIT_EMPTY, ICON_MOLECULE_CO2, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_TVOC_BASELINE): sensor.sensor_schema( + UNIT_EMPTY, ICON_RADIATOR, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean, cv.Optional(CONF_BASELINE): cv.Schema( { cv.Required(CONF_ECO2_BASELINE): cv.hex_uint16_t, @@ -58,7 +67,7 @@ CONFIG_SCHEMA = ( ), } ) - .extend(cv.polling_component_schema("60s")) + .extend(cv.polling_component_schema("1s")) .extend(i2c.i2c_device_schema(0x58)) ) @@ -76,6 +85,17 @@ async def to_code(config): sens = await sensor.new_sensor(config[CONF_TVOC]) cg.add(var.set_tvoc_sensor(sens)) + if CONF_ECO2_BASELINE in config: + sens = await sensor.new_sensor(config[CONF_ECO2_BASELINE]) + cg.add(var.set_eco2_baseline_sensor(sens)) + + if CONF_TVOC_BASELINE in config: + sens = await sensor.new_sensor(config[CONF_TVOC_BASELINE]) + cg.add(var.set_tvoc_baseline_sensor(sens)) + + if CONF_STORE_BASELINE in config: + cg.add(var.set_store_baseline(config[CONF_STORE_BASELINE])) + if CONF_BASELINE in config: baseline_config = config[CONF_BASELINE] cg.add(var.set_eco2_baseline(baseline_config[CONF_ECO2_BASELINE])) diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index 56e5c7214c..fdc6ae031d 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -1,5 +1,6 @@ #include "sgp30.h" #include "esphome/core/log.h" +#include "esphome/core/application.h" namespace esphome { namespace sgp30 { @@ -22,6 +23,13 @@ const uint32_t IAQ_BASELINE_WARM_UP_SECONDS_WITH_BASELINE_PROVIDED = 3600; // if the sensor starts without any prior baseline value provided const uint32_t IAQ_BASELINE_WARM_UP_SECONDS_WITHOUT_BASELINE = 43200; +// Shortest time interval of 1H for storing baseline values. +// Prevents wear of the flash because of too many write operations +const uint32_t SHORTEST_BASELINE_STORE_INTERVAL = 3600; + +// Store anyway if the baseline difference exceeds the max storage diff value +const uint32_t MAXIMUM_STORAGE_DIFF = 50; + void SGP30Component::setup() { ESP_LOGCONFIG(TAG, "Setting up SGP30..."); @@ -73,6 +81,21 @@ void SGP30Component::setup() { return; } + // Hash with compilation time + // This ensures the baseline storage is cleared after OTA + uint32_t hash = fnv1_hash(App.get_compilation_time()); + this->pref_ = global_preferences.make_preference(hash, true); + + if (this->pref_.load(&this->baselines_storage_)) { + ESP_LOGI(TAG, "Loaded eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", this->baselines_storage_.eco2, + baselines_storage_.tvoc); + this->eco2_baseline_ = this->baselines_storage_.eco2; + this->tvoc_baseline_ = this->baselines_storage_.tvoc; + } + + // Initialize storage timestamp + this->seconds_since_last_store_ = 0; + // Sensor baseline reliability timer if (this->eco2_baseline_ > 0 && this->tvoc_baseline_ > 0) { this->required_warm_up_time_ = IAQ_BASELINE_WARM_UP_SECONDS_WITH_BASELINE_PROVIDED; @@ -110,6 +133,31 @@ void SGP30Component::read_iaq_baseline_() { uint16_t tvocbaseline = (raw_data[1]); ESP_LOGI(TAG, "Current eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", eco2baseline, tvocbaseline); + if (eco2baseline != this->eco2_baseline_ || tvocbaseline != this->tvoc_baseline_) { + this->eco2_baseline_ = eco2baseline; + this->tvoc_baseline_ = tvocbaseline; + if (this->eco2_sensor_baseline_ != nullptr) + this->eco2_sensor_baseline_->publish_state(this->eco2_baseline_); + if (this->tvoc_sensor_baseline_ != nullptr) + this->tvoc_sensor_baseline_->publish_state(this->tvoc_baseline_); + + // Store baselines after defined interval or if the difference between current and stored baseline becomes too + // much + if (this->store_baseline_ && + (this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL || + abs(this->baselines_storage_.eco2 - this->eco2_baseline_) > MAXIMUM_STORAGE_DIFF || + abs(this->baselines_storage_.tvoc - this->tvoc_baseline_) > MAXIMUM_STORAGE_DIFF)) { + this->seconds_since_last_store_ = 0; + this->baselines_storage_.eco2 = this->eco2_baseline_; + this->baselines_storage_.tvoc = this->tvoc_baseline_; + if (this->pref_.save(&this->baselines_storage_)) { + ESP_LOGI(TAG, "Store eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", this->baselines_storage_.eco2, + this->baselines_storage_.tvoc); + } else { + ESP_LOGW(TAG, "Could not store eCO2 and TVOC baselines"); + } + } + } this->status_clear_warning(); }); } else { @@ -171,7 +219,8 @@ void SGP30Component::write_iaq_baseline_(uint16_t eco2_baseline, uint16_t tvoc_b if (!this->write_bytes(SGP30_CMD_SET_IAQ_BASELINE >> 8, data, 7)) { ESP_LOGE(TAG, "Error applying eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", eco2_baseline, tvoc_baseline); } else - ESP_LOGI(TAG, "Initial eCO2 and TVOC baselines applied successfully!"); + ESP_LOGI(TAG, "Initial baselines applied successfully! eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", eco2_baseline, + tvoc_baseline); } void SGP30Component::dump_config() { @@ -207,8 +256,11 @@ void SGP30Component::dump_config() { ESP_LOGCONFIG(TAG, " Warm up time: %us", this->required_warm_up_time_); } LOG_UPDATE_INTERVAL(this); - LOG_SENSOR(" ", "eCO2", this->eco2_sensor_); - LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); + LOG_SENSOR(" ", "eCO2 sensor", this->eco2_sensor_); + LOG_SENSOR(" ", "TVOC sensor", this->tvoc_sensor_); + LOG_SENSOR(" ", "eCO2 baseline sensor", this->eco2_sensor_baseline_); + LOG_SENSOR(" ", "TVOC baseline sensor", this->tvoc_sensor_baseline_); + ESP_LOGCONFIG(TAG, "Store baseline: %s", YESNO(this->store_baseline_)); if (this->humidity_sensor_ != nullptr && this->temperature_sensor_ != nullptr) { ESP_LOGCONFIG(TAG, " Compensation:"); LOG_SENSOR(" ", "Temperature Source:", this->temperature_sensor_); @@ -223,7 +275,7 @@ void SGP30Component::update() { this->status_set_warning(); return; } - + this->seconds_since_last_store_ += this->update_interval_ / 1000; this->set_timeout(50, [this]() { uint16_t raw_data[2]; if (!this->read_data_(raw_data, 2)) { @@ -239,6 +291,11 @@ void SGP30Component::update() { this->eco2_sensor_->publish_state(eco2); if (this->tvoc_sensor_ != nullptr) this->tvoc_sensor_->publish_state(tvoc); + + if (this->get_update_interval() != 1000) { + ESP_LOGW(TAG, "Update interval for SGP30 sensor must be set to 1s for optimized readout"); + } + this->status_clear_warning(); this->send_env_data_(); this->read_iaq_baseline_(); diff --git a/esphome/components/sgp30/sgp30.h b/esphome/components/sgp30/sgp30.h index 298a78e8dd..91a1c1e9c7 100644 --- a/esphome/components/sgp30/sgp30.h +++ b/esphome/components/sgp30/sgp30.h @@ -3,16 +3,25 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" +#include "esphome/core/preferences.h" #include namespace esphome { namespace sgp30 { +struct SGP30Baselines { + uint16_t eco2; + uint16_t tvoc; +} PACKED; + /// This class implements support for the Sensirion SGP30 i2c GAS (VOC and CO2eq) sensors. class SGP30Component : public PollingComponent, public i2c::I2CDevice { public: void set_eco2_sensor(sensor::Sensor *eco2) { eco2_sensor_ = eco2; } void set_tvoc_sensor(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } + void set_eco2_baseline_sensor(sensor::Sensor *eco2_baseline) { eco2_sensor_baseline_ = eco2_baseline; } + void set_tvoc_baseline_sensor(sensor::Sensor *tvoc_baseline) { tvoc_sensor_baseline_ = tvoc_baseline; } + void set_store_baseline(bool store_baseline) { store_baseline_ = store_baseline; } void set_eco2_baseline(uint16_t eco2_baseline) { eco2_baseline_ = eco2_baseline; } void set_tvoc_baseline(uint16_t tvoc_baseline) { tvoc_baseline_ = tvoc_baseline; } void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } @@ -34,6 +43,9 @@ class SGP30Component : public PollingComponent, public i2c::I2CDevice { uint64_t serial_number_; uint16_t featureset_; uint32_t required_warm_up_time_; + uint32_t seconds_since_last_store_; + SGP30Baselines baselines_storage_; + ESPPreferenceObject pref_; enum ErrorCode { COMMUNICATION_FAILED, @@ -45,8 +57,12 @@ class SGP30Component : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *eco2_sensor_{nullptr}; sensor::Sensor *tvoc_sensor_{nullptr}; + sensor::Sensor *eco2_sensor_baseline_{nullptr}; + sensor::Sensor *tvoc_sensor_baseline_{nullptr}; uint16_t eco2_baseline_{0x0000}; uint16_t tvoc_baseline_{0x0000}; + bool store_baseline_; + /// Input sensor for humidity and temperature compensation. sensor::Sensor *humidity_sensor_{nullptr}; sensor::Sensor *temperature_sensor_{nullptr}; diff --git a/esphome/const.py b/esphome/const.py index e82b66ee37..e676056581 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -77,6 +77,7 @@ CONF_AVAILABILITY = "availability" CONF_AWAY = "away" CONF_AWAY_CONFIG = "away_config" CONF_BACKLIGHT_PIN = "backlight_pin" +CONF_BASELINE = "baseline" CONF_BATTERY_LEVEL = "battery_level" CONF_BATTERY_VOLTAGE = "battery_voltage" CONF_BAUD_RATE = "baud_rate" @@ -189,6 +190,7 @@ CONF_DUMP = "dump" CONF_DURATION = "duration" CONF_EAP = "eap" CONF_ECHO_PIN = "echo_pin" +CONF_ECO2 = "eco2" CONF_EFFECT = "effect" CONF_EFFECTS = "effects" CONF_ELSE = "else" From fb8ec79a52120001ab59feb5205a5a06b2276cfb Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 13 Jul 2021 02:28:29 +0200 Subject: [PATCH 1003/1841] Color brightness fixes (#2008) --- esphome/components/light/light_call.cpp | 23 ++++++++++++------- esphome/components/light/light_color_values.h | 7 +++--- esphome/components/light/light_state.cpp | 3 +++ 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 557d001321..d7e8ce6298 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -32,19 +32,26 @@ LightCall &LightCall::parse_color_json(JsonObject &root) { if (root.containsKey("color")) { JsonObject &color = root["color"]; + // HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness. + float max_rgb = 0.0f; if (color.containsKey("r")) { - this->set_red(float(color["r"]) / 255.0f); + float r = float(color["r"]) / 255.0f; + max_rgb = fmaxf(max_rgb, r); + this->set_red(r); } if (color.containsKey("g")) { - this->set_green(float(color["g"]) / 255.0f); + float g = float(color["g"]) / 255.0f; + max_rgb = fmaxf(max_rgb, g); + this->set_green(g); } if (color.containsKey("b")) { - this->set_blue(float(color["b"]) / 255.0f); + float b = float(color["b"]) / 255.0f; + max_rgb = fmaxf(max_rgb, b); + this->set_blue(b); + } + if (color.containsKey("r") || color.containsKey("g") || color.containsKey("b")) { + this->set_color_brightness(max_rgb); } - } - - if (root.containsKey("color_brightness")) { - this->set_color_brightness(float(root["color_brightness"]) / 255.0f); } if (root.containsKey("white_value")) { @@ -418,7 +425,7 @@ LightCall &LightCall::set_brightness_if_supported(float brightness) { } LightCall &LightCall::set_color_brightness_if_supported(float brightness) { if (this->parent_->get_traits().get_supports_rgb_white_value()) - this->set_brightness(brightness); + this->set_color_brightness(brightness); return *this; } LightCall &LightCall::set_red_if_supported(float red) { diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index 4b6ca9e576..54dcaea5a3 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -135,12 +135,11 @@ class LightColorValues { root["brightness"] = uint8_t(this->get_brightness() * 255); if (traits.get_supports_rgb()) { JsonObject &color = root.createNestedObject("color"); - color["r"] = uint8_t(this->get_red() * 255); - color["g"] = uint8_t(this->get_green() * 255); - color["b"] = uint8_t(this->get_blue() * 255); + color["r"] = uint8_t(this->get_color_brightness() * this->get_red() * 255); + color["g"] = uint8_t(this->get_color_brightness() * this->get_green() * 255); + color["b"] = uint8_t(this->get_color_brightness() * this->get_blue() * 255); } if (traits.get_supports_rgb_white_value()) { - root["color_brightness"] = uint8_t(this->get_color_brightness() * 255); root["white_value"] = uint8_t(this->get_white() * 255); } if (traits.get_supports_color_temperature()) diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 69d147b6f3..a97e4f6790 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -18,6 +18,7 @@ LightCall LightState::make_call() { return LightCall(this); } struct LightStateRTCState { bool state{false}; float brightness{1.0f}; + float color_brightness{1.0f}; float red{1.0f}; float green{1.0f}; float blue{1.0f}; @@ -65,6 +66,7 @@ void LightState::setup() { call.set_state(recovered.state); call.set_brightness_if_supported(recovered.brightness); + call.set_color_brightness_if_supported(recovered.color_brightness); call.set_red_if_supported(recovered.red); call.set_green_if_supported(recovered.green); call.set_blue_if_supported(recovered.blue); @@ -244,6 +246,7 @@ void LightState::save_remote_values_() { LightStateRTCState saved; saved.state = this->remote_values.is_on(); saved.brightness = this->remote_values.get_brightness(); + saved.color_brightness = this->remote_values.get_color_brightness(); saved.red = this->remote_values.get_red(); saved.green = this->remote_values.get_green(); saved.blue = this->remote_values.get_blue(); From b632344596bad99b1f2890c0eecaf994382e2224 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jul 2021 11:39:04 +0200 Subject: [PATCH 1004/1841] Bump black from 21.5b1 to 21.6b0 (#2011) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 38717f3f77..f9566d5adc 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==2.8.2 flake8==3.9.2 -black==21.5b1 +black==21.6b0 pexpect==4.8.0 pre-commit From 04c3a43c17a1383597696be0483ffbebb8bedaad Mon Sep 17 00:00:00 2001 From: Sourabh Jaiswal Date: Wed, 14 Jul 2021 06:35:51 +0530 Subject: [PATCH 1005/1841] Added support for havells_solar sensor (#1988) Co-authored-by: Oxan van Leeuwen Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/havells_solar/__init__.py | 0 .../havells_solar/havells_solar.cpp | 165 ++++++++++ .../components/havells_solar/havells_solar.h | 115 +++++++ .../havells_solar/havells_solar_registers.h | 49 +++ esphome/components/havells_solar/sensor.py | 293 ++++++++++++++++++ 6 files changed, 623 insertions(+) create mode 100644 esphome/components/havells_solar/__init__.py create mode 100644 esphome/components/havells_solar/havells_solar.cpp create mode 100644 esphome/components/havells_solar/havells_solar.h create mode 100644 esphome/components/havells_solar/havells_solar_registers.h create mode 100644 esphome/components/havells_solar/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 2a57e5d81a..d6769800cd 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -47,6 +47,7 @@ esphome/components/fingerprint_grow/* @OnFreund @loongyh esphome/components/globals/* @esphome/core esphome/components/gpio/* @esphome/core esphome/components/gps/* @coogle +esphome/components/havells_solar/* @sourabhjaiswal esphome/components/homeassistant/* @OttoWinter esphome/components/i2c/* @esphome/core esphome/components/improv/* @jesserockz diff --git a/esphome/components/havells_solar/__init__.py b/esphome/components/havells_solar/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/havells_solar/havells_solar.cpp b/esphome/components/havells_solar/havells_solar.cpp new file mode 100644 index 0000000000..f029df10ad --- /dev/null +++ b/esphome/components/havells_solar/havells_solar.cpp @@ -0,0 +1,165 @@ +#include "havells_solar.h" +#include "havells_solar_registers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace havells_solar { + +static const char *const TAG = "havells_solar"; + +static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x03; +static const uint8_t MODBUS_REGISTER_COUNT = 48; // 48 x 16-bit registers + +void HavellsSolar::on_modbus_data(const std::vector &data) { + if (data.size() < MODBUS_REGISTER_COUNT * 2) { + ESP_LOGW(TAG, "Invalid size for HavellsSolar!"); + return; + } + + /* Usage: returns the float value of 1 register read by modbus + Arg1: Register address * number of bytes per register + Arg2: Multiplier for final register value + */ + auto havells_solar_get_2_registers = [&](size_t i, float unit) -> float { + uint32_t temp = encode_uint32(data[i], data[i + 1], data[i + 2], data[i + 3]); + return temp * unit; + }; + + /* Usage: returns the float value of 2 registers read by modbus + Arg1: Register address * number of bytes per register + Arg2: Multiplier for final register value + */ + auto havells_solar_get_1_register = [&](size_t i, float unit) -> float { + uint16_t temp = encode_uint16(data[i], data[i + 1]); + return temp * unit; + }; + + for (uint8_t i = 0; i < 3; i++) { + auto phase = this->phases_[i]; + if (!phase.setup) + continue; + + float voltage = havells_solar_get_1_register(HAVELLS_PHASE_1_VOLTAGE * 2 + (i * 4), ONE_DEC_UNIT); + float current = havells_solar_get_1_register(HAVELLS_PHASE_1_CURRENT * 2 + (i * 4), TWO_DEC_UNIT); + + if (phase.voltage_sensor_ != nullptr) + phase.voltage_sensor_->publish_state(voltage); + if (phase.current_sensor_ != nullptr) + phase.current_sensor_->publish_state(current); + } + + for (uint8_t i = 0; i < 2; i++) { + auto pv = this->pvs_[i]; + if (!pv.setup) + continue; + + float voltage = havells_solar_get_1_register(HAVELLS_PV_1_VOLTAGE * 2 + (i * 4), ONE_DEC_UNIT); + float current = havells_solar_get_1_register(HAVELLS_PV_1_CURRENT * 2 + (i * 4), TWO_DEC_UNIT); + float active_power = havells_solar_get_1_register(HAVELLS_PV_1_POWER * 2 + (i * 2), MULTIPLY_TEN_UNIT); + float voltage_sampled_by_secondary_cpu = + havells_solar_get_1_register(HAVELLS_PV1_VOLTAGE_SAMPLED_BY_SECONDARY_CPU * 2 + (i * 2), ONE_DEC_UNIT); + float insulation_of_p_to_ground = + havells_solar_get_1_register(HAVELLS_PV1_INSULATION_OF_P_TO_GROUND * 2 + (i * 2), NO_DEC_UNIT); + + if (pv.voltage_sensor_ != nullptr) + pv.voltage_sensor_->publish_state(voltage); + if (pv.current_sensor_ != nullptr) + pv.current_sensor_->publish_state(current); + if (pv.active_power_sensor_ != nullptr) + pv.active_power_sensor_->publish_state(active_power); + if (pv.voltage_sampled_by_secondary_cpu_sensor_ != nullptr) + pv.voltage_sampled_by_secondary_cpu_sensor_->publish_state(voltage_sampled_by_secondary_cpu); + if (pv.insulation_of_p_to_ground_sensor_ != nullptr) + pv.insulation_of_p_to_ground_sensor_->publish_state(insulation_of_p_to_ground); + } + + float frequency = havells_solar_get_1_register(HAVELLS_GRID_FREQUENCY * 2, TWO_DEC_UNIT); + float active_power = havells_solar_get_1_register(HAVELLS_SYSTEM_ACTIVE_POWER * 2, MULTIPLY_TEN_UNIT); + float reactive_power = havells_solar_get_1_register(HAVELLS_SYSTEM_REACTIVE_POWER * 2, TWO_DEC_UNIT); + float today_production = havells_solar_get_1_register(HAVELLS_TODAY_PRODUCTION * 2, TWO_DEC_UNIT); + float total_energy_production = havells_solar_get_2_registers(HAVELLS_TOTAL_ENERGY_PRODUCTION * 2, NO_DEC_UNIT); + float total_generation_time = havells_solar_get_2_registers(HAVELLS_TOTAL_GENERATION_TIME * 2, NO_DEC_UNIT); + float today_generation_time = havells_solar_get_1_register(HAVELLS_TODAY_GENERATION_TIME * 2, NO_DEC_UNIT); + float inverter_module_temp = havells_solar_get_1_register(HAVELLS_INVERTER_MODULE_TEMP * 2, NO_DEC_UNIT); + float inverter_inner_temp = havells_solar_get_1_register(HAVELLS_INVERTER_INNER_TEMP * 2, NO_DEC_UNIT); + float inverter_bus_voltage = havells_solar_get_1_register(HAVELLS_INVERTER_BUS_VOLTAGE * 2, NO_DEC_UNIT); + float insulation_pv_n_to_ground = havells_solar_get_1_register(HAVELLS_INSULATION_OF_PV_N_TO_GROUND * 2, NO_DEC_UNIT); + float gfci_value = havells_solar_get_1_register(HAVELLS_GFCI_VALUE * 2, NO_DEC_UNIT); + float dci_of_r = havells_solar_get_1_register(HAVELLS_DCI_OF_R * 2, NO_DEC_UNIT); + float dci_of_s = havells_solar_get_1_register(HAVELLS_DCI_OF_S * 2, NO_DEC_UNIT); + float dci_of_t = havells_solar_get_1_register(HAVELLS_DCI_OF_T * 2, NO_DEC_UNIT); + + if (this->frequency_sensor_ != nullptr) + this->frequency_sensor_->publish_state(frequency); + if (this->active_power_sensor_ != nullptr) + this->active_power_sensor_->publish_state(active_power); + if (this->reactive_power_sensor_ != nullptr) + this->reactive_power_sensor_->publish_state(reactive_power); + if (this->today_production_sensor_ != nullptr) + this->today_production_sensor_->publish_state(today_production); + if (this->total_energy_production_sensor_ != nullptr) + this->total_energy_production_sensor_->publish_state(total_energy_production); + if (this->total_generation_time_sensor_ != nullptr) + this->total_generation_time_sensor_->publish_state(total_generation_time); + if (this->today_generation_time_sensor_ != nullptr) + this->today_generation_time_sensor_->publish_state(today_generation_time); + if (this->inverter_module_temp_sensor_ != nullptr) + this->inverter_module_temp_sensor_->publish_state(inverter_module_temp); + if (this->inverter_inner_temp_sensor_ != nullptr) + this->inverter_inner_temp_sensor_->publish_state(inverter_inner_temp); + if (this->inverter_bus_voltage_sensor_ != nullptr) + this->inverter_bus_voltage_sensor_->publish_state(inverter_bus_voltage); + if (this->insulation_pv_n_to_ground_sensor_ != nullptr) + this->insulation_pv_n_to_ground_sensor_->publish_state(insulation_pv_n_to_ground); + if (this->gfci_value_sensor_ != nullptr) + this->gfci_value_sensor_->publish_state(gfci_value); + if (this->dci_of_r_sensor_ != nullptr) + this->dci_of_r_sensor_->publish_state(dci_of_r); + if (this->dci_of_s_sensor_ != nullptr) + this->dci_of_s_sensor_->publish_state(dci_of_s); + if (this->dci_of_t_sensor_ != nullptr) + this->dci_of_t_sensor_->publish_state(dci_of_t); +} + +void HavellsSolar::update() { this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT); } +void HavellsSolar::dump_config() { + ESP_LOGCONFIG(TAG, "HAVELLS Solar:"); + ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); + for (uint8_t i = 0; i < 3; i++) { + auto phase = this->phases_[i]; + if (!phase.setup) + continue; + ESP_LOGCONFIG(TAG, " Phase %c", i + 'A'); + LOG_SENSOR(" ", "Voltage", phase.voltage_sensor_); + LOG_SENSOR(" ", "Current", phase.current_sensor_); + } + for (uint8_t i = 0; i < 2; i++) { + auto pv = this->pvs_[i]; + if (!pv.setup) + continue; + ESP_LOGCONFIG(TAG, " PV %d", i + 1); + LOG_SENSOR(" ", "Voltage", pv.voltage_sensor_); + LOG_SENSOR(" ", "Current", pv.current_sensor_); + LOG_SENSOR(" ", "Active Power", pv.active_power_sensor_); + LOG_SENSOR(" ", "Voltage Sampled By Secondary CPU", pv.voltage_sampled_by_secondary_cpu_sensor_); + LOG_SENSOR(" ", "Insulation Of PV+ To Ground", pv.insulation_of_p_to_ground_sensor_); + } + LOG_SENSOR(" ", "Frequency", this->frequency_sensor_); + LOG_SENSOR(" ", "Active Power", this->active_power_sensor_); + LOG_SENSOR(" ", "Reactive Power", this->reactive_power_sensor_); + LOG_SENSOR(" ", "Today Generation", this->today_production_sensor_); + LOG_SENSOR(" ", "Total Generation", this->total_energy_production_sensor_); + LOG_SENSOR(" ", "Total Generation Time", this->total_generation_time_sensor_); + LOG_SENSOR(" ", "Today Generation Time", this->today_generation_time_sensor_); + LOG_SENSOR(" ", "Inverter Module Temp", this->inverter_module_temp_sensor_); + LOG_SENSOR(" ", "Inverter Inner Temp", this->inverter_inner_temp_sensor_); + LOG_SENSOR(" ", "Inverter Bus Voltage", this->inverter_bus_voltage_sensor_); + LOG_SENSOR(" ", "Insulation Of PV- To Ground", this->insulation_pv_n_to_ground_sensor_); + LOG_SENSOR(" ", "GFCI Value", this->gfci_value_sensor_); + LOG_SENSOR(" ", "DCI Of R", this->dci_of_r_sensor_); + LOG_SENSOR(" ", "DCI Of S", this->dci_of_s_sensor_); + LOG_SENSOR(" ", "DCI Of T", this->dci_of_t_sensor_); +} + +} // namespace havells_solar +} // namespace esphome diff --git a/esphome/components/havells_solar/havells_solar.h b/esphome/components/havells_solar/havells_solar.h new file mode 100644 index 0000000000..2ccc8be3d4 --- /dev/null +++ b/esphome/components/havells_solar/havells_solar.h @@ -0,0 +1,115 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/modbus/modbus.h" + +namespace esphome { +namespace havells_solar { + +class HavellsSolar : public PollingComponent, public modbus::ModbusDevice { + public: + void set_voltage_sensor(uint8_t phase, sensor::Sensor *voltage_sensor) { + this->phases_[phase].setup = true; + this->phases_[phase].voltage_sensor_ = voltage_sensor; + } + void set_current_sensor(uint8_t phase, sensor::Sensor *current_sensor) { + this->phases_[phase].setup = true; + this->phases_[phase].current_sensor_ = current_sensor; + } + void set_voltage_sensor_pv(uint8_t pv, sensor::Sensor *voltage_sensor) { + this->pvs_[pv].setup = true; + this->pvs_[pv].voltage_sensor_ = voltage_sensor; + } + void set_current_sensor_pv(uint8_t pv, sensor::Sensor *current_sensor) { + this->pvs_[pv].setup = true; + this->pvs_[pv].current_sensor_ = current_sensor; + } + void set_active_power_sensor_pv(uint8_t pv, sensor::Sensor *active_power_sensor) { + this->pvs_[pv].setup = true; + this->pvs_[pv].active_power_sensor_ = active_power_sensor; + } + void set_voltage_sampled_by_secondary_cpu_sensor_pv(uint8_t pv, + sensor::Sensor *voltage_sampled_by_secondary_cpu_sensor) { + this->pvs_[pv].setup = true; + this->pvs_[pv].voltage_sampled_by_secondary_cpu_sensor_ = voltage_sampled_by_secondary_cpu_sensor; + } + void set_insulation_of_p_to_ground_sensor_pv(uint8_t pv, sensor::Sensor *insulation_of_p_to_ground_sensor) { + this->pvs_[pv].setup = true; + this->pvs_[pv].insulation_of_p_to_ground_sensor_ = insulation_of_p_to_ground_sensor; + } + void set_frequency_sensor(sensor::Sensor *frequency_sensor) { this->frequency_sensor_ = frequency_sensor; } + void set_active_power_sensor(sensor::Sensor *active_power_sensor) { + this->active_power_sensor_ = active_power_sensor; + } + void set_reactive_power_sensor(sensor::Sensor *reactive_power_sensor) { + this->reactive_power_sensor_ = reactive_power_sensor; + } + void set_today_production_sensor(sensor::Sensor *today_production_sensor) { + this->today_production_sensor_ = today_production_sensor; + } + void set_total_energy_production_sensor(sensor::Sensor *total_energy_production_sensor) { + this->total_energy_production_sensor_ = total_energy_production_sensor; + } + void set_total_generation_time_sensor(sensor::Sensor *total_generation_time_sensor) { + this->total_generation_time_sensor_ = total_generation_time_sensor; + } + void set_today_generation_time_sensor(sensor::Sensor *today_generation_time_sensor) { + this->today_generation_time_sensor_ = today_generation_time_sensor; + } + void set_inverter_module_temp_sensor(sensor::Sensor *inverter_module_temp_sensor) { + this->inverter_module_temp_sensor_ = inverter_module_temp_sensor; + } + void set_inverter_inner_temp_sensor(sensor::Sensor *inverter_inner_temp_sensor) { + this->inverter_inner_temp_sensor_ = inverter_inner_temp_sensor; + } + void set_inverter_bus_voltage_sensor(sensor::Sensor *inverter_bus_voltage_sensor) { + this->inverter_bus_voltage_sensor_ = inverter_bus_voltage_sensor; + } + void set_insulation_pv_n_to_ground_sensor(sensor::Sensor *insulation_pv_n_to_ground_sensor) { + this->insulation_pv_n_to_ground_sensor_ = insulation_pv_n_to_ground_sensor; + } + void set_gfci_value_sensor(sensor::Sensor *gfci_value_sensor) { this->gfci_value_sensor_ = gfci_value_sensor; } + void set_dci_of_r_sensor(sensor::Sensor *dci_of_r_sensor) { this->dci_of_r_sensor_ = dci_of_r_sensor; } + void set_dci_of_s_sensor(sensor::Sensor *dci_of_s_sensor) { this->dci_of_s_sensor_ = dci_of_s_sensor; } + void set_dci_of_t_sensor(sensor::Sensor *dci_of_t_sensor) { this->dci_of_t_sensor_ = dci_of_t_sensor; } + + void update() override; + + void on_modbus_data(const std::vector &data) override; + + void dump_config() override; + + protected: + struct HAVELLSPhase { + bool setup{false}; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + } phases_[3]; + struct HAVELLSPV { + bool setup{false}; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *active_power_sensor_{nullptr}; + sensor::Sensor *voltage_sampled_by_secondary_cpu_sensor_{nullptr}; + sensor::Sensor *insulation_of_p_to_ground_sensor_{nullptr}; + } pvs_[2]; + sensor::Sensor *frequency_sensor_{nullptr}; + sensor::Sensor *active_power_sensor_{nullptr}; + sensor::Sensor *reactive_power_sensor_{nullptr}; + sensor::Sensor *today_production_sensor_{nullptr}; + sensor::Sensor *total_energy_production_sensor_{nullptr}; + sensor::Sensor *total_generation_time_sensor_{nullptr}; + sensor::Sensor *today_generation_time_sensor_{nullptr}; + sensor::Sensor *inverter_module_temp_sensor_{nullptr}; + sensor::Sensor *inverter_inner_temp_sensor_{nullptr}; + sensor::Sensor *inverter_bus_voltage_sensor_{nullptr}; + sensor::Sensor *insulation_pv_n_to_ground_sensor_{nullptr}; + sensor::Sensor *gfci_value_sensor_{nullptr}; + sensor::Sensor *dci_of_r_sensor_{nullptr}; + sensor::Sensor *dci_of_s_sensor_{nullptr}; + sensor::Sensor *dci_of_t_sensor_{nullptr}; +}; + +} // namespace havells_solar +} // namespace esphome diff --git a/esphome/components/havells_solar/havells_solar_registers.h b/esphome/components/havells_solar/havells_solar_registers.h new file mode 100644 index 0000000000..8e1cb3ec7a --- /dev/null +++ b/esphome/components/havells_solar/havells_solar_registers.h @@ -0,0 +1,49 @@ +#pragma once +namespace esphome { +namespace havells_solar { + +static const float TWO_DEC_UNIT = 0.01; +static const float ONE_DEC_UNIT = 0.1; +static const float NO_DEC_UNIT = 1; +static const float MULTIPLY_TEN_UNIT = 10; + +/* PV Input Message */ +static const uint16_t HAVELLS_PV_1_VOLTAGE = 0x0006; +static const uint16_t HAVELLS_PV_1_CURRENT = 0x0007; +static const uint16_t HAVELLS_PV_2_VOLTAGE = 0x0008; +static const uint16_t HAVELLS_PV_2_CURRENT = 0x0009; +static const uint16_t HAVELLS_PV_1_POWER = 0x000A; +static const uint16_t HAVELLS_PV_2_POWER = 0x000B; + +/* Output Grid Message */ +static const uint16_t HAVELLS_SYSTEM_ACTIVE_POWER = 0x000C; +static const uint16_t HAVELLS_SYSTEM_REACTIVE_POWER = 0x000D; +static const uint16_t HAVELLS_GRID_FREQUENCY = 0x000E; +static const uint16_t HAVELLS_PHASE_1_VOLTAGE = 0x000F; +static const uint16_t HAVELLS_PHASE_1_CURRENT = 0x0010; +static const uint16_t HAVELLS_PHASE_2_VOLTAGE = 0x0011; +static const uint16_t HAVELLS_PHASE_2_CURRENT = 0x0012; +static const uint16_t HAVELLS_PHASE_3_VOLTAGE = 0x0013; +static const uint16_t HAVELLS_PHASE_3_CURRENT = 0x0014; + +/* Inverter Generation message */ +static const uint16_t HAVELLS_TOTAL_ENERGY_PRODUCTION = 0x0015; +static const uint16_t HAVELLS_TOTAL_GENERATION_TIME = 0x0017; +static const uint16_t HAVELLS_TODAY_PRODUCTION = 0x0019; +static const uint16_t HAVELLS_TODAY_GENERATION_TIME = 0x001A; + +/* Inverter inner message */ +static const uint16_t HAVELLS_INVERTER_MODULE_TEMP = 0x001B; +static const uint16_t HAVELLS_INVERTER_INNER_TEMP = 0x001C; +static const uint16_t HAVELLS_INVERTER_BUS_VOLTAGE = 0x001D; +static const uint16_t HAVELLS_PV1_VOLTAGE_SAMPLED_BY_SECONDARY_CPU = 0x001E; +static const uint16_t HAVELLS_PV2_VOLTAGE_SAMPLED_BY_SECONDARY_CPU = 0x001F; +static const uint16_t HAVELLS_PV1_INSULATION_OF_P_TO_GROUND = 0x0024; +static const uint16_t HAVELLS_PV2_INSULATION_OF_P_TO_GROUND = 0x0025; +static const uint16_t HAVELLS_INSULATION_OF_PV_N_TO_GROUND = 0x0026; +static const uint16_t HAVELLS_GFCI_VALUE = 0x002A; +static const uint16_t HAVELLS_DCI_OF_R = 0x002B; +static const uint16_t HAVELLS_DCI_OF_S = 0x002C; +static const uint16_t HAVELLS_DCI_OF_T = 0x002D; +} // namespace havells_solar +} // namespace esphome diff --git a/esphome/components/havells_solar/sensor.py b/esphome/components/havells_solar/sensor.py new file mode 100644 index 0000000000..7d1e2be581 --- /dev/null +++ b/esphome/components/havells_solar/sensor.py @@ -0,0 +1,293 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, modbus +from esphome.const import ( + CONF_ACTIVE_POWER, + CONF_CURRENT, + CONF_FREQUENCY, + CONF_ID, + CONF_REACTIVE_POWER, + CONF_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + ICON_CURRENT_AC, + ICON_EMPTY, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_NONE, + UNIT_AMPERE, + UNIT_DEGREES, + UNIT_HERTZ, + UNIT_MINUTE, + UNIT_VOLT, + UNIT_VOLT_AMPS_REACTIVE, + UNIT_WATT, +) + +CONF_PHASE_A = "phase_a" +CONF_PHASE_B = "phase_b" +CONF_PHASE_C = "phase_c" +CONF_ENERGY_PRODUCTION_DAY = "energy_production_day" +CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production" +CONF_TOTAL_GENERATION_TIME = "total_generation_time" +CONF_TODAY_GENERATION_TIME = "today_generation_time" +CONF_PV1 = "pv1" +CONF_PV2 = "pv2" +UNIT_KILOWATT_HOURS = "kWh" +UNIT_HOURS = "h" +UNIT_KOHM = "kΩ" +UNIT_MILLIAMPERE = "mA" + + +CONF_INVERTER_MODULE_TEMP = "inverter_module_temp" +CONF_INVERTER_INNER_TEMP = "inverter_inner_temp" +CONF_INVERTER_BUS_VOLTAGE = "inverter_bus_voltage" +CONF_VOLTAGE_SAMPLED_BY_SECONDARY_CPU = "voltage_sampled_by_secondary_cpu" +CONF_INSULATION_OF_P_TO_GROUND = "insulation_of_p_to_ground" +CONF_INSULATION_OF_PV_N_TO_GROUND = "insulation_of_pv_n_to_ground" +CONF_GFCI_VALUE = "gfci_value" +CONF_DCI_OF_R = "dci_of_r" +CONF_DCI_OF_S = "dci_of_s" +CONF_DCI_OF_T = "dci_of_t" + + +AUTO_LOAD = ["modbus"] +CODEOWNERS = ["@sourabhjaiswal"] + +havells_solar_ns = cg.esphome_ns.namespace("havells_solar") +HavellsSolar = havells_solar_ns.class_( + "HavellsSolar", cg.PollingComponent, modbus.ModbusDevice +) + +PHASE_SENSORS = { + CONF_VOLTAGE: sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE), + CONF_CURRENT: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + ), +} +PV_SENSORS = { + CONF_VOLTAGE: sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE), + CONF_CURRENT: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + ), + CONF_ACTIVE_POWER: sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + CONF_VOLTAGE_SAMPLED_BY_SECONDARY_CPU: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + CONF_INSULATION_OF_P_TO_GROUND: sensor.sensor_schema( + UNIT_KOHM, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), +} + +PHASE_SCHEMA = cv.Schema( + {cv.Optional(sensor): schema for sensor, schema in PHASE_SENSORS.items()} +) +PV_SCHEMA = cv.Schema( + {cv.Optional(sensor): schema for sensor, schema in PV_SENSORS.items()} +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HavellsSolar), + cv.Optional(CONF_PHASE_A): PHASE_SCHEMA, + cv.Optional(CONF_PHASE_B): PHASE_SCHEMA, + cv.Optional(CONF_PHASE_C): PHASE_SCHEMA, + cv.Optional(CONF_PV1): PV_SCHEMA, + cv.Optional(CONF_PV2): PV_SCHEMA, + cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( + UNIT_HERTZ, + ICON_CURRENT_AC, + 2, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ACTIVE_POWER): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema( + UNIT_VOLT_AMPS_REACTIVE, + ICON_EMPTY, + 2, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ENERGY_PRODUCTION_DAY): sensor.sensor_schema( + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema( + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 0, + DEVICE_CLASS_ENERGY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_TOTAL_GENERATION_TIME): sensor.sensor_schema( + UNIT_HOURS, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_TODAY_GENERATION_TIME): sensor.sensor_schema( + UNIT_MINUTE, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_INVERTER_MODULE_TEMP): sensor.sensor_schema( + UNIT_DEGREES, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_INVERTER_INNER_TEMP): sensor.sensor_schema( + UNIT_DEGREES, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_INVERTER_BUS_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_INSULATION_OF_PV_N_TO_GROUND): sensor.sensor_schema( + UNIT_KOHM, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_GFCI_VALUE): sensor.sensor_schema( + UNIT_MILLIAMPERE, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DCI_OF_R): sensor.sensor_schema( + UNIT_MILLIAMPERE, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DCI_OF_S): sensor.sensor_schema( + UNIT_MILLIAMPERE, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DCI_OF_T): sensor.sensor_schema( + UNIT_MILLIAMPERE, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("10s")) + .extend(modbus.modbus_device_schema(0x01)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await modbus.register_modbus_device(var, config) + + if CONF_FREQUENCY in config: + sens = await sensor.new_sensor(config[CONF_FREQUENCY]) + cg.add(var.set_frequency_sensor(sens)) + + if CONF_ACTIVE_POWER in config: + sens = await sensor.new_sensor(config[CONF_ACTIVE_POWER]) + cg.add(var.set_active_power_sensor(sens)) + + if CONF_REACTIVE_POWER in config: + sens = await sensor.new_sensor(config[CONF_REACTIVE_POWER]) + cg.add(var.set_reactive_power_sensor(sens)) + + if CONF_ENERGY_PRODUCTION_DAY in config: + sens = await sensor.new_sensor(config[CONF_ENERGY_PRODUCTION_DAY]) + cg.add(var.set_today_production_sensor(sens)) + + if CONF_TOTAL_ENERGY_PRODUCTION in config: + sens = await sensor.new_sensor(config[CONF_TOTAL_ENERGY_PRODUCTION]) + cg.add(var.set_total_energy_production_sensor(sens)) + + if CONF_TOTAL_GENERATION_TIME in config: + sens = await sensor.new_sensor(config[CONF_TOTAL_GENERATION_TIME]) + cg.add(var.set_total_generation_time_sensor(sens)) + + if CONF_TODAY_GENERATION_TIME in config: + sens = await sensor.new_sensor(config[CONF_TODAY_GENERATION_TIME]) + cg.add(var.set_today_generation_time_sensor(sens)) + + if CONF_INVERTER_MODULE_TEMP in config: + sens = await sensor.new_sensor(config[CONF_INVERTER_MODULE_TEMP]) + cg.add(var.set_inverter_module_temp_sensor(sens)) + + if CONF_INVERTER_INNER_TEMP in config: + sens = await sensor.new_sensor(config[CONF_INVERTER_INNER_TEMP]) + cg.add(var.set_inverter_inner_temp_sensor(sens)) + + if CONF_INVERTER_BUS_VOLTAGE in config: + sens = await sensor.new_sensor(config[CONF_INVERTER_BUS_VOLTAGE]) + cg.add(var.set_inverter_bus_voltage_sensor(sens)) + + if CONF_INSULATION_OF_PV_N_TO_GROUND in config: + sens = await sensor.new_sensor(config[CONF_INSULATION_OF_PV_N_TO_GROUND]) + cg.add(var.set_insulation_pv_n_to_ground_sensor(sens)) + + if CONF_GFCI_VALUE in config: + sens = await sensor.new_sensor(config[CONF_GFCI_VALUE]) + cg.add(var.set_gfci_value_sensor(sens)) + + if CONF_DCI_OF_R in config: + sens = await sensor.new_sensor(config[CONF_DCI_OF_R]) + cg.add(var.set_dci_of_r_sensor(sens)) + + if CONF_DCI_OF_S in config: + sens = await sensor.new_sensor(config[CONF_DCI_OF_S]) + cg.add(var.set_dci_of_s_sensor(sens)) + + if CONF_DCI_OF_T in config: + sens = await sensor.new_sensor(config[CONF_DCI_OF_T]) + cg.add(var.set_dci_of_t_sensor(sens)) + + for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]): + if phase not in config: + continue + + phase_config = config[phase] + for sensor_type in PHASE_SENSORS: + if sensor_type in phase_config: + sens = await sensor.new_sensor(phase_config[sensor_type]) + cg.add(getattr(var, f"set_{sensor_type}_sensor")(i, sens)) + + for i, pv in enumerate([CONF_PV1, CONF_PV2]): + if pv not in config: + continue + + pv_config = config[pv] + for sensor_type in pv_config: + if sensor_type in pv_config: + sens = await sensor.new_sensor(pv_config[sensor_type]) + cg.add(getattr(var, f"set_{sensor_type}_sensor_pv")(i, sens)) From 07ae8ec553e7a81633e30f54b4343741f10aac80 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 14 Jul 2021 14:42:16 +1200 Subject: [PATCH 1006/1841] Remove a whole bunch of deprecated/removed stuff (#1981) --- esphome/components/binary_sensor/__init__.py | 10 ------ esphome/components/cover/cover.h | 15 ++++----- esphome/components/deep_sleep/__init__.py | 6 ---- .../components/esp32_ble_tracker/__init__.py | 3 -- .../esp32_ble_tracker/binary_sensor.py | 3 -- .../esp32_ble_tracker/esp32_ble_tracker.h | 6 ---- esphome/components/ethernet/__init__.py | 3 -- esphome/components/ledc/output.py | 5 --- esphome/components/light/light_state.cpp | 3 -- esphome/components/light/light_state.h | 8 ----- esphome/components/pcf8574/__init__.py | 18 ++++------ .../components/remote_transmitter/switch.py | 33 ------------------- esphome/components/sensor/sensor.cpp | 3 -- esphome/components/sensor/sensor.h | 10 ------ esphome/components/time/real_time_clock.h | 7 ++-- esphome/components/ultrasonic/sensor.py | 7 ---- esphome/components/wifi/__init__.py | 3 -- esphome/components/xiaomi_miflora/__init__.py | 0 esphome/components/xiaomi_miflora/sensor.py | 3 -- esphome/components/xiaomi_mijia/__init__.py | 0 esphome/components/xiaomi_mijia/sensor.py | 3 -- esphome/const.py | 1 - esphome/core/config.py | 5 --- 23 files changed, 14 insertions(+), 141 deletions(-) delete mode 100644 esphome/components/esp32_ble_tracker/binary_sensor.py delete mode 100644 esphome/components/remote_transmitter/switch.py delete mode 100644 esphome/components/xiaomi_miflora/__init__.py delete mode 100644 esphome/components/xiaomi_miflora/sensor.py delete mode 100644 esphome/components/xiaomi_mijia/__init__.py delete mode 100644 esphome/components/xiaomi_mijia/sensor.py diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 68d4d3e324..8f66978320 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -22,7 +22,6 @@ from esphome.const import ( CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, - CONF_FOR, CONF_NAME, CONF_MQTT_ID, DEVICE_CLASS_EMPTY, @@ -372,11 +371,6 @@ BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), } ), - cv.Optional(CONF_INVERTED): cv.invalid( - "The inverted binary_sensor property has been replaced by the " - "new 'invert' binary sensor filter. Please see " - "https://esphome.io/components/binary_sensor/index.html." - ), } ) @@ -455,10 +449,6 @@ async def new_binary_sensor(config): BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id( { cv.Required(CONF_ID): cv.use_id(BinarySensor), - cv.Optional(CONF_FOR): cv.invalid( - "This option has been removed in 1.13, please use the " - "'for' condition instead." - ), } ) diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index 1af4f9cbea..8f30750fbd 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -110,15 +110,12 @@ class Cover : public Nameable { /// The current operation of the cover (idle, opening, closing). CoverOperation current_operation{COVER_OPERATION_IDLE}; - union { - /** The position of the cover from 0.0 (fully closed) to 1.0 (fully open). - * - * For binary covers this is always equals to 0.0 or 1.0 (see also COVER_OPEN and - * COVER_CLOSED constants). - */ - float position; - ESPDEPRECATED(".state is deprecated, please use .position instead") float state; - }; + /** The position of the cover from 0.0 (fully closed) to 1.0 (fully open). + * + * For binary covers this is always equals to 0.0 or 1.0 (see also COVER_OPEN and + * COVER_CLOSED constants). + */ + float position; /// The current tilt value of the cover from 0.0 to 1.0. float tilt{COVER_OPEN}; diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 7011081774..b7bf27e79a 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -6,7 +6,6 @@ from esphome.const import ( CONF_MODE, CONF_NUMBER, CONF_PINS, - CONF_RUN_CYCLES, CONF_RUN_DURATION, CONF_SLEEP_DURATION, CONF_WAKEUP_PIN, @@ -69,11 +68,6 @@ CONFIG_SCHEMA = cv.Schema( } ), ), - cv.Optional(CONF_RUN_CYCLES): cv.invalid( - "The run_cycles option has been removed in 1.11.0 as " - "it was essentially the same as a run_duration of 0s." - "Please use run_duration now." - ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 5c70ddb27f..18f1c46ff2 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -163,9 +163,6 @@ CONFIG_SCHEMA = cv.Schema( cv.Required(CONF_MANUFACTURER_ID): bt_uuid, } ), - cv.Optional("scan_interval"): cv.invalid( - "This option has been removed in 1.14 (Reason: " "it never had an effect)" - ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/esp32_ble_tracker/binary_sensor.py b/esphome/components/esp32_ble_tracker/binary_sensor.py deleted file mode 100644 index 3bea6d9900..0000000000 --- a/esphome/components/esp32_ble_tracker/binary_sensor.py +++ /dev/null @@ -1,3 +0,0 @@ -import esphome.config_validation as cv - -CONFIG_SCHEMA = cv.invalid("This platform has been renamed to ble_presence in 1.13") diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 6f0c28a73c..0594c4a811 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -85,12 +85,6 @@ class ESPBTDevice { int get_rssi() const { return rssi_; } const std::string &get_name() const { return this->name_; } - ESPDEPRECATED("Use get_tx_powers() instead") - optional get_tx_power() const { - if (this->tx_powers_.empty()) - return {}; - return this->tx_powers_[0]; - } const std::vector &get_tx_powers() const { return tx_powers_; } const optional &get_appearance() const { return appearance_; } diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 94c9ddd2e9..95b7e40151 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -85,9 +85,6 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_ENABLE_MDNS, default=True): cv.boolean, cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name, cv.Optional(CONF_USE_ADDRESS): cv.string_strict, - cv.Optional("hostname"): cv.invalid( - "The hostname option has been removed in 1.11.0" - ), } ).extend(cv.COMPONENT_SCHEMA), _validate, diff --git a/esphome/components/ledc/output.py b/esphome/components/ledc/output.py index 150c5fa410..83b1c6f096 100644 --- a/esphome/components/ledc/output.py +++ b/esphome/components/ledc/output.py @@ -3,7 +3,6 @@ from esphome.components import output import esphome.config_validation as cv import esphome.codegen as cg from esphome.const import ( - CONF_BIT_DEPTH, CONF_CHANNEL, CONF_FREQUENCY, CONF_ID, @@ -50,10 +49,6 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency, cv.Optional(CONF_CHANNEL): cv.int_range(min=0, max=15), - cv.Optional(CONF_BIT_DEPTH): cv.invalid( - "The bit_depth option has been removed in v1.14, the " - "best bit depth is now automatically calculated." - ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index a97e4f6790..e32b15daf1 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -121,9 +121,6 @@ void LightState::loop() { float LightState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; } uint32_t LightState::hash_base() { return 1114400283; } -LightColorValues LightState::get_current_values() { return this->current_values; } -LightColorValues LightState::get_remote_values() { return this->remote_values; } - void LightState::publish_state() { this->remote_values_callback_.call(); this->next_write_ = true; diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index 5f3441daf7..cd5e6ad1cb 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -68,14 +68,6 @@ class LightState : public Nameable, public Component { */ LightColorValues remote_values; - /// Deprecated method to access current_values. - ESPDEPRECATED("get_current_values() is deprecated, please use .current_values instead.") - LightColorValues get_current_values(); - - /// Deprecated method to access remote_values. - ESPDEPRECATED("get_remote_values() is deprecated, please use .remote_values instead.") - LightColorValues get_remote_values(); - /// Publish the currently active state to the frontend. void publish_state(); diff --git a/esphome/components/pcf8574/__init__.py b/esphome/components/pcf8574/__init__.py index e96c526cb0..52e38febaa 100644 --- a/esphome/components/pcf8574/__init__.py +++ b/esphome/components/pcf8574/__init__.py @@ -38,21 +38,13 @@ async def to_code(config): cg.add(var.set_pcf8575(config[CONF_PCF8575])) -def validate_pcf8574_gpio_mode(value): - value = cv.string(value) - if value.upper() == "INPUT_PULLUP": - raise cv.Invalid( - "INPUT_PULLUP mode has been removed in 1.14 and been combined into " - "INPUT mode (they were the same thing). Please use INPUT instead." - ) - return cv.enum(PCF8674_GPIO_MODES, upper=True)(value) - - PCF8574_OUTPUT_PIN_SCHEMA = cv.Schema( { cv.Required(CONF_PCF8574): cv.use_id(PCF8574Component), cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="OUTPUT"): validate_pcf8574_gpio_mode, + cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum( + PCF8674_GPIO_MODES, upper=True + ), cv.Optional(CONF_INVERTED, default=False): cv.boolean, } ) @@ -60,7 +52,9 @@ PCF8574_INPUT_PIN_SCHEMA = cv.Schema( { cv.Required(CONF_PCF8574): cv.use_id(PCF8574Component), cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="INPUT"): validate_pcf8574_gpio_mode, + cv.Optional(CONF_MODE, default="INPUT"): cv.enum( + PCF8674_GPIO_MODES, upper=True + ), cv.Optional(CONF_INVERTED, default=False): cv.boolean, } ) diff --git a/esphome/components/remote_transmitter/switch.py b/esphome/components/remote_transmitter/switch.py deleted file mode 100644 index 3a2e43a31a..0000000000 --- a/esphome/components/remote_transmitter/switch.py +++ /dev/null @@ -1,33 +0,0 @@ -import esphome.config_validation as cv -from esphome.components.remote_base import BINARY_SENSOR_REGISTRY -from esphome.util import OrderedDict - - -def show_new(value): - from esphome import yaml_util - - for key in BINARY_SENSOR_REGISTRY: - if key in value: - break - else: - raise cv.Invalid( - "This platform has been removed in 1.13, please see the docs for updated " - "instructions." - ) - - val = value[key] - args = [("platform", "template")] - if "id" in value: - args.append(("id", value["id"])) - if "name" in value: - args.append(("name", value["name"])) - args.append(("turn_on_action", {f"remote_transmitter.transmit_{key}": val})) - - text = yaml_util.dump([OrderedDict(args)]) - raise cv.Invalid( - "This platform has been removed in 1.13, please change to:\n\n{}\n\n." - "".format(text) - ) - - -CONFIG_SCHEMA = show_new diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index b19d8be634..fe92f88308 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -28,7 +28,6 @@ void Sensor::publish_state(float state) { this->filter_list_->input(state); } } -void Sensor::push_new_value(float state) { this->publish_state(state); } std::string Sensor::unit_of_measurement() { return ""; } std::string Sensor::icon() { return ""; } uint32_t Sensor::update_interval() { return 0; } @@ -104,9 +103,7 @@ void Sensor::clear_filters() { } this->filter_list_ = nullptr; } -float Sensor::get_value() const { return this->state; } float Sensor::get_state() const { return this->state; } -float Sensor::get_raw_value() const { return this->raw_state; } float Sensor::get_raw_state() const { return this->raw_state; } std::string Sensor::unique_id() { return ""; } diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 9b1f4d7f86..123e7eddb3 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -87,12 +87,8 @@ class Sensor : public Nameable { /// Clear the entire filter chain. void clear_filters(); - /// Getter-syntax for .value. Please use .state instead. - float get_value() const ESPDEPRECATED(".value is deprecated, please use .state"); /// Getter-syntax for .state. float get_state() const; - /// Getter-syntax for .raw_value. Please use .raw_state instead. - float get_raw_value() const ESPDEPRECATED(".raw_value is deprecated, please use .raw_state"); /// Getter-syntax for .raw_state float get_raw_state() const; @@ -114,12 +110,6 @@ class Sensor : public Nameable { */ void publish_state(float state); - /** Push a new value to the MQTT front-end. - * - * Note: deprecated, please use publish_state. - */ - void push_new_value(float state) ESPDEPRECATED("push_new_value is deprecated. Please use .publish_state instead"); - // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) /// Add a callback that will be called every time a filtered value arrives. diff --git a/esphome/components/time/real_time_clock.h b/esphome/components/time/real_time_clock.h index 92a25fe993..0c6fa6f3a0 100644 --- a/esphome/components/time/real_time_clock.h +++ b/esphome/components/time/real_time_clock.h @@ -32,11 +32,8 @@ struct ESPTime { uint16_t year; /// daylight saving time flag bool is_dst; - union { - ESPDEPRECATED(".time is deprecated, use .timestamp instead") time_t time; - /// unix epoch time (seconds since UTC Midnight January 1, 1970) - time_t timestamp; - }; + /// unix epoch time (seconds since UTC Midnight January 1, 1970) + time_t timestamp; /** Convert this ESPTime struct to a null-terminated c string buffer as specified by the format argument. * Up to buffer_len bytes are written. diff --git a/esphome/components/ultrasonic/sensor.py b/esphome/components/ultrasonic/sensor.py index 77b08b3324..d5f2cef05f 100644 --- a/esphome/components/ultrasonic/sensor.py +++ b/esphome/components/ultrasonic/sensor.py @@ -37,13 +37,6 @@ CONFIG_SCHEMA = ( cv.Optional( CONF_PULSE_TIME, default="10us" ): cv.positive_time_period_microseconds, - cv.Optional("timeout_meter"): cv.invalid( - "The timeout_meter option has been renamed " "to 'timeout' in 1.12." - ), - cv.Optional("timeout_time"): cv.invalid( - "The timeout_time option has been removed. Please " - "use 'timeout' in 1.12." - ), } ) .extend(cv.polling_component_schema("60s")) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 7a1a01bcc4..d066570cc8 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -249,9 +249,6 @@ CONFIG_SCHEMA = cv.All( cv.SplitDefault(CONF_OUTPUT_POWER, esp8266=20.0): cv.All( cv.decibel, cv.float_range(min=10.0, max=20.5) ), - cv.Optional("hostname"): cv.invalid( - "The hostname option has been removed in 1.11.0" - ), } ), _validate, diff --git a/esphome/components/xiaomi_miflora/__init__.py b/esphome/components/xiaomi_miflora/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/esphome/components/xiaomi_miflora/sensor.py b/esphome/components/xiaomi_miflora/sensor.py deleted file mode 100644 index 0a0b3ff63f..0000000000 --- a/esphome/components/xiaomi_miflora/sensor.py +++ /dev/null @@ -1,3 +0,0 @@ -import esphome.config_validation as cv - -CONFIG_SCHEMA = cv.invalid("This sensor has been renamed to xiaomi_hhccjcy01") diff --git a/esphome/components/xiaomi_mijia/__init__.py b/esphome/components/xiaomi_mijia/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/esphome/components/xiaomi_mijia/sensor.py b/esphome/components/xiaomi_mijia/sensor.py deleted file mode 100644 index 597d8d1bce..0000000000 --- a/esphome/components/xiaomi_mijia/sensor.py +++ /dev/null @@ -1,3 +0,0 @@ -import esphome.config_validation as cv - -CONFIG_SCHEMA = cv.invalid("This sensor has been renamed to xiaomi_lywsdcgq") diff --git a/esphome/const.py b/esphome/const.py index e676056581..f02c17e0da 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -501,7 +501,6 @@ CONF_ROTATION = "rotation" CONF_RS_PIN = "rs_pin" CONF_RTD_NOMINAL_RESISTANCE = "rtd_nominal_resistance" CONF_RTD_WIRES = "rtd_wires" -CONF_RUN_CYCLES = "run_cycles" CONF_RUN_DURATION = "run_duration" CONF_RW_PIN = "rw_pin" CONF_RX_BUFFER_SIZE = "rx_buffer_size" diff --git a/esphome/core/config.py b/esphome/core/config.py index f55acc1892..9475225f4d 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -201,11 +201,6 @@ CONFIG_SCHEMA = cv.Schema( cv.Required(CONF_VERSION): cv.string_strict, } ), - cv.Optional("esphome_core_version"): cv.invalid( - "The esphome_core_version option has been " - "removed in 1.13 - the esphome core source " - "files are now bundled with ESPHome." - ), } ) From 08b67e7aead8079e80ed526e80f41bc772256fa9 Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Wed, 14 Jul 2021 12:43:30 +1000 Subject: [PATCH 1007/1841] catch 0.0 in float set_level pre-adjustment (#2013) --- esphome/components/output/float_output.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/output/float_output.cpp b/esphome/components/output/float_output.cpp index f44383db36..99ba798cb9 100644 --- a/esphome/components/output/float_output.cpp +++ b/esphome/components/output/float_output.cpp @@ -31,6 +31,10 @@ void FloatOutput::set_level(float state) { #endif if (this->is_inverted()) state = 1.0f - state; + if (state == 0.0f) { // regardless of min_power_, 0.0 means off + this->write_state(state); + return; + } float adjusted_value = (state * (this->max_power_ - this->min_power_)) + this->min_power_; this->write_state(adjusted_value); } From 5cb0c11feb8b0283762c16fa741eebf8f09b0f3b Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 14 Jul 2021 07:08:18 +0200 Subject: [PATCH 1008/1841] Introduce clamp as a template function (#1953) --- esphome/components/climate_ir_lg/climate_ir_lg.cpp | 2 +- esphome/components/coolix/coolix.cpp | 2 +- esphome/components/daikin/daikin.cpp | 2 +- esphome/components/fan/fan_helpers.cpp | 2 +- esphome/components/fan/fan_state.cpp | 2 +- esphome/components/fujitsu_general/fujitsu_general.cpp | 2 +- esphome/components/mitsubishi/mitsubishi.cpp | 4 ++-- esphome/components/ssd1306_base/ssd1306_base.cpp | 2 +- esphome/components/ssd1322_base/ssd1322_base.cpp | 2 +- esphome/components/ssd1327_base/ssd1327_base.cpp | 2 +- esphome/components/ssd1331_base/ssd1331_base.cpp | 2 +- esphome/core/helpers.cpp | 5 ++++- esphome/core/helpers.h | 2 +- 13 files changed, 17 insertions(+), 14 deletions(-) diff --git a/esphome/components/climate_ir_lg/climate_ir_lg.cpp b/esphome/components/climate_ir_lg/climate_ir_lg.cpp index 4c721d5e64..cbb1f7699b 100644 --- a/esphome/components/climate_ir_lg/climate_ir_lg.cpp +++ b/esphome/components/climate_ir_lg/climate_ir_lg.cpp @@ -94,7 +94,7 @@ void LgIrClimate::transmit_state() { // remote_state |= FAN_MODE_AUTO_DRY; } if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) { - auto temp = (uint8_t) roundf(clamp(this->target_temperature, TEMP_MIN, TEMP_MAX)); + auto temp = (uint8_t) roundf(clamp(this->target_temperature, TEMP_MIN, TEMP_MAX)); remote_state |= ((temp - 15) << TEMP_SHIFT); } } diff --git a/esphome/components/coolix/coolix.cpp b/esphome/components/coolix/coolix.cpp index 653d8344e0..c9145e4ecf 100644 --- a/esphome/components/coolix/coolix.cpp +++ b/esphome/components/coolix/coolix.cpp @@ -84,7 +84,7 @@ void CoolixClimate::transmit_state() { } if (this->mode != climate::CLIMATE_MODE_OFF) { if (this->mode != climate::CLIMATE_MODE_FAN_ONLY) { - auto temp = (uint8_t) roundf(clamp(this->target_temperature, COOLIX_TEMP_MIN, COOLIX_TEMP_MAX)); + auto temp = (uint8_t) roundf(clamp(this->target_temperature, COOLIX_TEMP_MIN, COOLIX_TEMP_MAX)); remote_state |= COOLIX_TEMP_MAP[temp - COOLIX_TEMP_MIN]; } else { remote_state |= COOLIX_FAN_TEMP_CODE; diff --git a/esphome/components/daikin/daikin.cpp b/esphome/components/daikin/daikin.cpp index 40734203da..5f8d0288e2 100644 --- a/esphome/components/daikin/daikin.cpp +++ b/esphome/components/daikin/daikin.cpp @@ -135,7 +135,7 @@ uint8_t DaikinClimate::temperature_() { case climate::CLIMATE_MODE_DRY: return 0xc0; default: - uint8_t temperature = (uint8_t) roundf(clamp(this->target_temperature, DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX)); + uint8_t temperature = (uint8_t) roundf(clamp(this->target_temperature, DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX)); return temperature << 1; } } diff --git a/esphome/components/fan/fan_helpers.cpp b/esphome/components/fan/fan_helpers.cpp index be16e6bb64..09be20991b 100644 --- a/esphome/components/fan/fan_helpers.cpp +++ b/esphome/components/fan/fan_helpers.cpp @@ -6,7 +6,7 @@ namespace fan { FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels) { const auto speed_ratio = static_cast(speed_level) / (supported_speed_levels + 1); - const auto legacy_level = static_cast(clamp(ceilf(speed_ratio * 3), 1, 3)); + const auto legacy_level = clamp(static_cast(ceilf(speed_ratio * 3)), 1, 3); return static_cast(legacy_level - 1); } diff --git a/esphome/components/fan/fan_state.cpp b/esphome/components/fan/fan_state.cpp index 7cfe3afef7..9b4ae53937 100644 --- a/esphome/components/fan/fan_state.cpp +++ b/esphome/components/fan/fan_state.cpp @@ -54,7 +54,7 @@ void FanStateCall::perform() const { } if (this->speed_.has_value()) { const int speed_count = this->state_->get_traits().supported_speed_count(); - this->state_->speed = static_cast(clamp(*this->speed_, 1, speed_count)); + this->state_->speed = clamp(*this->speed_, 1, speed_count); } FanStateRTCState saved{}; diff --git a/esphome/components/fujitsu_general/fujitsu_general.cpp b/esphome/components/fujitsu_general/fujitsu_general.cpp index 6dd569fa21..8d789bbcfc 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.cpp +++ b/esphome/components/fujitsu_general/fujitsu_general.cpp @@ -110,7 +110,7 @@ void FujitsuGeneralClimate::transmit_state() { // Set temperature uint8_t temperature_clamped = - (uint8_t) roundf(clamp(this->target_temperature, FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX)); + (uint8_t) roundf(clamp(this->target_temperature, FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX)); uint8_t temperature_offset = temperature_clamped - FUJITSU_GENERAL_TEMP_MIN; SET_NIBBLE(remote_state, FUJITSU_GENERAL_TEMPERATURE_NIBBLE, temperature_offset); diff --git a/esphome/components/mitsubishi/mitsubishi.cpp b/esphome/components/mitsubishi/mitsubishi.cpp index 0b4b9be1eb..43397770d1 100644 --- a/esphome/components/mitsubishi/mitsubishi.cpp +++ b/esphome/components/mitsubishi/mitsubishi.cpp @@ -42,8 +42,8 @@ void MitsubishiClimate::transmit_state() { break; } - remote_state[7] = - (uint8_t) roundf(clamp(this->target_temperature, MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX) - MITSUBISHI_TEMP_MIN); + remote_state[7] = (uint8_t) roundf(clamp(this->target_temperature, MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX) - + MITSUBISHI_TEMP_MIN); ESP_LOGV(TAG, "Sending Mitsubishi target temp: %.1f state: %02X mode: %02X temp: %02X", this->target_temperature, remote_state[5], remote_state[6], remote_state[7]); diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index 58f86fd182..10e66df784 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -130,7 +130,7 @@ void SSD1306::update() { } void SSD1306::set_brightness(float brightness) { // validation - this->brightness_ = clamp(brightness, 0, 1); + this->brightness_ = clamp(brightness, 0.0F, 1.0F); // now write the new brightness level to the display this->command(SSD1306_COMMAND_SET_CONTRAST); this->command(int(SSD1306_MAX_CONTRAST * (this->brightness_))); diff --git a/esphome/components/ssd1322_base/ssd1322_base.cpp b/esphome/components/ssd1322_base/ssd1322_base.cpp index 0a3233acfe..007f61d5c8 100644 --- a/esphome/components/ssd1322_base/ssd1322_base.cpp +++ b/esphome/components/ssd1322_base/ssd1322_base.cpp @@ -126,7 +126,7 @@ void SSD1322::update() { this->display(); } void SSD1322::set_brightness(float brightness) { - this->brightness_ = clamp(brightness, 0, 1); + this->brightness_ = clamp(brightness, 0.0F, 1.0F); // now write the new brightness level to the display this->command(SSD1322_SETCONTRAST); this->data(int(SSD1322_MAX_CONTRAST * (this->brightness_))); diff --git a/esphome/components/ssd1327_base/ssd1327_base.cpp b/esphome/components/ssd1327_base/ssd1327_base.cpp index 798f67e4fc..ae94be87df 100644 --- a/esphome/components/ssd1327_base/ssd1327_base.cpp +++ b/esphome/components/ssd1327_base/ssd1327_base.cpp @@ -100,7 +100,7 @@ void SSD1327::update() { } void SSD1327::set_brightness(float brightness) { // validation - this->brightness_ = clamp(brightness, 0, 1); + this->brightness_ = clamp(brightness, 0.0F, 1.0F); // now write the new brightness level to the display this->command(SSD1327_SETCONTRAST); this->command(int(SSD1327_MAX_CONTRAST * (this->brightness_))); diff --git a/esphome/components/ssd1331_base/ssd1331_base.cpp b/esphome/components/ssd1331_base/ssd1331_base.cpp index a4bbad508c..f25ef50075 100644 --- a/esphome/components/ssd1331_base/ssd1331_base.cpp +++ b/esphome/components/ssd1331_base/ssd1331_base.cpp @@ -97,7 +97,7 @@ void SSD1331::update() { } void SSD1331::set_brightness(float brightness) { // validation - this->brightness_ = clamp(brightness, 0, 1); + this->brightness_ = clamp(brightness, 0.0F, 1.0F); // now write the new brightness level to the display this->command(SSD1331_CONTRASTA); // 0x81 this->command(int(SSD1331_MAX_CONTRASTA * (this->brightness_))); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 194ab08af3..a6cf8b779c 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -287,13 +287,16 @@ void HighFrequencyLoopRequester::stop() { } bool HighFrequencyLoopRequester::is_high_frequency() { return high_freq_num_requests > 0; } -float clamp(float val, float min, float max) { +template T clamp(const T val, const T min, const T max) { if (val < min) return min; if (val > max) return max; return val; } +template float clamp(float, float, float); +template int clamp(int, int, int); + float lerp(float completion, float start, float end) { return start + (end - start) * completion; } bool str_startswith(const std::string &full, const std::string &start) { return full.rfind(start, 0) == 0; } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 8096228a0f..808f96d4b8 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -80,7 +80,7 @@ class HighFrequencyLoopRequester { * @param max The maximum value. * @return val clamped in between min and max. */ -float clamp(float val, float min, float max); +template T clamp(T val, T min, T max); /** Linearly interpolate between end start and end by completion. * From c399905675a5404abc04731fc2a8b31de113f1df Mon Sep 17 00:00:00 2001 From: St4n <2866240+St4n@users.noreply.github.com> Date: Wed, 14 Jul 2021 11:21:39 +0200 Subject: [PATCH 1009/1841] [Teleinfo] do not stop parsing frame if there is only a CRC error (#1999) Co-authored-by: Stephane Angot Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/teleinfo/teleinfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/teleinfo/teleinfo.cpp b/esphome/components/teleinfo/teleinfo.cpp index fa76dcfe76..8240615cc5 100644 --- a/esphome/components/teleinfo/teleinfo.cpp +++ b/esphome/components/teleinfo/teleinfo.cpp @@ -120,7 +120,7 @@ void TeleInfo::loop() { } if (!check_crc_(buf_finger, grp_end)) - break; + continue; /* Get tag */ field_len = get_field(tag_, buf_finger, grp_end, separator_); From 18c08f24adf5f2f54db8233ede436b9e1ef3c6b2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 15 Jul 2021 07:45:05 +1200 Subject: [PATCH 1010/1841] Bump version to v1.20.0b1 --- esphome/const.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/const.py b/esphome/const.py index 094dcace42..30c4748933 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,8 +1,8 @@ """Constants used by esphome.""" MAJOR_VERSION = 1 -MINOR_VERSION = 19 -PATCH_VERSION = "0b7" +MINOR_VERSION = 20 +PATCH_VERSION = "0b1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 0992609bf49d2cdac25fd2f815021079ed3bf51e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 15 Jul 2021 07:45:05 +1200 Subject: [PATCH 1011/1841] Bump version to v1.21.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f02c17e0da..7f1e3a2c58 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,7 +1,7 @@ """Constants used by esphome.""" MAJOR_VERSION = 1 -MINOR_VERSION = 20 +MINOR_VERSION = 21 PATCH_VERSION = "0-dev" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 0651716b9657dc109b168235ea280a9848b583ad Mon Sep 17 00:00:00 2001 From: SenexCrenshaw <35600301+SenexCrenshaw@users.noreply.github.com> Date: Wed, 14 Jul 2021 20:51:15 -0400 Subject: [PATCH 1012/1841] Nextion upload and sensors (#1464) Co-authored-by: Senex Crenshaw --- CODEOWNERS | 5 + esphome/components/nextion/__init__.py | 5 + esphome/components/nextion/automation.h | 30 + esphome/components/nextion/base_component.py | 126 ++ esphome/components/nextion/binary_sensor.py | 34 - .../nextion/binary_sensor/__init__.py | 54 + .../binary_sensor/nextion_binarysensor.cpp | 69 + .../binary_sensor/nextion_binarysensor.h | 42 + esphome/components/nextion/display.py | 75 +- esphome/components/nextion/nextion.cpp | 1143 ++++++++++++++--- esphome/components/nextion/nextion.h | 527 +++++++- esphome/components/nextion/nextion_base.h | 58 + .../components/nextion/nextion_commands.cpp | 234 ++++ .../components/nextion/nextion_component.cpp | 116 ++ .../components/nextion/nextion_component.h | 49 + .../nextion/nextion_component_base.h | 95 ++ esphome/components/nextion/nextion_upload.cpp | 343 +++++ esphome/components/nextion/sensor/__init__.py | 99 ++ .../nextion/sensor/nextion_sensor.cpp | 110 ++ .../nextion/sensor/nextion_sensor.h | 49 + esphome/components/nextion/switch/__init__.py | 39 + .../nextion/switch/nextion_switch.cpp | 52 + .../nextion/switch/nextion_switch.h | 34 + .../nextion/text_sensor/__init__.py | 38 + .../text_sensor/nextion_textsensor.cpp | 49 + .../nextion/text_sensor/nextion_textsensor.h | 32 + esphome/components/uart/uart.h | 1 + script/ci-custom.py | 3 +- tests/test1.yaml | 9 - tests/test3.yaml | 38 +- 30 files changed, 3295 insertions(+), 263 deletions(-) create mode 100644 esphome/components/nextion/automation.h create mode 100644 esphome/components/nextion/base_component.py delete mode 100644 esphome/components/nextion/binary_sensor.py create mode 100644 esphome/components/nextion/binary_sensor/__init__.py create mode 100644 esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp create mode 100644 esphome/components/nextion/binary_sensor/nextion_binarysensor.h create mode 100644 esphome/components/nextion/nextion_base.h create mode 100644 esphome/components/nextion/nextion_commands.cpp create mode 100644 esphome/components/nextion/nextion_component.cpp create mode 100644 esphome/components/nextion/nextion_component.h create mode 100644 esphome/components/nextion/nextion_component_base.h create mode 100644 esphome/components/nextion/nextion_upload.cpp create mode 100644 esphome/components/nextion/sensor/__init__.py create mode 100644 esphome/components/nextion/sensor/nextion_sensor.cpp create mode 100644 esphome/components/nextion/sensor/nextion_sensor.h create mode 100644 esphome/components/nextion/switch/__init__.py create mode 100644 esphome/components/nextion/switch/nextion_switch.cpp create mode 100644 esphome/components/nextion/switch/nextion_switch.h create mode 100644 esphome/components/nextion/text_sensor/__init__.py create mode 100644 esphome/components/nextion/text_sensor/nextion_textsensor.cpp create mode 100644 esphome/components/nextion/text_sensor/nextion_textsensor.h diff --git a/CODEOWNERS b/CODEOWNERS index d6769800cd..557fe7cc08 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -73,6 +73,11 @@ esphome/components/midea_ac/* @dudanov esphome/components/midea_dongle/* @dudanov esphome/components/mitsubishi/* @RubyBailey esphome/components/network/* @esphome/core +esphome/components/nextion/* @senexcrenshaw +esphome/components/nextion/binary_sensor/* @senexcrenshaw +esphome/components/nextion/sensor/* @senexcrenshaw +esphome/components/nextion/switch/* @senexcrenshaw +esphome/components/nextion/text_sensor/* @senexcrenshaw esphome/components/nfc/* @jesserockz esphome/components/number/* @esphome/core esphome/components/ota/* @esphome/core diff --git a/esphome/components/nextion/__init__.py b/esphome/components/nextion/__init__.py index 67a49df9fa..924d58198d 100644 --- a/esphome/components/nextion/__init__.py +++ b/esphome/components/nextion/__init__.py @@ -1,3 +1,8 @@ import esphome.codegen as cg +from esphome.components import uart nextion_ns = cg.esphome_ns.namespace("nextion") +Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice) +nextion_ref = Nextion.operator("ref") + +CONF_NEXTION_ID = "nextion_id" diff --git a/esphome/components/nextion/automation.h b/esphome/components/nextion/automation.h new file mode 100644 index 0000000000..5f4219acb1 --- /dev/null +++ b/esphome/components/nextion/automation.h @@ -0,0 +1,30 @@ +#pragma once +#include "esphome/core/automation.h" +#include "nextion.h" + +namespace esphome { +namespace nextion { + +class SetupTrigger : public Trigger<> { + public: + explicit SetupTrigger(Nextion *nextion) { + nextion->add_setup_state_callback([this]() { this->trigger(); }); + } +}; + +class SleepTrigger : public Trigger<> { + public: + explicit SleepTrigger(Nextion *nextion) { + nextion->add_sleep_state_callback([this]() { this->trigger(); }); + } +}; + +class WakeTrigger : public Trigger<> { + public: + explicit WakeTrigger(Nextion *nextion) { + nextion->add_wake_state_callback([this]() { this->trigger(); }); + } +}; + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/base_component.py b/esphome/components/nextion/base_component.py new file mode 100644 index 0000000000..3bf828aafa --- /dev/null +++ b/esphome/components/nextion/base_component.py @@ -0,0 +1,126 @@ +from string import ascii_letters, digits +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.components import color + +from . import CONF_NEXTION_ID +from . import Nextion + +CONF_VARIABLE_NAME = "variable_name" +CONF_COMPONENT_NAME = "component_name" +CONF_WAVE_CHANNEL_ID = "wave_channel_id" +CONF_WAVE_MAX_VALUE = "wave_max_value" +CONF_PRECISION = "precision" +CONF_WAVEFORM_SEND_LAST_VALUE = "waveform_send_last_value" +CONF_TFT_URL = "tft_url" +CONF_ON_SLEEP = "on_sleep" +CONF_ON_WAKE = "on_wake" +CONF_ON_SETUP = "on_setup" +CONF_TOUCH_SLEEP_TIMEOUT = "touch_sleep_timeout" +CONF_WAKE_UP_PAGE = "wake_up_page" +CONF_AUTO_WAKE_ON_TOUCH = "auto_wake_on_touch" +CONF_WAVE_MAX_LENGTH = "wave_max_length" +CONF_BACKGROUND_COLOR = "background_color" +CONF_BACKGROUND_PRESSED_COLOR = "background_pressed_color" +CONF_FOREGROUND_COLOR = "foreground_color" +CONF_FOREGROUND_PRESSED_COLOR = "foreground_pressed_color" +CONF_FONT_ID = "font_id" +CONF_VISIBLE = "visible" + + +def NextionName(value): + valid_chars = ascii_letters + digits + "." + if not isinstance(value, str) or len(value) > 29: + raise cv.Invalid("Must be a string less than 29 characters") + + for char in value: + if char not in valid_chars: + raise cv.Invalid( + "Must only consist of upper/lowercase characters, numbers and the period '.'. The character '{}' cannot be used.".format( + char + ) + ) + + return value + + +CONFIG_BASE_COMPONENT_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_NEXTION_ID): cv.use_id(Nextion), + cv.Optional(CONF_BACKGROUND_COLOR): cv.use_id(color), + cv.Optional(CONF_FOREGROUND_COLOR): cv.use_id(color), + cv.Optional(CONF_VISIBLE, default=True): cv.boolean, + } +) + + +CONFIG_TEXT_COMPONENT_SCHEMA = CONFIG_BASE_COMPONENT_SCHEMA.extend( + cv.Schema( + { + cv.Required(CONF_COMPONENT_NAME): NextionName, + cv.Optional(CONF_FONT_ID): cv.int_range(min=0, max=255), + } + ) +) + +CONFIG_BINARY_SENSOR_SCHEMA = CONFIG_BASE_COMPONENT_SCHEMA.extend( + cv.Schema( + { + cv.Optional(CONF_COMPONENT_NAME): NextionName, + cv.Optional(CONF_VARIABLE_NAME): NextionName, + } + ) +) + +CONFIG_SENSOR_COMPONENT_SCHEMA = CONFIG_BINARY_SENSOR_SCHEMA.extend( + cv.Schema( + { + cv.Optional(CONF_FONT_ID): cv.int_range(min=0, max=255), + } + ) +) + + +CONFIG_SWITCH_COMPONENT_SCHEMA = CONFIG_SENSOR_COMPONENT_SCHEMA.extend( + cv.Schema( + { + cv.Optional(CONF_FOREGROUND_PRESSED_COLOR): cv.use_id(color), + cv.Optional(CONF_BACKGROUND_PRESSED_COLOR): cv.use_id(color), + } + ) +) + + +async def setup_component_core_(var, config, arg): + + if CONF_VARIABLE_NAME in config: + cg.add(var.set_variable_name(config[CONF_VARIABLE_NAME])) + elif CONF_COMPONENT_NAME in config: + cg.add( + var.set_variable_name( + config[CONF_COMPONENT_NAME], + config[CONF_COMPONENT_NAME] + arg, + ) + ) + + if CONF_BACKGROUND_COLOR in config: + color_component = await cg.get_variable(config[CONF_BACKGROUND_COLOR]) + cg.add(var.set_background_color(color_component)) + + if CONF_BACKGROUND_PRESSED_COLOR in config: + color_component = await cg.get_variable(config[CONF_BACKGROUND_PRESSED_COLOR]) + cg.add(var.set_background_pressed_color(color_component)) + + if CONF_FOREGROUND_COLOR in config: + color_component = await cg.get_variable(config[CONF_FOREGROUND_COLOR]) + cg.add(var.set_foreground_color(color_component)) + + if CONF_FOREGROUND_PRESSED_COLOR in config: + color_component = await cg.get_variable(config[CONF_FOREGROUND_PRESSED_COLOR]) + cg.add(var.set_foreground_pressed_color(color_component)) + + if CONF_FONT_ID in config: + cg.add(var.set_font_id(config[CONF_FONT_ID])) + + if CONF_VISIBLE in config: + cg.add(var.set_visible(config[CONF_VISIBLE])) diff --git a/esphome/components/nextion/binary_sensor.py b/esphome/components/nextion/binary_sensor.py deleted file mode 100644 index ed4e8d832a..0000000000 --- a/esphome/components/nextion/binary_sensor.py +++ /dev/null @@ -1,34 +0,0 @@ -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components import binary_sensor -from esphome.const import CONF_COMPONENT_ID, CONF_PAGE_ID, CONF_ID -from . import nextion_ns -from .display import Nextion - -DEPENDENCIES = ["display"] - -CONF_NEXTION_ID = "nextion_id" - -NextionTouchComponent = nextion_ns.class_( - "NextionTouchComponent", binary_sensor.BinarySensor -) - -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(NextionTouchComponent), - cv.GenerateID(CONF_NEXTION_ID): cv.use_id(Nextion), - cv.Required(CONF_PAGE_ID): cv.uint8_t, - cv.Required(CONF_COMPONENT_ID): cv.uint8_t, - } -) - - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) - - hub = await cg.get_variable(config[CONF_NEXTION_ID]) - cg.add(hub.register_touch_component(var)) - - cg.add(var.set_component_id(config[CONF_COMPONENT_ID])) - cg.add(var.set_page_id(config[CONF_PAGE_ID])) diff --git a/esphome/components/nextion/binary_sensor/__init__.py b/esphome/components/nextion/binary_sensor/__init__.py new file mode 100644 index 0000000000..090fae3429 --- /dev/null +++ b/esphome/components/nextion/binary_sensor/__init__.py @@ -0,0 +1,54 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor + +from esphome.const import CONF_COMPONENT_ID, CONF_PAGE_ID, CONF_ID +from .. import nextion_ns, CONF_NEXTION_ID + + +from ..base_component import ( + setup_component_core_, + CONFIG_BINARY_SENSOR_SCHEMA, + CONF_VARIABLE_NAME, + CONF_COMPONENT_NAME, +) + +CODEOWNERS = ["@senexcrenshaw"] + +NextionBinarySensor = nextion_ns.class_( + "NextionBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent +) + +CONFIG_SCHEMA = cv.All( + binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(NextionBinarySensor), + cv.Optional(CONF_PAGE_ID): cv.uint8_t, + cv.Optional(CONF_COMPONENT_ID): cv.uint8_t, + } + ) + .extend(CONFIG_BINARY_SENSOR_SCHEMA) + .extend(cv.polling_component_schema("never")), + cv.has_at_least_one_key( + CONF_PAGE_ID, + CONF_COMPONENT_ID, + CONF_COMPONENT_NAME, + CONF_VARIABLE_NAME, + ), +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_NEXTION_ID]) + var = cg.new_Pvariable(config[CONF_ID], hub) + await binary_sensor.register_binary_sensor(var, config) + await cg.register_component(var, config) + + if config.keys() >= {CONF_PAGE_ID, CONF_COMPONENT_ID}: + cg.add(hub.register_touch_component(var)) + cg.add(var.set_component_id(config[CONF_COMPONENT_ID])) + cg.add(var.set_page_id(config[CONF_PAGE_ID])) + + if CONF_COMPONENT_NAME in config or CONF_VARIABLE_NAME in config: + await setup_component_core_(var, config, ".val") + cg.add(hub.register_binarysensor_component(var)) diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp new file mode 100644 index 0000000000..bf6e74cb38 --- /dev/null +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp @@ -0,0 +1,69 @@ +#include "nextion_binarysensor.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { + +static const char *const TAG = "nextion_binarysensor"; + +void NextionBinarySensor::process_bool(const std::string &variable_name, bool state) { + if (!this->nextion_->is_setup()) + return; + + if (this->variable_name_.empty()) // This is a touch component + return; + + if (this->variable_name_ == variable_name) { + this->publish_state(state); + ESP_LOGD(TAG, "Processed binarysensor \"%s\" state %s", variable_name.c_str(), state ? "ON" : "OFF"); + } +} + +void NextionBinarySensor::process_touch(uint8_t page_id, uint8_t component_id, bool state) { + if (this->page_id_ == page_id && this->component_id_ == component_id) { + this->publish_state(state); + } +} + +void NextionBinarySensor::update() { + if (!this->nextion_->is_setup()) + return; + + if (this->variable_name_.empty()) // This is a touch component + return; + + this->nextion_->add_to_get_queue(this); +} + +void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nextion) { + if (!this->nextion_->is_setup()) + return; + + if (this->component_id_ == 0) // This is a legacy touch component + return; + + if (send_to_nextion) { + if (this->nextion_->is_sleeping() || !this->visible_) { + this->needs_to_send_update_ = true; + } else { + this->needs_to_send_update_ = false; + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + } + } + + if (publish) { + this->publish_state(state); + } else { + this->state = state; + this->has_state_ = true; + } + + this->update_component_settings(); + + ESP_LOGN(TAG, "Wrote state for sensor \"%s\" state %s", this->variable_name_.c_str(), + ONOFF(this->variable_name_.c_str())); +} + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.h b/esphome/components/nextion/binary_sensor/nextion_binarysensor.h new file mode 100644 index 0000000000..b6b23ada85 --- /dev/null +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.h @@ -0,0 +1,42 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "../nextion_component.h" +#include "../nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionBinarySensor; + +class NextionBinarySensor : public NextionComponent, + public binary_sensor::BinarySensorInitiallyOff, + public PollingComponent { + public: + NextionBinarySensor(NextionBase *nextion) { this->nextion_ = nextion; } + + void update_component() override { this->update(); } + void update() override; + void send_state_to_nextion() override { this->set_state(this->state, false); }; + void process_bool(const std::string &variable_name, bool state) override; + void process_touch(uint8_t page_id, uint8_t component_id, bool state) override; + + // Set the components page id for Nextion Touch Component + void set_page_id(uint8_t page_id) { page_id_ = page_id; } + // Set the components component id for Nextion Touch Component + void set_component_id(uint8_t component_id) { component_id_ = component_id; } + + void set_state(bool state) override { this->set_state(state, true, true); } + void set_state(bool state, bool publish) override { this->set_state(state, publish, true); } + void set_state(bool state, bool publish, bool send_to_nextion) override; + + NextionQueueType get_queue_type() override { return NextionQueueType::BINARY_SENSOR; } + void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {} + void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override { + this->set_state(state_value != 0, publish, send_to_nextion); + } + + protected: + uint8_t page_id_; +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index 7d7018a4c4..e693b2f1ec 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -1,20 +1,58 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome import automation from esphome.components import display, uart -from esphome.const import CONF_ID, CONF_LAMBDA, CONF_BRIGHTNESS -from . import nextion_ns +from esphome.const import ( + CONF_ID, + CONF_LAMBDA, + CONF_BRIGHTNESS, + CONF_TRIGGER_ID, +) + +from . import Nextion, nextion_ns, nextion_ref +from .base_component import ( + CONF_ON_SLEEP, + CONF_ON_WAKE, + CONF_ON_SETUP, + CONF_TFT_URL, + CONF_TOUCH_SLEEP_TIMEOUT, + CONF_WAKE_UP_PAGE, + CONF_AUTO_WAKE_ON_TOUCH, +) + +CODEOWNERS = ["@senexcrenshaw"] DEPENDENCIES = ["uart"] -AUTO_LOAD = ["binary_sensor"] +AUTO_LOAD = ["binary_sensor", "switch", "sensor", "text_sensor"] -Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice) -NextionRef = Nextion.operator("ref") +SetupTrigger = nextion_ns.class_("SetupTrigger", automation.Trigger.template()) +SleepTrigger = nextion_ns.class_("SleepTrigger", automation.Trigger.template()) +WakeTrigger = nextion_ns.class_("WakeTrigger", automation.Trigger.template()) CONFIG_SCHEMA = ( display.BASIC_DISPLAY_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(Nextion), + cv.Optional(CONF_TFT_URL): cv.string, cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, + cv.Optional(CONF_ON_SETUP): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SetupTrigger), + } + ), + cv.Optional(CONF_ON_SLEEP): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SleepTrigger), + } + ), + cv.Optional(CONF_ON_WAKE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(WakeTrigger), + } + ), + cv.Optional(CONF_TOUCH_SLEEP_TIMEOUT): cv.int_range(min=3, max=65535), + cv.Optional(CONF_WAKE_UP_PAGE): cv.positive_int, + cv.Optional(CONF_AUTO_WAKE_ON_TOUCH, default=True): cv.boolean, } ) .extend(cv.polling_component_schema("5s")) @@ -31,8 +69,33 @@ async def to_code(config): cg.add(var.set_brightness(config[CONF_BRIGHTNESS])) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( - config[CONF_LAMBDA], [(NextionRef, "it")], return_type=cg.void + config[CONF_LAMBDA], [(nextion_ref, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) + if CONF_TFT_URL in config: + cg.add_define("USE_TFT_UPLOAD") + cg.add(var.set_tft_url(config[CONF_TFT_URL])) + + if CONF_TOUCH_SLEEP_TIMEOUT in config: + cg.add(var.set_touch_sleep_timeout_internal(config[CONF_TOUCH_SLEEP_TIMEOUT])) + + if CONF_WAKE_UP_PAGE in config: + cg.add(var.set_wake_up_page_internal(config[CONF_WAKE_UP_PAGE])) + + if CONF_AUTO_WAKE_ON_TOUCH in config: + cg.add(var.set_auto_wake_on_touch_internal(config[CONF_AUTO_WAKE_ON_TOUCH])) + await display.register_display(var, config) + + for conf in config.get(CONF_ON_SETUP, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_SLEEP, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_WAKE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index fe0767342b..9a5424917f 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -1,5 +1,7 @@ #include "nextion.h" +#include "esphome/core/util.h" #include "esphome/core/log.h" +#include "esphome/core/application.h" namespace esphome { namespace nextion { @@ -7,69 +9,171 @@ namespace nextion { static const char *const TAG = "nextion"; void Nextion::setup() { - this->send_command_no_ack(""); - this->send_command_printf("bkcmd=3"); - this->set_backlight_brightness(static_cast(brightness_ * 100)); - this->goto_page("0"); + this->is_setup_ = false; + this->ignore_is_setup_ = true; + + // Wake up the nextion + this->send_command_("bkcmd=0"); + this->send_command_("sleep=0"); + + this->send_command_("bkcmd=0"); + this->send_command_("sleep=0"); + + // Reboot it + this->send_command_("rest"); + + this->ignore_is_setup_ = false; } -float Nextion::get_setup_priority() const { return setup_priority::PROCESSOR; } + +bool Nextion::send_command_(const std::string &command) { + if (!this->ignore_is_setup_ && !this->is_setup()) { + return false; + } + + ESP_LOGN(TAG, "send_command %s", command.c_str()); + + this->write_str(command.c_str()); + const uint8_t to_send[3] = {0xFF, 0xFF, 0xFF}; + this->write_array(to_send, sizeof(to_send)); + return true; +} + +bool Nextion::check_connect_() { + if (this->get_is_connected_()) + return true; + + if (this->comok_sent_ == 0) { + this->reset_(false); + + this->ignore_is_setup_ = true; + this->send_command_("boguscommand=0"); // bogus command. needed sometimes after updating + this->send_command_("connect"); + + this->comok_sent_ = millis(); + this->ignore_is_setup_ = false; + + return false; + } + + if (millis() - this->comok_sent_ <= 500) // Wait 500 ms + return false; + + std::string response; + + this->recv_ret_string_(response, 0, false); + if (response.empty() || response.find("comok") == std::string::npos) { +#ifdef NEXTION_PROTOCOL_LOG + ESP_LOGN(TAG, "Bad connect request %s", response.c_str()); + for (int i = 0; i < response.length(); i++) { + ESP_LOGN(TAG, "response %s %d %d %c", response.c_str(), i, response[i], response[i]); + } +#endif + + ESP_LOGW(TAG, "Nextion is not connected! "); + comok_sent_ = 0; + return false; + } + + this->ignore_is_setup_ = true; + ESP_LOGI(TAG, "Nextion is connected"); + this->is_connected_ = true; + + ESP_LOGN(TAG, "connect request %s", response.c_str()); + + size_t start; + size_t end = 0; + std::vector connect_info; + while ((start = response.find_first_not_of(',', end)) != std::string::npos) { + end = response.find(',', start); + connect_info.push_back(response.substr(start, end - start)); + } + + if (connect_info.size() == 7) { + ESP_LOGN(TAG, "Received connect_info %zu", connect_info.size()); + + this->device_model_ = connect_info[2]; + this->firmware_version_ = connect_info[3]; + this->serial_number_ = connect_info[5]; + this->flash_size_ = connect_info[6]; + } else { + ESP_LOGE(TAG, "Nextion returned bad connect value \"%s\"", response.c_str()); + } + + this->ignore_is_setup_ = false; + this->dump_config(); + return true; +} + +void Nextion::reset_(bool reset_nextion) { + uint8_t d; + + while (this->available()) { // Clear receive buffer + this->read_byte(&d); + }; + this->nextion_queue_.clear(); +} + +void Nextion::dump_config() { + ESP_LOGCONFIG(TAG, "Nextion:"); + ESP_LOGCONFIG(TAG, " Device Model: %s", this->device_model_.c_str()); + ESP_LOGCONFIG(TAG, " Firmware Version: %s", this->firmware_version_.c_str()); + ESP_LOGCONFIG(TAG, " Serial Number: %s", this->serial_number_.c_str()); + ESP_LOGCONFIG(TAG, " Flash Size: %s", this->flash_size_.c_str()); + ESP_LOGCONFIG(TAG, " Wake On Touch: %s", this->auto_wake_on_touch_ ? "True" : "False"); + + if (this->touch_sleep_timeout_ != 0) { + ESP_LOGCONFIG(TAG, " Touch Timeout: %d", this->touch_sleep_timeout_); + } + + if (this->wake_up_page_ != -1) { + ESP_LOGCONFIG(TAG, " Wake Up Page : %d", this->wake_up_page_); + } +} + +float Nextion::get_setup_priority() const { return setup_priority::DATA; } void Nextion::update() { + if (!this->is_setup()) { + return; + } if (this->writer_.has_value()) { (*this->writer_)(*this); } } -void Nextion::send_command_no_ack(const char *command) { - // Flush RX... - this->loop(); - this->write_str(command); - const uint8_t data[3] = {0xFF, 0xFF, 0xFF}; - this->write_array(data, sizeof(data)); +void Nextion::add_sleep_state_callback(std::function &&callback) { + this->sleep_callback_.add(std::move(callback)); } -bool Nextion::ack_() { - if (!this->wait_for_ack_) - return true; +void Nextion::add_wake_state_callback(std::function &&callback) { + this->wake_callback_.add(std::move(callback)); +} - uint32_t start = millis(); - while (!this->read_until_ack_()) { - if (millis() - start > 100) { - ESP_LOGW(TAG, "Waiting for ACK timed out!"); - return false; - } +void Nextion::add_setup_state_callback(std::function &&callback) { + this->setup_callback_.add(std::move(callback)); +} + +void Nextion::update_all_components() { + if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) + return; + + for (auto *binarysensortype : this->binarysensortype_) { + binarysensortype->update_component(); + } + for (auto *sensortype : this->sensortype_) { + sensortype->update_component(); + } + for (auto *switchtype : this->switchtype_) { + switchtype->update_component(); + } + for (auto *textsensortype : this->textsensortype_) { + textsensortype->update_component(); } - return true; } -void Nextion::set_component_text(const char *component, const char *text) { - this->send_command_printf("%s.txt=\"%s\"", component, text); -} -void Nextion::set_component_value(const char *component, int value) { - this->send_command_printf("%s.val=%d", component, value); -} -void Nextion::display_picture(int picture_id, int x_start, int y_start) { - this->send_command_printf("pic %d %d %d", x_start, y_start, picture_id); -} -void Nextion::set_component_background_color(const char *component, const char *color) { - this->send_command_printf("%s.bco=\"%s\"", component, color); -} -void Nextion::set_component_pressed_background_color(const char *component, const char *color) { - this->send_command_printf("%s.bco2=\"%s\"", component, color); -} -void Nextion::set_component_font_color(const char *component, const char *color) { - this->send_command_printf("%s.pco=\"%s\"", component, color); -} -void Nextion::set_component_pressed_font_color(const char *component, const char *color) { - this->send_command_printf("%s.pco2=\"%s\"", component, color); -} -void Nextion::set_component_coordinates(const char *component, int x, int y) { - this->send_command_printf("%s.xcen=%d", component, x); - this->send_command_printf("%s.ycen=%d", component, y); -} -void Nextion::set_component_font(const char *component, uint8_t font_id) { - this->send_command_printf("%s.font=%d", component, font_id); -} -void Nextion::goto_page(const char *page) { this->send_command_printf("page %s", page); } + bool Nextion::send_command_printf(const char *format, ...) { + if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) + return false; + char buffer[256]; va_list arg; va_start(arg, format); @@ -79,208 +183,911 @@ bool Nextion::send_command_printf(const char *format, ...) { ESP_LOGW(TAG, "Building command for format '%s' failed!", format); return false; } - this->send_command_no_ack(buffer); - if (!this->ack_()) { - ESP_LOGW(TAG, "Sending command '%s' failed because no ACK was received", buffer); + + if (this->send_command_(buffer)) { + this->add_no_result_to_queue_("send_command_printf"); + return true; + } + return false; +} + +#ifdef NEXTION_PROTOCOL_LOG +void Nextion::print_queue_members_() { + ESP_LOGN(TAG, "print_queue_members_ (top 10) size %zu", this->nextion_queue_.size()); + ESP_LOGN(TAG, "*******************************************"); + int count = 0; + for (auto *i : this->nextion_queue_) { + if (count++ == 10) + break; + + if (i == nullptr) { + ESP_LOGN(TAG, "Nextion queue is null"); + } else { + ESP_LOGN(TAG, "Nextion queue type: %d:%s , name: %s", i->component->get_queue_type(), + i->component->get_queue_type_string().c_str(), i->component->get_variable_name().c_str()); + } + } + ESP_LOGN(TAG, "*******************************************"); +} +#endif + +void Nextion::loop() { + if (!this->check_connect_() || this->is_updating_) + return; + + if (this->nextion_reports_is_setup_ && !this->sent_setup_commands_) { + this->ignore_is_setup_ = true; + this->sent_setup_commands_ = true; + this->send_command_("bkcmd=3"); // Always, returns 0x00 to 0x23 result of serial command. + + this->set_backlight_brightness(this->brightness_); + this->goto_page("0"); + + this->set_auto_wake_on_touch(this->auto_wake_on_touch_); + + if (this->touch_sleep_timeout_ != 0) { + this->set_touch_sleep_timeout(this->touch_sleep_timeout_); + } + + if (this->wake_up_page_ != -1) { + this->set_wake_up_page(this->wake_up_page_); + } + + this->ignore_is_setup_ = false; + } + + this->process_serial_(); // Receive serial data + this->process_nextion_commands_(); // Process nextion return commands + + if (!this->nextion_reports_is_setup_) { + if (this->started_ms_ == 0) + this->started_ms_ = millis(); + + if (this->started_ms_ + this->startup_override_ms_ < millis()) { + ESP_LOGD(TAG, "Manually set nextion report ready"); + this->nextion_reports_is_setup_ = true; + } + } +} + +bool Nextion::remove_from_q_(bool report_empty) { + if (this->nextion_queue_.empty()) { + if (report_empty) + ESP_LOGE(TAG, "Nextion queue is empty!"); return false; } + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; + + ESP_LOGN(TAG, "Removing %s from the queue", component->get_variable_name().c_str()); + + if (component->get_queue_type() == NextionQueueType::NO_RESULT) { + if (component->get_variable_name() == "sleep_wake") { + this->is_sleeping_ = false; + } + delete component; + } + delete nb; + this->nextion_queue_.pop_front(); return true; } -void Nextion::hide_component(const char *component) { this->send_command_printf("vis %s,0", component); } -void Nextion::show_component(const char *component) { this->send_command_printf("vis %s,1", component); } -void Nextion::enable_component_touch(const char *component) { this->send_command_printf("tsw %s,1", component); } -void Nextion::disable_component_touch(const char *component) { this->send_command_printf("tsw %s,0", component); } -void Nextion::add_waveform_data(int component_id, uint8_t channel_number, uint8_t value) { - this->send_command_printf("add %d,%u,%u", component_id, channel_number, value); -} -void Nextion::fill_area(int x1, int y1, int width, int height, const char *color) { - this->send_command_printf("fill %d,%d,%d,%d,%s", x1, y1, width, height, color); -} -void Nextion::line(int x1, int y1, int x2, int y2, const char *color) { - this->send_command_printf("line %d,%d,%d,%d,%s", x1, y1, x2, y2, color); -} -void Nextion::rectangle(int x1, int y1, int width, int height, const char *color) { - this->send_command_printf("draw %d,%d,%d,%d,%s", x1, y1, x1 + width, y1 + height, color); -} -void Nextion::circle(int center_x, int center_y, int radius, const char *color) { - this->send_command_printf("cir %d,%d,%d,%s", center_x, center_y, radius, color); -} -void Nextion::filled_circle(int center_x, int center_y, int radius, const char *color) { - this->send_command_printf("cirs %d,%d,%d,%s", center_x, center_y, radius, color); -} -bool Nextion::read_until_ack_() { - while (this->available() >= 4) { - // flush preceding filler bytes - uint8_t temp; - while (this->available() && this->peek_byte(&temp) && temp == 0xFF) - this->read_byte(&temp); - if (!this->available()) - break; +void Nextion::process_serial_() { + uint8_t d; - uint8_t event; - // event type - this->read_byte(&event); + while (this->available()) { + read_byte(&d); + this->command_data_ += d; + } +} +// nextion.tech/instruction-set/ +void Nextion::process_nextion_commands_() { + if (this->command_data_.length() == 0) { + return; + } - uint8_t data[255]; - // total length of data (including end bytes) - uint8_t data_length = 0; - // message is terminated by three consecutive 0xFF - // this variable keeps track of ohow many of those have - // been received - uint8_t end_length = 0; - while (this->available() && end_length < 3 && data_length < sizeof(data)) { - uint8_t byte; - this->read_byte(&byte); - if (byte == 0xFF) { - end_length++; - } else { - end_length = 0; - } - data[data_length++] = byte; + size_t to_process_length = 0; + std::string to_process; + + ESP_LOGN(TAG, "this->command_data_ %s length %d", this->command_data_.c_str(), this->command_data_.length()); +#ifdef NEXTION_PROTOCOL_LOG + this->print_queue_members_(); +#endif + while ((to_process_length = this->command_data_.find(COMMAND_DELIMITER)) != std::string::npos) { + ESP_LOGN(TAG, "print_queue_members_ size %zu", this->nextion_queue_.size()); + while (to_process_length + COMMAND_DELIMITER.length() < this->command_data_.length() && + static_cast(this->command_data_[to_process_length + COMMAND_DELIMITER.length()]) == 0xFF) { + ++to_process_length; + ESP_LOGN(TAG, "Add extra 0xFF to process"); } - if (end_length != 3) { - ESP_LOGW(TAG, "Received unknown filler end bytes from Nextion!"); - continue; - } + this->nextion_event_ = this->command_data_[0]; - data_length -= 3; // remove filler bytes + to_process_length -= 1; + to_process = this->command_data_.substr(1, to_process_length); - bool invalid_data_length = false; - switch (event) { - case 0x01: // successful execution of instruction (ACK) - return true; - case 0x00: // invalid instruction + switch (this->nextion_event_) { + case 0x00: // instruction sent by user has failed ESP_LOGW(TAG, "Nextion reported invalid instruction!"); + this->remove_from_q_(); + break; - case 0x02: // component ID invalid - ESP_LOGW(TAG, "Nextion reported component ID invalid!"); + case 0x01: // instruction sent by user was successful + + ESP_LOGVV(TAG, "instruction sent by user was successful"); + ESP_LOGN(TAG, "this->nextion_queue_.empty() %s", this->nextion_queue_.empty() ? "True" : "False"); + + this->remove_from_q_(); + if (!this->is_setup_) { + if (this->nextion_queue_.empty()) { + ESP_LOGD(TAG, "Nextion is setup"); + this->is_setup_ = true; + this->setup_callback_.call(); + } + } + break; - case 0x03: // page ID invalid + case 0x02: // invalid Component ID or name was used + this->remove_from_q_(); + break; + case 0x03: // invalid Page ID or name was used ESP_LOGW(TAG, "Nextion reported page ID invalid!"); + this->remove_from_q_(); break; - case 0x04: // picture ID invalid + case 0x04: // invalid Picture ID was used ESP_LOGW(TAG, "Nextion reported picture ID invalid!"); + this->remove_from_q_(); break; - case 0x05: // font ID invalid + case 0x05: // invalid Font ID was used ESP_LOGW(TAG, "Nextion reported font ID invalid!"); + this->remove_from_q_(); break; - case 0x11: // baud rate setting invalid + case 0x06: // File operation fails + ESP_LOGW(TAG, "Nextion File operation fail!"); + break; + case 0x09: // Instructions with CRC validation fails their CRC check + ESP_LOGW(TAG, "Nextion Instructions with CRC validation fails their CRC check!"); + break; + case 0x11: // invalid Baud rate was used ESP_LOGW(TAG, "Nextion reported baud rate invalid!"); break; - case 0x12: // curve control ID number or channel number is invalid - ESP_LOGW(TAG, "Nextion reported control/channel ID invalid!"); + case 0x12: // invalid Waveform ID or Channel # was used + + if (!this->nextion_queue_.empty()) { + int index = 0; + int found = -1; + for (auto &nb : this->nextion_queue_) { + NextionComponentBase *component = nb->component; + + if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { + ESP_LOGW(TAG, "Nextion reported invalid Waveform ID %d or Channel # %d was used!", + component->get_component_id(), component->get_wave_channel_id()); + + ESP_LOGN(TAG, "Removing waveform from queue with component id %d and waveform id %d", + component->get_component_id(), component->get_wave_channel_id()); + + found = index; + + delete component; + delete nb; + + break; + } + ++index; + } + + if (found != -1) { + this->nextion_queue_.erase(this->nextion_queue_.begin() + found); + } else { + ESP_LOGW( + TAG, + "Nextion reported invalid Waveform ID or Channel # was used but no waveform sensor in queue found!"); + } + } break; case 0x1A: // variable name invalid - ESP_LOGW(TAG, "Nextion reported variable name invalid!"); + this->remove_from_q_(); + break; case 0x1B: // variable operation invalid ESP_LOGW(TAG, "Nextion reported variable operation invalid!"); + this->remove_from_q_(); break; case 0x1C: // failed to assign ESP_LOGW(TAG, "Nextion reported failed to assign variable!"); + this->remove_from_q_(); break; case 0x1D: // operate EEPROM failed ESP_LOGW(TAG, "Nextion reported operating EEPROM failed!"); break; case 0x1E: // parameter quantity invalid ESP_LOGW(TAG, "Nextion reported parameter quantity invalid!"); + this->remove_from_q_(); break; case 0x1F: // IO operation failed ESP_LOGW(TAG, "Nextion reported component I/O operation invalid!"); break; case 0x20: // undefined escape characters ESP_LOGW(TAG, "Nextion reported undefined escape characters!"); + this->remove_from_q_(); break; case 0x23: // too long variable name ESP_LOGW(TAG, "Nextion reported too long variable name!"); + this->remove_from_q_(); + + break; + case 0x24: // Serial Buffer overflow occurs + ESP_LOGW(TAG, "Nextion reported Serial Buffer overflow!"); break; case 0x65: { // touch event return data - if (data_length != 3) { - invalid_data_length = true; + if (to_process_length != 3) { + ESP_LOGW(TAG, "Touch event data is expecting 3, received %zu", to_process_length); + break; } - uint8_t page_id = data[0]; - uint8_t component_id = data[1]; - uint8_t touch_event = data[2]; // 0 -> release, 1 -> press + uint8_t page_id = to_process[0]; + uint8_t component_id = to_process[1]; + uint8_t touch_event = to_process[2]; // 0 -> release, 1 -> press ESP_LOGD(TAG, "Got touch page=%u component=%u type=%s", page_id, component_id, touch_event ? "PRESS" : "RELEASE"); for (auto *touch : this->touch_) { - touch->process(page_id, component_id, touch_event); + touch->process_touch(page_id, component_id, touch_event != 0); } break; } - case 0x67: - case 0x68: { // touch coordinate data - if (data_length != 5) { - invalid_data_length = true; + case 0x67: { // Touch Coordinate (awake) + break; + } + case 0x68: { // touch coordinate data (sleep) + + if (to_process_length != 5) { + ESP_LOGW(TAG, "Touch coordinate data is expecting 5, received %zu", to_process_length); + ESP_LOGW(TAG, "%s", to_process.c_str()); break; } - uint16_t x = (uint16_t(data[0]) << 8) | data[1]; - uint16_t y = (uint16_t(data[2]) << 8) | data[3]; - uint8_t touch_event = data[4]; // 0 -> release, 1 -> press + + uint16_t x = (uint16_t(to_process[0]) << 8) | to_process[1]; + uint16_t y = (uint16_t(to_process[2]) << 8) | to_process[3]; + uint8_t touch_event = to_process[4]; // 0 -> release, 1 -> press ESP_LOGD(TAG, "Got touch at x=%u y=%u type=%s", x, y, touch_event ? "PRESS" : "RELEASE"); break; } - case 0x66: // sendme page id + case 0x66: { + break; + } // sendme page id + + // 0x70 0x61 0x62 0x31 0x32 0x33 0xFF 0xFF 0xFF + // Returned when using get command for a string. + // Each byte is converted to char. + // data: ab123 case 0x70: // string variable data return + { + if (this->nextion_queue_.empty()) { + ESP_LOGW(TAG, "ERROR: Received string return but the queue is empty"); + break; + } + + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; + + if (component->get_queue_type() != NextionQueueType::TEXT_SENSOR) { + ESP_LOGE(TAG, "ERROR: Received string return but next in queue \"%s\" is not a text sensor", + component->get_variable_name().c_str()); + } else { + ESP_LOGN(TAG, "Received get_string response: \"%s\" for component id: %s, type: %s", to_process.c_str(), + component->get_variable_name().c_str(), component->get_queue_type_string().c_str()); + component->set_state_from_string(to_process, true, false); + } + + delete nb; + this->nextion_queue_.pop_front(); + + break; + } + // 0x71 0x01 0x02 0x03 0x04 0xFF 0xFF 0xFF + // Returned when get command to return a number + // 4 byte 32-bit value in little endian order. + // (0x01+0x02*256+0x03*65536+0x04*16777216) + // data: 67305985 case 0x71: // numeric variable data return - case 0x86: // device automatically enters into sleep mode + { + if (this->nextion_queue_.empty()) { + ESP_LOGE(TAG, "ERROR: Received numeric return but the queue is empty"); + break; + } + + if (to_process_length == 0) { + ESP_LOGE(TAG, "ERROR: Received numeric return but no data!"); + break; + } + + int dataindex = 0; + + int value = 0; + + for (int i = 0; i < 4; ++i) { + value += to_process[i] << (8 * i); + ++dataindex; + } + + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; + + if (component->get_queue_type() != NextionQueueType::SENSOR && + component->get_queue_type() != NextionQueueType::BINARY_SENSOR && + component->get_queue_type() != NextionQueueType::SWITCH) { + ESP_LOGE(TAG, "ERROR: Received numeric return but next in queue \"%s\" is not a valid sensor type %d", + component->get_variable_name().c_str(), component->get_queue_type()); + } else { + ESP_LOGN(TAG, "Received numeric return for variable %s, queue type %d:%s, value %d", + component->get_variable_name().c_str(), component->get_queue_type(), + component->get_queue_type_string().c_str(), value); + component->set_state_from_int(value, true, false); + } + + delete nb; + this->nextion_queue_.pop_front(); + + break; + } + + case 0x86: { // device automatically enters into sleep mode + ESP_LOGVV(TAG, "Received Nextion entering sleep automatically"); + this->is_sleeping_ = true; + this->sleep_callback_.call(); + break; + } case 0x87: // device automatically wakes up + { + ESP_LOGVV(TAG, "Received Nextion leaves sleep automatically"); + this->is_sleeping_ = false; + this->wake_callback_.call(); + break; + } case 0x88: // system successful start up - case 0x89: // start SD card upgrade - case 0xFD: // data transparent transmit finished - case 0xFE: // data transparent transmit ready + { + ESP_LOGD(TAG, "system successful start up %zu", to_process_length); + this->nextion_reports_is_setup_ = true; break; + } + case 0x89: { // start SD card upgrade + break; + } + // Data from nextion is + // 0x90 - Start + // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0 + // 00 - NULL + // 00/01 - Single byte for on/off + // FF FF FF - End + case 0x90: { // Switched component + std::string variable_name; + uint8_t index = 0; + + // Get variable name + index = to_process.find('\0'); + if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + ESP_LOGE(TAG, "Bad switch component data received for 0x90 event!"); + ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); + break; + } + + variable_name = to_process.substr(0, index); + ++index; + + ESP_LOGN(TAG, "Got Switch variable_name=%s value=%d", variable_name.c_str(), to_process[0] != 0); + + for (auto *switchtype : this->switchtype_) { + switchtype->process_bool(variable_name, to_process[index] != 0); + } + break; + } + // Data from nextion is + // 0x91 - Start + // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0 + // 00 - NULL + // variable length of 0x71 return data: prints temp1.val,0 + // FF FF FF - End + case 0x91: { // Sensor component + std::string variable_name; + uint8_t index = 0; + + index = to_process.find('\0'); + if (static_cast(index) == std::string::npos || (to_process_length - index - 1) != 4) { + ESP_LOGE(TAG, "Bad sensor component data received for 0x91 event!"); + ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); + break; + } + + index = to_process.find('\0'); + variable_name = to_process.substr(0, index); + // // Get variable name + int value = 0; + for (int i = 0; i < 4; ++i) { + value += to_process[i + index + 1] << (8 * i); + } + + ESP_LOGN(TAG, "Got sensor variable_name=%s value=%d", variable_name.c_str(), value); + + for (auto *sensor : this->sensortype_) { + sensor->process_sensor(variable_name, value); + } + break; + } + + // Data from nextion is + // 0x92 - Start + // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0 + // 00 - NULL + // variable length of 0x70 return formatted data (bytes) that contain the text prints temp1.txt,0 + // 00 - NULL + // FF FF FF - End + case 0x92: { // Text Sensor Component + std::string variable_name; + std::string text_value; + uint8_t index = 0; + + // Get variable name + index = to_process.find('\0'); + if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + ESP_LOGE(TAG, "Bad text sensor component data received for 0x92 event!"); + ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); + break; + } + + variable_name = to_process.substr(0, index); + ++index; + + text_value = to_process.substr(index); + + ESP_LOGN(TAG, "Got Text Sensor variable_name=%s value=%s", variable_name.c_str(), text_value.c_str()); + + // NextionTextSensorResponseQueue *nq = new NextionTextSensorResponseQueue; + // nq->variable_name = variable_name; + // nq->state = text_value; + // this->textsensorq_.push_back(nq); + for (auto *textsensortype : this->textsensortype_) { + textsensortype->process_text(variable_name, text_value); + } + break; + } + // Data from nextion is + // 0x93 - Start + // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0 + // 00 - NULL + // 00/01 - Single byte for on/off + // FF FF FF - End + case 0x93: { // Binary Sensor component + std::string variable_name; + uint8_t index = 0; + + // Get variable name + index = to_process.find('\0'); + if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + ESP_LOGE(TAG, "Bad binary sensor component data received for 0x92 event!"); + ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); + break; + } + + variable_name = to_process.substr(0, index); + ++index; + + ESP_LOGN(TAG, "Got Binary Sensor variable_name=%s value=%d", variable_name.c_str(), to_process[index] != 0); + + for (auto *binarysensortype : this->binarysensortype_) { + binarysensortype->process_bool(&variable_name[0], to_process[index] != 0); + } + break; + } + case 0xFD: { // data transparent transmit finished + ESP_LOGVV(TAG, "Nextion reported data transmit finished!"); + break; + } + case 0xFE: { // data transparent transmit ready + ESP_LOGVV(TAG, "Nextion reported ready for transmit!"); + + int index = 0; + int found = -1; + for (auto &nb : this->nextion_queue_) { + auto component = nb->component; + if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { + size_t buffer_to_send = component->get_wave_buffer().size() < 255 ? component->get_wave_buffer().size() + : 255; // ADDT command can only send 255 + + this->write_array(component->get_wave_buffer().data(), static_cast(buffer_to_send)); + + ESP_LOGN(TAG, "Nextion sending waveform data for component id %d and waveform id %d, size %zu", + component->get_component_id(), component->get_wave_channel_id(), buffer_to_send); + + if (component->get_wave_buffer().size() <= 255) { + component->get_wave_buffer().clear(); + } else { + component->get_wave_buffer().erase(component->get_wave_buffer().begin(), + component->get_wave_buffer().begin() + buffer_to_send); + } + found = index; + delete component; + delete nb; + break; + } + ++index; + } + + if (found == -1) { + ESP_LOGE(TAG, "No waveforms in queue to send data!"); + break; + } else { + this->nextion_queue_.erase(this->nextion_queue_.begin() + found); + } + break; + } default: - ESP_LOGW(TAG, "Received unknown event from nextion: 0x%02X", event); + ESP_LOGW(TAG, "Received unknown event from nextion: 0x%02X", this->nextion_event_); break; } - if (invalid_data_length) { - ESP_LOGW(TAG, "Invalid data length from nextion!"); + + // ESP_LOGN(TAG, "nextion_event_ deleting from 0 to %d", to_process_length + COMMAND_DELIMITER.length() + 1); + this->command_data_.erase(0, to_process_length + COMMAND_DELIMITER.length() + 1); + // App.feed_wdt(); Remove before master merge + this->process_serial_(); + } + + uint32_t ms = millis(); + + if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) { + for (int i = 0; i < this->nextion_queue_.size(); i++) { + NextionComponentBase *component = this->nextion_queue_[i]->component; + if (this->nextion_queue_[i]->queue_time + this->max_q_age_ms_ < ms) { + if (this->nextion_queue_[i]->queue_time == 0) + ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\" queue_time 0", + component->get_queue_type_string().c_str(), component->get_variable_name().c_str()); + + if (component->get_variable_name() == "sleep_wake") { + this->is_sleeping_ = false; + } + + ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\"", component->get_queue_type_string().c_str(), + component->get_variable_name().c_str()); + + if (component->get_queue_type() == NextionQueueType::NO_RESULT) { + if (component->get_variable_name() == "sleep_wake") { + this->is_sleeping_ = false; + } + delete component; + } + + delete this->nextion_queue_[i]; + + this->nextion_queue_.erase(this->nextion_queue_.begin() + i); + + } else { + break; + } + } + } + ESP_LOGN(TAG, "Loop End"); + // App.feed_wdt(); Remove before master merge + this->process_serial_(); +} // namespace nextion + +void Nextion::set_nextion_sensor_state(int queue_type, const std::string &name, float state) { + this->set_nextion_sensor_state(static_cast(queue_type), name, state); +} + +void Nextion::set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state) { + ESP_LOGN(TAG, "Received state for variable %s, state %lf for queue type %d", name.c_str(), state, queue_type); + + switch (queue_type) { + case NextionQueueType::SENSOR: { + for (auto *sensor : this->sensortype_) { + if (name == sensor->get_variable_name()) { + sensor->set_state(state, true, true); + break; + } + } + break; + } + case NextionQueueType::BINARY_SENSOR: { + for (auto *sensor : this->binarysensortype_) { + if (name == sensor->get_variable_name()) { + sensor->set_state(state != 0, true, true); + break; + } + } + break; + } + case NextionQueueType::SWITCH: { + for (auto *sensor : this->switchtype_) { + if (name == sensor->get_variable_name()) { + sensor->set_state(state != 0, true, true); + break; + } + } + break; + } + default: { + ESP_LOGW(TAG, "set_nextion_sensor_state does not support a queue type %d", queue_type); + } + } +} + +void Nextion::set_nextion_text_state(const std::string &name, const std::string &state) { + ESP_LOGD(TAG, "Received state for variable %s, state %s", name.c_str(), state.c_str()); + + for (auto *sensor : this->textsensortype_) { + if (name == sensor->get_variable_name()) { + sensor->set_state(state, true, true); + break; + } + } +} + +void Nextion::all_components_send_state_(bool force_update) { + ESP_LOGD(TAG, "all_components_send_state_ "); + for (auto *binarysensortype : this->binarysensortype_) { + if (force_update || binarysensortype->get_needs_to_send_update()) + binarysensortype->send_state_to_nextion(); + } + for (auto *sensortype : this->sensortype_) { + if ((force_update || sensortype->get_needs_to_send_update()) && sensortype->get_wave_chan_id() == 0) + sensortype->send_state_to_nextion(); + } + for (auto *switchtype : this->switchtype_) { + if (force_update || switchtype->get_needs_to_send_update()) + switchtype->send_state_to_nextion(); + } + for (auto *textsensortype : this->textsensortype_) { + if (force_update || textsensortype->get_needs_to_send_update()) + textsensortype->send_state_to_nextion(); + } +} + +void Nextion::update_components_by_prefix(const std::string &prefix) { + for (auto *binarysensortype : this->binarysensortype_) { + if (binarysensortype->get_variable_name().find(prefix, 0) != std::string::npos) + binarysensortype->update_component_settings(true); + } + for (auto *sensortype : this->sensortype_) { + if (sensortype->get_variable_name().find(prefix, 0) != std::string::npos) + sensortype->update_component_settings(true); + } + for (auto *switchtype : this->switchtype_) { + if (switchtype->get_variable_name().find(prefix, 0) != std::string::npos) + switchtype->update_component_settings(true); + } + for (auto *textsensortype : this->textsensortype_) { + if (textsensortype->get_variable_name().find(prefix, 0) != std::string::npos) + textsensortype->update_component_settings(true); + } +} + +uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag) { + uint16_t ret = 0; + uint8_t c = 0; + uint8_t nr_of_ff_bytes = 0; + uint64_t start; + bool exit_flag = false; + bool ff_flag = false; + + start = millis(); + + while ((timeout == 0 && this->available()) || millis() - start <= timeout) { + this->read_byte(&c); + if (c == 0xFF) + nr_of_ff_bytes++; + else { + nr_of_ff_bytes = 0; + ff_flag = false; + } + + if (nr_of_ff_bytes >= 3) + ff_flag = true; + + response += (char) c; + if (recv_flag) { + if (response.find(0x05) != std::string::npos) { + exit_flag = true; + } + } + App.feed_wdt(); + delay(1); + + if (exit_flag || ff_flag) { + break; } } - return false; + if (ff_flag) + response = response.substr(0, response.length() - 3); // Remove last 3 0xFF + + ret = response.length(); + return ret; } -void Nextion::loop() { - while (this->available() >= 4) { - this->read_until_ack_(); + +/** + * @brief + * + * @param variable_name Name for the queue + */ +void Nextion::add_no_result_to_queue_(const std::string &variable_name) { + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; + + nextion_queue->component = new nextion::NextionComponentBase; + nextion_queue->component->set_variable_name(variable_name); + + nextion_queue->queue_time = millis(); + + this->nextion_queue_.push_back(nextion_queue); + + ESP_LOGN(TAG, "Add to queue type: NORESULT component %s", nextion_queue->component->get_variable_name().c_str()); +} + +/** + * @brief + * + * @param variable_name Variable name for the queue + * @param command + */ +void Nextion::add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command) { + if ((!this->is_setup() && !this->ignore_is_setup_) || command.empty()) + return; + + if (this->send_command_(command)) { + this->add_no_result_to_queue_(variable_name); } } -#ifdef USE_TIME -void Nextion::set_nextion_rtc_time(time::ESPTime time) { - this->send_command_printf("rtc0=%u", time.year); - this->send_command_printf("rtc1=%u", time.month); - this->send_command_printf("rtc2=%u", time.day_of_month); - this->send_command_printf("rtc3=%u", time.hour); - this->send_command_printf("rtc4=%u", time.minute); - this->send_command_printf("rtc5=%u", time.second); -} -#endif -void Nextion::set_backlight_brightness(uint8_t brightness) { this->send_command_printf("dim=%u", brightness); } -void Nextion::set_touch_sleep_timeout(uint16_t timeout) { this->send_command_printf("thsp=%u", timeout); } +bool Nextion::add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format, + ...) { + if ((!this->is_setup() && !this->ignore_is_setup_)) + return false; -void Nextion::set_writer(const nextion_writer_t &writer) { this->writer_ = writer; } -void Nextion::set_component_text_printf(const char *component, const char *format, ...) { + char buffer[256]; va_list arg; va_start(arg, format); - char buffer[256]; int ret = vsnprintf(buffer, sizeof(buffer), format, arg); va_end(arg); - if (ret > 0) - this->set_component_text(component, buffer); -} -void Nextion::set_wait_for_ack(bool wait_for_ack) { this->wait_for_ack_ = wait_for_ack; } + if (ret <= 0) { + ESP_LOGW(TAG, "Building command for format '%s' failed!", format); + return false; + } -void NextionTouchComponent::process(uint8_t page_id, uint8_t component_id, bool on) { - if (this->page_id_ == page_id && this->component_id_ == component_id) { - this->publish_state(on); + this->add_no_result_to_queue_with_command_(variable_name, buffer); + return true; +} + +/** + * @brief Sends a formatted command to the nextion + * + * @param variable_name Variable name for the queue + * @param format The printf-style command format, like "vis %s,0" + * @param ... The format arguments + */ +bool Nextion::add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format, ...) { + if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) + return false; + + char buffer[256]; + va_list arg; + va_start(arg, format); + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + va_end(arg); + if (ret <= 0) { + ESP_LOGW(TAG, "Building command for format '%s' failed!", format); + return false; + } + + this->add_no_result_to_queue_with_command_(variable_name, buffer); + return true; +} + +/** + * @brief + * + * @param variable_name Variable name for the queue + * @param variable_name_to_send Variable name for the left of the command + * @param state_value Value to set + * @param is_sleep_safe The command is safe to send when the Nextion is sleeping + */ + +void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) { + this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(), + state_value); +} + +void Nextion::add_no_result_to_queue_with_set(const std::string &variable_name, + const std::string &variable_name_to_send, int state_value) { + this->add_no_result_to_queue_with_set_internal_(variable_name, variable_name_to_send, state_value); +} + +void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &variable_name, + const std::string &variable_name_to_send, int state_value, + bool is_sleep_safe) { + if ((!this->is_setup() && !this->ignore_is_setup_) || (!is_sleep_safe && this->is_sleeping())) + return; + + this->add_no_result_to_queue_with_ignore_sleep_printf_(variable_name, "%s=%d", variable_name_to_send.c_str(), + state_value); +} + +/** + * @brief + * + * @param variable_name Variable name for the queue + * @param variable_name_to_send Variable name for the left of the command + * @param state_value Sting value to set + * @param is_sleep_safe The command is safe to send when the Nextion is sleeping + */ +void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) { + this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(), + state_value); +} +void Nextion::add_no_result_to_queue_with_set(const std::string &variable_name, + const std::string &variable_name_to_send, + const std::string &state_value) { + this->add_no_result_to_queue_with_set_internal_(variable_name, variable_name_to_send, state_value); +} + +void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &variable_name, + const std::string &variable_name_to_send, + const std::string &state_value, bool is_sleep_safe) { + if ((!this->is_setup() && !this->ignore_is_setup_) || (!is_sleep_safe && this->is_sleeping())) + return; + + this->add_no_result_to_queue_with_printf_(variable_name, "%s=\"%s\"", variable_name_to_send.c_str(), + state_value.c_str()); +} + +void Nextion::add_to_get_queue(NextionComponentBase *component) { + if ((!this->is_setup() && !this->ignore_is_setup_)) + return; + + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; + + nextion_queue->component = component; + nextion_queue->queue_time = millis(); + + ESP_LOGN(TAG, "Add to queue type: %s component %s", component->get_queue_type_string().c_str(), + component->get_variable_name().c_str()); + + std::string command = "get " + component->get_variable_name_to_send(); + + if (this->send_command_(command)) { + this->nextion_queue_.push_back(nextion_queue); } } +/** + * @brief Add addt command to the queue + * + * @param component_id The waveform component id + * @param wave_chan_id The waveform channel to send it to + * @param buffer_to_send The buffer size + * @param buffer_size The buffer data + */ +void Nextion::add_addt_command_to_queue(NextionComponentBase *component) { + if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) + return; + + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; + + nextion_queue->component = new nextion::NextionComponentBase; + nextion_queue->queue_time = millis(); + + size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() + : 255; // ADDT command can only send 255 + + std::string command = "addt " + to_string(component->get_component_id()) + "," + + to_string(component->get_wave_channel_id()) + "," + to_string(buffer_to_send); + if (this->send_command_(command)) { + this->nextion_queue_.push_back(nextion_queue); + } +} + +void Nextion::set_writer(const nextion_writer_t &writer) { this->writer_ = writer; } + +ESPDEPRECATED("set_wait_for_ack(bool) is deprecated and has no effect") +void Nextion::set_wait_for_ack(bool wait_for_ack) { ESP_LOGE(TAG, "This command is depreciated"); } + } // namespace nextion } // namespace esphome diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index a55ff747ee..2389cc6235 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -1,9 +1,21 @@ #pragma once -#include "esphome/core/component.h" +#include #include "esphome/core/defines.h" #include "esphome/components/uart/uart.h" -#include "esphome/components/binary_sensor/binary_sensor.h" +#include "nextion_base.h" +#include "nextion_component.h" +#include "esphome/components/display/display_color_utils.h" + +#if defined(USE_ETHERNET) || defined(USE_WIFI) +#ifdef ARDUINO_ARCH_ESP32 +#include +#endif +#ifdef ARDUINO_ARCH_ESP8266 +#include +#include +#endif +#endif #ifdef USE_TIME #include "esphome/components/time/real_time_clock.h" @@ -12,12 +24,14 @@ namespace esphome { namespace nextion { -class NextionTouchComponent; class Nextion; +class NextionComponentBase; using nextion_writer_t = std::function; -class Nextion : public PollingComponent, public uart::UARTDevice { +static const std::string COMMAND_DELIMITER{static_cast(255), static_cast(255), static_cast(255)}; + +class Nextion : public NextionBase, public PollingComponent, public uart::UARTDevice { public: /** * Set the text of a component to a static string. @@ -73,9 +87,20 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * This will change the image of the component `pic` to the image with ID `4`. */ - void set_component_picture(const char *component, const char *picture) { - this->send_command_printf("%s.val=%s", component, picture); - } + void set_component_picture(const char *component, const char *picture); + /** + * Set the background color of a component. + * @param component The component name. + * @param color The color (as a uint32_t). + * + * Example: + * ```cpp + * it.set_component_background_color("button", 0xFF0000); + * ``` + * + * This will change the background color of the component `button` to red. + */ + void set_component_background_color(const char *component, uint32_t color); /** * Set the background color of a component. * @param component The component name. @@ -83,7 +108,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * it.set_component_background_color("button", "17013"); + * it.set_component_background_color("button", "RED"); * ``` * * This will change the background color of the component `button` to blue. @@ -91,6 +116,33 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * Nextion HMI colors. */ void set_component_background_color(const char *component, const char *color); + /** + * Set the background color of a component. + * @param component The component name. + * @param color The color (as Color). + * + * Example: + * ```cpp + * it.set_component_background_color("button", color); + * ``` + * + * This will change the background color of the component `button` to what color contains. + */ + void set_component_background_color(const char *component, Color color) override; + /** + * Set the pressed background color of a component. + * @param component The component name. + * @param color The color (as a int). + * + * Example: + * ```cpp + * it.set_component_pressed_background_color("button", 0xFF0000 ); + * ``` + * + * This will change the pressed background color of the component `button` to red. This is the background color that + * is shown when the component is pressed. + */ + void set_component_pressed_background_color(const char *component, uint32_t color); /** * Set the pressed background color of a component. * @param component The component name. @@ -98,7 +150,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * it.set_component_pressed_background_color("button", "17013"); + * it.set_component_pressed_background_color("button", "RED"); * ``` * * This will change the pressed background color of the component `button` to blue. This is the background color that @@ -107,6 +159,63 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * colors. */ void set_component_pressed_background_color(const char *component, const char *color); + /** + * Set the pressed background color of a component. + * @param component The component name. + * @param color The color (as Color). + * + * Example: + * ```cpp + * it.set_component_pressed_background_color("button", color); + * ``` + * + * This will change the pressed background color of the component `button` to blue. This is the background color that + * is shown when the component is pressed. Use this [color + * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI + * colors. + */ + void set_component_pressed_background_color(const char *component, Color color) override; + + /** + * Set the picture id of a component. + * @param component The component name. + * @param pic_id The picture ID. + * + * Example: + * ```cpp + * it.set_component_pic("textview", 1); + * ``` + * + * This will change the picture id of the component `textview`. + */ + void set_component_pic(const char *component, uint8_t pic_id); + /** + * Set the background picture id of component. + * @param component The component name. + * @param pic_id The picture ID. + * + * Example: + * ```cpp + * it.set_component_picc("textview", 1); + * ``` + * + * This will change the background picture id of the component `textview`. + */ + void set_component_picc(const char *component, uint8_t pic_id); + + /** + * Set the font color of a component. + * @param component The component name. + * @param color The color (as a uint32_t ). + * + * Example: + * ```cpp + * it.set_component_font_color("textview", 0xFF0000); + * ``` + * + * This will change the font color of the component `textview` to a red color. + */ + void set_component_font_color(const char *component, uint32_t color); /** * Set the font color of a component. * @param component The component name. @@ -114,7 +223,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * it.set_component_font_color("textview", "17013"); + * it.set_component_font_color("textview", "RED"); * ``` * * This will change the font color of the component `textview` to a blue color. @@ -122,6 +231,34 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * Nextion HMI colors. */ void set_component_font_color(const char *component, const char *color); + /** + * Set the font color of a component. + * @param component The component name. + * @param color The color (as Color). + * + * Example: + * ```cpp + * it.set_component_font_color("textview", color); + * ``` + * + * This will change the font color of the component `textview` to a blue color. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. + */ + void set_component_font_color(const char *component, Color color) override; + /** + * Set the pressed font color of a component. + * @param component The component name. + * @param color The color (as a uint32_t). + * + * Example: + * ```cpp + * it.set_component_pressed_font_color("button", 0xFF0000); + * ``` + * + * This will change the pressed font color of the component `button` to a red. + */ + void set_component_pressed_font_color(const char *component, uint32_t color); /** * Set the pressed font color of a component. * @param component The component name. @@ -129,7 +266,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * it.set_component_pressed_font_color("button", "17013"); + * it.set_component_pressed_font_color("button", "RED"); * ``` * * This will change the pressed font color of the component `button` to a blue color. @@ -137,6 +274,21 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * Nextion HMI colors. */ void set_component_pressed_font_color(const char *component, const char *color); + /** + * Set the pressed font color of a component. + * @param component The component name. + * @param color The color (as Color). + * + * Example: + * ```cpp + * it.set_component_pressed_font_color("button", color); + * ``` + * + * This will change the pressed font color of the component `button` to a blue color. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. + */ + void set_component_pressed_font_color(const char *component, Color color) override; /** * Set the coordinates of a component on screen. * @param component The component name. @@ -163,7 +315,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Changes the font of the component named `textveiw`. Font IDs are set in the Nextion Editor. */ - void set_component_font(const char *component, uint8_t font_id); + void set_component_font(const char *component, uint8_t font_id) override; #ifdef USE_TIME /** * Send the current time to the nextion display. @@ -195,7 +347,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Hides the component named `button`. */ - void hide_component(const char *component); + void hide_component(const char *component) override; /** * Show a component. * @param component The component name. @@ -207,7 +359,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Shows the component named `button`. */ - void show_component(const char *component); + void show_component(const char *component) override; /** * Enable touch for a component. * @param component The component name. @@ -239,6 +391,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * @param value The value to write. */ void add_waveform_data(int component_id, uint8_t channel_number, uint8_t value); + void open_waveform_channel(int component_id, uint8_t channel_number, uint8_t value); /** * Display a picture at coordinates. * @param picture_id The picture id. @@ -263,7 +416,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * fill_area(50, 50, 100, 100, "17013"); + * fill_area(50, 50, 100, 100, "RED"); * ``` * * Fills an area that starts at x coordiante `50` and y coordinate `50` with a height of `100` and width of `100` with @@ -271,6 +424,24 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * convert color codes to Nextion HMI colors */ void fill_area(int x1, int y1, int width, int height, const char *color); + /** + * Fill a rectangle with a color. + * @param x1 The starting x coordinate. + * @param y1 The starting y coordinate. + * @param width The width to draw. + * @param height The height to draw. + * @param color The color to draw with (as Color). + * + * Example: + * ```cpp + * fill_area(50, 50, 100, 100, color); + * ``` + * + * Fills an area that starts at x coordiante `50` and y coordinate `50` with a height of `100` and width of `100` with + * the color of blue. Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to + * convert color codes to Nextion HMI colors + */ + void fill_area(int x1, int y1, int width, int height, Color color); /** * Draw a line on the screen. * @param x1 The starting x coordinate. @@ -290,6 +461,25 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * colors. */ void line(int x1, int y1, int x2, int y2, const char *color); + /** + * Draw a line on the screen. + * @param x1 The starting x coordinate. + * @param y1 The starting y coordinate. + * @param x2 The ending x coordinate. + * @param y2 The ending y coordinate. + * @param color The color to draw with (as Color). + * + * Example: + * ```cpp + * it.line(50, 50, 75, 75, "17013"); + * ``` + * + * Makes a line that starts at x coordinate `50` and y coordinate `50` and ends at x coordinate `75` and y coordinate + * `75` with the color of blue. Use this [color + * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI + * colors. + */ + void line(int x1, int y1, int x2, int y2, Color color); /** * Draw a rectangle outline. * @param x1 The starting x coordinate. @@ -309,6 +499,25 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * colors. */ void rectangle(int x1, int y1, int width, int height, const char *color); + /** + * Draw a rectangle outline. + * @param x1 The starting x coordinate. + * @param y1 The starting y coordinate. + * @param width The width of the rectangle. + * @param height The height of the rectangle. + * @param color The color to draw with (as Color). + * + * Example: + * ```cpp + * it.rectangle(25, 35, 40, 50, "17013"); + * ``` + * + * Makes a outline of a rectangle that starts at x coordinate `25` and y coordinate `35` and has a width of `40` and a + * length of `50` with color of blue. Use this [color + * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI + * colors. + */ + void rectangle(int x1, int y1, int width, int height, Color color); /** * Draw a circle outline * @param center_x The center x coordinate. @@ -317,6 +526,14 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * @param color The color to draw with (as a string). */ void circle(int center_x, int center_y, int radius, const char *color); + /** + * Draw a circle outline + * @param center_x The center x coordinate. + * @param center_y The center y coordinate. + * @param radius The circle radius. + * @param color The color to draw with (as Color). + */ + void circle(int center_x, int center_y, int radius, Color color); /** * Draw a filled circled. * @param center_x The center x coordinate. @@ -334,19 +551,36 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * Nextion HMI colors. */ void filled_circle(int center_x, int center_y, int radius, const char *color); - - /** Set the brightness of the backlight. - * - * @param brightness The brightness, from 0 to 100. + /** + * Draw a filled circled. + * @param center_x The center x coordinate. + * @param center_y The center y coordinate. + * @param radius The circle radius. + * @param color The color to draw with (as Color). * * Example: * ```cpp - * it.set_backlight_brightness(30); + * it.filled_cricle(25, 25, 10, color); + * ``` + * + * Makes a filled circle at the x cordinates `25` and y coordinate `25` with a radius of `10` with a color of blue. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. + */ + void filled_circle(int center_x, int center_y, int radius, Color color); + + /** Set the brightness of the backlight. + * + * @param brightness The brightness percentage from 0 to 1.0. + * + * Example: + * ```cpp + * it.set_backlight_brightness(.3); * ``` * * Changes the brightness of the display to 30%. */ - void set_backlight_brightness(uint8_t brightness); + void set_backlight_brightness(float brightness); /** * Set the touch sleep timeout of the display. * @param timeout Timeout in seconds. @@ -360,10 +594,46 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * `thup`. */ void set_touch_sleep_timeout(uint16_t timeout); + /** + * Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode. + * @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to + * wakes up to current page. + * + * Example: + * ```cpp + * it.set_wake_up_page(2); + * ``` + * + * The display will wake up to page 2. + */ + void set_wake_up_page(uint8_t page_id = 255); + /** + * Sets if Nextion should auto-wake from sleep when touch press occurs. + * @param auto_wake True or false. When auto_wake is true and Nextion is in sleep mode, + * the first touch will only trigger the auto wake mode and not trigger a Touch Event. + * + * Example: + * ```cpp + * it.set_auto_wake_on_touch(true); + * ``` + * + * The display will wake up by touch. + */ + void set_auto_wake_on_touch(bool auto_wake); + /** + * Sets Nextion mode between sleep and awake + * @param True or false. Sleep=true to enter sleep mode or sleep=false to exit sleep mode. + */ + void sleep(bool sleep); // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) - void register_touch_component(NextionTouchComponent *obj) { this->touch_.push_back(obj); } + void register_touch_component(NextionComponentBase *obj) { this->touch_.push_back(obj); } + void register_switch_component(NextionComponentBase *obj) { this->switchtype_.push_back(obj); } + void register_binarysensor_component(NextionComponentBase *obj) { this->binarysensortype_.push_back(obj); } + void register_sensor_component(NextionComponentBase *obj) { this->sensortype_.push_back(obj); } + void register_textsensor_component(NextionComponentBase *obj) { this->textsensortype_.push_back(obj); } + void setup() override; void set_brightness(float brightness) { this->brightness_ = brightness; } float get_setup_priority() const override; @@ -371,11 +641,9 @@ class Nextion : public PollingComponent, public uart::UARTDevice { void loop() override; void set_writer(const nextion_writer_t &writer); - /** - * Manually send a raw command to the display and don't wait for an acknowledgement packet. - * @param command The command to write, for example "vis b0,0". - */ - void send_command_no_ack(const char *command); + // This function has been deprecated + void set_wait_for_ack(bool wait_for_ack); + /** * Manually send a raw formatted command to the display. * @param format The printf-style command format, like "vis %s,0" @@ -384,28 +652,199 @@ class Nextion : public PollingComponent, public uart::UARTDevice { */ bool send_command_printf(const char *format, ...) __attribute__((format(printf, 2, 3))); - void set_wait_for_ack(bool wait_for_ack); +#ifdef USE_TFT_UPLOAD + /** + * Set the tft file URL. https seems problamtic with arduino.. + */ + void set_tft_url(const std::string &tft_url) { this->tft_url_ = tft_url; } + +#endif + + /** + * Upload the tft file and softreset the Nextion + */ + void upload_tft(); + void dump_config() override; + + /** + * Softreset the Nextion + */ + void soft_reset(); + + /** Add a callback to be notified of sleep state changes. + * + * @param callback The void() callback. + */ + void add_sleep_state_callback(std::function &&callback); + + /** Add a callback to be notified of wake state changes. + * + * @param callback The void() callback. + */ + void add_wake_state_callback(std::function &&callback); + + /** Add a callback to be notified when the nextion completes its initialize setup. + * + * @param callback The void() callback. + */ + void add_setup_state_callback(std::function &&callback); + + void update_all_components(); + + /** + * @brief Set the nextion sensor state object. + * + * @param[in] queue_type + * Index of NextionQueueType. + * + * @param[in] name + * Component/variable name. + * + * @param[in] state + * State to set. + */ + void set_nextion_sensor_state(int queue_type, const std::string &name, float state); + void set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state); + void set_nextion_text_state(const std::string &name, const std::string &state); + + void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) override; + void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, + int state_value) override; + + void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) override; + void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, + const std::string &state_value) override; + + void add_to_get_queue(NextionComponentBase *component) override; + + void add_addt_command_to_queue(NextionComponentBase *component) override; + + void update_components_by_prefix(const std::string &prefix); + + void set_touch_sleep_timeout_internal(uint32_t touch_sleep_timeout) { + this->touch_sleep_timeout_ = touch_sleep_timeout; + } + void set_wake_up_page_internal(uint8_t wake_up_page) { this->wake_up_page_ = wake_up_page; } + void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; } protected: - bool ack_(); - bool read_until_ack_(); + std::deque nextion_queue_; + uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag); + void all_components_send_state_(bool force_update = false); + uint64_t comok_sent_ = 0; + bool remove_from_q_(bool report_empty = true); + /** + * @brief + * Sends commands ignoring of the Nextion has been setup. + */ + bool ignore_is_setup_ = false; + bool nextion_reports_is_setup_ = false; + uint8_t nextion_event_; + + void process_nextion_commands_(); + void process_serial_(); + bool is_updating_ = false; + uint32_t touch_sleep_timeout_ = 0; + int wake_up_page_ = -1; + bool auto_wake_on_touch_ = true; + + /** + * Manually send a raw command to the display and don't wait for an acknowledgement packet. + * @param command The command to write, for example "vis b0,0". + */ + bool send_command_(const std::string &command); + void add_no_result_to_queue_(const std::string &variable_name); + bool add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format, ...) + __attribute__((format(printf, 3, 4))); + void add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command); + + bool add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format, ...) + __attribute__((format(printf, 3, 4))); + + void add_no_result_to_queue_with_set_internal_(const std::string &variable_name, + const std::string &variable_name_to_send, int state_value, + bool is_sleep_safe = false); + + void add_no_result_to_queue_with_set_internal_(const std::string &variable_name, + const std::string &variable_name_to_send, + const std::string &state_value, bool is_sleep_safe = false); + +#ifdef USE_TFT_UPLOAD +#if defined(USE_ETHERNET) || defined(USE_WIFI) +#ifdef ARDUINO_ARCH_ESP8266 + WiFiClient *wifi_client_{nullptr}; + BearSSL::WiFiClientSecure *wifi_client_secure_{nullptr}; + WiFiClient *get_wifi_client_(); +#endif + + /** + * will request chunk_size chunks from the web server + * and send each to the nextion + * @param int contentLength Total size of the file + * @param uint32_t chunk_size + * @return true if success, false for failure. + */ + int content_length_ = 0; + int tft_size_ = 0; + int upload_by_chunks_(HTTPClient *http, int range_start); + + bool upload_with_range_(uint32_t range_start, uint32_t range_end); + + /** + * start update tft file to nextion. + * + * @param const uint8_t *file_buf + * @param size_t buf_size + * @return true if success, false for failure. + */ + bool upload_from_buffer_(const uint8_t *file_buf, size_t buf_size); + void upload_end_(); + +#endif + +#endif + + bool get_is_connected_() { return this->is_connected_; } + + bool check_connect_(); + + std::vector touch_; + std::vector switchtype_; + std::vector sensortype_; + std::vector textsensortype_; + std::vector binarysensortype_; + CallbackManager setup_callback_{}; + CallbackManager sleep_callback_{}; + CallbackManager wake_callback_{}; - std::vector touch_; optional writer_; - bool wait_for_ack_{true}; float brightness_{1.0}; + + std::string device_model_; + std::string firmware_version_; + std::string serial_number_; + std::string flash_size_; + + void remove_front_no_sensors_(); + +#ifdef USE_TFT_UPLOAD + std::string tft_url_; + uint8_t *transfer_buffer_{nullptr}; + size_t transfer_buffer_size_; + bool upload_first_chunk_sent_ = false; +#endif + +#ifdef NEXTION_PROTOCOL_LOG + void print_queue_members_(); +#endif + void reset_(bool reset_nextion = true); + + std::string command_data_; + bool is_connected_ = false; + uint32_t startup_override_ms_ = 8000; + uint32_t max_q_age_ms_ = 8000; + uint32_t started_ms_ = 0; + bool sent_setup_commands_ = false; }; - -class NextionTouchComponent : public binary_sensor::BinarySensorInitiallyOff { - public: - void set_page_id(uint8_t page_id) { page_id_ = page_id; } - void set_component_id(uint8_t component_id) { component_id_ = component_id; } - void process(uint8_t page_id, uint8_t component_id, bool on); - - protected: - uint8_t page_id_; - uint8_t component_id_; -}; - } // namespace nextion } // namespace esphome diff --git a/esphome/components/nextion/nextion_base.h b/esphome/components/nextion/nextion_base.h new file mode 100644 index 0000000000..a24fd74060 --- /dev/null +++ b/esphome/components/nextion/nextion_base.h @@ -0,0 +1,58 @@ +#pragma once +#include "esphome/core/defines.h" +#include "esphome/core/color.h" +#include "nextion_component_base.h" +namespace esphome { +namespace nextion { + +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE +#define NEXTION_PROTOCOL_LOG +#endif + +#ifdef NEXTION_PROTOCOL_LOG +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE +#define ESP_LOGN(tag, ...) ESP_LOGVV(tag, __VA_ARGS__) +#else +#define ESP_LOGN(tag, ...) ESP_LOGD(tag, __VA_ARGS__) +#endif +#else +#define ESP_LOGN(tag, ...) \ + {} +#endif + +class NextionBase; + +class NextionBase { + public: + virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) = 0; + virtual void add_no_result_to_queue_with_set(const std::string &variable_name, + const std::string &variable_name_to_send, int state_value) = 0; + + virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) = 0; + virtual void add_no_result_to_queue_with_set(const std::string &variable_name, + const std::string &variable_name_to_send, + const std::string &state_value) = 0; + + virtual void add_addt_command_to_queue(NextionComponentBase *component) = 0; + + virtual void add_to_get_queue(NextionComponentBase *component) = 0; + + virtual void set_component_background_color(const char *component, Color color) = 0; + virtual void set_component_pressed_background_color(const char *component, Color color) = 0; + virtual void set_component_font_color(const char *component, Color color) = 0; + virtual void set_component_pressed_font_color(const char *component, Color color) = 0; + virtual void set_component_font(const char *component, uint8_t font_id) = 0; + + virtual void show_component(const char *component) = 0; + virtual void hide_component(const char *component) = 0; + + bool is_sleeping() { return this->is_sleeping_; } + bool is_setup() { return this->is_setup_; } + + protected: + bool is_setup_ = false; + bool is_sleeping_ = false; +}; + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_commands.cpp b/esphome/components/nextion/nextion_commands.cpp new file mode 100644 index 0000000000..931b934ba2 --- /dev/null +++ b/esphome/components/nextion/nextion_commands.cpp @@ -0,0 +1,234 @@ +#include "nextion.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { +static const char *const TAG = "nextion"; + +// Sleep safe commands +void Nextion::soft_reset() { this->send_command_("rest"); } + +void Nextion::set_wake_up_page(uint8_t page_id) { + if (page_id > 255) { + ESP_LOGD(TAG, "Wake up page of bounds, range 0-255"); + return; + } + this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", page_id, true); +} + +void Nextion::set_touch_sleep_timeout(uint16_t timeout) { + if (timeout < 3 || timeout > 65535) { + ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535"); + return; + } + + this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", timeout, true); +} + +void Nextion::sleep(bool sleep) { + if (sleep) { // Set sleep + this->is_sleeping_ = true; + this->add_no_result_to_queue_with_set_internal_("sleep", "sleep", 1, true); + } else { // Turn off sleep. Wait for a sleep_wake return before setting sleep off + this->add_no_result_to_queue_with_set_internal_("sleep_wake", "sleep", 0, true); + } +} +// End sleep safe commands + +// Set Colors +void Nextion::set_component_background_color(const char *component, uint32_t color) { + this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%d", component, color); +} + +void Nextion::set_component_background_color(const char *component, const char *color) { + this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%s", component, color); +} + +void Nextion::set_component_background_color(const char *component, Color color) { + this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%d", component, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::set_component_pressed_background_color(const char *component, uint32_t color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%d", component, color); +} + +void Nextion::set_component_pressed_background_color(const char *component, const char *color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%s", component, color); +} + +void Nextion::set_component_pressed_background_color(const char *component, Color color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%d", component, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::set_component_pic(const char *component, uint8_t pic_id) { + this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.pic=%d", component, pic_id); +} + +void Nextion::set_component_picc(const char *component, uint8_t pic_id) { + this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.picc=%d", component, pic_id); +} + +void Nextion::set_component_font_color(const char *component, uint32_t color) { + this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%d", component, color); +} + +void Nextion::set_component_font_color(const char *component, const char *color) { + this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%s", component, color); +} + +void Nextion::set_component_font_color(const char *component, Color color) { + this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%d", component, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::set_component_pressed_font_color(const char *component, uint32_t color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%d", component, color); +} + +void Nextion::set_component_pressed_font_color(const char *component, const char *color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", " %s.pco2=%s", component, color); +} + +void Nextion::set_component_pressed_font_color(const char *component, Color color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%d", component, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::set_component_text_printf(const char *component, const char *format, ...) { + va_list arg; + va_start(arg, format); + char buffer[256]; + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + va_end(arg); + if (ret > 0) + this->set_component_text(component, buffer); +} + +// General Nextion +void Nextion::goto_page(const char *page) { this->add_no_result_to_queue_with_printf_("goto_page", "page %s", page); } + +void Nextion::set_backlight_brightness(float brightness) { + if (brightness < 0 || brightness > 1.0) { + ESP_LOGD(TAG, "Brightness out of bounds, percentage range 0-1.0"); + return; + } + this->add_no_result_to_queue_with_set("backlight_brightness", "dim", static_cast(brightness * 100)); +} + +void Nextion::set_auto_wake_on_touch(bool auto_wake) { + this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake ? 1 : 0); +} + +// General Component +void Nextion::set_component_font(const char *component, uint8_t font_id) { + this->add_no_result_to_queue_with_printf_("set_component_font", "%s.font=%d", component, font_id); +} + +void Nextion::hide_component(const char *component) { + this->add_no_result_to_queue_with_printf_("hide_component", "vis %s,0", component); +} + +void Nextion::show_component(const char *component) { + this->add_no_result_to_queue_with_printf_("show_component", "vis %s,1", component); +} + +void Nextion::enable_component_touch(const char *component) { + this->add_no_result_to_queue_with_printf_("enable_component_touch", "tsw %s,1", component); +} + +void Nextion::disable_component_touch(const char *component) { + this->add_no_result_to_queue_with_printf_("disable_component_touch", "tsw %s,0", component); +} + +void Nextion::set_component_picture(const char *component, const char *picture) { + this->add_no_result_to_queue_with_printf_("set_component_picture", "%s.val=%s", component, picture); +} + +void Nextion::set_component_text(const char *component, const char *text) { + this->add_no_result_to_queue_with_printf_("set_component_text", "%s.txt=\"%s\"", component, text); +} + +void Nextion::set_component_value(const char *component, int value) { + this->add_no_result_to_queue_with_printf_("set_component_value", "%s.val=%d", component, value); +} + +void Nextion::add_waveform_data(int component_id, uint8_t channel_number, uint8_t value) { + this->add_no_result_to_queue_with_printf_("add_waveform_data", "add %d,%u,%u", component_id, channel_number, value); +} + +void Nextion::open_waveform_channel(int component_id, uint8_t channel_number, uint8_t value) { + this->add_no_result_to_queue_with_printf_("open_waveform_channel", "addt %d,%u,%u", component_id, channel_number, + value); +} + +void Nextion::set_component_coordinates(const char *component, int x, int y) { + this->add_no_result_to_queue_with_printf_("set_component_coordinates command 1", "%s.xcen=%d", component, x); + this->add_no_result_to_queue_with_printf_("set_component_coordinates command 2", "%s.ycen=%d", component, y); +} + +// Drawing +void Nextion::display_picture(int picture_id, int x_start, int y_start) { + this->add_no_result_to_queue_with_printf_("display_picture", "pic %d %d %d", x_start, y_start, picture_id); +} + +void Nextion::fill_area(int x1, int y1, int width, int height, const char *color) { + this->add_no_result_to_queue_with_printf_("fill_area", "fill %d,%d,%d,%d,%s", x1, y1, width, height, color); +} + +void Nextion::fill_area(int x1, int y1, int width, int height, Color color) { + this->add_no_result_to_queue_with_printf_("fill_area", "fill %d,%d,%d,%d,%d", x1, y1, width, height, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::line(int x1, int y1, int x2, int y2, const char *color) { + this->add_no_result_to_queue_with_printf_("line", "line %d,%d,%d,%d,%s", x1, y1, x2, y2, color); +} + +void Nextion::line(int x1, int y1, int x2, int y2, Color color) { + this->add_no_result_to_queue_with_printf_("line", "line %d,%d,%d,%d,%d", x1, y1, x2, y2, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::rectangle(int x1, int y1, int width, int height, const char *color) { + this->add_no_result_to_queue_with_printf_("draw", "draw %d,%d,%d,%d,%s", x1, y1, x1 + width, y1 + height, color); +} + +void Nextion::rectangle(int x1, int y1, int width, int height, Color color) { + this->add_no_result_to_queue_with_printf_("draw", "draw %d,%d,%d,%d,%d", x1, y1, x1 + width, y1 + height, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::circle(int center_x, int center_y, int radius, const char *color) { + this->add_no_result_to_queue_with_printf_("cir", "cir %d,%d,%d,%s", center_x, center_y, radius, color); +} + +void Nextion::circle(int center_x, int center_y, int radius, Color color) { + this->add_no_result_to_queue_with_printf_("cir", "cir %d,%d,%d,%d", center_x, center_y, radius, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::filled_circle(int center_x, int center_y, int radius, const char *color) { + this->add_no_result_to_queue_with_printf_("cirs", "cirs %d,%d,%d,%s", center_x, center_y, radius, color); +} + +void Nextion::filled_circle(int center_x, int center_y, int radius, Color color) { + this->add_no_result_to_queue_with_printf_("cirs", "cirs %d,%d,%d,%d", center_x, center_y, radius, + display::ColorUtil::color_to_565(color)); +} + +#ifdef USE_TIME +void Nextion::set_nextion_rtc_time(time::ESPTime time) { + this->add_no_result_to_queue_with_printf_("rtc0", "rtc0=%u", time.year); + this->add_no_result_to_queue_with_printf_("rtc1", "rtc1=%u", time.month); + this->add_no_result_to_queue_with_printf_("rtc2", "rtc2=%u", time.day_of_month); + this->add_no_result_to_queue_with_printf_("rtc3", "rtc3=%u", time.hour); + this->add_no_result_to_queue_with_printf_("rtc4", "rtc4=%u", time.minute); + this->add_no_result_to_queue_with_printf_("rtc5", "rtc5=%u", time.second); +} +#endif + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_component.cpp b/esphome/components/nextion/nextion_component.cpp new file mode 100644 index 0000000000..bbb2cf6cb2 --- /dev/null +++ b/esphome/components/nextion/nextion_component.cpp @@ -0,0 +1,116 @@ +#include "nextion_component.h" + +namespace esphome { +namespace nextion { + +void NextionComponent::set_background_color(Color bco) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->bco_ = bco; + this->bco_needs_update_ = true; + this->bco_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_background_pressed_color(Color bco2) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + + this->bco2_ = bco2; + this->bco2_needs_update_ = true; + this->bco2_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_foreground_color(Color pco) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->pco_ = pco; + this->pco_needs_update_ = true; + this->pco_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_foreground_pressed_color(Color pco2) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->pco2_ = pco2; + this->pco2_needs_update_ = true; + this->pco2_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_font_id(uint8_t font_id) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->font_id_ = font_id; + this->font_id_needs_update_ = true; + this->font_id_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_visible(bool visible) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->visible_ = visible; + this->visible_needs_update_ = true; + this->visible_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::update_component_settings(bool force_update) { + if (this->nextion_->is_sleeping() || !this->nextion_->is_setup() || !this->visible_is_set_ || + (!this->visible_needs_update_ && !this->visible_)) { + this->needs_to_send_update_ = true; + return; + } + + if (this->visible_needs_update_ || (force_update && this->visible_is_set_)) { + std::string name_to_send = this->variable_name_; + + size_t pos = name_to_send.find_last_of('.'); + if (pos != std::string::npos) { + name_to_send = name_to_send.substr(pos + 1); + } + + this->visible_needs_update_ = false; + + if (this->visible_) { + this->nextion_->show_component(name_to_send.c_str()); + this->send_state_to_nextion(); + } else { + this->nextion_->hide_component(name_to_send.c_str()); + return; + } + } + + if (this->bco_needs_update_ || (force_update && this->bco2_is_set_)) { + this->nextion_->set_component_background_color(this->variable_name_.c_str(), this->bco_); + this->bco_needs_update_ = false; + } + if (this->bco2_needs_update_ || (force_update && this->bco2_is_set_)) { + this->nextion_->set_component_pressed_background_color(this->variable_name_.c_str(), this->bco2_); + this->bco2_needs_update_ = false; + } + if (this->pco_needs_update_ || (force_update && this->pco_is_set_)) { + this->nextion_->set_component_font_color(this->variable_name_.c_str(), this->pco_); + this->pco_needs_update_ = false; + } + if (this->pco2_needs_update_ || (force_update && this->pco2_is_set_)) { + this->nextion_->set_component_pressed_font_color(this->variable_name_.c_str(), this->pco2_); + this->pco2_needs_update_ = false; + } + + if (this->font_id_needs_update_ || (force_update && this->font_id_is_set_)) { + this->nextion_->set_component_font(this->variable_name_.c_str(), this->font_id_); + this->font_id_needs_update_ = false; + } +} +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_component.h b/esphome/components/nextion/nextion_component.h new file mode 100644 index 0000000000..2f3c4f3c16 --- /dev/null +++ b/esphome/components/nextion/nextion_component.h @@ -0,0 +1,49 @@ +#pragma once +#include "esphome/core/defines.h" +#include "esphome/core/color.h" +#include "nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionComponent; + +class NextionComponent : public NextionComponentBase { + public: + void update_component_settings() override { this->update_component_settings(false); }; + + void update_component_settings(bool force_update) override; + + void set_background_color(Color bco); + void set_background_pressed_color(Color bco2); + void set_foreground_color(Color pco); + void set_foreground_pressed_color(Color pco2); + void set_font_id(uint8_t font_id); + void set_visible(bool visible); + + protected: + NextionBase *nextion_; + + bool bco_needs_update_ = false; + bool bco_is_set_ = false; + Color bco_; + bool bco2_needs_update_ = false; + bool bco2_is_set_ = false; + Color bco2_; + bool pco_needs_update_ = false; + bool pco_is_set_ = false; + Color pco_; + bool pco2_needs_update_ = false; + bool pco2_is_set_ = false; + Color pco2_; + uint8_t font_id_ = 0; + bool font_id_needs_update_ = false; + bool font_id_is_set_ = false; + + bool visible_ = true; + bool visible_needs_update_ = false; + bool visible_is_set_ = false; + + // void send_state_to_nextion() = 0; +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_component_base.h b/esphome/components/nextion/nextion_component_base.h new file mode 100644 index 0000000000..71ad803bc4 --- /dev/null +++ b/esphome/components/nextion/nextion_component_base.h @@ -0,0 +1,95 @@ +#pragma once +#include +#include "esphome/core/defines.h" + +namespace esphome { +namespace nextion { + +enum NextionQueueType { + NO_RESULT = 0, + SENSOR = 1, + BINARY_SENSOR = 2, + SWITCH = 3, + TEXT_SENSOR = 4, + WAVEFORM_SENSOR = 5, +}; + +static const char *const NEXTION_QUEUE_TYPE_STRINGS[] = {"NO_RESULT", "SENSOR", "BINARY_SENSOR", + "SWITCH", "TEXT_SENSOR", "WAVEFORM_SENSOR"}; + +class NextionComponentBase; + +class NextionQueue { + public: + virtual ~NextionQueue() = default; + NextionComponentBase *component; + uint32_t queue_time = 0; +}; + +class NextionComponentBase { + public: + virtual ~NextionComponentBase() = default; + + void set_variable_name(const std::string &variable_name, const std::string &variable_name_to_send = "") { + variable_name_ = variable_name; + if (variable_name_to_send.empty()) { + variable_name_to_send_ = variable_name; + } else { + variable_name_to_send_ = variable_name_to_send; + } + } + + virtual void update_component_settings(){}; + virtual void update_component_settings(bool force_update){}; + + virtual void update_component(){}; + virtual void process_sensor(const std::string &variable_name, int state){}; + virtual void process_touch(uint8_t page_id, uint8_t component_id, bool on){}; + virtual void process_text(const std::string &variable_name, const std::string &text_value){}; + virtual void process_bool(const std::string &variable_name, bool on){}; + + virtual void set_state(float state){}; + virtual void set_state(float state, bool publish){}; + virtual void set_state(float state, bool publish, bool send_to_nextion){}; + + virtual void set_state(bool state){}; + virtual void set_state(bool state, bool publish){}; + virtual void set_state(bool state, bool publish, bool send_to_nextion){}; + + virtual void set_state(const std::string &state) {} + virtual void set_state(const std::string &state, bool publish) {} + virtual void set_state(const std::string &state, bool publish, bool send_to_nextion){}; + + uint8_t get_component_id() { return this->component_id_; } + void set_component_id(uint8_t component_id) { component_id_ = component_id; } + + uint8_t get_wave_channel_id() { return this->wave_chan_id_; } + void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; } + + std::vector get_wave_buffer() { return this->wave_buffer_; } + size_t get_wave_buffer_size() { return this->wave_buffer_.size(); } + + std::string get_variable_name() { return this->variable_name_; } + std::string get_variable_name_to_send() { return this->variable_name_to_send_; } + virtual NextionQueueType get_queue_type() { return NextionQueueType::NO_RESULT; } + virtual std::string get_queue_type_string() { return NEXTION_QUEUE_TYPE_STRINGS[this->get_queue_type()]; } + virtual void set_state_from_int(int state_value, bool publish, bool send_to_nextion){}; + virtual void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion){}; + virtual void send_state_to_nextion(){}; + bool get_needs_to_send_update() { return this->needs_to_send_update_; } + uint8_t get_wave_chan_id() { return this->wave_chan_id_; } + void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; } + + protected: + std::string variable_name_; + std::string variable_name_to_send_; + + uint8_t component_id_ = 0; + uint8_t wave_chan_id_ = UINT8_MAX; + std::vector wave_buffer_; + int wave_max_length_ = 255; + + bool needs_to_send_update_; +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp new file mode 100644 index 0000000000..6a681af6c5 --- /dev/null +++ b/esphome/components/nextion/nextion_upload.cpp @@ -0,0 +1,343 @@ + +#include "nextion.h" +#include "esphome/core/application.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { +static const char *const TAG = "nextion_upload"; + +#if defined(USE_TFT_UPLOAD) && (defined(USE_ETHERNET) || defined(USE_WIFI)) + +// Followed guide +// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2 + +int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { + int range_end = 0; + + if (range_start == 0 && this->transfer_buffer_size_ > 16384) { // Start small at the first run in case of a big skip + range_end = 16384 - 1; + } else { + range_end = range_start + this->transfer_buffer_size_ - 1; + } + + if (range_end > this->tft_size_) + range_end = this->tft_size_; + + bool begin_status = false; +#ifdef ARDUINO_ARCH_ESP32 + begin_status = http->begin(this->tft_url_.c_str()); +#endif +#ifdef ARDUINO_ARCH_ESP8266 +#ifndef CLANG_TIDY + http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http->setRedirectLimit(3); + begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str()); +#endif +#endif + + char range_header[64]; + sprintf(range_header, "bytes=%d-%d", range_start, range_end); + + ESP_LOGD(TAG, "Requesting range: %s", range_header); + + int tries = 1; + int code = 0; + while (tries <= 5) { +#ifdef ARDUINO_ARCH_ESP32 + begin_status = http->begin(this->tft_url_.c_str()); +#endif +#ifndef CLANG_TIDY +#ifdef ARDUINO_ARCH_ESP8266 + begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str()); +#endif +#endif + + ++tries; + if (!begin_status) { + ESP_LOGD(TAG, "upload_by_chunks_: connection failed"); + continue; + } + + http->addHeader("Range", range_header); + + code = http->GET(); + if (code == 200 || code == 206) { + break; + } + ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retries(%d/5)", this->tft_url_.c_str(), + HTTPClient::errorToString(code).c_str(), tries); + http->end(); + App.feed_wdt(); + delay(500); // NOLINT + } + + if (tries > 5) { + return -1; + } + + std::string recv_string; + size_t size = 0; + int sent = 0; + int range = range_end - range_start; + + while (sent < range) { + size = http->getStreamPtr()->available(); + if (!size) { + App.feed_wdt(); + delay(0); + continue; + } + int c = http->getStreamPtr()->readBytes( + &this->transfer_buffer_[sent], ((size > this->transfer_buffer_size_) ? this->transfer_buffer_size_ : size)); + sent += c; + } + http->end(); + ESP_LOGN(TAG, "this->content_length_ %d sent %d", this->content_length_, sent); + for (uint32_t i = 0; i < range; i += 4096) { + this->write_array(&this->transfer_buffer_[i], 4096); + this->content_length_ -= 4096; + ESP_LOGN(TAG, "this->content_length_ %d range %d range_end %d range_start %d", this->content_length_, range, + range_end, range_start); + + if (!this->upload_first_chunk_sent_) { + this->upload_first_chunk_sent_ = true; + delay(500); // NOLINT + App.feed_wdt(); + } + + this->recv_ret_string_(recv_string, 2048, true); + if (recv_string[0] == 0x08) { + uint32_t result = 0; + for (int i = 0; i < 4; ++i) { + result += static_cast(recv_string[i + 1]) << (8 * i); + } + if (result > 0) { + ESP_LOGD(TAG, "Nextion reported new range %d", result); + this->content_length_ = this->tft_size_ - result; + return result; + } + } + recv_string.clear(); + } + return range_end + 1; +} + +void Nextion::upload_tft() { + if (this->is_updating_) { + ESP_LOGD(TAG, "Currently updating"); + return; + } + + if (!network_is_connected()) { + ESP_LOGD(TAG, "network is not connected"); + return; + } + + this->is_updating_ = true; + + HTTPClient http; + http.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along + bool begin_status = false; +#ifdef ARDUINO_ARCH_ESP32 + begin_status = http.begin(this->tft_url_.c_str()); +#endif +#ifdef ARDUINO_ARCH_ESP8266 +#ifndef CLANG_TIDY + http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http.setRedirectLimit(3); + begin_status = http.begin(*this->get_wifi_client_(), this->tft_url_.c_str()); +#endif +#endif + + if (!begin_status) { + this->is_updating_ = false; + ESP_LOGD(TAG, "connection failed"); +#ifdef ARDUINO_ARCH_ESP32 + if (psramFound()) + free(this->transfer_buffer_); + else +#endif + delete this->transfer_buffer_; + return; + } else { + ESP_LOGD(TAG, "Connected"); + } + + http.addHeader("Range", "bytes=0-255"); + const char *header_names[] = {"Content-Range"}; + http.collectHeaders(header_names, 1); + ESP_LOGD(TAG, "Requesting URL: %s", this->tft_url_.c_str()); + + http.setReuse(true); + // try up to 5 times. DNS sometimes needs a second try or so + int tries = 1; + int code = http.GET(); + delay(100); // NOLINT + + App.feed_wdt(); + while (code != 200 && code != 206 && tries <= 5) { + ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retrying (%d/5)", this->tft_url_.c_str(), + HTTPClient::errorToString(code).c_str(), tries); + + delay(250); // NOLINT + App.feed_wdt(); + code = http.GET(); + ++tries; + } + + if ((code != 200 && code != 206) || tries > 5) { + this->upload_end_(); + } + + String content_range_string = http.header("Content-Range"); + content_range_string.remove(0, 12); + this->content_length_ = content_range_string.toInt(); + this->tft_size_ = content_length_; + http.end(); + + if (this->content_length_ < 4096) { + ESP_LOGE(TAG, "Failed to get file size"); + this->upload_end_(); + } + + ESP_LOGD(TAG, "Updating Nextion %s...", this->device_model_.c_str()); + // The Nextion will ignore the update command if it is sleeping + + this->send_command_("sleep=0"); + this->set_backlight_brightness(1.0); + delay(250); // NOLINT + + App.feed_wdt(); + + char command[128]; + // Tells the Nextion the content length of the tft file and baud rate it will be sent at + // Once the Nextion accepts the command it will wait until the file is successfully uploaded + // If it fails for any reason a power cycle of the display will be needed + sprintf(command, "whmi-wris %d,%d,1", this->content_length_, this->parent_->get_baud_rate()); + + // Clear serial receive buffer + uint8_t d; + while (this->available()) { + this->read_byte(&d); + }; + + this->send_command_(command); + + App.feed_wdt(); + + std::string response; + ESP_LOGD(TAG, "Waiting for upgrade response"); + this->recv_ret_string_(response, 2000, true); // This can take some time to return + + // The Nextion display will, if it's ready to accept data, send a 0x05 byte. + ESP_LOGD(TAG, "Upgrade response is %s %zu", response.c_str(), response.length()); + + for (int i = 0; i < response.length(); i++) { + ESP_LOGD(TAG, "Available %d : 0x%02X", i, response[i]); + } + + if (response.find(0x05) != std::string::npos) { + ESP_LOGD(TAG, "preparation for tft update done"); + } else { + ESP_LOGD(TAG, "preparation for tft update failed %d \"%s\"", response[0], response.c_str()); + this->upload_end_(); + } + + // Nextion wants 4096 bytes at a time. Make chunk_size a multiple of 4096 +#ifdef ARDUINO_ARCH_ESP32 + uint32_t chunk_size = 8192; + if (psramFound()) { + chunk_size = this->content_length_; + } else { + if (ESP.getFreeHeap() > 40960) { // 32K to keep on hand + int chunk = int((ESP.getFreeHeap() - 32768) / 4096); + chunk_size = chunk * 4096; + chunk_size = chunk_size > 65536 ? 65536 : chunk_size; + } else if (ESP.getFreeHeap() < 10240) { + chunk_size = 4096; + } + } +#else + uint32_t chunk_size = ESP.getFreeHeap() < 10240 ? 4096 : 8192; +#endif + + if (this->transfer_buffer_ == nullptr) { +#ifdef ARDUINO_ARCH_ESP32 + if (psramFound()) { + ESP_LOGD(TAG, "Allocating PSRAM buffer size %d, Free PSRAM size is %u", chunk_size, ESP.getFreePsram()); + this->transfer_buffer_ = (uint8_t *) ps_malloc(chunk_size); + if (this->transfer_buffer_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate buffer size %d!", chunk_size); + this->upload_end_(); + } + } else { +#endif + ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap()); + this->transfer_buffer_ = new uint8_t[chunk_size]; + if (!this->transfer_buffer_) { // Try a smaller size + ESP_LOGD(TAG, "Could not allocate buffer size: %d trying 4096 instead", chunk_size); + chunk_size = 4096; + ESP_LOGD(TAG, "Allocating %d buffer", chunk_size); + this->transfer_buffer_ = new uint8_t[chunk_size]; + + if (!this->transfer_buffer_) + this->upload_end_(); +#ifdef ARDUINO_ARCH_ESP32 + } +#endif + } + + this->transfer_buffer_size_ = chunk_size; + } + + ESP_LOGD(TAG, "Updating tft from \"%s\" with a file size of %d using %zu chunksize, Heap Size %d", + this->tft_url_.c_str(), this->content_length_, this->transfer_buffer_size_, ESP.getFreeHeap()); + + int result = 0; + while (this->content_length_ > 0) { + result = this->upload_by_chunks_(&http, result); + if (result < 0) { + ESP_LOGD(TAG, "Error updating Nextion!"); + this->upload_end_(); + } + App.feed_wdt(); + ESP_LOGD(TAG, "Heap Size %d, Bytes left %d", ESP.getFreeHeap(), this->content_length_); + } + ESP_LOGD(TAG, "Succesfully updated Nextion!"); + + this->upload_end_(); +} + +void Nextion::upload_end_() { + ESP_LOGD(TAG, "Restarting Nextion"); + this->soft_reset(); + delay(1500); // NOLINT + ESP_LOGD(TAG, "Restarting esphome"); + ESP.restart(); +} + +#ifdef ARDUINO_ARCH_ESP8266 +WiFiClient *Nextion::get_wifi_client_() { + if (this->tft_url_.compare(0, 6, "https:") == 0) { + if (this->wifi_client_secure_ == nullptr) { + this->wifi_client_secure_ = new BearSSL::WiFiClientSecure(); + this->wifi_client_secure_->setInsecure(); + this->wifi_client_secure_->setBufferSizes(512, 512); + } + return this->wifi_client_secure_; + } + + if (this->wifi_client_ == nullptr) { + this->wifi_client_ = new WiFiClient(); + } + return this->wifi_client_; +} +#endif + +#else +void Nextion::upload_tft() { ESP_LOGW(TAG, "tft_url, WIFI or Ethernet components are needed. Cannot upload."); } +#endif +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/sensor/__init__.py b/esphome/components/nextion/sensor/__init__.py new file mode 100644 index 0000000000..f8a383e3ac --- /dev/null +++ b/esphome/components/nextion/sensor/__init__.py @@ -0,0 +1,99 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor + +from esphome.const import ( + CONF_ID, + UNIT_EMPTY, + ICON_EMPTY, + CONF_COMPONENT_ID, + DEVICE_CLASS_EMPTY, +) +from .. import nextion_ns, CONF_NEXTION_ID + +from ..base_component import ( + setup_component_core_, + CONFIG_SENSOR_COMPONENT_SCHEMA, + CONF_VARIABLE_NAME, + CONF_COMPONENT_NAME, + CONF_PRECISION, + CONF_WAVE_CHANNEL_ID, + CONF_WAVE_MAX_VALUE, + CONF_WAVEFORM_SEND_LAST_VALUE, + CONF_WAVE_MAX_LENGTH, +) + + +CODEOWNERS = ["@senexcrenshaw"] + +NextionSensor = nextion_ns.class_("NextionSensor", sensor.Sensor, cg.PollingComponent) + + +def CheckWaveID(value): + value = cv.int_(value) + if value < 0 or value > 3: + raise cv.Invalid(f"Valid range for {CONF_WAVE_CHANNEL_ID} is 0-3") + return value + + +def _validate(config): + if CONF_WAVE_CHANNEL_ID in config and CONF_COMPONENT_ID not in config: + raise cv.Invalid( + f"{CONF_COMPONENT_ID} is required when {CONF_WAVE_CHANNEL_ID} is set" + ) + + return config + + +CONFIG_SCHEMA = cv.All( + sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 2, DEVICE_CLASS_EMPTY) + .extend( + { + cv.GenerateID(): cv.declare_id(NextionSensor), + cv.Optional(CONF_PRECISION, default=0): cv.int_range(min=0, max=8), + cv.Optional(CONF_WAVE_CHANNEL_ID): CheckWaveID, + cv.Optional(CONF_COMPONENT_ID): cv.uint8_t, + cv.Optional(CONF_WAVE_MAX_LENGTH, default=255): cv.int_range( + min=1, max=1024 + ), + cv.Optional(CONF_WAVE_MAX_VALUE, default=100): cv.int_range( + min=1, max=1024 + ), + cv.Optional(CONF_WAVEFORM_SEND_LAST_VALUE, default=True): cv.boolean, + } + ) + .extend(CONFIG_SENSOR_COMPONENT_SCHEMA) + .extend(cv.polling_component_schema("never")), + cv.has_exactly_one_key(CONF_COMPONENT_ID, CONF_COMPONENT_NAME, CONF_VARIABLE_NAME), + _validate, +) + + +async def to_code(config): + + hub = await cg.get_variable(config[CONF_NEXTION_ID]) + var = cg.new_Pvariable(config[CONF_ID], hub) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + + cg.add(hub.register_sensor_component(var)) + + await setup_component_core_(var, config, ".val") + + if CONF_PRECISION in config: + cg.add(var.set_precision(config[CONF_PRECISION])) + + if CONF_COMPONENT_ID in config: + cg.add(var.set_component_id(config[CONF_COMPONENT_ID])) + + if CONF_WAVE_CHANNEL_ID in config: + cg.add(var.set_wave_channel_id(config[CONF_WAVE_CHANNEL_ID])) + + if CONF_WAVEFORM_SEND_LAST_VALUE in config: + cg.add(var.set_waveform_send_last_value(config[CONF_WAVEFORM_SEND_LAST_VALUE])) + + if CONF_WAVE_MAX_VALUE in config: + cg.add(var.set_wave_max_value(config[CONF_WAVE_MAX_VALUE])) + + if CONF_WAVE_MAX_LENGTH in config: + cg.add(var.set_wave_max_length(config[CONF_WAVE_MAX_LENGTH])) diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp new file mode 100644 index 0000000000..bbcb465d85 --- /dev/null +++ b/esphome/components/nextion/sensor/nextion_sensor.cpp @@ -0,0 +1,110 @@ +#include "nextion_sensor.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { + +static const char *const TAG = "nextion_sensor"; + +void NextionSensor::process_sensor(const std::string &variable_name, int state) { + if (!this->nextion_->is_setup()) + return; + + if (this->wave_chan_id_ == UINT8_MAX && this->variable_name_ == variable_name) { + this->publish_state(state); + ESP_LOGD(TAG, "Processed sensor \"%s\" state %d", variable_name.c_str(), state); + } +} + +void NextionSensor::add_to_wave_buffer(float state) { + this->needs_to_send_update_ = true; + + int wave_state = (int) ((state / (float) this->wave_maxvalue_) * 100); + + wave_buffer_.push_back(wave_state); + + if (this->wave_buffer_.size() > this->wave_max_length_) { + this->wave_buffer_.erase(this->wave_buffer_.begin()); + } +} + +void NextionSensor::update() { + if (!this->nextion_->is_setup()) + return; + + if (this->wave_chan_id_ == UINT8_MAX) { + this->nextion_->add_to_get_queue(this); + } else { + if (this->send_last_value_) { + this->add_to_wave_buffer(this->last_value_); + } + + this->wave_update_(); + } +} + +void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) { + if (!this->nextion_->is_setup()) + return; + + if (isnan(state)) + return; + + if (this->wave_chan_id_ == UINT8_MAX) { + if (send_to_nextion) { + if (this->nextion_->is_sleeping() || !this->visible_) { + this->needs_to_send_update_ = true; + } else { + this->needs_to_send_update_ = false; + + if (this->precision_ > 0) { + double to_multiply = pow(10, this->precision_); + int state_value = (int) (state * to_multiply); + + this->nextion_->add_no_result_to_queue_with_set(this, (int) state_value); + } else { + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + } + } + } + } else { + if (this->send_last_value_) { + this->last_value_ = state; // Update will handle setting the buffer + } else { + this->add_to_wave_buffer(state); + } + } + + if (this->wave_chan_id_ == UINT8_MAX) { + if (publish) { + this->publish_state(state); + } else { + this->raw_state = state; + this->state = state; + this->has_state_ = true; + } + } + this->update_component_settings(); + + ESP_LOGN(TAG, "Wrote state for sensor \"%s\" state %lf", this->variable_name_.c_str(), state); +} + +void NextionSensor::wave_update_() { + if (this->nextion_->is_sleeping() || this->wave_buffer_.empty()) { + return; + } + +#ifdef NEXTION_PROTOCOL_LOG + size_t buffer_to_send = + this->wave_buffer_.size() < 255 ? this->wave_buffer_.size() : 255; // ADDT command can only send 255 + + ESP_LOGN(TAG, "wave_update send %zu of %zu value(s) to wave nextion component id %d and wave channel id %d", + buffer_to_send, this->wave_buffer_.size(), this->component_id_, this->wave_chan_id_); +#endif + + this->nextion_->add_addt_command_to_queue(this); +} + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/sensor/nextion_sensor.h b/esphome/components/nextion/sensor/nextion_sensor.h new file mode 100644 index 0000000000..e4dde9a513 --- /dev/null +++ b/esphome/components/nextion/sensor/nextion_sensor.h @@ -0,0 +1,49 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "../nextion_component.h" +#include "../nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionSensor; + +class NextionSensor : public NextionComponent, public sensor::Sensor, public PollingComponent { + public: + NextionSensor(NextionBase *nextion) { this->nextion_ = nextion; } + void send_state_to_nextion() override { this->set_state(this->state, false, true); }; + + void update_component() override { this->update(); } + void update() override; + void add_to_wave_buffer(float state); + void set_precision(uint8_t precision) { this->precision_ = precision; } + void set_component_id(uint8_t component_id) { component_id_ = component_id; } + void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; } + void set_wave_max_value(uint32_t wave_maxvalue) { this->wave_maxvalue_ = wave_maxvalue; } + void process_sensor(const std::string &variable_name, int state) override; + + void set_state(float state) override { this->set_state(state, true, true); } + void set_state(float state, bool publish) override { this->set_state(state, publish, true); } + void set_state(float state, bool publish, bool send_to_nextion) override; + + void set_waveform_send_last_value(bool send_last_value) { this->send_last_value_ = send_last_value; } + uint8_t get_wave_chan_id() { return this->wave_chan_id_; } + void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; } + NextionQueueType get_queue_type() override { + return this->wave_chan_id_ == UINT8_MAX ? NextionQueueType::SENSOR : NextionQueueType::WAVEFORM_SENSOR; + } + void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {} + void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override { + this->set_state(state_value, publish, send_to_nextion); + } + + protected: + uint8_t precision_ = 0; + uint32_t wave_maxvalue_ = 255; + + float last_value_ = 0; + bool send_last_value_ = true; + void wave_update_(); +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/switch/__init__.py b/esphome/components/nextion/switch/__init__.py new file mode 100644 index 0000000000..068681fa14 --- /dev/null +++ b/esphome/components/nextion/switch/__init__.py @@ -0,0 +1,39 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch + +from esphome.const import CONF_ID +from .. import nextion_ns, CONF_NEXTION_ID + +from ..base_component import ( + setup_component_core_, + CONF_COMPONENT_NAME, + CONF_VARIABLE_NAME, + CONFIG_SWITCH_COMPONENT_SCHEMA, +) + +CODEOWNERS = ["@senexcrenshaw"] + +NextionSwitch = nextion_ns.class_("NextionSwitch", switch.Switch, cg.PollingComponent) + +CONFIG_SCHEMA = cv.All( + switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(NextionSwitch), + } + ) + .extend(CONFIG_SWITCH_COMPONENT_SCHEMA) + .extend(cv.polling_component_schema("never")), + cv.has_exactly_one_key(CONF_COMPONENT_NAME, CONF_VARIABLE_NAME), +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_NEXTION_ID]) + var = cg.new_Pvariable(config[CONF_ID], hub) + await cg.register_component(var, config) + await switch.register_switch(var, config) + + cg.add(hub.register_switch_component(var)) + + await setup_component_core_(var, config, ".val") diff --git a/esphome/components/nextion/switch/nextion_switch.cpp b/esphome/components/nextion/switch/nextion_switch.cpp new file mode 100644 index 0000000000..1f32ad3425 --- /dev/null +++ b/esphome/components/nextion/switch/nextion_switch.cpp @@ -0,0 +1,52 @@ +#include "nextion_switch.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { + +static const char *const TAG = "nextion_switch"; + +void NextionSwitch::process_bool(const std::string &variable_name, bool on) { + if (!this->nextion_->is_setup()) + return; + if (this->variable_name_ == variable_name) { + this->publish_state(on); + + ESP_LOGD(TAG, "Processed switch \"%s\" state %s", variable_name.c_str(), state ? "ON" : "OFF"); + } +} + +void NextionSwitch::update() { + if (!this->nextion_->is_setup()) + return; + this->nextion_->add_to_get_queue(this); +} + +void NextionSwitch::set_state(bool state, bool publish, bool send_to_nextion) { + if (!this->nextion_->is_setup()) + return; + + if (send_to_nextion) { + if (this->nextion_->is_sleeping() || !this->visible_) { + this->needs_to_send_update_ = true; + } else { + this->needs_to_send_update_ = false; + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + } + } + if (publish) { + this->publish_state(state); + } else { + this->state = state; + } + + this->update_component_settings(); + + ESP_LOGN(TAG, "Updated switch \"%s\" state %s", this->variable_name_.c_str(), ONOFF(state)); +} + +void NextionSwitch::write_state(bool state) { this->set_state(state); } + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/switch/nextion_switch.h b/esphome/components/nextion/switch/nextion_switch.h new file mode 100644 index 0000000000..1548287473 --- /dev/null +++ b/esphome/components/nextion/switch/nextion_switch.h @@ -0,0 +1,34 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" +#include "../nextion_component.h" +#include "../nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionSwitch; + +class NextionSwitch : public NextionComponent, public switch_::Switch, public PollingComponent { + public: + NextionSwitch(NextionBase *nextion) { this->nextion_ = nextion; } + + void update() override; + void update_component() override { this->update(); } + void process_bool(const std::string &variable_name, bool on) override; + + void set_state(bool state) override { this->set_state(state, true, true); } + void set_state(bool state, bool publish) override { this->set_state(state, publish, true); } + void set_state(bool state, bool publish, bool send_to_nextion) override; + + void send_state_to_nextion() override { this->set_state(this->state, false, true); }; + NextionQueueType get_queue_type() override { return NextionQueueType::SWITCH; } + void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {} + void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override { + this->set_state(state_value != 0, publish, send_to_nextion); + } + + protected: + void write_state(bool state) override; +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/text_sensor/__init__.py b/esphome/components/nextion/text_sensor/__init__.py new file mode 100644 index 0000000000..9c170dd807 --- /dev/null +++ b/esphome/components/nextion/text_sensor/__init__.py @@ -0,0 +1,38 @@ +from esphome.components import text_sensor +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import CONF_ID + +from .. import nextion_ns, CONF_NEXTION_ID + +from ..base_component import ( + setup_component_core_, + CONFIG_TEXT_COMPONENT_SCHEMA, +) + +CODEOWNERS = ["@senexcrenshaw"] + +NextionTextSensor = nextion_ns.class_( + "NextionTextSensor", text_sensor.TextSensor, cg.PollingComponent +) + +CONFIG_SCHEMA = ( + text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(NextionTextSensor), + } + ) + .extend(CONFIG_TEXT_COMPONENT_SCHEMA) + .extend(cv.polling_component_schema("never")) +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_NEXTION_ID]) + var = cg.new_Pvariable(config[CONF_ID], hub) + await cg.register_component(var, config) + await text_sensor.register_text_sensor(var, config) + + cg.add(hub.register_textsensor_component(var)) + + await setup_component_core_(var, config, ".txt") diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.cpp b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp new file mode 100644 index 0000000000..08f032df74 --- /dev/null +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp @@ -0,0 +1,49 @@ +#include "nextion_textsensor.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { +static const char *const TAG = "nextion_textsensor"; + +void NextionTextSensor::process_text(const std::string &variable_name, const std::string &text_value) { + if (!this->nextion_->is_setup()) + return; + if (this->variable_name_ == variable_name) { + this->publish_state(text_value); + ESP_LOGD(TAG, "Processed text_sensor \"%s\" state \"%s\"", variable_name.c_str(), text_value.c_str()); + } +} + +void NextionTextSensor::update() { + if (!this->nextion_->is_setup()) + return; + this->nextion_->add_to_get_queue(this); +} + +void NextionTextSensor::set_state(const std::string &state, bool publish, bool send_to_nextion) { + if (!this->nextion_->is_setup()) + return; + + if (send_to_nextion) { + if (this->nextion_->is_sleeping() || !this->visible_) { + this->needs_to_send_update_ = true; + } else { + this->nextion_->add_no_result_to_queue_with_set(this, state); + } + } + + if (publish) { + this->publish_state(state); + } else { + this->state = state; + this->has_state_ = true; + } + + this->update_component_settings(); + + ESP_LOGN(TAG, "Wrote state for text_sensor \"%s\" state \"%s\"", this->variable_name_.c_str(), state.c_str()); +} + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.h b/esphome/components/nextion/text_sensor/nextion_textsensor.h new file mode 100644 index 0000000000..5716d0a008 --- /dev/null +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.h @@ -0,0 +1,32 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "../nextion_component.h" +#include "../nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionTextSensor; + +class NextionTextSensor : public NextionComponent, public text_sensor::TextSensor, public PollingComponent { + public: + NextionTextSensor(NextionBase *nextion) { this->nextion_ = nextion; } + void update() override; + void update_component() override { this->update(); } + void on_state_changed(const std::string &state); + + void process_text(const std::string &variable_name, const std::string &text_value) override; + + void set_state(const std::string &state, bool publish) override { this->set_state(state, publish, true); } + void set_state(const std::string &state) override { this->set_state(state, true, true); } + void set_state(const std::string &state, bool publish, bool send_to_nextion) override; + + void send_state_to_nextion() override { this->set_state(this->state, false, true); }; + NextionQueueType get_queue_type() override { return NextionQueueType::TEXT_SENSOR; } + void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override {} + void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override { + this->set_state(state_value, publish, send_to_nextion); + } +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index 6dd62070da..cd54290c73 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -56,6 +56,7 @@ class ESP8266SoftwareSerial { class UARTComponent : public Component, public Stream { public: void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; } + uint32_t get_baud_rate() const { return baud_rate_; } uint32_t get_config(); diff --git a/script/ci-custom.py b/script/ci-custom.py index 02f193c6e0..d79e5b5e2f 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +from helpers import git_ls_files, filter_changed import codecs import collections import fnmatch @@ -12,7 +13,6 @@ import functools import argparse sys.path.append(os.path.dirname(__file__)) -from helpers import git_ls_files, filter_changed def find_all(a_str, sub): @@ -562,6 +562,7 @@ def lint_inclusive_language(fname, match): "esphome/components/number/number.h", "esphome/components/output/binary_output.h", "esphome/components/output/float_output.h", + "esphome/components/nextion/nextion_base.h", "esphome/components/sensor/sensor.h", "esphome/components/stepper/stepper.h", "esphome/components/switch/switch.h", diff --git a/tests/test1.yaml b/tests/test1.yaml index 1c522a23d4..f9fe7d106d 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1055,10 +1055,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 @@ -1882,11 +1878,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 diff --git a/tests/test3.yaml b/tests/test3.yaml index c709539309..acd975d794 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -269,6 +269,7 @@ wled: adalight: + sensor: - platform: apds9960 type: proximity @@ -534,6 +535,15 @@ sensor: 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 @@ -605,7 +615,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 @@ -653,6 +670,11 @@ 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 @@ -704,6 +726,10 @@ switch: switches: - id: custom_switch name: Custom Switch + - platform: nextion + id: r0 + name: 'R0 Switch' + component_name: page0.r0 custom_component: lambda: |- @@ -1086,6 +1112,16 @@ 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 From 4dbf1c521ecb6a70dec86401f998686be83bcd52 Mon Sep 17 00:00:00 2001 From: SenexCrenshaw <35600301+SenexCrenshaw@users.noreply.github.com> Date: Wed, 14 Jul 2021 20:51:15 -0400 Subject: [PATCH 1013/1841] Nextion upload and sensors (#1464) Co-authored-by: Senex Crenshaw --- CODEOWNERS | 5 + esphome/components/nextion/__init__.py | 5 + esphome/components/nextion/automation.h | 30 + esphome/components/nextion/base_component.py | 126 ++ esphome/components/nextion/binary_sensor.py | 34 - .../nextion/binary_sensor/__init__.py | 54 + .../binary_sensor/nextion_binarysensor.cpp | 69 + .../binary_sensor/nextion_binarysensor.h | 42 + esphome/components/nextion/display.py | 75 +- esphome/components/nextion/nextion.cpp | 1143 ++++++++++++++--- esphome/components/nextion/nextion.h | 527 +++++++- esphome/components/nextion/nextion_base.h | 58 + .../components/nextion/nextion_commands.cpp | 234 ++++ .../components/nextion/nextion_component.cpp | 116 ++ .../components/nextion/nextion_component.h | 49 + .../nextion/nextion_component_base.h | 95 ++ esphome/components/nextion/nextion_upload.cpp | 343 +++++ esphome/components/nextion/sensor/__init__.py | 99 ++ .../nextion/sensor/nextion_sensor.cpp | 110 ++ .../nextion/sensor/nextion_sensor.h | 49 + esphome/components/nextion/switch/__init__.py | 39 + .../nextion/switch/nextion_switch.cpp | 52 + .../nextion/switch/nextion_switch.h | 34 + .../nextion/text_sensor/__init__.py | 38 + .../text_sensor/nextion_textsensor.cpp | 49 + .../nextion/text_sensor/nextion_textsensor.h | 32 + esphome/components/uart/uart.h | 1 + script/ci-custom.py | 3 +- tests/test1.yaml | 9 - tests/test3.yaml | 38 +- 30 files changed, 3295 insertions(+), 263 deletions(-) create mode 100644 esphome/components/nextion/automation.h create mode 100644 esphome/components/nextion/base_component.py delete mode 100644 esphome/components/nextion/binary_sensor.py create mode 100644 esphome/components/nextion/binary_sensor/__init__.py create mode 100644 esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp create mode 100644 esphome/components/nextion/binary_sensor/nextion_binarysensor.h create mode 100644 esphome/components/nextion/nextion_base.h create mode 100644 esphome/components/nextion/nextion_commands.cpp create mode 100644 esphome/components/nextion/nextion_component.cpp create mode 100644 esphome/components/nextion/nextion_component.h create mode 100644 esphome/components/nextion/nextion_component_base.h create mode 100644 esphome/components/nextion/nextion_upload.cpp create mode 100644 esphome/components/nextion/sensor/__init__.py create mode 100644 esphome/components/nextion/sensor/nextion_sensor.cpp create mode 100644 esphome/components/nextion/sensor/nextion_sensor.h create mode 100644 esphome/components/nextion/switch/__init__.py create mode 100644 esphome/components/nextion/switch/nextion_switch.cpp create mode 100644 esphome/components/nextion/switch/nextion_switch.h create mode 100644 esphome/components/nextion/text_sensor/__init__.py create mode 100644 esphome/components/nextion/text_sensor/nextion_textsensor.cpp create mode 100644 esphome/components/nextion/text_sensor/nextion_textsensor.h diff --git a/CODEOWNERS b/CODEOWNERS index d6769800cd..557fe7cc08 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -73,6 +73,11 @@ esphome/components/midea_ac/* @dudanov esphome/components/midea_dongle/* @dudanov esphome/components/mitsubishi/* @RubyBailey esphome/components/network/* @esphome/core +esphome/components/nextion/* @senexcrenshaw +esphome/components/nextion/binary_sensor/* @senexcrenshaw +esphome/components/nextion/sensor/* @senexcrenshaw +esphome/components/nextion/switch/* @senexcrenshaw +esphome/components/nextion/text_sensor/* @senexcrenshaw esphome/components/nfc/* @jesserockz esphome/components/number/* @esphome/core esphome/components/ota/* @esphome/core diff --git a/esphome/components/nextion/__init__.py b/esphome/components/nextion/__init__.py index 67a49df9fa..924d58198d 100644 --- a/esphome/components/nextion/__init__.py +++ b/esphome/components/nextion/__init__.py @@ -1,3 +1,8 @@ import esphome.codegen as cg +from esphome.components import uart nextion_ns = cg.esphome_ns.namespace("nextion") +Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice) +nextion_ref = Nextion.operator("ref") + +CONF_NEXTION_ID = "nextion_id" diff --git a/esphome/components/nextion/automation.h b/esphome/components/nextion/automation.h new file mode 100644 index 0000000000..5f4219acb1 --- /dev/null +++ b/esphome/components/nextion/automation.h @@ -0,0 +1,30 @@ +#pragma once +#include "esphome/core/automation.h" +#include "nextion.h" + +namespace esphome { +namespace nextion { + +class SetupTrigger : public Trigger<> { + public: + explicit SetupTrigger(Nextion *nextion) { + nextion->add_setup_state_callback([this]() { this->trigger(); }); + } +}; + +class SleepTrigger : public Trigger<> { + public: + explicit SleepTrigger(Nextion *nextion) { + nextion->add_sleep_state_callback([this]() { this->trigger(); }); + } +}; + +class WakeTrigger : public Trigger<> { + public: + explicit WakeTrigger(Nextion *nextion) { + nextion->add_wake_state_callback([this]() { this->trigger(); }); + } +}; + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/base_component.py b/esphome/components/nextion/base_component.py new file mode 100644 index 0000000000..3bf828aafa --- /dev/null +++ b/esphome/components/nextion/base_component.py @@ -0,0 +1,126 @@ +from string import ascii_letters, digits +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.components import color + +from . import CONF_NEXTION_ID +from . import Nextion + +CONF_VARIABLE_NAME = "variable_name" +CONF_COMPONENT_NAME = "component_name" +CONF_WAVE_CHANNEL_ID = "wave_channel_id" +CONF_WAVE_MAX_VALUE = "wave_max_value" +CONF_PRECISION = "precision" +CONF_WAVEFORM_SEND_LAST_VALUE = "waveform_send_last_value" +CONF_TFT_URL = "tft_url" +CONF_ON_SLEEP = "on_sleep" +CONF_ON_WAKE = "on_wake" +CONF_ON_SETUP = "on_setup" +CONF_TOUCH_SLEEP_TIMEOUT = "touch_sleep_timeout" +CONF_WAKE_UP_PAGE = "wake_up_page" +CONF_AUTO_WAKE_ON_TOUCH = "auto_wake_on_touch" +CONF_WAVE_MAX_LENGTH = "wave_max_length" +CONF_BACKGROUND_COLOR = "background_color" +CONF_BACKGROUND_PRESSED_COLOR = "background_pressed_color" +CONF_FOREGROUND_COLOR = "foreground_color" +CONF_FOREGROUND_PRESSED_COLOR = "foreground_pressed_color" +CONF_FONT_ID = "font_id" +CONF_VISIBLE = "visible" + + +def NextionName(value): + valid_chars = ascii_letters + digits + "." + if not isinstance(value, str) or len(value) > 29: + raise cv.Invalid("Must be a string less than 29 characters") + + for char in value: + if char not in valid_chars: + raise cv.Invalid( + "Must only consist of upper/lowercase characters, numbers and the period '.'. The character '{}' cannot be used.".format( + char + ) + ) + + return value + + +CONFIG_BASE_COMPONENT_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_NEXTION_ID): cv.use_id(Nextion), + cv.Optional(CONF_BACKGROUND_COLOR): cv.use_id(color), + cv.Optional(CONF_FOREGROUND_COLOR): cv.use_id(color), + cv.Optional(CONF_VISIBLE, default=True): cv.boolean, + } +) + + +CONFIG_TEXT_COMPONENT_SCHEMA = CONFIG_BASE_COMPONENT_SCHEMA.extend( + cv.Schema( + { + cv.Required(CONF_COMPONENT_NAME): NextionName, + cv.Optional(CONF_FONT_ID): cv.int_range(min=0, max=255), + } + ) +) + +CONFIG_BINARY_SENSOR_SCHEMA = CONFIG_BASE_COMPONENT_SCHEMA.extend( + cv.Schema( + { + cv.Optional(CONF_COMPONENT_NAME): NextionName, + cv.Optional(CONF_VARIABLE_NAME): NextionName, + } + ) +) + +CONFIG_SENSOR_COMPONENT_SCHEMA = CONFIG_BINARY_SENSOR_SCHEMA.extend( + cv.Schema( + { + cv.Optional(CONF_FONT_ID): cv.int_range(min=0, max=255), + } + ) +) + + +CONFIG_SWITCH_COMPONENT_SCHEMA = CONFIG_SENSOR_COMPONENT_SCHEMA.extend( + cv.Schema( + { + cv.Optional(CONF_FOREGROUND_PRESSED_COLOR): cv.use_id(color), + cv.Optional(CONF_BACKGROUND_PRESSED_COLOR): cv.use_id(color), + } + ) +) + + +async def setup_component_core_(var, config, arg): + + if CONF_VARIABLE_NAME in config: + cg.add(var.set_variable_name(config[CONF_VARIABLE_NAME])) + elif CONF_COMPONENT_NAME in config: + cg.add( + var.set_variable_name( + config[CONF_COMPONENT_NAME], + config[CONF_COMPONENT_NAME] + arg, + ) + ) + + if CONF_BACKGROUND_COLOR in config: + color_component = await cg.get_variable(config[CONF_BACKGROUND_COLOR]) + cg.add(var.set_background_color(color_component)) + + if CONF_BACKGROUND_PRESSED_COLOR in config: + color_component = await cg.get_variable(config[CONF_BACKGROUND_PRESSED_COLOR]) + cg.add(var.set_background_pressed_color(color_component)) + + if CONF_FOREGROUND_COLOR in config: + color_component = await cg.get_variable(config[CONF_FOREGROUND_COLOR]) + cg.add(var.set_foreground_color(color_component)) + + if CONF_FOREGROUND_PRESSED_COLOR in config: + color_component = await cg.get_variable(config[CONF_FOREGROUND_PRESSED_COLOR]) + cg.add(var.set_foreground_pressed_color(color_component)) + + if CONF_FONT_ID in config: + cg.add(var.set_font_id(config[CONF_FONT_ID])) + + if CONF_VISIBLE in config: + cg.add(var.set_visible(config[CONF_VISIBLE])) diff --git a/esphome/components/nextion/binary_sensor.py b/esphome/components/nextion/binary_sensor.py deleted file mode 100644 index ed4e8d832a..0000000000 --- a/esphome/components/nextion/binary_sensor.py +++ /dev/null @@ -1,34 +0,0 @@ -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components import binary_sensor -from esphome.const import CONF_COMPONENT_ID, CONF_PAGE_ID, CONF_ID -from . import nextion_ns -from .display import Nextion - -DEPENDENCIES = ["display"] - -CONF_NEXTION_ID = "nextion_id" - -NextionTouchComponent = nextion_ns.class_( - "NextionTouchComponent", binary_sensor.BinarySensor -) - -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(NextionTouchComponent), - cv.GenerateID(CONF_NEXTION_ID): cv.use_id(Nextion), - cv.Required(CONF_PAGE_ID): cv.uint8_t, - cv.Required(CONF_COMPONENT_ID): cv.uint8_t, - } -) - - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) - - hub = await cg.get_variable(config[CONF_NEXTION_ID]) - cg.add(hub.register_touch_component(var)) - - cg.add(var.set_component_id(config[CONF_COMPONENT_ID])) - cg.add(var.set_page_id(config[CONF_PAGE_ID])) diff --git a/esphome/components/nextion/binary_sensor/__init__.py b/esphome/components/nextion/binary_sensor/__init__.py new file mode 100644 index 0000000000..090fae3429 --- /dev/null +++ b/esphome/components/nextion/binary_sensor/__init__.py @@ -0,0 +1,54 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor + +from esphome.const import CONF_COMPONENT_ID, CONF_PAGE_ID, CONF_ID +from .. import nextion_ns, CONF_NEXTION_ID + + +from ..base_component import ( + setup_component_core_, + CONFIG_BINARY_SENSOR_SCHEMA, + CONF_VARIABLE_NAME, + CONF_COMPONENT_NAME, +) + +CODEOWNERS = ["@senexcrenshaw"] + +NextionBinarySensor = nextion_ns.class_( + "NextionBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent +) + +CONFIG_SCHEMA = cv.All( + binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(NextionBinarySensor), + cv.Optional(CONF_PAGE_ID): cv.uint8_t, + cv.Optional(CONF_COMPONENT_ID): cv.uint8_t, + } + ) + .extend(CONFIG_BINARY_SENSOR_SCHEMA) + .extend(cv.polling_component_schema("never")), + cv.has_at_least_one_key( + CONF_PAGE_ID, + CONF_COMPONENT_ID, + CONF_COMPONENT_NAME, + CONF_VARIABLE_NAME, + ), +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_NEXTION_ID]) + var = cg.new_Pvariable(config[CONF_ID], hub) + await binary_sensor.register_binary_sensor(var, config) + await cg.register_component(var, config) + + if config.keys() >= {CONF_PAGE_ID, CONF_COMPONENT_ID}: + cg.add(hub.register_touch_component(var)) + cg.add(var.set_component_id(config[CONF_COMPONENT_ID])) + cg.add(var.set_page_id(config[CONF_PAGE_ID])) + + if CONF_COMPONENT_NAME in config or CONF_VARIABLE_NAME in config: + await setup_component_core_(var, config, ".val") + cg.add(hub.register_binarysensor_component(var)) diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp new file mode 100644 index 0000000000..bf6e74cb38 --- /dev/null +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp @@ -0,0 +1,69 @@ +#include "nextion_binarysensor.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { + +static const char *const TAG = "nextion_binarysensor"; + +void NextionBinarySensor::process_bool(const std::string &variable_name, bool state) { + if (!this->nextion_->is_setup()) + return; + + if (this->variable_name_.empty()) // This is a touch component + return; + + if (this->variable_name_ == variable_name) { + this->publish_state(state); + ESP_LOGD(TAG, "Processed binarysensor \"%s\" state %s", variable_name.c_str(), state ? "ON" : "OFF"); + } +} + +void NextionBinarySensor::process_touch(uint8_t page_id, uint8_t component_id, bool state) { + if (this->page_id_ == page_id && this->component_id_ == component_id) { + this->publish_state(state); + } +} + +void NextionBinarySensor::update() { + if (!this->nextion_->is_setup()) + return; + + if (this->variable_name_.empty()) // This is a touch component + return; + + this->nextion_->add_to_get_queue(this); +} + +void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nextion) { + if (!this->nextion_->is_setup()) + return; + + if (this->component_id_ == 0) // This is a legacy touch component + return; + + if (send_to_nextion) { + if (this->nextion_->is_sleeping() || !this->visible_) { + this->needs_to_send_update_ = true; + } else { + this->needs_to_send_update_ = false; + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + } + } + + if (publish) { + this->publish_state(state); + } else { + this->state = state; + this->has_state_ = true; + } + + this->update_component_settings(); + + ESP_LOGN(TAG, "Wrote state for sensor \"%s\" state %s", this->variable_name_.c_str(), + ONOFF(this->variable_name_.c_str())); +} + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.h b/esphome/components/nextion/binary_sensor/nextion_binarysensor.h new file mode 100644 index 0000000000..b6b23ada85 --- /dev/null +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.h @@ -0,0 +1,42 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "../nextion_component.h" +#include "../nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionBinarySensor; + +class NextionBinarySensor : public NextionComponent, + public binary_sensor::BinarySensorInitiallyOff, + public PollingComponent { + public: + NextionBinarySensor(NextionBase *nextion) { this->nextion_ = nextion; } + + void update_component() override { this->update(); } + void update() override; + void send_state_to_nextion() override { this->set_state(this->state, false); }; + void process_bool(const std::string &variable_name, bool state) override; + void process_touch(uint8_t page_id, uint8_t component_id, bool state) override; + + // Set the components page id for Nextion Touch Component + void set_page_id(uint8_t page_id) { page_id_ = page_id; } + // Set the components component id for Nextion Touch Component + void set_component_id(uint8_t component_id) { component_id_ = component_id; } + + void set_state(bool state) override { this->set_state(state, true, true); } + void set_state(bool state, bool publish) override { this->set_state(state, publish, true); } + void set_state(bool state, bool publish, bool send_to_nextion) override; + + NextionQueueType get_queue_type() override { return NextionQueueType::BINARY_SENSOR; } + void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {} + void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override { + this->set_state(state_value != 0, publish, send_to_nextion); + } + + protected: + uint8_t page_id_; +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index 7d7018a4c4..e693b2f1ec 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -1,20 +1,58 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome import automation from esphome.components import display, uart -from esphome.const import CONF_ID, CONF_LAMBDA, CONF_BRIGHTNESS -from . import nextion_ns +from esphome.const import ( + CONF_ID, + CONF_LAMBDA, + CONF_BRIGHTNESS, + CONF_TRIGGER_ID, +) + +from . import Nextion, nextion_ns, nextion_ref +from .base_component import ( + CONF_ON_SLEEP, + CONF_ON_WAKE, + CONF_ON_SETUP, + CONF_TFT_URL, + CONF_TOUCH_SLEEP_TIMEOUT, + CONF_WAKE_UP_PAGE, + CONF_AUTO_WAKE_ON_TOUCH, +) + +CODEOWNERS = ["@senexcrenshaw"] DEPENDENCIES = ["uart"] -AUTO_LOAD = ["binary_sensor"] +AUTO_LOAD = ["binary_sensor", "switch", "sensor", "text_sensor"] -Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice) -NextionRef = Nextion.operator("ref") +SetupTrigger = nextion_ns.class_("SetupTrigger", automation.Trigger.template()) +SleepTrigger = nextion_ns.class_("SleepTrigger", automation.Trigger.template()) +WakeTrigger = nextion_ns.class_("WakeTrigger", automation.Trigger.template()) CONFIG_SCHEMA = ( display.BASIC_DISPLAY_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(Nextion), + cv.Optional(CONF_TFT_URL): cv.string, cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, + cv.Optional(CONF_ON_SETUP): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SetupTrigger), + } + ), + cv.Optional(CONF_ON_SLEEP): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SleepTrigger), + } + ), + cv.Optional(CONF_ON_WAKE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(WakeTrigger), + } + ), + cv.Optional(CONF_TOUCH_SLEEP_TIMEOUT): cv.int_range(min=3, max=65535), + cv.Optional(CONF_WAKE_UP_PAGE): cv.positive_int, + cv.Optional(CONF_AUTO_WAKE_ON_TOUCH, default=True): cv.boolean, } ) .extend(cv.polling_component_schema("5s")) @@ -31,8 +69,33 @@ async def to_code(config): cg.add(var.set_brightness(config[CONF_BRIGHTNESS])) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( - config[CONF_LAMBDA], [(NextionRef, "it")], return_type=cg.void + config[CONF_LAMBDA], [(nextion_ref, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) + if CONF_TFT_URL in config: + cg.add_define("USE_TFT_UPLOAD") + cg.add(var.set_tft_url(config[CONF_TFT_URL])) + + if CONF_TOUCH_SLEEP_TIMEOUT in config: + cg.add(var.set_touch_sleep_timeout_internal(config[CONF_TOUCH_SLEEP_TIMEOUT])) + + if CONF_WAKE_UP_PAGE in config: + cg.add(var.set_wake_up_page_internal(config[CONF_WAKE_UP_PAGE])) + + if CONF_AUTO_WAKE_ON_TOUCH in config: + cg.add(var.set_auto_wake_on_touch_internal(config[CONF_AUTO_WAKE_ON_TOUCH])) + await display.register_display(var, config) + + for conf in config.get(CONF_ON_SETUP, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_SLEEP, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_WAKE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index fe0767342b..9a5424917f 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -1,5 +1,7 @@ #include "nextion.h" +#include "esphome/core/util.h" #include "esphome/core/log.h" +#include "esphome/core/application.h" namespace esphome { namespace nextion { @@ -7,69 +9,171 @@ namespace nextion { static const char *const TAG = "nextion"; void Nextion::setup() { - this->send_command_no_ack(""); - this->send_command_printf("bkcmd=3"); - this->set_backlight_brightness(static_cast(brightness_ * 100)); - this->goto_page("0"); + this->is_setup_ = false; + this->ignore_is_setup_ = true; + + // Wake up the nextion + this->send_command_("bkcmd=0"); + this->send_command_("sleep=0"); + + this->send_command_("bkcmd=0"); + this->send_command_("sleep=0"); + + // Reboot it + this->send_command_("rest"); + + this->ignore_is_setup_ = false; } -float Nextion::get_setup_priority() const { return setup_priority::PROCESSOR; } + +bool Nextion::send_command_(const std::string &command) { + if (!this->ignore_is_setup_ && !this->is_setup()) { + return false; + } + + ESP_LOGN(TAG, "send_command %s", command.c_str()); + + this->write_str(command.c_str()); + const uint8_t to_send[3] = {0xFF, 0xFF, 0xFF}; + this->write_array(to_send, sizeof(to_send)); + return true; +} + +bool Nextion::check_connect_() { + if (this->get_is_connected_()) + return true; + + if (this->comok_sent_ == 0) { + this->reset_(false); + + this->ignore_is_setup_ = true; + this->send_command_("boguscommand=0"); // bogus command. needed sometimes after updating + this->send_command_("connect"); + + this->comok_sent_ = millis(); + this->ignore_is_setup_ = false; + + return false; + } + + if (millis() - this->comok_sent_ <= 500) // Wait 500 ms + return false; + + std::string response; + + this->recv_ret_string_(response, 0, false); + if (response.empty() || response.find("comok") == std::string::npos) { +#ifdef NEXTION_PROTOCOL_LOG + ESP_LOGN(TAG, "Bad connect request %s", response.c_str()); + for (int i = 0; i < response.length(); i++) { + ESP_LOGN(TAG, "response %s %d %d %c", response.c_str(), i, response[i], response[i]); + } +#endif + + ESP_LOGW(TAG, "Nextion is not connected! "); + comok_sent_ = 0; + return false; + } + + this->ignore_is_setup_ = true; + ESP_LOGI(TAG, "Nextion is connected"); + this->is_connected_ = true; + + ESP_LOGN(TAG, "connect request %s", response.c_str()); + + size_t start; + size_t end = 0; + std::vector connect_info; + while ((start = response.find_first_not_of(',', end)) != std::string::npos) { + end = response.find(',', start); + connect_info.push_back(response.substr(start, end - start)); + } + + if (connect_info.size() == 7) { + ESP_LOGN(TAG, "Received connect_info %zu", connect_info.size()); + + this->device_model_ = connect_info[2]; + this->firmware_version_ = connect_info[3]; + this->serial_number_ = connect_info[5]; + this->flash_size_ = connect_info[6]; + } else { + ESP_LOGE(TAG, "Nextion returned bad connect value \"%s\"", response.c_str()); + } + + this->ignore_is_setup_ = false; + this->dump_config(); + return true; +} + +void Nextion::reset_(bool reset_nextion) { + uint8_t d; + + while (this->available()) { // Clear receive buffer + this->read_byte(&d); + }; + this->nextion_queue_.clear(); +} + +void Nextion::dump_config() { + ESP_LOGCONFIG(TAG, "Nextion:"); + ESP_LOGCONFIG(TAG, " Device Model: %s", this->device_model_.c_str()); + ESP_LOGCONFIG(TAG, " Firmware Version: %s", this->firmware_version_.c_str()); + ESP_LOGCONFIG(TAG, " Serial Number: %s", this->serial_number_.c_str()); + ESP_LOGCONFIG(TAG, " Flash Size: %s", this->flash_size_.c_str()); + ESP_LOGCONFIG(TAG, " Wake On Touch: %s", this->auto_wake_on_touch_ ? "True" : "False"); + + if (this->touch_sleep_timeout_ != 0) { + ESP_LOGCONFIG(TAG, " Touch Timeout: %d", this->touch_sleep_timeout_); + } + + if (this->wake_up_page_ != -1) { + ESP_LOGCONFIG(TAG, " Wake Up Page : %d", this->wake_up_page_); + } +} + +float Nextion::get_setup_priority() const { return setup_priority::DATA; } void Nextion::update() { + if (!this->is_setup()) { + return; + } if (this->writer_.has_value()) { (*this->writer_)(*this); } } -void Nextion::send_command_no_ack(const char *command) { - // Flush RX... - this->loop(); - this->write_str(command); - const uint8_t data[3] = {0xFF, 0xFF, 0xFF}; - this->write_array(data, sizeof(data)); +void Nextion::add_sleep_state_callback(std::function &&callback) { + this->sleep_callback_.add(std::move(callback)); } -bool Nextion::ack_() { - if (!this->wait_for_ack_) - return true; +void Nextion::add_wake_state_callback(std::function &&callback) { + this->wake_callback_.add(std::move(callback)); +} - uint32_t start = millis(); - while (!this->read_until_ack_()) { - if (millis() - start > 100) { - ESP_LOGW(TAG, "Waiting for ACK timed out!"); - return false; - } +void Nextion::add_setup_state_callback(std::function &&callback) { + this->setup_callback_.add(std::move(callback)); +} + +void Nextion::update_all_components() { + if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) + return; + + for (auto *binarysensortype : this->binarysensortype_) { + binarysensortype->update_component(); + } + for (auto *sensortype : this->sensortype_) { + sensortype->update_component(); + } + for (auto *switchtype : this->switchtype_) { + switchtype->update_component(); + } + for (auto *textsensortype : this->textsensortype_) { + textsensortype->update_component(); } - return true; } -void Nextion::set_component_text(const char *component, const char *text) { - this->send_command_printf("%s.txt=\"%s\"", component, text); -} -void Nextion::set_component_value(const char *component, int value) { - this->send_command_printf("%s.val=%d", component, value); -} -void Nextion::display_picture(int picture_id, int x_start, int y_start) { - this->send_command_printf("pic %d %d %d", x_start, y_start, picture_id); -} -void Nextion::set_component_background_color(const char *component, const char *color) { - this->send_command_printf("%s.bco=\"%s\"", component, color); -} -void Nextion::set_component_pressed_background_color(const char *component, const char *color) { - this->send_command_printf("%s.bco2=\"%s\"", component, color); -} -void Nextion::set_component_font_color(const char *component, const char *color) { - this->send_command_printf("%s.pco=\"%s\"", component, color); -} -void Nextion::set_component_pressed_font_color(const char *component, const char *color) { - this->send_command_printf("%s.pco2=\"%s\"", component, color); -} -void Nextion::set_component_coordinates(const char *component, int x, int y) { - this->send_command_printf("%s.xcen=%d", component, x); - this->send_command_printf("%s.ycen=%d", component, y); -} -void Nextion::set_component_font(const char *component, uint8_t font_id) { - this->send_command_printf("%s.font=%d", component, font_id); -} -void Nextion::goto_page(const char *page) { this->send_command_printf("page %s", page); } + bool Nextion::send_command_printf(const char *format, ...) { + if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) + return false; + char buffer[256]; va_list arg; va_start(arg, format); @@ -79,208 +183,911 @@ bool Nextion::send_command_printf(const char *format, ...) { ESP_LOGW(TAG, "Building command for format '%s' failed!", format); return false; } - this->send_command_no_ack(buffer); - if (!this->ack_()) { - ESP_LOGW(TAG, "Sending command '%s' failed because no ACK was received", buffer); + + if (this->send_command_(buffer)) { + this->add_no_result_to_queue_("send_command_printf"); + return true; + } + return false; +} + +#ifdef NEXTION_PROTOCOL_LOG +void Nextion::print_queue_members_() { + ESP_LOGN(TAG, "print_queue_members_ (top 10) size %zu", this->nextion_queue_.size()); + ESP_LOGN(TAG, "*******************************************"); + int count = 0; + for (auto *i : this->nextion_queue_) { + if (count++ == 10) + break; + + if (i == nullptr) { + ESP_LOGN(TAG, "Nextion queue is null"); + } else { + ESP_LOGN(TAG, "Nextion queue type: %d:%s , name: %s", i->component->get_queue_type(), + i->component->get_queue_type_string().c_str(), i->component->get_variable_name().c_str()); + } + } + ESP_LOGN(TAG, "*******************************************"); +} +#endif + +void Nextion::loop() { + if (!this->check_connect_() || this->is_updating_) + return; + + if (this->nextion_reports_is_setup_ && !this->sent_setup_commands_) { + this->ignore_is_setup_ = true; + this->sent_setup_commands_ = true; + this->send_command_("bkcmd=3"); // Always, returns 0x00 to 0x23 result of serial command. + + this->set_backlight_brightness(this->brightness_); + this->goto_page("0"); + + this->set_auto_wake_on_touch(this->auto_wake_on_touch_); + + if (this->touch_sleep_timeout_ != 0) { + this->set_touch_sleep_timeout(this->touch_sleep_timeout_); + } + + if (this->wake_up_page_ != -1) { + this->set_wake_up_page(this->wake_up_page_); + } + + this->ignore_is_setup_ = false; + } + + this->process_serial_(); // Receive serial data + this->process_nextion_commands_(); // Process nextion return commands + + if (!this->nextion_reports_is_setup_) { + if (this->started_ms_ == 0) + this->started_ms_ = millis(); + + if (this->started_ms_ + this->startup_override_ms_ < millis()) { + ESP_LOGD(TAG, "Manually set nextion report ready"); + this->nextion_reports_is_setup_ = true; + } + } +} + +bool Nextion::remove_from_q_(bool report_empty) { + if (this->nextion_queue_.empty()) { + if (report_empty) + ESP_LOGE(TAG, "Nextion queue is empty!"); return false; } + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; + + ESP_LOGN(TAG, "Removing %s from the queue", component->get_variable_name().c_str()); + + if (component->get_queue_type() == NextionQueueType::NO_RESULT) { + if (component->get_variable_name() == "sleep_wake") { + this->is_sleeping_ = false; + } + delete component; + } + delete nb; + this->nextion_queue_.pop_front(); return true; } -void Nextion::hide_component(const char *component) { this->send_command_printf("vis %s,0", component); } -void Nextion::show_component(const char *component) { this->send_command_printf("vis %s,1", component); } -void Nextion::enable_component_touch(const char *component) { this->send_command_printf("tsw %s,1", component); } -void Nextion::disable_component_touch(const char *component) { this->send_command_printf("tsw %s,0", component); } -void Nextion::add_waveform_data(int component_id, uint8_t channel_number, uint8_t value) { - this->send_command_printf("add %d,%u,%u", component_id, channel_number, value); -} -void Nextion::fill_area(int x1, int y1, int width, int height, const char *color) { - this->send_command_printf("fill %d,%d,%d,%d,%s", x1, y1, width, height, color); -} -void Nextion::line(int x1, int y1, int x2, int y2, const char *color) { - this->send_command_printf("line %d,%d,%d,%d,%s", x1, y1, x2, y2, color); -} -void Nextion::rectangle(int x1, int y1, int width, int height, const char *color) { - this->send_command_printf("draw %d,%d,%d,%d,%s", x1, y1, x1 + width, y1 + height, color); -} -void Nextion::circle(int center_x, int center_y, int radius, const char *color) { - this->send_command_printf("cir %d,%d,%d,%s", center_x, center_y, radius, color); -} -void Nextion::filled_circle(int center_x, int center_y, int radius, const char *color) { - this->send_command_printf("cirs %d,%d,%d,%s", center_x, center_y, radius, color); -} -bool Nextion::read_until_ack_() { - while (this->available() >= 4) { - // flush preceding filler bytes - uint8_t temp; - while (this->available() && this->peek_byte(&temp) && temp == 0xFF) - this->read_byte(&temp); - if (!this->available()) - break; +void Nextion::process_serial_() { + uint8_t d; - uint8_t event; - // event type - this->read_byte(&event); + while (this->available()) { + read_byte(&d); + this->command_data_ += d; + } +} +// nextion.tech/instruction-set/ +void Nextion::process_nextion_commands_() { + if (this->command_data_.length() == 0) { + return; + } - uint8_t data[255]; - // total length of data (including end bytes) - uint8_t data_length = 0; - // message is terminated by three consecutive 0xFF - // this variable keeps track of ohow many of those have - // been received - uint8_t end_length = 0; - while (this->available() && end_length < 3 && data_length < sizeof(data)) { - uint8_t byte; - this->read_byte(&byte); - if (byte == 0xFF) { - end_length++; - } else { - end_length = 0; - } - data[data_length++] = byte; + size_t to_process_length = 0; + std::string to_process; + + ESP_LOGN(TAG, "this->command_data_ %s length %d", this->command_data_.c_str(), this->command_data_.length()); +#ifdef NEXTION_PROTOCOL_LOG + this->print_queue_members_(); +#endif + while ((to_process_length = this->command_data_.find(COMMAND_DELIMITER)) != std::string::npos) { + ESP_LOGN(TAG, "print_queue_members_ size %zu", this->nextion_queue_.size()); + while (to_process_length + COMMAND_DELIMITER.length() < this->command_data_.length() && + static_cast(this->command_data_[to_process_length + COMMAND_DELIMITER.length()]) == 0xFF) { + ++to_process_length; + ESP_LOGN(TAG, "Add extra 0xFF to process"); } - if (end_length != 3) { - ESP_LOGW(TAG, "Received unknown filler end bytes from Nextion!"); - continue; - } + this->nextion_event_ = this->command_data_[0]; - data_length -= 3; // remove filler bytes + to_process_length -= 1; + to_process = this->command_data_.substr(1, to_process_length); - bool invalid_data_length = false; - switch (event) { - case 0x01: // successful execution of instruction (ACK) - return true; - case 0x00: // invalid instruction + switch (this->nextion_event_) { + case 0x00: // instruction sent by user has failed ESP_LOGW(TAG, "Nextion reported invalid instruction!"); + this->remove_from_q_(); + break; - case 0x02: // component ID invalid - ESP_LOGW(TAG, "Nextion reported component ID invalid!"); + case 0x01: // instruction sent by user was successful + + ESP_LOGVV(TAG, "instruction sent by user was successful"); + ESP_LOGN(TAG, "this->nextion_queue_.empty() %s", this->nextion_queue_.empty() ? "True" : "False"); + + this->remove_from_q_(); + if (!this->is_setup_) { + if (this->nextion_queue_.empty()) { + ESP_LOGD(TAG, "Nextion is setup"); + this->is_setup_ = true; + this->setup_callback_.call(); + } + } + break; - case 0x03: // page ID invalid + case 0x02: // invalid Component ID or name was used + this->remove_from_q_(); + break; + case 0x03: // invalid Page ID or name was used ESP_LOGW(TAG, "Nextion reported page ID invalid!"); + this->remove_from_q_(); break; - case 0x04: // picture ID invalid + case 0x04: // invalid Picture ID was used ESP_LOGW(TAG, "Nextion reported picture ID invalid!"); + this->remove_from_q_(); break; - case 0x05: // font ID invalid + case 0x05: // invalid Font ID was used ESP_LOGW(TAG, "Nextion reported font ID invalid!"); + this->remove_from_q_(); break; - case 0x11: // baud rate setting invalid + case 0x06: // File operation fails + ESP_LOGW(TAG, "Nextion File operation fail!"); + break; + case 0x09: // Instructions with CRC validation fails their CRC check + ESP_LOGW(TAG, "Nextion Instructions with CRC validation fails their CRC check!"); + break; + case 0x11: // invalid Baud rate was used ESP_LOGW(TAG, "Nextion reported baud rate invalid!"); break; - case 0x12: // curve control ID number or channel number is invalid - ESP_LOGW(TAG, "Nextion reported control/channel ID invalid!"); + case 0x12: // invalid Waveform ID or Channel # was used + + if (!this->nextion_queue_.empty()) { + int index = 0; + int found = -1; + for (auto &nb : this->nextion_queue_) { + NextionComponentBase *component = nb->component; + + if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { + ESP_LOGW(TAG, "Nextion reported invalid Waveform ID %d or Channel # %d was used!", + component->get_component_id(), component->get_wave_channel_id()); + + ESP_LOGN(TAG, "Removing waveform from queue with component id %d and waveform id %d", + component->get_component_id(), component->get_wave_channel_id()); + + found = index; + + delete component; + delete nb; + + break; + } + ++index; + } + + if (found != -1) { + this->nextion_queue_.erase(this->nextion_queue_.begin() + found); + } else { + ESP_LOGW( + TAG, + "Nextion reported invalid Waveform ID or Channel # was used but no waveform sensor in queue found!"); + } + } break; case 0x1A: // variable name invalid - ESP_LOGW(TAG, "Nextion reported variable name invalid!"); + this->remove_from_q_(); + break; case 0x1B: // variable operation invalid ESP_LOGW(TAG, "Nextion reported variable operation invalid!"); + this->remove_from_q_(); break; case 0x1C: // failed to assign ESP_LOGW(TAG, "Nextion reported failed to assign variable!"); + this->remove_from_q_(); break; case 0x1D: // operate EEPROM failed ESP_LOGW(TAG, "Nextion reported operating EEPROM failed!"); break; case 0x1E: // parameter quantity invalid ESP_LOGW(TAG, "Nextion reported parameter quantity invalid!"); + this->remove_from_q_(); break; case 0x1F: // IO operation failed ESP_LOGW(TAG, "Nextion reported component I/O operation invalid!"); break; case 0x20: // undefined escape characters ESP_LOGW(TAG, "Nextion reported undefined escape characters!"); + this->remove_from_q_(); break; case 0x23: // too long variable name ESP_LOGW(TAG, "Nextion reported too long variable name!"); + this->remove_from_q_(); + + break; + case 0x24: // Serial Buffer overflow occurs + ESP_LOGW(TAG, "Nextion reported Serial Buffer overflow!"); break; case 0x65: { // touch event return data - if (data_length != 3) { - invalid_data_length = true; + if (to_process_length != 3) { + ESP_LOGW(TAG, "Touch event data is expecting 3, received %zu", to_process_length); + break; } - uint8_t page_id = data[0]; - uint8_t component_id = data[1]; - uint8_t touch_event = data[2]; // 0 -> release, 1 -> press + uint8_t page_id = to_process[0]; + uint8_t component_id = to_process[1]; + uint8_t touch_event = to_process[2]; // 0 -> release, 1 -> press ESP_LOGD(TAG, "Got touch page=%u component=%u type=%s", page_id, component_id, touch_event ? "PRESS" : "RELEASE"); for (auto *touch : this->touch_) { - touch->process(page_id, component_id, touch_event); + touch->process_touch(page_id, component_id, touch_event != 0); } break; } - case 0x67: - case 0x68: { // touch coordinate data - if (data_length != 5) { - invalid_data_length = true; + case 0x67: { // Touch Coordinate (awake) + break; + } + case 0x68: { // touch coordinate data (sleep) + + if (to_process_length != 5) { + ESP_LOGW(TAG, "Touch coordinate data is expecting 5, received %zu", to_process_length); + ESP_LOGW(TAG, "%s", to_process.c_str()); break; } - uint16_t x = (uint16_t(data[0]) << 8) | data[1]; - uint16_t y = (uint16_t(data[2]) << 8) | data[3]; - uint8_t touch_event = data[4]; // 0 -> release, 1 -> press + + uint16_t x = (uint16_t(to_process[0]) << 8) | to_process[1]; + uint16_t y = (uint16_t(to_process[2]) << 8) | to_process[3]; + uint8_t touch_event = to_process[4]; // 0 -> release, 1 -> press ESP_LOGD(TAG, "Got touch at x=%u y=%u type=%s", x, y, touch_event ? "PRESS" : "RELEASE"); break; } - case 0x66: // sendme page id + case 0x66: { + break; + } // sendme page id + + // 0x70 0x61 0x62 0x31 0x32 0x33 0xFF 0xFF 0xFF + // Returned when using get command for a string. + // Each byte is converted to char. + // data: ab123 case 0x70: // string variable data return + { + if (this->nextion_queue_.empty()) { + ESP_LOGW(TAG, "ERROR: Received string return but the queue is empty"); + break; + } + + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; + + if (component->get_queue_type() != NextionQueueType::TEXT_SENSOR) { + ESP_LOGE(TAG, "ERROR: Received string return but next in queue \"%s\" is not a text sensor", + component->get_variable_name().c_str()); + } else { + ESP_LOGN(TAG, "Received get_string response: \"%s\" for component id: %s, type: %s", to_process.c_str(), + component->get_variable_name().c_str(), component->get_queue_type_string().c_str()); + component->set_state_from_string(to_process, true, false); + } + + delete nb; + this->nextion_queue_.pop_front(); + + break; + } + // 0x71 0x01 0x02 0x03 0x04 0xFF 0xFF 0xFF + // Returned when get command to return a number + // 4 byte 32-bit value in little endian order. + // (0x01+0x02*256+0x03*65536+0x04*16777216) + // data: 67305985 case 0x71: // numeric variable data return - case 0x86: // device automatically enters into sleep mode + { + if (this->nextion_queue_.empty()) { + ESP_LOGE(TAG, "ERROR: Received numeric return but the queue is empty"); + break; + } + + if (to_process_length == 0) { + ESP_LOGE(TAG, "ERROR: Received numeric return but no data!"); + break; + } + + int dataindex = 0; + + int value = 0; + + for (int i = 0; i < 4; ++i) { + value += to_process[i] << (8 * i); + ++dataindex; + } + + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; + + if (component->get_queue_type() != NextionQueueType::SENSOR && + component->get_queue_type() != NextionQueueType::BINARY_SENSOR && + component->get_queue_type() != NextionQueueType::SWITCH) { + ESP_LOGE(TAG, "ERROR: Received numeric return but next in queue \"%s\" is not a valid sensor type %d", + component->get_variable_name().c_str(), component->get_queue_type()); + } else { + ESP_LOGN(TAG, "Received numeric return for variable %s, queue type %d:%s, value %d", + component->get_variable_name().c_str(), component->get_queue_type(), + component->get_queue_type_string().c_str(), value); + component->set_state_from_int(value, true, false); + } + + delete nb; + this->nextion_queue_.pop_front(); + + break; + } + + case 0x86: { // device automatically enters into sleep mode + ESP_LOGVV(TAG, "Received Nextion entering sleep automatically"); + this->is_sleeping_ = true; + this->sleep_callback_.call(); + break; + } case 0x87: // device automatically wakes up + { + ESP_LOGVV(TAG, "Received Nextion leaves sleep automatically"); + this->is_sleeping_ = false; + this->wake_callback_.call(); + break; + } case 0x88: // system successful start up - case 0x89: // start SD card upgrade - case 0xFD: // data transparent transmit finished - case 0xFE: // data transparent transmit ready + { + ESP_LOGD(TAG, "system successful start up %zu", to_process_length); + this->nextion_reports_is_setup_ = true; break; + } + case 0x89: { // start SD card upgrade + break; + } + // Data from nextion is + // 0x90 - Start + // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0 + // 00 - NULL + // 00/01 - Single byte for on/off + // FF FF FF - End + case 0x90: { // Switched component + std::string variable_name; + uint8_t index = 0; + + // Get variable name + index = to_process.find('\0'); + if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + ESP_LOGE(TAG, "Bad switch component data received for 0x90 event!"); + ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); + break; + } + + variable_name = to_process.substr(0, index); + ++index; + + ESP_LOGN(TAG, "Got Switch variable_name=%s value=%d", variable_name.c_str(), to_process[0] != 0); + + for (auto *switchtype : this->switchtype_) { + switchtype->process_bool(variable_name, to_process[index] != 0); + } + break; + } + // Data from nextion is + // 0x91 - Start + // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0 + // 00 - NULL + // variable length of 0x71 return data: prints temp1.val,0 + // FF FF FF - End + case 0x91: { // Sensor component + std::string variable_name; + uint8_t index = 0; + + index = to_process.find('\0'); + if (static_cast(index) == std::string::npos || (to_process_length - index - 1) != 4) { + ESP_LOGE(TAG, "Bad sensor component data received for 0x91 event!"); + ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); + break; + } + + index = to_process.find('\0'); + variable_name = to_process.substr(0, index); + // // Get variable name + int value = 0; + for (int i = 0; i < 4; ++i) { + value += to_process[i + index + 1] << (8 * i); + } + + ESP_LOGN(TAG, "Got sensor variable_name=%s value=%d", variable_name.c_str(), value); + + for (auto *sensor : this->sensortype_) { + sensor->process_sensor(variable_name, value); + } + break; + } + + // Data from nextion is + // 0x92 - Start + // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0 + // 00 - NULL + // variable length of 0x70 return formatted data (bytes) that contain the text prints temp1.txt,0 + // 00 - NULL + // FF FF FF - End + case 0x92: { // Text Sensor Component + std::string variable_name; + std::string text_value; + uint8_t index = 0; + + // Get variable name + index = to_process.find('\0'); + if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + ESP_LOGE(TAG, "Bad text sensor component data received for 0x92 event!"); + ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); + break; + } + + variable_name = to_process.substr(0, index); + ++index; + + text_value = to_process.substr(index); + + ESP_LOGN(TAG, "Got Text Sensor variable_name=%s value=%s", variable_name.c_str(), text_value.c_str()); + + // NextionTextSensorResponseQueue *nq = new NextionTextSensorResponseQueue; + // nq->variable_name = variable_name; + // nq->state = text_value; + // this->textsensorq_.push_back(nq); + for (auto *textsensortype : this->textsensortype_) { + textsensortype->process_text(variable_name, text_value); + } + break; + } + // Data from nextion is + // 0x93 - Start + // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0 + // 00 - NULL + // 00/01 - Single byte for on/off + // FF FF FF - End + case 0x93: { // Binary Sensor component + std::string variable_name; + uint8_t index = 0; + + // Get variable name + index = to_process.find('\0'); + if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + ESP_LOGE(TAG, "Bad binary sensor component data received for 0x92 event!"); + ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); + break; + } + + variable_name = to_process.substr(0, index); + ++index; + + ESP_LOGN(TAG, "Got Binary Sensor variable_name=%s value=%d", variable_name.c_str(), to_process[index] != 0); + + for (auto *binarysensortype : this->binarysensortype_) { + binarysensortype->process_bool(&variable_name[0], to_process[index] != 0); + } + break; + } + case 0xFD: { // data transparent transmit finished + ESP_LOGVV(TAG, "Nextion reported data transmit finished!"); + break; + } + case 0xFE: { // data transparent transmit ready + ESP_LOGVV(TAG, "Nextion reported ready for transmit!"); + + int index = 0; + int found = -1; + for (auto &nb : this->nextion_queue_) { + auto component = nb->component; + if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { + size_t buffer_to_send = component->get_wave_buffer().size() < 255 ? component->get_wave_buffer().size() + : 255; // ADDT command can only send 255 + + this->write_array(component->get_wave_buffer().data(), static_cast(buffer_to_send)); + + ESP_LOGN(TAG, "Nextion sending waveform data for component id %d and waveform id %d, size %zu", + component->get_component_id(), component->get_wave_channel_id(), buffer_to_send); + + if (component->get_wave_buffer().size() <= 255) { + component->get_wave_buffer().clear(); + } else { + component->get_wave_buffer().erase(component->get_wave_buffer().begin(), + component->get_wave_buffer().begin() + buffer_to_send); + } + found = index; + delete component; + delete nb; + break; + } + ++index; + } + + if (found == -1) { + ESP_LOGE(TAG, "No waveforms in queue to send data!"); + break; + } else { + this->nextion_queue_.erase(this->nextion_queue_.begin() + found); + } + break; + } default: - ESP_LOGW(TAG, "Received unknown event from nextion: 0x%02X", event); + ESP_LOGW(TAG, "Received unknown event from nextion: 0x%02X", this->nextion_event_); break; } - if (invalid_data_length) { - ESP_LOGW(TAG, "Invalid data length from nextion!"); + + // ESP_LOGN(TAG, "nextion_event_ deleting from 0 to %d", to_process_length + COMMAND_DELIMITER.length() + 1); + this->command_data_.erase(0, to_process_length + COMMAND_DELIMITER.length() + 1); + // App.feed_wdt(); Remove before master merge + this->process_serial_(); + } + + uint32_t ms = millis(); + + if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) { + for (int i = 0; i < this->nextion_queue_.size(); i++) { + NextionComponentBase *component = this->nextion_queue_[i]->component; + if (this->nextion_queue_[i]->queue_time + this->max_q_age_ms_ < ms) { + if (this->nextion_queue_[i]->queue_time == 0) + ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\" queue_time 0", + component->get_queue_type_string().c_str(), component->get_variable_name().c_str()); + + if (component->get_variable_name() == "sleep_wake") { + this->is_sleeping_ = false; + } + + ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\"", component->get_queue_type_string().c_str(), + component->get_variable_name().c_str()); + + if (component->get_queue_type() == NextionQueueType::NO_RESULT) { + if (component->get_variable_name() == "sleep_wake") { + this->is_sleeping_ = false; + } + delete component; + } + + delete this->nextion_queue_[i]; + + this->nextion_queue_.erase(this->nextion_queue_.begin() + i); + + } else { + break; + } + } + } + ESP_LOGN(TAG, "Loop End"); + // App.feed_wdt(); Remove before master merge + this->process_serial_(); +} // namespace nextion + +void Nextion::set_nextion_sensor_state(int queue_type, const std::string &name, float state) { + this->set_nextion_sensor_state(static_cast(queue_type), name, state); +} + +void Nextion::set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state) { + ESP_LOGN(TAG, "Received state for variable %s, state %lf for queue type %d", name.c_str(), state, queue_type); + + switch (queue_type) { + case NextionQueueType::SENSOR: { + for (auto *sensor : this->sensortype_) { + if (name == sensor->get_variable_name()) { + sensor->set_state(state, true, true); + break; + } + } + break; + } + case NextionQueueType::BINARY_SENSOR: { + for (auto *sensor : this->binarysensortype_) { + if (name == sensor->get_variable_name()) { + sensor->set_state(state != 0, true, true); + break; + } + } + break; + } + case NextionQueueType::SWITCH: { + for (auto *sensor : this->switchtype_) { + if (name == sensor->get_variable_name()) { + sensor->set_state(state != 0, true, true); + break; + } + } + break; + } + default: { + ESP_LOGW(TAG, "set_nextion_sensor_state does not support a queue type %d", queue_type); + } + } +} + +void Nextion::set_nextion_text_state(const std::string &name, const std::string &state) { + ESP_LOGD(TAG, "Received state for variable %s, state %s", name.c_str(), state.c_str()); + + for (auto *sensor : this->textsensortype_) { + if (name == sensor->get_variable_name()) { + sensor->set_state(state, true, true); + break; + } + } +} + +void Nextion::all_components_send_state_(bool force_update) { + ESP_LOGD(TAG, "all_components_send_state_ "); + for (auto *binarysensortype : this->binarysensortype_) { + if (force_update || binarysensortype->get_needs_to_send_update()) + binarysensortype->send_state_to_nextion(); + } + for (auto *sensortype : this->sensortype_) { + if ((force_update || sensortype->get_needs_to_send_update()) && sensortype->get_wave_chan_id() == 0) + sensortype->send_state_to_nextion(); + } + for (auto *switchtype : this->switchtype_) { + if (force_update || switchtype->get_needs_to_send_update()) + switchtype->send_state_to_nextion(); + } + for (auto *textsensortype : this->textsensortype_) { + if (force_update || textsensortype->get_needs_to_send_update()) + textsensortype->send_state_to_nextion(); + } +} + +void Nextion::update_components_by_prefix(const std::string &prefix) { + for (auto *binarysensortype : this->binarysensortype_) { + if (binarysensortype->get_variable_name().find(prefix, 0) != std::string::npos) + binarysensortype->update_component_settings(true); + } + for (auto *sensortype : this->sensortype_) { + if (sensortype->get_variable_name().find(prefix, 0) != std::string::npos) + sensortype->update_component_settings(true); + } + for (auto *switchtype : this->switchtype_) { + if (switchtype->get_variable_name().find(prefix, 0) != std::string::npos) + switchtype->update_component_settings(true); + } + for (auto *textsensortype : this->textsensortype_) { + if (textsensortype->get_variable_name().find(prefix, 0) != std::string::npos) + textsensortype->update_component_settings(true); + } +} + +uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag) { + uint16_t ret = 0; + uint8_t c = 0; + uint8_t nr_of_ff_bytes = 0; + uint64_t start; + bool exit_flag = false; + bool ff_flag = false; + + start = millis(); + + while ((timeout == 0 && this->available()) || millis() - start <= timeout) { + this->read_byte(&c); + if (c == 0xFF) + nr_of_ff_bytes++; + else { + nr_of_ff_bytes = 0; + ff_flag = false; + } + + if (nr_of_ff_bytes >= 3) + ff_flag = true; + + response += (char) c; + if (recv_flag) { + if (response.find(0x05) != std::string::npos) { + exit_flag = true; + } + } + App.feed_wdt(); + delay(1); + + if (exit_flag || ff_flag) { + break; } } - return false; + if (ff_flag) + response = response.substr(0, response.length() - 3); // Remove last 3 0xFF + + ret = response.length(); + return ret; } -void Nextion::loop() { - while (this->available() >= 4) { - this->read_until_ack_(); + +/** + * @brief + * + * @param variable_name Name for the queue + */ +void Nextion::add_no_result_to_queue_(const std::string &variable_name) { + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; + + nextion_queue->component = new nextion::NextionComponentBase; + nextion_queue->component->set_variable_name(variable_name); + + nextion_queue->queue_time = millis(); + + this->nextion_queue_.push_back(nextion_queue); + + ESP_LOGN(TAG, "Add to queue type: NORESULT component %s", nextion_queue->component->get_variable_name().c_str()); +} + +/** + * @brief + * + * @param variable_name Variable name for the queue + * @param command + */ +void Nextion::add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command) { + if ((!this->is_setup() && !this->ignore_is_setup_) || command.empty()) + return; + + if (this->send_command_(command)) { + this->add_no_result_to_queue_(variable_name); } } -#ifdef USE_TIME -void Nextion::set_nextion_rtc_time(time::ESPTime time) { - this->send_command_printf("rtc0=%u", time.year); - this->send_command_printf("rtc1=%u", time.month); - this->send_command_printf("rtc2=%u", time.day_of_month); - this->send_command_printf("rtc3=%u", time.hour); - this->send_command_printf("rtc4=%u", time.minute); - this->send_command_printf("rtc5=%u", time.second); -} -#endif -void Nextion::set_backlight_brightness(uint8_t brightness) { this->send_command_printf("dim=%u", brightness); } -void Nextion::set_touch_sleep_timeout(uint16_t timeout) { this->send_command_printf("thsp=%u", timeout); } +bool Nextion::add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format, + ...) { + if ((!this->is_setup() && !this->ignore_is_setup_)) + return false; -void Nextion::set_writer(const nextion_writer_t &writer) { this->writer_ = writer; } -void Nextion::set_component_text_printf(const char *component, const char *format, ...) { + char buffer[256]; va_list arg; va_start(arg, format); - char buffer[256]; int ret = vsnprintf(buffer, sizeof(buffer), format, arg); va_end(arg); - if (ret > 0) - this->set_component_text(component, buffer); -} -void Nextion::set_wait_for_ack(bool wait_for_ack) { this->wait_for_ack_ = wait_for_ack; } + if (ret <= 0) { + ESP_LOGW(TAG, "Building command for format '%s' failed!", format); + return false; + } -void NextionTouchComponent::process(uint8_t page_id, uint8_t component_id, bool on) { - if (this->page_id_ == page_id && this->component_id_ == component_id) { - this->publish_state(on); + this->add_no_result_to_queue_with_command_(variable_name, buffer); + return true; +} + +/** + * @brief Sends a formatted command to the nextion + * + * @param variable_name Variable name for the queue + * @param format The printf-style command format, like "vis %s,0" + * @param ... The format arguments + */ +bool Nextion::add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format, ...) { + if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) + return false; + + char buffer[256]; + va_list arg; + va_start(arg, format); + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + va_end(arg); + if (ret <= 0) { + ESP_LOGW(TAG, "Building command for format '%s' failed!", format); + return false; + } + + this->add_no_result_to_queue_with_command_(variable_name, buffer); + return true; +} + +/** + * @brief + * + * @param variable_name Variable name for the queue + * @param variable_name_to_send Variable name for the left of the command + * @param state_value Value to set + * @param is_sleep_safe The command is safe to send when the Nextion is sleeping + */ + +void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) { + this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(), + state_value); +} + +void Nextion::add_no_result_to_queue_with_set(const std::string &variable_name, + const std::string &variable_name_to_send, int state_value) { + this->add_no_result_to_queue_with_set_internal_(variable_name, variable_name_to_send, state_value); +} + +void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &variable_name, + const std::string &variable_name_to_send, int state_value, + bool is_sleep_safe) { + if ((!this->is_setup() && !this->ignore_is_setup_) || (!is_sleep_safe && this->is_sleeping())) + return; + + this->add_no_result_to_queue_with_ignore_sleep_printf_(variable_name, "%s=%d", variable_name_to_send.c_str(), + state_value); +} + +/** + * @brief + * + * @param variable_name Variable name for the queue + * @param variable_name_to_send Variable name for the left of the command + * @param state_value Sting value to set + * @param is_sleep_safe The command is safe to send when the Nextion is sleeping + */ +void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) { + this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(), + state_value); +} +void Nextion::add_no_result_to_queue_with_set(const std::string &variable_name, + const std::string &variable_name_to_send, + const std::string &state_value) { + this->add_no_result_to_queue_with_set_internal_(variable_name, variable_name_to_send, state_value); +} + +void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &variable_name, + const std::string &variable_name_to_send, + const std::string &state_value, bool is_sleep_safe) { + if ((!this->is_setup() && !this->ignore_is_setup_) || (!is_sleep_safe && this->is_sleeping())) + return; + + this->add_no_result_to_queue_with_printf_(variable_name, "%s=\"%s\"", variable_name_to_send.c_str(), + state_value.c_str()); +} + +void Nextion::add_to_get_queue(NextionComponentBase *component) { + if ((!this->is_setup() && !this->ignore_is_setup_)) + return; + + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; + + nextion_queue->component = component; + nextion_queue->queue_time = millis(); + + ESP_LOGN(TAG, "Add to queue type: %s component %s", component->get_queue_type_string().c_str(), + component->get_variable_name().c_str()); + + std::string command = "get " + component->get_variable_name_to_send(); + + if (this->send_command_(command)) { + this->nextion_queue_.push_back(nextion_queue); } } +/** + * @brief Add addt command to the queue + * + * @param component_id The waveform component id + * @param wave_chan_id The waveform channel to send it to + * @param buffer_to_send The buffer size + * @param buffer_size The buffer data + */ +void Nextion::add_addt_command_to_queue(NextionComponentBase *component) { + if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) + return; + + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; + + nextion_queue->component = new nextion::NextionComponentBase; + nextion_queue->queue_time = millis(); + + size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() + : 255; // ADDT command can only send 255 + + std::string command = "addt " + to_string(component->get_component_id()) + "," + + to_string(component->get_wave_channel_id()) + "," + to_string(buffer_to_send); + if (this->send_command_(command)) { + this->nextion_queue_.push_back(nextion_queue); + } +} + +void Nextion::set_writer(const nextion_writer_t &writer) { this->writer_ = writer; } + +ESPDEPRECATED("set_wait_for_ack(bool) is deprecated and has no effect") +void Nextion::set_wait_for_ack(bool wait_for_ack) { ESP_LOGE(TAG, "This command is depreciated"); } + } // namespace nextion } // namespace esphome diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index a55ff747ee..2389cc6235 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -1,9 +1,21 @@ #pragma once -#include "esphome/core/component.h" +#include #include "esphome/core/defines.h" #include "esphome/components/uart/uart.h" -#include "esphome/components/binary_sensor/binary_sensor.h" +#include "nextion_base.h" +#include "nextion_component.h" +#include "esphome/components/display/display_color_utils.h" + +#if defined(USE_ETHERNET) || defined(USE_WIFI) +#ifdef ARDUINO_ARCH_ESP32 +#include +#endif +#ifdef ARDUINO_ARCH_ESP8266 +#include +#include +#endif +#endif #ifdef USE_TIME #include "esphome/components/time/real_time_clock.h" @@ -12,12 +24,14 @@ namespace esphome { namespace nextion { -class NextionTouchComponent; class Nextion; +class NextionComponentBase; using nextion_writer_t = std::function; -class Nextion : public PollingComponent, public uart::UARTDevice { +static const std::string COMMAND_DELIMITER{static_cast(255), static_cast(255), static_cast(255)}; + +class Nextion : public NextionBase, public PollingComponent, public uart::UARTDevice { public: /** * Set the text of a component to a static string. @@ -73,9 +87,20 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * This will change the image of the component `pic` to the image with ID `4`. */ - void set_component_picture(const char *component, const char *picture) { - this->send_command_printf("%s.val=%s", component, picture); - } + void set_component_picture(const char *component, const char *picture); + /** + * Set the background color of a component. + * @param component The component name. + * @param color The color (as a uint32_t). + * + * Example: + * ```cpp + * it.set_component_background_color("button", 0xFF0000); + * ``` + * + * This will change the background color of the component `button` to red. + */ + void set_component_background_color(const char *component, uint32_t color); /** * Set the background color of a component. * @param component The component name. @@ -83,7 +108,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * it.set_component_background_color("button", "17013"); + * it.set_component_background_color("button", "RED"); * ``` * * This will change the background color of the component `button` to blue. @@ -91,6 +116,33 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * Nextion HMI colors. */ void set_component_background_color(const char *component, const char *color); + /** + * Set the background color of a component. + * @param component The component name. + * @param color The color (as Color). + * + * Example: + * ```cpp + * it.set_component_background_color("button", color); + * ``` + * + * This will change the background color of the component `button` to what color contains. + */ + void set_component_background_color(const char *component, Color color) override; + /** + * Set the pressed background color of a component. + * @param component The component name. + * @param color The color (as a int). + * + * Example: + * ```cpp + * it.set_component_pressed_background_color("button", 0xFF0000 ); + * ``` + * + * This will change the pressed background color of the component `button` to red. This is the background color that + * is shown when the component is pressed. + */ + void set_component_pressed_background_color(const char *component, uint32_t color); /** * Set the pressed background color of a component. * @param component The component name. @@ -98,7 +150,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * it.set_component_pressed_background_color("button", "17013"); + * it.set_component_pressed_background_color("button", "RED"); * ``` * * This will change the pressed background color of the component `button` to blue. This is the background color that @@ -107,6 +159,63 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * colors. */ void set_component_pressed_background_color(const char *component, const char *color); + /** + * Set the pressed background color of a component. + * @param component The component name. + * @param color The color (as Color). + * + * Example: + * ```cpp + * it.set_component_pressed_background_color("button", color); + * ``` + * + * This will change the pressed background color of the component `button` to blue. This is the background color that + * is shown when the component is pressed. Use this [color + * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI + * colors. + */ + void set_component_pressed_background_color(const char *component, Color color) override; + + /** + * Set the picture id of a component. + * @param component The component name. + * @param pic_id The picture ID. + * + * Example: + * ```cpp + * it.set_component_pic("textview", 1); + * ``` + * + * This will change the picture id of the component `textview`. + */ + void set_component_pic(const char *component, uint8_t pic_id); + /** + * Set the background picture id of component. + * @param component The component name. + * @param pic_id The picture ID. + * + * Example: + * ```cpp + * it.set_component_picc("textview", 1); + * ``` + * + * This will change the background picture id of the component `textview`. + */ + void set_component_picc(const char *component, uint8_t pic_id); + + /** + * Set the font color of a component. + * @param component The component name. + * @param color The color (as a uint32_t ). + * + * Example: + * ```cpp + * it.set_component_font_color("textview", 0xFF0000); + * ``` + * + * This will change the font color of the component `textview` to a red color. + */ + void set_component_font_color(const char *component, uint32_t color); /** * Set the font color of a component. * @param component The component name. @@ -114,7 +223,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * it.set_component_font_color("textview", "17013"); + * it.set_component_font_color("textview", "RED"); * ``` * * This will change the font color of the component `textview` to a blue color. @@ -122,6 +231,34 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * Nextion HMI colors. */ void set_component_font_color(const char *component, const char *color); + /** + * Set the font color of a component. + * @param component The component name. + * @param color The color (as Color). + * + * Example: + * ```cpp + * it.set_component_font_color("textview", color); + * ``` + * + * This will change the font color of the component `textview` to a blue color. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. + */ + void set_component_font_color(const char *component, Color color) override; + /** + * Set the pressed font color of a component. + * @param component The component name. + * @param color The color (as a uint32_t). + * + * Example: + * ```cpp + * it.set_component_pressed_font_color("button", 0xFF0000); + * ``` + * + * This will change the pressed font color of the component `button` to a red. + */ + void set_component_pressed_font_color(const char *component, uint32_t color); /** * Set the pressed font color of a component. * @param component The component name. @@ -129,7 +266,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * it.set_component_pressed_font_color("button", "17013"); + * it.set_component_pressed_font_color("button", "RED"); * ``` * * This will change the pressed font color of the component `button` to a blue color. @@ -137,6 +274,21 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * Nextion HMI colors. */ void set_component_pressed_font_color(const char *component, const char *color); + /** + * Set the pressed font color of a component. + * @param component The component name. + * @param color The color (as Color). + * + * Example: + * ```cpp + * it.set_component_pressed_font_color("button", color); + * ``` + * + * This will change the pressed font color of the component `button` to a blue color. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. + */ + void set_component_pressed_font_color(const char *component, Color color) override; /** * Set the coordinates of a component on screen. * @param component The component name. @@ -163,7 +315,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Changes the font of the component named `textveiw`. Font IDs are set in the Nextion Editor. */ - void set_component_font(const char *component, uint8_t font_id); + void set_component_font(const char *component, uint8_t font_id) override; #ifdef USE_TIME /** * Send the current time to the nextion display. @@ -195,7 +347,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Hides the component named `button`. */ - void hide_component(const char *component); + void hide_component(const char *component) override; /** * Show a component. * @param component The component name. @@ -207,7 +359,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Shows the component named `button`. */ - void show_component(const char *component); + void show_component(const char *component) override; /** * Enable touch for a component. * @param component The component name. @@ -239,6 +391,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * @param value The value to write. */ void add_waveform_data(int component_id, uint8_t channel_number, uint8_t value); + void open_waveform_channel(int component_id, uint8_t channel_number, uint8_t value); /** * Display a picture at coordinates. * @param picture_id The picture id. @@ -263,7 +416,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * fill_area(50, 50, 100, 100, "17013"); + * fill_area(50, 50, 100, 100, "RED"); * ``` * * Fills an area that starts at x coordiante `50` and y coordinate `50` with a height of `100` and width of `100` with @@ -271,6 +424,24 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * convert color codes to Nextion HMI colors */ void fill_area(int x1, int y1, int width, int height, const char *color); + /** + * Fill a rectangle with a color. + * @param x1 The starting x coordinate. + * @param y1 The starting y coordinate. + * @param width The width to draw. + * @param height The height to draw. + * @param color The color to draw with (as Color). + * + * Example: + * ```cpp + * fill_area(50, 50, 100, 100, color); + * ``` + * + * Fills an area that starts at x coordiante `50` and y coordinate `50` with a height of `100` and width of `100` with + * the color of blue. Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to + * convert color codes to Nextion HMI colors + */ + void fill_area(int x1, int y1, int width, int height, Color color); /** * Draw a line on the screen. * @param x1 The starting x coordinate. @@ -290,6 +461,25 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * colors. */ void line(int x1, int y1, int x2, int y2, const char *color); + /** + * Draw a line on the screen. + * @param x1 The starting x coordinate. + * @param y1 The starting y coordinate. + * @param x2 The ending x coordinate. + * @param y2 The ending y coordinate. + * @param color The color to draw with (as Color). + * + * Example: + * ```cpp + * it.line(50, 50, 75, 75, "17013"); + * ``` + * + * Makes a line that starts at x coordinate `50` and y coordinate `50` and ends at x coordinate `75` and y coordinate + * `75` with the color of blue. Use this [color + * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI + * colors. + */ + void line(int x1, int y1, int x2, int y2, Color color); /** * Draw a rectangle outline. * @param x1 The starting x coordinate. @@ -309,6 +499,25 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * colors. */ void rectangle(int x1, int y1, int width, int height, const char *color); + /** + * Draw a rectangle outline. + * @param x1 The starting x coordinate. + * @param y1 The starting y coordinate. + * @param width The width of the rectangle. + * @param height The height of the rectangle. + * @param color The color to draw with (as Color). + * + * Example: + * ```cpp + * it.rectangle(25, 35, 40, 50, "17013"); + * ``` + * + * Makes a outline of a rectangle that starts at x coordinate `25` and y coordinate `35` and has a width of `40` and a + * length of `50` with color of blue. Use this [color + * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI + * colors. + */ + void rectangle(int x1, int y1, int width, int height, Color color); /** * Draw a circle outline * @param center_x The center x coordinate. @@ -317,6 +526,14 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * @param color The color to draw with (as a string). */ void circle(int center_x, int center_y, int radius, const char *color); + /** + * Draw a circle outline + * @param center_x The center x coordinate. + * @param center_y The center y coordinate. + * @param radius The circle radius. + * @param color The color to draw with (as Color). + */ + void circle(int center_x, int center_y, int radius, Color color); /** * Draw a filled circled. * @param center_x The center x coordinate. @@ -334,19 +551,36 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * Nextion HMI colors. */ void filled_circle(int center_x, int center_y, int radius, const char *color); - - /** Set the brightness of the backlight. - * - * @param brightness The brightness, from 0 to 100. + /** + * Draw a filled circled. + * @param center_x The center x coordinate. + * @param center_y The center y coordinate. + * @param radius The circle radius. + * @param color The color to draw with (as Color). * * Example: * ```cpp - * it.set_backlight_brightness(30); + * it.filled_cricle(25, 25, 10, color); + * ``` + * + * Makes a filled circle at the x cordinates `25` and y coordinate `25` with a radius of `10` with a color of blue. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. + */ + void filled_circle(int center_x, int center_y, int radius, Color color); + + /** Set the brightness of the backlight. + * + * @param brightness The brightness percentage from 0 to 1.0. + * + * Example: + * ```cpp + * it.set_backlight_brightness(.3); * ``` * * Changes the brightness of the display to 30%. */ - void set_backlight_brightness(uint8_t brightness); + void set_backlight_brightness(float brightness); /** * Set the touch sleep timeout of the display. * @param timeout Timeout in seconds. @@ -360,10 +594,46 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * `thup`. */ void set_touch_sleep_timeout(uint16_t timeout); + /** + * Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode. + * @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to + * wakes up to current page. + * + * Example: + * ```cpp + * it.set_wake_up_page(2); + * ``` + * + * The display will wake up to page 2. + */ + void set_wake_up_page(uint8_t page_id = 255); + /** + * Sets if Nextion should auto-wake from sleep when touch press occurs. + * @param auto_wake True or false. When auto_wake is true and Nextion is in sleep mode, + * the first touch will only trigger the auto wake mode and not trigger a Touch Event. + * + * Example: + * ```cpp + * it.set_auto_wake_on_touch(true); + * ``` + * + * The display will wake up by touch. + */ + void set_auto_wake_on_touch(bool auto_wake); + /** + * Sets Nextion mode between sleep and awake + * @param True or false. Sleep=true to enter sleep mode or sleep=false to exit sleep mode. + */ + void sleep(bool sleep); // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) - void register_touch_component(NextionTouchComponent *obj) { this->touch_.push_back(obj); } + void register_touch_component(NextionComponentBase *obj) { this->touch_.push_back(obj); } + void register_switch_component(NextionComponentBase *obj) { this->switchtype_.push_back(obj); } + void register_binarysensor_component(NextionComponentBase *obj) { this->binarysensortype_.push_back(obj); } + void register_sensor_component(NextionComponentBase *obj) { this->sensortype_.push_back(obj); } + void register_textsensor_component(NextionComponentBase *obj) { this->textsensortype_.push_back(obj); } + void setup() override; void set_brightness(float brightness) { this->brightness_ = brightness; } float get_setup_priority() const override; @@ -371,11 +641,9 @@ class Nextion : public PollingComponent, public uart::UARTDevice { void loop() override; void set_writer(const nextion_writer_t &writer); - /** - * Manually send a raw command to the display and don't wait for an acknowledgement packet. - * @param command The command to write, for example "vis b0,0". - */ - void send_command_no_ack(const char *command); + // This function has been deprecated + void set_wait_for_ack(bool wait_for_ack); + /** * Manually send a raw formatted command to the display. * @param format The printf-style command format, like "vis %s,0" @@ -384,28 +652,199 @@ class Nextion : public PollingComponent, public uart::UARTDevice { */ bool send_command_printf(const char *format, ...) __attribute__((format(printf, 2, 3))); - void set_wait_for_ack(bool wait_for_ack); +#ifdef USE_TFT_UPLOAD + /** + * Set the tft file URL. https seems problamtic with arduino.. + */ + void set_tft_url(const std::string &tft_url) { this->tft_url_ = tft_url; } + +#endif + + /** + * Upload the tft file and softreset the Nextion + */ + void upload_tft(); + void dump_config() override; + + /** + * Softreset the Nextion + */ + void soft_reset(); + + /** Add a callback to be notified of sleep state changes. + * + * @param callback The void() callback. + */ + void add_sleep_state_callback(std::function &&callback); + + /** Add a callback to be notified of wake state changes. + * + * @param callback The void() callback. + */ + void add_wake_state_callback(std::function &&callback); + + /** Add a callback to be notified when the nextion completes its initialize setup. + * + * @param callback The void() callback. + */ + void add_setup_state_callback(std::function &&callback); + + void update_all_components(); + + /** + * @brief Set the nextion sensor state object. + * + * @param[in] queue_type + * Index of NextionQueueType. + * + * @param[in] name + * Component/variable name. + * + * @param[in] state + * State to set. + */ + void set_nextion_sensor_state(int queue_type, const std::string &name, float state); + void set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state); + void set_nextion_text_state(const std::string &name, const std::string &state); + + void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) override; + void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, + int state_value) override; + + void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) override; + void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, + const std::string &state_value) override; + + void add_to_get_queue(NextionComponentBase *component) override; + + void add_addt_command_to_queue(NextionComponentBase *component) override; + + void update_components_by_prefix(const std::string &prefix); + + void set_touch_sleep_timeout_internal(uint32_t touch_sleep_timeout) { + this->touch_sleep_timeout_ = touch_sleep_timeout; + } + void set_wake_up_page_internal(uint8_t wake_up_page) { this->wake_up_page_ = wake_up_page; } + void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; } protected: - bool ack_(); - bool read_until_ack_(); + std::deque nextion_queue_; + uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag); + void all_components_send_state_(bool force_update = false); + uint64_t comok_sent_ = 0; + bool remove_from_q_(bool report_empty = true); + /** + * @brief + * Sends commands ignoring of the Nextion has been setup. + */ + bool ignore_is_setup_ = false; + bool nextion_reports_is_setup_ = false; + uint8_t nextion_event_; + + void process_nextion_commands_(); + void process_serial_(); + bool is_updating_ = false; + uint32_t touch_sleep_timeout_ = 0; + int wake_up_page_ = -1; + bool auto_wake_on_touch_ = true; + + /** + * Manually send a raw command to the display and don't wait for an acknowledgement packet. + * @param command The command to write, for example "vis b0,0". + */ + bool send_command_(const std::string &command); + void add_no_result_to_queue_(const std::string &variable_name); + bool add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format, ...) + __attribute__((format(printf, 3, 4))); + void add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command); + + bool add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format, ...) + __attribute__((format(printf, 3, 4))); + + void add_no_result_to_queue_with_set_internal_(const std::string &variable_name, + const std::string &variable_name_to_send, int state_value, + bool is_sleep_safe = false); + + void add_no_result_to_queue_with_set_internal_(const std::string &variable_name, + const std::string &variable_name_to_send, + const std::string &state_value, bool is_sleep_safe = false); + +#ifdef USE_TFT_UPLOAD +#if defined(USE_ETHERNET) || defined(USE_WIFI) +#ifdef ARDUINO_ARCH_ESP8266 + WiFiClient *wifi_client_{nullptr}; + BearSSL::WiFiClientSecure *wifi_client_secure_{nullptr}; + WiFiClient *get_wifi_client_(); +#endif + + /** + * will request chunk_size chunks from the web server + * and send each to the nextion + * @param int contentLength Total size of the file + * @param uint32_t chunk_size + * @return true if success, false for failure. + */ + int content_length_ = 0; + int tft_size_ = 0; + int upload_by_chunks_(HTTPClient *http, int range_start); + + bool upload_with_range_(uint32_t range_start, uint32_t range_end); + + /** + * start update tft file to nextion. + * + * @param const uint8_t *file_buf + * @param size_t buf_size + * @return true if success, false for failure. + */ + bool upload_from_buffer_(const uint8_t *file_buf, size_t buf_size); + void upload_end_(); + +#endif + +#endif + + bool get_is_connected_() { return this->is_connected_; } + + bool check_connect_(); + + std::vector touch_; + std::vector switchtype_; + std::vector sensortype_; + std::vector textsensortype_; + std::vector binarysensortype_; + CallbackManager setup_callback_{}; + CallbackManager sleep_callback_{}; + CallbackManager wake_callback_{}; - std::vector touch_; optional writer_; - bool wait_for_ack_{true}; float brightness_{1.0}; + + std::string device_model_; + std::string firmware_version_; + std::string serial_number_; + std::string flash_size_; + + void remove_front_no_sensors_(); + +#ifdef USE_TFT_UPLOAD + std::string tft_url_; + uint8_t *transfer_buffer_{nullptr}; + size_t transfer_buffer_size_; + bool upload_first_chunk_sent_ = false; +#endif + +#ifdef NEXTION_PROTOCOL_LOG + void print_queue_members_(); +#endif + void reset_(bool reset_nextion = true); + + std::string command_data_; + bool is_connected_ = false; + uint32_t startup_override_ms_ = 8000; + uint32_t max_q_age_ms_ = 8000; + uint32_t started_ms_ = 0; + bool sent_setup_commands_ = false; }; - -class NextionTouchComponent : public binary_sensor::BinarySensorInitiallyOff { - public: - void set_page_id(uint8_t page_id) { page_id_ = page_id; } - void set_component_id(uint8_t component_id) { component_id_ = component_id; } - void process(uint8_t page_id, uint8_t component_id, bool on); - - protected: - uint8_t page_id_; - uint8_t component_id_; -}; - } // namespace nextion } // namespace esphome diff --git a/esphome/components/nextion/nextion_base.h b/esphome/components/nextion/nextion_base.h new file mode 100644 index 0000000000..a24fd74060 --- /dev/null +++ b/esphome/components/nextion/nextion_base.h @@ -0,0 +1,58 @@ +#pragma once +#include "esphome/core/defines.h" +#include "esphome/core/color.h" +#include "nextion_component_base.h" +namespace esphome { +namespace nextion { + +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE +#define NEXTION_PROTOCOL_LOG +#endif + +#ifdef NEXTION_PROTOCOL_LOG +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE +#define ESP_LOGN(tag, ...) ESP_LOGVV(tag, __VA_ARGS__) +#else +#define ESP_LOGN(tag, ...) ESP_LOGD(tag, __VA_ARGS__) +#endif +#else +#define ESP_LOGN(tag, ...) \ + {} +#endif + +class NextionBase; + +class NextionBase { + public: + virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) = 0; + virtual void add_no_result_to_queue_with_set(const std::string &variable_name, + const std::string &variable_name_to_send, int state_value) = 0; + + virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) = 0; + virtual void add_no_result_to_queue_with_set(const std::string &variable_name, + const std::string &variable_name_to_send, + const std::string &state_value) = 0; + + virtual void add_addt_command_to_queue(NextionComponentBase *component) = 0; + + virtual void add_to_get_queue(NextionComponentBase *component) = 0; + + virtual void set_component_background_color(const char *component, Color color) = 0; + virtual void set_component_pressed_background_color(const char *component, Color color) = 0; + virtual void set_component_font_color(const char *component, Color color) = 0; + virtual void set_component_pressed_font_color(const char *component, Color color) = 0; + virtual void set_component_font(const char *component, uint8_t font_id) = 0; + + virtual void show_component(const char *component) = 0; + virtual void hide_component(const char *component) = 0; + + bool is_sleeping() { return this->is_sleeping_; } + bool is_setup() { return this->is_setup_; } + + protected: + bool is_setup_ = false; + bool is_sleeping_ = false; +}; + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_commands.cpp b/esphome/components/nextion/nextion_commands.cpp new file mode 100644 index 0000000000..931b934ba2 --- /dev/null +++ b/esphome/components/nextion/nextion_commands.cpp @@ -0,0 +1,234 @@ +#include "nextion.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { +static const char *const TAG = "nextion"; + +// Sleep safe commands +void Nextion::soft_reset() { this->send_command_("rest"); } + +void Nextion::set_wake_up_page(uint8_t page_id) { + if (page_id > 255) { + ESP_LOGD(TAG, "Wake up page of bounds, range 0-255"); + return; + } + this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", page_id, true); +} + +void Nextion::set_touch_sleep_timeout(uint16_t timeout) { + if (timeout < 3 || timeout > 65535) { + ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535"); + return; + } + + this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", timeout, true); +} + +void Nextion::sleep(bool sleep) { + if (sleep) { // Set sleep + this->is_sleeping_ = true; + this->add_no_result_to_queue_with_set_internal_("sleep", "sleep", 1, true); + } else { // Turn off sleep. Wait for a sleep_wake return before setting sleep off + this->add_no_result_to_queue_with_set_internal_("sleep_wake", "sleep", 0, true); + } +} +// End sleep safe commands + +// Set Colors +void Nextion::set_component_background_color(const char *component, uint32_t color) { + this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%d", component, color); +} + +void Nextion::set_component_background_color(const char *component, const char *color) { + this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%s", component, color); +} + +void Nextion::set_component_background_color(const char *component, Color color) { + this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%d", component, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::set_component_pressed_background_color(const char *component, uint32_t color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%d", component, color); +} + +void Nextion::set_component_pressed_background_color(const char *component, const char *color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%s", component, color); +} + +void Nextion::set_component_pressed_background_color(const char *component, Color color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%d", component, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::set_component_pic(const char *component, uint8_t pic_id) { + this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.pic=%d", component, pic_id); +} + +void Nextion::set_component_picc(const char *component, uint8_t pic_id) { + this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.picc=%d", component, pic_id); +} + +void Nextion::set_component_font_color(const char *component, uint32_t color) { + this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%d", component, color); +} + +void Nextion::set_component_font_color(const char *component, const char *color) { + this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%s", component, color); +} + +void Nextion::set_component_font_color(const char *component, Color color) { + this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%d", component, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::set_component_pressed_font_color(const char *component, uint32_t color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%d", component, color); +} + +void Nextion::set_component_pressed_font_color(const char *component, const char *color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", " %s.pco2=%s", component, color); +} + +void Nextion::set_component_pressed_font_color(const char *component, Color color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%d", component, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::set_component_text_printf(const char *component, const char *format, ...) { + va_list arg; + va_start(arg, format); + char buffer[256]; + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + va_end(arg); + if (ret > 0) + this->set_component_text(component, buffer); +} + +// General Nextion +void Nextion::goto_page(const char *page) { this->add_no_result_to_queue_with_printf_("goto_page", "page %s", page); } + +void Nextion::set_backlight_brightness(float brightness) { + if (brightness < 0 || brightness > 1.0) { + ESP_LOGD(TAG, "Brightness out of bounds, percentage range 0-1.0"); + return; + } + this->add_no_result_to_queue_with_set("backlight_brightness", "dim", static_cast(brightness * 100)); +} + +void Nextion::set_auto_wake_on_touch(bool auto_wake) { + this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake ? 1 : 0); +} + +// General Component +void Nextion::set_component_font(const char *component, uint8_t font_id) { + this->add_no_result_to_queue_with_printf_("set_component_font", "%s.font=%d", component, font_id); +} + +void Nextion::hide_component(const char *component) { + this->add_no_result_to_queue_with_printf_("hide_component", "vis %s,0", component); +} + +void Nextion::show_component(const char *component) { + this->add_no_result_to_queue_with_printf_("show_component", "vis %s,1", component); +} + +void Nextion::enable_component_touch(const char *component) { + this->add_no_result_to_queue_with_printf_("enable_component_touch", "tsw %s,1", component); +} + +void Nextion::disable_component_touch(const char *component) { + this->add_no_result_to_queue_with_printf_("disable_component_touch", "tsw %s,0", component); +} + +void Nextion::set_component_picture(const char *component, const char *picture) { + this->add_no_result_to_queue_with_printf_("set_component_picture", "%s.val=%s", component, picture); +} + +void Nextion::set_component_text(const char *component, const char *text) { + this->add_no_result_to_queue_with_printf_("set_component_text", "%s.txt=\"%s\"", component, text); +} + +void Nextion::set_component_value(const char *component, int value) { + this->add_no_result_to_queue_with_printf_("set_component_value", "%s.val=%d", component, value); +} + +void Nextion::add_waveform_data(int component_id, uint8_t channel_number, uint8_t value) { + this->add_no_result_to_queue_with_printf_("add_waveform_data", "add %d,%u,%u", component_id, channel_number, value); +} + +void Nextion::open_waveform_channel(int component_id, uint8_t channel_number, uint8_t value) { + this->add_no_result_to_queue_with_printf_("open_waveform_channel", "addt %d,%u,%u", component_id, channel_number, + value); +} + +void Nextion::set_component_coordinates(const char *component, int x, int y) { + this->add_no_result_to_queue_with_printf_("set_component_coordinates command 1", "%s.xcen=%d", component, x); + this->add_no_result_to_queue_with_printf_("set_component_coordinates command 2", "%s.ycen=%d", component, y); +} + +// Drawing +void Nextion::display_picture(int picture_id, int x_start, int y_start) { + this->add_no_result_to_queue_with_printf_("display_picture", "pic %d %d %d", x_start, y_start, picture_id); +} + +void Nextion::fill_area(int x1, int y1, int width, int height, const char *color) { + this->add_no_result_to_queue_with_printf_("fill_area", "fill %d,%d,%d,%d,%s", x1, y1, width, height, color); +} + +void Nextion::fill_area(int x1, int y1, int width, int height, Color color) { + this->add_no_result_to_queue_with_printf_("fill_area", "fill %d,%d,%d,%d,%d", x1, y1, width, height, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::line(int x1, int y1, int x2, int y2, const char *color) { + this->add_no_result_to_queue_with_printf_("line", "line %d,%d,%d,%d,%s", x1, y1, x2, y2, color); +} + +void Nextion::line(int x1, int y1, int x2, int y2, Color color) { + this->add_no_result_to_queue_with_printf_("line", "line %d,%d,%d,%d,%d", x1, y1, x2, y2, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::rectangle(int x1, int y1, int width, int height, const char *color) { + this->add_no_result_to_queue_with_printf_("draw", "draw %d,%d,%d,%d,%s", x1, y1, x1 + width, y1 + height, color); +} + +void Nextion::rectangle(int x1, int y1, int width, int height, Color color) { + this->add_no_result_to_queue_with_printf_("draw", "draw %d,%d,%d,%d,%d", x1, y1, x1 + width, y1 + height, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::circle(int center_x, int center_y, int radius, const char *color) { + this->add_no_result_to_queue_with_printf_("cir", "cir %d,%d,%d,%s", center_x, center_y, radius, color); +} + +void Nextion::circle(int center_x, int center_y, int radius, Color color) { + this->add_no_result_to_queue_with_printf_("cir", "cir %d,%d,%d,%d", center_x, center_y, radius, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::filled_circle(int center_x, int center_y, int radius, const char *color) { + this->add_no_result_to_queue_with_printf_("cirs", "cirs %d,%d,%d,%s", center_x, center_y, radius, color); +} + +void Nextion::filled_circle(int center_x, int center_y, int radius, Color color) { + this->add_no_result_to_queue_with_printf_("cirs", "cirs %d,%d,%d,%d", center_x, center_y, radius, + display::ColorUtil::color_to_565(color)); +} + +#ifdef USE_TIME +void Nextion::set_nextion_rtc_time(time::ESPTime time) { + this->add_no_result_to_queue_with_printf_("rtc0", "rtc0=%u", time.year); + this->add_no_result_to_queue_with_printf_("rtc1", "rtc1=%u", time.month); + this->add_no_result_to_queue_with_printf_("rtc2", "rtc2=%u", time.day_of_month); + this->add_no_result_to_queue_with_printf_("rtc3", "rtc3=%u", time.hour); + this->add_no_result_to_queue_with_printf_("rtc4", "rtc4=%u", time.minute); + this->add_no_result_to_queue_with_printf_("rtc5", "rtc5=%u", time.second); +} +#endif + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_component.cpp b/esphome/components/nextion/nextion_component.cpp new file mode 100644 index 0000000000..bbb2cf6cb2 --- /dev/null +++ b/esphome/components/nextion/nextion_component.cpp @@ -0,0 +1,116 @@ +#include "nextion_component.h" + +namespace esphome { +namespace nextion { + +void NextionComponent::set_background_color(Color bco) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->bco_ = bco; + this->bco_needs_update_ = true; + this->bco_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_background_pressed_color(Color bco2) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + + this->bco2_ = bco2; + this->bco2_needs_update_ = true; + this->bco2_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_foreground_color(Color pco) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->pco_ = pco; + this->pco_needs_update_ = true; + this->pco_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_foreground_pressed_color(Color pco2) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->pco2_ = pco2; + this->pco2_needs_update_ = true; + this->pco2_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_font_id(uint8_t font_id) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->font_id_ = font_id; + this->font_id_needs_update_ = true; + this->font_id_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_visible(bool visible) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->visible_ = visible; + this->visible_needs_update_ = true; + this->visible_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::update_component_settings(bool force_update) { + if (this->nextion_->is_sleeping() || !this->nextion_->is_setup() || !this->visible_is_set_ || + (!this->visible_needs_update_ && !this->visible_)) { + this->needs_to_send_update_ = true; + return; + } + + if (this->visible_needs_update_ || (force_update && this->visible_is_set_)) { + std::string name_to_send = this->variable_name_; + + size_t pos = name_to_send.find_last_of('.'); + if (pos != std::string::npos) { + name_to_send = name_to_send.substr(pos + 1); + } + + this->visible_needs_update_ = false; + + if (this->visible_) { + this->nextion_->show_component(name_to_send.c_str()); + this->send_state_to_nextion(); + } else { + this->nextion_->hide_component(name_to_send.c_str()); + return; + } + } + + if (this->bco_needs_update_ || (force_update && this->bco2_is_set_)) { + this->nextion_->set_component_background_color(this->variable_name_.c_str(), this->bco_); + this->bco_needs_update_ = false; + } + if (this->bco2_needs_update_ || (force_update && this->bco2_is_set_)) { + this->nextion_->set_component_pressed_background_color(this->variable_name_.c_str(), this->bco2_); + this->bco2_needs_update_ = false; + } + if (this->pco_needs_update_ || (force_update && this->pco_is_set_)) { + this->nextion_->set_component_font_color(this->variable_name_.c_str(), this->pco_); + this->pco_needs_update_ = false; + } + if (this->pco2_needs_update_ || (force_update && this->pco2_is_set_)) { + this->nextion_->set_component_pressed_font_color(this->variable_name_.c_str(), this->pco2_); + this->pco2_needs_update_ = false; + } + + if (this->font_id_needs_update_ || (force_update && this->font_id_is_set_)) { + this->nextion_->set_component_font(this->variable_name_.c_str(), this->font_id_); + this->font_id_needs_update_ = false; + } +} +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_component.h b/esphome/components/nextion/nextion_component.h new file mode 100644 index 0000000000..2f3c4f3c16 --- /dev/null +++ b/esphome/components/nextion/nextion_component.h @@ -0,0 +1,49 @@ +#pragma once +#include "esphome/core/defines.h" +#include "esphome/core/color.h" +#include "nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionComponent; + +class NextionComponent : public NextionComponentBase { + public: + void update_component_settings() override { this->update_component_settings(false); }; + + void update_component_settings(bool force_update) override; + + void set_background_color(Color bco); + void set_background_pressed_color(Color bco2); + void set_foreground_color(Color pco); + void set_foreground_pressed_color(Color pco2); + void set_font_id(uint8_t font_id); + void set_visible(bool visible); + + protected: + NextionBase *nextion_; + + bool bco_needs_update_ = false; + bool bco_is_set_ = false; + Color bco_; + bool bco2_needs_update_ = false; + bool bco2_is_set_ = false; + Color bco2_; + bool pco_needs_update_ = false; + bool pco_is_set_ = false; + Color pco_; + bool pco2_needs_update_ = false; + bool pco2_is_set_ = false; + Color pco2_; + uint8_t font_id_ = 0; + bool font_id_needs_update_ = false; + bool font_id_is_set_ = false; + + bool visible_ = true; + bool visible_needs_update_ = false; + bool visible_is_set_ = false; + + // void send_state_to_nextion() = 0; +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_component_base.h b/esphome/components/nextion/nextion_component_base.h new file mode 100644 index 0000000000..71ad803bc4 --- /dev/null +++ b/esphome/components/nextion/nextion_component_base.h @@ -0,0 +1,95 @@ +#pragma once +#include +#include "esphome/core/defines.h" + +namespace esphome { +namespace nextion { + +enum NextionQueueType { + NO_RESULT = 0, + SENSOR = 1, + BINARY_SENSOR = 2, + SWITCH = 3, + TEXT_SENSOR = 4, + WAVEFORM_SENSOR = 5, +}; + +static const char *const NEXTION_QUEUE_TYPE_STRINGS[] = {"NO_RESULT", "SENSOR", "BINARY_SENSOR", + "SWITCH", "TEXT_SENSOR", "WAVEFORM_SENSOR"}; + +class NextionComponentBase; + +class NextionQueue { + public: + virtual ~NextionQueue() = default; + NextionComponentBase *component; + uint32_t queue_time = 0; +}; + +class NextionComponentBase { + public: + virtual ~NextionComponentBase() = default; + + void set_variable_name(const std::string &variable_name, const std::string &variable_name_to_send = "") { + variable_name_ = variable_name; + if (variable_name_to_send.empty()) { + variable_name_to_send_ = variable_name; + } else { + variable_name_to_send_ = variable_name_to_send; + } + } + + virtual void update_component_settings(){}; + virtual void update_component_settings(bool force_update){}; + + virtual void update_component(){}; + virtual void process_sensor(const std::string &variable_name, int state){}; + virtual void process_touch(uint8_t page_id, uint8_t component_id, bool on){}; + virtual void process_text(const std::string &variable_name, const std::string &text_value){}; + virtual void process_bool(const std::string &variable_name, bool on){}; + + virtual void set_state(float state){}; + virtual void set_state(float state, bool publish){}; + virtual void set_state(float state, bool publish, bool send_to_nextion){}; + + virtual void set_state(bool state){}; + virtual void set_state(bool state, bool publish){}; + virtual void set_state(bool state, bool publish, bool send_to_nextion){}; + + virtual void set_state(const std::string &state) {} + virtual void set_state(const std::string &state, bool publish) {} + virtual void set_state(const std::string &state, bool publish, bool send_to_nextion){}; + + uint8_t get_component_id() { return this->component_id_; } + void set_component_id(uint8_t component_id) { component_id_ = component_id; } + + uint8_t get_wave_channel_id() { return this->wave_chan_id_; } + void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; } + + std::vector get_wave_buffer() { return this->wave_buffer_; } + size_t get_wave_buffer_size() { return this->wave_buffer_.size(); } + + std::string get_variable_name() { return this->variable_name_; } + std::string get_variable_name_to_send() { return this->variable_name_to_send_; } + virtual NextionQueueType get_queue_type() { return NextionQueueType::NO_RESULT; } + virtual std::string get_queue_type_string() { return NEXTION_QUEUE_TYPE_STRINGS[this->get_queue_type()]; } + virtual void set_state_from_int(int state_value, bool publish, bool send_to_nextion){}; + virtual void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion){}; + virtual void send_state_to_nextion(){}; + bool get_needs_to_send_update() { return this->needs_to_send_update_; } + uint8_t get_wave_chan_id() { return this->wave_chan_id_; } + void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; } + + protected: + std::string variable_name_; + std::string variable_name_to_send_; + + uint8_t component_id_ = 0; + uint8_t wave_chan_id_ = UINT8_MAX; + std::vector wave_buffer_; + int wave_max_length_ = 255; + + bool needs_to_send_update_; +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp new file mode 100644 index 0000000000..6a681af6c5 --- /dev/null +++ b/esphome/components/nextion/nextion_upload.cpp @@ -0,0 +1,343 @@ + +#include "nextion.h" +#include "esphome/core/application.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { +static const char *const TAG = "nextion_upload"; + +#if defined(USE_TFT_UPLOAD) && (defined(USE_ETHERNET) || defined(USE_WIFI)) + +// Followed guide +// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2 + +int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { + int range_end = 0; + + if (range_start == 0 && this->transfer_buffer_size_ > 16384) { // Start small at the first run in case of a big skip + range_end = 16384 - 1; + } else { + range_end = range_start + this->transfer_buffer_size_ - 1; + } + + if (range_end > this->tft_size_) + range_end = this->tft_size_; + + bool begin_status = false; +#ifdef ARDUINO_ARCH_ESP32 + begin_status = http->begin(this->tft_url_.c_str()); +#endif +#ifdef ARDUINO_ARCH_ESP8266 +#ifndef CLANG_TIDY + http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http->setRedirectLimit(3); + begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str()); +#endif +#endif + + char range_header[64]; + sprintf(range_header, "bytes=%d-%d", range_start, range_end); + + ESP_LOGD(TAG, "Requesting range: %s", range_header); + + int tries = 1; + int code = 0; + while (tries <= 5) { +#ifdef ARDUINO_ARCH_ESP32 + begin_status = http->begin(this->tft_url_.c_str()); +#endif +#ifndef CLANG_TIDY +#ifdef ARDUINO_ARCH_ESP8266 + begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str()); +#endif +#endif + + ++tries; + if (!begin_status) { + ESP_LOGD(TAG, "upload_by_chunks_: connection failed"); + continue; + } + + http->addHeader("Range", range_header); + + code = http->GET(); + if (code == 200 || code == 206) { + break; + } + ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retries(%d/5)", this->tft_url_.c_str(), + HTTPClient::errorToString(code).c_str(), tries); + http->end(); + App.feed_wdt(); + delay(500); // NOLINT + } + + if (tries > 5) { + return -1; + } + + std::string recv_string; + size_t size = 0; + int sent = 0; + int range = range_end - range_start; + + while (sent < range) { + size = http->getStreamPtr()->available(); + if (!size) { + App.feed_wdt(); + delay(0); + continue; + } + int c = http->getStreamPtr()->readBytes( + &this->transfer_buffer_[sent], ((size > this->transfer_buffer_size_) ? this->transfer_buffer_size_ : size)); + sent += c; + } + http->end(); + ESP_LOGN(TAG, "this->content_length_ %d sent %d", this->content_length_, sent); + for (uint32_t i = 0; i < range; i += 4096) { + this->write_array(&this->transfer_buffer_[i], 4096); + this->content_length_ -= 4096; + ESP_LOGN(TAG, "this->content_length_ %d range %d range_end %d range_start %d", this->content_length_, range, + range_end, range_start); + + if (!this->upload_first_chunk_sent_) { + this->upload_first_chunk_sent_ = true; + delay(500); // NOLINT + App.feed_wdt(); + } + + this->recv_ret_string_(recv_string, 2048, true); + if (recv_string[0] == 0x08) { + uint32_t result = 0; + for (int i = 0; i < 4; ++i) { + result += static_cast(recv_string[i + 1]) << (8 * i); + } + if (result > 0) { + ESP_LOGD(TAG, "Nextion reported new range %d", result); + this->content_length_ = this->tft_size_ - result; + return result; + } + } + recv_string.clear(); + } + return range_end + 1; +} + +void Nextion::upload_tft() { + if (this->is_updating_) { + ESP_LOGD(TAG, "Currently updating"); + return; + } + + if (!network_is_connected()) { + ESP_LOGD(TAG, "network is not connected"); + return; + } + + this->is_updating_ = true; + + HTTPClient http; + http.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along + bool begin_status = false; +#ifdef ARDUINO_ARCH_ESP32 + begin_status = http.begin(this->tft_url_.c_str()); +#endif +#ifdef ARDUINO_ARCH_ESP8266 +#ifndef CLANG_TIDY + http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http.setRedirectLimit(3); + begin_status = http.begin(*this->get_wifi_client_(), this->tft_url_.c_str()); +#endif +#endif + + if (!begin_status) { + this->is_updating_ = false; + ESP_LOGD(TAG, "connection failed"); +#ifdef ARDUINO_ARCH_ESP32 + if (psramFound()) + free(this->transfer_buffer_); + else +#endif + delete this->transfer_buffer_; + return; + } else { + ESP_LOGD(TAG, "Connected"); + } + + http.addHeader("Range", "bytes=0-255"); + const char *header_names[] = {"Content-Range"}; + http.collectHeaders(header_names, 1); + ESP_LOGD(TAG, "Requesting URL: %s", this->tft_url_.c_str()); + + http.setReuse(true); + // try up to 5 times. DNS sometimes needs a second try or so + int tries = 1; + int code = http.GET(); + delay(100); // NOLINT + + App.feed_wdt(); + while (code != 200 && code != 206 && tries <= 5) { + ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retrying (%d/5)", this->tft_url_.c_str(), + HTTPClient::errorToString(code).c_str(), tries); + + delay(250); // NOLINT + App.feed_wdt(); + code = http.GET(); + ++tries; + } + + if ((code != 200 && code != 206) || tries > 5) { + this->upload_end_(); + } + + String content_range_string = http.header("Content-Range"); + content_range_string.remove(0, 12); + this->content_length_ = content_range_string.toInt(); + this->tft_size_ = content_length_; + http.end(); + + if (this->content_length_ < 4096) { + ESP_LOGE(TAG, "Failed to get file size"); + this->upload_end_(); + } + + ESP_LOGD(TAG, "Updating Nextion %s...", this->device_model_.c_str()); + // The Nextion will ignore the update command if it is sleeping + + this->send_command_("sleep=0"); + this->set_backlight_brightness(1.0); + delay(250); // NOLINT + + App.feed_wdt(); + + char command[128]; + // Tells the Nextion the content length of the tft file and baud rate it will be sent at + // Once the Nextion accepts the command it will wait until the file is successfully uploaded + // If it fails for any reason a power cycle of the display will be needed + sprintf(command, "whmi-wris %d,%d,1", this->content_length_, this->parent_->get_baud_rate()); + + // Clear serial receive buffer + uint8_t d; + while (this->available()) { + this->read_byte(&d); + }; + + this->send_command_(command); + + App.feed_wdt(); + + std::string response; + ESP_LOGD(TAG, "Waiting for upgrade response"); + this->recv_ret_string_(response, 2000, true); // This can take some time to return + + // The Nextion display will, if it's ready to accept data, send a 0x05 byte. + ESP_LOGD(TAG, "Upgrade response is %s %zu", response.c_str(), response.length()); + + for (int i = 0; i < response.length(); i++) { + ESP_LOGD(TAG, "Available %d : 0x%02X", i, response[i]); + } + + if (response.find(0x05) != std::string::npos) { + ESP_LOGD(TAG, "preparation for tft update done"); + } else { + ESP_LOGD(TAG, "preparation for tft update failed %d \"%s\"", response[0], response.c_str()); + this->upload_end_(); + } + + // Nextion wants 4096 bytes at a time. Make chunk_size a multiple of 4096 +#ifdef ARDUINO_ARCH_ESP32 + uint32_t chunk_size = 8192; + if (psramFound()) { + chunk_size = this->content_length_; + } else { + if (ESP.getFreeHeap() > 40960) { // 32K to keep on hand + int chunk = int((ESP.getFreeHeap() - 32768) / 4096); + chunk_size = chunk * 4096; + chunk_size = chunk_size > 65536 ? 65536 : chunk_size; + } else if (ESP.getFreeHeap() < 10240) { + chunk_size = 4096; + } + } +#else + uint32_t chunk_size = ESP.getFreeHeap() < 10240 ? 4096 : 8192; +#endif + + if (this->transfer_buffer_ == nullptr) { +#ifdef ARDUINO_ARCH_ESP32 + if (psramFound()) { + ESP_LOGD(TAG, "Allocating PSRAM buffer size %d, Free PSRAM size is %u", chunk_size, ESP.getFreePsram()); + this->transfer_buffer_ = (uint8_t *) ps_malloc(chunk_size); + if (this->transfer_buffer_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate buffer size %d!", chunk_size); + this->upload_end_(); + } + } else { +#endif + ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap()); + this->transfer_buffer_ = new uint8_t[chunk_size]; + if (!this->transfer_buffer_) { // Try a smaller size + ESP_LOGD(TAG, "Could not allocate buffer size: %d trying 4096 instead", chunk_size); + chunk_size = 4096; + ESP_LOGD(TAG, "Allocating %d buffer", chunk_size); + this->transfer_buffer_ = new uint8_t[chunk_size]; + + if (!this->transfer_buffer_) + this->upload_end_(); +#ifdef ARDUINO_ARCH_ESP32 + } +#endif + } + + this->transfer_buffer_size_ = chunk_size; + } + + ESP_LOGD(TAG, "Updating tft from \"%s\" with a file size of %d using %zu chunksize, Heap Size %d", + this->tft_url_.c_str(), this->content_length_, this->transfer_buffer_size_, ESP.getFreeHeap()); + + int result = 0; + while (this->content_length_ > 0) { + result = this->upload_by_chunks_(&http, result); + if (result < 0) { + ESP_LOGD(TAG, "Error updating Nextion!"); + this->upload_end_(); + } + App.feed_wdt(); + ESP_LOGD(TAG, "Heap Size %d, Bytes left %d", ESP.getFreeHeap(), this->content_length_); + } + ESP_LOGD(TAG, "Succesfully updated Nextion!"); + + this->upload_end_(); +} + +void Nextion::upload_end_() { + ESP_LOGD(TAG, "Restarting Nextion"); + this->soft_reset(); + delay(1500); // NOLINT + ESP_LOGD(TAG, "Restarting esphome"); + ESP.restart(); +} + +#ifdef ARDUINO_ARCH_ESP8266 +WiFiClient *Nextion::get_wifi_client_() { + if (this->tft_url_.compare(0, 6, "https:") == 0) { + if (this->wifi_client_secure_ == nullptr) { + this->wifi_client_secure_ = new BearSSL::WiFiClientSecure(); + this->wifi_client_secure_->setInsecure(); + this->wifi_client_secure_->setBufferSizes(512, 512); + } + return this->wifi_client_secure_; + } + + if (this->wifi_client_ == nullptr) { + this->wifi_client_ = new WiFiClient(); + } + return this->wifi_client_; +} +#endif + +#else +void Nextion::upload_tft() { ESP_LOGW(TAG, "tft_url, WIFI or Ethernet components are needed. Cannot upload."); } +#endif +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/sensor/__init__.py b/esphome/components/nextion/sensor/__init__.py new file mode 100644 index 0000000000..f8a383e3ac --- /dev/null +++ b/esphome/components/nextion/sensor/__init__.py @@ -0,0 +1,99 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor + +from esphome.const import ( + CONF_ID, + UNIT_EMPTY, + ICON_EMPTY, + CONF_COMPONENT_ID, + DEVICE_CLASS_EMPTY, +) +from .. import nextion_ns, CONF_NEXTION_ID + +from ..base_component import ( + setup_component_core_, + CONFIG_SENSOR_COMPONENT_SCHEMA, + CONF_VARIABLE_NAME, + CONF_COMPONENT_NAME, + CONF_PRECISION, + CONF_WAVE_CHANNEL_ID, + CONF_WAVE_MAX_VALUE, + CONF_WAVEFORM_SEND_LAST_VALUE, + CONF_WAVE_MAX_LENGTH, +) + + +CODEOWNERS = ["@senexcrenshaw"] + +NextionSensor = nextion_ns.class_("NextionSensor", sensor.Sensor, cg.PollingComponent) + + +def CheckWaveID(value): + value = cv.int_(value) + if value < 0 or value > 3: + raise cv.Invalid(f"Valid range for {CONF_WAVE_CHANNEL_ID} is 0-3") + return value + + +def _validate(config): + if CONF_WAVE_CHANNEL_ID in config and CONF_COMPONENT_ID not in config: + raise cv.Invalid( + f"{CONF_COMPONENT_ID} is required when {CONF_WAVE_CHANNEL_ID} is set" + ) + + return config + + +CONFIG_SCHEMA = cv.All( + sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 2, DEVICE_CLASS_EMPTY) + .extend( + { + cv.GenerateID(): cv.declare_id(NextionSensor), + cv.Optional(CONF_PRECISION, default=0): cv.int_range(min=0, max=8), + cv.Optional(CONF_WAVE_CHANNEL_ID): CheckWaveID, + cv.Optional(CONF_COMPONENT_ID): cv.uint8_t, + cv.Optional(CONF_WAVE_MAX_LENGTH, default=255): cv.int_range( + min=1, max=1024 + ), + cv.Optional(CONF_WAVE_MAX_VALUE, default=100): cv.int_range( + min=1, max=1024 + ), + cv.Optional(CONF_WAVEFORM_SEND_LAST_VALUE, default=True): cv.boolean, + } + ) + .extend(CONFIG_SENSOR_COMPONENT_SCHEMA) + .extend(cv.polling_component_schema("never")), + cv.has_exactly_one_key(CONF_COMPONENT_ID, CONF_COMPONENT_NAME, CONF_VARIABLE_NAME), + _validate, +) + + +async def to_code(config): + + hub = await cg.get_variable(config[CONF_NEXTION_ID]) + var = cg.new_Pvariable(config[CONF_ID], hub) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + + cg.add(hub.register_sensor_component(var)) + + await setup_component_core_(var, config, ".val") + + if CONF_PRECISION in config: + cg.add(var.set_precision(config[CONF_PRECISION])) + + if CONF_COMPONENT_ID in config: + cg.add(var.set_component_id(config[CONF_COMPONENT_ID])) + + if CONF_WAVE_CHANNEL_ID in config: + cg.add(var.set_wave_channel_id(config[CONF_WAVE_CHANNEL_ID])) + + if CONF_WAVEFORM_SEND_LAST_VALUE in config: + cg.add(var.set_waveform_send_last_value(config[CONF_WAVEFORM_SEND_LAST_VALUE])) + + if CONF_WAVE_MAX_VALUE in config: + cg.add(var.set_wave_max_value(config[CONF_WAVE_MAX_VALUE])) + + if CONF_WAVE_MAX_LENGTH in config: + cg.add(var.set_wave_max_length(config[CONF_WAVE_MAX_LENGTH])) diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp new file mode 100644 index 0000000000..bbcb465d85 --- /dev/null +++ b/esphome/components/nextion/sensor/nextion_sensor.cpp @@ -0,0 +1,110 @@ +#include "nextion_sensor.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { + +static const char *const TAG = "nextion_sensor"; + +void NextionSensor::process_sensor(const std::string &variable_name, int state) { + if (!this->nextion_->is_setup()) + return; + + if (this->wave_chan_id_ == UINT8_MAX && this->variable_name_ == variable_name) { + this->publish_state(state); + ESP_LOGD(TAG, "Processed sensor \"%s\" state %d", variable_name.c_str(), state); + } +} + +void NextionSensor::add_to_wave_buffer(float state) { + this->needs_to_send_update_ = true; + + int wave_state = (int) ((state / (float) this->wave_maxvalue_) * 100); + + wave_buffer_.push_back(wave_state); + + if (this->wave_buffer_.size() > this->wave_max_length_) { + this->wave_buffer_.erase(this->wave_buffer_.begin()); + } +} + +void NextionSensor::update() { + if (!this->nextion_->is_setup()) + return; + + if (this->wave_chan_id_ == UINT8_MAX) { + this->nextion_->add_to_get_queue(this); + } else { + if (this->send_last_value_) { + this->add_to_wave_buffer(this->last_value_); + } + + this->wave_update_(); + } +} + +void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) { + if (!this->nextion_->is_setup()) + return; + + if (isnan(state)) + return; + + if (this->wave_chan_id_ == UINT8_MAX) { + if (send_to_nextion) { + if (this->nextion_->is_sleeping() || !this->visible_) { + this->needs_to_send_update_ = true; + } else { + this->needs_to_send_update_ = false; + + if (this->precision_ > 0) { + double to_multiply = pow(10, this->precision_); + int state_value = (int) (state * to_multiply); + + this->nextion_->add_no_result_to_queue_with_set(this, (int) state_value); + } else { + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + } + } + } + } else { + if (this->send_last_value_) { + this->last_value_ = state; // Update will handle setting the buffer + } else { + this->add_to_wave_buffer(state); + } + } + + if (this->wave_chan_id_ == UINT8_MAX) { + if (publish) { + this->publish_state(state); + } else { + this->raw_state = state; + this->state = state; + this->has_state_ = true; + } + } + this->update_component_settings(); + + ESP_LOGN(TAG, "Wrote state for sensor \"%s\" state %lf", this->variable_name_.c_str(), state); +} + +void NextionSensor::wave_update_() { + if (this->nextion_->is_sleeping() || this->wave_buffer_.empty()) { + return; + } + +#ifdef NEXTION_PROTOCOL_LOG + size_t buffer_to_send = + this->wave_buffer_.size() < 255 ? this->wave_buffer_.size() : 255; // ADDT command can only send 255 + + ESP_LOGN(TAG, "wave_update send %zu of %zu value(s) to wave nextion component id %d and wave channel id %d", + buffer_to_send, this->wave_buffer_.size(), this->component_id_, this->wave_chan_id_); +#endif + + this->nextion_->add_addt_command_to_queue(this); +} + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/sensor/nextion_sensor.h b/esphome/components/nextion/sensor/nextion_sensor.h new file mode 100644 index 0000000000..e4dde9a513 --- /dev/null +++ b/esphome/components/nextion/sensor/nextion_sensor.h @@ -0,0 +1,49 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "../nextion_component.h" +#include "../nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionSensor; + +class NextionSensor : public NextionComponent, public sensor::Sensor, public PollingComponent { + public: + NextionSensor(NextionBase *nextion) { this->nextion_ = nextion; } + void send_state_to_nextion() override { this->set_state(this->state, false, true); }; + + void update_component() override { this->update(); } + void update() override; + void add_to_wave_buffer(float state); + void set_precision(uint8_t precision) { this->precision_ = precision; } + void set_component_id(uint8_t component_id) { component_id_ = component_id; } + void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; } + void set_wave_max_value(uint32_t wave_maxvalue) { this->wave_maxvalue_ = wave_maxvalue; } + void process_sensor(const std::string &variable_name, int state) override; + + void set_state(float state) override { this->set_state(state, true, true); } + void set_state(float state, bool publish) override { this->set_state(state, publish, true); } + void set_state(float state, bool publish, bool send_to_nextion) override; + + void set_waveform_send_last_value(bool send_last_value) { this->send_last_value_ = send_last_value; } + uint8_t get_wave_chan_id() { return this->wave_chan_id_; } + void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; } + NextionQueueType get_queue_type() override { + return this->wave_chan_id_ == UINT8_MAX ? NextionQueueType::SENSOR : NextionQueueType::WAVEFORM_SENSOR; + } + void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {} + void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override { + this->set_state(state_value, publish, send_to_nextion); + } + + protected: + uint8_t precision_ = 0; + uint32_t wave_maxvalue_ = 255; + + float last_value_ = 0; + bool send_last_value_ = true; + void wave_update_(); +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/switch/__init__.py b/esphome/components/nextion/switch/__init__.py new file mode 100644 index 0000000000..068681fa14 --- /dev/null +++ b/esphome/components/nextion/switch/__init__.py @@ -0,0 +1,39 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch + +from esphome.const import CONF_ID +from .. import nextion_ns, CONF_NEXTION_ID + +from ..base_component import ( + setup_component_core_, + CONF_COMPONENT_NAME, + CONF_VARIABLE_NAME, + CONFIG_SWITCH_COMPONENT_SCHEMA, +) + +CODEOWNERS = ["@senexcrenshaw"] + +NextionSwitch = nextion_ns.class_("NextionSwitch", switch.Switch, cg.PollingComponent) + +CONFIG_SCHEMA = cv.All( + switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(NextionSwitch), + } + ) + .extend(CONFIG_SWITCH_COMPONENT_SCHEMA) + .extend(cv.polling_component_schema("never")), + cv.has_exactly_one_key(CONF_COMPONENT_NAME, CONF_VARIABLE_NAME), +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_NEXTION_ID]) + var = cg.new_Pvariable(config[CONF_ID], hub) + await cg.register_component(var, config) + await switch.register_switch(var, config) + + cg.add(hub.register_switch_component(var)) + + await setup_component_core_(var, config, ".val") diff --git a/esphome/components/nextion/switch/nextion_switch.cpp b/esphome/components/nextion/switch/nextion_switch.cpp new file mode 100644 index 0000000000..1f32ad3425 --- /dev/null +++ b/esphome/components/nextion/switch/nextion_switch.cpp @@ -0,0 +1,52 @@ +#include "nextion_switch.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { + +static const char *const TAG = "nextion_switch"; + +void NextionSwitch::process_bool(const std::string &variable_name, bool on) { + if (!this->nextion_->is_setup()) + return; + if (this->variable_name_ == variable_name) { + this->publish_state(on); + + ESP_LOGD(TAG, "Processed switch \"%s\" state %s", variable_name.c_str(), state ? "ON" : "OFF"); + } +} + +void NextionSwitch::update() { + if (!this->nextion_->is_setup()) + return; + this->nextion_->add_to_get_queue(this); +} + +void NextionSwitch::set_state(bool state, bool publish, bool send_to_nextion) { + if (!this->nextion_->is_setup()) + return; + + if (send_to_nextion) { + if (this->nextion_->is_sleeping() || !this->visible_) { + this->needs_to_send_update_ = true; + } else { + this->needs_to_send_update_ = false; + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + } + } + if (publish) { + this->publish_state(state); + } else { + this->state = state; + } + + this->update_component_settings(); + + ESP_LOGN(TAG, "Updated switch \"%s\" state %s", this->variable_name_.c_str(), ONOFF(state)); +} + +void NextionSwitch::write_state(bool state) { this->set_state(state); } + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/switch/nextion_switch.h b/esphome/components/nextion/switch/nextion_switch.h new file mode 100644 index 0000000000..1548287473 --- /dev/null +++ b/esphome/components/nextion/switch/nextion_switch.h @@ -0,0 +1,34 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" +#include "../nextion_component.h" +#include "../nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionSwitch; + +class NextionSwitch : public NextionComponent, public switch_::Switch, public PollingComponent { + public: + NextionSwitch(NextionBase *nextion) { this->nextion_ = nextion; } + + void update() override; + void update_component() override { this->update(); } + void process_bool(const std::string &variable_name, bool on) override; + + void set_state(bool state) override { this->set_state(state, true, true); } + void set_state(bool state, bool publish) override { this->set_state(state, publish, true); } + void set_state(bool state, bool publish, bool send_to_nextion) override; + + void send_state_to_nextion() override { this->set_state(this->state, false, true); }; + NextionQueueType get_queue_type() override { return NextionQueueType::SWITCH; } + void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {} + void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override { + this->set_state(state_value != 0, publish, send_to_nextion); + } + + protected: + void write_state(bool state) override; +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/text_sensor/__init__.py b/esphome/components/nextion/text_sensor/__init__.py new file mode 100644 index 0000000000..9c170dd807 --- /dev/null +++ b/esphome/components/nextion/text_sensor/__init__.py @@ -0,0 +1,38 @@ +from esphome.components import text_sensor +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import CONF_ID + +from .. import nextion_ns, CONF_NEXTION_ID + +from ..base_component import ( + setup_component_core_, + CONFIG_TEXT_COMPONENT_SCHEMA, +) + +CODEOWNERS = ["@senexcrenshaw"] + +NextionTextSensor = nextion_ns.class_( + "NextionTextSensor", text_sensor.TextSensor, cg.PollingComponent +) + +CONFIG_SCHEMA = ( + text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(NextionTextSensor), + } + ) + .extend(CONFIG_TEXT_COMPONENT_SCHEMA) + .extend(cv.polling_component_schema("never")) +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_NEXTION_ID]) + var = cg.new_Pvariable(config[CONF_ID], hub) + await cg.register_component(var, config) + await text_sensor.register_text_sensor(var, config) + + cg.add(hub.register_textsensor_component(var)) + + await setup_component_core_(var, config, ".txt") diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.cpp b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp new file mode 100644 index 0000000000..08f032df74 --- /dev/null +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp @@ -0,0 +1,49 @@ +#include "nextion_textsensor.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { +static const char *const TAG = "nextion_textsensor"; + +void NextionTextSensor::process_text(const std::string &variable_name, const std::string &text_value) { + if (!this->nextion_->is_setup()) + return; + if (this->variable_name_ == variable_name) { + this->publish_state(text_value); + ESP_LOGD(TAG, "Processed text_sensor \"%s\" state \"%s\"", variable_name.c_str(), text_value.c_str()); + } +} + +void NextionTextSensor::update() { + if (!this->nextion_->is_setup()) + return; + this->nextion_->add_to_get_queue(this); +} + +void NextionTextSensor::set_state(const std::string &state, bool publish, bool send_to_nextion) { + if (!this->nextion_->is_setup()) + return; + + if (send_to_nextion) { + if (this->nextion_->is_sleeping() || !this->visible_) { + this->needs_to_send_update_ = true; + } else { + this->nextion_->add_no_result_to_queue_with_set(this, state); + } + } + + if (publish) { + this->publish_state(state); + } else { + this->state = state; + this->has_state_ = true; + } + + this->update_component_settings(); + + ESP_LOGN(TAG, "Wrote state for text_sensor \"%s\" state \"%s\"", this->variable_name_.c_str(), state.c_str()); +} + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.h b/esphome/components/nextion/text_sensor/nextion_textsensor.h new file mode 100644 index 0000000000..5716d0a008 --- /dev/null +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.h @@ -0,0 +1,32 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "../nextion_component.h" +#include "../nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionTextSensor; + +class NextionTextSensor : public NextionComponent, public text_sensor::TextSensor, public PollingComponent { + public: + NextionTextSensor(NextionBase *nextion) { this->nextion_ = nextion; } + void update() override; + void update_component() override { this->update(); } + void on_state_changed(const std::string &state); + + void process_text(const std::string &variable_name, const std::string &text_value) override; + + void set_state(const std::string &state, bool publish) override { this->set_state(state, publish, true); } + void set_state(const std::string &state) override { this->set_state(state, true, true); } + void set_state(const std::string &state, bool publish, bool send_to_nextion) override; + + void send_state_to_nextion() override { this->set_state(this->state, false, true); }; + NextionQueueType get_queue_type() override { return NextionQueueType::TEXT_SENSOR; } + void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override {} + void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override { + this->set_state(state_value, publish, send_to_nextion); + } +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index 6dd62070da..cd54290c73 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -56,6 +56,7 @@ class ESP8266SoftwareSerial { class UARTComponent : public Component, public Stream { public: void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; } + uint32_t get_baud_rate() const { return baud_rate_; } uint32_t get_config(); diff --git a/script/ci-custom.py b/script/ci-custom.py index 02f193c6e0..d79e5b5e2f 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +from helpers import git_ls_files, filter_changed import codecs import collections import fnmatch @@ -12,7 +13,6 @@ import functools import argparse sys.path.append(os.path.dirname(__file__)) -from helpers import git_ls_files, filter_changed def find_all(a_str, sub): @@ -562,6 +562,7 @@ def lint_inclusive_language(fname, match): "esphome/components/number/number.h", "esphome/components/output/binary_output.h", "esphome/components/output/float_output.h", + "esphome/components/nextion/nextion_base.h", "esphome/components/sensor/sensor.h", "esphome/components/stepper/stepper.h", "esphome/components/switch/switch.h", diff --git a/tests/test1.yaml b/tests/test1.yaml index 1c522a23d4..f9fe7d106d 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1055,10 +1055,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 @@ -1882,11 +1878,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 diff --git a/tests/test3.yaml b/tests/test3.yaml index c709539309..acd975d794 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -269,6 +269,7 @@ wled: adalight: + sensor: - platform: apds9960 type: proximity @@ -534,6 +535,15 @@ sensor: 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 @@ -605,7 +615,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 @@ -653,6 +670,11 @@ 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 @@ -704,6 +726,10 @@ switch: switches: - id: custom_switch name: Custom Switch + - platform: nextion + id: r0 + name: 'R0 Switch' + component_name: page0.r0 custom_component: lambda: |- @@ -1086,6 +1112,16 @@ 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 From 54eb6070fb47da94e208e9daac60b64273d2fb3c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 15 Jul 2021 13:55:58 +1200 Subject: [PATCH 1014/1841] Bump version to v1.20.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 30c4748933..35f1e560e1 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 20 -PATCH_VERSION = "0b1" +PATCH_VERSION = "0b2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 628a94bad3e5964ef64101a61407077f6ab9842e Mon Sep 17 00:00:00 2001 From: Sean Vig Date: Wed, 14 Jul 2021 23:45:41 -0400 Subject: [PATCH 1015/1841] Fix ethernet component hostname handling (#2010) Co-authored-by: Otto Winter --- .gitignore | 3 + .../ethernet/ethernet_component.cpp | 213 +++++++++--------- .../components/ethernet/ethernet_component.h | 10 +- 3 files changed, 120 insertions(+), 106 deletions(-) diff --git a/.gitignore b/.gitignore index a24550ad54..954ecb2cb8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ __pycache__/ # Intellij Idea .idea +# Vim +*.swp + # Hide some OS X stuff .DS_Store .AppleDouble diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 22007caafa..52c184eef6 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -25,6 +25,13 @@ static const char *const TAG = "ethernet"; EthernetComponent *global_eth_component; +#define ESPHL_ERROR_CHECK(err, message) \ + if (err != ESP_OK) { \ + ESP_LOGE(TAG, message ": (%d) %s", err, esp_err_to_name(err)); \ + this->mark_failed(); \ + return; \ + } + EthernetComponent::EthernetComponent() { global_eth_component = this; } void EthernetComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up Ethernet..."); @@ -36,103 +43,6 @@ void EthernetComponent::setup() { this->power_pin_->setup(); } - this->start_connect_(); - -#ifdef USE_MDNS - network_setup_mdns(); -#endif -} -void EthernetComponent::loop() { - const uint32_t now = millis(); - if (!this->connected_ && !this->last_connected_ && now - this->connect_begin_ > 15000) { - ESP_LOGW(TAG, "Connecting via ethernet failed! Re-connecting..."); - this->start_connect_(); - return; - } - - if (this->connected_ == this->last_connected_) - // nothing changed - return; - - if (this->connected_) { - // connection established - ESP_LOGI(TAG, "Connected via Ethernet!"); - this->dump_connect_params_(); - this->status_clear_warning(); - } else { - // connection lost - ESP_LOGW(TAG, "Connection via Ethernet lost! Re-connecting..."); - this->start_connect_(); - } - - this->last_connected_ = this->connected_; - - network_tick_mdns(); -} -void EthernetComponent::dump_config() { - ESP_LOGCONFIG(TAG, "Ethernet:"); - this->dump_connect_params_(); - LOG_PIN(" Power Pin: ", this->power_pin_); - ESP_LOGCONFIG(TAG, " MDC Pin: %u", this->mdc_pin_); - ESP_LOGCONFIG(TAG, " MDIO Pin: %u", this->mdio_pin_); - ESP_LOGCONFIG(TAG, " Type: %s", this->type_ == ETHERNET_TYPE_LAN8720 ? "LAN8720" : "TLK110"); -} -float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } -bool EthernetComponent::can_proceed() { return this->is_connected(); } -IPAddress EthernetComponent::get_ip_address() { - tcpip_adapter_ip_info_t ip; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); - return IPAddress(ip.ip.addr); -} - -void EthernetComponent::on_wifi_event_(system_event_id_t event, system_event_info_t info) { - const char *event_name; - - switch (event) { - case SYSTEM_EVENT_ETH_START: - event_name = "ETH started"; - break; - case SYSTEM_EVENT_ETH_STOP: - event_name = "ETH stopped"; - this->connected_ = false; - break; - case SYSTEM_EVENT_ETH_CONNECTED: - event_name = "ETH connected"; - break; - case SYSTEM_EVENT_ETH_DISCONNECTED: - event_name = "ETH disconnected"; - this->connected_ = false; - break; - case SYSTEM_EVENT_ETH_GOT_IP: - event_name = "ETH Got IP"; - this->connected_ = true; - break; - default: - return; - } - - ESP_LOGV(TAG, "[Ethernet event] %s (num=%d)", event_name, event); -} - -#define ESPHL_ERROR_CHECK(err, message) \ - if (err != ESP_OK) { \ - ESP_LOGE(TAG, message ": %d", err); \ - this->mark_failed(); \ - return; \ - } - -void EthernetComponent::start_connect_() { - this->connect_begin_ = millis(); - this->status_set_warning(); - - esp_err_t err; - if (this->initialized_) { - // already initialized - err = esp_eth_enable(); - ESPHL_ERROR_CHECK(err, "ETH enable error"); - return; - } - switch (this->type_) { case ETHERNET_TYPE_LAN8720: { memcpy(&this->eth_config, &phy_lan8720_default_ethernet_config, sizeof(eth_config_t)); @@ -160,16 +70,111 @@ void EthernetComponent::start_connect_() { tcpipInit(); + esp_err_t err; err = esp_eth_init(&this->eth_config); - if (err != ESP_OK) { - ESP_LOGE(TAG, "ETH init error: %d", err); - this->mark_failed(); - return; + ESPHL_ERROR_CHECK(err, "ETH init error"); + err = esp_eth_enable(); + ESPHL_ERROR_CHECK(err, "ETH enable error"); + +#ifdef USE_MDNS + network_setup_mdns(); +#endif +} +void EthernetComponent::loop() { + const uint32_t now = millis(); + + switch (this->state_) { + case EthernetComponentState::STOPPED: + if (this->started_) { + ESP_LOGI(TAG, "Starting ethernet connection"); + this->state_ = EthernetComponentState::CONNECTING; + this->start_connect_(); + } + break; + case EthernetComponentState::CONNECTING: + if (!this->started_) { + ESP_LOGI(TAG, "Stopped ethernet connection"); + this->state_ = EthernetComponentState::STOPPED; + } else if (this->connected_) { + // connection established + ESP_LOGI(TAG, "Connected via Ethernet!"); + this->state_ = EthernetComponentState::CONNECTED; + + this->dump_connect_params_(); + this->status_clear_warning(); + + network_tick_mdns(); + } else if (now - this->connect_begin_ > 15000) { + ESP_LOGW(TAG, "Connecting via ethernet failed! Re-connecting..."); + this->start_connect_(); + } + break; + case EthernetComponentState::CONNECTED: + if (!this->started_) { + ESP_LOGI(TAG, "Stopped ethernet connection"); + this->state_ = EthernetComponentState::STOPPED; + } else if (!this->connected_) { + ESP_LOGW(TAG, "Connection via Ethernet lost! Re-connecting..."); + this->state_ = EthernetComponentState::CONNECTING; + this->start_connect_(); + } + break; + } +} +void EthernetComponent::dump_config() { + ESP_LOGCONFIG(TAG, "Ethernet:"); + this->dump_connect_params_(); + LOG_PIN(" Power Pin: ", this->power_pin_); + ESP_LOGCONFIG(TAG, " MDC Pin: %u", this->mdc_pin_); + ESP_LOGCONFIG(TAG, " MDIO Pin: %u", this->mdio_pin_); + ESP_LOGCONFIG(TAG, " Type: %s", this->type_ == ETHERNET_TYPE_LAN8720 ? "LAN8720" : "TLK110"); +} +float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } +bool EthernetComponent::can_proceed() { return this->is_connected(); } +IPAddress EthernetComponent::get_ip_address() { + tcpip_adapter_ip_info_t ip; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); + return IPAddress(ip.ip.addr); +} + +void EthernetComponent::on_wifi_event_(system_event_id_t event, system_event_info_t info) { + const char *event_name; + + switch (event) { + case SYSTEM_EVENT_ETH_START: + event_name = "ETH started"; + this->started_ = true; + break; + case SYSTEM_EVENT_ETH_STOP: + event_name = "ETH stopped"; + this->started_ = false; + this->connected_ = false; + break; + case SYSTEM_EVENT_ETH_CONNECTED: + event_name = "ETH connected"; + break; + case SYSTEM_EVENT_ETH_DISCONNECTED: + event_name = "ETH disconnected"; + this->connected_ = false; + break; + case SYSTEM_EVENT_ETH_GOT_IP: + event_name = "ETH Got IP"; + this->connected_ = true; + break; + default: + return; } - this->initialized_ = true; + ESP_LOGV(TAG, "[Ethernet event] %s (num=%d)", event_name, event); +} - tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str()); +void EthernetComponent::start_connect_() { + this->connect_begin_ = millis(); + this->status_set_warning(); + + esp_err_t err; + err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str()); + ESPHL_ERROR_CHECK(err, "ETH set hostname error"); tcpip_adapter_ip_info_t info; if (this->manual_ip_.has_value()) { @@ -220,7 +225,7 @@ void EthernetComponent::eth_phy_power_enable_(bool enable) { delay(1); global_eth_component->orig_power_enable_fun_(enable); } -bool EthernetComponent::is_connected() { return this->connected_ && this->last_connected_; } +bool EthernetComponent::is_connected() { return this->state_ == EthernetComponentState::CONNECTED; } void EthernetComponent::dump_connect_params_() { tcpip_adapter_ip_info_t ip; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 2dbd8ccd9d..326cd1edea 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -26,6 +26,12 @@ struct ManualIP { IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default. }; +enum class EthernetComponentState { + STOPPED, + CONNECTING, + CONNECTED, +}; + class EthernetComponent : public Component { public: EthernetComponent(); @@ -65,9 +71,9 @@ class EthernetComponent : public Component { eth_clock_mode_t clk_mode_{ETH_CLOCK_GPIO0_IN}; optional manual_ip_{}; - bool initialized_{false}; + bool started_{false}; bool connected_{false}; - bool last_connected_{false}; + EthernetComponentState state_{EthernetComponentState::STOPPED}; uint32_t connect_begin_; eth_config_t eth_config; eth_phy_power_enable_func orig_power_enable_fun_; From 45d368e3a199dfd4e995d67a51c6173b02ce6428 Mon Sep 17 00:00:00 2001 From: Sean Vig Date: Thu, 15 Jul 2021 00:12:48 -0400 Subject: [PATCH 1016/1841] Always tick mdns in ethernet component (#2018) --- esphome/components/ethernet/ethernet_component.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 52c184eef6..963ee267b3 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -102,8 +102,6 @@ void EthernetComponent::loop() { this->dump_connect_params_(); this->status_clear_warning(); - - network_tick_mdns(); } else if (now - this->connect_begin_ > 15000) { ESP_LOGW(TAG, "Connecting via ethernet failed! Re-connecting..."); this->start_connect_(); @@ -120,6 +118,8 @@ void EthernetComponent::loop() { } break; } + + network_tick_mdns(); } void EthernetComponent::dump_config() { ESP_LOGCONFIG(TAG, "Ethernet:"); From cc7dbeada689a0017cc719d01735a31c5f2ac883 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 15 Jul 2021 21:30:04 +0200 Subject: [PATCH 1017/1841] Refactor docker build system and workflows (#2023) --- .github/workflows/ci-docker.yml | 47 ++-- .github/workflows/ci.yml | 178 ++++++------- .github/workflows/docker-lint-build.yml | 108 ++++++-- .github/workflows/matchers/pytest.json | 19 ++ .github/workflows/release-dev.yml | 247 ------------------- .github/workflows/release.yml | 315 ++++++------------------ docker/Dockerfile | 2 +- docker/Dockerfile.hassio | 2 +- docker/Dockerfile.lint | 3 +- docker/build.py | 177 +++++++++++++ esphome/const.py | 6 +- script/bump-docker-base-version.py | 50 ---- script/bump-version.py | 10 +- 13 files changed, 456 insertions(+), 708 deletions(-) create mode 100644 .github/workflows/matchers/pytest.json delete mode 100644 .github/workflows/release-dev.yml create mode 100755 docker/build.py delete mode 100755 script/bump-docker-base-version.py diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 91ec88aeb3..45fd3e141b 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -18,38 +18,23 @@ jobs: name: Build docker containers runs-on: ubuntu-latest strategy: - fail-fast: false matrix: arch: [amd64, armv7, aarch64] - build_type: ["hassio", "docker"] + build_type: ["ha-addon", "docker", "lint"] steps: - - uses: actions/checkout@v2 - - name: Set up env variables - run: | - base_version="3.4.0" + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' + - name: Set TAG + run: | + echo "TAG=check" >> $GITHUB_ENV - if [[ "${{ matrix.build_type }}" == "hassio" ]]; then - build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-hassio-${{ matrix.arch }}" - dockerfile="docker/Dockerfile.hassio" - else - build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-${{ matrix.arch }}" - dockerfile="docker/Dockerfile" - fi - - echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV - echo "BUILD_TO=${build_to}" >> $GITHUB_ENV - echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV - - name: Pull for cache - run: | - docker pull "${BUILD_TO}:dev" || true - - name: Register QEMU binfmt - run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes - - run: | - docker build \ - --build-arg "BUILD_FROM=${BUILD_FROM}" \ - --build-arg "BUILD_VERSION=ci" \ - --cache-from "${BUILD_TO}:dev" \ - --file "${DOCKERFILE}" \ - . + - name: Run build + run: | + docker/build.py \ + --tag "${TAG}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + build diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 121b3f1339..de777d5a3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,40 +4,36 @@ name: CI on: push: - # On dev branch release-dev already performs CI checks - # On other branches the `pull_request` trigger will be used - branches: [beta, release] + branches: [dev, beta, release] pull_request: jobs: - lint-clang-format: + ci-with-container: + name: ${{ matrix.name }} runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - steps: - - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - name: Run clang-format - run: script/clang-format -i - - name: Suggest changes - run: script/ci-suggest-changes - - lint-clang-tidy: - runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files strategy: fail-fast: false matrix: - split: [1, 2, 3, 4] + include: + - id: clang-format + name: Run script/clang-format + - id: clang-tidy + name: Run script/clang-tidy 1/4 + split: 1 + - id: clang-tidy + name: Run script/clang-tidy 2/4 + split: 2 + - id: clang-tidy + name: Run script/clang-tidy 3/4 + split: 3 + - id: clang-tidy + name: Run script/clang-tidy 4/4 + split: 4 + + # cpp lint job runs with esphome-lint docker image so that clang-format-* + # doesn't have to be installed + container: esphome/esphome-lint:1.1 steps: - uses: actions/checkout@v2 # Set up the pio project so that the cpp checks know how files are compiled @@ -45,26 +41,57 @@ jobs: - name: Set up platformio environment run: pio init --ide atom - - name: Register problem matchers run: | echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" echo "::add-matcher::.github/workflows/matchers/gcc.json" + + - name: Run clang-format + run: script/clang-format -i + if: ${{ matrix.id == 'clang-format' }} + - name: Run clang-tidy run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }} + if: ${{ matrix.id == 'clang-tidy' }} + - name: Suggest changes run: script/ci-suggest-changes - lint-python: + ci: # Don't use the esphome-lint docker image because it may contain outdated requirements. # This way, all dependencies are cached via the cache action. + name: ${{ matrix.name }} runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - id: ci-custom + name: Run script/ci-custom + - id: lint-python + name: Run script/lint-python + - id: test + file: tests/test1.yaml + name: Test tests/test1.yaml + - id: test + file: tests/test2.yaml + name: Test tests/test2.yaml + - id: test + file: tests/test3.yaml + name: Test tests/test3.yaml + - id: test + file: tests/test4.yaml + name: Test tests/test4.yaml + - id: pytest + name: Run pytest + steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.7' + - name: Cache pip modules uses: actions/cache@v1 with: @@ -72,6 +99,17 @@ jobs: key: esphome-pip-3.7-${{ hashFiles('setup.py') }} restore-keys: | esphome-pip-3.7- + + # Use per test platformio cache because tests have different platform versions + - name: Cache ~/.platformio + uses: actions/cache@v1 + with: + path: ~/.platformio + key: test-home-platformio-${{ matrix.file }}-${{ hashFiles('esphome/core/config.py') }} + restore-keys: | + test-home-platformio-${{ matrix.file }}- + if: ${{ matrix.id == 'test' }} + - name: Set up python environment run: script/setup @@ -80,82 +118,22 @@ jobs: echo "::add-matcher::.github/workflows/matchers/ci-custom.json" echo "::add-matcher::.github/workflows/matchers/lint-python.json" echo "::add-matcher::.github/workflows/matchers/python.json" + echo "::add-matcher::.github/workflows/matchers/pytest.json" + echo "::add-matcher::.github/workflows/matchers/gcc.json" + - name: Lint Custom - run: script/ci-custom.py + run: | + script/ci-custom.py + script/build_codeowners.py --check + if: ${{ matrix.id == 'ci-custom' }} - name: Lint Python run: script/lint-python - - name: Lint CODEOWNERS - run: script/build_codeowners.py --check + if: ${{ matrix.id == 'lint-python' }} - test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - test: - - test1 - - test2 - - test3 - - test4 - - test5 - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - # Use per test platformio cache because tests have different platform versions - - name: Cache ~/.platformio - uses: actions/cache@v1 - with: - path: ~/.platformio - key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }} - restore-keys: | - test-home-platformio-${{ matrix.test }}- - - name: Set up environment - run: script/setup + - run: esphome compile ${{ matrix.file }} + if: ${{ matrix.id == 'test' }} - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/gcc.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - run: esphome compile tests/${{ matrix.test }}.yaml - - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up environment - run: script/setup - - name: Install Github Actions annotator - run: pip install pytest-github-actions-annotate-failures - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/python.json" - name: Run pytest run: | - pytest \ - -qq \ - --durations=10 \ - -o console_output_style=count \ - tests + pytest -vv --tb=native tests + if: ${{ matrix.id == 'pytest' }} diff --git a/.github/workflows/docker-lint-build.yml b/.github/workflows/docker-lint-build.yml index d254ac332a..32aec87cdd 100644 --- a/.github/workflows/docker-lint-build.yml +++ b/.github/workflows/docker-lint-build.yml @@ -13,30 +13,88 @@ on: - '.github/workflows/docker-lint-build.yml' jobs: - publish-docker-lint-iage: - name: Build docker containers + deploy-docker: + name: Build and publish docker containers + if: github.repository == 'esphome/esphome' runs-on: ubuntu-latest + strategy: + matrix: + arch: [amd64, armv7, aarch64] + build_type: ["lint"] steps: - - uses: actions/checkout@v2 - - name: Set TAG - run: | - echo "TAG=1.1" >> $GITHUB_ENV - - name: Pull for cache - run: | - docker pull "esphome/esphome-lint:latest" || true - - name: Build - run: | - docker build \ - --cache-from "esphome/esphome-lint:latest" \ - --file "docker/Dockerfile.lint" \ - --tag "esphome/esphome-lint:latest" \ - --tag "esphome/esphome-lint:${TAG}" \ - . - - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - run: | - docker push "esphome/esphome-lint:${TAG}" - docker push "esphome/esphome-lint:latest" + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' + - name: Set TAG + run: | + echo "TAG=1.1" >> $GITHUB_ENV + + - name: Run build + run: | + docker/build.py \ + --tag "${TAG}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + build + + - name: Log in to docker hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Run push + run: | + docker/build.py \ + --tag "${TAG}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + push + + deploy-docker-manifest: + if: github.repository == 'esphome/esphome' + runs-on: ubuntu-latest + needs: [deploy-docker] + strategy: + matrix: + build_type: ["lint"] + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' + - name: Set TAG + run: | + echo "TAG=1.1" >> $GITHUB_ENV + - name: Enable experimental manifest support + run: | + mkdir -p ~/.docker + echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json + + - name: Log in to docker hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Run manifest + run: | + docker/build.py \ + --tag "${TAG}" \ + --build-type "${{ matrix.build_type }}" \ + manifest diff --git a/.github/workflows/matchers/pytest.json b/.github/workflows/matchers/pytest.json new file mode 100644 index 0000000000..0eb8f050e6 --- /dev/null +++ b/.github/workflows/matchers/pytest.json @@ -0,0 +1,19 @@ +{ + "problemMatcher": [ + { + "owner": "pytest", + "fileLocation": "absolute", + "pattern": [ + { + "regexp": "^\\s+File \"(.*)\", line (\\d+), in (.*)$", + "file": 1, + "line": 2 + }, + { + "regexp": "^\\s+(.*)$", + "message": 1 + } + ] + } + ] +} diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml deleted file mode 100644 index f8b90d524f..0000000000 --- a/.github/workflows/release-dev.yml +++ /dev/null @@ -1,247 +0,0 @@ -name: Publish dev releases to docker hub - -on: - push: - branches: - - dev - -jobs: - # THE LINT/TEST JOBS ARE COPIED FROM ci.yaml - - lint-clang-format: - runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - steps: - - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - name: Run clang-format - run: script/clang-format -i - - name: Suggest changes - run: script/ci-suggest-changes - - lint-clang-tidy: - runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files - strategy: - fail-fast: false - matrix: - split: [1, 2, 3, 4] - steps: - - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" - echo "::add-matcher::.github/workflows/matchers/gcc.json" - - name: Run clang-tidy - run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }} - - name: Suggest changes - run: script/ci-suggest-changes - - lint-python: - # Don't use the esphome-lint docker image because it may contain outdated requirements. - # This way, all dependencies are cached via the cache action. - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up python environment - run: script/setup - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/ci-custom.json" - echo "::add-matcher::.github/workflows/matchers/lint-python.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Lint Custom - run: script/ci-custom.py - - name: Lint Python - run: script/lint-python - - name: Lint CODEOWNERS - run: script/build_codeowners.py --check - - test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - test: - - test1 - - test2 - - test3 - - test4 - - test5 - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - # Use per test platformio cache because tests have different platform versions - - name: Cache ~/.platformio - uses: actions/cache@v1 - with: - path: ~/.platformio - key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }} - restore-keys: | - test-home-platformio-${{ matrix.test }}- - - name: Set up environment - run: script/setup - - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/gcc.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - run: esphome compile tests/${{ matrix.test }}.yaml - - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up environment - run: script/setup - - name: Install Github Actions annotator - run: pip install pytest-github-actions-annotate-failures - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Run pytest - run: | - pytest \ - -qq \ - --durations=10 \ - -o console_output_style=count \ - tests - - deploy-docker: - name: Build and publish docker containers - if: github.repository == 'esphome/esphome' - runs-on: ubuntu-latest - needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest] - strategy: - matrix: - arch: [amd64, armv7, aarch64] - # Hassio dev image doesn't use esphome/esphome-hassio-$arch and uses base directly - build_type: ["docker"] - steps: - - uses: actions/checkout@v2 - - name: Set TAG - run: | - TAG="${GITHUB_SHA:0:7}" - echo "TAG=${TAG}" >> $GITHUB_ENV - - name: Set up env variables - run: | - base_version="3.4.0" - - if [[ "${{ matrix.build_type }}" == "hassio" ]]; then - build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-hassio-${{ matrix.arch }}" - dockerfile="docker/Dockerfile.hassio" - else - build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-${{ matrix.arch }}" - dockerfile="docker/Dockerfile" - fi - - echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV - echo "BUILD_TO=${build_to}" >> $GITHUB_ENV - echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV - - name: Pull for cache - run: | - docker pull "${BUILD_TO}:dev" || true - - name: Register QEMU binfmt - run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes - - run: | - docker build \ - --build-arg "BUILD_FROM=${BUILD_FROM}" \ - --build-arg "BUILD_VERSION=${TAG}" \ - --tag "${BUILD_TO}:${TAG}" \ - --tag "${BUILD_TO}:dev" \ - --cache-from "${BUILD_TO}:dev" \ - --file "${DOCKERFILE}" \ - . - - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - run: | - docker push "${BUILD_TO}:${TAG}" - docker push "${BUILD_TO}:dev" - - - deploy-docker-manifest: - if: github.repository == 'esphome/esphome' - runs-on: ubuntu-latest - needs: [deploy-docker] - steps: - - name: Enable experimental manifest support - run: | - mkdir -p ~/.docker - echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json - - name: Set TAG - run: | - TAG="${GITHUB_SHA:0:7}" - echo "TAG=${TAG}" >> $GITHUB_ENV - - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - name: "Create the manifest" - run: | - docker manifest create esphome/esphome:${TAG} \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:${TAG} - - docker manifest create esphome/esphome:dev \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:dev diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 383f2878e5..1b15b540f6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,164 +1,35 @@ name: Publish Release on: + workflow_dispatch: release: types: [published] + schedule: + - cron: "0 2 * * *" jobs: - # THE LINT/TEST JOBS ARE COPIED FROM ci.yaml - - lint-clang-format: + init: + name: Initialize build runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 + outputs: + tag: ${{ steps.tag.outputs.tag }} steps: - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - name: Run clang-format - run: script/clang-format -i - - name: Suggest changes - run: script/ci-suggest-changes - - lint-clang-tidy: - runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files - strategy: - fail-fast: false - matrix: - split: [1, 2, 3, 4] - steps: - - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - - name: Register problem matchers + - name: Get tag + id: tag run: | - echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" - echo "::add-matcher::.github/workflows/matchers/gcc.json" - - name: Run clang-tidy - run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }} - - name: Suggest changes - run: script/ci-suggest-changes - - lint-python: - # Don't use the esphome-lint docker image because it may contain outdated requirements. - # This way, all dependencies are cached via the cache action. - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up python environment - run: script/setup - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/ci-custom.json" - echo "::add-matcher::.github/workflows/matchers/lint-python.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Lint Custom - run: script/ci-custom.py - - name: Lint Python - run: script/lint-python - - name: Lint CODEOWNERS - run: script/build_codeowners.py --check - - test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - test: - - test1 - - test2 - - test3 - - test4 - - test5 - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - # Use per test platformio cache because tests have different platform versions - - name: Cache ~/.platformio - uses: actions/cache@v1 - with: - path: ~/.platformio - key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }} - restore-keys: | - test-home-platformio-${{ matrix.test }}- - - name: Set up environment - run: script/setup - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/gcc.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - run: esphome compile tests/${{ matrix.test }}.yaml - - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up environment - run: script/setup - - name: Install Github Actions annotator - run: pip install pytest-github-actions-annotate-failures - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Run pytest - run: | - pytest \ - -qq \ - --durations=10 \ - -o console_output_style=count \ - tests + if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then + TAG="${GITHUB_REF#refs/tags/v}" + else + TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p") + today="$(date --utc '+%Y%m%d')" + TAG="${TAG}${today}" + fi + echo "::set-output name=tag::${TAG}" deploy-pypi: name: Build and publish to PyPi - if: github.repository == 'esphome/esphome' - needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest] + if: github.repository == 'esphome/esphome' && github.event_name == 'release' runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -182,119 +53,85 @@ jobs: name: Build and publish docker containers if: github.repository == 'esphome/esphome' runs-on: ubuntu-latest - needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest] + needs: [init] strategy: matrix: arch: [amd64, armv7, aarch64] - build_type: ["hassio", "docker"] + build_type: ["ha-addon", "docker"] steps: - - uses: actions/checkout@v2 - - name: Set TAG - run: | - TAG="${GITHUB_REF#refs/tags/v}" - echo "TAG=${TAG}" >> $GITHUB_ENV - - name: Set up env variables - run: | - base_version="3.4.0" + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' - if [[ "${{ matrix.build_type }}" == "hassio" ]]; then - build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-hassio-${{ matrix.arch }}" - dockerfile="docker/Dockerfile.hassio" - else - build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-${{ matrix.arch }}" - dockerfile="docker/Dockerfile" - fi + - name: Run build + run: | + docker/build.py \ + --tag "${{ needs.init.outputs.tag }}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + build - if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then - cache_tag="beta" - else - cache_tag="latest" - fi + - name: Log in to docker hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - # Set env variables so these values don't need to be calculated again - echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV - echo "BUILD_TO=${build_to}" >> $GITHUB_ENV - echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV - echo "CACHE_TAG=${cache_tag}" >> $GITHUB_ENV - - name: Pull for cache - run: | - docker pull "${BUILD_TO}:${CACHE_TAG}" || true - - name: Register QEMU binfmt - run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes - - run: | - docker build \ - --build-arg "BUILD_FROM=${BUILD_FROM}" \ - --build-arg "BUILD_VERSION=${TAG}" \ - --tag "${BUILD_TO}:${TAG}" \ - --cache-from "${BUILD_TO}:${CACHE_TAG}" \ - --file "${DOCKERFILE}" \ - . - - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - run: docker push "${BUILD_TO}:${TAG}" - - # Always publish to beta tag (also full releases) - - name: Publish docker beta tag - run: | - docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:beta" - docker push "${BUILD_TO}:beta" - - - if: ${{ !github.event.release.prerelease }} - name: Publish docker latest tag - run: | - docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:latest" - docker push "${BUILD_TO}:latest" + - name: Run push + run: | + docker/build.py \ + --tag "${{ needs.init.outputs.tag }}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + push deploy-docker-manifest: if: github.repository == 'esphome/esphome' runs-on: ubuntu-latest - needs: [deploy-docker] + needs: [init, deploy-docker] + strategy: + matrix: + build_type: ["ha-addon", "docker"] steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' - name: Enable experimental manifest support run: | mkdir -p ~/.docker echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json - - name: Set TAG - run: | - TAG="${GITHUB_REF#refs/tags/v}" - echo "TAG=${TAG}" >> $GITHUB_ENV + - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - name: "Create the manifest" - run: | - docker manifest create esphome/esphome:${TAG} \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:${TAG} + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Publish docker beta tag + - name: Run manifest run: | - docker manifest create esphome/esphome:beta \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:beta - - - name: Publish docker latest tag - if: ${{ !github.event.release.prerelease }} - run: | - docker manifest create esphome/esphome:latest \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:latest + docker/build.py \ + --tag "${{ needs.init.outputs.tag }}" \ + --build-type "${{ matrix.build_type }}" \ + manifest deploy-hassio-repo: - if: github.repository == 'esphome/esphome' + if: github.repository == 'esphome/esphome' && github.event_name == 'release' runs-on: ubuntu-latest needs: [deploy-docker] steps: diff --git a/docker/Dockerfile b/docker/Dockerfile index 0d126a2944..907c041119 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG BUILD_FROM=esphome/esphome-base-amd64:3.4.0 +ARG BUILD_FROM=esphome/esphome-base:latest FROM ${BUILD_FROM} # First install requirements to leverage caching when requirements don't change diff --git a/docker/Dockerfile.hassio b/docker/Dockerfile.hassio index 5dd9339b18..ad80074ada 100644 --- a/docker/Dockerfile.hassio +++ b/docker/Dockerfile.hassio @@ -1,4 +1,4 @@ -ARG BUILD_FROM +ARG BUILD_FROM=esphome/esphome-hassio-base:latest FROM ${BUILD_FROM} # First install requirements to leverage caching when requirements don't change diff --git a/docker/Dockerfile.lint b/docker/Dockerfile.lint index 60d63152a0..3a090c3b41 100644 --- a/docker/Dockerfile.lint +++ b/docker/Dockerfile.lint @@ -1,4 +1,5 @@ -FROM esphome/esphome-lint-base:3.4.0 +ARG BUILD_FROM=esphome/esphome-lint-base:latest +FROM ${BUILD_FROM} COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini / RUN \ diff --git a/docker/build.py b/docker/build.py new file mode 100755 index 0000000000..97222df8d1 --- /dev/null +++ b/docker/build.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +from dataclasses import dataclass +import subprocess +import argparse +import platform +import shlex +import re +import sys + + +CHANNEL_DEV = 'dev' +CHANNEL_BETA = 'beta' +CHANNEL_RELEASE = 'release' +CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE] + +ARCH_AMD64 = 'amd64' +ARCH_ARMV7 = 'armv7' +ARCH_AARCH64 = 'aarch64' +ARCHS = [ARCH_AMD64, ARCH_ARMV7, ARCH_AARCH64] + +TYPE_DOCKER = 'docker' +TYPE_HA_ADDON = 'ha-addon' +TYPE_LINT = 'lint' +TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT] + + +BASE_VERSION = "3.6.0" + + +parser = argparse.ArgumentParser() +parser.add_argument("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag") +parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for") +parser.add_argument("--build-type", choices=TYPES, required=True, help="The type of build to run") +parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them") +subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True) +build_parser = subparsers.add_parser("build", help="Build the image") +push_parser = subparsers.add_parser("push", help="Tag the already built image and push it to docker hub") +manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images") + + + +# only lists some possibilities, doesn't have to be perfect +# https://stackoverflow.com/a/45125525 +UNAME_TO_ARCH = { + "x86_64": ARCH_AMD64, + "aarch64": ARCH_AARCH64, + "aarch64_be": ARCH_AARCH64, + "arm": ARCH_ARMV7, +} + + +@dataclass(frozen=True) +class DockerParams: + build_from: str + build_to: str + manifest_to: str + dockerfile: str + + @classmethod + def for_type_arch(cls, build_type, arch): + prefix = { + TYPE_DOCKER: "esphome/esphome", + TYPE_HA_ADDON: "esphome/esphome-hassio", + TYPE_LINT: "esphome/esphome-lint" + }[build_type] + build_from = f"ghcr.io/{prefix}-base-{arch}:{BASE_VERSION}" + build_to = f"{prefix}-{arch}" + dockerfile = { + TYPE_DOCKER: "docker/Dockerfile", + TYPE_HA_ADDON: "docker/Dockerfile.hassio", + TYPE_LINT: "docker/Dockerfile.lint", + }[build_type] + return cls( + build_from=build_from, + build_to=build_to, + manifest_to=prefix, + dockerfile=dockerfile + ) + + +def main(): + args = parser.parse_args() + + def run_command(*cmd, ignore_error: bool = False): + print(f"$ {shlex.join(list(cmd))}") + if not args.dry_run: + rc = subprocess.call(list(cmd)) + if rc != 0 and not ignore_error: + print("Command failed") + sys.exit(1) + + # detect channel from tag + match = re.match(r'\d+\.\d+(?:\.\d+)?(b\d+)?', args.tag) + if match is None: + channel = CHANNEL_DEV + elif match.group(1) is None: + channel = CHANNEL_RELEASE + else: + channel = CHANNEL_BETA + + tags_to_push = [args.tag] + if channel == CHANNEL_DEV: + tags_to_push.append("dev") + elif channel == CHANNEL_BETA: + tags_to_push.append("beta") + elif channel == CHANNEL_RELEASE: + # Additionally push to beta + tags_to_push.append("beta") + tags_to_push.append("latest") + + if args.command == "build": + # 1. pull cache image + params = DockerParams.for_type_arch(args.build_type, args.arch) + cache_tag = { + CHANNEL_DEV: "dev", + CHANNEL_BETA: "beta", + CHANNEL_RELEASE: "latest", + }[channel] + cache_img = f"ghcr.io/{params.build_to}:{cache_tag}" + run_command("docker", "pull", cache_img, ignore_error=True) + + # 2. register QEMU binfmt (if not host arch) + is_native = UNAME_TO_ARCH.get(platform.machine()) == args.arch + if not is_native: + run_command( + "docker", "run", "--rm", "--privileged", "multiarch/qemu-user-static:5.2.0-2", + "--reset", "-p", "yes" + ) + + # 3. build + run_command( + "docker", "build", + "--build-arg", f"BUILD_FROM={params.build_from}", + "--build-arg", f"BUILD_VERSION={args.tag}", + "--tag", f"{params.build_to}:{args.tag}", + "--cache-from", cache_img, + "--file", params.dockerfile, + "." + ) + elif args.command == "push": + params = DockerParams.for_type_arch(args.build_type, args.arch) + imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push] + imgs += [f"ghcr.io/{params.build_to}:{tag}" for tag in tags_to_push] + src = imgs[0] + # 1. tag images + for img in imgs[1:]: + run_command( + "docker", "tag", src, img + ) + # 2. push images + for img in imgs: + run_command( + "docker", "push", img + ) + elif args.command == "manifest": + manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to + + targets = [f"{manifest}:{tag}" for tag in tags_to_push] + targets += [f"ghcr.io/{manifest}:{tag}" for tag in tags_to_push] + # 1. Create manifests + for target in targets: + cmd = ["docker", "manifest", "create", target] + for arch in ARCHS: + src = f"{DockerParams.for_type_arch(args.build_type, arch).build_to}:{args.tag}" + if target.startswith("ghcr.io"): + src = f"ghcr.io/{src}" + cmd.append(src) + run_command(*cmd) + # 2. Push manifests + for target in targets: + run_command( + "docker", "manifest", "push", target + ) + + +if __name__ == "__main__": + main() diff --git a/esphome/const.py b/esphome/const.py index 7f1e3a2c58..1ea4285747 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,10 +1,6 @@ """Constants used by esphome.""" -MAJOR_VERSION = 1 -MINOR_VERSION = 21 -PATCH_VERSION = "0-dev" -__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" -__version__ = f"{__short_version__}.{PATCH_VERSION}" +__version__ = "1.21.0-dev" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" diff --git a/script/bump-docker-base-version.py b/script/bump-docker-base-version.py deleted file mode 100755 index f5f4399fd2..0000000000 --- a/script/bump-docker-base-version.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import re -import sys - - -def sub(path, pattern, repl, expected_count=1): - with open(path) as fh: - content = fh.read() - content, count = re.subn(pattern, repl, content, flags=re.MULTILINE) - if expected_count is not None: - assert count == expected_count, f"Pattern {pattern} replacement failed!" - with open(path, "wt") as fh: - fh.write(content) - - -def write_version(version: str): - for p in [ - ".github/workflows/ci-docker.yml", - ".github/workflows/release-dev.yml", - ".github/workflows/release.yml", - ]: - sub(p, r'base_version=".*"', f'base_version="{version}"') - - sub( - "docker/Dockerfile", - r"ARG BUILD_FROM=esphome/esphome-base-amd64:.*", - f"ARG BUILD_FROM=esphome/esphome-base-amd64:{version}", - ) - sub( - "docker/Dockerfile.lint", - r"FROM esphome/esphome-lint-base:.*", - f"FROM esphome/esphome-lint-base:{version}", - ) - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("new_version", type=str) - args = parser.parse_args() - - version = args.new_version - print(f"Bumping to {version}") - write_version(version) - return 0 - - -if __name__ == "__main__": - sys.exit(main() or 0) diff --git a/script/bump-version.py b/script/bump-version.py index b7b048eb22..1f034344f9 100755 --- a/script/bump-version.py +++ b/script/bump-version.py @@ -50,16 +50,10 @@ def sub(path, pattern, repl, expected_count=1): def write_version(version: Version): - sub( - "esphome/const.py", r"^MAJOR_VERSION = \d+$", f"MAJOR_VERSION = {version.major}" - ) - sub( - "esphome/const.py", r"^MINOR_VERSION = \d+$", f"MINOR_VERSION = {version.minor}" - ) sub( "esphome/const.py", - r"^PATCH_VERSION = .*$", - f'PATCH_VERSION = "{version.full_patch}"', + r"^__version__ = .*$", + f'__version__ = "{version}"', ) From 799f04efc090837d82aa314559897d865c798cb1 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 15 Jul 2021 21:51:52 +0200 Subject: [PATCH 1018/1841] GH Actions CI use GHCR (#2027) --- .github/workflows/ci.yml | 2 +- docker/build.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de777d5a3b..4ccaaa47b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: # cpp lint job runs with esphome-lint docker image so that clang-format-* # doesn't have to be installed - container: esphome/esphome-lint:1.1 + container: ghcr.io/esphome/esphome-lint:1.1 steps: - uses: actions/checkout@v2 # Set up the pio project so that the cpp checks know how files are compiled diff --git a/docker/build.py b/docker/build.py index 97222df8d1..54a279f845 100755 --- a/docker/build.py +++ b/docker/build.py @@ -90,7 +90,7 @@ def main(): sys.exit(1) # detect channel from tag - match = re.match(r'\d+\.\d+(?:\.\d+)?(b\d+)?', args.tag) + match = re.match(r'^\d+\.\d+(?:\.\d+)?(b\d+)?$', args.tag) if match is None: channel = CHANNEL_DEV elif match.group(1) is None: From 442e58b07aba1f59f37b7433c9c89f2c7e7b56ec Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 16 Jul 2021 10:22:42 +0200 Subject: [PATCH 1019/1841] Dashboard disable assets caching (#2025) --- esphome/dashboard/dashboard.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 00b12199c0..66f72bfc00 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -782,10 +782,9 @@ def make_app(debug=get_bool_env(ENV_DEV)): class StaticFileHandler(tornado.web.StaticFileHandler): def set_extra_headers(self, path): - if debug: - self.set_header( - "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0" - ) + self.set_header( + "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0" + ) app_settings = { "debug": debug, From 06912b492f23d0af42ddad7b4add5d4347266eef Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 16 Jul 2021 10:23:08 +0200 Subject: [PATCH 1020/1841] Improve external components error messages (#2026) --- .../external_components/__init__.py | 95 +++++++++++-------- 1 file changed, 58 insertions(+), 37 deletions(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index 1602ac3b07..bf44dc1929 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -109,7 +109,15 @@ def _compute_destination_path(key: str) -> Path: return base_dir / h.hexdigest()[:8] -def _handle_git_response(ret): +def _run_git_command(cmd): + try: + ret = subprocess.run(cmd, capture_output=True, check=False) + except FileNotFoundError as err: + raise cv.Invalid( + "git is not installed but required for external_components.\n" + "Please see https://git-scm.com/book/en/v2/Getting-Started-Installing-Git for installing git" + ) from err + if ret.returncode != 0 and ret.stderr: err_str = ret.stderr.decode("utf-8") lines = [x.strip() for x in err_str.splitlines()] @@ -118,46 +126,59 @@ def _handle_git_response(ret): raise cv.Invalid(err_str) +def _process_git_config(config: dict, refresh) -> str: + key = f"{config[CONF_URL]}@{config.get(CONF_REF)}" + repo_dir = _compute_destination_path(key) + if not repo_dir.is_dir(): + _LOGGER.info("Cloning %s", key) + _LOGGER.debug("Location: %s", repo_dir) + cmd = ["git", "clone", "--depth=1"] + if CONF_REF in config: + cmd += ["--branch", config[CONF_REF]] + cmd += ["--", config[CONF_URL], str(repo_dir)] + _run_git_command(cmd) + + else: + # Check refresh needed + file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD") + # On first clone, FETCH_HEAD does not exists + if not file_timestamp.exists(): + file_timestamp = Path(repo_dir / ".git" / "HEAD") + age = datetime.datetime.now() - datetime.datetime.fromtimestamp( + file_timestamp.stat().st_mtime + ) + if age.seconds > refresh.total_seconds: + _LOGGER.info("Updating %s", key) + _LOGGER.debug("Location: %s", repo_dir) + # Stash local changes (if any) + _run_git_command(["git", "stash", "push", "--include-untracked"]) + # Fetch remote ref + cmd = ["git", "fetch", "--", "origin"] + if CONF_REF in config: + cmd.append(config[CONF_REF]) + _run_git_command(cmd) + # Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch) + _run_git_command(["git", "reset", "--hard", "FETCH_HEAD"]) + + if (repo_dir / "esphome" / "components").is_dir(): + components_dir = repo_dir / "esphome" / "components" + elif (repo_dir / "components").is_dir(): + components_dir = repo_dir / "components" + else: + raise cv.Invalid( + "Could not find components folder for source. Please check the source contains a 'components' or 'esphome/components' folder" + ) + + return components_dir + + def _process_single_config(config: dict): conf = config[CONF_SOURCE] if conf[CONF_TYPE] == TYPE_GIT: - key = f"{conf[CONF_URL]}@{conf.get(CONF_REF)}" - repo_dir = _compute_destination_path(key) - if not repo_dir.is_dir(): - cmd = ["git", "clone", "--depth=1"] - if CONF_REF in conf: - cmd += ["--branch", conf[CONF_REF]] - cmd += [conf[CONF_URL], str(repo_dir)] - ret = subprocess.run(cmd, capture_output=True, check=False) - _handle_git_response(ret) - - else: - # Check refresh needed - file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD") - # On first clone, FETCH_HEAD does not exists - if not file_timestamp.exists(): - file_timestamp = Path(repo_dir / ".git" / "HEAD") - age = datetime.datetime.now() - datetime.datetime.fromtimestamp( - file_timestamp.stat().st_mtime + with cv.prepend_path([CONF_SOURCE]): + components_dir = _process_git_config( + config[CONF_SOURCE], config[CONF_REFRESH] ) - if age.seconds > config[CONF_REFRESH].total_seconds: - _LOGGER.info("Executing git pull %s", key) - cmd = ["git", "pull"] - ret = subprocess.run( - cmd, cwd=repo_dir, capture_output=True, check=False - ) - _handle_git_response(ret) - - if (repo_dir / "esphome" / "components").is_dir(): - components_dir = repo_dir / "esphome" / "components" - elif (repo_dir / "components").is_dir(): - components_dir = repo_dir / "components" - else: - raise cv.Invalid( - "Could not find components folder for source. Please check the source contains a 'components' or 'esphome/components' folder", - [CONF_SOURCE], - ) - elif conf[CONF_TYPE] == TYPE_LOCAL: components_dir = Path(CORE.relative_config_path(conf[CONF_PATH])) else: From 2e49fd7b48e471a5f5c6c4d5010a409a8441f6ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jul 2021 08:09:00 +1200 Subject: [PATCH 1021/1841] Bump black from 21.6b0 to 21.7b0 (#2031) Bumps [black](https://github.com/psf/black) from 21.6b0 to 21.7b0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits) --- updated-dependencies: - dependency-name: black dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index f9566d5adc..dc46b6f3bf 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==2.8.2 flake8==3.9.2 -black==21.6b0 +black==21.7b0 pexpect==4.8.0 pre-commit From 3715ba030bfa93a396ad2e654c1009018b043eb3 Mon Sep 17 00:00:00 2001 From: Sean Vig Date: Wed, 14 Jul 2021 23:45:41 -0400 Subject: [PATCH 1022/1841] Fix ethernet component hostname handling (#2010) Co-authored-by: Otto Winter --- .gitignore | 3 + .../ethernet/ethernet_component.cpp | 213 +++++++++--------- .../components/ethernet/ethernet_component.h | 10 +- 3 files changed, 120 insertions(+), 106 deletions(-) diff --git a/.gitignore b/.gitignore index a24550ad54..954ecb2cb8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ __pycache__/ # Intellij Idea .idea +# Vim +*.swp + # Hide some OS X stuff .DS_Store .AppleDouble diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 22007caafa..52c184eef6 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -25,6 +25,13 @@ static const char *const TAG = "ethernet"; EthernetComponent *global_eth_component; +#define ESPHL_ERROR_CHECK(err, message) \ + if (err != ESP_OK) { \ + ESP_LOGE(TAG, message ": (%d) %s", err, esp_err_to_name(err)); \ + this->mark_failed(); \ + return; \ + } + EthernetComponent::EthernetComponent() { global_eth_component = this; } void EthernetComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up Ethernet..."); @@ -36,103 +43,6 @@ void EthernetComponent::setup() { this->power_pin_->setup(); } - this->start_connect_(); - -#ifdef USE_MDNS - network_setup_mdns(); -#endif -} -void EthernetComponent::loop() { - const uint32_t now = millis(); - if (!this->connected_ && !this->last_connected_ && now - this->connect_begin_ > 15000) { - ESP_LOGW(TAG, "Connecting via ethernet failed! Re-connecting..."); - this->start_connect_(); - return; - } - - if (this->connected_ == this->last_connected_) - // nothing changed - return; - - if (this->connected_) { - // connection established - ESP_LOGI(TAG, "Connected via Ethernet!"); - this->dump_connect_params_(); - this->status_clear_warning(); - } else { - // connection lost - ESP_LOGW(TAG, "Connection via Ethernet lost! Re-connecting..."); - this->start_connect_(); - } - - this->last_connected_ = this->connected_; - - network_tick_mdns(); -} -void EthernetComponent::dump_config() { - ESP_LOGCONFIG(TAG, "Ethernet:"); - this->dump_connect_params_(); - LOG_PIN(" Power Pin: ", this->power_pin_); - ESP_LOGCONFIG(TAG, " MDC Pin: %u", this->mdc_pin_); - ESP_LOGCONFIG(TAG, " MDIO Pin: %u", this->mdio_pin_); - ESP_LOGCONFIG(TAG, " Type: %s", this->type_ == ETHERNET_TYPE_LAN8720 ? "LAN8720" : "TLK110"); -} -float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } -bool EthernetComponent::can_proceed() { return this->is_connected(); } -IPAddress EthernetComponent::get_ip_address() { - tcpip_adapter_ip_info_t ip; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); - return IPAddress(ip.ip.addr); -} - -void EthernetComponent::on_wifi_event_(system_event_id_t event, system_event_info_t info) { - const char *event_name; - - switch (event) { - case SYSTEM_EVENT_ETH_START: - event_name = "ETH started"; - break; - case SYSTEM_EVENT_ETH_STOP: - event_name = "ETH stopped"; - this->connected_ = false; - break; - case SYSTEM_EVENT_ETH_CONNECTED: - event_name = "ETH connected"; - break; - case SYSTEM_EVENT_ETH_DISCONNECTED: - event_name = "ETH disconnected"; - this->connected_ = false; - break; - case SYSTEM_EVENT_ETH_GOT_IP: - event_name = "ETH Got IP"; - this->connected_ = true; - break; - default: - return; - } - - ESP_LOGV(TAG, "[Ethernet event] %s (num=%d)", event_name, event); -} - -#define ESPHL_ERROR_CHECK(err, message) \ - if (err != ESP_OK) { \ - ESP_LOGE(TAG, message ": %d", err); \ - this->mark_failed(); \ - return; \ - } - -void EthernetComponent::start_connect_() { - this->connect_begin_ = millis(); - this->status_set_warning(); - - esp_err_t err; - if (this->initialized_) { - // already initialized - err = esp_eth_enable(); - ESPHL_ERROR_CHECK(err, "ETH enable error"); - return; - } - switch (this->type_) { case ETHERNET_TYPE_LAN8720: { memcpy(&this->eth_config, &phy_lan8720_default_ethernet_config, sizeof(eth_config_t)); @@ -160,16 +70,111 @@ void EthernetComponent::start_connect_() { tcpipInit(); + esp_err_t err; err = esp_eth_init(&this->eth_config); - if (err != ESP_OK) { - ESP_LOGE(TAG, "ETH init error: %d", err); - this->mark_failed(); - return; + ESPHL_ERROR_CHECK(err, "ETH init error"); + err = esp_eth_enable(); + ESPHL_ERROR_CHECK(err, "ETH enable error"); + +#ifdef USE_MDNS + network_setup_mdns(); +#endif +} +void EthernetComponent::loop() { + const uint32_t now = millis(); + + switch (this->state_) { + case EthernetComponentState::STOPPED: + if (this->started_) { + ESP_LOGI(TAG, "Starting ethernet connection"); + this->state_ = EthernetComponentState::CONNECTING; + this->start_connect_(); + } + break; + case EthernetComponentState::CONNECTING: + if (!this->started_) { + ESP_LOGI(TAG, "Stopped ethernet connection"); + this->state_ = EthernetComponentState::STOPPED; + } else if (this->connected_) { + // connection established + ESP_LOGI(TAG, "Connected via Ethernet!"); + this->state_ = EthernetComponentState::CONNECTED; + + this->dump_connect_params_(); + this->status_clear_warning(); + + network_tick_mdns(); + } else if (now - this->connect_begin_ > 15000) { + ESP_LOGW(TAG, "Connecting via ethernet failed! Re-connecting..."); + this->start_connect_(); + } + break; + case EthernetComponentState::CONNECTED: + if (!this->started_) { + ESP_LOGI(TAG, "Stopped ethernet connection"); + this->state_ = EthernetComponentState::STOPPED; + } else if (!this->connected_) { + ESP_LOGW(TAG, "Connection via Ethernet lost! Re-connecting..."); + this->state_ = EthernetComponentState::CONNECTING; + this->start_connect_(); + } + break; + } +} +void EthernetComponent::dump_config() { + ESP_LOGCONFIG(TAG, "Ethernet:"); + this->dump_connect_params_(); + LOG_PIN(" Power Pin: ", this->power_pin_); + ESP_LOGCONFIG(TAG, " MDC Pin: %u", this->mdc_pin_); + ESP_LOGCONFIG(TAG, " MDIO Pin: %u", this->mdio_pin_); + ESP_LOGCONFIG(TAG, " Type: %s", this->type_ == ETHERNET_TYPE_LAN8720 ? "LAN8720" : "TLK110"); +} +float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } +bool EthernetComponent::can_proceed() { return this->is_connected(); } +IPAddress EthernetComponent::get_ip_address() { + tcpip_adapter_ip_info_t ip; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); + return IPAddress(ip.ip.addr); +} + +void EthernetComponent::on_wifi_event_(system_event_id_t event, system_event_info_t info) { + const char *event_name; + + switch (event) { + case SYSTEM_EVENT_ETH_START: + event_name = "ETH started"; + this->started_ = true; + break; + case SYSTEM_EVENT_ETH_STOP: + event_name = "ETH stopped"; + this->started_ = false; + this->connected_ = false; + break; + case SYSTEM_EVENT_ETH_CONNECTED: + event_name = "ETH connected"; + break; + case SYSTEM_EVENT_ETH_DISCONNECTED: + event_name = "ETH disconnected"; + this->connected_ = false; + break; + case SYSTEM_EVENT_ETH_GOT_IP: + event_name = "ETH Got IP"; + this->connected_ = true; + break; + default: + return; } - this->initialized_ = true; + ESP_LOGV(TAG, "[Ethernet event] %s (num=%d)", event_name, event); +} - tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str()); +void EthernetComponent::start_connect_() { + this->connect_begin_ = millis(); + this->status_set_warning(); + + esp_err_t err; + err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str()); + ESPHL_ERROR_CHECK(err, "ETH set hostname error"); tcpip_adapter_ip_info_t info; if (this->manual_ip_.has_value()) { @@ -220,7 +225,7 @@ void EthernetComponent::eth_phy_power_enable_(bool enable) { delay(1); global_eth_component->orig_power_enable_fun_(enable); } -bool EthernetComponent::is_connected() { return this->connected_ && this->last_connected_; } +bool EthernetComponent::is_connected() { return this->state_ == EthernetComponentState::CONNECTED; } void EthernetComponent::dump_connect_params_() { tcpip_adapter_ip_info_t ip; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 2dbd8ccd9d..326cd1edea 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -26,6 +26,12 @@ struct ManualIP { IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default. }; +enum class EthernetComponentState { + STOPPED, + CONNECTING, + CONNECTED, +}; + class EthernetComponent : public Component { public: EthernetComponent(); @@ -65,9 +71,9 @@ class EthernetComponent : public Component { eth_clock_mode_t clk_mode_{ETH_CLOCK_GPIO0_IN}; optional manual_ip_{}; - bool initialized_{false}; + bool started_{false}; bool connected_{false}; - bool last_connected_{false}; + EthernetComponentState state_{EthernetComponentState::STOPPED}; uint32_t connect_begin_; eth_config_t eth_config; eth_phy_power_enable_func orig_power_enable_fun_; From 4f9a56c8841a247f7d82fcd0cbe40902a77ba9e2 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 15 Jul 2021 21:30:04 +0200 Subject: [PATCH 1023/1841] Refactor docker build system and workflows (#2023) --- .github/workflows/ci-docker.yml | 47 ++-- .github/workflows/ci.yml | 178 ++++++------- .github/workflows/docker-lint-build.yml | 108 ++++++-- .github/workflows/matchers/pytest.json | 19 ++ .github/workflows/release-dev.yml | 247 ------------------- .github/workflows/release.yml | 315 ++++++------------------ docker/Dockerfile | 2 +- docker/Dockerfile.hassio | 2 +- docker/Dockerfile.lint | 3 +- docker/build.py | 177 +++++++++++++ esphome/const.py | 6 +- script/bump-docker-base-version.py | 50 ---- script/bump-version.py | 10 +- 13 files changed, 456 insertions(+), 708 deletions(-) create mode 100644 .github/workflows/matchers/pytest.json delete mode 100644 .github/workflows/release-dev.yml create mode 100755 docker/build.py delete mode 100755 script/bump-docker-base-version.py diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 91ec88aeb3..45fd3e141b 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -18,38 +18,23 @@ jobs: name: Build docker containers runs-on: ubuntu-latest strategy: - fail-fast: false matrix: arch: [amd64, armv7, aarch64] - build_type: ["hassio", "docker"] + build_type: ["ha-addon", "docker", "lint"] steps: - - uses: actions/checkout@v2 - - name: Set up env variables - run: | - base_version="3.4.0" + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' + - name: Set TAG + run: | + echo "TAG=check" >> $GITHUB_ENV - if [[ "${{ matrix.build_type }}" == "hassio" ]]; then - build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-hassio-${{ matrix.arch }}" - dockerfile="docker/Dockerfile.hassio" - else - build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-${{ matrix.arch }}" - dockerfile="docker/Dockerfile" - fi - - echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV - echo "BUILD_TO=${build_to}" >> $GITHUB_ENV - echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV - - name: Pull for cache - run: | - docker pull "${BUILD_TO}:dev" || true - - name: Register QEMU binfmt - run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes - - run: | - docker build \ - --build-arg "BUILD_FROM=${BUILD_FROM}" \ - --build-arg "BUILD_VERSION=ci" \ - --cache-from "${BUILD_TO}:dev" \ - --file "${DOCKERFILE}" \ - . + - name: Run build + run: | + docker/build.py \ + --tag "${TAG}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + build diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 121b3f1339..de777d5a3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,40 +4,36 @@ name: CI on: push: - # On dev branch release-dev already performs CI checks - # On other branches the `pull_request` trigger will be used - branches: [beta, release] + branches: [dev, beta, release] pull_request: jobs: - lint-clang-format: + ci-with-container: + name: ${{ matrix.name }} runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - steps: - - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - name: Run clang-format - run: script/clang-format -i - - name: Suggest changes - run: script/ci-suggest-changes - - lint-clang-tidy: - runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files strategy: fail-fast: false matrix: - split: [1, 2, 3, 4] + include: + - id: clang-format + name: Run script/clang-format + - id: clang-tidy + name: Run script/clang-tidy 1/4 + split: 1 + - id: clang-tidy + name: Run script/clang-tidy 2/4 + split: 2 + - id: clang-tidy + name: Run script/clang-tidy 3/4 + split: 3 + - id: clang-tidy + name: Run script/clang-tidy 4/4 + split: 4 + + # cpp lint job runs with esphome-lint docker image so that clang-format-* + # doesn't have to be installed + container: esphome/esphome-lint:1.1 steps: - uses: actions/checkout@v2 # Set up the pio project so that the cpp checks know how files are compiled @@ -45,26 +41,57 @@ jobs: - name: Set up platformio environment run: pio init --ide atom - - name: Register problem matchers run: | echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" echo "::add-matcher::.github/workflows/matchers/gcc.json" + + - name: Run clang-format + run: script/clang-format -i + if: ${{ matrix.id == 'clang-format' }} + - name: Run clang-tidy run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }} + if: ${{ matrix.id == 'clang-tidy' }} + - name: Suggest changes run: script/ci-suggest-changes - lint-python: + ci: # Don't use the esphome-lint docker image because it may contain outdated requirements. # This way, all dependencies are cached via the cache action. + name: ${{ matrix.name }} runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - id: ci-custom + name: Run script/ci-custom + - id: lint-python + name: Run script/lint-python + - id: test + file: tests/test1.yaml + name: Test tests/test1.yaml + - id: test + file: tests/test2.yaml + name: Test tests/test2.yaml + - id: test + file: tests/test3.yaml + name: Test tests/test3.yaml + - id: test + file: tests/test4.yaml + name: Test tests/test4.yaml + - id: pytest + name: Run pytest + steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.7' + - name: Cache pip modules uses: actions/cache@v1 with: @@ -72,6 +99,17 @@ jobs: key: esphome-pip-3.7-${{ hashFiles('setup.py') }} restore-keys: | esphome-pip-3.7- + + # Use per test platformio cache because tests have different platform versions + - name: Cache ~/.platformio + uses: actions/cache@v1 + with: + path: ~/.platformio + key: test-home-platformio-${{ matrix.file }}-${{ hashFiles('esphome/core/config.py') }} + restore-keys: | + test-home-platformio-${{ matrix.file }}- + if: ${{ matrix.id == 'test' }} + - name: Set up python environment run: script/setup @@ -80,82 +118,22 @@ jobs: echo "::add-matcher::.github/workflows/matchers/ci-custom.json" echo "::add-matcher::.github/workflows/matchers/lint-python.json" echo "::add-matcher::.github/workflows/matchers/python.json" + echo "::add-matcher::.github/workflows/matchers/pytest.json" + echo "::add-matcher::.github/workflows/matchers/gcc.json" + - name: Lint Custom - run: script/ci-custom.py + run: | + script/ci-custom.py + script/build_codeowners.py --check + if: ${{ matrix.id == 'ci-custom' }} - name: Lint Python run: script/lint-python - - name: Lint CODEOWNERS - run: script/build_codeowners.py --check + if: ${{ matrix.id == 'lint-python' }} - test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - test: - - test1 - - test2 - - test3 - - test4 - - test5 - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - # Use per test platformio cache because tests have different platform versions - - name: Cache ~/.platformio - uses: actions/cache@v1 - with: - path: ~/.platformio - key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }} - restore-keys: | - test-home-platformio-${{ matrix.test }}- - - name: Set up environment - run: script/setup + - run: esphome compile ${{ matrix.file }} + if: ${{ matrix.id == 'test' }} - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/gcc.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - run: esphome compile tests/${{ matrix.test }}.yaml - - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up environment - run: script/setup - - name: Install Github Actions annotator - run: pip install pytest-github-actions-annotate-failures - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/python.json" - name: Run pytest run: | - pytest \ - -qq \ - --durations=10 \ - -o console_output_style=count \ - tests + pytest -vv --tb=native tests + if: ${{ matrix.id == 'pytest' }} diff --git a/.github/workflows/docker-lint-build.yml b/.github/workflows/docker-lint-build.yml index d254ac332a..32aec87cdd 100644 --- a/.github/workflows/docker-lint-build.yml +++ b/.github/workflows/docker-lint-build.yml @@ -13,30 +13,88 @@ on: - '.github/workflows/docker-lint-build.yml' jobs: - publish-docker-lint-iage: - name: Build docker containers + deploy-docker: + name: Build and publish docker containers + if: github.repository == 'esphome/esphome' runs-on: ubuntu-latest + strategy: + matrix: + arch: [amd64, armv7, aarch64] + build_type: ["lint"] steps: - - uses: actions/checkout@v2 - - name: Set TAG - run: | - echo "TAG=1.1" >> $GITHUB_ENV - - name: Pull for cache - run: | - docker pull "esphome/esphome-lint:latest" || true - - name: Build - run: | - docker build \ - --cache-from "esphome/esphome-lint:latest" \ - --file "docker/Dockerfile.lint" \ - --tag "esphome/esphome-lint:latest" \ - --tag "esphome/esphome-lint:${TAG}" \ - . - - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - run: | - docker push "esphome/esphome-lint:${TAG}" - docker push "esphome/esphome-lint:latest" + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' + - name: Set TAG + run: | + echo "TAG=1.1" >> $GITHUB_ENV + + - name: Run build + run: | + docker/build.py \ + --tag "${TAG}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + build + + - name: Log in to docker hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Run push + run: | + docker/build.py \ + --tag "${TAG}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + push + + deploy-docker-manifest: + if: github.repository == 'esphome/esphome' + runs-on: ubuntu-latest + needs: [deploy-docker] + strategy: + matrix: + build_type: ["lint"] + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' + - name: Set TAG + run: | + echo "TAG=1.1" >> $GITHUB_ENV + - name: Enable experimental manifest support + run: | + mkdir -p ~/.docker + echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json + + - name: Log in to docker hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Run manifest + run: | + docker/build.py \ + --tag "${TAG}" \ + --build-type "${{ matrix.build_type }}" \ + manifest diff --git a/.github/workflows/matchers/pytest.json b/.github/workflows/matchers/pytest.json new file mode 100644 index 0000000000..0eb8f050e6 --- /dev/null +++ b/.github/workflows/matchers/pytest.json @@ -0,0 +1,19 @@ +{ + "problemMatcher": [ + { + "owner": "pytest", + "fileLocation": "absolute", + "pattern": [ + { + "regexp": "^\\s+File \"(.*)\", line (\\d+), in (.*)$", + "file": 1, + "line": 2 + }, + { + "regexp": "^\\s+(.*)$", + "message": 1 + } + ] + } + ] +} diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml deleted file mode 100644 index f8b90d524f..0000000000 --- a/.github/workflows/release-dev.yml +++ /dev/null @@ -1,247 +0,0 @@ -name: Publish dev releases to docker hub - -on: - push: - branches: - - dev - -jobs: - # THE LINT/TEST JOBS ARE COPIED FROM ci.yaml - - lint-clang-format: - runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - steps: - - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - name: Run clang-format - run: script/clang-format -i - - name: Suggest changes - run: script/ci-suggest-changes - - lint-clang-tidy: - runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files - strategy: - fail-fast: false - matrix: - split: [1, 2, 3, 4] - steps: - - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" - echo "::add-matcher::.github/workflows/matchers/gcc.json" - - name: Run clang-tidy - run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }} - - name: Suggest changes - run: script/ci-suggest-changes - - lint-python: - # Don't use the esphome-lint docker image because it may contain outdated requirements. - # This way, all dependencies are cached via the cache action. - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up python environment - run: script/setup - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/ci-custom.json" - echo "::add-matcher::.github/workflows/matchers/lint-python.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Lint Custom - run: script/ci-custom.py - - name: Lint Python - run: script/lint-python - - name: Lint CODEOWNERS - run: script/build_codeowners.py --check - - test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - test: - - test1 - - test2 - - test3 - - test4 - - test5 - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - # Use per test platformio cache because tests have different platform versions - - name: Cache ~/.platformio - uses: actions/cache@v1 - with: - path: ~/.platformio - key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }} - restore-keys: | - test-home-platformio-${{ matrix.test }}- - - name: Set up environment - run: script/setup - - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/gcc.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - run: esphome compile tests/${{ matrix.test }}.yaml - - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up environment - run: script/setup - - name: Install Github Actions annotator - run: pip install pytest-github-actions-annotate-failures - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Run pytest - run: | - pytest \ - -qq \ - --durations=10 \ - -o console_output_style=count \ - tests - - deploy-docker: - name: Build and publish docker containers - if: github.repository == 'esphome/esphome' - runs-on: ubuntu-latest - needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest] - strategy: - matrix: - arch: [amd64, armv7, aarch64] - # Hassio dev image doesn't use esphome/esphome-hassio-$arch and uses base directly - build_type: ["docker"] - steps: - - uses: actions/checkout@v2 - - name: Set TAG - run: | - TAG="${GITHUB_SHA:0:7}" - echo "TAG=${TAG}" >> $GITHUB_ENV - - name: Set up env variables - run: | - base_version="3.4.0" - - if [[ "${{ matrix.build_type }}" == "hassio" ]]; then - build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-hassio-${{ matrix.arch }}" - dockerfile="docker/Dockerfile.hassio" - else - build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-${{ matrix.arch }}" - dockerfile="docker/Dockerfile" - fi - - echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV - echo "BUILD_TO=${build_to}" >> $GITHUB_ENV - echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV - - name: Pull for cache - run: | - docker pull "${BUILD_TO}:dev" || true - - name: Register QEMU binfmt - run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes - - run: | - docker build \ - --build-arg "BUILD_FROM=${BUILD_FROM}" \ - --build-arg "BUILD_VERSION=${TAG}" \ - --tag "${BUILD_TO}:${TAG}" \ - --tag "${BUILD_TO}:dev" \ - --cache-from "${BUILD_TO}:dev" \ - --file "${DOCKERFILE}" \ - . - - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - run: | - docker push "${BUILD_TO}:${TAG}" - docker push "${BUILD_TO}:dev" - - - deploy-docker-manifest: - if: github.repository == 'esphome/esphome' - runs-on: ubuntu-latest - needs: [deploy-docker] - steps: - - name: Enable experimental manifest support - run: | - mkdir -p ~/.docker - echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json - - name: Set TAG - run: | - TAG="${GITHUB_SHA:0:7}" - echo "TAG=${TAG}" >> $GITHUB_ENV - - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - name: "Create the manifest" - run: | - docker manifest create esphome/esphome:${TAG} \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:${TAG} - - docker manifest create esphome/esphome:dev \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:dev diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 383f2878e5..1b15b540f6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,164 +1,35 @@ name: Publish Release on: + workflow_dispatch: release: types: [published] + schedule: + - cron: "0 2 * * *" jobs: - # THE LINT/TEST JOBS ARE COPIED FROM ci.yaml - - lint-clang-format: + init: + name: Initialize build runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 + outputs: + tag: ${{ steps.tag.outputs.tag }} steps: - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - name: Run clang-format - run: script/clang-format -i - - name: Suggest changes - run: script/ci-suggest-changes - - lint-clang-tidy: - runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files - strategy: - fail-fast: false - matrix: - split: [1, 2, 3, 4] - steps: - - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - - name: Register problem matchers + - name: Get tag + id: tag run: | - echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" - echo "::add-matcher::.github/workflows/matchers/gcc.json" - - name: Run clang-tidy - run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }} - - name: Suggest changes - run: script/ci-suggest-changes - - lint-python: - # Don't use the esphome-lint docker image because it may contain outdated requirements. - # This way, all dependencies are cached via the cache action. - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up python environment - run: script/setup - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/ci-custom.json" - echo "::add-matcher::.github/workflows/matchers/lint-python.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Lint Custom - run: script/ci-custom.py - - name: Lint Python - run: script/lint-python - - name: Lint CODEOWNERS - run: script/build_codeowners.py --check - - test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - test: - - test1 - - test2 - - test3 - - test4 - - test5 - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - # Use per test platformio cache because tests have different platform versions - - name: Cache ~/.platformio - uses: actions/cache@v1 - with: - path: ~/.platformio - key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }} - restore-keys: | - test-home-platformio-${{ matrix.test }}- - - name: Set up environment - run: script/setup - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/gcc.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - run: esphome compile tests/${{ matrix.test }}.yaml - - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up environment - run: script/setup - - name: Install Github Actions annotator - run: pip install pytest-github-actions-annotate-failures - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Run pytest - run: | - pytest \ - -qq \ - --durations=10 \ - -o console_output_style=count \ - tests + if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then + TAG="${GITHUB_REF#refs/tags/v}" + else + TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p") + today="$(date --utc '+%Y%m%d')" + TAG="${TAG}${today}" + fi + echo "::set-output name=tag::${TAG}" deploy-pypi: name: Build and publish to PyPi - if: github.repository == 'esphome/esphome' - needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest] + if: github.repository == 'esphome/esphome' && github.event_name == 'release' runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -182,119 +53,85 @@ jobs: name: Build and publish docker containers if: github.repository == 'esphome/esphome' runs-on: ubuntu-latest - needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest] + needs: [init] strategy: matrix: arch: [amd64, armv7, aarch64] - build_type: ["hassio", "docker"] + build_type: ["ha-addon", "docker"] steps: - - uses: actions/checkout@v2 - - name: Set TAG - run: | - TAG="${GITHUB_REF#refs/tags/v}" - echo "TAG=${TAG}" >> $GITHUB_ENV - - name: Set up env variables - run: | - base_version="3.4.0" + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' - if [[ "${{ matrix.build_type }}" == "hassio" ]]; then - build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-hassio-${{ matrix.arch }}" - dockerfile="docker/Dockerfile.hassio" - else - build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-${{ matrix.arch }}" - dockerfile="docker/Dockerfile" - fi + - name: Run build + run: | + docker/build.py \ + --tag "${{ needs.init.outputs.tag }}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + build - if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then - cache_tag="beta" - else - cache_tag="latest" - fi + - name: Log in to docker hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - # Set env variables so these values don't need to be calculated again - echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV - echo "BUILD_TO=${build_to}" >> $GITHUB_ENV - echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV - echo "CACHE_TAG=${cache_tag}" >> $GITHUB_ENV - - name: Pull for cache - run: | - docker pull "${BUILD_TO}:${CACHE_TAG}" || true - - name: Register QEMU binfmt - run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes - - run: | - docker build \ - --build-arg "BUILD_FROM=${BUILD_FROM}" \ - --build-arg "BUILD_VERSION=${TAG}" \ - --tag "${BUILD_TO}:${TAG}" \ - --cache-from "${BUILD_TO}:${CACHE_TAG}" \ - --file "${DOCKERFILE}" \ - . - - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - run: docker push "${BUILD_TO}:${TAG}" - - # Always publish to beta tag (also full releases) - - name: Publish docker beta tag - run: | - docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:beta" - docker push "${BUILD_TO}:beta" - - - if: ${{ !github.event.release.prerelease }} - name: Publish docker latest tag - run: | - docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:latest" - docker push "${BUILD_TO}:latest" + - name: Run push + run: | + docker/build.py \ + --tag "${{ needs.init.outputs.tag }}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + push deploy-docker-manifest: if: github.repository == 'esphome/esphome' runs-on: ubuntu-latest - needs: [deploy-docker] + needs: [init, deploy-docker] + strategy: + matrix: + build_type: ["ha-addon", "docker"] steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' - name: Enable experimental manifest support run: | mkdir -p ~/.docker echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json - - name: Set TAG - run: | - TAG="${GITHUB_REF#refs/tags/v}" - echo "TAG=${TAG}" >> $GITHUB_ENV + - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - name: "Create the manifest" - run: | - docker manifest create esphome/esphome:${TAG} \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:${TAG} + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Publish docker beta tag + - name: Run manifest run: | - docker manifest create esphome/esphome:beta \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:beta - - - name: Publish docker latest tag - if: ${{ !github.event.release.prerelease }} - run: | - docker manifest create esphome/esphome:latest \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:latest + docker/build.py \ + --tag "${{ needs.init.outputs.tag }}" \ + --build-type "${{ matrix.build_type }}" \ + manifest deploy-hassio-repo: - if: github.repository == 'esphome/esphome' + if: github.repository == 'esphome/esphome' && github.event_name == 'release' runs-on: ubuntu-latest needs: [deploy-docker] steps: diff --git a/docker/Dockerfile b/docker/Dockerfile index 0d126a2944..907c041119 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG BUILD_FROM=esphome/esphome-base-amd64:3.4.0 +ARG BUILD_FROM=esphome/esphome-base:latest FROM ${BUILD_FROM} # First install requirements to leverage caching when requirements don't change diff --git a/docker/Dockerfile.hassio b/docker/Dockerfile.hassio index 5dd9339b18..ad80074ada 100644 --- a/docker/Dockerfile.hassio +++ b/docker/Dockerfile.hassio @@ -1,4 +1,4 @@ -ARG BUILD_FROM +ARG BUILD_FROM=esphome/esphome-hassio-base:latest FROM ${BUILD_FROM} # First install requirements to leverage caching when requirements don't change diff --git a/docker/Dockerfile.lint b/docker/Dockerfile.lint index 60d63152a0..3a090c3b41 100644 --- a/docker/Dockerfile.lint +++ b/docker/Dockerfile.lint @@ -1,4 +1,5 @@ -FROM esphome/esphome-lint-base:3.4.0 +ARG BUILD_FROM=esphome/esphome-lint-base:latest +FROM ${BUILD_FROM} COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini / RUN \ diff --git a/docker/build.py b/docker/build.py new file mode 100755 index 0000000000..97222df8d1 --- /dev/null +++ b/docker/build.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +from dataclasses import dataclass +import subprocess +import argparse +import platform +import shlex +import re +import sys + + +CHANNEL_DEV = 'dev' +CHANNEL_BETA = 'beta' +CHANNEL_RELEASE = 'release' +CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE] + +ARCH_AMD64 = 'amd64' +ARCH_ARMV7 = 'armv7' +ARCH_AARCH64 = 'aarch64' +ARCHS = [ARCH_AMD64, ARCH_ARMV7, ARCH_AARCH64] + +TYPE_DOCKER = 'docker' +TYPE_HA_ADDON = 'ha-addon' +TYPE_LINT = 'lint' +TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT] + + +BASE_VERSION = "3.6.0" + + +parser = argparse.ArgumentParser() +parser.add_argument("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag") +parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for") +parser.add_argument("--build-type", choices=TYPES, required=True, help="The type of build to run") +parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them") +subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True) +build_parser = subparsers.add_parser("build", help="Build the image") +push_parser = subparsers.add_parser("push", help="Tag the already built image and push it to docker hub") +manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images") + + + +# only lists some possibilities, doesn't have to be perfect +# https://stackoverflow.com/a/45125525 +UNAME_TO_ARCH = { + "x86_64": ARCH_AMD64, + "aarch64": ARCH_AARCH64, + "aarch64_be": ARCH_AARCH64, + "arm": ARCH_ARMV7, +} + + +@dataclass(frozen=True) +class DockerParams: + build_from: str + build_to: str + manifest_to: str + dockerfile: str + + @classmethod + def for_type_arch(cls, build_type, arch): + prefix = { + TYPE_DOCKER: "esphome/esphome", + TYPE_HA_ADDON: "esphome/esphome-hassio", + TYPE_LINT: "esphome/esphome-lint" + }[build_type] + build_from = f"ghcr.io/{prefix}-base-{arch}:{BASE_VERSION}" + build_to = f"{prefix}-{arch}" + dockerfile = { + TYPE_DOCKER: "docker/Dockerfile", + TYPE_HA_ADDON: "docker/Dockerfile.hassio", + TYPE_LINT: "docker/Dockerfile.lint", + }[build_type] + return cls( + build_from=build_from, + build_to=build_to, + manifest_to=prefix, + dockerfile=dockerfile + ) + + +def main(): + args = parser.parse_args() + + def run_command(*cmd, ignore_error: bool = False): + print(f"$ {shlex.join(list(cmd))}") + if not args.dry_run: + rc = subprocess.call(list(cmd)) + if rc != 0 and not ignore_error: + print("Command failed") + sys.exit(1) + + # detect channel from tag + match = re.match(r'\d+\.\d+(?:\.\d+)?(b\d+)?', args.tag) + if match is None: + channel = CHANNEL_DEV + elif match.group(1) is None: + channel = CHANNEL_RELEASE + else: + channel = CHANNEL_BETA + + tags_to_push = [args.tag] + if channel == CHANNEL_DEV: + tags_to_push.append("dev") + elif channel == CHANNEL_BETA: + tags_to_push.append("beta") + elif channel == CHANNEL_RELEASE: + # Additionally push to beta + tags_to_push.append("beta") + tags_to_push.append("latest") + + if args.command == "build": + # 1. pull cache image + params = DockerParams.for_type_arch(args.build_type, args.arch) + cache_tag = { + CHANNEL_DEV: "dev", + CHANNEL_BETA: "beta", + CHANNEL_RELEASE: "latest", + }[channel] + cache_img = f"ghcr.io/{params.build_to}:{cache_tag}" + run_command("docker", "pull", cache_img, ignore_error=True) + + # 2. register QEMU binfmt (if not host arch) + is_native = UNAME_TO_ARCH.get(platform.machine()) == args.arch + if not is_native: + run_command( + "docker", "run", "--rm", "--privileged", "multiarch/qemu-user-static:5.2.0-2", + "--reset", "-p", "yes" + ) + + # 3. build + run_command( + "docker", "build", + "--build-arg", f"BUILD_FROM={params.build_from}", + "--build-arg", f"BUILD_VERSION={args.tag}", + "--tag", f"{params.build_to}:{args.tag}", + "--cache-from", cache_img, + "--file", params.dockerfile, + "." + ) + elif args.command == "push": + params = DockerParams.for_type_arch(args.build_type, args.arch) + imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push] + imgs += [f"ghcr.io/{params.build_to}:{tag}" for tag in tags_to_push] + src = imgs[0] + # 1. tag images + for img in imgs[1:]: + run_command( + "docker", "tag", src, img + ) + # 2. push images + for img in imgs: + run_command( + "docker", "push", img + ) + elif args.command == "manifest": + manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to + + targets = [f"{manifest}:{tag}" for tag in tags_to_push] + targets += [f"ghcr.io/{manifest}:{tag}" for tag in tags_to_push] + # 1. Create manifests + for target in targets: + cmd = ["docker", "manifest", "create", target] + for arch in ARCHS: + src = f"{DockerParams.for_type_arch(args.build_type, arch).build_to}:{args.tag}" + if target.startswith("ghcr.io"): + src = f"ghcr.io/{src}" + cmd.append(src) + run_command(*cmd) + # 2. Push manifests + for target in targets: + run_command( + "docker", "manifest", "push", target + ) + + +if __name__ == "__main__": + main() diff --git a/esphome/const.py b/esphome/const.py index 35f1e560e1..c336f38b22 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,10 +1,6 @@ """Constants used by esphome.""" -MAJOR_VERSION = 1 -MINOR_VERSION = 20 -PATCH_VERSION = "0b2" -__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" -__version__ = f"{__short_version__}.{PATCH_VERSION}" +__version__ = "1.20.0b2" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" diff --git a/script/bump-docker-base-version.py b/script/bump-docker-base-version.py deleted file mode 100755 index f5f4399fd2..0000000000 --- a/script/bump-docker-base-version.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import re -import sys - - -def sub(path, pattern, repl, expected_count=1): - with open(path) as fh: - content = fh.read() - content, count = re.subn(pattern, repl, content, flags=re.MULTILINE) - if expected_count is not None: - assert count == expected_count, f"Pattern {pattern} replacement failed!" - with open(path, "wt") as fh: - fh.write(content) - - -def write_version(version: str): - for p in [ - ".github/workflows/ci-docker.yml", - ".github/workflows/release-dev.yml", - ".github/workflows/release.yml", - ]: - sub(p, r'base_version=".*"', f'base_version="{version}"') - - sub( - "docker/Dockerfile", - r"ARG BUILD_FROM=esphome/esphome-base-amd64:.*", - f"ARG BUILD_FROM=esphome/esphome-base-amd64:{version}", - ) - sub( - "docker/Dockerfile.lint", - r"FROM esphome/esphome-lint-base:.*", - f"FROM esphome/esphome-lint-base:{version}", - ) - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("new_version", type=str) - args = parser.parse_args() - - version = args.new_version - print(f"Bumping to {version}") - write_version(version) - return 0 - - -if __name__ == "__main__": - sys.exit(main() or 0) diff --git a/script/bump-version.py b/script/bump-version.py index b7b048eb22..1f034344f9 100755 --- a/script/bump-version.py +++ b/script/bump-version.py @@ -50,16 +50,10 @@ def sub(path, pattern, repl, expected_count=1): def write_version(version: Version): - sub( - "esphome/const.py", r"^MAJOR_VERSION = \d+$", f"MAJOR_VERSION = {version.major}" - ) - sub( - "esphome/const.py", r"^MINOR_VERSION = \d+$", f"MINOR_VERSION = {version.minor}" - ) sub( "esphome/const.py", - r"^PATCH_VERSION = .*$", - f'PATCH_VERSION = "{version.full_patch}"', + r"^__version__ = .*$", + f'__version__ = "{version}"', ) From 70902029f88e2faf2a9ed41140febdede08e1331 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 15 Jul 2021 21:51:52 +0200 Subject: [PATCH 1024/1841] GH Actions CI use GHCR (#2027) --- .github/workflows/ci.yml | 2 +- docker/build.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de777d5a3b..4ccaaa47b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: # cpp lint job runs with esphome-lint docker image so that clang-format-* # doesn't have to be installed - container: esphome/esphome-lint:1.1 + container: ghcr.io/esphome/esphome-lint:1.1 steps: - uses: actions/checkout@v2 # Set up the pio project so that the cpp checks know how files are compiled diff --git a/docker/build.py b/docker/build.py index 97222df8d1..54a279f845 100755 --- a/docker/build.py +++ b/docker/build.py @@ -90,7 +90,7 @@ def main(): sys.exit(1) # detect channel from tag - match = re.match(r'\d+\.\d+(?:\.\d+)?(b\d+)?', args.tag) + match = re.match(r'^\d+\.\d+(?:\.\d+)?(b\d+)?$', args.tag) if match is None: channel = CHANNEL_DEV elif match.group(1) is None: From 7787fa8f29cbc4c1753c4e5c30cbf386620689d4 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 16 Jul 2021 10:22:42 +0200 Subject: [PATCH 1025/1841] Dashboard disable assets caching (#2025) --- esphome/dashboard/dashboard.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 00b12199c0..66f72bfc00 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -782,10 +782,9 @@ def make_app(debug=get_bool_env(ENV_DEV)): class StaticFileHandler(tornado.web.StaticFileHandler): def set_extra_headers(self, path): - if debug: - self.set_header( - "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0" - ) + self.set_header( + "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0" + ) app_settings = { "debug": debug, From f1e3ff2ed2c799a702e8f518d9b78fe558680283 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 16 Jul 2021 10:23:08 +0200 Subject: [PATCH 1026/1841] Improve external components error messages (#2026) --- .../external_components/__init__.py | 95 +++++++++++-------- 1 file changed, 58 insertions(+), 37 deletions(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index 1602ac3b07..bf44dc1929 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -109,7 +109,15 @@ def _compute_destination_path(key: str) -> Path: return base_dir / h.hexdigest()[:8] -def _handle_git_response(ret): +def _run_git_command(cmd): + try: + ret = subprocess.run(cmd, capture_output=True, check=False) + except FileNotFoundError as err: + raise cv.Invalid( + "git is not installed but required for external_components.\n" + "Please see https://git-scm.com/book/en/v2/Getting-Started-Installing-Git for installing git" + ) from err + if ret.returncode != 0 and ret.stderr: err_str = ret.stderr.decode("utf-8") lines = [x.strip() for x in err_str.splitlines()] @@ -118,46 +126,59 @@ def _handle_git_response(ret): raise cv.Invalid(err_str) +def _process_git_config(config: dict, refresh) -> str: + key = f"{config[CONF_URL]}@{config.get(CONF_REF)}" + repo_dir = _compute_destination_path(key) + if not repo_dir.is_dir(): + _LOGGER.info("Cloning %s", key) + _LOGGER.debug("Location: %s", repo_dir) + cmd = ["git", "clone", "--depth=1"] + if CONF_REF in config: + cmd += ["--branch", config[CONF_REF]] + cmd += ["--", config[CONF_URL], str(repo_dir)] + _run_git_command(cmd) + + else: + # Check refresh needed + file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD") + # On first clone, FETCH_HEAD does not exists + if not file_timestamp.exists(): + file_timestamp = Path(repo_dir / ".git" / "HEAD") + age = datetime.datetime.now() - datetime.datetime.fromtimestamp( + file_timestamp.stat().st_mtime + ) + if age.seconds > refresh.total_seconds: + _LOGGER.info("Updating %s", key) + _LOGGER.debug("Location: %s", repo_dir) + # Stash local changes (if any) + _run_git_command(["git", "stash", "push", "--include-untracked"]) + # Fetch remote ref + cmd = ["git", "fetch", "--", "origin"] + if CONF_REF in config: + cmd.append(config[CONF_REF]) + _run_git_command(cmd) + # Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch) + _run_git_command(["git", "reset", "--hard", "FETCH_HEAD"]) + + if (repo_dir / "esphome" / "components").is_dir(): + components_dir = repo_dir / "esphome" / "components" + elif (repo_dir / "components").is_dir(): + components_dir = repo_dir / "components" + else: + raise cv.Invalid( + "Could not find components folder for source. Please check the source contains a 'components' or 'esphome/components' folder" + ) + + return components_dir + + def _process_single_config(config: dict): conf = config[CONF_SOURCE] if conf[CONF_TYPE] == TYPE_GIT: - key = f"{conf[CONF_URL]}@{conf.get(CONF_REF)}" - repo_dir = _compute_destination_path(key) - if not repo_dir.is_dir(): - cmd = ["git", "clone", "--depth=1"] - if CONF_REF in conf: - cmd += ["--branch", conf[CONF_REF]] - cmd += [conf[CONF_URL], str(repo_dir)] - ret = subprocess.run(cmd, capture_output=True, check=False) - _handle_git_response(ret) - - else: - # Check refresh needed - file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD") - # On first clone, FETCH_HEAD does not exists - if not file_timestamp.exists(): - file_timestamp = Path(repo_dir / ".git" / "HEAD") - age = datetime.datetime.now() - datetime.datetime.fromtimestamp( - file_timestamp.stat().st_mtime + with cv.prepend_path([CONF_SOURCE]): + components_dir = _process_git_config( + config[CONF_SOURCE], config[CONF_REFRESH] ) - if age.seconds > config[CONF_REFRESH].total_seconds: - _LOGGER.info("Executing git pull %s", key) - cmd = ["git", "pull"] - ret = subprocess.run( - cmd, cwd=repo_dir, capture_output=True, check=False - ) - _handle_git_response(ret) - - if (repo_dir / "esphome" / "components").is_dir(): - components_dir = repo_dir / "esphome" / "components" - elif (repo_dir / "components").is_dir(): - components_dir = repo_dir / "components" - else: - raise cv.Invalid( - "Could not find components folder for source. Please check the source contains a 'components' or 'esphome/components' folder", - [CONF_SOURCE], - ) - elif conf[CONF_TYPE] == TYPE_LOCAL: components_dir = Path(CORE.relative_config_path(conf[CONF_PATH])) else: From 46e50ba53ff99fdd83fd1997c12c8adcd9e884e9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 19 Jul 2021 08:28:58 +1200 Subject: [PATCH 1027/1841] Bump version to v1.20.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index c336f38b22..65c6458dd6 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.0b2" +__version__ = "1.20.0b3" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 71d9d64a020db671aa7b2ee7c3193160017ae9d6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 20 Jul 2021 08:22:49 +1200 Subject: [PATCH 1028/1841] Number and Template Number updates (#2036) Co-authored-by: Otto winter --- esphome/components/api/api_connection.cpp | 8 +- esphome/components/mqtt/mqtt_number.cpp | 22 +++-- esphome/components/number/__init__.py | 27 ++++-- esphome/components/number/number.cpp | 69 ++++---------- esphome/components/number/number.h | 93 +++++++------------ .../components/template/number/__init__.py | 25 +++-- .../template/number/template_number.cpp | 33 ++++--- .../template/number/template_number.h | 19 ++-- esphome/components/web_server/web_server.cpp | 5 +- 9 files changed, 148 insertions(+), 153 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 8c76583fc7..79ffcfa69e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -570,11 +570,11 @@ bool APIConnection::send_number_info(number::Number *number) { msg.object_id = number->get_object_id(); msg.name = number->get_name(); msg.unique_id = get_default_unique_id("number", number); - msg.icon = number->get_icon(); + msg.icon = number->traits.get_icon(); - msg.min_value = number->get_min_value(); - msg.max_value = number->get_max_value(); - msg.step = number->get_step(); + msg.min_value = number->traits.get_min_value(); + msg.max_value = number->traits.get_max_value(); + msg.step = number->traits.get_step(); return this->send_list_entities_number_response(msg); } diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index bb67a225fd..0311526340 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -3,10 +3,6 @@ #ifdef USE_NUMBER -#ifdef USE_DEEP_SLEEP -#include "esphome/components/deep_sleep/deep_sleep_component.h" -#endif - namespace esphome { namespace mqtt { @@ -20,7 +16,7 @@ void MQTTNumberComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) { auto val = parse_float(state); if (!val.has_value()) { - ESP_LOGE(TAG, "Can't convert '%s' to number!", state.c_str()); + ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str()); return; } auto call = this->number_->make_call(); @@ -39,8 +35,15 @@ std::string MQTTNumberComponent::component_type() const { return "number"; } std::string MQTTNumberComponent::friendly_name() const { return this->number_->get_name(); } void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { - if (!this->number_->get_icon().empty()) - root["icon"] = this->number_->get_icon(); + const auto &traits = number_->traits; + // https://www.home-assistant.io/integrations/number.mqtt/ + if (!traits.get_icon().empty()) + root["icon"] = traits.get_icon(); + root["min_value"] = traits.get_min_value(); + root["max_value"] = traits.get_max_value(); + root["step"] = traits.get_step(); + + config.command_topic = true; } bool MQTTNumberComponent::send_initial_state() { if (this->number_->has_state()) { @@ -51,8 +54,9 @@ bool MQTTNumberComponent::send_initial_state() { } bool MQTTNumberComponent::is_internal() { return this->number_->is_internal(); } bool MQTTNumberComponent::publish_state(float value) { - int8_t accuracy = this->number_->get_accuracy_decimals(); - return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy)); + char buffer[64]; + snprintf(buffer, sizeof(buffer), "%f", value); + return this->publish(this->get_state_topic_(), buffer); } } // namespace mqtt diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index ed33931d8b..bf95cb1b31 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -1,3 +1,4 @@ +from typing import Optional import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation @@ -66,12 +67,18 @@ NUMBER_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( ) -async def setup_number_core_(var, config): +async def setup_number_core_( + var, config, *, min_value: float, max_value: float, step: Optional[float] +): cg.add(var.set_name(config[CONF_NAME])) if CONF_INTERNAL in config: cg.add(var.set_internal(config[CONF_INTERNAL])) - cg.add(var.set_icon(config[CONF_ICON])) + cg.add(var.traits.set_icon(config[CONF_ICON])) + cg.add(var.traits.set_min_value(min_value)) + cg.add(var.traits.set_max_value(max_value)) + if step is not None: + cg.add(var.traits.set_step(step)) for conf in config.get(CONF_ON_VALUE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) @@ -92,16 +99,24 @@ async def setup_number_core_(var, config): await mqtt.register_mqtt_component(mqtt_, config) -async def register_number(var, config): +async def register_number( + var, config, *, min_value: float, max_value: float, step: Optional[float] = None +): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) cg.add(cg.App.register_number(var)) - await setup_number_core_(var, config) + await setup_number_core_( + var, config, min_value=min_value, max_value=max_value, step=step + ) -async def new_number(config): +async def new_number( + config, *, min_value: float, max_value: float, step: Optional[float] = None +): var = cg.new_Pvariable(config[CONF_ID]) - await register_number(var, config) + await register_number( + var, config, min_value=min_value, max_value=max_value, step=step + ) return var diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index eaee5d4e69..dbc1c88a5d 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -8,67 +8,38 @@ static const char *const TAG = "number"; void NumberCall::perform() { ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); - if (this->value_.has_value()) { - auto value = *this->value_; - uint8_t accuracy = this->parent_->get_accuracy_decimals(); - float min_value = this->parent_->get_min_value(); - if (value < min_value) { - ESP_LOGW(TAG, " Value %s must not be less than minimum %s", value_accuracy_to_string(value, accuracy).c_str(), - value_accuracy_to_string(min_value, accuracy).c_str()); - this->value_.reset(); - return; - } - float max_value = this->parent_->get_max_value(); - if (value > max_value) { - ESP_LOGW(TAG, " Value %s must not be larger than maximum %s", value_accuracy_to_string(value, accuracy).c_str(), - value_accuracy_to_string(max_value, accuracy).c_str()); - this->value_.reset(); - return; - } - ESP_LOGD(TAG, " Value: %s", value_accuracy_to_string(*this->value_, accuracy).c_str()); - this->parent_->set(*this->value_); + if (!this->value_.has_value() || isnan(*this->value_)) { + ESP_LOGW(TAG, "No value set for NumberCall"); + return; } + + const auto &traits = this->parent_->traits; + auto value = *this->value_; + + float min_value = traits.get_min_value(); + if (value < min_value) { + ESP_LOGW(TAG, " Value %f must not be less than minimum %f", value, min_value); + return; + } + float max_value = traits.get_max_value(); + if (value > max_value) { + ESP_LOGW(TAG, " Value %f must not be greater than maximum %f", value, max_value); + return; + } + ESP_LOGD(TAG, " Value: %f", *this->value_); + this->parent_->control(*this->value_); } -NumberCall &NumberCall::set_value(float value) { - this->value_ = value; - return *this; -} - -const optional &NumberCall::get_value() const { return this->value_; } - -NumberCall Number::make_call() { return NumberCall(this); } - void Number::publish_state(float state) { this->has_state_ = true; this->state = state; - ESP_LOGD(TAG, "'%s': Sending state %.5f", this->get_name().c_str(), state); + ESP_LOGD(TAG, "'%s': Sending state %f", this->get_name().c_str(), state); this->state_callback_.call(state); } -uint32_t Number::update_interval() { return 0; } -Number::Number(const std::string &name) : Nameable(name), state(NAN) {} -Number::Number() : Number("") {} - void Number::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } -void Number::set_icon(const std::string &icon) { this->icon_ = icon; } -std::string Number::get_icon() { return *this->icon_; } -int8_t Number::get_accuracy_decimals() { - // use printf %g to find number of digits based on step - char buf[32]; - sprintf(buf, "%.5g", this->step_); - std::string str{buf}; - size_t dot_pos = str.find('.'); - if (dot_pos == std::string::npos) - return 0; - - return str.length() - dot_pos - 1; -} -float Number::get_state() const { return this->state; } - -bool Number::has_state() const { return this->has_state_; } uint32_t Number::hash_base() { return 2282307003UL; } diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index 4fe9692a6b..e32b53187b 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -9,8 +9,8 @@ namespace number { #define LOG_NUMBER(prefix, type, obj) \ if ((obj) != nullptr) { \ ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ - if (!(obj)->get_icon().empty()) { \ - ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ + if (!(obj)->traits.get_icon().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->traits.get_icon().c_str()); \ } \ } @@ -19,72 +19,56 @@ class Number; class NumberCall { public: explicit NumberCall(Number *parent) : parent_(parent) {} - NumberCall &set_value(float value); void perform(); - const optional &get_value() const; + NumberCall &set_value(float value) { + value_ = value; + return *this; + } + const optional &get_value() const { return value_; } protected: Number *const parent_; optional value_; }; +class NumberTraits { + public: + void set_min_value(float min_value) { min_value_ = min_value; } + float get_min_value() const { return min_value_; } + void set_max_value(float max_value) { max_value_ = max_value; } + float get_max_value() const { return max_value_; } + void set_step(float step) { step_ = step; } + float get_step() const { return step_; } + void set_icon(std::string icon) { icon_ = std::move(icon); } + const std::string &get_icon() const { return icon_; } + + protected: + float min_value_ = NAN; + float max_value_ = NAN; + float step_ = NAN; + std::string icon_; +}; + /** Base-class for all numbers. * * A number can use publish_state to send out a new value. */ class Number : public Nameable { public: - explicit Number(); - explicit Number(const std::string &name); - - /** Manually set the icon of this number. By default the number's default defined by icon() is used. - * - * @param icon The icon, for example "mdi:flash". "" to disable. - */ - void set_icon(const std::string &icon); - /// Get the Home Assistant Icon. Uses the manual override if specified or the default value instead. - std::string get_icon(); - - /// Getter-syntax for .state. - float get_state() const; - - /// Get the accuracy in decimals. Based on the step value. - int8_t get_accuracy_decimals(); - - /** Publish the current state to the front-end. - */ - void publish_state(float state); - - NumberCall make_call(); - - // ========== INTERNAL METHODS ========== - // (In most use cases you won't need these) - /// Add a callback that will be called every time the state changes. - void add_on_state_callback(std::function &&callback); - - /** This member variable stores the last state. - * - * On startup, when no state is available yet, this is NAN (not-a-number) and the validity - * can be checked using has_state(). - * - * This is exposed through a member variable for ease of use in esphome lambdas. - */ float state; + void publish_state(float state); + + NumberCall make_call() { return NumberCall(this); } + void set(float value) { make_call().set_value(value).perform(); } + + void add_on_state_callback(std::function &&callback); + + NumberTraits traits; + /// Return whether this number has gotten a full state yet. - bool has_state() const; - - /// Return with which interval the number is polled. Return 0 for non-polling mode. - virtual uint32_t update_interval(); - - void set_min_value(float min_value) { this->min_value_ = min_value; } - void set_max_value(float max_value) { this->max_value_ = max_value; } - void set_step(float step) { this->step_ = step; } - - float get_min_value() const { return this->min_value_; } - float get_max_value() const { return this->max_value_; } - float get_step() const { return this->step_; } + bool has_state() const { return has_state_; } protected: friend class NumberCall; @@ -95,17 +79,12 @@ class Number : public Nameable { * * @param value The value as validated by the NumberCall. */ - virtual void set(float value) = 0; + virtual void control(float value) = 0; uint32_t hash_base() override; CallbackManager state_callback_; - /// Override the icon advertised to Home Assistant, otherwise number's icon will be used. - optional icon_; bool has_state_{false}; - float step_{1.0}; - float min_value_{0}; - float max_value_{100}; }; } // namespace number diff --git a/esphome/components/template/number/__init__.py b/esphome/components/template/number/__init__.py index cf70a48c4d..557a01c6fa 100644 --- a/esphome/components/template/number/__init__.py +++ b/esphome/components/template/number/__init__.py @@ -4,6 +4,7 @@ import esphome.config_validation as cv from esphome.components import number from esphome.const import ( CONF_ID, + CONF_INITIAL_VALUE, CONF_LAMBDA, CONF_MAX_VALUE, CONF_MIN_VALUE, @@ -32,9 +33,10 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_MAX_VALUE): cv.float_, cv.Required(CONF_MIN_VALUE): cv.float_, cv.Required(CONF_STEP): cv.positive_float, - cv.Optional(CONF_LAMBDA): cv.returning_lambda, - cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + cv.Exclusive(CONF_LAMBDA, "lambda-optimistic"): cv.returning_lambda, + cv.Exclusive(CONF_OPTIMISTIC, "lambda-optimistic"): cv.boolean, cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_INITIAL_VALUE): cv.float_, } ).extend(cv.polling_component_schema("60s")), validate_min_max, @@ -44,20 +46,27 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - await number.register_number(var, config) + await number.register_number( + var, + config, + min_value=config[CONF_MIN_VALUE], + max_value=config[CONF_MAX_VALUE], + step=config[CONF_STEP], + ) if CONF_LAMBDA in config: template_ = await cg.process_lambda( config[CONF_LAMBDA], [], return_type=cg.optional.template(float) ) cg.add(var.set_template(template_)) + + elif CONF_OPTIMISTIC in config: + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + if CONF_SET_ACTION in config: await automation.build_automation( var.get_set_trigger(), [(float, "x")], config[CONF_SET_ACTION] ) - cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) - - cg.add(var.set_min_value(config[CONF_MIN_VALUE])) - cg.add(var.set_max_value(config[CONF_MAX_VALUE])) - cg.add(var.set_step(config[CONF_STEP])) + if CONF_INITIAL_VALUE in config: + cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) diff --git a/esphome/components/template/number/template_number.cpp b/esphome/components/template/number/template_number.cpp index 69c5d62684..500f9f2272 100644 --- a/esphome/components/template/number/template_number.cpp +++ b/esphome/components/template/number/template_number.cpp @@ -6,34 +6,45 @@ namespace template_ { static const char *const TAG = "template.number"; -TemplateNumber::TemplateNumber() : set_trigger_(new Trigger()) {} +void TemplateNumber::setup() { + if (this->f_.has_value() || !this->optimistic_) + return; + + this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); + float value; + if (!this->pref_.load(&value)) { + if (!isnan(this->initial_value_)) + value = this->initial_value_; + else + value = this->traits.get_min_value(); + } + this->publish_state(value); +} void TemplateNumber::update() { if (!this->f_.has_value()) return; auto val = (*this->f_)(); - if (val.has_value()) { - this->publish_state(*val); - } + if (!val.has_value()) + return; + + this->publish_state(*val); } -void TemplateNumber::set(float value) { +void TemplateNumber::control(float value) { this->set_trigger_->trigger(value); - if (this->optimistic_) + if (this->optimistic_) { this->publish_state(value); + this->pref_.save(&value); + } } -float TemplateNumber::get_setup_priority() const { return setup_priority::HARDWARE; } -void TemplateNumber::set_template(std::function()> &&f) { this->f_ = f; } void TemplateNumber::dump_config() { LOG_NUMBER("", "Template Number", this); ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); LOG_UPDATE_INTERVAL(this); } -void TemplateNumber::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } -Trigger *TemplateNumber::get_set_trigger() const { return this->set_trigger_; }; - } // namespace template_ } // namespace esphome diff --git a/esphome/components/template/number/template_number.h b/esphome/components/template/number/template_number.h index 4c633e3b53..50cd256b7f 100644 --- a/esphome/components/template/number/template_number.h +++ b/esphome/components/template/number/template_number.h @@ -3,27 +3,32 @@ #include "esphome/components/number/number.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" +#include "esphome/core/preferences.h" namespace esphome { namespace template_ { class TemplateNumber : public number::Number, public PollingComponent { public: - TemplateNumber(); - void set_template(std::function()> &&f); + void set_template(std::function()> &&f) { this->f_ = f; } + void setup() override; void update() override; void dump_config() override; - float get_setup_priority() const override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } - Trigger *get_set_trigger() const; - void set_optimistic(bool optimistic); + Trigger *get_set_trigger() const { return set_trigger_; } + void set_optimistic(bool optimistic) { optimistic_ = optimistic; } + void set_initial_value(float initial_value) { initial_value_ = initial_value; } protected: - void set(float value) override; + void control(float value) override; bool optimistic_{false}; - Trigger *set_trigger_; + float initial_value_{NAN}; + Trigger *set_trigger_ = new Trigger(); optional()>> f_; + + ESPPreferenceObject pref_; }; } // namespace template_ diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 57eef7a946..b775d44211 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -614,8 +614,9 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM std::string WebServer::number_json(number::Number *obj, float value) { return json::build_json([obj, value](JsonObject &root) { root["id"] = "number-" + obj->get_object_id(); - std::string state = value_accuracy_to_string(value, obj->get_accuracy_decimals()); - root["state"] = state; + char buffer[64]; + snprintf(buffer, sizeof(buffer), "%f", value); + root["state"] = buffer; root["value"] = value; }); } From 98855e4123f236530509f7f40c3854d538375e3e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 20 Jul 2021 08:22:49 +1200 Subject: [PATCH 1029/1841] Number and Template Number updates (#2036) Co-authored-by: Otto winter --- esphome/components/api/api_connection.cpp | 8 +- esphome/components/mqtt/mqtt_number.cpp | 22 +++-- esphome/components/number/__init__.py | 27 ++++-- esphome/components/number/number.cpp | 69 ++++---------- esphome/components/number/number.h | 93 +++++++------------ .../components/template/number/__init__.py | 25 +++-- .../template/number/template_number.cpp | 33 ++++--- .../template/number/template_number.h | 19 ++-- esphome/components/web_server/web_server.cpp | 5 +- 9 files changed, 148 insertions(+), 153 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 8c76583fc7..79ffcfa69e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -570,11 +570,11 @@ bool APIConnection::send_number_info(number::Number *number) { msg.object_id = number->get_object_id(); msg.name = number->get_name(); msg.unique_id = get_default_unique_id("number", number); - msg.icon = number->get_icon(); + msg.icon = number->traits.get_icon(); - msg.min_value = number->get_min_value(); - msg.max_value = number->get_max_value(); - msg.step = number->get_step(); + msg.min_value = number->traits.get_min_value(); + msg.max_value = number->traits.get_max_value(); + msg.step = number->traits.get_step(); return this->send_list_entities_number_response(msg); } diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index bb67a225fd..0311526340 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -3,10 +3,6 @@ #ifdef USE_NUMBER -#ifdef USE_DEEP_SLEEP -#include "esphome/components/deep_sleep/deep_sleep_component.h" -#endif - namespace esphome { namespace mqtt { @@ -20,7 +16,7 @@ void MQTTNumberComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) { auto val = parse_float(state); if (!val.has_value()) { - ESP_LOGE(TAG, "Can't convert '%s' to number!", state.c_str()); + ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str()); return; } auto call = this->number_->make_call(); @@ -39,8 +35,15 @@ std::string MQTTNumberComponent::component_type() const { return "number"; } std::string MQTTNumberComponent::friendly_name() const { return this->number_->get_name(); } void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { - if (!this->number_->get_icon().empty()) - root["icon"] = this->number_->get_icon(); + const auto &traits = number_->traits; + // https://www.home-assistant.io/integrations/number.mqtt/ + if (!traits.get_icon().empty()) + root["icon"] = traits.get_icon(); + root["min_value"] = traits.get_min_value(); + root["max_value"] = traits.get_max_value(); + root["step"] = traits.get_step(); + + config.command_topic = true; } bool MQTTNumberComponent::send_initial_state() { if (this->number_->has_state()) { @@ -51,8 +54,9 @@ bool MQTTNumberComponent::send_initial_state() { } bool MQTTNumberComponent::is_internal() { return this->number_->is_internal(); } bool MQTTNumberComponent::publish_state(float value) { - int8_t accuracy = this->number_->get_accuracy_decimals(); - return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy)); + char buffer[64]; + snprintf(buffer, sizeof(buffer), "%f", value); + return this->publish(this->get_state_topic_(), buffer); } } // namespace mqtt diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index ed33931d8b..bf95cb1b31 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -1,3 +1,4 @@ +from typing import Optional import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation @@ -66,12 +67,18 @@ NUMBER_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( ) -async def setup_number_core_(var, config): +async def setup_number_core_( + var, config, *, min_value: float, max_value: float, step: Optional[float] +): cg.add(var.set_name(config[CONF_NAME])) if CONF_INTERNAL in config: cg.add(var.set_internal(config[CONF_INTERNAL])) - cg.add(var.set_icon(config[CONF_ICON])) + cg.add(var.traits.set_icon(config[CONF_ICON])) + cg.add(var.traits.set_min_value(min_value)) + cg.add(var.traits.set_max_value(max_value)) + if step is not None: + cg.add(var.traits.set_step(step)) for conf in config.get(CONF_ON_VALUE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) @@ -92,16 +99,24 @@ async def setup_number_core_(var, config): await mqtt.register_mqtt_component(mqtt_, config) -async def register_number(var, config): +async def register_number( + var, config, *, min_value: float, max_value: float, step: Optional[float] = None +): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) cg.add(cg.App.register_number(var)) - await setup_number_core_(var, config) + await setup_number_core_( + var, config, min_value=min_value, max_value=max_value, step=step + ) -async def new_number(config): +async def new_number( + config, *, min_value: float, max_value: float, step: Optional[float] = None +): var = cg.new_Pvariable(config[CONF_ID]) - await register_number(var, config) + await register_number( + var, config, min_value=min_value, max_value=max_value, step=step + ) return var diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index eaee5d4e69..dbc1c88a5d 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -8,67 +8,38 @@ static const char *const TAG = "number"; void NumberCall::perform() { ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); - if (this->value_.has_value()) { - auto value = *this->value_; - uint8_t accuracy = this->parent_->get_accuracy_decimals(); - float min_value = this->parent_->get_min_value(); - if (value < min_value) { - ESP_LOGW(TAG, " Value %s must not be less than minimum %s", value_accuracy_to_string(value, accuracy).c_str(), - value_accuracy_to_string(min_value, accuracy).c_str()); - this->value_.reset(); - return; - } - float max_value = this->parent_->get_max_value(); - if (value > max_value) { - ESP_LOGW(TAG, " Value %s must not be larger than maximum %s", value_accuracy_to_string(value, accuracy).c_str(), - value_accuracy_to_string(max_value, accuracy).c_str()); - this->value_.reset(); - return; - } - ESP_LOGD(TAG, " Value: %s", value_accuracy_to_string(*this->value_, accuracy).c_str()); - this->parent_->set(*this->value_); + if (!this->value_.has_value() || isnan(*this->value_)) { + ESP_LOGW(TAG, "No value set for NumberCall"); + return; } + + const auto &traits = this->parent_->traits; + auto value = *this->value_; + + float min_value = traits.get_min_value(); + if (value < min_value) { + ESP_LOGW(TAG, " Value %f must not be less than minimum %f", value, min_value); + return; + } + float max_value = traits.get_max_value(); + if (value > max_value) { + ESP_LOGW(TAG, " Value %f must not be greater than maximum %f", value, max_value); + return; + } + ESP_LOGD(TAG, " Value: %f", *this->value_); + this->parent_->control(*this->value_); } -NumberCall &NumberCall::set_value(float value) { - this->value_ = value; - return *this; -} - -const optional &NumberCall::get_value() const { return this->value_; } - -NumberCall Number::make_call() { return NumberCall(this); } - void Number::publish_state(float state) { this->has_state_ = true; this->state = state; - ESP_LOGD(TAG, "'%s': Sending state %.5f", this->get_name().c_str(), state); + ESP_LOGD(TAG, "'%s': Sending state %f", this->get_name().c_str(), state); this->state_callback_.call(state); } -uint32_t Number::update_interval() { return 0; } -Number::Number(const std::string &name) : Nameable(name), state(NAN) {} -Number::Number() : Number("") {} - void Number::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } -void Number::set_icon(const std::string &icon) { this->icon_ = icon; } -std::string Number::get_icon() { return *this->icon_; } -int8_t Number::get_accuracy_decimals() { - // use printf %g to find number of digits based on step - char buf[32]; - sprintf(buf, "%.5g", this->step_); - std::string str{buf}; - size_t dot_pos = str.find('.'); - if (dot_pos == std::string::npos) - return 0; - - return str.length() - dot_pos - 1; -} -float Number::get_state() const { return this->state; } - -bool Number::has_state() const { return this->has_state_; } uint32_t Number::hash_base() { return 2282307003UL; } diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index 4fe9692a6b..e32b53187b 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -9,8 +9,8 @@ namespace number { #define LOG_NUMBER(prefix, type, obj) \ if ((obj) != nullptr) { \ ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ - if (!(obj)->get_icon().empty()) { \ - ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ + if (!(obj)->traits.get_icon().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->traits.get_icon().c_str()); \ } \ } @@ -19,72 +19,56 @@ class Number; class NumberCall { public: explicit NumberCall(Number *parent) : parent_(parent) {} - NumberCall &set_value(float value); void perform(); - const optional &get_value() const; + NumberCall &set_value(float value) { + value_ = value; + return *this; + } + const optional &get_value() const { return value_; } protected: Number *const parent_; optional value_; }; +class NumberTraits { + public: + void set_min_value(float min_value) { min_value_ = min_value; } + float get_min_value() const { return min_value_; } + void set_max_value(float max_value) { max_value_ = max_value; } + float get_max_value() const { return max_value_; } + void set_step(float step) { step_ = step; } + float get_step() const { return step_; } + void set_icon(std::string icon) { icon_ = std::move(icon); } + const std::string &get_icon() const { return icon_; } + + protected: + float min_value_ = NAN; + float max_value_ = NAN; + float step_ = NAN; + std::string icon_; +}; + /** Base-class for all numbers. * * A number can use publish_state to send out a new value. */ class Number : public Nameable { public: - explicit Number(); - explicit Number(const std::string &name); - - /** Manually set the icon of this number. By default the number's default defined by icon() is used. - * - * @param icon The icon, for example "mdi:flash". "" to disable. - */ - void set_icon(const std::string &icon); - /// Get the Home Assistant Icon. Uses the manual override if specified or the default value instead. - std::string get_icon(); - - /// Getter-syntax for .state. - float get_state() const; - - /// Get the accuracy in decimals. Based on the step value. - int8_t get_accuracy_decimals(); - - /** Publish the current state to the front-end. - */ - void publish_state(float state); - - NumberCall make_call(); - - // ========== INTERNAL METHODS ========== - // (In most use cases you won't need these) - /// Add a callback that will be called every time the state changes. - void add_on_state_callback(std::function &&callback); - - /** This member variable stores the last state. - * - * On startup, when no state is available yet, this is NAN (not-a-number) and the validity - * can be checked using has_state(). - * - * This is exposed through a member variable for ease of use in esphome lambdas. - */ float state; + void publish_state(float state); + + NumberCall make_call() { return NumberCall(this); } + void set(float value) { make_call().set_value(value).perform(); } + + void add_on_state_callback(std::function &&callback); + + NumberTraits traits; + /// Return whether this number has gotten a full state yet. - bool has_state() const; - - /// Return with which interval the number is polled. Return 0 for non-polling mode. - virtual uint32_t update_interval(); - - void set_min_value(float min_value) { this->min_value_ = min_value; } - void set_max_value(float max_value) { this->max_value_ = max_value; } - void set_step(float step) { this->step_ = step; } - - float get_min_value() const { return this->min_value_; } - float get_max_value() const { return this->max_value_; } - float get_step() const { return this->step_; } + bool has_state() const { return has_state_; } protected: friend class NumberCall; @@ -95,17 +79,12 @@ class Number : public Nameable { * * @param value The value as validated by the NumberCall. */ - virtual void set(float value) = 0; + virtual void control(float value) = 0; uint32_t hash_base() override; CallbackManager state_callback_; - /// Override the icon advertised to Home Assistant, otherwise number's icon will be used. - optional icon_; bool has_state_{false}; - float step_{1.0}; - float min_value_{0}; - float max_value_{100}; }; } // namespace number diff --git a/esphome/components/template/number/__init__.py b/esphome/components/template/number/__init__.py index cf70a48c4d..557a01c6fa 100644 --- a/esphome/components/template/number/__init__.py +++ b/esphome/components/template/number/__init__.py @@ -4,6 +4,7 @@ import esphome.config_validation as cv from esphome.components import number from esphome.const import ( CONF_ID, + CONF_INITIAL_VALUE, CONF_LAMBDA, CONF_MAX_VALUE, CONF_MIN_VALUE, @@ -32,9 +33,10 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_MAX_VALUE): cv.float_, cv.Required(CONF_MIN_VALUE): cv.float_, cv.Required(CONF_STEP): cv.positive_float, - cv.Optional(CONF_LAMBDA): cv.returning_lambda, - cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + cv.Exclusive(CONF_LAMBDA, "lambda-optimistic"): cv.returning_lambda, + cv.Exclusive(CONF_OPTIMISTIC, "lambda-optimistic"): cv.boolean, cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_INITIAL_VALUE): cv.float_, } ).extend(cv.polling_component_schema("60s")), validate_min_max, @@ -44,20 +46,27 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - await number.register_number(var, config) + await number.register_number( + var, + config, + min_value=config[CONF_MIN_VALUE], + max_value=config[CONF_MAX_VALUE], + step=config[CONF_STEP], + ) if CONF_LAMBDA in config: template_ = await cg.process_lambda( config[CONF_LAMBDA], [], return_type=cg.optional.template(float) ) cg.add(var.set_template(template_)) + + elif CONF_OPTIMISTIC in config: + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + if CONF_SET_ACTION in config: await automation.build_automation( var.get_set_trigger(), [(float, "x")], config[CONF_SET_ACTION] ) - cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) - - cg.add(var.set_min_value(config[CONF_MIN_VALUE])) - cg.add(var.set_max_value(config[CONF_MAX_VALUE])) - cg.add(var.set_step(config[CONF_STEP])) + if CONF_INITIAL_VALUE in config: + cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) diff --git a/esphome/components/template/number/template_number.cpp b/esphome/components/template/number/template_number.cpp index 69c5d62684..500f9f2272 100644 --- a/esphome/components/template/number/template_number.cpp +++ b/esphome/components/template/number/template_number.cpp @@ -6,34 +6,45 @@ namespace template_ { static const char *const TAG = "template.number"; -TemplateNumber::TemplateNumber() : set_trigger_(new Trigger()) {} +void TemplateNumber::setup() { + if (this->f_.has_value() || !this->optimistic_) + return; + + this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); + float value; + if (!this->pref_.load(&value)) { + if (!isnan(this->initial_value_)) + value = this->initial_value_; + else + value = this->traits.get_min_value(); + } + this->publish_state(value); +} void TemplateNumber::update() { if (!this->f_.has_value()) return; auto val = (*this->f_)(); - if (val.has_value()) { - this->publish_state(*val); - } + if (!val.has_value()) + return; + + this->publish_state(*val); } -void TemplateNumber::set(float value) { +void TemplateNumber::control(float value) { this->set_trigger_->trigger(value); - if (this->optimistic_) + if (this->optimistic_) { this->publish_state(value); + this->pref_.save(&value); + } } -float TemplateNumber::get_setup_priority() const { return setup_priority::HARDWARE; } -void TemplateNumber::set_template(std::function()> &&f) { this->f_ = f; } void TemplateNumber::dump_config() { LOG_NUMBER("", "Template Number", this); ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); LOG_UPDATE_INTERVAL(this); } -void TemplateNumber::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } -Trigger *TemplateNumber::get_set_trigger() const { return this->set_trigger_; }; - } // namespace template_ } // namespace esphome diff --git a/esphome/components/template/number/template_number.h b/esphome/components/template/number/template_number.h index 4c633e3b53..50cd256b7f 100644 --- a/esphome/components/template/number/template_number.h +++ b/esphome/components/template/number/template_number.h @@ -3,27 +3,32 @@ #include "esphome/components/number/number.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" +#include "esphome/core/preferences.h" namespace esphome { namespace template_ { class TemplateNumber : public number::Number, public PollingComponent { public: - TemplateNumber(); - void set_template(std::function()> &&f); + void set_template(std::function()> &&f) { this->f_ = f; } + void setup() override; void update() override; void dump_config() override; - float get_setup_priority() const override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } - Trigger *get_set_trigger() const; - void set_optimistic(bool optimistic); + Trigger *get_set_trigger() const { return set_trigger_; } + void set_optimistic(bool optimistic) { optimistic_ = optimistic; } + void set_initial_value(float initial_value) { initial_value_ = initial_value; } protected: - void set(float value) override; + void control(float value) override; bool optimistic_{false}; - Trigger *set_trigger_; + float initial_value_{NAN}; + Trigger *set_trigger_ = new Trigger(); optional()>> f_; + + ESPPreferenceObject pref_; }; } // namespace template_ diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 57eef7a946..b775d44211 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -614,8 +614,9 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM std::string WebServer::number_json(number::Number *obj, float value) { return json::build_json([obj, value](JsonObject &root) { root["id"] = "number-" + obj->get_object_id(); - std::string state = value_accuracy_to_string(value, obj->get_accuracy_decimals()); - root["state"] = state; + char buffer[64]; + snprintf(buffer, sizeof(buffer), "%f", value); + root["state"] = buffer; root["value"] = value; }); } From 0a82e6e792477c579ae60f19ab593ec8fcd47369 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 20 Jul 2021 10:28:23 +1200 Subject: [PATCH 1030/1841] Bump version to v1.20.0b4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 65c6458dd6..7f4f08274b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.0b3" +__version__ = "1.20.0b4" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 7619507e6c93251b147d258958d854cbc1921a82 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 20 Jul 2021 15:31:54 +1200 Subject: [PATCH 1031/1841] Convert Arduino boolean to bool (#2042) --- esphome/components/dfplayer/dfplayer.h | 4 ++-- esphome/components/st7735/st7735.cpp | 3 +-- esphome/components/st7735/st7735.h | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/esphome/components/dfplayer/dfplayer.h b/esphome/components/dfplayer/dfplayer.h index 5cd49c311d..ae47cb33f1 100644 --- a/esphome/components/dfplayer/dfplayer.h +++ b/esphome/components/dfplayer/dfplayer.h @@ -116,7 +116,7 @@ DFPLAYER_SIMPLE_ACTION(PreviousAction, previous) template class PlayFileAction : public Action, public Parented { public: TEMPLATABLE_VALUE(uint16_t, file) - TEMPLATABLE_VALUE(boolean, loop) + TEMPLATABLE_VALUE(bool, loop) void play(Ts... x) override { auto file = this->file_.value(x...); @@ -133,7 +133,7 @@ template class PlayFolderAction : public Action, public P public: TEMPLATABLE_VALUE(uint16_t, folder) TEMPLATABLE_VALUE(uint16_t, file) - TEMPLATABLE_VALUE(boolean, loop) + TEMPLATABLE_VALUE(bool, loop) void play(Ts... x) override { auto folder = this->folder_.value(x...); diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index f329ef4620..0467ed83db 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -220,8 +220,7 @@ static const uint8_t PROGMEM // clang-format on static const char *const TAG = "st7735"; -ST7735::ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, boolean eightbitcolor, - boolean usebgr) { +ST7735::ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr) { model_ = model; this->width_ = width; this->height_ = height; diff --git a/esphome/components/st7735/st7735.h b/esphome/components/st7735/st7735.h index 11bcc746f0..737170e99b 100644 --- a/esphome/components/st7735/st7735.h +++ b/esphome/components/st7735/st7735.h @@ -37,7 +37,7 @@ class ST7735 : public PollingComponent, public spi::SPIDevice { public: - ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, boolean eightbitcolor, boolean usebgr); + ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr); void dump_config() override; void setup() override; @@ -75,8 +75,8 @@ class ST7735 : public PollingComponent, ST7735Model model_{ST7735_INITR_18BLACKTAB}; uint8_t colstart_ = 0, rowstart_ = 0; - boolean eightbitcolor_ = false; - boolean usebgr_ = false; + bool eightbitcolor_ = false; + bool usebgr_ = false; int16_t width_ = 80, height_ = 80; // Watch heap size GPIOPin *reset_pin_{nullptr}; From 99d2db42cd6336f0bc6a5e8ad7425eb2f008473c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 20 Jul 2021 15:40:42 +1200 Subject: [PATCH 1032/1841] Add restore_value to template number (#2041) --- .../components/template/number/__init__.py | 30 ++++++++++++++----- .../template/number/template_number.cpp | 23 ++++++++------ .../template/number/template_number.h | 2 ++ 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/esphome/components/template/number/__init__.py b/esphome/components/template/number/__init__.py index 557a01c6fa..22bbaacc15 100644 --- a/esphome/components/template/number/__init__.py +++ b/esphome/components/template/number/__init__.py @@ -9,6 +9,7 @@ from esphome.const import ( CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_OPTIMISTIC, + CONF_RESTORE_VALUE, CONF_STEP, ) from .. import template_ns @@ -26,6 +27,17 @@ def validate_min_max(config): return config +def validate(config): + if CONF_LAMBDA in config: + if CONF_OPTIMISTIC in config: + raise cv.Invalid("optimistic cannot be used with lambda") + if CONF_INITIAL_VALUE in config: + raise cv.Invalid("initial_value cannot be used with lambda") + if CONF_RESTORE_VALUE in config: + raise cv.Invalid("restore_value cannot be used with lambda") + return config + + CONFIG_SCHEMA = cv.All( number.NUMBER_SCHEMA.extend( { @@ -33,13 +45,15 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_MAX_VALUE): cv.float_, cv.Required(CONF_MIN_VALUE): cv.float_, cv.Required(CONF_STEP): cv.positive_float, - cv.Exclusive(CONF_LAMBDA, "lambda-optimistic"): cv.returning_lambda, - cv.Exclusive(CONF_OPTIMISTIC, "lambda-optimistic"): cv.boolean, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_OPTIMISTIC): cv.boolean, cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_INITIAL_VALUE): cv.float_, + cv.Optional(CONF_RESTORE_VALUE): cv.boolean, } ).extend(cv.polling_component_schema("60s")), validate_min_max, + validate, ) @@ -60,13 +74,15 @@ async def to_code(config): ) cg.add(var.set_template(template_)) - elif CONF_OPTIMISTIC in config: - cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + else: + if CONF_OPTIMISTIC in config: + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + if CONF_INITIAL_VALUE in config: + cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) + if CONF_RESTORE_VALUE in config: + cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE])) if CONF_SET_ACTION in config: await automation.build_automation( var.get_set_trigger(), [(float, "x")], config[CONF_SET_ACTION] ) - - if CONF_INITIAL_VALUE in config: - cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) diff --git a/esphome/components/template/number/template_number.cpp b/esphome/components/template/number/template_number.cpp index 500f9f2272..eb9b17b976 100644 --- a/esphome/components/template/number/template_number.cpp +++ b/esphome/components/template/number/template_number.cpp @@ -7,16 +7,20 @@ namespace template_ { static const char *const TAG = "template.number"; void TemplateNumber::setup() { - if (this->f_.has_value() || !this->optimistic_) + if (this->f_.has_value()) return; - this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); float value; - if (!this->pref_.load(&value)) { - if (!isnan(this->initial_value_)) - value = this->initial_value_; - else - value = this->traits.get_min_value(); + if (!this->restore_value_) { + value = this->initial_value_; + } else { + this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); + if (!this->pref_.load(&value)) { + if (!isnan(this->initial_value_)) + value = this->initial_value_; + else + value = this->traits.get_min_value(); + } } this->publish_state(value); } @@ -35,10 +39,11 @@ void TemplateNumber::update() { void TemplateNumber::control(float value) { this->set_trigger_->trigger(value); - if (this->optimistic_) { + if (this->optimistic_) this->publish_state(value); + + if (this->restore_value_) this->pref_.save(&value); - } } void TemplateNumber::dump_config() { LOG_NUMBER("", "Template Number", this); diff --git a/esphome/components/template/number/template_number.h b/esphome/components/template/number/template_number.h index 50cd256b7f..9a82e44339 100644 --- a/esphome/components/template/number/template_number.h +++ b/esphome/components/template/number/template_number.h @@ -20,11 +20,13 @@ class TemplateNumber : public number::Number, public PollingComponent { Trigger *get_set_trigger() const { return set_trigger_; } void set_optimistic(bool optimistic) { optimistic_ = optimistic; } void set_initial_value(float initial_value) { initial_value_ = initial_value; } + void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } protected: void control(float value) override; bool optimistic_{false}; float initial_value_{NAN}; + bool restore_value_{false}; Trigger *set_trigger_ = new Trigger(); optional()>> f_; From d2ed3b9becaedca26953d8976a29fdc51e087192 Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Tue, 20 Jul 2021 08:26:07 +0400 Subject: [PATCH 1033/1841] midea_ac: Fix turbo mode. Preset BOOST. (#2029) --- esphome/components/midea_ac/midea_frame.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/components/midea_ac/midea_frame.h b/esphome/components/midea_ac/midea_frame.h index a84161b4af..3777f6fd77 100644 --- a/esphome/components/midea_ac/midea_frame.h +++ b/esphome/components/midea_ac/midea_frame.h @@ -102,8 +102,11 @@ class PropertiesFrame : public midea_dongle::BaseFrame { void set_sleep_mode(bool state) { this->set_bytemask_(20, 0x01, state); } /* TURBO MODE */ - bool get_turbo_mode() const { return this->pbuf_[18] & 0x20; } - void set_turbo_mode(bool state) { this->set_bytemask_(18, 0x20, state); } + bool get_turbo_mode() const { return this->pbuf_[18] & 0x20 || this->pbuf_[20] & 0x02; } + void set_turbo_mode(bool state) { + this->set_bytemask_(18, 0x20, state); + this->set_bytemask_(20, 0x02, state); + } /* FREEZE PROTECTION */ bool get_freeze_protection_mode() const { return this->pbuf_[31] & 0x80; } From 9b5a3cbcd30ef1b3b638bf2baeeb554900176dc9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 19 Jul 2021 21:44:39 -0700 Subject: [PATCH 1034/1841] Bump dashboard to 20210719.0 (#2043) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c3562f492d..ab54828194 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210623.0 +esphome-dashboard==20210719.0 From 766866197bd0d217655c2976aa45323395465fec Mon Sep 17 00:00:00 2001 From: Sean Vig Date: Tue, 20 Jul 2021 01:05:25 -0400 Subject: [PATCH 1035/1841] Correct ADS1115 handling of multiple sensors in continuous mode (#2016) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/ads1115/ads1115.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/esphome/components/ads1115/ads1115.cpp b/esphome/components/ads1115/ads1115.cpp index d33ac83813..8cac897a15 100644 --- a/esphome/components/ads1115/ads1115.cpp +++ b/esphome/components/ads1115/ads1115.cpp @@ -107,17 +107,22 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) { } this->prev_config_ = config; - // about 1.6 ms with 860 samples per second + // about 1.2 ms with 860 samples per second delay(2); - uint32_t start = millis(); - while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) { - if (millis() - start > 100) { - ESP_LOGW(TAG, "Reading ADS1115 timed out"); - this->status_set_warning(); - return NAN; + // in continuous mode, conversion will always be running, rely on the delay + // to ensure conversion is taking place with the correct settings + // can we use the rdy pin to trigger when a conversion is done? + if (!this->continuous_mode_) { + uint32_t start = millis(); + while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) { + if (millis() - start > 100) { + ESP_LOGW(TAG, "Reading ADS1115 timed out"); + this->status_set_warning(); + return NAN; + } + yield(); } - yield(); } } From 01a4b4e82f9037b25565f694207b2be329d7f40d Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 20 Jul 2021 07:05:56 +0200 Subject: [PATCH 1036/1841] ESP32 ADC use esp-idf (#2024) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/adc/adc_sensor.cpp | 52 +++++++++++++++++++++------ esphome/components/adc/adc_sensor.h | 8 +++-- esphome/components/adc/sensor.py | 8 ++--- 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 960d9ed8e2..d6469ab785 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -11,7 +11,30 @@ namespace adc { static const char *const TAG = "adc"; #ifdef ARDUINO_ARCH_ESP32 -void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuation_ = attenuation; } +void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } + +inline adc1_channel_t gpio_to_adc1(uint8_t pin) { + switch (pin) { + case 36: + return ADC1_CHANNEL_0; + case 37: + return ADC1_CHANNEL_1; + case 38: + return ADC1_CHANNEL_2; + case 39: + return ADC1_CHANNEL_3; + case 32: + return ADC1_CHANNEL_4; + case 33: + return ADC1_CHANNEL_5; + case 34: + return ADC1_CHANNEL_6; + case 35: + return ADC1_CHANNEL_7; + default: + return ADC1_CHANNEL_MAX; + } +} #endif void ADCSensor::setup() { @@ -21,7 +44,9 @@ void ADCSensor::setup() { #endif #ifdef ARDUINO_ARCH_ESP32 - analogSetPinAttenuation(this->pin_, this->attenuation_); + adc1_config_channel_atten(gpio_to_adc1(pin_), attenuation_); + adc1_config_width(ADC_WIDTH_BIT_12); + adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_)); #endif } void ADCSensor::dump_config() { @@ -36,18 +61,20 @@ void ADCSensor::dump_config() { #ifdef ARDUINO_ARCH_ESP32 ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); switch (this->attenuation_) { - case ADC_0db: + case ADC_ATTEN_DB_0: ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); break; - case ADC_2_5db: + case ADC_ATTEN_DB_2_5: ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); break; - case ADC_6db: + case ADC_ATTEN_DB_6: ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); break; - case ADC_11db: + case ADC_ATTEN_DB_11: ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)"); break; + default: // This is to satisfy the unused ADC_ATTEN_MAX + break; } #endif LOG_UPDATE_INTERVAL(this); @@ -60,20 +87,23 @@ void ADCSensor::update() { } float ADCSensor::sample() { #ifdef ARDUINO_ARCH_ESP32 - float value_v = analogRead(this->pin_) / 4095.0f; // NOLINT + int raw = adc1_get_raw(gpio_to_adc1(pin_)); + float value_v = raw / 4095.0f; switch (this->attenuation_) { - case ADC_0db: + case ADC_ATTEN_DB_0: value_v *= 1.1; break; - case ADC_2_5db: + case ADC_ATTEN_DB_2_5: value_v *= 1.5; break; - case ADC_6db: + case ADC_ATTEN_DB_6: value_v *= 2.2; break; - case ADC_11db: + case ADC_ATTEN_DB_11: value_v *= 3.9; break; + default: // This is to satisfy the unused ADC_ATTEN_MAX + break; } return value_v; #endif diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index 3a08ff6be4..4591ed758d 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -6,6 +6,10 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/voltage_sampler/voltage_sampler.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "driver/adc.h" +#endif + namespace esphome { namespace adc { @@ -13,7 +17,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage public: #ifdef ARDUINO_ARCH_ESP32 /// Set the attenuation for this pin. Only available on the ESP32. - void set_attenuation(adc_attenuation_t attenuation); + void set_attenuation(adc_atten_t attenuation); #endif /// Update adc values. @@ -34,7 +38,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage uint8_t pin_; #ifdef ARDUINO_ARCH_ESP32 - adc_attenuation_t attenuation_{ADC_0db}; + adc_atten_t attenuation_{ADC_ATTEN_DB_0}; #endif }; diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 90561679b7..7a944a7260 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -16,10 +16,10 @@ from esphome.const import ( AUTO_LOAD = ["voltage_sampler"] ATTENUATION_MODES = { - "0db": cg.global_ns.ADC_0db, - "2.5db": cg.global_ns.ADC_2_5db, - "6db": cg.global_ns.ADC_6db, - "11db": cg.global_ns.ADC_11db, + "0db": cg.global_ns.ADC_ATTEN_DB_0, + "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5, + "6db": cg.global_ns.ADC_ATTEN_DB_6, + "11db": cg.global_ns.ADC_ATTEN_DB_11, } From c7a52c3894db640d4b5e793e542443d85766de1f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 20 Jul 2021 15:40:42 +1200 Subject: [PATCH 1037/1841] Add restore_value to template number (#2041) --- .../components/template/number/__init__.py | 30 ++++++++++++++----- .../template/number/template_number.cpp | 23 ++++++++------ .../template/number/template_number.h | 2 ++ 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/esphome/components/template/number/__init__.py b/esphome/components/template/number/__init__.py index 557a01c6fa..22bbaacc15 100644 --- a/esphome/components/template/number/__init__.py +++ b/esphome/components/template/number/__init__.py @@ -9,6 +9,7 @@ from esphome.const import ( CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_OPTIMISTIC, + CONF_RESTORE_VALUE, CONF_STEP, ) from .. import template_ns @@ -26,6 +27,17 @@ def validate_min_max(config): return config +def validate(config): + if CONF_LAMBDA in config: + if CONF_OPTIMISTIC in config: + raise cv.Invalid("optimistic cannot be used with lambda") + if CONF_INITIAL_VALUE in config: + raise cv.Invalid("initial_value cannot be used with lambda") + if CONF_RESTORE_VALUE in config: + raise cv.Invalid("restore_value cannot be used with lambda") + return config + + CONFIG_SCHEMA = cv.All( number.NUMBER_SCHEMA.extend( { @@ -33,13 +45,15 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_MAX_VALUE): cv.float_, cv.Required(CONF_MIN_VALUE): cv.float_, cv.Required(CONF_STEP): cv.positive_float, - cv.Exclusive(CONF_LAMBDA, "lambda-optimistic"): cv.returning_lambda, - cv.Exclusive(CONF_OPTIMISTIC, "lambda-optimistic"): cv.boolean, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_OPTIMISTIC): cv.boolean, cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_INITIAL_VALUE): cv.float_, + cv.Optional(CONF_RESTORE_VALUE): cv.boolean, } ).extend(cv.polling_component_schema("60s")), validate_min_max, + validate, ) @@ -60,13 +74,15 @@ async def to_code(config): ) cg.add(var.set_template(template_)) - elif CONF_OPTIMISTIC in config: - cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + else: + if CONF_OPTIMISTIC in config: + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + if CONF_INITIAL_VALUE in config: + cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) + if CONF_RESTORE_VALUE in config: + cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE])) if CONF_SET_ACTION in config: await automation.build_automation( var.get_set_trigger(), [(float, "x")], config[CONF_SET_ACTION] ) - - if CONF_INITIAL_VALUE in config: - cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) diff --git a/esphome/components/template/number/template_number.cpp b/esphome/components/template/number/template_number.cpp index 500f9f2272..eb9b17b976 100644 --- a/esphome/components/template/number/template_number.cpp +++ b/esphome/components/template/number/template_number.cpp @@ -7,16 +7,20 @@ namespace template_ { static const char *const TAG = "template.number"; void TemplateNumber::setup() { - if (this->f_.has_value() || !this->optimistic_) + if (this->f_.has_value()) return; - this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); float value; - if (!this->pref_.load(&value)) { - if (!isnan(this->initial_value_)) - value = this->initial_value_; - else - value = this->traits.get_min_value(); + if (!this->restore_value_) { + value = this->initial_value_; + } else { + this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); + if (!this->pref_.load(&value)) { + if (!isnan(this->initial_value_)) + value = this->initial_value_; + else + value = this->traits.get_min_value(); + } } this->publish_state(value); } @@ -35,10 +39,11 @@ void TemplateNumber::update() { void TemplateNumber::control(float value) { this->set_trigger_->trigger(value); - if (this->optimistic_) { + if (this->optimistic_) this->publish_state(value); + + if (this->restore_value_) this->pref_.save(&value); - } } void TemplateNumber::dump_config() { LOG_NUMBER("", "Template Number", this); diff --git a/esphome/components/template/number/template_number.h b/esphome/components/template/number/template_number.h index 50cd256b7f..9a82e44339 100644 --- a/esphome/components/template/number/template_number.h +++ b/esphome/components/template/number/template_number.h @@ -20,11 +20,13 @@ class TemplateNumber : public number::Number, public PollingComponent { Trigger *get_set_trigger() const { return set_trigger_; } void set_optimistic(bool optimistic) { optimistic_ = optimistic; } void set_initial_value(float initial_value) { initial_value_ = initial_value; } + void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } protected: void control(float value) override; bool optimistic_{false}; float initial_value_{NAN}; + bool restore_value_{false}; Trigger *set_trigger_ = new Trigger(); optional()>> f_; From e25935ef213bdefedbe9b8358d9aaaab9ef6fa2c Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Tue, 20 Jul 2021 08:26:07 +0400 Subject: [PATCH 1038/1841] midea_ac: Fix turbo mode. Preset BOOST. (#2029) --- esphome/components/midea_ac/midea_frame.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/components/midea_ac/midea_frame.h b/esphome/components/midea_ac/midea_frame.h index a84161b4af..3777f6fd77 100644 --- a/esphome/components/midea_ac/midea_frame.h +++ b/esphome/components/midea_ac/midea_frame.h @@ -102,8 +102,11 @@ class PropertiesFrame : public midea_dongle::BaseFrame { void set_sleep_mode(bool state) { this->set_bytemask_(20, 0x01, state); } /* TURBO MODE */ - bool get_turbo_mode() const { return this->pbuf_[18] & 0x20; } - void set_turbo_mode(bool state) { this->set_bytemask_(18, 0x20, state); } + bool get_turbo_mode() const { return this->pbuf_[18] & 0x20 || this->pbuf_[20] & 0x02; } + void set_turbo_mode(bool state) { + this->set_bytemask_(18, 0x20, state); + this->set_bytemask_(20, 0x02, state); + } /* FREEZE PROTECTION */ bool get_freeze_protection_mode() const { return this->pbuf_[31] & 0x80; } From 5c57b5137809770a5781f07495e44fe075ad2f1e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 19 Jul 2021 21:44:39 -0700 Subject: [PATCH 1039/1841] Bump dashboard to 20210719.0 (#2043) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c3562f492d..ab54828194 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210623.0 +esphome-dashboard==20210719.0 From 73ead5f3281a7042a09499cf3ccee27062bda096 Mon Sep 17 00:00:00 2001 From: Sean Vig Date: Tue, 20 Jul 2021 01:05:25 -0400 Subject: [PATCH 1040/1841] Correct ADS1115 handling of multiple sensors in continuous mode (#2016) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/ads1115/ads1115.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/esphome/components/ads1115/ads1115.cpp b/esphome/components/ads1115/ads1115.cpp index d33ac83813..8cac897a15 100644 --- a/esphome/components/ads1115/ads1115.cpp +++ b/esphome/components/ads1115/ads1115.cpp @@ -107,17 +107,22 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) { } this->prev_config_ = config; - // about 1.6 ms with 860 samples per second + // about 1.2 ms with 860 samples per second delay(2); - uint32_t start = millis(); - while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) { - if (millis() - start > 100) { - ESP_LOGW(TAG, "Reading ADS1115 timed out"); - this->status_set_warning(); - return NAN; + // in continuous mode, conversion will always be running, rely on the delay + // to ensure conversion is taking place with the correct settings + // can we use the rdy pin to trigger when a conversion is done? + if (!this->continuous_mode_) { + uint32_t start = millis(); + while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) { + if (millis() - start > 100) { + ESP_LOGW(TAG, "Reading ADS1115 timed out"); + this->status_set_warning(); + return NAN; + } + yield(); } - yield(); } } From e5afb1c4eaf8c6a9f8603ff780ce9c70b2beede4 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 20 Jul 2021 07:05:56 +0200 Subject: [PATCH 1041/1841] ESP32 ADC use esp-idf (#2024) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/adc/adc_sensor.cpp | 52 +++++++++++++++++++++------ esphome/components/adc/adc_sensor.h | 8 +++-- esphome/components/adc/sensor.py | 8 ++--- 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 960d9ed8e2..d6469ab785 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -11,7 +11,30 @@ namespace adc { static const char *const TAG = "adc"; #ifdef ARDUINO_ARCH_ESP32 -void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuation_ = attenuation; } +void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } + +inline adc1_channel_t gpio_to_adc1(uint8_t pin) { + switch (pin) { + case 36: + return ADC1_CHANNEL_0; + case 37: + return ADC1_CHANNEL_1; + case 38: + return ADC1_CHANNEL_2; + case 39: + return ADC1_CHANNEL_3; + case 32: + return ADC1_CHANNEL_4; + case 33: + return ADC1_CHANNEL_5; + case 34: + return ADC1_CHANNEL_6; + case 35: + return ADC1_CHANNEL_7; + default: + return ADC1_CHANNEL_MAX; + } +} #endif void ADCSensor::setup() { @@ -21,7 +44,9 @@ void ADCSensor::setup() { #endif #ifdef ARDUINO_ARCH_ESP32 - analogSetPinAttenuation(this->pin_, this->attenuation_); + adc1_config_channel_atten(gpio_to_adc1(pin_), attenuation_); + adc1_config_width(ADC_WIDTH_BIT_12); + adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_)); #endif } void ADCSensor::dump_config() { @@ -36,18 +61,20 @@ void ADCSensor::dump_config() { #ifdef ARDUINO_ARCH_ESP32 ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); switch (this->attenuation_) { - case ADC_0db: + case ADC_ATTEN_DB_0: ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); break; - case ADC_2_5db: + case ADC_ATTEN_DB_2_5: ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); break; - case ADC_6db: + case ADC_ATTEN_DB_6: ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); break; - case ADC_11db: + case ADC_ATTEN_DB_11: ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)"); break; + default: // This is to satisfy the unused ADC_ATTEN_MAX + break; } #endif LOG_UPDATE_INTERVAL(this); @@ -60,20 +87,23 @@ void ADCSensor::update() { } float ADCSensor::sample() { #ifdef ARDUINO_ARCH_ESP32 - float value_v = analogRead(this->pin_) / 4095.0f; // NOLINT + int raw = adc1_get_raw(gpio_to_adc1(pin_)); + float value_v = raw / 4095.0f; switch (this->attenuation_) { - case ADC_0db: + case ADC_ATTEN_DB_0: value_v *= 1.1; break; - case ADC_2_5db: + case ADC_ATTEN_DB_2_5: value_v *= 1.5; break; - case ADC_6db: + case ADC_ATTEN_DB_6: value_v *= 2.2; break; - case ADC_11db: + case ADC_ATTEN_DB_11: value_v *= 3.9; break; + default: // This is to satisfy the unused ADC_ATTEN_MAX + break; } return value_v; #endif diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index 3a08ff6be4..4591ed758d 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -6,6 +6,10 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/voltage_sampler/voltage_sampler.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "driver/adc.h" +#endif + namespace esphome { namespace adc { @@ -13,7 +17,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage public: #ifdef ARDUINO_ARCH_ESP32 /// Set the attenuation for this pin. Only available on the ESP32. - void set_attenuation(adc_attenuation_t attenuation); + void set_attenuation(adc_atten_t attenuation); #endif /// Update adc values. @@ -34,7 +38,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage uint8_t pin_; #ifdef ARDUINO_ARCH_ESP32 - adc_attenuation_t attenuation_{ADC_0db}; + adc_atten_t attenuation_{ADC_ATTEN_DB_0}; #endif }; diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 90561679b7..7a944a7260 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -16,10 +16,10 @@ from esphome.const import ( AUTO_LOAD = ["voltage_sampler"] ATTENUATION_MODES = { - "0db": cg.global_ns.ADC_0db, - "2.5db": cg.global_ns.ADC_2_5db, - "6db": cg.global_ns.ADC_6db, - "11db": cg.global_ns.ADC_11db, + "0db": cg.global_ns.ADC_ATTEN_DB_0, + "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5, + "6db": cg.global_ns.ADC_ATTEN_DB_6, + "11db": cg.global_ns.ADC_ATTEN_DB_11, } From fa72990a633025f612aef937e72877904141e42d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 20 Jul 2021 17:09:58 +1200 Subject: [PATCH 1042/1841] Bump version to v1.20.0b5 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 7f4f08274b..556c8c70fc 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.0b4" +__version__ = "1.20.0b5" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 9f2b2f51ffbdc455e2ebbe6edde0f93041f73c2b Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Tue, 20 Jul 2021 11:12:22 +0200 Subject: [PATCH 1043/1841] Esp32 c3 support (#2035) --- esphome/components/api/api_server.h | 1 - esphome/components/api/user_services.cpp | 2 +- esphome/core/esphal.cpp | 15 ++++++++++++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index add22e121e..68d1df2c1f 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -9,7 +9,6 @@ #include "util.h" #include "list_entities.h" #include "subscribe_state.h" -#include "homeassistant_service.h" #include "user_services.h" #ifdef ARDUINO_ARCH_ESP32 diff --git a/esphome/components/api/user_services.cpp b/esphome/components/api/user_services.cpp index 39e42bcc02..49618f5467 100644 --- a/esphome/components/api/user_services.cpp +++ b/esphome/components/api/user_services.cpp @@ -15,7 +15,7 @@ template<> std::string get_execute_arg_value(const ExecuteServiceAr template<> std::vector get_execute_arg_value>(const ExecuteServiceArgument &arg) { return arg.bool_array; } -template<> std::vector get_execute_arg_value>(const ExecuteServiceArgument &arg) { +template<> std::vector get_execute_arg_value>(const ExecuteServiceArgument &arg) { return arg.int_array; } template<> std::vector get_execute_arg_value>(const ExecuteServiceArgument &arg) { diff --git a/esphome/core/esphal.cpp b/esphome/core/esphal.cpp index d7adc8dbf1..6b8350991e 100644 --- a/esphome/core/esphal.cpp +++ b/esphome/core/esphal.cpp @@ -27,11 +27,16 @@ GPIOPin::GPIOPin(uint8_t pin, uint8_t mode, bool inverted) #ifdef ARDUINO_ARCH_ESP8266 gpio_read_(pin < 16 ? &GPI : &GP16I), gpio_mask_(pin < 16 ? (1UL << pin) : 1) -#endif -#ifdef ARDUINO_ARCH_ESP32 - gpio_set_(pin < 32 ? &GPIO.out_w1ts : &GPIO.out1_w1ts.val), +#elif ARDUINO_ARCH_ESP32 +#ifdef CONFIG_IDF_TARGET_ESP32C3 + gpio_set_(&GPIO.out_w1ts.val), + gpio_clear_(&GPIO.out_w1tc.val), + gpio_read_(&GPIO.in.val), +#else + gpio_set_(pin < 32 ? &GPIO.out_w1ts : &GPIO.out1_w1ts.val), gpio_clear_(pin < 32 ? &GPIO.out_w1tc : &GPIO.out1_w1tc.val), gpio_read_(pin < 32 ? &GPIO.in : &GPIO.in1.val), +#endif gpio_mask_(pin < 32 ? (1UL << pin) : (1UL << (pin - 32))) #endif { @@ -194,12 +199,16 @@ void ICACHE_RAM_ATTR ISRInternalGPIOPin::clear_interrupt() { GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, this->gpio_mask_); #endif #ifdef ARDUINO_ARCH_ESP32 +#ifdef CONFIG_IDF_TARGET_ESP32C3 + GPIO.status_w1tc.val = this->gpio_mask_; +#else if (this->pin_ < 32) { GPIO.status_w1tc = this->gpio_mask_; } else { GPIO.status1_w1tc.intr_st = this->gpio_mask_; } #endif +#endif } void ICACHE_RAM_ATTR HOT GPIOPin::pin_mode(uint8_t mode) { From fc0deb642a0f5328e5866c7d585e40da27221117 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 20 Jul 2021 22:42:03 +0200 Subject: [PATCH 1044/1841] Fix white value transition for addressable lights (#2045) --- esphome/components/light/addressable_light.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index ea24736c63..4ef293cd03 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -68,10 +68,7 @@ void AddressableLight::write_state(LightState *state) { // our transition will handle brightness, disable brightness in correction. this->correction_.set_local_brightness(255); - uint8_t orig_w = target_color.w; target_color *= static_cast(roundf(end_values.get_brightness() * end_values.get_state() * 255.0f)); - // w is not scaled by brightness - target_color.w = orig_w; float denom = (1.0f - new_smoothed); float alpha = denom == 0.0f ? 0.0f : (new_smoothed - prev_smoothed) / denom; From 3b3297d2695d831b062e935d2879455a775ad2a0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 21 Jul 2021 09:20:20 +1200 Subject: [PATCH 1045/1841] Adding last_reset_type to sensors that should support it. (#2039) --- esphome/components/api/api.proto | 7 ++++++ esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 21 ++++++++++++++++++ esphome/components/api/api_pb2.h | 6 ++++++ esphome/components/atm90e32/sensor.py | 15 +++++++++++-- esphome/components/havells_solar/sensor.py | 7 ++++-- esphome/components/hlw8012/sensor.py | 9 ++++++-- esphome/components/pzem004t/sensor.py | 5 +++-- esphome/components/pzemac/sensor.py | 5 +++-- esphome/components/sdm_meter/sensor.py | 22 ++++++++++++++----- esphome/components/sensor/__init__.py | 25 ++++++++++++++++++++++ esphome/components/sensor/sensor.cpp | 13 +++++++++++ esphome/components/sensor/sensor.h | 24 +++++++++++++++++++++ esphome/const.py | 8 +++++++ 14 files changed, 153 insertions(+), 15 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 40be1fd0db..073775ed2e 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -422,6 +422,12 @@ enum SensorStateClass { STATE_CLASS_MEASUREMENT = 1; } +enum SensorLastResetType { + LAST_RESET_NONE = 0; + LAST_RESET_NEVER = 1; + LAST_RESET_AUTO = 2; +} + message ListEntitiesSensorResponse { option (id) = 16; option (source) = SOURCE_SERVER; @@ -438,6 +444,7 @@ message ListEntitiesSensorResponse { bool force_update = 8; string device_class = 9; SensorStateClass state_class = 10; + SensorLastResetType last_reset_type = 11; } message SensorStateResponse { option (id) = 25; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 79ffcfa69e..fb05772e5e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -399,6 +399,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { msg.force_update = sensor->get_force_update(); msg.device_class = sensor->get_device_class(); msg.state_class = static_cast(sensor->state_class); + msg.last_reset_type = static_cast(sensor->last_reset_type); return this->send_list_entities_sensor_response(msg); } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index c3cfc8cd76..057d71324f 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -72,6 +72,18 @@ template<> const char *proto_enum_to_string(enums::Sens return "UNKNOWN"; } } +template<> const char *proto_enum_to_string(enums::SensorLastResetType value) { + switch (value) { + case enums::LAST_RESET_NONE: + return "LAST_RESET_NONE"; + case enums::LAST_RESET_NEVER: + return "LAST_RESET_NEVER"; + case enums::LAST_RESET_AUTO: + return "LAST_RESET_AUTO"; + default: + return "UNKNOWN"; + } +} template<> const char *proto_enum_to_string(enums::LogLevel value) { switch (value) { case enums::LOG_LEVEL_NONE: @@ -1592,6 +1604,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->state_class = value.as_enum(); return true; } + case 11: { + this->last_reset_type = value.as_enum(); + return true; + } default: return false; } @@ -1647,6 +1663,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(8, this->force_update); buffer.encode_string(9, this->device_class); buffer.encode_enum(10, this->state_class); + buffer.encode_enum(11, this->last_reset_type); } void ListEntitiesSensorResponse::dump_to(std::string &out) const { char buffer[64]; @@ -1692,6 +1709,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append(" state_class: "); out.append(proto_enum_to_string(this->state_class)); out.append("\n"); + + out.append(" last_reset_type: "); + out.append(proto_enum_to_string(this->last_reset_type)); + out.append("\n"); out.append("}"); } bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index e3bb1d9106..0551508b4b 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -36,6 +36,11 @@ enum SensorStateClass : uint32_t { STATE_CLASS_NONE = 0, STATE_CLASS_MEASUREMENT = 1, }; +enum SensorLastResetType : uint32_t { + LAST_RESET_NONE = 0, + LAST_RESET_NEVER = 1, + LAST_RESET_AUTO = 2, +}; enum LogLevel : uint32_t { LOG_LEVEL_NONE = 0, LOG_LEVEL_ERROR = 1, @@ -429,6 +434,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { bool force_update{false}; std::string device_class{}; enums::SensorStateClass state_class{}; + enums::SensorLastResetType last_reset_type{}; void encode(ProtoWriteBuffer buffer) const override; void dump_to(std::string &out) const override; diff --git a/esphome/components/atm90e32/sensor.py b/esphome/components/atm90e32/sensor.py index 68ec199bff..2c34d76b52 100644 --- a/esphome/components/atm90e32/sensor.py +++ b/esphome/components/atm90e32/sensor.py @@ -21,6 +21,7 @@ from esphome.const import ( ICON_EMPTY, ICON_LIGHTBULB, ICON_CURRENT_AC, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, UNIT_HERTZ, UNIT_VOLT, @@ -91,10 +92,20 @@ ATM90E32_PHASE_SCHEMA = cv.Schema( STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT + UNIT_WATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT + UNIT_WATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t, cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t, diff --git a/esphome/components/havells_solar/sensor.py b/esphome/components/havells_solar/sensor.py index 7d1e2be581..1926d4d68a 100644 --- a/esphome/components/havells_solar/sensor.py +++ b/esphome/components/havells_solar/sensor.py @@ -15,6 +15,7 @@ from esphome.const import ( DEVICE_CLASS_VOLTAGE, ICON_CURRENT_AC, ICON_EMPTY, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, UNIT_AMPERE, @@ -121,14 +122,16 @@ CONFIG_SCHEMA = ( ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema( UNIT_KILOWATT_HOURS, ICON_EMPTY, 0, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_TOTAL_GENERATION_TIME): sensor.sensor_schema( UNIT_HOURS, diff --git a/esphome/components/hlw8012/sensor.py b/esphome/components/hlw8012/sensor.py index e24e995eba..face32872e 100644 --- a/esphome/components/hlw8012/sensor.py +++ b/esphome/components/hlw8012/sensor.py @@ -19,8 +19,8 @@ from esphome.const import ( DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ICON_EMPTY, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, @@ -67,7 +67,12 @@ CONFIG_SCHEMA = cv.Schema( UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional(CONF_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 1, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_WATT_HOURS, + ICON_EMPTY, + 1, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance, cv.Optional(CONF_VOLTAGE_DIVIDER, default=2351): cv.positive_float, diff --git a/esphome/components/pzem004t/sensor.py b/esphome/components/pzem004t/sensor.py index e3859f090c..b358b8c650 100644 --- a/esphome/components/pzem004t/sensor.py +++ b/esphome/components/pzem004t/sensor.py @@ -12,8 +12,8 @@ from esphome.const import ( DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ICON_EMPTY, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, @@ -47,7 +47,8 @@ CONFIG_SCHEMA = ( ICON_EMPTY, 0, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), } ) diff --git a/esphome/components/pzemac/sensor.py b/esphome/components/pzemac/sensor.py index 778c5054a0..1dd77a0371 100644 --- a/esphome/components/pzemac/sensor.py +++ b/esphome/components/pzemac/sensor.py @@ -17,8 +17,8 @@ from esphome.const import ( DEVICE_CLASS_ENERGY, ICON_EMPTY, ICON_CURRENT_AC, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_HERTZ, UNIT_VOLT, UNIT_AMPERE, @@ -54,7 +54,8 @@ CONFIG_SCHEMA = ( ICON_EMPTY, 0, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( UNIT_HERTZ, diff --git a/esphome/components/sdm_meter/sensor.py b/esphome/components/sdm_meter/sensor.py index 39ef280fef..ce560b9d4b 100644 --- a/esphome/components/sdm_meter/sensor.py +++ b/esphome/components/sdm_meter/sensor.py @@ -25,8 +25,8 @@ from esphome.const import ( ICON_CURRENT_AC, ICON_EMPTY, ICON_FLASH, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_AMPERE, UNIT_DEGREES, UNIT_EMPTY, @@ -88,24 +88,36 @@ CONFIG_SCHEMA = ( STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_IMPORT_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_WATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_EXPORT_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_WATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_IMPORT_REACTIVE_ENERGY): sensor.sensor_schema( UNIT_VOLT_AMPS_REACTIVE_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_EXPORT_REACTIVE_ENERGY): sensor.sensor_schema( UNIT_VOLT_AMPS_REACTIVE_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), } ) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 89bde9476a..0a0c3a9214 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -17,6 +17,7 @@ from esphome.const import ( CONF_ICON, CONF_ID, CONF_INTERNAL, + CONF_LAST_RESET_TYPE, CONF_ON_RAW_VALUE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, @@ -30,6 +31,9 @@ from esphome.const import ( CONF_NAME, CONF_MQTT_ID, CONF_FORCE_UPDATE, + LAST_RESET_TYPE_AUTO, + LAST_RESET_TYPE_NEVER, + LAST_RESET_TYPE_NONE, UNIT_EMPTY, ICON_EMPTY, DEVICE_CLASS_EMPTY, @@ -79,6 +83,15 @@ STATE_CLASSES = { } validate_state_class = cv.enum(STATE_CLASSES, lower=True, space="_") +LastResetTypes = sensor_ns.enum("LastResetType") +LAST_RESET_TYPES = { + LAST_RESET_TYPE_NONE: LastResetTypes.LAST_RESET_TYPE_NONE, + LAST_RESET_TYPE_NEVER: LastResetTypes.LAST_RESET_TYPE_NEVER, + LAST_RESET_TYPE_AUTO: LastResetTypes.LAST_RESET_TYPE_AUTO, +} +validate_last_reset_type = cv.enum(LAST_RESET_TYPES, lower=True, space="_") + + IS_PLATFORM_COMPONENT = True @@ -168,6 +181,7 @@ SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( cv.Optional(CONF_ACCURACY_DECIMALS): accuracy_decimals, cv.Optional(CONF_DEVICE_CLASS): device_class, cv.Optional(CONF_STATE_CLASS): validate_state_class, + cv.Optional(CONF_LAST_RESET_TYPE): validate_last_reset_type, cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean, cv.Optional(CONF_EXPIRE_AFTER): cv.All( cv.requires_component("mqtt"), @@ -202,6 +216,7 @@ def sensor_schema( accuracy_decimals_: int, device_class_: Optional[str] = DEVICE_CLASS_EMPTY, state_class_: Optional[str] = STATE_CLASS_NONE, + last_reset_type_: Optional[str] = LAST_RESET_TYPE_NONE, ) -> cv.Schema: schema = SENSOR_SCHEMA if unit_of_measurement_ != UNIT_EMPTY: @@ -230,6 +245,14 @@ def sensor_schema( schema = schema.extend( {cv.Optional(CONF_STATE_CLASS, default=state_class_): validate_state_class} ) + if last_reset_type_ != LAST_RESET_TYPE_NONE: + schema = schema.extend( + { + cv.Optional( + CONF_LAST_RESET_TYPE, default=last_reset_type_ + ): validate_last_reset_type + } + ) return schema @@ -479,6 +502,8 @@ async def setup_sensor_core_(var, config): cg.add(var.set_icon(config[CONF_ICON])) if CONF_ACCURACY_DECIMALS in config: cg.add(var.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS])) + if CONF_LAST_RESET_TYPE in config: + cg.add(var.set_last_reset_type(config[CONF_LAST_RESET_TYPE])) cg.add(var.set_force_update(config[CONF_FORCE_UPDATE])) if config.get(CONF_FILTERS): # must exist and not be empty filters = await build_filters(config[CONF_FILTERS]) diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index fe92f88308..6e8765a8df 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -16,6 +16,18 @@ const char *state_class_to_string(StateClass state_class) { } } +const char *last_reset_type_to_string(LastResetType last_reset_type) { + switch (last_reset_type) { + case LAST_RESET_TYPE_NEVER: + return "never"; + case LAST_RESET_TYPE_AUTO: + return "auto"; + case LAST_RESET_TYPE_NONE: + default: + return ""; + } +} + void Sensor::publish_state(float state) { this->raw_state = state; this->raw_callback_.call(state); @@ -64,6 +76,7 @@ void Sensor::set_state_class(const std::string &state_class) { ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str()); } } +void Sensor::set_last_reset_type(LastResetType last_reset_type) { this->last_reset_type = last_reset_type; } std::string Sensor::get_unit_of_measurement() { if (this->unit_of_measurement_.has_value()) return *this->unit_of_measurement_; diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 123e7eddb3..b9908b6cbe 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -14,6 +14,10 @@ namespace sensor { ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ } \ ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->state_class)); \ + if ((obj)->state_class == sensor::STATE_CLASS_MEASUREMENT && \ + (obj)->last_reset_type != sensor::LAST_RESET_TYPE_NONE) { \ + ESP_LOGCONFIG(TAG, "%s Last Reset Type: '%s'", prefix, last_reset_type_to_string((obj)->last_reset_type)); \ + } \ ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \ ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \ if (!(obj)->get_icon().empty()) { \ @@ -37,6 +41,20 @@ enum StateClass : uint8_t { const char *state_class_to_string(StateClass state_class); +/** + * Sensor last reset types + */ +enum LastResetType : uint8_t { + /// This sensor does not support resetting. ie, it is not accumulative + LAST_RESET_TYPE_NONE = 0, + /// This sensor is expected to never reset its value + LAST_RESET_TYPE_NEVER = 1, + /// This sensor may reset and Home Assistant will watch for this + LAST_RESET_TYPE_AUTO = 2, +}; + +const char *last_reset_type_to_string(LastResetType last_reset_type); + /** Base-class for all sensors. * * A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy. @@ -155,6 +173,12 @@ class Sensor : public Nameable { */ virtual std::string device_class(); + // The Last reset type of this sensor + LastResetType last_reset_type{LAST_RESET_TYPE_NONE}; + + /// Manually set the Home Assistant last reset type for this sensor. + void set_last_reset_type(LastResetType last_reset_type); + /** A unique ID for this sensor, empty for no unique id. See unique ID requirements: * https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements * diff --git a/esphome/const.py b/esphome/const.py index 1ea4285747..8baca4d34f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -297,6 +297,7 @@ CONF_KEY = "key" CONF_LAMBDA = "lambda" CONF_LAST_CONFIDENCE = "last_confidence" CONF_LAST_FINGER_ID = "last_finger_id" +CONF_LAST_RESET_TYPE = "last_reset_type" CONF_LATITUDE = "latitude" CONF_LENGTH = "length" CONF_LEVEL = "level" @@ -785,3 +786,10 @@ STATE_CLASS_NONE = "" # The state represents a measurement in present time STATE_CLASS_MEASUREMENT = "measurement" + +# This sensor does not support resetting. ie, it is not accumulative +LAST_RESET_TYPE_NONE = "" +# This sensor is expected to never reset its value +LAST_RESET_TYPE_NEVER = "never" +# This sensor may reset and Home Assistant will watch for this +LAST_RESET_TYPE_AUTO = "auto" From 3e65e6c69aaed96ea302240bd64282f4db360530 Mon Sep 17 00:00:00 2001 From: Sean Vig Date: Tue, 20 Jul 2021 17:35:45 -0400 Subject: [PATCH 1046/1841] Remove superfluous polling on ADS1115 (#2015) --- esphome/components/ads1115/ads1115.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/esphome/components/ads1115/ads1115.cpp b/esphome/components/ads1115/ads1115.cpp index 8cac897a15..92f0ffbe3a 100644 --- a/esphome/components/ads1115/ads1115.cpp +++ b/esphome/components/ads1115/ads1115.cpp @@ -64,11 +64,6 @@ void ADS1115Component::setup() { return; } this->prev_config_ = config; - - for (auto *sensor : this->sensors_) { - this->set_interval(sensor->get_name(), sensor->update_interval(), - [this, sensor] { this->request_measurement(sensor); }); - } } void ADS1115Component::dump_config() { ESP_LOGCONFIG(TAG, "Setting up ADS1115..."); From 35b5c1ed5600c4dc814bc328d24b33799d4d4b04 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 20 Jul 2021 22:42:03 +0200 Subject: [PATCH 1047/1841] Fix white value transition for addressable lights (#2045) --- esphome/components/light/addressable_light.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index ea24736c63..4ef293cd03 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -68,10 +68,7 @@ void AddressableLight::write_state(LightState *state) { // our transition will handle brightness, disable brightness in correction. this->correction_.set_local_brightness(255); - uint8_t orig_w = target_color.w; target_color *= static_cast(roundf(end_values.get_brightness() * end_values.get_state() * 255.0f)); - // w is not scaled by brightness - target_color.w = orig_w; float denom = (1.0f - new_smoothed); float alpha = denom == 0.0f ? 0.0f : (new_smoothed - prev_smoothed) / denom; From e19aa3bbe0ca9d3cacd92cc011a8e01a553f6965 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 21 Jul 2021 09:20:20 +1200 Subject: [PATCH 1048/1841] Adding last_reset_type to sensors that should support it. (#2039) --- esphome/components/api/api.proto | 7 ++++++ esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 21 ++++++++++++++++++ esphome/components/api/api_pb2.h | 6 ++++++ esphome/components/atm90e32/sensor.py | 15 +++++++++++-- esphome/components/havells_solar/sensor.py | 7 ++++-- esphome/components/hlw8012/sensor.py | 9 ++++++-- esphome/components/pzem004t/sensor.py | 5 +++-- esphome/components/pzemac/sensor.py | 5 +++-- esphome/components/sdm_meter/sensor.py | 22 ++++++++++++++----- esphome/components/sensor/__init__.py | 25 ++++++++++++++++++++++ esphome/components/sensor/sensor.cpp | 13 +++++++++++ esphome/components/sensor/sensor.h | 24 +++++++++++++++++++++ esphome/const.py | 8 +++++++ 14 files changed, 153 insertions(+), 15 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 40be1fd0db..073775ed2e 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -422,6 +422,12 @@ enum SensorStateClass { STATE_CLASS_MEASUREMENT = 1; } +enum SensorLastResetType { + LAST_RESET_NONE = 0; + LAST_RESET_NEVER = 1; + LAST_RESET_AUTO = 2; +} + message ListEntitiesSensorResponse { option (id) = 16; option (source) = SOURCE_SERVER; @@ -438,6 +444,7 @@ message ListEntitiesSensorResponse { bool force_update = 8; string device_class = 9; SensorStateClass state_class = 10; + SensorLastResetType last_reset_type = 11; } message SensorStateResponse { option (id) = 25; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 79ffcfa69e..fb05772e5e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -399,6 +399,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { msg.force_update = sensor->get_force_update(); msg.device_class = sensor->get_device_class(); msg.state_class = static_cast(sensor->state_class); + msg.last_reset_type = static_cast(sensor->last_reset_type); return this->send_list_entities_sensor_response(msg); } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index c3cfc8cd76..057d71324f 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -72,6 +72,18 @@ template<> const char *proto_enum_to_string(enums::Sens return "UNKNOWN"; } } +template<> const char *proto_enum_to_string(enums::SensorLastResetType value) { + switch (value) { + case enums::LAST_RESET_NONE: + return "LAST_RESET_NONE"; + case enums::LAST_RESET_NEVER: + return "LAST_RESET_NEVER"; + case enums::LAST_RESET_AUTO: + return "LAST_RESET_AUTO"; + default: + return "UNKNOWN"; + } +} template<> const char *proto_enum_to_string(enums::LogLevel value) { switch (value) { case enums::LOG_LEVEL_NONE: @@ -1592,6 +1604,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->state_class = value.as_enum(); return true; } + case 11: { + this->last_reset_type = value.as_enum(); + return true; + } default: return false; } @@ -1647,6 +1663,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(8, this->force_update); buffer.encode_string(9, this->device_class); buffer.encode_enum(10, this->state_class); + buffer.encode_enum(11, this->last_reset_type); } void ListEntitiesSensorResponse::dump_to(std::string &out) const { char buffer[64]; @@ -1692,6 +1709,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append(" state_class: "); out.append(proto_enum_to_string(this->state_class)); out.append("\n"); + + out.append(" last_reset_type: "); + out.append(proto_enum_to_string(this->last_reset_type)); + out.append("\n"); out.append("}"); } bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index e3bb1d9106..0551508b4b 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -36,6 +36,11 @@ enum SensorStateClass : uint32_t { STATE_CLASS_NONE = 0, STATE_CLASS_MEASUREMENT = 1, }; +enum SensorLastResetType : uint32_t { + LAST_RESET_NONE = 0, + LAST_RESET_NEVER = 1, + LAST_RESET_AUTO = 2, +}; enum LogLevel : uint32_t { LOG_LEVEL_NONE = 0, LOG_LEVEL_ERROR = 1, @@ -429,6 +434,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { bool force_update{false}; std::string device_class{}; enums::SensorStateClass state_class{}; + enums::SensorLastResetType last_reset_type{}; void encode(ProtoWriteBuffer buffer) const override; void dump_to(std::string &out) const override; diff --git a/esphome/components/atm90e32/sensor.py b/esphome/components/atm90e32/sensor.py index 68ec199bff..2c34d76b52 100644 --- a/esphome/components/atm90e32/sensor.py +++ b/esphome/components/atm90e32/sensor.py @@ -21,6 +21,7 @@ from esphome.const import ( ICON_EMPTY, ICON_LIGHTBULB, ICON_CURRENT_AC, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, UNIT_HERTZ, UNIT_VOLT, @@ -91,10 +92,20 @@ ATM90E32_PHASE_SCHEMA = cv.Schema( STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT + UNIT_WATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT + UNIT_WATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t, cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t, diff --git a/esphome/components/havells_solar/sensor.py b/esphome/components/havells_solar/sensor.py index 7d1e2be581..1926d4d68a 100644 --- a/esphome/components/havells_solar/sensor.py +++ b/esphome/components/havells_solar/sensor.py @@ -15,6 +15,7 @@ from esphome.const import ( DEVICE_CLASS_VOLTAGE, ICON_CURRENT_AC, ICON_EMPTY, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, UNIT_AMPERE, @@ -121,14 +122,16 @@ CONFIG_SCHEMA = ( ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema( UNIT_KILOWATT_HOURS, ICON_EMPTY, 0, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_TOTAL_GENERATION_TIME): sensor.sensor_schema( UNIT_HOURS, diff --git a/esphome/components/hlw8012/sensor.py b/esphome/components/hlw8012/sensor.py index e24e995eba..face32872e 100644 --- a/esphome/components/hlw8012/sensor.py +++ b/esphome/components/hlw8012/sensor.py @@ -19,8 +19,8 @@ from esphome.const import ( DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ICON_EMPTY, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, @@ -67,7 +67,12 @@ CONFIG_SCHEMA = cv.Schema( UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional(CONF_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 1, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_WATT_HOURS, + ICON_EMPTY, + 1, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance, cv.Optional(CONF_VOLTAGE_DIVIDER, default=2351): cv.positive_float, diff --git a/esphome/components/pzem004t/sensor.py b/esphome/components/pzem004t/sensor.py index e3859f090c..b358b8c650 100644 --- a/esphome/components/pzem004t/sensor.py +++ b/esphome/components/pzem004t/sensor.py @@ -12,8 +12,8 @@ from esphome.const import ( DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ICON_EMPTY, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, @@ -47,7 +47,8 @@ CONFIG_SCHEMA = ( ICON_EMPTY, 0, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), } ) diff --git a/esphome/components/pzemac/sensor.py b/esphome/components/pzemac/sensor.py index 778c5054a0..1dd77a0371 100644 --- a/esphome/components/pzemac/sensor.py +++ b/esphome/components/pzemac/sensor.py @@ -17,8 +17,8 @@ from esphome.const import ( DEVICE_CLASS_ENERGY, ICON_EMPTY, ICON_CURRENT_AC, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_HERTZ, UNIT_VOLT, UNIT_AMPERE, @@ -54,7 +54,8 @@ CONFIG_SCHEMA = ( ICON_EMPTY, 0, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( UNIT_HERTZ, diff --git a/esphome/components/sdm_meter/sensor.py b/esphome/components/sdm_meter/sensor.py index 39ef280fef..ce560b9d4b 100644 --- a/esphome/components/sdm_meter/sensor.py +++ b/esphome/components/sdm_meter/sensor.py @@ -25,8 +25,8 @@ from esphome.const import ( ICON_CURRENT_AC, ICON_EMPTY, ICON_FLASH, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_AMPERE, UNIT_DEGREES, UNIT_EMPTY, @@ -88,24 +88,36 @@ CONFIG_SCHEMA = ( STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_IMPORT_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_WATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_EXPORT_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_WATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_IMPORT_REACTIVE_ENERGY): sensor.sensor_schema( UNIT_VOLT_AMPS_REACTIVE_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_EXPORT_REACTIVE_ENERGY): sensor.sensor_schema( UNIT_VOLT_AMPS_REACTIVE_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), } ) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 89bde9476a..0a0c3a9214 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -17,6 +17,7 @@ from esphome.const import ( CONF_ICON, CONF_ID, CONF_INTERNAL, + CONF_LAST_RESET_TYPE, CONF_ON_RAW_VALUE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, @@ -30,6 +31,9 @@ from esphome.const import ( CONF_NAME, CONF_MQTT_ID, CONF_FORCE_UPDATE, + LAST_RESET_TYPE_AUTO, + LAST_RESET_TYPE_NEVER, + LAST_RESET_TYPE_NONE, UNIT_EMPTY, ICON_EMPTY, DEVICE_CLASS_EMPTY, @@ -79,6 +83,15 @@ STATE_CLASSES = { } validate_state_class = cv.enum(STATE_CLASSES, lower=True, space="_") +LastResetTypes = sensor_ns.enum("LastResetType") +LAST_RESET_TYPES = { + LAST_RESET_TYPE_NONE: LastResetTypes.LAST_RESET_TYPE_NONE, + LAST_RESET_TYPE_NEVER: LastResetTypes.LAST_RESET_TYPE_NEVER, + LAST_RESET_TYPE_AUTO: LastResetTypes.LAST_RESET_TYPE_AUTO, +} +validate_last_reset_type = cv.enum(LAST_RESET_TYPES, lower=True, space="_") + + IS_PLATFORM_COMPONENT = True @@ -168,6 +181,7 @@ SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( cv.Optional(CONF_ACCURACY_DECIMALS): accuracy_decimals, cv.Optional(CONF_DEVICE_CLASS): device_class, cv.Optional(CONF_STATE_CLASS): validate_state_class, + cv.Optional(CONF_LAST_RESET_TYPE): validate_last_reset_type, cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean, cv.Optional(CONF_EXPIRE_AFTER): cv.All( cv.requires_component("mqtt"), @@ -202,6 +216,7 @@ def sensor_schema( accuracy_decimals_: int, device_class_: Optional[str] = DEVICE_CLASS_EMPTY, state_class_: Optional[str] = STATE_CLASS_NONE, + last_reset_type_: Optional[str] = LAST_RESET_TYPE_NONE, ) -> cv.Schema: schema = SENSOR_SCHEMA if unit_of_measurement_ != UNIT_EMPTY: @@ -230,6 +245,14 @@ def sensor_schema( schema = schema.extend( {cv.Optional(CONF_STATE_CLASS, default=state_class_): validate_state_class} ) + if last_reset_type_ != LAST_RESET_TYPE_NONE: + schema = schema.extend( + { + cv.Optional( + CONF_LAST_RESET_TYPE, default=last_reset_type_ + ): validate_last_reset_type + } + ) return schema @@ -479,6 +502,8 @@ async def setup_sensor_core_(var, config): cg.add(var.set_icon(config[CONF_ICON])) if CONF_ACCURACY_DECIMALS in config: cg.add(var.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS])) + if CONF_LAST_RESET_TYPE in config: + cg.add(var.set_last_reset_type(config[CONF_LAST_RESET_TYPE])) cg.add(var.set_force_update(config[CONF_FORCE_UPDATE])) if config.get(CONF_FILTERS): # must exist and not be empty filters = await build_filters(config[CONF_FILTERS]) diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index fe92f88308..6e8765a8df 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -16,6 +16,18 @@ const char *state_class_to_string(StateClass state_class) { } } +const char *last_reset_type_to_string(LastResetType last_reset_type) { + switch (last_reset_type) { + case LAST_RESET_TYPE_NEVER: + return "never"; + case LAST_RESET_TYPE_AUTO: + return "auto"; + case LAST_RESET_TYPE_NONE: + default: + return ""; + } +} + void Sensor::publish_state(float state) { this->raw_state = state; this->raw_callback_.call(state); @@ -64,6 +76,7 @@ void Sensor::set_state_class(const std::string &state_class) { ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str()); } } +void Sensor::set_last_reset_type(LastResetType last_reset_type) { this->last_reset_type = last_reset_type; } std::string Sensor::get_unit_of_measurement() { if (this->unit_of_measurement_.has_value()) return *this->unit_of_measurement_; diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 123e7eddb3..b9908b6cbe 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -14,6 +14,10 @@ namespace sensor { ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ } \ ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->state_class)); \ + if ((obj)->state_class == sensor::STATE_CLASS_MEASUREMENT && \ + (obj)->last_reset_type != sensor::LAST_RESET_TYPE_NONE) { \ + ESP_LOGCONFIG(TAG, "%s Last Reset Type: '%s'", prefix, last_reset_type_to_string((obj)->last_reset_type)); \ + } \ ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \ ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \ if (!(obj)->get_icon().empty()) { \ @@ -37,6 +41,20 @@ enum StateClass : uint8_t { const char *state_class_to_string(StateClass state_class); +/** + * Sensor last reset types + */ +enum LastResetType : uint8_t { + /// This sensor does not support resetting. ie, it is not accumulative + LAST_RESET_TYPE_NONE = 0, + /// This sensor is expected to never reset its value + LAST_RESET_TYPE_NEVER = 1, + /// This sensor may reset and Home Assistant will watch for this + LAST_RESET_TYPE_AUTO = 2, +}; + +const char *last_reset_type_to_string(LastResetType last_reset_type); + /** Base-class for all sensors. * * A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy. @@ -155,6 +173,12 @@ class Sensor : public Nameable { */ virtual std::string device_class(); + // The Last reset type of this sensor + LastResetType last_reset_type{LAST_RESET_TYPE_NONE}; + + /// Manually set the Home Assistant last reset type for this sensor. + void set_last_reset_type(LastResetType last_reset_type); + /** A unique ID for this sensor, empty for no unique id. See unique ID requirements: * https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements * diff --git a/esphome/const.py b/esphome/const.py index 556c8c70fc..f85016aeb5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -297,6 +297,7 @@ CONF_KEY = "key" CONF_LAMBDA = "lambda" CONF_LAST_CONFIDENCE = "last_confidence" CONF_LAST_FINGER_ID = "last_finger_id" +CONF_LAST_RESET_TYPE = "last_reset_type" CONF_LATITUDE = "latitude" CONF_LENGTH = "length" CONF_LEVEL = "level" @@ -785,3 +786,10 @@ STATE_CLASS_NONE = "" # The state represents a measurement in present time STATE_CLASS_MEASUREMENT = "measurement" + +# This sensor does not support resetting. ie, it is not accumulative +LAST_RESET_TYPE_NONE = "" +# This sensor is expected to never reset its value +LAST_RESET_TYPE_NEVER = "never" +# This sensor may reset and Home Assistant will watch for this +LAST_RESET_TYPE_AUTO = "auto" From 837930234fe0ba3df3d5f22d1b648d7ab9082a8f Mon Sep 17 00:00:00 2001 From: Sean Vig Date: Tue, 20 Jul 2021 17:35:45 -0400 Subject: [PATCH 1049/1841] Remove superfluous polling on ADS1115 (#2015) --- esphome/components/ads1115/ads1115.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/esphome/components/ads1115/ads1115.cpp b/esphome/components/ads1115/ads1115.cpp index 8cac897a15..92f0ffbe3a 100644 --- a/esphome/components/ads1115/ads1115.cpp +++ b/esphome/components/ads1115/ads1115.cpp @@ -64,11 +64,6 @@ void ADS1115Component::setup() { return; } this->prev_config_ = config; - - for (auto *sensor : this->sensors_) { - this->set_interval(sensor->get_name(), sensor->update_interval(), - [this, sensor] { this->request_measurement(sensor); }); - } } void ADS1115Component::dump_config() { ESP_LOGCONFIG(TAG, "Setting up ADS1115..."); From 27112e2ace456eea883ee37d13d8ea970428908f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 21 Jul 2021 10:52:48 +1200 Subject: [PATCH 1050/1841] Bump version to v1.20.0b6 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f85016aeb5..d8a4448d1f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.0b5" +__version__ = "1.20.0b6" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From fc42f14448458b3791ea0584872bf246e7ce0705 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Jul 2021 14:40:09 +1200 Subject: [PATCH 1051/1841] Bump pylint from 2.8.2 to 2.9.4 (#2047) * Bump pylint from 2.8.2 to 2.9.4 Bumps [pylint](https://github.com/PyCQA/pylint) from 2.8.2 to 2.9.4. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - [Commits](https://github.com/PyCQA/pylint/compare/v2.8.2...v2.9.4) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Fix up functionality needed for latest pylint (#2049) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sean Vig --- esphome/final_validate.py | 5 +++-- esphome/yaml_util.py | 4 +++- requirements_test.txt | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/esphome/final_validate.py b/esphome/final_validate.py index 47071b5391..199c68210e 100644 --- a/esphome/final_validate.py +++ b/esphome/final_validate.py @@ -1,4 +1,4 @@ -from abc import ABC, abstractmethod, abstractproperty +from abc import ABC, abstractmethod from typing import Dict, Any import contextvars @@ -14,7 +14,8 @@ from esphome.core import CORE class FinalValidateConfig(ABC): - @abstractproperty + @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 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/requirements_test.txt b/requirements_test.txt index dc46b6f3bf..985f7d0932 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.8.2 +pylint==2.9.4 flake8==3.9.2 black==21.7b0 pexpect==4.8.0 From 4b8ec44262922e056c6ab84894d75c2e9575e293 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 22 Jul 2021 07:55:49 +1200 Subject: [PATCH 1052/1841] Bump version to v1.20.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index d8a4448d1f..48790cf16a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.0b6" +__version__ = "1.20.0" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From c9062599df0b4b275fa734cdeadc872320791dd3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jul 2021 08:58:41 +1200 Subject: [PATCH 1053/1841] Bump pylint from 2.9.4 to 2.9.5 (#2050) Bumps [pylint](https://github.com/PyCQA/pylint) from 2.9.4 to 2.9.5. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - [Commits](https://github.com/PyCQA/pylint/compare/v2.9.4...v2.9.5) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 985f7d0932..b0e24b5b2f 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.9.4 +pylint==2.9.5 flake8==3.9.2 black==21.7b0 pexpect==4.8.0 From 0a32321c8543924b4222a987594b67372b4c409d Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Wed, 21 Jul 2021 16:09:31 -0500 Subject: [PATCH 1054/1841] Thermostat fixes+updates 1 (#2032) Co-authored-by: Otto Winter --- esphome/components/thermostat/climate.py | 42 ++++++++++++ .../thermostat/thermostat_climate.cpp | 64 ++++++++++++++----- .../thermostat/thermostat_climate.h | 16 +++++ esphome/const.py | 2 + 4 files changed, 107 insertions(+), 17 deletions(-) diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index 07a94fd184..bf3195a5dd 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -7,6 +7,7 @@ from esphome.const import ( CONF_AWAY_CONFIG, CONF_COOL_ACTION, CONF_COOL_MODE, + CONF_DEFAULT_MODE, CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_DRY_ACTION, @@ -33,10 +34,12 @@ from esphome.const import ( CONF_SWING_HORIZONTAL_ACTION, CONF_SWING_OFF_ACTION, CONF_SWING_VERTICAL_ACTION, + CONF_TARGET_TEMPERATURE_CHANGE_ACTION, ) CODEOWNERS = ["@kbx81"] +climate_ns = cg.esphome_ns.namespace("climate") thermostat_ns = cg.esphome_ns.namespace("thermostat") ThermostatClimate = thermostat_ns.class_( "ThermostatClimate", climate.Climate, cg.Component @@ -44,6 +47,17 @@ ThermostatClimate = thermostat_ns.class_( ThermostatClimateTargetTempConfig = thermostat_ns.struct( "ThermostatClimateTargetTempConfig" ) +ClimateMode = climate_ns.enum("ClimateMode") +CLIMATE_MODES = { + "OFF": ClimateMode.CLIMATE_MODE_OFF, + "HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL, + "COOL": ClimateMode.CLIMATE_MODE_COOL, + "HEAT": ClimateMode.CLIMATE_MODE_HEAT, + "DRY": ClimateMode.CLIMATE_MODE_DRY, + "FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY, + "AUTO": ClimateMode.CLIMATE_MODE_AUTO, +} +validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True) def validate_thermostat(config): @@ -141,6 +155,21 @@ def validate_thermostat(config): CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION ) ) + # verify default climate mode is valid given above configuration + default_mode = config[CONF_DEFAULT_MODE] + requirements = { + "HEAT_COOL": [CONF_COOL_ACTION, CONF_HEAT_ACTION], + "COOL": [CONF_COOL_ACTION], + "HEAT": [CONF_HEAT_ACTION], + "DRY": [CONF_DRY_ACTION], + "FAN_ONLY": [CONF_FAN_ONLY_ACTION], + "AUTO": [CONF_COOL_ACTION, CONF_HEAT_ACTION], + }.get(default_mode, []) + for req in requirements: + if req not in config: + raise cv.Invalid( + f"{CONF_DEFAULT_MODE} is set to {default_mode} but {req} is not present in the configuration" + ) return config @@ -204,6 +233,12 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_SWING_VERTICAL_ACTION): automation.validate_automation( single=True ), + cv.Optional( + CONF_TARGET_TEMPERATURE_CHANGE_ACTION + ): automation.validate_automation(single=True), + cv.Optional(CONF_DEFAULT_MODE, default="OFF"): cv.templatable( + validate_climate_mode + ), cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, cv.Optional(CONF_HYSTERESIS, default=0.5): cv.temperature, @@ -233,6 +268,7 @@ async def to_code(config): ) sens = await cg.get_variable(config[CONF_SENSOR]) + cg.add(var.set_default_mode(config[CONF_DEFAULT_MODE])) cg.add(var.set_sensor(sens)) cg.add(var.set_hysteresis(config[CONF_HYSTERESIS])) @@ -380,6 +416,12 @@ async def to_code(config): config[CONF_SWING_VERTICAL_ACTION], ) cg.add(var.set_supports_swing_mode_vertical(True)) + if CONF_TARGET_TEMPERATURE_CHANGE_ACTION in config: + await automation.build_automation( + var.get_temperature_change_trigger(), + [], + config[CONF_TARGET_TEMPERATURE_CHANGE_ACTION], + ) if CONF_AWAY_CONFIG in config: away = config[CONF_AWAY_CONFIG] diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 305db66f16..4610d5caad 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -21,7 +21,7 @@ void ThermostatClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles temps for us - this->mode = climate::CLIMATE_MODE_HEAT_COOL; + this->mode = this->default_mode_; this->change_away_(false); } // refresh the climate action based on the restored settings @@ -35,9 +35,18 @@ void ThermostatClimate::refresh() { this->switch_to_action_(compute_action_()); this->switch_to_fan_mode_(this->fan_mode.value()); this->switch_to_swing_mode_(this->swing_mode); + this->check_temperature_change_trigger_(); this->publish_state(); } void ThermostatClimate::control(const climate::ClimateCall &call) { + if (call.get_preset().has_value()) { + // setup_complete_ blocks modifying/resetting the temps immediately after boot + if (this->setup_complete_) { + this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY); + } else { + this->preset = *call.get_preset(); + } + } if (call.get_mode().has_value()) this->mode = *call.get_mode(); if (call.get_fan_mode().has_value()) @@ -50,15 +59,6 @@ void ThermostatClimate::control(const climate::ClimateCall &call) { this->target_temperature_low = *call.get_target_temperature_low(); if (call.get_target_temperature_high().has_value()) this->target_temperature_high = *call.get_target_temperature_high(); - if (call.get_preset().has_value()) { - // setup_complete_ blocks modifying/resetting the temps immediately after boot - if (this->setup_complete_) { - this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY); - } else { - this->preset = *call.get_preset(); - ; - } - } // set point validation if (this->supports_two_points_) { if (this->target_temperature_low < this->get_traits().get_visual_min_temperature()) @@ -128,7 +128,16 @@ climate::ClimateTraits ThermostatClimate::traits() { return traits; } climate::ClimateAction ThermostatClimate::compute_action_() { + // we need to know the current climate action before anything else happens here climate::ClimateAction target_action = this->action; + // if the climate mode is OFF then the climate action must be OFF + if (this->mode == climate::CLIMATE_MODE_OFF) { + return climate::CLIMATE_ACTION_OFF; + } else if (this->action == climate::CLIMATE_ACTION_OFF) { + // ...but if the climate mode is NOT OFF then the climate action must not be OFF + target_action = climate::CLIMATE_ACTION_IDLE; + } + if (this->supports_two_points_) { if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || isnan(this->target_temperature_high) || isnan(this->hysteresis_)) @@ -153,9 +162,6 @@ climate::ClimateAction ThermostatClimate::compute_action_() { case climate::CLIMATE_MODE_DRY: target_action = climate::CLIMATE_ACTION_DRYING; break; - case climate::CLIMATE_MODE_OFF: - target_action = climate::CLIMATE_ACTION_OFF; - break; case climate::CLIMATE_MODE_HEAT_COOL: case climate::CLIMATE_MODE_COOL: case climate::CLIMATE_MODE_HEAT: @@ -200,9 +206,6 @@ climate::ClimateAction ThermostatClimate::compute_action_() { case climate::CLIMATE_MODE_DRY: target_action = climate::CLIMATE_ACTION_DRYING; break; - case climate::CLIMATE_MODE_OFF: - target_action = climate::CLIMATE_ACTION_OFF; - break; case climate::CLIMATE_MODE_COOL: if (this->supports_cool_) { if (this->current_temperature > this->target_temperature + this->hysteresis_) @@ -410,6 +413,30 @@ void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mo this->prev_swing_mode_ = swing_mode; this->prev_swing_mode_trigger_ = trig; } +void ThermostatClimate::check_temperature_change_trigger_() { + if (this->supports_two_points_) { + // setup_complete_ helps us ensure an action is called immediately after boot + if ((this->prev_target_temperature_low_ == this->target_temperature_low) && + (this->prev_target_temperature_high_ == this->target_temperature_high) && this->setup_complete_) { + return; // nothing changed, no reason to trigger + } else { + // save the new temperatures so we can check them again later; the trigger will fire below + this->prev_target_temperature_low_ = this->target_temperature_low; + this->prev_target_temperature_high_ = this->target_temperature_high; + } + } else { + if ((this->prev_target_temperature_ == this->target_temperature) && this->setup_complete_) { + return; // nothing changed, no reason to trigger + } else { + // save the new temperature so we can check it again later; the trigger will fire below + this->prev_target_temperature_ = this->target_temperature; + } + } + // trigger the action + Trigger<> *trig = this->temperature_change_trigger_; + assert(trig != nullptr); + trig->trigger(); +} void ThermostatClimate::change_away_(bool away) { if (!away) { if (this->supports_two_points_) { @@ -457,7 +484,9 @@ ThermostatClimate::ThermostatClimate() swing_mode_both_trigger_(new Trigger<>()), swing_mode_off_trigger_(new Trigger<>()), swing_mode_horizontal_trigger_(new Trigger<>()), - swing_mode_vertical_trigger_(new Trigger<>()) {} + swing_mode_vertical_trigger_(new Trigger<>()), + temperature_change_trigger_(new Trigger<>()) {} +void ThermostatClimate::set_default_mode(climate::ClimateMode default_mode) { this->default_mode_ = default_mode; } void ThermostatClimate::set_hysteresis(float hysteresis) { this->hysteresis_ = hysteresis; } void ThermostatClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } void ThermostatClimate::set_supports_heat_cool(bool supports_heat_cool) { @@ -534,6 +563,7 @@ Trigger<> *ThermostatClimate::get_swing_mode_both_trigger() const { return this- Trigger<> *ThermostatClimate::get_swing_mode_off_trigger() const { return this->swing_mode_off_trigger_; } Trigger<> *ThermostatClimate::get_swing_mode_horizontal_trigger() const { return this->swing_mode_horizontal_trigger_; } Trigger<> *ThermostatClimate::get_swing_mode_vertical_trigger() const { return this->swing_mode_vertical_trigger_; } +Trigger<> *ThermostatClimate::get_temperature_change_trigger() const { return this->temperature_change_trigger_; } void ThermostatClimate::dump_config() { LOG_CLIMATE("", "Thermostat", this); if (this->supports_heat_) { diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 3fd482da53..bff9e9bdc1 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -26,6 +26,7 @@ class ThermostatClimate : public climate::Climate, public Component { void setup() override; void dump_config() override; + void set_default_mode(climate::ClimateMode default_mode); void set_hysteresis(float hysteresis); void set_sensor(sensor::Sensor *sensor); void set_supports_auto(bool supports_auto); @@ -76,6 +77,7 @@ class ThermostatClimate : public climate::Climate, public Component { Trigger<> *get_swing_mode_horizontal_trigger() const; Trigger<> *get_swing_mode_off_trigger() const; Trigger<> *get_swing_mode_vertical_trigger() const; + Trigger<> *get_temperature_change_trigger() const; /// Get current hysteresis value float hysteresis(); /// Call triggers based on updated climate states (modes/actions) @@ -106,6 +108,9 @@ class ThermostatClimate : public climate::Climate, public Component { /// Switch the climate device to the given climate swing mode. void switch_to_swing_mode_(climate::ClimateSwingMode swing_mode); + /// Check if the temperature change trigger should be called. + void check_temperature_change_trigger_(); + /// The sensor used for getting the current temperature sensor::Sensor *sensor_{nullptr}; @@ -242,6 +247,9 @@ class ThermostatClimate : public climate::Climate, public Component { /// The trigger to call when the controller should switch the swing mode to "vertical". Trigger<> *swing_mode_vertical_trigger_{nullptr}; + /// The trigger to call when the target temperature(s) change(es). + Trigger<> *temperature_change_trigger_{nullptr}; + /// A reference to the trigger that was previously active. /// /// This is so that the previous trigger can be stopped before enabling a new one @@ -256,8 +264,16 @@ class ThermostatClimate : public climate::Climate, public Component { /// These are used to determine when a trigger/action needs to be called climate::ClimateFanMode prev_fan_mode_{climate::CLIMATE_FAN_ON}; climate::ClimateMode prev_mode_{climate::CLIMATE_MODE_OFF}; + climate::ClimateMode default_mode_{climate::CLIMATE_MODE_OFF}; climate::ClimateSwingMode prev_swing_mode_{climate::CLIMATE_SWING_OFF}; + /// Store previously-known temperatures + /// + /// These are used to determine when the temperature change trigger/action needs to be called + float prev_target_temperature_{NAN}; + float prev_target_temperature_low_{NAN}; + float prev_target_temperature_high_{NAN}; + /// Temperature data for normal/home and away modes ThermostatClimateTargetTempConfig normal_config_{}; ThermostatClimateTargetTempConfig away_config_{}; diff --git a/esphome/const.py b/esphome/const.py index 8baca4d34f..3eb56c7cf6 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -159,6 +159,7 @@ CONF_DAYS_OF_WEEK = "days_of_week" CONF_DC_PIN = "dc_pin" CONF_DEBOUNCE = "debounce" CONF_DECELERATION = "deceleration" +CONF_DEFAULT_MODE = "default_mode" CONF_DEFAULT_TARGET_TEMPERATURE_HIGH = "default_target_temperature_high" CONF_DEFAULT_TARGET_TEMPERATURE_LOW = "default_target_temperature_low" CONF_DEFAULT_TRANSITION_LENGTH = "default_transition_length" @@ -572,6 +573,7 @@ CONF_TABLET = "tablet" CONF_TAG = "tag" CONF_TARGET = "target" CONF_TARGET_TEMPERATURE = "target_temperature" +CONF_TARGET_TEMPERATURE_CHANGE_ACTION = "target_temperature_change_action" CONF_TARGET_TEMPERATURE_HIGH = "target_temperature_high" CONF_TARGET_TEMPERATURE_LOW = "target_temperature_low" CONF_TEMPERATURE = "temperature" From 5379794f16b9c2c6fb6e721272850c428c759889 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 22 Jul 2021 13:24:01 +1200 Subject: [PATCH 1055/1841] Add test5 back to CI (#2052) --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ccaaa47b5..a0be9be903 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,6 +82,9 @@ jobs: - id: test file: tests/test4.yaml name: Test tests/test4.yaml + - id: test + file: tests/test5.yaml + name: Test tests/test5.yaml - id: pytest name: Run pytest From 90394a50df8bcc0ede3af16a3d7ebbf0257a95c8 Mon Sep 17 00:00:00 2001 From: Pasi Suominen Date: Thu, 22 Jul 2021 06:21:08 +0300 Subject: [PATCH 1056/1841] Added support for pvvx_mithermometer sensor (#1546) Co-authored-by: Pasi Suominen Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + .../components/pvvx_mithermometer/__init__.py | 0 .../pvvx_mithermometer/pvvx_mithermometer.cpp | 145 ++++++++++++++++++ .../pvvx_mithermometer/pvvx_mithermometer.h | 48 ++++++ .../components/pvvx_mithermometer/sensor.py | 72 +++++++++ tests/test2.yaml | 10 ++ 6 files changed, 276 insertions(+) create mode 100644 esphome/components/pvvx_mithermometer/__init__.py create mode 100644 esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp create mode 100644 esphome/components/pvvx_mithermometer/pvvx_mithermometer.h create mode 100644 esphome/components/pvvx_mithermometer/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 557fe7cc08..1decc5c330 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -88,6 +88,7 @@ esphome/components/pn532_i2c/* @OttoWinter @jesserockz esphome/components/pn532_spi/* @OttoWinter @jesserockz esphome/components/power_supply/* @esphome/core esphome/components/pulse_meter/* @stevebaxter +esphome/components/pvvx_mithermometer/* @pasiz esphome/components/rc522/* @glmnet esphome/components/rc522_i2c/* @glmnet esphome/components/rc522_spi/* @glmnet diff --git a/esphome/components/pvvx_mithermometer/__init__.py b/esphome/components/pvvx_mithermometer/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp new file mode 100644 index 0000000000..34f190e45e --- /dev/null +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp @@ -0,0 +1,145 @@ +#include "pvvx_mithermometer.h" +#include "esphome/core/log.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace pvvx_mithermometer { + +static const char *TAG = "pvvx_mithermometer"; + +void PVVXMiThermometer::dump_config() { + ESP_LOGCONFIG(TAG, "PVVX MiThermometer"); + LOG_SENSOR(" ", "Temperature", this->temperature_); + LOG_SENSOR(" ", "Humidity", this->humidity_); + LOG_SENSOR(" ", "Battery Level", this->battery_level_); + LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_); +} + +bool PVVXMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + if (device.address_uint64() != this->address_) { + ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); + return false; + } + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + + bool success = false; + for (auto &service_data : device.get_service_datas()) { + auto res = parse_header(service_data); + if (res->is_duplicate) { + continue; + } + if (!(parse_message(service_data.data, *res))) { + continue; + } + if (!(report_results(res, device.address_str()))) { + continue; + } + if (res->temperature.has_value() && this->temperature_ != nullptr) + this->temperature_->publish_state(*res->temperature); + if (res->humidity.has_value() && this->humidity_ != nullptr) + this->humidity_->publish_state(*res->humidity); + if (res->battery_level.has_value() && this->battery_level_ != nullptr) + this->battery_level_->publish_state(*res->battery_level); + if (res->battery_voltage.has_value() && this->battery_voltage_ != nullptr) + this->battery_voltage_->publish_state(*res->battery_voltage); + success = true; + } + + if (!success) { + return false; + } + + return true; +} + +optional PVVXMiThermometer::parse_header(const esp32_ble_tracker::ServiceData &service_data) { + ParseResult result; + if (!service_data.uuid.contains(0x1A, 0x18)) { + ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes."); + return {}; + } + + auto raw = service_data.data; + + static uint8_t last_frame_count = 0; + if (last_frame_count == raw[13]) { + ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%d).", static_cast(last_frame_count)); + result.is_duplicate = true; + return {}; + } + last_frame_count = raw[13]; + result.is_duplicate = false; + + return result; +} + +bool PVVXMiThermometer::parse_message(const std::vector &message, ParseResult &result) { + /* + All data little endian + uint8_t size; // = 19 + uint8_t uid; // = 0x16, 16-bit UUID + uint16_t UUID; // = 0x181A, GATT Service 0x181A Environmental Sensing + uint8_t MAC[6]; // [0] - lo, .. [5] - hi digits + int16_t temperature; // x 0.01 degree [6,7] + uint16_t humidity; // x 0.01 % [8,9] + uint16_t battery_mv; // mV [10,11] + uint8_t battery_level; // 0..100 % [12] + uint8_t counter; // measurement count [13] + uint8_t flags; [14] + */ + + const uint8_t *data = message.data(); + const int data_length = 15; + + if (message.size() != data_length) { + ESP_LOGVV(TAG, "parse_message(): payload has wrong size (%d)!", message.size()); + return false; + } + + // int16_t temperature; // x 0.01 degree [6,7] + const int16_t temperature = int16_t(data[6]) | (int16_t(data[7]) << 8); + result.temperature = temperature / 1.0e2f; + + // uint16_t humidity; // x 0.01 % [8,9] + const int16_t humidity = uint16_t(data[8]) | (uint16_t(data[9]) << 8); + result.humidity = humidity / 1.0e2f; + + // uint16_t battery_mv; // mV [10,11] + const int16_t battery_voltage = uint16_t(data[10]) | (uint16_t(data[11]) << 8); + result.battery_voltage = battery_voltage / 1.0e3f; + + // uint8_t battery_level; // 0..100 % [12] + result.battery_level = uint8_t(data[12]); + + return true; +} + +bool PVVXMiThermometer::report_results(const optional &result, const std::string &address) { + if (!result.has_value()) { + ESP_LOGVV(TAG, "report_results(): no results available."); + return false; + } + + ESP_LOGD(TAG, "Got PVVX MiThermometer (%s):", address.c_str()); + + if (result->temperature.has_value()) { + ESP_LOGD(TAG, " Temperature: %.2f °C", *result->temperature); + } + if (result->humidity.has_value()) { + ESP_LOGD(TAG, " Humidity: %.2f %%", *result->humidity); + } + if (result->battery_level.has_value()) { + ESP_LOGD(TAG, " Battery Level: %.0f %%", *result->battery_level); + } + if (result->battery_voltage.has_value()) { + ESP_LOGD(TAG, " Battery Voltage: %.3f V", *result->battery_voltage); + } + + return true; +} + +} // namespace pvvx_mithermometer +} // namespace esphome + +#endif diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h new file mode 100644 index 0000000000..42a40d4200 --- /dev/null +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h @@ -0,0 +1,48 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace pvvx_mithermometer { + +struct ParseResult { + optional temperature; + optional humidity; + optional battery_level; + optional battery_voltage; + bool is_duplicate; + int raw_offset; +}; + +class PVVXMiThermometer : public Component, public esp32_ble_tracker::ESPBTDeviceListener { + public: + void set_address(uint64_t address) { address_ = address; }; + + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } + void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } + void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } + void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; } + + protected: + uint64_t address_; + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *humidity_{nullptr}; + sensor::Sensor *battery_level_{nullptr}; + sensor::Sensor *battery_voltage_{nullptr}; + + optional parse_header(const esp32_ble_tracker::ServiceData &service_data); + bool parse_message(const std::vector &message, ParseResult &result); + bool report_results(const optional &result, const std::string &address); +}; + +} // namespace pvvx_mithermometer +} // namespace esphome + +#endif diff --git a/esphome/components/pvvx_mithermometer/sensor.py b/esphome/components/pvvx_mithermometer/sensor.py new file mode 100644 index 0000000000..cccf92b277 --- /dev/null +++ b/esphome/components/pvvx_mithermometer/sensor.py @@ -0,0 +1,72 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, esp32_ble_tracker +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_BATTERY_VOLTAGE, + CONF_MAC_ADDRESS, + CONF_HUMIDITY, + CONF_TEMPERATURE, + CONF_ID, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, + ICON_EMPTY, + UNIT_CELSIUS, + UNIT_PERCENT, + UNIT_VOLT, +) + +CODEOWNERS = ["@pasiz"] + +DEPENDENCIES = ["esp32_ble_tracker"] + +pvvx_mithermometer_ns = cg.esphome_ns.namespace("pvvx_mithermometer") +PVVXMiThermometer = pvvx_mithermometer_ns.class_( + "PVVXMiThermometer", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(PVVXMiThermometer), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 2, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 2, DEVICE_CLASS_HUMIDITY + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY + ), + cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield esp32_ble_tracker.register_ble_device(var, config) + + cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) + + if CONF_TEMPERATURE in config: + sens = yield sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature(sens)) + if CONF_HUMIDITY in config: + sens = yield sensor.new_sensor(config[CONF_HUMIDITY]) + cg.add(var.set_humidity(sens)) + if CONF_BATTERY_LEVEL in config: + sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) + cg.add(var.set_battery_level(sens)) + if CONF_BATTERY_VOLTAGE in config: + sens = yield sensor.new_sensor(config[CONF_BATTERY_VOLTAGE]) + cg.add(var.set_battery_voltage(sens)) diff --git a/tests/test2.yaml b/tests/test2.yaml index faa76300cc..1746804061 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -218,6 +218,16 @@ 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: From acbb8e9fd028339ac143a9dd9df1a7060a79a4b1 Mon Sep 17 00:00:00 2001 From: Sourabh Jaiswal Date: Thu, 22 Jul 2021 09:01:28 +0530 Subject: [PATCH 1057/1841] Added support for Selec Energy Meter (#1993) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/selec_meter/__init__.py | 0 .../components/selec_meter/selec_meter.cpp | 108 +++++++++++ esphome/components/selec_meter/selec_meter.h | 45 +++++ .../selec_meter/selec_meter_registers.h | 32 ++++ esphome/components/selec_meter/sensor.py | 169 ++++++++++++++++++ tests/test5.yaml | 44 +++++ 7 files changed, 399 insertions(+) create mode 100644 esphome/components/selec_meter/__init__.py create mode 100644 esphome/components/selec_meter/selec_meter.cpp create mode 100644 esphome/components/selec_meter/selec_meter.h create mode 100644 esphome/components/selec_meter/selec_meter_registers.h create mode 100644 esphome/components/selec_meter/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 1decc5c330..844bea763e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -97,6 +97,7 @@ esphome/components/rf_bridge/* @jesserockz esphome/components/rtttl/* @glmnet esphome/components/script/* @esphome/core esphome/components/sdm_meter/* @jesserockz @polyfaces +esphome/components/selec_meter/* @sourabhjaiswal esphome/components/sensor/* @esphome/core esphome/components/sgp40/* @SenexCrenshaw esphome/components/sht4x/* @sjtrny diff --git a/esphome/components/selec_meter/__init__.py b/esphome/components/selec_meter/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/selec_meter/selec_meter.cpp b/esphome/components/selec_meter/selec_meter.cpp new file mode 100644 index 0000000000..8bcf91f3c0 --- /dev/null +++ b/esphome/components/selec_meter/selec_meter.cpp @@ -0,0 +1,108 @@ +#include "selec_meter.h" +#include "selec_meter_registers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace selec_meter { + +static const char *const TAG = "selec_meter"; + +static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x04; +static const uint8_t MODBUS_REGISTER_COUNT = 34; // 34 x 16-bit registers + +void SelecMeter::on_modbus_data(const std::vector &data) { + if (data.size() < MODBUS_REGISTER_COUNT * 2) { + ESP_LOGW(TAG, "Invalid size for SelecMeter!"); + return; + } + + auto selec_meter_get_float = [&](size_t i, float unit) -> float { + uint32_t temp = encode_uint32(data[i + 2], data[i + 3], data[i], data[i + 1]); + + float f; + memcpy(&f, &temp, sizeof(f)); + return (f * unit); + }; + + float total_active_energy = selec_meter_get_float(SELEC_TOTAL_ACTIVE_ENERGY * 2, NO_DEC_UNIT); + float import_active_energy = selec_meter_get_float(SELEC_IMPORT_ACTIVE_ENERGY * 2, NO_DEC_UNIT); + float export_active_energy = selec_meter_get_float(SELEC_EXPORT_ACTIVE_ENERGY * 2, NO_DEC_UNIT); + float total_reactive_energy = selec_meter_get_float(SELEC_TOTAL_REACTIVE_ENERGY * 2, NO_DEC_UNIT); + float import_reactive_energy = selec_meter_get_float(SELEC_IMPORT_REACTIVE_ENERGY * 2, NO_DEC_UNIT); + float export_reactive_energy = selec_meter_get_float(SELEC_EXPORT_REACTIVE_ENERGY * 2, NO_DEC_UNIT); + float apparent_energy = selec_meter_get_float(SELEC_APPARENT_ENERGY * 2, NO_DEC_UNIT); + float active_power = selec_meter_get_float(SELEC_ACTIVE_POWER * 2, MULTIPLY_THOUSAND_UNIT); + float reactive_power = selec_meter_get_float(SELEC_REACTIVE_POWER * 2, MULTIPLY_THOUSAND_UNIT); + float apparent_power = selec_meter_get_float(SELEC_APPARENT_POWER * 2, MULTIPLY_THOUSAND_UNIT); + float voltage = selec_meter_get_float(SELEC_VOLTAGE * 2, NO_DEC_UNIT); + float current = selec_meter_get_float(SELEC_CURRENT * 2, NO_DEC_UNIT); + float power_factor = selec_meter_get_float(SELEC_POWER_FACTOR * 2, NO_DEC_UNIT); + float frequency = selec_meter_get_float(SELEC_FREQUENCY * 2, NO_DEC_UNIT); + float maximum_demand_active_power = + selec_meter_get_float(SELEC_MAXIMUM_DEMAND_ACTIVE_POWER * 2, MULTIPLY_THOUSAND_UNIT); + float maximum_demand_reactive_power = + selec_meter_get_float(SELEC_MAXIMUM_DEMAND_REACTIVE_POWER * 2, MULTIPLY_THOUSAND_UNIT); + float maximum_demand_apparent_power = + selec_meter_get_float(SELEC_MAXIMUM_DEMAND_APPARENT_POWER * 2, MULTIPLY_THOUSAND_UNIT); + + if (this->total_active_energy_sensor_ != nullptr) + this->total_active_energy_sensor_->publish_state(total_active_energy); + if (this->import_active_energy_sensor_ != nullptr) + this->import_active_energy_sensor_->publish_state(import_active_energy); + if (this->export_active_energy_sensor_ != nullptr) + this->export_active_energy_sensor_->publish_state(export_active_energy); + if (this->total_reactive_energy_sensor_ != nullptr) + this->total_reactive_energy_sensor_->publish_state(total_reactive_energy); + if (this->import_reactive_energy_sensor_ != nullptr) + this->import_reactive_energy_sensor_->publish_state(import_reactive_energy); + if (this->export_reactive_energy_sensor_ != nullptr) + this->export_reactive_energy_sensor_->publish_state(export_reactive_energy); + if (this->apparent_energy_sensor_ != nullptr) + this->apparent_energy_sensor_->publish_state(apparent_energy); + if (this->active_power_sensor_ != nullptr) + this->active_power_sensor_->publish_state(active_power); + if (this->reactive_power_sensor_ != nullptr) + this->reactive_power_sensor_->publish_state(reactive_power); + if (this->apparent_power_sensor_ != nullptr) + this->apparent_power_sensor_->publish_state(apparent_power); + if (this->voltage_sensor_ != nullptr) + this->voltage_sensor_->publish_state(voltage); + if (this->current_sensor_ != nullptr) + this->current_sensor_->publish_state(current); + if (this->power_factor_sensor_ != nullptr) + this->power_factor_sensor_->publish_state(power_factor); + if (this->frequency_sensor_ != nullptr) + this->frequency_sensor_->publish_state(frequency); + if (this->maximum_demand_active_power_sensor_ != nullptr) + this->maximum_demand_active_power_sensor_->publish_state(maximum_demand_active_power); + if (this->maximum_demand_reactive_power_sensor_ != nullptr) + this->maximum_demand_reactive_power_sensor_->publish_state(maximum_demand_reactive_power); + if (this->maximum_demand_apparent_power_sensor_ != nullptr) + this->maximum_demand_apparent_power_sensor_->publish_state(maximum_demand_apparent_power); +} + +void SelecMeter::update() { this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT); } +void SelecMeter::dump_config() { + ESP_LOGCONFIG(TAG, "SELEC Meter:"); + ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); + LOG_SENSOR(" ", "Total Active Energy", this->total_active_energy_sensor_); + LOG_SENSOR(" ", "Import Active Energy", this->import_active_energy_sensor_); + LOG_SENSOR(" ", "Export Active Energy", this->export_active_energy_sensor_); + LOG_SENSOR(" ", "Total Reactive Energy", this->total_reactive_energy_sensor_); + LOG_SENSOR(" ", "Import Reactive Energy", this->import_reactive_energy_sensor_); + LOG_SENSOR(" ", "Export Reactive Energy", this->export_reactive_energy_sensor_); + LOG_SENSOR(" ", "Apparent Energy", this->apparent_energy_sensor_); + LOG_SENSOR(" ", "Active Power", this->active_power_sensor_); + LOG_SENSOR(" ", "Reactive Power", this->reactive_power_sensor_); + LOG_SENSOR(" ", "Apparent Power", this->apparent_power_sensor_); + LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); + LOG_SENSOR(" ", "Current", this->current_sensor_); + LOG_SENSOR(" ", "Power Factor", this->power_factor_sensor_); + LOG_SENSOR(" ", "Frequency", this->frequency_sensor_); + LOG_SENSOR(" ", "Maximum Demand Active Power", this->maximum_demand_active_power_sensor_); + LOG_SENSOR(" ", "Maximum Demand Reactive Power", this->maximum_demand_reactive_power_sensor_); + LOG_SENSOR(" ", "Maximum Demand Apparent Power", this->maximum_demand_apparent_power_sensor_); +} + +} // namespace selec_meter +} // namespace esphome diff --git a/esphome/components/selec_meter/selec_meter.h b/esphome/components/selec_meter/selec_meter.h new file mode 100644 index 0000000000..0477cd2a62 --- /dev/null +++ b/esphome/components/selec_meter/selec_meter.h @@ -0,0 +1,45 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/modbus/modbus.h" + +namespace esphome { +namespace selec_meter { + +#define SELEC_METER_SENSOR(name) \ + protected: \ + sensor::Sensor *name##_sensor_{nullptr}; \ +\ + public: \ + void set_##name##_sensor(sensor::Sensor *(name)) { this->name##_sensor_ = name; } + +class SelecMeter : public PollingComponent, public modbus::ModbusDevice { + public: + SELEC_METER_SENSOR(total_active_energy) + SELEC_METER_SENSOR(import_active_energy) + SELEC_METER_SENSOR(export_active_energy) + SELEC_METER_SENSOR(total_reactive_energy) + SELEC_METER_SENSOR(import_reactive_energy) + SELEC_METER_SENSOR(export_reactive_energy) + SELEC_METER_SENSOR(apparent_energy) + SELEC_METER_SENSOR(active_power) + SELEC_METER_SENSOR(reactive_power) + SELEC_METER_SENSOR(apparent_power) + SELEC_METER_SENSOR(voltage) + SELEC_METER_SENSOR(current) + SELEC_METER_SENSOR(power_factor) + SELEC_METER_SENSOR(frequency) + SELEC_METER_SENSOR(maximum_demand_active_power) + SELEC_METER_SENSOR(maximum_demand_reactive_power) + SELEC_METER_SENSOR(maximum_demand_apparent_power) + + void update() override; + + void on_modbus_data(const std::vector &data) override; + + void dump_config() override; +}; + +} // namespace selec_meter +} // namespace esphome diff --git a/esphome/components/selec_meter/selec_meter_registers.h b/esphome/components/selec_meter/selec_meter_registers.h new file mode 100644 index 0000000000..dfaf65ff08 --- /dev/null +++ b/esphome/components/selec_meter/selec_meter_registers.h @@ -0,0 +1,32 @@ +#pragma once + +namespace esphome { +namespace selec_meter { + +static const float TWO_DEC_UNIT = 0.01; +static const float ONE_DEC_UNIT = 0.1; +static const float NO_DEC_UNIT = 1; +static const float MULTIPLY_TEN_UNIT = 10; +static const float MULTIPLY_THOUSAND_UNIT = 1000; + +/* PHASE STATUS REGISTERS */ +static const uint16_t SELEC_TOTAL_ACTIVE_ENERGY = 0x0000; +static const uint16_t SELEC_IMPORT_ACTIVE_ENERGY = 0x0002; +static const uint16_t SELEC_EXPORT_ACTIVE_ENERGY = 0x0004; +static const uint16_t SELEC_TOTAL_REACTIVE_ENERGY = 0x0006; +static const uint16_t SELEC_IMPORT_REACTIVE_ENERGY = 0x0008; +static const uint16_t SELEC_EXPORT_REACTIVE_ENERGY = 0x000A; +static const uint16_t SELEC_APPARENT_ENERGY = 0x000C; +static const uint16_t SELEC_ACTIVE_POWER = 0x000E; +static const uint16_t SELEC_REACTIVE_POWER = 0x0010; +static const uint16_t SELEC_APPARENT_POWER = 0x0012; +static const uint16_t SELEC_VOLTAGE = 0x0014; +static const uint16_t SELEC_CURRENT = 0x0016; +static const uint16_t SELEC_POWER_FACTOR = 0x0018; +static const uint16_t SELEC_FREQUENCY = 0x001A; +static const uint16_t SELEC_MAXIMUM_DEMAND_ACTIVE_POWER = 0x001C; +static const uint16_t SELEC_MAXIMUM_DEMAND_REACTIVE_POWER = 0x001E; +static const uint16_t SELEC_MAXIMUM_DEMAND_APPARENT_POWER = 0x0020; + +} // namespace selec_meter +} // namespace esphome diff --git a/esphome/components/selec_meter/sensor.py b/esphome/components/selec_meter/sensor.py new file mode 100644 index 0000000000..7eb526aec7 --- /dev/null +++ b/esphome/components/selec_meter/sensor.py @@ -0,0 +1,169 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, modbus +from esphome.const import ( + CONF_ACTIVE_POWER, + CONF_APPARENT_POWER, + CONF_CURRENT, + CONF_EXPORT_ACTIVE_ENERGY, + CONF_EXPORT_REACTIVE_ENERGY, + CONF_FREQUENCY, + CONF_ID, + CONF_IMPORT_ACTIVE_ENERGY, + CONF_IMPORT_REACTIVE_ENERGY, + CONF_POWER_FACTOR, + CONF_REACTIVE_POWER, + CONF_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_POWER_FACTOR, + DEVICE_CLASS_VOLTAGE, + ICON_CURRENT_AC, + ICON_EMPTY, + LAST_RESET_TYPE_AUTO, + STATE_CLASS_MEASUREMENT, + UNIT_AMPERE, + UNIT_EMPTY, + UNIT_HERTZ, + UNIT_VOLT, + UNIT_VOLT_AMPS, + UNIT_VOLT_AMPS_REACTIVE, + UNIT_WATT, +) + +AUTO_LOAD = ["modbus"] +CODEOWNERS = ["@sourabhjaiswal"] + +CONF_TOTAL_ACTIVE_ENERGY = "total_active_energy" +CONF_TOTAL_REACTIVE_ENERGY = "total_reactive_energy" +CONF_APPARENT_ENERGY = "apparent_energy" +CONF_MAXIMUM_DEMAND_ACTIVE_POWER = "maximum_demand_active_power" +CONF_MAXIMUM_DEMAND_REACTIVE_POWER = "maximum_demand_reactive_power" +CONF_MAXIMUM_DEMAND_APPARENT_POWER = "maximum_demand_apparent_power" + +UNIT_KILOWATT_HOURS = "kWh" +UNIT_KILOVOLT_AMPS_HOURS = "kVAh" +UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kVARh" + +selec_meter_ns = cg.esphome_ns.namespace("selec_meter") +SelecMeter = selec_meter_ns.class_( + "SelecMeter", cg.PollingComponent, modbus.ModbusDevice +) + +SENSORS = { + CONF_TOTAL_ACTIVE_ENERGY: sensor.sensor_schema( + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, + ), + CONF_IMPORT_ACTIVE_ENERGY: sensor.sensor_schema( + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, + ), + CONF_EXPORT_ACTIVE_ENERGY: sensor.sensor_schema( + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, + ), + CONF_TOTAL_REACTIVE_ENERGY: sensor.sensor_schema( + UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, + ), + CONF_IMPORT_REACTIVE_ENERGY: sensor.sensor_schema( + UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, + ), + CONF_EXPORT_REACTIVE_ENERGY: sensor.sensor_schema( + UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, + ), + CONF_APPARENT_ENERGY: sensor.sensor_schema( + UNIT_KILOVOLT_AMPS_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, + ), + CONF_ACTIVE_POWER: sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + CONF_REACTIVE_POWER: sensor.sensor_schema( + UNIT_VOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, + ), + CONF_APPARENT_POWER: sensor.sensor_schema( + UNIT_VOLT_AMPS, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + CONF_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + ), + CONF_CURRENT: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 3, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + ), + CONF_POWER_FACTOR: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 3, DEVICE_CLASS_POWER_FACTOR, STATE_CLASS_MEASUREMENT + ), + CONF_FREQUENCY: sensor.sensor_schema( + UNIT_HERTZ, ICON_CURRENT_AC, 2, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + ), + CONF_MAXIMUM_DEMAND_ACTIVE_POWER: sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + CONF_MAXIMUM_DEMAND_REACTIVE_POWER: sensor.sensor_schema( + UNIT_VOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, + ), + CONF_MAXIMUM_DEMAND_APPARENT_POWER: sensor.sensor_schema( + UNIT_VOLT_AMPS, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), +} + +CONFIG_SCHEMA = ( + cv.Schema({cv.GenerateID(): cv.declare_id(SelecMeter)}) + .extend( + {cv.Optional(sensor_name): schema for sensor_name, schema in SENSORS.items()} + ) + .extend(cv.polling_component_schema("10s")) + .extend(modbus.modbus_device_schema(0x01)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await modbus.register_modbus_device(var, config) + for name in SENSORS: + if name in config: + sens = await sensor.new_sensor(config[name]) + cg.add(getattr(var, f"set_{name}_sensor")(sens)) diff --git a/tests/test5.yaml b/tests/test5.yaml index 35225402a3..5d1c99d777 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -18,6 +18,13 @@ ota: logger: +uart: + tx_pin: 1 + rx_pin: 3 + baud_rate: 9600 + +modbus: + binary_sensor: - platform: gpio pin: GPIO0 @@ -55,3 +62,40 @@ number: max_value: 100 min_value: 0 step: 5 + +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" From 80949521b6937d8e82435460a052436b12d71c59 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Thu, 22 Jul 2021 14:37:42 +0200 Subject: [PATCH 1058/1841] Accept change as proposed by black. (#2055) --- esphome/components/external_components/__init__.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index bf44dc1929..3e833d0b66 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -109,9 +109,9 @@ def _compute_destination_path(key: str) -> Path: return base_dir / h.hexdigest()[:8] -def _run_git_command(cmd): +def _run_git_command(cmd, cwd=None): try: - ret = subprocess.run(cmd, capture_output=True, check=False) + ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False) except FileNotFoundError as err: raise cv.Invalid( "git is not installed but required for external_components.\n" @@ -151,14 +151,16 @@ def _process_git_config(config: dict, refresh) -> str: _LOGGER.info("Updating %s", key) _LOGGER.debug("Location: %s", repo_dir) # Stash local changes (if any) - _run_git_command(["git", "stash", "push", "--include-untracked"]) + _run_git_command( + ["git", "stash", "push", "--include-untracked"], str(repo_dir) + ) # Fetch remote ref cmd = ["git", "fetch", "--", "origin"] if CONF_REF in config: cmd.append(config[CONF_REF]) - _run_git_command(cmd) + _run_git_command(cmd, str(repo_dir)) # Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch) - _run_git_command(["git", "reset", "--hard", "FETCH_HEAD"]) + _run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir)) if (repo_dir / "esphome" / "components").is_dir(): components_dir = repo_dir / "esphome" / "components" From ba461e51a83e1d589d23b246ccdd136369d9a1dc Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Thu, 22 Jul 2021 16:39:21 +0400 Subject: [PATCH 1059/1841] midea_ac: fix presets implementation (#2054) --- esphome/components/midea_ac/midea_climate.cpp | 4 ++-- esphome/components/midea_ac/midea_frame.cpp | 22 ++++++++++++------- esphome/components/midea_ac/midea_frame.h | 1 + 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/esphome/components/midea_ac/midea_climate.cpp b/esphome/components/midea_ac/midea_climate.cpp index 9fe5df7de3..72f7d23404 100644 --- a/esphome/components/midea_ac/midea_climate.cpp +++ b/esphome/components/midea_ac/midea_climate.cpp @@ -100,7 +100,7 @@ bool MideaAC::allow_preset(climate::ClimatePreset preset) const { ESP_LOGD(TAG, "BOOST preset is only available in HEAT or COOL mode"); } break; - case climate::CLIMATE_PRESET_HOME: + case climate::CLIMATE_PRESET_NONE: return true; default: break; @@ -191,7 +191,7 @@ climate::ClimateTraits MideaAC::traits() { if (traits_swing_both_) traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH); traits.set_supported_presets({ - climate::CLIMATE_PRESET_HOME, + climate::CLIMATE_PRESET_NONE, }); if (traits_preset_eco_) traits.add_supported_preset(climate::CLIMATE_PRESET_ECO); diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp index 5f09f4314f..c0a5ce4b55 100644 --- a/esphome/components/midea_ac/midea_frame.cpp +++ b/esphome/components/midea_ac/midea_frame.cpp @@ -86,18 +86,17 @@ void PropertiesFrame::set_mode(climate::ClimateMode mode) { } optional PropertiesFrame::get_preset() const { - if (this->get_eco_mode()) { + if (this->get_eco_mode()) return climate::CLIMATE_PRESET_ECO; - } else if (this->get_sleep_mode()) { + if (this->get_sleep_mode()) return climate::CLIMATE_PRESET_SLEEP; - } else if (this->get_turbo_mode()) { + if (this->get_turbo_mode()) return climate::CLIMATE_PRESET_BOOST; - } else { - return climate::CLIMATE_PRESET_HOME; - } + return climate::CLIMATE_PRESET_NONE; } void PropertiesFrame::set_preset(climate::ClimatePreset preset) { + this->clear_presets(); switch (preset) { case climate::CLIMATE_PRESET_ECO: this->set_eco_mode(true); @@ -113,14 +112,21 @@ void PropertiesFrame::set_preset(climate::ClimatePreset preset) { } } +void PropertiesFrame::clear_presets() { + this->set_eco_mode(false); + this->set_sleep_mode(false); + this->set_turbo_mode(false); + this->set_freeze_protection_mode(false); +} + bool PropertiesFrame::is_custom_preset() const { return this->get_freeze_protection_mode(); } const std::string &PropertiesFrame::get_custom_preset() const { return midea_ac::MIDEA_FREEZE_PROTECTION_PRESET; }; void PropertiesFrame::set_custom_preset(const std::string &preset) { - if (preset == MIDEA_FREEZE_PROTECTION_PRESET) { + this->clear_presets(); + if (preset == MIDEA_FREEZE_PROTECTION_PRESET) this->set_freeze_protection_mode(true); - } } bool PropertiesFrame::is_custom_fan_mode() const { diff --git a/esphome/components/midea_ac/midea_frame.h b/esphome/components/midea_ac/midea_frame.h index 3777f6fd77..e1d6fed49d 100644 --- a/esphome/components/midea_ac/midea_frame.h +++ b/esphome/components/midea_ac/midea_frame.h @@ -115,6 +115,7 @@ class PropertiesFrame : public midea_dongle::BaseFrame { /* PRESET */ optional get_preset() const; void set_preset(climate::ClimatePreset preset); + void clear_presets(); bool is_custom_preset() const; const std::string &get_custom_preset() const; From 03e317d052e2f3fe7cb6114956218f2d6978b48e Mon Sep 17 00:00:00 2001 From: carstenschroeder Date: Thu, 22 Jul 2021 14:39:57 +0200 Subject: [PATCH 1060/1841] Fixes new auto mode COOL and HEAT after #1994 (#2053) --- esphome/components/pid/pid_climate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index dac4426698..ef8a4df962 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -35,8 +35,8 @@ void PIDClimate::control(const climate::ClimateCall &call) { if (call.get_target_temperature().has_value()) this->target_temperature = *call.get_target_temperature(); - // If switching to non-auto mode, set output immediately - if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) + // If switching to off mode, set output immediately + if (this->mode == climate::CLIMATE_MODE_OFF) this->handle_non_auto_mode_(); this->publish_state(); From f0d9ad6a4e35c8cf3abac8fbc9f4875cb1859a3d Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 23 Jul 2021 17:53:59 +0200 Subject: [PATCH 1061/1841] Add TAG to all compile units (#2060) When using static TAG is only valid in the current compile unit. For some reason it seems that the current ESP8266/ESP32 compiler use the instance from ble.cpp, but it seems that this causes issues with newer compiler leading to compile time errors like this: In file included from /root/.platformio/packages/framework-arduinoespressif32/cores/esp32/esp32-hal-log.h:164, from /root/.platformio/packages/framework-arduinoespressif32/cores/esp32/esp32-hal.h:71, from /root/.platformio/packages/framework-arduinoespressif32/cores/esp32/Arduino.h:36, from src/esphome/core/esphal.h:3, from src/esphome/core/helpers.h:10, from src/esphome/components/esp32_ble/ble_uuid.h:3, from src/esphome/components/esp32_ble/ble_advertising.cpp:5: src/esphome/components/esp32_ble/ble_advertising.cpp: In member function 'void esphome::esp32_ble::BLEAdvertising::start()': src/esphome/components/esp32_ble/ble_advertising.cpp:64:14: error: 'TAG' was not declared in this scope ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Advertising): %d", err); ^~~ --- esphome/components/esp32_ble/ble_advertising.cpp | 2 ++ esphome/components/esp32_ble/ble_uuid.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/esphome/components/esp32_ble/ble_advertising.cpp b/esphome/components/esp32_ble/ble_advertising.cpp index f215fb48a3..92270124dd 100644 --- a/esphome/components/esp32_ble/ble_advertising.cpp +++ b/esphome/components/esp32_ble/ble_advertising.cpp @@ -7,6 +7,8 @@ namespace esphome { namespace esp32_ble { +static const char *const TAG = "esp32_ble"; + BLEAdvertising::BLEAdvertising() { this->advertising_data_.set_scan_rsp = false; this->advertising_data_.include_name = true; diff --git a/esphome/components/esp32_ble/ble_uuid.cpp b/esphome/components/esp32_ble/ble_uuid.cpp index cb0b99c62b..0de938d9a8 100644 --- a/esphome/components/esp32_ble/ble_uuid.cpp +++ b/esphome/components/esp32_ble/ble_uuid.cpp @@ -5,6 +5,8 @@ namespace esphome { namespace esp32_ble { +static const char *const TAG = "esp32_ble"; + ESPBTUUID::ESPBTUUID() : uuid_() {} ESPBTUUID ESPBTUUID::from_uint16(uint16_t uuid) { ESPBTUUID ret; From 66cdb761dc26d3c76fd54705aefc1ef7212e38d7 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sat, 24 Jul 2021 11:55:25 +0200 Subject: [PATCH 1062/1841] Fix minor build issues with Arduino ESP32 2.0.0-rc1 (#2057) --- esphome/components/uart/uart.h | 1 + esphome/components/uart/uart_esp32.cpp | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index cd54290c73..1dc1e18412 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "esphome/core/esphal.h" #include "esphome/core/component.h" diff --git a/esphome/components/uart/uart_esp32.cpp b/esphome/components/uart/uart_esp32.cpp index 89de4c0cc1..16d683e4a6 100644 --- a/esphome/components/uart/uart_esp32.cpp +++ b/esphome/components/uart/uart_esp32.cpp @@ -12,7 +12,7 @@ uint8_t next_uart_num = 1; static const uint32_t UART_PARITY_EVEN = 0 << 0; static const uint32_t UART_PARITY_ODD = 1 << 0; -static const uint32_t UART_PARITY_EN = 1 << 1; +static const uint32_t UART_PARITY_ENABLE = 1 << 1; static const uint32_t UART_NB_BIT_5 = 0 << 2; static const uint32_t UART_NB_BIT_6 = 1 << 2; static const uint32_t UART_NB_BIT_7 = 2 << 2; @@ -39,9 +39,9 @@ uint32_t UARTComponent::get_config() { */ if (this->parity_ == UART_CONFIG_PARITY_EVEN) - config |= UART_PARITY_EVEN | UART_PARITY_EN; + config |= UART_PARITY_EVEN | UART_PARITY_ENABLE; else if (this->parity_ == UART_CONFIG_PARITY_ODD) - config |= UART_PARITY_ODD | UART_PARITY_EN; + config |= UART_PARITY_ODD | UART_PARITY_ENABLE; switch (this->data_bits_) { case 5: From 3749c11f21ca5648b1cc17309c2b0b6459cfd120 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 25 Jul 2021 23:54:32 +0200 Subject: [PATCH 1063/1841] Fix clang-format script behaviour without -i + code cleanup (#2002) Co-authored-by: Stefan Agner --- .github/workflows/ci.yml | 9 +++-- script/clang-format | 75 ++++++++++++++++------------------------ script/clang-tidy | 46 +++++++++--------------- script/helpers.py | 4 ++- 4 files changed, 57 insertions(+), 77 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0be9be903..433c7fb872 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,16 +46,21 @@ jobs: echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" echo "::add-matcher::.github/workflows/matchers/gcc.json" + # Also run git-diff-index so that the step is marked as failed on formatting errors, + # since clang-format doesn't do anything but change files if -i is passed. - name: Run clang-format - run: script/clang-format -i + run: | + script/clang-format -i + git diff-index --quiet HEAD -- if: ${{ matrix.id == 'clang-format' }} - name: Run clang-tidy run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }} if: ${{ matrix.id == 'clang-tidy' }} - - name: Suggest changes + - name: Suggested changes run: script/ci-suggest-changes + if: always() ci: # Don't use the esphome-lint docker image because it may contain outdated requirements. diff --git a/script/clang-format b/script/clang-format index bb2b722e1c..d6588f1ccb 100755 --- a/script/clang-format +++ b/script/clang-format @@ -1,10 +1,9 @@ #!/usr/bin/env python3 -from __future__ import print_function - import argparse import multiprocessing import os +import queue import re import subprocess import sys @@ -13,59 +12,47 @@ import threading import click sys.path.append(os.path.dirname(__file__)) -from helpers import basepath, get_output, git_ls_files, filter_changed - -is_py2 = sys.version[0] == '2' - -if is_py2: - import Queue as queue -else: - import queue as queue - -root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, '..', '..'))) -basepath = os.path.join(root_path, 'esphome') -rel_basepath = os.path.relpath(basepath, os.getcwd()) +from helpers import get_output, git_ls_files, filter_changed -def run_format(args, queue, lock): - """Takes filenames out of queue and runs clang-tidy on them.""" +def run_format(args, queue, lock, failed_files): + """Takes filenames out of queue and runs clang-format on them.""" while True: path = queue.get() invocation = ['clang-format-11'] if args.inplace: invocation.append('-i') + else: + invocation.extend(['--dry-run', '-Werror']) invocation.append(path) - proc = subprocess.Popen(invocation, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - output, err = proc.communicate() - with lock: - if proc.returncode != 0: - print(' '.join(invocation)) - print(output.decode('utf-8')) - print(err.decode('utf-8')) + proc = subprocess.run(invocation, capture_output=True, encoding='utf-8') + if proc.returncode != 0: + with lock: + print() + print("\033[0;32m************* File \033[1;32m{}\033[0m".format(path)) + print(proc.stdout) + print(proc.stderr) + print() + failed_files.append(path) queue.task_done() def progress_bar_show(value): - if value is None: - return '' - return value + return value if value is not None else '' def main(): parser = argparse.ArgumentParser() parser.add_argument('-j', '--jobs', type=int, default=multiprocessing.cpu_count(), - help='number of tidy instances to be run in parallel.') + help='number of format instances to be run in parallel.') parser.add_argument('files', nargs='*', default=[], help='files to be processed (regex on path)') parser.add_argument('-i', '--inplace', action='store_true', - help='apply fix-its') - parser.add_argument('-q', '--quiet', action='store_false', - help='Run clang-tidy in quiet mode') + help='reformat files in-place') parser.add_argument('-c', '--changed', action='store_true', - help='Only run on changed files') + help='only run on changed files') args = parser.parse_args() try: @@ -75,7 +62,7 @@ def main(): Oops. It looks like clang-format is not installed. Please check you can run "clang-format-11 -version" in your terminal and install - clang-format (v7) if necessary. + clang-format (v11) if necessary. Note you can also upload your code as a pull request on GitHub and see the CI check output to apply clang-format. @@ -83,28 +70,26 @@ def main(): return 1 files = [] - for path in git_ls_files(): - filetypes = ('.cpp', '.h', '.tcc') - ext = os.path.splitext(path)[1] - if ext in filetypes: - path = os.path.relpath(path, os.getcwd()) - files.append(path) - # Match against re - file_name_re = re.compile('|'.join(args.files)) - files = [p for p in files if file_name_re.search(p)] + for path in git_ls_files(['*.cpp', '*.h', '*.tcc']): + files.append(os.path.relpath(path, os.getcwd())) + + if args.files: + # Match against files specified on command-line + file_name_re = re.compile('|'.join(args.files)) + files = [p for p in files if file_name_re.search(p)] if args.changed: files = filter_changed(files) files.sort() - return_code = 0 + failed_files = [] try: task_queue = queue.Queue(args.jobs) lock = threading.Lock() for _ in range(args.jobs): t = threading.Thread(target=run_format, - args=(args, task_queue, lock)) + args=(args, task_queue, lock, failed_files)) t.daemon = True t.start() @@ -122,7 +107,7 @@ def main(): print('Ctrl-C detected, goodbye.') os.kill(0, 9) - sys.exit(return_code) + sys.exit(len(failed_files)) if __name__ == '__main__': diff --git a/script/clang-tidy b/script/clang-tidy index 0bf17f9076..11b9818016 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -1,10 +1,9 @@ #!/usr/bin/env python3 -from __future__ import print_function - import argparse import multiprocessing import os +import queue import re import shutil import subprocess @@ -19,13 +18,6 @@ sys.path.append(os.path.dirname(__file__)) from helpers import basepath, shlex_quote, get_output, build_compile_commands, \ build_all_include, temp_header_file, git_ls_files, filter_changed -is_py2 = sys.version[0] == '2' - -if is_py2: - import Queue as queue -else: - import queue as queue - def run_tidy(args, tmpdir, queue, lock, failed_files): while True: @@ -49,8 +41,8 @@ def run_tidy(args, tmpdir, queue, lock, failed_files): # Use pexpect for a pseudy-TTY with colored output output, rc = pexpect.run(invocation_s, withexitstatus=True, encoding='utf-8', timeout=15 * 60) - with lock: - if rc != 0: + if rc != 0: + with lock: print() print("\033[0;32m************* File \033[1;32m{}\033[0m".format(path)) print(output) @@ -78,15 +70,15 @@ def main(): help='files to be processed (regex on path)') parser.add_argument('--fix', action='store_true', help='apply fix-its') parser.add_argument('-q', '--quiet', action='store_false', - help='Run clang-tidy in quiet mode') + help='run clang-tidy in quiet mode') parser.add_argument('-c', '--changed', action='store_true', - help='Only run on changed files') - parser.add_argument('--split-num', type=int, help='Split the files into X jobs.', + help='only run on changed files') + parser.add_argument('--split-num', type=int, help='split the files into X jobs.', default=None) - parser.add_argument('--split-at', type=int, help='Which split is this? Starts at 1', + parser.add_argument('--split-at', type=int, help='which split is this? starts at 1', default=None) parser.add_argument('--all-headers', action='store_true', - help='Create a dummy file that checks all headers') + help='create a dummy file that checks all headers') args = parser.parse_args() try: @@ -107,15 +99,13 @@ def main(): build_compile_commands() files = [] - for path in git_ls_files(): - filetypes = ('.cpp',) - ext = os.path.splitext(path)[1] - if ext in filetypes: - path = os.path.relpath(path, os.getcwd()) - files.append(path) - # Match against re - file_name_re = re.compile('|'.join(args.files)) - files = [p for p in files if file_name_re.search(p)] + for path in git_ls_files(['*.cpp']): + files.append(os.path.relpath(path, os.getcwd())) + + if args.files: + # Match against files specified on command-line + file_name_re = re.compile('|'.join(args.files)) + files = [p for p in files if file_name_re.search(p)] if args.changed: files = filter_changed(files) @@ -133,7 +123,6 @@ def main(): tmpdir = tempfile.mkdtemp() failed_files = [] - return_code = 0 try: task_queue = queue.Queue(args.jobs) lock = threading.Lock() @@ -151,7 +140,6 @@ def main(): # Wait for all threads to be done. task_queue.join() - return_code = len(failed_files) except KeyboardInterrupt: print() @@ -168,8 +156,8 @@ def main(): print('Error applying fixes.\n', file=sys.stderr) raise - return return_code + sys.exit(len(failed_files)) if __name__ == '__main__': - sys.exit(main()) + main() diff --git a/script/helpers.py b/script/helpers.py index 1a4402aa1d..e0ec2d169e 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -145,8 +145,10 @@ def filter_changed(files): return files -def git_ls_files(): +def git_ls_files(patterns=None): command = ["git", "ls-files", "-s"] + if patterns is not None: + command.extend(patterns) proc = subprocess.Popen(command, stdout=subprocess.PIPE) output, err = proc.communicate() lines = [x.split() for x in output.decode("utf-8").splitlines()] From 1f5cbca5092c716c9f1dfa2d3a314515e8705537 Mon Sep 17 00:00:00 2001 From: Trevor North Date: Mon, 26 Jul 2021 07:59:18 +0100 Subject: [PATCH 1064/1841] Merge build flags from platformio_options (#1651) --- esphome/writer.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/esphome/writer.py b/esphome/writer.py index 57698f8c25..572e976025 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -72,9 +72,7 @@ upload_flags = """, ) -UPLOAD_SPEED_OVERRIDE = { - "esp210": 57600, -} +UPLOAD_SPEED_OVERRIDE = {"esp210": 57600} def get_flags(key): @@ -210,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 = """\ @@ -228,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, @@ -275,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) From 1dd43a75f222507ddaeea343732484e0cd328003 Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Mon, 26 Jul 2021 17:20:02 +1000 Subject: [PATCH 1065/1841] Correctly invert esp32 RMT TX (#2022) --- esphome/components/remote_transmitter/remote_transmitter.h | 1 + .../remote_transmitter/remote_transmitter_esp32.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index 000fbabfee..853b5b6289 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -41,6 +41,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, bool initialized_{false}; std::vector rmt_temp_; esp_err_t error_code_{ESP_OK}; + bool inverted_{false}; #endif uint8_t carrier_duty_percent_{50}; }; diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index 3d3e26160a..7b366fa52b 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -50,6 +50,7 @@ void RemoteTransmitterComponent::configure_rmt() { } else { c.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; c.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH; + this->inverted_ = true; } esp_err_t error = rmt_config(&c); @@ -95,10 +96,10 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen val -= item; if (rmt_i % 2 == 0) { - rmt_item.level0 = static_cast(level); + rmt_item.level0 = static_cast(level ^ this->inverted_); rmt_item.duration0 = static_cast(item); } else { - rmt_item.level1 = static_cast(level); + rmt_item.level1 = static_cast(level ^ this->inverted_); rmt_item.duration1 = static_cast(item); this->rmt_temp_.push_back(rmt_item); } From a34d5e3901bba6ffc0a499368b8040b907669434 Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Mon, 26 Jul 2021 17:32:08 +1000 Subject: [PATCH 1066/1841] Move configure_rmt() into setup() (#2028) --- .../components/remote_transmitter/remote_transmitter_esp32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index 7b366fa52b..90166d2741 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -9,7 +9,7 @@ namespace remote_transmitter { static const char *const TAG = "remote_transmitter"; -void RemoteTransmitterComponent::setup() {} +void RemoteTransmitterComponent::setup() { this->configure_rmt(); } void RemoteTransmitterComponent::dump_config() { ESP_LOGCONFIG(TAG, "Remote Transmitter..."); From 237edd75d18c2465da9117148855a3efc24851c8 Mon Sep 17 00:00:00 2001 From: buxtronix Date: Mon, 26 Jul 2021 18:41:54 +1000 Subject: [PATCH 1067/1841] Log warning about lack of support for Anova nano (#2063) Co-authored-by: Ben Buxton --- esphome/components/anova/anova.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index c4b08ca6b5..63e0710936 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -60,6 +60,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ auto chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID); if (chr == nullptr) { ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str()); + ESP_LOGW(TAG, "[%s] Note, this component does not currently support Anova Nano.", this->get_name().c_str()); break; } this->char_handle_ = chr->handle; From c2637a76f7264f8876ea19b02b0a93df70aebe39 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 26 Jul 2021 10:49:38 +0200 Subject: [PATCH 1068/1841] Print BLE 128-bit UUIDs according to spec (#2061) Since the iterator integer counts the bytes backwards we need to use the complement to 15. --- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 2a3403f88d..b3db651655 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -372,7 +372,7 @@ std::string ESPBTUUID::to_string() { for (int8_t i = 15; i >= 0; i--) { sprintf(bpos, "%02X", this->uuid_.uuid.uuid128[i]); bpos += 2; - if (i == 3 || i == 5 || i == 7 || i == 9) + if (i == 6 || i == 8 || i == 10 || i == 12) sprintf(bpos++, "-"); } sbuf[47] = '\0'; From 159744e09ea3eac9e7bb4893089e35c86efb31cd Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 26 Jul 2021 10:50:45 +0200 Subject: [PATCH 1069/1841] Support library override using named library with repository (#2056) --- esphome/core/__init__.py | 30 +++++++++++++++++++++++++++--- esphome/core/config.py | 8 ++++++++ esphome/cpp_generator.py | 4 ++-- tests/unit_tests/test_core.py | 23 ++++++++++++++++------- 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index df98e1b150..743e6938d7 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -408,19 +408,28 @@ class Define: class Library: - def __init__(self, name, version): + def __init__(self, name, version, repository=None): self.name = name self.version = version + self.repository = repository + + def __str__(self): + return self.as_lib_dep @property def as_lib_dep(self): + if self.repository is not None: + if self.name is not None: + return f"{self.name}={self.repository}" + return self.repository + if self.version is None: return self.name return f"{self.name}@{self.version}" @property def as_tuple(self): - return self.name, self.version + return self.name, self.version, self.repository def __hash__(self): return hash(self.as_tuple) @@ -632,10 +641,24 @@ class EsphomeCore: "Library {} must be instance of Library, not {}" "".format(library, type(library)) ) - _LOGGER.debug("Adding library: %s", library) for other in self.libraries[:]: if other.name != library.name: continue + if other.repository is not None: + if library.repository is None or other.repository == library.repository: + # Other is using a/the same repository, takes precendence + break + raise ValueError( + "Adding named Library with repository failed! Libraries {} and {} " + "requested with conflicting repositories!" + "".format(library, other) + ) + + if library.repository is not None: + # This is more specific since its using a repository + self.libraries.remove(other) + continue + if library.version is None: # Other requirement is more specific break @@ -652,6 +675,7 @@ class EsphomeCore: "".format(library, other) ) else: + _LOGGER.debug("Adding library: %s", library) self.libraries.append(library) return library diff --git a/esphome/core/config.py b/esphome/core/config.py index 9475225f4d..eb748d89ac 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -332,6 +332,14 @@ async def to_code(config): if "@" in lib: name, vers = lib.split("@", 1) cg.add_library(name, vers) + elif "://" in lib: + # Repository... + if "=" in lib: + name, repo = lib.split("=", 1) + cg.add_library(name, None, repo) + else: + cg.add_library(None, None, lib) + else: cg.add_library(lib, None) diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 802e9a9d38..eda378e5eb 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -543,13 +543,13 @@ def add_global(expression: Union[SafeExpType, Statement]): CORE.add_global(expression) -def add_library(name: str, version: Optional[str]): +def add_library(name: str, version: Optional[str], repository: Optional[str] = None): """Add a library to the codegen library storage. :param name: The name of the library (for example 'AsyncTCP') :param version: The version of the library, may be None. """ - CORE.add_library(Library(name, version)) + CORE.add_library(Library(name, version, repository)) def add_build_flag(build_flag: str): diff --git a/tests/unit_tests/test_core.py b/tests/unit_tests/test_core.py index 4e60880033..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), From d9f09a7523e4a31b7498fdd63283987f42aff090 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 26 Jul 2021 11:10:56 +0200 Subject: [PATCH 1070/1841] Initial ESP32-C3-DevKitM-1 board support (#2062) Co-authored-by: Stijn Tintel --- esphome/boards.py | 875 +++++++++++++++++++++++++ esphome/components/adc/adc_sensor.cpp | 38 ++ esphome/core/__init__.py | 11 + esphome/core/config.py | 13 +- esphome/pins.py | 895 +------------------------- esphome/platformio_api.py | 5 + esphome/wizard.py | 2 +- esphome/writer.py | 2 +- tests/unit_tests/test_pins.py | 28 +- tests/unit_tests/test_wizard.py | 2 +- 10 files changed, 984 insertions(+), 887 deletions(-) create mode 100644 esphome/boards.py diff --git a/esphome/boards.py b/esphome/boards.py new file mode 100644 index 0000000000..220d440a37 --- /dev/null +++ b/esphome/boards.py @@ -0,0 +1,875 @@ +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}, +} + +ESP32_C3_BASE_PINS = { + "TX": 21, + "RX": 20, + "ADC1_0": 0, + "ADC1_1": 1, + "ADC1_2": 2, + "ADC1_3": 3, + "ADC1_4": 4, + "ADC2_0": 5, +} + +ESP32_C3_BOARD_PINS = { + "esp32-c3-devkitm-1": {"LED": 8}, + "esp32-c3-devkitc-02": "esp32-c3-devkitm-1", +} diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index d6469ab785..78f12a6b9e 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -14,6 +14,7 @@ static const char *const TAG = "adc"; void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } inline adc1_channel_t gpio_to_adc1(uint8_t pin) { +#if CONFIG_IDF_TARGET_ESP32 switch (pin) { case 36: return ADC1_CHANNEL_0; @@ -34,6 +35,22 @@ inline adc1_channel_t gpio_to_adc1(uint8_t pin) { default: return ADC1_CHANNEL_MAX; } +#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 + switch (pin) { + case 0: + return ADC1_CHANNEL_0; + case 1: + return ADC1_CHANNEL_1; + case 2: + return ADC1_CHANNEL_2; + case 3: + return ADC1_CHANNEL_3; + case 4: + return ADC1_CHANNEL_4; + default: + return ADC1_CHANNEL_MAX; + } +#endif } #endif @@ -46,8 +63,10 @@ void ADCSensor::setup() { #ifdef ARDUINO_ARCH_ESP32 adc1_config_channel_atten(gpio_to_adc1(pin_), attenuation_); adc1_config_width(ADC_WIDTH_BIT_12); +#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_)); #endif +#endif } void ADCSensor::dump_config() { LOG_SENSOR("", "ADC Sensor", this); @@ -89,6 +108,7 @@ float ADCSensor::sample() { #ifdef ARDUINO_ARCH_ESP32 int raw = adc1_get_raw(gpio_to_adc1(pin_)); float value_v = raw / 4095.0f; +#if CONFIG_IDF_TARGET_ESP32 switch (this->attenuation_) { case ADC_ATTEN_DB_0: value_v *= 1.1; @@ -105,6 +125,24 @@ float ADCSensor::sample() { default: // This is to satisfy the unused ADC_ATTEN_MAX break; } +#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 + switch (this->attenuation_) { + case ADC_ATTEN_DB_0: + value_v *= 0.84; + break; + case ADC_ATTEN_DB_2_5: + value_v *= 1.13; + break; + case ADC_ATTEN_DB_6: + value_v *= 1.56; + break; + case ADC_ATTEN_DB_11: + value_v *= 3.0; + break; + default: // This is to satisfy the unused ADC_ATTEN_MAX + break; + } +#endif return value_v; #endif diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 743e6938d7..c61b288d09 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -20,6 +20,7 @@ from esphome.coroutine import FakeEventLoop as _FakeEventLoop from esphome.coroutine import coroutine, coroutine_with_priority # noqa from esphome.helpers import ensure_unique_string, is_hassio from esphome.util import OrderedDict +from esphome import boards if TYPE_CHECKING: from ..cpp_generator import MockObj, MockObjClass, Statement @@ -593,10 +594,20 @@ class EsphomeCore: @property def is_esp32(self): + """Check if the ESP32 platform is used. + + This checks if the ESP32 platform is in use, which + support ESP32 as well as other chips such as ESP32-C3 + """ if self.esp_platform is None: raise ValueError("No platform specified") return self.esp_platform == "ESP32" + @property + def is_esp32_c3(self): + """Check if the ESP32-C3 SoC is being used.""" + return self.is_esp32 and self.board in boards.ESP32_C3_BOARD_PINS + def add_job(self, func, *args, **kwargs): self.event_loop.add_job(func, *args, **kwargs) diff --git a/esphome/core/config.py b/esphome/core/config.py index eb748d89ac..23e6e34625 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -4,7 +4,7 @@ import re import esphome.codegen as cg import esphome.config_validation as cv -from esphome import automation, pins +from esphome import automation, boards from esphome.const import ( CONF_ARDUINO_VERSION, CONF_BOARD, @@ -50,18 +50,19 @@ VERSION_REGEX = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:[ab]\d+)?$") CONF_NAME_ADD_MAC_SUFFIX = "name_add_mac_suffix" -def validate_board(value): +def validate_board(value: str): if CORE.is_esp8266: - board_pins = pins.ESP8266_BOARD_PINS + boardlist = boards.ESP8266_BOARD_PINS.keys() elif CORE.is_esp32: - board_pins = pins.ESP32_BOARD_PINS + boardlist = list(boards.ESP32_BOARD_PINS.keys()) + boardlist += list(boards.ESP32_C3_BOARD_PINS.keys()) else: raise NotImplementedError - if value not in board_pins: + if value not in boardlist: raise cv.Invalid( "Could not find board '{}'. Valid boards are {}".format( - value, ", ".join(sorted(board_pins.keys())) + value, ", ".join(sorted(boardlist)) ) ) return value diff --git a/esphome/pins.py b/esphome/pins.py index 6356ae9bd0..5eef60e15d 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.") 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/wizard.py b/esphome/wizard.py index 0d912e4bbf..3f989dd93d 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -10,7 +10,7 @@ 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 diff --git a/esphome/writer.py b/esphome/writer.py index 572e976025..641ae9b3cc 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -25,7 +25,7 @@ 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__) 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 0ca7c83e1b..56bd5119b5 100644 --- a/tests/unit_tests/test_wizard.py +++ b/tests/unit_tests/test_wizard.py @@ -2,7 +2,7 @@ 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 From 6b535b11f8eae61713dd279e8d7934ad98137020 Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Mon, 26 Jul 2021 04:39:03 -0500 Subject: [PATCH 1071/1841] Couple more updates for the Tuya component (#2065) Co-authored-by: Chris Nussbaum --- esphome/components/tuya/tuya.cpp | 40 ++++++++++++++++++++++++++++---- esphome/components/tuya/tuya.h | 1 + 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index d9a0a9932a..916a550675 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -8,9 +8,10 @@ namespace tuya { static const char *const TAG = "tuya"; static const int COMMAND_DELAY = 50; +static const int RECEIVE_TIMEOUT = 300; void Tuya::setup() { - this->set_interval("heartbeat", 10000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); + this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); } void Tuya::loop() { @@ -117,7 +118,13 @@ void Tuya::handle_char_(uint8_t c) { } void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len) { - switch ((TuyaCommandType) command) { + TuyaCommandType command_type = (TuyaCommandType) command; + + if (this->expected_response_.has_value() && this->expected_response_ == command_type) { + this->expected_response_.reset(); + } + + switch (command_type) { case TuyaCommandType::HEARTBEAT: ESP_LOGV(TAG, "MCU Heartbeat (0x%02X)", buffer[0]); this->protocol_version_ = version; @@ -316,6 +323,25 @@ void Tuya::send_raw_command_(TuyaCommand command) { uint8_t version = 0; this->last_command_timestamp_ = millis(); + switch (command.cmd) { + case TuyaCommandType::HEARTBEAT: + this->expected_response_ = TuyaCommandType::HEARTBEAT; + break; + case TuyaCommandType::PRODUCT_QUERY: + this->expected_response_ = TuyaCommandType::PRODUCT_QUERY; + break; + case TuyaCommandType::CONF_QUERY: + this->expected_response_ = TuyaCommandType::CONF_QUERY; + break; + case TuyaCommandType::DATAPOINT_DELIVER: + this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT; + break; + case TuyaCommandType::DATAPOINT_QUERY: + this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT; + break; + default: + break; + } ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast(command.cmd), version, hexencode(command.payload).c_str(), static_cast(this->init_state_)); @@ -332,8 +358,14 @@ void Tuya::send_raw_command_(TuyaCommand command) { void Tuya::process_command_queue_() { uint32_t delay = millis() - this->last_command_timestamp_; + + if (this->expected_response_.has_value() && delay > RECEIVE_TIMEOUT) { + this->expected_response_.reset(); + } + // Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly - if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty()) { + if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty() && + !this->expected_response_.has_value()) { this->send_raw_command_(command_queue_.front()); this->command_queue_.erase(command_queue_.begin()); } @@ -345,7 +377,7 @@ void Tuya::send_command_(const TuyaCommand &command) { } void Tuya::send_empty_command_(TuyaCommandType command) { - send_command_(TuyaCommand{.cmd = command, .payload = std::vector{0x04}}); + send_command_(TuyaCommand{.cmd = command, .payload = std::vector{}}); } void Tuya::send_wifi_status_() { diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 4ca4f56366..68decf7e9e 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -113,6 +113,7 @@ class Tuya : public Component, public uart::UARTDevice { std::vector rx_message_; std::vector ignore_mcu_update_on_datapoints_{}; std::vector command_queue_; + optional expected_response_{}; uint8_t wifi_status_ = -1; }; From a3dcac62f92cea8d9c23d92ab99dfe40ac35f06d Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 26 Jul 2021 14:48:57 +0200 Subject: [PATCH 1072/1841] Fix a bunch of typos (#2058) Co-authored-by: Stefan Agner Co-authored-by: Otto Winter Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/ac_dimmer/ac_dimmer.cpp | 2 +- esphome/components/aht10/aht10.cpp | 8 ++--- esphome/components/atm90e32/atm90e32_reg.h | 2 +- esphome/components/b_parasite/b_parasite.cpp | 2 +- .../components/dallas/dallas_component.cpp | 2 +- esphome/components/dht/dht.cpp | 2 +- esphome/components/e131/e131.cpp | 2 +- esphome/components/esp32_ble/queue.h | 2 +- esphome/components/esp32_ble_tracker/queue.h | 2 +- .../ethernet/ethernet_component.cpp | 2 +- .../fujitsu_general/fujitsu_general.h | 2 +- .../inkbird_ibsth1_mini.cpp | 8 ++--- .../components/max7219digit/max7219digit.cpp | 6 ++-- esphome/components/nextion/nextion.cpp | 2 +- esphome/components/nextion/nextion.h | 8 ++--- esphome/components/nextion/nextion_upload.cpp | 2 +- esphome/components/qmc5883l/qmc5883l.cpp | 2 +- esphome/components/rc522/rc522.cpp | 4 +-- esphome/components/rc522/rc522.h | 2 +- .../components/remote_base/remote_base.cpp | 2 +- esphome/components/scd30/scd30.cpp | 2 +- esphome/components/script/__init__.py | 2 +- esphome/components/script/script.h | 2 +- .../components/ssd1331_base/ssd1331_base.cpp | 2 +- esphome/components/st7789v/st7789v.h | 32 +++++++++---------- esphome/components/sx1509/sx1509_registers.h | 2 +- esphome/components/tuya/tuya.cpp | 4 +-- esphome/components/vl53l0x/LICENSE.txt | 2 +- .../waveshare_epaper/waveshare_epaper.cpp | 2 +- esphome/components/xiaomi_ble/xiaomi_ble.cpp | 4 +-- .../xiaomi_miscale/xiaomi_miscale.cpp | 2 +- esphome/core/component.h | 2 +- script/build_jsonschema.py | 4 +-- 33 files changed, 63 insertions(+), 63 deletions(-) diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index ad6018268e..7e9251ddf3 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -125,7 +125,7 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() { } void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) { - // Attaching pin interrupts on the same pin will override the previous interupt + // Attaching pin interrupts on the same pin will override the previous interrupt // However, the user expects that multiple dimmers sharing the same ZC pin will work. // We solve this in a bit of a hacky way: On each pin interrupt, we check all dimmers // if any of them are using the same ZC pin, and also trigger the interrupt for *them*. diff --git a/esphome/components/aht10/aht10.cpp b/esphome/components/aht10/aht10.cpp index 4688440d80..da8c6e844e 100644 --- a/esphome/components/aht10/aht10.cpp +++ b/esphome/components/aht10/aht10.cpp @@ -10,7 +10,7 @@ // // According to the datasheet, the component is supposed to respond in more than 75ms. In fact, it can answer almost // immediately for temperature. But for humidity, it takes >90ms to get a valid data. From experience, we have best -// results making successive requests; the current implementation make 3 attemps with a delay of 30ms each time. +// results making successive requests; the current implementation makes 3 attempts with a delay of 30ms each time. #include "aht10.h" #include "esphome/core/log.h" @@ -23,7 +23,7 @@ static const uint8_t AHT10_CALIBRATE_CMD[] = {0xE1}; static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00}; static const uint8_t AHT10_DEFAULT_DELAY = 5; // ms, for calibration and temperature measurement static const uint8_t AHT10_HUMIDITY_DELAY = 30; // ms -static const uint8_t AHT10_ATTEMPS = 3; // safety margin, normally 3 attemps are enough: 3*30=90ms +static const uint8_t AHT10_ATTEMPTS = 3; // safety margin, normally 3 attempts are enough: 3*30=90ms void AHT10Component::setup() { ESP_LOGCONFIG(TAG, "Setting up AHT10..."); @@ -58,8 +58,8 @@ void AHT10Component::update() { uint8_t delay = AHT10_DEFAULT_DELAY; if (this->humidity_sensor_ != nullptr) delay = AHT10_HUMIDITY_DELAY; - for (int i = 0; i < AHT10_ATTEMPS; ++i) { - ESP_LOGVV(TAG, "Attemps %u at %6ld", i, millis()); + for (int i = 0; i < AHT10_ATTEMPTS; ++i) { + ESP_LOGVV(TAG, "Attempt %u at %6ld", i, millis()); delay_microseconds_accurate(4); if (!this->read_bytes(0, data, 6, delay)) { ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); diff --git a/esphome/components/atm90e32/atm90e32_reg.h b/esphome/components/atm90e32/atm90e32_reg.h index dc2048fbc2..7927a7fdfb 100644 --- a/esphome/components/atm90e32/atm90e32_reg.h +++ b/esphome/components/atm90e32/atm90e32_reg.h @@ -39,7 +39,7 @@ static const uint16_t ATM90E32_STATUS_S0_OVPHASEBST = 1 << 11; // Over voltage static const uint16_t ATM90E32_STATUS_S0_OVPHASECST = 1 << 10; // Over voltage on phase C static const uint16_t ATM90E32_STATUS_S0_UREVWNST = 1 << 9; // Voltage Phase Sequence Error status static const uint16_t ATM90E32_STATUS_S0_IREVWNST = 1 << 8; // Current Phase Sequence Error status -static const uint16_t ATM90E32_STATUS_S0_INOV0ST = 1 << 7; // Calculated N line current greater tha INWarnTh reg +static const uint16_t ATM90E32_STATUS_S0_INOV0ST = 1 << 7; // Calculated N line current greater than INWarnTh reg static const uint16_t ATM90E32_STATUS_S0_TQNOLOADST = 1 << 6; // All phase sum reactive power no-load condition status static const uint16_t ATM90E32_STATUS_S0_TPNOLOADST = 1 << 5; // All phase sum active power no-load condition status static const uint16_t ATM90E32_STATUS_S0_TASNOLOADST = 1 << 4; // All phase sum apparent power no-load status diff --git a/esphome/components/b_parasite/b_parasite.cpp b/esphome/components/b_parasite/b_parasite.cpp index 81c9243bdb..3098d462d2 100644 --- a/esphome/components/b_parasite/b_parasite.cpp +++ b/esphome/components/b_parasite/b_parasite.cpp @@ -47,7 +47,7 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { uint16_t battery_millivolt = data[2] << 8 | data[3]; float battery_voltage = battery_millivolt / 1000.0f; - // Temperature in 1000 * Celcius. + // Temperature in 1000 * Celsius. uint16_t temp_millicelcius = data[4] << 8 | data[5]; float temp_celcius = temp_millicelcius / 1000.0f; diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index 7fc5e424f0..7e34546078 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -137,7 +137,7 @@ void DallasComponent::update() { } if (!res) { - ESP_LOGW(TAG, "'%s' - Reseting bus for read failed!", sensor->get_name().c_str()); + ESP_LOGW(TAG, "'%s' - Resetting bus for read failed!", sensor->get_name().c_str()); sensor->publish_state(NAN); this->status_set_warning(); return; diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index 4a5c418e0a..a7f5747d68 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -203,7 +203,7 @@ bool HOT ICACHE_RAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, const uint16_t raw_humidity = uint16_t(data[0]) * 10 + data[1]; *humidity = raw_humidity / 10.0f; } else { - // For compatibily with DHT11 models which might only use 2 bytes checksums, only use the data from these two + // For compatibility with DHT11 models which might only use 2 bytes checksums, only use the data from these two // bytes *temperature = data[2]; *humidity = data[0]; diff --git a/esphome/components/e131/e131.cpp b/esphome/components/e131/e131.cpp index fb72e5b470..bd749683c5 100644 --- a/esphome/components/e131/e131.cpp +++ b/esphome/components/e131/e131.cpp @@ -50,7 +50,7 @@ void E131Component::loop() { } if (!packet_(payload, universe, packet)) { - ESP_LOGV(TAG, "Invalid packet recevied of size %zu.", payload.size()); + ESP_LOGV(TAG, "Invalid packet received of size %zu.", payload.size()); continue; } diff --git a/esphome/components/esp32_ble/queue.h b/esphome/components/esp32_ble/queue.h index 3c87a90f22..cd123d5469 100644 --- a/esphome/components/esp32_ble/queue.h +++ b/esphome/components/esp32_ble/queue.h @@ -13,7 +13,7 @@ /* * BLE events come in from a separate Task (thread) in the ESP32 stack. Rather - * than trying to deal wth various locking strategies, all incoming GAP and GATT + * than trying to deal with various locking strategies, all incoming GAP and GATT * events will simply be placed on a semaphore guarded queue. The next time the * component runs loop(), these events are popped off the queue and handed at * this safer time. diff --git a/esphome/components/esp32_ble_tracker/queue.h b/esphome/components/esp32_ble_tracker/queue.h index 17adb98034..f0f6ab9f17 100644 --- a/esphome/components/esp32_ble_tracker/queue.h +++ b/esphome/components/esp32_ble_tracker/queue.h @@ -12,7 +12,7 @@ /* * BLE events come in from a separate Task (thread) in the ESP32 stack. Rather - * than trying to deal wth various locking strategies, all incoming GAP and GATT + * than trying to deal with various locking strategies, all incoming GAP and GATT * events will simply be placed on a semaphore guarded queue. The next time the * component runs loop(), these events are popped off the queue and handed at * this safer time. diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 963ee267b3..b22f8c97d1 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -9,7 +9,7 @@ #include #include -/// Macro for IDF version comparision +/// Macro for IDF version comparison #ifndef ESP_IDF_VERSION_VAL #define ESP_IDF_VERSION_VAL(major, minor, patch) (((major) << 16) | ((minor) << 8) | (patch)) #endif diff --git a/esphome/components/fujitsu_general/fujitsu_general.h b/esphome/components/fujitsu_general/fujitsu_general.h index 7a26cd7b6b..8dc7a3e484 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.h +++ b/esphome/components/fujitsu_general/fujitsu_general.h @@ -62,7 +62,7 @@ class FujitsuGeneralClimate : public climate_ir::ClimateIR { /// Transmit via IR power off command. void transmit_off_(); - /// Parse incomming message + /// Parse incoming message bool on_receive(remote_base::RemoteReceiveData data) override; /// Transmit message as IR pulses diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp index 45be7c1acf..a940a77148 100644 --- a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp @@ -23,9 +23,9 @@ bool InkbirdIBSTH1_MINI::parse_device(const esp32_ble_tracker::ESPBTDevice &devi // for Inkbird IBS-TH1 Mini device we expect // 1) expected mac address // 2) device address type == PUBLIC - // 3) no service datas - // 4) one manufacturer datas - // 5) the manufacturer datas should contain a 16-bit uuid amd a 7-byte data vector + // 3) no service data + // 4) one manufacturer data + // 5) the manufacturer data should contain a 16-bit uuid amd a 7-byte data vector // 6) the 7-byte data component should have data[2] == 0 and data[6] == 8 // the address should match the address we declared @@ -63,7 +63,7 @@ bool InkbirdIBSTH1_MINI::parse_device(const esp32_ble_tracker::ESPBTDevice &devi // sensor output encoding // data[5] is a battery level // data[0] and data[1] is humidity * 100 (in pct) - // uuid is a temperature * 100 (in Celcius) + // uuid is a temperature * 100 (in Celsius) // when data[2] == 0 temperature is from internal sensor (IBS-TH1 or IBS-TH1 Mini) // when data[2] == 1 temperature is from external sensor (IBS-TH1 only) diff --git a/esphome/components/max7219digit/max7219digit.cpp b/esphome/components/max7219digit/max7219digit.cpp index b130823c12..520694af9d 100644 --- a/esphome/components/max7219digit/max7219digit.cpp +++ b/esphome/components/max7219digit/max7219digit.cpp @@ -104,7 +104,7 @@ void MAX7219Component::display() { uint8_t pixels[8]; // Run this loop for every MAX CHIP (GRID OF 64 leds) // Run this routine for the rows of every chip 8x row 0 top to 7 bottom - // Fill the pixel parameter with diplay data + // Fill the pixel parameter with display data // Send the data to the chip for (uint8_t i = 0; i < this->num_chips_; i++) { for (uint8_t j = 0; j < 8; j++) { @@ -119,7 +119,7 @@ void MAX7219Component::display() { } int MAX7219Component::get_height_internal() { - return 8; // TO BE DONE -> STACK TWO DISPLAYS ON TOP OF EACH OTHE + return 8; // TO BE DONE -> STACK TWO DISPLAYS ON TOP OF EACH OTHER // TO BE DONE -> CREATE Virtual size of screen and scroll } @@ -238,7 +238,7 @@ void MAX7219Component::send64pixels(uint8_t chip, const uint8_t pixels[8]) { } else { b = pixels[7 - col]; } - // send this byte to dispay at selected chip + // send this byte to display at selected chip if (this->invert_) { this->send_byte_(col + 1, ~b); } else { diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 9a5424917f..b6b0d83885 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -1015,7 +1015,7 @@ void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &varia * * @param variable_name Variable name for the queue * @param variable_name_to_send Variable name for the left of the command - * @param state_value Sting value to set + * @param state_value String value to set * @param is_sleep_safe The command is safe to send when the Nextion is sleeping */ void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) { diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 2389cc6235..3a43a51975 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -419,7 +419,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * fill_area(50, 50, 100, 100, "RED"); * ``` * - * Fills an area that starts at x coordiante `50` and y coordinate `50` with a height of `100` and width of `100` with + * Fills an area that starts at x coordinate `50` and y coordinate `50` with a height of `100` and width of `100` with * the color of blue. Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to * convert color codes to Nextion HMI colors */ @@ -437,7 +437,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * fill_area(50, 50, 100, 100, color); * ``` * - * Fills an area that starts at x coordiante `50` and y coordinate `50` with a height of `100` and width of `100` with + * Fills an area that starts at x coordinate `50` and y coordinate `50` with a height of `100` and width of `100` with * the color of blue. Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to * convert color codes to Nextion HMI colors */ @@ -546,7 +546,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * it.filled_cricle(25, 25, 10, "17013"); * ``` * - * Makes a filled circle at the x cordinates `25` and y coordinate `25` with a radius of `10` with a color of blue. + * Makes a filled circle at the x coordinate `25` and y coordinate `25` with a radius of `10` with a color of blue. * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to * Nextion HMI colors. */ @@ -563,7 +563,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * it.filled_cricle(25, 25, 10, color); * ``` * - * Makes a filled circle at the x cordinates `25` and y coordinate `25` with a radius of `10` with a color of blue. + * Makes a filled circle at the x coordinate `25` and y coordinate `25` with a radius of `10` with a color of blue. * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to * Nextion HMI colors. */ diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index 6a681af6c5..1a6e6d1066 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -305,7 +305,7 @@ void Nextion::upload_tft() { App.feed_wdt(); ESP_LOGD(TAG, "Heap Size %d, Bytes left %d", ESP.getFreeHeap(), this->content_length_); } - ESP_LOGD(TAG, "Succesfully updated Nextion!"); + ESP_LOGD(TAG, "Successfully updated Nextion!"); this->upload_end_(); } diff --git a/esphome/components/qmc5883l/qmc5883l.cpp b/esphome/components/qmc5883l/qmc5883l.cpp index 43e7939cf1..9e80cdbd88 100644 --- a/esphome/components/qmc5883l/qmc5883l.cpp +++ b/esphome/components/qmc5883l/qmc5883l.cpp @@ -116,7 +116,7 @@ void QMC5883LComponent::update() { bool QMC5883LComponent::read_byte_16_(uint8_t a_register, uint16_t *data) { bool success = this->read_byte_16(a_register, data); - *data = (*data & 0x00FF) << 8 | (*data & 0xFF00) >> 8; // Flip Byte oder, LSB first; + *data = (*data & 0x00FF) << 8 | (*data & 0xFF00) >> 8; // Flip Byte order, LSB first; return success; } diff --git a/esphome/components/rc522/rc522.cpp b/esphome/components/rc522/rc522.cpp index 789ab6197b..6261f63132 100644 --- a/esphome/components/rc522/rc522.cpp +++ b/esphome/components/rc522/rc522.cpp @@ -162,7 +162,7 @@ void RC522::loop() { ESP_LOGW(TAG, "CMD_REQA -> Not OK %d", status); state_ = STATE_DONE; } else if (back_length_ != 2) { // || *valid_bits_ != 0) { // ATQA must be exactly 16 bits. - ESP_LOGW(TAG, "CMD_REQA -> OK, but unexpacted back_length_ of %d", back_length_); + ESP_LOGW(TAG, "CMD_REQA -> OK, but unexpected back_length_ of %d", back_length_); state_ = STATE_DONE; } else { state_ = STATE_READ_SERIAL; @@ -470,7 +470,7 @@ RC522::StatusCode RC522::await_crc_() { return STATUS_WAITING; ESP_LOGD(TAG, "pcd_calculate_crc_() TIMEOUT"); - // 89ms passed and nothing happend. Communication with the MFRC522 might be down. + // 89ms passed and nothing happened. Communication with the MFRC522 might be down. return STATUS_TIMEOUT; } diff --git a/esphome/components/rc522/rc522.h b/esphome/components/rc522/rc522.h index 7fb49e97fd..6880651ac4 100644 --- a/esphome/components/rc522/rc522.h +++ b/esphome/components/rc522/rc522.h @@ -32,7 +32,7 @@ class RC522 : public PollingComponent { STATUS_OK, // Success STATUS_WAITING, // Waiting result from RC522 chip STATUS_ERROR, // Error in communication - STATUS_COLLISION, // Collission detected + STATUS_COLLISION, // Collision detected STATUS_TIMEOUT, // Timeout in communication. STATUS_NO_ROOM, // A buffer is not big enough. STATUS_INTERNAL_ERROR, // Internal error in the code. Should not happen ;-) diff --git a/esphome/components/remote_base/remote_base.cpp b/esphome/components/remote_base/remote_base.cpp index 7198f2d917..d36c0d7ebe 100644 --- a/esphome/components/remote_base/remote_base.cpp +++ b/esphome/components/remote_base/remote_base.cpp @@ -16,7 +16,7 @@ RemoteRMTChannel::RemoteRMTChannel(uint8_t mem_block_num) : mem_block_num_(mem_b void RemoteRMTChannel::config_rmt(rmt_config_t &rmt) { if (rmt_channel_t(int(this->channel_) + this->mem_block_num_) > RMT_CHANNEL_7) { this->mem_block_num_ = int(RMT_CHANNEL_7) - int(this->channel_) + 1; - ESP_LOGW(TAG, "Not enough RMT memory blocks avaiable, reduced to %i blocks.", this->mem_block_num_); + ESP_LOGW(TAG, "Not enough RMT memory blocks available, reduced to %i blocks.", this->mem_block_num_); } rmt.channel = this->channel_; rmt.clk_div = this->clock_divider_; diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index 3eda98d41d..838c92f04e 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -55,7 +55,7 @@ void SCD30Component::setup() { // According ESP32 clock stretching is typically 30ms and up to 150ms "due to // internal calibration processes". The I2C peripheral only supports 13ms (at // least when running at 80MHz). - // In practise it seems that clock stretching occures during this calibration + // In practise it seems that clock stretching occurs during this calibration // calls. It also seems that delays in between calls makes them // disappear/shorter. Hence work around with delays for ESP32. // diff --git a/esphome/components/script/__init__.py b/esphome/components/script/__init__.py index 43356c0036..9702878475 100644 --- a/esphome/components/script/__init__.py +++ b/esphome/components/script/__init__.py @@ -50,7 +50,7 @@ def assign_declare_id(value): CONFIG_SCHEMA = automation.validate_automation( { # Don't declare id as cv.declare_id yet, because the ID type - # dpeends on the mode. Will be checked later with assign_declare_id + # depends on the mode. Will be checked later with assign_declare_id cv.Required(CONF_ID): cv.string_strict, cv.Optional(CONF_MODE, default=CONF_SINGLE): cv.one_of( *SCRIPT_MODES, lower=True diff --git a/esphome/components/script/script.h b/esphome/components/script/script.h index 64db6b80e7..5663d32ce8 100644 --- a/esphome/components/script/script.h +++ b/esphome/components/script/script.h @@ -65,7 +65,7 @@ class QueueingScript : public Script, public Component { /** A script type that executes new instances in parallel. * * If a new instance is started while previous ones haven't finished yet, - * the new one is exeucted in parallel to the other instances. + * the new one is executed in parallel to the other instances. */ class ParallelScript : public Script { public: diff --git a/esphome/components/ssd1331_base/ssd1331_base.cpp b/esphome/components/ssd1331_base/ssd1331_base.cpp index f25ef50075..7f761b4b55 100644 --- a/esphome/components/ssd1331_base/ssd1331_base.cpp +++ b/esphome/components/ssd1331_base/ssd1331_base.cpp @@ -19,7 +19,7 @@ static const uint8_t SSD1331_DRAWLINE = 0x21; // Draw line static const uint8_t SSD1331_DRAWRECT = 0x22; // Draw rectangle static const uint8_t SSD1331_FILL = 0x26; // Fill enable/disable static const uint8_t SSD1331_SETCOLUMN = 0x15; // Set column address -static const uint8_t SSD1331_SETROW = 0x75; // Set row adress +static const uint8_t SSD1331_SETROW = 0x75; // Set row address static const uint8_t SSD1331_CONTRASTA = 0x81; // Set contrast for color A static const uint8_t SSD1331_CONTRASTB = 0x82; // Set contrast for color B static const uint8_t SSD1331_CONTRASTC = 0x83; // Set contrast for color C diff --git a/esphome/components/st7789v/st7789v.h b/esphome/components/st7789v/st7789v.h index 0e17e65fd7..2aef043ba0 100644 --- a/esphome/components/st7789v/st7789v.h +++ b/esphome/components/st7789v/st7789v.h @@ -19,13 +19,13 @@ static const uint8_t ST7789_RDDMADCTL = 0x0B; // Read Display MADCTL static const uint8_t ST7789_RDDCOLMOD = 0x0C; // Read Display Pixel Format static const uint8_t ST7789_RDDIM = 0x0D; // Read Display Image Mode static const uint8_t ST7789_RDDSM = 0x0E; // Read Display Signal Mod -static const uint8_t ST7789_RDDSDR = 0x0F; // Read Display Self-Diagnostic Resul +static const uint8_t ST7789_RDDSDR = 0x0F; // Read Display Self-Diagnostic Result static const uint8_t ST7789_SLPIN = 0x10; // Sleep in static const uint8_t ST7789_SLPOUT = 0x11; // Sleep Out -static const uint8_t ST7789_PTLON = 0x12; // Partial Display Mode O -static const uint8_t ST7789_NORON = 0x13; // Normal Display Mode O +static const uint8_t ST7789_PTLON = 0x12; // Partial Display Mode On +static const uint8_t ST7789_NORON = 0x13; // Normal Display Mode On static const uint8_t ST7789_INVOFF = 0x20; // Display Inversion Off -static const uint8_t ST7789_INVON = 0x21; // Display Inversion O +static const uint8_t ST7789_INVON = 0x21; // Display Inversion On static const uint8_t ST7789_GAMSET = 0x26; // Gamma Set static const uint8_t ST7789_DISPOFF = 0x28; // Display Off static const uint8_t ST7789_DISPON = 0x29; // Display On @@ -34,18 +34,18 @@ static const uint8_t ST7789_RASET = 0x2B; // Row Address Set static const uint8_t ST7789_RAMWR = 0x2C; // Memory Write static const uint8_t ST7789_RAMRD = 0x2E; // Memory Read static const uint8_t ST7789_PTLAR = 0x30; // Partial Area -static const uint8_t ST7789_VSCRDEF = 0x33; // Vertical Scrolling Definitio -static const uint8_t ST7789_TEOFF = 0x34; // Tearing Effect Line OFF +static const uint8_t ST7789_VSCRDEF = 0x33; // Vertical Scrolling Definition +static const uint8_t ST7789_TEOFF = 0x34; // Tearing Effect Line Off static const uint8_t ST7789_TEON = 0x35; // Tearing Effect Line On static const uint8_t ST7789_MADCTL = 0x36; // Memory Data Access Control static const uint8_t ST7789_VSCSAD = 0x37; // Vertical Scroll Start Address of RAM static const uint8_t ST7789_IDMOFF = 0x38; // Idle Mode Off -static const uint8_t ST7789_IDMON = 0x39; // Idle mode on +static const uint8_t ST7789_IDMON = 0x39; // Idle Mode On static const uint8_t ST7789_COLMOD = 0x3A; // Interface Pixel Format static const uint8_t ST7789_WRMEMC = 0x3C; // Write Memory Continue static const uint8_t ST7789_RDMEMC = 0x3E; // Read Memory Continue static const uint8_t ST7789_STE = 0x44; // Set Tear Scanline -static const uint8_t ST7789_GSCAN = 0x45; // Get Scanlin +static const uint8_t ST7789_GSCAN = 0x45; // Get Scanline static const uint8_t ST7789_WRDISBV = 0x51; // Write Display Brightness static const uint8_t ST7789_RDDISBV = 0x52; // Read Display Brightness Value static const uint8_t ST7789_WRCTRLD = 0x53; // Write CTRL Display @@ -59,17 +59,17 @@ static const uint8_t ST7789_RDID1 = 0xDA; // Read ID1 static const uint8_t ST7789_RDID2 = 0xDB; // Read ID2 static const uint8_t ST7789_RDID3 = 0xDC; // Read ID3 static const uint8_t ST7789_RAMCTRL = 0xB0; // RAM Control -static const uint8_t ST7789_RGBCTRL = 0xB1; // RGB Interface Contro +static const uint8_t ST7789_RGBCTRL = 0xB1; // RGB Interface Control static const uint8_t ST7789_PORCTRL = 0xB2; // Porch Setting static const uint8_t ST7789_FRCTRL1 = 0xB3; // Frame Rate Control 1 (In partial mode/ idle colors) -static const uint8_t ST7789_PARCTRL = 0xB5; // Partial mode Contro -static const uint8_t ST7789_GCTRL = 0xB7; // Gate Contro -static const uint8_t ST7789_GTADJ = 0xB8; // Gate On Timing Adjustmen +static const uint8_t ST7789_PARCTRL = 0xB5; // Partial mode Control +static const uint8_t ST7789_GCTRL = 0xB7; // Gate Control +static const uint8_t ST7789_GTADJ = 0xB8; // Gate On Timing Adjustment static const uint8_t ST7789_DGMEN = 0xBA; // Digital Gamma Enable static const uint8_t ST7789_VCOMS = 0xBB; // VCOMS Setting static const uint8_t ST7789_LCMCTRL = 0xC0; // LCM Control -static const uint8_t ST7789_IDSET = 0xC1; // ID Code Settin -static const uint8_t ST7789_VDVVRHEN = 0xC2; // VDV and VRH Command Enabl +static const uint8_t ST7789_IDSET = 0xC1; // ID Code Setting +static const uint8_t ST7789_VDVVRHEN = 0xC2; // VDV and VRH Command Enable static const uint8_t ST7789_VRHS = 0xC3; // VRH Set static const uint8_t ST7789_VDVS = 0xC4; // VDV Set static const uint8_t ST7789_VCMOFSET = 0xC5; // VCOMS Offset Set @@ -89,8 +89,8 @@ static const uint8_t ST7789_GATECTRL = 0xE4; // Gate Control static const uint8_t ST7789_SPI2EN = 0xE7; // SPI2 Enable static const uint8_t ST7789_PWCTRL2 = 0xE8; // Power Control 2 static const uint8_t ST7789_EQCTRL = 0xE9; // Equalize time control -static const uint8_t ST7789_PROMCTRL = 0xEC; // Program Mode Contro -static const uint8_t ST7789_PROMEN = 0xFA; // Program Mode Enabl +static const uint8_t ST7789_PROMCTRL = 0xEC; // Program Mode Control +static const uint8_t ST7789_PROMEN = 0xFA; // Program Mode Enable static const uint8_t ST7789_NVMSET = 0xFC; // NVM Setting static const uint8_t ST7789_PROMACT = 0xFE; // Program action diff --git a/esphome/components/sx1509/sx1509_registers.h b/esphome/components/sx1509/sx1509_registers.h index d73f397f16..b97b85993f 100644 --- a/esphome/components/sx1509/sx1509_registers.h +++ b/esphome/components/sx1509/sx1509_registers.h @@ -9,7 +9,7 @@ Here you'll find the Arduino code used to interface with the SX1509 I2C 16 I/O expander. There are functions to take advantage of everything the SX1509 provides - input/output setting, writing pins high/low, reading the input value of pins, LED driver utilities (blink, breath, pwm), and -keypad engine utilites. +keypad engine utilities. Development environment specifics: IDE: Arduino 1.6.5 diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 916a550675..42ecd2477b 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -163,7 +163,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff this->gpio_reset_ = buffer[1]; } if (this->init_state_ == TuyaInitState::INIT_CONF) { - // If mcu returned status gpio, then we can ommit sending wifi state + // If mcu returned status gpio, then we can omit sending wifi state if (this->gpio_status_ != -1) { this->init_state_ = TuyaInitState::INIT_DATAPOINT; this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY); @@ -363,7 +363,7 @@ void Tuya::process_command_queue_() { this->expected_response_.reset(); } - // Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly + // Left check of delay since last command in case there's ever a command sent by calling send_raw_command_ directly if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty() && !this->expected_response_.has_value()) { this->send_raw_command_(command_queue_.front()); diff --git a/esphome/components/vl53l0x/LICENSE.txt b/esphome/components/vl53l0x/LICENSE.txt index fe33583414..f7a234d023 100644 --- a/esphome/components/vl53l0x/LICENSE.txt +++ b/esphome/components/vl53l0x/LICENSE.txt @@ -3,7 +3,7 @@ by Pololu (Pololu Corporation), which in turn is based on the VL53L0X API from ST. The code has been adapted to work with ESPHome's i2c APIs. Please see the top-level LICENSE.txt for information about ESPHome's license. The licenses for Pololu's and ST's software are included below. -Orignally taken from https://github.com/pololu/vl53l0x-arduino (accessed 20th october 2019). +Originally taken from https://github.com/pololu/vl53l0x-arduino (accessed 20th october 2019). ================================================================= diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 4518dd60df..458f04c674 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -598,7 +598,7 @@ void WaveshareEPaper2P9InB::initialize() { this->data(0x9F); // COMMAND RESOLUTION SETTING - // set to 128x296 by COMMAND PANNEL SETTING + // set to 128x296 by COMMAND PANEL SETTING // COMMAND VCOM AND DATA INTERVAL SETTING // use defaults for white border and ESPHome image polarity diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index c736a236a1..c4434801b4 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -344,9 +344,9 @@ bool report_xiaomi_results(const optional &result, const std: bool XiaomiListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { // Previously the message was parsed twice per packet, once by XiaomiListener::parse_device() // and then again by the respective device class's parse_device() function. Parsing the header - // here and then for each device seems to be unneccessary and complicates the duplicate packet filtering. + // here and then for each device seems to be unnecessary and complicates the duplicate packet filtering. // Hence I disabled the call to parse_xiaomi_header() here and the message parsing is done entirely - // in the respecive device instance. The XiaomiListener class is defined in __init__.py and I was not + // in the respective device instance. The XiaomiListener class is defined in __init__.py and I was not // able to remove it entirely. return false; // with true it's not showing device scans diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp index 78464da6e3..36b4c8cc00 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp @@ -51,7 +51,7 @@ optional XiaomiMiscale::parse_header(const esp32_ble_tracker::Servi } bool XiaomiMiscale::parse_message(const std::vector &message, ParseResult &result) { - // exemple 1d18 a2 6036 e307 07 11 0f1f11 + // example 1d18 a2 6036 e307 07 11 0f1f11 // 1-2 Weight (MISCALE 181D) // 3-4 Years (MISCALE 181D) // 5 month (MISCALE 181D) diff --git a/esphome/core/component.h b/esphome/core/component.h index 001620fe4a..433259f627 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -22,7 +22,7 @@ extern const float IO; extern const float HARDWARE; /// For components that import data from directly connected sensors like DHT. extern const float DATA; -/// Alias for DATA (here for compatability reasons) +/// Alias for DATA (here for compatibility reasons) extern const float HARDWARE_LATE; /// For components that use data from sensors like displays extern const float PROCESSOR; diff --git a/script/build_jsonschema.py b/script/build_jsonschema.py index 89d621fd5a..1ab0ffa015 100644 --- a/script/build_jsonschema.py +++ b/script/build_jsonschema.py @@ -419,7 +419,7 @@ def get_jschema(path, vschema, create_return_ref=True): def get_schema_str(vschema): - # Hack on cs.use_id, in the future this can be improved by trackign which type is required by + # Hack on cs.use_id, in the future this can be improved by tracking which type is required by # the id, this information can be added somehow to schema (not supported by jsonschema) and # completion can be improved listing valid ids only Meanwhile it's a problem because it makes # all partial schemas with cv.use_id different, e.g. i2c @@ -675,7 +675,7 @@ def dump_schema(): # The root directory of the repo root = Path(__file__).parent.parent - # Fake some diretory so that get_component works + # Fake some directory so that get_component works CORE.config_path = str(root) file_path = args.output From 2a9e3d84fd83e28620d6a2ca67e108624d8c9bc6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 26 Jul 2021 15:19:50 +0200 Subject: [PATCH 1073/1841] Fix climate restore schema changed resulting in invalid restore (#2068) Co-authored-by: Stefan Agner --- esphome/components/climate/climate.cpp | 6 +++++- esphome/components/climate/climate.h | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 07347e4eee..8da2206f37 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -312,8 +312,12 @@ void Climate::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } +// Random 32bit value; If this changes existing restore preferences are invalidated +static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL; + optional Climate::restore_state_() { - this->rtc_ = global_preferences.make_preference(this->get_object_id_hash()); + this->rtc_ = + global_preferences.make_preference(this->get_object_id_hash() ^ RESTORE_STATE_VERSION); ClimateDeviceRestoreState recovered{}; if (!this->rtc_.load(&recovered)) return {}; diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index ed5c5069b7..690e81c250 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -120,6 +120,7 @@ class ClimateCall { }; /// Struct used to save the state of the climate device in restore memory. +/// Make sure to update RESTORE_STATE_VERSION when changing the struct entries. struct ClimateDeviceRestoreState { ClimateMode mode; bool uses_custom_fan_mode{false}; From b0a38914987b4df5c45901c6b29c844046d96c8b Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 26 Jul 2021 21:46:13 +0200 Subject: [PATCH 1074/1841] Fix MQTT climate custom fan modes without regular ones (#2071) --- esphome/components/mqtt/mqtt_climate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index ab8354e66c..5809b6616c 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -72,7 +72,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC root["act_t"] = this->get_action_state_topic(); } - if (traits.get_supports_fan_modes()) { + if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) { // fan_mode_command_topic root["fan_mode_cmd_t"] = this->get_fan_mode_command_topic(); // fan_mode_state_topic From caa651e55badb77b3f3bac20346ef1384a473569 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Thu, 22 Jul 2021 14:37:42 +0200 Subject: [PATCH 1075/1841] Accept change as proposed by black. (#2055) --- esphome/components/external_components/__init__.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index bf44dc1929..3e833d0b66 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -109,9 +109,9 @@ def _compute_destination_path(key: str) -> Path: return base_dir / h.hexdigest()[:8] -def _run_git_command(cmd): +def _run_git_command(cmd, cwd=None): try: - ret = subprocess.run(cmd, capture_output=True, check=False) + ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False) except FileNotFoundError as err: raise cv.Invalid( "git is not installed but required for external_components.\n" @@ -151,14 +151,16 @@ def _process_git_config(config: dict, refresh) -> str: _LOGGER.info("Updating %s", key) _LOGGER.debug("Location: %s", repo_dir) # Stash local changes (if any) - _run_git_command(["git", "stash", "push", "--include-untracked"]) + _run_git_command( + ["git", "stash", "push", "--include-untracked"], str(repo_dir) + ) # Fetch remote ref cmd = ["git", "fetch", "--", "origin"] if CONF_REF in config: cmd.append(config[CONF_REF]) - _run_git_command(cmd) + _run_git_command(cmd, str(repo_dir)) # Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch) - _run_git_command(["git", "reset", "--hard", "FETCH_HEAD"]) + _run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir)) if (repo_dir / "esphome" / "components").is_dir(): components_dir = repo_dir / "esphome" / "components" From 1efabd27d88d1596d79a9b61fddc7a18a83bb7fc Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Thu, 22 Jul 2021 16:39:21 +0400 Subject: [PATCH 1076/1841] midea_ac: fix presets implementation (#2054) --- esphome/components/midea_ac/midea_climate.cpp | 4 ++-- esphome/components/midea_ac/midea_frame.cpp | 22 ++++++++++++------- esphome/components/midea_ac/midea_frame.h | 1 + 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/esphome/components/midea_ac/midea_climate.cpp b/esphome/components/midea_ac/midea_climate.cpp index 9fe5df7de3..72f7d23404 100644 --- a/esphome/components/midea_ac/midea_climate.cpp +++ b/esphome/components/midea_ac/midea_climate.cpp @@ -100,7 +100,7 @@ bool MideaAC::allow_preset(climate::ClimatePreset preset) const { ESP_LOGD(TAG, "BOOST preset is only available in HEAT or COOL mode"); } break; - case climate::CLIMATE_PRESET_HOME: + case climate::CLIMATE_PRESET_NONE: return true; default: break; @@ -191,7 +191,7 @@ climate::ClimateTraits MideaAC::traits() { if (traits_swing_both_) traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH); traits.set_supported_presets({ - climate::CLIMATE_PRESET_HOME, + climate::CLIMATE_PRESET_NONE, }); if (traits_preset_eco_) traits.add_supported_preset(climate::CLIMATE_PRESET_ECO); diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp index 5f09f4314f..c0a5ce4b55 100644 --- a/esphome/components/midea_ac/midea_frame.cpp +++ b/esphome/components/midea_ac/midea_frame.cpp @@ -86,18 +86,17 @@ void PropertiesFrame::set_mode(climate::ClimateMode mode) { } optional PropertiesFrame::get_preset() const { - if (this->get_eco_mode()) { + if (this->get_eco_mode()) return climate::CLIMATE_PRESET_ECO; - } else if (this->get_sleep_mode()) { + if (this->get_sleep_mode()) return climate::CLIMATE_PRESET_SLEEP; - } else if (this->get_turbo_mode()) { + if (this->get_turbo_mode()) return climate::CLIMATE_PRESET_BOOST; - } else { - return climate::CLIMATE_PRESET_HOME; - } + return climate::CLIMATE_PRESET_NONE; } void PropertiesFrame::set_preset(climate::ClimatePreset preset) { + this->clear_presets(); switch (preset) { case climate::CLIMATE_PRESET_ECO: this->set_eco_mode(true); @@ -113,14 +112,21 @@ void PropertiesFrame::set_preset(climate::ClimatePreset preset) { } } +void PropertiesFrame::clear_presets() { + this->set_eco_mode(false); + this->set_sleep_mode(false); + this->set_turbo_mode(false); + this->set_freeze_protection_mode(false); +} + bool PropertiesFrame::is_custom_preset() const { return this->get_freeze_protection_mode(); } const std::string &PropertiesFrame::get_custom_preset() const { return midea_ac::MIDEA_FREEZE_PROTECTION_PRESET; }; void PropertiesFrame::set_custom_preset(const std::string &preset) { - if (preset == MIDEA_FREEZE_PROTECTION_PRESET) { + this->clear_presets(); + if (preset == MIDEA_FREEZE_PROTECTION_PRESET) this->set_freeze_protection_mode(true); - } } bool PropertiesFrame::is_custom_fan_mode() const { diff --git a/esphome/components/midea_ac/midea_frame.h b/esphome/components/midea_ac/midea_frame.h index 3777f6fd77..e1d6fed49d 100644 --- a/esphome/components/midea_ac/midea_frame.h +++ b/esphome/components/midea_ac/midea_frame.h @@ -115,6 +115,7 @@ class PropertiesFrame : public midea_dongle::BaseFrame { /* PRESET */ optional get_preset() const; void set_preset(climate::ClimatePreset preset); + void clear_presets(); bool is_custom_preset() const; const std::string &get_custom_preset() const; From fda8dd4ce35671b7a8fe8b4de6a335bd31f349d0 Mon Sep 17 00:00:00 2001 From: carstenschroeder Date: Thu, 22 Jul 2021 14:39:57 +0200 Subject: [PATCH 1077/1841] Fixes new auto mode COOL and HEAT after #1994 (#2053) --- esphome/components/pid/pid_climate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index dac4426698..ef8a4df962 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -35,8 +35,8 @@ void PIDClimate::control(const climate::ClimateCall &call) { if (call.get_target_temperature().has_value()) this->target_temperature = *call.get_target_temperature(); - // If switching to non-auto mode, set output immediately - if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) + // If switching to off mode, set output immediately + if (this->mode == climate::CLIMATE_MODE_OFF) this->handle_non_auto_mode_(); this->publish_state(); From 64a3aa7092e6470c57e432681628811997f65c9b Mon Sep 17 00:00:00 2001 From: buxtronix Date: Mon, 26 Jul 2021 18:41:54 +1000 Subject: [PATCH 1078/1841] Log warning about lack of support for Anova nano (#2063) Co-authored-by: Ben Buxton --- esphome/components/anova/anova.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index c4b08ca6b5..63e0710936 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -60,6 +60,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ auto chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID); if (chr == nullptr) { ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str()); + ESP_LOGW(TAG, "[%s] Note, this component does not currently support Anova Nano.", this->get_name().c_str()); break; } this->char_handle_ = chr->handle; From 1e2a9e8348eb01a40a21a5fc06cf34241ed451dd Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Mon, 26 Jul 2021 04:39:03 -0500 Subject: [PATCH 1079/1841] Couple more updates for the Tuya component (#2065) Co-authored-by: Chris Nussbaum --- esphome/components/tuya/tuya.cpp | 40 ++++++++++++++++++++++++++++---- esphome/components/tuya/tuya.h | 1 + 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index d9a0a9932a..916a550675 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -8,9 +8,10 @@ namespace tuya { static const char *const TAG = "tuya"; static const int COMMAND_DELAY = 50; +static const int RECEIVE_TIMEOUT = 300; void Tuya::setup() { - this->set_interval("heartbeat", 10000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); + this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); } void Tuya::loop() { @@ -117,7 +118,13 @@ void Tuya::handle_char_(uint8_t c) { } void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len) { - switch ((TuyaCommandType) command) { + TuyaCommandType command_type = (TuyaCommandType) command; + + if (this->expected_response_.has_value() && this->expected_response_ == command_type) { + this->expected_response_.reset(); + } + + switch (command_type) { case TuyaCommandType::HEARTBEAT: ESP_LOGV(TAG, "MCU Heartbeat (0x%02X)", buffer[0]); this->protocol_version_ = version; @@ -316,6 +323,25 @@ void Tuya::send_raw_command_(TuyaCommand command) { uint8_t version = 0; this->last_command_timestamp_ = millis(); + switch (command.cmd) { + case TuyaCommandType::HEARTBEAT: + this->expected_response_ = TuyaCommandType::HEARTBEAT; + break; + case TuyaCommandType::PRODUCT_QUERY: + this->expected_response_ = TuyaCommandType::PRODUCT_QUERY; + break; + case TuyaCommandType::CONF_QUERY: + this->expected_response_ = TuyaCommandType::CONF_QUERY; + break; + case TuyaCommandType::DATAPOINT_DELIVER: + this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT; + break; + case TuyaCommandType::DATAPOINT_QUERY: + this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT; + break; + default: + break; + } ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast(command.cmd), version, hexencode(command.payload).c_str(), static_cast(this->init_state_)); @@ -332,8 +358,14 @@ void Tuya::send_raw_command_(TuyaCommand command) { void Tuya::process_command_queue_() { uint32_t delay = millis() - this->last_command_timestamp_; + + if (this->expected_response_.has_value() && delay > RECEIVE_TIMEOUT) { + this->expected_response_.reset(); + } + // Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly - if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty()) { + if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty() && + !this->expected_response_.has_value()) { this->send_raw_command_(command_queue_.front()); this->command_queue_.erase(command_queue_.begin()); } @@ -345,7 +377,7 @@ void Tuya::send_command_(const TuyaCommand &command) { } void Tuya::send_empty_command_(TuyaCommandType command) { - send_command_(TuyaCommand{.cmd = command, .payload = std::vector{0x04}}); + send_command_(TuyaCommand{.cmd = command, .payload = std::vector{}}); } void Tuya::send_wifi_status_() { diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 4ca4f56366..68decf7e9e 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -113,6 +113,7 @@ class Tuya : public Component, public uart::UARTDevice { std::vector rx_message_; std::vector ignore_mcu_update_on_datapoints_{}; std::vector command_queue_; + optional expected_response_{}; uint8_t wifi_status_ = -1; }; From 71ded24fced532004c726c9578021e1cd7b0b8f6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 26 Jul 2021 21:46:13 +0200 Subject: [PATCH 1080/1841] Fix MQTT climate custom fan modes without regular ones (#2071) --- esphome/components/mqtt/mqtt_climate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index ab8354e66c..5809b6616c 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -72,7 +72,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC root["act_t"] = this->get_action_state_topic(); } - if (traits.get_supports_fan_modes()) { + if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) { // fan_mode_command_topic root["fan_mode_cmd_t"] = this->get_fan_mode_command_topic(); // fan_mode_state_topic From 887081fd710c80c2aabe9954d19d3f25b19f6424 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 27 Jul 2021 09:43:05 +1200 Subject: [PATCH 1081/1841] Bump version to v1.20.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 48790cf16a..a8e9e9cfc4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.0" +__version__ = "1.20.1" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 5abbe385c55740138b89c224dd0a2ebbc1f30e29 Mon Sep 17 00:00:00 2001 From: "John K. Luebs" Date: Tue, 27 Jul 2021 21:01:15 -0500 Subject: [PATCH 1082/1841] More Tuya MCU robustness (#2080) --- esphome/components/tuya/tuya.cpp | 11 +++++++++-- esphome/components/tuya/tuya.h | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 42ecd2477b..9e036feda9 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -7,7 +7,7 @@ namespace esphome { namespace tuya { static const char *const TAG = "tuya"; -static const int COMMAND_DELAY = 50; +static const int COMMAND_DELAY = 10; static const int RECEIVE_TIMEOUT = 300; void Tuya::setup() { @@ -114,6 +114,8 @@ void Tuya::handle_char_(uint8_t c) { this->rx_message_.push_back(c); if (!this->validate_message_()) { this->rx_message_.clear(); + } else { + this->last_rx_char_timestamp_ = millis(); } } @@ -357,7 +359,12 @@ void Tuya::send_raw_command_(TuyaCommand command) { } void Tuya::process_command_queue_() { - uint32_t delay = millis() - this->last_command_timestamp_; + uint32_t now = millis(); + uint32_t delay = now - this->last_command_timestamp_; + + if (now - this->last_rx_char_timestamp_ > RECEIVE_TIMEOUT) { + this->rx_message_.clear(); + } if (this->expected_response_.has_value() && delay > RECEIVE_TIMEOUT) { this->expected_response_.reset(); diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 68decf7e9e..7ce4be4315 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -107,6 +107,7 @@ class Tuya : public Component, public uart::UARTDevice { int gpio_status_ = -1; int gpio_reset_ = -1; uint32_t last_command_timestamp_ = 0; + uint32_t last_rx_char_timestamp_ = 0; std::string product_ = ""; std::vector listeners_; std::vector datapoints_; From b9259a0238b731f6e487a93f39bd7254dd6087ae Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 28 Jul 2021 15:18:31 +1200 Subject: [PATCH 1083/1841] Bump esphome dashboard to 20210728.0 (#2081) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ab54828194..561bf0f4d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210719.0 +esphome-dashboard==20210728.0 From f97cfe99167da2a37bd68237e97654ee2fffede9 Mon Sep 17 00:00:00 2001 From: Peter van Dijk Date: Wed, 28 Jul 2021 10:41:21 +0200 Subject: [PATCH 1084/1841] pm1006: add rx-only support (#2038) --- esphome/components/pm1006/__init__.py | 0 esphome/components/pm1006/pm1006.cpp | 96 +++++++++++++++++++++++++++ esphome/components/pm1006/pm1006.h | 35 ++++++++++ esphome/components/pm1006/sensor.py | 44 ++++++++++++ esphome/const.py | 1 + 5 files changed, 176 insertions(+) create mode 100644 esphome/components/pm1006/__init__.py create mode 100644 esphome/components/pm1006/pm1006.cpp create mode 100644 esphome/components/pm1006/pm1006.h create mode 100644 esphome/components/pm1006/sensor.py diff --git a/esphome/components/pm1006/__init__.py b/esphome/components/pm1006/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/pm1006/pm1006.cpp b/esphome/components/pm1006/pm1006.cpp new file mode 100644 index 0000000000..9bedb3cfc0 --- /dev/null +++ b/esphome/components/pm1006/pm1006.cpp @@ -0,0 +1,96 @@ +#include "pm1006.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pm1006 { + +static const char *const TAG = "pm1006"; + +static const uint8_t PM1006_RESPONSE_HEADER[] = {0x16, 0x11, 0x0B}; + +void PM1006Component::setup() { + // because this implementation is currently rx-only, there is nothing to setup +} + +void PM1006Component::dump_config() { + ESP_LOGCONFIG(TAG, "PM1006:"); + LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_); + this->check_uart_settings(9600); +} + +void PM1006Component::loop() { + while (this->available() != 0) { + this->read_byte(&this->data_[this->data_index_]); + auto check = this->check_byte_(); + if (!check.has_value()) { + // finished + this->parse_data_(); + this->data_index_ = 0; + } else if (!*check) { + // wrong data + ESP_LOGV(TAG, "Byte %i of received data frame is invalid.", this->data_index_); + this->data_index_ = 0; + } else { + // next byte + this->data_index_++; + } + } +} + +float PM1006Component::get_setup_priority() const { return setup_priority::DATA; } + +uint8_t PM1006Component::pm1006_checksum_(const uint8_t *command_data, uint8_t length) const { + uint8_t sum = 0; + for (uint8_t i = 0; i < length; i++) { + sum += command_data[i]; + } + return sum; +} + +optional PM1006Component::check_byte_() const { + uint8_t index = this->data_index_; + uint8_t byte = this->data_[index]; + + // index 0..2 are the fixed header + if (index < sizeof(PM1006_RESPONSE_HEADER)) { + return byte == PM1006_RESPONSE_HEADER[index]; + } + + // just some additional notes here: + // index 3..4 is unused + // index 5..6 is our PM2.5 reading (3..6 is called DF1-DF4 in the datasheet at + // http://www.jdscompany.co.kr/download.asp?gubun=07&filename=PM1006_LED_PARTICLE_SENSOR_MODULE_SPECIFICATIONS.pdf + // that datasheet goes on up to DF16, which is unused for PM1006 but used in PM1006K + // so this code should be trivially extensible to support that one later + if (index < (sizeof(PM1006_RESPONSE_HEADER) + 16)) + return true; + + // checksum + if (index == (sizeof(PM1006_RESPONSE_HEADER) + 16)) { + uint8_t checksum = pm1006_checksum_(this->data_, sizeof(PM1006_RESPONSE_HEADER) + 17); + if (checksum != 0) { + ESP_LOGW(TAG, "PM1006 checksum is wrong: %02x, expected zero", checksum); + return false; + } + return {}; + } + + return false; +} + +void PM1006Component::parse_data_() { + const int pm_2_5_concentration = this->get_16_bit_uint_(5); + + ESP_LOGD(TAG, "Got PM2.5 Concentration: %d µg/m³", pm_2_5_concentration); + + if (this->pm_2_5_sensor_ != nullptr) { + this->pm_2_5_sensor_->publish_state(pm_2_5_concentration); + } +} + +uint16_t PM1006Component::get_16_bit_uint_(uint8_t start_index) const { + return encode_uint16(this->data_[start_index], this->data_[start_index + 1]); +} + +} // namespace pm1006 +} // namespace esphome diff --git a/esphome/components/pm1006/pm1006.h b/esphome/components/pm1006/pm1006.h new file mode 100644 index 0000000000..66f4cf0311 --- /dev/null +++ b/esphome/components/pm1006/pm1006.h @@ -0,0 +1,35 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace pm1006 { + +class PM1006Component : public Component, public uart::UARTDevice { + public: + PM1006Component() = default; + + void set_pm_2_5_sensor(sensor::Sensor *pm_2_5_sensor) { this->pm_2_5_sensor_ = pm_2_5_sensor; } + void setup() override; + void dump_config() override; + void loop() override; + + float get_setup_priority() const override; + + protected: + optional check_byte_() const; + void parse_data_(); + uint16_t get_16_bit_uint_(uint8_t start_index) const; + uint8_t pm1006_checksum_(const uint8_t *command_data, uint8_t length) const; + + sensor::Sensor *pm_2_5_sensor_{nullptr}; + + uint8_t data_[20]; + uint8_t data_index_{0}; + uint32_t last_transmission_{0}; +}; + +} // namespace pm1006 +} // namespace esphome diff --git a/esphome/components/pm1006/sensor.py b/esphome/components/pm1006/sensor.py new file mode 100644 index 0000000000..18e1b0d87c --- /dev/null +++ b/esphome/components/pm1006/sensor.py @@ -0,0 +1,44 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, uart +from esphome.const import ( + CONF_ID, + CONF_PM_2_5, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_BLUR, +) + +DEPENDENCIES = ["uart"] + +pm1006_ns = cg.esphome_ns.namespace("pm1006") +PM1006Component = pm1006_ns.class_("PM1006Component", uart.UARTDevice, cg.Component) + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(PM1006Component), + cv.Optional(CONF_PM_2_5): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_BLUR, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + + if CONF_PM_2_5 in config: + sens = await sensor.new_sensor(config[CONF_PM_2_5]) + cg.add(var.set_pm_2_5_sensor(sens)) diff --git a/esphome/const.py b/esphome/const.py index 3eb56c7cf6..b22bc5e1de 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -654,6 +654,7 @@ ICON_ACCOUNT_CHECK = "mdi:account-check" ICON_ARROW_EXPAND_VERTICAL = "mdi:arrow-expand-vertical" ICON_BATTERY = "mdi:battery" ICON_BLUETOOTH = "mdi:bluetooth" +ICON_BLUR = "mdi:blur" ICON_BRIEFCASE_DOWNLOAD = "mdi:briefcase-download" ICON_BRIGHTNESS_5 = "mdi:brightness-5" ICON_BUG = "mdi:bug" From 618cfd9ec524f8b5f15341de04afeb3585f67b44 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 28 Jul 2021 15:34:18 +0200 Subject: [PATCH 1085/1841] Add sensor monetary device_class (#2083) --- esphome/components/sensor/__init__.py | 2 ++ esphome/const.py | 1 + 2 files changed, 3 insertions(+) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 0a0c3a9214..62ed07b408 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -44,6 +44,7 @@ from esphome.const import ( DEVICE_CLASS_ENERGY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_MONETARY, DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_POWER, @@ -66,6 +67,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_ENERGY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_MONETARY, DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TIMESTAMP, diff --git a/esphome/const.py b/esphome/const.py index b22bc5e1de..6702e773c6 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -777,6 +777,7 @@ DEVICE_CLASS_CURRENT = "current" DEVICE_CLASS_ENERGY = "energy" DEVICE_CLASS_HUMIDITY = "humidity" DEVICE_CLASS_ILLUMINANCE = "illuminance" +DEVICE_CLASS_MONETARY = "monetary" DEVICE_CLASS_SIGNAL_STRENGTH = "signal_strength" DEVICE_CLASS_TEMPERATURE = "temperature" DEVICE_CLASS_POWER_FACTOR = "power_factor" From 1652914d39f34fb31fe96e5f6d02db9bd1a51479 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 28 Jul 2021 19:49:43 +0200 Subject: [PATCH 1086/1841] Make light.addressable_set color parameters behave as documented & consistent with elsewhere (#2009) --- .../components/light/addressable_light.cpp | 12 +++---- esphome/components/light/addressable_light.h | 4 +-- .../light/addressable_light_effect.h | 2 +- esphome/components/light/automation.cpp | 15 ++++++++ esphome/components/light/automation.h | 36 ++++++++++++------- esphome/components/light/automation.py | 23 +++++------- .../components/light/esp_color_correction.cpp | 5 +-- esphome/components/light/light_color_values.h | 2 ++ 8 files changed, 61 insertions(+), 38 deletions(-) create mode 100644 esphome/components/light/automation.cpp diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index 4ef293cd03..4eb9124a7b 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -25,16 +25,16 @@ void AddressableLight::call_setup() { } Color esp_color_from_light_color_values(LightColorValues val) { - auto r = static_cast(roundf(val.get_color_brightness() * val.get_red() * 255.0f)); - auto g = static_cast(roundf(val.get_color_brightness() * val.get_green() * 255.0f)); - auto b = static_cast(roundf(val.get_color_brightness() * val.get_blue() * 255.0f)); - auto w = static_cast(roundf(val.get_white() * 255.0f)); + auto r = to_uint8_scale(val.get_color_brightness() * val.get_red()); + auto g = to_uint8_scale(val.get_color_brightness() * val.get_green()); + auto b = to_uint8_scale(val.get_color_brightness() * val.get_blue()); + auto w = to_uint8_scale(val.get_white()); return Color(r, g, b, w); } void AddressableLight::write_state(LightState *state) { auto val = state->current_values; - auto max_brightness = static_cast(roundf(val.get_brightness() * val.get_state() * 255.0f)); + auto max_brightness = to_uint8_scale(val.get_brightness() * val.get_state()); this->correction_.set_local_brightness(max_brightness); this->last_transition_progress_ = 0.0f; @@ -68,7 +68,7 @@ void AddressableLight::write_state(LightState *state) { // our transition will handle brightness, disable brightness in correction. this->correction_.set_local_brightness(255); - target_color *= static_cast(roundf(end_values.get_brightness() * end_values.get_state() * 255.0f)); + target_color *= to_uint8_scale(end_values.get_brightness() * end_values.get_state()); float denom = (1.0f - new_smoothed); float alpha = denom == 0.0f ? 0.0f : (new_smoothed - prev_smoothed) / denom; diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index 460cb88935..f64abb3eec 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -54,8 +54,8 @@ class AddressableLight : public LightOutput, public Component { void set_effect_active(bool effect_active) { this->effect_active_ = effect_active; } void write_state(LightState *state) override; void set_correction(float red, float green, float blue, float white = 1.0f) { - this->correction_.set_max_brightness(Color(uint8_t(roundf(red * 255.0f)), uint8_t(roundf(green * 255.0f)), - uint8_t(roundf(blue * 255.0f)), uint8_t(roundf(white * 255.0f)))); + this->correction_.set_max_brightness( + Color(to_uint8_scale(red), to_uint8_scale(green), to_uint8_scale(blue), to_uint8_scale(white))); } void setup_state(LightState *state) override { this->correction_.calculate_gamma_table(state->get_gamma_correct()); diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index d1ea9e3ff0..25493a30ea 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -337,7 +337,7 @@ class AddressableFlickerEffect : public AddressableLightEffect { } } void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; } - void set_intensity(float intensity) { this->intensity_ = static_cast(roundf(intensity * 255.0f)); } + void set_intensity(float intensity) { this->intensity_ = to_uint8_scale(intensity); } protected: uint32_t update_interval_{16}; diff --git a/esphome/components/light/automation.cpp b/esphome/components/light/automation.cpp new file mode 100644 index 0000000000..8c1785f061 --- /dev/null +++ b/esphome/components/light/automation.cpp @@ -0,0 +1,15 @@ +#include "automation.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace light { + +static const char *const TAG = "light.automation"; + +void addressableset_warn_about_scale(const char *field) { + ESP_LOGW(TAG, "Lambda for parameter %s of light.addressable_set should return values in range 0-1 instead of 0-255.", + field); +} + +} // namespace light +} // namespace esphome diff --git a/esphome/components/light/automation.h b/esphome/components/light/automation.h index e99d5c58bd..02c6163555 100644 --- a/esphome/components/light/automation.h +++ b/esphome/components/light/automation.h @@ -135,39 +135,51 @@ class LightTurnOffTrigger : public Trigger<> { } }; +// This is slightly ugly, but we can't log in headers, and can't make this a static method on AddressableSet +// due to the template. It's just a temporary warning anyway. +void addressableset_warn_about_scale(const char *field); + template class AddressableSet : public Action { public: explicit AddressableSet(LightState *parent) : parent_(parent) {} TEMPLATABLE_VALUE(int32_t, range_from) TEMPLATABLE_VALUE(int32_t, range_to) - TEMPLATABLE_VALUE(uint8_t, color_brightness) - TEMPLATABLE_VALUE(uint8_t, red) - TEMPLATABLE_VALUE(uint8_t, green) - TEMPLATABLE_VALUE(uint8_t, blue) - TEMPLATABLE_VALUE(uint8_t, white) + TEMPLATABLE_VALUE(float, color_brightness) + TEMPLATABLE_VALUE(float, red) + TEMPLATABLE_VALUE(float, green) + TEMPLATABLE_VALUE(float, blue) + TEMPLATABLE_VALUE(float, white) void play(Ts... x) override { auto *out = (AddressableLight *) this->parent_->get_output(); int32_t range_from = this->range_from_.value_or(x..., 0); int32_t range_to = this->range_to_.value_or(x..., out->size() - 1) + 1; - uint8_t remote_color_brightness = - static_cast(roundf(this->parent_->remote_values.get_color_brightness() * 255.0f)); - uint8_t color_brightness = this->color_brightness_.value_or(x..., remote_color_brightness); + uint8_t color_brightness = + to_uint8_scale(this->color_brightness_.value_or(x..., this->parent_->remote_values.get_color_brightness())); auto range = out->range(range_from, range_to); if (this->red_.has_value()) - range.set_red(esp_scale8(this->red_.value(x...), color_brightness)); + range.set_red(esp_scale8(to_uint8_compat(this->red_.value(x...), "red"), color_brightness)); if (this->green_.has_value()) - range.set_green(esp_scale8(this->green_.value(x...), color_brightness)); + range.set_green(esp_scale8(to_uint8_compat(this->green_.value(x...), "green"), color_brightness)); if (this->blue_.has_value()) - range.set_blue(esp_scale8(this->blue_.value(x...), color_brightness)); + range.set_blue(esp_scale8(to_uint8_compat(this->blue_.value(x...), "blue"), color_brightness)); if (this->white_.has_value()) - range.set_white(this->white_.value(x...)); + range.set_white(to_uint8_compat(this->white_.value(x...), "white")); out->schedule_show(); } protected: LightState *parent_; + + // Historically, this action required uint8_t (0-255) for RGBW values from lambdas. Keep compatibility. + static inline uint8_t to_uint8_compat(float value, const char *field) { + if (value > 1.0f) { + addressableset_warn_about_scale(field); + return static_cast(value); + } + return to_uint8_scale(value); + } }; } // namespace light diff --git a/esphome/components/light/automation.py b/esphome/components/light/automation.py index dd1148131a..85741f1ffd 100644 --- a/esphome/components/light/automation.py +++ b/esphome/components/light/automation.py @@ -173,6 +173,7 @@ LIGHT_ADDRESSABLE_SET_ACTION_SCHEMA = cv.Schema( cv.Required(CONF_ID): cv.use_id(AddressableLightState), cv.Optional(CONF_RANGE_FROM): cv.templatable(cv.positive_int), cv.Optional(CONF_RANGE_TO): cv.templatable(cv.positive_int), + cv.Optional(CONF_COLOR_BRIGHTNESS): cv.templatable(cv.percentage), cv.Optional(CONF_RED): cv.templatable(cv.percentage), cv.Optional(CONF_GREEN): cv.templatable(cv.percentage), cv.Optional(CONF_BLUE): cv.templatable(cv.percentage), @@ -194,28 +195,20 @@ async def light_addressable_set_to_code(config, action_id, template_arg, args): templ = await cg.templatable(config[CONF_RANGE_TO], args, cg.int32) cg.add(var.set_range_to(templ)) - def rgbw_to_exp(x): - return int(round(x * 255)) - + if CONF_COLOR_BRIGHTNESS in config: + templ = await cg.templatable(config[CONF_COLOR_BRIGHTNESS], args, cg.float_) + cg.add(var.set_color_brightness(templ)) if CONF_RED in config: - templ = await cg.templatable( - config[CONF_RED], args, cg.uint8, to_exp=rgbw_to_exp - ) + templ = await cg.templatable(config[CONF_RED], args, cg.float_) cg.add(var.set_red(templ)) if CONF_GREEN in config: - templ = await cg.templatable( - config[CONF_GREEN], args, cg.uint8, to_exp=rgbw_to_exp - ) + templ = await cg.templatable(config[CONF_GREEN], args, cg.float_) cg.add(var.set_green(templ)) if CONF_BLUE in config: - templ = await cg.templatable( - config[CONF_BLUE], args, cg.uint8, to_exp=rgbw_to_exp - ) + templ = await cg.templatable(config[CONF_BLUE], args, cg.float_) cg.add(var.set_blue(templ)) if CONF_WHITE in config: - templ = await cg.templatable( - config[CONF_WHITE], args, cg.uint8, to_exp=rgbw_to_exp - ) + templ = await cg.templatable(config[CONF_WHITE], args, cg.float_) cg.add(var.set_white(templ)) return var diff --git a/esphome/components/light/esp_color_correction.cpp b/esphome/components/light/esp_color_correction.cpp index 19a2af3da1..e5e68264cc 100644 --- a/esphome/components/light/esp_color_correction.cpp +++ b/esphome/components/light/esp_color_correction.cpp @@ -1,4 +1,5 @@ #include "esp_color_correction.h" +#include "light_color_values.h" #include "esphome/core/log.h" namespace esphome { @@ -7,7 +8,7 @@ namespace light { void ESPColorCorrection::calculate_gamma_table(float gamma) { for (uint16_t i = 0; i < 256; i++) { // corrected = val ^ gamma - auto corrected = static_cast(roundf(255.0f * gamma_correct(i / 255.0f, gamma))); + auto corrected = to_uint8_scale(gamma_correct(i / 255.0f, gamma)); this->gamma_table_[i] = corrected; } if (gamma == 0.0f) { @@ -17,7 +18,7 @@ void ESPColorCorrection::calculate_gamma_table(float gamma) { } for (uint16_t i = 0; i < 256; i++) { // val = corrected ^ (1/gamma) - auto uncorrected = static_cast(roundf(255.0f * powf(i / 255.0f, 1.0f / gamma))); + auto uncorrected = to_uint8_scale(powf(i / 255.0f, 1.0f / gamma)); this->gamma_reverse_table_[i] = uncorrected; } } diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index 54dcaea5a3..344411b75e 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -11,6 +11,8 @@ namespace esphome { namespace light { +inline static uint8_t to_uint8_scale(float x) { return static_cast(roundf(x * 255.0f)); } + /** This class represents the color state for a light object. * * All values in this class (except color temperature) are represented using floats in the range From 5c3a6164bb39a0c83a04aca09976e7f11ffdf848 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jul 2021 21:23:24 +0200 Subject: [PATCH 1087/1841] Bump pylint from 2.9.5 to 2.9.6 (#2087) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index b0e24b5b2f..684582bd4c 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.9.5 +pylint==2.9.6 flake8==3.9.2 black==21.7b0 pexpect==4.8.0 From 31d6a54b06c6f10c06b87fb4ef66b01c58ab0e87 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 28 Jul 2021 21:23:41 +0200 Subject: [PATCH 1088/1841] Fix PID climate breaks when restoring old modes (#2086) --- esphome/components/pid/pid_climate.cpp | 13 ++----------- esphome/components/pid/pid_climate.h | 1 - 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index ef8a4df962..4c7d92e26d 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -37,7 +37,7 @@ void PIDClimate::control(const climate::ClimateCall &call) { // If switching to off mode, set output immediately if (this->mode == climate::CLIMATE_MODE_OFF) - this->handle_non_auto_mode_(); + this->write_output_(0.0f); this->publish_state(); } @@ -98,15 +98,6 @@ void PIDClimate::write_output_(float value) { } this->pid_computed_callback_.call(); } -void PIDClimate::handle_non_auto_mode_() { - // in non-auto mode, switch directly to appropriate action - // - OFF mode -> Output at 0% - if (this->mode == climate::CLIMATE_MODE_OFF) { - this->write_output_(0.0); - } else { - assert(false); - } -} void PIDClimate::update_pid_() { float value; if (isnan(this->current_temperature) || isnan(this->target_temperature)) { @@ -135,7 +126,7 @@ void PIDClimate::update_pid_() { } if (this->mode == climate::CLIMATE_MODE_OFF) { - this->handle_non_auto_mode_(); + this->write_output_(0.0); } else { this->write_output_(value); } diff --git a/esphome/components/pid/pid_climate.h b/esphome/components/pid/pid_climate.h index f11d768867..ff301386b6 100644 --- a/esphome/components/pid/pid_climate.h +++ b/esphome/components/pid/pid_climate.h @@ -56,7 +56,6 @@ class PIDClimate : public climate::Climate, public Component { bool supports_heat_() const { return this->heat_output_ != nullptr; } void write_output_(float value); - void handle_non_auto_mode_(); /// The sensor used for getting the current temperature sensor::Sensor *sensor_; From 246950159d3e5782730a328ac28b77aaa1e9ccdb Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 28 Jul 2021 21:24:10 +0200 Subject: [PATCH 1089/1841] Bump ESPAsyncWebServer-esphome to 1.3.0 (#2075) --- esphome/components/web_server_base/__init__.py | 4 ++-- platformio.ini | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 37f3c989e4..8f64c473f3 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -25,5 +25,5 @@ async def to_code(config): if CORE.is_esp32: cg.add_library("FS", None) - # https://github.com/OttoWinter/ESPAsyncWebServer/blob/master/library.json - cg.add_library("ESPAsyncWebServer-esphome", "1.2.7") + # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json + cg.add_library("esphome/ESPAsyncWebServer-esphome", "1.3.0") diff --git a/platformio.ini b/platformio.ini index ba7724ad24..e1fb845358 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,7 +12,7 @@ include_dir = include lib_deps = 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.6.2 1655@1.0.2 ; TinyGPSPlus (has name conflict) From 316777f757b087553dc3595ed726824c9a0a6255 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 29 Jul 2021 07:53:54 +1200 Subject: [PATCH 1090/1841] HLW8012 - Dump energy sensor config (#2082) --- esphome/components/hlw8012/hlw8012.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/hlw8012/hlw8012.cpp b/esphome/components/hlw8012/hlw8012.cpp index 79c25a45b0..ecdaa07ab2 100644 --- a/esphome/components/hlw8012/hlw8012.cpp +++ b/esphome/components/hlw8012/hlw8012.cpp @@ -45,6 +45,7 @@ void HLW8012Component::dump_config() { LOG_SENSOR(" ", "Voltage", this->voltage_sensor_) LOG_SENSOR(" ", "Current", this->current_sensor_) LOG_SENSOR(" ", "Power", this->power_sensor_) + LOG_SENSOR(" ", "Energy", this->energy_sensor_) } float HLW8012Component::get_setup_priority() const { return setup_priority::DATA; } void HLW8012Component::update() { From 513066ba520ac698b7a88e0cefccc93950cc6fdf Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 29 Jul 2021 19:46:15 +1200 Subject: [PATCH 1091/1841] Use sensor_schema for total_daily_energy (#2090) Co-authored-by: Otto Winter --- .../components/total_daily_energy/sensor.py | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index b70a0cece9..7fdd176d42 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -1,7 +1,15 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, time -from esphome.const import CONF_ID, CONF_TIME_ID +from esphome.const import ( + CONF_ID, + CONF_TIME_ID, + DEVICE_CLASS_ENERGY, + ICON_EMPTY, + LAST_RESET_TYPE_AUTO, + STATE_CLASS_MEASUREMENT, + UNIT_EMPTY, +) DEPENDENCIES = ["time"] @@ -11,13 +19,24 @@ TotalDailyEnergy = total_daily_energy_ns.class_( "TotalDailyEnergy", sensor.Sensor, cg.Component ) -CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(TotalDailyEnergy), - cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), - cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + sensor.sensor_schema( + UNIT_EMPTY, + ICON_EMPTY, + 0, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, + ) + .extend( + { + cv.GenerateID(): cv.declare_id(TotalDailyEnergy), + cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), + cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): From bfac6607d16cf6f7d1b5a3874a1a0793cc916e33 Mon Sep 17 00:00:00 2001 From: "John K. Luebs" Date: Tue, 27 Jul 2021 21:01:15 -0500 Subject: [PATCH 1092/1841] More Tuya MCU robustness (#2080) --- esphome/components/tuya/tuya.cpp | 11 +++++++++-- esphome/components/tuya/tuya.h | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 916a550675..325f12a58d 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -7,7 +7,7 @@ namespace esphome { namespace tuya { static const char *const TAG = "tuya"; -static const int COMMAND_DELAY = 50; +static const int COMMAND_DELAY = 10; static const int RECEIVE_TIMEOUT = 300; void Tuya::setup() { @@ -114,6 +114,8 @@ void Tuya::handle_char_(uint8_t c) { this->rx_message_.push_back(c); if (!this->validate_message_()) { this->rx_message_.clear(); + } else { + this->last_rx_char_timestamp_ = millis(); } } @@ -357,7 +359,12 @@ void Tuya::send_raw_command_(TuyaCommand command) { } void Tuya::process_command_queue_() { - uint32_t delay = millis() - this->last_command_timestamp_; + uint32_t now = millis(); + uint32_t delay = now - this->last_command_timestamp_; + + if (now - this->last_rx_char_timestamp_ > RECEIVE_TIMEOUT) { + this->rx_message_.clear(); + } if (this->expected_response_.has_value() && delay > RECEIVE_TIMEOUT) { this->expected_response_.reset(); diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 68decf7e9e..7ce4be4315 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -107,6 +107,7 @@ class Tuya : public Component, public uart::UARTDevice { int gpio_status_ = -1; int gpio_reset_ = -1; uint32_t last_command_timestamp_ = 0; + uint32_t last_rx_char_timestamp_ = 0; std::string product_ = ""; std::vector listeners_; std::vector datapoints_; From c2f9ed7c59ded423a25710cc5841fcfc948b7fa8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 28 Jul 2021 15:18:31 +1200 Subject: [PATCH 1093/1841] Bump esphome dashboard to 20210728.0 (#2081) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ab54828194..561bf0f4d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210719.0 +esphome-dashboard==20210728.0 From be4c71885932266aaff51b94f3127943ed5072b2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 29 Jul 2021 07:53:54 +1200 Subject: [PATCH 1094/1841] HLW8012 - Dump energy sensor config (#2082) --- esphome/components/hlw8012/hlw8012.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/hlw8012/hlw8012.cpp b/esphome/components/hlw8012/hlw8012.cpp index 79c25a45b0..ecdaa07ab2 100644 --- a/esphome/components/hlw8012/hlw8012.cpp +++ b/esphome/components/hlw8012/hlw8012.cpp @@ -45,6 +45,7 @@ void HLW8012Component::dump_config() { LOG_SENSOR(" ", "Voltage", this->voltage_sensor_) LOG_SENSOR(" ", "Current", this->current_sensor_) LOG_SENSOR(" ", "Power", this->power_sensor_) + LOG_SENSOR(" ", "Energy", this->energy_sensor_) } float HLW8012Component::get_setup_priority() const { return setup_priority::DATA; } void HLW8012Component::update() { From 62f3039d82c2996ee3e8f2e3a7d70e5a917adbdb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 29 Jul 2021 19:46:15 +1200 Subject: [PATCH 1095/1841] Use sensor_schema for total_daily_energy (#2090) Co-authored-by: Otto Winter --- .../components/total_daily_energy/sensor.py | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index b70a0cece9..7fdd176d42 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -1,7 +1,15 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, time -from esphome.const import CONF_ID, CONF_TIME_ID +from esphome.const import ( + CONF_ID, + CONF_TIME_ID, + DEVICE_CLASS_ENERGY, + ICON_EMPTY, + LAST_RESET_TYPE_AUTO, + STATE_CLASS_MEASUREMENT, + UNIT_EMPTY, +) DEPENDENCIES = ["time"] @@ -11,13 +19,24 @@ TotalDailyEnergy = total_daily_energy_ns.class_( "TotalDailyEnergy", sensor.Sensor, cg.Component ) -CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(TotalDailyEnergy), - cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), - cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + sensor.sensor_schema( + UNIT_EMPTY, + ICON_EMPTY, + 0, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, + ) + .extend( + { + cv.GenerateID(): cv.declare_id(TotalDailyEnergy), + cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), + cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): From 456824669ff1bdfac7c0ec6fb4a0011c2683b407 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 29 Jul 2021 19:51:17 +1200 Subject: [PATCH 1096/1841] Bump version to v1.20.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a8e9e9cfc4..1c1e9eb3a5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.1" +__version__ = "1.20.2" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 8dbac20f8b50e0cf81026739efb14cf570cf68d3 Mon Sep 17 00:00:00 2001 From: Nicholas Peters Date: Thu, 29 Jul 2021 04:57:52 -0400 Subject: [PATCH 1097/1841] Add SDP3x sensor (#2064) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Otto Winter --- CODEOWNERS | 2 + esphome/components/sdp3x/__init__.py | 0 esphome/components/sdp3x/sdp3x.cpp | 124 +++++++++++++++++++++++++++ esphome/components/sdp3x/sdp3x.h | 30 +++++++ esphome/components/sdp3x/sensor.py | 40 +++++++++ esphome/components/tmp117/sensor.py | 1 + tests/test1.yaml | 5 ++ 7 files changed, 202 insertions(+) create mode 100644 esphome/components/sdp3x/__init__.py create mode 100644 esphome/components/sdp3x/sdp3x.cpp create mode 100644 esphome/components/sdp3x/sdp3x.h create mode 100644 esphome/components/sdp3x/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 844bea763e..21f464863d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -97,6 +97,7 @@ esphome/components/rf_bridge/* @jesserockz esphome/components/rtttl/* @glmnet esphome/components/script/* @esphome/core esphome/components/sdm_meter/* @jesserockz @polyfaces +esphome/components/sdp3x/* @Azimath esphome/components/selec_meter/* @sourabhjaiswal esphome/components/sensor/* @esphome/core esphome/components/sgp40/* @SenexCrenshaw @@ -128,6 +129,7 @@ esphome/components/thermostat/* @kbx81 esphome/components/time/* @OttoWinter esphome/components/tm1637/* @glmnet esphome/components/tmp102/* @timsavage +esphome/components/tmp117/* @Azimath esphome/components/tof10120/* @wstrzalka esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/climate/* @jesserockz diff --git a/esphome/components/sdp3x/__init__.py b/esphome/components/sdp3x/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/sdp3x/sdp3x.cpp b/esphome/components/sdp3x/sdp3x.cpp new file mode 100644 index 0000000000..5e6c5887ac --- /dev/null +++ b/esphome/components/sdp3x/sdp3x.cpp @@ -0,0 +1,124 @@ +#include "sdp3x.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace sdp3x { + +static const char *const TAG = "sdp3x.sensor"; +static const uint8_t SDP3X_SOFT_RESET[2] = {0x00, 0x06}; +static const uint8_t SDP3X_READ_ID1[2] = {0x36, 0x7C}; +static const uint8_t SDP3X_READ_ID2[2] = {0xE1, 0x02}; +static const uint8_t SDP3X_START_DP_AVG[2] = {0x36, 0x15}; +static const uint8_t SDP3X_STOP_MEAS[2] = {0x3F, 0xF9}; + +void SDP3XComponent::update() { this->read_pressure_(); } + +void SDP3XComponent::setup() { + ESP_LOGD(TAG, "Setting up SDP3X..."); + + if (!this->write_bytes_raw(SDP3X_STOP_MEAS, 2)) { + ESP_LOGW(TAG, "Stop SDP3X failed!"); // This sometimes fails for no good reason + } + + if (!this->write_bytes_raw(SDP3X_SOFT_RESET, 2)) { + ESP_LOGW(TAG, "Soft Reset SDP3X failed!"); // This sometimes fails for no good reason + } + + delay_microseconds_accurate(20000); + + if (!this->write_bytes_raw(SDP3X_READ_ID1, 2)) { + ESP_LOGE(TAG, "Read ID1 SDP3X failed!"); + this->mark_failed(); + return; + } + if (!this->write_bytes_raw(SDP3X_READ_ID2, 2)) { + ESP_LOGE(TAG, "Read ID2 SDP3X failed!"); + this->mark_failed(); + return; + } + + uint8_t data[18]; + if (!this->read_bytes_raw(data, 18)) { + ESP_LOGE(TAG, "Read ID SDP3X failed!"); + this->mark_failed(); + return; + } + + if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]))) { + ESP_LOGE(TAG, "CRC ID SDP3X failed!"); + this->mark_failed(); + return; + } + + if (data[3] == 0x01) { + ESP_LOGCONFIG(TAG, "SDP3X is SDP31"); + pressure_scale_factor_ = 60.0f * 100.0f; // Scale factors converted to hPa per count + } else if (data[3] == 0x02) { + ESP_LOGCONFIG(TAG, "SDP3X is SDP32"); + pressure_scale_factor_ = 240.0f * 100.0f; + } + + if (!this->write_bytes_raw(SDP3X_START_DP_AVG, 2)) { + ESP_LOGE(TAG, "Start Measurements SDP3X failed!"); + this->mark_failed(); + return; + } + ESP_LOGCONFIG(TAG, "SDP3X started!"); +} +void SDP3XComponent::dump_config() { + LOG_SENSOR(" ", "SDP3X", this); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, " Connection with SDP3X failed!"); + } + LOG_UPDATE_INTERVAL(this); +} + +void SDP3XComponent::read_pressure_() { + uint8_t data[9]; + if (!this->read_bytes_raw(data, 9)) { + ESP_LOGW(TAG, "Couldn't read SDP3X data!"); + this->status_set_warning(); + return; + } + + if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]) && check_crc_(&data[6], 2, data[8]))) { + ESP_LOGW(TAG, "Invalid SDP3X data!"); + this->status_set_warning(); + return; + } + + int16_t pressure_raw = encode_uint16(data[0], data[1]); + float pressure = pressure_raw / pressure_scale_factor_; + ESP_LOGV(TAG, "Got raw pressure=%d, scale factor =%.3f ", pressure_raw, pressure_scale_factor_); + ESP_LOGD(TAG, "Got Pressure=%.3f hPa", pressure); + + this->publish_state(pressure); + this->status_clear_warning(); +} + +float SDP3XComponent::get_setup_priority() const { return setup_priority::DATA; } + +// Check CRC function from SDP3X sample code provided by sensirion +// Returns true if a checksum is OK +bool SDP3XComponent::check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum) { + uint8_t crc = 0xFF; + + // calculates 8-Bit checksum with given polynomial 0x31 (x^8 + x^5 + x^4 + 1) + for (int i = 0; i < size; i++) { + crc ^= (data[i]); + for (uint8_t bit = 8; bit > 0; --bit) { + if (crc & 0x80) + crc = (crc << 1) ^ 0x31; + else + crc = (crc << 1); + } + } + + // verify checksum + return (crc == checksum); +} + +} // namespace sdp3x +} // namespace esphome diff --git a/esphome/components/sdp3x/sdp3x.h b/esphome/components/sdp3x/sdp3x.h new file mode 100644 index 0000000000..51c9973c61 --- /dev/null +++ b/esphome/components/sdp3x/sdp3x.h @@ -0,0 +1,30 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace sdp3x { + +class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor { + public: + /// Schedule temperature+pressure readings. + void update() override; + /// Setup the sensor and test for a connection. + void setup() override; + void dump_config() override; + + float get_setup_priority() const override; + + protected: + /// Internal method to read the pressure from the component after it has been scheduled. + void read_pressure_(); + + bool check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum); + + float pressure_scale_factor_ = 0.0f; // hPa per count +}; + +} // namespace sdp3x +} // namespace esphome diff --git a/esphome/components/sdp3x/sensor.py b/esphome/components/sdp3x/sensor.py new file mode 100644 index 0000000000..68e3d2812c --- /dev/null +++ b/esphome/components/sdp3x/sensor.py @@ -0,0 +1,40 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_PRESSURE, + ICON_EMPTY, + STATE_CLASS_MEASUREMENT, + UNIT_HECTOPASCAL, +) + +DEPENDENCIES = ["i2c"] +CODEOWNERS = ["@Azimath"] + +sdp3x_ns = cg.esphome_ns.namespace("sdp3x") +SDP3XComponent = sdp3x_ns.class_("SDP3XComponent", cg.PollingComponent, i2c.I2CDevice) + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + UNIT_HECTOPASCAL, + ICON_EMPTY, + 3, + DEVICE_CLASS_PRESSURE, + STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.GenerateID(): cv.declare_id(SDP3XComponent), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x21)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + await sensor.register_sensor(var, config) diff --git a/esphome/components/tmp117/sensor.py b/esphome/components/tmp117/sensor.py index a5fc027b20..c3fed06953 100644 --- a/esphome/components/tmp117/sensor.py +++ b/esphome/components/tmp117/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( ) DEPENDENCIES = ["i2c"] +CODEOWNERS = ["@Azimath"] tmp117_ns = cg.esphome_ns.namespace("tmp117") TMP117Component = tmp117_ns.class_( diff --git a/tests/test1.yaml b/tests/test1.yaml index f9fe7d106d..b25852c93d 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -922,6 +922,11 @@ sensor: id: ph_ezo address: 99 unit_of_measurement: 'pH' + - platform: sdp3x + name: "HVAC Filter Pressure drop" + id: filter_pressure + update_interval: 5s + accuracy_decimals: 3 - platform: cs5460a id: cs5460a1 current: From c6c857dfff1b52b7925ba6a87f272a7890ae6dfb Mon Sep 17 00:00:00 2001 From: rnauber <7414650+rnauber@users.noreply.github.com> Date: Thu, 29 Jul 2021 11:16:04 +0200 Subject: [PATCH 1098/1841] Add support for the TLC5947 24-Channel, 12-Bit PWM LED Driver (#2066) Co-authored-by: Richard Nauber --- CODEOWNERS | 1 + esphome/components/tlc5947/__init__.py | 50 +++++++++++++++++++ esphome/components/tlc5947/output.py | 28 +++++++++++ esphome/components/tlc5947/tlc5947.cpp | 65 ++++++++++++++++++++++++ esphome/components/tlc5947/tlc5947.h | 69 ++++++++++++++++++++++++++ tests/test5.yaml | 10 ++++ 6 files changed, 223 insertions(+) create mode 100644 esphome/components/tlc5947/__init__.py create mode 100644 esphome/components/tlc5947/output.py create mode 100644 esphome/components/tlc5947/tlc5947.cpp create mode 100644 esphome/components/tlc5947/tlc5947.h diff --git a/CODEOWNERS b/CODEOWNERS index 21f464863d..40883d39b0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -127,6 +127,7 @@ esphome/components/tcl112/* @glmnet esphome/components/teleinfo/* @0hax esphome/components/thermostat/* @kbx81 esphome/components/time/* @OttoWinter +esphome/components/tlc5947/* @rnauber esphome/components/tm1637/* @glmnet esphome/components/tmp102/* @timsavage esphome/components/tmp117/* @Azimath diff --git a/esphome/components/tlc5947/__init__.py b/esphome/components/tlc5947/__init__.py new file mode 100644 index 0000000000..84380bdace --- /dev/null +++ b/esphome/components/tlc5947/__init__.py @@ -0,0 +1,50 @@ +# this component is for the "TLC5947 24-Channel, 12-Bit PWM LED Driver" [https://www.ti.com/lit/ds/symlink/tlc5947.pdf], +# which is used e.g. on [https://www.adafruit.com/product/1429]. The code is based on the components sm2135 and sm26716. + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.const import ( + CONF_CLOCK_PIN, + CONF_DATA_PIN, + CONF_ID, + CONF_NUM_CHIPS, +) + +CONF_LAT_PIN = "lat_pin" +CONF_OE_PIN = "oe_pin" + +AUTO_LOAD = ["output"] +CODEOWNERS = ["@rnauber"] + +tlc5947_ns = cg.esphome_ns.namespace("tlc5947") +TLC5947 = tlc5947_ns.class_("TLC5947", cg.Component) + +MULTI_CONF = True +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(TLC5947), + cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_LAT_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_OE_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_NUM_CHIPS, default=1): cv.int_range(min=1, max=85), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + data = await cg.gpio_pin_expression(config[CONF_DATA_PIN]) + cg.add(var.set_data_pin(data)) + clock = await cg.gpio_pin_expression(config[CONF_CLOCK_PIN]) + cg.add(var.set_clock_pin(clock)) + lat = await cg.gpio_pin_expression(config[CONF_LAT_PIN]) + cg.add(var.set_lat_pin(lat)) + if CONF_OE_PIN in config: + outenable = await cg.gpio_pin_expression(config[CONF_OE_PIN]) + cg.add(var.set_outenable_pin(outenable)) + + cg.add(var.set_num_chips(config[CONF_NUM_CHIPS])) diff --git a/esphome/components/tlc5947/output.py b/esphome/components/tlc5947/output.py new file mode 100644 index 0000000000..ece47fa63d --- /dev/null +++ b/esphome/components/tlc5947/output.py @@ -0,0 +1,28 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import output +from esphome.const import CONF_CHANNEL, CONF_ID +from . import TLC5947 + +DEPENDENCIES = ["tlc5947"] +CODEOWNERS = ["@rnauber"] + +Channel = TLC5947.class_("Channel", output.FloatOutput) + +CONF_TLC5947_ID = "tlc5947_id" +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.GenerateID(CONF_TLC5947_ID): cv.use_id(TLC5947), + cv.Required(CONF_ID): cv.declare_id(Channel), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=65535), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await output.register_output(var, config) + + parent = await cg.get_variable(config[CONF_TLC5947_ID]) + cg.add(var.set_parent(parent)) + cg.add(var.set_channel(config[CONF_CHANNEL])) diff --git a/esphome/components/tlc5947/tlc5947.cpp b/esphome/components/tlc5947/tlc5947.cpp new file mode 100644 index 0000000000..a7e08c8341 --- /dev/null +++ b/esphome/components/tlc5947/tlc5947.cpp @@ -0,0 +1,65 @@ +#include "tlc5947.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace tlc5947 { + +static const char *const TAG = "tlc5947"; + +void TLC5947::setup() { + this->data_pin_->setup(); + this->data_pin_->digital_write(true); + this->clock_pin_->setup(); + this->clock_pin_->digital_write(true); + this->lat_pin_->setup(); + this->lat_pin_->digital_write(true); + if (this->outenable_pin_ != nullptr) { + this->outenable_pin_->setup(); + this->outenable_pin_->digital_write(false); + } + + this->pwm_amounts_.resize(this->num_chips_ * N_CHANNELS_PER_CHIP, 0); + + ESP_LOGCONFIG(TAG, "Done setting up TLC5947 output component."); +} +void TLC5947::dump_config() { + ESP_LOGCONFIG(TAG, "TLC5947:"); + LOG_PIN(" Data Pin: ", this->data_pin_); + LOG_PIN(" Clock Pin: ", this->clock_pin_); + LOG_PIN(" LAT Pin: ", this->lat_pin_); + if (this->outenable_pin_ != nullptr) + LOG_PIN(" OE Pin: ", this->outenable_pin_); + ESP_LOGCONFIG(TAG, " Number of chips: %u", this->num_chips_); +} + +void TLC5947::loop() { + if (!this->update_) + return; + + this->lat_pin_->digital_write(false); + + // push the data out, MSB first, 12 bit word per channel, 24 channels per chip + for (int32_t ch = N_CHANNELS_PER_CHIP * num_chips_ - 1; ch >= 0; ch--) { + uint16_t word = pwm_amounts_[ch]; + for (uint8_t bit = 0; bit < 12; bit++) { + this->clock_pin_->digital_write(false); + this->data_pin_->digital_write(word & 0x800); + word <<= 1; + + this->clock_pin_->digital_write(true); + this->clock_pin_->digital_write(true); // TWH0>12ns, so we should be fine using this as delay + } + } + + this->clock_pin_->digital_write(false); + + // latch the values, so they will be applied + this->lat_pin_->digital_write(true); + delayMicroseconds(1); // TWH1 > 30ns + this->lat_pin_->digital_write(false); + + this->update_ = false; +} + +} // namespace tlc5947 +} // namespace esphome diff --git a/esphome/components/tlc5947/tlc5947.h b/esphome/components/tlc5947/tlc5947.h new file mode 100644 index 0000000000..b608b861e7 --- /dev/null +++ b/esphome/components/tlc5947/tlc5947.h @@ -0,0 +1,69 @@ +#pragma once +// TLC5947 24-Channel, 12-Bit PWM LED Driver +// https://www.ti.com/lit/ds/symlink/tlc5947.pdf + +#include "esphome/core/component.h" +#include "esphome/core/esphal.h" +#include "esphome/components/output/float_output.h" + +namespace esphome { +namespace tlc5947 { + +class TLC5947 : public Component { + public: + class Channel; + + const uint8_t N_CHANNELS_PER_CHIP = 24; + + void set_data_pin(GPIOPin *data_pin) { data_pin_ = data_pin; } + void set_clock_pin(GPIOPin *clock_pin) { clock_pin_ = clock_pin; } + void set_lat_pin(GPIOPin *lat_pin) { lat_pin_ = lat_pin; } + void set_outenable_pin(GPIOPin *outenable_pin) { outenable_pin_ = outenable_pin; } + void set_num_chips(uint8_t num_chips) { num_chips_ = num_chips; } + + void setup() override; + + void dump_config() override; + + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + /// Send new values if they were updated. + void loop() override; + + class Channel : public output::FloatOutput { + public: + void set_parent(TLC5947 *parent) { parent_ = parent; } + void set_channel(uint8_t channel) { channel_ = channel; } + + protected: + void write_state(float state) override { + auto amount = static_cast(state * 0xfff); + this->parent_->set_channel_value_(this->channel_, amount); + } + + TLC5947 *parent_; + uint8_t channel_; + }; + + protected: + void set_channel_value_(uint16_t channel, uint16_t value) { + if (channel >= this->num_chips_ * N_CHANNELS_PER_CHIP) + return; + if (this->pwm_amounts_[channel] != value) { + this->update_ = true; + } + this->pwm_amounts_[channel] = value; + } + + GPIOPin *data_pin_; + GPIOPin *clock_pin_; + GPIOPin *lat_pin_; + GPIOPin *outenable_pin_{nullptr}; + uint8_t num_chips_; + + std::vector pwm_amounts_; + bool update_{true}; +}; + +} // namespace tlc5947 +} // namespace esphome diff --git a/tests/test5.yaml b/tests/test5.yaml index 5d1c99d777..e117b3244f 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -30,11 +30,21 @@ binary_sensor: 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 + esp32_ble: esp32_ble_server: From 5e2d4e332aaa40559e1120577cdec1c3fb990e0b Mon Sep 17 00:00:00 2001 From: Tyler Menezes Date: Thu, 29 Jul 2021 05:24:36 -0400 Subject: [PATCH 1099/1841] Add T6615 (#1170) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/t6615/__init__.py | 0 esphome/components/t6615/sensor.py | 48 +++++++++++++++++ esphome/components/t6615/t6615.cpp | 81 ++++++++++++++++++++++++++++ esphome/components/t6615/t6615.h | 42 +++++++++++++++ tests/test5.yaml | 17 ++++-- 6 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 esphome/components/t6615/__init__.py create mode 100644 esphome/components/t6615/sensor.py create mode 100644 esphome/components/t6615/t6615.cpp create mode 100644 esphome/components/t6615/t6615.h diff --git a/CODEOWNERS b/CODEOWNERS index 40883d39b0..b3a4668f3a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -122,6 +122,7 @@ esphome/components/st7789v/* @kbx81 esphome/components/substitutions/* @esphome/core esphome/components/sun/* @OttoWinter esphome/components/switch/* @esphome/core +esphome/components/t6615/* @tylermenezes esphome/components/tca9548a/* @andreashergert1984 esphome/components/tcl112/* @glmnet esphome/components/teleinfo/* @0hax diff --git a/esphome/components/t6615/__init__.py b/esphome/components/t6615/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/t6615/sensor.py b/esphome/components/t6615/sensor.py new file mode 100644 index 0000000000..efe4994a97 --- /dev/null +++ b/esphome/components/t6615/sensor.py @@ -0,0 +1,48 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, uart +from esphome.const import ( + CONF_CO2, + CONF_ID, + DEVICE_CLASS_CARBON_DIOXIDE, + ICON_EMPTY, + STATE_CLASS_MEASUREMENT, + UNIT_PARTS_PER_MILLION, +) + +CODEOWNERS = ["@tylermenezes"] +DEPENDENCIES = ["uart"] + +t6615_ns = cg.esphome_ns.namespace("t6615") +T6615Component = t6615_ns.class_("T6615Component", cg.PollingComponent, uart.UARTDevice) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(T6615Component), + cv.Required(CONF_CO2): sensor.sensor_schema( + UNIT_PARTS_PER_MILLION, + ICON_EMPTY, + 0, + DEVICE_CLASS_CARBON_DIOXIDE, + STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(uart.UART_DEVICE_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "t6615", baud_rate=19200, require_rx=True, require_tx=True +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + + if CONF_CO2 in config: + sens = await sensor.new_sensor(config[CONF_CO2]) + cg.add(var.set_co2_sensor(sens)) diff --git a/esphome/components/t6615/t6615.cpp b/esphome/components/t6615/t6615.cpp new file mode 100644 index 0000000000..09ff61827c --- /dev/null +++ b/esphome/components/t6615/t6615.cpp @@ -0,0 +1,81 @@ +#include "t6615.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace t6615 { + +static const char *const TAG = "t6615"; + +static const uint8_t T6615_RESPONSE_BUFFER_LENGTH = 32; +static const uint8_t T6615_MAGIC = 0xFF; +static const uint8_t T6615_ADDR_HOST = 0xFA; +static const uint8_t T6615_ADDR_SENSOR = 0xFE; +static const uint8_t T6615_COMMAND_GET_PPM[] = {0x02, 0x03}; +static const uint8_t T6615_COMMAND_GET_SERIAL[] = {0x02, 0x01}; +static const uint8_t T6615_COMMAND_GET_VERSION[] = {0x02, 0x0D}; +static const uint8_t T6615_COMMAND_GET_ELEVATION[] = {0x02, 0x0F}; +static const uint8_t T6615_COMMAND_GET_ABC[] = {0xB7, 0x00}; +static const uint8_t T6615_COMMAND_ENABLE_ABC[] = {0xB7, 0x01}; +static const uint8_t T6615_COMMAND_DISABLE_ABC[] = {0xB7, 0x02}; +static const uint8_t T6615_COMMAND_SET_ELEVATION[] = {0x03, 0x0F}; + +void T6615Component::loop() { + if (!this->available()) + return; + + // Read header + uint8_t header[3]; + this->read_array(header, 3); + if (header[0] != T6615_MAGIC || header[1] != T6615_ADDR_HOST) { + ESP_LOGW(TAG, "Reading data from T6615 failed!"); + while (this->available()) + this->read(); // Clear the incoming buffer + this->status_set_warning(); + return; + } + + // Read body + uint8_t length = header[2]; + uint8_t response[T6615_RESPONSE_BUFFER_LENGTH]; + this->read_array(response, length); + + this->status_clear_warning(); + + switch (this->command_) { + case T6615Command::GET_PPM: { + const uint16_t ppm = encode_uint16(response[0], response[1]); + ESP_LOGD(TAG, "T6615 Received CO₂=%uppm", ppm); + this->co2_sensor_->publish_state(ppm); + break; + } + default: + break; + } + + this->command_ = T6615Command::NONE; +} + +void T6615Component::update() { this->query_ppm_(); } + +void T6615Component::query_ppm_() { + if (this->co2_sensor_ == nullptr || this->command_ != T6615Command::NONE) { + return; + } + + this->command_ = T6615Command::GET_PPM; + + this->write_byte(T6615_MAGIC); + this->write_byte(T6615_ADDR_SENSOR); + this->write_byte(sizeof(T6615_COMMAND_GET_PPM)); + this->write_array(T6615_COMMAND_GET_PPM, sizeof(T6615_COMMAND_GET_PPM)); +} + +float T6615Component::get_setup_priority() const { return setup_priority::DATA; } +void T6615Component::dump_config() { + ESP_LOGCONFIG(TAG, "T6615:"); + LOG_SENSOR(" ", "CO2", this->co2_sensor_); + this->check_uart_settings(19200); +} + +} // namespace t6615 +} // namespace esphome diff --git a/esphome/components/t6615/t6615.h b/esphome/components/t6615/t6615.h new file mode 100644 index 0000000000..a7da3b4cf6 --- /dev/null +++ b/esphome/components/t6615/t6615.h @@ -0,0 +1,42 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace t6615 { + +enum class T6615Command : uint8_t { + NONE = 0, + GET_PPM, + GET_SERIAL, + GET_VERSION, + GET_ELEVATION, + GET_ABC, + ENABLE_ABC, + DISABLE_ABC, + SET_ELEVATION, +}; + +class T6615Component : public PollingComponent, public uart::UARTDevice { + public: + float get_setup_priority() const override; + + void loop() override; + void update() override; + void dump_config() override; + + void set_co2_sensor(sensor::Sensor *co2_sensor) { this->co2_sensor_ = co2_sensor; } + + protected: + void query_ppm_(); + + T6615Command command_ = T6615Command::NONE; + + sensor::Sensor *co2_sensor_{nullptr}; +}; + +} // namespace t6615 +} // namespace esphome diff --git a/tests/test5.yaml b/tests/test5.yaml index e117b3244f..23f8ae6c4d 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -19,11 +19,18 @@ ota: logger: uart: - tx_pin: 1 - rx_pin: 3 - baud_rate: 9600 + - 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 @@ -109,3 +116,7 @@ sensor: 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 From ee19ef1aaca41cd4d8434a3b75b62751a9aab280 Mon Sep 17 00:00:00 2001 From: Mike Meessen Date: Thu, 29 Jul 2021 11:37:31 +0200 Subject: [PATCH 1100/1841] Add support for the HRXL MaxSonar WR series sensors (#2020) --- CODEOWNERS | 1 + .../components/hrxl_maxsonar_wr/__init__.py | 0 .../hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp | 66 +++++++++++++++++++ .../hrxl_maxsonar_wr/hrxl_maxsonar_wr.h | 25 +++++++ esphome/components/hrxl_maxsonar_wr/sensor.py | 41 ++++++++++++ tests/test4.yaml | 9 +++ 6 files changed, 142 insertions(+) create mode 100644 esphome/components/hrxl_maxsonar_wr/__init__.py create mode 100644 esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp create mode 100644 esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.h create mode 100644 esphome/components/hrxl_maxsonar_wr/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index b3a4668f3a..8806489884 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -49,6 +49,7 @@ esphome/components/gpio/* @esphome/core esphome/components/gps/* @coogle esphome/components/havells_solar/* @sourabhjaiswal esphome/components/homeassistant/* @OttoWinter +esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/i2c/* @esphome/core esphome/components/improv/* @jesserockz esphome/components/inkbird_ibsth1_mini/* @fkirill diff --git a/esphome/components/hrxl_maxsonar_wr/__init__.py b/esphome/components/hrxl_maxsonar_wr/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp b/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp new file mode 100644 index 0000000000..cf6c9eea65 --- /dev/null +++ b/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp @@ -0,0 +1,66 @@ +// Official Datasheet: +// https://www.maxbotix.com/documents/HRXL-MaxSonar-WR_Datasheet.pdf +// +// This implementation is designed to work with the TTL Versions of the +// MaxBotix HRXL MaxSonar WR sensor series. The sensor's TTL Pin (5) should be +// wired to one of the ESP's input pins and configured as uart rx_pin. + +#include "hrxl_maxsonar_wr.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace hrxl_maxsonar_wr { + +static const char *const TAG = "hrxl.maxsonar.wr.sensor"; +static const uint8_t ASCII_CR = 0x0D; +static const uint8_t ASCII_NBSP = 0xFF; +static const int MAX_DATA_LENGTH_BYTES = 6; + +/** + * The sensor outputs something like "R1234\r" at a fixed rate of 6 Hz. Where + * 1234 means a distance of 1,234 m. + */ +void HrxlMaxsonarWrComponent::loop() { + uint8_t data; + while (this->available() > 0) { + if (this->read_byte(&data)) { + buffer_ += (char) data; + this->check_buffer_(); + } + } +} + +void HrxlMaxsonarWrComponent::check_buffer_() { + // The sensor seems to inject a rogue ASCII 255 byte from time to time. Get rid of that. + if (this->buffer_.back() == static_cast(ASCII_NBSP)) { + this->buffer_.pop_back(); + return; + } + + // Stop reading at ASCII_CR. Also prevent the buffer from growing + // indefinitely if no ASCII_CR is received after MAX_DATA_LENGTH_BYTES. + if (this->buffer_.back() == static_cast(ASCII_CR) || this->buffer_.length() >= MAX_DATA_LENGTH_BYTES) { + ESP_LOGV(TAG, "Read from serial: %s", this->buffer_.c_str()); + + if (this->buffer_.length() == MAX_DATA_LENGTH_BYTES && this->buffer_[0] == 'R' && + this->buffer_.back() == static_cast(ASCII_CR)) { + int millimeters = strtol(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2).c_str(), nullptr, 10); + float meters = float(millimeters) / 1000.0; + ESP_LOGV(TAG, "Distance from sensor: %d mm, %f m", millimeters, meters); + this->publish_state(meters); + } else { + ESP_LOGW(TAG, "Invalid data read from sensor: %s", this->buffer_.c_str()); + } + this->buffer_.clear(); + } +} + +void HrxlMaxsonarWrComponent::dump_config() { + ESP_LOGCONFIG(TAG, "HRXL MaxSonar WR Sensor:"); + LOG_SENSOR(" ", "Distance", this); + // As specified in the sensor's data sheet + this->check_uart_settings(9600, 1, esphome::uart::UART_CONFIG_PARITY_NONE, 8); +} + +} // namespace hrxl_maxsonar_wr +} // namespace esphome diff --git a/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.h b/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.h new file mode 100644 index 0000000000..efb8bc5f4b --- /dev/null +++ b/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.h @@ -0,0 +1,25 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace hrxl_maxsonar_wr { + +class HrxlMaxsonarWrComponent : public sensor::Sensor, public Component, public uart::UARTDevice { + public: + // Nothing really public. + + // ========== INTERNAL METHODS ========== + void loop() override; + void dump_config() override; + + protected: + void check_buffer_(); + + std::string buffer_; +}; + +} // namespace hrxl_maxsonar_wr +} // namespace esphome diff --git a/esphome/components/hrxl_maxsonar_wr/sensor.py b/esphome/components/hrxl_maxsonar_wr/sensor.py new file mode 100644 index 0000000000..370ee04b98 --- /dev/null +++ b/esphome/components/hrxl_maxsonar_wr/sensor.py @@ -0,0 +1,41 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, uart +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + UNIT_METER, + ICON_ARROW_EXPAND_VERTICAL, +) + +CODEOWNERS = ["@netmikey"] +DEPENDENCIES = ["uart"] + +hrxlmaxsonarwr_ns = cg.esphome_ns.namespace("hrxl_maxsonar_wr") +HrxlMaxsonarWrComponent = hrxlmaxsonarwr_ns.class_( + "HrxlMaxsonarWrComponent", sensor.Sensor, cg.Component, uart.UARTDevice +) + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + UNIT_METER, + ICON_ARROW_EXPAND_VERTICAL, + 3, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.GenerateID(): cv.declare_id(HrxlMaxsonarWrComponent), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + await uart.register_uart_device(var, config) diff --git a/tests/test4.yaml b/tests/test4.yaml index 7868fd4968..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 # From af8d04818db1ed1f060f54022c93413d1823fa5c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 29 Jul 2021 11:44:19 +0200 Subject: [PATCH 1101/1841] Pull ESP32 Wifi fixes from arduino-esp32 (#2069) --- .../components/wifi/wifi_component_esp32.cpp | 47 +++++++++++++++++-- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp32.cpp b/esphome/components/wifi/wifi_component_esp32.cpp index c51c17d60c..1bccf08a7f 100644 --- a/esphome/components/wifi/wifi_component_esp32.cpp +++ b/esphome/components/wifi/wifi_component_esp32.cpp @@ -142,11 +142,7 @@ IPAddress WiFiComponent::wifi_sta_ip_() { } bool WiFiComponent::wifi_apply_hostname_() { - esp_err_t err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, App.get_name().c_str()); - if (err != ESP_OK) { - ESP_LOGV(TAG, "Setting hostname failed: %d", err); - return false; - } + // setting is done in SYSTEM_EVENT_STA_START callback return true; } bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { @@ -154,11 +150,25 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { if (!this->wifi_mode_(true, {})) return false; + // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv417wifi_sta_config_t wifi_config_t conf; memset(&conf, 0, sizeof(conf)); strcpy(reinterpret_cast(conf.sta.ssid), ap.get_ssid().c_str()); strcpy(reinterpret_cast(conf.sta.password), ap.get_password().c_str()); + // The weakest authmode to accept in the fast scan mode + if (ap.get_password().empty()) { + conf.sta.threshold.authmode = WIFI_AUTH_OPEN; + } else { + conf.sta.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK; + } + +#ifdef ESPHOME_WIFI_WPA2_EAP + if (ap.get_eap().has_value()) { + conf.sta.threshold.authmode = WIFI_AUTH_WPA2_ENTERPRISE; + } +#endif + if (ap.get_bssid().has_value()) { conf.sta.bssid_set = 1; memcpy(conf.sta.bssid, ap.get_bssid()->data(), 6); @@ -167,7 +177,26 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { } if (ap.get_channel().has_value()) { conf.sta.channel = *ap.get_channel(); + conf.sta.scan_method = WIFI_FAST_SCAN; + } else { + conf.sta.scan_method = WIFI_ALL_CHANNEL_SCAN; } + // Listen interval for ESP32 station to receive beacon when WIFI_PS_MAX_MODEM is set. + // Units: AP beacon intervals. Defaults to 3 if set to 0. + conf.sta.listen_interval = 0; + +#if ESP_IDF_VERSION_MAJOR >= 4 + // Protected Management Frame + // Device will prefer to connect in PMF mode if other device also advertizes PMF capability. + conf.sta.pmf_cfg.capable = true; + conf.sta.pmf_cfg.required = false; +#endif + + // note, we do our own filtering + // The minimum rssi to accept in the fast scan mode + conf.sta.threshold.rssi = -127; + + conf.sta.threshold.authmode = WIFI_AUTH_OPEN; wifi_config_t current_conf; esp_err_t err; @@ -348,6 +377,8 @@ const char *get_disconnect_reason_str(uint8_t reason) { return "Association Failed"; case WIFI_REASON_HANDSHAKE_TIMEOUT: return "Handshake Failed"; + case WIFI_REASON_CONNECTION_FAIL: + return "Connection Failed"; case WIFI_REASON_UNSPECIFIED: default: return "Unspecified"; @@ -374,6 +405,7 @@ void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_i } case SYSTEM_EVENT_STA_START: { ESP_LOGV(TAG, "Event: WiFi STA start"); + tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, App.get_name().c_str()); break; } case SYSTEM_EVENT_STA_STOP: { @@ -636,6 +668,11 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { strcpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str()); } +#if ESP_IDF_VERSION_MAJOR >= 4 + // pairwise cipher of SoftAP, group cipher will be derived using this. + conf.ap.pairwise_cipher = WIFI_CIPHER_TYPE_CCMP; +#endif + esp_err_t err = esp_wifi_set_config(WIFI_IF_AP, &conf); if (err != ESP_OK) { ESP_LOGV(TAG, "esp_wifi_set_config failed! %d", err); From 16dbbfabc61809c8df1a79249af0bfe4ec80c2ee Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 29 Jul 2021 11:50:55 +0200 Subject: [PATCH 1102/1841] Add demo integration (#2085) --- esphome/components/demo/__init__.py | 451 +++++++++++++++++++ esphome/components/demo/demo_binary_sensor.h | 22 + esphome/components/demo/demo_climate.h | 157 +++++++ esphome/components/demo/demo_cover.h | 86 ++++ esphome/components/demo/demo_fan.h | 54 +++ esphome/components/demo/demo_light.h | 82 ++++ esphome/components/demo/demo_number.h | 39 ++ esphome/components/demo/demo_sensor.h | 28 ++ esphome/components/demo/demo_switch.h | 22 + esphome/components/demo/demo_text_sensor.h | 25 + tests/test5.yaml | 3 + 11 files changed, 969 insertions(+) create mode 100644 esphome/components/demo/__init__.py create mode 100644 esphome/components/demo/demo_binary_sensor.h create mode 100644 esphome/components/demo/demo_climate.h create mode 100644 esphome/components/demo/demo_cover.h create mode 100644 esphome/components/demo/demo_fan.h create mode 100644 esphome/components/demo/demo_light.h create mode 100644 esphome/components/demo/demo_number.h create mode 100644 esphome/components/demo/demo_sensor.h create mode 100644 esphome/components/demo/demo_switch.h create mode 100644 esphome/components/demo/demo_text_sensor.h diff --git a/esphome/components/demo/__init__.py b/esphome/components/demo/__init__.py new file mode 100644 index 0000000000..b3ea47d869 --- /dev/null +++ b/esphome/components/demo/__init__.py @@ -0,0 +1,451 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import ( + binary_sensor, + climate, + cover, + fan, + light, + number, + sensor, + switch, + text_sensor, +) +from esphome.const import ( + CONF_ACCURACY_DECIMALS, + CONF_BINARY_SENSORS, + CONF_DEVICE_CLASS, + CONF_FORCE_UPDATE, + CONF_ICON, + CONF_ID, + CONF_INVERTED, + CONF_LAST_RESET_TYPE, + CONF_MAX_VALUE, + CONF_MIN_VALUE, + CONF_NAME, + CONF_OUTPUT_ID, + CONF_SENSORS, + CONF_STATE_CLASS, + CONF_STEP, + CONF_SWITCHES, + CONF_TEXT_SENSORS, + CONF_TYPE, + CONF_UNIT_OF_MEASUREMENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_MOTION, + DEVICE_CLASS_TEMPERATURE, + ICON_BLUETOOTH, + ICON_BLUR, + ICON_EMPTY, + ICON_THERMOMETER, + LAST_RESET_TYPE_AUTO, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_EMPTY, + UNIT_PERCENT, + UNIT_WATT_HOURS, +) + +AUTO_LOAD = [ + "binary_sensor", + "climate", + "cover", + "fan", + "light", + "number", + "sensor", + "switch", + "text_sensor", +] + +demo_ns = cg.esphome_ns.namespace("demo") +DemoBinarySensor = demo_ns.class_( + "DemoBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent +) +DemoClimate = demo_ns.class_("DemoClimate", climate.Climate, cg.Component) +DemoClimateType = demo_ns.enum("DemoClimateType", is_class=True) +DemoCover = demo_ns.class_("DemoCover", cover.Cover, cg.Component) +DemoCoverType = demo_ns.enum("DemoCoverType", is_class=True) +DemoFan = demo_ns.class_("DemoFan", cg.Component) +DemoFanType = demo_ns.enum("DemoFanType", is_class=True) +DemoLight = demo_ns.class_("DemoLight", light.LightOutput, cg.Component) +DemoLightType = demo_ns.enum("DemoLightType", is_class=True) +DemoNumber = demo_ns.class_("DemoNumber", number.Number, cg.Component) +DemoNumberType = demo_ns.enum("DemoNumberType", is_class=True) +DemoSensor = demo_ns.class_("DemoSensor", sensor.Sensor, cg.PollingComponent) +DemoSwitch = demo_ns.class_("DemoSwitch", switch.Switch, cg.Component) +DemoTextSensor = demo_ns.class_( + "DemoTextSensor", text_sensor.TextSensor, cg.PollingComponent +) + + +CLIMATE_TYPES = { + 1: DemoClimateType.TYPE_1, + 2: DemoClimateType.TYPE_2, + 3: DemoClimateType.TYPE_3, +} +COVER_TYPES = { + 1: DemoCoverType.TYPE_1, + 2: DemoCoverType.TYPE_2, + 3: DemoCoverType.TYPE_3, + 4: DemoCoverType.TYPE_4, +} +FAN_TYPES = { + 1: DemoFanType.TYPE_1, + 2: DemoFanType.TYPE_2, + 3: DemoFanType.TYPE_3, + 4: DemoFanType.TYPE_4, +} +LIGHT_TYPES = { + 1: DemoLightType.TYPE_1, + 2: DemoLightType.TYPE_2, + 3: DemoLightType.TYPE_3, + 4: DemoLightType.TYPE_4, + 5: DemoLightType.TYPE_5, + 6: DemoLightType.TYPE_6, + 7: DemoLightType.TYPE_7, +} +NUMBER_TYPES = { + 1: DemoNumberType.TYPE_1, + 2: DemoNumberType.TYPE_2, + 3: DemoNumberType.TYPE_3, +} + + +CONF_CLIMATES = "climates" +CONF_COVERS = "covers" +CONF_FANS = "fans" +CONF_LIGHTS = "lights" +CONF_NUMBERS = "numbers" + +CONFIG_SCHEMA = cv.Schema( + { + cv.Optional( + CONF_BINARY_SENSORS, + default=[ + { + CONF_NAME: "Demo Basement Floor Wet", + CONF_DEVICE_CLASS: DEVICE_CLASS_MOISTURE, + }, + { + CONF_NAME: "Demo Movement Backyard", + CONF_DEVICE_CLASS: DEVICE_CLASS_MOTION, + }, + ], + ): [ + binary_sensor.BINARY_SENSOR_SCHEMA.extend( + cv.polling_component_schema("60s") + ).extend( + { + cv.GenerateID(): cv.declare_id(DemoBinarySensor), + } + ) + ], + cv.Optional( + CONF_CLIMATES, + default=[ + { + CONF_NAME: "Demo Heatpump", + CONF_TYPE: 1, + }, + { + CONF_NAME: "Demo HVAC", + CONF_TYPE: 2, + }, + { + CONF_NAME: "Demo Ecobee", + CONF_TYPE: 3, + }, + ], + ): [ + climate.CLIMATE_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( + { + cv.GenerateID(): cv.declare_id(DemoClimate), + cv.Required(CONF_TYPE): cv.enum(CLIMATE_TYPES, int=True), + } + ) + ], + cv.Optional( + CONF_COVERS, + default=[ + { + CONF_NAME: "Demo Kitchen Window", + CONF_TYPE: 1, + }, + { + CONF_NAME: "Demo Garage Door", + CONF_TYPE: 2, + CONF_DEVICE_CLASS: "garage", + }, + { + CONF_NAME: "Demo Living Room Window", + CONF_TYPE: 3, + }, + { + CONF_NAME: "Demo Hall Window", + CONF_TYPE: 4, + CONF_DEVICE_CLASS: "window", + }, + ], + ): [ + cover.COVER_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( + { + cv.GenerateID(): cv.declare_id(DemoCover), + cv.Required(CONF_TYPE): cv.enum(COVER_TYPES, int=True), + } + ) + ], + cv.Optional( + CONF_FANS, + default=[ + { + CONF_NAME: "Demo Living Room Fan", + CONF_TYPE: 1, + }, + { + CONF_NAME: "Demo Ceiling Fan", + CONF_TYPE: 2, + }, + { + CONF_NAME: "Demo Percentage Limited Fan", + CONF_TYPE: 3, + }, + { + CONF_NAME: "Demo Percentage Full Fan", + CONF_TYPE: 4, + }, + ], + ): [ + fan.FAN_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(DemoFan), + cv.Required(CONF_TYPE): cv.enum(FAN_TYPES, int=True), + } + ) + ], + cv.Optional( + CONF_LIGHTS, + default=[ + { + CONF_NAME: "Demo Binary Light", + CONF_TYPE: 1, + }, + { + CONF_NAME: "Demo Brightness Light", + CONF_TYPE: 2, + }, + { + CONF_NAME: "Demo RGB Light", + CONF_TYPE: 3, + }, + { + CONF_NAME: "Demo RGBW Light", + CONF_TYPE: 4, + }, + { + CONF_NAME: "Demo RGBWW Light", + CONF_TYPE: 5, + }, + { + CONF_NAME: "Demo CWWW Light", + CONF_TYPE: 6, + }, + { + CONF_NAME: "Demo RGBW interlock Light", + CONF_TYPE: 7, + }, + ], + ): [ + light.RGB_LIGHT_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(DemoLight), + cv.Required(CONF_TYPE): cv.enum(LIGHT_TYPES, int=True), + } + ) + ], + cv.Optional( + CONF_NUMBERS, + default=[ + { + CONF_NAME: "Demo Number 0-100", + CONF_TYPE: 1, + CONF_MIN_VALUE: 0, + CONF_MAX_VALUE: 100, + CONF_STEP: 1, + }, + { + CONF_NAME: "Demo Number -50-50", + CONF_TYPE: 2, + CONF_MIN_VALUE: -50, + CONF_MAX_VALUE: 50, + CONF_STEP: 5, + }, + { + CONF_NAME: "Demo Number 40-60", + CONF_TYPE: 3, + CONF_MIN_VALUE: 40, + CONF_MAX_VALUE: 60, + CONF_STEP: 0.2, + }, + ], + ): [ + number.NUMBER_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( + { + cv.GenerateID(): cv.declare_id(DemoNumber), + cv.Required(CONF_TYPE): cv.enum(NUMBER_TYPES, int=True), + cv.Required(CONF_MIN_VALUE): cv.float_, + cv.Required(CONF_MAX_VALUE): cv.float_, + cv.Required(CONF_STEP): cv.float_, + } + ) + ], + cv.Optional( + CONF_SENSORS, + default=[ + { + CONF_NAME: "Demo Plain Sensor", + }, + { + CONF_NAME: "Demo Temperature Sensor", + CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS, + CONF_ICON: ICON_THERMOMETER, + CONF_ACCURACY_DECIMALS: 1, + CONF_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, + CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT, + }, + { + CONF_NAME: "Demo Temperature Sensor", + CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS, + CONF_ICON: ICON_THERMOMETER, + CONF_ACCURACY_DECIMALS: 1, + CONF_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, + CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT, + }, + { + CONF_NAME: "Demo Force Update Sensor", + CONF_UNIT_OF_MEASUREMENT: UNIT_PERCENT, + CONF_ACCURACY_DECIMALS: 0, + CONF_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY, + CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT, + CONF_FORCE_UPDATE: True, + }, + { + CONF_NAME: "Demo Energy Sensor", + CONF_UNIT_OF_MEASUREMENT: UNIT_WATT_HOURS, + CONF_ACCURACY_DECIMALS: 0, + CONF_DEVICE_CLASS: DEVICE_CLASS_ENERGY, + CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT, + CONF_LAST_RESET_TYPE: LAST_RESET_TYPE_AUTO, + }, + ], + ): [ + sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 0) + .extend(cv.polling_component_schema("60s")) + .extend( + { + cv.GenerateID(): cv.declare_id(DemoSensor), + } + ) + ], + cv.Optional( + CONF_SWITCHES, + default=[ + { + CONF_NAME: "Demo Switch 1", + }, + { + CONF_NAME: "Demo Switch 2", + CONF_INVERTED: True, + CONF_ICON: ICON_BLUETOOTH, + }, + ], + ): [ + switch.SWITCH_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( + { + cv.GenerateID(): cv.declare_id(DemoSwitch), + } + ) + ], + cv.Optional( + CONF_TEXT_SENSORS, + default=[ + { + CONF_NAME: "Demo Text Sensor 1", + }, + { + CONF_NAME: "Demo Text Sensor 2", + CONF_ICON: ICON_BLUR, + }, + ], + ): [ + text_sensor.TEXT_SENSOR_SCHEMA.extend( + cv.polling_component_schema("60s") + ).extend( + { + cv.GenerateID(): cv.declare_id(DemoTextSensor), + } + ) + ], + } +) + + +async def to_code(config): + for conf in config[CONF_BINARY_SENSORS]: + var = cg.new_Pvariable(conf[CONF_ID]) + await cg.register_component(var, conf) + await binary_sensor.register_binary_sensor(var, conf) + + for conf in config[CONF_CLIMATES]: + var = cg.new_Pvariable(conf[CONF_ID]) + await cg.register_component(var, conf) + await climate.register_climate(var, conf) + cg.add(var.set_type(conf[CONF_TYPE])) + + for conf in config[CONF_COVERS]: + var = cg.new_Pvariable(conf[CONF_ID]) + await cg.register_component(var, conf) + await cover.register_cover(var, conf) + cg.add(var.set_type(conf[CONF_TYPE])) + + for conf in config[CONF_FANS]: + var = cg.new_Pvariable(conf[CONF_OUTPUT_ID]) + await cg.register_component(var, conf) + fan_ = await fan.create_fan_state(conf) + cg.add(var.set_fan(fan_)) + cg.add(var.set_type(conf[CONF_TYPE])) + + for conf in config[CONF_LIGHTS]: + var = cg.new_Pvariable(conf[CONF_OUTPUT_ID]) + await cg.register_component(var, conf) + await light.register_light(var, conf) + cg.add(var.set_type(conf[CONF_TYPE])) + + for conf in config[CONF_NUMBERS]: + var = cg.new_Pvariable(conf[CONF_ID]) + await cg.register_component(var, conf) + await number.register_number( + var, + conf, + min_value=conf[CONF_MIN_VALUE], + max_value=conf[CONF_MAX_VALUE], + step=conf[CONF_STEP], + ) + cg.add(var.set_type(conf[CONF_TYPE])) + + for conf in config[CONF_SENSORS]: + var = cg.new_Pvariable(conf[CONF_ID]) + await cg.register_component(var, conf) + await sensor.register_sensor(var, conf) + + for conf in config[CONF_SWITCHES]: + var = cg.new_Pvariable(conf[CONF_ID]) + await cg.register_component(var, conf) + await switch.register_switch(var, conf) + + for conf in config[CONF_TEXT_SENSORS]: + var = cg.new_Pvariable(conf[CONF_ID]) + await cg.register_component(var, conf) + await text_sensor.register_text_sensor(var, conf) diff --git a/esphome/components/demo/demo_binary_sensor.h b/esphome/components/demo/demo_binary_sensor.h new file mode 100644 index 0000000000..4dfd038761 --- /dev/null +++ b/esphome/components/demo/demo_binary_sensor.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" + +namespace esphome { +namespace demo { + +class DemoBinarySensor : public binary_sensor::BinarySensor, public PollingComponent { + public: + void setup() override { this->publish_initial_state(false); } + void update() override { + bool new_state = last_state_ = !last_state_; + this->publish_state(new_state); + } + + protected: + bool last_state_ = false; +}; + +} // namespace demo +} // namespace esphome diff --git a/esphome/components/demo/demo_climate.h b/esphome/components/demo/demo_climate.h new file mode 100644 index 0000000000..0cf48dd4ee --- /dev/null +++ b/esphome/components/demo/demo_climate.h @@ -0,0 +1,157 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/climate/climate.h" + +namespace esphome { +namespace demo { + +enum class DemoClimateType { + TYPE_1, + TYPE_2, + TYPE_3, +}; + +class DemoClimate : public climate::Climate, public Component { + public: + void set_type(DemoClimateType type) { type_ = type; } + void setup() override { + switch (type_) { + case DemoClimateType::TYPE_1: + this->current_temperature = 20.0; + this->target_temperature = 21.0; + this->mode = climate::CLIMATE_MODE_HEAT; + this->action = climate::CLIMATE_ACTION_HEATING; + break; + case DemoClimateType::TYPE_2: + this->target_temperature = 21.5; + this->mode = climate::CLIMATE_MODE_AUTO; + this->action = climate::CLIMATE_ACTION_COOLING; + this->fan_mode = climate::CLIMATE_FAN_HIGH; + this->custom_preset = {"My Preset"}; + break; + case DemoClimateType::TYPE_3: + this->current_temperature = 21.5; + this->target_temperature_low = 21.0; + this->target_temperature_high = 22.5; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; + this->custom_fan_mode = {"Auto Low"}; + this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; + this->preset = climate::CLIMATE_PRESET_AWAY; + break; + } + this->publish_state(); + } + + protected: + void control(const climate::ClimateCall &call) override { + if (call.get_mode().has_value()) { + this->mode = *call.get_mode(); + } + if (call.get_target_temperature().has_value()) { + this->target_temperature = *call.get_target_temperature(); + } + if (call.get_target_temperature_low().has_value()) { + this->target_temperature_low = *call.get_target_temperature_low(); + } + if (call.get_target_temperature_high().has_value()) { + this->target_temperature_high = *call.get_target_temperature_high(); + } + if (call.get_fan_mode().has_value()) { + this->fan_mode = *call.get_fan_mode(); + this->custom_fan_mode.reset(); + } + if (call.get_swing_mode().has_value()) { + this->swing_mode = *call.get_swing_mode(); + } + if (call.get_custom_fan_mode().has_value()) { + this->custom_fan_mode = *call.get_custom_fan_mode(); + this->fan_mode.reset(); + } + if (call.get_preset().has_value()) { + this->preset = *call.get_preset(); + this->custom_preset.reset(); + } + if (call.get_custom_preset().has_value()) { + this->custom_preset = *call.get_custom_preset(); + this->preset.reset(); + } + this->publish_state(); + } + climate::ClimateTraits traits() override { + climate::ClimateTraits traits{}; + switch (type_) { + case DemoClimateType::TYPE_1: + traits.set_supports_current_temperature(true); + traits.set_supported_modes({ + climate::CLIMATE_MODE_OFF, + climate::CLIMATE_MODE_HEAT, + }); + traits.set_supports_action(true); + traits.set_visual_temperature_step(0.5); + break; + case DemoClimateType::TYPE_2: + traits.set_supports_current_temperature(false); + traits.set_supported_modes({ + climate::CLIMATE_MODE_OFF, + climate::CLIMATE_MODE_HEAT, + climate::CLIMATE_MODE_COOL, + climate::CLIMATE_MODE_AUTO, + climate::CLIMATE_MODE_DRY, + climate::CLIMATE_MODE_FAN_ONLY, + }); + traits.set_supports_action(true); + traits.set_supported_fan_modes({ + climate::CLIMATE_FAN_ON, + climate::CLIMATE_FAN_OFF, + climate::CLIMATE_FAN_AUTO, + climate::CLIMATE_FAN_LOW, + climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH, + climate::CLIMATE_FAN_MIDDLE, + climate::CLIMATE_FAN_FOCUS, + climate::CLIMATE_FAN_DIFFUSE, + }); + traits.set_supported_custom_fan_modes({"Auto Low", "Auto High"}); + traits.set_supported_swing_modes({ + climate::CLIMATE_SWING_OFF, + climate::CLIMATE_SWING_BOTH, + climate::CLIMATE_SWING_VERTICAL, + climate::CLIMATE_SWING_HORIZONTAL, + }); + traits.set_supported_custom_presets({"My Preset"}); + break; + case DemoClimateType::TYPE_3: + traits.set_supports_current_temperature(true); + traits.set_supports_two_point_target_temperature(true); + traits.set_supported_modes({ + climate::CLIMATE_MODE_OFF, + climate::CLIMATE_MODE_COOL, + climate::CLIMATE_MODE_HEAT, + climate::CLIMATE_MODE_HEAT_COOL, + }); + traits.set_supported_custom_fan_modes({"Auto Low", "Auto High"}); + traits.set_supported_swing_modes({ + climate::CLIMATE_SWING_OFF, + climate::CLIMATE_SWING_HORIZONTAL, + }); + traits.set_supported_presets({ + climate::CLIMATE_PRESET_NONE, + climate::CLIMATE_PRESET_HOME, + climate::CLIMATE_PRESET_AWAY, + climate::CLIMATE_PRESET_BOOST, + climate::CLIMATE_PRESET_COMFORT, + climate::CLIMATE_PRESET_ECO, + climate::CLIMATE_PRESET_SLEEP, + climate::CLIMATE_PRESET_ACTIVITY, + }); + break; + } + return traits; + } + + DemoClimateType type_; +}; + +} // namespace demo +} // namespace esphome diff --git a/esphome/components/demo/demo_cover.h b/esphome/components/demo/demo_cover.h new file mode 100644 index 0000000000..ab039736fb --- /dev/null +++ b/esphome/components/demo/demo_cover.h @@ -0,0 +1,86 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/cover/cover.h" + +namespace esphome { +namespace demo { + +enum class DemoCoverType { + TYPE_1, + TYPE_2, + TYPE_3, + TYPE_4, +}; + +class DemoCover : public cover::Cover, public Component { + public: + void set_type(DemoCoverType type) { type_ = type; } + void setup() override { + switch (type_) { + case DemoCoverType::TYPE_1: + this->position = cover::COVER_OPEN; + break; + case DemoCoverType::TYPE_2: + this->position = 0.7; + break; + case DemoCoverType::TYPE_3: + this->position = 0.1; + this->tilt = 0.8; + break; + case DemoCoverType::TYPE_4: + this->position = cover::COVER_CLOSED; + this->tilt = 1.0; + break; + } + this->publish_state(); + } + + protected: + void control(const cover::CoverCall &call) override { + if (call.get_position().has_value()) { + float target = *call.get_position(); + this->current_operation = + target > this->position ? cover::COVER_OPERATION_OPENING : cover::COVER_OPERATION_CLOSING; + + this->set_timeout("move", 2000, [this, target]() { + this->current_operation = cover::COVER_OPERATION_IDLE; + this->position = target; + this->publish_state(); + }); + } + if (call.get_tilt().has_value()) { + this->tilt = *call.get_tilt(); + } + if (call.get_stop()) { + this->cancel_timeout("move"); + } + + this->publish_state(); + } + cover::CoverTraits get_traits() override { + cover::CoverTraits traits{}; + switch (type_) { + case DemoCoverType::TYPE_1: + traits.set_is_assumed_state(true); + break; + case DemoCoverType::TYPE_2: + traits.set_supports_position(true); + break; + case DemoCoverType::TYPE_3: + traits.set_supports_position(true); + traits.set_supports_tilt(true); + break; + case DemoCoverType::TYPE_4: + traits.set_is_assumed_state(true); + traits.set_supports_tilt(true); + break; + } + return traits; + } + + DemoCoverType type_; +}; + +} // namespace demo +} // namespace esphome diff --git a/esphome/components/demo/demo_fan.h b/esphome/components/demo/demo_fan.h new file mode 100644 index 0000000000..e926f68edb --- /dev/null +++ b/esphome/components/demo/demo_fan.h @@ -0,0 +1,54 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/fan/fan_state.h" + +namespace esphome { +namespace demo { + +enum class DemoFanType { + TYPE_1, + TYPE_2, + TYPE_3, + TYPE_4, +}; + +class DemoFan : public Component { + public: + void set_type(DemoFanType type) { type_ = type; } + void set_fan(fan::FanState *fan) { fan_ = fan; } + void setup() override { + fan::FanTraits traits{}; + + // oscillation + // speed + // direction + // speed_count + switch (type_) { + case DemoFanType::TYPE_1: + break; + case DemoFanType::TYPE_2: + traits.set_oscillation(true); + break; + case DemoFanType::TYPE_3: + traits.set_direction(true); + traits.set_speed(true); + traits.set_supported_speed_count(5); + break; + case DemoFanType::TYPE_4: + traits.set_direction(true); + traits.set_speed(true); + traits.set_supported_speed_count(100); + traits.set_oscillation(true); + break; + } + + this->fan_->set_traits(traits); + } + + fan::FanState *fan_; + DemoFanType type_; +}; + +} // namespace demo +} // namespace esphome diff --git a/esphome/components/demo/demo_light.h b/esphome/components/demo/demo_light.h new file mode 100644 index 0000000000..989dbf3306 --- /dev/null +++ b/esphome/components/demo/demo_light.h @@ -0,0 +1,82 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/light/light_output.h" + +namespace esphome { +namespace demo { + +enum class DemoLightType { + // binary + TYPE_1, + // brightness + TYPE_2, + // RGB + TYPE_3, + // RGBW + TYPE_4, + // RGBWW + TYPE_5, + // CWWW + TYPE_6, + // RGBW + color_interlock + TYPE_7, +}; + +class DemoLight : public light::LightOutput, public Component { + public: + void set_type(DemoLightType type) { type_ = type; } + light::LightTraits get_traits() override { + light::LightTraits traits{}; + // brightness + // rgb + // rgb_white_value + // color_temperature, min_mireds, max_mireds + // color_interlock + switch (type_) { + case DemoLightType::TYPE_1: + break; + case DemoLightType::TYPE_2: + traits.set_supports_brightness(true); + break; + case DemoLightType::TYPE_3: + traits.set_supports_brightness(true); + traits.set_supports_rgb(true); + break; + case DemoLightType::TYPE_4: + traits.set_supports_brightness(true); + traits.set_supports_rgb(true); + traits.set_supports_rgb_white_value(true); + break; + case DemoLightType::TYPE_5: + traits.set_supports_brightness(true); + traits.set_supports_rgb(true); + traits.set_supports_rgb_white_value(true); + traits.set_supports_color_temperature(true); + traits.set_min_mireds(153); + traits.set_max_mireds(500); + break; + case DemoLightType::TYPE_6: + traits.set_supports_brightness(true); + traits.set_supports_color_temperature(true); + traits.set_min_mireds(153); + traits.set_max_mireds(500); + break; + case DemoLightType::TYPE_7: + traits.set_supports_brightness(true); + traits.set_supports_rgb(true); + traits.set_supports_rgb_white_value(true); + traits.set_supports_color_interlock(true); + break; + } + return traits; + } + void write_state(light::LightState *state) override { + // do nothing + } + + DemoLightType type_; +}; + +} // namespace demo +} // namespace esphome diff --git a/esphome/components/demo/demo_number.h b/esphome/components/demo/demo_number.h new file mode 100644 index 0000000000..2ce3a269bc --- /dev/null +++ b/esphome/components/demo/demo_number.h @@ -0,0 +1,39 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/number/number.h" + +namespace esphome { +namespace demo { + +enum class DemoNumberType { + TYPE_1, + TYPE_2, + TYPE_3, +}; + +class DemoNumber : public number::Number, public Component { + public: + void set_type(DemoNumberType type) { type_ = type; } + void setup() override { + switch (type_) { + case DemoNumberType::TYPE_1: + this->publish_state(50); + break; + case DemoNumberType::TYPE_2: + this->publish_state(-10); + break; + case DemoNumberType::TYPE_3: + this->publish_state(42); + break; + } + } + + protected: + void control(float value) override { this->publish_state(value); } + + DemoNumberType type_; +}; + +} // namespace demo +} // namespace esphome diff --git a/esphome/components/demo/demo_sensor.h b/esphome/components/demo/demo_sensor.h new file mode 100644 index 0000000000..117468793b --- /dev/null +++ b/esphome/components/demo/demo_sensor.h @@ -0,0 +1,28 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace demo { + +class DemoSensor : public sensor::Sensor, public PollingComponent { + public: + void update() override { + float val = random_float(); + bool is_auto = this->last_reset_type == sensor::LAST_RESET_TYPE_AUTO; + if (is_auto) { + float base = isnan(this->state) ? 0.0f : this->state; + this->publish_state(base + val * 10); + } else { + if (val < 0.1) + this->publish_state(NAN); + else + this->publish_state(val * 100); + } + } +}; + +} // namespace demo +} // namespace esphome diff --git a/esphome/components/demo/demo_switch.h b/esphome/components/demo/demo_switch.h new file mode 100644 index 0000000000..9c291318ca --- /dev/null +++ b/esphome/components/demo/demo_switch.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "esphome/components/switch/switch.h" + +namespace esphome { +namespace demo { + +class DemoSwitch : public switch_::Switch, public Component { + public: + void setup() override { + bool initial = random_float() < 0.5; + this->publish_state(initial); + } + + protected: + void write_state(bool state) override { this->publish_state(state); } +}; + +} // namespace demo +} // namespace esphome diff --git a/esphome/components/demo/demo_text_sensor.h b/esphome/components/demo/demo_text_sensor.h new file mode 100644 index 0000000000..b4152fc248 --- /dev/null +++ b/esphome/components/demo/demo_text_sensor.h @@ -0,0 +1,25 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "esphome/components/text_sensor/text_sensor.h" + +namespace esphome { +namespace demo { + +class DemoTextSensor : public text_sensor::TextSensor, public PollingComponent { + public: + void update() override { + float val = random_float(); + if (val < 0.33) { + this->publish_state("foo"); + } else if (val < 0.66) { + this->publish_state("bar"); + } else { + this->publish_state("foobar"); + } + } +}; + +} // namespace demo +} // namespace esphome diff --git a/tests/test5.yaml b/tests/test5.yaml index 23f8ae6c4d..1a2dde1010 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -52,6 +52,8 @@ output: channel: 0 max_power: 0.8 +demo: + esp32_ble: esp32_ble_server: @@ -116,6 +118,7 @@ sensor: name: "SelecEM2M Maximum Demand Reactive Power" maximum_demand_apparent_power: name: "SelecEM2M Maximum Demand Apparent Power" + - platform: t6615 uart_id: uart2 co2: From de382b704c76d89d72a73a3ad0d12cd60663a1eb Mon Sep 17 00:00:00 2001 From: Kodey Converse Date: Thu, 29 Jul 2021 10:08:48 -0400 Subject: [PATCH 1103/1841] Add device class support to MQTT cover (#2092) --- esphome/components/mqtt/mqtt_cover.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index 25ac430abb..b11ae1fb93 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -61,6 +61,9 @@ void MQTTCoverComponent::dump_config() { } } void MQTTCoverComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { + if (!this->cover_->get_device_class().empty()) + root["device_class"] = this->cover_->get_device_class(); + auto traits = this->cover_->get_traits(); if (traits.get_is_assumed_state()) { root["optimistic"] = true; From 5983ccc55ca722f3ddd676e6a6e59d1fcdd59f16 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 29 Jul 2021 19:11:56 +0200 Subject: [PATCH 1104/1841] Color mode implementation (#2012) --- esphome/components/api/api.proto | 31 +- esphome/components/api/api_connection.cpp | 39 +- esphome/components/api/api_pb2.cpp | 156 ++++++- esphome/components/api/api_pb2.h | 30 +- .../binary/light/binary_light_output.h | 2 +- esphome/components/cwww/cwww_light_output.h | 9 +- esphome/components/cwww/light.py | 19 +- esphome/components/demo/demo_light.h | 28 +- .../components/fastled_base/fastled_light.h | 3 +- .../components/hbridge/hbridge_light_output.h | 28 +- esphome/components/light/__init__.py | 4 +- esphome/components/light/automation.h | 6 + esphome/components/light/automation.py | 17 + esphome/components/light/base_light_effects.h | 39 +- esphome/components/light/color_mode.h | 107 +++++ esphome/components/light/effects.py | 20 + esphome/components/light/light_call.cpp | 429 +++++++++++------- esphome/components/light/light_call.h | 37 +- esphome/components/light/light_color_values.h | 224 ++++----- .../components/light/light_json_schema.cpp | 165 +++++++ esphome/components/light/light_json_schema.h | 25 + esphome/components/light/light_state.cpp | 28 +- esphome/components/light/light_state.h | 5 - esphome/components/light/light_traits.h | 53 ++- esphome/components/light/light_transformer.h | 35 +- esphome/components/light/types.py | 14 + .../monochromatic_light_output.h | 2 +- esphome/components/mqtt/mqtt_light.cpp | 34 +- .../neopixelbus/neopixelbus_light.h | 7 +- esphome/components/rgb/rgb_light_output.h | 3 +- esphome/components/rgbw/rgbw_light_output.h | 8 +- esphome/components/rgbww/light.py | 18 +- esphome/components/rgbww/rgbww_light_output.h | 17 +- esphome/components/tuya/light/tuya_light.cpp | 9 +- esphome/components/web_server/web_server.cpp | 6 +- esphome/config_validation.py | 17 + esphome/const.py | 1 + esphome/core/helpers.cpp | 9 + esphome/core/helpers.h | 2 + 39 files changed, 1210 insertions(+), 476 deletions(-) create mode 100644 esphome/components/light/color_mode.h create mode 100644 esphome/components/light/light_json_schema.cpp create mode 100644 esphome/components/light/light_json_schema.h diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 073775ed2e..c04e0a32f1 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -352,6 +352,18 @@ message FanCommandRequest { } // ==================== LIGHT ==================== +enum ColorMode { + COLOR_MODE_UNKNOWN = 0; + COLOR_MODE_ON_OFF = 1; + COLOR_MODE_BRIGHTNESS = 2; + COLOR_MODE_WHITE = 7; + COLOR_MODE_COLOR_TEMPERATURE = 11; + COLOR_MODE_COLD_WARM_WHITE = 19; + COLOR_MODE_RGB = 35; + COLOR_MODE_RGB_WHITE = 39; + COLOR_MODE_RGB_COLOR_TEMPERATURE = 47; + COLOR_MODE_RGB_COLD_WARM_WHITE = 51; +} message ListEntitiesLightResponse { option (id) = 15; option (source) = SOURCE_SERVER; @@ -362,10 +374,12 @@ message ListEntitiesLightResponse { string name = 3; string unique_id = 4; - bool supports_brightness = 5; - bool supports_rgb = 6; - bool supports_white_value = 7; - bool supports_color_temperature = 8; + repeated ColorMode supported_color_modes = 12; + // next four supports_* are for legacy clients, newer clients should use color modes + bool legacy_supports_brightness = 5 [deprecated=true]; + bool legacy_supports_rgb = 6 [deprecated=true]; + bool legacy_supports_white_value = 7 [deprecated=true]; + bool legacy_supports_color_temperature = 8 [deprecated=true]; float min_mireds = 9; float max_mireds = 10; repeated string effects = 11; @@ -379,12 +393,15 @@ message LightStateResponse { fixed32 key = 1; bool state = 2; float brightness = 3; + ColorMode color_mode = 11; float color_brightness = 10; float red = 4; float green = 5; float blue = 6; float white = 7; float color_temperature = 8; + float cold_white = 12; + float warm_white = 13; string effect = 9; } message LightCommandRequest { @@ -398,6 +415,8 @@ message LightCommandRequest { bool state = 3; bool has_brightness = 4; float brightness = 5; + bool has_color_mode = 22; + ColorMode color_mode = 23; bool has_color_brightness = 20; float color_brightness = 21; bool has_rgb = 6; @@ -408,6 +427,10 @@ message LightCommandRequest { float white = 11; bool has_color_temperature = 12; float color_temperature = 13; + bool has_cold_white = 24; + float cold_white = 25; + bool has_warm_white = 26; + float warm_white = 27; bool has_transition_length = 14; uint32 transition_length = 15; bool has_flash_length = 16; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index fb05772e5e..b92c67b042 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -301,22 +301,28 @@ bool APIConnection::send_light_state(light::LightState *light) { auto traits = light->get_traits(); auto values = light->remote_values; + auto color_mode = values.get_color_mode(); LightStateResponse resp{}; resp.key = light->get_object_id_hash(); resp.state = values.is_on(); - if (traits.get_supports_brightness()) + resp.color_mode = static_cast(color_mode); + if (color_mode & light::ColorCapability::BRIGHTNESS) resp.brightness = values.get_brightness(); - if (traits.get_supports_rgb()) { + if (color_mode & light::ColorCapability::RGB) { resp.color_brightness = values.get_color_brightness(); resp.red = values.get_red(); resp.green = values.get_green(); resp.blue = values.get_blue(); } - if (traits.get_supports_rgb_white_value()) + if (color_mode & light::ColorCapability::WHITE) resp.white = values.get_white(); - if (traits.get_supports_color_temperature()) + if (color_mode & light::ColorCapability::COLOR_TEMPERATURE) resp.color_temperature = values.get_color_temperature(); + if (color_mode & light::ColorCapability::COLD_WARM_WHITE) { + resp.cold_white = values.get_cold_white(); + resp.warm_white = values.get_warm_white(); + } if (light->supports_effects()) resp.effect = light->get_effect_name(); return this->send_light_state_response(resp); @@ -328,11 +334,18 @@ bool APIConnection::send_light_info(light::LightState *light) { msg.object_id = light->get_object_id(); msg.name = light->get_name(); msg.unique_id = get_default_unique_id("light", light); - msg.supports_brightness = traits.get_supports_brightness(); - msg.supports_rgb = traits.get_supports_rgb(); - msg.supports_white_value = traits.get_supports_rgb_white_value(); - msg.supports_color_temperature = traits.get_supports_color_temperature(); - if (msg.supports_color_temperature) { + for (auto mode : traits.get_supported_color_modes()) + msg.supported_color_modes.push_back(static_cast(mode)); + + msg.legacy_supports_brightness = traits.supports_color_capability(light::ColorCapability::BRIGHTNESS); + msg.legacy_supports_rgb = traits.supports_color_capability(light::ColorCapability::RGB); + msg.legacy_supports_white_value = + msg.legacy_supports_rgb && (traits.supports_color_capability(light::ColorCapability::WHITE) || + traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)); + msg.legacy_supports_color_temperature = traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) || + traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE); + + if (msg.legacy_supports_color_temperature) { msg.min_mireds = traits.get_min_mireds(); msg.max_mireds = traits.get_max_mireds(); } @@ -353,6 +366,8 @@ void APIConnection::light_command(const LightCommandRequest &msg) { call.set_state(msg.state); if (msg.has_brightness) call.set_brightness(msg.brightness); + if (msg.has_color_mode) + call.set_color_mode(static_cast(msg.color_mode)); if (msg.has_color_brightness) call.set_color_brightness(msg.color_brightness); if (msg.has_rgb) { @@ -364,6 +379,10 @@ void APIConnection::light_command(const LightCommandRequest &msg) { call.set_white(msg.white); if (msg.has_color_temperature) call.set_color_temperature(msg.color_temperature); + if (msg.has_cold_white) + call.set_cold_white(msg.cold_white); + if (msg.has_warm_white) + call.set_warm_white(msg.warm_white); if (msg.has_transition_length) call.set_transition_length(msg.transition_length); if (msg.has_flash_length) @@ -655,7 +674,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { HelloResponse resp; resp.api_version_major = 1; - resp.api_version_minor = 5; + resp.api_version_minor = 6; resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; this->connection_state_ = ConnectionState::CONNECTED; return resp; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 057d71324f..79092cb511 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -62,6 +62,32 @@ template<> const char *proto_enum_to_string(enums::FanDirec return "UNKNOWN"; } } +template<> const char *proto_enum_to_string(enums::ColorMode value) { + switch (value) { + case enums::COLOR_MODE_UNKNOWN: + return "COLOR_MODE_UNKNOWN"; + case enums::COLOR_MODE_ON_OFF: + return "COLOR_MODE_ON_OFF"; + case enums::COLOR_MODE_BRIGHTNESS: + return "COLOR_MODE_BRIGHTNESS"; + case enums::COLOR_MODE_WHITE: + return "COLOR_MODE_WHITE"; + case enums::COLOR_MODE_COLOR_TEMPERATURE: + return "COLOR_MODE_COLOR_TEMPERATURE"; + case enums::COLOR_MODE_COLD_WARM_WHITE: + return "COLOR_MODE_COLD_WARM_WHITE"; + case enums::COLOR_MODE_RGB: + return "COLOR_MODE_RGB"; + case enums::COLOR_MODE_RGB_WHITE: + return "COLOR_MODE_RGB_WHITE"; + case enums::COLOR_MODE_RGB_COLOR_TEMPERATURE: + return "COLOR_MODE_RGB_COLOR_TEMPERATURE"; + case enums::COLOR_MODE_RGB_COLD_WARM_WHITE: + return "COLOR_MODE_RGB_COLD_WARM_WHITE"; + default: + return "UNKNOWN"; + } +} template<> const char *proto_enum_to_string(enums::SensorStateClass value) { switch (value) { case enums::STATE_CLASS_NONE: @@ -1117,20 +1143,24 @@ void FanCommandRequest::dump_to(std::string &out) const { } bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { + case 12: { + this->supported_color_modes.push_back(value.as_enum()); + return true; + } case 5: { - this->supports_brightness = value.as_bool(); + this->legacy_supports_brightness = value.as_bool(); return true; } case 6: { - this->supports_rgb = value.as_bool(); + this->legacy_supports_rgb = value.as_bool(); return true; } case 7: { - this->supports_white_value = value.as_bool(); + this->legacy_supports_white_value = value.as_bool(); return true; } case 8: { - this->supports_color_temperature = value.as_bool(); + this->legacy_supports_color_temperature = value.as_bool(); return true; } default: @@ -1182,10 +1212,13 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(2, this->key); buffer.encode_string(3, this->name); buffer.encode_string(4, this->unique_id); - buffer.encode_bool(5, this->supports_brightness); - buffer.encode_bool(6, this->supports_rgb); - buffer.encode_bool(7, this->supports_white_value); - buffer.encode_bool(8, this->supports_color_temperature); + for (auto &it : this->supported_color_modes) { + buffer.encode_enum(12, it, true); + } + buffer.encode_bool(5, this->legacy_supports_brightness); + buffer.encode_bool(6, this->legacy_supports_rgb); + buffer.encode_bool(7, this->legacy_supports_white_value); + buffer.encode_bool(8, this->legacy_supports_color_temperature); buffer.encode_float(9, this->min_mireds); buffer.encode_float(10, this->max_mireds); for (auto &it : this->effects) { @@ -1212,20 +1245,26 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append("'").append(this->unique_id).append("'"); out.append("\n"); - out.append(" supports_brightness: "); - out.append(YESNO(this->supports_brightness)); + for (const auto &it : this->supported_color_modes) { + out.append(" supported_color_modes: "); + out.append(proto_enum_to_string(it)); + out.append("\n"); + } + + out.append(" legacy_supports_brightness: "); + out.append(YESNO(this->legacy_supports_brightness)); out.append("\n"); - out.append(" supports_rgb: "); - out.append(YESNO(this->supports_rgb)); + out.append(" legacy_supports_rgb: "); + out.append(YESNO(this->legacy_supports_rgb)); out.append("\n"); - out.append(" supports_white_value: "); - out.append(YESNO(this->supports_white_value)); + out.append(" legacy_supports_white_value: "); + out.append(YESNO(this->legacy_supports_white_value)); out.append("\n"); - out.append(" supports_color_temperature: "); - out.append(YESNO(this->supports_color_temperature)); + out.append(" legacy_supports_color_temperature: "); + out.append(YESNO(this->legacy_supports_color_temperature)); out.append("\n"); out.append(" min_mireds: "); @@ -1251,6 +1290,10 @@ bool LightStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->state = value.as_bool(); return true; } + case 11: { + this->color_mode = value.as_enum(); + return true; + } default: return false; } @@ -1299,6 +1342,14 @@ bool LightStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { this->color_temperature = value.as_float(); return true; } + case 12: { + this->cold_white = value.as_float(); + return true; + } + case 13: { + this->warm_white = value.as_float(); + return true; + } default: return false; } @@ -1307,12 +1358,15 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_bool(2, this->state); buffer.encode_float(3, this->brightness); + buffer.encode_enum(11, this->color_mode); buffer.encode_float(10, this->color_brightness); buffer.encode_float(4, this->red); buffer.encode_float(5, this->green); buffer.encode_float(6, this->blue); buffer.encode_float(7, this->white); buffer.encode_float(8, this->color_temperature); + buffer.encode_float(12, this->cold_white); + buffer.encode_float(13, this->warm_white); buffer.encode_string(9, this->effect); } void LightStateResponse::dump_to(std::string &out) const { @@ -1332,6 +1386,10 @@ void LightStateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); + out.append(" color_mode: "); + out.append(proto_enum_to_string(this->color_mode)); + out.append("\n"); + out.append(" color_brightness: "); sprintf(buffer, "%g", this->color_brightness); out.append(buffer); @@ -1362,6 +1420,16 @@ void LightStateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); + out.append(" cold_white: "); + sprintf(buffer, "%g", this->cold_white); + out.append(buffer); + out.append("\n"); + + out.append(" warm_white: "); + sprintf(buffer, "%g", this->warm_white); + out.append(buffer); + out.append("\n"); + out.append(" effect: "); out.append("'").append(this->effect).append("'"); out.append("\n"); @@ -1381,6 +1449,14 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { this->has_brightness = value.as_bool(); return true; } + case 22: { + this->has_color_mode = value.as_bool(); + return true; + } + case 23: { + this->color_mode = value.as_enum(); + return true; + } case 20: { this->has_color_brightness = value.as_bool(); return true; @@ -1397,6 +1473,14 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { this->has_color_temperature = value.as_bool(); return true; } + case 24: { + this->has_cold_white = value.as_bool(); + return true; + } + case 26: { + this->has_warm_white = value.as_bool(); + return true; + } case 14: { this->has_transition_length = value.as_bool(); return true; @@ -1465,6 +1549,14 @@ bool LightCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { this->color_temperature = value.as_float(); return true; } + case 25: { + this->cold_white = value.as_float(); + return true; + } + case 27: { + this->warm_white = value.as_float(); + return true; + } default: return false; } @@ -1475,6 +1567,8 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(3, this->state); buffer.encode_bool(4, this->has_brightness); buffer.encode_float(5, this->brightness); + buffer.encode_bool(22, this->has_color_mode); + buffer.encode_enum(23, this->color_mode); buffer.encode_bool(20, this->has_color_brightness); buffer.encode_float(21, this->color_brightness); buffer.encode_bool(6, this->has_rgb); @@ -1485,6 +1579,10 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(11, this->white); buffer.encode_bool(12, this->has_color_temperature); buffer.encode_float(13, this->color_temperature); + buffer.encode_bool(24, this->has_cold_white); + buffer.encode_float(25, this->cold_white); + buffer.encode_bool(26, this->has_warm_white); + buffer.encode_float(27, this->warm_white); buffer.encode_bool(14, this->has_transition_length); buffer.encode_uint32(15, this->transition_length); buffer.encode_bool(16, this->has_flash_length); @@ -1517,6 +1615,14 @@ void LightCommandRequest::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); + out.append(" has_color_mode: "); + out.append(YESNO(this->has_color_mode)); + out.append("\n"); + + out.append(" color_mode: "); + out.append(proto_enum_to_string(this->color_mode)); + out.append("\n"); + out.append(" has_color_brightness: "); out.append(YESNO(this->has_color_brightness)); out.append("\n"); @@ -1563,6 +1669,24 @@ void LightCommandRequest::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); + out.append(" has_cold_white: "); + out.append(YESNO(this->has_cold_white)); + out.append("\n"); + + out.append(" cold_white: "); + sprintf(buffer, "%g", this->cold_white); + out.append(buffer); + out.append("\n"); + + out.append(" has_warm_white: "); + out.append(YESNO(this->has_warm_white)); + out.append("\n"); + + out.append(" warm_white: "); + sprintf(buffer, "%g", this->warm_white); + out.append(buffer); + out.append("\n"); + out.append(" has_transition_length: "); out.append(YESNO(this->has_transition_length)); out.append("\n"); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 0551508b4b..4c92b7b00d 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -32,6 +32,18 @@ enum FanDirection : uint32_t { FAN_DIRECTION_FORWARD = 0, FAN_DIRECTION_REVERSE = 1, }; +enum ColorMode : uint32_t { + COLOR_MODE_UNKNOWN = 0, + COLOR_MODE_ON_OFF = 1, + COLOR_MODE_BRIGHTNESS = 2, + COLOR_MODE_WHITE = 7, + COLOR_MODE_COLOR_TEMPERATURE = 11, + COLOR_MODE_COLD_WARM_WHITE = 19, + COLOR_MODE_RGB = 35, + COLOR_MODE_RGB_WHITE = 39, + COLOR_MODE_RGB_COLOR_TEMPERATURE = 47, + COLOR_MODE_RGB_COLD_WARM_WHITE = 51, +}; enum SensorStateClass : uint32_t { STATE_CLASS_NONE = 0, STATE_CLASS_MEASUREMENT = 1, @@ -356,10 +368,11 @@ class ListEntitiesLightResponse : public ProtoMessage { uint32_t key{0}; std::string name{}; std::string unique_id{}; - bool supports_brightness{false}; - bool supports_rgb{false}; - bool supports_white_value{false}; - bool supports_color_temperature{false}; + std::vector supported_color_modes{}; + bool legacy_supports_brightness{false}; + bool legacy_supports_rgb{false}; + bool legacy_supports_white_value{false}; + bool legacy_supports_color_temperature{false}; float min_mireds{0.0f}; float max_mireds{0.0f}; std::vector effects{}; @@ -376,12 +389,15 @@ class LightStateResponse : public ProtoMessage { uint32_t key{0}; bool state{false}; float brightness{0.0f}; + enums::ColorMode color_mode{}; float color_brightness{0.0f}; float red{0.0f}; float green{0.0f}; float blue{0.0f}; float white{0.0f}; float color_temperature{0.0f}; + float cold_white{0.0f}; + float warm_white{0.0f}; std::string effect{}; void encode(ProtoWriteBuffer buffer) const override; void dump_to(std::string &out) const override; @@ -398,6 +414,8 @@ class LightCommandRequest : public ProtoMessage { bool state{false}; bool has_brightness{false}; float brightness{0.0f}; + bool has_color_mode{false}; + enums::ColorMode color_mode{}; bool has_color_brightness{false}; float color_brightness{0.0f}; bool has_rgb{false}; @@ -408,6 +426,10 @@ class LightCommandRequest : public ProtoMessage { float white{0.0f}; bool has_color_temperature{false}; float color_temperature{0.0f}; + bool has_cold_white{false}; + float cold_white{0.0f}; + bool has_warm_white{false}; + float warm_white{0.0f}; bool has_transition_length{false}; uint32_t transition_length{0}; bool has_flash_length{false}; diff --git a/esphome/components/binary/light/binary_light_output.h b/esphome/components/binary/light/binary_light_output.h index 731973bdad..86c83aff5c 100644 --- a/esphome/components/binary/light/binary_light_output.h +++ b/esphome/components/binary/light/binary_light_output.h @@ -12,7 +12,7 @@ class BinaryLightOutput : public light::LightOutput { void set_output(output::BinaryOutput *output) { output_ = output; } light::LightTraits get_traits() override { auto traits = light::LightTraits(); - traits.set_supports_brightness(false); + traits.set_supported_color_modes({light::ColorMode::ON_OFF}); return traits; } void write_state(light::LightState *state) override { diff --git a/esphome/components/cwww/cwww_light_output.h b/esphome/components/cwww/cwww_light_output.h index 3351a98d24..2b7698ce5a 100644 --- a/esphome/components/cwww/cwww_light_output.h +++ b/esphome/components/cwww/cwww_light_output.h @@ -16,10 +16,7 @@ class CWWWLightOutput : public light::LightOutput { void set_constant_brightness(bool constant_brightness) { constant_brightness_ = constant_brightness; } light::LightTraits get_traits() override { auto traits = light::LightTraits(); - traits.set_supports_brightness(true); - traits.set_supports_rgb(false); - traits.set_supports_rgb_white_value(false); - traits.set_supports_color_temperature(true); + traits.set_supported_color_modes({light::ColorMode::COLD_WARM_WHITE}); traits.set_min_mireds(this->cold_white_temperature_); traits.set_max_mireds(this->warm_white_temperature_); return traits; @@ -34,8 +31,8 @@ class CWWWLightOutput : public light::LightOutput { protected: output::FloatOutput *cold_white_; output::FloatOutput *warm_white_; - float cold_white_temperature_; - float warm_white_temperature_; + float cold_white_temperature_{0}; + float warm_white_temperature_{0}; bool constant_brightness_; }; diff --git a/esphome/components/cwww/light.py b/esphome/components/cwww/light.py index 1a027a86b4..734f9aa1e7 100644 --- a/esphome/components/cwww/light.py +++ b/esphome/components/cwww/light.py @@ -20,11 +20,14 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(CWWWLightOutput), cv.Required(CONF_COLD_WHITE): cv.use_id(output.FloatOutput), cv.Required(CONF_WARM_WHITE): cv.use_id(output.FloatOutput), - cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, - cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, + cv.Optional(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, + cv.Optional(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, cv.Optional(CONF_CONSTANT_BRIGHTNESS, default=False): cv.boolean, } ), + cv.has_none_or_all_keys( + [CONF_COLD_WHITE_COLOR_TEMPERATURE, CONF_WARM_WHITE_COLOR_TEMPERATURE] + ), light.validate_color_temperature_channels, ) @@ -32,11 +35,19 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) await light.register_light(var, config) + cwhite = await cg.get_variable(config[CONF_COLD_WHITE]) cg.add(var.set_cold_white(cwhite)) - cg.add(var.set_cold_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE])) + if CONF_COLD_WHITE_COLOR_TEMPERATURE in config: + cg.add( + var.set_cold_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE]) + ) wwhite = await cg.get_variable(config[CONF_WARM_WHITE]) cg.add(var.set_warm_white(wwhite)) - cg.add(var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE])) + if CONF_WARM_WHITE_COLOR_TEMPERATURE in config: + cg.add( + var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE]) + ) + cg.add(var.set_constant_brightness(config[CONF_CONSTANT_BRIGHTNESS])) diff --git a/esphome/components/demo/demo_light.h b/esphome/components/demo/demo_light.h index 989dbf3306..2007e9ff50 100644 --- a/esphome/components/demo/demo_light.h +++ b/esphome/components/demo/demo_light.h @@ -28,45 +28,31 @@ class DemoLight : public light::LightOutput, public Component { void set_type(DemoLightType type) { type_ = type; } light::LightTraits get_traits() override { light::LightTraits traits{}; - // brightness - // rgb - // rgb_white_value - // color_temperature, min_mireds, max_mireds - // color_interlock switch (type_) { case DemoLightType::TYPE_1: + traits.set_supported_color_modes({light::ColorMode::ON_OFF}); break; case DemoLightType::TYPE_2: - traits.set_supports_brightness(true); + traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS}); break; case DemoLightType::TYPE_3: - traits.set_supports_brightness(true); - traits.set_supports_rgb(true); + traits.set_supported_color_modes({light::ColorMode::RGB}); break; case DemoLightType::TYPE_4: - traits.set_supports_brightness(true); - traits.set_supports_rgb(true); - traits.set_supports_rgb_white_value(true); + traits.set_supported_color_modes({light::ColorMode::RGB_WHITE}); break; case DemoLightType::TYPE_5: - traits.set_supports_brightness(true); - traits.set_supports_rgb(true); - traits.set_supports_rgb_white_value(true); - traits.set_supports_color_temperature(true); + traits.set_supported_color_modes({light::ColorMode::RGB_COLOR_TEMPERATURE}); traits.set_min_mireds(153); traits.set_max_mireds(500); break; case DemoLightType::TYPE_6: - traits.set_supports_brightness(true); - traits.set_supports_color_temperature(true); + traits.set_supported_color_modes({light::ColorMode::COLD_WARM_WHITE}); traits.set_min_mireds(153); traits.set_max_mireds(500); break; case DemoLightType::TYPE_7: - traits.set_supports_brightness(true); - traits.set_supports_rgb(true); - traits.set_supports_rgb_white_value(true); - traits.set_supports_color_interlock(true); + traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::WHITE}); break; } return traits; diff --git a/esphome/components/fastled_base/fastled_light.h b/esphome/components/fastled_base/fastled_light.h index 59d143dbef..ac6acc95a5 100644 --- a/esphome/components/fastled_base/fastled_light.h +++ b/esphome/components/fastled_base/fastled_light.h @@ -208,8 +208,7 @@ class FastLEDLightOutput : public light::AddressableLight { // (In most use cases you won't need these) light::LightTraits get_traits() override { auto traits = light::LightTraits(); - traits.set_supports_brightness(true); - traits.set_supports_rgb(true); + traits.set_supported_color_modes({light::ColorMode::RGB}); return traits; } void setup() override; diff --git a/esphome/components/hbridge/hbridge_light_output.h b/esphome/components/hbridge/hbridge_light_output.h index 03a5b3a88c..2f4f87134c 100644 --- a/esphome/components/hbridge/hbridge_light_output.h +++ b/esphome/components/hbridge/hbridge_light_output.h @@ -18,10 +18,7 @@ class HBridgeLightOutput : public PollingComponent, public light::LightOutput { light::LightTraits get_traits() override { auto traits = light::LightTraits(); - traits.set_supports_brightness(true); // Dimming - traits.set_supports_rgb(false); - traits.set_supports_rgb_white_value(true); // hbridge color - traits.set_supports_color_temperature(false); + traits.set_supported_color_modes({light::ColorMode::COLD_WARM_WHITE}); return traits; } @@ -31,11 +28,11 @@ class HBridgeLightOutput : public PollingComponent, public light::LightOutput { // This method runs around 60 times per second // We cannot do the PWM ourselves so we are reliant on the hardware PWM if (!this->forward_direction_) { // First LED Direction - this->pinb_pin_->set_level(this->duty_off_); this->pina_pin_->set_level(this->pina_duty_); + this->pinb_pin_->set_level(0); this->forward_direction_ = true; } else { // Second LED Direction - this->pina_pin_->set_level(this->duty_off_); + this->pina_pin_->set_level(0); this->pinb_pin_->set_level(this->pinb_duty_); this->forward_direction_ = false; } @@ -44,23 +41,7 @@ class HBridgeLightOutput : public PollingComponent, public light::LightOutput { float get_setup_priority() const override { return setup_priority::HARDWARE; } void write_state(light::LightState *state) override { - float bright; - state->current_values_as_brightness(&bright); - - state->set_gamma_correct(0); - float red, green, blue, white; - state->current_values_as_rgbw(&red, &green, &blue, &white); - - if ((white / bright) > 0.55) { - this->pina_duty_ = (bright * (1 - (white / bright))); - this->pinb_duty_ = bright; - } else if (white < 0.45) { - this->pina_duty_ = bright; - this->pinb_duty_ = white; - } else { - this->pina_duty_ = bright; - this->pinb_duty_ = bright; - } + state->current_values_as_cwww(&this->pina_duty_, &this->pinb_duty_, false); } protected: @@ -68,7 +49,6 @@ class HBridgeLightOutput : public PollingComponent, public light::LightOutput { output::FloatOutput *pinb_pin_; float pina_duty_ = 0; float pinb_duty_ = 0; - float duty_off_ = 0; bool forward_direction_ = false; }; diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index 119ff3703c..ec7d6bcbc0 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -108,7 +108,9 @@ ADDRESSABLE_LIGHT_SCHEMA = RGB_LIGHT_SCHEMA.extend( def validate_color_temperature_channels(value): if ( - value[CONF_COLD_WHITE_COLOR_TEMPERATURE] + CONF_COLD_WHITE_COLOR_TEMPERATURE in value + and CONF_WARM_WHITE_COLOR_TEMPERATURE in value + and value[CONF_COLD_WHITE_COLOR_TEMPERATURE] >= value[CONF_WARM_WHITE_COLOR_TEMPERATURE] ): raise cv.Invalid( diff --git a/esphome/components/light/automation.h b/esphome/components/light/automation.h index 02c6163555..a816049f08 100644 --- a/esphome/components/light/automation.h +++ b/esphome/components/light/automation.h @@ -27,6 +27,7 @@ template class LightControlAction : public Action { public: explicit LightControlAction(LightState *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(ColorMode, color_mode) TEMPLATABLE_VALUE(bool, state) TEMPLATABLE_VALUE(uint32_t, transition_length) TEMPLATABLE_VALUE(uint32_t, flash_length) @@ -37,10 +38,13 @@ template class LightControlAction : public Action { TEMPLATABLE_VALUE(float, blue) TEMPLATABLE_VALUE(float, white) TEMPLATABLE_VALUE(float, color_temperature) + TEMPLATABLE_VALUE(float, cold_white) + TEMPLATABLE_VALUE(float, warm_white) TEMPLATABLE_VALUE(std::string, effect) void play(Ts... x) override { auto call = this->parent_->make_call(); + call.set_color_mode(this->color_mode_.optional_value(x...)); call.set_state(this->state_.optional_value(x...)); call.set_brightness(this->brightness_.optional_value(x...)); call.set_color_brightness(this->color_brightness_.optional_value(x...)); @@ -49,6 +53,8 @@ template class LightControlAction : public Action { call.set_blue(this->blue_.optional_value(x...)); call.set_white(this->white_.optional_value(x...)); call.set_color_temperature(this->color_temperature_.optional_value(x...)); + call.set_cold_white(this->cold_white_.optional_value(x...)); + call.set_warm_white(this->warm_white_.optional_value(x...)); call.set_effect(this->effect_.optional_value(x...)); call.set_flash_length(this->flash_length_.optional_value(x...)); call.set_transition_length(this->transition_length_.optional_value(x...)); diff --git a/esphome/components/light/automation.py b/esphome/components/light/automation.py index 85741f1ffd..cfba273565 100644 --- a/esphome/components/light/automation.py +++ b/esphome/components/light/automation.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome import automation from esphome.const import ( CONF_ID, + CONF_COLOR_MODE, CONF_TRANSITION_LENGTH, CONF_STATE, CONF_FLASH_LENGTH, @@ -14,10 +15,14 @@ from esphome.const import ( CONF_BLUE, CONF_WHITE, CONF_COLOR_TEMPERATURE, + CONF_COLD_WHITE, + CONF_WARM_WHITE, CONF_RANGE_FROM, CONF_RANGE_TO, ) from .types import ( + ColorMode, + COLOR_MODES, DimRelativeAction, ToggleAction, LightState, @@ -55,6 +60,7 @@ async def light_toggle_to_code(config, action_id, template_arg, args): LIGHT_CONTROL_ACTION_SCHEMA = cv.Schema( { cv.Required(CONF_ID): cv.use_id(LightState), + cv.Optional(CONF_COLOR_MODE): cv.enum(COLOR_MODES, upper=True, space="_"), cv.Optional(CONF_STATE): cv.templatable(cv.boolean), cv.Exclusive(CONF_TRANSITION_LENGTH, "transformer"): cv.templatable( cv.positive_time_period_milliseconds @@ -70,6 +76,8 @@ LIGHT_CONTROL_ACTION_SCHEMA = cv.Schema( cv.Optional(CONF_BLUE): cv.templatable(cv.percentage), cv.Optional(CONF_WHITE): cv.templatable(cv.percentage), cv.Optional(CONF_COLOR_TEMPERATURE): cv.templatable(cv.color_temperature), + cv.Optional(CONF_COLD_WHITE): cv.templatable(cv.percentage), + cv.Optional(CONF_WARM_WHITE): cv.templatable(cv.percentage), } ) LIGHT_TURN_OFF_ACTION_SCHEMA = automation.maybe_simple_id( @@ -102,6 +110,9 @@ LIGHT_TURN_ON_ACTION_SCHEMA = automation.maybe_simple_id( async def light_control_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) + if CONF_COLOR_MODE in config: + template_ = await cg.templatable(config[CONF_COLOR_MODE], args, ColorMode) + cg.add(var.set_color_mode(template_)) if CONF_STATE in config: template_ = await cg.templatable(config[CONF_STATE], args, bool) cg.add(var.set_state(template_)) @@ -134,6 +145,12 @@ async def light_control_to_code(config, action_id, template_arg, args): if CONF_COLOR_TEMPERATURE in config: template_ = await cg.templatable(config[CONF_COLOR_TEMPERATURE], args, float) cg.add(var.set_color_temperature(template_)) + if CONF_COLD_WHITE in config: + template_ = await cg.templatable(config[CONF_COLD_WHITE], args, float) + cg.add(var.set_cold_white(template_)) + if CONF_WARM_WHITE in config: + template_ = await cg.templatable(config[CONF_WARM_WHITE], args, float) + cg.add(var.set_warm_white(template_)) if CONF_EFFECT in config: template_ = await cg.templatable(config[CONF_EFFECT], args, cg.std_string) cg.add(var.set_effect(template_)) diff --git a/esphome/components/light/base_light_effects.h b/esphome/components/light/base_light_effects.h index eb92fec642..f66b90f665 100644 --- a/esphome/components/light/base_light_effects.h +++ b/esphome/components/light/base_light_effects.h @@ -57,16 +57,31 @@ class RandomLightEffect : public LightEffect { if (now - this->last_color_change_ < this->update_interval_) { return; } + + auto color_mode = this->state_->remote_values.get_color_mode(); auto call = this->state_->turn_on(); - if (this->state_->get_traits().get_supports_rgb()) { - call.set_red_if_supported(random_float()); - call.set_green_if_supported(random_float()); - call.set_blue_if_supported(random_float()); - call.set_white_if_supported(random_float()); - } else { - call.set_brightness_if_supported(random_float()); + bool changed = false; + if (color_mode & ColorCapability::RGB) { + call.set_red(random_float()); + call.set_green(random_float()); + call.set_blue(random_float()); + changed = true; + } + if (color_mode & ColorCapability::COLOR_TEMPERATURE) { + float min = this->state_->get_traits().get_min_mireds(); + float max = this->state_->get_traits().get_max_mireds(); + call.set_color_temperature(min + random_float() * (max - min)); + changed = true; + } + if (color_mode & ColorCapability::COLD_WARM_WHITE) { + call.set_cold_white(random_float()); + call.set_warm_white(random_float()); + changed = true; + } + if (!changed) { + // only randomize brightness if there's no colored option available + call.set_brightness(random_float()); } - call.set_color_temperature_if_supported(random_float()); call.set_transition_length_if_supported(this->transition_length_); call.set_publish(true); call.set_save(false); @@ -142,7 +157,6 @@ class StrobeLightEffect : public LightEffect { if (!color.is_on()) { // Don't turn the light off, otherwise the light effect will be stopped call.set_brightness_if_supported(0.0f); - call.set_white_if_supported(0.0f); call.set_state(true); } call.set_publish(false); @@ -177,13 +191,16 @@ class FlickerLightEffect : public LightEffect { out.set_green(remote.get_green() * beta + current.get_green() * alpha + (random_cubic_float() * this->intensity_)); out.set_blue(remote.get_blue() * beta + current.get_blue() * alpha + (random_cubic_float() * this->intensity_)); out.set_white(remote.get_white() * beta + current.get_white() * alpha + (random_cubic_float() * this->intensity_)); + out.set_cold_white(remote.get_cold_white() * beta + current.get_cold_white() * alpha + + (random_cubic_float() * this->intensity_)); + out.set_warm_white(remote.get_warm_white() * beta + current.get_warm_white() * alpha + + (random_cubic_float() * this->intensity_)); auto traits = this->state_->get_traits(); auto call = this->state_->make_call(); call.set_publish(false); call.set_save(false); - if (traits.get_supports_brightness()) - call.set_transition_length(0); + call.set_transition_length_if_supported(0); call.from_light_color_values(out); call.set_state(true); call.perform(); diff --git a/esphome/components/light/color_mode.h b/esphome/components/light/color_mode.h new file mode 100644 index 0000000000..0f5b7b4b93 --- /dev/null +++ b/esphome/components/light/color_mode.h @@ -0,0 +1,107 @@ +#pragma once + +#include + +namespace esphome { +namespace light { + +/// Color capabilities are the various outputs that a light has and that can be independently controlled by the user. +enum class ColorCapability : uint8_t { + /// Light can be turned on/off. + ON_OFF = 1 << 0, + /// Master brightness of the light can be controlled. + BRIGHTNESS = 1 << 1, + /// Brightness of white channel can be controlled separately from other channels. + WHITE = 1 << 2, + /// Color temperature can be controlled. + COLOR_TEMPERATURE = 1 << 3, + /// Brightness of cold and warm white output can be controlled. + COLD_WARM_WHITE = 1 << 4, + /// Color can be controlled using RGB format (includes a brightness control for the color). + RGB = 1 << 5 +}; + +/// Helper class to allow bitwise operations on ColorCapability +class ColorCapabilityHelper { + public: + constexpr ColorCapabilityHelper(ColorCapability val) : val_(val) {} + constexpr operator ColorCapability() const { return val_; } + constexpr operator uint8_t() const { return static_cast(val_); } + constexpr operator bool() const { return static_cast(val_) != 0; } + + protected: + ColorCapability val_; +}; +constexpr ColorCapabilityHelper operator&(ColorCapability lhs, ColorCapability rhs) { + return static_cast(static_cast(lhs) & static_cast(rhs)); +} +constexpr ColorCapabilityHelper operator&(ColorCapabilityHelper lhs, ColorCapability rhs) { + return static_cast(static_cast(lhs) & static_cast(rhs)); +} +constexpr ColorCapabilityHelper operator|(ColorCapability lhs, ColorCapability rhs) { + return static_cast(static_cast(lhs) | static_cast(rhs)); +} +constexpr ColorCapabilityHelper operator|(ColorCapabilityHelper lhs, ColorCapability rhs) { + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +/// Color modes are a combination of color capabilities that can be used at the same time. +enum class ColorMode : uint8_t { + /// No color mode configured (cannot be a supported mode, only active when light is off). + UNKNOWN = 0, + /// Only on/off control. + ON_OFF = (uint8_t) ColorCapability::ON_OFF, + /// Dimmable light. + BRIGHTNESS = (uint8_t) ColorCapability::BRIGHTNESS, + /// White output only (use only if the light also has another color mode such as RGB). + WHITE = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::WHITE), + /// Controllable color temperature output. + COLOR_TEMPERATURE = + (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::COLOR_TEMPERATURE), + /// Cold and warm white output with individually controllable brightness. + COLD_WARM_WHITE = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::COLD_WARM_WHITE), + /// RGB color output. + RGB = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::RGB), + /// RGB color output and a separate white output. + RGB_WHITE = + (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::RGB | ColorCapability::WHITE), + /// RGB color output and a separate white output with controllable color temperature. + RGB_COLOR_TEMPERATURE = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::RGB | + ColorCapability::WHITE | ColorCapability::COLOR_TEMPERATURE), + /// RGB color output, and separate cold and warm white outputs. + RGB_COLD_WARM_WHITE = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::RGB | + ColorCapability::COLD_WARM_WHITE), +}; + +/// Helper class to allow bitwise operations on ColorMode with ColorCapability +class ColorModeHelper { + public: + constexpr ColorModeHelper(ColorMode val) : val_(val) {} + constexpr operator ColorMode() const { return val_; } + constexpr operator uint8_t() const { return static_cast(val_); } + constexpr operator bool() const { return static_cast(val_) != 0; } + + protected: + ColorMode val_; +}; +constexpr ColorModeHelper operator&(ColorMode lhs, ColorMode rhs) { + return static_cast(static_cast(lhs) & static_cast(rhs)); +} +constexpr ColorModeHelper operator&(ColorMode lhs, ColorCapability rhs) { + return static_cast(static_cast(lhs) & static_cast(rhs)); +} +constexpr ColorModeHelper operator&(ColorModeHelper lhs, ColorMode rhs) { + return static_cast(static_cast(lhs) & static_cast(rhs)); +} +constexpr ColorModeHelper operator|(ColorMode lhs, ColorMode rhs) { + return static_cast(static_cast(lhs) | static_cast(rhs)); +} +constexpr ColorModeHelper operator|(ColorMode lhs, ColorCapability rhs) { + return static_cast(static_cast(lhs) | static_cast(rhs)); +} +constexpr ColorModeHelper operator|(ColorModeHelper lhs, ColorMode rhs) { + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +} // namespace light +} // namespace esphome diff --git a/esphome/components/light/effects.py b/esphome/components/light/effects.py index f6ce812c34..be558a8140 100644 --- a/esphome/components/light/effects.py +++ b/esphome/components/light/effects.py @@ -12,11 +12,15 @@ from esphome.const import ( CONF_STATE, CONF_DURATION, CONF_BRIGHTNESS, + CONF_COLOR_MODE, CONF_COLOR_BRIGHTNESS, CONF_RED, CONF_GREEN, CONF_BLUE, CONF_WHITE, + CONF_COLOR_TEMPERATURE, + CONF_COLD_WHITE, + CONF_WARM_WHITE, CONF_ALPHA, CONF_INTENSITY, CONF_SPEED, @@ -27,6 +31,8 @@ from esphome.const import ( ) from esphome.util import Registry from .types import ( + ColorMode, + COLOR_MODES, LambdaLightEffect, PulseLightEffect, RandomLightEffect, @@ -212,11 +218,17 @@ async def random_effect_to_code(config, effect_id): { cv.Optional(CONF_STATE, default=True): cv.boolean, cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, + cv.Optional(CONF_COLOR_MODE): cv.enum( + COLOR_MODES, upper=True, space="_" + ), cv.Optional(CONF_COLOR_BRIGHTNESS, default=1.0): cv.percentage, cv.Optional(CONF_RED, default=1.0): cv.percentage, cv.Optional(CONF_GREEN, default=1.0): cv.percentage, cv.Optional(CONF_BLUE, default=1.0): cv.percentage, cv.Optional(CONF_WHITE, default=1.0): cv.percentage, + cv.Optional(CONF_COLOR_TEMPERATURE): cv.color_temperature, + cv.Optional(CONF_COLD_WHITE, default=1.0): cv.percentage, + cv.Optional(CONF_WARM_WHITE, default=1.0): cv.percentage, cv.Required( CONF_DURATION ): cv.positive_time_period_milliseconds, @@ -225,11 +237,15 @@ async def random_effect_to_code(config, effect_id): cv.has_at_least_one_key( CONF_STATE, CONF_BRIGHTNESS, + CONF_COLOR_MODE, CONF_COLOR_BRIGHTNESS, CONF_RED, CONF_GREEN, CONF_BLUE, CONF_WHITE, + CONF_COLOR_TEMPERATURE, + CONF_COLD_WHITE, + CONF_WARM_WHITE, ), ), cv.Length(min=2), @@ -246,6 +262,7 @@ async def strobe_effect_to_code(config, effect_id): ( "color", LightColorValues( + color.get(CONF_COLOR_MODE, ColorMode.UNKNOWN), color[CONF_STATE], color[CONF_BRIGHTNESS], color[CONF_COLOR_BRIGHTNESS], @@ -253,6 +270,9 @@ async def strobe_effect_to_code(config, effect_id): color[CONF_GREEN], color[CONF_BLUE], color[CONF_WHITE], + color.get(CONF_COLOR_TEMPERATURE, 0.0), + color[CONF_COLD_WHITE], + color[CONF_WARM_WHITE], ), ), ("duration", color[CONF_DURATION]), diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index d7e8ce6298..536018c33b 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -7,95 +7,42 @@ namespace light { static const char *const TAG = "light"; -#ifdef USE_JSON -LightCall &LightCall::parse_color_json(JsonObject &root) { - if (root.containsKey("state")) { - auto val = parse_on_off(root["state"]); - switch (val) { - case PARSE_ON: - this->set_state(true); - break; - case PARSE_OFF: - this->set_state(false); - break; - case PARSE_TOGGLE: - this->set_state(!this->parent_->remote_values.is_on()); - break; - case PARSE_NONE: - break; - } +static const char *color_mode_to_human(ColorMode color_mode) { + switch (color_mode) { + case ColorMode::UNKNOWN: + return "Unknown"; + case ColorMode::WHITE: + return "White"; + case ColorMode::COLOR_TEMPERATURE: + return "Color temperature"; + case ColorMode::COLD_WARM_WHITE: + return "Cold/warm white"; + case ColorMode::RGB: + return "RGB"; + case ColorMode::RGB_WHITE: + return "RGBW"; + case ColorMode::RGB_COLD_WARM_WHITE: + return "RGB + cold/warm white"; + case ColorMode::RGB_COLOR_TEMPERATURE: + return "RGB + color temperature"; + default: + return ""; } - - if (root.containsKey("brightness")) { - this->set_brightness(float(root["brightness"]) / 255.0f); - } - - if (root.containsKey("color")) { - JsonObject &color = root["color"]; - // HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness. - float max_rgb = 0.0f; - if (color.containsKey("r")) { - float r = float(color["r"]) / 255.0f; - max_rgb = fmaxf(max_rgb, r); - this->set_red(r); - } - if (color.containsKey("g")) { - float g = float(color["g"]) / 255.0f; - max_rgb = fmaxf(max_rgb, g); - this->set_green(g); - } - if (color.containsKey("b")) { - float b = float(color["b"]) / 255.0f; - max_rgb = fmaxf(max_rgb, b); - this->set_blue(b); - } - if (color.containsKey("r") || color.containsKey("g") || color.containsKey("b")) { - this->set_color_brightness(max_rgb); - } - } - - if (root.containsKey("white_value")) { - this->set_white(float(root["white_value"]) / 255.0f); - } - - if (root.containsKey("color_temp")) { - this->set_color_temperature(float(root["color_temp"])); - } - - return *this; } -LightCall &LightCall::parse_json(JsonObject &root) { - this->parse_color_json(root); - - if (root.containsKey("flash")) { - auto length = uint32_t(float(root["flash"]) * 1000); - this->set_flash_length(length); - } - - if (root.containsKey("transition")) { - auto length = uint32_t(float(root["transition"]) * 1000); - this->set_transition_length(length); - } - - if (root.containsKey("effect")) { - const char *effect = root["effect"]; - this->set_effect(effect); - } - - return *this; -} -#endif void LightCall::perform() { - // use remote values for fallback const char *name = this->parent_->get_name().c_str(); - if (this->publish_) { - ESP_LOGD(TAG, "'%s' Setting:", name); - } - LightColorValues v = this->validate_(); if (this->publish_) { + ESP_LOGD(TAG, "'%s' Setting:", name); + + // Only print color mode when it's being changed + ColorMode current_color_mode = this->parent_->remote_values.get_color_mode(); + if (this->color_mode_.value_or(current_color_mode) != current_color_mode) { + ESP_LOGD(TAG, " Color mode: %s", color_mode_to_human(v.get_color_mode())); + } + // Only print state when it's being changed bool current_state = this->parent_->remote_values.is_on(); if (this->state_.value_or(current_state) != current_state) { @@ -106,30 +53,35 @@ void LightCall::perform() { ESP_LOGD(TAG, " Brightness: %.0f%%", v.get_brightness() * 100.0f); } - if (this->color_temperature_.has_value()) { - ESP_LOGD(TAG, " Color Temperature: %.1f mireds", v.get_color_temperature()); - } - if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { - ESP_LOGD(TAG, " Red=%.0f%%, Green=%.0f%%, Blue=%.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f, + ESP_LOGD(TAG, " Red: %.0f%%, Green: %.0f%%, Blue: %.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f, v.get_blue() * 100.0f); } + if (this->white_.has_value()) { - ESP_LOGD(TAG, " White Value: %.0f%%", v.get_white() * 100.0f); + ESP_LOGD(TAG, " White: %.0f%%", v.get_white() * 100.0f); + } + if (this->color_temperature_.has_value()) { + ESP_LOGD(TAG, " Color temperature: %.1f mireds", v.get_color_temperature()); + } + + if (this->cold_white_.has_value() || this->warm_white_.has_value()) { + ESP_LOGD(TAG, " Cold white: %.0f%%, warm white: %.0f%%", v.get_cold_white() * 100.0f, + v.get_warm_white() * 100.0f); } } if (this->has_flash_()) { // FLASH if (this->publish_) { - ESP_LOGD(TAG, " Flash Length: %.1fs", *this->flash_length_ / 1e3f); + ESP_LOGD(TAG, " Flash length: %.1fs", *this->flash_length_ / 1e3f); } this->parent_->start_flash_(v, *this->flash_length_); } else if (this->has_transition_()) { // TRANSITION if (this->publish_) { - ESP_LOGD(TAG, " Transition Length: %.1fs", *this->transition_length_ / 1e3f); + ESP_LOGD(TAG, " Transition length: %.1fs", *this->transition_length_ / 1e3f); } // Special case: Transition and effect can be set when turning off @@ -177,32 +129,48 @@ void LightCall::perform() { } LightColorValues LightCall::validate_() { - // use remote values for fallback auto *name = this->parent_->get_name().c_str(); auto traits = this->parent_->get_traits(); + // Color mode check + if (this->color_mode_.has_value() && !traits.supports_color_mode(this->color_mode_.value())) { + ESP_LOGW(TAG, "'%s' - This light does not support color mode %s!", name, + color_mode_to_human(this->color_mode_.value())); + this->color_mode_.reset(); + } + + // Ensure there is always a color mode set + if (!this->color_mode_.has_value()) { + this->color_mode_ = this->compute_color_mode_(); + } + auto color_mode = *this->color_mode_; + + // Transform calls that use non-native parameters for the current mode. + this->transform_parameters_(); + // Brightness exists check - if (this->brightness_.has_value() && !traits.get_supports_brightness()) { + if (this->brightness_.has_value() && !(color_mode & ColorCapability::BRIGHTNESS)) { ESP_LOGW(TAG, "'%s' - This light does not support setting brightness!", name); this->brightness_.reset(); } // Transition length possible check - if (this->transition_length_.has_value() && *this->transition_length_ != 0 && !traits.get_supports_brightness()) { + if (this->transition_length_.has_value() && *this->transition_length_ != 0 && + !(color_mode & ColorCapability::BRIGHTNESS)) { ESP_LOGW(TAG, "'%s' - This light does not support transitions!", name); this->transition_length_.reset(); } // Color brightness exists check - if (this->color_brightness_.has_value() && !traits.get_supports_rgb()) { - ESP_LOGW(TAG, "'%s' - This light does not support setting RGB brightness!", name); + if (this->color_brightness_.has_value() && !(color_mode & ColorCapability::RGB)) { + ESP_LOGW(TAG, "'%s' - This color mode does not support setting RGB brightness!", name); this->color_brightness_.reset(); } // RGB exists check if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { - if (!traits.get_supports_rgb()) { - ESP_LOGW(TAG, "'%s' - This light does not support setting RGB color!", name); + if (!(color_mode & ColorCapability::RGB)) { + ESP_LOGW(TAG, "'%s' - This color mode does not support setting RGB color!", name); this->red_.reset(); this->green_.reset(); this->blue_.reset(); @@ -210,68 +178,25 @@ LightColorValues LightCall::validate_() { } // White value exists check - if (this->white_.has_value() && !traits.get_supports_rgb_white_value()) { - ESP_LOGW(TAG, "'%s' - This light does not support setting white value!", name); + if (this->white_.has_value() && + !(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) { + ESP_LOGW(TAG, "'%s' - This color mode does not support setting white value!", name); this->white_.reset(); } // Color temperature exists check - if (this->color_temperature_.has_value() && !traits.get_supports_color_temperature()) { - ESP_LOGW(TAG, "'%s' - This light does not support setting color temperature!", name); + if (this->color_temperature_.has_value() && + !(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE)) { + ESP_LOGW(TAG, "'%s' - This color mode does not support setting color temperature!", name); this->color_temperature_.reset(); } - // Set color brightness to 100% if currently zero and a color is set. This is both for compatibility with older - // clients that don't know about color brightness, and it's intuitive UX anyway: if I set a color, it should show up. - if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { - if (!this->color_brightness_.has_value() && this->parent_->remote_values.get_color_brightness() == 0.0f) - this->color_brightness_ = optional(1.0f); - } - - // Handle interaction between RGB and white for color interlock - if (traits.get_supports_color_interlock()) { - // Find out which channel (white or color) the user wanted to enable - bool output_white = this->white_.has_value() && *this->white_ > 0.0f; - bool output_color = (this->color_brightness_.has_value() && *this->color_brightness_ > 0.0f) || - this->red_.has_value() || this->green_.has_value() || this->blue_.has_value(); - - // Interpret setting the color to white as setting the white channel. - if (output_color && *this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f) { - output_white = true; - output_color = false; - - if (!this->white_.has_value()) - this->white_ = optional(this->color_brightness_.value_or(1.0f)); - } - - // Ensure either the white value or the color brightness is always zero. - if (output_white && output_color) { - ESP_LOGW(TAG, "'%s' - Cannot enable color and white channel simultaneously with interlock!", name); - // For compatibility with historic behaviour, prefer white channel in this case. - this->color_brightness_ = optional(0.0f); - } else if (output_white) { - this->color_brightness_ = optional(0.0f); - } else if (output_color) { - this->white_ = optional(0.0f); - } - } - - // If only a color temperature is specified, change to white light - if (this->color_temperature_.has_value() && !this->white_.has_value() && !this->red_.has_value() && - !this->green_.has_value() && !this->blue_.has_value()) { - // Disable color LEDs explicitly if not already set - if (traits.get_supports_rgb() && !this->color_brightness_.has_value()) - this->color_brightness_ = optional(0.0f); - - this->red_ = optional(1.0f); - this->green_ = optional(1.0f); - this->blue_ = optional(1.0f); - - // if setting color temperature from color (i.e. switching to white light), set White to 100% - auto cv = this->parent_->remote_values; - bool was_color = cv.get_red() != 1.0f || cv.get_blue() != 1.0f || cv.get_green() != 1.0f; - if (traits.get_supports_color_interlock() || was_color) { - this->white_ = optional(1.0f); + // Cold/warm white value exists check + if (this->cold_white_.has_value() || this->warm_white_.has_value()) { + if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) { + ESP_LOGW(TAG, "'%s' - This color mode does not support setting cold/warm white value!", name); + this->cold_white_.reset(); + this->warm_white_.reset(); } } @@ -292,13 +217,22 @@ LightColorValues LightCall::validate_() { VALIDATE_RANGE(green, "Green") VALIDATE_RANGE(blue, "Blue") VALIDATE_RANGE(white, "White") + VALIDATE_RANGE(cold_white, "Cold white") + VALIDATE_RANGE(warm_white, "Warm white") + + // Set color brightness to 100% if currently zero and a color is set. + if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { + if (!this->color_brightness_.has_value() && this->parent_->remote_values.get_color_brightness() == 0.0f) + this->color_brightness_ = optional(1.0f); + } auto v = this->parent_->remote_values; + if (this->color_mode_.has_value()) + v.set_color_mode(*this->color_mode_); if (this->state_.has_value()) v.set_state(*this->state_); if (this->brightness_.has_value()) v.set_brightness(*this->brightness_); - if (this->color_brightness_.has_value()) v.set_color_brightness(*this->color_brightness_); if (this->red_.has_value()) @@ -309,9 +243,12 @@ LightColorValues LightCall::validate_() { v.set_blue(*this->blue_); if (this->white_.has_value()) v.set_white(*this->white_); - if (this->color_temperature_.has_value()) v.set_color_temperature(*this->color_temperature_); + if (this->cold_white_.has_value()) + v.set_cold_white(*this->cold_white_); + if (this->warm_white_.has_value()) + v.set_warm_white(*this->warm_white_); v.normalize_color(traits); @@ -322,7 +259,7 @@ LightColorValues LightCall::validate_() { } // validate transition length/flash length/effect not used at the same time - bool supports_transition = traits.get_supports_brightness(); + bool supports_transition = color_mode & ColorCapability::BRIGHTNESS; // If effect is already active, remove effect start if (this->has_effect_() && *this->effect_ == this->parent_->active_effect_index_) { @@ -331,7 +268,7 @@ LightColorValues LightCall::validate_() { // validate effect index if (this->has_effect_() && *this->effect_ > this->parent_->effects_.size()) { - ESP_LOGW(TAG, "'%s' Invalid effect index %u", name, *this->effect_); + ESP_LOGW(TAG, "'%s' - Invalid effect index %u!", name, *this->effect_); this->effect_.reset(); } @@ -381,6 +318,127 @@ LightColorValues LightCall::validate_() { return v; } +void LightCall::transform_parameters_() { + auto traits = this->parent_->get_traits(); + + // Allow CWWW modes to be set with a white value and/or color temperature. This is used by HA, + // which doesn't support CWWW modes (yet?), and for compatibility with the pre-colormode model, + // as CWWW and RGBWW lights used to represent their values as white + color temperature. + if (((this->white_.has_value() && *this->white_ > 0.0f) || this->color_temperature_.has_value()) && // + (*this->color_mode_ & ColorCapability::COLD_WARM_WHITE) && // + !(*this->color_mode_ & ColorCapability::WHITE) && // + !(*this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) && // + traits.get_min_mireds() > 0.0f && traits.get_max_mireds() > 0.0f) { + ESP_LOGD(TAG, "'%s' - Setting cold/warm white channels using white/color temperature values.", + this->parent_->get_name().c_str()); + auto current_values = this->parent_->remote_values; + if (this->color_temperature_.has_value()) { + const float white = + this->white_.value_or(fmaxf(current_values.get_cold_white(), current_values.get_warm_white())); + const float color_temp = clamp(*this->color_temperature_, traits.get_min_mireds(), traits.get_max_mireds()); + const float ww_fraction = + (color_temp - traits.get_min_mireds()) / (traits.get_max_mireds() - traits.get_min_mireds()); + const float cw_fraction = 1.0f - ww_fraction; + const float max_cw_ww = std::max(ww_fraction, cw_fraction); + this->cold_white_ = white * gamma_uncorrect(cw_fraction / max_cw_ww, this->parent_->get_gamma_correct()); + this->warm_white_ = white * gamma_uncorrect(ww_fraction / max_cw_ww, this->parent_->get_gamma_correct()); + } else { + const float max_cw_ww = std::max(current_values.get_warm_white(), current_values.get_cold_white()); + this->cold_white_ = *this->white_ * current_values.get_cold_white() / max_cw_ww; + this->warm_white_ = *this->white_ * current_values.get_warm_white() / max_cw_ww; + } + } +} +ColorMode LightCall::compute_color_mode_() { + auto supported_modes = this->parent_->get_traits().get_supported_color_modes(); + int supported_count = supported_modes.size(); + + // Some lights don't support any color modes (e.g. monochromatic light), leave it at unknown. + if (supported_count == 0) + return ColorMode::UNKNOWN; + + // In the common case of lights supporting only a single mode, use that one. + if (supported_count == 1) + return *supported_modes.begin(); + + // Don't change if the light is being turned off. + ColorMode current_mode = this->parent_->remote_values.get_color_mode(); + if (this->state_.has_value() && !*this->state_) + return current_mode; + + // If no color mode is specified, we try to guess the color mode. This is needed for backward compatibility to + // pre-colormode clients and automations, but also for the MQTT API, where HA doesn't let us know which color mode + // was used for some reason. + std::set suitable_modes = this->get_suitable_color_modes_(); + + // Don't change if the current mode is suitable. + if (suitable_modes.count(current_mode) > 0) { + ESP_LOGI(TAG, "'%s' - Keeping current color mode %s for call without color mode.", + this->parent_->get_name().c_str(), color_mode_to_human(current_mode)); + return current_mode; + } + + // Use the preferred suitable mode. + for (auto mode : suitable_modes) { + if (supported_modes.count(mode) == 0) + continue; + + ESP_LOGI(TAG, "'%s' - Using color mode %s for call without color mode.", this->parent_->get_name().c_str(), + color_mode_to_human(mode)); + return mode; + } + + // There's no supported mode for this call, so warn, use the current more or a mode at random and let validation strip + // out whatever we don't support. + auto color_mode = current_mode != ColorMode::UNKNOWN ? current_mode : *supported_modes.begin(); + ESP_LOGW(TAG, "'%s' - No color mode suitable for this call supported, defaulting to %s!", + this->parent_->get_name().c_str(), color_mode_to_human(color_mode)); + return color_mode; +} +std::set LightCall::get_suitable_color_modes_() { + bool has_white = this->white_.has_value() && *this->white_ > 0.0f; + bool has_ct = this->color_temperature_.has_value(); + bool has_cwww = (this->cold_white_.has_value() && *this->cold_white_ > 0.0f) || + (this->warm_white_.has_value() && *this->warm_white_ > 0.0f); + bool has_rgb = (this->color_brightness_.has_value() && *this->color_brightness_ > 0.0f) || + (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()); + +#define KEY(white, ct, cwww, rgb) ((white) << 0 | (ct) << 1 | (cwww) << 2 | (rgb) << 3) +#define ENTRY(white, ct, cwww, rgb, ...) \ + std::make_tuple>(KEY(white, ct, cwww, rgb), __VA_ARGS__) + + // Flag order: white, color temperature, cwww, rgb + std::array>, 10> lookup_table{ + ENTRY(true, false, false, false, + {ColorMode::WHITE, ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE, + ColorMode::RGB_COLD_WARM_WHITE}), + ENTRY(false, true, false, false, + {ColorMode::COLOR_TEMPERATURE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE, + ColorMode::RGB_COLD_WARM_WHITE}), + ENTRY(true, true, false, false, + {ColorMode::COLD_WARM_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}), + ENTRY(false, false, true, false, {ColorMode::COLD_WARM_WHITE, ColorMode::RGB_COLD_WARM_WHITE}), + ENTRY(false, false, false, false, + {ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE, ColorMode::RGB, + ColorMode::WHITE, ColorMode::COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE}), + ENTRY(true, false, false, true, + {ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}), + ENTRY(false, true, false, true, {ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}), + ENTRY(true, true, false, true, {ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}), + ENTRY(false, false, true, true, {ColorMode::RGB_COLD_WARM_WHITE}), + ENTRY(false, false, false, true, + {ColorMode::RGB, ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}), + }; + + auto key = KEY(has_white, has_ct, has_cwww, has_rgb); + for (auto &item : lookup_table) + if (std::get<0>(item) == key) + return std::get<1>(item); + + // This happens if there are conflicting flags given. + return {}; +} + LightCall &LightCall::set_effect(const std::string &effect) { if (strcasecmp(effect.c_str(), "none") == 0) { this->set_effect(0); @@ -406,53 +464,74 @@ LightCall &LightCall::from_light_color_values(const LightColorValues &values) { this->set_state(values.is_on()); this->set_brightness_if_supported(values.get_brightness()); this->set_color_brightness_if_supported(values.get_color_brightness()); + this->set_color_mode_if_supported(values.get_color_mode()); this->set_red_if_supported(values.get_red()); this->set_green_if_supported(values.get_green()); this->set_blue_if_supported(values.get_blue()); this->set_white_if_supported(values.get_white()); this->set_color_temperature_if_supported(values.get_color_temperature()); + this->set_cold_white_if_supported(values.get_cold_white()); + this->set_warm_white_if_supported(values.get_warm_white()); return *this; } +ColorMode LightCall::get_active_color_mode_() { + return this->color_mode_.value_or(this->parent_->remote_values.get_color_mode()); +} LightCall &LightCall::set_transition_length_if_supported(uint32_t transition_length) { - if (this->parent_->get_traits().get_supports_brightness()) + if (this->get_active_color_mode_() & ColorCapability::BRIGHTNESS) this->set_transition_length(transition_length); return *this; } LightCall &LightCall::set_brightness_if_supported(float brightness) { - if (this->parent_->get_traits().get_supports_brightness()) + if (this->get_active_color_mode_() & ColorCapability::BRIGHTNESS) this->set_brightness(brightness); return *this; } +LightCall &LightCall::set_color_mode_if_supported(ColorMode color_mode) { + if (this->parent_->get_traits().supports_color_mode(color_mode)) + this->color_mode_ = color_mode; + return *this; +} LightCall &LightCall::set_color_brightness_if_supported(float brightness) { - if (this->parent_->get_traits().get_supports_rgb_white_value()) + if (this->get_active_color_mode_() & ColorCapability::RGB) this->set_color_brightness(brightness); return *this; } LightCall &LightCall::set_red_if_supported(float red) { - if (this->parent_->get_traits().get_supports_rgb()) + if (this->get_active_color_mode_() & ColorCapability::RGB) this->set_red(red); return *this; } LightCall &LightCall::set_green_if_supported(float green) { - if (this->parent_->get_traits().get_supports_rgb()) + if (this->get_active_color_mode_() & ColorCapability::RGB) this->set_green(green); return *this; } LightCall &LightCall::set_blue_if_supported(float blue) { - if (this->parent_->get_traits().get_supports_rgb()) + if (this->get_active_color_mode_() & ColorCapability::RGB) this->set_blue(blue); return *this; } LightCall &LightCall::set_white_if_supported(float white) { - if (this->parent_->get_traits().get_supports_rgb_white_value()) + if (this->get_active_color_mode_() & ColorCapability::WHITE) this->set_white(white); return *this; } LightCall &LightCall::set_color_temperature_if_supported(float color_temperature) { - if (this->parent_->get_traits().get_supports_color_temperature()) + if (this->get_active_color_mode_() & ColorCapability::COLOR_TEMPERATURE) this->set_color_temperature(color_temperature); return *this; } +LightCall &LightCall::set_cold_white_if_supported(float cold_white) { + if (this->get_active_color_mode_() & ColorCapability::COLD_WARM_WHITE) + this->set_cold_white(cold_white); + return *this; +} +LightCall &LightCall::set_warm_white_if_supported(float warm_white) { + if (this->get_active_color_mode_() & ColorCapability::COLD_WARM_WHITE) + this->set_warm_white(warm_white); + return *this; +} LightCall &LightCall::set_state(optional state) { this->state_ = state; return *this; @@ -485,6 +564,14 @@ LightCall &LightCall::set_brightness(float brightness) { this->brightness_ = brightness; return *this; } +LightCall &LightCall::set_color_mode(optional color_mode) { + this->color_mode_ = color_mode; + return *this; +} +LightCall &LightCall::set_color_mode(ColorMode color_mode) { + this->color_mode_ = color_mode; + return *this; +} LightCall &LightCall::set_color_brightness(optional brightness) { this->color_brightness_ = brightness; return *this; @@ -533,6 +620,22 @@ LightCall &LightCall::set_color_temperature(float color_temperature) { this->color_temperature_ = color_temperature; return *this; } +LightCall &LightCall::set_cold_white(optional cold_white) { + this->cold_white_ = cold_white; + return *this; +} +LightCall &LightCall::set_cold_white(float cold_white) { + this->cold_white_ = cold_white; + return *this; +} +LightCall &LightCall::set_warm_white(optional warm_white) { + this->warm_white_ = warm_white; + return *this; +} +LightCall &LightCall::set_warm_white(float warm_white) { + this->warm_white_ = warm_white; + return *this; +} LightCall &LightCall::set_effect(optional effect) { if (effect.has_value()) this->set_effect(*effect); diff --git a/esphome/components/light/light_call.h b/esphome/components/light/light_call.h index c63b63bc54..31dc47e55d 100644 --- a/esphome/components/light/light_call.h +++ b/esphome/components/light/light_call.h @@ -44,6 +44,14 @@ class LightCall { LightCall &set_brightness(float brightness); /// Set the brightness property if the light supports brightness. LightCall &set_brightness_if_supported(float brightness); + + /// Set the color mode of the light. + LightCall &set_color_mode(optional color_mode); + /// Set the color mode of the light. + LightCall &set_color_mode(ColorMode color_mode); + /// Set the color mode of the light, if this mode is supported. + LightCall &set_color_mode_if_supported(ColorMode color_mode); + /// Set the color brightness of the light from 0.0 (no color) to 1.0 (fully on) LightCall &set_color_brightness(optional brightness); /// Set the color brightness of the light from 0.0 (no color) to 1.0 (fully on) @@ -98,6 +106,18 @@ class LightCall { LightCall &set_color_temperature(float color_temperature); /// Set the color_temperature property if the light supports color temperature. LightCall &set_color_temperature_if_supported(float color_temperature); + /// Set the cold white value of the light from 0.0 to 1.0. + LightCall &set_cold_white(optional cold_white); + /// Set the cold white value of the light from 0.0 to 1.0. + LightCall &set_cold_white(float cold_white); + /// Set the cold white property if the light supports cold white output. + LightCall &set_cold_white_if_supported(float cold_white); + /// Set the warm white value of the light from 0.0 to 1.0. + LightCall &set_warm_white(optional warm_white); + /// Set the warm white value of the light from 0.0 to 1.0. + LightCall &set_warm_white(float warm_white); + /// Set the warm white property if the light supports cold white output. + LightCall &set_warm_white_if_supported(float warm_white); /// Set the effect of the light by its name. LightCall &set_effect(optional effect); /// Set the effect of the light by its name. @@ -131,18 +151,24 @@ class LightCall { * @return The light call for chaining setters. */ LightCall &set_rgbw(float red, float green, float blue, float white); -#ifdef USE_JSON - LightCall &parse_color_json(JsonObject &root); - LightCall &parse_json(JsonObject &root); -#endif LightCall &from_light_color_values(const LightColorValues &values); void perform(); protected: + /// Get the currently targeted, or active if none set, color mode. + ColorMode get_active_color_mode_(); + /// Validate all properties and return the target light color values. LightColorValues validate_(); + //// Compute the color mode that should be used for this call. + ColorMode compute_color_mode_(); + /// Get potential color modes for this light call. + std::set get_suitable_color_modes_(); + /// Some color modes also can be set using non-native parameters, transform those calls. + void transform_parameters_(); + bool has_transition_() { return this->transition_length_.has_value(); } bool has_flash_() { return this->flash_length_.has_value(); } bool has_effect_() { return this->effect_.has_value(); } @@ -151,6 +177,7 @@ class LightCall { optional state_; optional transition_length_; optional flash_length_; + optional color_mode_; optional brightness_; optional color_brightness_; optional red_; @@ -158,6 +185,8 @@ class LightCall { optional blue_; optional white_; optional color_temperature_; + optional cold_white_; + optional warm_white_; optional effect_; bool publish_{true}; bool save_{true}; diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index 344411b75e..5a5a2f6e34 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -15,40 +15,56 @@ inline static uint8_t to_uint8_scale(float x) { return static_cast(roun /** This class represents the color state for a light object. * - * All values in this class (except color temperature) are represented using floats in the range - * from 0.0 (off) to 1.0 (on). Please note that all values are automatically clamped to this range. + * The representation of the color state is dependent on the active color mode. A color mode consists of multiple + * color capabilities, and each color capability has its own representation in this class. The fields available are as + * follows: * - * This class has the following properties: - * - state: Whether the light should be on/off. Represented as a float for transitions. Used for - * all lights. - * - brightness: The master brightness of the light, applied to all channels. Used for all lights - * with brightness control. - * - color_brightness: The brightness of the color channels of the light. Used for RGB, RGBW and - * RGBWW lights. - * - red, green, blue: The RGB values of the current color. They are normalized, so at least one of - * them is always 1.0. - * - white: The brightness of the white channel of the light. Used for RGBW and RGBWW lights. - * - color_temperature: The color temperature of the white channel in mireds. Used for RGBWW and - * CWWW lights. + * Always: + * - color_mode: The currently active color mode. * - * For lights with a color interlock (RGB lights and white light cannot be on at the same time), a - * valid state has always either color_brightness or white (or both) set to zero. + * For ON_OFF capability: + * - state: Whether the light should be on/off. Represented as a float for transitions. + * + * For BRIGHTNESS capability: + * - brightness: The master brightness of the light, should be applied to all channels. + * + * For RGB capability: + * - color_brightness: The brightness of the color channels of the light. + * - red, green, blue: The RGB values of the current color. They are normalized, so at least one of them is always 1.0. + * + * For WHITE capability: + * - white: The brightness of the white channel of the light. + * + * For COLOR_TEMPERATURE capability: + * - color_temperature: The color temperature of the white channel in mireds. Note that it is not clamped to the valid + * range as set in the traits, so the output needs to do this. + * + * For COLD_WARM_WHITE capability: + * - cold_white, warm_white: The brightness of the cald and warm white channels of the light. + * + * All values (except color temperature) are represented using floats in the range 0.0 (off) to 1.0 (on), and are + * automatically clamped to this range. Properties not used in the current color mode can still have (invalid) values + * and must not be accessed by the light output. */ class LightColorValues { public: - /// Construct the LightColorValues with all attributes enabled, but state set to 0.0 + /// Construct the LightColorValues with all attributes enabled, but state set to off. LightColorValues() - : state_(0.0f), + : color_mode_(ColorMode::UNKNOWN), + state_(0.0f), brightness_(1.0f), color_brightness_(1.0f), red_(1.0f), green_(1.0f), blue_(1.0f), white_(1.0f), - color_temperature_{1.0f} {} + color_temperature_{0.0f}, + cold_white_{1.0f}, + warm_white_{1.0f} {} - LightColorValues(float state, float brightness, float color_brightness, float red, float green, float blue, - float white, float color_temperature = 1.0f) { + LightColorValues(ColorMode color_mode, float state, float brightness, float color_brightness, float red, float green, + float blue, float white, float color_temperature, float cold_white, float warm_white) { + this->set_color_mode(color_mode); this->set_state(state); this->set_brightness(brightness); this->set_color_brightness(color_brightness); @@ -57,49 +73,8 @@ class LightColorValues { this->set_blue(blue); this->set_white(white); this->set_color_temperature(color_temperature); - } - - LightColorValues(bool state, float brightness, float color_brightness, float red, float green, float blue, - float white, float color_temperature = 1.0f) - : LightColorValues(state ? 1.0f : 0.0f, brightness, color_brightness, red, green, blue, white, - color_temperature) {} - - /// Create light color values from a binary true/false state. - static LightColorValues from_binary(bool state) { return {state, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; } - - /// Create light color values from a monochromatic brightness state. - static LightColorValues from_monochromatic(float brightness) { - if (brightness == 0.0f) - return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; - else - return {1.0f, brightness, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; - } - - /// Create light color values from an RGB state. - static LightColorValues from_rgb(float r, float g, float b) { - float brightness = std::max(r, std::max(g, b)); - if (brightness == 0.0f) { - return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; - } else { - return {1.0f, brightness, 1.0f, r / brightness, g / brightness, b / brightness, 1.0f}; - } - } - - /// Create light color values from an RGBW state. - static LightColorValues from_rgbw(float r, float g, float b, float w) { - float color_brightness = std::max(r, std::max(g, b)); - float master_brightness = std::max(color_brightness, w); - if (master_brightness == 0.0f) { - return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; - } else { - return {1.0f, - master_brightness, - color_brightness / master_brightness, - r / color_brightness, - g / color_brightness, - b / color_brightness, - w / master_brightness}; - } + this->set_cold_white(cold_white); + this->set_warm_white(warm_white); } /** Linearly interpolate between the values in start to the values in end. @@ -114,6 +89,7 @@ class LightColorValues { */ static LightColorValues lerp(const LightColorValues &start, const LightColorValues &end, float completion) { LightColorValues v; + v.set_color_mode(end.color_mode_); v.set_state(esphome::lerp(completion, start.get_state(), end.get_state())); v.set_brightness(esphome::lerp(completion, start.get_brightness(), end.get_brightness())); v.set_color_brightness(esphome::lerp(completion, start.get_color_brightness(), end.get_color_brightness())); @@ -122,33 +98,11 @@ class LightColorValues { v.set_blue(esphome::lerp(completion, start.get_blue(), end.get_blue())); v.set_white(esphome::lerp(completion, start.get_white(), end.get_white())); v.set_color_temperature(esphome::lerp(completion, start.get_color_temperature(), end.get_color_temperature())); + v.set_cold_white(esphome::lerp(completion, start.get_cold_white(), end.get_cold_white())); + v.set_warm_white(esphome::lerp(completion, start.get_warm_white(), end.get_warm_white())); return v; } -#ifdef USE_JSON - /** Dump this color into a JsonObject. Only dumps values if the corresponding traits are marked supported by traits. - * - * @param root The json root object. - * @param traits The traits object used for determining whether to include certain attributes. - */ - void dump_json(JsonObject &root, const LightTraits &traits) const { - root["state"] = (this->get_state() != 0.0f) ? "ON" : "OFF"; - if (traits.get_supports_brightness()) - root["brightness"] = uint8_t(this->get_brightness() * 255); - if (traits.get_supports_rgb()) { - JsonObject &color = root.createNestedObject("color"); - color["r"] = uint8_t(this->get_color_brightness() * this->get_red() * 255); - color["g"] = uint8_t(this->get_color_brightness() * this->get_green() * 255); - color["b"] = uint8_t(this->get_color_brightness() * this->get_blue() * 255); - } - if (traits.get_supports_rgb_white_value()) { - root["white_value"] = uint8_t(this->get_white() * 255); - } - if (traits.get_supports_color_temperature()) - root["color_temp"] = uint32_t(this->get_color_temperature()); - } -#endif - /** Normalize the color (RGB/W) component. * * Divides all color attributes by the maximum attribute, so effectively set at least one attribute to 1. @@ -159,7 +113,7 @@ class LightColorValues { * @param traits Used for determining which attributes to consider. */ void normalize_color(const LightTraits &traits) { - if (traits.get_supports_rgb()) { + if (this->color_mode_ & ColorCapability::RGB) { float max_value = fmaxf(this->get_red(), fmaxf(this->get_green(), this->get_blue())); if (max_value == 0.0f) { this->set_red(1.0f); @@ -172,7 +126,7 @@ class LightColorValues { } } - if (traits.get_supports_brightness() && this->get_brightness() == 0.0f) { + if (this->color_mode_ & ColorCapability::BRIGHTNESS && this->get_brightness() == 0.0f) { // 0% brightness means off this->set_state(false); // reset brightness to 100% @@ -180,6 +134,9 @@ class LightColorValues { } } + // Note that method signature of as_* methods is kept as-is for compatibility reasons, so not all parameters + // are always used or necessary. Methods will be deprecated later. + /// Convert these light color values to a binary representation and write them to binary. void as_binary(bool *binary) const { *binary = this->state_ == 1.0f; } @@ -190,64 +147,68 @@ class LightColorValues { /// Convert these light color values to an RGB representation and write them to red, green, blue. void as_rgb(float *red, float *green, float *blue, float gamma = 0, bool color_interlock = false) const { - float brightness = this->state_ * this->brightness_ * this->color_brightness_; - if (color_interlock && this->white_ > 0.0f) { - brightness = 0; + if (this->color_mode_ & ColorCapability::RGB) { + float brightness = this->state_ * this->brightness_ * this->color_brightness_; + *red = gamma_correct(brightness * this->red_, gamma); + *green = gamma_correct(brightness * this->green_, gamma); + *blue = gamma_correct(brightness * this->blue_, gamma); + } else { + *red = *green = *blue = 0; } - *red = gamma_correct(brightness * this->red_, gamma); - *green = gamma_correct(brightness * this->green_, gamma); - *blue = gamma_correct(brightness * this->blue_, gamma); } /// Convert these light color values to an RGBW representation and write them to red, green, blue, white. void as_rgbw(float *red, float *green, float *blue, float *white, float gamma = 0, bool color_interlock = false) const { - this->as_rgb(red, green, blue, gamma, color_interlock); - *white = gamma_correct(this->state_ * this->brightness_ * this->white_, gamma); + this->as_rgb(red, green, blue, gamma); + if (this->color_mode_ & ColorCapability::WHITE) { + *white = gamma_correct(this->state_ * this->brightness_ * this->white_, gamma); + } else { + *white = 0; + } } /// Convert these light color values to an RGBWW representation with the given parameters. void as_rgbww(float color_temperature_cw, float color_temperature_ww, float *red, float *green, float *blue, float *cold_white, float *warm_white, float gamma = 0, bool constant_brightness = false, bool color_interlock = false) const { - this->as_rgb(red, green, blue, gamma, color_interlock); - const float color_temp = clamp(this->color_temperature_, color_temperature_cw, color_temperature_ww); - const float ww_fraction = (color_temp - color_temperature_cw) / (color_temperature_ww - color_temperature_cw); - const float cw_fraction = 1.0f - ww_fraction; - const float white_level = gamma_correct(this->state_ * this->brightness_ * this->white_, gamma); - *cold_white = white_level * cw_fraction; - *warm_white = white_level * ww_fraction; - if (!constant_brightness) { - const float max_cw_ww = std::max(ww_fraction, cw_fraction); - *cold_white /= max_cw_ww; - *warm_white /= max_cw_ww; - } + this->as_rgb(red, green, blue, gamma); + this->as_cwww(0, 0, cold_white, warm_white, gamma, constant_brightness); } /// Convert these light color values to an CWWW representation with the given parameters. void as_cwww(float color_temperature_cw, float color_temperature_ww, float *cold_white, float *warm_white, float gamma = 0, bool constant_brightness = false) const { - const float color_temp = clamp(this->color_temperature_, color_temperature_cw, color_temperature_ww); - const float ww_fraction = (color_temp - color_temperature_cw) / (color_temperature_ww - color_temperature_cw); - const float cw_fraction = 1.0f - ww_fraction; - const float white_level = gamma_correct(this->state_ * this->brightness_ * this->white_, gamma); - *cold_white = white_level * cw_fraction; - *warm_white = white_level * ww_fraction; - if (!constant_brightness) { - const float max_cw_ww = std::max(ww_fraction, cw_fraction); - *cold_white /= max_cw_ww; - *warm_white /= max_cw_ww; + if (this->color_mode_ & ColorMode::COLD_WARM_WHITE) { + const float cw_level = gamma_correct(this->cold_white_, gamma); + const float ww_level = gamma_correct(this->warm_white_, gamma); + const float white_level = gamma_correct(this->state_ * this->brightness_, gamma); + *cold_white = white_level * cw_level; + *warm_white = white_level * ww_level; + if (constant_brightness && (cw_level > 0 || ww_level > 0)) { + const float sum = cw_level + ww_level; + *cold_white /= sum; + *warm_white /= sum; + } + } else { + *cold_white = *warm_white = 0; } } /// Compare this LightColorValues to rhs, return true if and only if all attributes match. bool operator==(const LightColorValues &rhs) const { - return state_ == rhs.state_ && brightness_ == rhs.brightness_ && color_brightness_ == rhs.color_brightness_ && - red_ == rhs.red_ && green_ == rhs.green_ && blue_ == rhs.blue_ && white_ == rhs.white_ && - color_temperature_ == rhs.color_temperature_; + return color_mode_ == rhs.color_mode_ && state_ == rhs.state_ && brightness_ == rhs.brightness_ && + color_brightness_ == rhs.color_brightness_ && red_ == rhs.red_ && green_ == rhs.green_ && + blue_ == rhs.blue_ && white_ == rhs.white_ && color_temperature_ == rhs.color_temperature_ && + cold_white_ == rhs.cold_white_ && warm_white_ == rhs.warm_white_; } bool operator!=(const LightColorValues &rhs) const { return !(rhs == *this); } + /// Get the color mode of these light color values. + ColorMode get_color_mode() const { return this->color_mode_; } + /// Set the color mode of these light color values. + void set_color_mode(ColorMode color_mode) { this->color_mode_ = color_mode; } + /// Get the state of these light color values. In range from 0.0 (off) to 1.0 (on) float get_state() const { return this->state_; } /// Get the binary true/false state of these light color values. @@ -290,11 +251,20 @@ class LightColorValues { /// Get the color temperature property of these light color values in mired. float get_color_temperature() const { return this->color_temperature_; } /// Set the color temperature property of these light color values in mired. - void set_color_temperature(float color_temperature) { - this->color_temperature_ = std::max(0.000001f, color_temperature); - } + void set_color_temperature(float color_temperature) { this->color_temperature_ = color_temperature; } + + /// Get the cold white property of these light color values. In range 0.0 to 1.0. + float get_cold_white() const { return this->cold_white_; } + /// Set the cold white property of these light color values. In range 0.0 to 1.0. + void set_cold_white(float cold_white) { this->cold_white_ = clamp(cold_white, 0.0f, 1.0f); } + + /// Get the warm white property of these light color values. In range 0.0 to 1.0. + float get_warm_white() const { return this->warm_white_; } + /// Set the warm white property of these light color values. In range 0.0 to 1.0. + void set_warm_white(float warm_white) { this->warm_white_ = clamp(warm_white, 0.0f, 1.0f); } protected: + ColorMode color_mode_; float state_; ///< ON / OFF, float for transition float brightness_; float color_brightness_; @@ -303,6 +273,8 @@ class LightColorValues { float blue_; float white_; float color_temperature_; ///< Color Temperature in Mired + float cold_white_; + float warm_white_; }; } // namespace light diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp new file mode 100644 index 0000000000..2e07d91046 --- /dev/null +++ b/esphome/components/light/light_json_schema.cpp @@ -0,0 +1,165 @@ +#include "light_json_schema.h" +#include "light_output.h" + +#ifdef USE_JSON + +namespace esphome { +namespace light { + +// See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema + +void LightJSONSchema::dump_json(LightState &state, JsonObject &root) { + if (state.supports_effects()) + root["effect"] = state.get_effect_name(); + + auto values = state.remote_values; + auto traits = state.get_output()->get_traits(); + + switch (values.get_color_mode()) { + case ColorMode::UNKNOWN: // don't need to set color mode if we don't know it + break; + case ColorMode::ON_OFF: + root["color_mode"] = "onoff"; + break; + case ColorMode::BRIGHTNESS: + root["color_mode"] = "brightness"; + break; + case ColorMode::WHITE: // not supported by HA in MQTT + root["color_mode"] = "white"; + break; + case ColorMode::COLOR_TEMPERATURE: + root["color_mode"] = "color_temp"; + break; + case ColorMode::COLD_WARM_WHITE: // not supported by HA + root["color_mode"] = "cwww"; + break; + case ColorMode::RGB: + root["color_mode"] = "rgb"; + break; + case ColorMode::RGB_WHITE: + root["color_mode"] = "rgbw"; + break; + case ColorMode::RGB_COLOR_TEMPERATURE: // not supported by HA + root["color_mode"] = "rgbct"; + break; + case ColorMode::RGB_COLD_WARM_WHITE: + root["color_mode"] = "rgbww"; + break; + } + + if (values.get_color_mode() & ColorCapability::ON_OFF) + root["state"] = (values.get_state() != 0.0f) ? "ON" : "OFF"; + if (values.get_color_mode() & ColorCapability::BRIGHTNESS) + root["brightness"] = uint8_t(values.get_brightness() * 255); + + JsonObject &color = root.createNestedObject("color"); + if (values.get_color_mode() & ColorCapability::RGB) { + color["r"] = uint8_t(values.get_color_brightness() * values.get_red() * 255); + color["g"] = uint8_t(values.get_color_brightness() * values.get_green() * 255); + color["b"] = uint8_t(values.get_color_brightness() * values.get_blue() * 255); + } + if (values.get_color_mode() & ColorCapability::WHITE) { + color["w"] = uint8_t(values.get_white() * 255); + root["white_value"] = uint8_t(values.get_white() * 255); // legacy API + } + if (values.get_color_mode() & ColorCapability::COLOR_TEMPERATURE) { + // this one isn't under the color subkey for some reason + root["color_temp"] = uint32_t(values.get_color_temperature()); + } + if (values.get_color_mode() & ColorCapability::COLD_WARM_WHITE) { + color["c"] = uint8_t(values.get_cold_white() * 255); + color["w"] = uint8_t(values.get_warm_white() * 255); + } +} + +void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonObject &root) { + if (root.containsKey("state")) { + auto val = parse_on_off(root["state"]); + switch (val) { + case PARSE_ON: + call.set_state(true); + break; + case PARSE_OFF: + call.set_state(false); + break; + case PARSE_TOGGLE: + call.set_state(!state.remote_values.is_on()); + break; + case PARSE_NONE: + break; + } + } + + if (root.containsKey("brightness")) { + call.set_brightness(float(root["brightness"]) / 255.0f); + } + + if (root.containsKey("color")) { + JsonObject &color = root["color"]; + // HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness. + float max_rgb = 0.0f; + if (color.containsKey("r")) { + float r = float(color["r"]) / 255.0f; + max_rgb = fmaxf(max_rgb, r); + call.set_red(r); + } + if (color.containsKey("g")) { + float g = float(color["g"]) / 255.0f; + max_rgb = fmaxf(max_rgb, g); + call.set_green(g); + } + if (color.containsKey("b")) { + float b = float(color["b"]) / 255.0f; + max_rgb = fmaxf(max_rgb, b); + call.set_blue(b); + } + if (color.containsKey("r") || color.containsKey("g") || color.containsKey("b")) { + call.set_color_brightness(max_rgb); + } + + if (color.containsKey("c")) { + call.set_cold_white(float(color["c"]) / 255.0f); + } + if (color.containsKey("w")) { + // the HA scheme is ambigious here, the same key is used for white channel in RGBW and warm + // white channel in RGBWW. + if (color.containsKey("c")) { + call.set_warm_white(float(color["w"]) / 255.0f); + } else { + call.set_white(float(color["w"]) / 255.0f); + } + } + } + + if (root.containsKey("white_value")) { // legacy API + call.set_white(float(root["white_value"]) / 255.0f); + } + + if (root.containsKey("color_temp")) { + call.set_color_temperature(float(root["color_temp"])); + } +} + +void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject &root) { + LightJSONSchema::parse_color_json(state, call, root); + + if (root.containsKey("flash")) { + auto length = uint32_t(float(root["flash"]) * 1000); + call.set_flash_length(length); + } + + if (root.containsKey("transition")) { + auto length = uint32_t(float(root["transition"]) * 1000); + call.set_transition_length(length); + } + + if (root.containsKey("effect")) { + const char *effect = root["effect"]; + call.set_effect(effect); + } +} + +} // namespace light +} // namespace esphome + +#endif diff --git a/esphome/components/light/light_json_schema.h b/esphome/components/light/light_json_schema.h new file mode 100644 index 0000000000..bb7ff812ab --- /dev/null +++ b/esphome/components/light/light_json_schema.h @@ -0,0 +1,25 @@ +#pragma once + +#include "light_call.h" +#include "light_state.h" + +#ifdef USE_JSON + +namespace esphome { +namespace light { + +class LightJSONSchema { + public: + /// Dump the state of a light as JSON. + static void dump_json(LightState &state, JsonObject &root); + /// Parse the JSON state of a light to a LightCall. + static void parse_json(LightState &state, LightCall &call, JsonObject &root); + + protected: + static void parse_color_json(LightState &state, LightCall &call, JsonObject &root); +}; + +} // namespace light +} // namespace esphome + +#endif diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index e32b15daf1..d5bf720f61 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -16,6 +16,7 @@ LightCall LightState::toggle() { return this->make_call().set_state(!this->remot LightCall LightState::make_call() { return LightCall(this); } struct LightStateRTCState { + ColorMode color_mode{ColorMode::UNKNOWN}; bool state{false}; float brightness{1.0f}; float color_brightness{1.0f}; @@ -24,6 +25,8 @@ struct LightStateRTCState { float blue{1.0f}; float white{1.0f}; float color_temp{1.0f}; + float cold_white{1.0f}; + float warm_white{1.0f}; uint32_t effect{0}; }; @@ -64,6 +67,7 @@ void LightState::setup() { break; } + call.set_color_mode_if_supported(recovered.color_mode); call.set_state(recovered.state); call.set_brightness_if_supported(recovered.brightness); call.set_color_brightness_if_supported(recovered.color_brightness); @@ -72,6 +76,8 @@ void LightState::setup() { call.set_blue_if_supported(recovered.blue); call.set_white_if_supported(recovered.white); call.set_color_temperature_if_supported(recovered.color_temp); + call.set_cold_white_if_supported(recovered.cold_white); + call.set_warm_white_if_supported(recovered.warm_white); if (recovered.effect != 0) { call.set_effect(recovered.effect); } else { @@ -81,11 +87,11 @@ void LightState::setup() { } void LightState::dump_config() { ESP_LOGCONFIG(TAG, "Light '%s'", this->get_name().c_str()); - if (this->get_traits().get_supports_brightness()) { + if (this->get_traits().supports_color_capability(ColorCapability::BRIGHTNESS)) { ESP_LOGCONFIG(TAG, " Default Transition Length: %.1fs", this->default_transition_length_ / 1e3f); ESP_LOGCONFIG(TAG, " Gamma Correct: %.2f", this->gamma_correct_); } - if (this->get_traits().get_supports_color_temperature()) { + if (this->get_traits().supports_color_capability(ColorCapability::COLOR_TEMPERATURE)) { ESP_LOGCONFIG(TAG, " Min Mireds: %.1f", this->get_traits().get_min_mireds()); ESP_LOGCONFIG(TAG, " Max Mireds: %.1f", this->get_traits().get_max_mireds()); } @@ -141,14 +147,6 @@ void LightState::add_new_target_state_reached_callback(std::function &&s this->target_state_reached_callback_.add(std::move(send_callback)); } -#ifdef USE_JSON -void LightState::dump_json(JsonObject &root) { - if (this->supports_effects()) - root["effect"] = this->get_effect_name(); - this->remote_values.dump_json(root, this->output_->get_traits()); -} -#endif - void LightState::set_default_transition_length(uint32_t default_transition_length) { this->default_transition_length_ = default_transition_length; } @@ -169,18 +167,17 @@ void LightState::current_values_as_brightness(float *brightness) { } void LightState::current_values_as_rgb(float *red, float *green, float *blue, bool color_interlock) { auto traits = this->get_traits(); - this->current_values.as_rgb(red, green, blue, this->gamma_correct_, traits.get_supports_color_interlock()); + this->current_values.as_rgb(red, green, blue, this->gamma_correct_, false); } void LightState::current_values_as_rgbw(float *red, float *green, float *blue, float *white, bool color_interlock) { auto traits = this->get_traits(); - this->current_values.as_rgbw(red, green, blue, white, this->gamma_correct_, traits.get_supports_color_interlock()); + this->current_values.as_rgbw(red, green, blue, white, this->gamma_correct_, false); } void LightState::current_values_as_rgbww(float *red, float *green, float *blue, float *cold_white, float *warm_white, bool constant_brightness, bool color_interlock) { auto traits = this->get_traits(); this->current_values.as_rgbww(traits.get_min_mireds(), traits.get_max_mireds(), red, green, blue, cold_white, - warm_white, this->gamma_correct_, constant_brightness, - traits.get_supports_color_interlock()); + warm_white, this->gamma_correct_, constant_brightness, false); } void LightState::current_values_as_cwww(float *cold_white, float *warm_white, bool constant_brightness) { auto traits = this->get_traits(); @@ -241,6 +238,7 @@ void LightState::set_transformer_(std::unique_ptr transformer) void LightState::save_remote_values_() { LightStateRTCState saved; + saved.color_mode = this->remote_values.get_color_mode(); saved.state = this->remote_values.is_on(); saved.brightness = this->remote_values.get_brightness(); saved.color_brightness = this->remote_values.get_color_brightness(); @@ -249,6 +247,8 @@ void LightState::save_remote_values_() { saved.blue = this->remote_values.get_blue(); saved.white = this->remote_values.get_white(); saved.color_temp = this->remote_values.get_color_temperature(); + saved.cold_white = this->remote_values.get_cold_white(); + saved.warm_white = this->remote_values.get_warm_white(); saved.effect = this->active_effect_index_; this->rtc_.save(&saved); } diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index cd5e6ad1cb..fb34b76367 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -93,11 +93,6 @@ class LightState : public Nameable, public Component { */ void add_new_target_state_reached_callback(std::function &&send_callback); -#ifdef USE_JSON - /// Dump the state of this light as JSON. - void dump_json(JsonObject &root); -#endif - /// Set the default transition length, i.e. the transition length when no transition is provided. void set_default_transition_length(uint32_t default_transition_length); diff --git a/esphome/components/light/light_traits.h b/esphome/components/light/light_traits.h index ed9c0d44ea..cbade64d77 100644 --- a/esphome/components/light/light_traits.h +++ b/esphome/components/light/light_traits.h @@ -1,5 +1,8 @@ #pragma once +#include "color_mode.h" +#include + namespace esphome { namespace light { @@ -8,35 +11,49 @@ class LightTraits { public: LightTraits() = default; - bool get_supports_brightness() const { return this->supports_brightness_; } - void set_supports_brightness(bool supports_brightness) { this->supports_brightness_ = supports_brightness; } - bool get_supports_rgb() const { return this->supports_rgb_; } - void set_supports_rgb(bool supports_rgb) { this->supports_rgb_ = supports_rgb; } - bool get_supports_rgb_white_value() const { return this->supports_rgb_white_value_; } - void set_supports_rgb_white_value(bool supports_rgb_white_value) { - this->supports_rgb_white_value_ = supports_rgb_white_value; + const std::set &get_supported_color_modes() const { return this->supported_color_modes_; } + void set_supported_color_modes(std::set supported_color_modes) { + this->supported_color_modes_ = std::move(supported_color_modes); } - bool get_supports_color_temperature() const { return this->supports_color_temperature_; } - void set_supports_color_temperature(bool supports_color_temperature) { - this->supports_color_temperature_ = supports_color_temperature; + + bool supports_color_mode(ColorMode color_mode) const { return this->supported_color_modes_.count(color_mode); } + bool supports_color_capability(ColorCapability color_capability) const { + for (auto mode : this->supported_color_modes_) { + if (mode & color_capability) + return true; + } + return false; } - bool get_supports_color_interlock() const { return this->supports_color_interlock_; } - void set_supports_color_interlock(bool supports_color_interlock) { - this->supports_color_interlock_ = supports_color_interlock; + + ESPDEPRECATED("get_supports_brightness() is deprecated, use color modes instead.") + bool get_supports_brightness() const { return this->supports_color_capability(ColorCapability::BRIGHTNESS); } + ESPDEPRECATED("get_supports_rgb() is deprecated, use color modes instead.") + bool get_supports_rgb() const { return this->supports_color_capability(ColorCapability::RGB); } + ESPDEPRECATED("get_supports_rgb_white_value() is deprecated, use color modes instead.") + bool get_supports_rgb_white_value() const { + return this->supports_color_mode(ColorMode::RGB_WHITE) || + this->supports_color_mode(ColorMode::RGB_COLOR_TEMPERATURE); } + ESPDEPRECATED("get_supports_color_temperature() is deprecated, use color modes instead.") + bool get_supports_color_temperature() const { + return this->supports_color_capability(ColorCapability::COLOR_TEMPERATURE); + } + ESPDEPRECATED("get_supports_color_interlock() is deprecated, use color modes instead.") + bool get_supports_color_interlock() const { + return this->supports_color_mode(ColorMode::RGB) && + (this->supports_color_mode(ColorMode::WHITE) || this->supports_color_mode(ColorMode::COLD_WARM_WHITE) || + this->supports_color_mode(ColorMode::COLOR_TEMPERATURE)); + } + float get_min_mireds() const { return this->min_mireds_; } void set_min_mireds(float min_mireds) { this->min_mireds_ = min_mireds; } float get_max_mireds() const { return this->max_mireds_; } void set_max_mireds(float max_mireds) { this->max_mireds_ = max_mireds; } protected: - bool supports_brightness_{false}; - bool supports_rgb_{false}; - bool supports_rgb_white_value_{false}; - bool supports_color_temperature_{false}; + std::set supported_color_modes_{}; float min_mireds_{0}; float max_mireds_{0}; - bool supports_color_interlock_{false}; }; } // namespace light diff --git a/esphome/components/light/light_transformer.h b/esphome/components/light/light_transformer.h index 222be7802c..ddd0f4dd22 100644 --- a/esphome/components/light/light_transformer.h +++ b/esphome/components/light/light_transformer.h @@ -51,24 +51,45 @@ class LightTransitionTransformer : public LightTransformer { : LightTransformer(start_time, length, start_values, target_values) { // When turning light on from off state, use colors from new. if (!this->start_values_.is_on() && this->target_values_.is_on()) { + this->start_values_ = LightColorValues(target_values); this->start_values_.set_brightness(0.0f); - this->start_values_.set_red(target_values.get_red()); - this->start_values_.set_green(target_values.get_green()); - this->start_values_.set_blue(target_values.get_blue()); - this->start_values_.set_white(target_values.get_white()); - this->start_values_.set_color_temperature(target_values.get_color_temperature()); + } + + // When changing color mode, go through off state, as color modes are orthogonal and there can't be two active. + if (this->start_values_.get_color_mode() != this->target_values_.get_color_mode()) { + this->changing_color_mode_ = true; + this->intermediate_values_ = LightColorValues(this->get_start_values_()); + this->intermediate_values_.set_state(false); } } LightColorValues get_values() override { - float v = LightTransitionTransformer::smoothed_progress(this->get_progress()); - return LightColorValues::lerp(this->get_start_values_(), this->get_target_values_(), v); + float p = this->get_progress(); + + // Halfway through, when intermediate state (off) is reached, flip it to the target, but remain off. + if (this->changing_color_mode_ && p > 0.5f && + this->intermediate_values_.get_color_mode() != this->target_values_.get_color_mode()) { + this->intermediate_values_ = LightColorValues(this->get_end_values()); + this->intermediate_values_.set_state(false); + } + + LightColorValues &start = this->changing_color_mode_ && p > 0.5f ? this->intermediate_values_ : this->start_values_; + LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->target_values_; + if (this->changing_color_mode_) + p = p < 0.5f ? p * 2 : (p - 0.5) * 2; + + float v = LightTransitionTransformer::smoothed_progress(p); + return LightColorValues::lerp(start, end, v); } bool publish_at_end() override { return false; } bool is_transition() override { return true; } static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); } + + protected: + bool changing_color_mode_{false}; + LightColorValues intermediate_values_{}; }; class LightFlashTransformer : public LightTransformer { diff --git a/esphome/components/light/types.py b/esphome/components/light/types.py index 7c96cda7b1..66329f7cf9 100644 --- a/esphome/components/light/types.py +++ b/esphome/components/light/types.py @@ -13,6 +13,20 @@ AddressableLightRef = AddressableLight.operator("ref") Color = cg.esphome_ns.class_("Color") LightColorValues = light_ns.class_("LightColorValues") +# Color modes +ColorMode = light_ns.enum("ColorMode", is_class=True) +COLOR_MODES = { + "ON_OFF": ColorMode.ON_OFF, + "BRIGHTNESS": ColorMode.BRIGHTNESS, + "WHITE": ColorMode.WHITE, + "COLOR_TEMPERATURE": ColorMode.COLOR_TEMPERATURE, + "COLD_WARM_WHITE": ColorMode.COLD_WARM_WHITE, + "RGB": ColorMode.RGB, + "RGB_WHITE": ColorMode.RGB_WHITE, + "RGB_COLOR_TEMPERATURE": ColorMode.RGB_COLOR_TEMPERATURE, + "RGB_COLD_WARM_WHITE": ColorMode.RGB_COLD_WARM_WHITE, +} + # Actions ToggleAction = light_ns.class_("ToggleAction", automation.Action) LightControlAction = light_ns.class_("LightControlAction", automation.Action) diff --git a/esphome/components/monochromatic/monochromatic_light_output.h b/esphome/components/monochromatic/monochromatic_light_output.h index c3a015ff3c..f1708ae70b 100644 --- a/esphome/components/monochromatic/monochromatic_light_output.h +++ b/esphome/components/monochromatic/monochromatic_light_output.h @@ -12,7 +12,7 @@ class MonochromaticLightOutput : public light::LightOutput { void set_output(output::FloatOutput *output) { output_ = output; } light::LightTraits get_traits() override { auto traits = light::LightTraits(); - traits.set_supports_brightness(true); + traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS}); return traits; } void write_state(light::LightState *state) override { diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index 4bd0882b8c..bf483bf947 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -1,4 +1,5 @@ #include "mqtt_light.h" +#include "esphome/components/light/light_json_schema.h" #include "esphome/core/log.h" #ifdef USE_LIGHT @@ -14,7 +15,9 @@ std::string MQTTJSONLightComponent::component_type() const { return "light"; } void MQTTJSONLightComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject &root) { - this->state_->make_call().parse_json(root).perform(); + LightCall call = this->state_->make_call(); + LightJSONSchema::parse_json(*this->state_, call, root); + call.perform(); }); auto f = std::bind(&MQTTJSONLightComponent::publish_state_, this); @@ -24,21 +27,40 @@ void MQTTJSONLightComponent::setup() { MQTTJSONLightComponent::MQTTJSONLightComponent(LightState *state) : MQTTComponent(), state_(state) {} bool MQTTJSONLightComponent::publish_state_() { - return this->publish_json(this->get_state_topic_(), [this](JsonObject &root) { this->state_->dump_json(root); }); + return this->publish_json(this->get_state_topic_(), + [this](JsonObject &root) { LightJSONSchema::dump_json(*this->state_, root); }); } LightState *MQTTJSONLightComponent::get_state() const { return this->state_; } std::string MQTTJSONLightComponent::friendly_name() const { return this->state_->get_name(); } void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { root["schema"] = "json"; auto traits = this->state_->get_traits(); - if (traits.get_supports_brightness()) + + root["color_mode"] = true; + JsonArray &color_modes = root.createNestedArray("supported_color_modes"); + if (traits.supports_color_mode(ColorMode::COLOR_TEMPERATURE)) + color_modes.add("color_temp"); + if (traits.supports_color_mode(ColorMode::RGB)) + color_modes.add("rgb"); + if (traits.supports_color_mode(ColorMode::RGB_WHITE)) + color_modes.add("rgbw"); + if (traits.supports_color_mode(ColorMode::RGB_COLD_WARM_WHITE)) + color_modes.add("rgbww"); + if (traits.supports_color_mode(ColorMode::BRIGHTNESS)) + color_modes.add("brightness"); + if (traits.supports_color_mode(ColorMode::ON_OFF)) + color_modes.add("onoff"); + + // legacy API + if (traits.supports_color_capability(ColorCapability::BRIGHTNESS)) root["brightness"] = true; - if (traits.get_supports_rgb()) + if (traits.supports_color_capability(ColorCapability::RGB)) root["rgb"] = true; - if (traits.get_supports_color_temperature()) + if (traits.supports_color_capability(ColorCapability::COLOR_TEMPERATURE)) root["color_temp"] = true; - if (traits.get_supports_rgb_white_value()) + if (traits.supports_color_capability(ColorCapability::WHITE)) root["white_value"] = true; + if (this->state_->supports_effects()) { root["effect"] = true; JsonArray &effect_list = root.createNestedArray("effect_list"); diff --git a/esphome/components/neopixelbus/neopixelbus_light.h b/esphome/components/neopixelbus/neopixelbus_light.h index 2f279e1c9b..c7f7badc5a 100644 --- a/esphome/components/neopixelbus/neopixelbus_light.h +++ b/esphome/components/neopixelbus/neopixelbus_light.h @@ -115,8 +115,7 @@ class NeoPixelRGBLightOutput : public NeoPixelBusLightOutputBasecolor_interlock_); - traits.set_supports_rgb(true); - traits.set_supports_rgb_white_value(true); + if (this->color_interlock_) + traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::WHITE}); + else + traits.set_supported_color_modes({light::ColorMode::RGB_WHITE}); return traits; } void write_state(light::LightState *state) override { diff --git a/esphome/components/rgbww/light.py b/esphome/components/rgbww/light.py index 41cba1f7a1..37bc668215 100644 --- a/esphome/components/rgbww/light.py +++ b/esphome/components/rgbww/light.py @@ -27,12 +27,15 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_BLUE): cv.use_id(output.FloatOutput), cv.Required(CONF_COLD_WHITE): cv.use_id(output.FloatOutput), cv.Required(CONF_WARM_WHITE): cv.use_id(output.FloatOutput), - cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, - cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, + cv.Optional(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, + cv.Optional(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, cv.Optional(CONF_CONSTANT_BRIGHTNESS, default=False): cv.boolean, cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean, } ), + cv.has_none_or_all_keys( + [CONF_COLD_WHITE_COLOR_TEMPERATURE, CONF_WARM_WHITE_COLOR_TEMPERATURE] + ), light.validate_color_temperature_channels, ) @@ -50,10 +53,17 @@ async def to_code(config): cwhite = await cg.get_variable(config[CONF_COLD_WHITE]) cg.add(var.set_cold_white(cwhite)) - cg.add(var.set_cold_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE])) + if CONF_COLD_WHITE_COLOR_TEMPERATURE in config: + cg.add( + var.set_cold_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE]) + ) wwhite = await cg.get_variable(config[CONF_WARM_WHITE]) cg.add(var.set_warm_white(wwhite)) - cg.add(var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE])) + if CONF_WARM_WHITE_COLOR_TEMPERATURE in config: + cg.add( + var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE]) + ) + cg.add(var.set_constant_brightness(config[CONF_CONSTANT_BRIGHTNESS])) cg.add(var.set_color_interlock(config[CONF_COLOR_INTERLOCK])) diff --git a/esphome/components/rgbww/rgbww_light_output.h b/esphome/components/rgbww/rgbww_light_output.h index e14b967530..2182cc201e 100644 --- a/esphome/components/rgbww/rgbww_light_output.h +++ b/esphome/components/rgbww/rgbww_light_output.h @@ -14,17 +14,16 @@ class RGBWWLightOutput : public light::LightOutput { void set_blue(output::FloatOutput *blue) { blue_ = blue; } void set_cold_white(output::FloatOutput *cold_white) { cold_white_ = cold_white; } void set_warm_white(output::FloatOutput *warm_white) { warm_white_ = warm_white; } - void set_cold_white_temperature(int cold_white_temperature) { cold_white_temperature_ = cold_white_temperature; } - void set_warm_white_temperature(int warm_white_temperature) { warm_white_temperature_ = warm_white_temperature; } + void set_cold_white_temperature(float cold_white_temperature) { cold_white_temperature_ = cold_white_temperature; } + void set_warm_white_temperature(float warm_white_temperature) { warm_white_temperature_ = warm_white_temperature; } void set_constant_brightness(bool constant_brightness) { constant_brightness_ = constant_brightness; } void set_color_interlock(bool color_interlock) { color_interlock_ = color_interlock; } light::LightTraits get_traits() override { auto traits = light::LightTraits(); - traits.set_supports_brightness(true); - traits.set_supports_rgb(true); - traits.set_supports_rgb_white_value(true); - traits.set_supports_color_temperature(true); - traits.set_supports_color_interlock(this->color_interlock_); + if (this->color_interlock_) + traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::COLD_WARM_WHITE}); + else + traits.set_supported_color_modes({light::ColorMode::RGB_COLD_WARM_WHITE}); traits.set_min_mireds(this->cold_white_temperature_); traits.set_max_mireds(this->warm_white_temperature_); return traits; @@ -46,8 +45,8 @@ class RGBWWLightOutput : public light::LightOutput { output::FloatOutput *blue_; output::FloatOutput *cold_white_; output::FloatOutput *warm_white_; - int cold_white_temperature_; - int warm_white_temperature_; + float cold_white_temperature_{0}; + float warm_white_temperature_{0}; bool constant_brightness_; bool color_interlock_{false}; }; diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index d7e3561328..e0ca026248 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -45,11 +45,14 @@ void TuyaLight::dump_config() { light::LightTraits TuyaLight::get_traits() { auto traits = light::LightTraits(); - traits.set_supports_brightness(this->dimmer_id_.has_value()); - traits.set_supports_color_temperature(this->color_temperature_id_.has_value()); - if (this->color_temperature_id_.has_value()) { + if (this->color_temperature_id_.has_value() && this->dimmer_id_.has_value()) { + traits.set_supported_color_modes({light::ColorMode::COLOR_TEMPERATURE}); traits.set_min_mireds(this->cold_white_temperature_); traits.set_max_mireds(this->warm_white_temperature_); + } else if (this->dimmer_id_.has_value()) { + traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS}); + } else { + traits.set_supported_color_modes({light::ColorMode::ON_OFF}); } return traits; } diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index b775d44211..5e45a87e26 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -8,6 +8,10 @@ #include +#ifdef USE_LIGHT +#include "esphome/components/light/light_json_schema.h" +#endif + #ifdef USE_LOGGER #include #endif @@ -528,7 +532,7 @@ std::string WebServer::light_json(light::LightState *obj) { return json::build_json([obj](JsonObject &root) { root["id"] = "light-" + obj->get_object_id(); root["state"] = obj->remote_values.is_on() ? "ON" : "OFF"; - obj->dump_json(root); + light::LightJSONSchema::dump_json(*obj, root); }); } #endif diff --git a/esphome/config_validation.py b/esphome/config_validation.py index aad147dbc9..9afed58f80 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -563,6 +563,23 @@ def has_at_most_one_key(*keys): return validate +def has_none_or_all_keys(*keys): + """Validate that none or all of the given keys exist in the config.""" + + def validate(obj): + if not isinstance(obj, dict): + raise Invalid("expected dictionary") + + number = sum(k in keys for k in obj) + if number != 0 and number != len(keys): + raise Invalid( + "Must specify either none or all of {}.".format(", ".join(keys)) + ) + return obj + + return validate + + TIME_PERIOD_ERROR = ( "Time period {} should be format number + unit, for example 5ms, 5s, 5min, 5h" ) diff --git a/esphome/const.py b/esphome/const.py index 6702e773c6..5c21836c7d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -120,6 +120,7 @@ CONF_COLD_WHITE_COLOR_TEMPERATURE = "cold_white_color_temperature" CONF_COLOR = "color" CONF_COLOR_BRIGHTNESS = "color_brightness" CONF_COLOR_CORRECT = "color_correct" +CONF_COLOR_MODE = "color_mode" CONF_COLOR_TEMPERATURE = "color_temperature" CONF_COLORS = "colors" CONF_COMMAND = "command" diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index a6cf8b779c..9e9c775899 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -79,6 +79,15 @@ float gamma_correct(float value, float gamma) { return powf(value, gamma); } +float gamma_uncorrect(float value, float gamma) { + if (value <= 0.0f) + return 0.0f; + if (gamma <= 0.0f) + return value; + + return powf(value, 1 / gamma); +} + std::string to_lowercase_underscore(std::string s) { std::transform(s.begin(), s.end(), s.begin(), ::tolower); std::replace(s.begin(), s.end(), ' ', '_'); diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 808f96d4b8..6f6281fd01 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -116,6 +116,8 @@ uint8_t fast_random_8(); /// Applies gamma correction with the provided gamma to value. float gamma_correct(float value, float gamma); +/// Reverts gamma correction with the provided gamma to value. +float gamma_uncorrect(float value, float gamma); /// Create a string from a value and an accuracy in decimals. std::string value_accuracy_to_string(float value, int8_t accuracy_decimals); From 2966a624291ee4c55c5e139abe00f6ba8ae101e1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 30 Jul 2021 10:53:33 +1200 Subject: [PATCH 1105/1841] Set pulse meter total to use state class measurement and last reset type auto (#2097) --- esphome/components/pulse_meter/sensor.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/esphome/components/pulse_meter/sensor.py b/esphome/components/pulse_meter/sensor.py index e732971c3a..4b8e8a1be0 100644 --- a/esphome/components/pulse_meter/sensor.py +++ b/esphome/components/pulse_meter/sensor.py @@ -11,8 +11,8 @@ from esphome.const import ( CONF_TOTAL, CONF_VALUE, ICON_PULSE, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_PULSES, UNIT_PULSES_PER_MINUTE, DEVICE_CLASS_EMPTY, @@ -59,7 +59,12 @@ CONFIG_SCHEMA = sensor.sensor_schema( cv.Optional(CONF_INTERNAL_FILTER, default="13us"): validate_internal_filter, cv.Optional(CONF_TIMEOUT, default="5min"): validate_timeout, cv.Optional(CONF_TOTAL): sensor.sensor_schema( - UNIT_PULSES, ICON_PULSE, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + UNIT_PULSES, + ICON_PULSE, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), } ) From 34e8979d40d55702bb590d0bbea54f93cc30cb8e Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 30 Jul 2021 00:54:10 +0200 Subject: [PATCH 1106/1841] Convert more code to async-def syntax (#2095) --- esphome/components/anova/climate.py | 8 ++++---- esphome/components/pvvx_mithermometer/sensor.py | 14 +++++++------- esphome/components/teleinfo/__init__.py | 6 +++--- esphome/components/teleinfo/sensor/__init__.py | 8 ++++---- .../components/teleinfo/text_sensor/__init__.py | 8 ++++---- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/esphome/components/anova/climate.py b/esphome/components/anova/climate.py index ab1c9045d8..763ae1f4be 100644 --- a/esphome/components/anova/climate.py +++ b/esphome/components/anova/climate.py @@ -18,8 +18,8 @@ CONFIG_SCHEMA = ( ) -def to_code(config): +async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - yield cg.register_component(var, config) - yield climate.register_climate(var, config) - yield ble_client.register_ble_node(var, config) + await cg.register_component(var, config) + await climate.register_climate(var, config) + await ble_client.register_ble_node(var, config) diff --git a/esphome/components/pvvx_mithermometer/sensor.py b/esphome/components/pvvx_mithermometer/sensor.py index cccf92b277..16b3a86dfd 100644 --- a/esphome/components/pvvx_mithermometer/sensor.py +++ b/esphome/components/pvvx_mithermometer/sensor.py @@ -51,22 +51,22 @@ CONFIG_SCHEMA = ( ) -def to_code(config): +async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - yield cg.register_component(var, config) - yield esp32_ble_tracker.register_ble_device(var, config) + await cg.register_component(var, config) + await esp32_ble_tracker.register_ble_device(var, config) cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) if CONF_TEMPERATURE in config: - sens = yield sensor.new_sensor(config[CONF_TEMPERATURE]) + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) cg.add(var.set_temperature(sens)) if CONF_HUMIDITY in config: - sens = yield sensor.new_sensor(config[CONF_HUMIDITY]) + sens = await sensor.new_sensor(config[CONF_HUMIDITY]) cg.add(var.set_humidity(sens)) if CONF_BATTERY_LEVEL in config: - sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) + sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) cg.add(var.set_battery_level(sens)) if CONF_BATTERY_VOLTAGE in config: - sens = yield sensor.new_sensor(config[CONF_BATTERY_VOLTAGE]) + sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE]) cg.add(var.set_battery_voltage(sens)) diff --git a/esphome/components/teleinfo/__init__.py b/esphome/components/teleinfo/__init__.py index d7bf8999ef..9a5712e10f 100644 --- a/esphome/components/teleinfo/__init__.py +++ b/esphome/components/teleinfo/__init__.py @@ -22,7 +22,7 @@ CONFIG_SCHEMA = ( ) -def to_code(config): +async def to_code(config): var = cg.new_Pvariable(config[CONF_ID], config[CONF_HISTORICAL_MODE]) - yield cg.register_component(var, config) - yield uart.register_uart_device(var, config) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) diff --git a/esphome/components/teleinfo/sensor/__init__.py b/esphome/components/teleinfo/sensor/__init__.py index ffdb1509be..f45052bae9 100644 --- a/esphome/components/teleinfo/sensor/__init__.py +++ b/esphome/components/teleinfo/sensor/__init__.py @@ -18,9 +18,9 @@ CONFIG_SCHEMA = sensor.sensor_schema(UNIT_WATT_HOURS, ICON_FLASH, 0).extend( ) -def to_code(config): +async def to_code(config): var = cg.new_Pvariable(config[CONF_ID], config[CONF_TAG_NAME]) - yield cg.register_component(var, config) - yield sensor.register_sensor(var, config) - teleinfo = yield cg.get_variable(config[CONF_TELEINFO_ID]) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + teleinfo = await cg.get_variable(config[CONF_TELEINFO_ID]) cg.add(teleinfo.register_teleinfo_listener(var)) diff --git a/esphome/components/teleinfo/text_sensor/__init__.py b/esphome/components/teleinfo/text_sensor/__init__.py index b1ade4df41..3bd73ff272 100644 --- a/esphome/components/teleinfo/text_sensor/__init__.py +++ b/esphome/components/teleinfo/text_sensor/__init__.py @@ -20,9 +20,9 @@ CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( ) -def to_code(config): +async def to_code(config): var = cg.new_Pvariable(config[CONF_ID], config[CONF_TAG_NAME]) - yield cg.register_component(var, config) - yield text_sensor.register_text_sensor(var, config) - teleinfo = yield cg.get_variable(config[CONF_TELEINFO_ID]) + await cg.register_component(var, config) + await text_sensor.register_text_sensor(var, config) + teleinfo = await cg.get_variable(config[CONF_TELEINFO_ID]) cg.add(teleinfo.register_teleinfo_listener(var)) From 1d56f0b035961cc80d20162fe2cb9c08b77b8005 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 30 Jul 2021 10:53:33 +1200 Subject: [PATCH 1107/1841] Set pulse meter total to use state class measurement and last reset type auto (#2097) --- esphome/components/pulse_meter/sensor.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/esphome/components/pulse_meter/sensor.py b/esphome/components/pulse_meter/sensor.py index e732971c3a..4b8e8a1be0 100644 --- a/esphome/components/pulse_meter/sensor.py +++ b/esphome/components/pulse_meter/sensor.py @@ -11,8 +11,8 @@ from esphome.const import ( CONF_TOTAL, CONF_VALUE, ICON_PULSE, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_PULSES, UNIT_PULSES_PER_MINUTE, DEVICE_CLASS_EMPTY, @@ -59,7 +59,12 @@ CONFIG_SCHEMA = sensor.sensor_schema( cv.Optional(CONF_INTERNAL_FILTER, default="13us"): validate_internal_filter, cv.Optional(CONF_TIMEOUT, default="5min"): validate_timeout, cv.Optional(CONF_TOTAL): sensor.sensor_schema( - UNIT_PULSES, ICON_PULSE, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + UNIT_PULSES, + ICON_PULSE, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), } ) From fee446c28a5b331477fa80df8e44641ff193a65a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 30 Jul 2021 11:00:10 +1200 Subject: [PATCH 1108/1841] Bump version to v1.20.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 1c1e9eb3a5..d2cc495952 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.2" +__version__ = "1.20.3" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 80076f935d1296956a34f12099e17297f4331c8f Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Fri, 30 Jul 2021 01:55:26 -0300 Subject: [PATCH 1109/1841] fix diplay trigger missing base class (#2099) --- esphome/components/display/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/display/__init__.py b/esphome/components/display/__init__.py index 2dff00da03..947b09a258 100644 --- a/esphome/components/display/__init__.py +++ b/esphome/components/display/__init__.py @@ -31,7 +31,9 @@ DisplayPageShowPrevAction = display_ns.class_( DisplayIsDisplayingPageCondition = display_ns.class_( "DisplayIsDisplayingPageCondition", automation.Condition ) -DisplayOnPageChangeTrigger = display_ns.class_("DisplayOnPageChangeTrigger") +DisplayOnPageChangeTrigger = display_ns.class_( + "DisplayOnPageChangeTrigger", automation.Trigger +) CONF_ON_PAGE_CHANGE = "on_page_change" From a8b90283d84ef4f3e87d2902d6e9752d0ea1b6a2 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Sat, 31 Jul 2021 23:20:10 +1200 Subject: [PATCH 1110/1841] Fix min/max keys in MQTT Number to match Home Assistant (#2102) --- esphome/components/mqtt/mqtt_number.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 0311526340..f209f4fe20 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -39,8 +39,8 @@ void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo // https://www.home-assistant.io/integrations/number.mqtt/ if (!traits.get_icon().empty()) root["icon"] = traits.get_icon(); - root["min_value"] = traits.get_min_value(); - root["max_value"] = traits.get_max_value(); + root["min"] = traits.get_min_value(); + root["max"] = traits.get_max_value(); root["step"] = traits.get_step(); config.command_topic = true; From 593a3d48fb15a6601169d1bfcc3c00976175e243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20=C5=9Aliwi=C5=84ski?= Date: Sat, 31 Jul 2021 14:37:48 +0200 Subject: [PATCH 1111/1841] Use proper schema for the analog pin shorthand (#2103) The wrong error message is displayed like: > GPIO17 (TOUT) is an analog-only pin on the ESP8266. in case of the analog pin validation because the method `shorthand_analog_pin` uses wrong `GPIO_FULL_INPUT_PIN_SCHEMA` instead of `GPIO_FULL_ANALOG_PIN_SCHEMA`. --- esphome/pins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/pins.py b/esphome/pins.py index 5eef60e15d..ff4ed9d9c1 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -271,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): From 81ae6709e4671c34bc3e522116aedd9ba3a02fc3 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Sun, 1 Aug 2021 01:56:05 -0700 Subject: [PATCH 1112/1841] Fix parity bit calculation for ESP8266SoftwareSerial (#1873) --- esphome/components/uart/uart_esp8266.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/uart/uart_esp8266.cpp b/esphome/components/uart/uart_esp8266.cpp index 6f7d4c1f8b..c45f48644c 100644 --- a/esphome/components/uart/uart_esp8266.cpp +++ b/esphome/components/uart/uart_esp8266.cpp @@ -242,9 +242,9 @@ void ICACHE_RAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { bool parity_bit = false; bool need_parity_bit = true; if (this->parity_ == UART_CONFIG_PARITY_EVEN) - parity_bit = true; - else if (this->parity_ == UART_CONFIG_PARITY_ODD) parity_bit = false; + else if (this->parity_ == UART_CONFIG_PARITY_ODD) + parity_bit = true; else need_parity_bit = false; From 5c65f9f9ad8af021f0d73cb0eb12babd2b784c1a Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 1 Aug 2021 12:21:32 +0200 Subject: [PATCH 1113/1841] Convert sensor_schema to use kwargs (#2094) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/adc/sensor.py | 6 +- esphome/components/ade7953/sensor.py | 34 ++-- esphome/components/ads1115/sensor.py | 6 +- esphome/components/aht10/sensor.py | 19 +- esphome/components/am2320/sensor.py | 19 +- esphome/components/apds9960/sensor.py | 6 +- esphome/components/as3935/sensor.py | 15 +- .../components/atc_mithermometer/sensor.py | 33 ++-- esphome/components/atm90e32/sensor.py | 79 ++++----- esphome/components/b_parasite/sensor.py | 33 ++-- esphome/components/bh1750/sensor.py | 6 +- .../components/binary_sensor_map/sensor.py | 10 +- .../components/ble_client/sensor/__init__.py | 6 +- esphome/components/ble_rssi/sensor.py | 10 +- esphome/components/bme280/sensor.py | 28 ++- esphome/components/bme680/sensor.py | 38 ++-- esphome/components/bme680_bsec/sensor.py | 64 +++---- esphome/components/bmp085/sensor.py | 19 +- esphome/components/bmp280/sensor.py | 19 +- esphome/components/ccs811/sensor.py | 19 +- esphome/components/cs5460a/sensor.py | 13 +- esphome/components/cse7766/sensor.py | 20 ++- esphome/components/ct_clamp/sensor.py | 6 +- esphome/components/dallas/sensor.py | 6 +- esphome/components/dht/sensor.py | 15 +- esphome/components/dht12/sensor.py | 19 +- esphome/components/duty_cycle/sensor.py | 6 +- esphome/components/esp32_hall/sensor.py | 6 +- esphome/components/fingerprint_grow/sensor.py | 26 ++- esphome/components/gps/__init__.py | 30 ++-- esphome/components/havells_solar/sensor.py | 164 +++++++++--------- esphome/components/hdc1080/sensor.py | 19 +- esphome/components/hlw8012/sensor.py | 27 +-- esphome/components/hm3301/sensor.py | 37 ++-- esphome/components/hmc5883l/sensor.py | 11 +- .../homeassistant/sensor/__init__.py | 6 +- esphome/components/hrxl_maxsonar_wr/sensor.py | 10 +- esphome/components/htu21d/sensor.py | 19 +- esphome/components/hx711/sensor.py | 6 +- esphome/components/ina219/sensor.py | 25 ++- esphome/components/ina226/sensor.py | 25 ++- esphome/components/ina3221/sensor.py | 21 ++- .../components/inkbird_ibsth1_mini/sensor.py | 37 ++-- esphome/components/max31855/sensor.py | 16 +- esphome/components/max31856/sensor.py | 10 +- esphome/components/max31865/sensor.py | 6 +- esphome/components/max6675/sensor.py | 6 +- esphome/components/mcp9808/sensor.py | 6 +- esphome/components/mhz19/sensor.py | 20 +-- esphome/components/midea_ac/climate.py | 26 +-- esphome/components/mpu6050/sensor.py | 25 ++- .../mqtt_subscribe/sensor/__init__.py | 6 +- esphome/components/ms5611/sensor.py | 20 +-- esphome/components/nextion/sensor/__init__.py | 7 +- esphome/components/ntc/sensor.py | 6 +- esphome/components/pid/sensor/__init__.py | 6 +- esphome/components/pm1006/sensor.py | 10 +- esphome/components/pmsx003/sensor.py | 56 +++--- esphome/components/pulse_counter/sensor.py | 15 +- esphome/components/pulse_meter/sensor.py | 17 +- esphome/components/pulse_width/sensor.py | 6 +- .../components/pvvx_mithermometer/sensor.py | 17 +- esphome/components/pzem004t/sensor.py | 31 ++-- esphome/components/pzemac/sensor.py | 50 +++--- esphome/components/pzemdc/sensor.py | 20 ++- esphome/components/qmc5883l/sensor.py | 11 +- esphome/components/resistance/sensor.py | 6 +- esphome/components/rotary_encoder/sensor.py | 6 +- esphome/components/ruuvitag/sensor.py | 88 +++++----- esphome/components/scd30/sensor.py | 29 ++-- esphome/components/sdm_meter/sensor.py | 94 +++++----- esphome/components/sdp3x/sensor.py | 10 +- esphome/components/sds011/sensor.py | 19 +- esphome/components/selec_meter/sensor.py | 137 ++++++++------- esphome/components/senseair/sensor.py | 10 +- esphome/components/sensor/__init__.py | 74 ++++---- esphome/components/sgp30/sensor.py | 26 ++- esphome/components/sgp40/sensor.py | 6 +- esphome/components/sht3xd/sensor.py | 19 +- esphome/components/sht4x/sensor.py | 20 +-- esphome/components/shtcx/sensor.py | 19 +- esphome/components/sm300d2/sensor.py | 65 +++---- esphome/components/sps30/sensor.py | 91 +++++----- esphome/components/sts3x/sensor.py | 6 +- esphome/components/sun/sensor/__init__.py | 6 +- esphome/components/t6615/sensor.py | 10 +- esphome/components/tcs34725/sensor.py | 17 +- .../components/teleinfo/sensor/__init__.py | 4 +- .../components/template/sensor/__init__.py | 10 +- esphome/components/tmp102/sensor.py | 6 +- esphome/components/tmp117/sensor.py | 6 +- esphome/components/tof10120/sensor.py | 10 +- .../components/total_daily_energy/sensor.py | 12 +- esphome/components/tsl2561/sensor.py | 6 +- esphome/components/tx20/sensor.py | 15 +- esphome/components/ultrasonic/sensor.py | 10 +- esphome/components/uptime/sensor.py | 6 +- esphome/components/vl53l0x/sensor.py | 10 +- esphome/components/wifi_signal/sensor.py | 10 +- esphome/components/xiaomi_cgd1/sensor.py | 28 ++- esphome/components/xiaomi_cgdk2/sensor.py | 28 ++- esphome/components/xiaomi_cgg1/sensor.py | 28 ++- esphome/components/xiaomi_gcls002/sensor.py | 38 ++-- esphome/components/xiaomi_hhccjcy01/sensor.py | 47 +++-- .../components/xiaomi_hhccpot002/sensor.py | 19 +- esphome/components/xiaomi_jqjcy01ym/sensor.py | 38 ++-- esphome/components/xiaomi_lywsd02/sensor.py | 28 ++- .../components/xiaomi_lywsd03mmc/sensor.py | 28 ++- esphome/components/xiaomi_lywsdcgq/sensor.py | 28 ++- esphome/components/xiaomi_mhoc401/sensor.py | 28 ++- esphome/components/xiaomi_miscale/sensor.py | 10 +- esphome/components/xiaomi_miscale2/sensor.py | 15 +- .../xiaomi_mjyd02yla/binary_sensor.py | 25 ++- .../components/xiaomi_wx08zm/binary_sensor.py | 16 +- esphome/components/zyaura/sensor.py | 25 ++- 115 files changed, 1337 insertions(+), 1371 deletions(-) diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 7a944a7260..7c32e4a923 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_ID, CONF_PIN, DEVICE_CLASS_VOLTAGE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_VOLT, ) @@ -37,7 +36,10 @@ ADCSensor = adc_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/ade7953/sensor.py b/esphome/components/ade7953/sensor.py index 90873f1a5e..80dafe2417 100644 --- a/esphome/components/ade7953/sensor.py +++ b/esphome/components/ade7953/sensor.py @@ -8,7 +8,6 @@ from esphome.const import ( DEVICE_CLASS_CURRENT, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_VOLT, UNIT_AMPERE, @@ -32,27 +31,34 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(ADE7953), cv.Optional(CONF_IRQ_PIN): pins.input_pin, cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CURRENT_A): sensor.sensor_schema( - UNIT_AMPERE, - ICON_EMPTY, - 2, - DEVICE_CLASS_CURRENT, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CURRENT_B): sensor.sensor_schema( - UNIT_AMPERE, - ICON_EMPTY, - 2, - DEVICE_CLASS_CURRENT, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/ads1115/sensor.py b/esphome/components/ads1115/sensor.py index c521769279..da33a39041 100644 --- a/esphome/components/ads1115/sensor.py +++ b/esphome/components/ads1115/sensor.py @@ -5,7 +5,6 @@ from esphome.const import ( CONF_GAIN, CONF_MULTIPLEXER, DEVICE_CLASS_VOLTAGE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_VOLT, CONF_ID, @@ -53,7 +52,10 @@ ADS1115Sensor = ads1115_ns.class_( CONF_ADS1115_ID = "ads1115_id" CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/aht10/sensor.py b/esphome/components/aht10/sensor.py index 35168be54a..654d645966 100644 --- a/esphome/components/aht10/sensor.py +++ b/esphome/components/aht10/sensor.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_TEMPERATURE, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -23,18 +22,16 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(AHT10Component), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 2, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 2, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/am2320/sensor.py b/esphome/components/am2320/sensor.py index 5d6cb9eded..088978a8f1 100644 --- a/esphome/components/am2320/sensor.py +++ b/esphome/components/am2320/sensor.py @@ -9,7 +9,6 @@ from esphome.const import ( DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, - ICON_EMPTY, UNIT_PERCENT, ) @@ -25,18 +24,16 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(AM2320Component), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 1, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/apds9960/sensor.py b/esphome/components/apds9960/sensor.py index cb0c52735d..e1990ec26e 100644 --- a/esphome/components/apds9960/sensor.py +++ b/esphome/components/apds9960/sensor.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( CONF_TYPE, - DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_PERCENT, ICON_LIGHTBULB, @@ -21,7 +20,10 @@ TYPES = { } CONFIG_SCHEMA = sensor.sensor_schema( - UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_PERCENT, + icon=ICON_LIGHTBULB, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ).extend( { cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True), diff --git a/esphome/components/as3935/sensor.py b/esphome/components/as3935/sensor.py index a571121742..271a29e0fc 100644 --- a/esphome/components/as3935/sensor.py +++ b/esphome/components/as3935/sensor.py @@ -4,10 +4,8 @@ from esphome.components import sensor from esphome.const import ( CONF_DISTANCE, CONF_LIGHTNING_ENERGY, - DEVICE_CLASS_EMPTY, STATE_CLASS_NONE, UNIT_KILOMETER, - UNIT_EMPTY, ICON_SIGNAL_DISTANCE_VARIANT, ICON_FLASH, ) @@ -19,14 +17,15 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935), cv.Optional(CONF_DISTANCE): sensor.sensor_schema( - UNIT_KILOMETER, - ICON_SIGNAL_DISTANCE_VARIANT, - 1, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + unit_of_measurement=UNIT_KILOMETER, + icon=ICON_SIGNAL_DISTANCE_VARIANT, + accuracy_decimals=1, + state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_LIGHTNING_ENERGY): sensor.sensor_schema( - UNIT_EMPTY, ICON_FLASH, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + icon=ICON_FLASH, + accuracy_decimals=1, + state_class=STATE_CLASS_NONE, ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/atc_mithermometer/sensor.py b/esphome/components/atc_mithermometer/sensor.py index efa3f2b51a..0f6cc1abcb 100644 --- a/esphome/components/atc_mithermometer/sensor.py +++ b/esphome/components/atc_mithermometer/sensor.py @@ -12,7 +12,6 @@ from esphome.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -34,28 +33,28 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(ATCMiThermometer), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_BATTERY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/atm90e32/sensor.py b/esphome/components/atm90e32/sensor.py index 2c34d76b52..28b49604ff 100644 --- a/esphome/components/atm90e32/sensor.py +++ b/esphome/components/atm90e32/sensor.py @@ -12,13 +12,11 @@ from esphome.const import ( CONF_FORWARD_ACTIVE_ENERGY, CONF_REVERSE_ACTIVE_ENERGY, DEVICE_CLASS_CURRENT, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, - ICON_EMPTY, ICON_LIGHTBULB, ICON_CURRENT_AC, LAST_RESET_TYPE_AUTO, @@ -27,7 +25,6 @@ from esphome.const import ( UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, - UNIT_EMPTY, UNIT_CELSIUS, UNIT_VOLT_AMPS_REACTIVE, UNIT_WATT_HOURS, @@ -65,47 +62,47 @@ ATM90E32Component = atm90e32_ns.class_( ATM90E32_PHASE_SCHEMA = cv.Schema( { cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, - ICON_EMPTY, - 2, - DEVICE_CLASS_VOLTAGE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CURRENT): sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_POWER): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema( - UNIT_VOLT_AMPS_REACTIVE, - ICON_LIGHTBULB, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, + icon=ICON_LIGHTBULB, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema( - UNIT_EMPTY, - ICON_EMPTY, - 2, - DEVICE_CLASS_POWER_FACTOR, - STATE_CLASS_MEASUREMENT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER_FACTOR, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, - ICON_EMPTY, - 2, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, - ICON_EMPTY, - 2, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t, cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t, @@ -120,18 +117,16 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA, cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA, cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( - UNIT_HERTZ, - ICON_CURRENT_AC, - 1, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_HERTZ, + icon=ICON_CURRENT_AC, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True), cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum( diff --git a/esphome/components/b_parasite/sensor.py b/esphome/components/b_parasite/sensor.py index d93e41816b..46ed64337f 100644 --- a/esphome/components/b_parasite/sensor.py +++ b/esphome/components/b_parasite/sensor.py @@ -11,7 +11,6 @@ from esphome.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -33,28 +32,28 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(BParasite), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 1, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_MOISTURE): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 1, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/bh1750/sensor.py b/esphome/components/bh1750/sensor.py index e688241dcc..156c7bb375 100644 --- a/esphome/components/bh1750/sensor.py +++ b/esphome/components/bh1750/sensor.py @@ -5,7 +5,6 @@ from esphome.const import ( CONF_ID, CONF_RESOLUTION, DEVICE_CLASS_ILLUMINANCE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_LUX, CONF_MEASUREMENT_DURATION, @@ -28,7 +27,10 @@ BH1750Sensor = bh1750_ns.class_( CONF_MEASUREMENT_TIME = "measurement_time" CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_LUX, ICON_EMPTY, 1, DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_LUX, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/binary_sensor_map/sensor.py b/esphome/components/binary_sensor_map/sensor.py index 131a050052..946e2f9e62 100644 --- a/esphome/components/binary_sensor_map/sensor.py +++ b/esphome/components/binary_sensor_map/sensor.py @@ -7,8 +7,6 @@ from esphome.const import ( CONF_CHANNELS, CONF_VALUE, CONF_TYPE, - DEVICE_CLASS_EMPTY, - UNIT_EMPTY, ICON_CHECK_CIRCLE_OUTLINE, CONF_BINARY_SENSOR, CONF_GROUP, @@ -35,11 +33,9 @@ entry = { CONFIG_SCHEMA = cv.typed_schema( { CONF_GROUP: sensor.sensor_schema( - UNIT_EMPTY, - ICON_CHECK_CIRCLE_OUTLINE, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + icon=ICON_CHECK_CIRCLE_OUTLINE, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, ).extend( { cv.GenerateID(): cv.declare_id(BinarySensorMap), diff --git a/esphome/components/ble_client/sensor/__init__.py b/esphome/components/ble_client/sensor/__init__.py index c6f05932ef..efe4bf0e9a 100644 --- a/esphome/components/ble_client/sensor/__init__.py +++ b/esphome/components/ble_client/sensor/__init__.py @@ -2,12 +2,9 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, ble_client, esp32_ble_tracker from esphome.const import ( - DEVICE_CLASS_EMPTY, CONF_ID, CONF_LAMBDA, STATE_CLASS_NONE, - UNIT_EMPTY, - ICON_EMPTY, CONF_TRIGGER_ID, CONF_SERVICE_UUID, ) @@ -34,7 +31,8 @@ BLESensorNotifyTrigger = ble_client_ns.class_( CONFIG_SCHEMA = cv.All( sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, ) .extend( { diff --git a/esphome/components/ble_rssi/sensor.py b/esphome/components/ble_rssi/sensor.py index 819b7c6fd7..bca73328f9 100644 --- a/esphome/components/ble_rssi/sensor.py +++ b/esphome/components/ble_rssi/sensor.py @@ -8,7 +8,6 @@ from esphome.const import ( DEVICE_CLASS_SIGNAL_STRENGTH, STATE_CLASS_MEASUREMENT, UNIT_DECIBEL, - ICON_EMPTY, ) DEPENDENCIES = ["esp32_ble_tracker"] @@ -20,11 +19,10 @@ BLERSSISensor = ble_rssi_ns.class_( CONFIG_SCHEMA = cv.All( sensor.sensor_schema( - UNIT_DECIBEL, - ICON_EMPTY, - 0, - DEVICE_CLASS_SIGNAL_STRENGTH, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_DECIBEL, + accuracy_decimals=0, + device_class=DEVICE_CLASS_SIGNAL_STRENGTH, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/bme280/sensor.py b/esphome/components/bme280/sensor.py index 8c6cc7ae56..dcb842d879 100644 --- a/esphome/components/bme280/sensor.py +++ b/esphome/components/bme280/sensor.py @@ -11,7 +11,6 @@ from esphome.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_HECTOPASCAL, @@ -49,11 +48,10 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(BME280Component), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ).extend( { cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( @@ -62,11 +60,10 @@ CONFIG_SCHEMA = ( } ), cv.Optional(CONF_PRESSURE): sensor.sensor_schema( - UNIT_HECTOPASCAL, - ICON_EMPTY, - 1, - DEVICE_CLASS_PRESSURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, ).extend( { cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( @@ -75,11 +72,10 @@ CONFIG_SCHEMA = ( } ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 1, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ).extend( { cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( diff --git a/esphome/components/bme680/sensor.py b/esphome/components/bme680/sensor.py index eaa158c9f8..76472c7562 100644 --- a/esphome/components/bme680/sensor.py +++ b/esphome/components/bme680/sensor.py @@ -12,7 +12,6 @@ from esphome.const import ( CONF_OVERSAMPLING, CONF_PRESSURE, CONF_TEMPERATURE, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, @@ -20,7 +19,6 @@ from esphome.const import ( UNIT_OHM, ICON_GAS_CYLINDER, UNIT_CELSIUS, - ICON_EMPTY, UNIT_HECTOPASCAL, UNIT_PERCENT, ) @@ -59,11 +57,10 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(BME680Component), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ).extend( { cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( @@ -72,11 +69,10 @@ CONFIG_SCHEMA = ( } ), cv.Optional(CONF_PRESSURE): sensor.sensor_schema( - UNIT_HECTOPASCAL, - ICON_EMPTY, - 1, - DEVICE_CLASS_PRESSURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, ).extend( { cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( @@ -85,11 +81,10 @@ CONFIG_SCHEMA = ( } ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 1, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ).extend( { cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( @@ -98,11 +93,10 @@ CONFIG_SCHEMA = ( } ), cv.Optional(CONF_GAS_RESISTANCE): sensor.sensor_schema( - UNIT_OHM, - ICON_GAS_CYLINDER, - 1, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_OHM, + icon=ICON_GAS_CYLINDER, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum( IIR_FILTER_OPTIONS, upper=True diff --git a/esphome/components/bme680_bsec/sensor.py b/esphome/components/bme680_bsec/sensor.py index 4520bf3480..8d00012150 100644 --- a/esphome/components/bme680_bsec/sensor.py +++ b/esphome/components/bme680_bsec/sensor.py @@ -6,13 +6,11 @@ from esphome.const import ( CONF_HUMIDITY, CONF_PRESSURE, CONF_TEMPERATURE, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, - UNIT_EMPTY, UNIT_HECTOPASCAL, UNIT_OHM, UNIT_PARTS_PER_MILLION, @@ -54,54 +52,60 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_THERMOMETER, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ).extend( {cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)} ), cv.Optional(CONF_PRESSURE): sensor.sensor_schema( - UNIT_HECTOPASCAL, - ICON_GAUGE, - 1, - DEVICE_CLASS_PRESSURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_HECTOPASCAL, + icon=ICON_GAUGE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, ).extend( {cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)} ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_WATER_PERCENT, - 1, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + icon=ICON_WATER_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ).extend( {cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)} ), cv.Optional(CONF_GAS_RESISTANCE): sensor.sensor_schema( - UNIT_OHM, ICON_GAS_CYLINDER, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_OHM, + icon=ICON_GAS_CYLINDER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_IAQ): sensor.sensor_schema( - UNIT_IAQ, ICON_GAUGE, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_IAQ, + icon=ICON_GAUGE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_IAQ_ACCURACY): sensor.sensor_schema( - UNIT_EMPTY, ICON_ACCURACY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + icon=ICON_ACCURACY, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema( - UNIT_PARTS_PER_MILLION, - ICON_TEST_TUBE, - 1, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PARTS_PER_MILLION, + icon=ICON_TEST_TUBE, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema( - UNIT_PARTS_PER_MILLION, - ICON_TEST_TUBE, - 1, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PARTS_PER_MILLION, + icon=ICON_TEST_TUBE, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/bmp085/sensor.py b/esphome/components/bmp085/sensor.py index 1b48f2e440..52f554120a 100644 --- a/esphome/components/bmp085/sensor.py +++ b/esphome/components/bmp085/sensor.py @@ -9,7 +9,6 @@ from esphome.const import ( DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, - ICON_EMPTY, UNIT_HECTOPASCAL, ) @@ -25,18 +24,16 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(BMP085Component), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PRESSURE): sensor.sensor_schema( - UNIT_HECTOPASCAL, - ICON_EMPTY, - 1, - DEVICE_CLASS_PRESSURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/bmp280/sensor.py b/esphome/components/bmp280/sensor.py index 48953d0259..95a9577f7e 100644 --- a/esphome/components/bmp280/sensor.py +++ b/esphome/components/bmp280/sensor.py @@ -9,7 +9,6 @@ from esphome.const import ( DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, - ICON_EMPTY, UNIT_HECTOPASCAL, CONF_IIR_FILTER, CONF_OVERSAMPLING, @@ -46,11 +45,10 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(BMP280Component), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ).extend( { cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( @@ -59,11 +57,10 @@ CONFIG_SCHEMA = ( } ), cv.Optional(CONF_PRESSURE): sensor.sensor_schema( - UNIT_HECTOPASCAL, - ICON_EMPTY, - 1, - DEVICE_CLASS_PRESSURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, ).extend( { cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( diff --git a/esphome/components/ccs811/sensor.py b/esphome/components/ccs811/sensor.py index 4e81d6ac10..4c09a14c3e 100644 --- a/esphome/components/ccs811/sensor.py +++ b/esphome/components/ccs811/sensor.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( CONF_ID, - DEVICE_CLASS_EMPTY, ICON_RADIATOR, STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, @@ -28,18 +27,16 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(CCS811Component), cv.Required(CONF_ECO2): sensor.sensor_schema( - UNIT_PARTS_PER_MILLION, - ICON_MOLECULE_CO2, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PARTS_PER_MILLION, + icon=ICON_MOLECULE_CO2, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Required(CONF_TVOC): sensor.sensor_schema( - UNIT_PARTS_PER_BILLION, - ICON_RADIATOR, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PARTS_PER_BILLION, + icon=ICON_RADIATOR, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BASELINE): cv.hex_uint16_t, cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor), diff --git a/esphome/components/cs5460a/sensor.py b/esphome/components/cs5460a/sensor.py index efb1d1d426..82df881bfc 100644 --- a/esphome/components/cs5460a/sensor.py +++ b/esphome/components/cs5460a/sensor.py @@ -9,7 +9,6 @@ from esphome.const import ( UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, - ICON_EMPTY, DEVICE_CLASS_POWER, DEVICE_CLASS_CURRENT, DEVICE_CLASS_VOLTAGE, @@ -80,13 +79,19 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_VOLTAGE_HPF, default=True): cv.boolean, cv.Optional(CONF_PULSE_ENERGY, default=10.0): validate_energy, cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 0, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, ), cv.Optional(CONF_CURRENT): sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, ), cv.Optional(CONF_POWER): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, ), } ) diff --git a/esphome/components/cse7766/sensor.py b/esphome/components/cse7766/sensor.py index 4ccb346efd..1c8efc4f72 100644 --- a/esphome/components/cse7766/sensor.py +++ b/esphome/components/cse7766/sensor.py @@ -9,7 +9,6 @@ from esphome.const import ( DEVICE_CLASS_CURRENT, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_VOLT, UNIT_AMPERE, @@ -28,17 +27,22 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(CSE7766Component), cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CURRENT): sensor.sensor_schema( - UNIT_AMPERE, - ICON_EMPTY, - 2, - DEVICE_CLASS_CURRENT, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_POWER): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/ct_clamp/sensor.py b/esphome/components/ct_clamp/sensor.py index e44d46e7f4..049905d0a7 100644 --- a/esphome/components/ct_clamp/sensor.py +++ b/esphome/components/ct_clamp/sensor.py @@ -5,7 +5,6 @@ from esphome.const import ( CONF_SENSOR, CONF_ID, DEVICE_CLASS_CURRENT, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_AMPERE, ) @@ -20,7 +19,10 @@ CTClampSensor = ct_clamp_ns.class_("CTClampSensor", sensor.Sensor, cg.PollingCom CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/dallas/sensor.py b/esphome/components/dallas/sensor.py index 1c8db8fa2f..5e09701bae 100644 --- a/esphome/components/dallas/sensor.py +++ b/esphome/components/dallas/sensor.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_INDEX, CONF_RESOLUTION, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, CONF_ID, @@ -18,7 +17,10 @@ DallasTemperatureSensor = dallas_ns.class_("DallasTemperatureSensor", sensor.Sen CONFIG_SCHEMA = cv.All( sensor.sensor_schema( - UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ).extend( { cv.GenerateID(): cv.declare_id(DallasTemperatureSensor), diff --git a/esphome/components/dht/sensor.py b/esphome/components/dht/sensor.py index c33ddd2286..1334f0270c 100644 --- a/esphome/components/dht/sensor.py +++ b/esphome/components/dht/sensor.py @@ -8,7 +8,6 @@ from esphome.const import ( CONF_MODEL, CONF_PIN, CONF_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -36,14 +35,16 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(): cv.declare_id(DHT), cv.Required(CONF_PIN): pins.gpio_input_pin_schema, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_HUMIDITY, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_MODEL, default="auto detect"): cv.enum( DHT_MODELS, upper=True, space="_" diff --git a/esphome/components/dht12/sensor.py b/esphome/components/dht12/sensor.py index 14c01f5d34..ae2173ef22 100644 --- a/esphome/components/dht12/sensor.py +++ b/esphome/components/dht12/sensor.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, - ICON_EMPTY, UNIT_PERCENT, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY, @@ -23,18 +22,16 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(DHT12Component), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 1, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/duty_cycle/sensor.py b/esphome/components/duty_cycle/sensor.py index 39f6ebc88f..3537cb0973 100644 --- a/esphome/components/duty_cycle/sensor.py +++ b/esphome/components/duty_cycle/sensor.py @@ -5,7 +5,6 @@ from esphome.components import sensor from esphome.const import ( CONF_ID, CONF_PIN, - DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_PERCENT, ICON_PERCENT, @@ -18,7 +17,10 @@ DutyCycleSensor = duty_cycle_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_PERCENT, ICON_PERCENT, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_PERCENT, + icon=ICON_PERCENT, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/esp32_hall/sensor.py b/esphome/components/esp32_hall/sensor.py index b800b3436a..4ba79de714 100644 --- a/esphome/components/esp32_hall/sensor.py +++ b/esphome/components/esp32_hall/sensor.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( CONF_ID, - DEVICE_CLASS_EMPTY, ESP_PLATFORM_ESP32, STATE_CLASS_MEASUREMENT, UNIT_MICROTESLA, @@ -19,7 +18,10 @@ ESP32HallSensor = esp32_hall_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_MICROTESLA, ICON_MAGNET, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_MICROTESLA, + icon=ICON_MAGNET, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/fingerprint_grow/sensor.py b/esphome/components/fingerprint_grow/sensor.py index f8a44eb0da..f359a10348 100644 --- a/esphome/components/fingerprint_grow/sensor.py +++ b/esphome/components/fingerprint_grow/sensor.py @@ -8,15 +8,12 @@ from esphome.const import ( CONF_LAST_FINGER_ID, CONF_SECURITY_LEVEL, CONF_STATUS, - DEVICE_CLASS_EMPTY, ICON_ACCOUNT, ICON_ACCOUNT_CHECK, ICON_DATABASE, - ICON_EMPTY, ICON_FINGERPRINT, ICON_SECURITY, STATE_CLASS_NONE, - UNIT_EMPTY, ) from . import CONF_FINGERPRINT_GROW_ID, FingerprintGrowComponent @@ -26,22 +23,33 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_FINGERPRINT_GROW_ID): cv.use_id(FingerprintGrowComponent), cv.Optional(CONF_FINGERPRINT_COUNT): sensor.sensor_schema( - UNIT_EMPTY, ICON_FINGERPRINT, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + icon=ICON_FINGERPRINT, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_STATUS): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_CAPACITY): sensor.sensor_schema( - UNIT_EMPTY, ICON_DATABASE, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + icon=ICON_DATABASE, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_SECURITY_LEVEL): sensor.sensor_schema( - UNIT_EMPTY, ICON_SECURITY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + icon=ICON_SECURITY, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_LAST_FINGER_ID): sensor.sensor_schema( - UNIT_EMPTY, ICON_ACCOUNT, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + icon=ICON_ACCOUNT, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_LAST_CONFIDENCE): sensor.sensor_schema( - UNIT_EMPTY, ICON_ACCOUNT_CHECK, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + icon=ICON_ACCOUNT_CHECK, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, ), } ) diff --git a/esphome/components/gps/__init__.py b/esphome/components/gps/__init__.py index 2867aa7325..0d5c3f3f3d 100644 --- a/esphome/components/gps/__init__.py +++ b/esphome/components/gps/__init__.py @@ -15,9 +15,6 @@ from esphome.const import ( UNIT_DEGREES, UNIT_KILOMETER_PER_HOUR, UNIT_METER, - UNIT_EMPTY, - ICON_EMPTY, - DEVICE_CLASS_EMPTY, ) DEPENDENCIES = ["uart"] @@ -36,26 +33,33 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(GPS), cv.Optional(CONF_LATITUDE): sensor.sensor_schema( - UNIT_DEGREES, ICON_EMPTY, 6, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + unit_of_measurement=UNIT_DEGREES, + accuracy_decimals=6, + state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_LONGITUDE): sensor.sensor_schema( - UNIT_DEGREES, ICON_EMPTY, 6, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + unit_of_measurement=UNIT_DEGREES, + accuracy_decimals=6, + state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_SPEED): sensor.sensor_schema( - UNIT_KILOMETER_PER_HOUR, - ICON_EMPTY, - 6, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + unit_of_measurement=UNIT_KILOMETER_PER_HOUR, + accuracy_decimals=6, + state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_COURSE): sensor.sensor_schema( - UNIT_DEGREES, ICON_EMPTY, 2, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + unit_of_measurement=UNIT_DEGREES, + accuracy_decimals=2, + state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_ALTITUDE): sensor.sensor_schema( - UNIT_METER, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + unit_of_measurement=UNIT_METER, + accuracy_decimals=1, + state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_SATELLITES): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/havells_solar/sensor.py b/esphome/components/havells_solar/sensor.py index 1926d4d68a..1d685b9b2e 100644 --- a/esphome/components/havells_solar/sensor.py +++ b/esphome/components/havells_solar/sensor.py @@ -9,12 +9,10 @@ from esphome.const import ( CONF_REACTIVE_POWER, CONF_VOLTAGE, DEVICE_CLASS_CURRENT, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ICON_CURRENT_AC, - ICON_EMPTY, LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, @@ -63,24 +61,47 @@ HavellsSolar = havells_solar_ns.class_( ) PHASE_SENSORS = { - CONF_VOLTAGE: sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE), + CONF_VOLTAGE: sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + ), CONF_CURRENT: sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), } PV_SENSORS = { - CONF_VOLTAGE: sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE), + CONF_VOLTAGE: sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + ), CONF_CURRENT: sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_ACTIVE_POWER: sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_VOLTAGE_SAMPLED_BY_SECONDARY_CPU: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_INSULATION_OF_P_TO_GROUND: sensor.sensor_schema( - UNIT_KOHM, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_KOHM, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), } @@ -101,107 +122,86 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_PV1): PV_SCHEMA, cv.Optional(CONF_PV2): PV_SCHEMA, cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( - UNIT_HERTZ, - ICON_CURRENT_AC, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_HERTZ, + icon=ICON_CURRENT_AC, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ACTIVE_POWER): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema( - UNIT_VOLT_AMPS_REACTIVE, - ICON_EMPTY, - 2, - DEVICE_CLASS_POWER, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ENERGY_PRODUCTION_DAY): sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 2, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 0, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_TOTAL_GENERATION_TIME): sensor.sensor_schema( - UNIT_HOURS, - ICON_EMPTY, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + unit_of_measurement=UNIT_HOURS, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_TODAY_GENERATION_TIME): sensor.sensor_schema( - UNIT_MINUTE, - ICON_EMPTY, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + unit_of_measurement=UNIT_MINUTE, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_INVERTER_MODULE_TEMP): sensor.sensor_schema( - UNIT_DEGREES, - ICON_EMPTY, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_DEGREES, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_INVERTER_INNER_TEMP): sensor.sensor_schema( - UNIT_DEGREES, - ICON_EMPTY, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_DEGREES, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_INVERTER_BUS_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, - ICON_EMPTY, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_INSULATION_OF_PV_N_TO_GROUND): sensor.sensor_schema( - UNIT_KOHM, - ICON_EMPTY, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KOHM, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_GFCI_VALUE): sensor.sensor_schema( - UNIT_MILLIAMPERE, - ICON_EMPTY, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MILLIAMPERE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_DCI_OF_R): sensor.sensor_schema( - UNIT_MILLIAMPERE, - ICON_EMPTY, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MILLIAMPERE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_DCI_OF_S): sensor.sensor_schema( - UNIT_MILLIAMPERE, - ICON_EMPTY, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MILLIAMPERE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_DCI_OF_T): sensor.sensor_schema( - UNIT_MILLIAMPERE, - ICON_EMPTY, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MILLIAMPERE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/hdc1080/sensor.py b/esphome/components/hdc1080/sensor.py index 26ec3ad0a9..39727f7159 100644 --- a/esphome/components/hdc1080/sensor.py +++ b/esphome/components/hdc1080/sensor.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_TEMPERATURE, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -25,18 +24,16 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(HDC1080Component), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/hlw8012/sensor.py b/esphome/components/hlw8012/sensor.py index face32872e..75590f8572 100644 --- a/esphome/components/hlw8012/sensor.py +++ b/esphome/components/hlw8012/sensor.py @@ -18,7 +18,6 @@ from esphome.const import ( DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, - ICON_EMPTY, LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, UNIT_VOLT, @@ -58,21 +57,29 @@ CONFIG_SCHEMA = cv.Schema( pins.internal_gpio_input_pullup_pin_schema, pins.validate_has_interrupt ), cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CURRENT): sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_POWER): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, - ICON_EMPTY, - 1, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance, cv.Optional(CONF_VOLTAGE_DIVIDER, default=2351): cv.positive_float, diff --git a/esphome/components/hm3301/sensor.py b/esphome/components/hm3301/sensor.py index 48a29ed5f8..fe1c6008d4 100644 --- a/esphome/components/hm3301/sensor.py +++ b/esphome/components/hm3301/sensor.py @@ -6,7 +6,6 @@ from esphome.const import ( CONF_PM_2_5, CONF_PM_10_0, CONF_PM_1_0, - DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, @@ -43,32 +42,28 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(HM3301Component), cv.Optional(CONF_PM_1_0): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_2_5): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_AQI): sensor.sensor_schema( - UNIT_INDEX, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_INDEX, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ).extend( { cv.Required(CONF_CALCULATION_TYPE): cv.enum( diff --git a/esphome/components/hmc5883l/sensor.py b/esphome/components/hmc5883l/sensor.py index 65469003ed..73e7472dcf 100644 --- a/esphome/components/hmc5883l/sensor.py +++ b/esphome/components/hmc5883l/sensor.py @@ -6,7 +6,6 @@ from esphome.const import ( CONF_ID, CONF_OVERSAMPLING, CONF_RANGE, - DEVICE_CLASS_EMPTY, ICON_MAGNET, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, @@ -80,10 +79,16 @@ def validate_enum(enum_values, units=None, int=True): field_strength_schema = sensor.sensor_schema( - UNIT_MICROTESLA, ICON_MAGNET, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_MICROTESLA, + icon=ICON_MAGNET, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ) heading_schema = sensor.sensor_schema( - UNIT_DEGREES, ICON_SCREEN_ROTATION, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + unit_of_measurement=UNIT_DEGREES, + icon=ICON_SCREEN_ROTATION, + accuracy_decimals=1, + state_class=STATE_CLASS_NONE, ) CONFIG_SCHEMA = ( diff --git a/esphome/components/homeassistant/sensor/__init__.py b/esphome/components/homeassistant/sensor/__init__.py index 0dadb78b73..cf29db8bb8 100644 --- a/esphome/components/homeassistant/sensor/__init__.py +++ b/esphome/components/homeassistant/sensor/__init__.py @@ -5,10 +5,7 @@ from esphome.const import ( CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_ID, - ICON_EMPTY, STATE_CLASS_NONE, - UNIT_EMPTY, - DEVICE_CLASS_EMPTY, ) from .. import homeassistant_ns @@ -19,7 +16,8 @@ HomeassistantSensor = homeassistant_ns.class_( ) CONFIG_SCHEMA = sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=1, + state_class=STATE_CLASS_NONE, ).extend( { cv.GenerateID(): cv.declare_id(HomeassistantSensor), diff --git a/esphome/components/hrxl_maxsonar_wr/sensor.py b/esphome/components/hrxl_maxsonar_wr/sensor.py index 370ee04b98..dd43bd84a7 100644 --- a/esphome/components/hrxl_maxsonar_wr/sensor.py +++ b/esphome/components/hrxl_maxsonar_wr/sensor.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv from esphome.components import sensor, uart from esphome.const import ( CONF_ID, - DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_METER, ICON_ARROW_EXPAND_VERTICAL, @@ -19,11 +18,10 @@ HrxlMaxsonarWrComponent = hrxlmaxsonarwr_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_METER, - ICON_ARROW_EXPAND_VERTICAL, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_METER, + icon=ICON_ARROW_EXPAND_VERTICAL, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/htu21d/sensor.py b/esphome/components/htu21d/sensor.py index 435c5bf1bb..37422f0329 100644 --- a/esphome/components/htu21d/sensor.py +++ b/esphome/components/htu21d/sensor.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_TEMPERATURE, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -25,18 +24,16 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(HTU21DComponent), cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Required(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 1, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/hx711/sensor.py b/esphome/components/hx711/sensor.py index 17a4e35d5f..cd06cc770f 100644 --- a/esphome/components/hx711/sensor.py +++ b/esphome/components/hx711/sensor.py @@ -6,10 +6,8 @@ from esphome.const import ( CONF_CLK_PIN, CONF_GAIN, CONF_ID, - DEVICE_CLASS_EMPTY, ICON_SCALE, STATE_CLASS_MEASUREMENT, - UNIT_EMPTY, ) hx711_ns = cg.esphome_ns.namespace("hx711") @@ -26,7 +24,9 @@ GAINS = { CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_EMPTY, ICON_SCALE, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + icon=ICON_SCALE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/ina219/sensor.py b/esphome/components/ina219/sensor.py index ed88ace967..020be9bc6e 100644 --- a/esphome/components/ina219/sensor.py +++ b/esphome/components/ina219/sensor.py @@ -13,7 +13,6 @@ from esphome.const import ( DEVICE_CLASS_CURRENT, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_VOLT, UNIT_AMPERE, @@ -32,20 +31,28 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(INA219Component), cv.Optional(CONF_BUS_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_SHUNT_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CURRENT): sensor.sensor_schema( - UNIT_AMPERE, - ICON_EMPTY, - 3, - DEVICE_CLASS_CURRENT, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_POWER): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_SHUNT_RESISTANCE, default=0.1): cv.All( cv.resistance, cv.Range(min=0.0, max=32.0) diff --git a/esphome/components/ina226/sensor.py b/esphome/components/ina226/sensor.py index e4ceda39c1..ee4036ce7e 100644 --- a/esphome/components/ina226/sensor.py +++ b/esphome/components/ina226/sensor.py @@ -12,7 +12,6 @@ from esphome.const import ( DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_POWER, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_VOLT, UNIT_AMPERE, @@ -31,20 +30,28 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(INA226Component), cv.Optional(CONF_BUS_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_SHUNT_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CURRENT): sensor.sensor_schema( - UNIT_AMPERE, - ICON_EMPTY, - 3, - DEVICE_CLASS_CURRENT, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_POWER): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_SHUNT_RESISTANCE, default=0.1): cv.All( cv.resistance, cv.Range(min=0.0) diff --git a/esphome/components/ina3221/sensor.py b/esphome/components/ina3221/sensor.py index 8b861d972d..9c42ecbb9d 100644 --- a/esphome/components/ina3221/sensor.py +++ b/esphome/components/ina3221/sensor.py @@ -11,7 +11,6 @@ from esphome.const import ( DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_POWER, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_VOLT, UNIT_AMPERE, @@ -32,16 +31,28 @@ INA3221Component = ina3221_ns.class_( INA3221_CHANNEL_SCHEMA = cv.Schema( { cv.Optional(CONF_BUS_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_SHUNT_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CURRENT): sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_POWER): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_SHUNT_RESISTANCE, default=0.1): cv.All( cv.resistance, cv.Range(min=0.0, max=32.0) diff --git a/esphome/components/inkbird_ibsth1_mini/sensor.py b/esphome/components/inkbird_ibsth1_mini/sensor.py index aaaaddb890..a71921f8ed 100644 --- a/esphome/components/inkbird_ibsth1_mini/sensor.py +++ b/esphome/components/inkbird_ibsth1_mini/sensor.py @@ -9,7 +9,6 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -32,32 +31,28 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(InkbirdUBSTH1_MINI), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_EXTERNAL_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 1, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_BATTERY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/max31855/sensor.py b/esphome/components/max31855/sensor.py index a8b5d25c61..c7732dfbe3 100644 --- a/esphome/components/max31855/sensor.py +++ b/esphome/components/max31855/sensor.py @@ -5,7 +5,6 @@ from esphome.const import ( CONF_ID, CONF_REFERENCE_TEMPERATURE, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) @@ -16,16 +15,19 @@ MAX31855Sensor = max31855_ns.class_( ) CONFIG_SCHEMA = ( - sensor.sensor_schema(UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE) + sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + ) .extend( { cv.GenerateID(): cv.declare_id(MAX31855Sensor), cv.Optional(CONF_REFERENCE_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 2, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/max31856/sensor.py b/esphome/components/max31856/sensor.py index 9583c0bcf9..083d2ac30c 100644 --- a/esphome/components/max31856/sensor.py +++ b/esphome/components/max31856/sensor.py @@ -5,7 +5,6 @@ from esphome.const import ( CONF_ID, CONF_MAINS_FILTER, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) @@ -23,11 +22,10 @@ FILTER = { CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/max31865/sensor.py b/esphome/components/max31865/sensor.py index 64495ebd7a..33d9c42be3 100644 --- a/esphome/components/max31865/sensor.py +++ b/esphome/components/max31865/sensor.py @@ -8,7 +8,6 @@ from esphome.const import ( CONF_RTD_NOMINAL_RESISTANCE, CONF_RTD_WIRES, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) @@ -26,7 +25,10 @@ FILTER = { CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_CELSIUS, ICON_EMPTY, 2, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/max6675/sensor.py b/esphome/components/max6675/sensor.py index ad0e89c028..dff8360226 100644 --- a/esphome/components/max6675/sensor.py +++ b/esphome/components/max6675/sensor.py @@ -4,7 +4,6 @@ from esphome.components import sensor, spi from esphome.const import ( CONF_ID, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) @@ -16,7 +15,10 @@ MAX6675Sensor = max6675_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/mcp9808/sensor.py b/esphome/components/mcp9808/sensor.py index d417f45955..c7f6226e0b 100644 --- a/esphome/components/mcp9808/sensor.py +++ b/esphome/components/mcp9808/sensor.py @@ -4,7 +4,6 @@ from esphome.components import i2c, sensor from esphome.const import ( CONF_ID, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) @@ -19,7 +18,10 @@ MCP9808Sensor = mcp9808_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/mhz19/sensor.py b/esphome/components/mhz19/sensor.py index ebcecb84e2..1a111f7891 100644 --- a/esphome/components/mhz19/sensor.py +++ b/esphome/components/mhz19/sensor.py @@ -7,13 +7,11 @@ from esphome.const import ( CONF_CO2, CONF_ID, CONF_TEMPERATURE, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_TEMPERATURE, ICON_MOLECULE_CO2, STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, UNIT_CELSIUS, - ICON_EMPTY, ) DEPENDENCIES = ["uart"] @@ -33,18 +31,16 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(MHZ19Component), cv.Required(CONF_CO2): sensor.sensor_schema( - UNIT_PARTS_PER_MILLION, - ICON_MOLECULE_CO2, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PARTS_PER_MILLION, + icon=ICON_MOLECULE_CO2, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 0, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_AUTOMATIC_BASELINE_CALIBRATION): cv.boolean, } diff --git a/esphome/components/midea_ac/climate.py b/esphome/components/midea_ac/climate.py index 00aa979515..741741fd03 100644 --- a/esphome/components/midea_ac/climate.py +++ b/esphome/components/midea_ac/climate.py @@ -63,21 +63,25 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_PRESET_SLEEP, default=False): cv.boolean, cv.Optional(CONF_PRESET_BOOST, default=False): cv.boolean, cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_THERMOMETER, - 0, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_POWER_USAGE): sensor.sensor_schema( - UNIT_WATT, ICON_POWER, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + icon=ICON_POWER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY_SETPOINT): sensor.sensor_schema( - UNIT_PERCENT, - ICON_WATER_PERCENT, - 0, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + icon=ICON_WATER_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/mpu6050/sensor.py b/esphome/components/mpu6050/sensor.py index 05c26289b4..f9b61dcadc 100644 --- a/esphome/components/mpu6050/sensor.py +++ b/esphome/components/mpu6050/sensor.py @@ -4,10 +4,8 @@ from esphome.components import i2c, sensor from esphome.const import ( CONF_ID, CONF_TEMPERATURE, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_TEMPERATURE, ICON_BRIEFCASE_DOWNLOAD, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_METER_PER_SECOND_SQUARED, ICON_SCREEN_ROTATION, @@ -30,21 +28,22 @@ MPU6050Component = mpu6050_ns.class_( ) accel_schema = sensor.sensor_schema( - UNIT_METER_PER_SECOND_SQUARED, - ICON_BRIEFCASE_DOWNLOAD, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_METER_PER_SECOND_SQUARED, + icon=ICON_BRIEFCASE_DOWNLOAD, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ) gyro_schema = sensor.sensor_schema( - UNIT_DEGREE_PER_SECOND, - ICON_SCREEN_ROTATION, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_DEGREE_PER_SECOND, + icon=ICON_SCREEN_ROTATION, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ) temperature_schema = sensor.sensor_schema( - UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ) CONFIG_SCHEMA = ( diff --git a/esphome/components/mqtt_subscribe/sensor/__init__.py b/esphome/components/mqtt_subscribe/sensor/__init__.py index d640b254de..420d4f152c 100644 --- a/esphome/components/mqtt_subscribe/sensor/__init__.py +++ b/esphome/components/mqtt_subscribe/sensor/__init__.py @@ -6,9 +6,6 @@ from esphome.const import ( CONF_QOS, CONF_TOPIC, STATE_CLASS_NONE, - UNIT_EMPTY, - ICON_EMPTY, - DEVICE_CLASS_EMPTY, ) from .. import mqtt_subscribe_ns @@ -21,7 +18,8 @@ MQTTSubscribeSensor = mqtt_subscribe_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=1, + state_class=STATE_CLASS_NONE, ) .extend( { diff --git a/esphome/components/ms5611/sensor.py b/esphome/components/ms5611/sensor.py index 34198e04eb..5decb13436 100644 --- a/esphome/components/ms5611/sensor.py +++ b/esphome/components/ms5611/sensor.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_TEMPERATURE, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ICON_GAUGE, @@ -26,18 +25,17 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(MS5611Component), cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Required(CONF_PRESSURE): sensor.sensor_schema( - UNIT_HECTOPASCAL, - ICON_GAUGE, - 1, - DEVICE_CLASS_PRESSURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_HECTOPASCAL, + icon=ICON_GAUGE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/nextion/sensor/__init__.py b/esphome/components/nextion/sensor/__init__.py index f8a383e3ac..8a32adc1f6 100644 --- a/esphome/components/nextion/sensor/__init__.py +++ b/esphome/components/nextion/sensor/__init__.py @@ -4,10 +4,7 @@ from esphome.components import sensor from esphome.const import ( CONF_ID, - UNIT_EMPTY, - ICON_EMPTY, CONF_COMPONENT_ID, - DEVICE_CLASS_EMPTY, ) from .. import nextion_ns, CONF_NEXTION_ID @@ -46,7 +43,9 @@ def _validate(config): CONFIG_SCHEMA = cv.All( - sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 2, DEVICE_CLASS_EMPTY) + sensor.sensor_schema( + accuracy_decimals=2, + ) .extend( { cv.GenerateID(): cv.declare_id(NextionSensor), diff --git a/esphome/components/ntc/sensor.py b/esphome/components/ntc/sensor.py index e7b8c03586..bf819ffd16 100644 --- a/esphome/components/ntc/sensor.py +++ b/esphome/components/ntc/sensor.py @@ -12,7 +12,6 @@ from esphome.const import ( CONF_TEMPERATURE, CONF_VALUE, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) @@ -120,7 +119,10 @@ def process_calibration(value): CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/pid/sensor/__init__.py b/esphome/components/pid/sensor/__init__.py index 61669d4716..d1007fcbc4 100644 --- a/esphome/components/pid/sensor/__init__.py +++ b/esphome/components/pid/sensor/__init__.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( CONF_ID, - DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_PERCENT, ICON_GAUGE, @@ -30,7 +29,10 @@ PID_CLIMATE_SENSOR_TYPES = { CONF_CLIMATE_ID = "climate_id" CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_PERCENT, ICON_GAUGE, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_PERCENT, + icon=ICON_GAUGE, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/pm1006/sensor.py b/esphome/components/pm1006/sensor.py index 18e1b0d87c..8ea0e303f3 100644 --- a/esphome/components/pm1006/sensor.py +++ b/esphome/components/pm1006/sensor.py @@ -4,7 +4,6 @@ from esphome.components import sensor, uart from esphome.const import ( CONF_ID, CONF_PM_2_5, - DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_BLUR, @@ -21,11 +20,10 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(PM1006Component), cv.Optional(CONF_PM_2_5): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_BLUR, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_BLUR, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index 80f2b80e5e..935208fe03 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -10,11 +10,9 @@ from esphome.const import ( CONF_PM_2_5, CONF_TEMPERATURE, CONF_TYPE, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, ICON_CHEMICAL_WEAPON, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, UNIT_CELSIUS, @@ -63,46 +61,40 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(PMSX003Component), cv.Required(CONF_TYPE): cv.enum(PMSX003_TYPES, upper=True), cv.Optional(CONF_PM_1_0): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_2_5): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 1, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_FORMALDEHYDE): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/pulse_counter/sensor.py b/esphome/components/pulse_counter/sensor.py index 71227ec491..767728fc80 100644 --- a/esphome/components/pulse_counter/sensor.py +++ b/esphome/components/pulse_counter/sensor.py @@ -11,7 +11,6 @@ from esphome.const import ( CONF_RISING_EDGE, CONF_NUMBER, CONF_TOTAL, - DEVICE_CLASS_EMPTY, ICON_PULSE, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, @@ -67,11 +66,10 @@ def validate_count_mode(value): CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_PULSES_PER_MINUTE, - ICON_PULSE, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PULSES_PER_MINUTE, + icon=ICON_PULSE, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { @@ -94,7 +92,10 @@ CONFIG_SCHEMA = ( ), cv.Optional(CONF_INTERNAL_FILTER, default="13us"): validate_internal_filter, cv.Optional(CONF_TOTAL): sensor.sensor_schema( - UNIT_PULSES, ICON_PULSE, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + unit_of_measurement=UNIT_PULSES, + icon=ICON_PULSE, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, ), } ) diff --git a/esphome/components/pulse_meter/sensor.py b/esphome/components/pulse_meter/sensor.py index 4b8e8a1be0..18da842bad 100644 --- a/esphome/components/pulse_meter/sensor.py +++ b/esphome/components/pulse_meter/sensor.py @@ -15,7 +15,6 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_PULSES, UNIT_PULSES_PER_MINUTE, - DEVICE_CLASS_EMPTY, ) from esphome.core import CORE @@ -51,7 +50,10 @@ def validate_pulse_meter_pin(value): CONFIG_SCHEMA = sensor.sensor_schema( - UNIT_PULSES_PER_MINUTE, ICON_PULSE, 2, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_PULSES_PER_MINUTE, + icon=ICON_PULSE, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ).extend( { cv.GenerateID(): cv.declare_id(PulseMeterSensor), @@ -59,12 +61,11 @@ CONFIG_SCHEMA = sensor.sensor_schema( cv.Optional(CONF_INTERNAL_FILTER, default="13us"): validate_internal_filter, cv.Optional(CONF_TIMEOUT, default="5min"): validate_timeout, cv.Optional(CONF_TOTAL): sensor.sensor_schema( - UNIT_PULSES, - ICON_PULSE, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_PULSES, + icon=ICON_PULSE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), } ) diff --git a/esphome/components/pulse_width/sensor.py b/esphome/components/pulse_width/sensor.py index 6a6147c6aa..6c91104036 100644 --- a/esphome/components/pulse_width/sensor.py +++ b/esphome/components/pulse_width/sensor.py @@ -5,7 +5,6 @@ from esphome.components import sensor from esphome.const import ( CONF_ID, CONF_PIN, - DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_SECOND, ICON_TIMER, @@ -19,7 +18,10 @@ PulseWidthSensor = pulse_width_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_SECOND, ICON_TIMER, 3, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_SECOND, + icon=ICON_TIMER, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/pvvx_mithermometer/sensor.py b/esphome/components/pvvx_mithermometer/sensor.py index 16b3a86dfd..d6c2d68b8c 100644 --- a/esphome/components/pvvx_mithermometer/sensor.py +++ b/esphome/components/pvvx_mithermometer/sensor.py @@ -12,7 +12,6 @@ from esphome.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, - ICON_EMPTY, UNIT_CELSIUS, UNIT_PERCENT, UNIT_VOLT, @@ -33,16 +32,24 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(PVVXMiThermometer), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, ICON_EMPTY, 2, DEVICE_CLASS_TEMPERATURE + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, ICON_EMPTY, 2, DEVICE_CLASS_HUMIDITY + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_HUMIDITY, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, ), cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, ), } ) diff --git a/esphome/components/pzem004t/sensor.py b/esphome/components/pzem004t/sensor.py index b358b8c650..23502e849a 100644 --- a/esphome/components/pzem004t/sensor.py +++ b/esphome/components/pzem004t/sensor.py @@ -11,7 +11,6 @@ from esphome.const import ( DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, - ICON_EMPTY, LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, UNIT_VOLT, @@ -30,25 +29,29 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(PZEM004T), cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CURRENT): sensor.sensor_schema( - UNIT_AMPERE, - ICON_EMPTY, - 2, - DEVICE_CLASS_CURRENT, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_POWER): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, - ICON_EMPTY, - 0, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), } ) diff --git a/esphome/components/pzemac/sensor.py b/esphome/components/pzemac/sensor.py index 1dd77a0371..1616bf0ace 100644 --- a/esphome/components/pzemac/sensor.py +++ b/esphome/components/pzemac/sensor.py @@ -9,13 +9,11 @@ from esphome.const import ( CONF_VOLTAGE, CONF_FREQUENCY, CONF_POWER_FACTOR, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_POWER, DEVICE_CLASS_ENERGY, - ICON_EMPTY, ICON_CURRENT_AC, LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, @@ -23,7 +21,6 @@ from esphome.const import ( UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, - UNIT_EMPTY, UNIT_WATT_HOURS, ) @@ -37,39 +34,40 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(PZEMAC), cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CURRENT): sensor.sensor_schema( - UNIT_AMPERE, - ICON_EMPTY, - 3, - DEVICE_CLASS_CURRENT, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_POWER): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, - ICON_EMPTY, - 0, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( - UNIT_HERTZ, - ICON_CURRENT_AC, - 1, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_HERTZ, + icon=ICON_CURRENT_AC, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema( - UNIT_EMPTY, - ICON_EMPTY, - 2, - DEVICE_CLASS_POWER_FACTOR, - STATE_CLASS_MEASUREMENT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER_FACTOR, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/pzemdc/sensor.py b/esphome/components/pzemdc/sensor.py index 58afea8e30..08ec688afb 100644 --- a/esphome/components/pzemdc/sensor.py +++ b/esphome/components/pzemdc/sensor.py @@ -9,7 +9,6 @@ from esphome.const import ( DEVICE_CLASS_CURRENT, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_VOLT, UNIT_AMPERE, @@ -26,17 +25,22 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(PZEMDC), cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CURRENT): sensor.sensor_schema( - UNIT_AMPERE, - ICON_EMPTY, - 3, - DEVICE_CLASS_CURRENT, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_POWER): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/qmc5883l/sensor.py b/esphome/components/qmc5883l/sensor.py index d0fdf1b77a..27d1df5b29 100644 --- a/esphome/components/qmc5883l/sensor.py +++ b/esphome/components/qmc5883l/sensor.py @@ -6,7 +6,6 @@ from esphome.const import ( CONF_ID, CONF_OVERSAMPLING, CONF_RANGE, - DEVICE_CLASS_EMPTY, ICON_MAGNET, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, @@ -71,10 +70,16 @@ def validate_enum(enum_values, units=None, int=True): field_strength_schema = sensor.sensor_schema( - UNIT_MICROTESLA, ICON_MAGNET, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_MICROTESLA, + icon=ICON_MAGNET, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ) heading_schema = sensor.sensor_schema( - UNIT_DEGREES, ICON_SCREEN_ROTATION, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + unit_of_measurement=UNIT_DEGREES, + icon=ICON_SCREEN_ROTATION, + accuracy_decimals=1, + state_class=STATE_CLASS_NONE, ) CONFIG_SCHEMA = ( diff --git a/esphome/components/resistance/sensor.py b/esphome/components/resistance/sensor.py index ca1501195a..329192e902 100644 --- a/esphome/components/resistance/sensor.py +++ b/esphome/components/resistance/sensor.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( CONF_SENSOR, - DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_OHM, ICON_FLASH, @@ -25,7 +24,10 @@ CONFIGURATIONS = { CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_OHM, ICON_FLASH, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_OHM, + icon=ICON_FLASH, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/rotary_encoder/sensor.py b/esphome/components/rotary_encoder/sensor.py index 079f00d284..311e63a7ba 100644 --- a/esphome/components/rotary_encoder/sensor.py +++ b/esphome/components/rotary_encoder/sensor.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_RESOLUTION, CONF_MIN_VALUE, CONF_MAX_VALUE, - DEVICE_CLASS_EMPTY, STATE_CLASS_NONE, UNIT_STEPS, ICON_ROTATE_RIGHT, @@ -58,7 +57,10 @@ def validate_min_max_value(config): CONFIG_SCHEMA = cv.All( sensor.sensor_schema( - UNIT_STEPS, ICON_ROTATE_RIGHT, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + unit_of_measurement=UNIT_STEPS, + icon=ICON_ROTATE_RIGHT, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, ) .extend( { diff --git a/esphome/components/ruuvitag/sensor.py b/esphome/components/ruuvitag/sensor.py index 12b8425d14..342a5eff24 100644 --- a/esphome/components/ruuvitag/sensor.py +++ b/esphome/components/ruuvitag/sensor.py @@ -14,13 +14,11 @@ from esphome.const import ( CONF_TX_POWER, CONF_MEASUREMENT_SEQUENCE_NUMBER, CONF_MOVEMENT_COUNTER, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, UNIT_CELSIUS, @@ -29,7 +27,6 @@ from esphome.const import ( UNIT_HECTOPASCAL, UNIT_G, UNIT_DECIBEL_MILLIWATT, - UNIT_EMPTY, ICON_GAUGE, ICON_ACCELERATION, ICON_ACCELERATION_X, @@ -52,69 +49,68 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(RuuviTag), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 2, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 2, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PRESSURE): sensor.sensor_schema( - UNIT_HECTOPASCAL, - ICON_EMPTY, - 2, - DEVICE_CLASS_PRESSURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=2, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ACCELERATION): sensor.sensor_schema( - UNIT_G, - ICON_ACCELERATION, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_G, + icon=ICON_ACCELERATION, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ACCELERATION_X): sensor.sensor_schema( - UNIT_G, - ICON_ACCELERATION_X, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_G, + icon=ICON_ACCELERATION_X, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ACCELERATION_Y): sensor.sensor_schema( - UNIT_G, - ICON_ACCELERATION_Y, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_G, + icon=ICON_ACCELERATION_Y, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ACCELERATION_Z): sensor.sensor_schema( - UNIT_G, - ICON_ACCELERATION_Z, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_G, + icon=ICON_ACCELERATION_Z, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TX_POWER): sensor.sensor_schema( - UNIT_DECIBEL_MILLIWATT, - ICON_EMPTY, - 0, - DEVICE_CLASS_SIGNAL_STRENGTH, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_DECIBEL_MILLIWATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_SIGNAL_STRENGTH, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_MOVEMENT_COUNTER): sensor.sensor_schema( - UNIT_EMPTY, ICON_GAUGE, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + icon=ICON_GAUGE, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_MEASUREMENT_SEQUENCE_NUMBER): sensor.sensor_schema( - UNIT_EMPTY, ICON_GAUGE, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + icon=ICON_GAUGE, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, ), } ) diff --git a/esphome/components/scd30/sensor.py b/esphome/components/scd30/sensor.py index 7a08289474..c0317c96e0 100644 --- a/esphome/components/scd30/sensor.py +++ b/esphome/components/scd30/sensor.py @@ -3,13 +3,11 @@ import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( CONF_ID, - DEVICE_CLASS_EMPTY, CONF_HUMIDITY, CONF_TEMPERATURE, CONF_CO2, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, @@ -33,25 +31,22 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(SCD30Component), cv.Optional(CONF_CO2): sensor.sensor_schema( - UNIT_PARTS_PER_MILLION, - ICON_MOLECULE_CO2, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PARTS_PER_MILLION, + icon=ICON_MOLECULE_CO2, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 1, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_AUTOMATIC_SELF_CALIBRATION, default=True): cv.boolean, cv.Optional(CONF_ALTITUDE_COMPENSATION): cv.All( diff --git a/esphome/components/sdm_meter/sensor.py b/esphome/components/sdm_meter/sensor.py index ce560b9d4b..13cc94786c 100644 --- a/esphome/components/sdm_meter/sensor.py +++ b/esphome/components/sdm_meter/sensor.py @@ -17,19 +17,16 @@ from esphome.const import ( CONF_REACTIVE_POWER, CONF_VOLTAGE, DEVICE_CLASS_CURRENT, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_VOLTAGE, ICON_CURRENT_AC, - ICON_EMPTY, ICON_FLASH, LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, UNIT_AMPERE, UNIT_DEGREES, - UNIT_EMPTY, UNIT_HERTZ, UNIT_VOLT, UNIT_VOLT_AMPS, @@ -46,27 +43,43 @@ sdm_meter_ns = cg.esphome_ns.namespace("sdm_meter") SDMMeter = sdm_meter_ns.class_("SDMMeter", cg.PollingComponent, modbus.ModbusDevice) PHASE_SENSORS = { - CONF_VOLTAGE: sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE), + CONF_VOLTAGE: sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + ), CONF_CURRENT: sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 3, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_ACTIVE_POWER: sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_APPARENT_POWER: sensor.sensor_schema( - UNIT_VOLT_AMPS, ICON_EMPTY, 2, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT_AMPS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_REACTIVE_POWER: sensor.sensor_schema( - UNIT_VOLT_AMPS_REACTIVE, - ICON_EMPTY, - 2, - DEVICE_CLASS_POWER, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_POWER_FACTOR: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 3, DEVICE_CLASS_POWER_FACTOR, STATE_CLASS_MEASUREMENT + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER_FACTOR, + state_class=STATE_CLASS_MEASUREMENT, + ), + CONF_PHASE_ANGLE: sensor.sensor_schema( + unit_of_measurement=UNIT_DEGREES, icon=ICON_FLASH, accuracy_decimals=3 ), - CONF_PHASE_ANGLE: sensor.sensor_schema(UNIT_DEGREES, ICON_FLASH, 3), } PHASE_SCHEMA = cv.Schema( @@ -81,43 +94,38 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_PHASE_B): PHASE_SCHEMA, cv.Optional(CONF_PHASE_C): PHASE_SCHEMA, cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( - UNIT_HERTZ, - ICON_CURRENT_AC, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_HERTZ, + icon=ICON_CURRENT_AC, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_IMPORT_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, - ICON_EMPTY, - 2, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_EXPORT_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, - ICON_EMPTY, - 2, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_IMPORT_REACTIVE_ENERGY): sensor.sensor_schema( - UNIT_VOLT_AMPS_REACTIVE_HOURS, - ICON_EMPTY, - 2, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_EXPORT_REACTIVE_ENERGY): sensor.sensor_schema( - UNIT_VOLT_AMPS_REACTIVE_HOURS, - ICON_EMPTY, - 2, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), } ) diff --git a/esphome/components/sdp3x/sensor.py b/esphome/components/sdp3x/sensor.py index 68e3d2812c..08d7250f6e 100644 --- a/esphome/components/sdp3x/sensor.py +++ b/esphome/components/sdp3x/sensor.py @@ -4,7 +4,6 @@ from esphome.components import i2c, sensor from esphome.const import ( CONF_ID, DEVICE_CLASS_PRESSURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_HECTOPASCAL, ) @@ -17,11 +16,10 @@ SDP3XComponent = sdp3x_ns.class_("SDP3XComponent", cg.PollingComponent, i2c.I2CD CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_HECTOPASCAL, - ICON_EMPTY, - 3, - DEVICE_CLASS_PRESSURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=3, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/sds011/sensor.py b/esphome/components/sds011/sensor.py index af482839a9..0997b47ef6 100644 --- a/esphome/components/sds011/sensor.py +++ b/esphome/components/sds011/sensor.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_PM_2_5, CONF_RX_ONLY, CONF_UPDATE_INTERVAL, - DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, @@ -39,18 +38,16 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(SDS011Component), cv.Optional(CONF_PM_2_5): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 1, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 1, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_RX_ONLY, default=False): cv.boolean, cv.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_minutes, diff --git a/esphome/components/selec_meter/sensor.py b/esphome/components/selec_meter/sensor.py index 7eb526aec7..2d05d00380 100644 --- a/esphome/components/selec_meter/sensor.py +++ b/esphome/components/selec_meter/sensor.py @@ -15,17 +15,14 @@ from esphome.const import ( CONF_REACTIVE_POWER, CONF_VOLTAGE, DEVICE_CLASS_CURRENT, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_VOLTAGE, ICON_CURRENT_AC, - ICON_EMPTY, LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, UNIT_AMPERE, - UNIT_EMPTY, UNIT_HERTZ, UNIT_VOLT, UNIT_VOLT_AMPS, @@ -54,98 +51,112 @@ SelecMeter = selec_meter_ns.class_( SENSORS = { CONF_TOTAL_ACTIVE_ENERGY: sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 2, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), CONF_IMPORT_ACTIVE_ENERGY: sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 2, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), CONF_EXPORT_ACTIVE_ENERGY: sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 2, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), CONF_TOTAL_REACTIVE_ENERGY: sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, - ICON_EMPTY, - 2, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), CONF_IMPORT_REACTIVE_ENERGY: sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, - ICON_EMPTY, - 2, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), CONF_EXPORT_REACTIVE_ENERGY: sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, - ICON_EMPTY, - 2, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), CONF_APPARENT_ENERGY: sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_HOURS, - ICON_EMPTY, - 2, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + unit_of_measurement=UNIT_KILOVOLT_AMPS_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ), CONF_ACTIVE_POWER: sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_REACTIVE_POWER: sensor.sensor_schema( - UNIT_VOLT_AMPS_REACTIVE, - ICON_EMPTY, - 3, - DEVICE_CLASS_POWER, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_APPARENT_POWER: sensor.sensor_schema( - UNIT_VOLT_AMPS, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_CURRENT: sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 3, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_POWER_FACTOR: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 3, DEVICE_CLASS_POWER_FACTOR, STATE_CLASS_MEASUREMENT + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER_FACTOR, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_FREQUENCY: sensor.sensor_schema( - UNIT_HERTZ, ICON_CURRENT_AC, 2, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_HERTZ, + icon=ICON_CURRENT_AC, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_MAXIMUM_DEMAND_ACTIVE_POWER: sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_MAXIMUM_DEMAND_REACTIVE_POWER: sensor.sensor_schema( - UNIT_VOLT_AMPS_REACTIVE, - ICON_EMPTY, - 3, - DEVICE_CLASS_POWER, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_MAXIMUM_DEMAND_APPARENT_POWER: sensor.sensor_schema( - UNIT_VOLT_AMPS, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), } diff --git a/esphome/components/senseair/sensor.py b/esphome/components/senseair/sensor.py index 2d40e12a09..739a8ada50 100644 --- a/esphome/components/senseair/sensor.py +++ b/esphome/components/senseair/sensor.py @@ -6,7 +6,6 @@ from esphome.components import sensor, uart from esphome.const import ( CONF_CO2, CONF_ID, - DEVICE_CLASS_EMPTY, ICON_MOLECULE_CO2, STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, @@ -39,11 +38,10 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(SenseAirComponent), cv.Required(CONF_CO2): sensor.sensor_schema( - UNIT_PARTS_PER_MILLION, - ICON_MOLECULE_CO2, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PARTS_PER_MILLION, + icon=ICON_MOLECULE_CO2, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 62ed07b408..6152f2cca5 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -1,5 +1,4 @@ import math -from typing import Optional import esphome.codegen as cg import esphome.config_validation as cv @@ -34,8 +33,6 @@ from esphome.const import ( LAST_RESET_TYPE_AUTO, LAST_RESET_TYPE_NEVER, LAST_RESET_TYPE_NONE, - UNIT_EMPTY, - ICON_EMPTY, DEVICE_CLASS_EMPTY, DEVICE_CLASS_BATTERY, DEVICE_CLASS_CARBON_MONOXIDE, @@ -52,7 +49,6 @@ from esphome.const import ( DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TIMESTAMP, DEVICE_CLASS_VOLTAGE, - STATE_CLASS_NONE, ) from esphome.core import CORE, coroutine_with_priority from esphome.util import Registry @@ -169,19 +165,19 @@ CalibrateLinearFilter = sensor_ns.class_("CalibrateLinearFilter", Filter) CalibratePolynomialFilter = sensor_ns.class_("CalibratePolynomialFilter", Filter) SensorInRangeCondition = sensor_ns.class_("SensorInRangeCondition", Filter) -unit_of_measurement = cv.string_strict -accuracy_decimals = cv.int_ -icon = cv.icon -device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") +validate_unit_of_measurement = cv.string_strict +validate_accuracy_decimals = cv.int_ +validate_icon = cv.icon +validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSensorComponent), cv.GenerateID(): cv.declare_id(Sensor), - cv.Optional(CONF_UNIT_OF_MEASUREMENT): unit_of_measurement, - cv.Optional(CONF_ICON): icon, - cv.Optional(CONF_ACCURACY_DECIMALS): accuracy_decimals, - cv.Optional(CONF_DEVICE_CLASS): device_class, + cv.Optional(CONF_UNIT_OF_MEASUREMENT): validate_unit_of_measurement, + cv.Optional(CONF_ICON): validate_icon, + cv.Optional(CONF_ACCURACY_DECIMALS): validate_accuracy_decimals, + cv.Optional(CONF_DEVICE_CLASS): validate_device_class, cv.Optional(CONF_STATE_CLASS): validate_state_class, cv.Optional(CONF_LAST_RESET_TYPE): validate_last_reset_type, cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean, @@ -211,47 +207,53 @@ SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( } ) +_UNDEF = object() + def sensor_schema( - unit_of_measurement_: str, - icon_: str, - accuracy_decimals_: int, - device_class_: Optional[str] = DEVICE_CLASS_EMPTY, - state_class_: Optional[str] = STATE_CLASS_NONE, - last_reset_type_: Optional[str] = LAST_RESET_TYPE_NONE, + unit_of_measurement: str = _UNDEF, + icon: str = _UNDEF, + accuracy_decimals: int = _UNDEF, + device_class: str = _UNDEF, + state_class: str = _UNDEF, + last_reset_type: str = _UNDEF, ) -> cv.Schema: schema = SENSOR_SCHEMA - if unit_of_measurement_ != UNIT_EMPTY: + if unit_of_measurement is not _UNDEF: schema = schema.extend( { cv.Optional( - CONF_UNIT_OF_MEASUREMENT, default=unit_of_measurement_ - ): unit_of_measurement + CONF_UNIT_OF_MEASUREMENT, default=unit_of_measurement + ): validate_unit_of_measurement } ) - if icon_ != ICON_EMPTY: - schema = schema.extend({cv.Optional(CONF_ICON, default=icon_): icon}) - if accuracy_decimals_ != 0: + if icon is not _UNDEF: + schema = schema.extend({cv.Optional(CONF_ICON, default=icon): validate_icon}) + if accuracy_decimals is not _UNDEF: schema = schema.extend( { cv.Optional( - CONF_ACCURACY_DECIMALS, default=accuracy_decimals_ - ): accuracy_decimals, + CONF_ACCURACY_DECIMALS, default=accuracy_decimals + ): validate_accuracy_decimals, } ) - if device_class_ != DEVICE_CLASS_EMPTY: - schema = schema.extend( - {cv.Optional(CONF_DEVICE_CLASS, default=device_class_): device_class} - ) - if state_class_ != STATE_CLASS_NONE: - schema = schema.extend( - {cv.Optional(CONF_STATE_CLASS, default=state_class_): validate_state_class} - ) - if last_reset_type_ != LAST_RESET_TYPE_NONE: + if device_class is not _UNDEF: schema = schema.extend( { cv.Optional( - CONF_LAST_RESET_TYPE, default=last_reset_type_ + CONF_DEVICE_CLASS, default=device_class + ): validate_device_class + } + ) + if state_class is not _UNDEF: + schema = schema.extend( + {cv.Optional(CONF_STATE_CLASS, default=state_class): validate_state_class} + ) + if last_reset_type is not _UNDEF: + schema = schema.extend( + { + cv.Optional( + CONF_LAST_RESET_TYPE, default=last_reset_type ): validate_last_reset_type } ) diff --git a/esphome/components/sgp30/sensor.py b/esphome/components/sgp30/sensor.py index 7a3e870f6d..3e33af3b4a 100644 --- a/esphome/components/sgp30/sensor.py +++ b/esphome/components/sgp30/sensor.py @@ -4,14 +4,12 @@ from esphome.components import i2c, sensor from esphome.const import ( CONF_ID, CONF_BASELINE, - DEVICE_CLASS_EMPTY, CONF_ECO2, CONF_TVOC, ICON_RADIATOR, STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, UNIT_PARTS_PER_BILLION, - UNIT_EMPTY, ICON_MOLECULE_CO2, ) @@ -33,24 +31,24 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(SGP30Component), cv.Required(CONF_ECO2): sensor.sensor_schema( - UNIT_PARTS_PER_MILLION, - ICON_MOLECULE_CO2, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PARTS_PER_MILLION, + icon=ICON_MOLECULE_CO2, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Required(CONF_TVOC): sensor.sensor_schema( - UNIT_PARTS_PER_BILLION, - ICON_RADIATOR, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PARTS_PER_BILLION, + icon=ICON_RADIATOR, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ECO2_BASELINE): sensor.sensor_schema( - UNIT_EMPTY, ICON_MOLECULE_CO2, 0, DEVICE_CLASS_EMPTY + icon=ICON_MOLECULE_CO2, + accuracy_decimals=0, ), cv.Optional(CONF_TVOC_BASELINE): sensor.sensor_schema( - UNIT_EMPTY, ICON_RADIATOR, 0, DEVICE_CLASS_EMPTY + icon=ICON_RADIATOR, + accuracy_decimals=0, ), cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean, cv.Optional(CONF_BASELINE): cv.Schema( diff --git a/esphome/components/sgp40/sensor.py b/esphome/components/sgp40/sensor.py index 36e039d2b5..0f562048ac 100644 --- a/esphome/components/sgp40/sensor.py +++ b/esphome/components/sgp40/sensor.py @@ -3,10 +3,8 @@ import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( CONF_ID, - DEVICE_CLASS_EMPTY, ICON_RADIATOR, STATE_CLASS_MEASUREMENT, - UNIT_EMPTY, ) DEPENDENCIES = ["i2c"] @@ -26,7 +24,9 @@ CONF_VOC_BASELINE = "voc_baseline" CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_EMPTY, ICON_RADIATOR, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + icon=ICON_RADIATOR, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/sht3xd/sensor.py b/esphome/components/sht3xd/sensor.py index 2a4bb6594e..b9e7bce733 100644 --- a/esphome/components/sht3xd/sensor.py +++ b/esphome/components/sht3xd/sensor.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_TEMPERATURE, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -25,18 +24,16 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(SHT3XDComponent), cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Required(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 1, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/sht4x/sensor.py b/esphome/components/sht4x/sensor.py index a746ecde07..a66ca1a526 100644 --- a/esphome/components/sht4x/sensor.py +++ b/esphome/components/sht4x/sensor.py @@ -51,18 +51,18 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(SHT4XComponent), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_THERMOMETER, - 2, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_WATER_PERCENT, - 2, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + icon=ICON_WATER_PERCENT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PRECISION, default="High"): cv.enum(PRECISION_OPTIONS), cv.Optional(CONF_HEATER_POWER, default="High"): cv.enum( diff --git a/esphome/components/shtcx/sensor.py b/esphome/components/shtcx/sensor.py index af9379218c..ba2283a9b4 100644 --- a/esphome/components/shtcx/sensor.py +++ b/esphome/components/shtcx/sensor.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_TEMPERATURE, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -25,18 +24,16 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(SHTCXComponent), cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Required(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 1, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/sm300d2/sensor.py b/esphome/components/sm300d2/sensor.py index 3d522b3bd5..73cada0eb3 100644 --- a/esphome/components/sm300d2/sensor.py +++ b/esphome/components/sm300d2/sensor.py @@ -10,7 +10,6 @@ from esphome.const import ( CONF_PM_10_0, CONF_TEMPERATURE, CONF_HUMIDITY, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY, STATE_CLASS_MEASUREMENT, @@ -18,7 +17,6 @@ from esphome.const import ( UNIT_MICROGRAMS_PER_CUBIC_METER, UNIT_CELSIUS, UNIT_PERCENT, - ICON_EMPTY, ICON_MOLECULE_CO2, ICON_FLASK, ICON_CHEMICAL_WEAPON, @@ -35,53 +33,46 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(SM300D2Sensor), cv.Optional(CONF_CO2): sensor.sensor_schema( - UNIT_PARTS_PER_MILLION, - ICON_MOLECULE_CO2, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PARTS_PER_MILLION, + icon=ICON_MOLECULE_CO2, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_FORMALDEHYDE): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_FLASK, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_FLASK, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TVOC): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_2_5): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_GRAIN, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_GRAIN, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_GRAIN, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_GRAIN, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 0, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/sps30/sensor.py b/esphome/components/sps30/sensor.py index 219f68c5c8..959b427861 100644 --- a/esphome/components/sps30/sensor.py +++ b/esphome/components/sps30/sensor.py @@ -13,7 +13,6 @@ from esphome.const import ( CONF_PMC_4_0, CONF_PMC_10_0, CONF_PM_SIZE, - DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, UNIT_COUNTS_PER_CUBIC_METER, @@ -33,74 +32,64 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(SPS30Component), cv.Optional(CONF_PM_1_0): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_2_5): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_4_0): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_0_5): sensor.sensor_schema( - UNIT_COUNTS_PER_CUBIC_METER, - ICON_COUNTER, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER, + icon=ICON_COUNTER, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_1_0): sensor.sensor_schema( - UNIT_COUNTS_PER_CUBIC_METER, - ICON_COUNTER, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER, + icon=ICON_COUNTER, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_2_5): sensor.sensor_schema( - UNIT_COUNTS_PER_CUBIC_METER, - ICON_COUNTER, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER, + icon=ICON_COUNTER, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_4_0): sensor.sensor_schema( - UNIT_COUNTS_PER_CUBIC_METER, - ICON_COUNTER, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER, + icon=ICON_COUNTER, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_10_0): sensor.sensor_schema( - UNIT_COUNTS_PER_CUBIC_METER, - ICON_COUNTER, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER, + icon=ICON_COUNTER, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_SIZE): sensor.sensor_schema( - UNIT_MICROMETER, - ICON_RULER, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROMETER, + icon=ICON_RULER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/sts3x/sensor.py b/esphome/components/sts3x/sensor.py index 9de077c20a..b02c835ef8 100644 --- a/esphome/components/sts3x/sensor.py +++ b/esphome/components/sts3x/sensor.py @@ -4,7 +4,6 @@ from esphome.components import i2c, sensor from esphome.const import ( CONF_ID, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) @@ -19,7 +18,10 @@ STS3XComponent = sts3x_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/sun/sensor/__init__.py b/esphome/components/sun/sensor/__init__.py index 644490ffc6..236acfadef 100644 --- a/esphome/components/sun/sensor/__init__.py +++ b/esphome/components/sun/sensor/__init__.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( - DEVICE_CLASS_EMPTY, STATE_CLASS_NONE, UNIT_DEGREES, ICON_WEATHER_SUNSET, @@ -22,7 +21,10 @@ TYPES = { CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_DEGREES, ICON_WEATHER_SUNSET, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + unit_of_measurement=UNIT_DEGREES, + icon=ICON_WEATHER_SUNSET, + accuracy_decimals=1, + state_class=STATE_CLASS_NONE, ) .extend( { diff --git a/esphome/components/t6615/sensor.py b/esphome/components/t6615/sensor.py index efe4994a97..71a099d635 100644 --- a/esphome/components/t6615/sensor.py +++ b/esphome/components/t6615/sensor.py @@ -5,7 +5,6 @@ from esphome.const import ( CONF_CO2, CONF_ID, DEVICE_CLASS_CARBON_DIOXIDE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, ) @@ -21,11 +20,10 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(T6615Component), cv.Required(CONF_CO2): sensor.sensor_schema( - UNIT_PARTS_PER_MILLION, - ICON_EMPTY, - 0, - DEVICE_CLASS_CARBON_DIOXIDE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PARTS_PER_MILLION, + accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/tcs34725/sensor.py b/esphome/components/tcs34725/sensor.py index d0fa0c1732..6c74c86faf 100644 --- a/esphome/components/tcs34725/sensor.py +++ b/esphome/components/tcs34725/sensor.py @@ -7,9 +7,7 @@ from esphome.const import ( CONF_ID, CONF_ILLUMINANCE, CONF_INTEGRATION_TIME, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_ILLUMINANCE, - ICON_EMPTY, ICON_LIGHTBULB, STATE_CLASS_MEASUREMENT, UNIT_PERCENT, @@ -49,13 +47,22 @@ TCS34725_GAINS = { } color_channel_schema = sensor.sensor_schema( - UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_PERCENT, + icon=ICON_LIGHTBULB, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ) color_temperature_schema = sensor.sensor_schema( - UNIT_KELVIN, ICON_THERMOMETER, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_KELVIN, + icon=ICON_THERMOMETER, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ) illuminance_schema = sensor.sensor_schema( - UNIT_LUX, ICON_EMPTY, 1, DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_LUX, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, ) CONFIG_SCHEMA = ( diff --git a/esphome/components/teleinfo/sensor/__init__.py b/esphome/components/teleinfo/sensor/__init__.py index f45052bae9..e7cc2fcb1b 100644 --- a/esphome/components/teleinfo/sensor/__init__.py +++ b/esphome/components/teleinfo/sensor/__init__.py @@ -9,7 +9,9 @@ CONF_TAG_NAME = "tag_name" TeleInfoSensor = teleinfo_ns.class_("TeleInfoSensor", sensor.Sensor, cg.Component) -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_WATT_HOURS, ICON_FLASH, 0).extend( +CONFIG_SCHEMA = sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, icon=ICON_FLASH, accuracy_decimals=0 +).extend( { cv.GenerateID(): cv.declare_id(TeleInfoSensor), cv.GenerateID(CONF_TELEINFO_ID): cv.use_id(TeleInfo), diff --git a/esphome/components/template/sensor/__init__.py b/esphome/components/template/sensor/__init__.py index 47027583bf..75fb505d91 100644 --- a/esphome/components/template/sensor/__init__.py +++ b/esphome/components/template/sensor/__init__.py @@ -6,10 +6,7 @@ from esphome.const import ( CONF_ID, CONF_LAMBDA, CONF_STATE, - DEVICE_CLASS_EMPTY, - ICON_EMPTY, STATE_CLASS_NONE, - UNIT_EMPTY, ) from .. import template_ns @@ -19,11 +16,8 @@ TemplateSensor = template_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_EMPTY, - ICON_EMPTY, - 1, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + accuracy_decimals=1, + state_class=STATE_CLASS_NONE, ) .extend( { diff --git a/esphome/components/tmp102/sensor.py b/esphome/components/tmp102/sensor.py index b54d5646ba..c5ffbb8df5 100644 --- a/esphome/components/tmp102/sensor.py +++ b/esphome/components/tmp102/sensor.py @@ -13,7 +13,6 @@ from esphome.components import i2c, sensor from esphome.const import ( CONF_ID, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) @@ -28,7 +27,10 @@ TMP102Component = tmp102_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/tmp117/sensor.py b/esphome/components/tmp117/sensor.py index c3fed06953..054864dd83 100644 --- a/esphome/components/tmp117/sensor.py +++ b/esphome/components/tmp117/sensor.py @@ -5,7 +5,6 @@ from esphome.const import ( CONF_ID, CONF_UPDATE_INTERVAL, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) @@ -20,7 +19,10 @@ TMP117Component = tmp117_ns.class_( CONFIG_SCHEMA = cv.All( sensor.sensor_schema( - UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/tof10120/sensor.py b/esphome/components/tof10120/sensor.py index 2110cbfcf8..2d3add2399 100644 --- a/esphome/components/tof10120/sensor.py +++ b/esphome/components/tof10120/sensor.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( CONF_ID, - DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_METER, ICON_ARROW_EXPAND_VERTICAL, @@ -19,11 +18,10 @@ TOF10120Sensor = tof10120_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_METER, - ICON_ARROW_EXPAND_VERTICAL, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_METER, + icon=ICON_ARROW_EXPAND_VERTICAL, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ) .extend({cv.GenerateID(): cv.declare_id(TOF10120Sensor)}) .extend(cv.polling_component_schema("60s")) diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index 7fdd176d42..df823d2997 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -5,10 +5,8 @@ from esphome.const import ( CONF_ID, CONF_TIME_ID, DEVICE_CLASS_ENERGY, - ICON_EMPTY, LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - UNIT_EMPTY, ) DEPENDENCIES = ["time"] @@ -21,12 +19,10 @@ TotalDailyEnergy = total_daily_energy_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_EMPTY, - ICON_EMPTY, - 0, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_AUTO, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + last_reset_type=LAST_RESET_TYPE_AUTO, ) .extend( { diff --git a/esphome/components/tsl2561/sensor.py b/esphome/components/tsl2561/sensor.py index c05079f668..cf3837cb4d 100644 --- a/esphome/components/tsl2561/sensor.py +++ b/esphome/components/tsl2561/sensor.py @@ -6,7 +6,6 @@ from esphome.const import ( CONF_ID, CONF_INTEGRATION_TIME, DEVICE_CLASS_ILLUMINANCE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_LUX, ) @@ -41,7 +40,10 @@ TSL2561Sensor = tsl2561_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_LUX, ICON_EMPTY, 1, DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_LUX, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/tx20/sensor.py b/esphome/components/tx20/sensor.py index 57c3165d16..ceb9b88d8d 100644 --- a/esphome/components/tx20/sensor.py +++ b/esphome/components/tx20/sensor.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_WIND_SPEED, CONF_PIN, CONF_WIND_DIRECTION_DEGREES, - DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, UNIT_KILOMETER_PER_HOUR, @@ -23,14 +22,16 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(Tx20Component), cv.Optional(CONF_WIND_SPEED): sensor.sensor_schema( - UNIT_KILOMETER_PER_HOUR, - ICON_WEATHER_WINDY, - 1, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOMETER_PER_HOUR, + icon=ICON_WEATHER_WINDY, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_WIND_DIRECTION_DEGREES): sensor.sensor_schema( - UNIT_DEGREES, ICON_SIGN_DIRECTION, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + unit_of_measurement=UNIT_DEGREES, + icon=ICON_SIGN_DIRECTION, + accuracy_decimals=1, + state_class=STATE_CLASS_NONE, ), cv.Required(CONF_PIN): cv.All( pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt diff --git a/esphome/components/ultrasonic/sensor.py b/esphome/components/ultrasonic/sensor.py index d5f2cef05f..f7026e884c 100644 --- a/esphome/components/ultrasonic/sensor.py +++ b/esphome/components/ultrasonic/sensor.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_ID, CONF_TRIGGER_PIN, CONF_TIMEOUT, - DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_METER, ICON_ARROW_EXPAND_VERTICAL, @@ -22,11 +21,10 @@ UltrasonicSensorComponent = ultrasonic_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_METER, - ICON_ARROW_EXPAND_VERTICAL, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_METER, + icon=ICON_ARROW_EXPAND_VERTICAL, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/uptime/sensor.py b/esphome/components/uptime/sensor.py index eaaee5a2d5..6ea3cca189 100644 --- a/esphome/components/uptime/sensor.py +++ b/esphome/components/uptime/sensor.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( CONF_ID, - DEVICE_CLASS_EMPTY, STATE_CLASS_NONE, UNIT_SECOND, ICON_TIMER, @@ -14,7 +13,10 @@ UptimeSensor = uptime_ns.class_("UptimeSensor", sensor.Sensor, cg.PollingCompone CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_SECOND, ICON_TIMER, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + unit_of_measurement=UNIT_SECOND, + icon=ICON_TIMER, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, ) .extend( { diff --git a/esphome/components/vl53l0x/sensor.py b/esphome/components/vl53l0x/sensor.py index 8a9667a1bd..0ce3197366 100644 --- a/esphome/components/vl53l0x/sensor.py +++ b/esphome/components/vl53l0x/sensor.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( CONF_ID, - DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_METER, ICON_ARROW_EXPAND_VERTICAL, @@ -42,11 +41,10 @@ def check_timeout(value): CONFIG_SCHEMA = cv.All( sensor.sensor_schema( - UNIT_METER, - ICON_ARROW_EXPAND_VERTICAL, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_METER, + icon=ICON_ARROW_EXPAND_VERTICAL, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/wifi_signal/sensor.py b/esphome/components/wifi_signal/sensor.py index f1807966a2..37bee75928 100644 --- a/esphome/components/wifi_signal/sensor.py +++ b/esphome/components/wifi_signal/sensor.py @@ -4,7 +4,6 @@ from esphome.components import sensor from esphome.const import ( CONF_ID, DEVICE_CLASS_SIGNAL_STRENGTH, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_DECIBEL_MILLIWATT, ) @@ -17,11 +16,10 @@ WiFiSignalSensor = wifi_signal_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - UNIT_DECIBEL_MILLIWATT, - ICON_EMPTY, - 0, - DEVICE_CLASS_SIGNAL_STRENGTH, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_DECIBEL_MILLIWATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_SIGNAL_STRENGTH, + state_class=STATE_CLASS_MEASUREMENT, ) .extend( { diff --git a/esphome/components/xiaomi_cgd1/sensor.py b/esphome/components/xiaomi_cgd1/sensor.py index e7f18a6be9..774c87fee9 100644 --- a/esphome/components/xiaomi_cgd1/sensor.py +++ b/esphome/components/xiaomi_cgd1/sensor.py @@ -10,7 +10,6 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -32,25 +31,22 @@ CONFIG_SCHEMA = ( cv.Required(CONF_BINDKEY): cv.bind_key, cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 1, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_BATTERY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/xiaomi_cgdk2/sensor.py b/esphome/components/xiaomi_cgdk2/sensor.py index 6b2c144911..d4e7230fd0 100644 --- a/esphome/components/xiaomi_cgdk2/sensor.py +++ b/esphome/components/xiaomi_cgdk2/sensor.py @@ -9,7 +9,6 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -32,25 +31,22 @@ CONFIG_SCHEMA = ( cv.Required(CONF_BINDKEY): cv.bind_key, cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 1, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_BATTERY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/xiaomi_cgg1/sensor.py b/esphome/components/xiaomi_cgg1/sensor.py index f26a7ae54e..4e606d95f8 100644 --- a/esphome/components/xiaomi_cgg1/sensor.py +++ b/esphome/components/xiaomi_cgg1/sensor.py @@ -11,7 +11,6 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -32,25 +31,22 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_BINDKEY): cv.bind_key, cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 1, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_BATTERY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/xiaomi_gcls002/sensor.py b/esphome/components/xiaomi_gcls002/sensor.py index a5c702aa9d..4154b64233 100644 --- a/esphome/components/xiaomi_gcls002/sensor.py +++ b/esphome/components/xiaomi_gcls002/sensor.py @@ -4,10 +4,8 @@ from esphome.components import sensor, esp32_ble_tracker from esphome.const import ( CONF_MAC_ADDRESS, CONF_TEMPERATURE, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, ICON_WATER_PERCENT, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, @@ -35,32 +33,28 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(XiaomiGCLS002), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_MOISTURE): sensor.sensor_schema( - UNIT_PERCENT, - ICON_WATER_PERCENT, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + icon=ICON_WATER_PERCENT, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( - UNIT_LUX, - ICON_EMPTY, - 0, - DEVICE_CLASS_ILLUMINANCE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_LUX, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CONDUCTIVITY): sensor.sensor_schema( - UNIT_MICROSIEMENS_PER_CENTIMETER, - ICON_FLOWER, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROSIEMENS_PER_CENTIMETER, + icon=ICON_FLOWER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/xiaomi_hhccjcy01/sensor.py b/esphome/components/xiaomi_hhccjcy01/sensor.py index 03289a6219..1818731a0f 100644 --- a/esphome/components/xiaomi_hhccjcy01/sensor.py +++ b/esphome/components/xiaomi_hhccjcy01/sensor.py @@ -4,10 +4,8 @@ from esphome.components import sensor, esp32_ble_tracker from esphome.const import ( CONF_MAC_ADDRESS, CONF_TEMPERATURE, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, ICON_WATER_PERCENT, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, @@ -37,39 +35,34 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(XiaomiHHCCJCY01), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_MOISTURE): sensor.sensor_schema( - UNIT_PERCENT, - ICON_WATER_PERCENT, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + icon=ICON_WATER_PERCENT, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( - UNIT_LUX, - ICON_EMPTY, - 0, - DEVICE_CLASS_ILLUMINANCE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_LUX, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CONDUCTIVITY): sensor.sensor_schema( - UNIT_MICROSIEMENS_PER_CENTIMETER, - ICON_FLOWER, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROSIEMENS_PER_CENTIMETER, + icon=ICON_FLOWER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_BATTERY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/xiaomi_hhccpot002/sensor.py b/esphome/components/xiaomi_hhccpot002/sensor.py index 8393de5e5a..82ee12d8d1 100644 --- a/esphome/components/xiaomi_hhccpot002/sensor.py +++ b/esphome/components/xiaomi_hhccpot002/sensor.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv from esphome.components import sensor, esp32_ble_tracker from esphome.const import ( CONF_MAC_ADDRESS, - DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_PERCENT, ICON_WATER_PERCENT, @@ -28,18 +27,16 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(XiaomiHHCCPOT002), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_MOISTURE): sensor.sensor_schema( - UNIT_PERCENT, - ICON_WATER_PERCENT, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + icon=ICON_WATER_PERCENT, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CONDUCTIVITY): sensor.sensor_schema( - UNIT_MICROSIEMENS_PER_CENTIMETER, - ICON_FLOWER, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MICROSIEMENS_PER_CENTIMETER, + icon=ICON_FLOWER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/xiaomi_jqjcy01ym/sensor.py b/esphome/components/xiaomi_jqjcy01ym/sensor.py index 70036eb5d9..40991c3d0f 100644 --- a/esphome/components/xiaomi_jqjcy01ym/sensor.py +++ b/esphome/components/xiaomi_jqjcy01ym/sensor.py @@ -7,10 +7,8 @@ from esphome.const import ( CONF_TEMPERATURE, CONF_ID, DEVICE_CLASS_BATTERY, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -34,32 +32,28 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(XiaomiJQJCY01YM), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_FORMALDEHYDE): sensor.sensor_schema( - UNIT_MILLIGRAMS_PER_CUBIC_METER, - ICON_FLASK_OUTLINE, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_MILLIGRAMS_PER_CUBIC_METER, + icon=ICON_FLASK_OUTLINE, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_BATTERY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/xiaomi_lywsd02/sensor.py b/esphome/components/xiaomi_lywsd02/sensor.py index ca55f28176..339c5e673a 100644 --- a/esphome/components/xiaomi_lywsd02/sensor.py +++ b/esphome/components/xiaomi_lywsd02/sensor.py @@ -9,7 +9,6 @@ from esphome.const import ( DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, - ICON_EMPTY, UNIT_PERCENT, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_BATTERY, @@ -30,25 +29,22 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(XiaomiLYWSD02), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 1, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_BATTERY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/xiaomi_lywsd03mmc/sensor.py b/esphome/components/xiaomi_lywsd03mmc/sensor.py index 05b3798955..f27cee3800 100644 --- a/esphome/components/xiaomi_lywsd03mmc/sensor.py +++ b/esphome/components/xiaomi_lywsd03mmc/sensor.py @@ -8,7 +8,6 @@ from esphome.const import ( CONF_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, - ICON_EMPTY, UNIT_PERCENT, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY, @@ -34,25 +33,22 @@ CONFIG_SCHEMA = ( cv.Required(CONF_BINDKEY): cv.bind_key, cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_BATTERY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/xiaomi_lywsdcgq/sensor.py b/esphome/components/xiaomi_lywsdcgq/sensor.py index 82bb4c83fb..39a207327e 100644 --- a/esphome/components/xiaomi_lywsdcgq/sensor.py +++ b/esphome/components/xiaomi_lywsdcgq/sensor.py @@ -8,7 +8,6 @@ from esphome.const import ( CONF_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, - ICON_EMPTY, UNIT_PERCENT, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY, @@ -30,25 +29,22 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(XiaomiLYWSDCGQ), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 1, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_BATTERY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/xiaomi_mhoc401/sensor.py b/esphome/components/xiaomi_mhoc401/sensor.py index 5180bdbb89..57b2190150 100644 --- a/esphome/components/xiaomi_mhoc401/sensor.py +++ b/esphome/components/xiaomi_mhoc401/sensor.py @@ -8,7 +8,6 @@ from esphome.const import ( CONF_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, - ICON_EMPTY, UNIT_PERCENT, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY, @@ -33,25 +32,22 @@ CONFIG_SCHEMA = ( cv.Required(CONF_BINDKEY): cv.bind_key, cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_HUMIDITY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_BATTERY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/xiaomi_miscale/sensor.py b/esphome/components/xiaomi_miscale/sensor.py index 9fe76c0645..3a112dfa34 100644 --- a/esphome/components/xiaomi_miscale/sensor.py +++ b/esphome/components/xiaomi_miscale/sensor.py @@ -8,7 +8,6 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_KILOGRAM, ICON_SCALE_BATHROOM, - DEVICE_CLASS_EMPTY, ) DEPENDENCIES = ["esp32_ble_tracker"] @@ -24,11 +23,10 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(XiaomiMiscale), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_WEIGHT): sensor.sensor_schema( - UNIT_KILOGRAM, - ICON_SCALE_BATHROOM, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOGRAM, + icon=ICON_SCALE_BATHROOM, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/xiaomi_miscale2/sensor.py b/esphome/components/xiaomi_miscale2/sensor.py index 9944098407..7cc5984c62 100644 --- a/esphome/components/xiaomi_miscale2/sensor.py +++ b/esphome/components/xiaomi_miscale2/sensor.py @@ -11,7 +11,6 @@ from esphome.const import ( UNIT_OHM, CONF_IMPEDANCE, ICON_OMEGA, - DEVICE_CLASS_EMPTY, ) DEPENDENCIES = ["esp32_ble_tracker"] @@ -27,14 +26,16 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(XiaomiMiscale2), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_WEIGHT): sensor.sensor_schema( - UNIT_KILOGRAM, - ICON_SCALE_BATHROOM, - 2, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOGRAM, + icon=ICON_SCALE_BATHROOM, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_IMPEDANCE): sensor.sensor_schema( - UNIT_OHM, ICON_OMEGA, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_OHM, + icon=ICON_OMEGA, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py index 90b971c08a..fd4bae60c1 100644 --- a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py +++ b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py @@ -9,9 +9,7 @@ from esphome.const import ( CONF_LIGHT, CONF_BATTERY_LEVEL, DEVICE_CLASS_BATTERY, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_ILLUMINANCE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, UNIT_PERCENT, @@ -43,21 +41,22 @@ CONFIG_SCHEMA = cv.All( CONF_DEVICE_CLASS, default="motion" ): binary_sensor.device_class, cv.Optional(CONF_IDLE_TIME): sensor.sensor_schema( - UNIT_MINUTE, ICON_TIMELAPSE, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + unit_of_measurement=UNIT_MINUTE, + icon=ICON_TIMELAPSE, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_BATTERY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( - UNIT_LUX, - ICON_EMPTY, - 0, - DEVICE_CLASS_ILLUMINANCE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_LUX, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_LIGHT): binary_sensor.BINARY_SENSOR_SCHEMA.extend( { diff --git a/esphome/components/xiaomi_wx08zm/binary_sensor.py b/esphome/components/xiaomi_wx08zm/binary_sensor.py index 90d4702da4..d2b353beff 100644 --- a/esphome/components/xiaomi_wx08zm/binary_sensor.py +++ b/esphome/components/xiaomi_wx08zm/binary_sensor.py @@ -6,8 +6,6 @@ from esphome.const import ( CONF_MAC_ADDRESS, CONF_TABLET, DEVICE_CLASS_BATTERY, - DEVICE_CLASS_EMPTY, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_PERCENT, ICON_BUG, @@ -32,14 +30,16 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(): cv.declare_id(XiaomiWX08ZM), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TABLET): sensor.sensor_schema( - UNIT_PERCENT, ICON_BUG, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_PERCENT, + icon=ICON_BUG, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, - ICON_EMPTY, - 0, - DEVICE_CLASS_BATTERY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/zyaura/sensor.py b/esphome/components/zyaura/sensor.py index 5f9a5e3add..f2273afa9e 100644 --- a/esphome/components/zyaura/sensor.py +++ b/esphome/components/zyaura/sensor.py @@ -9,10 +9,8 @@ from esphome.const import ( CONF_CO2, CONF_TEMPERATURE, CONF_HUMIDITY, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, UNIT_CELSIUS, @@ -34,21 +32,22 @@ CONFIG_SCHEMA = cv.Schema( pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt ), cv.Optional(CONF_CO2): sensor.sensor_schema( - UNIT_PARTS_PER_MILLION, - ICON_MOLECULE_CO2, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PARTS_PER_MILLION, + icon=ICON_MOLECULE_CO2, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 1, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), } ).extend(cv.polling_component_schema("60s")) From f751c3828ed614cdc140d93c1f6b3995ba95275f Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 1 Aug 2021 12:33:10 +0200 Subject: [PATCH 1114/1841] Fix MQTT light include (#2104) Fixes https://github.com/esphome/issues/issues/2285 --- esphome/components/mqtt/mqtt_light.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index bf483bf947..be662867cf 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -1,9 +1,9 @@ #include "mqtt_light.h" -#include "esphome/components/light/light_json_schema.h" #include "esphome/core/log.h" #ifdef USE_LIGHT +#include "esphome/components/light/light_json_schema.h" namespace esphome { namespace mqtt { From 69c7cf783e775a6e63fd3be21ed6f6349e6a3925 Mon Sep 17 00:00:00 2001 From: "John K. Luebs" Date: Sun, 1 Aug 2021 14:13:46 -0500 Subject: [PATCH 1115/1841] Fix missing include in light_traits.h (#2105) --- esphome/components/light/light_traits.h | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/light/light_traits.h b/esphome/components/light/light_traits.h index cbade64d77..50da299134 100644 --- a/esphome/components/light/light_traits.h +++ b/esphome/components/light/light_traits.h @@ -1,5 +1,6 @@ #pragma once +#include "esphome/core/helpers.h" #include "color_mode.h" #include From 76991cdcc4d682b4d2b0c75d0f522d8ef18fed0b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 2 Aug 2021 20:00:51 +1200 Subject: [PATCH 1116/1841] Add select entities and implement template select (#2067) Co-authored-by: Otto Winter --- CODEOWNERS | 1 + esphome/components/api/api.proto | 37 ++++ esphome/components/api/api_connection.cpp | 35 ++++ esphome/components/api/api_connection.h | 5 + esphome/components/api/api_pb2.cpp | 166 ++++++++++++++++++ esphome/components/api/api_pb2.h | 39 ++++ esphome/components/api/api_pb2_service.cpp | 36 ++++ esphome/components/api/api_pb2_service.h | 15 ++ esphome/components/api/api_server.cpp | 9 + esphome/components/api/api_server.h | 3 + esphome/components/api/list_entities.cpp | 4 + esphome/components/api/list_entities.h | 3 + esphome/components/api/subscribe_state.cpp | 5 + esphome/components/api/subscribe_state.h | 3 + esphome/components/api/util.cpp | 15 ++ esphome/components/api/util.h | 6 + esphome/components/mqtt/__init__.py | 1 + esphome/components/mqtt/mqtt_select.cpp | 58 ++++++ esphome/components/mqtt/mqtt_select.h | 46 +++++ esphome/components/select/__init__.py | 102 +++++++++++ esphome/components/select/automation.h | 33 ++++ esphome/components/select/select.cpp | 43 +++++ esphome/components/select/select.h | 87 +++++++++ .../components/template/select/__init__.py | 74 ++++++++ .../template/select/template_select.cpp | 74 ++++++++ .../template/select/template_select.h | 37 ++++ esphome/components/web_server/web_server.cpp | 48 +++++ esphome/components/web_server/web_server.h | 9 + esphome/const.py | 3 + esphome/core/application.h | 19 ++ esphome/core/controller.cpp | 6 + esphome/core/controller.h | 6 + esphome/core/defines.h | 1 + script/ci-custom.py | 1 + tests/test5.yaml | 23 +++ 35 files changed, 1053 insertions(+) create mode 100644 esphome/components/mqtt/mqtt_select.cpp create mode 100644 esphome/components/mqtt/mqtt_select.h create mode 100644 esphome/components/select/__init__.py create mode 100644 esphome/components/select/automation.h create mode 100644 esphome/components/select/select.cpp create mode 100644 esphome/components/select/select.h create mode 100644 esphome/components/template/select/__init__.py create mode 100644 esphome/components/template/select/template_select.cpp create mode 100644 esphome/components/template/select/template_select.h diff --git a/CODEOWNERS b/CODEOWNERS index 8806489884..b235643539 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -100,6 +100,7 @@ esphome/components/script/* @esphome/core esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdp3x/* @Azimath esphome/components/selec_meter/* @sourabhjaiswal +esphome/components/select/* @esphome/core esphome/components/sensor/* @esphome/core esphome/components/sgp40/* @SenexCrenshaw esphome/components/sht4x/* @sjtrny diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index c04e0a32f1..c0bbbaaeab 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -39,6 +39,7 @@ service APIConnection { rpc camera_image (CameraImageRequest) returns (void) {} rpc climate_command (ClimateCommandRequest) returns (void) {} rpc number_command (NumberCommandRequest) returns (void) {} + rpc select_command (SelectCommandRequest) returns (void) {} } @@ -867,3 +868,39 @@ message NumberCommandRequest { fixed32 key = 1; float state = 2; } + +// ==================== SELECT ==================== +message ListEntitiesSelectResponse { + option (id) = 52; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_SELECT"; + + string object_id = 1; + fixed32 key = 2; + string name = 3; + string unique_id = 4; + + string icon = 5; + repeated string options = 6; +} +message SelectStateResponse { + option (id) = 53; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_SELECT"; + option (no_delay) = true; + + fixed32 key = 1; + string state = 2; + // If the select does not have a valid state yet. + // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller + bool missing_state = 3; +} +message SelectCommandRequest { + option (id) = 54; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_SELECT"; + option (no_delay) = true; + + fixed32 key = 1; + string state = 2; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index b92c67b042..94522a13be 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -609,6 +609,41 @@ void APIConnection::number_command(const NumberCommandRequest &msg) { } #endif +#ifdef USE_SELECT +bool APIConnection::send_select_state(select::Select *select, std::string state) { + if (!this->state_subscription_) + return false; + + SelectStateResponse resp{}; + resp.key = select->get_object_id_hash(); + resp.state = std::move(state); + resp.missing_state = !select->has_state(); + return this->send_select_state_response(resp); +} +bool APIConnection::send_select_info(select::Select *select) { + ListEntitiesSelectResponse msg; + msg.key = select->get_object_id_hash(); + msg.object_id = select->get_object_id(); + msg.name = select->get_name(); + msg.unique_id = get_default_unique_id("select", select); + msg.icon = select->traits.get_icon(); + + for (const auto &option : select->traits.get_options()) + msg.options.push_back(option); + + return this->send_list_entities_select_response(msg); +} +void APIConnection::select_command(const SelectCommandRequest &msg) { + select::Select *select = App.get_select_by_key(msg.key); + if (select == nullptr) + return; + + auto call = select->make_call(); + call.set_option(msg.state); + call.perform(); +} +#endif + #ifdef USE_ESP32_CAMERA void APIConnection::send_camera_state(std::shared_ptr image) { if (!this->state_subscription_) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 1d7fc48563..bc9839a423 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -67,6 +67,11 @@ class APIConnection : public APIServerConnection { bool send_number_state(number::Number *number, float state); bool send_number_info(number::Number *number); void number_command(const NumberCommandRequest &msg) override; +#endif +#ifdef USE_SELECT + bool send_select_state(select::Select *select, std::string state); + bool send_select_info(select::Select *select); + void select_command(const SelectCommandRequest &msg) override; #endif bool send_log_message(int level, const char *tag, const char *line); void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 79092cb511..210ba49dfc 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -3574,6 +3574,172 @@ void NumberCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +bool ListEntitiesSelectResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: { + this->object_id = value.as_string(); + return true; + } + case 3: { + this->name = value.as_string(); + return true; + } + case 4: { + this->unique_id = value.as_string(); + return true; + } + case 5: { + this->icon = value.as_string(); + return true; + } + case 6: { + this->options.push_back(value.as_string()); + return true; + } + default: + return false; + } +} +bool ListEntitiesSelectResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 2: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->object_id); + buffer.encode_fixed32(2, this->key); + buffer.encode_string(3, this->name); + buffer.encode_string(4, this->unique_id); + buffer.encode_string(5, this->icon); + for (auto &it : this->options) { + buffer.encode_string(6, it, true); + } +} +void ListEntitiesSelectResponse::dump_to(std::string &out) const { + char buffer[64]; + out.append("ListEntitiesSelectResponse {\n"); + out.append(" object_id: "); + out.append("'").append(this->object_id).append("'"); + out.append("\n"); + + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" name: "); + out.append("'").append(this->name).append("'"); + out.append("\n"); + + out.append(" unique_id: "); + out.append("'").append(this->unique_id).append("'"); + out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); + + for (const auto &it : this->options) { + out.append(" options: "); + out.append("'").append(it).append("'"); + out.append("\n"); + } + out.append("}"); +} +bool SelectStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 3: { + this->missing_state = value.as_bool(); + return true; + } + default: + return false; + } +} +bool SelectStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 2: { + this->state = value.as_string(); + return true; + } + default: + return false; + } +} +bool SelectStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void SelectStateResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_fixed32(1, this->key); + buffer.encode_string(2, this->state); + buffer.encode_bool(3, this->missing_state); +} +void SelectStateResponse::dump_to(std::string &out) const { + char buffer[64]; + out.append("SelectStateResponse {\n"); + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" state: "); + out.append("'").append(this->state).append("'"); + out.append("\n"); + + out.append(" missing_state: "); + out.append(YESNO(this->missing_state)); + out.append("\n"); + out.append("}"); +} +bool SelectCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 2: { + this->state = value.as_string(); + return true; + } + default: + return false; + } +} +bool SelectCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void SelectCommandRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_fixed32(1, this->key); + buffer.encode_string(2, this->state); +} +void SelectCommandRequest::dump_to(std::string &out) const { + char buffer[64]; + out.append("SelectCommandRequest {\n"); + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" state: "); + out.append("'").append(this->state).append("'"); + out.append("\n"); + out.append("}"); +} } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 4c92b7b00d..47b0229dd5 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -849,6 +849,45 @@ class NumberCommandRequest : public ProtoMessage { protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; }; +class ListEntitiesSelectResponse : public ProtoMessage { + public: + std::string object_id{}; + uint32_t key{0}; + std::string name{}; + std::string unique_id{}; + std::string icon{}; + std::vector options{}; + void encode(ProtoWriteBuffer buffer) const override; + void dump_to(std::string &out) const override; + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; +}; +class SelectStateResponse : public ProtoMessage { + public: + uint32_t key{0}; + std::string state{}; + bool missing_state{false}; + void encode(ProtoWriteBuffer buffer) const override; + void dump_to(std::string &out) const override; + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class SelectCommandRequest : public ProtoMessage { + public: + uint32_t key{0}; + std::string state{}; + void encode(ProtoWriteBuffer buffer) const override; + void dump_to(std::string &out) const override; + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; +}; } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 440a5d0ab3..682317801a 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -198,6 +198,20 @@ bool APIServerConnectionBase::send_number_state_response(const NumberStateRespon #endif #ifdef USE_NUMBER #endif +#ifdef USE_SELECT +bool APIServerConnectionBase::send_list_entities_select_response(const ListEntitiesSelectResponse &msg) { + ESP_LOGVV(TAG, "send_list_entities_select_response: %s", msg.dump().c_str()); + return this->send_message_(msg, 52); +} +#endif +#ifdef USE_SELECT +bool APIServerConnectionBase::send_select_state_response(const SelectStateResponse &msg) { + ESP_LOGVV(TAG, "send_select_state_response: %s", msg.dump().c_str()); + return this->send_message_(msg, 53); +} +#endif +#ifdef USE_SELECT +#endif bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { switch (msg_type) { case 1: { @@ -372,6 +386,15 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, msg.decode(msg_data, msg_size); ESP_LOGVV(TAG, "on_number_command_request: %s", msg.dump().c_str()); this->on_number_command_request(msg); +#endif + break; + } + case 54: { +#ifdef USE_SELECT + SelectCommandRequest msg; + msg.decode(msg_data, msg_size); + ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str()); + this->on_select_command_request(msg); #endif break; } @@ -583,6 +606,19 @@ void APIServerConnection::on_number_command_request(const NumberCommandRequest & this->number_command(msg); } #endif +#ifdef USE_SELECT +void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->select_command(msg); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 398c10a811..1b8d990b05 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -120,6 +120,15 @@ class APIServerConnectionBase : public ProtoService { #endif #ifdef USE_NUMBER virtual void on_number_command_request(const NumberCommandRequest &value){}; +#endif +#ifdef USE_SELECT + bool send_list_entities_select_response(const ListEntitiesSelectResponse &msg); +#endif +#ifdef USE_SELECT + bool send_select_state_response(const SelectStateResponse &msg); +#endif +#ifdef USE_SELECT + virtual void on_select_command_request(const SelectCommandRequest &value){}; #endif protected: bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; @@ -159,6 +168,9 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_NUMBER virtual void number_command(const NumberCommandRequest &msg) = 0; +#endif +#ifdef USE_SELECT + virtual void select_command(const SelectCommandRequest &msg) = 0; #endif protected: void on_hello_request(const HelloRequest &msg) override; @@ -194,6 +206,9 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_NUMBER void on_number_command_request(const NumberCommandRequest &msg) override; #endif +#ifdef USE_SELECT + void on_select_command_request(const SelectCommandRequest &msg) override; +#endif }; } // namespace api diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 7434030565..d48c0a4fd8 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -206,6 +206,15 @@ void APIServer::on_number_update(number::Number *obj, float state) { } #endif +#ifdef USE_SELECT +void APIServer::on_select_update(select::Select *obj, const std::string &state) { + if (obj->is_internal()) + return; + for (auto *c : this->clients_) + c->send_select_state(obj, state); +} +#endif + float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; } void APIServer::set_port(uint16_t port) { this->port_ = port; } APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 68d1df2c1f..96b3192e9e 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -62,6 +62,9 @@ class APIServer : public Component, public Controller { #endif #ifdef USE_NUMBER void on_number_update(number::Number *obj, float state) override; +#endif +#ifdef USE_SELECT + void on_select_update(select::Select *obj, const std::string &state) override; #endif void send_homeassistant_service_call(const HomeassistantServiceResponse &call); void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index 8897758073..745dd92c89 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -55,5 +55,9 @@ bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this-> bool ListEntitiesIterator::on_number(number::Number *number) { return this->client_->send_number_info(number); } #endif +#ifdef USE_SELECT +bool ListEntitiesIterator::on_select(select::Select *select) { return this->client_->send_select_info(select); } +#endif + } // namespace api } // namespace esphome diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index c55ba5089e..c728fb0a97 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -42,6 +42,9 @@ class ListEntitiesIterator : public ComponentIterator { #endif #ifdef USE_NUMBER bool on_number(number::Number *number) override; +#endif +#ifdef USE_SELECT + bool on_select(select::Select *select) override; #endif bool on_end() override; diff --git a/esphome/components/api/subscribe_state.cpp b/esphome/components/api/subscribe_state.cpp index 25aa7c8b31..1b8453f233 100644 --- a/esphome/components/api/subscribe_state.cpp +++ b/esphome/components/api/subscribe_state.cpp @@ -42,6 +42,11 @@ bool InitialStateIterator::on_number(number::Number *number) { return this->client_->send_number_state(number, number->state); } #endif +#ifdef USE_SELECT +bool InitialStateIterator::on_select(select::Select *select) { + return this->client_->send_select_state(select, select->state); +} +#endif InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client) : ComponentIterator(server), client_(client) {} diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index f03322ac4a..beb9b947d4 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -39,6 +39,9 @@ class InitialStateIterator : public ComponentIterator { #endif #ifdef USE_NUMBER bool on_number(number::Number *number) override; +#endif +#ifdef USE_SELECT + bool on_select(select::Select *select) override; #endif protected: APIConnection *client_; diff --git a/esphome/components/api/util.cpp b/esphome/components/api/util.cpp index 6e05d49b74..5085994607 100644 --- a/esphome/components/api/util.cpp +++ b/esphome/components/api/util.cpp @@ -182,6 +182,21 @@ void ComponentIterator::advance() { } } break; +#endif +#ifdef USE_SELECT + case IteratorState::SELECT: + if (this->at_ >= App.get_selects().size()) { + advance_platform = true; + } else { + auto *select = App.get_selects()[this->at_]; + if (select->is_internal()) { + success = true; + break; + } else { + success = this->on_select(select); + } + } + break; #endif case IteratorState::MAX: if (this->on_end()) { diff --git a/esphome/components/api/util.h b/esphome/components/api/util.h index f8b248056b..e404a95619 100644 --- a/esphome/components/api/util.h +++ b/esphome/components/api/util.h @@ -50,6 +50,9 @@ class ComponentIterator { #endif #ifdef USE_NUMBER virtual bool on_number(number::Number *number) = 0; +#endif +#ifdef USE_SELECT + virtual bool on_select(select::Select *select) = 0; #endif virtual bool on_end(); @@ -87,6 +90,9 @@ class ComponentIterator { #endif #ifdef USE_NUMBER NUMBER, +#endif +#ifdef USE_SELECT + SELECT, #endif MAX, } state_{IteratorState::NONE}; diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 3559fce046..56ea9027af 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -92,6 +92,7 @@ MQTTSensorComponent = mqtt_ns.class_("MQTTSensorComponent", MQTTComponent) MQTTSwitchComponent = mqtt_ns.class_("MQTTSwitchComponent", MQTTComponent) MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent) MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent) +MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent) def validate_config(value): diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp new file mode 100644 index 0000000000..c0ac472d46 --- /dev/null +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -0,0 +1,58 @@ +#include "mqtt_select.h" +#include "esphome/core/log.h" + +#ifdef USE_SELECT + +namespace esphome { +namespace mqtt { + +static const char *const TAG = "mqtt.select"; + +using namespace esphome::select; + +MQTTSelectComponent::MQTTSelectComponent(Select *select) : MQTTComponent(), select_(select) {} + +void MQTTSelectComponent::setup() { + this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) { + auto call = this->select_->make_call(); + call.set_option(state); + call.perform(); + }); + this->select_->add_on_state_callback([this](const std::string &state) { this->publish_state(state); }); +} + +void MQTTSelectComponent::dump_config() { + ESP_LOGCONFIG(TAG, "MQTT Select '%s':", this->select_->get_name().c_str()); + LOG_MQTT_COMPONENT(true, false) +} + +std::string MQTTSelectComponent::component_type() const { return "select"; } + +std::string MQTTSelectComponent::friendly_name() const { return this->select_->get_name(); } +void MQTTSelectComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { + const auto &traits = select_->traits; + // https://www.home-assistant.io/integrations/select.mqtt/ + if (!traits.get_icon().empty()) + root["icon"] = traits.get_icon(); + JsonArray &options = root.createNestedArray("options"); + for (const auto &option : traits.get_options()) + options.add(option); + + config.command_topic = true; +} +bool MQTTSelectComponent::send_initial_state() { + if (this->select_->has_state()) { + return this->publish_state(this->select_->state); + } else { + return true; + } +} +bool MQTTSelectComponent::is_internal() { return this->select_->is_internal(); } +bool MQTTSelectComponent::publish_state(const std::string &value) { + return this->publish(this->get_state_topic_(), value); +} + +} // namespace mqtt +} // namespace esphome + +#endif diff --git a/esphome/components/mqtt/mqtt_select.h b/esphome/components/mqtt/mqtt_select.h new file mode 100644 index 0000000000..013e905ead --- /dev/null +++ b/esphome/components/mqtt/mqtt_select.h @@ -0,0 +1,46 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_SELECT + +#include "esphome/components/select/select.h" +#include "mqtt_component.h" + +namespace esphome { +namespace mqtt { + +class MQTTSelectComponent : public mqtt::MQTTComponent { + public: + /** Construct this MQTTSelectComponent instance with the provided friendly_name and select + * + * @param select The select. + */ + explicit MQTTSelectComponent(select::Select *select); + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + /// Override setup. + void setup() override; + void dump_config() override; + + void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; + + bool send_initial_state() override; + bool is_internal() override; + + bool publish_state(const std::string &value); + + protected: + /// Override for MQTTComponent, returns "select". + std::string component_type() const override; + + std::string friendly_name() const override; + + select::Select *select_; +}; + +} // namespace mqtt +} // namespace esphome + +#endif diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py new file mode 100644 index 0000000000..5be09b0832 --- /dev/null +++ b/esphome/components/select/__init__.py @@ -0,0 +1,102 @@ +from typing import List +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.components import mqtt +from esphome.const import ( + CONF_ICON, + CONF_ID, + CONF_INTERNAL, + CONF_ON_VALUE, + CONF_OPTION, + CONF_TRIGGER_ID, + CONF_NAME, + CONF_MQTT_ID, + ICON_EMPTY, +) +from esphome.core import CORE, coroutine_with_priority + +CODEOWNERS = ["@esphome/core"] +IS_PLATFORM_COMPONENT = True + +select_ns = cg.esphome_ns.namespace("select") +Select = select_ns.class_("Select", cg.Nameable) +SelectPtr = Select.operator("ptr") + +# Triggers +SelectStateTrigger = select_ns.class_( + "SelectStateTrigger", automation.Trigger.template(cg.float_) +) + +# Actions +SelectSetAction = select_ns.class_("SelectSetAction", automation.Action) + +icon = cv.icon + + +SELECT_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent), + cv.GenerateID(): cv.declare_id(Select), + cv.Optional(CONF_ICON, default=ICON_EMPTY): icon, + cv.Optional(CONF_ON_VALUE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SelectStateTrigger), + } + ), + } +) + + +async def setup_select_core_(var, config, *, options: List[str]): + cg.add(var.set_name(config[CONF_NAME])) + if CONF_INTERNAL in config: + cg.add(var.set_internal(config[CONF_INTERNAL])) + + cg.add(var.traits.set_icon(config[CONF_ICON])) + cg.add(var.traits.set_options(options)) + + for conf in config.get(CONF_ON_VALUE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.std_string, "x")], conf) + + if CONF_MQTT_ID in config: + mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) + await mqtt.register_mqtt_component(mqtt_, config) + + +async def register_select(var, config, *, options: List[str]): + if not CORE.has_id(config[CONF_ID]): + var = cg.Pvariable(config[CONF_ID], var) + cg.add(cg.App.register_select(var)) + await setup_select_core_(var, config, options=options) + + +async def new_select(config, *, options: List[str]): + var = cg.new_Pvariable(config[CONF_ID]) + await register_select(var, config, options=options) + return var + + +@coroutine_with_priority(40.0) +async def to_code(config): + cg.add_define("USE_SELECT") + cg.add_global(select_ns.using) + + +@automation.register_action( + "select.set", + SelectSetAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(Select), + cv.Required(CONF_OPTION): cv.templatable(cv.string_strict), + } + ), +) +async def select_set_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_OPTION], args, str) + cg.add(var.set_option(template_)) + return var diff --git a/esphome/components/select/automation.h b/esphome/components/select/automation.h new file mode 100644 index 0000000000..59525f879e --- /dev/null +++ b/esphome/components/select/automation.h @@ -0,0 +1,33 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "select.h" + +namespace esphome { +namespace select { + +class SelectStateTrigger : public Trigger { + public: + explicit SelectStateTrigger(Select *parent) { + parent->add_on_state_callback([this](std::string value) { this->trigger(std::move(value)); }); + } +}; + +template class SelectSetAction : public Action { + public: + SelectSetAction(Select *select) : select_(select) {} + TEMPLATABLE_VALUE(std::string, option) + + void play(Ts... x) override { + auto call = this->select_->make_call(); + call.set_option(this->option_.value(x...)); + call.perform(); + } + + protected: + Select *select_; +}; + +} // namespace select +} // namespace esphome diff --git a/esphome/components/select/select.cpp b/esphome/components/select/select.cpp new file mode 100644 index 0000000000..14f4d9277d --- /dev/null +++ b/esphome/components/select/select.cpp @@ -0,0 +1,43 @@ +#include "select.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace select { + +static const char *const TAG = "select"; + +void SelectCall::perform() { + ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); + if (!this->option_.has_value()) { + ESP_LOGW(TAG, "No value set for SelectCall"); + return; + } + + const auto &traits = this->parent_->traits; + auto value = *this->option_; + auto options = traits.get_options(); + + if (std::find(options.begin(), options.end(), value) == options.end()) { + ESP_LOGW(TAG, " Option %s is not a valid option.", value.c_str()); + return; + } + + ESP_LOGD(TAG, " Option: %s", (*this->option_).c_str()); + this->parent_->control(*this->option_); +} + +void Select::publish_state(const std::string &state) { + this->has_state_ = true; + this->state = state; + ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), state.c_str()); + this->state_callback_.call(state); +} + +void Select::add_on_state_callback(std::function &&callback) { + this->state_callback_.add(std::move(callback)); +} + +uint32_t Select::hash_base() { return 2812997003UL; } + +} // namespace select +} // namespace esphome diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h new file mode 100644 index 0000000000..414a8daabb --- /dev/null +++ b/esphome/components/select/select.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace select { + +#define LOG_SELECT(prefix, type, obj) \ + if ((obj) != nullptr) { \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ + if (!(obj)->traits.get_icon().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->traits.get_icon().c_str()); \ + } \ + } + +class Select; + +class SelectCall { + public: + explicit SelectCall(Select *parent) : parent_(parent) {} + void perform(); + + SelectCall &set_option(const std::string &option) { + option_ = option; + return *this; + } + const optional &get_option() const { return option_; } + + protected: + Select *const parent_; + optional option_; +}; + +class SelectTraits { + public: + void set_options(std::vector options) { this->options_ = std::move(options); } + const std::vector get_options() const { return this->options_; } + void set_icon(std::string icon) { icon_ = std::move(icon); } + const std::string &get_icon() const { return icon_; } + + protected: + std::vector options_; + std::string icon_; +}; + +/** Base-class for all selects. + * + * A select can use publish_state to send out a new value. + */ +class Select : public Nameable { + public: + std::string state; + + void publish_state(const std::string &state); + + SelectCall make_call() { return SelectCall(this); } + void set(const std::string &value) { make_call().set_option(value).perform(); } + + void add_on_state_callback(std::function &&callback); + + SelectTraits traits; + + /// Return whether this select has gotten a full state yet. + bool has_state() const { return has_state_; } + + protected: + friend class SelectCall; + + /** Set the value of the select, this is a virtual method that each select integration must implement. + * + * This method is called by the SelectCall. + * + * @param value The value as validated by the SelectCall. + */ + virtual void control(const std::string &value) = 0; + + uint32_t hash_base() override; + + CallbackManager state_callback_; + bool has_state_{false}; +}; + +} // namespace select +} // namespace esphome diff --git a/esphome/components/template/select/__init__.py b/esphome/components/template/select/__init__.py new file mode 100644 index 0000000000..4044a407f3 --- /dev/null +++ b/esphome/components/template/select/__init__.py @@ -0,0 +1,74 @@ +from esphome import automation +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import select +from esphome.const import ( + CONF_ID, + CONF_INITIAL_OPTION, + CONF_LAMBDA, + CONF_OPTIONS, + CONF_OPTIMISTIC, + CONF_RESTORE_VALUE, +) +from .. import template_ns + +TemplateSelect = template_ns.class_( + "TemplateSelect", select.Select, cg.PollingComponent +) + +CONF_SET_ACTION = "set_action" + + +def validate_initial_value_in_options(config): + if CONF_INITIAL_OPTION in config: + if config[CONF_INITIAL_OPTION] not in config[CONF_OPTIONS]: + raise cv.Invalid( + f"initial_option '{config[CONF_INITIAL_OPTION]}' is not a valid option [{', '.join(config[CONF_OPTIONS])}]" + ) + else: + config[CONF_INITIAL_OPTION] = config[CONF_OPTIONS][0] + return config + + +CONFIG_SCHEMA = cv.All( + select.SELECT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TemplateSelect), + cv.Required(CONF_OPTIONS): cv.All( + cv.ensure_list(cv.string_strict), cv.Length(min=1) + ), + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_OPTIMISTIC): cv.boolean, + cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_INITIAL_OPTION): cv.string_strict, + cv.Optional(CONF_RESTORE_VALUE): cv.boolean, + } + ).extend(cv.polling_component_schema("60s")), + validate_initial_value_in_options, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await select.register_select(var, config, options=config[CONF_OPTIONS]) + + if CONF_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_LAMBDA], [], return_type=cg.optional.template(str) + ) + cg.add(var.set_template(template_)) + + else: + if CONF_OPTIMISTIC in config: + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + + cg.add(var.set_initial_option(config[CONF_INITIAL_OPTION])) + + if CONF_RESTORE_VALUE in config: + cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE])) + + if CONF_SET_ACTION in config: + await automation.build_automation( + var.get_set_trigger(), [(cg.std_string, "x")], config[CONF_SET_ACTION] + ) diff --git a/esphome/components/template/select/template_select.cpp b/esphome/components/template/select/template_select.cpp new file mode 100644 index 0000000000..782c0ee6f9 --- /dev/null +++ b/esphome/components/template/select/template_select.cpp @@ -0,0 +1,74 @@ +#include "template_select.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace template_ { + +static const char *const TAG = "template.select"; + +void TemplateSelect::setup() { + if (this->f_.has_value()) + return; + + std::string value; + ESP_LOGD(TAG, "Setting up Template Number"); + if (!this->restore_value_) { + value = this->initial_option_; + ESP_LOGD(TAG, "State from initial: %s", value.c_str()); + } else { + size_t index; + this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); + if (!this->pref_.load(&index)) { + value = this->initial_option_; + ESP_LOGD(TAG, "State from initial (could not load): %s", value.c_str()); + } else { + value = this->traits.get_options().at(index); + ESP_LOGD(TAG, "State from restore: %s", value.c_str()); + } + } + + this->publish_state(value); +} + +void TemplateSelect::update() { + if (!this->f_.has_value()) + return; + + auto val = (*this->f_)(); + if (!val.has_value()) + return; + + auto options = this->traits.get_options(); + if (std::find(options.begin(), options.end(), *val) == options.end()) { + ESP_LOGE(TAG, "lambda returned an invalid option %s", (*val).c_str()); + return; + } + + this->publish_state(*val); +} + +void TemplateSelect::control(const std::string &value) { + this->set_trigger_->trigger(value); + + if (this->optimistic_) + this->publish_state(value); + + if (this->restore_value_) { + auto options = this->traits.get_options(); + size_t index = std::find(options.begin(), options.end(), value) - options.begin(); + + this->pref_.save(&index); + } +} +void TemplateSelect::dump_config() { + LOG_SELECT("", "Template Select", this); + LOG_UPDATE_INTERVAL(this); + if (this->f_.has_value()) + return; + ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); + ESP_LOGCONFIG(TAG, " Initial Option: %s", this->initial_option_.c_str()); + ESP_LOGCONFIG(TAG, " Restore Value: %s", YESNO(this->restore_value_)); +} + +} // namespace template_ +} // namespace esphome diff --git a/esphome/components/template/select/template_select.h b/esphome/components/template/select/template_select.h new file mode 100644 index 0000000000..e24eb6e880 --- /dev/null +++ b/esphome/components/template/select/template_select.h @@ -0,0 +1,37 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/preferences.h" + +namespace esphome { +namespace template_ { + +class TemplateSelect : public select::Select, public PollingComponent { + public: + void set_template(std::function()> &&f) { this->f_ = f; } + + void setup() override; + void update() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + Trigger *get_set_trigger() const { return this->set_trigger_; } + void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } + void set_initial_option(std::string initial_option) { this->initial_option_ = std::move(initial_option); } + void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } + + protected: + void control(const std::string &value) override; + bool optimistic_ = false; + std::string initial_option_; + bool restore_value_ = false; + Trigger *set_trigger_ = new Trigger(); + optional()>> f_; + + ESPPreferenceObject pref_; +}; + +} // namespace template_ +} // namespace esphome diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 5e45a87e26..9dad61bb5b 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -129,6 +129,12 @@ void WebServer::setup() { if (!obj->is_internal()) client->send(this->number_json(obj, obj->state).c_str(), "state"); #endif + +#ifdef USE_SELECT + for (auto *obj : App.get_selects()) + if (!obj->is_internal()) + client->send(this->select_json(obj, obj->state).c_str(), "state"); +#endif }); #ifdef USE_LOGGER @@ -211,6 +217,11 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { write_row(stream, obj, "number", ""); #endif +#ifdef USE_SELECT + for (auto *obj : App.get_selects()) + write_row(stream, obj, "select", ""); +#endif + stream->print(F("

    See ESPHome Web API for " "REST API documentation.

    " "

    OTA Update

    events_.send(this->select_json(obj, state).c_str(), "state"); +} +void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) { + for (auto *obj : App.get_selects()) { + if (obj->is_internal()) + continue; + if (obj->get_object_id() != match.id) + continue; + std::string data = this->select_json(obj, obj->state); + request->send(200, "text/json", data.c_str()); + return; + } + request->send(404); +} +std::string WebServer::select_json(select::Select *obj, const std::string &value) { + return json::build_json([obj, value](JsonObject &root) { + root["id"] = "select-" + obj->get_object_id(); + root["state"] = value; + root["value"] = value; + }); +} +#endif + bool WebServer::canHandle(AsyncWebServerRequest *request) { if (request->url() == "/") return true; @@ -683,6 +719,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return true; #endif +#ifdef USE_SELECT + if (request->method() == HTTP_GET && match.domain == "select") + return true; +#endif + return false; } void WebServer::handleRequest(AsyncWebServerRequest *request) { @@ -765,6 +806,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { return; } #endif + +#ifdef USE_SELECT + if (match.domain == "select") { + this->handle_select_request(request, match); + return; + } +#endif } bool WebServer::isRequestHandlerTrivial() { return false; } diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 4789c6e1c0..54d7356ac9 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -163,6 +163,15 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { std::string number_json(number::Number *obj, float value); #endif +#ifdef USE_SELECT + void on_select_update(select::Select *obj, const std::string &state) override; + /// Handle a select request under '/select/'. + void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match); + + /// Dump the number state with its value as a JSON string. + std::string select_json(select::Select *obj, const std::string &value); +#endif + /// Override the web handler's canHandle method. bool canHandle(AsyncWebServerRequest *request) override; /// Override the web handler's handleRequest method. diff --git a/esphome/const.py b/esphome/const.py index 5c21836c7d..ae6c5716d9 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -278,6 +278,7 @@ CONF_INCLUDES = "includes" CONF_INDEX = "index" CONF_INDOOR = "indoor" CONF_INITIAL_MODE = "initial_mode" +CONF_INITIAL_OPTION = "initial_option" CONF_INITIAL_VALUE = "initial_value" CONF_INTEGRATION_TIME = "integration_time" CONF_INTENSITY = "intensity" @@ -407,6 +408,8 @@ CONF_OPEN_DRAIN_INTERRUPT = "open_drain_interrupt" CONF_OPEN_DURATION = "open_duration" CONF_OPEN_ENDSTOP = "open_endstop" CONF_OPTIMISTIC = "optimistic" +CONF_OPTION = "option" +CONF_OPTIONS = "options" CONF_OR = "or" CONF_OSCILLATING = "oscillating" CONF_OSCILLATION_COMMAND_TOPIC = "oscillation_command_topic" diff --git a/esphome/core/application.h b/esphome/core/application.h index e065552a74..edbfbd130b 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -35,6 +35,9 @@ #ifdef USE_NUMBER #include "esphome/components/number/number.h" #endif +#ifdef USE_SELECT +#include "esphome/components/select/select.h" +#endif namespace esphome { @@ -89,6 +92,10 @@ class Application { void register_number(number::Number *number) { this->numbers_.push_back(number); } #endif +#ifdef USE_SELECT + void register_select(select::Select *select) { this->selects_.push_back(select); } +#endif + /// Register the component in this Application instance. template C *register_component(C *c) { static_assert(std::is_base_of::value, "Only Component subclasses can be registered"); @@ -224,6 +231,15 @@ class Application { return nullptr; } #endif +#ifdef USE_SELECT + const std::vector &get_selects() { return this->selects_; } + select::Select *get_select_by_key(uint32_t key, bool include_internal = false) { + for (auto *obj : this->selects_) + if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) + return obj; + return nullptr; + } +#endif Scheduler scheduler; @@ -264,6 +280,9 @@ class Application { #ifdef USE_NUMBER std::vector numbers_{}; #endif +#ifdef USE_SELECT + std::vector selects_{}; +#endif std::string name_; std::string compilation_time_; diff --git a/esphome/core/controller.cpp b/esphome/core/controller.cpp index 305fe93532..1d25be41f2 100644 --- a/esphome/core/controller.cpp +++ b/esphome/core/controller.cpp @@ -59,6 +59,12 @@ void Controller::setup_controller() { obj->add_on_state_callback([this, obj](float state) { this->on_number_update(obj, state); }); } #endif +#ifdef USE_SELECT + for (auto *obj : App.get_selects()) { + if (!obj->is_internal()) + obj->add_on_state_callback([this, obj](const std::string &state) { this->on_select_update(obj, state); }); + } +#endif } } // namespace esphome diff --git a/esphome/core/controller.h b/esphome/core/controller.h index 746658075f..0de8f7ea19 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -28,6 +28,9 @@ #ifdef USE_NUMBER #include "esphome/components/number/number.h" #endif +#ifdef USE_SELECT +#include "esphome/components/select/select.h" +#endif namespace esphome { @@ -61,6 +64,9 @@ class Controller { #ifdef USE_NUMBER virtual void on_number_update(number::Number *obj, float state){}; #endif +#ifdef USE_SELECT + virtual void on_select_update(select::Select *obj, const std::string &state){}; +#endif }; } // namespace esphome diff --git a/esphome/core/defines.h b/esphome/core/defines.h index cac03fc703..5c176d1b33 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -14,6 +14,7 @@ #define USE_LIGHT #define USE_CLIMATE #define USE_NUMBER +#define USE_SELECT #define USE_MQTT #define USE_POWER_SUPPLY #define USE_HOMEASSISTANT_TIME diff --git a/script/ci-custom.py b/script/ci-custom.py index d79e5b5e2f..5dad3e2445 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -563,6 +563,7 @@ def lint_inclusive_language(fname, match): "esphome/components/output/binary_output.h", "esphome/components/output/float_output.h", "esphome/components/nextion/nextion_base.h", + "esphome/components/select/select.h", "esphome/components/sensor/sensor.h", "esphome/components/stepper/stepper.h", "esphome/components/switch/switch.h", diff --git a/tests/test5.yaml b/tests/test5.yaml index 1a2dde1010..6ccb83a11a 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -82,6 +82,29 @@ number: 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: From b58ca46a465b022ab03aa824bfeb7df79f119530 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Mon, 2 Aug 2021 10:28:25 +0200 Subject: [PATCH 1117/1841] [duty_cycle] initialize two missing variables (#2088) --- esphome/components/duty_cycle/duty_cycle_sensor.cpp | 1 + esphome/components/duty_cycle/duty_cycle_sensor.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.cpp b/esphome/components/duty_cycle/duty_cycle_sensor.cpp index 8b7446b681..c989421948 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.cpp +++ b/esphome/components/duty_cycle/duty_cycle_sensor.cpp @@ -13,6 +13,7 @@ void DutyCycleSensor::setup() { this->store_.pin = this->pin_->to_isr(); this->store_.last_level = this->pin_->digital_read(); this->last_update_ = micros(); + this->store_.last_interrupt = micros(); this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, CHANGE); } diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.h b/esphome/components/duty_cycle/duty_cycle_sensor.h index 2205bec729..e168f20eff 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.h +++ b/esphome/components/duty_cycle/duty_cycle_sensor.h @@ -29,7 +29,7 @@ class DutyCycleSensor : public sensor::Sensor, public PollingComponent { protected: GPIOPin *pin_; - DutyCycleSensorStore store_; + DutyCycleSensorStore store_{}; uint32_t last_update_; }; From fb24e55c8de8468b1e0b681b43b2b6d1dbcdbc9e Mon Sep 17 00:00:00 2001 From: "John \"Warthog9\" Hawley" Date: Mon, 2 Aug 2021 01:32:08 -0700 Subject: [PATCH 1118/1841] pmsx003: add standard particle, particle counts (#1694) --- esphome/components/pmsx003/pmsx003.cpp | 114 ++++++++++++++++++++----- esphome/components/pmsx003/pmsx003.h | 29 +++++++ esphome/components/pmsx003/sensor.py | 100 ++++++++++++++++++++++ esphome/const.py | 10 +++ tests/test3.yaml | 60 +++++++++++++ 5 files changed, 290 insertions(+), 23 deletions(-) diff --git a/esphome/components/pmsx003/pmsx003.cpp b/esphome/components/pmsx003/pmsx003.cpp index abea287c0b..0474d6ffd0 100644 --- a/esphome/components/pmsx003/pmsx003.cpp +++ b/esphome/components/pmsx003/pmsx003.cpp @@ -6,9 +6,39 @@ namespace pmsx003 { static const char *const TAG = "pmsx003"; +void PMSX003Component::set_pm_1_0_std_sensor(sensor::Sensor *pm_1_0_std_sensor) { + pm_1_0_std_sensor_ = pm_1_0_std_sensor; +} +void PMSX003Component::set_pm_2_5_std_sensor(sensor::Sensor *pm_2_5_std_sensor) { + pm_2_5_std_sensor_ = pm_2_5_std_sensor; +} +void PMSX003Component::set_pm_10_0_std_sensor(sensor::Sensor *pm_10_0_std_sensor) { + pm_10_0_std_sensor_ = pm_10_0_std_sensor; +} + void PMSX003Component::set_pm_1_0_sensor(sensor::Sensor *pm_1_0_sensor) { pm_1_0_sensor_ = pm_1_0_sensor; } void PMSX003Component::set_pm_2_5_sensor(sensor::Sensor *pm_2_5_sensor) { pm_2_5_sensor_ = pm_2_5_sensor; } void PMSX003Component::set_pm_10_0_sensor(sensor::Sensor *pm_10_0_sensor) { pm_10_0_sensor_ = pm_10_0_sensor; } + +void PMSX003Component::set_pm_particles_03um_sensor(sensor::Sensor *pm_particles_03um_sensor) { + pm_particles_03um_sensor_ = pm_particles_03um_sensor; +} +void PMSX003Component::set_pm_particles_05um_sensor(sensor::Sensor *pm_particles_05um_sensor) { + pm_particles_05um_sensor_ = pm_particles_05um_sensor; +} +void PMSX003Component::set_pm_particles_10um_sensor(sensor::Sensor *pm_particles_10um_sensor) { + pm_particles_10um_sensor_ = pm_particles_10um_sensor; +} +void PMSX003Component::set_pm_particles_25um_sensor(sensor::Sensor *pm_particles_25um_sensor) { + pm_particles_25um_sensor_ = pm_particles_25um_sensor; +} +void PMSX003Component::set_pm_particles_50um_sensor(sensor::Sensor *pm_particles_50um_sensor) { + pm_particles_50um_sensor_ = pm_particles_50um_sensor; +} +void PMSX003Component::set_pm_particles_100um_sensor(sensor::Sensor *pm_particles_100um_sensor) { + pm_particles_100um_sensor_ = pm_particles_100um_sensor; +} + void PMSX003Component::set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } @@ -102,19 +132,68 @@ optional PMSX003Component::check_byte_() { void PMSX003Component::parse_data_() { switch (this->type_) { + case PMSX003_TYPE_5003ST: { + uint16_t formaldehyde = this->get_16_bit_uint_(28); + float temperature = this->get_16_bit_uint_(30) / 10.0f; + float humidity = this->get_16_bit_uint_(32) / 10.0f; + + ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%% Formaldehyde: %u µg/m^3", temperature, humidity, + formaldehyde); + + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(temperature); + if (this->humidity_sensor_ != nullptr) + this->humidity_sensor_->publish_state(humidity); + if (this->formaldehyde_sensor_ != nullptr) + this->formaldehyde_sensor_->publish_state(formaldehyde); + // The rest of the PMS5003ST matches the PMS5003, continue on + } case PMSX003_TYPE_X003: { + uint16_t pm_1_0_std_concentration = this->get_16_bit_uint_(4); + uint16_t pm_2_5_std_concentration = this->get_16_bit_uint_(6); + uint16_t pm_10_0_std_concentration = this->get_16_bit_uint_(8); + uint16_t pm_1_0_concentration = this->get_16_bit_uint_(10); uint16_t pm_2_5_concentration = this->get_16_bit_uint_(12); uint16_t pm_10_0_concentration = this->get_16_bit_uint_(14); + + uint16_t pm_particles_03um = this->get_16_bit_uint_(16); + uint16_t pm_particles_05um = this->get_16_bit_uint_(18); + uint16_t pm_particles_10um = this->get_16_bit_uint_(20); + uint16_t pm_particles_25um = this->get_16_bit_uint_(22); + uint16_t pm_particles_50um = this->get_16_bit_uint_(24); + uint16_t pm_particles_100um = this->get_16_bit_uint_(26); + ESP_LOGD(TAG, "Got PM1.0 Concentration: %u µg/m^3, PM2.5 Concentration %u µg/m^3, PM10.0 Concentration: %u µg/m^3", pm_1_0_concentration, pm_2_5_concentration, pm_10_0_concentration); + + if (this->pm_1_0_std_sensor_ != nullptr) + this->pm_1_0_std_sensor_->publish_state(pm_1_0_std_concentration); + if (this->pm_2_5_std_sensor_ != nullptr) + this->pm_2_5_std_sensor_->publish_state(pm_2_5_std_concentration); + if (this->pm_10_0_std_sensor_ != nullptr) + this->pm_10_0_std_sensor_->publish_state(pm_10_0_std_concentration); + if (this->pm_1_0_sensor_ != nullptr) this->pm_1_0_sensor_->publish_state(pm_1_0_concentration); if (this->pm_2_5_sensor_ != nullptr) this->pm_2_5_sensor_->publish_state(pm_2_5_concentration); if (this->pm_10_0_sensor_ != nullptr) this->pm_10_0_sensor_->publish_state(pm_10_0_concentration); + + if (this->pm_particles_03um_sensor_ != nullptr) + this->pm_particles_03um_sensor_->publish_state(pm_particles_03um); + if (this->pm_particles_05um_sensor_ != nullptr) + this->pm_particles_05um_sensor_->publish_state(pm_particles_05um); + if (this->pm_particles_10um_sensor_ != nullptr) + this->pm_particles_10um_sensor_->publish_state(pm_particles_10um); + if (this->pm_particles_25um_sensor_ != nullptr) + this->pm_particles_25um_sensor_->publish_state(pm_particles_25um); + if (this->pm_particles_50um_sensor_ != nullptr) + this->pm_particles_50um_sensor_->publish_state(pm_particles_50um); + if (this->pm_particles_100um_sensor_ != nullptr) + this->pm_particles_100um_sensor_->publish_state(pm_particles_100um); break; } case PMSX003_TYPE_5003T: { @@ -131,29 +210,6 @@ void PMSX003Component::parse_data_() { this->humidity_sensor_->publish_state(humidity); break; } - case PMSX003_TYPE_5003ST: { - uint16_t pm_1_0_concentration = this->get_16_bit_uint_(10); - uint16_t pm_2_5_concentration = this->get_16_bit_uint_(12); - uint16_t pm_10_0_concentration = this->get_16_bit_uint_(14); - uint16_t formaldehyde = this->get_16_bit_uint_(28); - float temperature = this->get_16_bit_uint_(30) / 10.0f; - float humidity = this->get_16_bit_uint_(32) / 10.0f; - ESP_LOGD(TAG, "Got PM2.5 Concentration: %u µg/m^3, Temperature: %.1f°C, Humidity: %.1f%% Formaldehyde: %u µg/m^3", - pm_2_5_concentration, temperature, humidity, formaldehyde); - if (this->pm_1_0_sensor_ != nullptr) - this->pm_1_0_sensor_->publish_state(pm_1_0_concentration); - if (this->pm_2_5_sensor_ != nullptr) - this->pm_2_5_sensor_->publish_state(pm_2_5_concentration); - if (this->pm_10_0_sensor_ != nullptr) - this->pm_10_0_sensor_->publish_state(pm_10_0_concentration); - if (this->temperature_sensor_ != nullptr) - this->temperature_sensor_->publish_state(temperature); - if (this->humidity_sensor_ != nullptr) - this->humidity_sensor_->publish_state(humidity); - if (this->formaldehyde_sensor_ != nullptr) - this->formaldehyde_sensor_->publish_state(formaldehyde); - break; - } } this->status_clear_warning(); @@ -163,9 +219,21 @@ uint16_t PMSX003Component::get_16_bit_uint_(uint8_t start_index) { } void PMSX003Component::dump_config() { ESP_LOGCONFIG(TAG, "PMSX003:"); + LOG_SENSOR(" ", "PM1.0STD", this->pm_1_0_std_sensor_); + LOG_SENSOR(" ", "PM2.5STD", this->pm_2_5_std_sensor_); + LOG_SENSOR(" ", "PM10.0STD", this->pm_10_0_std_sensor_); + LOG_SENSOR(" ", "PM1.0", this->pm_1_0_sensor_); LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_); LOG_SENSOR(" ", "PM10.0", this->pm_10_0_sensor_); + + LOG_SENSOR(" ", "PM0.3um", this->pm_particles_03um_sensor_); + LOG_SENSOR(" ", "PM0.5um", this->pm_particles_05um_sensor_); + LOG_SENSOR(" ", "PM1.0um", this->pm_particles_10um_sensor_); + LOG_SENSOR(" ", "PM2.5um", this->pm_particles_25um_sensor_); + LOG_SENSOR(" ", "PM5.0um", this->pm_particles_50um_sensor_); + LOG_SENSOR(" ", "PM10.0um", this->pm_particles_100um_sensor_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); LOG_SENSOR(" ", "Formaldehyde", this->formaldehyde_sensor_); diff --git a/esphome/components/pmsx003/pmsx003.h b/esphome/components/pmsx003/pmsx003.h index 163d25c694..a5adecb534 100644 --- a/esphome/components/pmsx003/pmsx003.h +++ b/esphome/components/pmsx003/pmsx003.h @@ -21,9 +21,22 @@ class PMSX003Component : public uart::UARTDevice, public Component { void dump_config() override; void set_type(PMSX003Type type) { type_ = type; } + + void set_pm_1_0_std_sensor(sensor::Sensor *pm_1_0_std_sensor); + void set_pm_2_5_std_sensor(sensor::Sensor *pm_2_5_std_sensor); + void set_pm_10_0_std_sensor(sensor::Sensor *pm_10_0_std_sensor); + void set_pm_1_0_sensor(sensor::Sensor *pm_1_0_sensor); void set_pm_2_5_sensor(sensor::Sensor *pm_2_5_sensor); void set_pm_10_0_sensor(sensor::Sensor *pm_10_0_sensor); + + void set_pm_particles_03um_sensor(sensor::Sensor *pm_particles_03um_sensor); + void set_pm_particles_05um_sensor(sensor::Sensor *pm_particles_05um_sensor); + void set_pm_particles_10um_sensor(sensor::Sensor *pm_particles_10um_sensor); + void set_pm_particles_25um_sensor(sensor::Sensor *pm_particles_25um_sensor); + void set_pm_particles_50um_sensor(sensor::Sensor *pm_particles_50um_sensor); + void set_pm_particles_100um_sensor(sensor::Sensor *pm_particles_100um_sensor); + void set_temperature_sensor(sensor::Sensor *temperature_sensor); void set_humidity_sensor(sensor::Sensor *humidity_sensor); void set_formaldehyde_sensor(sensor::Sensor *formaldehyde_sensor); @@ -37,9 +50,25 @@ class PMSX003Component : public uart::UARTDevice, public Component { uint8_t data_index_{0}; uint32_t last_transmission_{0}; PMSX003Type type_; + + // "Standard Particle" + sensor::Sensor *pm_1_0_std_sensor_{nullptr}; + sensor::Sensor *pm_2_5_std_sensor_{nullptr}; + sensor::Sensor *pm_10_0_std_sensor_{nullptr}; + + // "Under Atmospheric Pressure" sensor::Sensor *pm_1_0_sensor_{nullptr}; sensor::Sensor *pm_2_5_sensor_{nullptr}; sensor::Sensor *pm_10_0_sensor_{nullptr}; + + // Particle counts by size + sensor::Sensor *pm_particles_03um_sensor_{nullptr}; + sensor::Sensor *pm_particles_05um_sensor_{nullptr}; + sensor::Sensor *pm_particles_10um_sensor_{nullptr}; + sensor::Sensor *pm_particles_25um_sensor_{nullptr}; + sensor::Sensor *pm_particles_50um_sensor_{nullptr}; + sensor::Sensor *pm_particles_100um_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; sensor::Sensor *formaldehyde_sensor_{nullptr}; diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index 935208fe03..fcded50c25 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -8,6 +8,15 @@ from esphome.const import ( CONF_PM_10_0, CONF_PM_1_0, CONF_PM_2_5, + CONF_PM_10_0_STD, + CONF_PM_1_0_STD, + CONF_PM_2_5_STD, + CONF_PM_0_3UM, + CONF_PM_0_5UM, + CONF_PM_1_0UM, + CONF_PM_2_5UM, + CONF_PM_5_0UM, + CONF_PM_10_0UM, CONF_TEMPERATURE, CONF_TYPE, DEVICE_CLASS_HUMIDITY, @@ -16,6 +25,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, UNIT_CELSIUS, + UNIT_COUNT_DECILITRE, UNIT_PERCENT, ) @@ -60,6 +70,24 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(PMSX003Component), cv.Required(CONF_TYPE): cv.enum(PMSX003_TYPES, upper=True), + cv.Optional(CONF_PM_1_0_STD): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 0, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PM_2_5_STD): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 0, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PM_10_0_STD): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 0, + DEVICE_CLASS_EMPTY, + ), cv.Optional(CONF_PM_1_0): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, @@ -78,6 +106,42 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_PM_0_3UM): sensor.sensor_schema( + UNIT_COUNT_DECILITRE, + ICON_CHEMICAL_WEAPON, + 0, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PM_0_5UM): sensor.sensor_schema( + UNIT_COUNT_DECILITRE, + ICON_CHEMICAL_WEAPON, + 0, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PM_1_0UM): sensor.sensor_schema( + UNIT_COUNT_DECILITRE, + ICON_CHEMICAL_WEAPON, + 0, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PM_2_5UM): sensor.sensor_schema( + UNIT_COUNT_DECILITRE, + ICON_CHEMICAL_WEAPON, + 0, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PM_5_0UM): sensor.sensor_schema( + UNIT_COUNT_DECILITRE, + ICON_CHEMICAL_WEAPON, + 0, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PM_10_0UM): sensor.sensor_schema( + UNIT_COUNT_DECILITRE, + ICON_CHEMICAL_WEAPON, + 0, + DEVICE_CLASS_EMPTY, + ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, @@ -110,6 +174,18 @@ async def to_code(config): cg.add(var.set_type(config[CONF_TYPE])) + if CONF_PM_1_0_STD in config: + sens = await sensor.new_sensor(config[CONF_PM_1_0_STD]) + cg.add(var.set_pm_1_0_std_sensor(sens)) + + if CONF_PM_2_5_STD in config: + sens = await sensor.new_sensor(config[CONF_PM_2_5_STD]) + cg.add(var.set_pm_2_5_std_sensor(sens)) + + if CONF_PM_10_0_STD in config: + sens = await sensor.new_sensor(config[CONF_PM_10_0_STD]) + cg.add(var.set_pm_10_0_std_sensor(sens)) + if CONF_PM_1_0 in config: sens = await sensor.new_sensor(config[CONF_PM_1_0]) cg.add(var.set_pm_1_0_sensor(sens)) @@ -122,6 +198,30 @@ async def to_code(config): sens = await sensor.new_sensor(config[CONF_PM_10_0]) cg.add(var.set_pm_10_0_sensor(sens)) + if CONF_PM_0_3UM in config: + sens = await sensor.new_sensor(config[CONF_PM_0_3UM]) + cg.add(var.set_pm_particles_03um_sensor(sens)) + + if CONF_PM_0_5UM in config: + sens = await sensor.new_sensor(config[CONF_PM_0_5UM]) + cg.add(var.set_pm_particles_05um_sensor(sens)) + + if CONF_PM_1_0UM in config: + sens = await sensor.new_sensor(config[CONF_PM_1_0UM]) + cg.add(var.set_pm_particles_10um_sensor(sens)) + + if CONF_PM_2_5UM in config: + sens = await sensor.new_sensor(config[CONF_PM_2_5UM]) + cg.add(var.set_pm_particles_25um_sensor(sens)) + + if CONF_PM_5_0UM in config: + sens = await sensor.new_sensor(config[CONF_PM_5_0UM]) + cg.add(var.set_pm_particles_50um_sensor(sens)) + + if CONF_PM_10_0UM in config: + sens = await sensor.new_sensor(config[CONF_PM_10_0UM]) + cg.add(var.set_pm_particles_100um_sensor(sens)) + if CONF_TEMPERATURE in config: sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) cg.add(var.set_temperature_sensor(sens)) diff --git a/esphome/const.py b/esphome/const.py index ae6c5716d9..8327921211 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -441,10 +441,19 @@ CONF_PINS = "pins" CONF_PIXEL_MAPPER = "pixel_mapper" CONF_PLATFORM = "platform" CONF_PLATFORMIO_OPTIONS = "platformio_options" +CONF_PM_0_3UM = "pm_0_3um" +CONF_PM_0_5UM = "pm_0_5um" CONF_PM_1_0 = "pm_1_0" +CONF_PM_1_0_STD = "pm_1_0_std" +CONF_PM_1_0UM = "pm_1_0um" CONF_PM_10_0 = "pm_10_0" +CONF_PM_10_0_STD = "pm_10_0_std" +CONF_PM_10_0UM = "pm_10_0um" CONF_PM_2_5 = "pm_2_5" +CONF_PM_2_5_STD = "pm_2_5_std" +CONF_PM_2_5UM = "pm_2_5um" CONF_PM_4_0 = "pm_4_0" +CONF_PM_5_0UM = "pm_5_0um" CONF_PM_SIZE = "pm_size" CONF_PMC_0_5 = "pmc_0_5" CONF_PMC_1_0 = "pmc_1_0" @@ -709,6 +718,7 @@ ICON_WIFI = "mdi:wifi" UNIT_AMPERE = "A" UNIT_CELSIUS = "°C" +UNIT_COUNT_DECILITRE = "/dL" UNIT_COUNTS_PER_CUBIC_METER = "#/m³" UNIT_CUBIC_METER = "m³" UNIT_DECIBEL = "dB" diff --git a/tests/test3.yaml b/tests/test3.yaml index acd975d794..d4c8e526fe 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -450,6 +450,66 @@ 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' + temperature: + name: 'PMS Temperature' + 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: + name: 'PMS Humidity' + formaldehyde: + name: 'PMS Formaldehyde Concentration' - platform: cse7766 uart_id: uart3 voltage: From f7311aa02576cb3d64c2b37173f81e58d2522c97 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 2 Aug 2021 20:33:00 +1200 Subject: [PATCH 1119/1841] Dont force 0 state instead of min_power unless explicit config set (#2107) --- esphome/components/output/__init__.py | 5 +++++ esphome/components/output/float_output.cpp | 4 +++- esphome/components/output/float_output.h | 7 +++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/esphome/components/output/__init__.py b/esphome/components/output/__init__.py index 4471794033..4f1fb33fe7 100644 --- a/esphome/components/output/__init__.py +++ b/esphome/components/output/__init__.py @@ -17,6 +17,8 @@ from esphome.core import CORE CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True +CONF_ZERO_MEANS_ZERO = "zero_means_zero" + BINARY_OUTPUT_SCHEMA = cv.Schema( { cv.Optional(CONF_POWER_SUPPLY): cv.use_id(power_supply.PowerSupply), @@ -28,6 +30,7 @@ FLOAT_OUTPUT_SCHEMA = BINARY_OUTPUT_SCHEMA.extend( { cv.Optional(CONF_MAX_POWER): cv.percentage, cv.Optional(CONF_MIN_POWER): cv.percentage, + cv.Optional(CONF_ZERO_MEANS_ZERO, default=False): cv.boolean, } ) @@ -53,6 +56,8 @@ async def setup_output_platform_(obj, config): cg.add(obj.set_max_power(config[CONF_MAX_POWER])) if CONF_MIN_POWER in config: cg.add(obj.set_min_power(config[CONF_MIN_POWER])) + if CONF_ZERO_MEANS_ZERO in config: + cg.add(obj.set_zero_means_zero(config[CONF_ZERO_MEANS_ZERO])) async def register_output(var, config): diff --git a/esphome/components/output/float_output.cpp b/esphome/components/output/float_output.cpp index 99ba798cb9..5820a2d7cd 100644 --- a/esphome/components/output/float_output.cpp +++ b/esphome/components/output/float_output.cpp @@ -17,6 +17,8 @@ void FloatOutput::set_min_power(float min_power) { this->min_power_ = clamp(min_power, 0.0f, this->max_power_); // Clamp to 0.0>=MIN>=MAX } +void FloatOutput::set_zero_means_zero(bool zero_means_zero) { this->zero_means_zero_ = zero_means_zero; } + float FloatOutput::get_min_power() const { return this->min_power_; } void FloatOutput::set_level(float state) { @@ -31,7 +33,7 @@ void FloatOutput::set_level(float state) { #endif if (this->is_inverted()) state = 1.0f - state; - if (state == 0.0f) { // regardless of min_power_, 0.0 means off + if (state == 0.0f && this->zero_means_zero_) { // regardless of min_power_, 0.0 means off this->write_state(state); return; } diff --git a/esphome/components/output/float_output.h b/esphome/components/output/float_output.h index 1b969c9225..3e2b3ada8d 100644 --- a/esphome/components/output/float_output.h +++ b/esphome/components/output/float_output.h @@ -46,6 +46,12 @@ class FloatOutput : public BinaryOutput { */ void set_min_power(float min_power); + /** Sets this output to ignore min_power for a 0 state + * + * @param zero True if a 0 state should mean 0 and not min_power. + */ + void set_zero_means_zero(bool zero_means_zero); + /** Set the level of this float output, this is called from the front-end. * * @param state The new state. @@ -76,6 +82,7 @@ class FloatOutput : public BinaryOutput { float max_power_{1.0f}; float min_power_{0.0f}; + bool zero_means_zero_; }; } // namespace output From 9b04e657db0afb4c2cb4026f7c7ff8989ec6fecd Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 2 Aug 2021 20:53:34 +1200 Subject: [PATCH 1120/1841] Fix import (#2108) --- esphome/components/pmsx003/sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index fcded50c25..c3dd7d5a97 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -19,6 +19,7 @@ from esphome.const import ( CONF_PM_10_0UM, CONF_TEMPERATURE, CONF_TYPE, + DEVICE_CLASS_EMPTY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, ICON_CHEMICAL_WEAPON, From 335210d788498b92177a882353980676ce01a9c1 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 2 Aug 2021 04:08:24 -0500 Subject: [PATCH 1121/1841] Thermostat enhancements and code clean-up (#2073) --- esphome/components/thermostat/climate.py | 177 +++++---- .../thermostat/thermostat_climate.cpp | 347 ++++++++++++------ .../thermostat/thermostat_climate.h | 42 ++- esphome/const.py | 6 + 4 files changed, 355 insertions(+), 217 deletions(-) diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index bf3195a5dd..55fb534682 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -6,7 +6,9 @@ from esphome.const import ( CONF_AUTO_MODE, CONF_AWAY_CONFIG, CONF_COOL_ACTION, + CONF_COOL_DEADBAND, CONF_COOL_MODE, + CONF_COOL_OVERRUN, CONF_DEFAULT_MODE, CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, CONF_DEFAULT_TARGET_TEMPERATURE_LOW, @@ -22,14 +24,18 @@ from esphome.const import ( CONF_FAN_MODE_FOCUS_ACTION, CONF_FAN_MODE_DIFFUSE_ACTION, CONF_FAN_ONLY_ACTION, + CONF_FAN_ONLY_COOLING, CONF_FAN_ONLY_MODE, CONF_HEAT_ACTION, + CONF_HEAT_DEADBAND, CONF_HEAT_MODE, + CONF_HEAT_OVERRUN, CONF_HYSTERESIS, CONF_ID, CONF_IDLE_ACTION, CONF_OFF_MODE, CONF_SENSOR, + CONF_SET_POINT_MINIMUM_DIFFERENTIAL, CONF_SWING_BOTH_ACTION, CONF_SWING_HORIZONTAL_ACTION, CONF_SWING_OFF_ACTION, @@ -62,99 +68,59 @@ validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True) def validate_thermostat(config): # verify corresponding climate action action exists for any defined climate mode action - if CONF_COOL_MODE in config and CONF_COOL_ACTION not in config: - raise cv.Invalid( - "{} must be defined to use {}".format(CONF_COOL_ACTION, CONF_COOL_MODE) - ) - if CONF_DRY_MODE in config and CONF_DRY_ACTION not in config: - raise cv.Invalid( - "{} must be defined to use {}".format(CONF_DRY_ACTION, CONF_DRY_MODE) - ) - if CONF_FAN_ONLY_MODE in config and CONF_FAN_ONLY_ACTION not in config: - raise cv.Invalid( - "{} must be defined to use {}".format( - CONF_FAN_ONLY_ACTION, CONF_FAN_ONLY_MODE - ) - ) - if CONF_HEAT_MODE in config and CONF_HEAT_ACTION not in config: - raise cv.Invalid( - "{} must be defined to use {}".format(CONF_HEAT_ACTION, CONF_HEAT_MODE) - ) - # verify corresponding default target temperature exists when a given climate action exists - if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH not in config and ( - CONF_COOL_ACTION in config or CONF_FAN_ONLY_ACTION in config - ): - raise cv.Invalid( - "{} must be defined when using {} or {}".format( - CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, + requirements = { + CONF_AUTO_MODE: [CONF_COOL_ACTION, CONF_HEAT_ACTION], + CONF_COOL_MODE: [CONF_COOL_ACTION], + CONF_DRY_MODE: [CONF_DRY_ACTION], + CONF_FAN_ONLY_MODE: [CONF_FAN_ONLY_ACTION], + CONF_HEAT_MODE: [CONF_HEAT_ACTION], + } + for config_mode, req_actions in requirements.items(): + for req_action in req_actions: + if config_mode in config and req_action not in config: + raise cv.Invalid(f"{req_action} must be defined to use {config_mode}") + + # determine validation requirements based on fan_only_cooling setting + if config[CONF_FAN_ONLY_COOLING] is True: + requirements = { + CONF_DEFAULT_TARGET_TEMPERATURE_HIGH: [ CONF_COOL_ACTION, CONF_FAN_ONLY_ACTION, - ) - ) - if CONF_DEFAULT_TARGET_TEMPERATURE_LOW not in config and CONF_HEAT_ACTION in config: - raise cv.Invalid( - "{} must be defined when using {}".format( - CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION - ) - ) - # if a given climate action is NOT defined, it should not have a default target temperature - if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in config and ( - CONF_COOL_ACTION not in config and CONF_FAN_ONLY_ACTION not in config - ): - raise cv.Invalid( - "{} is defined with no {}".format( - CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, CONF_COOL_ACTION - ) - ) - if CONF_DEFAULT_TARGET_TEMPERATURE_LOW in config and CONF_HEAT_ACTION not in config: - raise cv.Invalid( - "{} is defined with no {}".format( - CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION - ) - ) + ], + CONF_DEFAULT_TARGET_TEMPERATURE_LOW: [CONF_HEAT_ACTION], + } + else: + requirements = { + CONF_DEFAULT_TARGET_TEMPERATURE_HIGH: [CONF_COOL_ACTION], + CONF_DEFAULT_TARGET_TEMPERATURE_LOW: [CONF_HEAT_ACTION], + } + + for config_temp, req_actions in requirements.items(): + for req_action in req_actions: + # verify corresponding default target temperature exists when a given climate action exists + if config_temp not in config and req_action in config: + raise cv.Invalid( + f"{config_temp} must be defined when using {req_action}" + ) + # if a given climate action is NOT defined, it should not have a default target temperature + if config_temp in config and req_action not in config: + raise cv.Invalid(f"{config_temp} is defined with no {req_action}") if CONF_AWAY_CONFIG in config: away = config[CONF_AWAY_CONFIG] - # verify corresponding default target temperature exists when a given climate action exists - if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH not in away and ( - CONF_COOL_ACTION in config or CONF_FAN_ONLY_ACTION in config - ): - raise cv.Invalid( - "{} must be defined in away configuration when using {} or {}".format( - CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, - CONF_COOL_ACTION, - CONF_FAN_ONLY_ACTION, - ) - ) - if ( - CONF_DEFAULT_TARGET_TEMPERATURE_LOW not in away - and CONF_HEAT_ACTION in config - ): - raise cv.Invalid( - "{} must be defined in away configuration when using {}".format( - CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION - ) - ) - # if a given climate action is NOT defined, it should not have a default target temperature - if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in away and ( - CONF_COOL_ACTION not in config and CONF_FAN_ONLY_ACTION not in config - ): - raise cv.Invalid( - "{} is defined in away configuration with no {} or {}".format( - CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, - CONF_COOL_ACTION, - CONF_FAN_ONLY_ACTION, - ) - ) - if ( - CONF_DEFAULT_TARGET_TEMPERATURE_LOW in away - and CONF_HEAT_ACTION not in config - ): - raise cv.Invalid( - "{} is defined in away configuration with no {}".format( - CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION - ) - ) + for config_temp, req_actions in requirements.items(): + for req_action in req_actions: + # verify corresponding default target temperature exists when a given climate action exists + if config_temp not in away and req_action in config: + raise cv.Invalid( + f"{config_temp} must be defined in away configuration when using {req_action}" + ) + # if a given climate action is NOT defined, it should not have a default target temperature + if config_temp in away and req_action not in config: + raise cv.Invalid( + f"{config_temp} is defined in away configuration with no {req_action}" + ) + # verify default climate mode is valid given above configuration default_mode = config[CONF_DEFAULT_MODE] requirements = { @@ -241,7 +207,15 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, + cv.Optional( + CONF_SET_POINT_MINIMUM_DIFFERENTIAL, default=0.5 + ): cv.temperature, + cv.Optional(CONF_COOL_DEADBAND): cv.temperature, + cv.Optional(CONF_COOL_OVERRUN): cv.temperature, + cv.Optional(CONF_HEAT_DEADBAND): cv.temperature, + cv.Optional(CONF_HEAT_OVERRUN): cv.temperature, cv.Optional(CONF_HYSTERESIS, default=0.5): cv.temperature, + cv.Optional(CONF_FAN_ONLY_COOLING, default=False): cv.boolean, cv.Optional(CONF_AWAY_CONFIG): cv.Schema( { cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, @@ -269,8 +243,32 @@ async def to_code(config): sens = await cg.get_variable(config[CONF_SENSOR]) cg.add(var.set_default_mode(config[CONF_DEFAULT_MODE])) + cg.add( + var.set_set_point_minimum_differential( + config[CONF_SET_POINT_MINIMUM_DIFFERENTIAL] + ) + ) cg.add(var.set_sensor(sens)) - cg.add(var.set_hysteresis(config[CONF_HYSTERESIS])) + + if CONF_COOL_DEADBAND in config: + cg.add(var.set_cool_deadband(config[CONF_COOL_DEADBAND])) + else: + cg.add(var.set_cool_deadband(config[CONF_HYSTERESIS])) + + if CONF_COOL_OVERRUN in config: + cg.add(var.set_cool_overrun(config[CONF_COOL_OVERRUN])) + else: + cg.add(var.set_cool_overrun(config[CONF_HYSTERESIS])) + + if CONF_HEAT_DEADBAND in config: + cg.add(var.set_heat_deadband(config[CONF_HEAT_DEADBAND])) + else: + cg.add(var.set_heat_deadband(config[CONF_HYSTERESIS])) + + if CONF_HEAT_OVERRUN in config: + cg.add(var.set_heat_overrun(config[CONF_HEAT_OVERRUN])) + else: + cg.add(var.set_heat_overrun(config[CONF_HYSTERESIS])) if two_points_available is True: cg.add(var.set_supports_two_points(True)) @@ -288,6 +286,7 @@ async def to_code(config): normal_config = ThermostatClimateTargetTempConfig( config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW] ) + cg.add(var.set_supports_fan_only_cooling(config[CONF_FAN_ONLY_COOLING])) cg.add(var.set_normal_config(normal_config)) await automation.build_automation( diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 4610d5caad..1176a75514 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -7,6 +7,7 @@ namespace thermostat { static const char *const TAG = "thermostat.climate"; void ThermostatClimate::setup() { + // add a callback so that whenever the sensor state changes we can take action this->sensor_->add_on_state_callback([this](float state) { this->current_temperature = state; // required action may have changed, recompute, refresh @@ -29,7 +30,12 @@ void ThermostatClimate::setup() { this->setup_complete_ = true; this->publish_state(); } -float ThermostatClimate::hysteresis() { return this->hysteresis_; } + +float ThermostatClimate::cool_deadband() { return this->cool_deadband_; } +float ThermostatClimate::cool_overrun() { return this->cool_overrun_; } +float ThermostatClimate::heat_deadband() { return this->heat_deadband_; } +float ThermostatClimate::heat_overrun() { return this->heat_overrun_; } + void ThermostatClimate::refresh() { this->switch_to_mode_(this->mode); this->switch_to_action_(compute_action_()); @@ -38,6 +44,77 @@ void ThermostatClimate::refresh() { this->check_temperature_change_trigger_(); this->publish_state(); } + +bool ThermostatClimate::hysteresis_valid() { + if ((this->supports_cool_ || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) && + (isnan(this->cool_deadband_) || isnan(this->cool_overrun_))) + return false; + + if (this->supports_heat_ && (isnan(this->heat_deadband_) || isnan(this->heat_overrun_))) + return false; + + return true; +} + +void ThermostatClimate::validate_target_temperature() { + if (isnan(this->target_temperature)) { + this->target_temperature = + ((this->get_traits().get_visual_max_temperature() - this->get_traits().get_visual_min_temperature()) / 2) + + this->get_traits().get_visual_min_temperature(); + } else { + // target_temperature must be between the visual minimum and the visual maximum + if (this->target_temperature < this->get_traits().get_visual_min_temperature()) + this->target_temperature = this->get_traits().get_visual_min_temperature(); + if (this->target_temperature > this->get_traits().get_visual_max_temperature()) + this->target_temperature = this->get_traits().get_visual_max_temperature(); + } +} + +void ThermostatClimate::validate_target_temperatures() { + if (this->supports_two_points_) { + validate_target_temperature_low(); + validate_target_temperature_high(); + } else { + validate_target_temperature(); + } +} + +void ThermostatClimate::validate_target_temperature_low() { + if (isnan(this->target_temperature_low)) { + this->target_temperature_low = this->get_traits().get_visual_min_temperature(); + } else { + // target_temperature_low must not be lower than the visual minimum + if (this->target_temperature_low < this->get_traits().get_visual_min_temperature()) + this->target_temperature_low = this->get_traits().get_visual_min_temperature(); + // target_temperature_low must not be greater than the visual maximum minus set_point_minimum_differential_ + if (this->target_temperature_low > + this->get_traits().get_visual_max_temperature() - this->set_point_minimum_differential_) + this->target_temperature_low = + this->get_traits().get_visual_max_temperature() - this->set_point_minimum_differential_; + // if target_temperature_low is set greater than target_temperature_high, move up target_temperature_high + if (this->target_temperature_low > this->target_temperature_high - this->set_point_minimum_differential_) + this->target_temperature_high = this->target_temperature_low + this->set_point_minimum_differential_; + } +} + +void ThermostatClimate::validate_target_temperature_high() { + if (isnan(this->target_temperature_high)) { + this->target_temperature_high = this->get_traits().get_visual_max_temperature(); + } else { + // target_temperature_high must not be lower than the visual maximum + if (this->target_temperature_high > this->get_traits().get_visual_max_temperature()) + this->target_temperature_high = this->get_traits().get_visual_max_temperature(); + // target_temperature_high must not be lower than the visual minimum plus set_point_minimum_differential_ + if (this->target_temperature_high < + this->get_traits().get_visual_min_temperature() + this->set_point_minimum_differential_) + this->target_temperature_high = + this->get_traits().get_visual_min_temperature() + this->set_point_minimum_differential_; + // if target_temperature_high is set less than target_temperature_low, move down target_temperature_low + if (this->target_temperature_high < this->target_temperature_low + this->set_point_minimum_differential_) + this->target_temperature_low = this->target_temperature_high - this->set_point_minimum_differential_; + } +} + void ThermostatClimate::control(const climate::ClimateCall &call) { if (call.get_preset().has_value()) { // setup_complete_ blocks modifying/resetting the temps immediately after boot @@ -53,29 +130,25 @@ void ThermostatClimate::control(const climate::ClimateCall &call) { this->fan_mode = *call.get_fan_mode(); if (call.get_swing_mode().has_value()) this->swing_mode = *call.get_swing_mode(); - if (call.get_target_temperature().has_value()) - this->target_temperature = *call.get_target_temperature(); - if (call.get_target_temperature_low().has_value()) - this->target_temperature_low = *call.get_target_temperature_low(); - if (call.get_target_temperature_high().has_value()) - this->target_temperature_high = *call.get_target_temperature_high(); - // set point validation if (this->supports_two_points_) { - if (this->target_temperature_low < this->get_traits().get_visual_min_temperature()) - this->target_temperature_low = this->get_traits().get_visual_min_temperature(); - if (this->target_temperature_high > this->get_traits().get_visual_max_temperature()) - this->target_temperature_high = this->get_traits().get_visual_max_temperature(); - if (this->target_temperature_high < this->target_temperature_low) - this->target_temperature_high = this->target_temperature_low; + if (call.get_target_temperature_low().has_value()) { + this->target_temperature_low = *call.get_target_temperature_low(); + validate_target_temperature_low(); + } + if (call.get_target_temperature_high().has_value()) { + this->target_temperature_high = *call.get_target_temperature_high(); + validate_target_temperature_high(); + } } else { - if (this->target_temperature < this->get_traits().get_visual_min_temperature()) - this->target_temperature = this->get_traits().get_visual_min_temperature(); - if (this->target_temperature > this->get_traits().get_visual_max_temperature()) - this->target_temperature = this->get_traits().get_visual_max_temperature(); + if (call.get_target_temperature().has_value()) { + this->target_temperature = *call.get_target_temperature(); + validate_target_temperature(); + } } // make any changes happen refresh(); } + climate::ClimateTraits ThermostatClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); @@ -127,114 +200,54 @@ climate::ClimateTraits ThermostatClimate::traits() { traits.set_supports_action(true); return traits; } + climate::ClimateAction ThermostatClimate::compute_action_() { - // we need to know the current climate action before anything else happens here - climate::ClimateAction target_action = this->action; - // if the climate mode is OFF then the climate action must be OFF - if (this->mode == climate::CLIMATE_MODE_OFF) { + auto target_action = climate::CLIMATE_ACTION_IDLE; + // if any hysteresis values or current_temperature is not valid, we go to OFF; + if (isnan(this->current_temperature) || !this->hysteresis_valid()) { return climate::CLIMATE_ACTION_OFF; - } else if (this->action == climate::CLIMATE_ACTION_OFF) { - // ...but if the climate mode is NOT OFF then the climate action must not be OFF - target_action = climate::CLIMATE_ACTION_IDLE; } - - if (this->supports_two_points_) { - if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || - isnan(this->target_temperature_high) || isnan(this->hysteresis_)) - // if any control parameters are nan, go to OFF action (not IDLE!) - return climate::CLIMATE_ACTION_OFF; - - if (((this->action == climate::CLIMATE_ACTION_FAN) && (this->mode != climate::CLIMATE_MODE_FAN_ONLY)) || - ((this->action == climate::CLIMATE_ACTION_DRYING) && (this->mode != climate::CLIMATE_MODE_DRY))) { - target_action = climate::CLIMATE_ACTION_IDLE; - } - - switch (this->mode) { - case climate::CLIMATE_MODE_FAN_ONLY: - if (this->supports_fan_only_) { - if (this->current_temperature > this->target_temperature_high + this->hysteresis_) - target_action = climate::CLIMATE_ACTION_FAN; - else if (this->current_temperature < this->target_temperature_high - this->hysteresis_) - if (this->action == climate::CLIMATE_ACTION_FAN) - target_action = climate::CLIMATE_ACTION_IDLE; - } - break; - case climate::CLIMATE_MODE_DRY: - target_action = climate::CLIMATE_ACTION_DRYING; - break; - case climate::CLIMATE_MODE_HEAT_COOL: - case climate::CLIMATE_MODE_COOL: - case climate::CLIMATE_MODE_HEAT: - if (this->supports_cool_) { - if (this->current_temperature > this->target_temperature_high + this->hysteresis_) - target_action = climate::CLIMATE_ACTION_COOLING; - else if (this->current_temperature < this->target_temperature_high - this->hysteresis_) - if (this->action == climate::CLIMATE_ACTION_COOLING) - target_action = climate::CLIMATE_ACTION_IDLE; - } - if (this->supports_heat_) { - if (this->current_temperature < this->target_temperature_low - this->hysteresis_) - target_action = climate::CLIMATE_ACTION_HEATING; - else if (this->current_temperature > this->target_temperature_low + this->hysteresis_) - if (this->action == climate::CLIMATE_ACTION_HEATING) - target_action = climate::CLIMATE_ACTION_IDLE; - } - break; - default: - break; - } - } else { - if (isnan(this->current_temperature) || isnan(this->target_temperature) || isnan(this->hysteresis_)) - // if any control parameters are nan, go to OFF action (not IDLE!) - return climate::CLIMATE_ACTION_OFF; - - if (((this->action == climate::CLIMATE_ACTION_FAN) && (this->mode != climate::CLIMATE_MODE_FAN_ONLY)) || - ((this->action == climate::CLIMATE_ACTION_DRYING) && (this->mode != climate::CLIMATE_MODE_DRY))) { - target_action = climate::CLIMATE_ACTION_IDLE; - } - - switch (this->mode) { - case climate::CLIMATE_MODE_FAN_ONLY: - if (this->supports_fan_only_) { - if (this->current_temperature > this->target_temperature + this->hysteresis_) - target_action = climate::CLIMATE_ACTION_FAN; - else if (this->current_temperature < this->target_temperature - this->hysteresis_) - if (this->action == climate::CLIMATE_ACTION_FAN) - target_action = climate::CLIMATE_ACTION_IDLE; - } - break; - case climate::CLIMATE_MODE_DRY: - target_action = climate::CLIMATE_ACTION_DRYING; - break; - case climate::CLIMATE_MODE_COOL: - if (this->supports_cool_) { - if (this->current_temperature > this->target_temperature + this->hysteresis_) - target_action = climate::CLIMATE_ACTION_COOLING; - else if (this->current_temperature < this->target_temperature - this->hysteresis_) - if (this->action == climate::CLIMATE_ACTION_COOLING) - target_action = climate::CLIMATE_ACTION_IDLE; - } - case climate::CLIMATE_MODE_HEAT: - if (this->supports_heat_) { - if (this->current_temperature < this->target_temperature - this->hysteresis_) - target_action = climate::CLIMATE_ACTION_HEATING; - else if (this->current_temperature > this->target_temperature + this->hysteresis_) - if (this->action == climate::CLIMATE_ACTION_HEATING) - target_action = climate::CLIMATE_ACTION_IDLE; - } - break; - default: - break; - } + // ensure set point(s) is/are valid before computing the action + this->validate_target_temperatures(); + // everything has been validated so we can now safely compute the action + switch (this->mode) { + // if the climate mode is OFF then the climate action must be OFF + case climate::CLIMATE_MODE_OFF: + target_action = climate::CLIMATE_ACTION_OFF; + break; + case climate::CLIMATE_MODE_FAN_ONLY: + if (this->fanning_required_()) + target_action = climate::CLIMATE_ACTION_FAN; + break; + case climate::CLIMATE_MODE_DRY: + target_action = climate::CLIMATE_ACTION_DRYING; + break; + case climate::CLIMATE_MODE_HEAT_COOL: + if (this->cooling_required_() && this->heating_required_()) { + // this is bad and should never happen, so just stop. + // target_action = climate::CLIMATE_ACTION_IDLE; + } else if (this->cooling_required_()) { + target_action = climate::CLIMATE_ACTION_COOLING; + } else if (this->heating_required_()) { + target_action = climate::CLIMATE_ACTION_HEATING; + } + break; + case climate::CLIMATE_MODE_COOL: + if (this->cooling_required_()) { + target_action = climate::CLIMATE_ACTION_COOLING; + } + break; + case climate::CLIMATE_MODE_HEAT: + if (this->heating_required_()) { + target_action = climate::CLIMATE_ACTION_HEATING; + } + break; + default: + break; } - // do not switch to an action that isn't enabled per the active climate mode - if ((this->mode == climate::CLIMATE_MODE_COOL) && (target_action == climate::CLIMATE_ACTION_HEATING)) - target_action = climate::CLIMATE_ACTION_IDLE; - if ((this->mode == climate::CLIMATE_MODE_HEAT) && (target_action == climate::CLIMATE_ACTION_COOLING)) - target_action = climate::CLIMATE_ACTION_IDLE; - return target_action; } + void ThermostatClimate::switch_to_action_(climate::ClimateAction action) { // setup_complete_ helps us ensure an action is called immediately after boot if ((action == this->action) && this->setup_complete_) @@ -284,6 +297,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action) { this->action = action; this->prev_action_trigger_ = trig; } + void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { // setup_complete_ helps us ensure an action is called immediately after boot if ((fan_mode == this->prev_fan_mode_) && this->setup_complete_) @@ -335,6 +349,7 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { this->prev_fan_mode_ = fan_mode; this->prev_fan_mode_trigger_ = trig; } + void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) { // setup_complete_ helps us ensure an action is called immediately after boot if ((mode == this->prev_mode_) && this->setup_complete_) @@ -377,6 +392,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) { this->prev_mode_ = mode; this->prev_mode_trigger_ = trig; } + void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mode) { // setup_complete_ helps us ensure an action is called immediately after boot if ((swing_mode == this->prev_swing_mode_) && this->setup_complete_) @@ -413,6 +429,7 @@ void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mo this->prev_swing_mode_ = swing_mode; this->prev_swing_mode_trigger_ = trig; } + void ThermostatClimate::check_temperature_change_trigger_() { if (this->supports_two_points_) { // setup_complete_ helps us ensure an action is called immediately after boot @@ -437,6 +454,70 @@ void ThermostatClimate::check_temperature_change_trigger_() { assert(trig != nullptr); trig->trigger(); } + +bool ThermostatClimate::cooling_required_() { + auto temperature = this->supports_two_points_ ? this->target_temperature_high : this->target_temperature; + + if (this->supports_cool_) { + if (this->current_temperature > (temperature + this->cool_deadband_)) { + // if the current temperature exceeds the target + deadband, cooling is required + return true; + } else if (this->current_temperature < (temperature - this->cool_overrun_)) { + // if the current temperature is less than the target - overrun, cooling should stop + return false; + } else { + // if we get here, the current temperature is between target + deadband and target - overrun, + // so the action should not change unless it conflicts with the current mode + return (this->action == climate::CLIMATE_ACTION_COOLING) && + ((this->mode == climate::CLIMATE_MODE_HEAT_COOL) || (this->mode == climate::CLIMATE_MODE_COOL)); + } + } + return false; +} + +bool ThermostatClimate::fanning_required_() { + auto temperature = this->supports_two_points_ ? this->target_temperature_high : this->target_temperature; + + if (this->supports_fan_only_) { + if (this->supports_fan_only_cooling_) { + if (this->current_temperature > (temperature + this->cool_deadband_)) { + // if the current temperature exceeds the target + deadband, fanning is required + return true; + } else if (this->current_temperature < (temperature - this->cool_overrun_)) { + // if the current temperature is less than the target - overrun, fanning should stop + return false; + } else { + // if we get here, the current temperature is between target + deadband and target - overrun, + // so the action should not change unless it conflicts with the current mode + return (this->action == climate::CLIMATE_ACTION_FAN) && (this->mode == climate::CLIMATE_MODE_FAN_ONLY); + } + } else { + return true; + } + } + return false; +} + +bool ThermostatClimate::heating_required_() { + auto temperature = this->supports_two_points_ ? this->target_temperature_low : this->target_temperature; + + if (this->supports_heat_) { + if (this->current_temperature < temperature - this->heat_deadband_) { + // if the current temperature is below the target - deadband, heating is required + return true; + } else if (this->current_temperature > temperature + this->heat_overrun_) { + // if the current temperature is above the target + overrun, heating should stop + return false; + } else { + // if we get here, the current temperature is between target - deadband and target + overrun, + // so the action should not change unless it conflicts with the current mode + return (this->action == climate::CLIMATE_ACTION_HEATING) && + ((this->mode == climate::CLIMATE_MODE_HEAT_COOL) || (this->mode == climate::CLIMATE_MODE_HEAT)); + } + } + return false; +} + void ThermostatClimate::change_away_(bool away) { if (!away) { if (this->supports_two_points_) { @@ -453,13 +534,16 @@ void ThermostatClimate::change_away_(bool away) { } this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME; } + void ThermostatClimate::set_normal_config(const ThermostatClimateTargetTempConfig &normal_config) { this->normal_config_ = normal_config; } + void ThermostatClimate::set_away_config(const ThermostatClimateTargetTempConfig &away_config) { this->supports_away_ = true; this->away_config_ = away_config; } + ThermostatClimate::ThermostatClimate() : cool_action_trigger_(new Trigger<>()), cool_mode_trigger_(new Trigger<>()), @@ -486,8 +570,15 @@ ThermostatClimate::ThermostatClimate() swing_mode_horizontal_trigger_(new Trigger<>()), swing_mode_vertical_trigger_(new Trigger<>()), temperature_change_trigger_(new Trigger<>()) {} + void ThermostatClimate::set_default_mode(climate::ClimateMode default_mode) { this->default_mode_ = default_mode; } -void ThermostatClimate::set_hysteresis(float hysteresis) { this->hysteresis_ = hysteresis; } +void ThermostatClimate::set_set_point_minimum_differential(float differential) { + this->set_point_minimum_differential_ = differential; +} +void ThermostatClimate::set_cool_deadband(float deadband) { this->cool_deadband_ = deadband; } +void ThermostatClimate::set_cool_overrun(float overrun) { this->cool_overrun_ = overrun; } +void ThermostatClimate::set_heat_deadband(float deadband) { this->heat_deadband_ = deadband; } +void ThermostatClimate::set_heat_overrun(float overrun) { this->heat_overrun_ = overrun; } void ThermostatClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } void ThermostatClimate::set_supports_heat_cool(bool supports_heat_cool) { this->supports_heat_cool_ = supports_heat_cool; @@ -496,6 +587,9 @@ void ThermostatClimate::set_supports_auto(bool supports_auto) { this->supports_a void ThermostatClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } void ThermostatClimate::set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; } void ThermostatClimate::set_supports_fan_only(bool supports_fan_only) { this->supports_fan_only_ = supports_fan_only; } +void ThermostatClimate::set_supports_fan_only_cooling(bool supports_fan_only_cooling) { + this->supports_fan_only_cooling_ = supports_fan_only_cooling; +} void ThermostatClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } void ThermostatClimate::set_supports_fan_mode_on(bool supports_fan_mode_on) { this->supports_fan_mode_on_ = supports_fan_mode_on; @@ -539,6 +633,7 @@ void ThermostatClimate::set_supports_swing_mode_vertical(bool supports_swing_mod void ThermostatClimate::set_supports_two_points(bool supports_two_points) { this->supports_two_points_ = supports_two_points; } + Trigger<> *ThermostatClimate::get_cool_action_trigger() const { return this->cool_action_trigger_; } Trigger<> *ThermostatClimate::get_dry_action_trigger() const { return this->dry_action_trigger_; } Trigger<> *ThermostatClimate::get_fan_only_action_trigger() const { return this->fan_only_action_trigger_; } @@ -564,6 +659,7 @@ Trigger<> *ThermostatClimate::get_swing_mode_off_trigger() const { return this-> Trigger<> *ThermostatClimate::get_swing_mode_horizontal_trigger() const { return this->swing_mode_horizontal_trigger_; } Trigger<> *ThermostatClimate::get_swing_mode_vertical_trigger() const { return this->swing_mode_vertical_trigger_; } Trigger<> *ThermostatClimate::get_temperature_change_trigger() const { return this->temperature_change_trigger_; } + void ThermostatClimate::dump_config() { LOG_CLIMATE("", "Thermostat", this); if (this->supports_heat_) { @@ -578,12 +674,17 @@ void ThermostatClimate::dump_config() { else ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C", this->normal_config_.default_temperature); } - ESP_LOGCONFIG(TAG, " Hysteresis: %.1f°C", this->hysteresis_); + ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_); + ESP_LOGCONFIG(TAG, " Cool Deadband: %.1f°C", this->cool_deadband_); + ESP_LOGCONFIG(TAG, " Cool Overrun: %.1f°C", this->cool_overrun_); + ESP_LOGCONFIG(TAG, " Heat Deadband: %.1f°C", this->heat_deadband_); + ESP_LOGCONFIG(TAG, " Heat Overrun: %.1f°C", this->heat_overrun_); ESP_LOGCONFIG(TAG, " Supports AUTO: %s", YESNO(this->supports_auto_)); ESP_LOGCONFIG(TAG, " Supports HEAT/COOL: %s", YESNO(this->supports_heat_cool_)); ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_)); ESP_LOGCONFIG(TAG, " Supports DRY: %s", YESNO(this->supports_dry_)); ESP_LOGCONFIG(TAG, " Supports FAN_ONLY: %s", YESNO(this->supports_fan_only_)); + ESP_LOGCONFIG(TAG, " Supports FAN_ONLY_COOLING: %s", YESNO(this->supports_fan_only_cooling_)); ESP_LOGCONFIG(TAG, " Supports HEAT: %s", YESNO(this->supports_heat_)); ESP_LOGCONFIG(TAG, " Supports FAN MODE ON: %s", YESNO(this->supports_fan_mode_on_)); ESP_LOGCONFIG(TAG, " Supports FAN MODE OFF: %s", YESNO(this->supports_fan_mode_off_)); @@ -619,8 +720,10 @@ void ThermostatClimate::dump_config() { } ThermostatClimateTargetTempConfig::ThermostatClimateTargetTempConfig() = default; + ThermostatClimateTargetTempConfig::ThermostatClimateTargetTempConfig(float default_temperature) : default_temperature(default_temperature) {} + ThermostatClimateTargetTempConfig::ThermostatClimateTargetTempConfig(float default_temperature_low, float default_temperature_high) : default_temperature_low(default_temperature_low), default_temperature_high(default_temperature_high) {} diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index bff9e9bdc1..67f002a22a 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -17,7 +17,10 @@ struct ThermostatClimateTargetTempConfig { float default_temperature{NAN}; float default_temperature_low{NAN}; float default_temperature_high{NAN}; - float hysteresis{NAN}; + float cool_deadband_{NAN}; + float cool_overrun_{NAN}; + float heat_deadband_{NAN}; + float heat_overrun_{NAN}; }; class ThermostatClimate : public climate::Climate, public Component { @@ -27,13 +30,18 @@ class ThermostatClimate : public climate::Climate, public Component { void dump_config() override; void set_default_mode(climate::ClimateMode default_mode); - void set_hysteresis(float hysteresis); + void set_set_point_minimum_differential(float differential); + void set_cool_deadband(float deadband); + void set_cool_overrun(float overrun); + void set_heat_deadband(float deadband); + void set_heat_overrun(float overrun); void set_sensor(sensor::Sensor *sensor); void set_supports_auto(bool supports_auto); void set_supports_heat_cool(bool supports_heat_cool); void set_supports_cool(bool supports_cool); void set_supports_dry(bool supports_dry); void set_supports_fan_only(bool supports_fan_only); + void set_supports_fan_only_cooling(bool supports_fan_only_cooling); void set_supports_heat(bool supports_heat); void set_supports_fan_mode_on(bool supports_fan_mode_on); void set_supports_fan_mode_off(bool supports_fan_mode_off); @@ -78,10 +86,19 @@ class ThermostatClimate : public climate::Climate, public Component { Trigger<> *get_swing_mode_off_trigger() const; Trigger<> *get_swing_mode_vertical_trigger() const; Trigger<> *get_temperature_change_trigger() const; - /// Get current hysteresis value - float hysteresis(); + /// Get current hysteresis values + float cool_deadband(); + float cool_overrun(); + float heat_deadband(); + float heat_overrun(); /// Call triggers based on updated climate states (modes/actions) void refresh(); + /// Set point and hysteresis validation + bool hysteresis_valid(); // returns true if valid + void validate_target_temperature(); + void validate_target_temperatures(); + void validate_target_temperature_low(); + void validate_target_temperature_high(); protected: /// Override control to change settings of the climate device. @@ -111,6 +128,11 @@ class ThermostatClimate : public climate::Climate, public Component { /// Check if the temperature change trigger should be called. void check_temperature_change_trigger_(); + /// Check if cooling/fanning/heating actions are required; returns true if so + bool cooling_required_(); + bool fanning_required_(); + bool heating_required_(); + /// The sensor used for getting the current temperature sensor::Sensor *sensor_{nullptr}; @@ -124,6 +146,8 @@ class ThermostatClimate : public climate::Climate, public Component { bool supports_dry_{false}; bool supports_fan_only_{false}; bool supports_heat_{false}; + /// Special flag -- enables fan to be switched based on target_temperature_high + bool supports_fan_only_cooling_{false}; /// Whether the controller supports turning on or off just the fan. /// @@ -278,8 +302,14 @@ class ThermostatClimate : public climate::Climate, public Component { ThermostatClimateTargetTempConfig normal_config_{}; ThermostatClimateTargetTempConfig away_config_{}; - /// Hysteresis value used for computing climate actions - float hysteresis_{0}; + /// Minimum differential required between set points + float set_point_minimum_differential_{0}; + + /// Hysteresis values used for computing climate actions + float cool_deadband_{0}; + float cool_overrun_{0}; + float heat_deadband_{0}; + float heat_overrun_{0}; /// setup_complete_ blocks modifying/resetting the temps immediately after boot bool setup_complete_{false}; diff --git a/esphome/const.py b/esphome/const.py index 8327921211..1ed8aeb402 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -134,7 +134,9 @@ CONF_CONDITION_ID = "condition_id" CONF_CONDUCTIVITY = "conductivity" CONF_CONTRAST = "contrast" CONF_COOL_ACTION = "cool_action" +CONF_COOL_DEADBAND = "cool_deadband" CONF_COOL_MODE = "cool_mode" +CONF_COOL_OVERRUN = "cool_overrun" CONF_COUNT = "count" CONF_COUNT_MODE = "count_mode" CONF_COURSE = "course" @@ -219,6 +221,7 @@ CONF_FAN_MODE_MIDDLE_ACTION = "fan_mode_middle_action" CONF_FAN_MODE_OFF_ACTION = "fan_mode_off_action" CONF_FAN_MODE_ON_ACTION = "fan_mode_on_action" CONF_FAN_ONLY_ACTION = "fan_only_action" +CONF_FAN_ONLY_COOLING = "fan_only_cooling" CONF_FAN_ONLY_MODE = "fan_only_mode" CONF_FAST_CONNECT = "fast_connect" CONF_FILE = "file" @@ -248,7 +251,9 @@ CONF_GROUP = "group" CONF_HARDWARE_UART = "hardware_uart" CONF_HEARTBEAT = "heartbeat" CONF_HEAT_ACTION = "heat_action" +CONF_HEAT_DEADBAND = "heat_deadband" CONF_HEAT_MODE = "heat_mode" +CONF_HEAT_OVERRUN = "heat_overrun" CONF_HEATER = "heater" CONF_HEIGHT = "height" CONF_HIDDEN = "hidden" @@ -541,6 +546,7 @@ CONF_SERVERS = "servers" CONF_SERVICE = "service" CONF_SERVICE_UUID = "service_uuid" CONF_SERVICES = "services" +CONF_SET_POINT_MINIMUM_DIFFERENTIAL = "set_point_minimum_differential" CONF_SETUP_MODE = "setup_mode" CONF_SETUP_PRIORITY = "setup_priority" CONF_SHUNT_RESISTANCE = "shunt_resistance" From 4c8a70308445956615070af7a51d2da5566e4c91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Aug 2021 08:49:45 +0200 Subject: [PATCH 1122/1841] Bump esptool from 2.8 to 3.1 (#1839) Bumps [esptool](https://github.com/espressif/esptool) from 2.8 to 3.1. - [Release notes](https://github.com/espressif/esptool/releases) - [Commits](https://github.com/espressif/esptool/compare/v2.8...v3.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 561bf0f4d5..b4d557f06e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,6 @@ pytz==2021.1 pyserial==3.5 ifaddr==0.1.7 platformio==5.1.1 -esptool==2.8 +esptool==3.1 click==7.1.2 esphome-dashboard==20210728.0 From 6516c64e677e9def52f0e3a09d7ca7d0c287a5b9 Mon Sep 17 00:00:00 2001 From: Andreas Hergert <36455093+andreashergert1984@users.noreply.github.com> Date: Tue, 3 Aug 2021 16:41:34 +0200 Subject: [PATCH 1123/1841] Add min_save_interval to total_energy/integration for memory wear (#1665) Co-authored-by: Andreas Hergert Co-authored-by: Otto Winter --- esphome/components/integration/integration_sensor.cpp | 2 ++ esphome/components/integration/integration_sensor.h | 7 +++++++ esphome/components/integration/sensor.py | 6 ++++++ esphome/components/total_daily_energy/sensor.py | 5 +++++ .../components/total_daily_energy/total_daily_energy.cpp | 8 +++++++- .../components/total_daily_energy/total_daily_energy.h | 3 +++ tests/test1.yaml | 5 +++++ 7 files changed, 35 insertions(+), 1 deletion(-) diff --git a/esphome/components/integration/integration_sensor.cpp b/esphome/components/integration/integration_sensor.cpp index bfcf8d3561..9a0f9fc58b 100644 --- a/esphome/components/integration/integration_sensor.cpp +++ b/esphome/components/integration/integration_sensor.cpp @@ -16,6 +16,8 @@ void IntegrationSensor::setup() { } this->last_update_ = millis(); + this->last_save_ = this->last_update_; + this->publish_and_save_(this->result_); this->sensor_->add_on_state_callback([this](float state) { this->process_sensor_value_(state); }); } diff --git a/esphome/components/integration/integration_sensor.h b/esphome/components/integration/integration_sensor.h index 2fcec069b2..c575de094c 100644 --- a/esphome/components/integration/integration_sensor.h +++ b/esphome/components/integration/integration_sensor.h @@ -27,6 +27,7 @@ class IntegrationSensor : public sensor::Sensor, public Component { void setup() override; void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } + void set_min_save_interval(uint32_t min_interval) { this->min_save_interval_ = min_interval; } void set_sensor(Sensor *sensor) { sensor_ = sensor; } void set_time(IntegrationSensorTime time) { time_ = time; } void set_method(IntegrationMethod method) { method_ = method; } @@ -55,6 +56,10 @@ class IntegrationSensor : public sensor::Sensor, public Component { this->result_ = result; this->publish_state(result); float result_f = result; + const uint32_t now = millis(); + if (now - this->last_save_ < this->min_save_interval_) + return; + this->last_save_ = now; this->rtc_.save(&result_f); } std::string unit_of_measurement() override; @@ -67,6 +72,8 @@ class IntegrationSensor : public sensor::Sensor, public Component { bool restore_; ESPPreferenceObject rtc_; + uint32_t last_save_{0}; + uint32_t min_save_interval_{0}; uint32_t last_update_; double result_{0.0f}; float last_value_{0.0f}; diff --git a/esphome/components/integration/sensor.py b/esphome/components/integration/sensor.py index 3f32394ff6..460dd46619 100644 --- a/esphome/components/integration/sensor.py +++ b/esphome/components/integration/sensor.py @@ -27,6 +27,8 @@ INTEGRATION_METHODS = { CONF_TIME_UNIT = "time_unit" CONF_INTEGRATION_METHOD = "integration_method" +CONF_MIN_SAVE_INTERVAL = "min_save_interval" + CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( { @@ -37,6 +39,9 @@ CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( INTEGRATION_METHODS, lower=True ), cv.Optional(CONF_RESTORE, default=False): cv.boolean, + cv.Optional( + CONF_MIN_SAVE_INTERVAL, default="0s" + ): cv.positive_time_period_milliseconds, } ).extend(cv.COMPONENT_SCHEMA) @@ -52,6 +57,7 @@ async def to_code(config): cg.add(var.set_time(config[CONF_TIME_UNIT])) cg.add(var.set_method(config[CONF_INTEGRATION_METHOD])) cg.add(var.set_restore(config[CONF_RESTORE])) + cg.add(var.set_min_save_interval(config[CONF_MIN_SAVE_INTERVAL])) @automation.register_action( diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index df823d2997..ec38daaf57 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -12,6 +12,7 @@ from esphome.const import ( DEPENDENCIES = ["time"] CONF_POWER_ID = "power_id" +CONF_MIN_SAVE_INTERVAL = "min_save_interval" total_daily_energy_ns = cg.esphome_ns.namespace("total_daily_energy") TotalDailyEnergy = total_daily_energy_ns.class_( "TotalDailyEnergy", sensor.Sensor, cg.Component @@ -29,6 +30,9 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(TotalDailyEnergy), cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), + cv.Optional( + CONF_MIN_SAVE_INTERVAL, default="0s" + ): cv.positive_time_period_milliseconds, } ) .extend(cv.COMPONENT_SCHEMA) @@ -45,3 +49,4 @@ async def to_code(config): cg.add(var.set_parent(sens)) time_ = await cg.get_variable(config[CONF_TIME_ID]) cg.add(var.set_time(time_)) + cg.add(var.set_min_save_interval(config[CONF_MIN_SAVE_INTERVAL])) diff --git a/esphome/components/total_daily_energy/total_daily_energy.cpp b/esphome/components/total_daily_energy/total_daily_energy.cpp index 8c5ef8c137..1e60442ae7 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.cpp +++ b/esphome/components/total_daily_energy/total_daily_energy.cpp @@ -16,6 +16,7 @@ void TotalDailyEnergy::setup() { this->publish_state_and_save(0); } this->last_update_ = millis(); + this->last_save_ = this->last_update_; this->parent_->add_on_state_callback([this](float state) { this->process_new_state_(state); }); } @@ -37,9 +38,14 @@ void TotalDailyEnergy::loop() { } } void TotalDailyEnergy::publish_state_and_save(float state) { - this->pref_.save(&state); this->total_energy_ = state; this->publish_state(state); + const uint32_t now = millis(); + if (now - this->last_save_ < this->min_save_interval_) { + return; + } + this->last_save_ = now; + this->pref_.save(&state); } void TotalDailyEnergy::process_new_state_(float state) { if (isnan(state)) diff --git a/esphome/components/total_daily_energy/total_daily_energy.h b/esphome/components/total_daily_energy/total_daily_energy.h index ae44125ffb..123446c534 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.h +++ b/esphome/components/total_daily_energy/total_daily_energy.h @@ -10,6 +10,7 @@ namespace total_daily_energy { class TotalDailyEnergy : public sensor::Sensor, public Component { public: + void set_min_save_interval(uint32_t min_interval) { this->min_save_interval_ = min_interval; } void set_time(time::RealTimeClock *time) { time_ = time; } void set_parent(Sensor *parent) { parent_ = parent; } void setup() override; @@ -30,6 +31,8 @@ class TotalDailyEnergy : public sensor::Sensor, public Component { Sensor *parent_; uint16_t last_day_of_year_{}; uint32_t last_update_{0}; + uint32_t last_save_{0}; + uint32_t min_save_interval_{0}; float total_energy_{0.0f}; }; diff --git a/tests/test1.yaml b/tests/test1.yaml index b25852c93d..c1f3e378c4 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -540,6 +540,11 @@ sensor: sensor: hlw8012_power name: 'Integration Sensor' time_unit: s + - platform: integration + sensor: hlw8012_power + name: 'Integration Sensor lazy' + time_unit: s + min_save_interval: 60s - platform: hmc5883l address: 0x68 field_strength_x: From 160429eb24d08053be64ffb70a31aca82e001c5f Mon Sep 17 00:00:00 2001 From: Brett Profitt Date: Tue, 3 Aug 2021 10:41:53 -0400 Subject: [PATCH 1124/1841] Add support for Waveshare E-Paper 4.2" B V2 (#1610) Co-authored-by: Otto winter --- .../components/waveshare_epaper/display.py | 4 ++ .../waveshare_epaper/waveshare_epaper.cpp | 56 +++++++++++++++++++ .../waveshare_epaper/waveshare_epaper.h | 29 ++++++++++ 3 files changed, 89 insertions(+) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 3e1132bb1d..a1a81e0997 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -31,6 +31,9 @@ WaveshareEPaper2P9InB = waveshare_epaper_ns.class_( WaveshareEPaper4P2In = waveshare_epaper_ns.class_( "WaveshareEPaper4P2In", WaveshareEPaper ) +WaveshareEPaper4P2InBV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper4P2InBV2", WaveshareEPaper +) WaveshareEPaper5P8In = waveshare_epaper_ns.class_( "WaveshareEPaper5P8In", WaveshareEPaper ) @@ -56,6 +59,7 @@ MODELS = { "2.70in": ("b", WaveshareEPaper2P7In), "2.90in-b": ("b", WaveshareEPaper2P9InB), "4.20in": ("b", WaveshareEPaper4P2In), + "4.20in-bv2": ("b", WaveshareEPaper4P2InBV2), "5.83in": ("b", WaveshareEPaper5P8In), "7.50in": ("b", WaveshareEPaper7P5In), "7.50inv2": ("b", WaveshareEPaper7P5InV2), diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 458f04c674..cb1a158332 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -765,6 +765,62 @@ void WaveshareEPaper4P2In::dump_config() { LOG_UPDATE_INTERVAL(this); } +// ======================================================== +// 4.20in Type B (LUT from OTP) +// Datasheet: +// - https://www.waveshare.com/w/upload/2/20/4.2inch-e-paper-module-user-manual-en.pdf +// - https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_4in2b_V2.c +// ======================================================== +void WaveshareEPaper4P2InBV2::initialize() { + // these exact timings are required for a proper reset/init + this->reset_pin_->digital_write(false); + delay(2); + this->reset_pin_->digital_write(true); + delay(200); // NOLINT + + // COMMAND POWER ON + this->command(0x04); + this->wait_until_idle_(); + + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0x0f); // LUT from OTP +} + +void HOT WaveshareEPaper4P2InBV2::display() { + // COMMAND DATA START TRANSMISSION 1 (B/W data) + this->command(0x10); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // COMMAND DATA START TRANSMISSION 2 (RED data) + this->command(0x13); + this->start_data_(); + for (int i = 0; i < this->get_buffer_length_(); i++) + this->write_byte(0xFF); + this->end_data_(); + delay(2); + + // COMMAND DISPLAY REFRESH + this->command(0x12); + this->wait_until_idle_(); + + // COMMAND POWER OFF + // NOTE: power off < deep sleep + this->command(0x02); +} +int WaveshareEPaper4P2InBV2::get_width_internal() { return 400; } +int WaveshareEPaper4P2InBV2::get_height_internal() { return 300; } +void WaveshareEPaper4P2InBV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 4.2in (B V2)"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + void WaveshareEPaper5P8In::initialize() { // COMMAND POWER SETTING this->command(0x01); diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 8ab77d653b..302238af7e 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -115,6 +115,7 @@ class WaveshareEPaperTypeA : public WaveshareEPaper { enum WaveshareEPaperTypeBModel { WAVESHARE_EPAPER_2_7_IN = 0, WAVESHARE_EPAPER_4_2_IN, + WAVESHARE_EPAPER_4_2_IN_B_V2, WAVESHARE_EPAPER_7_5_IN, WAVESHARE_EPAPER_7_5_INV2, }; @@ -202,6 +203,34 @@ class WaveshareEPaper4P2In : public WaveshareEPaper { int get_height_internal() override; }; +class WaveshareEPaper4P2InBV2 : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0xF7); // border floating + + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check code + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + class WaveshareEPaper5P8In : public WaveshareEPaper { public: void initialize() override; From 39f64f597e22185e173212a4cbd4bc72bb7ca578 Mon Sep 17 00:00:00 2001 From: Rob Gridley Date: Tue, 3 Aug 2021 10:56:29 -0400 Subject: [PATCH 1125/1841] Add SM16703 to supported FastLED chipsets (#1751) Co-authored-by: Otto winter --- esphome/components/fastled_clockless/light.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/fastled_clockless/light.py b/esphome/components/fastled_clockless/light.py index cfc62e930b..d437d01dcf 100644 --- a/esphome/components/fastled_clockless/light.py +++ b/esphome/components/fastled_clockless/light.py @@ -31,6 +31,7 @@ CHIPSETS = [ "GW6205_400", "LPD1886", "LPD1886_8BIT", + "SM16703", ] From 9fa19df2ffed83c51b9516600d3e5245f9093146 Mon Sep 17 00:00:00 2001 From: brambo123 <52667932+brambo123@users.noreply.github.com> Date: Tue, 3 Aug 2021 19:56:23 +0200 Subject: [PATCH 1126/1841] Fix time.on_time triggering if time jumped back (#1806) Co-authored-by: Otto winter --- esphome/components/time/automation.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/time/automation.cpp b/esphome/components/time/automation.cpp index 6d34459fea..84cc76a762 100644 --- a/esphome/components/time/automation.cpp +++ b/esphome/components/time/automation.cpp @@ -22,7 +22,10 @@ void CronTrigger::loop() { return; if (this->last_check_.has_value()) { - if (*this->last_check_ >= time) { + if (*this->last_check_ > time && this->last_check_->timestamp - time.timestamp > 900) { + // We went back in time (a lot), probably caused by time synchronization + ESP_LOGW(TAG, "Time has jumped back!"); + } else if (*this->last_check_ >= time) { // already handled this one return; } From 441d5bd44da67cebac7446b89a22cf2fee60f183 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 3 Aug 2021 23:21:57 +0200 Subject: [PATCH 1127/1841] Migrate COLOR constants to Color class & disallow implicit conversions to Color (#2093) Co-authored-by: Xo Wang --- .../adalight/adalight_light_effect.cpp | 2 +- esphome/components/display/display_buffer.cpp | 12 ++++++------ esphome/components/ili9341/ili9341_display.cpp | 4 ++-- esphome/components/light/addressable_light.h | 2 +- .../light/addressable_light_effect.h | 6 +++--- .../components/ssd1306_base/ssd1306_base.cpp | 6 ++---- .../components/ssd1322_base/ssd1322_base.cpp | 6 +++--- .../components/ssd1325_base/ssd1325_base.cpp | 8 +++----- .../components/ssd1327_base/ssd1327_base.cpp | 6 +++--- .../components/ssd1331_base/ssd1331_base.cpp | 8 +++----- .../components/ssd1351_base/ssd1351_base.cpp | 8 +++----- esphome/components/wled/wled_light_effect.cpp | 2 +- esphome/core/color.cpp | 8 ++++++++ esphome/core/color.h | 18 ++++++++++++------ 14 files changed, 51 insertions(+), 45 deletions(-) create mode 100644 esphome/core/color.cpp diff --git a/esphome/components/adalight/adalight_light_effect.cpp b/esphome/components/adalight/adalight_light_effect.cpp index 63fd7c60cc..5f60fbe0b2 100644 --- a/esphome/components/adalight/adalight_light_effect.cpp +++ b/esphome/components/adalight/adalight_light_effect.cpp @@ -42,7 +42,7 @@ void AdalightLightEffect::reset_frame_(light::AddressableLight &it) { void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) { for (int led = it.size(); led-- > 0;) { - it[led].set(COLOR_BLACK); + it[led].set(Color::BLACK); } } diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 08050b2078..bc2d8d5ef0 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -461,7 +461,7 @@ bool Image::get_pixel(int x, int y) const { } Color Image::get_color_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) - return 0; + return Color::BLACK; const uint32_t pos = (x + y * this->width_) * 3; const uint32_t color32 = (pgm_read_byte(this->data_start_ + pos + 2) << 0) | (pgm_read_byte(this->data_start_ + pos + 1) << 8) | @@ -470,7 +470,7 @@ Color Image::get_color_pixel(int x, int y) const { } Color Image::get_grayscale_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) - return 0; + return Color::BLACK; const uint32_t pos = (x + y * this->width_); const uint8_t gray = pgm_read_byte(this->data_start_ + pos); return Color(gray | gray << 8 | gray << 16 | gray << 24); @@ -493,10 +493,10 @@ bool Animation::get_pixel(int x, int y) const { } Color Animation::get_color_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) - return 0; + return Color::BLACK; const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_; if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) - return 0; + return Color::BLACK; const uint32_t pos = (x + y * this->width_ + frame_index) * 3; const uint32_t color32 = (pgm_read_byte(this->data_start_ + pos + 2) << 0) | (pgm_read_byte(this->data_start_ + pos + 1) << 8) | @@ -505,10 +505,10 @@ Color Animation::get_color_pixel(int x, int y) const { } Color Animation::get_grayscale_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) - return 0; + return Color::BLACK; const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_; if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) - return 0; + return Color::BLACK; const uint32_t pos = (x + y * this->width_ + frame_index); const uint8_t gray = pgm_read_byte(this->data_start_ + pos); return Color(gray | gray << 8 | gray << 16 | gray << 24); diff --git a/esphome/components/ili9341/ili9341_display.cpp b/esphome/components/ili9341/ili9341_display.cpp index e973671acc..c06d487e7d 100644 --- a/esphome/components/ili9341/ili9341_display.cpp +++ b/esphome/components/ili9341/ili9341_display.cpp @@ -225,7 +225,7 @@ void ILI9341M5Stack::initialize() { this->width_ = 320; this->height_ = 240; this->invert_display_(true); - this->fill_internal_(COLOR_BLACK); + this->fill_internal_(Color::BLACK); } // 24_TFT display @@ -233,7 +233,7 @@ void ILI9341TFT24::initialize() { this->init_lcd_(INITCMD_TFT); this->width_ = 240; this->height_ = 320; - this->fill_internal_(COLOR_BLACK); + this->fill_internal_(Color::BLACK); } } // namespace ili9341 diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index f64abb3eec..fe74d2e9b5 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -16,7 +16,7 @@ namespace esphome { namespace light { -using ESPColor = Color; +using ESPColor ESPDEPRECATED("esphome::light::ESPColor is deprecated, use esphome::Color instead.") = Color; class AddressableLight : public LightOutput, public Component { public: diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index 25493a30ea..3a2ba66845 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -151,7 +151,7 @@ class AddressableScanEffect : public AddressableLightEffect { void set_move_interval(uint32_t move_interval) { this->move_interval_ = move_interval; } void set_scan_width(uint32_t scan_width) { this->scan_width_ = scan_width; } void apply(AddressableLight &it, const Color ¤t_color) override { - it.all() = COLOR_BLACK; + it.all() = Color::BLACK; for (auto i = 0; i < this->scan_width_; i++) { it[this->at_led_ + i] = current_color; @@ -201,7 +201,7 @@ class AddressableTwinkleEffect : public AddressableLightEffect { else view.set_effect_data(new_pos); } else { - view = COLOR_BLACK; + view = Color::BLACK; } } while (random_float() < this->twinkle_probability_) { @@ -272,7 +272,7 @@ class AddressableFireworksEffect : public AddressableLightEffect { explicit AddressableFireworksEffect(const std::string &name) : AddressableLightEffect(name) {} void start() override { auto &it = *this->get_addressable_(); - it.all() = COLOR_BLACK; + it.all() = Color::BLACK; } void apply(AddressableLight &it, const Color ¤t_color) override { const uint32_t now = millis(); diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index 10e66df784..d321933e8f 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -7,8 +7,6 @@ namespace ssd1306_base { static const char *const TAG = "ssd1306"; -static const uint8_t BLACK = 0; -static const uint8_t WHITE = 1; static const uint8_t SSD1306_MAX_CONTRAST = 255; static const uint8_t SSD1306_COMMAND_DISPLAY_OFF = 0xAE; @@ -89,8 +87,8 @@ void SSD1306::setup() { set_brightness(this->brightness_); - this->fill(BLACK); // clear display - ensures we do not see garbage at power-on - this->display(); // ...write buffer, which actually clears the display's memory + this->fill(Color::BLACK); // clear display - ensures we do not see garbage at power-on + this->display(); // ...write buffer, which actually clears the display's memory this->turn_on(); } diff --git a/esphome/components/ssd1322_base/ssd1322_base.cpp b/esphome/components/ssd1322_base/ssd1322_base.cpp index 007f61d5c8..520248a66e 100644 --- a/esphome/components/ssd1322_base/ssd1322_base.cpp +++ b/esphome/components/ssd1322_base/ssd1322_base.cpp @@ -106,9 +106,9 @@ void SSD1322::setup() { this->data(180); this->command(SSD1322_ENABLEGRAYSCALETABLE); set_brightness(this->brightness_); - this->fill(COLOR_BLACK); // clear display - ensures we do not see garbage at power-on - this->display(); // ...write buffer, which actually clears the display's memory - this->turn_on(); // display ON + this->fill(Color::BLACK); // clear display - ensures we do not see garbage at power-on + this->display(); // ...write buffer, which actually clears the display's memory + this->turn_on(); // display ON } void SSD1322::display() { this->command(SSD1322_SETCOLUMNADDRESS); // set column address diff --git a/esphome/components/ssd1325_base/ssd1325_base.cpp b/esphome/components/ssd1325_base/ssd1325_base.cpp index 1cca1853b1..60e46f573f 100644 --- a/esphome/components/ssd1325_base/ssd1325_base.cpp +++ b/esphome/components/ssd1325_base/ssd1325_base.cpp @@ -7,8 +7,6 @@ namespace ssd1325_base { static const char *const TAG = "ssd1325"; -static const uint8_t BLACK = 0; -static const uint8_t WHITE = 15; static const uint8_t SSD1325_MAX_CONTRAST = 127; static const uint8_t SSD1325_COLORMASK = 0x0f; static const uint8_t SSD1325_COLORSHIFT = 4; @@ -114,9 +112,9 @@ void SSD1325::setup() { this->command(0x0D | 0x02); this->command(SSD1325_NORMALDISPLAY); // set display mode set_brightness(this->brightness_); - this->fill(BLACK); // clear display - ensures we do not see garbage at power-on - this->display(); // ...write buffer, which actually clears the display's memory - this->turn_on(); // display ON + this->fill(Color::BLACK); // clear display - ensures we do not see garbage at power-on + this->display(); // ...write buffer, which actually clears the display's memory + this->turn_on(); // display ON } void SSD1325::display() { this->command(SSD1325_SETCOLADDR); // set column address diff --git a/esphome/components/ssd1327_base/ssd1327_base.cpp b/esphome/components/ssd1327_base/ssd1327_base.cpp index ae94be87df..4cb8d17a3d 100644 --- a/esphome/components/ssd1327_base/ssd1327_base.cpp +++ b/esphome/components/ssd1327_base/ssd1327_base.cpp @@ -78,9 +78,9 @@ void SSD1327::setup() { this->command(0x1C); this->command(SSD1327_NORMALDISPLAY); // set display mode set_brightness(this->brightness_); - this->fill(COLOR_BLACK); // clear display - ensures we do not see garbage at power-on - this->display(); // ...write buffer, which actually clears the display's memory - this->turn_on(); // display ON + this->fill(Color::BLACK); // clear display - ensures we do not see garbage at power-on + this->display(); // ...write buffer, which actually clears the display's memory + this->turn_on(); // display ON } void SSD1327::display() { this->command(SSD1327_SETCOLUMNADDRESS); // set column address diff --git a/esphome/components/ssd1331_base/ssd1331_base.cpp b/esphome/components/ssd1331_base/ssd1331_base.cpp index 7f761b4b55..88764c3d90 100644 --- a/esphome/components/ssd1331_base/ssd1331_base.cpp +++ b/esphome/components/ssd1331_base/ssd1331_base.cpp @@ -7,8 +7,6 @@ namespace ssd1331_base { static const char *const TAG = "ssd1331"; -static const uint16_t BLACK = 0; -static const uint16_t WHITE = 0xffff; static const uint16_t SSD1331_COLORMASK = 0xffff; static const uint8_t SSD1331_MAX_CONTRASTA = 0x91; static const uint8_t SSD1331_MAX_CONTRASTB = 0x50; @@ -78,9 +76,9 @@ void SSD1331::setup() { this->command(SSD1331_MASTERCURRENT); // 0x87 this->command(0x06); set_brightness(this->brightness_); - this->fill(BLACK); // clear display - ensures we do not see garbage at power-on - this->display(); // ...write buffer, which actually clears the display's memory - this->turn_on(); // display ON + this->fill(Color::BLACK); // clear display - ensures we do not see garbage at power-on + this->display(); // ...write buffer, which actually clears the display's memory + this->turn_on(); // display ON } void SSD1331::display() { this->command(SSD1331_SETCOLUMN); // set column address diff --git a/esphome/components/ssd1351_base/ssd1351_base.cpp b/esphome/components/ssd1351_base/ssd1351_base.cpp index 34f357e38a..f26cd7c697 100644 --- a/esphome/components/ssd1351_base/ssd1351_base.cpp +++ b/esphome/components/ssd1351_base/ssd1351_base.cpp @@ -7,8 +7,6 @@ namespace ssd1351_base { static const char *const TAG = "ssd1351"; -static const uint16_t BLACK = 0; -static const uint16_t WHITE = 0xffff; static const uint16_t SSD1351_COLORMASK = 0xffff; static const uint8_t SSD1351_MAX_CONTRAST = 15; static const uint8_t SSD1351_BYTESPERPIXEL = 2; @@ -87,9 +85,9 @@ void SSD1351::setup() { this->data(0x80); this->data(0xC8); set_brightness(this->brightness_); - this->fill(BLACK); // clear display - ensures we do not see garbage at power-on - this->display(); // ...write buffer, which actually clears the display's memory - this->turn_on(); // display ON + this->fill(Color::BLACK); // clear display - ensures we do not see garbage at power-on + this->display(); // ...write buffer, which actually clears the display's memory + this->turn_on(); // display ON } void SSD1351::display() { this->command(SSD1351_SETCOLUMN); // set column address diff --git a/esphome/components/wled/wled_light_effect.cpp b/esphome/components/wled/wled_light_effect.cpp index afff956c9c..690d2f3b00 100644 --- a/esphome/components/wled/wled_light_effect.cpp +++ b/esphome/components/wled/wled_light_effect.cpp @@ -40,7 +40,7 @@ void WLEDLightEffect::stop() { void WLEDLightEffect::blank_all_leds_(light::AddressableLight &it) { for (int led = it.size(); led-- > 0;) { - it[led].set(COLOR_BLACK); + it[led].set(Color::BLACK); } } diff --git a/esphome/core/color.cpp b/esphome/core/color.cpp new file mode 100644 index 0000000000..b9a9f27e46 --- /dev/null +++ b/esphome/core/color.cpp @@ -0,0 +1,8 @@ +#include "esphome/core/color.h" + +namespace esphome { + +const Color Color::BLACK(0, 0, 0, 0); +const Color Color::WHITE(255, 255, 255, 255); + +} // namespace esphome diff --git a/esphome/core/color.h b/esphome/core/color.h index 6e8c769d10..a59030e518 100644 --- a/esphome/core/color.h +++ b/esphome/core/color.h @@ -38,10 +38,10 @@ struct Color { g(green), b(blue), w(white) {} - inline Color(uint32_t colorcode) ALWAYS_INLINE : r((colorcode >> 16) & 0xFF), - g((colorcode >> 8) & 0xFF), - b((colorcode >> 0) & 0xFF), - w((colorcode >> 24) & 0xFF) {} + inline explicit Color(uint32_t colorcode) ALWAYS_INLINE : r((colorcode >> 16) & 0xFF), + g((colorcode >> 8) & 0xFF), + b((colorcode >> 0) & 0xFF), + w((colorcode >> 24) & 0xFF) {} inline bool is_on() ALWAYS_INLINE { return this->raw_32 != 0; } inline Color &operator=(const Color &rhs) ALWAYS_INLINE { // NOLINT @@ -143,8 +143,14 @@ struct Color { Color fade_to_black(uint8_t amnt) { return *this * amnt; } Color lighten(uint8_t delta) { return *this + delta; } Color darken(uint8_t delta) { return *this - delta; } + + static const Color BLACK; + static const Color WHITE; }; -static const Color COLOR_BLACK(0, 0, 0); +ESPDEPRECATED("Use Color::BLACK instead of COLOR_BLACK") +static const Color COLOR_BLACK(0, 0, 0, 0); +ESPDEPRECATED("Use Color::WHITE instead of COLOR_WHITE") static const Color COLOR_WHITE(255, 255, 255, 255); -}; // namespace esphome + +} // namespace esphome From 20f7eb73279f360b5273512f560fb0e6f731b6ff Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 4 Aug 2021 00:43:01 +0200 Subject: [PATCH 1128/1841] Add version argument to ESPDEPRECATED macro (#2116) --- esphome/components/climate/climate.h | 8 ++-- esphome/components/climate/climate_traits.h | 42 ++++++++++---------- esphome/components/light/addressable_light.h | 2 +- esphome/components/light/light_traits.h | 10 ++--- esphome/components/nextion/nextion.cpp | 4 +- esphome/core/color.h | 4 +- esphome/core/helpers.h | 2 +- 7 files changed, 36 insertions(+), 36 deletions(-) diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 690e81c250..b208e5946a 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -63,9 +63,9 @@ class ClimateCall { * For climate devices with two point target temperature control */ ClimateCall &set_target_temperature_high(optional target_temperature_high); - ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead") + ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20") ClimateCall &set_away(bool away); - ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead") + ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20") ClimateCall &set_away(optional away); /// Set the fan mode of the climate device. ClimateCall &set_fan_mode(ClimateFanMode fan_mode); @@ -96,7 +96,7 @@ class ClimateCall { const optional &get_target_temperature() const; const optional &get_target_temperature_low() const; const optional &get_target_temperature_high() const; - ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead") + ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead", "v1.20") optional get_away() const; const optional &get_fan_mode() const; const optional &get_swing_mode() const; @@ -193,7 +193,7 @@ class Climate : public Nameable { * Away allows climate devices to have two different target temperature configs: * one for normal mode and one for away mode. */ - ESPDEPRECATED("away is deprecated, use preset instead") + ESPDEPRECATED("away is deprecated, use preset instead", "v1.20") bool away{false}; /// The active fan mode of the climate device. diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index fbd6f158e6..48493b500c 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -50,19 +50,19 @@ class ClimateTraits { } void set_supported_modes(std::set modes) { supported_modes_ = std::move(modes); } void add_supported_mode(ClimateMode mode) { supported_modes_.insert(mode); } - ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20") void set_supports_auto_mode(bool supports_auto_mode) { set_mode_support_(CLIMATE_MODE_AUTO, supports_auto_mode); } - ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20") void set_supports_cool_mode(bool supports_cool_mode) { set_mode_support_(CLIMATE_MODE_COOL, supports_cool_mode); } - ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20") void set_supports_heat_mode(bool supports_heat_mode) { set_mode_support_(CLIMATE_MODE_HEAT, supports_heat_mode); } - ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20") void set_supports_heat_cool_mode(bool supported) { set_mode_support_(CLIMATE_MODE_HEAT_COOL, supported); } - ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20") void set_supports_fan_only_mode(bool supports_fan_only_mode) { set_mode_support_(CLIMATE_MODE_FAN_ONLY, supports_fan_only_mode); } - ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20") void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); } bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); } const std::set get_supported_modes() const { return supported_modes_; } @@ -72,23 +72,23 @@ class ClimateTraits { void set_supported_fan_modes(std::set modes) { supported_fan_modes_ = std::move(modes); } void add_supported_fan_mode(ClimateFanMode mode) { supported_fan_modes_.insert(mode); } - ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20") void set_supports_fan_mode_on(bool supported) { set_fan_mode_support_(CLIMATE_FAN_ON, supported); } - ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20") void set_supports_fan_mode_off(bool supported) { set_fan_mode_support_(CLIMATE_FAN_OFF, supported); } - ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20") void set_supports_fan_mode_auto(bool supported) { set_fan_mode_support_(CLIMATE_FAN_AUTO, supported); } - ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20") void set_supports_fan_mode_low(bool supported) { set_fan_mode_support_(CLIMATE_FAN_LOW, supported); } - ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20") void set_supports_fan_mode_medium(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MEDIUM, supported); } - ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20") void set_supports_fan_mode_high(bool supported) { set_fan_mode_support_(CLIMATE_FAN_HIGH, supported); } - ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20") void set_supports_fan_mode_middle(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MIDDLE, supported); } - ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20") void set_supports_fan_mode_focus(bool supported) { set_fan_mode_support_(CLIMATE_FAN_FOCUS, supported); } - ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20") void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); } bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); } bool get_supports_fan_modes() const { return !supported_fan_modes_.empty() || !supported_custom_fan_modes_.empty(); } @@ -115,25 +115,25 @@ class ClimateTraits { bool supports_custom_preset(const std::string &custom_preset) const { return supported_custom_presets_.count(custom_preset); } - ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead", "v1.20") void set_supports_away(bool supports) { if (supports) { supported_presets_.insert(CLIMATE_PRESET_AWAY); supported_presets_.insert(CLIMATE_PRESET_HOME); } } - ESPDEPRECATED("This method is deprecated, use supports_preset() instead") + ESPDEPRECATED("This method is deprecated, use supports_preset() instead", "v1.20") bool get_supports_away() const { return supports_preset(CLIMATE_PRESET_AWAY); } void set_supported_swing_modes(std::set modes) { supported_swing_modes_ = std::move(modes); } void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); } - ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20") void set_supports_swing_mode_off(bool supported) { set_swing_mode_support_(CLIMATE_SWING_OFF, supported); } - ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20") void set_supports_swing_mode_both(bool supported) { set_swing_mode_support_(CLIMATE_SWING_BOTH, supported); } - ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20") void set_supports_swing_mode_vertical(bool supported) { set_swing_mode_support_(CLIMATE_SWING_VERTICAL, supported); } - ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead") + ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20") void set_supports_swing_mode_horizontal(bool supported) { set_swing_mode_support_(CLIMATE_SWING_HORIZONTAL, supported); } diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index fe74d2e9b5..54a1e3c6b4 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -16,7 +16,7 @@ namespace esphome { namespace light { -using ESPColor ESPDEPRECATED("esphome::light::ESPColor is deprecated, use esphome::Color instead.") = Color; +using ESPColor ESPDEPRECATED("esphome::light::ESPColor is deprecated, use esphome::Color instead.", "v1.21") = Color; class AddressableLight : public LightOutput, public Component { public: diff --git a/esphome/components/light/light_traits.h b/esphome/components/light/light_traits.h index 50da299134..7c99d721f0 100644 --- a/esphome/components/light/light_traits.h +++ b/esphome/components/light/light_traits.h @@ -26,20 +26,20 @@ class LightTraits { return false; } - ESPDEPRECATED("get_supports_brightness() is deprecated, use color modes instead.") + ESPDEPRECATED("get_supports_brightness() is deprecated, use color modes instead.", "v1.21") bool get_supports_brightness() const { return this->supports_color_capability(ColorCapability::BRIGHTNESS); } - ESPDEPRECATED("get_supports_rgb() is deprecated, use color modes instead.") + ESPDEPRECATED("get_supports_rgb() is deprecated, use color modes instead.", "v1.21") bool get_supports_rgb() const { return this->supports_color_capability(ColorCapability::RGB); } - ESPDEPRECATED("get_supports_rgb_white_value() is deprecated, use color modes instead.") + ESPDEPRECATED("get_supports_rgb_white_value() is deprecated, use color modes instead.", "v1.21") bool get_supports_rgb_white_value() const { return this->supports_color_mode(ColorMode::RGB_WHITE) || this->supports_color_mode(ColorMode::RGB_COLOR_TEMPERATURE); } - ESPDEPRECATED("get_supports_color_temperature() is deprecated, use color modes instead.") + ESPDEPRECATED("get_supports_color_temperature() is deprecated, use color modes instead.", "v1.21") bool get_supports_color_temperature() const { return this->supports_color_capability(ColorCapability::COLOR_TEMPERATURE); } - ESPDEPRECATED("get_supports_color_interlock() is deprecated, use color modes instead.") + ESPDEPRECATED("get_supports_color_interlock() is deprecated, use color modes instead.", "v1.21") bool get_supports_color_interlock() const { return this->supports_color_mode(ColorMode::RGB) && (this->supports_color_mode(ColorMode::WHITE) || this->supports_color_mode(ColorMode::COLD_WARM_WHITE) || diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index b6b0d83885..14c7b34a4c 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -1086,8 +1086,8 @@ void Nextion::add_addt_command_to_queue(NextionComponentBase *component) { void Nextion::set_writer(const nextion_writer_t &writer) { this->writer_ = writer; } -ESPDEPRECATED("set_wait_for_ack(bool) is deprecated and has no effect") -void Nextion::set_wait_for_ack(bool wait_for_ack) { ESP_LOGE(TAG, "This command is depreciated"); } +ESPDEPRECATED("set_wait_for_ack(bool) is deprecated and has no effect", "v1.20") +void Nextion::set_wait_for_ack(bool wait_for_ack) { ESP_LOGE(TAG, "This command is deprecated"); } } // namespace nextion } // namespace esphome diff --git a/esphome/core/color.h b/esphome/core/color.h index a59030e518..ba1a855e86 100644 --- a/esphome/core/color.h +++ b/esphome/core/color.h @@ -148,9 +148,9 @@ struct Color { static const Color WHITE; }; -ESPDEPRECATED("Use Color::BLACK instead of COLOR_BLACK") +ESPDEPRECATED("Use Color::BLACK instead of COLOR_BLACK", "v1.21") static const Color COLOR_BLACK(0, 0, 0, 0); -ESPDEPRECATED("Use Color::WHITE instead of COLOR_WHITE") +ESPDEPRECATED("Use Color::WHITE instead of COLOR_WHITE", "v1.21") static const Color COLOR_WHITE(255, 255, 255, 255); } // namespace esphome diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 6f6281fd01..5868918cd6 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -17,7 +17,7 @@ #endif #define HOT __attribute__((hot)) -#define ESPDEPRECATED(msg) __attribute__((deprecated(msg))) +#define ESPDEPRECATED(msg, when) __attribute__((deprecated(msg))) #define ALWAYS_INLINE __attribute__((always_inline)) #define PACKED __attribute__((packed)) From ceb0564ebf9ebbab9d22babbcf4f695e8c92df6f Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 4 Aug 2021 02:32:42 +0200 Subject: [PATCH 1129/1841] Fix mixup between ColorMode and ColorCapability (#2121) --- esphome/components/light/light_color_values.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index 5a5a2f6e34..1bc22977a1 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -179,7 +179,7 @@ class LightColorValues { /// Convert these light color values to an CWWW representation with the given parameters. void as_cwww(float color_temperature_cw, float color_temperature_ww, float *cold_white, float *warm_white, float gamma = 0, bool constant_brightness = false) const { - if (this->color_mode_ & ColorMode::COLD_WARM_WHITE) { + if (this->color_mode_ & ColorCapability::COLD_WARM_WHITE) { const float cw_level = gamma_correct(this->cold_white_, gamma); const float ww_level = gamma_correct(this->warm_white_, gamma); const float white_level = gamma_correct(this->state_ * this->brightness_, gamma); From 768c71830b3e30d4fd71e5afff6c6765b0db671f Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 4 Aug 2021 17:33:17 +0200 Subject: [PATCH 1130/1841] Fix external components not refreshing with default or high refresh time (#2122) --- esphome/components/external_components/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index 3e833d0b66..8f6e2bece4 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -147,7 +147,7 @@ def _process_git_config(config: dict, refresh) -> str: age = datetime.datetime.now() - datetime.datetime.fromtimestamp( file_timestamp.stat().st_mtime ) - if age.seconds > refresh.total_seconds: + if age.total_seconds() > refresh.total_seconds: _LOGGER.info("Updating %s", key) _LOGGER.debug("Location: %s", repo_dir) # Stash local changes (if any) From 7828f48b9affbc3520bb2f8909a5950cdcfbd7ae Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Mon, 26 Jul 2021 17:20:02 +1000 Subject: [PATCH 1131/1841] Correctly invert esp32 RMT TX (#2022) --- esphome/components/remote_transmitter/remote_transmitter.h | 1 + .../remote_transmitter/remote_transmitter_esp32.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index 000fbabfee..853b5b6289 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -41,6 +41,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, bool initialized_{false}; std::vector rmt_temp_; esp_err_t error_code_{ESP_OK}; + bool inverted_{false}; #endif uint8_t carrier_duty_percent_{50}; }; diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index 3d3e26160a..7b366fa52b 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -50,6 +50,7 @@ void RemoteTransmitterComponent::configure_rmt() { } else { c.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; c.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH; + this->inverted_ = true; } esp_err_t error = rmt_config(&c); @@ -95,10 +96,10 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen val -= item; if (rmt_i % 2 == 0) { - rmt_item.level0 = static_cast(level); + rmt_item.level0 = static_cast(level ^ this->inverted_); rmt_item.duration0 = static_cast(item); } else { - rmt_item.level1 = static_cast(level); + rmt_item.level1 = static_cast(level ^ this->inverted_); rmt_item.duration1 = static_cast(item); this->rmt_temp_.push_back(rmt_item); } From 790d6ef94cad7e69f00e2800f4018c3f8195e060 Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Mon, 26 Jul 2021 17:32:08 +1000 Subject: [PATCH 1132/1841] Move configure_rmt() into setup() (#2028) --- .../components/remote_transmitter/remote_transmitter_esp32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index 7b366fa52b..90166d2741 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -9,7 +9,7 @@ namespace remote_transmitter { static const char *const TAG = "remote_transmitter"; -void RemoteTransmitterComponent::setup() {} +void RemoteTransmitterComponent::setup() { this->configure_rmt(); } void RemoteTransmitterComponent::dump_config() { ESP_LOGCONFIG(TAG, "Remote Transmitter..."); From 3ffa59f0cd43becb7d1a5ad1954bec1ac706b217 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 26 Jul 2021 15:19:50 +0200 Subject: [PATCH 1133/1841] Fix climate restore schema changed resulting in invalid restore (#2068) Co-authored-by: Stefan Agner --- esphome/components/climate/climate.cpp | 6 +++++- esphome/components/climate/climate.h | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 07347e4eee..8da2206f37 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -312,8 +312,12 @@ void Climate::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } +// Random 32bit value; If this changes existing restore preferences are invalidated +static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL; + optional Climate::restore_state_() { - this->rtc_ = global_preferences.make_preference(this->get_object_id_hash()); + this->rtc_ = + global_preferences.make_preference(this->get_object_id_hash() ^ RESTORE_STATE_VERSION); ClimateDeviceRestoreState recovered{}; if (!this->rtc_.load(&recovered)) return {}; diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index ed5c5069b7..690e81c250 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -120,6 +120,7 @@ class ClimateCall { }; /// Struct used to save the state of the climate device in restore memory. +/// Make sure to update RESTORE_STATE_VERSION when changing the struct entries. struct ClimateDeviceRestoreState { ClimateMode mode; bool uses_custom_fan_mode{false}; From 29f0508dc21393b03a877a1439ee3d5b5d3426c6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 28 Jul 2021 21:23:41 +0200 Subject: [PATCH 1134/1841] Fix PID climate breaks when restoring old modes (#2086) --- esphome/components/pid/pid_climate.cpp | 13 ++----------- esphome/components/pid/pid_climate.h | 1 - 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index ef8a4df962..4c7d92e26d 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -37,7 +37,7 @@ void PIDClimate::control(const climate::ClimateCall &call) { // If switching to off mode, set output immediately if (this->mode == climate::CLIMATE_MODE_OFF) - this->handle_non_auto_mode_(); + this->write_output_(0.0f); this->publish_state(); } @@ -98,15 +98,6 @@ void PIDClimate::write_output_(float value) { } this->pid_computed_callback_.call(); } -void PIDClimate::handle_non_auto_mode_() { - // in non-auto mode, switch directly to appropriate action - // - OFF mode -> Output at 0% - if (this->mode == climate::CLIMATE_MODE_OFF) { - this->write_output_(0.0); - } else { - assert(false); - } -} void PIDClimate::update_pid_() { float value; if (isnan(this->current_temperature) || isnan(this->target_temperature)) { @@ -135,7 +126,7 @@ void PIDClimate::update_pid_() { } if (this->mode == climate::CLIMATE_MODE_OFF) { - this->handle_non_auto_mode_(); + this->write_output_(0.0); } else { this->write_output_(value); } diff --git a/esphome/components/pid/pid_climate.h b/esphome/components/pid/pid_climate.h index f11d768867..ff301386b6 100644 --- a/esphome/components/pid/pid_climate.h +++ b/esphome/components/pid/pid_climate.h @@ -56,7 +56,6 @@ class PIDClimate : public climate::Climate, public Component { bool supports_heat_() const { return this->heat_output_ != nullptr; } void write_output_(float value); - void handle_non_auto_mode_(); /// The sensor used for getting the current temperature sensor::Sensor *sensor_; From 5ce923ea90d03fceb98e158dadcab5bd0c478b99 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Fri, 30 Jul 2021 01:55:26 -0300 Subject: [PATCH 1135/1841] fix diplay trigger missing base class (#2099) --- esphome/components/display/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/display/__init__.py b/esphome/components/display/__init__.py index 2dff00da03..947b09a258 100644 --- a/esphome/components/display/__init__.py +++ b/esphome/components/display/__init__.py @@ -31,7 +31,9 @@ DisplayPageShowPrevAction = display_ns.class_( DisplayIsDisplayingPageCondition = display_ns.class_( "DisplayIsDisplayingPageCondition", automation.Condition ) -DisplayOnPageChangeTrigger = display_ns.class_("DisplayOnPageChangeTrigger") +DisplayOnPageChangeTrigger = display_ns.class_( + "DisplayOnPageChangeTrigger", automation.Trigger +) CONF_ON_PAGE_CHANGE = "on_page_change" From a6fac2b1750b05a62acb3985556de2df7a063535 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Sat, 31 Jul 2021 23:20:10 +1200 Subject: [PATCH 1136/1841] Fix min/max keys in MQTT Number to match Home Assistant (#2102) --- esphome/components/mqtt/mqtt_number.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 0311526340..f209f4fe20 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -39,8 +39,8 @@ void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo // https://www.home-assistant.io/integrations/number.mqtt/ if (!traits.get_icon().empty()) root["icon"] = traits.get_icon(); - root["min_value"] = traits.get_min_value(); - root["max_value"] = traits.get_max_value(); + root["min"] = traits.get_min_value(); + root["max"] = traits.get_max_value(); root["step"] = traits.get_step(); config.command_topic = true; From bdbd8134553baf231bd0395d477ec4a74d2c0c65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20=C5=9Aliwi=C5=84ski?= Date: Sat, 31 Jul 2021 14:37:48 +0200 Subject: [PATCH 1137/1841] Use proper schema for the analog pin shorthand (#2103) The wrong error message is displayed like: > GPIO17 (TOUT) is an analog-only pin on the ESP8266. in case of the analog pin validation because the method `shorthand_analog_pin` uses wrong `GPIO_FULL_INPUT_PIN_SCHEMA` instead of `GPIO_FULL_ANALOG_PIN_SCHEMA`. --- esphome/pins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/pins.py b/esphome/pins.py index 6356ae9bd0..e314e3fc30 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -1104,7 +1104,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): From b0d12aeea11f39153cc9b8c0f2b43da64a895c6f Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Mon, 2 Aug 2021 10:28:25 +0200 Subject: [PATCH 1138/1841] [duty_cycle] initialize two missing variables (#2088) --- esphome/components/duty_cycle/duty_cycle_sensor.cpp | 1 + esphome/components/duty_cycle/duty_cycle_sensor.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.cpp b/esphome/components/duty_cycle/duty_cycle_sensor.cpp index 8b7446b681..c989421948 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.cpp +++ b/esphome/components/duty_cycle/duty_cycle_sensor.cpp @@ -13,6 +13,7 @@ void DutyCycleSensor::setup() { this->store_.pin = this->pin_->to_isr(); this->store_.last_level = this->pin_->digital_read(); this->last_update_ = micros(); + this->store_.last_interrupt = micros(); this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, CHANGE); } diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.h b/esphome/components/duty_cycle/duty_cycle_sensor.h index 2205bec729..e168f20eff 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.h +++ b/esphome/components/duty_cycle/duty_cycle_sensor.h @@ -29,7 +29,7 @@ class DutyCycleSensor : public sensor::Sensor, public PollingComponent { protected: GPIOPin *pin_; - DutyCycleSensorStore store_; + DutyCycleSensorStore store_{}; uint32_t last_update_; }; From dd637582a43e0eb297887e36c25287a62db61679 Mon Sep 17 00:00:00 2001 From: brambo123 <52667932+brambo123@users.noreply.github.com> Date: Tue, 3 Aug 2021 19:56:23 +0200 Subject: [PATCH 1139/1841] Fix time.on_time triggering if time jumped back (#1806) Co-authored-by: Otto winter --- esphome/components/time/automation.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/time/automation.cpp b/esphome/components/time/automation.cpp index 6d34459fea..84cc76a762 100644 --- a/esphome/components/time/automation.cpp +++ b/esphome/components/time/automation.cpp @@ -22,7 +22,10 @@ void CronTrigger::loop() { return; if (this->last_check_.has_value()) { - if (*this->last_check_ >= time) { + if (*this->last_check_ > time && this->last_check_->timestamp - time.timestamp > 900) { + // We went back in time (a lot), probably caused by time synchronization + ESP_LOGW(TAG, "Time has jumped back!"); + } else if (*this->last_check_ >= time) { // already handled this one return; } From eeaba74553d8a5ed629fd5fc6be83ec232eefca4 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 4 Aug 2021 17:33:17 +0200 Subject: [PATCH 1140/1841] Fix external components not refreshing with default or high refresh time (#2122) --- esphome/components/external_components/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index 3e833d0b66..8f6e2bece4 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -147,7 +147,7 @@ def _process_git_config(config: dict, refresh) -> str: age = datetime.datetime.now() - datetime.datetime.fromtimestamp( file_timestamp.stat().st_mtime ) - if age.seconds > refresh.total_seconds: + if age.total_seconds() > refresh.total_seconds: _LOGGER.info("Updating %s", key) _LOGGER.debug("Location: %s", repo_dir) # Stash local changes (if any) From 532907219baaa3b856c501308a2cbbc7bb38568b Mon Sep 17 00:00:00 2001 From: Otto winter Date: Wed, 4 Aug 2021 17:46:10 +0200 Subject: [PATCH 1141/1841] Bump version to v1.20.4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index d2cc495952..6dda61cce2 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.3" +__version__ = "1.20.4" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 0d104776bc1465562e89f7197e0530ce37681ddd Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 5 Aug 2021 01:28:39 +0200 Subject: [PATCH 1142/1841] Various follow-up fixes to color mode changes (#2118) --- esphome/components/light/light_call.cpp | 34 +++++++++++++------ esphome/components/light/light_call.h | 1 + esphome/components/light/light_color_values.h | 16 ++------- esphome/components/light/light_effect.h | 2 -- esphome/components/light/light_json_schema.h | 7 ++-- esphome/components/light/light_output.h | 2 -- esphome/components/light/light_state.h | 5 +++ esphome/components/light/light_transformer.h | 2 ++ 8 files changed, 38 insertions(+), 31 deletions(-) diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 536018c33b..cb0a7bb711 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -53,6 +53,9 @@ void LightCall::perform() { ESP_LOGD(TAG, " Brightness: %.0f%%", v.get_brightness() * 100.0f); } + if (this->color_brightness_.has_value()) { + ESP_LOGD(TAG, " Color brightness: %.0f%%", v.get_color_brightness() * 100.0f); + } if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { ESP_LOGD(TAG, " Red: %.0f%%, Green: %.0f%%, Blue: %.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f, v.get_blue() * 100.0f); @@ -149,7 +152,7 @@ LightColorValues LightCall::validate_() { this->transform_parameters_(); // Brightness exists check - if (this->brightness_.has_value() && !(color_mode & ColorCapability::BRIGHTNESS)) { + if (this->brightness_.has_value() && *this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) { ESP_LOGW(TAG, "'%s' - This light does not support setting brightness!", name); this->brightness_.reset(); } @@ -162,13 +165,14 @@ LightColorValues LightCall::validate_() { } // Color brightness exists check - if (this->color_brightness_.has_value() && !(color_mode & ColorCapability::RGB)) { + if (this->color_brightness_.has_value() && *this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) { ESP_LOGW(TAG, "'%s' - This color mode does not support setting RGB brightness!", name); this->color_brightness_.reset(); } // RGB exists check - if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { + if ((this->red_.has_value() && *this->red_ > 0.0f) || (this->green_.has_value() && *this->green_ > 0.0f) || + (this->blue_.has_value() && *this->blue_ > 0.0f)) { if (!(color_mode & ColorCapability::RGB)) { ESP_LOGW(TAG, "'%s' - This color mode does not support setting RGB color!", name); this->red_.reset(); @@ -178,7 +182,7 @@ LightColorValues LightCall::validate_() { } // White value exists check - if (this->white_.has_value() && + if (this->white_.has_value() && *this->white_ > 0.0f && !(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) { ESP_LOGW(TAG, "'%s' - This color mode does not support setting white value!", name); this->white_.reset(); @@ -192,7 +196,8 @@ LightColorValues LightCall::validate_() { } // Cold/warm white value exists check - if (this->cold_white_.has_value() || this->warm_white_.has_value()) { + if ((this->cold_white_.has_value() && *this->cold_white_ > 0.0f) || + (this->warm_white_.has_value() && *this->warm_white_ > 0.0f)) { if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) { ESP_LOGW(TAG, "'%s' - This color mode does not support setting cold/warm white value!", name); this->cold_white_.reset(); @@ -200,15 +205,15 @@ LightColorValues LightCall::validate_() { } } -#define VALIDATE_RANGE_(name_, upper_name) \ +#define VALIDATE_RANGE_(name_, upper_name, min, max) \ if (name_##_.has_value()) { \ auto val = *name_##_; \ - if (val < 0.0f || val > 1.0f) { \ - ESP_LOGW(TAG, "'%s' - %s value %.2f is out of range [0.0 - 1.0]!", name, upper_name, val); \ - name_##_ = clamp(val, 0.0f, 1.0f); \ + if (val < (min) || val > (max)) { \ + ESP_LOGW(TAG, "'%s' - %s value %.2f is out of range [%.1f - %.1f]!", name, upper_name, val, (min), (max)); \ + name_##_ = clamp(val, (min), (max)); \ } \ } -#define VALIDATE_RANGE(name, upper_name) VALIDATE_RANGE_(name, upper_name) +#define VALIDATE_RANGE(name, upper_name) VALIDATE_RANGE_(name, upper_name, 0.0f, 1.0f) // Range checks VALIDATE_RANGE(brightness, "Brightness") @@ -219,6 +224,13 @@ LightColorValues LightCall::validate_() { VALIDATE_RANGE(white, "White") VALIDATE_RANGE(cold_white, "Cold white") VALIDATE_RANGE(warm_white, "Warm white") + VALIDATE_RANGE_(color_temperature, "Color temperature", traits.get_min_mireds(), traits.get_max_mireds()) + + // Turn off when brightness is set to zero, and reset brightness (so that it has nonzero brightness when turned on). + if (this->brightness_.has_value() && *this->brightness_ == 0.0f) { + this->state_ = optional(false); + this->brightness_ = optional(1.0f); + } // Set color brightness to 100% if currently zero and a color is set. if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { @@ -250,7 +262,7 @@ LightColorValues LightCall::validate_() { if (this->warm_white_.has_value()) v.set_warm_white(*this->warm_white_); - v.normalize_color(traits); + v.normalize_color(); // Flash length check if (this->has_flash_() && *this->flash_length_ == 0) { diff --git a/esphome/components/light/light_call.h b/esphome/components/light/light_call.h index 31dc47e55d..bca2ac7b07 100644 --- a/esphome/components/light/light_call.h +++ b/esphome/components/light/light_call.h @@ -2,6 +2,7 @@ #include "esphome/core/optional.h" #include "light_color_values.h" +#include namespace esphome { namespace light { diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index 1bc22977a1..a6abac8cdf 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -1,12 +1,7 @@ #pragma once #include "esphome/core/helpers.h" -#include "esphome/core/defines.h" -#include "light_traits.h" - -#ifdef USE_JSON -#include "esphome/components/json/json_util.h" -#endif +#include "color_mode.h" namespace esphome { namespace light { @@ -112,7 +107,7 @@ class LightColorValues { * * @param traits Used for determining which attributes to consider. */ - void normalize_color(const LightTraits &traits) { + void normalize_color() { if (this->color_mode_ & ColorCapability::RGB) { float max_value = fmaxf(this->get_red(), fmaxf(this->get_green(), this->get_blue())); if (max_value == 0.0f) { @@ -125,13 +120,6 @@ class LightColorValues { this->set_blue(this->get_blue() / max_value); } } - - if (this->color_mode_ & ColorCapability::BRIGHTNESS && this->get_brightness() == 0.0f) { - // 0% brightness means off - this->set_state(false); - // reset brightness to 100% - this->set_brightness(1.0f); - } } // Note that method signature of as_* methods is kept as-is for compatibility reasons, so not all parameters diff --git a/esphome/components/light/light_effect.h b/esphome/components/light/light_effect.h index f9903397b4..8da51fe8b3 100644 --- a/esphome/components/light/light_effect.h +++ b/esphome/components/light/light_effect.h @@ -3,8 +3,6 @@ #include #include "esphome/core/component.h" -#include "light_color_values.h" -#include "light_state.h" namespace esphome { namespace light { diff --git a/esphome/components/light/light_json_schema.h b/esphome/components/light/light_json_schema.h index bb7ff812ab..09a372f11c 100644 --- a/esphome/components/light/light_json_schema.h +++ b/esphome/components/light/light_json_schema.h @@ -1,10 +1,13 @@ #pragma once -#include "light_call.h" -#include "light_state.h" +#include "esphome/core/defines.h" #ifdef USE_JSON +#include "esphome/components/json/json_util.h" +#include "light_call.h" +#include "light_state.h" + namespace esphome { namespace light { diff --git a/esphome/components/light/light_output.h b/esphome/components/light/light_output.h index 9e47092b0f..d05cbf9436 100644 --- a/esphome/components/light/light_output.h +++ b/esphome/components/light/light_output.h @@ -7,8 +7,6 @@ namespace esphome { namespace light { -class LightState; - /// Interface to write LightStates to hardware. class LightOutput { public: diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index fb34b76367..98fa21d480 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -54,6 +54,8 @@ class LightState : public Nameable, public Component { * property will be changed continuously (in contrast to .remote_values, where they * are constant during transitions). * + * This value does not have gamma correction applied. + * * This property is read-only for users. Any changes to it will be ignored. */ LightColorValues current_values; @@ -64,6 +66,8 @@ class LightState : public Nameable, public Component { * continuously change the "current" values. But the remote values will immediately * switch to the target value for a transition, reducing the number of packets sent. * + * This value does not have gamma correction applied. + * * This property is read-only for users. Any changes to it will be ignored. */ LightColorValues remote_values; @@ -112,6 +116,7 @@ class LightState : public Nameable, public Component { /// Add effects for this light state. void add_effects(const std::vector &effects); + /// The result of all the current_values_as_* methods have gamma correction applied. void current_values_as_binary(bool *binary); void current_values_as_brightness(float *brightness); diff --git a/esphome/components/light/light_transformer.h b/esphome/components/light/light_transformer.h index ddd0f4dd22..7db6265a89 100644 --- a/esphome/components/light/light_transformer.h +++ b/esphome/components/light/light_transformer.h @@ -85,6 +85,8 @@ class LightTransitionTransformer : public LightTransformer { bool publish_at_end() override { return false; } bool is_transition() override { return true; } + // This looks crazy, but it reduces to 6x^5 - 15x^4 + 10x^3 which is just a smooth sigmoid-like + // transition from 0 to 1 on x = [0, 1] static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); } protected: From cb21c7c18dd6d03e488726075c06f0c1e95b1d6a Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 5 Aug 2021 01:30:32 +0200 Subject: [PATCH 1143/1841] Fix crash when using addressable_set with out-of-range indices (#2120) --- esphome/components/light/automation.h | 10 ++++++++-- esphome/components/light/esp_range_view.h | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/esphome/components/light/automation.h b/esphome/components/light/automation.h index a816049f08..5ec2cb626a 100644 --- a/esphome/components/light/automation.h +++ b/esphome/components/light/automation.h @@ -159,8 +159,14 @@ template class AddressableSet : public Action { void play(Ts... x) override { auto *out = (AddressableLight *) this->parent_->get_output(); - int32_t range_from = this->range_from_.value_or(x..., 0); - int32_t range_to = this->range_to_.value_or(x..., out->size() - 1) + 1; + int32_t range_from = interpret_index(this->range_from_.value_or(x..., 0), out->size()); + if (range_from < 0 || range_from >= out->size()) + range_from = 0; + + int32_t range_to = interpret_index(this->range_to_.value_or(x..., out->size() - 1) + 1, out->size()); + if (range_to < 0 || range_to >= out->size()) + range_to = out->size(); + uint8_t color_brightness = to_uint8_scale(this->color_brightness_.value_or(x..., this->parent_->remote_values.get_color_brightness())); auto range = out->range(range_from, range_to); diff --git a/esphome/components/light/esp_range_view.h b/esphome/components/light/esp_range_view.h index f2cc347176..f4a7980543 100644 --- a/esphome/components/light/esp_range_view.h +++ b/esphome/components/light/esp_range_view.h @@ -11,6 +11,9 @@ int32_t interpret_index(int32_t index, int32_t size); class AddressableLight; class ESPRangeIterator; +/** + * A half-open range of LEDs, inclusive of the begin index and exclusive of the end index, using zero-based numbering. + */ class ESPRangeView : public ESPColorSettable { public: ESPRangeView(AddressableLight *parent, int32_t begin, int32_t end) From cb8a6f66fa82c25025be34f5fa07970f90987a5b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 5 Aug 2021 12:05:36 +1200 Subject: [PATCH 1144/1841] Add state classes to pvvx_mithermometer (#2125) --- esphome/components/pvvx_mithermometer/sensor.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/pvvx_mithermometer/sensor.py b/esphome/components/pvvx_mithermometer/sensor.py index d6c2d68b8c..b17878f01b 100644 --- a/esphome/components/pvvx_mithermometer/sensor.py +++ b/esphome/components/pvvx_mithermometer/sensor.py @@ -12,6 +12,7 @@ from esphome.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, UNIT_VOLT, @@ -35,21 +36,25 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=2, device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, accuracy_decimals=2, device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), } ) From 1d5f628c7a566ca51483a356138aad60181369bf Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sat, 7 Aug 2021 13:14:57 +0200 Subject: [PATCH 1145/1841] Add support for ESP8266 Arduino v3.0.1 (#2128) --- esphome/const.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/const.py b/esphome/const.py index 1ed8aeb402..075dc47411 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -24,6 +24,7 @@ ARDUINO_VERSION_ESP32 = { # See also https://github.com/platformio/platform-espressif8266/releases ARDUINO_VERSION_ESP8266 = { "dev": "https://github.com/platformio/platform-espressif8266.git", + "3.0.1": "platformio/espressif8266@3.1.0", "3.0.0": "platformio/espressif8266@3.0.0", "2.7.4": "platformio/espressif8266@2.6.2", "2.7.3": "platformio/espressif8266@2.6.1", From dfffaace26258871e027ddd63af56f192b176c13 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sat, 7 Aug 2021 13:15:32 +0200 Subject: [PATCH 1146/1841] Drop legacy esphomeyaml command wrapper code (#2130) --- esphome/legacy.py | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 esphome/legacy.py diff --git a/esphome/legacy.py b/esphome/legacy.py deleted file mode 100644 index 6b3b1d6c99..0000000000 --- a/esphome/legacy.py +++ /dev/null @@ -1,12 +0,0 @@ -import sys - - -def main(): - print("The esphomeyaml command has been renamed to esphome.") - print("") - print("$ esphome {}".format(" ".join(sys.argv[1:]))) - return 1 - - -if __name__ == "__main__": - sys.exit(main()) From 5bfac5ec09145b9a0255d67d7bec6e3f00f7272d Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sat, 7 Aug 2021 13:16:34 +0200 Subject: [PATCH 1147/1841] Allow multiple unnamed libraries (#2132) --- esphome/core/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index c61b288d09..c45d0969e1 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -653,7 +653,7 @@ class EsphomeCore: "".format(library, type(library)) ) for other in self.libraries[:]: - if other.name != library.name: + if other.name != library.name or other.name is None or library.name is None: continue if other.repository is not None: if library.repository is None or other.repository == library.repository: From 1d6b4bfcefc1a1095c8773b7d662a86993c57667 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sat, 7 Aug 2021 13:24:47 +0200 Subject: [PATCH 1148/1841] Don't stop effects if brightness goes to zero (#2134) --- esphome/components/light/light_call.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index cb0a7bb711..6945d37ded 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -226,6 +226,9 @@ LightColorValues LightCall::validate_() { VALIDATE_RANGE(warm_white, "Warm white") VALIDATE_RANGE_(color_temperature, "Color temperature", traits.get_min_mireds(), traits.get_max_mireds()) + // Flag whether an explicit turn off was requested, in which case we'll also stop the effect. + bool explicit_turn_off_request = this->state_.has_value() && !*this->state_; + // Turn off when brightness is set to zero, and reset brightness (so that it has nonzero brightness when turned on). if (this->brightness_.has_value() && *this->brightness_ == 0.0f) { this->state_ = optional(false); @@ -238,6 +241,7 @@ LightColorValues LightCall::validate_() { this->color_brightness_ = optional(1.0f); } + // Create color values for the light with this call applied. auto v = this->parent_->remote_values; if (this->color_mode_.has_value()) v.set_color_mode(*this->color_mode_); @@ -318,7 +322,7 @@ LightColorValues LightCall::validate_() { if (this->has_effect_()) { ESP_LOGW(TAG, "'%s' - Cannot start an effect when turning off!", name); this->effect_.reset(); - } else if (this->parent_->active_effect_index_ != 0) { + } else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) { // Auto turn off effect this->effect_ = 0; } From 29f72037feefe9e0448c23985034452f5f5d826e Mon Sep 17 00:00:00 2001 From: Sourabh Jaiswal Date: Sun, 8 Aug 2021 19:29:52 +0530 Subject: [PATCH 1149/1841] Added support for Hitachi AC424 remote type (#2101) --- CODEOWNERS | 1 + esphome/components/hitachi_ac424/__init__.py | 1 + esphome/components/hitachi_ac424/climate.py | 20 + .../hitachi_ac424/hitachi_ac424.cpp | 368 ++++++++++++++++++ .../components/hitachi_ac424/hitachi_ac424.h | 123 ++++++ 5 files changed, 513 insertions(+) create mode 100644 esphome/components/hitachi_ac424/__init__.py create mode 100644 esphome/components/hitachi_ac424/climate.py create mode 100644 esphome/components/hitachi_ac424/hitachi_ac424.cpp create mode 100644 esphome/components/hitachi_ac424/hitachi_ac424.h diff --git a/CODEOWNERS b/CODEOWNERS index b235643539..c79dfc066e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -48,6 +48,7 @@ esphome/components/globals/* @esphome/core esphome/components/gpio/* @esphome/core esphome/components/gps/* @coogle esphome/components/havells_solar/* @sourabhjaiswal +esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/homeassistant/* @OttoWinter esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/i2c/* @esphome/core diff --git a/esphome/components/hitachi_ac424/__init__.py b/esphome/components/hitachi_ac424/__init__.py new file mode 100644 index 0000000000..10f2c27fe8 --- /dev/null +++ b/esphome/components/hitachi_ac424/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@sourabhjaiswal"] diff --git a/esphome/components/hitachi_ac424/climate.py b/esphome/components/hitachi_ac424/climate.py new file mode 100644 index 0000000000..33532230df --- /dev/null +++ b/esphome/components/hitachi_ac424/climate.py @@ -0,0 +1,20 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate_ir +from esphome.const import CONF_ID + +AUTO_LOAD = ["climate_ir"] + +hitachi_ac424_ns = cg.esphome_ns.namespace("hitachi_ac424") +HitachiClimate = hitachi_ac424_ns.class_("HitachiClimate", climate_ir.ClimateIR) + +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(HitachiClimate), + } +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await climate_ir.register_climate_ir(var, config) diff --git a/esphome/components/hitachi_ac424/hitachi_ac424.cpp b/esphome/components/hitachi_ac424/hitachi_ac424.cpp new file mode 100644 index 0000000000..10b83cbd58 --- /dev/null +++ b/esphome/components/hitachi_ac424/hitachi_ac424.cpp @@ -0,0 +1,368 @@ +#include "hitachi_ac424.h" + +namespace esphome { +namespace hitachi_ac424 { + +static const char *const TAG = "climate.hitachi_ac424"; + +void set_bits(uint8_t *const dst, const uint8_t offset, const uint8_t nbits, const uint8_t data) { + if (offset >= 8 || !nbits) + return; // Short circuit as it won't change. + // Calculate the mask for the supplied value. + uint8_t mask = UINT8_MAX >> (8 - ((nbits > 8) ? 8 : nbits)); + // Calculate the mask & clear the space for the data. + // Clear the destination bits. + *dst &= ~(uint8_t)(mask << offset); + // Merge in the data. + *dst |= ((data & mask) << offset); +} + +void set_bit(uint8_t *const data, const uint8_t position, const bool on) { + uint8_t mask = 1 << position; + if (on) + *data |= mask; + else + *data &= ~mask; +} + +uint8_t *invert_byte_pairs(uint8_t *ptr, const uint16_t length) { + for (uint16_t i = 1; i < length; i += 2) { + // Code done this way to avoid a compiler warning bug. + uint8_t inv = ~*(ptr + i - 1); + *(ptr + i) = inv; + } + return ptr; +} + +bool HitachiClimate::get_power_() { return remote_state_[HITACHI_AC424_POWER_BYTE] == HITACHI_AC424_POWER_ON; } + +void HitachiClimate::set_power_(bool on) { + set_button_(HITACHI_AC424_BUTTON_POWER); + remote_state_[HITACHI_AC424_POWER_BYTE] = on ? HITACHI_AC424_POWER_ON : HITACHI_AC424_POWER_OFF; +} + +uint8_t HitachiClimate::get_mode_() { return remote_state_[HITACHI_AC424_MODE_BYTE] & 0xF; } + +void HitachiClimate::set_mode_(uint8_t mode) { + uint8_t new_mode = mode; + switch (mode) { + // Fan mode sets a special temp. + case HITACHI_AC424_MODE_FAN: + set_temp_(HITACHI_AC424_TEMP_FAN, false); + break; + case HITACHI_AC424_MODE_HEAT: + case HITACHI_AC424_MODE_COOL: + case HITACHI_AC424_MODE_DRY: + break; + default: + new_mode = HITACHI_AC424_MODE_COOL; + } + set_bits(&remote_state_[HITACHI_AC424_MODE_BYTE], 0, 4, new_mode); + if (new_mode != HITACHI_AC424_MODE_FAN) + set_temp_(previous_temp_); + set_fan_(get_fan_()); // Reset the fan speed after the mode change. + set_power_(true); +} + +void HitachiClimate::set_temp_(uint8_t celsius, bool set_previous) { + uint8_t temp; + temp = std::min(celsius, HITACHI_AC424_TEMP_MAX); + temp = std::max(temp, HITACHI_AC424_TEMP_MIN); + set_bits(&remote_state_[HITACHI_AC424_TEMP_BYTE], HITACHI_AC424_TEMP_OFFSET, HITACHI_AC424_TEMP_SIZE, temp); + if (previous_temp_ > temp) + set_button_(HITACHI_AC424_BUTTON_TEMP_DOWN); + else if (previous_temp_ < temp) + set_button_(HITACHI_AC424_BUTTON_TEMP_UP); + if (set_previous) + previous_temp_ = temp; +} + +uint8_t HitachiClimate::get_fan_() { return remote_state_[HITACHI_AC424_FAN_BYTE] >> 4 & 0xF; } + +void HitachiClimate::set_fan_(uint8_t speed) { + uint8_t new_speed = std::max(speed, HITACHI_AC424_FAN_MIN); + uint8_t fan_max = HITACHI_AC424_FAN_MAX; + + // Only 2 x low speeds in Dry mode or Auto + if (get_mode_() == HITACHI_AC424_MODE_DRY && speed == HITACHI_AC424_FAN_AUTO) { + fan_max = HITACHI_AC424_FAN_AUTO; + } else if (get_mode_() == HITACHI_AC424_MODE_DRY) { + fan_max = HITACHI_AC424_FAN_MAX_DRY; + } else if (get_mode_() == HITACHI_AC424_MODE_FAN && speed == HITACHI_AC424_FAN_AUTO) { + // Fan Mode does not have auto. Set to safe low + new_speed = HITACHI_AC424_FAN_MIN; + } + + new_speed = std::min(new_speed, fan_max); + // Handle the setting the button value if we are going to change the value. + if (new_speed != get_fan_()) + set_button_(HITACHI_AC424_BUTTON_FAN); + // Set the values + + set_bits(&remote_state_[HITACHI_AC424_FAN_BYTE], 4, 4, new_speed); + remote_state_[9] = 0x92; + + // When fan is at min/max, additional bytes seem to be set + if (new_speed == HITACHI_AC424_FAN_MIN) + remote_state_[9] = 0x98; + remote_state_[29] = 0x01; +} + +void HitachiClimate::set_swing_v_toggle_(bool on) { + uint8_t button = get_button_(); // Get the current button value. + if (on) + button = HITACHI_AC424_BUTTON_SWINGV; // Set the button to SwingV. + else if (button == HITACHI_AC424_BUTTON_SWINGV) // Asked to unset it + // It was set previous, so use Power as a default + button = HITACHI_AC424_BUTTON_POWER; + set_button_(button); +} + +bool HitachiClimate::get_swing_v_toggle_() { return get_button_() == HITACHI_AC424_BUTTON_SWINGV; } + +void HitachiClimate::set_swing_v_(bool on) { + set_swing_v_toggle_(on); // Set the button value. + set_bit(&remote_state_[HITACHI_AC424_SWINGV_BYTE], HITACHI_AC424_SWINGV_OFFSET, on); +} + +bool HitachiClimate::get_swing_v_() { + return HITACHI_AC424_GETBIT8(remote_state_[HITACHI_AC424_SWINGV_BYTE], HITACHI_AC424_SWINGV_OFFSET); +} + +void HitachiClimate::set_swing_h_(uint8_t position) { + if (position > HITACHI_AC424_SWINGH_LEFT_MAX) + return set_swing_h_(HITACHI_AC424_SWINGH_MIDDLE); + set_bits(&remote_state_[HITACHI_AC424_SWINGH_BYTE], HITACHI_AC424_SWINGH_OFFSET, HITACHI_AC424_SWINGH_SIZE, position); + set_button_(HITACHI_AC424_BUTTON_SWINGH); +} + +uint8_t HitachiClimate::get_swing_h_() { + return HITACHI_AC424_GETBITS8(remote_state_[HITACHI_AC424_SWINGH_BYTE], HITACHI_AC424_SWINGH_OFFSET, + HITACHI_AC424_SWINGH_SIZE); +} + +uint8_t HitachiClimate::get_button_() { return remote_state_[HITACHI_AC424_BUTTON_BYTE]; } + +void HitachiClimate::set_button_(uint8_t button) { remote_state_[HITACHI_AC424_BUTTON_BYTE] = button; } + +void HitachiClimate::transmit_state() { + switch (this->mode) { + case climate::CLIMATE_MODE_COOL: + set_mode_(HITACHI_AC424_MODE_COOL); + break; + case climate::CLIMATE_MODE_DRY: + set_mode_(HITACHI_AC424_MODE_DRY); + break; + case climate::CLIMATE_MODE_HEAT: + set_mode_(HITACHI_AC424_MODE_HEAT); + break; + case climate::CLIMATE_MODE_HEAT_COOL: + set_mode_(HITACHI_AC424_MODE_AUTO); + break; + case climate::CLIMATE_MODE_FAN_ONLY: + set_mode_(HITACHI_AC424_MODE_FAN); + break; + case climate::CLIMATE_MODE_OFF: + set_power_(false); + break; + default: + ESP_LOGW(TAG, "Unsupported mode: %s", climate_mode_to_string(this->mode)); + } + + set_temp_(static_cast(this->target_temperature)); + + switch (this->fan_mode.value()) { + case climate::CLIMATE_FAN_LOW: + set_fan_(HITACHI_AC424_FAN_LOW); + break; + case climate::CLIMATE_FAN_MEDIUM: + set_fan_(HITACHI_AC424_FAN_MEDIUM); + break; + case climate::CLIMATE_FAN_HIGH: + set_fan_(HITACHI_AC424_FAN_HIGH); + break; + case climate::CLIMATE_FAN_ON: + case climate::CLIMATE_FAN_AUTO: + default: + set_fan_(HITACHI_AC424_FAN_AUTO); + } + + switch (this->swing_mode) { + case climate::CLIMATE_SWING_BOTH: + set_swing_v_(true); + set_swing_h_(HITACHI_AC424_SWINGH_AUTO); + break; + case climate::CLIMATE_SWING_VERTICAL: + set_swing_v_(true); + set_swing_h_(HITACHI_AC424_SWINGH_MIDDLE); + break; + case climate::CLIMATE_SWING_HORIZONTAL: + set_swing_v_(false); + set_swing_h_(HITACHI_AC424_SWINGH_AUTO); + break; + case climate::CLIMATE_SWING_OFF: + set_swing_v_(false); + set_swing_h_(HITACHI_AC424_SWINGH_MIDDLE); + break; + } + + // TODO: find change value to set button, now always set to power button + set_button_(HITACHI_AC424_BUTTON_POWER); + + invert_byte_pairs(remote_state_ + 3, HITACHI_AC424_STATE_LENGTH - 3); + + auto transmit = this->transmitter_->transmit(); + auto data = transmit.get_data(); + data->set_carrier_frequency(HITACHI_AC424_FREQ); + + uint8_t repeat = 0; + for (uint8_t r = 0; r <= repeat; r++) { + // Header + data->item(HITACHI_AC424_HDR_MARK, HITACHI_AC424_HDR_SPACE); + // Data + for (uint8_t i : remote_state_) { + for (uint8_t j = 0; j < 8; j++) { + data->mark(HITACHI_AC424_BIT_MARK); + bool bit = i & (1 << j); + data->space(bit ? HITACHI_AC424_ONE_SPACE : HITACHI_AC424_ZERO_SPACE); + } + } + // Footer + data->item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_MIN_GAP); + } + transmit.perform(); + + dump_state_("Sent", remote_state_); +} + +bool HitachiClimate::parse_mode_(const uint8_t remote_state[]) { + uint8_t power = remote_state[HITACHI_AC424_POWER_BYTE]; + ESP_LOGV(TAG, "Power: %02X %02X", remote_state[HITACHI_AC424_POWER_BYTE], power); + uint8_t mode = remote_state[HITACHI_AC424_MODE_BYTE] & 0xF; + ESP_LOGV(TAG, "Mode: %02X %02X", remote_state[HITACHI_AC424_MODE_BYTE], mode); + if (power == HITACHI_AC424_POWER_ON) { + switch (mode) { + case HITACHI_AC424_MODE_COOL: + this->mode = climate::CLIMATE_MODE_COOL; + break; + case HITACHI_AC424_MODE_DRY: + this->mode = climate::CLIMATE_MODE_DRY; + break; + case HITACHI_AC424_MODE_HEAT: + this->mode = climate::CLIMATE_MODE_HEAT; + break; + case HITACHI_AC424_MODE_AUTO: + this->mode = climate::CLIMATE_MODE_HEAT_COOL; + break; + case HITACHI_AC424_MODE_FAN: + this->mode = climate::CLIMATE_MODE_FAN_ONLY; + break; + } + } else { + this->mode = climate::CLIMATE_MODE_OFF; + } + return true; +} + +bool HitachiClimate::parse_temperature_(const uint8_t remote_state[]) { + uint8_t temperature = + HITACHI_AC424_GETBITS8(remote_state[HITACHI_AC424_TEMP_BYTE], HITACHI_AC424_TEMP_OFFSET, HITACHI_AC424_TEMP_SIZE); + this->target_temperature = temperature; + ESP_LOGV(TAG, "Temperature: %02X %02u %04f", remote_state[HITACHI_AC424_TEMP_BYTE], temperature, + this->target_temperature); + return true; +} + +bool HitachiClimate::parse_fan_(const uint8_t remote_state[]) { + uint8_t fan_mode = remote_state[HITACHI_AC424_FAN_BYTE] >> 4 & 0xF; + ESP_LOGV(TAG, "Fan: %02X %02X", remote_state[HITACHI_AC424_FAN_BYTE], fan_mode); + switch (fan_mode) { + case HITACHI_AC424_FAN_MIN: + case HITACHI_AC424_FAN_LOW: + this->fan_mode = climate::CLIMATE_FAN_LOW; + break; + case HITACHI_AC424_FAN_MEDIUM: + this->fan_mode = climate::CLIMATE_FAN_MEDIUM; + break; + case HITACHI_AC424_FAN_HIGH: + case HITACHI_AC424_FAN_MAX: + this->fan_mode = climate::CLIMATE_FAN_HIGH; + break; + case HITACHI_AC424_FAN_AUTO: + this->fan_mode = climate::CLIMATE_FAN_AUTO; + break; + } + return true; +} + +bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) { + uint8_t swing_modeh = HITACHI_AC424_GETBITS8(remote_state[HITACHI_AC424_SWINGH_BYTE], HITACHI_AC424_SWINGH_OFFSET, + HITACHI_AC424_SWINGH_SIZE); + ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC424_SWINGH_BYTE], swing_modeh); + + if ((swing_modeh & 0x7) == 0x0) { + this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; + } else if ((swing_modeh & 0x3) == 0x3) { + this->swing_mode = climate::CLIMATE_SWING_OFF; + } else { + this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; + } + + return true; +} + +bool HitachiClimate::on_receive(remote_base::RemoteReceiveData data) { + // Validate header + if (!data.expect_item(HITACHI_AC424_HDR_MARK, HITACHI_AC424_HDR_SPACE)) { + ESP_LOGVV(TAG, "Header fail"); + return false; + } + + uint8_t recv_state[HITACHI_AC424_STATE_LENGTH] = {0}; + // Read all bytes. + for (uint8_t pos = 0; pos < HITACHI_AC424_STATE_LENGTH; pos++) { + // Read bit + for (int8_t bit = 0; bit < 8; bit++) { + if (data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ONE_SPACE)) + recv_state[pos] |= 1 << bit; + else if (!data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ZERO_SPACE)) { + ESP_LOGVV(TAG, "Byte %d bit %d fail", pos, bit); + return false; + } + } + } + + // Validate footer + if (!data.expect_mark(HITACHI_AC424_BIT_MARK)) { + ESP_LOGVV(TAG, "Footer fail"); + return false; + } + + dump_state_("Recv", recv_state); + + // parse mode + this->parse_mode_(recv_state); + // parse temperature + this->parse_temperature_(recv_state); + // parse fan + this->parse_fan_(recv_state); + // parse swingv + this->parse_swing_(recv_state); + this->publish_state(); + for (uint8_t i = 0; i < HITACHI_AC424_STATE_LENGTH; i++) + remote_state_[i] = recv_state[i]; + + return true; +} + +void HitachiClimate::dump_state_(const char action[], uint8_t state[]) { + for (uint16_t i = 0; i < HITACHI_AC424_STATE_LENGTH - 10; i += 10) { + ESP_LOGV(TAG, "%s: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", action, state[i + 0], state[i + 1], + state[i + 2], state[i + 3], state[i + 4], state[i + 5], state[i + 6], state[i + 7], state[i + 8], + state[i + 9]); + } + ESP_LOGV(TAG, "%s: %02X %02X %02X", action, state[40], state[41], state[42]); +} + +} // namespace hitachi_ac424 +} // namespace esphome diff --git a/esphome/components/hitachi_ac424/hitachi_ac424.h b/esphome/components/hitachi_ac424/hitachi_ac424.h new file mode 100644 index 0000000000..1005aa6df7 --- /dev/null +++ b/esphome/components/hitachi_ac424/hitachi_ac424.h @@ -0,0 +1,123 @@ +#pragma once + +#include "esphome/core/log.h" +#include "esphome/components/climate_ir/climate_ir.h" + +namespace esphome { +namespace hitachi_ac424 { + +const uint16_t HITACHI_AC424_HDR_MARK = 3416; // ac +const uint16_t HITACHI_AC424_HDR_SPACE = 1604; // ac +const uint16_t HITACHI_AC424_BIT_MARK = 463; +const uint16_t HITACHI_AC424_ONE_SPACE = 1208; +const uint16_t HITACHI_AC424_ZERO_SPACE = 372; +const uint32_t HITACHI_AC424_MIN_GAP = 100000; // just a guess. +const uint16_t HITACHI_AC424_FREQ = 38000; // Hz. + +const uint8_t HITACHI_AC424_BUTTON_BYTE = 11; +const uint8_t HITACHI_AC424_BUTTON_POWER = 0x13; +const uint8_t HITACHI_AC424_BUTTON_SLEEP = 0x31; +const uint8_t HITACHI_AC424_BUTTON_MODE = 0x41; +const uint8_t HITACHI_AC424_BUTTON_FAN = 0x42; +const uint8_t HITACHI_AC424_BUTTON_TEMP_DOWN = 0x43; +const uint8_t HITACHI_AC424_BUTTON_TEMP_UP = 0x44; +const uint8_t HITACHI_AC424_BUTTON_SWINGV = 0x81; +const uint8_t HITACHI_AC424_BUTTON_SWINGH = 0x8C; +const uint8_t HITACHI_AC424_BUTTON_MILDEWPROOF = 0xE2; + +const uint8_t HITACHI_AC424_TEMP_BYTE = 13; +const uint8_t HITACHI_AC424_TEMP_OFFSET = 2; +const uint8_t HITACHI_AC424_TEMP_SIZE = 6; +const uint8_t HITACHI_AC424_TEMP_MIN = 16; // 16C +const uint8_t HITACHI_AC424_TEMP_MAX = 32; // 32C +const uint8_t HITACHI_AC424_TEMP_FAN = 27; // 27C + +const uint8_t HITACHI_AC424_TIMER_BYTE = 15; + +const uint8_t HITACHI_AC424_MODE_BYTE = 25; +const uint8_t HITACHI_AC424_MODE_FAN = 1; +const uint8_t HITACHI_AC424_MODE_COOL = 3; +const uint8_t HITACHI_AC424_MODE_DRY = 5; +const uint8_t HITACHI_AC424_MODE_HEAT = 6; +const uint8_t HITACHI_AC424_MODE_AUTO = 14; +const uint8_t HITACHI_AC424_MODE_POWERFUL = 19; + +const uint8_t HITACHI_AC424_FAN_BYTE = HITACHI_AC424_MODE_BYTE; +const uint8_t HITACHI_AC424_FAN_MIN = 1; +const uint8_t HITACHI_AC424_FAN_LOW = 2; +const uint8_t HITACHI_AC424_FAN_MEDIUM = 3; +const uint8_t HITACHI_AC424_FAN_HIGH = 4; +const uint8_t HITACHI_AC424_FAN_AUTO = 5; +const uint8_t HITACHI_AC424_FAN_MAX = 6; +const uint8_t HITACHI_AC424_FAN_MAX_DRY = 2; + +const uint8_t HITACHI_AC424_POWER_BYTE = 27; +const uint8_t HITACHI_AC424_POWER_ON = 0xF1; +const uint8_t HITACHI_AC424_POWER_OFF = 0xE1; + +const uint8_t HITACHI_AC424_SWINGH_BYTE = 35; +const uint8_t HITACHI_AC424_SWINGH_OFFSET = 0; // Mask 0b00000xxx +const uint8_t HITACHI_AC424_SWINGH_SIZE = 3; // Mask 0b00000xxx +const uint8_t HITACHI_AC424_SWINGH_AUTO = 0; // 0b000 +const uint8_t HITACHI_AC424_SWINGH_RIGHT_MAX = 1; // 0b001 +const uint8_t HITACHI_AC424_SWINGH_RIGHT = 2; // 0b010 +const uint8_t HITACHI_AC424_SWINGH_MIDDLE = 3; // 0b011 +const uint8_t HITACHI_AC424_SWINGH_LEFT = 4; // 0b100 +const uint8_t HITACHI_AC424_SWINGH_LEFT_MAX = 5; // 0b101 + +const uint8_t HITACHI_AC424_SWINGV_BYTE = 37; +const uint8_t HITACHI_AC424_SWINGV_OFFSET = 5; // Mask 0b00x00000 + +const uint8_t HITACHI_AC424_MILDEWPROOF_BYTE = HITACHI_AC424_SWINGV_BYTE; +const uint8_t HITACHI_AC424_MILDEWPROOF_OFFSET = 2; // Mask 0b00000x00 + +const uint16_t HITACHI_AC424_STATE_LENGTH = 53; +const uint16_t HITACHI_AC424_BITS = HITACHI_AC424_STATE_LENGTH * 8; + +#define HITACHI_AC424_GETBIT8(a, b) ((a) & ((uint8_t) 1 << (b))) +#define HITACHI_AC424_GETBITS8(data, offset, size) \ + (((data) & (((uint8_t) UINT8_MAX >> (8 - (size))) << (offset))) >> (offset)) + +class HitachiClimate : public climate_ir::ClimateIR { + public: + HitachiClimate() + : climate_ir::ClimateIR(HITACHI_AC424_TEMP_MIN, HITACHI_AC424_TEMP_MAX, 1.0F, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL}) {} + + protected: + uint8_t remote_state_[HITACHI_AC424_STATE_LENGTH]{ + 0x01, 0x10, 0x00, 0x40, 0xBF, 0xFF, 0x00, 0xCC, 0x33, 0x92, 0x6D, 0x13, 0xEC, 0x5C, 0xA3, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x53, 0xAC, 0xF1, 0x0E, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0x7F, 0x03, + 0xFC, 0x01, 0xFE, 0x88, 0x77, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}; + uint8_t previous_temp_{27}; + // Transmit via IR the state of this climate controller. + void transmit_state() override; + bool get_power_(); + void set_power_(bool on); + uint8_t get_mode_(); + void set_mode_(uint8_t mode); + void set_temp_(uint8_t celsius, bool set_previous = false); + uint8_t get_fan_(); + void set_fan_(uint8_t speed); + void set_swing_v_toggle_(bool on); + bool get_swing_v_toggle_(); + void set_swing_v_(bool on); + bool get_swing_v_(); + void set_swing_h_(uint8_t position); + uint8_t get_swing_h_(); + uint8_t get_button_(); + void set_button_(uint8_t button); + // Handle received IR Buffer + bool on_receive(remote_base::RemoteReceiveData data) override; + bool parse_mode_(const uint8_t remote_state[]); + bool parse_temperature_(const uint8_t remote_state[]); + bool parse_fan_(const uint8_t remote_state[]); + bool parse_swing_(const uint8_t remote_state[]); + bool parse_state_frame_(const uint8_t frame[]); + void dump_state_(const char action[], uint8_t remote_state[]); +}; + +} // namespace hitachi_ac424 +} // namespace esphome From fe7af21c91e5845fb52d243a6d30188c15a4ef7d Mon Sep 17 00:00:00 2001 From: buxtronix Date: Mon, 9 Aug 2021 06:05:36 +1000 Subject: [PATCH 1150/1841] Anova fahrenheit support (#2126) Co-authored-by: Ben Buxton --- esphome/components/anova/anova.cpp | 19 ++++++++++++++----- esphome/components/anova/anova.h | 2 ++ esphome/components/anova/anova_base.cpp | 20 +++++++++++++++++++- esphome/components/anova/anova_base.h | 1 + esphome/components/anova/climate.py | 15 +++++++++++++-- tests/test1.yaml | 1 + 6 files changed, 50 insertions(+), 8 deletions(-) diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index 63e0710936..f330969c33 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -90,19 +90,24 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ if (this->codec_->has_running()) { this->mode = this->codec_->running_ ? climate::CLIMATE_MODE_HEAT : climate::CLIMATE_MODE_OFF; } + if (this->codec_->has_unit()) { + this->fahrenheit_ = (this->codec_->unit_ == 'f'); + ESP_LOGD(TAG, "Anova units is %s", this->fahrenheit_ ? "fahrenheit" : "celcius"); + this->current_request_++; + } this->publish_state(); - if (this->current_request_ > 0) { + if (this->current_request_ > 1) { AnovaPacket *pkt = nullptr; switch (this->current_request_++) { - case 1: + case 2: pkt = this->codec_->get_read_target_temp_request(); break; - case 2: + case 3: pkt = this->codec_->get_read_current_temp_request(); break; default: - this->current_request_ = 0; + this->current_request_ = 1; break; } if (pkt != nullptr) { @@ -121,12 +126,16 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ } } +void Anova::set_unit_of_measurement(const char *unit) { this->fahrenheit_ = !strncmp(unit, "f", 1); } + void Anova::update() { if (this->node_state != espbt::ClientState::Established) return; - if (this->current_request_ == 0) { + if (this->current_request_ < 2) { auto pkt = this->codec_->get_read_device_status_request(); + if (this->current_request_ == 0) + auto pkt = this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c'); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) diff --git a/esphome/components/anova/anova.h b/esphome/components/anova/anova.h index 63d03cb329..42bdbcaed0 100644 --- a/esphome/components/anova/anova.h +++ b/esphome/components/anova/anova.h @@ -36,12 +36,14 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode traits.set_visual_temperature_step(0.1); return traits; } + void set_unit_of_measurement(const char *); protected: AnovaCodec *codec_; void control(const climate::ClimateCall &call) override; uint16_t char_handle_; uint8_t current_request_; + bool fahrenheit_; }; } // namespace anova diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp index 8cbc481643..ad581b1e6c 100644 --- a/esphome/components/anova/anova_base.cpp +++ b/esphome/components/anova/anova_base.cpp @@ -3,6 +3,10 @@ namespace esphome { namespace anova { +float ftoc(float f) { return (f - 32.0) * (5.0f / 9.0f); } + +float ctof(float c) { return (c * 9.0f / 5.0f) + 32.0; } + AnovaPacket *AnovaCodec::clean_packet_() { this->packet_.length = strlen((char *) this->packet_.data); this->packet_.data[this->packet_.length] = '\0'; @@ -42,6 +46,8 @@ AnovaPacket *AnovaCodec::get_read_data_request() { AnovaPacket *AnovaCodec::get_set_target_temp_request(float temperature) { this->current_query_ = SET_TARGET_TEMPERATURE; + if (this->fahrenheit_) + temperature = ctof(temperature); sprintf((char *) this->packet_.data, CMD_SET_TARGET_TEMP, temperature); return this->clean_packet_(); } @@ -67,7 +73,6 @@ AnovaPacket *AnovaCodec::get_stop_request() { void AnovaCodec::decode(const uint8_t *data, uint16_t length) { memset(this->buf_, 0, 32); strncpy(this->buf_, (char *) data, length); - ESP_LOGV("anova", "Received: %s\n", this->buf_); this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false; switch (this->current_query_) { case READ_DEVICE_STATUS: { @@ -97,19 +102,32 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) { } case READ_TARGET_TEMPERATURE: { this->target_temp_ = strtof(this->buf_, nullptr); + if (this->fahrenheit_) + this->target_temp_ = ftoc(this->target_temp_); this->has_target_temp_ = true; break; } case SET_TARGET_TEMPERATURE: { this->target_temp_ = strtof(this->buf_, nullptr); + if (this->fahrenheit_) + this->target_temp_ = ftoc(this->target_temp_); this->has_target_temp_ = true; break; } case READ_CURRENT_TEMPERATURE: { this->current_temp_ = strtof(this->buf_, nullptr); + if (this->fahrenheit_) + this->current_temp_ = ftoc(this->current_temp_); this->has_current_temp_ = true; break; } + case SET_UNIT: + case READ_UNIT: { + this->unit_ = this->buf_[0]; + this->fahrenheit_ = this->buf_[0] == 'f'; + this->has_unit_ = true; + break; + } default: break; } diff --git a/esphome/components/anova/anova_base.h b/esphome/components/anova/anova_base.h index e94fe619a6..7c1383512d 100644 --- a/esphome/components/anova/anova_base.h +++ b/esphome/components/anova/anova_base.h @@ -71,6 +71,7 @@ class AnovaCodec { bool has_unit_; bool has_running_; char buf_[32]; + bool fahrenheit_; CurrentQuery current_query_; }; diff --git a/esphome/components/anova/climate.py b/esphome/components/anova/climate.py index 763ae1f4be..bdd77d6a33 100644 --- a/esphome/components/anova/climate.py +++ b/esphome/components/anova/climate.py @@ -1,7 +1,12 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import climate, ble_client -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_UNIT_OF_MEASUREMENT + +UNITS = { + "f": "f", + "c": "c", +} CODEOWNERS = ["@buxtronix"] DEPENDENCIES = ["ble_client"] @@ -12,7 +17,12 @@ Anova = anova_ns.class_( ) CONFIG_SCHEMA = ( - climate.CLIMATE_SCHEMA.extend({cv.GenerateID(): cv.declare_id(Anova)}) + climate.CLIMATE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(Anova), + cv.Required(CONF_UNIT_OF_MEASUREMENT): cv.enum(UNITS), + } + ) .extend(ble_client.BLE_CLIENT_SCHEMA) .extend(cv.polling_component_schema("60s")) ) @@ -23,3 +33,4 @@ async def to_code(config): await cg.register_component(var, config) await climate.register_climate(var, config) await ble_client.register_ble_node(var, config) + cg.add(var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT])) diff --git a/tests/test1.yaml b/tests/test1.yaml index c1f3e378c4..6d6fc113ea 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1566,6 +1566,7 @@ climate: - platform: anova name: Anova cooker ble_client_id: ble_blah + unit_of_measurement: c midea_dongle: uart_id: uart0 From b3ae3e1feb0d084d413e7153a66d4b6d93a1b901 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 9 Aug 2021 10:30:19 +1200 Subject: [PATCH 1151/1841] Tidy HA addon (#1937) --- docker/rootfs/etc/cont-init.d/30-esphome.sh | 23 --------------------- docker/rootfs/etc/cont-init.d/40-migrate.sh | 11 ---------- 2 files changed, 34 deletions(-) delete mode 100755 docker/rootfs/etc/cont-init.d/30-esphome.sh delete mode 100755 docker/rootfs/etc/cont-init.d/40-migrate.sh diff --git a/docker/rootfs/etc/cont-init.d/30-esphome.sh b/docker/rootfs/etc/cont-init.d/30-esphome.sh deleted file mode 100755 index d9a80cde2e..0000000000 --- a/docker/rootfs/etc/cont-init.d/30-esphome.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/with-contenv bashio -# ============================================================================== -# Community Hass.io Add-ons: ESPHome -# This files installs the user ESPHome version if specified -# ============================================================================== - -declare esphome_version - -if bashio::config.has_value 'esphome_version'; then - esphome_version=$(bashio::config 'esphome_version') - if [[ $esphome_version == *":"* ]]; then - IFS=':' read -r -a array <<< "$esphome_version" - username=${array[0]} - ref=${array[1]} - else - username="esphome" - ref=$esphome_version - fi - full_url="https://github.com/${username}/esphome/archive/${ref}.zip" - bashio::log.info "Installing esphome version '${esphome_version}' (${full_url})..." - pip3 install -U --no-cache-dir "${full_url}" \ - || bashio::exit.nok "Failed installing esphome pinned version." -fi diff --git a/docker/rootfs/etc/cont-init.d/40-migrate.sh b/docker/rootfs/etc/cont-init.d/40-migrate.sh deleted file mode 100755 index 88e8be26b9..0000000000 --- a/docker/rootfs/etc/cont-init.d/40-migrate.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/with-contenv bashio -# ============================================================================== -# Community Hass.io Add-ons: ESPHome -# This files migrates the esphome config directory from the old path -# ============================================================================== - -if [[ ! -d /config/esphome && -d /config/esphomeyaml ]]; then - echo "Moving config directory from /config/esphomeyaml to /config/esphome" - mv /config/esphomeyaml /config/esphome - mv /config/esphome/.esphomeyaml /config/esphome/.esphome -fi From ea4a458214cbfebfe68410572548e56c36a736db Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 9 Aug 2021 16:44:52 +1200 Subject: [PATCH 1152/1841] Removed unused arguments from rgbww code (#2137) --- esphome/components/light/light_color_values.h | 10 ++++------ esphome/components/light/light_state.cpp | 10 ++++------ esphome/components/light/light_state.h | 6 +++--- esphome/components/rgbww/rgbww_light_output.h | 3 +-- 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index a6abac8cdf..81dcb614a6 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -157,16 +157,14 @@ class LightColorValues { } /// Convert these light color values to an RGBWW representation with the given parameters. - void as_rgbww(float color_temperature_cw, float color_temperature_ww, float *red, float *green, float *blue, - float *cold_white, float *warm_white, float gamma = 0, bool constant_brightness = false, - bool color_interlock = false) const { + void as_rgbww(float *red, float *green, float *blue, float *cold_white, float *warm_white, float gamma = 0, + bool constant_brightness = false) const { this->as_rgb(red, green, blue, gamma); - this->as_cwww(0, 0, cold_white, warm_white, gamma, constant_brightness); + this->as_cwww(cold_white, warm_white, gamma, constant_brightness); } /// Convert these light color values to an CWWW representation with the given parameters. - void as_cwww(float color_temperature_cw, float color_temperature_ww, float *cold_white, float *warm_white, - float gamma = 0, bool constant_brightness = false) const { + void as_cwww(float *cold_white, float *warm_white, float gamma = 0, bool constant_brightness = false) const { if (this->color_mode_ & ColorCapability::COLD_WARM_WHITE) { const float cw_level = gamma_correct(this->cold_white_, gamma); const float ww_level = gamma_correct(this->warm_white_, gamma); diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index d5bf720f61..d24b40e68e 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -1,6 +1,6 @@ #include "light_state.h" -#include "light_output.h" #include "esphome/core/log.h" +#include "light_output.h" namespace esphome { namespace light { @@ -174,15 +174,13 @@ void LightState::current_values_as_rgbw(float *red, float *green, float *blue, f this->current_values.as_rgbw(red, green, blue, white, this->gamma_correct_, false); } void LightState::current_values_as_rgbww(float *red, float *green, float *blue, float *cold_white, float *warm_white, - bool constant_brightness, bool color_interlock) { + bool constant_brightness) { auto traits = this->get_traits(); - this->current_values.as_rgbww(traits.get_min_mireds(), traits.get_max_mireds(), red, green, blue, cold_white, - warm_white, this->gamma_correct_, constant_brightness, false); + this->current_values.as_rgbww(red, green, blue, cold_white, warm_white, this->gamma_correct_, constant_brightness); } void LightState::current_values_as_cwww(float *cold_white, float *warm_white, bool constant_brightness) { auto traits = this->get_traits(); - this->current_values.as_cwww(traits.get_min_mireds(), traits.get_max_mireds(), cold_white, warm_white, - this->gamma_correct_, constant_brightness); + this->current_values.as_cwww(cold_white, warm_white, this->gamma_correct_, constant_brightness); } void LightState::start_effect_(uint32_t effect_index) { diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index 98fa21d480..b0c60c625a 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -3,9 +3,9 @@ #include "esphome/core/component.h" #include "esphome/core/optional.h" #include "esphome/core/preferences.h" -#include "light_effect.h" -#include "light_color_values.h" #include "light_call.h" +#include "light_color_values.h" +#include "light_effect.h" #include "light_traits.h" #include "light_transformer.h" @@ -126,7 +126,7 @@ class LightState : public Nameable, public Component { void current_values_as_rgbw(float *red, float *green, float *blue, float *white, bool color_interlock = false); void current_values_as_rgbww(float *red, float *green, float *blue, float *cold_white, float *warm_white, - bool constant_brightness = false, bool color_interlock = false); + bool constant_brightness = false); void current_values_as_cwww(float *cold_white, float *warm_white, bool constant_brightness = false); diff --git a/esphome/components/rgbww/rgbww_light_output.h b/esphome/components/rgbww/rgbww_light_output.h index 2182cc201e..5a86b88595 100644 --- a/esphome/components/rgbww/rgbww_light_output.h +++ b/esphome/components/rgbww/rgbww_light_output.h @@ -30,8 +30,7 @@ class RGBWWLightOutput : public light::LightOutput { } void write_state(light::LightState *state) override { float red, green, blue, cwhite, wwhite; - state->current_values_as_rgbww(&red, &green, &blue, &cwhite, &wwhite, this->constant_brightness_, - this->color_interlock_); + state->current_values_as_rgbww(&red, &green, &blue, &cwhite, &wwhite, this->constant_brightness_); this->red_->set_level(red); this->green_->set_level(green); this->blue_->set_level(blue); From 926bcc71ae7d19d82e39b38bc8ecb25f956dd0f3 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 9 Aug 2021 22:32:06 +0200 Subject: [PATCH 1153/1841] Only compile protobuf dumping when very verbose logging is enabled (#2139) --- esphome/components/api/api_pb2.cpp | 114 ++++++++++++++++++++ esphome/components/api/api_pb2.h | 114 ++++++++++++++++++++ esphome/components/api/api_pb2_service.cpp | 118 +++++++++++++++++++++ esphome/components/api/proto.cpp | 2 + esphome/components/api/proto.h | 7 ++ script/api_protobuf/api_protobuf.py | 10 +- 6 files changed, 364 insertions(+), 1 deletion(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 210ba49dfc..65fe16d6e0 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -261,6 +261,7 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) } } void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); } +#ifdef HAS_PROTO_MESSAGE_DUMP void HelloRequest::dump_to(std::string &out) const { char buffer[64]; out.append("HelloRequest {\n"); @@ -269,6 +270,7 @@ void HelloRequest::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool HelloResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { @@ -298,6 +300,7 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(2, this->api_version_minor); buffer.encode_string(3, this->server_info); } +#ifdef HAS_PROTO_MESSAGE_DUMP void HelloResponse::dump_to(std::string &out) const { char buffer[64]; out.append("HelloResponse {\n"); @@ -316,6 +319,7 @@ void HelloResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -327,6 +331,7 @@ bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value } } void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->password); } +#ifdef HAS_PROTO_MESSAGE_DUMP void ConnectRequest::dump_to(std::string &out) const { char buffer[64]; out.append("ConnectRequest {\n"); @@ -335,6 +340,7 @@ void ConnectRequest::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool ConnectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { @@ -346,6 +352,7 @@ bool ConnectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { } } void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); } +#ifdef HAS_PROTO_MESSAGE_DUMP void ConnectResponse::dump_to(std::string &out) const { char buffer[64]; out.append("ConnectResponse {\n"); @@ -354,16 +361,27 @@ void ConnectResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif void DisconnectRequest::encode(ProtoWriteBuffer buffer) const {} +#ifdef HAS_PROTO_MESSAGE_DUMP void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); } +#endif void DisconnectResponse::encode(ProtoWriteBuffer buffer) const {} +#ifdef HAS_PROTO_MESSAGE_DUMP void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); } +#endif void PingRequest::encode(ProtoWriteBuffer buffer) const {} +#ifdef HAS_PROTO_MESSAGE_DUMP void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); } +#endif void PingResponse::encode(ProtoWriteBuffer buffer) const {} +#ifdef HAS_PROTO_MESSAGE_DUMP void PingResponse::dump_to(std::string &out) const { out.append("PingResponse {}"); } +#endif void DeviceInfoRequest::encode(ProtoWriteBuffer buffer) const {} +#ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); } +#endif bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { @@ -423,6 +441,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(8, this->project_name); buffer.encode_string(9, this->project_version); } +#ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoResponse::dump_to(std::string &out) const { char buffer[64]; out.append("DeviceInfoResponse {\n"); @@ -463,12 +482,19 @@ void DeviceInfoResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif void ListEntitiesRequest::encode(ProtoWriteBuffer buffer) const {} +#ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesRequest::dump_to(std::string &out) const { out.append("ListEntitiesRequest {}"); } +#endif void ListEntitiesDoneResponse::encode(ProtoWriteBuffer buffer) const {} +#ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesDoneResponse::dump_to(std::string &out) const { out.append("ListEntitiesDoneResponse {}"); } +#endif void SubscribeStatesRequest::encode(ProtoWriteBuffer buffer) const {} +#ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeStatesRequest::dump_to(std::string &out) const { out.append("SubscribeStatesRequest {}"); } +#endif bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 6: { @@ -519,6 +545,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->device_class); buffer.encode_bool(6, this->is_status_binary_sensor); } +#ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { char buffer[64]; out.append("ListEntitiesBinarySensorResponse {\n"); @@ -548,6 +575,7 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool BinarySensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { @@ -577,6 +605,7 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(2, this->state); buffer.encode_bool(3, this->missing_state); } +#ifdef HAS_PROTO_MESSAGE_DUMP void BinarySensorStateResponse::dump_to(std::string &out) const { char buffer[64]; out.append("BinarySensorStateResponse {\n"); @@ -594,6 +623,7 @@ void BinarySensorStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 5: { @@ -654,6 +684,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->supports_tilt); buffer.encode_string(8, this->device_class); } +#ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { char buffer[64]; out.append("ListEntitiesCoverResponse {\n"); @@ -691,6 +722,7 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool CoverStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { @@ -730,6 +762,7 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(4, this->tilt); buffer.encode_enum(5, this->current_operation); } +#ifdef HAS_PROTO_MESSAGE_DUMP void CoverStateResponse::dump_to(std::string &out) const { char buffer[64]; out.append("CoverStateResponse {\n"); @@ -757,6 +790,7 @@ void CoverStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool CoverCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { @@ -811,6 +845,7 @@ void CoverCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(7, this->tilt); buffer.encode_bool(8, this->stop); } +#ifdef HAS_PROTO_MESSAGE_DUMP void CoverCommandRequest::dump_to(std::string &out) const { char buffer[64]; out.append("CoverCommandRequest {\n"); @@ -850,6 +885,7 @@ void CoverCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 5: { @@ -910,6 +946,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->supports_direction); buffer.encode_int32(8, this->supported_speed_count); } +#ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesFanResponse::dump_to(std::string &out) const { char buffer[64]; out.append("ListEntitiesFanResponse {\n"); @@ -948,6 +985,7 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { @@ -992,6 +1030,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(5, this->direction); buffer.encode_int32(6, this->speed_level); } +#ifdef HAS_PROTO_MESSAGE_DUMP void FanStateResponse::dump_to(std::string &out) const { char buffer[64]; out.append("FanStateResponse {\n"); @@ -1022,6 +1061,7 @@ void FanStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { @@ -1091,6 +1131,7 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(10, this->has_speed_level); buffer.encode_int32(11, this->speed_level); } +#ifdef HAS_PROTO_MESSAGE_DUMP void FanCommandRequest::dump_to(std::string &out) const { char buffer[64]; out.append("FanCommandRequest {\n"); @@ -1141,6 +1182,7 @@ void FanCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 12: { @@ -1225,6 +1267,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(11, it, true); } } +#ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLightResponse::dump_to(std::string &out) const { char buffer[64]; out.append("ListEntitiesLightResponse {\n"); @@ -1284,6 +1327,7 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { } out.append("}"); } +#endif bool LightStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { @@ -1369,6 +1413,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(13, this->warm_white); buffer.encode_string(9, this->effect); } +#ifdef HAS_PROTO_MESSAGE_DUMP void LightStateResponse::dump_to(std::string &out) const { char buffer[64]; out.append("LightStateResponse {\n"); @@ -1435,6 +1480,7 @@ void LightStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { @@ -1590,6 +1636,7 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(18, this->has_effect); buffer.encode_string(19, this->effect); } +#ifdef HAS_PROTO_MESSAGE_DUMP void LightCommandRequest::dump_to(std::string &out) const { char buffer[64]; out.append("LightCommandRequest {\n"); @@ -1714,6 +1761,7 @@ void LightCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 7: { @@ -1789,6 +1837,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(10, this->state_class); buffer.encode_enum(11, this->last_reset_type); } +#ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSensorResponse::dump_to(std::string &out) const { char buffer[64]; out.append("ListEntitiesSensorResponse {\n"); @@ -1839,6 +1888,7 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 3: { @@ -1868,6 +1918,7 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(2, this->state); buffer.encode_bool(3, this->missing_state); } +#ifdef HAS_PROTO_MESSAGE_DUMP void SensorStateResponse::dump_to(std::string &out) const { char buffer[64]; out.append("SensorStateResponse {\n"); @@ -1886,6 +1937,7 @@ void SensorStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 6: { @@ -1936,6 +1988,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->assumed_state); } +#ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSwitchResponse::dump_to(std::string &out) const { char buffer[64]; out.append("ListEntitiesSwitchResponse {\n"); @@ -1965,6 +2018,7 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool SwitchStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { @@ -1989,6 +2043,7 @@ void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_bool(2, this->state); } +#ifdef HAS_PROTO_MESSAGE_DUMP void SwitchStateResponse::dump_to(std::string &out) const { char buffer[64]; out.append("SwitchStateResponse {\n"); @@ -2002,6 +2057,7 @@ void SwitchStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool SwitchCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { @@ -2026,6 +2082,7 @@ void SwitchCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_bool(2, this->state); } +#ifdef HAS_PROTO_MESSAGE_DUMP void SwitchCommandRequest::dump_to(std::string &out) const { char buffer[64]; out.append("SwitchCommandRequest {\n"); @@ -2039,6 +2096,7 @@ void SwitchCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool ListEntitiesTextSensorResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -2078,6 +2136,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(4, this->unique_id); buffer.encode_string(5, this->icon); } +#ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { char buffer[64]; out.append("ListEntitiesTextSensorResponse {\n"); @@ -2103,6 +2162,7 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool TextSensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 3: { @@ -2138,6 +2198,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(2, this->state); buffer.encode_bool(3, this->missing_state); } +#ifdef HAS_PROTO_MESSAGE_DUMP void TextSensorStateResponse::dump_to(std::string &out) const { char buffer[64]; out.append("TextSensorStateResponse {\n"); @@ -2155,6 +2216,7 @@ void TextSensorStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool SubscribeLogsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { @@ -2173,6 +2235,7 @@ void SubscribeLogsRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(1, this->level); buffer.encode_bool(2, this->dump_config); } +#ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeLogsRequest::dump_to(std::string &out) const { char buffer[64]; out.append("SubscribeLogsRequest {\n"); @@ -2185,6 +2248,7 @@ void SubscribeLogsRequest::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool SubscribeLogsResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { @@ -2219,6 +2283,7 @@ void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(3, this->message); buffer.encode_bool(4, this->send_failed); } +#ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeLogsResponse::dump_to(std::string &out) const { char buffer[64]; out.append("SubscribeLogsResponse {\n"); @@ -2239,10 +2304,13 @@ void SubscribeLogsResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif void SubscribeHomeassistantServicesRequest::encode(ProtoWriteBuffer buffer) const {} +#ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const { out.append("SubscribeHomeassistantServicesRequest {}"); } +#endif bool HomeassistantServiceMap::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -2261,6 +2329,7 @@ void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->key); buffer.encode_string(2, this->value); } +#ifdef HAS_PROTO_MESSAGE_DUMP void HomeassistantServiceMap::dump_to(std::string &out) const { char buffer[64]; out.append("HomeassistantServiceMap {\n"); @@ -2273,6 +2342,7 @@ void HomeassistantServiceMap::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool HomeassistantServiceResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 5: { @@ -2318,6 +2388,7 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_bool(5, this->is_event); } +#ifdef HAS_PROTO_MESSAGE_DUMP void HomeassistantServiceResponse::dump_to(std::string &out) const { char buffer[64]; out.append("HomeassistantServiceResponse {\n"); @@ -2348,10 +2419,13 @@ void HomeassistantServiceResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif void SubscribeHomeAssistantStatesRequest::encode(ProtoWriteBuffer buffer) const {} +#ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const { out.append("SubscribeHomeAssistantStatesRequest {}"); } +#endif bool SubscribeHomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -2370,6 +2444,7 @@ void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const buffer.encode_string(1, this->entity_id); buffer.encode_string(2, this->attribute); } +#ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { char buffer[64]; out.append("SubscribeHomeAssistantStateResponse {\n"); @@ -2382,6 +2457,7 @@ void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -2405,6 +2481,7 @@ void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(2, this->state); buffer.encode_string(3, this->attribute); } +#ifdef HAS_PROTO_MESSAGE_DUMP void HomeAssistantStateResponse::dump_to(std::string &out) const { char buffer[64]; out.append("HomeAssistantStateResponse {\n"); @@ -2421,8 +2498,11 @@ void HomeAssistantStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif void GetTimeRequest::encode(ProtoWriteBuffer buffer) const {} +#ifdef HAS_PROTO_MESSAGE_DUMP void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); } +#endif bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { switch (field_id) { case 1: { @@ -2434,6 +2514,7 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { } } void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); } +#ifdef HAS_PROTO_MESSAGE_DUMP void GetTimeResponse::dump_to(std::string &out) const { char buffer[64]; out.append("GetTimeResponse {\n"); @@ -2443,6 +2524,7 @@ void GetTimeResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool ListEntitiesServicesArgument::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { @@ -2467,6 +2549,7 @@ void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->name); buffer.encode_enum(2, this->type); } +#ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesServicesArgument::dump_to(std::string &out) const { char buffer[64]; out.append("ListEntitiesServicesArgument {\n"); @@ -2479,6 +2562,7 @@ void ListEntitiesServicesArgument::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool ListEntitiesServicesResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -2510,6 +2594,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_message(3, it, true); } } +#ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesServicesResponse::dump_to(std::string &out) const { char buffer[64]; out.append("ListEntitiesServicesResponse {\n"); @@ -2529,6 +2614,7 @@ void ListEntitiesServicesResponse::dump_to(std::string &out) const { } out.append("}"); } +#endif bool ExecuteServiceArgument::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { @@ -2602,6 +2688,7 @@ void ExecuteServiceArgument::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(9, it, true); } } +#ifdef HAS_PROTO_MESSAGE_DUMP void ExecuteServiceArgument::dump_to(std::string &out) const { char buffer[64]; out.append("ExecuteServiceArgument {\n"); @@ -2655,6 +2742,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const { } out.append("}"); } +#endif bool ExecuteServiceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { @@ -2681,6 +2769,7 @@ void ExecuteServiceRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_message(2, it, true); } } +#ifdef HAS_PROTO_MESSAGE_DUMP void ExecuteServiceRequest::dump_to(std::string &out) const { char buffer[64]; out.append("ExecuteServiceRequest {\n"); @@ -2696,6 +2785,7 @@ void ExecuteServiceRequest::dump_to(std::string &out) const { } out.append("}"); } +#endif bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -2730,6 +2820,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(3, this->name); buffer.encode_string(4, this->unique_id); } +#ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCameraResponse::dump_to(std::string &out) const { char buffer[64]; out.append("ListEntitiesCameraResponse {\n"); @@ -2751,6 +2842,7 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool CameraImageResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 3: { @@ -2786,6 +2878,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(2, this->data); buffer.encode_bool(3, this->done); } +#ifdef HAS_PROTO_MESSAGE_DUMP void CameraImageResponse::dump_to(std::string &out) const { char buffer[64]; out.append("CameraImageResponse {\n"); @@ -2803,6 +2896,7 @@ void CameraImageResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool CameraImageRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { @@ -2821,6 +2915,7 @@ void CameraImageRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->single); buffer.encode_bool(2, this->stream); } +#ifdef HAS_PROTO_MESSAGE_DUMP void CameraImageRequest::dump_to(std::string &out) const { char buffer[64]; out.append("CameraImageRequest {\n"); @@ -2833,6 +2928,7 @@ void CameraImageRequest::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 5: { @@ -2950,6 +3046,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(17, it, true); } } +#ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesClimateResponse::dump_to(std::string &out) const { char buffer[64]; out.append("ListEntitiesClimateResponse {\n"); @@ -3038,6 +3135,7 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { } out.append("}"); } +#endif bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { @@ -3123,6 +3221,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(12, this->preset); buffer.encode_string(13, this->custom_preset); } +#ifdef HAS_PROTO_MESSAGE_DUMP void ClimateStateResponse::dump_to(std::string &out) const { char buffer[64]; out.append("ClimateStateResponse {\n"); @@ -3184,6 +3283,7 @@ void ClimateStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { @@ -3309,6 +3409,7 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(20, this->has_custom_preset); buffer.encode_string(21, this->custom_preset); } +#ifdef HAS_PROTO_MESSAGE_DUMP void ClimateCommandRequest::dump_to(std::string &out) const { char buffer[64]; out.append("ClimateCommandRequest {\n"); @@ -3401,6 +3502,7 @@ void ClimateCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -3455,6 +3557,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(7, this->max_value); buffer.encode_float(8, this->step); } +#ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { char buffer[64]; out.append("ListEntitiesNumberResponse {\n"); @@ -3495,6 +3598,7 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool NumberStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 3: { @@ -3524,6 +3628,7 @@ void NumberStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(2, this->state); buffer.encode_bool(3, this->missing_state); } +#ifdef HAS_PROTO_MESSAGE_DUMP void NumberStateResponse::dump_to(std::string &out) const { char buffer[64]; out.append("NumberStateResponse {\n"); @@ -3542,6 +3647,7 @@ void NumberStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool NumberCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { switch (field_id) { case 1: { @@ -3560,6 +3666,7 @@ void NumberCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_float(2, this->state); } +#ifdef HAS_PROTO_MESSAGE_DUMP void NumberCommandRequest::dump_to(std::string &out) const { char buffer[64]; out.append("NumberCommandRequest {\n"); @@ -3574,6 +3681,7 @@ void NumberCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool ListEntitiesSelectResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -3620,6 +3728,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(6, it, true); } } +#ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSelectResponse::dump_to(std::string &out) const { char buffer[64]; out.append("ListEntitiesSelectResponse {\n"); @@ -3651,6 +3760,7 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { } out.append("}"); } +#endif bool SelectStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 3: { @@ -3686,6 +3796,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(2, this->state); buffer.encode_bool(3, this->missing_state); } +#ifdef HAS_PROTO_MESSAGE_DUMP void SelectStateResponse::dump_to(std::string &out) const { char buffer[64]; out.append("SelectStateResponse {\n"); @@ -3703,6 +3814,7 @@ void SelectStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif bool SelectCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { @@ -3727,6 +3839,7 @@ void SelectCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_string(2, this->state); } +#ifdef HAS_PROTO_MESSAGE_DUMP void SelectCommandRequest::dump_to(std::string &out) const { char buffer[64]; out.append("SelectCommandRequest {\n"); @@ -3740,6 +3853,7 @@ void SelectCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 47b0229dd5..9849f05a98 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -123,7 +123,9 @@ class HelloRequest : public ProtoMessage { public: std::string client_info{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; @@ -134,7 +136,9 @@ class HelloResponse : public ProtoMessage { uint32_t api_version_minor{0}; std::string server_info{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; @@ -144,7 +148,9 @@ class ConnectRequest : public ProtoMessage { public: std::string password{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; @@ -153,7 +159,9 @@ class ConnectResponse : public ProtoMessage { public: bool invalid_password{false}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_varint(uint32_t field_id, ProtoVarInt value) override; @@ -161,35 +169,45 @@ class ConnectResponse : public ProtoMessage { class DisconnectRequest : public ProtoMessage { public: void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: }; class DisconnectResponse : public ProtoMessage { public: void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: }; class PingRequest : public ProtoMessage { public: void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: }; class PingResponse : public ProtoMessage { public: void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: }; class DeviceInfoRequest : public ProtoMessage { public: void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: }; @@ -205,7 +223,9 @@ class DeviceInfoResponse : public ProtoMessage { std::string project_name{}; std::string project_version{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; @@ -214,21 +234,27 @@ class DeviceInfoResponse : public ProtoMessage { class ListEntitiesRequest : public ProtoMessage { public: void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: }; class ListEntitiesDoneResponse : public ProtoMessage { public: void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: }; class SubscribeStatesRequest : public ProtoMessage { public: void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: }; @@ -241,7 +267,9 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage { std::string device_class{}; bool is_status_binary_sensor{false}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -254,7 +282,9 @@ class BinarySensorStateResponse : public ProtoMessage { bool state{false}; bool missing_state{false}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -271,7 +301,9 @@ class ListEntitiesCoverResponse : public ProtoMessage { bool supports_tilt{false}; std::string device_class{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -286,7 +318,9 @@ class CoverStateResponse : public ProtoMessage { float tilt{0.0f}; enums::CoverOperation current_operation{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -303,7 +337,9 @@ class CoverCommandRequest : public ProtoMessage { float tilt{0.0f}; bool stop{false}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -320,7 +356,9 @@ class ListEntitiesFanResponse : public ProtoMessage { bool supports_direction{false}; int32_t supported_speed_count{0}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -336,7 +374,9 @@ class FanStateResponse : public ProtoMessage { enums::FanDirection direction{}; int32_t speed_level{0}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -356,7 +396,9 @@ class FanCommandRequest : public ProtoMessage { bool has_speed_level{false}; int32_t speed_level{0}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -377,7 +419,9 @@ class ListEntitiesLightResponse : public ProtoMessage { float max_mireds{0.0f}; std::vector effects{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -400,7 +444,9 @@ class LightStateResponse : public ProtoMessage { float warm_white{0.0f}; std::string effect{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -437,7 +483,9 @@ class LightCommandRequest : public ProtoMessage { bool has_effect{false}; std::string effect{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -458,7 +506,9 @@ class ListEntitiesSensorResponse : public ProtoMessage { enums::SensorStateClass state_class{}; enums::SensorLastResetType last_reset_type{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -471,7 +521,9 @@ class SensorStateResponse : public ProtoMessage { float state{0.0f}; bool missing_state{false}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -486,7 +538,9 @@ class ListEntitiesSwitchResponse : public ProtoMessage { std::string icon{}; bool assumed_state{false}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -498,7 +552,9 @@ class SwitchStateResponse : public ProtoMessage { uint32_t key{0}; bool state{false}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -509,7 +565,9 @@ class SwitchCommandRequest : public ProtoMessage { uint32_t key{0}; bool state{false}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -523,7 +581,9 @@ class ListEntitiesTextSensorResponse : public ProtoMessage { std::string unique_id{}; std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -535,7 +595,9 @@ class TextSensorStateResponse : public ProtoMessage { std::string state{}; bool missing_state{false}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -547,7 +609,9 @@ class SubscribeLogsRequest : public ProtoMessage { enums::LogLevel level{}; bool dump_config{false}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_varint(uint32_t field_id, ProtoVarInt value) override; @@ -559,7 +623,9 @@ class SubscribeLogsResponse : public ProtoMessage { std::string message{}; bool send_failed{false}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; @@ -568,7 +634,9 @@ class SubscribeLogsResponse : public ProtoMessage { class SubscribeHomeassistantServicesRequest : public ProtoMessage { public: void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: }; @@ -577,7 +645,9 @@ class HomeassistantServiceMap : public ProtoMessage { std::string key{}; std::string value{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; @@ -590,7 +660,9 @@ class HomeassistantServiceResponse : public ProtoMessage { std::vector variables{}; bool is_event{false}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; @@ -599,7 +671,9 @@ class HomeassistantServiceResponse : public ProtoMessage { class SubscribeHomeAssistantStatesRequest : public ProtoMessage { public: void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: }; @@ -608,7 +682,9 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage { std::string entity_id{}; std::string attribute{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; @@ -619,7 +695,9 @@ class HomeAssistantStateResponse : public ProtoMessage { std::string state{}; std::string attribute{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; @@ -627,7 +705,9 @@ class HomeAssistantStateResponse : public ProtoMessage { class GetTimeRequest : public ProtoMessage { public: void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: }; @@ -635,7 +715,9 @@ class GetTimeResponse : public ProtoMessage { public: uint32_t epoch_seconds{0}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -645,7 +727,9 @@ class ListEntitiesServicesArgument : public ProtoMessage { std::string name{}; enums::ServiceArgType type{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; @@ -657,7 +741,9 @@ class ListEntitiesServicesResponse : public ProtoMessage { uint32_t key{0}; std::vector args{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -675,7 +761,9 @@ class ExecuteServiceArgument : public ProtoMessage { std::vector float_array{}; std::vector string_array{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -687,7 +775,9 @@ class ExecuteServiceRequest : public ProtoMessage { uint32_t key{0}; std::vector args{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -700,7 +790,9 @@ class ListEntitiesCameraResponse : public ProtoMessage { std::string name{}; std::string unique_id{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -712,7 +804,9 @@ class CameraImageResponse : public ProtoMessage { std::string data{}; bool done{false}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -724,7 +818,9 @@ class CameraImageRequest : public ProtoMessage { bool single{false}; bool stream{false}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_varint(uint32_t field_id, ProtoVarInt value) override; @@ -749,7 +845,9 @@ class ListEntitiesClimateResponse : public ProtoMessage { std::vector supported_presets{}; std::vector supported_custom_presets{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -772,7 +870,9 @@ class ClimateStateResponse : public ProtoMessage { enums::ClimatePreset preset{}; std::string custom_preset{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -803,7 +903,9 @@ class ClimateCommandRequest : public ProtoMessage { bool has_custom_preset{false}; std::string custom_preset{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -821,7 +923,9 @@ class ListEntitiesNumberResponse : public ProtoMessage { float max_value{0.0f}; float step{0.0f}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -833,7 +937,9 @@ class NumberStateResponse : public ProtoMessage { float state{0.0f}; bool missing_state{false}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -844,7 +950,9 @@ class NumberCommandRequest : public ProtoMessage { uint32_t key{0}; float state{0.0f}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -858,7 +966,9 @@ class ListEntitiesSelectResponse : public ProtoMessage { std::string icon{}; std::vector options{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -870,7 +980,9 @@ class SelectStateResponse : public ProtoMessage { std::string state{}; bool missing_state{false}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; @@ -882,7 +994,9 @@ class SelectCommandRequest : public ProtoMessage { uint32_t key{0}; std::string state{}; void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; +#endif protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 682317801a..ad2413ea57 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -9,58 +9,82 @@ namespace api { static const char *const TAG = "api.service"; bool APIServerConnectionBase::send_hello_response(const HelloResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_hello_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 2); } bool APIServerConnectionBase::send_connect_response(const ConnectResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_connect_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 4); } bool APIServerConnectionBase::send_disconnect_request(const DisconnectRequest &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_disconnect_request: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 5); } bool APIServerConnectionBase::send_disconnect_response(const DisconnectResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_disconnect_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 6); } bool APIServerConnectionBase::send_ping_request(const PingRequest &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_ping_request: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 7); } bool APIServerConnectionBase::send_ping_response(const PingResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_ping_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 8); } bool APIServerConnectionBase::send_device_info_response(const DeviceInfoResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_device_info_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 10); } bool APIServerConnectionBase::send_list_entities_done_response(const ListEntitiesDoneResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_list_entities_done_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 19); } #ifdef USE_BINARY_SENSOR bool APIServerConnectionBase::send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_list_entities_binary_sensor_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 12); } #endif #ifdef USE_BINARY_SENSOR bool APIServerConnectionBase::send_binary_sensor_state_response(const BinarySensorStateResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_binary_sensor_state_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 21); } #endif #ifdef USE_COVER bool APIServerConnectionBase::send_list_entities_cover_response(const ListEntitiesCoverResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_list_entities_cover_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 13); } #endif #ifdef USE_COVER bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_cover_state_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 22); } #endif @@ -68,13 +92,17 @@ bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse #endif #ifdef USE_FAN bool APIServerConnectionBase::send_list_entities_fan_response(const ListEntitiesFanResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_list_entities_fan_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 14); } #endif #ifdef USE_FAN bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_fan_state_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 23); } #endif @@ -82,13 +110,17 @@ bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &ms #endif #ifdef USE_LIGHT bool APIServerConnectionBase::send_list_entities_light_response(const ListEntitiesLightResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_list_entities_light_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 15); } #endif #ifdef USE_LIGHT bool APIServerConnectionBase::send_light_state_response(const LightStateResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_light_state_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 24); } #endif @@ -96,25 +128,33 @@ bool APIServerConnectionBase::send_light_state_response(const LightStateResponse #endif #ifdef USE_SENSOR bool APIServerConnectionBase::send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_list_entities_sensor_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 16); } #endif #ifdef USE_SENSOR bool APIServerConnectionBase::send_sensor_state_response(const SensorStateResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_sensor_state_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 25); } #endif #ifdef USE_SWITCH bool APIServerConnectionBase::send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_list_entities_switch_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 17); } #endif #ifdef USE_SWITCH bool APIServerConnectionBase::send_switch_state_response(const SwitchStateResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_switch_state_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 26); } #endif @@ -122,13 +162,17 @@ bool APIServerConnectionBase::send_switch_state_response(const SwitchStateRespon #endif #ifdef USE_TEXT_SENSOR bool APIServerConnectionBase::send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_list_entities_text_sensor_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 18); } #endif #ifdef USE_TEXT_SENSOR bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorStateResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_text_sensor_state_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 27); } #endif @@ -136,35 +180,49 @@ bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsRe return this->send_message_(msg, 29); } bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 35); } bool APIServerConnectionBase::send_subscribe_home_assistant_state_response( const SubscribeHomeAssistantStateResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_subscribe_home_assistant_state_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 39); } bool APIServerConnectionBase::send_get_time_request(const GetTimeRequest &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_get_time_request: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 36); } bool APIServerConnectionBase::send_get_time_response(const GetTimeResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_get_time_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 37); } bool APIServerConnectionBase::send_list_entities_services_response(const ListEntitiesServicesResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_list_entities_services_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 41); } #ifdef USE_ESP32_CAMERA bool APIServerConnectionBase::send_list_entities_camera_response(const ListEntitiesCameraResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_list_entities_camera_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 43); } #endif #ifdef USE_ESP32_CAMERA bool APIServerConnectionBase::send_camera_image_response(const CameraImageResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_camera_image_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 44); } #endif @@ -172,13 +230,17 @@ bool APIServerConnectionBase::send_camera_image_response(const CameraImageRespon #endif #ifdef USE_CLIMATE bool APIServerConnectionBase::send_list_entities_climate_response(const ListEntitiesClimateResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_list_entities_climate_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 46); } #endif #ifdef USE_CLIMATE bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_climate_state_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 47); } #endif @@ -186,13 +248,17 @@ bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResp #endif #ifdef USE_NUMBER bool APIServerConnectionBase::send_list_entities_number_response(const ListEntitiesNumberResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_list_entities_number_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 49); } #endif #ifdef USE_NUMBER bool APIServerConnectionBase::send_number_state_response(const NumberStateResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_number_state_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 50); } #endif @@ -200,13 +266,17 @@ bool APIServerConnectionBase::send_number_state_response(const NumberStateRespon #endif #ifdef USE_SELECT bool APIServerConnectionBase::send_list_entities_select_response(const ListEntitiesSelectResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_list_entities_select_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 52); } #endif #ifdef USE_SELECT bool APIServerConnectionBase::send_select_state_response(const SelectStateResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_select_state_response: %s", msg.dump().c_str()); +#endif return this->send_message_(msg, 53); } #endif @@ -217,70 +287,90 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, case 1: { HelloRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_hello_request: %s", msg.dump().c_str()); +#endif this->on_hello_request(msg); break; } case 3: { ConnectRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_connect_request: %s", msg.dump().c_str()); +#endif this->on_connect_request(msg); break; } case 5: { DisconnectRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_disconnect_request: %s", msg.dump().c_str()); +#endif this->on_disconnect_request(msg); break; } case 6: { DisconnectResponse msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_disconnect_response: %s", msg.dump().c_str()); +#endif this->on_disconnect_response(msg); break; } case 7: { PingRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_ping_request: %s", msg.dump().c_str()); +#endif this->on_ping_request(msg); break; } case 8: { PingResponse msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_ping_response: %s", msg.dump().c_str()); +#endif this->on_ping_response(msg); break; } case 9: { DeviceInfoRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_device_info_request: %s", msg.dump().c_str()); +#endif this->on_device_info_request(msg); break; } case 11: { ListEntitiesRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_list_entities_request: %s", msg.dump().c_str()); +#endif this->on_list_entities_request(msg); break; } case 20: { SubscribeStatesRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_subscribe_states_request: %s", msg.dump().c_str()); +#endif this->on_subscribe_states_request(msg); break; } case 28: { SubscribeLogsRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_subscribe_logs_request: %s", msg.dump().c_str()); +#endif this->on_subscribe_logs_request(msg); break; } @@ -288,7 +378,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, #ifdef USE_COVER CoverCommandRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_cover_command_request: %s", msg.dump().c_str()); +#endif this->on_cover_command_request(msg); #endif break; @@ -297,7 +389,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, #ifdef USE_FAN FanCommandRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_fan_command_request: %s", msg.dump().c_str()); +#endif this->on_fan_command_request(msg); #endif break; @@ -306,7 +400,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, #ifdef USE_LIGHT LightCommandRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_light_command_request: %s", msg.dump().c_str()); +#endif this->on_light_command_request(msg); #endif break; @@ -315,7 +411,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, #ifdef USE_SWITCH SwitchCommandRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_switch_command_request: %s", msg.dump().c_str()); +#endif this->on_switch_command_request(msg); #endif break; @@ -323,42 +421,54 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, case 34: { SubscribeHomeassistantServicesRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_subscribe_homeassistant_services_request: %s", msg.dump().c_str()); +#endif this->on_subscribe_homeassistant_services_request(msg); break; } case 36: { GetTimeRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_get_time_request: %s", msg.dump().c_str()); +#endif this->on_get_time_request(msg); break; } case 37: { GetTimeResponse msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_get_time_response: %s", msg.dump().c_str()); +#endif this->on_get_time_response(msg); break; } case 38: { SubscribeHomeAssistantStatesRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_subscribe_home_assistant_states_request: %s", msg.dump().c_str()); +#endif this->on_subscribe_home_assistant_states_request(msg); break; } case 40: { HomeAssistantStateResponse msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_home_assistant_state_response: %s", msg.dump().c_str()); +#endif this->on_home_assistant_state_response(msg); break; } case 42: { ExecuteServiceRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_execute_service_request: %s", msg.dump().c_str()); +#endif this->on_execute_service_request(msg); break; } @@ -366,7 +476,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, #ifdef USE_ESP32_CAMERA CameraImageRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_camera_image_request: %s", msg.dump().c_str()); +#endif this->on_camera_image_request(msg); #endif break; @@ -375,7 +487,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, #ifdef USE_CLIMATE ClimateCommandRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_climate_command_request: %s", msg.dump().c_str()); +#endif this->on_climate_command_request(msg); #endif break; @@ -384,7 +498,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, #ifdef USE_NUMBER NumberCommandRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_number_command_request: %s", msg.dump().c_str()); +#endif this->on_number_command_request(msg); #endif break; @@ -393,7 +509,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, #ifdef USE_SELECT SelectCommandRequest msg; msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str()); +#endif this->on_select_command_request(msg); #endif break; diff --git a/esphome/components/api/proto.cpp b/esphome/components/api/proto.cpp index 0d2eb2d279..0ba277d90a 100644 --- a/esphome/components/api/proto.cpp +++ b/esphome/components/api/proto.cpp @@ -80,11 +80,13 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) { } } +#ifdef HAS_PROTO_MESSAGE_DUMP std::string ProtoMessage::dump() const { std::string out; this->dump_to(out); return out; } +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index f1486a2511..dd054bab7e 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -1,8 +1,13 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/log.h" #include "esphome/core/helpers.h" +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE +#define HAS_PROTO_MESSAGE_DUMP +#endif + namespace esphome { namespace api { @@ -243,8 +248,10 @@ class ProtoMessage { public: virtual void encode(ProtoWriteBuffer buffer) const = 0; void decode(const uint8_t *buffer, size_t length); +#ifdef HAS_PROTO_MESSAGE_DUMP std::string dump() const; virtual void dump_to(std::string &out) const = 0; +#endif protected: virtual bool decode_varint(uint32_t field_id, ProtoVarInt value) { return false; } diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 56c3e8ccc8..6983090fd9 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -661,8 +661,12 @@ def build_message_type(desc): o += "\n" o += f" {o2}\n" o += "}\n" + cpp += f"#ifdef HAS_PROTO_MESSAGE_DUMP\n" cpp += o - prot = "void dump_to(std::string &out) const override;" + cpp += f"#endif\n" + prot = "#ifdef HAS_PROTO_MESSAGE_DUMP\n" + prot += "void dump_to(std::string &out) const override;\n" + prot += "#endif\n" public_content.append(prot) out = f"class {desc.name} : public ProtoMessage {{\n" @@ -774,7 +778,9 @@ def build_service_message_type(mt): hout += f"bool {func}(const {mt.name} &msg);\n" cout += f"bool {class_name}::{func}(const {mt.name} &msg) {{\n" if log: + cout += f'#ifdef HAS_PROTO_MESSAGE_DUMP\n' cout += f' ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n' + cout += f'#endif\n' # cout += f' this->set_nodelay({str(nodelay).lower()});\n' cout += f" return this->send_message_<{mt.name}>(msg, {id_});\n" cout += f"}}\n" @@ -788,7 +794,9 @@ def build_service_message_type(mt): case += f"{mt.name} msg;\n" case += f"msg.decode(msg_data, msg_size);\n" if log: + case += f'#ifdef HAS_PROTO_MESSAGE_DUMP\n' case += f'ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n' + case += f'#endif\n' case += f"this->{func}(msg);\n" if ifdef is not None: case += f"#endif\n" From bf5f846fc689156792f91dc035c61648bea6bd8b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 9 Aug 2021 22:43:18 +0200 Subject: [PATCH 1154/1841] Refactor clang-tidy script to use actual compiler flags and includes (#2133) Co-authored-by: Otto winter --- .clang-tidy | 6 +- .github/workflows/ci.yml | 4 - .gitignore | 1 + .vscode/tasks.json | 2 +- esphome/components/sgp30/sgp30.cpp | 4 +- esphome/components/sgp40/sgp40.cpp | 4 +- esphome/components/sntp/sntp_component.cpp | 5 ++ platformio.ini | 52 +++++++++--- script/build_compile_commands.py | 16 ---- script/clang-tidy | 54 ++++++++++--- script/helpers.py | 92 ++++++++-------------- 11 files changed, 131 insertions(+), 109 deletions(-) delete mode 100755 script/build_compile_commands.py diff --git a/.clang-tidy b/.clang-tidy index fc5ce854f1..473529a9b1 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -14,7 +14,12 @@ Checks: >- -cert-str34-c, -clang-analyzer-optin.cplusplus.UninitializedObject, -clang-analyzer-osx.*, + -clang-diagnostic-delete-abstract-non-virtual-dtor, + -clang-diagnostic-delete-non-abstract-non-virtual-dtor, -clang-diagnostic-shadow-field, + -clang-diagnostic-sign-compare, + -clang-diagnostic-unused-variable, + -clang-diagnostic-unused-const-variable, -cppcoreguidelines-avoid-c-arrays, -cppcoreguidelines-avoid-goto, -cppcoreguidelines-avoid-magic-numbers, @@ -80,7 +85,6 @@ Checks: >- -readability-use-anyofallof, -warnings-as-errors WarningsAsErrors: '*' -HeaderFilterRegex: '^.*/src/esphome/.*' AnalyzeTemporaryDtors: false FormatStyle: google CheckOptions: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 433c7fb872..d7a841f84c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,10 +36,6 @@ jobs: container: ghcr.io/esphome/esphome-lint:1.1 steps: - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - name: Register problem matchers run: | diff --git a/.gitignore b/.gitignore index 954ecb2cb8..92d92e4b3b 100644 --- a/.gitignore +++ b/.gitignore @@ -125,4 +125,5 @@ config/ tests/build/ tests/.esphome/ /.temp-clang-tidy.cpp +/.temp/ .pio/ diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8c55646f28..b6584bc735 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -10,7 +10,7 @@ { "label": "clang-tidy", "type": "shell", - "command": "test -f .gcc-flags.json || pio init --silent --ide atom; ./script/clang-tidy", + "command": "./script/clang-tidy", "problemMatcher": [ { "owner": "clang-tidy", diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index fdc6ae031d..e30d6d3adc 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -47,7 +47,7 @@ void SGP30Component::setup() { } this->serial_number_ = (uint64_t(raw_serial_number[0]) << 24) | (uint64_t(raw_serial_number[1]) << 16) | (uint64_t(raw_serial_number[2])); - ESP_LOGD(TAG, "Serial Number: %llu", this->serial_number_); + ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_); // Featureset identification for future use if (!this->write_command_(SGP30_CMD_GET_FEATURESET)) { @@ -245,7 +245,7 @@ void SGP30Component::dump_config() { break; } } else { - ESP_LOGCONFIG(TAG, " Serial number: %llu", this->serial_number_); + ESP_LOGCONFIG(TAG, " Serial number: %" PRIu64, this->serial_number_); if (this->eco2_baseline_ != 0x0000 && this->tvoc_baseline_ != 0x0000) { ESP_LOGCONFIG(TAG, " Baseline:"); ESP_LOGCONFIG(TAG, " eCO2 Baseline: 0x%04X", this->eco2_baseline_); diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp index 3b634353c4..8d93b3e1b1 100644 --- a/esphome/components/sgp40/sgp40.cpp +++ b/esphome/components/sgp40/sgp40.cpp @@ -23,7 +23,7 @@ void SGP40Component::setup() { } this->serial_number_ = (uint64_t(raw_serial_number[0]) << 24) | (uint64_t(raw_serial_number[1]) << 16) | (uint64_t(raw_serial_number[2])); - ESP_LOGD(TAG, "Serial Number: %llu", this->serial_number_); + ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_); // Featureset identification for future use if (!this->write_command_(SGP40_CMD_GET_FEATURESET)) { @@ -248,7 +248,7 @@ void SGP40Component::dump_config() { break; } } else { - ESP_LOGCONFIG(TAG, " Serial number: %llu", this->serial_number_); + ESP_LOGCONFIG(TAG, " Serial number: %" PRIu64, this->serial_number_); ESP_LOGCONFIG(TAG, " Minimum Samples: %f", VOC_ALGORITHM_INITIAL_BLACKOUT); } LOG_UPDATE_INTERVAL(this); diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index b667f3b1ce..ff176b1d4e 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -8,6 +8,11 @@ #include "sntp.h" #endif +// Yes, the server names are leaked, but that's fine. +#ifdef CLANG_TIDY +#define strdup(x) (const_cast(x)) +#endif + namespace esphome { namespace sntp { diff --git a/platformio.ini b/platformio.ini index e1fb845358..9f1366d9af 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,12 +1,28 @@ -; This file is so that the C++ files in this repo -; can be edited with IDEs like VSCode or CLion -; with the platformio system +; This PlatformIO project is for development purposes *only*: clang-tidy derives its compilation +; database from here, and IDEs like CLion and VSCode also use it. This does not actually create a +; usable binary. ; It's *not* used during runtime. [platformio] -default_envs = livingroom8266 +default_envs = esp8266 src_dir = . -include_dir = include +include_dir = + +[runtime] +; This are the flags as set by the runtime. +build_flags = + -Wno-unused-variable + -Wno-unused-but-set-variable + -Wno-sign-compare + +[clangtidy] +; This are the flags for clang-tidy. +build_flags = + -Wall + -Wunreachable-code + -Wfor-loop-analysis + -Wshadow-field + -Wshadow-field-in-constructor [common] lib_deps = @@ -19,17 +35,13 @@ lib_deps = 6865@1.0.0 ; TM1651 Battery Display 6306@1.0.3 ; HM3301 build_flags = - -fno-exceptions - -Wno-sign-compare - -Wno-unused-but-set-variable - -Wno-unused-variable - -DCLANG_TIDY -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = + + + +<.temp/all-include.cpp> -[env:livingroom8266] +[common:esp8266] ; 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 @@ -42,7 +54,7 @@ lib_deps = build_flags = ${common.build_flags} src_filter = ${common.src_filter} -[env:livingroom32] +[common:esp32] platform = platformio/espressif32@3.2.0 framework = arduino board = nodemcu-32s @@ -56,3 +68,19 @@ build_flags = src_filter = ${common.src_filter} - + +[env:esp8266] +extends = common:esp8266 +build_flags = ${common:esp8266.build_flags} ${runtime.build_flags} + +[env:esp8266-tidy] +extends = common:esp8266 +build_flags = ${common:esp8266.build_flags} ${clangtidy.build_flags} + +[env:esp32] +extends = common:esp32 +build_flags = ${common:esp32.build_flags} ${runtime.build_flags} + +[env:esp32-tidy] +extends = common:esp32 +build_flags = ${common:esp32.build_flags} ${clangtidy.build_flags} diff --git a/script/build_compile_commands.py b/script/build_compile_commands.py deleted file mode 100755 index 4ac14f08b4..0000000000 --- a/script/build_compile_commands.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python3 -import sys -import os.path - -sys.path.append(os.path.dirname(__file__)) -from helpers import build_all_include, build_compile_commands - - -def main(): - build_all_include() - build_compile_commands() - print("Done.") - - -if __name__ == "__main__": - main() diff --git a/script/clang-tidy b/script/clang-tidy index 11b9818016..c2f47bad11 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import argparse +import json import multiprocessing import os import queue @@ -15,27 +16,57 @@ import click import pexpect sys.path.append(os.path.dirname(__file__)) -from helpers import basepath, shlex_quote, get_output, build_compile_commands, \ - build_all_include, temp_header_file, git_ls_files, filter_changed +from helpers import shlex_quote, get_output, \ + build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata -def run_tidy(args, tmpdir, queue, lock, failed_files): +def clang_options(idedata): + cmd = [ + # target 32-bit arch (this prevents size mismatch errors on a 64-bit host) + '-m32', + # disable built-in include directories from the host + '-nostdinc', + '-nostdinc++', + # allow to condition code on the presence of clang-tidy + '-DCLANG_TIDY' + ] + + # copy compiler flags, except those clang doesn't understand. + cmd.extend(flag for flag in idedata['cxx_flags'].split(' ') + if flag not in ('-free', '-fipa-pta', '-mlongcalls', '-mtext-section-literals')) + + # defines + cmd.extend(f'-D{define}' for define in idedata['defines']) + + # add include directories, using -isystem for dependencies to suppress their errors + for directory in idedata['includes']['toolchain']: + cmd.extend(['-isystem', directory]) + for directory in sorted(set(idedata['includes']['build'])): + dependency = "framework-arduino" in directory or "/libdeps/" in directory + cmd.extend(['-isystem' if dependency else '-I', directory]) + + return cmd + + +def run_tidy(args, options, tmpdir, queue, lock, failed_files): while True: path = queue.get() - invocation = ['clang-tidy-11', '-header-filter=^{}/.*'.format(re.escape(basepath))] + invocation = ['clang-tidy-11'] + if tmpdir is not None: - invocation.append('-export-fixes') + invocation.append('--export-fixes') # Get a temporary file. We immediately close the handle so clang-tidy can # overwrite it. (handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir) os.close(handle) invocation.append(name) - invocation.append('-p=.') + if args.quiet: invocation.append('-quiet') - for arg in ['-Wfor-loop-analysis', '-Wshadow-field', '-Wshadow-field-in-constructor']: - invocation.append('-extra-arg={}'.format(arg)) + invocation.append(os.path.abspath(path)) + invocation.append('--') + invocation.extend(options) invocation_s = ' '.join(shlex_quote(x) for x in invocation) # Use pexpect for a pseudy-TTY with colored output @@ -95,8 +126,8 @@ def main(): """) return 1 - build_all_include() - build_compile_commands() + idedata = load_idedata("esp8266-tidy") + options = clang_options(idedata) files = [] for path in git_ls_files(['*.cpp']): @@ -116,6 +147,7 @@ def main(): files = split_list(files, args.split_num)[args.split_at - 1] if args.all_headers and args.split_at in (None, 1): + build_all_include() files.insert(0, temp_header_file) tmpdir = None @@ -128,7 +160,7 @@ def main(): lock = threading.Lock() for _ in range(args.jobs): t = threading.Thread(target=run_tidy, - args=(args, tmpdir, task_queue, lock, failed_files)) + args=(args, options, tmpdir, task_queue, lock, failed_files)) t.daemon = True t.start() diff --git a/script/helpers.py b/script/helpers.py index e0ec2d169e..8bf8eec5cc 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -1,13 +1,14 @@ import codecs -import json import os.path import re import subprocess -import sys +import json +from pathlib import Path root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, "..", ".."))) basepath = os.path.join(root_path, "esphome") -temp_header_file = os.path.join(root_path, ".temp-clang-tidy.cpp") +temp_folder = os.path.join(root_path, ".temp") +temp_header_file = os.path.join(temp_folder, "all-include.cpp") def shlex_quote(s): @@ -33,63 +34,9 @@ def build_all_include(): headers.sort() headers.append("") content = "\n".join(headers) - with codecs.open(temp_header_file, "w", encoding="utf-8") as f: - f.write(content) - - -def build_compile_commands(): - gcc_flags_json = os.path.join(root_path, ".gcc-flags.json") - if not os.path.isfile(gcc_flags_json): - print("Could not find {} file which is required for clang-tidy.".format(gcc_flags_json)) - print( - 'Please run "pio init --ide atom" in the root esphome folder to generate that file.' - ) - sys.exit(1) - with codecs.open(gcc_flags_json, "r", encoding="utf-8") as f: - gcc_flags = json.load(f) - exec_path = gcc_flags["execPath"] - include_paths = gcc_flags["gccIncludePaths"].split(",") - includes = [f"-I{p}" for p in include_paths] - cpp_flags = gcc_flags["gccDefaultCppFlags"].split(" ") - defines = [flag for flag in cpp_flags if flag.startswith("-D")] - command = [exec_path] - command.extend(includes) - command.extend(defines) - command.append("-std=gnu++11") - command.append("-Wall") - command.append("-Wno-delete-non-virtual-dtor") - command.append("-Wno-unused-variable") - command.append("-Wunreachable-code") - - source_files = [] - for path in walk_files(basepath): - filetypes = (".cpp",) - ext = os.path.splitext(path)[1] - if ext in filetypes: - source_files.append(os.path.abspath(path)) - source_files.append(temp_header_file) - source_files.sort() - compile_commands = [ - { - "directory": root_path, - "command": " ".join( - shlex_quote(x) for x in (command + ["-o", p + ".o", "-c", p]) - ), - "file": p, - } - for p in source_files - ] - compile_commands_json = os.path.join(root_path, "compile_commands.json") - if os.path.isfile(compile_commands_json): - with codecs.open(compile_commands_json, "r", encoding="utf-8") as f: - try: - if json.load(f) == compile_commands: - return - # pylint: disable=bare-except - except: - pass - with codecs.open(compile_commands_json, "w", encoding="utf-8") as f: - json.dump(compile_commands, f, indent=2) + p = Path(temp_header_file) + p.parent.mkdir(exist_ok=True) + p.write_text(content) def walk_files(path): @@ -153,3 +100,28 @@ def git_ls_files(patterns=None): output, err = proc.communicate() lines = [x.split() for x in output.decode("utf-8").splitlines()] return {s[3].strip(): int(s[0]) for s in lines} + + +def load_idedata(environment): + platformio_ini = Path(root_path) / "platformio.ini" + temp_idedata = Path(temp_folder) / f"idedata-{environment}.json" + if not platformio_ini.is_file() or not temp_idedata.is_file(): + changed = True + elif platformio_ini.stat().st_mtime >= temp_idedata.stat().st_mtime: + changed = True + else: + changed = False + + if not changed: + text = temp_idedata.read_text() + else: + stdout = subprocess.check_output( + ["pio", "run", "-t", "idedata", "-e", environment] + ) + match = re.search(r'{\s*".*}', stdout.decode("utf-8")) + text = match.group() + + temp_idedata.parent.mkdir(exist_ok=True) + temp_idedata.write_text(text) + + return json.loads(text) From 90c0d3e12feceedb6be95bbb34067d555ec1cbd5 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 9 Aug 2021 20:21:10 -0500 Subject: [PATCH 1155/1841] Add Toshiba AC generic IR remote protocol (#2019) --- esphome/components/remote_base/__init__.py | 47 ++++++++ .../remote_base/toshiba_ac_protocol.cpp | 111 ++++++++++++++++++ .../remote_base/toshiba_ac_protocol.h | 39 ++++++ tests/test1.yaml | 6 + 4 files changed, 203 insertions(+) create mode 100644 esphome/components/remote_base/toshiba_ac_protocol.cpp create mode 100644 esphome/components/remote_base/toshiba_ac_protocol.h diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 7c27c1b736..c4cdc98bbb 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -950,6 +950,53 @@ async def samsung36_action(var, config, args): cg.add(var.set_command(template_)) +# Toshiba AC +( + ToshibaAcData, + ToshibaAcBinarySensor, + ToshibaAcTrigger, + ToshibaAcAction, + ToshibaAcDumper, +) = declare_protocol("ToshibaAc") +TOSHIBAAC_SCHEMA = cv.Schema( + { + cv.Required(CONF_RC_CODE_1): cv.hex_uint64_t, + cv.Optional(CONF_RC_CODE_2, default=0): cv.hex_uint64_t, + } +) + + +@register_binary_sensor("toshiba_ac", ToshibaAcBinarySensor, TOSHIBAAC_SCHEMA) +def toshibaac_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + ToshibaAcData, + ("rc_code_1", config[CONF_RC_CODE_1]), + ("rc_code_2", config[CONF_RC_CODE_2]), + ) + ) + ) + + +@register_trigger("toshiba_ac", ToshibaAcTrigger, ToshibaAcData) +def toshibaac_trigger(var, config): + pass + + +@register_dumper("toshiba_ac", ToshibaAcDumper) +def toshibaac_dumper(var, config): + pass + + +@register_action("toshiba_ac", ToshibaAcAction, TOSHIBAAC_SCHEMA) +async def toshibaac_action(var, config, args): + template_ = await cg.templatable(config[CONF_RC_CODE_1], args, cg.uint64) + cg.add(var.set_rc_code_1(template_)) + template_ = await cg.templatable(config[CONF_RC_CODE_2], args, cg.uint64) + cg.add(var.set_rc_code_2(template_)) + + # Panasonic ( PanasonicData, diff --git a/esphome/components/remote_base/toshiba_ac_protocol.cpp b/esphome/components/remote_base/toshiba_ac_protocol.cpp new file mode 100644 index 0000000000..27d042ace0 --- /dev/null +++ b/esphome/components/remote_base/toshiba_ac_protocol.cpp @@ -0,0 +1,111 @@ +#include "toshiba_ac_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.toshibaac"; + +static const uint32_t HEADER_HIGH_US = 4500; +static const uint32_t HEADER_LOW_US = 4500; +static const uint32_t BIT_HIGH_US = 560; +static const uint32_t BIT_ONE_LOW_US = 1690; +static const uint32_t BIT_ZERO_LOW_US = 560; +static const uint32_t FOOTER_HIGH_US = 560; +static const uint32_t FOOTER_LOW_US = 4500; +static const uint16_t PACKET_SPACE = 5500; + +void ToshibaAcProtocol::encode(RemoteTransmitData *dst, const ToshibaAcData &data) { + dst->set_carrier_frequency(38000); + dst->reserve((3 + (48 * 2)) * 3); + + for (uint8_t repeat = 0; repeat < 2; repeat++) { + dst->item(HEADER_HIGH_US, HEADER_LOW_US); + for (uint8_t bit = 48; bit > 0; bit--) { + dst->mark(BIT_HIGH_US); + if ((data.rc_code_1 >> (bit - 1)) & 1) + dst->space(BIT_ONE_LOW_US); + else + dst->space(BIT_ZERO_LOW_US); + } + dst->item(FOOTER_HIGH_US, FOOTER_LOW_US); + } + + if (data.rc_code_2 != 0) { + dst->item(HEADER_HIGH_US, HEADER_LOW_US); + for (uint8_t bit = 48; bit > 0; bit--) { + dst->mark(BIT_HIGH_US); + if ((data.rc_code_2 >> (bit - 1)) & 1) + dst->space(BIT_ONE_LOW_US); + else + dst->space(BIT_ZERO_LOW_US); + } + dst->item(FOOTER_HIGH_US, FOOTER_LOW_US); + } +} + +optional ToshibaAcProtocol::decode(RemoteReceiveData src) { + uint64_t packet = 0; + ToshibaAcData out{ + .rc_code_1 = 0, + .rc_code_2 = 0, + }; + // *** Packet 1 + if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US)) + return {}; + for (uint8_t bit_counter = 0; bit_counter < 48; bit_counter++) { + if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { + packet = (packet << 1) | 1; + } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { + packet = (packet << 1) | 0; + } else { + return {}; + } + } + if (!src.expect_item(FOOTER_HIGH_US, PACKET_SPACE)) + return {}; + + // *** Packet 2 + if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US)) + return {}; + for (uint8_t bit_counter = 0; bit_counter < 48; bit_counter++) { + if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { + out.rc_code_1 = (out.rc_code_1 << 1) | 1; + } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { + out.rc_code_1 = (out.rc_code_1 << 1) | 0; + } else { + return {}; + } + } + // The first two packets must match + if (packet != out.rc_code_1) + return {}; + // The third packet isn't always present + if (!src.expect_item(FOOTER_HIGH_US, PACKET_SPACE)) + return out; + + // *** Packet 3 + if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US)) + return {}; + for (uint8_t bit_counter = 0; bit_counter < 48; bit_counter++) { + if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { + out.rc_code_2 = (out.rc_code_2 << 1) | 1; + } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { + out.rc_code_2 = (out.rc_code_2 << 1) | 0; + } else { + return {}; + } + } + + return out; +} + +void ToshibaAcProtocol::dump(const ToshibaAcData &data) { + if (data.rc_code_2 != 0) + ESP_LOGD(TAG, "Received Toshiba AC: rc_code_1=0x%" PRIX64 ", rc_code_2=0x%" PRIX64, data.rc_code_1, data.rc_code_2); + else + ESP_LOGD(TAG, "Received Toshiba AC: rc_code_1=0x%" PRIX64, data.rc_code_1); +} + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/toshiba_ac_protocol.h b/esphome/components/remote_base/toshiba_ac_protocol.h new file mode 100644 index 0000000000..c69401c378 --- /dev/null +++ b/esphome/components/remote_base/toshiba_ac_protocol.h @@ -0,0 +1,39 @@ +#pragma once + +#include "esphome/core/component.h" +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +struct ToshibaAcData { + uint64_t rc_code_1; + uint64_t rc_code_2; + + bool operator==(const ToshibaAcData &rhs) const { return rc_code_1 == rhs.rc_code_1 && rc_code_2 == rhs.rc_code_2; } +}; + +class ToshibaAcProtocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const ToshibaAcData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const ToshibaAcData &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(ToshibaAc) + +template class ToshibaAcAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(uint64_t, rc_code_1) + TEMPLATABLE_VALUE(uint64_t, rc_code_2) + + void encode(RemoteTransmitData *dst, Ts... x) override { + ToshibaAcData data{}; + data.rc_code_1 = this->rc_code_1_.value(x...); + data.rc_code_2 = this->rc_code_2_.value(x...); + ToshibaAcProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index 6d6fc113ea..54f8eb589c 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1626,6 +1626,12 @@ switch: remote_transmitter.transmit_samsung36: address: 0x0400 command: 0x000E00FF + - platform: template + name: ToshibaAC + turn_on_action: + - remote_transmitter.transmit_toshiba_ac: + rc_code_1: 0xB24DBF4050AF + rc_code_2: 0xD5660001003C - platform: template name: Sony turn_on_action: From 922f7167f55adad85368e4c7a96c0dedb0c004de Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 9 Aug 2021 20:25:11 -0500 Subject: [PATCH 1156/1841] Add new Toshiba AC unit protocol (#1987) --- CODEOWNERS | 1 + esphome/components/toshiba/climate.py | 12 +- esphome/components/toshiba/toshiba.cpp | 703 +++++++++++++++++++++---- esphome/components/toshiba/toshiba.h | 62 ++- 4 files changed, 675 insertions(+), 103 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index c79dfc066e..cd828395a6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -136,6 +136,7 @@ esphome/components/tm1637/* @glmnet esphome/components/tmp102/* @timsavage esphome/components/tmp117/* @Azimath esphome/components/tof10120/* @wstrzalka +esphome/components/toshiba/* @kbx81 esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/climate/* @jesserockz esphome/components/tuya/sensor/* @jesserockz diff --git a/esphome/components/toshiba/climate.py b/esphome/components/toshiba/climate.py index 95c9f1f127..3f2c644c87 100644 --- a/esphome/components/toshiba/climate.py +++ b/esphome/components/toshiba/climate.py @@ -1,16 +1,25 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import climate_ir -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_MODEL AUTO_LOAD = ["climate_ir"] +CODEOWNERS = ["@kbx81"] toshiba_ns = cg.esphome_ns.namespace("toshiba") ToshibaClimate = toshiba_ns.class_("ToshibaClimate", climate_ir.ClimateIR) +Model = toshiba_ns.enum("Model") +MODELS = { + "GENERIC": Model.MODEL_GENERIC, + "RAC-PT1411HWRU-C": Model.MODEL_RAC_PT1411HWRU_C, + "RAC-PT1411HWRU-F": Model.MODEL_RAC_PT1411HWRU_F, +} + CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(ToshibaClimate), + cv.Optional(CONF_MODEL, default="generic"): cv.enum(MODELS, upper=True), } ) @@ -18,3 +27,4 @@ CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await climate_ir.register_climate_ir(var, config) + cg.add(var.set_model(config[CONF_MODEL])) diff --git a/esphome/components/toshiba/toshiba.cpp b/esphome/components/toshiba/toshiba.cpp index c08ae898b5..81ed5ddce4 100644 --- a/esphome/components/toshiba/toshiba.cpp +++ b/esphome/components/toshiba/toshiba.cpp @@ -3,13 +3,23 @@ namespace esphome { namespace toshiba { +struct RacPt1411hwruFanSpeed { + uint8_t code1; + uint8_t code2; +}; + +static const char *const TAG = "toshiba.climate"; +// Timings for IR bits/data const uint16_t TOSHIBA_HEADER_MARK = 4380; const uint16_t TOSHIBA_HEADER_SPACE = 4370; const uint16_t TOSHIBA_GAP_SPACE = 5480; +const uint16_t TOSHIBA_PACKET_SPACE = 10500; const uint16_t TOSHIBA_BIT_MARK = 540; const uint16_t TOSHIBA_ZERO_SPACE = 540; const uint16_t TOSHIBA_ONE_SPACE = 1620; - +const uint16_t TOSHIBA_CARRIER_FREQUENCY = 38000; +const uint8_t TOSHIBA_HEADER_LENGTH = 4; +// Generic Toshiba commands/flags const uint8_t TOSHIBA_COMMAND_DEFAULT = 0x01; const uint8_t TOSHIBA_COMMAND_TIMER = 0x02; const uint8_t TOSHIBA_COMMAND_POWER = 0x08; @@ -36,36 +46,122 @@ const uint8_t TOSHIBA_POWER_ECO = 0x03; const uint8_t TOSHIBA_MOTION_SWING = 0x04; const uint8_t TOSHIBA_MOTION_FIX = 0x00; -static const char *const TAG = "toshiba.climate"; +// RAC-PT1411HWRU temperature code flag bits +const uint8_t RAC_PT1411HWRU_FLAG_FAH = 0x01; +const uint8_t RAC_PT1411HWRU_FLAG_FRAC = 0x20; +const uint8_t RAC_PT1411HWRU_FLAG_NEG = 0x10; +// RAC-PT1411HWRU temperature short code flags mask +const uint8_t RAC_PT1411HWRU_FLAG_MASK = 0x0F; +// RAC-PT1411HWRU Headers, Footers and such +const uint8_t RAC_PT1411HWRU_MESSAGE_HEADER0 = 0xB2; +const uint8_t RAC_PT1411HWRU_MESSAGE_HEADER1 = 0xD5; +const uint8_t RAC_PT1411HWRU_MESSAGE_LENGTH = 6; +// RAC-PT1411HWRU "Comfort Sense" feature bits +const uint8_t RAC_PT1411HWRU_CS_ENABLED = 0x40; +const uint8_t RAC_PT1411HWRU_CS_DATA = 0x80; +const uint8_t RAC_PT1411HWRU_CS_HEADER = 0xBA; +const uint8_t RAC_PT1411HWRU_CS_FOOTER_AUTO = 0x7A; +const uint8_t RAC_PT1411HWRU_CS_FOOTER_COOL = 0x72; +const uint8_t RAC_PT1411HWRU_CS_FOOTER_HEAT = 0x7E; +// RAC-PT1411HWRU Swing +const uint8_t RAC_PT1411HWRU_SWING_HEADER = 0xB9; +const std::vector RAC_PT1411HWRU_SWING_VERTICAL{0xB9, 0x46, 0xF5, 0x0A, 0x04, 0xFB}; +const std::vector RAC_PT1411HWRU_SWING_OFF{0xB9, 0x46, 0xF5, 0x0A, 0x05, 0xFA}; +// RAC-PT1411HWRU Fan speeds +const uint8_t RAC_PT1411HWRU_FAN_OFF = 0x7B; +constexpr RacPt1411hwruFanSpeed RAC_PT1411HWRU_FAN_AUTO{0xBF, 0x66}; +constexpr RacPt1411hwruFanSpeed RAC_PT1411HWRU_FAN_LOW{0x9F, 0x28}; +constexpr RacPt1411hwruFanSpeed RAC_PT1411HWRU_FAN_MED{0x5F, 0x3C}; +constexpr RacPt1411hwruFanSpeed RAC_PT1411HWRU_FAN_HIGH{0x3F, 0x64}; +// RAC-PT1411HWRU Fan speed for Auto and Dry climate modes +const RacPt1411hwruFanSpeed RAC_PT1411HWRU_NO_FAN{0x1F, 0x65}; +// RAC-PT1411HWRU Modes +const uint8_t RAC_PT1411HWRU_MODE_AUTO = 0x08; +const uint8_t RAC_PT1411HWRU_MODE_COOL = 0x00; +const uint8_t RAC_PT1411HWRU_MODE_DRY = 0x04; +const uint8_t RAC_PT1411HWRU_MODE_FAN = 0x04; +const uint8_t RAC_PT1411HWRU_MODE_HEAT = 0x0C; +const uint8_t RAC_PT1411HWRU_MODE_OFF = 0x00; +// RAC-PT1411HWRU Fan-only "temperature"/system off +const uint8_t RAC_PT1411HWRU_TEMPERATURE_FAN_ONLY = 0x0E; +// RAC-PT1411HWRU temperature codes are not sequential; they instead follow a modified Gray code. +// Hence these look-up tables. In addition, the upper nibble is used here for additional +// "negative" and "fractional value" flags as required for some temperatures. +// RAC-PT1411HWRU °C Temperatures (short codes) +const std::vector RAC_PT1411HWRU_TEMPERATURE_C{0x10, 0x00, 0x01, 0x03, 0x02, 0x06, 0x07, 0x05, + 0x04, 0x0C, 0x0D, 0x09, 0x08, 0x0A, 0x0B}; +// RAC-PT1411HWRU °F Temperatures (short codes) +const std::vector RAC_PT1411HWRU_TEMPERATURE_F{0x10, 0x30, 0x00, 0x20, 0x01, 0x21, 0x03, 0x23, 0x02, + 0x22, 0x06, 0x26, 0x07, 0x05, 0x25, 0x04, 0x24, 0x0C, + 0x2C, 0x0D, 0x2D, 0x09, 0x08, 0x28, 0x0A, 0x2A, 0x0B}; + +void ToshibaClimate::setup() { + if (this->sensor_) { + this->sensor_->add_on_state_callback([this](float state) { + this->current_temperature = state; + this->transmit_rac_pt1411hwru_temp_(); + // current temperature changed, publish state + this->publish_state(); + }); + this->current_temperature = this->sensor_->state; + } else + this->current_temperature = NAN; + // restore set points + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->apply(this); + } else { + // restore from defaults + this->mode = climate::CLIMATE_MODE_OFF; + // initialize target temperature to some value so that it's not NAN + this->target_temperature = + roundf(clamp(this->current_temperature, this->minimum_temperature_, this->maximum_temperature_)); + this->fan_mode = climate::CLIMATE_FAN_AUTO; + this->swing_mode = climate::CLIMATE_SWING_OFF; + } + // Set supported modes & temperatures based on model + this->minimum_temperature_ = this->temperature_min_(); + this->maximum_temperature_ = this->temperature_max_(); + this->supports_dry_ = this->toshiba_supports_dry_(); + this->supports_fan_only_ = this->toshiba_supports_fan_only_(); + this->fan_modes_ = this->toshiba_fan_modes_(); + this->swing_modes_ = this->toshiba_swing_modes_(); + // Never send nan to HA + if (isnan(this->target_temperature)) + this->target_temperature = 24; +} void ToshibaClimate::transmit_state() { + if (this->model_ == MODEL_RAC_PT1411HWRU_C || this->model_ == MODEL_RAC_PT1411HWRU_F) { + transmit_rac_pt1411hwru_(); + } else { + transmit_generic_(); + } +} + +void ToshibaClimate::transmit_generic_() { uint8_t message[16] = {0}; uint8_t message_length = 9; - /* Header */ + // Header message[0] = 0xf2; message[1] = 0x0d; - /* Message length */ + // Message length message[2] = message_length - 6; - /* First checksum */ + // First checksum message[3] = message[0] ^ message[1] ^ message[2]; - /* Command */ + // Command message[4] = TOSHIBA_COMMAND_DEFAULT; - /* Temperature */ - uint8_t temperature = static_cast(this->target_temperature); - if (temperature < 17) { - temperature = 17; - } - if (temperature > 30) { - temperature = 30; - } - message[5] = (temperature - 17) << 4; + // Temperature + uint8_t temperature = static_cast( + clamp(this->target_temperature, TOSHIBA_GENERIC_TEMP_C_MIN, TOSHIBA_GENERIC_TEMP_C_MAX)); + message[5] = (temperature - static_cast(TOSHIBA_GENERIC_TEMP_C_MIN)) << 4; - /* Mode and fan */ + // Mode and fan uint8_t mode; switch (this->mode) { case climate::CLIMATE_MODE_OFF: @@ -87,28 +183,504 @@ void ToshibaClimate::transmit_state() { message[6] |= mode | TOSHIBA_FAN_SPEED_AUTO; - /* Zero */ + // Zero message[7] = 0x00; - /* If timers bit in the command is set, two extra bytes are added here */ + // If timers bit in the command is set, two extra bytes are added here - /* If power bit is set in the command, one extra byte is added here */ + // If power bit is set in the command, one extra byte is added here - /* The last byte is the xor of all bytes from [4] */ + // The last byte is the xor of all bytes from [4] for (uint8_t i = 4; i < 8; i++) { message[8] ^= message[i]; } - /* Transmit */ + // Transmit auto transmit = this->transmitter_->transmit(); auto data = transmit.get_data(); - data->set_carrier_frequency(38000); - for (uint8_t copy = 0; copy < 2; copy++) { - data->mark(TOSHIBA_HEADER_MARK); - data->space(TOSHIBA_HEADER_SPACE); + encode_(data, message, message_length, 1); - for (uint8_t byte = 0; byte < message_length; byte++) { + transmit.perform(); +} + +void ToshibaClimate::transmit_rac_pt1411hwru_() { + uint8_t code = 0, index = 0, message[RAC_PT1411HWRU_MESSAGE_LENGTH * 2] = {0}; + float temperature = + clamp(this->target_temperature, TOSHIBA_RAC_PT1411HWRU_TEMP_C_MIN, TOSHIBA_RAC_PT1411HWRU_TEMP_C_MAX); + float temp_adjd = temperature - TOSHIBA_RAC_PT1411HWRU_TEMP_C_MIN; + auto transmit = this->transmitter_->transmit(); + auto data = transmit.get_data(); + + // Byte 0: Header upper (0xB2) + message[0] = RAC_PT1411HWRU_MESSAGE_HEADER0; + // Byte 1: Header lower (0x4D) + message[1] = ~message[0]; + // Byte 2u: Fan speed + // Byte 2l: 1111 (on) or 1011 (off) + if (this->mode == climate::CLIMATE_MODE_OFF) { + message[2] = RAC_PT1411HWRU_FAN_OFF; + } else if ((this->mode == climate::CLIMATE_MODE_HEAT_COOL) || (this->mode == climate::CLIMATE_MODE_DRY)) { + message[2] = RAC_PT1411HWRU_NO_FAN.code1; + message[7] = RAC_PT1411HWRU_NO_FAN.code2; + } else { + switch (this->fan_mode.value()) { + case climate::CLIMATE_FAN_LOW: + message[2] = RAC_PT1411HWRU_FAN_LOW.code1; + message[7] = RAC_PT1411HWRU_FAN_LOW.code2; + break; + + case climate::CLIMATE_FAN_MEDIUM: + message[2] = RAC_PT1411HWRU_FAN_MED.code1; + message[7] = RAC_PT1411HWRU_FAN_MED.code2; + break; + + case climate::CLIMATE_FAN_HIGH: + message[2] = RAC_PT1411HWRU_FAN_HIGH.code1; + message[7] = RAC_PT1411HWRU_FAN_HIGH.code2; + break; + + case climate::CLIMATE_FAN_AUTO: + default: + message[2] = RAC_PT1411HWRU_FAN_AUTO.code1; + message[7] = RAC_PT1411HWRU_FAN_AUTO.code2; + } + } + // Byte 3u: ~Fan speed + // Byte 3l: 0000 (on) or 0100 (off) + message[3] = ~message[2]; + // Byte 4u: Temp + if (this->model_ == MODEL_RAC_PT1411HWRU_F) { + temperature = (temperature * 1.8) + 32; + temp_adjd = temperature - TOSHIBA_RAC_PT1411HWRU_TEMP_F_MIN; + } + + index = static_cast(roundf(temp_adjd)); + + if (this->model_ == MODEL_RAC_PT1411HWRU_F) { + code = RAC_PT1411HWRU_TEMPERATURE_F[index]; + message[9] |= RAC_PT1411HWRU_FLAG_FAH; + } else { + code = RAC_PT1411HWRU_TEMPERATURE_C[index]; + } + if ((this->mode == climate::CLIMATE_MODE_FAN_ONLY) || (this->mode == climate::CLIMATE_MODE_OFF)) { + code = RAC_PT1411HWRU_TEMPERATURE_FAN_ONLY; + } + + if (code & RAC_PT1411HWRU_FLAG_FRAC) { + message[8] |= RAC_PT1411HWRU_FLAG_FRAC; + } + if (code & RAC_PT1411HWRU_FLAG_NEG) { + message[9] |= RAC_PT1411HWRU_FLAG_NEG; + } + message[4] = (code & RAC_PT1411HWRU_FLAG_MASK) << 4; + // Byte 4l: Mode + switch (this->mode) { + case climate::CLIMATE_MODE_OFF: + // zerooooo + break; + + case climate::CLIMATE_MODE_HEAT: + message[4] |= RAC_PT1411HWRU_MODE_HEAT; + break; + + case climate::CLIMATE_MODE_COOL: + message[4] |= RAC_PT1411HWRU_MODE_COOL; + break; + + case climate::CLIMATE_MODE_DRY: + message[4] |= RAC_PT1411HWRU_MODE_DRY; + break; + + case climate::CLIMATE_MODE_FAN_ONLY: + message[4] |= RAC_PT1411HWRU_MODE_FAN; + break; + + case climate::CLIMATE_MODE_HEAT_COOL: + default: + message[4] |= RAC_PT1411HWRU_MODE_AUTO; + } + + // Byte 5u: ~Temp + // Byte 5l: ~Mode + message[5] = ~message[4]; + + if (this->mode != climate::CLIMATE_MODE_OFF) { + // Byte 6: Header (0xD5) + message[6] = RAC_PT1411HWRU_MESSAGE_HEADER1; + // Byte 7: Fan speed part 2 (done above) + // Byte 8: 0x20 for °F frac, else 0 (done above) + // Byte 9: 0x10=NEG, 0x01=°F (done above) + // Byte 10: 0 + // Byte 11: Checksum (bytes 6 through 10) + for (index = 6; index <= 10; index++) { + message[11] += message[index]; + } + } + ESP_LOGV(TAG, "*** Generated codes: 0x%.2X%.2X%.2X%.2X%.2X%.2X 0x%.2X%.2X%.2X%.2X%.2X%.2X", message[0], message[1], + message[2], message[3], message[4], message[5], message[6], message[7], message[8], message[9], message[10], + message[11]); + + // load first block of IR code and repeat it once + encode_(data, &message[0], RAC_PT1411HWRU_MESSAGE_LENGTH, 1); + // load second block of IR code, if present + if (message[6] != 0) { + encode_(data, &message[6], RAC_PT1411HWRU_MESSAGE_LENGTH, 0); + } + + transmit.perform(); + + // Swing Mode + data->reset(); + data->space(TOSHIBA_PACKET_SPACE); + switch (this->swing_mode) { + case climate::CLIMATE_SWING_VERTICAL: + encode_(data, &RAC_PT1411HWRU_SWING_VERTICAL[0], RAC_PT1411HWRU_MESSAGE_LENGTH, 1); + break; + + case climate::CLIMATE_SWING_OFF: + default: + encode_(data, &RAC_PT1411HWRU_SWING_OFF[0], RAC_PT1411HWRU_MESSAGE_LENGTH, 1); + } + + data->space(TOSHIBA_PACKET_SPACE); + transmit.perform(); + + if (this->sensor_) { + transmit_rac_pt1411hwru_temp_(true, false); + } +} + +void ToshibaClimate::transmit_rac_pt1411hwru_temp_(const bool cs_state, const bool cs_send_update) { + if ((this->mode == climate::CLIMATE_MODE_HEAT) || (this->mode == climate::CLIMATE_MODE_COOL) || + (this->mode == climate::CLIMATE_MODE_HEAT_COOL)) { + uint8_t message[RAC_PT1411HWRU_MESSAGE_LENGTH] = {0}; + float temperature = clamp(this->current_temperature, 0.0, TOSHIBA_RAC_PT1411HWRU_TEMP_C_MAX + 1); + auto transmit = this->transmitter_->transmit(); + auto data = transmit.get_data(); + // "Comfort Sense" feature notes + // IR Code: 0xBA45 xxXX yyYY + // xx: Temperature in °C + // Bit 6: feature state (on/off) + // Bit 7: message contains temperature data for feature (bit 6 must also be set) + // XX: Bitwise complement of xx + // yy: Mode: Auto=0x7A, Cool=0x72, Heat=0x7E + // YY: Bitwise complement of yy + // + // Byte 0: Header upper (0xBA) + message[0] = RAC_PT1411HWRU_CS_HEADER; + // Byte 1: Header lower (0x45) + message[1] = ~message[0]; + // Byte 2: Temperature in °C + message[2] = static_cast(roundf(temperature)); + if (cs_send_update) { + message[2] |= RAC_PT1411HWRU_CS_ENABLED | RAC_PT1411HWRU_CS_DATA; + } else if (cs_state) { + message[2] |= RAC_PT1411HWRU_CS_ENABLED; + } + // Byte 3: Bitwise complement of byte 2 + message[3] = ~message[2]; + // Byte 4: Footer upper + switch (this->mode) { + case climate::CLIMATE_MODE_HEAT: + message[4] = RAC_PT1411HWRU_CS_FOOTER_HEAT; + break; + + case climate::CLIMATE_MODE_COOL: + message[4] = RAC_PT1411HWRU_CS_FOOTER_COOL; + break; + + case climate::CLIMATE_MODE_HEAT_COOL: + message[4] = RAC_PT1411HWRU_CS_FOOTER_AUTO; + + default: + break; + } + // Byte 5: Footer lower/bitwise complement of byte 4 + message[5] = ~message[4]; + + ESP_LOGV(TAG, "*** Generated code: 0x%.2X%.2X%.2X%.2X%.2X%.2X", message[0], message[1], message[2], message[3], + message[4], message[5]); + // load IR code and repeat it once + encode_(data, message, RAC_PT1411HWRU_MESSAGE_LENGTH, 1); + + transmit.perform(); + } +} + +uint8_t ToshibaClimate::is_valid_rac_pt1411hwru_header_(const uint8_t *message) { + const std::vector header{RAC_PT1411HWRU_MESSAGE_HEADER0, RAC_PT1411HWRU_CS_HEADER, + RAC_PT1411HWRU_SWING_HEADER}; + + for (auto i : header) { + if ((message[0] == i) && (message[1] == static_cast(~i))) + return i; + } + if (message[0] == RAC_PT1411HWRU_MESSAGE_HEADER1) + return RAC_PT1411HWRU_MESSAGE_HEADER1; + + return 0; +} + +bool ToshibaClimate::compare_rac_pt1411hwru_packets_(const uint8_t *message1, const uint8_t *message2) { + for (uint8_t i = 0; i < RAC_PT1411HWRU_MESSAGE_LENGTH; i++) { + if (message1[i] != message2[i]) + return false; + } + return true; +} + +bool ToshibaClimate::is_valid_rac_pt1411hwru_message_(const uint8_t *message) { + uint8_t checksum = 0; + + switch (is_valid_rac_pt1411hwru_header_(message)) { + case RAC_PT1411HWRU_MESSAGE_HEADER0: + case RAC_PT1411HWRU_CS_HEADER: + case RAC_PT1411HWRU_SWING_HEADER: + if (is_valid_rac_pt1411hwru_header_(message) && (message[2] == static_cast(~message[3])) && + (message[4] == static_cast(~message[5]))) { + return true; + } + break; + + case RAC_PT1411HWRU_MESSAGE_HEADER1: + for (uint8_t i = 0; i < RAC_PT1411HWRU_MESSAGE_LENGTH - 1; i++) { + checksum += message[i]; + } + if (checksum == message[RAC_PT1411HWRU_MESSAGE_LENGTH - 1]) { + return true; + } + break; + + default: + return false; + } + + return false; +} + +bool ToshibaClimate::on_receive(remote_base::RemoteReceiveData data) { + uint8_t message[18] = {0}; + uint8_t message_length = TOSHIBA_HEADER_LENGTH, temperature_code = 0; + + // Validate header + if (!data.expect_item(TOSHIBA_HEADER_MARK, TOSHIBA_HEADER_SPACE)) { + return false; + } + // Read incoming bits into buffer + if (!decode_(&data, message, message_length)) { + return false; + } + // Determine incoming message protocol version and/or length + if (is_valid_rac_pt1411hwru_header_(message)) { + // We already received four bytes + message_length = RAC_PT1411HWRU_MESSAGE_LENGTH - 4; + } else if ((message[0] ^ message[1] ^ message[2]) != message[3]) { + // Return false if first checksum was not valid + return false; + } else { + // First checksum was valid so continue receiving the remaining bits + message_length = message[2] + 2; + } + // Decode the remaining bytes + if (!decode_(&data, &message[4], message_length)) { + return false; + } + // If this is a RAC-PT1411HWRU message, we expect the first packet a second time and also possibly a third packet + if (is_valid_rac_pt1411hwru_header_(message)) { + // There is always a space between packets + if (!data.expect_item(TOSHIBA_BIT_MARK, TOSHIBA_GAP_SPACE)) { + return false; + } + // Validate header 2 + if (!data.expect_item(TOSHIBA_HEADER_MARK, TOSHIBA_HEADER_SPACE)) { + return false; + } + if (!decode_(&data, &message[6], RAC_PT1411HWRU_MESSAGE_LENGTH)) { + return false; + } + // If this is a RAC-PT1411HWRU message, there may also be a third packet. + // We do not fail the receive if we don't get this; it isn't always present + if (data.expect_item(TOSHIBA_BIT_MARK, TOSHIBA_GAP_SPACE)) { + // Validate header 3 + data.expect_item(TOSHIBA_HEADER_MARK, TOSHIBA_HEADER_SPACE); + if (decode_(&data, &message[12], RAC_PT1411HWRU_MESSAGE_LENGTH)) { + if (!is_valid_rac_pt1411hwru_message_(&message[12])) { + // If a third packet was received but the checksum is not valid, fail + return false; + } + } + } + if (!compare_rac_pt1411hwru_packets_(&message[0], &message[6])) { + // If the first two packets don't match each other, fail + return false; + } + if (!is_valid_rac_pt1411hwru_message_(&message[0])) { + // If the first packet isn't valid, fail + return false; + } + } + + // Header has been verified, now determine protocol version and set the climate component properties + switch (is_valid_rac_pt1411hwru_header_(message)) { + // Power, temperature, mode, fan speed + case RAC_PT1411HWRU_MESSAGE_HEADER0: + // Get the mode + switch (message[4] & 0x0F) { + case RAC_PT1411HWRU_MODE_AUTO: + this->mode = climate::CLIMATE_MODE_HEAT_COOL; + break; + + // case RAC_PT1411HWRU_MODE_OFF: + case RAC_PT1411HWRU_MODE_COOL: + if (((message[4] >> 4) == RAC_PT1411HWRU_TEMPERATURE_FAN_ONLY) && (message[2] == RAC_PT1411HWRU_FAN_OFF)) { + this->mode = climate::CLIMATE_MODE_OFF; + } else { + this->mode = climate::CLIMATE_MODE_COOL; + } + break; + + // case RAC_PT1411HWRU_MODE_DRY: + case RAC_PT1411HWRU_MODE_FAN: + if ((message[4] >> 4) == RAC_PT1411HWRU_TEMPERATURE_FAN_ONLY) + this->mode = climate::CLIMATE_MODE_FAN_ONLY; + else + this->mode = climate::CLIMATE_MODE_DRY; + break; + + case RAC_PT1411HWRU_MODE_HEAT: + this->mode = climate::CLIMATE_MODE_HEAT; + break; + + default: + this->mode = climate::CLIMATE_MODE_OFF; + break; + } + // Get the fan speed/mode + switch (message[2]) { + case RAC_PT1411HWRU_FAN_LOW.code1: + this->fan_mode = climate::CLIMATE_FAN_LOW; + break; + + case RAC_PT1411HWRU_FAN_MED.code1: + this->fan_mode = climate::CLIMATE_FAN_MEDIUM; + break; + + case RAC_PT1411HWRU_FAN_HIGH.code1: + this->fan_mode = climate::CLIMATE_FAN_HIGH; + break; + + case RAC_PT1411HWRU_FAN_AUTO.code1: + default: + this->fan_mode = climate::CLIMATE_FAN_AUTO; + break; + } + // Get the target temperature + if (is_valid_rac_pt1411hwru_message_(&message[12])) { + temperature_code = + (message[4] >> 4) | (message[14] & RAC_PT1411HWRU_FLAG_FRAC) | (message[15] & RAC_PT1411HWRU_FLAG_NEG); + if (message[15] & RAC_PT1411HWRU_FLAG_FAH) { + for (uint8_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_F.size(); i++) { + if (RAC_PT1411HWRU_TEMPERATURE_F[i] == temperature_code) { + this->target_temperature = static_cast((i + TOSHIBA_RAC_PT1411HWRU_TEMP_F_MIN - 32) * 5) / 9; + } + } + } else { + for (uint8_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_C.size(); i++) { + if (RAC_PT1411HWRU_TEMPERATURE_C[i] == temperature_code) { + this->target_temperature = i + TOSHIBA_RAC_PT1411HWRU_TEMP_C_MIN; + } + } + } + } + break; + // "Comfort Sense" temperature packet + case RAC_PT1411HWRU_CS_HEADER: + // "Comfort Sense" feature notes + // IR Code: 0xBA45 xxXX yyYY + // xx: Temperature in °C + // Bit 6: feature state (on/off) + // Bit 7: message contains temperature data for feature (bit 6 must also be set) + // XX: Bitwise complement of xx + // yy: Mode: Auto: 7A + // Cool: 72 + // Heat: 7E + // YY: Bitwise complement of yy + if ((message[2] & RAC_PT1411HWRU_CS_ENABLED) && (message[2] & RAC_PT1411HWRU_CS_DATA)) { + // Setting current_temperature this way allows the unit's remote to provide the temperature to HA + this->current_temperature = message[2] & ~(RAC_PT1411HWRU_CS_ENABLED | RAC_PT1411HWRU_CS_DATA); + } + break; + // Swing mode + case RAC_PT1411HWRU_SWING_HEADER: + if (message[4] == RAC_PT1411HWRU_SWING_VERTICAL[4]) { + this->swing_mode = climate::CLIMATE_SWING_VERTICAL; + } else { + this->swing_mode = climate::CLIMATE_SWING_OFF; + } + break; + // Generic (old) Toshiba packet + default: + uint8_t checksum = 0; + // Add back the length of the header (we pruned it above) + message_length += TOSHIBA_HEADER_LENGTH; + // Validate the second checksum before trusting any more of the message + for (uint8_t i = TOSHIBA_HEADER_LENGTH; i < message_length - 1; i++) { + checksum ^= message[i]; + } + // Did our computed checksum and the provided checksum match? + if (checksum != message[message_length - 1]) { + return false; + } + // Check if this is a short swing/fix message + if (message[4] & TOSHIBA_COMMAND_MOTION) { + // Not supported yet + return false; + } + + // Get the mode + switch (message[6] & 0x0F) { + case TOSHIBA_MODE_OFF: + this->mode = climate::CLIMATE_MODE_OFF; + break; + + case TOSHIBA_MODE_COOL: + this->mode = climate::CLIMATE_MODE_COOL; + break; + + case TOSHIBA_MODE_DRY: + this->mode = climate::CLIMATE_MODE_DRY; + break; + + case TOSHIBA_MODE_FAN_ONLY: + this->mode = climate::CLIMATE_MODE_FAN_ONLY; + break; + + case TOSHIBA_MODE_HEAT: + this->mode = climate::CLIMATE_MODE_HEAT; + break; + + case TOSHIBA_MODE_AUTO: + default: + this->mode = climate::CLIMATE_MODE_HEAT_COOL; + } + + // Get the target temperature + this->target_temperature = (message[5] >> 4) + TOSHIBA_GENERIC_TEMP_C_MIN; + } + + this->publish_state(); + return true; +} + +void ToshibaClimate::encode_(remote_base::RemoteTransmitData *data, const uint8_t *message, const uint8_t nbytes, + const uint8_t repeat) { + data->set_carrier_frequency(TOSHIBA_CARRIER_FREQUENCY); + + for (uint8_t copy = 0; copy <= repeat; copy++) { + data->item(TOSHIBA_HEADER_MARK, TOSHIBA_HEADER_SPACE); + + for (uint8_t byte = 0; byte < nbytes; byte++) { for (uint8_t bit = 0; bit < 8; bit++) { data->mark(TOSHIBA_BIT_MARK); if (message[byte] & (1 << (7 - bit))) { @@ -118,87 +690,24 @@ void ToshibaClimate::transmit_state() { } } } - - data->mark(TOSHIBA_BIT_MARK); - data->space(TOSHIBA_GAP_SPACE); + data->item(TOSHIBA_BIT_MARK, TOSHIBA_GAP_SPACE); } - - transmit.perform(); } -bool ToshibaClimate::on_receive(remote_base::RemoteReceiveData data) { - uint8_t message[16] = {0}; - uint8_t message_length = 4; - - /* Validate header */ - if (!data.expect_item(TOSHIBA_HEADER_MARK, TOSHIBA_HEADER_SPACE)) { - return false; - } - - /* Decode bytes */ - for (uint8_t byte = 0; byte < message_length; byte++) { +bool ToshibaClimate::decode_(remote_base::RemoteReceiveData *data, uint8_t *message, const uint8_t nbytes) { + for (uint8_t byte = 0; byte < nbytes; byte++) { for (uint8_t bit = 0; bit < 8; bit++) { - if (data.expect_item(TOSHIBA_BIT_MARK, TOSHIBA_ONE_SPACE)) { + if (data->expect_item(TOSHIBA_BIT_MARK, TOSHIBA_ONE_SPACE)) { message[byte] |= 1 << (7 - bit); - } else if (data.expect_item(TOSHIBA_BIT_MARK, TOSHIBA_ZERO_SPACE)) { - /* Bit is already clear */ + } else if (data->expect_item(TOSHIBA_BIT_MARK, TOSHIBA_ZERO_SPACE)) { + message[byte] &= static_cast(~(1 << (7 - bit))); } else { return false; } } - - /* Update length */ - if (byte == 3) { - /* Validate the first checksum before trusting the length field */ - if ((message[0] ^ message[1] ^ message[2]) != message[3]) { - return false; - } - message_length = message[2] + 6; - } } - - /* Validate the second checksum before trusting any more of the message */ - uint8_t checksum = 0; - for (uint8_t i = 4; i < message_length - 1; i++) { - checksum ^= message[i]; - } - - if (checksum != message[message_length - 1]) { - return false; - } - - /* Check if this is a short swing/fix message */ - if (message[4] & TOSHIBA_COMMAND_MOTION) { - /* Not supported yet */ - return false; - } - - /* Get the mode. */ - switch (message[6] & 0x0f) { - case TOSHIBA_MODE_OFF: - this->mode = climate::CLIMATE_MODE_OFF; - break; - - case TOSHIBA_MODE_HEAT: - this->mode = climate::CLIMATE_MODE_HEAT; - break; - - case TOSHIBA_MODE_COOL: - this->mode = climate::CLIMATE_MODE_COOL; - break; - - case TOSHIBA_MODE_AUTO: - default: - /* Note: Dry and Fan-only modes are reported as Auto, as they are not supported yet */ - this->mode = climate::CLIMATE_MODE_HEAT_COOL; - } - - /* Get the target temperature */ - this->target_temperature = (message[5] >> 4) + 17; - - this->publish_state(); return true; } -} /* namespace toshiba */ -} /* namespace esphome */ +} // namespace toshiba +} // namespace esphome diff --git a/esphome/components/toshiba/toshiba.h b/esphome/components/toshiba/toshiba.h index 3ab0dcdcdb..36e8760169 100644 --- a/esphome/components/toshiba/toshiba.h +++ b/esphome/components/toshiba/toshiba.h @@ -5,17 +5,69 @@ namespace esphome { namespace toshiba { -const float TOSHIBA_TEMP_MIN = 17.0; -const float TOSHIBA_TEMP_MAX = 30.0; +// Simple enum to represent models. +enum Model { + MODEL_GENERIC = 0, // Temperature range is from 17 to 30 + MODEL_RAC_PT1411HWRU_C = 1, // Temperature range is from 16 to 30 + MODEL_RAC_PT1411HWRU_F = 2, // Temperature range is from 16 to 30 +}; + +// Supported temperature ranges +const float TOSHIBA_GENERIC_TEMP_C_MIN = 17.0; +const float TOSHIBA_GENERIC_TEMP_C_MAX = 30.0; +const float TOSHIBA_RAC_PT1411HWRU_TEMP_C_MIN = 16.0; +const float TOSHIBA_RAC_PT1411HWRU_TEMP_C_MAX = 30.0; +const float TOSHIBA_RAC_PT1411HWRU_TEMP_F_MIN = 60.0; +const float TOSHIBA_RAC_PT1411HWRU_TEMP_F_MAX = 86.0; class ToshibaClimate : public climate_ir::ClimateIR { public: - ToshibaClimate() : climate_ir::ClimateIR(TOSHIBA_TEMP_MIN, TOSHIBA_TEMP_MAX, 1.0f) {} + ToshibaClimate() : climate_ir::ClimateIR(TOSHIBA_GENERIC_TEMP_C_MIN, TOSHIBA_GENERIC_TEMP_C_MAX, 1.0f) {} + + void setup() override; + void set_model(Model model) { this->model_ = model; } protected: void transmit_state() override; + void transmit_generic_(); + void transmit_rac_pt1411hwru_(); + void transmit_rac_pt1411hwru_temp_(bool cs_state = true, bool cs_send_update = true); + // Returns the header if valid, else returns zero + uint8_t is_valid_rac_pt1411hwru_header_(const uint8_t *message); + // Returns true if message is a valid RAC-PT1411HWRU IR message, regardless if first or second packet + bool is_valid_rac_pt1411hwru_message_(const uint8_t *message); + // Returns true if message1 and message 2 are the same + bool compare_rac_pt1411hwru_packets_(const uint8_t *message1, const uint8_t *message2); bool on_receive(remote_base::RemoteReceiveData data) override; + + float temperature_min_() { + return (this->model_ == MODEL_GENERIC) ? TOSHIBA_GENERIC_TEMP_C_MIN : TOSHIBA_RAC_PT1411HWRU_TEMP_C_MIN; + } + float temperature_max_() { + return (this->model_ == MODEL_GENERIC) ? TOSHIBA_GENERIC_TEMP_C_MAX : TOSHIBA_RAC_PT1411HWRU_TEMP_C_MAX; + } + bool toshiba_supports_dry_() { + return ((this->model_ == MODEL_RAC_PT1411HWRU_C) || (this->model_ == MODEL_RAC_PT1411HWRU_F)); + } + bool toshiba_supports_fan_only_() { + return ((this->model_ == MODEL_RAC_PT1411HWRU_C) || (this->model_ == MODEL_RAC_PT1411HWRU_F)); + } + std::set toshiba_fan_modes_() { + return (this->model_ == MODEL_GENERIC) + ? std::set{} + : std::set{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, + climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}; + } + std::set toshiba_swing_modes_() { + return (this->model_ == MODEL_GENERIC) + ? std::set{} + : std::set{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}; + } + void encode_(remote_base::RemoteTransmitData *data, const uint8_t *message, uint8_t nbytes, uint8_t repeat); + bool decode_(remote_base::RemoteReceiveData *data, uint8_t *message, uint8_t nbytes); + + Model model_; }; -} /* namespace toshiba */ -} /* namespace esphome */ +} // namespace toshiba +} // namespace esphome From 06bde559dadb0b0ec0b27eeb94cd4f01ba8579ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Panella?= Date: Mon, 9 Aug 2021 20:26:42 -0500 Subject: [PATCH 1157/1841] Add Dish Network protocol (#2117) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/remote_base/__init__.py | 43 +++++++++ .../components/remote_base/dish_protocol.cpp | 92 +++++++++++++++++++ .../components/remote_base/dish_protocol.h | 38 ++++++++ 3 files changed, 173 insertions(+) create mode 100644 esphome/components/remote_base/dish_protocol.cpp create mode 100644 esphome/components/remote_base/dish_protocol.h diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index c4cdc98bbb..c9f1c611a8 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -234,6 +234,49 @@ async def build_dumpers(config): return dumpers +# Dish +DishData, DishBinarySensor, DishTrigger, DishAction, DishDumper = declare_protocol( + "Dish" +) +DISH_SCHEMA = cv.Schema( + { + cv.Optional(CONF_ADDRESS, default=1): cv.int_range(min=1, max=16), + cv.Required(CONF_COMMAND): cv.int_range(min=0, max=63), + } +) + + +@register_binary_sensor("dish", DishBinarySensor, DISH_SCHEMA) +def dish_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + DishData, + ("address", config[CONF_ADDRESS]), + ("command", config[CONF_COMMAND]), + ) + ) + ) + + +@register_trigger("dish", DishTrigger, DishData) +def dish_trigger(var, config): + pass + + +@register_dumper("dish", DishDumper) +def dish_dumper(var, config): + pass + + +@register_action("dish", DishAction, DISH_SCHEMA) +async def dish_action(var, config, args): + template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8) + cg.add(var.set_address(template_)) + template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8) + cg.add(var.set_command(template_)) + + # JVC JVCData, JVCBinarySensor, JVCTrigger, JVCAction, JVCDumper = declare_protocol("JVC") JVC_SCHEMA = cv.Schema({cv.Required(CONF_DATA): cv.hex_uint32_t}) diff --git a/esphome/components/remote_base/dish_protocol.cpp b/esphome/components/remote_base/dish_protocol.cpp new file mode 100644 index 0000000000..1257e22a45 --- /dev/null +++ b/esphome/components/remote_base/dish_protocol.cpp @@ -0,0 +1,92 @@ +#include "dish_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.dish"; + +static const uint32_t HEADER_HIGH_US = 400; +static const uint32_t HEADER_LOW_US = 6100; +static const uint32_t BIT_HIGH_US = 400; +static const uint32_t BIT_ONE_LOW_US = 1700; +static const uint32_t BIT_ZERO_LOW_US = 2800; + +void DishProtocol::encode(RemoteTransmitData *dst, const DishData &data) { + dst->reserve(138); + dst->set_carrier_frequency(57600); + + // HEADER + dst->item(HEADER_HIGH_US, HEADER_LOW_US); + + // Typically a DISH device needs to get a command a total of + // at least 4 times to accept it. + for (uint i = 0; i < 4; i++) { + // COMMAND (function, in MSB) + for (uint8_t mask = 1UL << 5; mask; mask >>= 1) { + if (data.command & mask) + dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); + else + dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } + + // ADDRESS (unit code, in LSB) + for (uint8_t mask = 1UL; mask < 1UL << 4; mask <<= 1) { + if ((data.address - 1) & mask) + dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); + else + dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } + // PADDING + for (uint j = 0; j < 6; j++) + dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + + // FOOTER + dst->item(HEADER_HIGH_US, HEADER_LOW_US); + } +} +optional DishProtocol::decode(RemoteReceiveData src) { + DishData data{ + .address = 0, + .command = 0, + }; + if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US)) + return {}; + + for (uint8_t mask = 1UL << 5; mask != 0; mask >>= 1) { + if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { + data.command |= mask; + } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { + data.command &= ~mask; + } else { + return {}; + } + } + + for (uint8_t mask = 1UL; mask < 1UL << 5; mask <<= 1) { + if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { + data.address |= mask; + } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { + data.address &= ~mask; + } else { + return {}; + } + } + for (uint j = 0; j < 6; j++) { + if (!src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { + return {}; + } + } + data.address++; + + src.expect_item(HEADER_HIGH_US, HEADER_LOW_US); + + return data; +} + +void DishProtocol::dump(const DishData &data) { + ESP_LOGD(TAG, "Received Dish: address=0x%02X, command=0x%02X", data.address, data.command); +} + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/dish_protocol.h b/esphome/components/remote_base/dish_protocol.h new file mode 100644 index 0000000000..ca4d04ed34 --- /dev/null +++ b/esphome/components/remote_base/dish_protocol.h @@ -0,0 +1,38 @@ +#pragma once + +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +struct DishData { + uint8_t address; + uint8_t command; + + bool operator==(const DishData &rhs) const { return address == rhs.address && command == rhs.command; } +}; + +class DishProtocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const DishData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const DishData &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(Dish) + +template class DishAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(uint8_t, address) + TEMPLATABLE_VALUE(uint8_t, command) + + void encode(RemoteTransmitData *dst, Ts... x) override { + DishData data{}; + data.address = this->address_.value(x...); + data.command = this->command_.value(x...); + DishProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome From 1f42d32eb5934fb2fdd2d6b6ab0dd5a823a6b42e Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 10 Aug 2021 03:27:21 +0200 Subject: [PATCH 1158/1841] Fix some issues with deprecated argv syntax detection (#2127) --- esphome/__main__.py | 3 ++- esphome/dashboard/dashboard.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index a7a3836b69..614bc12bae 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -408,7 +408,7 @@ def command_update_all(args): print("-" * twidth) print() rc = run_external_process( - "esphome", "--dashboard", "run", "--no-logs", "--device", "OTA", f + "esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA" ) if rc == 0: print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f)) @@ -505,6 +505,7 @@ def parse_args(argv): "clean", "dashboard", "vscode", + "update-all", ], ) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 66f72bfc00..d10e5ff002 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -329,7 +329,7 @@ class EsphomeVscodeHandler(EsphomeCommandWebSocket): class EsphomeAceEditorHandler(EsphomeCommandWebSocket): def build_command(self, json_message): - return ["esphome", "--dashboard", "-q", "vscode", settings.config_dir, "--ace"] + return ["esphome", "--dashboard", "-q", "vscode", "--ace", settings.config_dir] class EsphomeUpdateAllHandler(EsphomeCommandWebSocket): From c038cf27a7bdc621ccf7aa2fb9832f0f79360750 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 10 Aug 2021 03:30:29 +0200 Subject: [PATCH 1159/1841] Don't discard cold/warm white brightness in constant brightness mode (#2136) --- esphome/components/light/light_color_values.h | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index 81dcb614a6..c696ed8516 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -169,12 +169,18 @@ class LightColorValues { const float cw_level = gamma_correct(this->cold_white_, gamma); const float ww_level = gamma_correct(this->warm_white_, gamma); const float white_level = gamma_correct(this->state_ * this->brightness_, gamma); - *cold_white = white_level * cw_level; - *warm_white = white_level * ww_level; - if (constant_brightness && (cw_level > 0 || ww_level > 0)) { - const float sum = cw_level + ww_level; - *cold_white /= sum; - *warm_white /= sum; + if (!constant_brightness) { + *cold_white = white_level * cw_level; + *warm_white = white_level * ww_level; + } else { + // Just multiplying by cw_level / (cw_level + ww_level) would divide out the brightness information from the + // cold_white and warm_white settings (i.e. cw=0.8, ww=0.4 would be identical to cw=0.4, ww=0.2), which breaks + // transitions. Use the highest value as the brightness for the white channels (the alternative, using cw+ww/2, + // reduces to cw/2 and ww/2, which would still limit brightness to 100% of a single channel, but isn't very + // useful in all other aspects -- that behaviour can also be achieved by limiting the output power). + const float sum = cw_level > 0 || ww_level > 0 ? cw_level + ww_level : 1; // Don't divide by zero. + *cold_white = white_level * std::max(cw_level, ww_level) * cw_level / sum; + *warm_white = white_level * std::max(cw_level, ww_level) * ww_level / sum; } } else { *cold_white = *warm_white = 0; From 93796491af9efe0fd5a7293fee32fc706c6670a4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 10 Aug 2021 13:45:31 +1200 Subject: [PATCH 1160/1841] Allow entities to be disabled by default in HA (#2113) Co-authored-by: Otto Winter --- esphome/components/api/api.proto | 11 ++ esphome/components/api/api_connection.cpp | 15 +++ esphome/components/api/api_pb2.cpp | 123 +++++++++++++++++++ esphome/components/api/api_pb2.h | 15 +++ esphome/components/binary_sensor/__init__.py | 4 +- esphome/components/climate/__init__.py | 4 +- esphome/components/cover/__init__.py | 4 +- esphome/components/esp32_camera/__init__.py | 3 + esphome/components/fan/__init__.py | 4 +- esphome/components/light/__init__.py | 4 +- esphome/components/number/__init__.py | 4 +- esphome/components/select/__init__.py | 4 +- esphome/components/sensor/__init__.py | 4 +- esphome/components/switch/__init__.py | 4 +- esphome/components/text_sensor/__init__.py | 4 +- esphome/config_validation.py | 14 ++- esphome/const.py | 1 + esphome/core/component.cpp | 3 + esphome/core/component.h | 9 ++ tests/test5.yaml | 3 + 20 files changed, 224 insertions(+), 13 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index c0bbbaaeab..b9b56d3f8e 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -214,6 +214,7 @@ message ListEntitiesBinarySensorResponse { string device_class = 5; bool is_status_binary_sensor = 6; + bool disabled_by_default = 7; } message BinarySensorStateResponse { option (id) = 21; @@ -243,6 +244,7 @@ message ListEntitiesCoverResponse { bool supports_position = 6; bool supports_tilt = 7; string device_class = 8; + bool disabled_by_default = 9; } enum LegacyCoverState { @@ -310,6 +312,7 @@ message ListEntitiesFanResponse { bool supports_speed = 6; bool supports_direction = 7; int32 supported_speed_count = 8; + bool disabled_by_default = 9; } enum FanSpeed { FAN_SPEED_LOW = 0; @@ -384,6 +387,7 @@ message ListEntitiesLightResponse { float min_mireds = 9; float max_mireds = 10; repeated string effects = 11; + bool disabled_by_default = 13; } message LightStateResponse { option (id) = 24; @@ -469,6 +473,7 @@ message ListEntitiesSensorResponse { string device_class = 9; SensorStateClass state_class = 10; SensorLastResetType last_reset_type = 11; + bool disabled_by_default = 12; } message SensorStateResponse { option (id) = 25; @@ -496,6 +501,7 @@ message ListEntitiesSwitchResponse { string icon = 5; bool assumed_state = 6; + bool disabled_by_default = 7; } message SwitchStateResponse { option (id) = 26; @@ -528,6 +534,7 @@ message ListEntitiesTextSensorResponse { string unique_id = 4; string icon = 5; + bool disabled_by_default = 6; } message TextSensorStateResponse { option (id) = 27; @@ -687,6 +694,7 @@ message ListEntitiesCameraResponse { fixed32 key = 2; string name = 3; string unique_id = 4; + bool disabled_by_default = 5; } message CameraImageResponse { @@ -779,6 +787,7 @@ message ListEntitiesClimateResponse { repeated string supported_custom_fan_modes = 15; repeated ClimatePreset supported_presets = 16; repeated string supported_custom_presets = 17; + bool disabled_by_default = 18; } message ClimateStateResponse { option (id) = 47; @@ -846,6 +855,7 @@ message ListEntitiesNumberResponse { float min_value = 6; float max_value = 7; float step = 8; + bool disabled_by_default = 9; } message NumberStateResponse { option (id) = 50; @@ -882,6 +892,7 @@ message ListEntitiesSelectResponse { string icon = 5; repeated string options = 6; + bool disabled_by_default = 7; } message SelectStateResponse { option (id) = 53; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 94522a13be..5fba549a57 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -176,6 +176,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_ msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor); msg.device_class = binary_sensor->get_device_class(); msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); + msg.disabled_by_default = binary_sensor->is_disabled_by_default(); return this->send_list_entities_binary_sensor_response(msg); } #endif @@ -207,6 +208,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) { msg.supports_position = traits.get_supports_position(); msg.supports_tilt = traits.get_supports_tilt(); msg.device_class = cover->get_device_class(); + msg.disabled_by_default = cover->is_disabled_by_default(); return this->send_list_entities_cover_response(msg); } void APIConnection::cover_command(const CoverCommandRequest &msg) { @@ -268,6 +270,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) { msg.supports_speed = traits.supports_speed(); msg.supports_direction = traits.supports_direction(); msg.supported_speed_count = traits.supported_speed_count(); + msg.disabled_by_default = fan->is_disabled_by_default(); return this->send_list_entities_fan_response(msg); } void APIConnection::fan_command(const FanCommandRequest &msg) { @@ -334,6 +337,9 @@ bool APIConnection::send_light_info(light::LightState *light) { msg.object_id = light->get_object_id(); msg.name = light->get_name(); msg.unique_id = get_default_unique_id("light", light); + + msg.disabled_by_default = light->is_disabled_by_default(); + for (auto mode : traits.get_supported_color_modes()) msg.supported_color_modes.push_back(static_cast(mode)); @@ -419,6 +425,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { msg.device_class = sensor->get_device_class(); msg.state_class = static_cast(sensor->state_class); msg.last_reset_type = static_cast(sensor->last_reset_type); + msg.disabled_by_default = sensor->is_disabled_by_default(); return this->send_list_entities_sensor_response(msg); } @@ -442,6 +449,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) { msg.unique_id = get_default_unique_id("switch", a_switch); msg.icon = a_switch->get_icon(); msg.assumed_state = a_switch->assumed_state(); + msg.disabled_by_default = a_switch->is_disabled_by_default(); return this->send_list_entities_switch_response(msg); } void APIConnection::switch_command(const SwitchCommandRequest &msg) { @@ -476,6 +484,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) if (msg.unique_id.empty()) msg.unique_id = get_default_unique_id("text_sensor", text_sensor); msg.icon = text_sensor->get_icon(); + msg.disabled_by_default = text_sensor->is_disabled_by_default(); return this->send_list_entities_text_sensor_response(msg); } #endif @@ -519,6 +528,9 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { msg.object_id = climate->get_object_id(); msg.name = climate->get_name(); msg.unique_id = get_default_unique_id("climate", climate); + + msg.disabled_by_default = climate->is_disabled_by_default(); + msg.supports_current_temperature = traits.get_supports_current_temperature(); msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); @@ -591,6 +603,7 @@ bool APIConnection::send_number_info(number::Number *number) { msg.name = number->get_name(); msg.unique_id = get_default_unique_id("number", number); msg.icon = number->traits.get_icon(); + msg.disabled_by_default = number->is_disabled_by_default(); msg.min_value = number->traits.get_min_value(); msg.max_value = number->traits.get_max_value(); @@ -627,6 +640,7 @@ bool APIConnection::send_select_info(select::Select *select) { msg.name = select->get_name(); msg.unique_id = get_default_unique_id("select", select); msg.icon = select->traits.get_icon(); + msg.disabled_by_default = select->is_disabled_by_default(); for (const auto &option : select->traits.get_options()) msg.options.push_back(option); @@ -658,6 +672,7 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { msg.object_id = camera->get_object_id(); msg.name = camera->get_name(); msg.unique_id = get_default_unique_id("camera", camera); + msg.disabled_by_default = camera->is_disabled_by_default(); return this->send_list_entities_camera_response(msg); } void APIConnection::camera_image(const CameraImageRequest &msg) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 65fe16d6e0..eecba7a68e 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -501,6 +501,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar this->is_status_binary_sensor = value.as_bool(); return true; } + case 7: { + this->disabled_by_default = value.as_bool(); + return true; + } default: return false; } @@ -544,6 +548,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(4, this->unique_id); buffer.encode_string(5, this->device_class); buffer.encode_bool(6, this->is_status_binary_sensor); + buffer.encode_bool(7, this->disabled_by_default); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { @@ -573,6 +578,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { out.append(" is_status_binary_sensor: "); out.append(YESNO(this->is_status_binary_sensor)); out.append("\n"); + + out.append(" disabled_by_default: "); + out.append(YESNO(this->disabled_by_default)); + out.append("\n"); out.append("}"); } #endif @@ -638,6 +647,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val this->supports_tilt = value.as_bool(); return true; } + case 9: { + this->disabled_by_default = value.as_bool(); + return true; + } default: return false; } @@ -683,6 +696,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->supports_position); buffer.encode_bool(7, this->supports_tilt); buffer.encode_string(8, this->device_class); + buffer.encode_bool(9, this->disabled_by_default); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { @@ -720,6 +734,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { out.append(" device_class: "); out.append("'").append(this->device_class).append("'"); out.append("\n"); + + out.append(" disabled_by_default: "); + out.append(YESNO(this->disabled_by_default)); + out.append("\n"); out.append("}"); } #endif @@ -904,6 +922,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value this->supported_speed_count = value.as_int32(); return true; } + case 9: { + this->disabled_by_default = value.as_bool(); + return true; + } default: return false; } @@ -945,6 +967,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->supports_speed); buffer.encode_bool(7, this->supports_direction); buffer.encode_int32(8, this->supported_speed_count); + buffer.encode_bool(9, this->disabled_by_default); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesFanResponse::dump_to(std::string &out) const { @@ -983,6 +1006,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { sprintf(buffer, "%d", this->supported_speed_count); out.append(buffer); out.append("\n"); + + out.append(" disabled_by_default: "); + out.append(YESNO(this->disabled_by_default)); + out.append("\n"); out.append("}"); } #endif @@ -1205,6 +1232,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val this->legacy_supports_color_temperature = value.as_bool(); return true; } + case 13: { + this->disabled_by_default = value.as_bool(); + return true; + } default: return false; } @@ -1266,6 +1297,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->effects) { buffer.encode_string(11, it, true); } + buffer.encode_bool(13, this->disabled_by_default); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLightResponse::dump_to(std::string &out) const { @@ -1325,6 +1357,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append("'").append(it).append("'"); out.append("\n"); } + + out.append(" disabled_by_default: "); + out.append(YESNO(this->disabled_by_default)); + out.append("\n"); out.append("}"); } #endif @@ -1780,6 +1816,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->last_reset_type = value.as_enum(); return true; } + case 12: { + this->disabled_by_default = value.as_bool(); + return true; + } default: return false; } @@ -1836,6 +1876,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(9, this->device_class); buffer.encode_enum(10, this->state_class); buffer.encode_enum(11, this->last_reset_type); + buffer.encode_bool(12, this->disabled_by_default); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSensorResponse::dump_to(std::string &out) const { @@ -1886,6 +1927,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append(" last_reset_type: "); out.append(proto_enum_to_string(this->last_reset_type)); out.append("\n"); + + out.append(" disabled_by_default: "); + out.append(YESNO(this->disabled_by_default)); + out.append("\n"); out.append("}"); } #endif @@ -1944,6 +1989,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->assumed_state = value.as_bool(); return true; } + case 7: { + this->disabled_by_default = value.as_bool(); + return true; + } default: return false; } @@ -1987,6 +2036,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(4, this->unique_id); buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->assumed_state); + buffer.encode_bool(7, this->disabled_by_default); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSwitchResponse::dump_to(std::string &out) const { @@ -2016,6 +2066,10 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const { out.append(" assumed_state: "); out.append(YESNO(this->assumed_state)); out.append("\n"); + + out.append(" disabled_by_default: "); + out.append(YESNO(this->disabled_by_default)); + out.append("\n"); out.append("}"); } #endif @@ -2097,6 +2151,16 @@ void SwitchCommandRequest::dump_to(std::string &out) const { out.append("}"); } #endif +bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 6: { + this->disabled_by_default = value.as_bool(); + return true; + } + default: + return false; + } +} bool ListEntitiesTextSensorResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -2135,6 +2199,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(3, this->name); buffer.encode_string(4, this->unique_id); buffer.encode_string(5, this->icon); + buffer.encode_bool(6, this->disabled_by_default); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { @@ -2160,6 +2225,10 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" disabled_by_default: "); + out.append(YESNO(this->disabled_by_default)); + out.append("\n"); out.append("}"); } #endif @@ -2786,6 +2855,16 @@ void ExecuteServiceRequest::dump_to(std::string &out) const { out.append("}"); } #endif +bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 5: { + this->disabled_by_default = value.as_bool(); + return true; + } + default: + return false; + } +} bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -2819,6 +2898,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(2, this->key); buffer.encode_string(3, this->name); buffer.encode_string(4, this->unique_id); + buffer.encode_bool(5, this->disabled_by_default); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCameraResponse::dump_to(std::string &out) const { @@ -2840,6 +2920,10 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const { out.append(" unique_id: "); out.append("'").append(this->unique_id).append("'"); out.append("\n"); + + out.append(" disabled_by_default: "); + out.append(YESNO(this->disabled_by_default)); + out.append("\n"); out.append("}"); } #endif @@ -2963,6 +3047,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v this->supported_presets.push_back(value.as_enum()); return true; } + case 18: { + this->disabled_by_default = value.as_bool(); + return true; + } default: return false; } @@ -3045,6 +3133,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->supported_custom_presets) { buffer.encode_string(17, it, true); } + buffer.encode_bool(18, this->disabled_by_default); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesClimateResponse::dump_to(std::string &out) const { @@ -3133,6 +3222,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append("'").append(it).append("'"); out.append("\n"); } + + out.append(" disabled_by_default: "); + out.append(YESNO(this->disabled_by_default)); + out.append("\n"); out.append("}"); } #endif @@ -3503,6 +3596,16 @@ void ClimateCommandRequest::dump_to(std::string &out) const { out.append("}"); } #endif +bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 9: { + this->disabled_by_default = value.as_bool(); + return true; + } + default: + return false; + } +} bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -3556,6 +3659,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(6, this->min_value); buffer.encode_float(7, this->max_value); buffer.encode_float(8, this->step); + buffer.encode_bool(9, this->disabled_by_default); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { @@ -3596,6 +3700,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { sprintf(buffer, "%g", this->step); out.append(buffer); out.append("\n"); + + out.append(" disabled_by_default: "); + out.append(YESNO(this->disabled_by_default)); + out.append("\n"); out.append("}"); } #endif @@ -3682,6 +3790,16 @@ void NumberCommandRequest::dump_to(std::string &out) const { out.append("}"); } #endif +bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 7: { + this->disabled_by_default = value.as_bool(); + return true; + } + default: + return false; + } +} bool ListEntitiesSelectResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -3727,6 +3845,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->options) { buffer.encode_string(6, it, true); } + buffer.encode_bool(7, this->disabled_by_default); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSelectResponse::dump_to(std::string &out) const { @@ -3758,6 +3877,10 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { out.append("'").append(it).append("'"); out.append("\n"); } + + out.append(" disabled_by_default: "); + out.append(YESNO(this->disabled_by_default)); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 9849f05a98..be32488391 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -266,6 +266,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage { std::string unique_id{}; std::string device_class{}; bool is_status_binary_sensor{false}; + bool disabled_by_default{false}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -300,6 +301,7 @@ class ListEntitiesCoverResponse : public ProtoMessage { bool supports_position{false}; bool supports_tilt{false}; std::string device_class{}; + bool disabled_by_default{false}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -355,6 +357,7 @@ class ListEntitiesFanResponse : public ProtoMessage { bool supports_speed{false}; bool supports_direction{false}; int32_t supported_speed_count{0}; + bool disabled_by_default{false}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -418,6 +421,7 @@ class ListEntitiesLightResponse : public ProtoMessage { float min_mireds{0.0f}; float max_mireds{0.0f}; std::vector effects{}; + bool disabled_by_default{false}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -505,6 +509,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { std::string device_class{}; enums::SensorStateClass state_class{}; enums::SensorLastResetType last_reset_type{}; + bool disabled_by_default{false}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -537,6 +542,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage { std::string unique_id{}; std::string icon{}; bool assumed_state{false}; + bool disabled_by_default{false}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -580,6 +586,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage { std::string name{}; std::string unique_id{}; std::string icon{}; + bool disabled_by_default{false}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -588,6 +595,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage { protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class TextSensorStateResponse : public ProtoMessage { public: @@ -789,6 +797,7 @@ class ListEntitiesCameraResponse : public ProtoMessage { uint32_t key{0}; std::string name{}; std::string unique_id{}; + bool disabled_by_default{false}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -797,6 +806,7 @@ class ListEntitiesCameraResponse : public ProtoMessage { protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class CameraImageResponse : public ProtoMessage { public: @@ -844,6 +854,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { std::vector supported_custom_fan_modes{}; std::vector supported_presets{}; std::vector supported_custom_presets{}; + bool disabled_by_default{false}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -922,6 +933,7 @@ class ListEntitiesNumberResponse : public ProtoMessage { float min_value{0.0f}; float max_value{0.0f}; float step{0.0f}; + bool disabled_by_default{false}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -930,6 +942,7 @@ class ListEntitiesNumberResponse : public ProtoMessage { protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class NumberStateResponse : public ProtoMessage { public: @@ -965,6 +978,7 @@ class ListEntitiesSelectResponse : public ProtoMessage { std::string unique_id{}; std::string icon{}; std::vector options{}; + bool disabled_by_default{false}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -973,6 +987,7 @@ class ListEntitiesSelectResponse : public ProtoMessage { protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class SelectStateResponse : public ProtoMessage { public: diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 8f66978320..3c2169a922 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -6,6 +6,7 @@ from esphome.components import mqtt from esphome.const import ( CONF_DELAY, CONF_DEVICE_CLASS, + CONF_DISABLED_BY_DEFAULT, CONF_FILTERS, CONF_ID, CONF_INTERNAL, @@ -315,7 +316,7 @@ def validate_multi_click_timing(value): device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") -BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( +BINARY_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(BinarySensor), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id( @@ -377,6 +378,7 @@ BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( async def setup_binary_sensor_core_(var, config): cg.add(var.set_name(config[CONF_NAME])) + cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) if CONF_INTERNAL in config: cg.add(var.set_internal(config[CONF_INTERNAL])) if CONF_DEVICE_CLASS in config: diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 88991ff795..f6a9fa2927 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_AWAY, CONF_CUSTOM_FAN_MODE, CONF_CUSTOM_PRESET, + CONF_DISABLED_BY_DEFAULT, CONF_ID, CONF_INTERNAL, CONF_MAX_TEMPERATURE, @@ -86,7 +87,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True) # Actions ControlAction = climate_ns.class_("ControlAction", automation.Action) -CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend( +CLIMATE_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(Climate), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent), @@ -104,6 +105,7 @@ CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend( async def setup_climate_core_(var, config): cg.add(var.set_name(config[CONF_NAME])) + cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) if CONF_INTERNAL in config: cg.add(var.set_internal(config[CONF_INTERNAL])) visual = config[CONF_VISUAL] diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 4a7266303d..6fd6ac81b0 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -4,6 +4,7 @@ from esphome import automation from esphome.automation import maybe_simple_id, Condition from esphome.components import mqtt from esphome.const import ( + CONF_DISABLED_BY_DEFAULT, CONF_ID, CONF_INTERNAL, CONF_DEVICE_CLASS, @@ -63,7 +64,7 @@ CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action) CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition) CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition) -COVER_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend( +COVER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(Cover), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent), @@ -75,6 +76,7 @@ COVER_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend( async def setup_cover_core_(var, config): cg.add(var.set_name(config[CONF_NAME])) + cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) if CONF_INTERNAL in config: cg.add(var.set_internal(config[CONF_INTERNAL])) if CONF_DEVICE_CLASS in config: diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index abbb4b1b7e..e3c7383953 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins from esphome.const import ( + CONF_DISABLED_BY_DEFAULT, CONF_FREQUENCY, CONF_ID, CONF_NAME, @@ -66,6 +67,7 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(ESP32Camera), cv.Required(CONF_NAME): cv.string, + cv.Optional(CONF_DISABLED_BY_DEFAULT, default=False): cv.boolean, cv.Required(CONF_DATA_PINS): cv.All([pins.input_pin], cv.Length(min=8, max=8)), cv.Required(CONF_VSYNC_PIN): pins.input_pin, cv.Required(CONF_HREF_PIN): pins.input_pin, @@ -124,6 +126,7 @@ SETTERS = { async def to_code(config): var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME]) + cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) await cg.register_component(var, config) for key, setter in SETTERS.items(): diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index d1f43467ed..9db2e9ed12 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -4,6 +4,7 @@ from esphome import automation from esphome.automation import maybe_simple_id from esphome.components import mqtt from esphome.const import ( + CONF_DISABLED_BY_DEFAULT, CONF_ID, CONF_INTERNAL, CONF_MQTT_ID, @@ -34,7 +35,7 @@ ToggleAction = fan_ns.class_("ToggleAction", automation.Action) FanTurnOnTrigger = fan_ns.class_("FanTurnOnTrigger", automation.Trigger.template()) FanTurnOffTrigger = fan_ns.class_("FanTurnOffTrigger", automation.Trigger.template()) -FAN_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend( +FAN_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(FanState), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent), @@ -66,6 +67,7 @@ FAN_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend( async def setup_fan_core_(var, config): cg.add(var.set_name(config[CONF_NAME])) + cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) if CONF_INTERNAL in config: cg.add(var.set_internal(config[CONF_INTERNAL])) diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index ec7d6bcbc0..52e8984545 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -5,6 +5,7 @@ from esphome.components import mqtt, power_supply from esphome.const import ( CONF_COLOR_CORRECT, CONF_DEFAULT_TRANSITION_LENGTH, + CONF_DISABLED_BY_DEFAULT, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_ID, @@ -52,7 +53,7 @@ RESTORE_MODES = { "RESTORE_INVERTED_DEFAULT_ON": LightRestoreMode.LIGHT_RESTORE_INVERTED_DEFAULT_ON, } -LIGHT_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend( +LIGHT_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(LightState), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTJSONLightComponent), @@ -121,6 +122,7 @@ def validate_color_temperature_channels(value): async def setup_light_core_(light_var, output_var, config): + cg.add(light_var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE])) if CONF_INTERNAL in config: cg.add(light_var.set_internal(config[CONF_INTERNAL])) diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index bf95cb1b31..88153492d3 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -6,6 +6,7 @@ from esphome.components import mqtt from esphome.const import ( CONF_ABOVE, CONF_BELOW, + CONF_DISABLED_BY_DEFAULT, CONF_ICON, CONF_ID, CONF_INTERNAL, @@ -45,7 +46,7 @@ NumberInRangeCondition = number_ns.class_( icon = cv.icon -NUMBER_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( +NUMBER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent), cv.GenerateID(): cv.declare_id(Number), @@ -71,6 +72,7 @@ async def setup_number_core_( var, config, *, min_value: float, max_value: float, step: Optional[float] ): cg.add(var.set_name(config[CONF_NAME])) + cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) if CONF_INTERNAL in config: cg.add(var.set_internal(config[CONF_INTERNAL])) diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index 5be09b0832..d3ab344926 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -4,6 +4,7 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import mqtt from esphome.const import ( + CONF_DISABLED_BY_DEFAULT, CONF_ICON, CONF_ID, CONF_INTERNAL, @@ -34,7 +35,7 @@ SelectSetAction = select_ns.class_("SelectSetAction", automation.Action) icon = cv.icon -SELECT_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( +SELECT_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent), cv.GenerateID(): cv.declare_id(Select), @@ -50,6 +51,7 @@ SELECT_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( async def setup_select_core_(var, config, *, options: List[str]): cg.add(var.set_name(config[CONF_NAME])) + cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) if CONF_INTERNAL in config: cg.add(var.set_internal(config[CONF_INTERNAL])) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 6152f2cca5..19f2a9f0e6 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_ACCURACY_DECIMALS, CONF_ALPHA, CONF_BELOW, + CONF_DISABLED_BY_DEFAULT, CONF_EXPIRE_AFTER, CONF_FILTERS, CONF_FROM, @@ -170,7 +171,7 @@ validate_accuracy_decimals = cv.int_ validate_icon = cv.icon validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") -SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( +SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSensorComponent), cv.GenerateID(): cv.declare_id(Sensor), @@ -494,6 +495,7 @@ async def build_filters(config): async def setup_sensor_core_(var, config): cg.add(var.set_name(config[CONF_NAME])) + cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) if CONF_INTERNAL in config: cg.add(var.set_internal(config[CONF_INTERNAL])) if CONF_DEVICE_CLASS in config: diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py index 647041c19c..8aa213a9f6 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -4,6 +4,7 @@ from esphome import automation from esphome.automation import Condition, maybe_simple_id from esphome.components import mqtt from esphome.const import ( + CONF_DISABLED_BY_DEFAULT, CONF_ICON, CONF_ID, CONF_INTERNAL, @@ -38,7 +39,7 @@ SwitchTurnOffTrigger = switch_ns.class_( icon = cv.icon -SWITCH_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend( +SWITCH_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSwitchComponent), cv.Optional(CONF_ICON): icon, @@ -59,6 +60,7 @@ SWITCH_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend( async def setup_switch_core_(var, config): cg.add(var.set_name(config[CONF_NAME])) + cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) if CONF_INTERNAL in config: cg.add(var.set_internal(config[CONF_INTERNAL])) if CONF_ICON in config: diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index 84fedc8d94..d06f12de0e 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import mqtt from esphome.const import ( + CONF_DISABLED_BY_DEFAULT, CONF_ICON, CONF_ID, CONF_INTERNAL, @@ -33,7 +34,7 @@ TextSensorStateCondition = text_sensor_ns.class_( icon = cv.icon -TEXT_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( +TEXT_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor), cv.Optional(CONF_ICON): icon, @@ -48,6 +49,7 @@ TEXT_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( async def setup_text_sensor_core_(var, config): cg.add(var.set_name(config[CONF_NAME])) + cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) if CONF_INTERNAL in config: cg.add(var.set_internal(config[CONF_INTERNAL])) if CONF_ICON in config: diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 9afed58f80..84452839ad 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -15,6 +15,7 @@ from esphome.const import ( ALLOWED_NAME_CHARS, CONF_AVAILABILITY, CONF_COMMAND_TOPIC, + CONF_DISABLED_BY_DEFAULT, CONF_DISCOVERY, CONF_ID, CONF_INTERNAL, @@ -1556,17 +1557,14 @@ MQTT_COMPONENT_AVAILABILITY_SCHEMA = Schema( MQTT_COMPONENT_SCHEMA = Schema( { - Optional(CONF_NAME): string, Optional(CONF_RETAIN): All(requires_component("mqtt"), boolean), Optional(CONF_DISCOVERY): All(requires_component("mqtt"), boolean), Optional(CONF_STATE_TOPIC): All(requires_component("mqtt"), publish_topic), Optional(CONF_AVAILABILITY): All( requires_component("mqtt"), Any(None, MQTT_COMPONENT_AVAILABILITY_SCHEMA) ), - Optional(CONF_INTERNAL): boolean, } ) -MQTT_COMPONENT_SCHEMA.add_extra(_nameable_validator) MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend( { @@ -1574,6 +1572,16 @@ MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend( } ) +NAMEABLE_SCHEMA = Schema( + { + Optional(CONF_NAME): string, + Optional(CONF_INTERNAL): boolean, + Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean, + } +) + +NAMEABLE_SCHEMA.add_extra(_nameable_validator) + COMPONENT_SCHEMA = Schema({Optional(CONF_SETUP_PRIORITY): float_}) diff --git a/esphome/const.py b/esphome/const.py index 075dc47411..117b9ccc3e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -176,6 +176,7 @@ CONF_DIO_PIN = "dio_pin" CONF_DIR_PIN = "dir_pin" CONF_DIRECTION = "direction" CONF_DIRECTION_OUTPUT = "direction_output" +CONF_DISABLED_BY_DEFAULT = "disabled_by_default" CONF_DISCOVERY = "discovery" CONF_DISCOVERY_PREFIX = "discovery_prefix" CONF_DISCOVERY_RETAIN = "discovery_retain" diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 09c91fbb0c..f6b15b1977 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -187,4 +187,7 @@ void Nameable::calc_object_id_() { } uint32_t Nameable::get_object_id_hash() { return this->object_id_hash_; } +bool Nameable::is_disabled_by_default() const { return this->disabled_by_default_; } +void Nameable::set_disabled_by_default(bool disabled_by_default) { this->disabled_by_default_ = disabled_by_default; } + } // namespace esphome diff --git a/esphome/core/component.h b/esphome/core/component.h index 433259f627..a4a945ef2a 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -256,6 +256,14 @@ class Nameable { bool is_internal() const; void set_internal(bool internal); + /** Check if this object is declared to be disabled by default. + * + * That means that when the device gets added to Home Assistant (or other clients) it should + * not be added to the default view by default, and a user action is necessary to manually add it. + */ + bool is_disabled_by_default() const; + void set_disabled_by_default(bool disabled_by_default); + protected: virtual uint32_t hash_base() = 0; @@ -265,6 +273,7 @@ class Nameable { std::string object_id_; uint32_t object_id_hash_; bool internal_{false}; + bool disabled_by_default_{false}; }; } // namespace esphome diff --git a/tests/test5.yaml b/tests/test5.yaml index 6ccb83a11a..5d4ff025d9 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -137,10 +137,13 @@ sensor: name: "SelecEM2M Frequency" maximum_demand_active_power: name: "SelecEM2M Maximum Demand Active Power" + disabled_by_default: true maximum_demand_reactive_power: name: "SelecEM2M Maximum Demand Reactive Power" + disabled_by_default: true maximum_demand_apparent_power: name: "SelecEM2M Maximum Demand Apparent Power" + disabled_by_default: true - platform: t6615 uart_id: uart2 From b92311402ad00b5320affff533deb4c76b514e88 Mon Sep 17 00:00:00 2001 From: Tom Matheussen Date: Tue, 10 Aug 2021 07:06:04 +0200 Subject: [PATCH 1161/1841] Adds CGPR1 - Qingping Motion & Ambient light sensor support (#1675) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/xiaomi_ble/xiaomi_ble.cpp | 7 +- esphome/components/xiaomi_ble/xiaomi_ble.h | 3 +- esphome/components/xiaomi_cgpr1/__init__.py | 0 .../components/xiaomi_cgpr1/binary_sensor.py | 75 ++++++++++++++++++ .../components/xiaomi_cgpr1/xiaomi_cgpr1.cpp | 79 +++++++++++++++++++ .../components/xiaomi_cgpr1/xiaomi_cgpr1.h | 40 ++++++++++ tests/test2.yaml | 10 +++ 7 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 esphome/components/xiaomi_cgpr1/__init__.py create mode 100644 esphome/components/xiaomi_cgpr1/binary_sensor.py create mode 100644 esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp create mode 100644 esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index c4434801b4..f86dc44eeb 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -104,7 +104,7 @@ bool parse_xiaomi_message(const std::vector &message, XiaomiParseResult } while (payload_length > 3) { - if (payload[payload_offset + 1] != 0x10) { + if (payload[payload_offset + 1] != 0x10 && payload[payload_offset + 1] != 0x00) { ESP_LOGVV(TAG, "parse_xiaomi_message(): fixed byte not found, stop parsing residual data."); break; } @@ -203,6 +203,11 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service } else if ((raw[2] == 0x87) && (raw[3] == 0x03)) { // square body, e-ink display result.type = XiaomiParseResult::TYPE_MHOC401; result.name = "MHOC401"; + } else if ((raw[2] == 0x83) && (raw[3] == 0x0A)) { // Qingping-branded, motion & ambient light sensor + result.type = XiaomiParseResult::TYPE_CGPR1; + result.name = "CGPR1"; + if (raw.size() == 19) + result.raw_offset -= 6; } else { ESP_LOGVV(TAG, "parse_xiaomi_header(): unknown device, no magic bytes."); return {}; diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.h b/esphome/components/xiaomi_ble/xiaomi_ble.h index f431eca11e..7681cbd89c 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.h +++ b/esphome/components/xiaomi_ble/xiaomi_ble.h @@ -23,7 +23,8 @@ struct XiaomiParseResult { TYPE_MUE4094RT, TYPE_WX08ZM, TYPE_MJYD02YLA, - TYPE_MHOC401 + TYPE_MHOC401, + TYPE_CGPR1 } type; std::string name; optional temperature; diff --git a/esphome/components/xiaomi_cgpr1/__init__.py b/esphome/components/xiaomi_cgpr1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/xiaomi_cgpr1/binary_sensor.py b/esphome/components/xiaomi_cgpr1/binary_sensor.py new file mode 100644 index 0000000000..a7f6c41225 --- /dev/null +++ b/esphome/components/xiaomi_cgpr1/binary_sensor.py @@ -0,0 +1,75 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, binary_sensor, esp32_ble_tracker +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_BINDKEY, + CONF_DEVICE_CLASS, + CONF_MAC_ADDRESS, + CONF_ID, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_ILLUMINANCE, + ICON_EMPTY, + UNIT_PERCENT, + CONF_IDLE_TIME, + CONF_ILLUMINANCE, + UNIT_MINUTE, + UNIT_LUX, + ICON_TIMELAPSE, +) + +DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["xiaomi_ble", "sensor"] + +xiaomi_cgpr1_ns = cg.esphome_ns.namespace("xiaomi_cgpr1") +XiaomiCGPR1 = xiaomi_cgpr1_ns.class_( + "XiaomiCGPR1", + binary_sensor.BinarySensor, + cg.Component, + esp32_ble_tracker.ESPBTDeviceListener, +) + +CONFIG_SCHEMA = cv.All( + binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(XiaomiCGPR1), + cv.Required(CONF_BINDKEY): cv.bind_key, + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional( + CONF_DEVICE_CLASS, default="motion" + ): binary_sensor.device_class, + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY + ), + cv.Optional(CONF_IDLE_TIME): sensor.sensor_schema( + UNIT_MINUTE, ICON_TIMELAPSE, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( + UNIT_LUX, ICON_EMPTY, 0, DEVICE_CLASS_ILLUMINANCE + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield esp32_ble_tracker.register_ble_device(var, config) + yield binary_sensor.register_binary_sensor(var, config) + + cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) + cg.add(var.set_bindkey(config[CONF_BINDKEY])) + + if CONF_IDLE_TIME in config: + sens = yield sensor.new_sensor(config[CONF_IDLE_TIME]) + cg.add(var.set_idle_time(sens)) + if CONF_BATTERY_LEVEL in config: + sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) + cg.add(var.set_battery_level(sens)) + if CONF_ILLUMINANCE in config: + sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE]) + cg.add(var.set_illuminance(sens)) diff --git a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp new file mode 100644 index 0000000000..2ed1024076 --- /dev/null +++ b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp @@ -0,0 +1,79 @@ +#include "xiaomi_cgpr1.h" +#include "esphome/core/log.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace xiaomi_cgpr1 { + +static const char *TAG = "xiaomi_cgpr1"; + +void XiaomiCGPR1::dump_config() { + ESP_LOGCONFIG(TAG, "Xiaomi CGPR1"); + LOG_BINARY_SENSOR(" ", "Motion", this); + LOG_SENSOR(" ", "Idle Time", this->idle_time_); + LOG_SENSOR(" ", "Battery Level", this->battery_level_); + LOG_SENSOR(" ", "Illuminance", this->illuminance_); +} + +bool XiaomiCGPR1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + if (device.address_uint64() != this->address_) { + ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); + return false; + } + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + + bool success = false; + for (auto &service_data : device.get_service_datas()) { + auto res = xiaomi_ble::parse_xiaomi_header(service_data); + if (!res.has_value()) { + continue; + } + if (res->is_duplicate) { + continue; + } + if (res->has_encryption && + (!(xiaomi_ble::decrypt_xiaomi_payload(const_cast &>(service_data.data), this->bindkey_, + this->address_)))) { + continue; + } + if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { + continue; + } + if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + continue; + } + if (res->idle_time.has_value() && this->idle_time_ != nullptr) + this->idle_time_->publish_state(*res->idle_time); + if (res->battery_level.has_value() && this->battery_level_ != nullptr) + this->battery_level_->publish_state(*res->battery_level); + if (res->illuminance.has_value() && this->illuminance_ != nullptr) + this->illuminance_->publish_state(*res->illuminance); + if (res->has_motion.has_value()) + this->publish_state(*res->has_motion); + success = true; + } + + if (!success) { + return false; + } + + return true; +} + +void XiaomiCGPR1::set_bindkey(const std::string &bindkey) { + memset(bindkey_, 0, 16); + if (bindkey.size() != 32) { + return; + } + char temp[3] = {0}; + for (int i = 0; i < 16; i++) { + strncpy(temp, &(bindkey.c_str()[i * 2]), 2); + bindkey_[i] = std::strtoul(temp, NULL, 16); + } +} + +} // namespace xiaomi_cgpr1 +} // namespace esphome + +#endif diff --git a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h new file mode 100644 index 0000000000..0b7369c798 --- /dev/null +++ b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h @@ -0,0 +1,40 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/xiaomi_ble/xiaomi_ble.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace xiaomi_cgpr1 { + +class XiaomiCGPR1 : public Component, + public binary_sensor::BinarySensorInitiallyOff, + public esp32_ble_tracker::ESPBTDeviceListener { + public: + void set_address(uint64_t address) { address_ = address; } + void set_bindkey(const std::string &bindkey); + + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } + void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; } + void set_idle_time(sensor::Sensor *idle_time) { idle_time_ = idle_time; } + + protected: + uint64_t address_; + uint8_t bindkey_[16]; + sensor::Sensor *idle_time_{nullptr}; + sensor::Sensor *battery_level_{nullptr}; + sensor::Sensor *illuminance_{nullptr}; +}; + +} // namespace xiaomi_cgpr1 +} // namespace esphome + +#endif diff --git a/tests/test2.yaml b/tests/test2.yaml index 1746804061..6807278c0d 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -303,6 +303,16 @@ binary_sensor: name: 'WX08ZM Tablet Resource' battery_level: name: 'WX08ZM Battery Level' + - platform: xiaomi_cgpr1 + name: 'CGPR1 Motion' + mac_address: '12:34:56:12:34:56' + bindkey: '48403ebe2d385db8d0c187f81e62cb64' + battery_level: + name: 'CGPR1 battery Level' + idle_time: + name: 'CGPR1 Idle Time' + illuminance: + name: 'CGPR1 Illuminance' esp32_ble_tracker: on_ble_advertise: From 553df1d57bf743ea962728aa6530a4acacf2581e Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 10 Aug 2021 09:53:48 +0200 Subject: [PATCH 1162/1841] Don't mark COLOR_* constants as static in header (#2141) --- esphome/core/color.cpp | 3 +++ esphome/core/color.h | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/core/color.cpp b/esphome/core/color.cpp index b9a9f27e46..58d995db2f 100644 --- a/esphome/core/color.cpp +++ b/esphome/core/color.cpp @@ -5,4 +5,7 @@ namespace esphome { const Color Color::BLACK(0, 0, 0, 0); const Color Color::WHITE(255, 255, 255, 255); +const Color COLOR_BLACK(0, 0, 0, 0); +const Color COLOR_WHITE(255, 255, 255, 255); + } // namespace esphome diff --git a/esphome/core/color.h b/esphome/core/color.h index ba1a855e86..c9ca3bcfc3 100644 --- a/esphome/core/color.h +++ b/esphome/core/color.h @@ -149,8 +149,8 @@ struct Color { }; ESPDEPRECATED("Use Color::BLACK instead of COLOR_BLACK", "v1.21") -static const Color COLOR_BLACK(0, 0, 0, 0); +extern const Color COLOR_BLACK; ESPDEPRECATED("Use Color::WHITE instead of COLOR_WHITE", "v1.21") -static const Color COLOR_WHITE(255, 255, 255, 255); +extern const Color COLOR_WHITE; } // namespace esphome From cc15aaacbb471300c38bd4c9427251a96dd9a31d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Panella?= Date: Tue, 10 Aug 2021 02:55:34 -0500 Subject: [PATCH 1163/1841] RFC: status_led: allow to share single light (#1974) Co-authored-by: Otto winter --- .../components/status_led/light/__init__.py | 26 +++++++ .../status_led/light/status_led_light.cpp | 68 +++++++++++++++++++ .../status_led/light/status_led_light.h | 40 +++++++++++ 3 files changed, 134 insertions(+) create mode 100644 esphome/components/status_led/light/__init__.py create mode 100644 esphome/components/status_led/light/status_led_light.cpp create mode 100644 esphome/components/status_led/light/status_led_light.h diff --git a/esphome/components/status_led/light/__init__.py b/esphome/components/status_led/light/__init__.py new file mode 100644 index 0000000000..8896046998 --- /dev/null +++ b/esphome/components/status_led/light/__init__.py @@ -0,0 +1,26 @@ +from esphome import pins +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import light +from esphome.const import CONF_OUTPUT_ID, CONF_PIN +from .. import status_led_ns + +StatusLEDLightOutput = status_led_ns.class_( + "StatusLEDLightOutput", light.LightOutput, cg.Component +) + +CONFIG_SCHEMA = light.BINARY_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(StatusLEDLightOutput), + cv.Required(CONF_PIN): pins.gpio_output_pin_schema, + } +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + pin = await cg.gpio_pin_expression(config[CONF_PIN]) + cg.add(var.set_pin(pin)) + await cg.register_component(var, config) + # cg.add(cg.App.register_component(var)) + await light.register_light(var, config) diff --git a/esphome/components/status_led/light/status_led_light.cpp b/esphome/components/status_led/light/status_led_light.cpp new file mode 100644 index 0000000000..760c89f972 --- /dev/null +++ b/esphome/components/status_led/light/status_led_light.cpp @@ -0,0 +1,68 @@ +#include "status_led_light.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace status_led { + +static const char *const TAG = "status_led"; + +void StatusLEDLightOutput::loop() { + uint32_t new_state = App.get_app_state() & STATUS_LED_MASK; + + if (new_state != this->last_app_state_) { + ESP_LOGV(TAG, "New app state 0x%08X", new_state); + } + + if ((new_state & STATUS_LED_ERROR) != 0u) { + this->pin_->digital_write(millis() % 250u < 150u); + this->last_app_state_ = new_state; + } else if ((new_state & STATUS_LED_WARNING) != 0u) { + this->pin_->digital_write(millis() % 1500u < 250u); + this->last_app_state_ = new_state; + } else if (new_state != this->last_app_state_) { + // if no error/warning -> restore light state or turn off + bool state = false; + + if (lightstate_) + lightstate_->current_values_as_binary(&state); + + this->pin_->digital_write(state); + this->last_app_state_ = new_state; + + ESP_LOGD(TAG, "Restoring light state %s", ONOFF(state)); + } +} + +void StatusLEDLightOutput::setup_state(light::LightState *state) { + lightstate_ = state; + ESP_LOGD(TAG, "'%s': Setting initital state", state->get_name().c_str()); + this->write_state(state); +} + +void StatusLEDLightOutput::write_state(light::LightState *state) { + bool binary; + state->current_values_as_binary(&binary); + + // if in warning/error, don't overwrite the status_led + // once it is back to OK, the loop will restore the state + if ((App.get_app_state() & (STATUS_LED_ERROR | STATUS_LED_WARNING)) == 0u) { + this->pin_->digital_write(binary); + ESP_LOGD(TAG, "'%s': Setting state %s", state->get_name().c_str(), ONOFF(binary)); + } +} + +void StatusLEDLightOutput::setup() { + ESP_LOGCONFIG(TAG, "Setting up Status LED..."); + + this->pin_->setup(); + this->pin_->digital_write(false); +} + +void StatusLEDLightOutput::dump_config() { + ESP_LOGCONFIG(TAG, "Status Led Light:"); + LOG_PIN(" Pin: ", this->pin_); +} + +} // namespace status_led +} // namespace esphome diff --git a/esphome/components/status_led/light/status_led_light.h b/esphome/components/status_led/light/status_led_light.h new file mode 100644 index 0000000000..8a7f4b4da8 --- /dev/null +++ b/esphome/components/status_led/light/status_led_light.h @@ -0,0 +1,40 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/esphal.h" +#include "esphome/components/light/light_output.h" + +namespace esphome { +namespace status_led { + +class StatusLEDLightOutput : public light::LightOutput, public Component { + public: + void set_pin(GPIOPin *pin) { pin_ = pin; } + + light::LightTraits get_traits() override { + auto traits = light::LightTraits(); + traits.set_supported_color_modes({light::ColorMode::ON_OFF}); + return traits; + } + + void loop() override; + + void setup_state(light::LightState *state) override; + + void write_state(light::LightState *state) override; + + void setup() override; + + void dump_config() override; + + float get_setup_priority() const override { return setup_priority::HARDWARE; } + float get_loop_priority() const override { return 50.0f; } + + protected: + GPIOPin *pin_; + light::LightState *lightstate_{}; + uint32_t last_app_state_{0xFFFF}; +}; + +} // namespace status_led +} // namespace esphome From e5d0f3c0362d234561a8be10d90333a8afa97918 Mon Sep 17 00:00:00 2001 From: Trammell Hudson Date: Tue, 10 Aug 2021 10:09:46 +0200 Subject: [PATCH 1164/1841] waveshare_epaper: add support for ttgo t5 b74 variant display (#1869) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .../components/waveshare_epaper/display.py | 1 + .../waveshare_epaper/waveshare_epaper.cpp | 28 +++++++++++++++++++ .../waveshare_epaper/waveshare_epaper.h | 1 + 3 files changed, 30 insertions(+) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index a1a81e0997..5a40b92ce2 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -54,6 +54,7 @@ MODELS = { "2.13in-ttgo": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN), "2.13in-ttgo-b1": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B1), "2.13in-ttgo-b73": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B73), + "2.13in-ttgo-b74": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B74), "2.90in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN), "2.90inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN_V2), "2.70in": ("b", WaveshareEPaper2P7In), diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index cb1a158332..17d04b556b 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -163,6 +163,17 @@ void WaveshareEPaper::on_safe_shutdown() { this->deep_sleep(); } // ======================================================== void WaveshareEPaperTypeA::initialize() { + if (this->model_ == TTGO_EPAPER_2_13_IN_B74) { + this->reset_pin_->digital_write(false); + delay(10); + this->reset_pin_->digital_write(true); + delay(10); + this->wait_until_idle_(); + + this->command(0x12); // SWRESET + this->wait_until_idle_(); + } + // COMMAND DRIVER OUTPUT CONTROL this->command(0x01); this->data(this->get_height_internal() - 1); @@ -193,6 +204,7 @@ void WaveshareEPaperTypeA::initialize() { case TTGO_EPAPER_2_13_IN_B1: this->data(0x01); // x increase, y decrease : as in demo code break; + case TTGO_EPAPER_2_13_IN_B74: case WAVESHARE_EPAPER_2_9_IN_V2: this->data(0x03); // from top left to bottom right // RAM content option for Display Update @@ -222,6 +234,9 @@ void WaveshareEPaperTypeA::dump_config() { case TTGO_EPAPER_2_13_IN_B73: ESP_LOGCONFIG(TAG, " Model: 2.13in (TTGO B73)"); break; + case TTGO_EPAPER_2_13_IN_B74: + ESP_LOGCONFIG(TAG, " Model: 2.13in (TTGO B74)"); + break; case TTGO_EPAPER_2_13_IN_B1: ESP_LOGCONFIG(TAG, " Model: 2.13in (TTGO B1)"); break; @@ -256,6 +271,9 @@ void HOT WaveshareEPaperTypeA::display() { case TTGO_EPAPER_2_13_IN_B73: this->write_lut_(full_update ? FULL_UPDATE_LUT_TTGO_B73 : PARTIAL_UPDATE_LUT_TTGO_B73, LUT_SIZE_TTGO_B73); break; + case TTGO_EPAPER_2_13_IN_B74: + // there is no LUT + break; case TTGO_EPAPER_2_13_IN_B1: this->write_lut_(full_update ? FULL_UPDATE_LUT_TTGO_B1 : PARTIAL_UPDATE_LUT_TTGO_B1, LUT_SIZE_TTGO_B1); break; @@ -289,7 +307,12 @@ void HOT WaveshareEPaperTypeA::display() { this->data((this->get_height_internal() - 1) >> 8); break; + case TTGO_EPAPER_2_13_IN_B74: + // BorderWaveform + this->command(0x3C); + this->data(full_update ? 0x05 : 0x80); + // fall through default: // COMMAND SET RAM X ADDRESS START END POSITION this->command(0x44); @@ -341,6 +364,9 @@ void HOT WaveshareEPaperTypeA::display() { this->data(full_update ? 0xF7 : 0xFF); } else if (this->model_ == TTGO_EPAPER_2_13_IN_B73) { this->data(0xC7); + } else if (this->model_ == TTGO_EPAPER_2_13_IN_B74) { + // this->data(0xC7); + this->data(full_update ? 0xF7 : 0xFF); } else { this->data(0xC4); } @@ -363,6 +389,7 @@ int WaveshareEPaperTypeA::get_width_internal() { case TTGO_EPAPER_2_13_IN: return 128; case TTGO_EPAPER_2_13_IN_B73: + case TTGO_EPAPER_2_13_IN_B74: return 128; case TTGO_EPAPER_2_13_IN_B1: return 128; @@ -384,6 +411,7 @@ int WaveshareEPaperTypeA::get_height_internal() { case TTGO_EPAPER_2_13_IN: return 250; case TTGO_EPAPER_2_13_IN_B73: + case TTGO_EPAPER_2_13_IN_B74: return 250; case TTGO_EPAPER_2_13_IN_B1: return 250; diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 302238af7e..228753d1b7 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -73,6 +73,7 @@ enum WaveshareEPaperTypeAModel { TTGO_EPAPER_2_13_IN, TTGO_EPAPER_2_13_IN_B73, TTGO_EPAPER_2_13_IN_B1, + TTGO_EPAPER_2_13_IN_B74, }; class WaveshareEPaperTypeA : public WaveshareEPaper { From 98d32876b590950337e5a01792a4c00d78c33d97 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Tue, 10 Aug 2021 03:16:44 -0500 Subject: [PATCH 1165/1841] Thermostat enhancements 2 (#2114) --- esphome/components/thermostat/climate.py | 325 ++++++++- .../thermostat/thermostat_climate.cpp | 647 +++++++++++++++--- .../thermostat/thermostat_climate.h | 139 +++- esphome/const.py | 18 + tests/test3.yaml | 27 +- 5 files changed, 1019 insertions(+), 137 deletions(-) diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index 55fb534682..7b5ee7c624 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -24,18 +24,35 @@ from esphome.const import ( CONF_FAN_MODE_FOCUS_ACTION, CONF_FAN_MODE_DIFFUSE_ACTION, CONF_FAN_ONLY_ACTION, + CONF_FAN_ONLY_ACTION_USES_FAN_MODE_TIMER, CONF_FAN_ONLY_COOLING, CONF_FAN_ONLY_MODE, + CONF_FAN_WITH_COOLING, + CONF_FAN_WITH_HEATING, CONF_HEAT_ACTION, CONF_HEAT_DEADBAND, CONF_HEAT_MODE, CONF_HEAT_OVERRUN, - CONF_HYSTERESIS, CONF_ID, CONF_IDLE_ACTION, + CONF_MAX_COOLING_RUN_TIME, + CONF_MAX_HEATING_RUN_TIME, + CONF_MIN_COOLING_OFF_TIME, + CONF_MIN_COOLING_RUN_TIME, + CONF_MIN_FAN_MODE_SWITCHING_TIME, + CONF_MIN_FANNING_OFF_TIME, + CONF_MIN_FANNING_RUN_TIME, + CONF_MIN_HEATING_OFF_TIME, + CONF_MIN_HEATING_RUN_TIME, + CONF_MIN_IDLE_TIME, CONF_OFF_MODE, CONF_SENSOR, CONF_SET_POINT_MINIMUM_DIFFERENTIAL, + CONF_STARTUP_DELAY, + CONF_SUPPLEMENTAL_COOLING_ACTION, + CONF_SUPPLEMENTAL_COOLING_DELTA, + CONF_SUPPLEMENTAL_HEATING_ACTION, + CONF_SUPPLEMENTAL_HEATING_DELTA, CONF_SWING_BOTH_ACTION, CONF_SWING_HORIZONTAL_ACTION, CONF_SWING_OFF_ACTION, @@ -67,18 +84,141 @@ validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True) def validate_thermostat(config): - # verify corresponding climate action action exists for any defined climate mode action + # verify corresponding action(s) exist(s) for any defined climate mode or action requirements = { - CONF_AUTO_MODE: [CONF_COOL_ACTION, CONF_HEAT_ACTION], - CONF_COOL_MODE: [CONF_COOL_ACTION], - CONF_DRY_MODE: [CONF_DRY_ACTION], - CONF_FAN_ONLY_MODE: [CONF_FAN_ONLY_ACTION], - CONF_HEAT_MODE: [CONF_HEAT_ACTION], + CONF_AUTO_MODE: [ + CONF_COOL_ACTION, + CONF_HEAT_ACTION, + CONF_MIN_COOLING_OFF_TIME, + CONF_MIN_COOLING_RUN_TIME, + CONF_MIN_HEATING_OFF_TIME, + CONF_MIN_HEATING_RUN_TIME, + ], + CONF_COOL_MODE: [ + CONF_COOL_ACTION, + CONF_MIN_COOLING_OFF_TIME, + CONF_MIN_COOLING_RUN_TIME, + ], + CONF_DRY_MODE: [ + CONF_DRY_ACTION, + CONF_MIN_COOLING_OFF_TIME, + CONF_MIN_COOLING_RUN_TIME, + ], + CONF_FAN_ONLY_MODE: [ + CONF_FAN_ONLY_ACTION, + ], + CONF_HEAT_MODE: [ + CONF_HEAT_ACTION, + CONF_MIN_HEATING_OFF_TIME, + CONF_MIN_HEATING_RUN_TIME, + ], + CONF_COOL_ACTION: [ + CONF_MIN_COOLING_OFF_TIME, + CONF_MIN_COOLING_RUN_TIME, + ], + CONF_DRY_ACTION: [ + CONF_MIN_COOLING_OFF_TIME, + CONF_MIN_COOLING_RUN_TIME, + ], + CONF_HEAT_ACTION: [ + CONF_MIN_HEATING_OFF_TIME, + CONF_MIN_HEATING_RUN_TIME, + ], + CONF_SUPPLEMENTAL_COOLING_ACTION: [ + CONF_COOL_ACTION, + CONF_MAX_COOLING_RUN_TIME, + CONF_MIN_COOLING_OFF_TIME, + CONF_MIN_COOLING_RUN_TIME, + CONF_SUPPLEMENTAL_COOLING_DELTA, + ], + CONF_SUPPLEMENTAL_HEATING_ACTION: [ + CONF_HEAT_ACTION, + CONF_MAX_HEATING_RUN_TIME, + CONF_MIN_HEATING_OFF_TIME, + CONF_MIN_HEATING_RUN_TIME, + CONF_SUPPLEMENTAL_HEATING_DELTA, + ], + CONF_MAX_COOLING_RUN_TIME: [ + CONF_COOL_ACTION, + CONF_SUPPLEMENTAL_COOLING_ACTION, + CONF_SUPPLEMENTAL_COOLING_DELTA, + ], + CONF_MAX_HEATING_RUN_TIME: [ + CONF_HEAT_ACTION, + CONF_SUPPLEMENTAL_HEATING_ACTION, + CONF_SUPPLEMENTAL_HEATING_DELTA, + ], + CONF_MIN_COOLING_OFF_TIME: [ + CONF_COOL_ACTION, + ], + CONF_MIN_COOLING_RUN_TIME: [ + CONF_COOL_ACTION, + ], + CONF_MIN_FANNING_OFF_TIME: [ + CONF_FAN_ONLY_ACTION, + ], + CONF_MIN_FANNING_RUN_TIME: [ + CONF_FAN_ONLY_ACTION, + ], + CONF_MIN_HEATING_OFF_TIME: [ + CONF_HEAT_ACTION, + ], + CONF_MIN_HEATING_RUN_TIME: [ + CONF_HEAT_ACTION, + ], + CONF_SUPPLEMENTAL_COOLING_DELTA: [ + CONF_COOL_ACTION, + CONF_MAX_COOLING_RUN_TIME, + CONF_SUPPLEMENTAL_COOLING_ACTION, + ], + CONF_SUPPLEMENTAL_HEATING_DELTA: [ + CONF_HEAT_ACTION, + CONF_MAX_HEATING_RUN_TIME, + CONF_SUPPLEMENTAL_HEATING_ACTION, + ], } - for config_mode, req_actions in requirements.items(): - for req_action in req_actions: - if config_mode in config and req_action not in config: - raise cv.Invalid(f"{req_action} must be defined to use {config_mode}") + for config_trigger, req_triggers in requirements.items(): + for req_trigger in req_triggers: + if config_trigger in config and req_trigger not in config: + raise cv.Invalid( + f"{req_trigger} must be defined to use {config_trigger}" + ) + + if CONF_FAN_ONLY_ACTION in config: + # determine validation requirements based on fan_only_action_uses_fan_mode_timer setting + if config[CONF_FAN_ONLY_ACTION_USES_FAN_MODE_TIMER] is True: + requirements = [CONF_MIN_FAN_MODE_SWITCHING_TIME] + else: + requirements = [ + CONF_MIN_FANNING_OFF_TIME, + CONF_MIN_FANNING_RUN_TIME, + ] + for config_req_action in requirements: + if config_req_action not in config: + raise cv.Invalid( + f"{config_req_action} must be defined to use {CONF_FAN_ONLY_ACTION}" + ) + + # for any fan_mode action, confirm min_fan_mode_switching_time is defined + requirements = { + CONF_MIN_FAN_MODE_SWITCHING_TIME: [ + CONF_FAN_MODE_ON_ACTION, + CONF_FAN_MODE_OFF_ACTION, + CONF_FAN_MODE_AUTO_ACTION, + CONF_FAN_MODE_LOW_ACTION, + CONF_FAN_MODE_MEDIUM_ACTION, + CONF_FAN_MODE_HIGH_ACTION, + CONF_FAN_MODE_MIDDLE_ACTION, + CONF_FAN_MODE_FOCUS_ACTION, + CONF_FAN_MODE_DIFFUSE_ACTION, + ], + } + for req_config_item, config_triggers in requirements.items(): + for config_trigger in config_triggers: + if config_trigger in config and req_config_item not in config: + raise cv.Invalid( + f"{req_config_item} must be defined to use {config_trigger}" + ) # determine validation requirements based on fan_only_cooling setting if config[CONF_FAN_ONLY_COOLING] is True: @@ -137,6 +277,34 @@ def validate_thermostat(config): f"{CONF_DEFAULT_MODE} is set to {default_mode} but {req} is not present in the configuration" ) + if config[CONF_FAN_WITH_COOLING] is True and CONF_FAN_ONLY_ACTION not in config: + raise cv.Invalid( + f"{CONF_FAN_ONLY_ACTION} must be defined to use {CONF_FAN_WITH_COOLING}" + ) + if config[CONF_FAN_WITH_HEATING] is True and CONF_FAN_ONLY_ACTION not in config: + raise cv.Invalid( + f"{CONF_FAN_ONLY_ACTION} must be defined to use {CONF_FAN_WITH_HEATING}" + ) + + # if min_fan_mode_switching_time is defined, at least one fan_mode action should be defined + if CONF_MIN_FAN_MODE_SWITCHING_TIME in config: + requirements = [ + CONF_FAN_MODE_ON_ACTION, + CONF_FAN_MODE_OFF_ACTION, + CONF_FAN_MODE_AUTO_ACTION, + CONF_FAN_MODE_LOW_ACTION, + CONF_FAN_MODE_MEDIUM_ACTION, + CONF_FAN_MODE_HIGH_ACTION, + CONF_FAN_MODE_MIDDLE_ACTION, + CONF_FAN_MODE_FOCUS_ACTION, + CONF_FAN_MODE_DIFFUSE_ACTION, + ] + for config_req_action in requirements: + if config_req_action in config: + return config + raise cv.Invalid( + f"At least one of {CONF_FAN_MODE_ON_ACTION}, {CONF_FAN_MODE_OFF_ACTION}, {CONF_FAN_MODE_AUTO_ACTION}, {CONF_FAN_MODE_LOW_ACTION}, {CONF_FAN_MODE_MEDIUM_ACTION}, {CONF_FAN_MODE_HIGH_ACTION}, {CONF_FAN_MODE_MIDDLE_ACTION}, {CONF_FAN_MODE_FOCUS_ACTION}, {CONF_FAN_MODE_DIFFUSE_ACTION} must be defined to use {CONF_MIN_FAN_MODE_SWITCHING_TIME}" + ) return config @@ -147,11 +315,17 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_COOL_ACTION): automation.validate_automation(single=True), + cv.Optional( + CONF_SUPPLEMENTAL_COOLING_ACTION + ): automation.validate_automation(single=True), cv.Optional(CONF_DRY_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_FAN_ONLY_ACTION): automation.validate_automation( single=True ), cv.Optional(CONF_HEAT_ACTION): automation.validate_automation(single=True), + cv.Optional( + CONF_SUPPLEMENTAL_HEATING_ACTION + ): automation.validate_automation(single=True), cv.Optional(CONF_AUTO_MODE): automation.validate_automation(single=True), cv.Optional(CONF_COOL_MODE): automation.validate_automation(single=True), cv.Optional(CONF_DRY_MODE): automation.validate_automation(single=True), @@ -210,12 +384,31 @@ CONFIG_SCHEMA = cv.All( cv.Optional( CONF_SET_POINT_MINIMUM_DIFFERENTIAL, default=0.5 ): cv.temperature, - cv.Optional(CONF_COOL_DEADBAND): cv.temperature, - cv.Optional(CONF_COOL_OVERRUN): cv.temperature, - cv.Optional(CONF_HEAT_DEADBAND): cv.temperature, - cv.Optional(CONF_HEAT_OVERRUN): cv.temperature, - cv.Optional(CONF_HYSTERESIS, default=0.5): cv.temperature, + cv.Optional(CONF_COOL_DEADBAND, default=0.5): cv.temperature, + cv.Optional(CONF_COOL_OVERRUN, default=0.5): cv.temperature, + cv.Optional(CONF_HEAT_DEADBAND, default=0.5): cv.temperature, + cv.Optional(CONF_HEAT_OVERRUN, default=0.5): cv.temperature, + cv.Optional(CONF_MAX_COOLING_RUN_TIME): cv.positive_time_period_seconds, + cv.Optional(CONF_MAX_HEATING_RUN_TIME): cv.positive_time_period_seconds, + cv.Optional(CONF_MIN_COOLING_OFF_TIME): cv.positive_time_period_seconds, + cv.Optional(CONF_MIN_COOLING_RUN_TIME): cv.positive_time_period_seconds, + cv.Optional( + CONF_MIN_FAN_MODE_SWITCHING_TIME + ): cv.positive_time_period_seconds, + cv.Optional(CONF_MIN_FANNING_OFF_TIME): cv.positive_time_period_seconds, + cv.Optional(CONF_MIN_FANNING_RUN_TIME): cv.positive_time_period_seconds, + cv.Optional(CONF_MIN_HEATING_OFF_TIME): cv.positive_time_period_seconds, + cv.Optional(CONF_MIN_HEATING_RUN_TIME): cv.positive_time_period_seconds, + cv.Required(CONF_MIN_IDLE_TIME): cv.positive_time_period_seconds, + cv.Optional(CONF_SUPPLEMENTAL_COOLING_DELTA): cv.temperature, + cv.Optional(CONF_SUPPLEMENTAL_HEATING_DELTA): cv.temperature, + cv.Optional( + CONF_FAN_ONLY_ACTION_USES_FAN_MODE_TIMER, default=False + ): cv.boolean, cv.Optional(CONF_FAN_ONLY_COOLING, default=False): cv.boolean, + cv.Optional(CONF_FAN_WITH_COOLING, default=False): cv.boolean, + cv.Optional(CONF_FAN_WITH_HEATING, default=False): cv.boolean, + cv.Optional(CONF_STARTUP_DELAY, default=False): cv.boolean, cv.Optional(CONF_AWAY_CONFIG): cv.Schema( { cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, @@ -250,25 +443,10 @@ async def to_code(config): ) cg.add(var.set_sensor(sens)) - if CONF_COOL_DEADBAND in config: - cg.add(var.set_cool_deadband(config[CONF_COOL_DEADBAND])) - else: - cg.add(var.set_cool_deadband(config[CONF_HYSTERESIS])) - - if CONF_COOL_OVERRUN in config: - cg.add(var.set_cool_overrun(config[CONF_COOL_OVERRUN])) - else: - cg.add(var.set_cool_overrun(config[CONF_HYSTERESIS])) - - if CONF_HEAT_DEADBAND in config: - cg.add(var.set_heat_deadband(config[CONF_HEAT_DEADBAND])) - else: - cg.add(var.set_heat_deadband(config[CONF_HYSTERESIS])) - - if CONF_HEAT_OVERRUN in config: - cg.add(var.set_heat_overrun(config[CONF_HEAT_OVERRUN])) - else: - cg.add(var.set_heat_overrun(config[CONF_HYSTERESIS])) + cg.add(var.set_cool_deadband(config[CONF_COOL_DEADBAND])) + cg.add(var.set_cool_overrun(config[CONF_COOL_OVERRUN])) + cg.add(var.set_heat_deadband(config[CONF_HEAT_DEADBAND])) + cg.add(var.set_heat_overrun(config[CONF_HEAT_OVERRUN])) if two_points_available is True: cg.add(var.set_supports_two_points(True)) @@ -286,7 +464,72 @@ async def to_code(config): normal_config = ThermostatClimateTargetTempConfig( config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW] ) + + if CONF_MAX_COOLING_RUN_TIME in config: + cg.add( + var.set_cooling_maximum_run_time_in_sec(config[CONF_MAX_COOLING_RUN_TIME]) + ) + + if CONF_MAX_HEATING_RUN_TIME in config: + cg.add( + var.set_heating_maximum_run_time_in_sec(config[CONF_MAX_HEATING_RUN_TIME]) + ) + + if CONF_MIN_COOLING_OFF_TIME in config: + cg.add( + var.set_cooling_minimum_off_time_in_sec(config[CONF_MIN_COOLING_OFF_TIME]) + ) + + if CONF_MIN_COOLING_RUN_TIME in config: + cg.add( + var.set_cooling_minimum_run_time_in_sec(config[CONF_MIN_COOLING_RUN_TIME]) + ) + + if CONF_MIN_FAN_MODE_SWITCHING_TIME in config: + cg.add( + var.set_fan_mode_minimum_switching_time_in_sec( + config[CONF_MIN_FAN_MODE_SWITCHING_TIME] + ) + ) + + if CONF_MIN_FANNING_OFF_TIME in config: + cg.add( + var.set_fanning_minimum_off_time_in_sec(config[CONF_MIN_FANNING_OFF_TIME]) + ) + + if CONF_MIN_FANNING_RUN_TIME in config: + cg.add( + var.set_fanning_minimum_run_time_in_sec(config[CONF_MIN_FANNING_RUN_TIME]) + ) + + if CONF_MIN_HEATING_OFF_TIME in config: + cg.add( + var.set_heating_minimum_off_time_in_sec(config[CONF_MIN_HEATING_OFF_TIME]) + ) + + if CONF_MIN_HEATING_RUN_TIME in config: + cg.add( + var.set_heating_minimum_run_time_in_sec(config[CONF_MIN_HEATING_RUN_TIME]) + ) + + if CONF_SUPPLEMENTAL_COOLING_DELTA in config: + cg.add(var.set_supplemental_cool_delta(config[CONF_SUPPLEMENTAL_COOLING_DELTA])) + + if CONF_SUPPLEMENTAL_HEATING_DELTA in config: + cg.add(var.set_supplemental_heat_delta(config[CONF_SUPPLEMENTAL_HEATING_DELTA])) + + cg.add(var.set_idle_minimum_time_in_sec(config[CONF_MIN_IDLE_TIME])) + + cg.add( + var.set_supports_fan_only_action_uses_fan_mode_timer( + config[CONF_FAN_ONLY_ACTION_USES_FAN_MODE_TIMER] + ) + ) cg.add(var.set_supports_fan_only_cooling(config[CONF_FAN_ONLY_COOLING])) + cg.add(var.set_supports_fan_with_cooling(config[CONF_FAN_WITH_COOLING])) + cg.add(var.set_supports_fan_with_heating(config[CONF_FAN_WITH_HEATING])) + + cg.add(var.set_use_startup_delay(config[CONF_STARTUP_DELAY])) cg.add(var.set_normal_config(normal_config)) await automation.build_automation( @@ -303,6 +546,12 @@ async def to_code(config): var.get_cool_action_trigger(), [], config[CONF_COOL_ACTION] ) cg.add(var.set_supports_cool(True)) + if CONF_SUPPLEMENTAL_COOLING_ACTION in config: + await automation.build_automation( + var.get_supplemental_cool_action_trigger(), + [], + config[CONF_SUPPLEMENTAL_COOLING_ACTION], + ) if CONF_DRY_ACTION in config: await automation.build_automation( var.get_dry_action_trigger(), [], config[CONF_DRY_ACTION] @@ -318,6 +567,12 @@ async def to_code(config): var.get_heat_action_trigger(), [], config[CONF_HEAT_ACTION] ) cg.add(var.set_supports_heat(True)) + if CONF_SUPPLEMENTAL_HEATING_ACTION in config: + await automation.build_automation( + var.get_supplemental_heat_action_trigger(), + [], + config[CONF_SUPPLEMENTAL_HEATING_ACTION], + ) if CONF_AUTO_MODE in config: await automation.build_automation( var.get_auto_mode_trigger(), [], config[CONF_AUTO_MODE] diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 1176a75514..d9d8b106ea 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -7,11 +7,20 @@ namespace thermostat { static const char *const TAG = "thermostat.climate"; void ThermostatClimate::setup() { + if (this->use_startup_delay_) { + // start timers so that no actions are called for a moment + this->start_timer_(thermostat::TIMER_COOLING_OFF); + this->start_timer_(thermostat::TIMER_FANNING_OFF); + this->start_timer_(thermostat::TIMER_HEATING_OFF); + if (this->supports_fan_only_action_uses_fan_mode_timer_) + this->start_timer_(thermostat::TIMER_FAN_MODE); + } // add a callback so that whenever the sensor state changes we can take action this->sensor_->add_on_state_callback([this](float state) { this->current_temperature = state; // required action may have changed, recompute, refresh - this->switch_to_action_(compute_action_()); + this->switch_to_action_(this->compute_action_()); + this->switch_to_supplemental_action_(this->compute_supplemental_action_()); // current temperature and possibly action changed, so publish the new state this->publish_state(); }); @@ -26,31 +35,58 @@ void ThermostatClimate::setup() { this->change_away_(false); } // refresh the climate action based on the restored settings - this->switch_to_action_(compute_action_()); + this->switch_to_action_(this->compute_action_()); + this->switch_to_supplemental_action_(this->compute_supplemental_action_()); this->setup_complete_ = true; this->publish_state(); } -float ThermostatClimate::cool_deadband() { return this->cool_deadband_; } -float ThermostatClimate::cool_overrun() { return this->cool_overrun_; } -float ThermostatClimate::heat_deadband() { return this->heat_deadband_; } -float ThermostatClimate::heat_overrun() { return this->heat_overrun_; } +float ThermostatClimate::cool_deadband() { return this->cooling_deadband_; } +float ThermostatClimate::cool_overrun() { return this->cooling_overrun_; } +float ThermostatClimate::heat_deadband() { return this->heating_deadband_; } +float ThermostatClimate::heat_overrun() { return this->heating_overrun_; } void ThermostatClimate::refresh() { this->switch_to_mode_(this->mode); - this->switch_to_action_(compute_action_()); + this->switch_to_action_(this->compute_action_()); + this->switch_to_supplemental_action_(this->compute_supplemental_action_()); this->switch_to_fan_mode_(this->fan_mode.value()); this->switch_to_swing_mode_(this->swing_mode); this->check_temperature_change_trigger_(); this->publish_state(); } +bool ThermostatClimate::climate_action_change_delayed() { + switch (this->compute_action_(true)) { + case climate::CLIMATE_ACTION_OFF: + case climate::CLIMATE_ACTION_IDLE: + return !this->idle_action_ready_(); + case climate::CLIMATE_ACTION_COOLING: + return !this->cooling_action_ready_(); + case climate::CLIMATE_ACTION_HEATING: + return !this->heating_action_ready_(); + case climate::CLIMATE_ACTION_FAN: + return !this->fanning_action_ready_(); + case climate::CLIMATE_ACTION_DRYING: + return !this->drying_action_ready_(); + default: + break; + } + return false; +} + +bool ThermostatClimate::fan_mode_change_delayed() { return !this->fan_mode_ready_(); } + +climate::ClimateAction ThermostatClimate::delayed_climate_action() { return this->compute_action_(true); } + +climate::ClimateFanMode ThermostatClimate::delayed_fan_mode() { return this->desired_fan_mode_; } + bool ThermostatClimate::hysteresis_valid() { if ((this->supports_cool_ || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) && - (isnan(this->cool_deadband_) || isnan(this->cool_overrun_))) + (isnan(this->cooling_deadband_) || isnan(this->cooling_overrun_))) return false; - if (this->supports_heat_ && (isnan(this->heat_deadband_) || isnan(this->heat_overrun_))) + if (this->supports_heat_ && (isnan(this->heating_deadband_) || isnan(this->heating_overrun_))) return false; return true; @@ -72,10 +108,10 @@ void ThermostatClimate::validate_target_temperature() { void ThermostatClimate::validate_target_temperatures() { if (this->supports_two_points_) { - validate_target_temperature_low(); - validate_target_temperature_high(); + this->validate_target_temperature_low(); + this->validate_target_temperature_high(); } else { - validate_target_temperature(); + this->validate_target_temperature(); } } @@ -201,12 +237,19 @@ climate::ClimateTraits ThermostatClimate::traits() { return traits; } -climate::ClimateAction ThermostatClimate::compute_action_() { +climate::ClimateAction ThermostatClimate::compute_action_(const bool ignore_timers) { auto target_action = climate::CLIMATE_ACTION_IDLE; // if any hysteresis values or current_temperature is not valid, we go to OFF; if (isnan(this->current_temperature) || !this->hysteresis_valid()) { return climate::CLIMATE_ACTION_OFF; } + // do not change the action if an "ON" timer is running + if ((!ignore_timers) && + (timer_active_(thermostat::TIMER_IDLE_ON) || timer_active_(thermostat::TIMER_COOLING_ON) || + timer_active_(thermostat::TIMER_FANNING_ON) || timer_active_(thermostat::TIMER_HEATING_ON))) { + return this->action; + } + // ensure set point(s) is/are valid before computing the action this->validate_target_temperatures(); // everything has been validated so we can now safely compute the action @@ -245,6 +288,56 @@ climate::ClimateAction ThermostatClimate::compute_action_() { default: break; } + // do not abruptly switch actions. cycle through IDLE, first. we'll catch this at the next update. + if ((((this->action == climate::CLIMATE_ACTION_COOLING) || (this->action == climate::CLIMATE_ACTION_DRYING)) && + (target_action == climate::CLIMATE_ACTION_HEATING)) || + ((this->action == climate::CLIMATE_ACTION_HEATING) && + ((target_action == climate::CLIMATE_ACTION_COOLING) || (target_action == climate::CLIMATE_ACTION_DRYING)))) { + return climate::CLIMATE_ACTION_IDLE; + } + + return target_action; +} + +climate::ClimateAction ThermostatClimate::compute_supplemental_action_() { + auto target_action = climate::CLIMATE_ACTION_IDLE; + // if any hysteresis values or current_temperature is not valid, we go to OFF; + if (isnan(this->current_temperature) || !this->hysteresis_valid()) { + return climate::CLIMATE_ACTION_OFF; + } + + // ensure set point(s) is/are valid before computing the action + this->validate_target_temperatures(); + // everything has been validated so we can now safely compute the action + switch (this->mode) { + // if the climate mode is OFF then the climate action must be OFF + case climate::CLIMATE_MODE_OFF: + target_action = climate::CLIMATE_ACTION_OFF; + break; + case climate::CLIMATE_MODE_HEAT_COOL: + if (this->supplemental_cooling_required_() && this->supplemental_heating_required_()) { + // this is bad and should never happen, so just stop. + // target_action = climate::CLIMATE_ACTION_IDLE; + } else if (this->supplemental_cooling_required_()) { + target_action = climate::CLIMATE_ACTION_COOLING; + } else if (this->supplemental_heating_required_()) { + target_action = climate::CLIMATE_ACTION_HEATING; + } + break; + case climate::CLIMATE_MODE_COOL: + if (this->supplemental_cooling_required_()) { + target_action = climate::CLIMATE_ACTION_COOLING; + } + break; + case climate::CLIMATE_MODE_HEAT: + if (this->supplemental_heating_required_()) { + target_action = climate::CLIMATE_ACTION_HEATING; + } + break; + default: + break; + } + return target_action; } @@ -257,34 +350,81 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action) { if (((action == climate::CLIMATE_ACTION_OFF && this->action == climate::CLIMATE_ACTION_IDLE) || (action == climate::CLIMATE_ACTION_IDLE && this->action == climate::CLIMATE_ACTION_OFF)) && this->setup_complete_) { - // switching from OFF to IDLE or vice-versa - // these only have visual difference. OFF means user manually disabled, - // IDLE means it's in auto mode but value is in target range. + // switching from OFF to IDLE or vice-versa -- this is only a visual difference. + // OFF means user manually disabled, IDLE means the temperature is in target range. this->action = action; return; } - if (this->prev_action_trigger_ != nullptr) { - this->prev_action_trigger_->stop_action(); - this->prev_action_trigger_ = nullptr; - } - Trigger<> *trig = this->idle_action_trigger_; + bool action_ready = false; + Trigger<> *trig = this->idle_action_trigger_, *trig_fan = nullptr; switch (action) { case climate::CLIMATE_ACTION_OFF: case climate::CLIMATE_ACTION_IDLE: - // trig = this->idle_action_trigger_; + if (this->idle_action_ready_()) { + this->start_timer_(thermostat::TIMER_IDLE_ON); + if (this->action == climate::CLIMATE_ACTION_COOLING) + this->start_timer_(thermostat::TIMER_COOLING_OFF); + if (this->action == climate::CLIMATE_ACTION_FAN) { + if (this->supports_fan_only_action_uses_fan_mode_timer_) + this->start_timer_(thermostat::TIMER_FAN_MODE); + else + this->start_timer_(thermostat::TIMER_FANNING_OFF); + } + if (this->action == climate::CLIMATE_ACTION_HEATING) + this->start_timer_(thermostat::TIMER_HEATING_OFF); + // trig = this->idle_action_trigger_; + ESP_LOGVV(TAG, "Switching to IDLE/OFF action"); + this->cooling_max_runtime_exceeded_ = false; + this->heating_max_runtime_exceeded_ = false; + action_ready = true; + } break; case climate::CLIMATE_ACTION_COOLING: - trig = this->cool_action_trigger_; + if (this->cooling_action_ready_()) { + this->start_timer_(thermostat::TIMER_COOLING_ON); + this->start_timer_(thermostat::TIMER_COOLING_MAX_RUN_TIME); + if (this->supports_fan_with_cooling_) { + this->start_timer_(thermostat::TIMER_FANNING_ON); + trig_fan = this->fan_only_action_trigger_; + } + trig = this->cool_action_trigger_; + ESP_LOGVV(TAG, "Switching to COOLING action"); + action_ready = true; + } break; case climate::CLIMATE_ACTION_HEATING: - trig = this->heat_action_trigger_; + if (this->heating_action_ready_()) { + this->start_timer_(thermostat::TIMER_HEATING_ON); + this->start_timer_(thermostat::TIMER_HEATING_MAX_RUN_TIME); + if (this->supports_fan_with_heating_) { + this->start_timer_(thermostat::TIMER_FANNING_ON); + trig_fan = this->fan_only_action_trigger_; + } + trig = this->heat_action_trigger_; + ESP_LOGVV(TAG, "Switching to HEATING action"); + action_ready = true; + } break; case climate::CLIMATE_ACTION_FAN: - trig = this->fan_only_action_trigger_; + if (this->fanning_action_ready_()) { + if (this->supports_fan_only_action_uses_fan_mode_timer_) + this->start_timer_(thermostat::TIMER_FAN_MODE); + else + this->start_timer_(thermostat::TIMER_FANNING_ON); + trig = this->fan_only_action_trigger_; + ESP_LOGVV(TAG, "Switching to FAN_ONLY action"); + action_ready = true; + } break; case climate::CLIMATE_ACTION_DRYING: - trig = this->dry_action_trigger_; + if (this->drying_action_ready_()) { + this->start_timer_(thermostat::TIMER_COOLING_ON); + this->start_timer_(thermostat::TIMER_FANNING_ON); + trig = this->dry_action_trigger_; + ESP_LOGVV(TAG, "Switching to DRYING action"); + action_ready = true; + } break; default: // we cannot report an invalid mode back to HA (even if it asked for one) @@ -292,10 +432,76 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action) { action = climate::CLIMATE_ACTION_OFF; // trig = this->idle_action_trigger_; } - assert(trig != nullptr); - trig->trigger(); - this->action = action; - this->prev_action_trigger_ = trig; + + if (action_ready) { + if (this->prev_action_trigger_ != nullptr) { + this->prev_action_trigger_->stop_action(); + this->prev_action_trigger_ = nullptr; + } + this->action = action; + this->prev_action_trigger_ = trig; + assert(trig != nullptr); + trig->trigger(); + // if enabled, call the fan_only action with cooling/heating actions + if (trig_fan != nullptr) { + ESP_LOGVV(TAG, "Calling FAN_ONLY action with HEATING/COOLING action"); + trig_fan->trigger(); + } + } +} + +void ThermostatClimate::switch_to_supplemental_action_(climate::ClimateAction action) { + // setup_complete_ helps us ensure an action is called immediately after boot + if ((action == this->supplemental_action_) && this->setup_complete_) + // already in target mode + return; + + switch (action) { + case climate::CLIMATE_ACTION_OFF: + case climate::CLIMATE_ACTION_IDLE: + this->cancel_timer_(thermostat::TIMER_COOLING_MAX_RUN_TIME); + this->cancel_timer_(thermostat::TIMER_HEATING_MAX_RUN_TIME); + break; + case climate::CLIMATE_ACTION_COOLING: + this->cancel_timer_(thermostat::TIMER_COOLING_MAX_RUN_TIME); + break; + case climate::CLIMATE_ACTION_HEATING: + this->cancel_timer_(thermostat::TIMER_HEATING_MAX_RUN_TIME); + break; + default: + return; + } + ESP_LOGVV(TAG, "Updating supplemental action..."); + this->supplemental_action_ = action; + this->trigger_supplemental_action_(); +} + +void ThermostatClimate::trigger_supplemental_action_() { + Trigger<> *trig = nullptr; + + switch (this->supplemental_action_) { + case climate::CLIMATE_ACTION_COOLING: + if (!this->timer_active_(thermostat::TIMER_COOLING_MAX_RUN_TIME)) { + this->start_timer_(thermostat::TIMER_COOLING_MAX_RUN_TIME); + } + trig = this->supplemental_cool_action_trigger_; + ESP_LOGVV(TAG, "Calling supplemental COOLING action"); + break; + case climate::CLIMATE_ACTION_HEATING: + if (!this->timer_active_(thermostat::TIMER_HEATING_MAX_RUN_TIME)) { + this->start_timer_(thermostat::TIMER_HEATING_MAX_RUN_TIME); + } + trig = this->supplemental_heat_action_trigger_; + ESP_LOGVV(TAG, "Calling supplemental HEATING action"); + break; + default: + break; + } + + if (trig != nullptr) { + assert(trig != nullptr); + trig->trigger(); + } } void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { @@ -304,50 +510,64 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { // already in target mode return; - if (this->prev_fan_mode_trigger_ != nullptr) { - this->prev_fan_mode_trigger_->stop_action(); - this->prev_fan_mode_trigger_ = nullptr; + this->desired_fan_mode_ = fan_mode; // needed for timer callback + + if (this->fan_mode_ready_()) { + Trigger<> *trig = this->fan_mode_auto_trigger_; + switch (fan_mode) { + case climate::CLIMATE_FAN_ON: + trig = this->fan_mode_on_trigger_; + ESP_LOGVV(TAG, "Switching to FAN_ON mode"); + break; + case climate::CLIMATE_FAN_OFF: + trig = this->fan_mode_off_trigger_; + ESP_LOGVV(TAG, "Switching to FAN_OFF mode"); + break; + case climate::CLIMATE_FAN_AUTO: + // trig = this->fan_mode_auto_trigger_; + ESP_LOGVV(TAG, "Switching to FAN_AUTO mode"); + break; + case climate::CLIMATE_FAN_LOW: + trig = this->fan_mode_low_trigger_; + ESP_LOGVV(TAG, "Switching to FAN_LOW mode"); + break; + case climate::CLIMATE_FAN_MEDIUM: + trig = this->fan_mode_medium_trigger_; + ESP_LOGVV(TAG, "Switching to FAN_MEDIUM mode"); + break; + case climate::CLIMATE_FAN_HIGH: + trig = this->fan_mode_high_trigger_; + ESP_LOGVV(TAG, "Switching to FAN_HIGH mode"); + break; + case climate::CLIMATE_FAN_MIDDLE: + trig = this->fan_mode_middle_trigger_; + ESP_LOGVV(TAG, "Switching to FAN_MIDDLE mode"); + break; + case climate::CLIMATE_FAN_FOCUS: + trig = this->fan_mode_focus_trigger_; + ESP_LOGVV(TAG, "Switching to FAN_FOCUS mode"); + break; + case climate::CLIMATE_FAN_DIFFUSE: + trig = this->fan_mode_diffuse_trigger_; + ESP_LOGVV(TAG, "Switching to FAN_DIFFUSE mode"); + break; + default: + // we cannot report an invalid mode back to HA (even if it asked for one) + // and must assume some valid value + fan_mode = climate::CLIMATE_FAN_AUTO; + // trig = this->fan_mode_auto_trigger_; + } + if (this->prev_fan_mode_trigger_ != nullptr) { + this->prev_fan_mode_trigger_->stop_action(); + this->prev_fan_mode_trigger_ = nullptr; + } + this->start_timer_(thermostat::TIMER_FAN_MODE); + assert(trig != nullptr); + trig->trigger(); + this->fan_mode = fan_mode; + this->prev_fan_mode_ = fan_mode; + this->prev_fan_mode_trigger_ = trig; } - Trigger<> *trig = this->fan_mode_auto_trigger_; - switch (fan_mode) { - case climate::CLIMATE_FAN_ON: - trig = this->fan_mode_on_trigger_; - break; - case climate::CLIMATE_FAN_OFF: - trig = this->fan_mode_off_trigger_; - break; - case climate::CLIMATE_FAN_AUTO: - // trig = this->fan_mode_auto_trigger_; - break; - case climate::CLIMATE_FAN_LOW: - trig = this->fan_mode_low_trigger_; - break; - case climate::CLIMATE_FAN_MEDIUM: - trig = this->fan_mode_medium_trigger_; - break; - case climate::CLIMATE_FAN_HIGH: - trig = this->fan_mode_high_trigger_; - break; - case climate::CLIMATE_FAN_MIDDLE: - trig = this->fan_mode_middle_trigger_; - break; - case climate::CLIMATE_FAN_FOCUS: - trig = this->fan_mode_focus_trigger_; - break; - case climate::CLIMATE_FAN_DIFFUSE: - trig = this->fan_mode_diffuse_trigger_; - break; - default: - // we cannot report an invalid mode back to HA (even if it asked for one) - // and must assume some valid value - fan_mode = climate::CLIMATE_FAN_AUTO; - // trig = this->fan_mode_auto_trigger_; - } - assert(trig != nullptr); - trig->trigger(); - this->fan_mode = fan_mode; - this->prev_fan_mode_ = fan_mode; - this->prev_fan_mode_trigger_ = trig; } void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) { @@ -430,6 +650,135 @@ void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mo this->prev_swing_mode_trigger_ = trig; } +bool ThermostatClimate::idle_action_ready_() { + if (this->supports_fan_only_action_uses_fan_mode_timer_) { + return !(this->timer_active_(thermostat::TIMER_COOLING_ON) || this->timer_active_(thermostat::TIMER_FAN_MODE) || + this->timer_active_(thermostat::TIMER_HEATING_ON)); + } + return !(this->timer_active_(thermostat::TIMER_COOLING_ON) || this->timer_active_(thermostat::TIMER_FANNING_ON) || + this->timer_active_(thermostat::TIMER_HEATING_ON)); +} + +bool ThermostatClimate::cooling_action_ready_() { + return !(this->timer_active_(thermostat::TIMER_IDLE_ON) || this->timer_active_(thermostat::TIMER_FANNING_OFF) || + this->timer_active_(thermostat::TIMER_COOLING_OFF) || this->timer_active_(thermostat::TIMER_HEATING_ON)); +} + +bool ThermostatClimate::drying_action_ready_() { + return !(this->timer_active_(thermostat::TIMER_IDLE_ON) || this->timer_active_(thermostat::TIMER_FANNING_OFF) || + this->timer_active_(thermostat::TIMER_COOLING_OFF) || this->timer_active_(thermostat::TIMER_HEATING_ON)); +} + +bool ThermostatClimate::fan_mode_ready_() { return !(this->timer_active_(thermostat::TIMER_FAN_MODE)); } + +bool ThermostatClimate::fanning_action_ready_() { + if (this->supports_fan_only_action_uses_fan_mode_timer_) { + return !(this->timer_active_(thermostat::TIMER_FAN_MODE)); + } + return !(this->timer_active_(thermostat::TIMER_IDLE_ON) || this->timer_active_(thermostat::TIMER_FANNING_OFF)); +} + +bool ThermostatClimate::heating_action_ready_() { + return !(this->timer_active_(thermostat::TIMER_IDLE_ON) || this->timer_active_(thermostat::TIMER_COOLING_ON) || + this->timer_active_(thermostat::TIMER_FANNING_OFF) || this->timer_active_(thermostat::TIMER_HEATING_OFF)); +} + +void ThermostatClimate::start_timer_(const ThermostatClimateTimerIndex timer_index) { + if (this->timer_duration_(timer_index) > 0) { + this->set_timeout(this->timer_[timer_index].name, this->timer_duration_(timer_index), + this->timer_cbf_(timer_index)); + this->timer_[timer_index].active = true; + } +} + +bool ThermostatClimate::cancel_timer_(ThermostatClimateTimerIndex timer_index) { + this->timer_[timer_index].active = false; + return this->cancel_timeout(this->timer_[timer_index].name); +} + +bool ThermostatClimate::timer_active_(ThermostatClimateTimerIndex timer_index) { + return this->timer_[timer_index].active; +} + +uint32_t ThermostatClimate::timer_duration_(ThermostatClimateTimerIndex timer_index) { + return this->timer_[timer_index].time; +} + +std::function ThermostatClimate::timer_cbf_(ThermostatClimateTimerIndex timer_index) { + return this->timer_[timer_index].func; +} + +void ThermostatClimate::cooling_max_run_time_timer_callback_() { + ESP_LOGVV(TAG, "cooling_max_run_time timer expired"); + this->timer_[thermostat::TIMER_COOLING_MAX_RUN_TIME].active = false; + this->cooling_max_runtime_exceeded_ = true; + this->trigger_supplemental_action_(); + this->switch_to_supplemental_action_(this->compute_supplemental_action_()); +} + +void ThermostatClimate::cooling_off_timer_callback_() { + ESP_LOGVV(TAG, "cooling_off timer expired"); + this->timer_[thermostat::TIMER_COOLING_OFF].active = false; + this->switch_to_action_(this->compute_action_()); + this->switch_to_supplemental_action_(this->compute_supplemental_action_()); +} + +void ThermostatClimate::cooling_on_timer_callback_() { + ESP_LOGVV(TAG, "cooling_on timer expired"); + this->timer_[thermostat::TIMER_COOLING_ON].active = false; + this->switch_to_action_(this->compute_action_()); + this->switch_to_supplemental_action_(this->compute_supplemental_action_()); +} + +void ThermostatClimate::fan_mode_timer_callback_() { + ESP_LOGVV(TAG, "fan_mode timer expired"); + this->timer_[thermostat::TIMER_FAN_MODE].active = false; + this->switch_to_fan_mode_(this->desired_fan_mode_); + if (this->supports_fan_only_action_uses_fan_mode_timer_) + this->switch_to_action_(this->compute_action_()); +} + +void ThermostatClimate::fanning_off_timer_callback_() { + ESP_LOGVV(TAG, "fanning_off timer expired"); + this->timer_[thermostat::TIMER_FANNING_OFF].active = false; + this->switch_to_action_(this->compute_action_()); +} + +void ThermostatClimate::fanning_on_timer_callback_() { + ESP_LOGVV(TAG, "fanning_on timer expired"); + this->timer_[thermostat::TIMER_FANNING_ON].active = false; + this->switch_to_action_(this->compute_action_()); +} + +void ThermostatClimate::heating_max_run_time_timer_callback_() { + ESP_LOGVV(TAG, "heating_max_run_time timer expired"); + this->timer_[thermostat::TIMER_HEATING_MAX_RUN_TIME].active = false; + this->heating_max_runtime_exceeded_ = true; + this->trigger_supplemental_action_(); + this->switch_to_supplemental_action_(this->compute_supplemental_action_()); +} + +void ThermostatClimate::heating_off_timer_callback_() { + ESP_LOGVV(TAG, "heating_off timer expired"); + this->timer_[thermostat::TIMER_HEATING_OFF].active = false; + this->switch_to_action_(this->compute_action_()); + this->switch_to_supplemental_action_(this->compute_supplemental_action_()); +} + +void ThermostatClimate::heating_on_timer_callback_() { + ESP_LOGVV(TAG, "heating_on timer expired"); + this->timer_[thermostat::TIMER_HEATING_ON].active = false; + this->switch_to_action_(this->compute_action_()); + this->switch_to_supplemental_action_(this->compute_supplemental_action_()); +} + +void ThermostatClimate::idle_on_timer_callback_() { + ESP_LOGVV(TAG, "idle_on timer expired"); + this->timer_[thermostat::TIMER_IDLE_ON].active = false; + this->switch_to_action_(this->compute_action_()); + this->switch_to_supplemental_action_(this->compute_supplemental_action_()); +} + void ThermostatClimate::check_temperature_change_trigger_() { if (this->supports_two_points_) { // setup_complete_ helps us ensure an action is called immediately after boot @@ -459,10 +808,10 @@ bool ThermostatClimate::cooling_required_() { auto temperature = this->supports_two_points_ ? this->target_temperature_high : this->target_temperature; if (this->supports_cool_) { - if (this->current_temperature > (temperature + this->cool_deadband_)) { + if (this->current_temperature > temperature + this->cooling_deadband_) { // if the current temperature exceeds the target + deadband, cooling is required return true; - } else if (this->current_temperature < (temperature - this->cool_overrun_)) { + } else if (this->current_temperature < temperature - this->cooling_overrun_) { // if the current temperature is less than the target - overrun, cooling should stop return false; } else { @@ -480,10 +829,10 @@ bool ThermostatClimate::fanning_required_() { if (this->supports_fan_only_) { if (this->supports_fan_only_cooling_) { - if (this->current_temperature > (temperature + this->cool_deadband_)) { + if (this->current_temperature > temperature + this->cooling_deadband_) { // if the current temperature exceeds the target + deadband, fanning is required return true; - } else if (this->current_temperature < (temperature - this->cool_overrun_)) { + } else if (this->current_temperature < temperature - this->cooling_overrun_) { // if the current temperature is less than the target - overrun, fanning should stop return false; } else { @@ -502,10 +851,10 @@ bool ThermostatClimate::heating_required_() { auto temperature = this->supports_two_points_ ? this->target_temperature_low : this->target_temperature; if (this->supports_heat_) { - if (this->current_temperature < temperature - this->heat_deadband_) { + if (this->current_temperature < temperature - this->heating_deadband_) { // if the current temperature is below the target - deadband, heating is required return true; - } else if (this->current_temperature > temperature + this->heat_overrun_) { + } else if (this->current_temperature > temperature + this->heating_overrun_) { // if the current temperature is above the target + overrun, heating should stop return false; } else { @@ -518,6 +867,26 @@ bool ThermostatClimate::heating_required_() { return false; } +bool ThermostatClimate::supplemental_cooling_required_() { + auto temperature = this->supports_two_points_ ? this->target_temperature_high : this->target_temperature; + // the component must supports_cool_ and the climate action must be climate::CLIMATE_ACTION_COOLING. then... + // supplemental cooling is required if the max delta or max runtime was exceeded or the action is already engaged + return this->supports_cool_ && (this->action == climate::CLIMATE_ACTION_COOLING) && + (this->cooling_max_runtime_exceeded_ || + (this->current_temperature > temperature + this->supplemental_cool_delta_) || + (this->supplemental_action_ == climate::CLIMATE_ACTION_COOLING)); +} + +bool ThermostatClimate::supplemental_heating_required_() { + auto temperature = this->supports_two_points_ ? this->target_temperature_low : this->target_temperature; + // the component must supports_heat_ and the climate action must be climate::CLIMATE_ACTION_HEATING. then... + // supplemental heating is required if the max delta or max runtime was exceeded or the action is already engaged + return this->supports_heat_ && (this->action == climate::CLIMATE_ACTION_HEATING) && + (this->heating_max_runtime_exceeded_ || + (this->current_temperature < temperature - this->supplemental_heat_delta_) || + (this->supplemental_action_ == climate::CLIMATE_ACTION_HEATING)); +} + void ThermostatClimate::change_away_(bool away) { if (!away) { if (this->supports_two_points_) { @@ -546,10 +915,12 @@ void ThermostatClimate::set_away_config(const ThermostatClimateTargetTempConfig ThermostatClimate::ThermostatClimate() : cool_action_trigger_(new Trigger<>()), + supplemental_cool_action_trigger_(new Trigger<>()), cool_mode_trigger_(new Trigger<>()), dry_action_trigger_(new Trigger<>()), dry_mode_trigger_(new Trigger<>()), heat_action_trigger_(new Trigger<>()), + supplemental_heat_action_trigger_(new Trigger<>()), heat_mode_trigger_(new Trigger<>()), auto_mode_trigger_(new Trigger<>()), idle_action_trigger_(new Trigger<>()), @@ -575,11 +946,54 @@ void ThermostatClimate::set_default_mode(climate::ClimateMode default_mode) { th void ThermostatClimate::set_set_point_minimum_differential(float differential) { this->set_point_minimum_differential_ = differential; } -void ThermostatClimate::set_cool_deadband(float deadband) { this->cool_deadband_ = deadband; } -void ThermostatClimate::set_cool_overrun(float overrun) { this->cool_overrun_ = overrun; } -void ThermostatClimate::set_heat_deadband(float deadband) { this->heat_deadband_ = deadband; } -void ThermostatClimate::set_heat_overrun(float overrun) { this->heat_overrun_ = overrun; } +void ThermostatClimate::set_cool_deadband(float deadband) { this->cooling_deadband_ = deadband; } +void ThermostatClimate::set_cool_overrun(float overrun) { this->cooling_overrun_ = overrun; } +void ThermostatClimate::set_heat_deadband(float deadband) { this->heating_deadband_ = deadband; } +void ThermostatClimate::set_heat_overrun(float overrun) { this->heating_overrun_ = overrun; } +void ThermostatClimate::set_supplemental_cool_delta(float delta) { this->supplemental_cool_delta_ = delta; } +void ThermostatClimate::set_supplemental_heat_delta(float delta) { this->supplemental_heat_delta_ = delta; } +void ThermostatClimate::set_cooling_maximum_run_time_in_sec(uint32_t time) { + this->timer_[thermostat::TIMER_COOLING_MAX_RUN_TIME].time = + 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); +} +void ThermostatClimate::set_cooling_minimum_off_time_in_sec(uint32_t time) { + this->timer_[thermostat::TIMER_COOLING_OFF].time = + 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); +} +void ThermostatClimate::set_cooling_minimum_run_time_in_sec(uint32_t time) { + this->timer_[thermostat::TIMER_COOLING_ON].time = + 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); +} +void ThermostatClimate::set_fan_mode_minimum_switching_time_in_sec(uint32_t time) { + this->timer_[thermostat::TIMER_FAN_MODE].time = + 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); +} +void ThermostatClimate::set_fanning_minimum_off_time_in_sec(uint32_t time) { + this->timer_[thermostat::TIMER_FANNING_OFF].time = + 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); +} +void ThermostatClimate::set_fanning_minimum_run_time_in_sec(uint32_t time) { + this->timer_[thermostat::TIMER_FANNING_ON].time = + 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); +} +void ThermostatClimate::set_heating_maximum_run_time_in_sec(uint32_t time) { + this->timer_[thermostat::TIMER_HEATING_MAX_RUN_TIME].time = + 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); +} +void ThermostatClimate::set_heating_minimum_off_time_in_sec(uint32_t time) { + this->timer_[thermostat::TIMER_HEATING_OFF].time = + 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); +} +void ThermostatClimate::set_heating_minimum_run_time_in_sec(uint32_t time) { + this->timer_[thermostat::TIMER_HEATING_ON].time = + 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); +} +void ThermostatClimate::set_idle_minimum_time_in_sec(uint32_t time) { + this->timer_[thermostat::TIMER_IDLE_ON].time = + 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); +} void ThermostatClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } +void ThermostatClimate::set_use_startup_delay(bool use_startup_delay) { this->use_startup_delay_ = use_startup_delay; } void ThermostatClimate::set_supports_heat_cool(bool supports_heat_cool) { this->supports_heat_cool_ = supports_heat_cool; } @@ -587,9 +1001,19 @@ void ThermostatClimate::set_supports_auto(bool supports_auto) { this->supports_a void ThermostatClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } void ThermostatClimate::set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; } void ThermostatClimate::set_supports_fan_only(bool supports_fan_only) { this->supports_fan_only_ = supports_fan_only; } +void ThermostatClimate::set_supports_fan_only_action_uses_fan_mode_timer( + bool supports_fan_only_action_uses_fan_mode_timer) { + this->supports_fan_only_action_uses_fan_mode_timer_ = supports_fan_only_action_uses_fan_mode_timer; +} void ThermostatClimate::set_supports_fan_only_cooling(bool supports_fan_only_cooling) { this->supports_fan_only_cooling_ = supports_fan_only_cooling; } +void ThermostatClimate::set_supports_fan_with_cooling(bool supports_fan_with_cooling) { + this->supports_fan_with_cooling_ = supports_fan_with_cooling; +} +void ThermostatClimate::set_supports_fan_with_heating(bool supports_fan_with_heating) { + this->supports_fan_with_heating_ = supports_fan_with_heating; +} void ThermostatClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } void ThermostatClimate::set_supports_fan_mode_on(bool supports_fan_mode_on) { this->supports_fan_mode_on_ = supports_fan_mode_on; @@ -635,9 +1059,15 @@ void ThermostatClimate::set_supports_two_points(bool supports_two_points) { } Trigger<> *ThermostatClimate::get_cool_action_trigger() const { return this->cool_action_trigger_; } +Trigger<> *ThermostatClimate::get_supplemental_cool_action_trigger() const { + return this->supplemental_cool_action_trigger_; +} Trigger<> *ThermostatClimate::get_dry_action_trigger() const { return this->dry_action_trigger_; } Trigger<> *ThermostatClimate::get_fan_only_action_trigger() const { return this->fan_only_action_trigger_; } Trigger<> *ThermostatClimate::get_heat_action_trigger() const { return this->heat_action_trigger_; } +Trigger<> *ThermostatClimate::get_supplemental_heat_action_trigger() const { + return this->supplemental_heat_action_trigger_; +} Trigger<> *ThermostatClimate::get_idle_action_trigger() const { return this->idle_action_trigger_; } Trigger<> *ThermostatClimate::get_auto_mode_trigger() const { return this->auto_mode_trigger_; } Trigger<> *ThermostatClimate::get_cool_mode_trigger() const { return this->cool_mode_trigger_; } @@ -668,23 +1098,62 @@ void ThermostatClimate::dump_config() { else ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.1f°C", this->normal_config_.default_temperature); } - if ((this->supports_cool_) || (this->supports_fan_only_)) { + if ((this->supports_cool_) || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) { if (this->supports_two_points_) ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C", this->normal_config_.default_temperature_high); else ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C", this->normal_config_.default_temperature); } - ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_); - ESP_LOGCONFIG(TAG, " Cool Deadband: %.1f°C", this->cool_deadband_); - ESP_LOGCONFIG(TAG, " Cool Overrun: %.1f°C", this->cool_overrun_); - ESP_LOGCONFIG(TAG, " Heat Deadband: %.1f°C", this->heat_deadband_); - ESP_LOGCONFIG(TAG, " Heat Overrun: %.1f°C", this->heat_overrun_); + if (this->supports_two_points_) + ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_); + ESP_LOGCONFIG(TAG, " Start-up Delay Enabled: %s", YESNO(this->use_startup_delay_)); + if (this->supports_cool_) { + ESP_LOGCONFIG(TAG, " Cooling Parameters:"); + ESP_LOGCONFIG(TAG, " Deadband: %.1f°C", this->cooling_deadband_); + ESP_LOGCONFIG(TAG, " Overrun: %.1f°C", this->cooling_overrun_); + if ((this->supplemental_cool_delta_ > 0) || (this->timer_duration_(thermostat::TIMER_COOLING_MAX_RUN_TIME) > 0)) { + ESP_LOGCONFIG(TAG, " Supplemental Delta: %.1f°C", this->supplemental_cool_delta_); + ESP_LOGCONFIG(TAG, " Maximum Run Time: %us", + this->timer_duration_(thermostat::TIMER_COOLING_MAX_RUN_TIME) / 1000); + } + ESP_LOGCONFIG(TAG, " Minimum Off Time: %us", this->timer_duration_(thermostat::TIMER_COOLING_OFF) / 1000); + ESP_LOGCONFIG(TAG, " Minimum Run Time: %us", this->timer_duration_(thermostat::TIMER_COOLING_ON) / 1000); + } + if (this->supports_heat_) { + ESP_LOGCONFIG(TAG, " Heating Parameters:"); + ESP_LOGCONFIG(TAG, " Deadband: %.1f°C", this->heating_deadband_); + ESP_LOGCONFIG(TAG, " Overrun: %.1f°C", this->heating_overrun_); + if ((this->supplemental_heat_delta_ > 0) || (this->timer_duration_(thermostat::TIMER_HEATING_MAX_RUN_TIME) > 0)) { + ESP_LOGCONFIG(TAG, " Supplemental Delta: %.1f°C", this->supplemental_heat_delta_); + ESP_LOGCONFIG(TAG, " Maximum Run Time: %us", + this->timer_duration_(thermostat::TIMER_HEATING_MAX_RUN_TIME) / 1000); + } + ESP_LOGCONFIG(TAG, " Minimum Off Time: %us", this->timer_duration_(thermostat::TIMER_HEATING_OFF) / 1000); + ESP_LOGCONFIG(TAG, " Minimum Run Time: %us", this->timer_duration_(thermostat::TIMER_HEATING_ON) / 1000); + } + if (this->supports_fan_only_) { + ESP_LOGCONFIG(TAG, " Fanning Minimum Off Time: %us", this->timer_duration_(thermostat::TIMER_FANNING_OFF) / 1000); + ESP_LOGCONFIG(TAG, " Fanning Minimum Run Time: %us", this->timer_duration_(thermostat::TIMER_FANNING_ON) / 1000); + } + if (this->supports_fan_mode_on_ || this->supports_fan_mode_off_ || this->supports_fan_mode_auto_ || + this->supports_fan_mode_low_ || this->supports_fan_mode_medium_ || this->supports_fan_mode_high_ || + this->supports_fan_mode_middle_ || this->supports_fan_mode_focus_ || this->supports_fan_mode_diffuse_) { + ESP_LOGCONFIG(TAG, " Minimum Fan Mode Switching Time: %us", + this->timer_duration_(thermostat::TIMER_FAN_MODE) / 1000); + } + ESP_LOGCONFIG(TAG, " Minimum Idle Time: %us", this->timer_[thermostat::TIMER_IDLE_ON].time / 1000); ESP_LOGCONFIG(TAG, " Supports AUTO: %s", YESNO(this->supports_auto_)); ESP_LOGCONFIG(TAG, " Supports HEAT/COOL: %s", YESNO(this->supports_heat_cool_)); ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_)); ESP_LOGCONFIG(TAG, " Supports DRY: %s", YESNO(this->supports_dry_)); ESP_LOGCONFIG(TAG, " Supports FAN_ONLY: %s", YESNO(this->supports_fan_only_)); + ESP_LOGCONFIG(TAG, " Supports FAN_ONLY_ACTION_USES_FAN_MODE_TIMER: %s", + YESNO(this->supports_fan_only_action_uses_fan_mode_timer_)); ESP_LOGCONFIG(TAG, " Supports FAN_ONLY_COOLING: %s", YESNO(this->supports_fan_only_cooling_)); + if (this->supports_cool_) + ESP_LOGCONFIG(TAG, " Supports FAN_WITH_COOLING: %s", YESNO(this->supports_fan_with_cooling_)); + if (this->supports_heat_) + ESP_LOGCONFIG(TAG, " Supports FAN_WITH_HEATING: %s", YESNO(this->supports_fan_with_heating_)); ESP_LOGCONFIG(TAG, " Supports HEAT: %s", YESNO(this->supports_heat_)); ESP_LOGCONFIG(TAG, " Supports FAN MODE ON: %s", YESNO(this->supports_fan_mode_on_)); ESP_LOGCONFIG(TAG, " Supports FAN MODE OFF: %s", YESNO(this->supports_fan_mode_off_)); diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 67f002a22a..1a5fd82ac0 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -8,6 +8,26 @@ namespace esphome { namespace thermostat { +enum ThermostatClimateTimerIndex : size_t { + TIMER_COOLING_MAX_RUN_TIME = 0, + TIMER_COOLING_OFF = 1, + TIMER_COOLING_ON = 2, + TIMER_FAN_MODE = 3, + TIMER_FANNING_OFF = 4, + TIMER_FANNING_ON = 5, + TIMER_HEATING_MAX_RUN_TIME = 6, + TIMER_HEATING_OFF = 7, + TIMER_HEATING_ON = 8, + TIMER_IDLE_ON = 9, +}; + +struct ThermostatClimateTimer { + const std::string name; + bool active; + uint32_t time; + std::function func; +}; + struct ThermostatClimateTargetTempConfig { public: ThermostatClimateTargetTempConfig(); @@ -35,13 +55,29 @@ class ThermostatClimate : public climate::Climate, public Component { void set_cool_overrun(float overrun); void set_heat_deadband(float deadband); void set_heat_overrun(float overrun); + void set_supplemental_cool_delta(float delta); + void set_supplemental_heat_delta(float delta); + void set_cooling_maximum_run_time_in_sec(uint32_t time); + void set_heating_maximum_run_time_in_sec(uint32_t time); + void set_cooling_minimum_off_time_in_sec(uint32_t time); + void set_cooling_minimum_run_time_in_sec(uint32_t time); + void set_fan_mode_minimum_switching_time_in_sec(uint32_t time); + void set_fanning_minimum_off_time_in_sec(uint32_t time); + void set_fanning_minimum_run_time_in_sec(uint32_t time); + void set_heating_minimum_off_time_in_sec(uint32_t time); + void set_heating_minimum_run_time_in_sec(uint32_t time); + void set_idle_minimum_time_in_sec(uint32_t time); void set_sensor(sensor::Sensor *sensor); + void set_use_startup_delay(bool use_startup_delay); void set_supports_auto(bool supports_auto); void set_supports_heat_cool(bool supports_heat_cool); void set_supports_cool(bool supports_cool); void set_supports_dry(bool supports_dry); void set_supports_fan_only(bool supports_fan_only); + void set_supports_fan_only_action_uses_fan_mode_timer(bool fan_only_action_uses_fan_mode_timer); void set_supports_fan_only_cooling(bool supports_fan_only_cooling); + void set_supports_fan_with_cooling(bool supports_fan_with_cooling); + void set_supports_fan_with_heating(bool supports_fan_with_heating); void set_supports_heat(bool supports_heat); void set_supports_fan_mode_on(bool supports_fan_mode_on); void set_supports_fan_mode_off(bool supports_fan_mode_off); @@ -62,9 +98,11 @@ class ThermostatClimate : public climate::Climate, public Component { void set_away_config(const ThermostatClimateTargetTempConfig &away_config); Trigger<> *get_cool_action_trigger() const; + Trigger<> *get_supplemental_cool_action_trigger() const; Trigger<> *get_dry_action_trigger() const; Trigger<> *get_fan_only_action_trigger() const; Trigger<> *get_heat_action_trigger() const; + Trigger<> *get_supplemental_heat_action_trigger() const; Trigger<> *get_idle_action_trigger() const; Trigger<> *get_auto_mode_trigger() const; Trigger<> *get_cool_mode_trigger() const; @@ -93,6 +131,13 @@ class ThermostatClimate : public climate::Climate, public Component { float heat_overrun(); /// Call triggers based on updated climate states (modes/actions) void refresh(); + /// Returns true if a climate action/fan mode transition is being delayed + bool climate_action_change_delayed(); + bool fan_mode_change_delayed(); + /// Returns the climate action that is being delayed (check climate_action_change_delayed(), first!) + climate::ClimateAction delayed_climate_action(); + /// Returns the fan mode that is being delayed (check fan_mode_change_delayed(), first!) + climate::ClimateFanMode delayed_fan_mode(); /// Set point and hysteresis validation bool hysteresis_valid(); // returns true if valid void validate_target_temperature(); @@ -111,10 +156,13 @@ class ThermostatClimate : public climate::Climate, public Component { climate::ClimateTraits traits() override; /// Re-compute the required action of this climate controller. - climate::ClimateAction compute_action_(); + climate::ClimateAction compute_action_(bool ignore_timers = false); + climate::ClimateAction compute_supplemental_action_(); /// Switch the climate device to the given climate action. void switch_to_action_(climate::ClimateAction action); + void switch_to_supplemental_action_(climate::ClimateAction action); + void trigger_supplemental_action_(); /// Switch the climate device to the given climate fan mode. void switch_to_fan_mode_(climate::ClimateFanMode fan_mode); @@ -128,10 +176,39 @@ class ThermostatClimate : public climate::Climate, public Component { /// Check if the temperature change trigger should be called. void check_temperature_change_trigger_(); + /// Is the action ready to be called? Returns true if so + bool idle_action_ready_(); + bool cooling_action_ready_(); + bool drying_action_ready_(); + bool fan_mode_ready_(); + bool fanning_action_ready_(); + bool heating_action_ready_(); + + /// Start/cancel/get status of climate action timer + void start_timer_(ThermostatClimateTimerIndex timer_index); + bool cancel_timer_(ThermostatClimateTimerIndex timer_index); + bool timer_active_(ThermostatClimateTimerIndex timer_index); + uint32_t timer_duration_(ThermostatClimateTimerIndex timer_index); + std::function timer_cbf_(ThermostatClimateTimerIndex timer_index); + + /// set_timeout() callbacks for various actions (see above) + void cooling_max_run_time_timer_callback_(); + void cooling_off_timer_callback_(); + void cooling_on_timer_callback_(); + void fan_mode_timer_callback_(); + void fanning_off_timer_callback_(); + void fanning_on_timer_callback_(); + void heating_max_run_time_timer_callback_(); + void heating_off_timer_callback_(); + void heating_on_timer_callback_(); + void idle_on_timer_callback_(); + /// Check if cooling/fanning/heating actions are required; returns true if so bool cooling_required_(); bool fanning_required_(); bool heating_required_(); + bool supplemental_cooling_required_(); + bool supplemental_heating_required_(); /// The sensor used for getting the current temperature sensor::Sensor *sensor_{nullptr}; @@ -146,8 +223,13 @@ class ThermostatClimate : public climate::Climate, public Component { bool supports_dry_{false}; bool supports_fan_only_{false}; bool supports_heat_{false}; + /// Special flag -- enables fan_modes to share timer with fan_only climate action + bool supports_fan_only_action_uses_fan_mode_timer_{false}; /// Special flag -- enables fan to be switched based on target_temperature_high bool supports_fan_only_cooling_{false}; + /// Special flags -- enables fan_only action to be called with cooling/heating actions + bool supports_fan_with_cooling_{false}; + bool supports_fan_with_heating_{false}; /// Whether the controller supports turning on or off just the fan. /// @@ -190,12 +272,23 @@ class ThermostatClimate : public climate::Climate, public Component { /// A false value means that the controller has no such mode. bool supports_away_{false}; + /// Flags indicating if maximum allowable run time was exceeded + bool cooling_max_runtime_exceeded_{false}; + bool heating_max_runtime_exceeded_{false}; + + /// Used to start "off" delay timers at boot + bool use_startup_delay_{false}; + + /// setup_complete_ blocks modifying/resetting the temps immediately after boot + bool setup_complete_{false}; + /// The trigger to call when the controller should switch to cooling action/mode. /// /// A null value for this attribute means that the controller has no cooling action /// For example electric heat, where only heating (power on) and not-heating /// (power off) is possible. Trigger<> *cool_action_trigger_{nullptr}; + Trigger<> *supplemental_cool_action_trigger_{nullptr}; Trigger<> *cool_mode_trigger_{nullptr}; /// The trigger to call when the controller should switch to dry (dehumidification) mode. @@ -211,6 +304,7 @@ class ThermostatClimate : public climate::Climate, public Component { /// For example window blinds, where only cooling (blinds closed) and not-cooling /// (blinds open) is possible. Trigger<> *heat_action_trigger_{nullptr}; + Trigger<> *supplemental_heat_action_trigger_{nullptr}; Trigger<> *heat_mode_trigger_{nullptr}; /// The trigger to call when the controller should switch to auto mode. @@ -283,12 +377,16 @@ class ThermostatClimate : public climate::Climate, public Component { Trigger<> *prev_mode_trigger_{nullptr}; Trigger<> *prev_swing_mode_trigger_{nullptr}; + /// Desired fan_mode -- used to store desired mode for callback when switching is delayed + climate::ClimateFanMode desired_fan_mode_{climate::CLIMATE_FAN_ON}; + /// Store previously-known states /// /// These are used to determine when a trigger/action needs to be called + climate::ClimateAction supplemental_action_{climate::CLIMATE_ACTION_OFF}; climate::ClimateFanMode prev_fan_mode_{climate::CLIMATE_FAN_ON}; - climate::ClimateMode prev_mode_{climate::CLIMATE_MODE_OFF}; climate::ClimateMode default_mode_{climate::CLIMATE_MODE_OFF}; + climate::ClimateMode prev_mode_{climate::CLIMATE_MODE_OFF}; climate::ClimateSwingMode prev_swing_mode_{climate::CLIMATE_SWING_OFF}; /// Store previously-known temperatures @@ -298,21 +396,38 @@ class ThermostatClimate : public climate::Climate, public Component { float prev_target_temperature_low_{NAN}; float prev_target_temperature_high_{NAN}; - /// Temperature data for normal/home and away modes - ThermostatClimateTargetTempConfig normal_config_{}; - ThermostatClimateTargetTempConfig away_config_{}; - /// Minimum differential required between set points float set_point_minimum_differential_{0}; /// Hysteresis values used for computing climate actions - float cool_deadband_{0}; - float cool_overrun_{0}; - float heat_deadband_{0}; - float heat_overrun_{0}; + float cooling_deadband_{0}; + float cooling_overrun_{0}; + float heating_deadband_{0}; + float heating_overrun_{0}; - /// setup_complete_ blocks modifying/resetting the temps immediately after boot - bool setup_complete_{false}; + /// Maximum allowable temperature deltas before engauging supplemental cooling/heating actions + float supplemental_cool_delta_{0}; + float supplemental_heat_delta_{0}; + + /// Minimum allowable duration in seconds for action timers + const uint8_t min_timer_duration_{1}; + + /// Temperature data for normal/home and away modes + ThermostatClimateTargetTempConfig normal_config_{}; + ThermostatClimateTargetTempConfig away_config_{}; + + /// Climate action timers + std::vector timer_{ + {"cool_run", false, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)}, + {"cool_off", false, 0, std::bind(&ThermostatClimate::cooling_off_timer_callback_, this)}, + {"cool_on", false, 0, std::bind(&ThermostatClimate::cooling_on_timer_callback_, this)}, + {"fan_mode", false, 0, std::bind(&ThermostatClimate::fan_mode_timer_callback_, this)}, + {"fan_off", false, 0, std::bind(&ThermostatClimate::fanning_off_timer_callback_, this)}, + {"fan_on", false, 0, std::bind(&ThermostatClimate::fanning_on_timer_callback_, this)}, + {"heat_run", false, 0, std::bind(&ThermostatClimate::heating_max_run_time_timer_callback_, this)}, + {"heat_off", false, 0, std::bind(&ThermostatClimate::heating_off_timer_callback_, this)}, + {"heat_on", false, 0, std::bind(&ThermostatClimate::heating_on_timer_callback_, this)}, + {"idle_on", false, 0, std::bind(&ThermostatClimate::idle_on_timer_callback_, this)}}; }; } // namespace thermostat diff --git a/esphome/const.py b/esphome/const.py index 117b9ccc3e..02fb92d305 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -223,8 +223,11 @@ CONF_FAN_MODE_MIDDLE_ACTION = "fan_mode_middle_action" CONF_FAN_MODE_OFF_ACTION = "fan_mode_off_action" CONF_FAN_MODE_ON_ACTION = "fan_mode_on_action" CONF_FAN_ONLY_ACTION = "fan_only_action" +CONF_FAN_ONLY_ACTION_USES_FAN_MODE_TIMER = "fan_only_action_uses_fan_mode_timer" CONF_FAN_ONLY_COOLING = "fan_only_cooling" CONF_FAN_ONLY_MODE = "fan_only_mode" +CONF_FAN_WITH_COOLING = "fan_with_cooling" +CONF_FAN_WITH_HEATING = "fan_with_heating" CONF_FAST_CONNECT = "fast_connect" CONF_FILE = "file" CONF_FILTER = "filter" @@ -330,8 +333,10 @@ CONF_MAKE_ID = "make_id" CONF_MANUAL_IP = "manual_ip" CONF_MANUFACTURER_ID = "manufacturer_id" CONF_MASK_DISTURBER = "mask_disturber" +CONF_MAX_COOLING_RUN_TIME = "max_cooling_run_time" CONF_MAX_CURRENT = "max_current" CONF_MAX_DURATION = "max_duration" +CONF_MAX_HEATING_RUN_TIME = "max_heating_run_time" CONF_MAX_LENGTH = "max_length" CONF_MAX_LEVEL = "max_level" CONF_MAX_POWER = "max_power" @@ -345,6 +350,14 @@ CONF_MEASUREMENT_SEQUENCE_NUMBER = "measurement_sequence_number" CONF_MEDIUM = "medium" CONF_MEMORY_BLOCKS = "memory_blocks" CONF_METHOD = "method" +CONF_MIN_COOLING_OFF_TIME = "min_cooling_off_time" +CONF_MIN_COOLING_RUN_TIME = "min_cooling_run_time" +CONF_MIN_FAN_MODE_SWITCHING_TIME = "min_fan_mode_switching_time" +CONF_MIN_FANNING_OFF_TIME = "min_fanning_off_time" +CONF_MIN_FANNING_RUN_TIME = "min_fanning_run_time" +CONF_MIN_HEATING_OFF_TIME = "min_heating_off_time" +CONF_MIN_HEATING_RUN_TIME = "min_heating_run_time" +CONF_MIN_IDLE_TIME = "min_idle_time" CONF_MIN_LENGTH = "min_length" CONF_MIN_LEVEL = "min_level" CONF_MIN_POWER = "min_power" @@ -568,6 +581,7 @@ CONF_SPI_ID = "spi_id" CONF_SPIKE_REJECTION = "spike_rejection" CONF_SSID = "ssid" CONF_SSL_FINGERPRINTS = "ssl_fingerprints" +CONF_STARTUP_DELAY = "startup_delay" CONF_STATE = "state" CONF_STATE_CLASS = "state_class" CONF_STATE_TOPIC = "state_topic" @@ -580,6 +594,10 @@ CONF_STOP = "stop" CONF_STOP_ACTION = "stop_action" CONF_SUBNET = "subnet" CONF_SUBSTITUTIONS = "substitutions" +CONF_SUPPLEMENTAL_COOLING_ACTION = "supplemental_cooling_action" +CONF_SUPPLEMENTAL_COOLING_DELTA = "supplemental_cooling_delta" +CONF_SUPPLEMENTAL_HEATING_ACTION = "supplemental_heating_action" +CONF_SUPPLEMENTAL_HEATING_DELTA = "supplemental_heating_delta" CONF_SUPPORTS_COOL = "supports_cool" CONF_SUPPORTS_HEAT = "supports_heat" CONF_SWING_BOTH_ACTION = "swing_both_action" diff --git a/tests/test3.yaml b/tests/test3.yaml index d4c8e526fe..6402684c5d 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -858,8 +858,12 @@ climate: - switch.turn_on: gpio_switch1 cool_action: - switch.turn_on: gpio_switch2 + supplemental_cooling_action: + - switch.turn_on: gpio_switch3 heat_action: - switch.turn_on: gpio_switch1 + supplemental_heating_action: + - switch.turn_on: gpio_switch3 dry_action: - switch.turn_on: gpio_switch2 fan_only_action: @@ -902,7 +906,28 @@ climate: - switch.turn_on: gpio_switch1 swing_both_action: - switch.turn_on: gpio_switch2 - hysteresis: 0.2 + startup_delay: true + supplemental_cooling_delta: 2.0 + cool_deadband: 0.5 + cool_overrun: 0.5 + min_cooling_off_time: 300s + min_cooling_run_time: 300s + max_cooling_run_time: 600s + supplemental_heating_delta: 2.0 + heat_deadband: 0.5 + heat_overrun: 0.5 + min_heating_off_time: 300s + min_heating_run_time: 300s + max_heating_run_time: 600s + min_fanning_off_time: 30s + min_fanning_run_time: 30s + min_fan_mode_switching_time: 15s + min_idle_time: 30s + set_point_minimum_differential: 0.5 + fan_only_action_uses_fan_mode_timer: true + fan_only_cooling: true + fan_with_cooling: true + fan_with_heating: true away_config: default_target_temperature_low: 16°C default_target_temperature_high: 20°C From f26767b65e6e61ad20a9fad7a475a65c15983ba1 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Tue, 10 Aug 2021 05:32:16 -0300 Subject: [PATCH 1166/1841] Dsmr component (#1881) Co-authored-by: Otto winter --- CODEOWNERS | 1 + esphome/components/dsmr/__init__.py | 59 +++++++ esphome/components/dsmr/dsmr.cpp | 182 +++++++++++++++++++++ esphome/components/dsmr/dsmr.h | 104 ++++++++++++ esphome/components/dsmr/sensor.py | 210 +++++++++++++++++++++++++ esphome/components/dsmr/text_sensor.py | 94 +++++++++++ platformio.ini | 3 + tests/test3.yaml | 12 ++ 8 files changed, 665 insertions(+) create mode 100644 esphome/components/dsmr/__init__.py create mode 100644 esphome/components/dsmr/dsmr.cpp create mode 100644 esphome/components/dsmr/dsmr.h create mode 100644 esphome/components/dsmr/sensor.py create mode 100644 esphome/components/dsmr/text_sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index cd828395a6..b5603ea1c0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -37,6 +37,7 @@ esphome/components/debug/* @OttoWinter esphome/components/dfplayer/* @glmnet esphome/components/dht/* @OttoWinter esphome/components/ds1307/* @badbadc0ffee +esphome/components/dsmr/* @glmnet @zuidwijk esphome/components/esp32_ble/* @jesserockz esphome/components/esp32_ble_server/* @jesserockz esphome/components/esp32_improv/* @jesserockz diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py new file mode 100644 index 0000000000..df11a14ee8 --- /dev/null +++ b/esphome/components/dsmr/__init__.py @@ -0,0 +1,59 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart +from esphome.const import ( + CONF_ID, + CONF_UART_ID, +) + +CODEOWNERS = ["@glmnet", "@zuidwijk"] + +DEPENDENCIES = ["uart"] +AUTO_LOAD = ["sensor", "text_sensor"] + +CONF_DSMR_ID = "dsmr_id" +CONF_DECRYPTION_KEY = "decryption_key" + +# Hack to prevent compile error due to ambiguity with lib namespace +dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr") +Dsmr = dsmr_ns.class_("Dsmr", cg.Component, uart.UARTDevice) + + +def _validate_key(value): + value = cv.string_strict(value) + parts = [value[i : i + 2] for i in range(0, len(value), 2)] + if len(parts) != 16: + raise cv.Invalid("Decryption key must consist of 16 hexadecimal numbers") + parts_int = [] + if any(len(part) != 2 for part in parts): + raise cv.Invalid("Decryption key must be format XX") + for part in parts: + try: + parts_int.append(int(part, 16)) + except ValueError: + # pylint: disable=raise-missing-from + raise cv.Invalid("Decryption key must be hex values from 00 to FF") + + return "".join(f"{part:02X}" for part in parts_int) + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(Dsmr), + cv.Optional(CONF_DECRYPTION_KEY): _validate_key, + } +).extend(uart.UART_DEVICE_SCHEMA) + + +async def to_code(config): + uart_component = await cg.get_variable(config[CONF_UART_ID]) + var = cg.new_Pvariable(config[CONF_ID], uart_component) + if CONF_DECRYPTION_KEY in config: + cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY])) + await cg.register_component(var, config) + + # DSMR Parser + cg.add_library("glmnet/Dsmr", "0.3") + + # Crypto + cg.add_library("rweather/Crypto", "0.2.0") diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp new file mode 100644 index 0000000000..9bce7a382f --- /dev/null +++ b/esphome/components/dsmr/dsmr.cpp @@ -0,0 +1,182 @@ +#include "dsmr.h" +#include "esphome/core/log.h" + +#include +#include +#include + +namespace esphome { +namespace dsmr { + +static const char *const TAG = "dsmr"; + +void Dsmr::loop() { + if (this->decryption_key_.empty()) + this->receive_telegram_(); + else + this->receive_encrypted_(); +} + +void Dsmr::receive_telegram_() { + while (available()) { + const char c = read(); + + if (c == '/') { // header: forward slash + ESP_LOGV(TAG, "Header found"); + header_found_ = true; + footer_found_ = false; + telegram_len_ = 0; + } + + if (!header_found_) + continue; + if (telegram_len_ >= MAX_TELEGRAM_LENGTH) { // Buffer overflow + header_found_ = false; + footer_found_ = false; + ESP_LOGE(TAG, "Error: Message larger than buffer"); + return; + } + + telegram_[telegram_len_] = c; + telegram_len_++; + if (c == '!') { // footer: exclamation mark + ESP_LOGV(TAG, "Footer found"); + footer_found_ = true; + } else { + if (footer_found_ && c == 10) { // last \n after footer + header_found_ = false; + // Parse message + if (parse_telegram()) + return; + } + } + } +} + +void Dsmr::receive_encrypted_() { + // Encrypted buffer + uint8_t buffer[MAX_TELEGRAM_LENGTH]; + size_t buffer_length = 0; + + size_t packet_size = 0; + while (available()) { + const char c = read(); + + if (!header_found_) { + if ((uint8_t) c == 0xdb) { + ESP_LOGV(TAG, "Start byte 0xDB found"); + header_found_ = true; + } + } + + // Sanity check + if (!header_found_ || buffer_length >= MAX_TELEGRAM_LENGTH) { + if (buffer_length == 0) { + ESP_LOGE(TAG, "First byte of encrypted telegram should be 0xDB, aborting."); + } else { + ESP_LOGW(TAG, "Unexpected data"); + } + this->status_momentary_warning("unexpected_data"); + this->flush(); + while (available()) + read(); + return; + } + + buffer[buffer_length++] = c; + + if (packet_size == 0 && buffer_length > 20) { + // Complete header + a few bytes of data + packet_size = buffer[11] << 8 | buffer[12]; + } + if (buffer_length == packet_size + 13 && packet_size > 0) { + ESP_LOGV(TAG, "Encrypted data: %d bytes", buffer_length); + + GCM *gcmaes128{new GCM()}; + gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize()); + // the iv is 8 bytes of the system title + 4 bytes frame counter + // system title is at byte 2 and frame counter at byte 15 + for (int i = 10; i < 14; i++) + buffer[i] = buffer[i + 4]; + constexpr uint16_t iv_size{12}; + gcmaes128->setIV(&buffer[2], iv_size); + gcmaes128->decrypt(reinterpret_cast(this->telegram_), + // the ciphertext start at byte 18 + &buffer[18], + // cipher size + buffer_length - 17); + delete gcmaes128; + + telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_)); + ESP_LOGV(TAG, "Decrypted data length: %d", telegram_len_); + ESP_LOGVV(TAG, "Decrypted data %s", this->telegram_); + + parse_telegram(); + telegram_len_ = 0; + return; + } + + if (!available()) { + // baud rate is 115200 for encrypted data, this means a few byte should arrive every time + // program runs faster than buffer loading then available() might return false in the middle + delay(4); // Wait for data + } + } + if (buffer_length > 0) + ESP_LOGW(TAG, "Timeout while waiting for encrypted data or invalid data received."); +} + +bool Dsmr::parse_telegram() { + MyData data; + ESP_LOGV(TAG, "Trying to parse"); + ::dsmr::ParseResult res = + ::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, + false); // Parse telegram according to data definition. Ignore unknown values. + if (res.err) { + // Parsing error, show it + auto err_str = res.fullError(telegram_, telegram_ + telegram_len_); + ESP_LOGE(TAG, "%s", err_str.c_str()); + return false; + } else { + this->status_clear_warning(); + publish_sensors(data); + return true; + } +} + +void Dsmr::dump_config() { + ESP_LOGCONFIG(TAG, "dsmr:"); + +#define DSMR_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s_##s##_); + DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, ) + +#define DSMR_LOG_TEXT_SENSOR(s) LOG_TEXT_SENSOR(" ", #s, this->s_##s##_); + DSMR_TEXT_SENSOR_LIST(DSMR_LOG_TEXT_SENSOR, ) +} + +void Dsmr::set_decryption_key(const std::string &decryption_key) { + if (decryption_key.length() == 0) { + ESP_LOGI(TAG, "Disabling decryption"); + this->decryption_key_.clear(); + return; + } + + if (decryption_key.length() != 32) { + ESP_LOGE(TAG, "Error, decryption key must be 32 character long."); + return; + } + this->decryption_key_.clear(); + + ESP_LOGI(TAG, "Decryption key is set."); + // Verbose level prints decryption key + ESP_LOGV(TAG, "Using decryption key: %s", decryption_key.c_str()); + + char temp[3] = {0}; + for (int i = 0; i < 16; i++) { + strncpy(temp, &(decryption_key.c_str()[i * 2]), 2); + decryption_key_.push_back(std::strtoul(temp, nullptr, 16)); + } +} + +} // namespace dsmr +} // namespace esphome diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h new file mode 100644 index 0000000000..984f2596db --- /dev/null +++ b/esphome/components/dsmr/dsmr.h @@ -0,0 +1,104 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/components/uart/uart.h" +#include "esphome/core/log.h" +#include "esphome/core/defines.h" + +// don't include because it puts everything in global namespace +#include +#include + +namespace esphome { +namespace dsmr { + +static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500; +static constexpr uint32_t POLL_TIMEOUT = 1000; + +using namespace ::dsmr::fields; + +// DSMR_**_LIST generated by ESPHome and written in esphome/core/defines + +#if !defined(DSMR_SENSOR_LIST) && !defined(DSMR_TEXT_SENSOR_LIST) +// Neither set, set it to a dummy value to not break build +#define DSMR_TEXT_SENSOR_LIST(F, SEP) F(identification) +#endif + +#if defined(DSMR_SENSOR_LIST) && defined(DSMR_TEXT_SENSOR_LIST) +#define DSMR_BOTH , +#else +#define DSMR_BOTH +#endif + +#ifndef DSMR_SENSOR_LIST +#define DSMR_SENSOR_LIST(F, SEP) +#endif + +#ifndef DSMR_TEXT_SENSOR_LIST +#define DSMR_TEXT_SENSOR_LIST(F, SEP) +#endif + +#define DSMR_DATA_SENSOR(s) s +#define DSMR_COMMA , + +using MyData = ::dsmr::ParsedData; + +class Dsmr : public Component, public uart::UARTDevice { + public: + Dsmr(uart::UARTComponent *uart) : uart::UARTDevice(uart) {} + + void loop() override; + + bool parse_telegram(); + + void publish_sensors(MyData &data) { +#define DSMR_PUBLISH_SENSOR(s) \ + if (data.s##_present && this->s_##s##_ != nullptr) \ + s_##s##_->publish_state(data.s); + DSMR_SENSOR_LIST(DSMR_PUBLISH_SENSOR, ) + +#define DSMR_PUBLISH_TEXT_SENSOR(s) \ + if (data.s##_present && this->s_##s##_ != nullptr) \ + s_##s##_->publish_state(data.s.c_str()); + DSMR_TEXT_SENSOR_LIST(DSMR_PUBLISH_TEXT_SENSOR, ) + }; + + void dump_config() override; + + void set_decryption_key(const std::string &decryption_key); + +// Sensor setters +#define DSMR_SET_SENSOR(s) \ + void set_##s(sensor::Sensor *sensor) { s_##s##_ = sensor; } + DSMR_SENSOR_LIST(DSMR_SET_SENSOR, ) + +#define DSMR_SET_TEXT_SENSOR(s) \ + void set_##s(text_sensor::TextSensor *sensor) { s_##s##_ = sensor; } + DSMR_TEXT_SENSOR_LIST(DSMR_SET_TEXT_SENSOR, ) + + protected: + void receive_telegram_(); + void receive_encrypted_(); + + // Telegram buffer + char telegram_[MAX_TELEGRAM_LENGTH]; + int telegram_len_{0}; + + // Serial parser + bool header_found_{false}; + bool footer_found_{false}; + +// Sensor member pointers +#define DSMR_DECLARE_SENSOR(s) sensor::Sensor *s_##s##_{nullptr}; + DSMR_SENSOR_LIST(DSMR_DECLARE_SENSOR, ) + +#define DSMR_DECLARE_TEXT_SENSOR(s) text_sensor::TextSensor *s_##s##_{nullptr}; + DSMR_TEXT_SENSOR_LIST(DSMR_DECLARE_TEXT_SENSOR, ) + + std::vector decryption_key_{}; +}; +} // namespace dsmr +} // namespace esphome diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py new file mode 100644 index 0000000000..05c568e21a --- /dev/null +++ b/esphome/components/dsmr/sensor.py @@ -0,0 +1,210 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + ICON_EMPTY, + LAST_RESET_TYPE_NEVER, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_NONE, + UNIT_AMPERE, + UNIT_EMPTY, + UNIT_VOLT, + UNIT_WATT, +) +from . import Dsmr, CONF_DSMR_ID + +AUTO_LOAD = ["dsmr"] + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_DSMR_ID): cv.use_id(Dsmr), + cv.Optional("energy_delivered_lux"): sensor.sensor_schema( + "kWh", + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_NEVER, + ), + cv.Optional("energy_delivered_tariff1"): sensor.sensor_schema( + "kWh", + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_NEVER, + ), + cv.Optional("energy_delivered_tariff2"): sensor.sensor_schema( + "kWh", + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_NEVER, + ), + cv.Optional("energy_returned_lux"): sensor.sensor_schema( + "kWh", + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_NEVER, + ), + cv.Optional("energy_returned_tariff1"): sensor.sensor_schema( + "kWh", + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_NEVER, + ), + cv.Optional("energy_returned_tariff2"): sensor.sensor_schema( + "kWh", + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_NEVER, + ), + cv.Optional("total_imported_energy"): sensor.sensor_schema( + "kvarh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + ), + cv.Optional("total_exported_energy"): sensor.sensor_schema( + "kvarh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + ), + cv.Optional("power_delivered"): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + cv.Optional("power_returned"): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + cv.Optional("reactive_power_delivered"): sensor.sensor_schema( + "kvar", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + ), + cv.Optional("reactive_power_returned"): sensor.sensor_schema( + "kvar", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT + ), + cv.Optional("electricity_threshold"): sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 3, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + ), + cv.Optional("electricity_switch_position"): sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 3, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + ), + cv.Optional("electricity_failures"): sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + ), + cv.Optional("electricity_long_failures"): sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + ), + cv.Optional("electricity_sags_l1"): sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + ), + cv.Optional("electricity_sags_l2"): sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + ), + cv.Optional("electricity_sags_l3"): sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + ), + cv.Optional("electricity_swells_l1"): sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + ), + cv.Optional("electricity_swells_l2"): sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + ), + cv.Optional("electricity_swells_l3"): sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + ), + cv.Optional("current_l1"): sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + ), + cv.Optional("current_l2"): sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + ), + cv.Optional("current_l3"): sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + ), + cv.Optional("power_delivered_l1"): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + cv.Optional("power_delivered_l2"): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + cv.Optional("power_delivered_l3"): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + cv.Optional("power_returned_l1"): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + cv.Optional("power_returned_l2"): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + cv.Optional("power_returned_l3"): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + cv.Optional("reactive_power_delivered_l1"): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + cv.Optional("reactive_power_delivered_l2"): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + cv.Optional("reactive_power_delivered_l3"): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + cv.Optional("reactive_power_returned_l1"): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + cv.Optional("reactive_power_returned_l2"): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + cv.Optional("reactive_power_returned_l3"): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + cv.Optional("voltage_l1"): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE + ), + cv.Optional("voltage_l2"): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE + ), + cv.Optional("voltage_l3"): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE + ), + cv.Optional("gas_delivered"): sensor.sensor_schema( + "m³", + ICON_EMPTY, + 3, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_NEVER, + ), + cv.Optional("gas_delivered_be"): sensor.sensor_schema( + "m³", + ICON_EMPTY, + 3, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_NEVER, + ), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_DSMR_ID]) + + sensors = [] + for key, conf in config.items(): + if not isinstance(conf, dict): + continue + id = conf.get("id") + if id and id.type == sensor.Sensor: + s = await sensor.new_sensor(conf) + cg.add(getattr(hub, f"set_{key}")(s)) + sensors.append(f"F({key})") + + cg.add_define("DSMR_SENSOR_LIST(F, sep)", cg.RawExpression(" sep ".join(sensors))) diff --git a/esphome/components/dsmr/text_sensor.py b/esphome/components/dsmr/text_sensor.py new file mode 100644 index 0000000000..821b07dc6b --- /dev/null +++ b/esphome/components/dsmr/text_sensor.py @@ -0,0 +1,94 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import ( + CONF_ID, +) +from . import Dsmr, CONF_DSMR_ID + +AUTO_LOAD = ["dsmr"] + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_DSMR_ID): cv.use_id(Dsmr), + cv.Optional("identification"): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + } + ), + cv.Optional("p1_version"): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + } + ), + cv.Optional("p1_version_be"): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + } + ), + cv.Optional("timestamp"): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + } + ), + cv.Optional("electricity_tariff"): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + } + ), + cv.Optional("electricity_failure_log"): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + } + ), + cv.Optional("message_short"): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + } + ), + cv.Optional("message_long"): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + } + ), + cv.Optional("gas_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + } + ), + cv.Optional("thermal_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + } + ), + cv.Optional("water_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + } + ), + cv.Optional("sub_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + } + ), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_DSMR_ID]) + + text_sensors = [] + for key, conf in config.items(): + if not isinstance(conf, dict): + continue + id = conf.get("id") + if id and id.type == text_sensor.TextSensor: + var = cg.new_Pvariable(conf[CONF_ID]) + await text_sensor.register_text_sensor(var, conf) + cg.add(getattr(hub, f"set_{key}")(var)) + text_sensors.append(f"F({key})") + + cg.add_define( + "DSMR_TEXT_SENSOR_LIST(F, sep)", cg.RawExpression(" sep ".join(text_sensors)) + ) diff --git a/platformio.ini b/platformio.ini index 9f1366d9af..c280c54a21 100644 --- a/platformio.ini +++ b/platformio.ini @@ -34,6 +34,9 @@ lib_deps = 1655@1.0.2 ; TinyGPSPlus (has name conflict) 6865@1.0.0 ; TM1651 Battery Display 6306@1.0.3 ; HM3301 + glmnet/Dsmr@0.3 ; used by dsmr + rweather/Crypto@0.2.0 ; used by dsmr + build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = diff --git a/tests/test3.yaml b/tests/test3.yaml index 6402684c5d..e35c1e611c 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -594,6 +594,9 @@ sensor: name: 'Import Reactive Energy' export_reactive_energy: name: 'Export Reactive Energy' + - platform: dsmr + energy_delivered_tariff1: + name: dsmr_energy_delivered_tariff1 - platform: nextion id: testnumber @@ -735,6 +738,11 @@ text_sensor: id: text0 update_interval: 4s component_name: text0 + - platform: dsmr + identification: + name: "dsmr_identification" + p1_version: + name: "dsmr_p1_version" script: - id: my_script @@ -1242,3 +1250,7 @@ fingerprint_grow: data: finger_id: !lambda 'return finger_id;' uart_id: uart6 + +dsmr: + decryption_key: 00112233445566778899aabbccddeeff + uart_id: uart6 From c6c2842bdb5d2b44cfa2b9f63a758eed9d034fdd Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 10 Aug 2021 10:46:46 +0200 Subject: [PATCH 1167/1841] Always abort on allocation when out-of-memory (#2129) Co-authored-by: Otto winter --- esphome/components/display/display_buffer.cpp | 2 +- esphome/components/nextion/nextion_upload.cpp | 4 ++-- esphome/core/config.py | 9 +++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index bc2d8d5ef0..29f86dbd5f 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -14,7 +14,7 @@ const Color COLOR_OFF(0, 0, 0, 0); const Color COLOR_ON(255, 255, 255, 255); void DisplayBuffer::init_internal_(uint32_t buffer_length) { - this->buffer_ = new uint8_t[buffer_length]; + this->buffer_ = new (std::nothrow) uint8_t[buffer_length]; if (this->buffer_ == nullptr) { ESP_LOGE(TAG, "Could not allocate buffer for display!"); return; diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index 1a6e6d1066..43c8867e56 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -275,8 +275,8 @@ void Nextion::upload_tft() { } else { #endif ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap()); - this->transfer_buffer_ = new uint8_t[chunk_size]; - if (!this->transfer_buffer_) { // Try a smaller size + this->transfer_buffer_ = new (std::nothrow) uint8_t[chunk_size]; + if (this->transfer_buffer_ == nullptr) { // Try a smaller size ESP_LOGD(TAG, "Could not allocate buffer size: %d trying 4096 instead", chunk_size); chunk_size = 4096; ESP_LOGD(TAG, "Allocating %d buffer", chunk_size); diff --git a/esphome/core/config.py b/esphome/core/config.py index 23e6e34625..97748d71d8 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -344,6 +344,15 @@ async def to_code(config): else: cg.add_library(lib, None) + if CORE.is_esp8266: + # Arduino 2 has a non-standards conformant new that returns a nullptr instead of failing when + # out of memory and exceptions are disabled. Since Arduino 2.6.0, this flag can be used to make + # new abort instead. Use it so that OOM fails early (on allocation) instead of on dereference of + # a NULL pointer (so the stacktrace makes more sense), and for consistency with Arduino 3, + # which always aborts if exceptions are disabled. + # For cases where nullptrs can be handled, use nothrow: `new (std::nothrow) T;` + cg.add_build_flag("-DNEW_OOM_ABORT") + cg.add_build_flag("-Wno-unused-variable") cg.add_build_flag("-Wno-unused-but-set-variable") cg.add_build_flag("-Wno-sign-compare") From 183e2a847145d6cb7e3e120c10de229ad14cf9b4 Mon Sep 17 00:00:00 2001 From: WJCarpenter Date: Tue, 10 Aug 2021 01:48:06 -0700 Subject: [PATCH 1168/1841] Support component tsl2591 (#2131) Co-authored-by: Oxan van Leeuwen Co-authored-by: WJCarpenter Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/nextion/base_component.py | 5 +- esphome/components/tsl2591/__init__.py | 1 + esphome/components/tsl2591/sensor.py | 168 +++++++++ esphome/components/tsl2591/tsl2591.cpp | 369 +++++++++++++++++++ esphome/components/tsl2591/tsl2591.h | 245 ++++++++++++ esphome/const.py | 7 + tests/test1.yaml | 19 + 8 files changed, 813 insertions(+), 2 deletions(-) create mode 100644 esphome/components/tsl2591/__init__.py create mode 100644 esphome/components/tsl2591/sensor.py create mode 100644 esphome/components/tsl2591/tsl2591.cpp create mode 100644 esphome/components/tsl2591/tsl2591.h diff --git a/CODEOWNERS b/CODEOWNERS index b5603ea1c0..f45c9c1db4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -138,6 +138,7 @@ esphome/components/tmp102/* @timsavage esphome/components/tmp117/* @Azimath esphome/components/tof10120/* @wstrzalka esphome/components/toshiba/* @kbx81 +esphome/components/tsl2591/* @wjcarpenter esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/climate/* @jesserockz esphome/components/tuya/sensor/* @jesserockz diff --git a/esphome/components/nextion/base_component.py b/esphome/components/nextion/base_component.py index 3bf828aafa..ce567bc3f1 100644 --- a/esphome/components/nextion/base_component.py +++ b/esphome/components/nextion/base_component.py @@ -2,7 +2,9 @@ from string import ascii_letters, digits import esphome.config_validation as cv import esphome.codegen as cg from esphome.components import color - +from esphome.const import ( + CONF_VISIBLE, +) from . import CONF_NEXTION_ID from . import Nextion @@ -25,7 +27,6 @@ CONF_BACKGROUND_PRESSED_COLOR = "background_pressed_color" CONF_FOREGROUND_COLOR = "foreground_color" CONF_FOREGROUND_PRESSED_COLOR = "foreground_pressed_color" CONF_FONT_ID = "font_id" -CONF_VISIBLE = "visible" def NextionName(value): diff --git a/esphome/components/tsl2591/__init__.py b/esphome/components/tsl2591/__init__.py new file mode 100644 index 0000000000..63331641c5 --- /dev/null +++ b/esphome/components/tsl2591/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@wjcarpenter"] diff --git a/esphome/components/tsl2591/sensor.py b/esphome/components/tsl2591/sensor.py new file mode 100644 index 0000000000..095a8c886c --- /dev/null +++ b/esphome/components/tsl2591/sensor.py @@ -0,0 +1,168 @@ +# Credit where due.... +# I put a certain amount of work into this, but a lot of ESPHome integration is +# "look for other examples and see what they do" programming-by-example. Here are +# things that helped me along with this: +# +# - I mined the existing tsl2561 integration for basic structural framing for both +# the code and documentation. +# +# - I looked at the existing bme280 integration as an example of a single device +# with multiple sensors. +# +# - Comments and code in this thread got me going with the Adafruit TSL2591 library +# and prompted my desired to have tsl2591 as a standard component instead of a +# custom/external component. +# +# - And, of course, the handy and available Adafruit TSL2591 library was very +# helpful in understanding what the device is actually talking about. +# +# Here is the project that started me down the TSL2591 device trail in the first +# place: https://hackaday.io/project/176690-the-water-watcher + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_GAIN, + CONF_ID, + CONF_NAME, + CONF_INTEGRATION_TIME, + CONF_FULL_SPECTRUM, + CONF_INFRARED, + CONF_POWER_SAVE_MODE, + CONF_VISIBLE, + CONF_CALCULATED_LUX, + CONF_DEVICE_FACTOR, + CONF_GLASS_ATTENUATION_FACTOR, + ICON_BRIGHTNESS_6, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_ILLUMINANCE, + STATE_CLASS_MEASUREMENT, + UNIT_EMPTY, + UNIT_LUX, +) + +DEPENDENCIES = ["i2c"] + +tsl2591_ns = cg.esphome_ns.namespace("tsl2591") + +TSL2591IntegrationTime = tsl2591_ns.enum("TSL2591IntegrationTime") +INTEGRATION_TIMES = { + 100: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_100MS, + 200: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_200MS, + 300: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_300MS, + 400: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_400MS, + 500: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_500MS, + 600: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_600MS, +} + +TSL2591Gain = tsl2591_ns.enum("TSL2591Gain") +GAINS = { + "1X": TSL2591Gain.TSL2591_GAIN_LOW, + "LOW": TSL2591Gain.TSL2591_GAIN_LOW, + "25X": TSL2591Gain.TSL2591_GAIN_MED, + "MED": TSL2591Gain.TSL2591_GAIN_MED, + "MEDIUM": TSL2591Gain.TSL2591_GAIN_MED, + "400X": TSL2591Gain.TSL2591_GAIN_HIGH, + "HIGH": TSL2591Gain.TSL2591_GAIN_HIGH, + "9500X": TSL2591Gain.TSL2591_GAIN_MAX, + "MAX": TSL2591Gain.TSL2591_GAIN_MAX, + "MAXIMUM": TSL2591Gain.TSL2591_GAIN_MAX, +} + + +def validate_integration_time(value): + value = cv.positive_time_period_milliseconds(value).total_milliseconds + return cv.enum(INTEGRATION_TIMES, int=True)(value) + + +TSL2591Component = tsl2591_ns.class_( + "TSL2591Component", cg.PollingComponent, i2c.I2CDevice +) + + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(TSL2591Component), + cv.Optional(CONF_INFRARED): sensor.sensor_schema( + UNIT_EMPTY, + ICON_BRIGHTNESS_6, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_VISIBLE): sensor.sensor_schema( + UNIT_EMPTY, + ICON_BRIGHTNESS_6, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_FULL_SPECTRUM): sensor.sensor_schema( + UNIT_EMPTY, + ICON_BRIGHTNESS_6, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CALCULATED_LUX): sensor.sensor_schema( + UNIT_LUX, + ICON_BRIGHTNESS_6, + 4, + DEVICE_CLASS_ILLUMINANCE, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + CONF_INTEGRATION_TIME, default="100ms" + ): validate_integration_time, + cv.Optional(CONF_NAME, default="TLS2591"): cv.string, + cv.Optional(CONF_GAIN, default="MEDIUM"): cv.enum(GAINS, upper=True), + cv.Optional(CONF_POWER_SAVE_MODE, default=True): cv.boolean, + cv.Optional(CONF_DEVICE_FACTOR, default=53.0): cv.float_with_unit( + "device_factor", "", True + ), + cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=7.7): cv.float_with_unit( + "glass_attenuation_factor", "", True + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x29)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if CONF_FULL_SPECTRUM in config: + conf = config[CONF_FULL_SPECTRUM] + sens = await sensor.new_sensor(conf) + cg.add(var.set_full_spectrum_sensor(sens)) + + if CONF_INFRARED in config: + conf = config[CONF_INFRARED] + sens = await sensor.new_sensor(conf) + cg.add(var.set_infrared_sensor(sens)) + + if CONF_VISIBLE in config: + conf = config[CONF_VISIBLE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_visible_sensor(sens)) + + if CONF_CALCULATED_LUX in config: + conf = config[CONF_CALCULATED_LUX] + sens = await sensor.new_sensor(conf) + cg.add(var.set_calculated_lux_sensor(sens)) + + cg.add(var.set_name(config[CONF_NAME])) + cg.add(var.set_power_save_mode(config[CONF_POWER_SAVE_MODE])) + cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) + cg.add(var.set_gain(config[CONF_GAIN])) + cg.add( + var.set_device_and_glass_attenuation_factors( + config[CONF_DEVICE_FACTOR], config[CONF_GLASS_ATTENUATION_FACTOR] + ) + ) diff --git a/esphome/components/tsl2591/tsl2591.cpp b/esphome/components/tsl2591/tsl2591.cpp new file mode 100644 index 0000000000..1785fa46b4 --- /dev/null +++ b/esphome/components/tsl2591/tsl2591.cpp @@ -0,0 +1,369 @@ +#include "tsl2591.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace tsl2591 { + +static const char *const TAG = "tsl2591.sensor"; + +// Various constants used in TSL2591 register manipulation +#define TSL2591_COMMAND_BIT (0xA0) // 1010 0000: bits 7 and 5 for 'command, normal' +#define TSL2591_ENABLE_POWERON (0x01) // Flag for ENABLE register, to enable +#define TSL2591_ENABLE_POWEROFF (0x00) // Flag for ENABLE register, to disable +#define TSL2591_ENABLE_AEN (0x02) // Flag for ENABLE register, to turn on ADCs + +// TSL2591 registers from the datasheet. We only define what we use. +#define TSL2591_REGISTER_ENABLE (0x00) +#define TSL2591_REGISTER_CONTROL (0x01) +#define TSL2591_REGISTER_DEVICE_ID (0x12) +#define TSL2591_REGISTER_STATUS (0x13) +#define TSL2591_REGISTER_CHAN0_LOW (0x14) +#define TSL2591_REGISTER_CHAN0_HIGH (0x15) +#define TSL2591_REGISTER_CHAN1_LOW (0x16) +#define TSL2591_REGISTER_CHAN1_HIGH (0x17) + +void TSL2591Component::enable() { + // Enable the device by setting the control bit to 0x01. Also turn on ADCs. + if (!this->write_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_ENABLE, TSL2591_ENABLE_POWERON | TSL2591_ENABLE_AEN)) { + ESP_LOGE(TAG, "Failed I2C write during enable()"); + } +} + +void TSL2591Component::disable() { + if (!this->write_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_ENABLE, TSL2591_ENABLE_POWEROFF)) { + ESP_LOGE(TAG, "Failed I2C write during disable()"); + } +} + +void TSL2591Component::disable_if_power_saving_() { + if (this->power_save_mode_enabled_) { + this->disable(); + } +} + +void TSL2591Component::setup() { + uint8_t address = this->address_; + ESP_LOGI(TAG, "Setting up TSL2591 sensor at I2C address 0x%02X", address); + uint8_t id; + if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_DEVICE_ID, &id)) { + ESP_LOGE(TAG, "Failed I2C read during setup()"); + this->mark_failed(); + return; + } + if (id != 0x50) { + ESP_LOGE(TAG, + "Could not find the TSL2591 sensor. The ID register of the device at address 0x%02X reported 0x%02X " + "instead of 0x50.", + address, id); + this->mark_failed(); + return; + } + this->set_integration_time_and_gain(this->integration_time_, this->gain_); + this->disable_if_power_saving_(); +} + +void TSL2591Component::dump_config() { + ESP_LOGCONFIG(TAG, "TSL2591:"); + LOG_I2C_DEVICE(this); + + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with TSL2591 failed earlier, during setup"); + return; + } + + ESP_LOGCONFIG(TAG, " Name: %s", this->name_); + TSL2591Gain raw_gain = this->gain_; + int gain = 0; + std::string gain_word = "unknown"; + switch (raw_gain) { + case TSL2591_GAIN_LOW: + gain = 1; + gain_word = "low"; + break; + case TSL2591_GAIN_MED: + gain = 25; + gain_word = "medium"; + break; + case TSL2591_GAIN_HIGH: + gain = 400; + gain_word = "high"; + break; + case TSL2591_GAIN_MAX: + gain = 9500; + gain_word = "maximum"; + break; + } + ESP_LOGCONFIG(TAG, " Gain: %dx (%s)", gain, gain_word.c_str()); + TSL2591IntegrationTime raw_timing = this->integration_time_; + int timing_ms = (1 + raw_timing) * 100; + ESP_LOGCONFIG(TAG, " Integration Time: %d ms", timing_ms); + ESP_LOGCONFIG(TAG, " Power save mode enabled: %s", ONOFF(this->power_save_mode_enabled_)); + ESP_LOGCONFIG(TAG, " Device factor: %f", this->device_factor_); + ESP_LOGCONFIG(TAG, " Glass attenuation factor: %f", this->glass_attenuation_factor_); + LOG_SENSOR(" ", "Full spectrum:", this->full_spectrum_sensor_); + LOG_SENSOR(" ", "Infrared:", this->infrared_sensor_); + LOG_SENSOR(" ", "Visible:", this->visible_sensor_); + LOG_SENSOR(" ", "Calculated lux:", this->calculated_lux_sensor_); + + LOG_UPDATE_INTERVAL(this); +} + +void TSL2591Component::process_update_() { + uint32_t combined = this->get_combined_illuminance(); + uint16_t visible = this->get_illuminance(TSL2591_SENSOR_CHANNEL_VISIBLE, combined); + uint16_t infrared = this->get_illuminance(TSL2591_SENSOR_CHANNEL_INFRARED, combined); + uint16_t full = this->get_illuminance(TSL2591_SENSOR_CHANNEL_FULL_SPECTRUM, combined); + float lux = this->get_calculated_lux(full, infrared); + ESP_LOGD(TAG, "Got illuminance: combined 0x%X, full %d, IR %d, vis %d. Calc lux: %f", combined, full, infrared, + visible, lux); + if (this->full_spectrum_sensor_ != nullptr) { + this->full_spectrum_sensor_->publish_state(full); + } + if (this->infrared_sensor_ != nullptr) { + this->infrared_sensor_->publish_state(infrared); + } + if (this->visible_sensor_ != nullptr) { + this->visible_sensor_->publish_state(visible); + } + if (this->calculated_lux_sensor_ != nullptr) { + this->calculated_lux_sensor_->publish_state(lux); + } + this->status_clear_warning(); +} + +#define interval_name "tsl2591_interval_for_update" + +void TSL2591Component::interval_function_for_update_() { + if (!this->is_adc_valid()) { + uint64_t now = millis(); + ESP_LOGD(TAG, "Elapsed %3llu ms; still waiting for valid ADC", (now - this->interval_start_)); + if (now > this->interval_timeout_) { + ESP_LOGW(TAG, "Interval timeout for TSL2591 '%s' expired before ADCs became valid.", this->name_); + this->cancel_interval(interval_name); + } + return; + } + this->cancel_interval(interval_name); + this->process_update_(); +} + +void TSL2591Component::update() { + if (!is_failed()) { + if (this->power_save_mode_enabled_) { + // we enabled it here, else ADC will never become valid + // but actually doing the reads will disable device if needed + this->enable(); + } + if (this->is_adc_valid()) { + this->process_update_(); + } else { + this->interval_start_ = millis(); + this->interval_timeout_ = this->interval_start_ + 620; + this->set_interval(interval_name, 100, [this] { this->interval_function_for_update_(); }); + } + } +} + +void TSL2591Component::set_infrared_sensor(sensor::Sensor *infrared_sensor) { + this->infrared_sensor_ = infrared_sensor; +} + +void TSL2591Component::set_visible_sensor(sensor::Sensor *visible_sensor) { this->visible_sensor_ = visible_sensor; } + +void TSL2591Component::set_full_spectrum_sensor(sensor::Sensor *full_spectrum_sensor) { + this->full_spectrum_sensor_ = full_spectrum_sensor; +} + +void TSL2591Component::set_calculated_lux_sensor(sensor::Sensor *calculated_lux_sensor) { + this->calculated_lux_sensor_ = calculated_lux_sensor; +} + +void TSL2591Component::set_integration_time(TSL2591IntegrationTime integration_time) { + this->integration_time_ = integration_time; +} + +void TSL2591Component::set_gain(TSL2591Gain gain) { this->gain_ = gain; } + +void TSL2591Component::set_device_and_glass_attenuation_factors(float device_factor, float glass_attenuation_factor) { + this->device_factor_ = device_factor; + this->glass_attenuation_factor_ = glass_attenuation_factor; +} + +void TSL2591Component::set_integration_time_and_gain(TSL2591IntegrationTime integration_time, TSL2591Gain gain) { + this->enable(); + this->integration_time_ = integration_time; + this->gain_ = gain; + if (!this->write_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CONTROL, + this->integration_time_ | this->gain_)) { // NOLINT + ESP_LOGE(TAG, "Failed I2C write during set_integration_time_and_gain()"); + } + // The ADC values can be confused if gain or integration time are changed in the middle of a cycle. + // So, we unconditionally disable the device to turn the ADCs off. When re-enabling, the ADCs + // will tell us when they are ready again. That avoids an initial bogus reading. + this->disable(); + if (!this->power_save_mode_enabled_) { + this->enable(); + } +} + +void TSL2591Component::set_power_save_mode(bool enable) { this->power_save_mode_enabled_ = enable; } + +void TSL2591Component::set_name(const char *name) { this->name_ = name; } + +float TSL2591Component::get_setup_priority() const { return setup_priority::DATA; } + +bool TSL2591Component::is_adc_valid() { + uint8_t status; + if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_STATUS, &status)) { + ESP_LOGE(TAG, "Failed I2C read during is_adc_valid()"); + return false; + } + return status & 0x01; +} + +uint32_t TSL2591Component::get_combined_illuminance() { + this->enable(); + // Wait x ms for ADC to complete and signal valid. + // The max integration time is 600ms, so that's our max delay. + // (But we use 620ms as a bit of slack.) + // We'll do mini-delays and break out as soon as the ADC is good. + bool avalid; + const uint8_t mini_delay = 100; + for (uint16_t d = 0; d < 620; d += mini_delay) { + avalid = this->is_adc_valid(); + if (avalid) { + break; + } + // we only log this if we need any delay, since normally we don't + ESP_LOGD(TAG, " after %3d ms: ADC valid? %s", d, avalid ? "true" : "false"); + delay(mini_delay); + } + if (!avalid) { + // still not valid after a sutiable delay + // we don't mark the device as failed since it might come around in the future (probably not :-() + ESP_LOGE(TAG, "tsl2591 device '%s' did not return valid readings.", this->name_); + this->disable_if_power_saving_(); + return 0; + } + + // CHAN0 must be read before CHAN1 + // See: https://forums.adafruit.com/viewtopic.php?f=19&t=124176 + // Also, low byte must be read before high byte.. + // We read the registers in the order described in the datasheet. + uint32_t x32; + uint8_t ch0low, ch0high, ch1low, ch1high; + uint16_t ch0_16; + uint16_t ch1_16; + if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN0_LOW, &ch0low)) { + ESP_LOGE(TAG, "Failed I2C read during get_combined_illuminance()"); + return 0; + } + if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN0_HIGH, &ch0high)) { + ESP_LOGE(TAG, "Failed I2C read during get_combined_illuminance()"); + return 0; + } + ch0_16 = (ch0high << 8) | ch0low; + if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN1_LOW, &ch1low)) { + ESP_LOGE(TAG, "Failed I2C read during get_combined_illuminance()"); + return 0; + } + if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN1_HIGH, &ch1high)) { + ESP_LOGE(TAG, "Failed I2C read during get_combined_illuminance()"); + return 0; + } + ch1_16 = (ch1high << 8) | ch1low; + x32 = (ch1_16 << 16) | ch0_16; + + this->disable_if_power_saving_(); + return x32; +} + +uint16_t TSL2591Component::get_illuminance(TSL2591SensorChannel channel) { + uint32_t combined = this->get_combined_illuminance(); + return this->get_illuminance(channel, combined); +} +// logic cloned from Adafruit TSL2591 library +uint16_t TSL2591Component::get_illuminance(TSL2591SensorChannel channel, uint32_t combined_illuminance) { + if (channel == TSL2591_SENSOR_CHANNEL_FULL_SPECTRUM) { + // Reads two byte value from channel 0 (visible + infrared) + return (combined_illuminance & 0xFFFF); + } else if (channel == TSL2591_SENSOR_CHANNEL_INFRARED) { + // Reads two byte value from channel 1 (infrared) + return (combined_illuminance >> 16); + } else if (channel == TSL2591_SENSOR_CHANNEL_VISIBLE) { + // Reads all and subtracts out the infrared + return ((combined_illuminance & 0xFFFF) - (combined_illuminance >> 16)); + } + // unknown channel! + ESP_LOGE(TAG, "TSL2591Component::get_illuminance() caller requested an unknown channel: %d", channel); + return 0; +} + +/** Calculates a lux value from the two TSL2591 physical sensor ADC readings. + * + * The lux calculation is copied from the Adafruit TSL2591 library. + * There is some debate about whether it is the correct lux equation to use. + * We use that lux equation because (a) it helps with a transition from + * using that Adafruit library to using this ESPHome integration, and (b) we + * don't have a definitive better idea. + * + * Since the raw ADC readings are available, you can ignore this method and + * implement your own lux equation. + * + * @param full_spectrum The ADC reading for TSL2591 channel 0. + * @param infrared The ADC reading for TSL2591 channel 1. + */ +float TSL2591Component::get_calculated_lux(uint16_t full_spectrum, uint16_t infrared) { + // Check for overflow conditions first + uint16_t max_count = (this->integration_time_ == TSL2591_INTEGRATION_TIME_100MS ? 36863 : 65535); + if ((full_spectrum == max_count) || (infrared == max_count)) { + // Signal an overflow + ESP_LOGW(TAG, "Apparent saturation on TSL2591 (%s). You could reduce the gain.", this->name_); + return -1.0F; + } + + if ((full_spectrum == 0) && (infrared == 0)) { + // trivial conversion; avoids divide by 0 + ESP_LOGW(TAG, "Zero reading on both TSL2591 (%s) sensors. Is the device having a problem?", this->name_); + return 0.0F; + } + + float atime = 100.F + (this->integration_time_ * 100); + + float again; + switch (this->gain_) { + case TSL2591_GAIN_LOW: + again = 1.0F; + break; + case TSL2591_GAIN_MED: + again = 25.0F; + break; + case TSL2591_GAIN_HIGH: + again = 400.0F; + break; + case TSL2591_GAIN_MAX: + again = 9500.0F; + break; + default: + again = 1.0F; + break; + } + + // This lux equation is copied from the Adafruit TSL2591 v1.4.0 and modified slightly. + // See: https://github.com/adafruit/Adafruit_TSL2591_Library/issues/14 + // and that library code. + // They said: + // Note: This algorithm is based on preliminary coefficients + // provided by AMS and may need to be updated in the future + // However, we use gain multipliers that are more in line with the midpoints + // of ranges from the datasheet. We don't know why the other libraries + // used the values they did for HIGH and MAX. + // If cpl or full_spectrum are 0, this will return NaN due to divide by 0. + // For the curious "cpl" is counts per lux, a term used in AMS application notes. + float cpl = (atime * again) / (this->device_factor_ * this->glass_attenuation_factor_); + float lux = (((float) full_spectrum - (float) infrared)) * (1.0F - ((float) infrared / (float) full_spectrum)) / cpl; + return max(lux, 0.0F); +} + +} // namespace tsl2591 +} // namespace esphome diff --git a/esphome/components/tsl2591/tsl2591.h b/esphome/components/tsl2591/tsl2591.h new file mode 100644 index 0000000000..d377d082a8 --- /dev/null +++ b/esphome/components/tsl2591/tsl2591.h @@ -0,0 +1,245 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace tsl2591 { + +/** Enum listing all conversion/integration time settings for the TSL2591. + * + * Specific values of the enum constants are register values taken from the TSL2591 datasheet. + * Longer times mean more accurate results, but will take more energy/more time. + */ +enum TSL2591IntegrationTime { + TSL2591_INTEGRATION_TIME_100MS = 0b000, + TSL2591_INTEGRATION_TIME_200MS = 0b001, + TSL2591_INTEGRATION_TIME_300MS = 0b010, + TSL2591_INTEGRATION_TIME_400MS = 0b011, + TSL2591_INTEGRATION_TIME_500MS = 0b100, + TSL2591_INTEGRATION_TIME_600MS = 0b101, +}; + +/** Enum listing all gain settings for the TSL2591. + * + * Specific values of the enum constants are register values taken from the TSL2591 datasheet. + * Higher values are better for low light situations, but can increase noise. + */ +enum TSL2591Gain { + TSL2591_GAIN_LOW = 0b00 << 4, // 1x + TSL2591_GAIN_MED = 0b01 << 4, // 25x + TSL2591_GAIN_HIGH = 0b10 << 4, // 400x + TSL2591_GAIN_MAX = 0b11 << 4, // 9500x +}; + +/** Enum listing sensor channels. + * + * They identify the type of light to report. + */ +enum TSL2591SensorChannel { + TSL2591_SENSOR_CHANNEL_VISIBLE, + TSL2591_SENSOR_CHANNEL_INFRARED, + TSL2591_SENSOR_CHANNEL_FULL_SPECTRUM, +}; + +/// This class includes support for the TSL2591 i2c ambient light +/// sensor. The device has two distinct sensors. One is for visible +/// light plus infrared light, and the other is for infrared +/// light. They are reported as separate sensors, and the difference +/// between the values is reported as a third sensor as a convenience +/// for visible light only. +class TSL2591Component : public PollingComponent, public i2c::I2CDevice { + public: + /** Set device integration time and gain. + * + * These are set as a single I2C transaction, so you must supply values + * for both. + * + * Longer integration times provides more accurate values, but also + * means more power consumption. Higher gain values are useful for + * lower light intensities but are also subject to more noise. The + * device might use a slightly different gain multiplier than those + * indicated; see the datasheet for details. + * + * Possible values for integration_time (from enum + * TSL2591IntegrationTime) are: + * + * - `esphome::tsl2591::TSL2591_INTEGRATION_TIME_100MS` + * - `esphome::tsl2591::TSL2591_INTEGRATION_TIME_200MS` + * - `esphome::tsl2591::TSL2591_INTEGRATION_TIME_300MS` + * - `esphome::tsl2591::TSL2591_INTEGRATION_TIME_400MS` + * - `esphome::tsl2591::TSL2591_INTEGRATION_TIME_500MS` + * - `esphome::tsl2591::TSL2591_INTEGRATION_TIME_600MS` + * + * Possible values for gain (from enum TSL2591Gain) are: + * + * - `esphome::tsl2591::TSL2591_GAIN_LOW` (1x) + * - `esphome::tsl2591::TSL2591_GAIN_MED` (25x) + * - `esphome::tsl2591::TSL2591_GAIN_HIGH` (400x) + * - `esphome::tsl2591::TSL2591_GAIN_MAX` (9500x) + * + * @param integration_time The new integration time. + * @param gain The new gain. + */ + void set_integration_time_and_gain(TSL2591IntegrationTime integration_time, TSL2591Gain gain); + + /** Should the device be powered down between readings? + * + * The disadvantage of powering down the device between readings + * is that you have to wait for the ADC to go through an + * integration cycle before a reliable reading is available. + * This happens during ESPHome's update loop, so waiting slows + * down the entire ESP device. You should only enable this if + * you need to minimize power consumption and you can tolerate + * that delay. Otherwise, keep the default of disabling + * power save mode. + * + * @param enable Enable or disable power save mode. + */ + void set_power_save_mode(bool enable); + + /** Sets the name for this instance of the device. + * + * @param name The user-friendly name. + */ + void set_name(const char *name); + + /** Sets the device and glass attenuation factors. + * + * The lux equation, used to calculate the lux from the ADC readings, + * involves a scaling coefficient that is the product of a device + * factor (specific to the type of device being used) and a glass + * attenuation factor (specific to whatever glass or plastic cover + * is installed in front of the light sensors. + * + * AMS does not publish the device factor for the TSL2591. In the + * datasheet for the earlier TSL2571 and in application notes, they + * use the value 53, so we use that as the default. + * + * The glass attenuation factor depends on factors external to the + * TSL2591 and is best obtained through experimental measurements. + * The Adafruit TSL2591 library use a value of ~7.7, which we use as + * a default. Waveshare uses a value of ~14.4. Presumably, those + * factors are appropriate to the breakout boards from those vendors, + * but we have not verified that. + * + * @param device_factor The device factor. + * @param glass_attenuation_factor The glass attenuation factor. + */ + void set_device_and_glass_attenuation_factors(float device_factor, float glass_attenuation_factor); + + /** Calculates and returns a lux value based on the ADC readings. + * + * @param full_spectrum The ADC reading for the full spectrum sensor. + * @param infrared The ADC reading for the infrared sensor. + */ + float get_calculated_lux(uint16_t full_spectrum, uint16_t infrared); + + /** Get the combined illuminance value. + * + * This is encoded into a 32 bit value. The high 16 bits are the value of the + * infrared sensor. The low 16 bits are the sum of the combined sensor values. + * + * If power saving mode is enabled, there can be a delay (up to the value of the integration + * time) while waiting for the device ADCs to signal that values are valid. + */ + uint32_t get_combined_illuminance(); + + /** Get an individual sensor channel reading. + * + * This gets an individual light sensor reading. Since it goes through + * the entire component read cycle to get one value, it's not optimal if + * you want to get all possible channel values. If you want that, first + * call `get_combined_illuminance()` and pass that value to the companion + * method with a different signature. + * + * If power saving mode is enabled, there can be a delay (up to the value of the integration + * time) while waiting for the device ADCs to signal that values are valid. + * + * @param channel The sensor channel of interest. + */ + uint16_t get_illuminance(TSL2591SensorChannel channel); + + /** Get an individual sensor channel reading from combined illuminance. + * + * This gets an individual light sensor reading from a combined illuminance + * value, which you would obtain from calling `getCombinedIlluminance()`. + * This method does not communicate with the sensor at all. It's strictly + * local calculations, so it is efficient if you call it multiple times. + * + * @param channel The sensor channel of interest. + * @param combined_illuminance The previously obtained combined illuminance value. + */ + uint16_t get_illuminance(TSL2591SensorChannel channel, uint32_t combined_illuminance); + + /** Are the device ADC values valid? + * + * Useful for scripting. This should be checked before calling update(). + * It asks the TSL2591 if the ADC has completed an integration cycle + * and has reliable values in the device registers. If you call update() + * before the ADC values are valid, you may cause a general delay in + * the ESPHome update loop. + * + * It should take no more than the configured integration time for + * the ADC values to become valid after the TSL2591 device is enabled. + */ + bool is_adc_valid(); + + /** Powers on the TSL2591 device and enables its sensors. + * + * You only need to call this if you have disabled the device. + * The device starts enabled in ESPHome unless power save mode is enabled. + */ + void enable(); + /** Powers off the TSL2591 device. + * + * You can call this from an ESPHome script if you are explicitly + * controlling TSL2591 power consumption. + * The device starts enabled in ESPHome unless power save mode is enabled. + */ + void disable(); + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these. They're for ESPHome integration use.) + /** Used by ESPHome framework. */ + void set_full_spectrum_sensor(sensor::Sensor *full_spectrum_sensor); + /** Used by ESPHome framework. */ + void set_infrared_sensor(sensor::Sensor *infrared_sensor); + /** Used by ESPHome framework. */ + void set_visible_sensor(sensor::Sensor *visible_sensor); + /** Used by ESPHome framework. */ + void set_calculated_lux_sensor(sensor::Sensor *calculated_lux_sensor); + /** Used by ESPHome framework. Does NOT actually set the value on the device. */ + void set_integration_time(TSL2591IntegrationTime integration_time); + /** Used by ESPHome framework. Does NOT actually set the value on the device. */ + void set_gain(TSL2591Gain gain); + /** Used by ESPHome framework. */ + void setup() override; + /** Used by ESPHome framework. */ + void dump_config() override; + /** Used by ESPHome framework. */ + void update() override; + /** Used by ESPHome framework. */ + float get_setup_priority() const override; + + protected: + const char *name_; // TODO: extend esphome::Nameable + sensor::Sensor *full_spectrum_sensor_; + sensor::Sensor *infrared_sensor_; + sensor::Sensor *visible_sensor_; + sensor::Sensor *calculated_lux_sensor_; + TSL2591IntegrationTime integration_time_; + TSL2591Gain gain_; + bool power_save_mode_enabled_; + float device_factor_; + float glass_attenuation_factor_; + uint64_t interval_start_; + uint64_t interval_timeout_; + void disable_if_power_saving_(); + void process_update_(); + void interval_function_for_update_(); +}; + +} // namespace tsl2591 +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 02fb92d305..a2f49fe3ca 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -96,6 +96,7 @@ CONF_BUFFER_SIZE = "buffer_size" CONF_BUILD_PATH = "build_path" CONF_BUS_VOLTAGE = "bus_voltage" CONF_BUSY_PIN = "busy_pin" +CONF_CALCULATED_LUX = "calculated_lux" CONF_CALIBRATE_LINEAR = "calibrate_linear" CONF_CALIBRATION = "calibration" CONF_CAPACITANCE = "capacitance" @@ -171,6 +172,7 @@ CONF_DELAY = "delay" CONF_DELTA = "delta" CONF_DEVICE = "device" CONF_DEVICE_CLASS = "device_class" +CONF_DEVICE_FACTOR = "device_factor" CONF_DIMENSIONS = "dimensions" CONF_DIO_PIN = "dio_pin" CONF_DIR_PIN = "dir_pin" @@ -244,11 +246,13 @@ CONF_FORMAT = "format" CONF_FORWARD_ACTIVE_ENERGY = "forward_active_energy" CONF_FREQUENCY = "frequency" CONF_FROM = "from" +CONF_FULL_SPECTRUM = "full_spectrum" CONF_FULL_UPDATE_EVERY = "full_update_every" CONF_GAIN = "gain" CONF_GAMMA_CORRECT = "gamma_correct" CONF_GAS_RESISTANCE = "gas_resistance" CONF_GATEWAY = "gateway" +CONF_GLASS_ATTENUATION_FACTOR = "glass_attenuation_factor" CONF_GLYPHS = "glyphs" CONF_GPIO = "gpio" CONF_GREEN = "green" @@ -287,6 +291,7 @@ CONF_IMPORT_REACTIVE_ENERGY = "import_reactive_energy" CONF_INCLUDES = "includes" CONF_INDEX = "index" CONF_INDOOR = "indoor" +CONF_INFRARED = "infrared" CONF_INITIAL_MODE = "initial_mode" CONF_INITIAL_OPTION = "initial_option" CONF_INITIAL_VALUE = "initial_value" @@ -660,6 +665,7 @@ CONF_VALUE = "value" CONF_VARIABLES = "variables" CONF_VARIANT = "variant" CONF_VERSION = "version" +CONF_VISIBLE = "visible" CONF_VISUAL = "visual" CONF_VOLTAGE = "voltage" CONF_VOLTAGE_ATTENUATION = "voltage_attenuation" @@ -696,6 +702,7 @@ ICON_BLUETOOTH = "mdi:bluetooth" ICON_BLUR = "mdi:blur" ICON_BRIEFCASE_DOWNLOAD = "mdi:briefcase-download" ICON_BRIGHTNESS_5 = "mdi:brightness-5" +ICON_BRIGHTNESS_6 = "mdi:brightness-6" ICON_BUG = "mdi:bug" ICON_CHECK_CIRCLE_OUTLINE = "mdi:check-circle-outline" ICON_CHEMICAL_WEAPON = "mdi:chemical-weapon" diff --git a/tests/test1.yaml b/tests/test1.yaml index 54f8eb589c..0e81b19998 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -840,6 +840,25 @@ sensor: is_cs_package: true integration_time: 402ms gain: 16x + - platform: tsl2591 + id: this_little_light_of_mine + address: 0x29 + update_interval: 15s + integration_time: 600ms + gain: high + visible: + name: "tsl2591 visible" + id: tsl2591_vis + unit_of_measurement: 'pH' + infrared: + name: "tsl2591 infrared" + id: tsl2591_ir + full_spectrum: + name: "tsl2591 full_spectrum" + id: tsl2591_fs + calculated_lux: + name: "tsl2591 calculated_lux" + id: tsl2591_cl - platform: ultrasonic trigger_pin: GPIO25 echo_pin: From 6a2f0f51439077d40f960cb624734f97812a531c Mon Sep 17 00:00:00 2001 From: Stephen Tierney Date: Tue, 10 Aug 2021 19:00:16 +1000 Subject: [PATCH 1169/1841] Add support for PMSA003i (#1501) Co-authored-by: Otto Winter Co-authored-by: steve Co-authored-by: Otto winter --- CODEOWNERS | 1 + esphome/components/pmsa003i/__init__.py | 0 esphome/components/pmsa003i/pmsa003i.cpp | 100 ++++++++++++++++++++++ esphome/components/pmsa003i/pmsa003i.h | 68 +++++++++++++++ esphome/components/pmsa003i/sensor.py | 104 +++++++++++++++++++++++ tests/test1.yaml | 21 +++++ 6 files changed, 294 insertions(+) create mode 100644 esphome/components/pmsa003i/__init__.py create mode 100644 esphome/components/pmsa003i/pmsa003i.cpp create mode 100644 esphome/components/pmsa003i/pmsa003i.h create mode 100644 esphome/components/pmsa003i/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index f45c9c1db4..1edea18157 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -86,6 +86,7 @@ esphome/components/number/* @esphome/core esphome/components/ota/* @esphome/core esphome/components/output/* @esphome/core esphome/components/pid/* @OttoWinter +esphome/components/pmsa003i/* @sjtrny esphome/components/pn532/* @OttoWinter @jesserockz esphome/components/pn532_i2c/* @OttoWinter @jesserockz esphome/components/pn532_spi/* @OttoWinter @jesserockz diff --git a/esphome/components/pmsa003i/__init__.py b/esphome/components/pmsa003i/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/pmsa003i/pmsa003i.cpp b/esphome/components/pmsa003i/pmsa003i.cpp new file mode 100644 index 0000000000..1396c9f3d4 --- /dev/null +++ b/esphome/components/pmsa003i/pmsa003i.cpp @@ -0,0 +1,100 @@ +#include "pmsa003i.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pmsa003i { + +static const char *const TAG = "pmsa003i"; + +void PMSA003IComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up pmsa003i..."); + + PM25AQIData data; + bool successful_read = this->read_data_(&data); + + if (!successful_read) { + this->mark_failed(); + return; + } +} + +void PMSA003IComponent::dump_config() { LOG_I2C_DEVICE(this); } + +void PMSA003IComponent::update() { + PM25AQIData data; + + bool successful_read = this->read_data_(&data); + + // Update sensors + if (successful_read) { + this->status_clear_warning(); + ESP_LOGV(TAG, "Read success. Updating sensors."); + + if (this->standard_units_) { + if (this->pm_1_0_sensor_ != nullptr) + this->pm_1_0_sensor_->publish_state(data.pm10_standard); + if (this->pm_2_5_sensor_ != nullptr) + this->pm_2_5_sensor_->publish_state(data.pm25_standard); + if (this->pm_10_0_sensor_ != nullptr) + this->pm_10_0_sensor_->publish_state(data.pm100_standard); + } else { + if (this->pm_1_0_sensor_ != nullptr) + this->pm_1_0_sensor_->publish_state(data.pm10_env); + if (this->pm_2_5_sensor_ != nullptr) + this->pm_2_5_sensor_->publish_state(data.pm25_env); + if (this->pm_10_0_sensor_ != nullptr) + this->pm_10_0_sensor_->publish_state(data.pm100_env); + } + + if (this->pmc_0_3_sensor_ != nullptr) + this->pmc_0_3_sensor_->publish_state(data.particles_03um); + if (this->pmc_0_5_sensor_ != nullptr) + this->pmc_0_5_sensor_->publish_state(data.particles_05um); + if (this->pmc_1_0_sensor_ != nullptr) + this->pmc_1_0_sensor_->publish_state(data.particles_10um); + if (this->pmc_2_5_sensor_ != nullptr) + this->pmc_2_5_sensor_->publish_state(data.particles_25um); + if (this->pmc_5_0_sensor_ != nullptr) + this->pmc_5_0_sensor_->publish_state(data.particles_50um); + if (this->pmc_10_0_sensor_ != nullptr) + this->pmc_10_0_sensor_->publish_state(data.particles_100um); + } else { + this->status_set_warning(); + ESP_LOGV(TAG, "Read failure. Skipping update."); + } +} + +bool PMSA003IComponent::read_data_(PM25AQIData *data) { + const uint8_t num_bytes = 32; + uint8_t buffer[num_bytes]; + + this->read_bytes_raw(buffer, num_bytes); + + // https://github.com/adafruit/Adafruit_PM25AQI + + // Check that start byte is correct! + if (buffer[0] != 0x42) { + return false; + } + + // get checksum ready + int16_t sum = 0; + for (uint8_t i = 0; i < 30; i++) { + sum += buffer[i]; + } + + // The data comes in endian'd, this solves it so it works on all platforms + uint16_t buffer_u16[15]; + for (uint8_t i = 0; i < 15; i++) { + buffer_u16[i] = buffer[2 + i * 2 + 1]; + buffer_u16[i] += (buffer[2 + i * 2] << 8); + } + + // put it into a nice struct :) + memcpy((void *) data, (void *) buffer_u16, 30); + + return (sum == data->checksum); +} + +} // namespace pmsa003i +} // namespace esphome diff --git a/esphome/components/pmsa003i/pmsa003i.h b/esphome/components/pmsa003i/pmsa003i.h new file mode 100644 index 0000000000..10176218ed --- /dev/null +++ b/esphome/components/pmsa003i/pmsa003i.h @@ -0,0 +1,68 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace pmsa003i { + +/**! Structure holding Plantower's standard packet **/ +// From https://github.com/adafruit/Adafruit_PM25AQI +struct PM25AQIData { + uint16_t framelen; ///< How long this data chunk is + uint16_t pm10_standard, ///< Standard PM1.0 + pm25_standard, ///< Standard PM2.5 + pm100_standard; ///< Standard PM10.0 + uint16_t pm10_env, ///< Environmental PM1.0 + pm25_env, ///< Environmental PM2.5 + pm100_env; ///< Environmental PM10.0 + uint16_t particles_03um, ///< 0.3um Particle Count + particles_05um, ///< 0.5um Particle Count + particles_10um, ///< 1.0um Particle Count + particles_25um, ///< 2.5um Particle Count + particles_50um, ///< 5.0um Particle Count + particles_100um; ///< 10.0um Particle Count + uint16_t unused; ///< Unused + uint16_t checksum; ///< Packet checksum +}; + +class PMSA003IComponent : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + void update() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + void set_standard_units(bool standard_units) { standard_units_ = standard_units; } + + void set_pm_1_0_sensor(sensor::Sensor *pm_1_0) { pm_1_0_sensor_ = pm_1_0; } + void set_pm_2_5_sensor(sensor::Sensor *pm_2_5) { pm_2_5_sensor_ = pm_2_5; } + void set_pm_10_0_sensor(sensor::Sensor *pm_10_0) { pm_10_0_sensor_ = pm_10_0; } + + void set_pmc_0_3_sensor(sensor::Sensor *pmc_0_3) { pmc_0_3_sensor_ = pmc_0_3; } + void set_pmc_0_5_sensor(sensor::Sensor *pmc_0_5) { pmc_0_5_sensor_ = pmc_0_5; } + void set_pmc_1_0_sensor(sensor::Sensor *pmc_1_0) { pmc_1_0_sensor_ = pmc_1_0; } + void set_pmc_2_5_sensor(sensor::Sensor *pmc_2_5) { pmc_2_5_sensor_ = pmc_2_5; } + void set_pmc_5_0_sensor(sensor::Sensor *pmc_5_0) { pmc_5_0_sensor_ = pmc_5_0; } + void set_pmc_10_0_sensor(sensor::Sensor *pmc_10_0) { pmc_10_0_sensor_ = pmc_10_0; } + + protected: + bool read_data_(PM25AQIData *data); + + bool standard_units_; + + sensor::Sensor *pm_1_0_sensor_{nullptr}; + sensor::Sensor *pm_2_5_sensor_{nullptr}; + sensor::Sensor *pm_10_0_sensor_{nullptr}; + + sensor::Sensor *pmc_0_3_sensor_{nullptr}; + sensor::Sensor *pmc_0_5_sensor_{nullptr}; + sensor::Sensor *pmc_1_0_sensor_{nullptr}; + sensor::Sensor *pmc_2_5_sensor_{nullptr}; + sensor::Sensor *pmc_5_0_sensor_{nullptr}; + sensor::Sensor *pmc_10_0_sensor_{nullptr}; +}; + +} // namespace pmsa003i +} // namespace esphome diff --git a/esphome/components/pmsa003i/sensor.py b/esphome/components/pmsa003i/sensor.py new file mode 100644 index 0000000000..ac26270cfc --- /dev/null +++ b/esphome/components/pmsa003i/sensor.py @@ -0,0 +1,104 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_PM_1_0, + CONF_PM_2_5, + CONF_PM_10_0, + CONF_PMC_0_5, + CONF_PMC_1_0, + CONF_PMC_2_5, + CONF_PMC_10_0, + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + ICON_COUNTER, + DEVICE_CLASS_EMPTY, +) + +CODEOWNERS = ["@sjtrny"] +DEPENDENCIES = ["i2c"] + +pmsa003i_ns = cg.esphome_ns.namespace("pmsa003i") + +PMSA003IComponent = pmsa003i_ns.class_( + "PMSA003IComponent", cg.PollingComponent, i2c.I2CDevice +) + +CONF_STANDARD_UNITS = "standard_units" +UNIT_COUNTS_PER_100ML = "#/0.1L" +CONF_PMC_0_3 = "pmc_0_3" +CONF_PMC_5_0 = "pmc_5_0" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(PMSA003IComponent), + cv.Optional(CONF_STANDARD_UNITS, default=True): cv.boolean, + cv.Optional(CONF_PM_1_0): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 2, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PM_2_5): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 2, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PM_10_0): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 2, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PMC_0_3): sensor.sensor_schema( + UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_PMC_0_5): sensor.sensor_schema( + UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_PMC_1_0): sensor.sensor_schema( + UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_PMC_2_5): sensor.sensor_schema( + UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_PMC_5_0): sensor.sensor_schema( + UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_PMC_10_0): sensor.sensor_schema( + UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x12)) +) + +TYPES = { + CONF_PM_1_0: "set_pm_1_0_sensor", + CONF_PM_2_5: "set_pm_2_5_sensor", + CONF_PM_10_0: "set_pm_10_0_sensor", + CONF_PMC_0_3: "set_pmc_0_3_sensor", + CONF_PMC_0_5: "set_pmc_0_5_sensor", + CONF_PMC_1_0: "set_pmc_1_0_sensor", + CONF_PMC_2_5: "set_pmc_2_5_sensor", + CONF_PMC_5_0: "set_pmc_5_0_sensor", + CONF_PMC_10_0: "set_pmc_10_0_sensor", +} + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield i2c.register_i2c_device(var, config) + + cg.add(var.set_standard_units(config[CONF_STANDARD_UNITS])) + + for key, funcName in TYPES.items(): + + if key in config: + sens = yield sensor.new_sensor(config[key]) + cg.add(getattr(var, funcName)(sens)) diff --git a/tests/test1.yaml b/tests/test1.yaml index 0e81b19998..2bc767c885 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -675,6 +675,27 @@ sensor: name: 'Outside Pressure' address: 0x77 update_interval: 15s + - platform: pmsa003i + pm_1_0: + name: "PMSA003i PM1.0" + pm_2_5: + name: "PMSA003i PM2.5" + pm_10_0: + name: "PMSA003i PM10.0" + pmc_0_3: + name: "PMSA003i PMC <0.3µm" + pmc_0_5: + name: "PMSA003i PMC <0.5µm" + pmc_1_0: + name: "PMSA003i PMC <1µm" + pmc_2_5: + name: "PMSA003i PMC <2.5µm" + pmc_5_0: + name: "PMSA003i PMC <5µm" + pmc_10_0: + name: "PMSA003i PMC <10µm" + address: 0x12 + standard_units: True - platform: pulse_counter name: 'Pulse Counter' pin: GPIO12 From f94c221a9ac8b81bd801071203f1a5bf06c5c9ab Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Tue, 10 Aug 2021 11:10:52 +0200 Subject: [PATCH 1170/1841] Increase task wdt timeout for ESP32/ESP32-C3 (#2096) --- esphome/core/application.cpp | 8 ++------ esphome/core/application.h | 2 ++ esphome/core/application_esp32.cpp | 21 +++++++++++++++++++++ esphome/core/application_esp8266.cpp | 12 ++++++++++++ 4 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 esphome/core/application_esp32.cpp create mode 100644 esphome/core/application_esp8266.cpp diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 17a2725de5..1a3158e4ce 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -53,6 +53,7 @@ void Application::setup() { } this->app_state_ = new_app_state; yield(); + this->feed_wdt(); } while (!component->can_proceed()); } @@ -117,12 +118,7 @@ void ICACHE_RAM_ATTR HOT Application::feed_wdt() { static uint32_t LAST_FEED = 0; uint32_t now = millis(); if (now - LAST_FEED > 3) { -#ifdef ARDUINO_ARCH_ESP8266 - ESP.wdtFeed(); -#endif -#ifdef ARDUINO_ARCH_ESP32 - yield(); -#endif + this->feed_wdt_arch_(); LAST_FEED = now; #ifdef USE_STATUS_LED if (status_led::global_status_led != nullptr) { diff --git a/esphome/core/application.h b/esphome/core/application.h index edbfbd130b..e5f686a320 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -250,6 +250,8 @@ class Application { void calculate_looping_components_(); + void feed_wdt_arch_(); + std::vector components_{}; std::vector looping_components_{}; diff --git a/esphome/core/application_esp32.cpp b/esphome/core/application_esp32.cpp new file mode 100644 index 0000000000..9f084428bb --- /dev/null +++ b/esphome/core/application_esp32.cpp @@ -0,0 +1,21 @@ +#include "esphome/core/application.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { + +static const char *const TAG = "app_esp32"; + +void ICACHE_RAM_ATTR HOT Application::feed_wdt_arch_() { +#if CONFIG_ARDUINO_RUNNING_CORE == 0 +#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 + // ESP32 uses "Task Watchdog" which is hooked to the FreeRTOS idle task. + // To cause the Watchdog to be triggered we need to put the current task + // to sleep to get the idle task scheduled. + delay(1); +#endif +#endif +} + +} // namespace esphome +#endif diff --git a/esphome/core/application_esp8266.cpp b/esphome/core/application_esp8266.cpp new file mode 100644 index 0000000000..95139ca112 --- /dev/null +++ b/esphome/core/application_esp8266.cpp @@ -0,0 +1,12 @@ +#include "esphome/core/application.h" + +#ifdef ARDUINO_ARCH_ESP8266 + +namespace esphome { + +static const char *const TAG = "app_esp8266"; + +void ICACHE_RAM_ATTR HOT Application::feed_wdt_arch_() { ESP.wdtFeed(); } + +} // namespace esphome +#endif From 854f4a88968a326604ddfa4b8f9ae2af98481c6e Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 10 Aug 2021 11:14:04 +0200 Subject: [PATCH 1171/1841] Format dev temp idedata (#2142) --- script/helpers.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/script/helpers.py b/script/helpers.py index 8bf8eec5cc..7200078822 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -113,15 +113,12 @@ def load_idedata(environment): changed = False if not changed: - text = temp_idedata.read_text() - else: - stdout = subprocess.check_output( - ["pio", "run", "-t", "idedata", "-e", environment] - ) - match = re.search(r'{\s*".*}', stdout.decode("utf-8")) - text = match.group() + return json.loads(temp_idedata.read_text()) - temp_idedata.parent.mkdir(exist_ok=True) - temp_idedata.write_text(text) + stdout = subprocess.check_output(["pio", "run", "-t", "idedata", "-e", environment]) + match = re.search(r'{\s*".*}', stdout.decode("utf-8")) + data = json.loads(match.group()) - return json.loads(text) + temp_idedata.parent.mkdir(exist_ok=True) + temp_idedata.write_text(json.dumps(data, indent=2) + "\n") + return data From d258e06fd79cdcc9f0628ac170cd3c622deb40fa Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 10 Aug 2021 21:28:56 +1200 Subject: [PATCH 1172/1841] Add rgbct and color_temperature light platforms (#2138) Co-authored-by: Oxan van Leeuwen --- CODEOWNERS | 2 + .../components/color_temperature/__init__.py | 0 .../color_temperature/ct_light_output.h | 38 ++++++++++++ esphome/components/color_temperature/light.py | 42 +++++++++++++ esphome/components/cwww/light.py | 3 +- esphome/components/light/light_color_values.h | 20 +++++++ esphome/components/light/light_state.cpp | 12 +++- esphome/components/light/light_state.h | 5 ++ esphome/components/rgbct/__init__.py | 0 esphome/components/rgbct/light.py | 59 +++++++++++++++++++ esphome/components/rgbct/rgbct_light_output.h | 58 ++++++++++++++++++ esphome/components/rgbww/light.py | 2 +- esphome/const.py | 1 + tests/test1.yaml | 16 +++++ 14 files changed, 254 insertions(+), 4 deletions(-) create mode 100644 esphome/components/color_temperature/__init__.py create mode 100644 esphome/components/color_temperature/ct_light_output.h create mode 100644 esphome/components/color_temperature/light.py create mode 100644 esphome/components/rgbct/__init__.py create mode 100644 esphome/components/rgbct/light.py create mode 100644 esphome/components/rgbct/rgbct_light_output.h diff --git a/CODEOWNERS b/CODEOWNERS index 1edea18157..714ef58538 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -29,6 +29,7 @@ esphome/components/canbus/* @danielschramm @mvturnho esphome/components/captive_portal/* @OttoWinter esphome/components/climate/* @esphome/core esphome/components/climate_ir/* @glmnet +esphome/components/color_temperature/* @jesserockz esphome/components/coolix/* @glmnet esphome/components/cover/* @esphome/core esphome/components/cs5460a/* @balrog-kun @@ -98,6 +99,7 @@ esphome/components/rc522_i2c/* @glmnet esphome/components/rc522_spi/* @glmnet esphome/components/restart/* @esphome/core esphome/components/rf_bridge/* @jesserockz +esphome/components/rgbct/* @jesserockz esphome/components/rtttl/* @glmnet esphome/components/script/* @esphome/core esphome/components/sdm_meter/* @jesserockz @polyfaces diff --git a/esphome/components/color_temperature/__init__.py b/esphome/components/color_temperature/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/color_temperature/ct_light_output.h b/esphome/components/color_temperature/ct_light_output.h new file mode 100644 index 0000000000..4ff86c8b80 --- /dev/null +++ b/esphome/components/color_temperature/ct_light_output.h @@ -0,0 +1,38 @@ +#pragma once + +#include "esphome/components/light/light_output.h" +#include "esphome/components/output/float_output.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace color_temperature { + +class CTLightOutput : public light::LightOutput { + public: + void set_color_temperature(output::FloatOutput *color_temperature) { color_temperature_ = color_temperature; } + void set_brightness(output::FloatOutput *brightness) { brightness_ = brightness; } + void set_cold_white_temperature(float cold_white_temperature) { cold_white_temperature_ = cold_white_temperature; } + void set_warm_white_temperature(float warm_white_temperature) { warm_white_temperature_ = warm_white_temperature; } + light::LightTraits get_traits() override { + auto traits = light::LightTraits(); + traits.set_supported_color_modes({light::ColorMode::COLOR_TEMPERATURE}); + traits.set_min_mireds(this->cold_white_temperature_); + traits.set_max_mireds(this->warm_white_temperature_); + return traits; + } + void write_state(light::LightState *state) override { + float color_temperature, brightness; + state->current_values_as_ct(&color_temperature, &brightness); + this->color_temperature_->set_level(color_temperature); + this->brightness_->set_level(brightness); + } + + protected: + output::FloatOutput *color_temperature_; + output::FloatOutput *brightness_; + float cold_white_temperature_; + float warm_white_temperature_; +}; + +} // namespace color_temperature +} // namespace esphome diff --git a/esphome/components/color_temperature/light.py b/esphome/components/color_temperature/light.py new file mode 100644 index 0000000000..3e7a0e73ae --- /dev/null +++ b/esphome/components/color_temperature/light.py @@ -0,0 +1,42 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import light, output +from esphome.const import ( + CONF_BRIGHTNESS, + CONF_COLOR_TEMPERATURE, + CONF_OUTPUT_ID, + CONF_COLD_WHITE_COLOR_TEMPERATURE, + CONF_WARM_WHITE_COLOR_TEMPERATURE, +) + +CODEOWNERS = ["@jesserockz"] + +color_temperature_ns = cg.esphome_ns.namespace("color_temperature") +CTLightOutput = color_temperature_ns.class_("CTLightOutput", light.LightOutput) + +CONFIG_SCHEMA = cv.All( + light.RGB_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(CTLightOutput), + cv.Required(CONF_COLOR_TEMPERATURE): cv.use_id(output.FloatOutput), + cv.Required(CONF_BRIGHTNESS): cv.use_id(output.FloatOutput), + cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, + cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, + } + ), + light.validate_color_temperature_channels, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + await light.register_light(var, config) + + color_temperature = await cg.get_variable(config[CONF_COLOR_TEMPERATURE]) + cg.add(var.set_color_temperature(color_temperature)) + + brightness = await cg.get_variable(config[CONF_BRIGHTNESS]) + cg.add(var.set_brightness(brightness)) + + cg.add(var.set_cold_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE])) + cg.add(var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE])) diff --git a/esphome/components/cwww/light.py b/esphome/components/cwww/light.py index 734f9aa1e7..fc204b2f3b 100644 --- a/esphome/components/cwww/light.py +++ b/esphome/components/cwww/light.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import light, output from esphome.const import ( + CONF_CONSTANT_BRIGHTNESS, CONF_OUTPUT_ID, CONF_COLD_WHITE, CONF_WARM_WHITE, @@ -12,8 +13,6 @@ from esphome.const import ( cwww_ns = cg.esphome_ns.namespace("cwww") CWWWLightOutput = cwww_ns.class_("CWWWLightOutput", light.LightOutput) -CONF_CONSTANT_BRIGHTNESS = "constant_brightness" - CONFIG_SCHEMA = cv.All( light.RGB_LIGHT_SCHEMA.extend( { diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index c696ed8516..dd74c396c6 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -163,6 +163,13 @@ class LightColorValues { this->as_cwww(cold_white, warm_white, gamma, constant_brightness); } + /// Convert these light color values to an RGB+CT+BR representation with the given parameters. + void as_rgbct(float color_temperature_cw, float color_temperature_ww, float *red, float *green, float *blue, + float *color_temperature, float *white_brightness, float gamma = 0) const { + this->as_rgb(red, green, blue, gamma); + this->as_ct(color_temperature_cw, color_temperature_ww, color_temperature, white_brightness, gamma); + } + /// Convert these light color values to an CWWW representation with the given parameters. void as_cwww(float *cold_white, float *warm_white, float gamma = 0, bool constant_brightness = false) const { if (this->color_mode_ & ColorCapability::COLD_WARM_WHITE) { @@ -187,6 +194,19 @@ class LightColorValues { } } + /// Convert these light color values to a CT+BR representation with the given parameters. + void as_ct(float color_temperature_cw, float color_temperature_ww, float *color_temperature, float *white_brightness, + float gamma = 0) const { + const float white_level = this->color_mode_ & ColorCapability::RGB ? this->white_ : 1; + if (this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) { + *color_temperature = + (this->color_temperature_ - color_temperature_cw) / (color_temperature_ww - color_temperature_cw); + *white_brightness = gamma_correct(this->state_ * this->brightness_ * white_level, gamma); + } else { // Probably wont get here but put this here anyway. + *white_brightness = 0; + } + } + /// Compare this LightColorValues to rhs, return true if and only if all attributes match. bool operator==(const LightColorValues &rhs) const { return color_mode_ == rhs.color_mode_ && state_ == rhs.state_ && brightness_ == rhs.brightness_ && diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index d24b40e68e..3c4a10c88e 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -175,13 +175,23 @@ void LightState::current_values_as_rgbw(float *red, float *green, float *blue, f } void LightState::current_values_as_rgbww(float *red, float *green, float *blue, float *cold_white, float *warm_white, bool constant_brightness) { - auto traits = this->get_traits(); this->current_values.as_rgbww(red, green, blue, cold_white, warm_white, this->gamma_correct_, constant_brightness); } +void LightState::current_values_as_rgbct(float *red, float *green, float *blue, float *color_temperature, + float *white_brightness) { + auto traits = this->get_traits(); + this->current_values.as_rgbct(traits.get_min_mireds(), traits.get_max_mireds(), red, green, blue, color_temperature, + white_brightness, this->gamma_correct_); +} void LightState::current_values_as_cwww(float *cold_white, float *warm_white, bool constant_brightness) { auto traits = this->get_traits(); this->current_values.as_cwww(cold_white, warm_white, this->gamma_correct_, constant_brightness); } +void LightState::current_values_as_ct(float *color_temperature, float *white_brightness) { + auto traits = this->get_traits(); + this->current_values.as_ct(traits.get_min_mireds(), traits.get_max_mireds(), color_temperature, white_brightness, + this->gamma_correct_); +} void LightState::start_effect_(uint32_t effect_index) { this->stop_effect_(); diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index b0c60c625a..23527e8a47 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -128,8 +128,13 @@ class LightState : public Nameable, public Component { void current_values_as_rgbww(float *red, float *green, float *blue, float *cold_white, float *warm_white, bool constant_brightness = false); + void current_values_as_rgbct(float *red, float *green, float *blue, float *color_temperature, + float *white_brightness); + void current_values_as_cwww(float *cold_white, float *warm_white, bool constant_brightness = false); + void current_values_as_ct(float *color_temperature, float *white_brightness); + protected: friend LightOutput; friend LightCall; diff --git a/esphome/components/rgbct/__init__.py b/esphome/components/rgbct/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/rgbct/light.py b/esphome/components/rgbct/light.py new file mode 100644 index 0000000000..e525c207c7 --- /dev/null +++ b/esphome/components/rgbct/light.py @@ -0,0 +1,59 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import light, output +from esphome.const import ( + CONF_BLUE, + CONF_COLOR_TEMPERATURE, + CONF_GREEN, + CONF_RED, + CONF_OUTPUT_ID, + CONF_COLD_WHITE_COLOR_TEMPERATURE, + CONF_WARM_WHITE_COLOR_TEMPERATURE, +) + +CODEOWNERS = ["@jesserockz"] + +rgbct_ns = cg.esphome_ns.namespace("rgbct") +RGBCTLightOutput = rgbct_ns.class_("RGBCTLightOutput", light.LightOutput) + +CONF_COLOR_INTERLOCK = "color_interlock" +CONF_WHITE_BRIGHTNESS = "white_brightness" + +CONFIG_SCHEMA = cv.All( + light.RGB_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(RGBCTLightOutput), + cv.Required(CONF_RED): cv.use_id(output.FloatOutput), + cv.Required(CONF_GREEN): cv.use_id(output.FloatOutput), + cv.Required(CONF_BLUE): cv.use_id(output.FloatOutput), + cv.Required(CONF_COLOR_TEMPERATURE): cv.use_id(output.FloatOutput), + cv.Required(CONF_WHITE_BRIGHTNESS): cv.use_id(output.FloatOutput), + cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, + cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, + cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean, + } + ), + light.validate_color_temperature_channels, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + await light.register_light(var, config) + + red = await cg.get_variable(config[CONF_RED]) + cg.add(var.set_red(red)) + green = await cg.get_variable(config[CONF_GREEN]) + cg.add(var.set_green(green)) + blue = await cg.get_variable(config[CONF_BLUE]) + cg.add(var.set_blue(blue)) + + color_temp = await cg.get_variable(config[CONF_COLOR_TEMPERATURE]) + cg.add(var.set_color_temperature(color_temp)) + white_brightness = await cg.get_variable(config[CONF_WHITE_BRIGHTNESS]) + cg.add(var.set_white_brightness(white_brightness)) + + cg.add(var.set_cold_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE])) + cg.add(var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE])) + + cg.add(var.set_color_interlock(config[CONF_COLOR_INTERLOCK])) diff --git a/esphome/components/rgbct/rgbct_light_output.h b/esphome/components/rgbct/rgbct_light_output.h new file mode 100644 index 0000000000..9257d67cd1 --- /dev/null +++ b/esphome/components/rgbct/rgbct_light_output.h @@ -0,0 +1,58 @@ +#pragma once + +#include "esphome/components/light/color_mode.h" +#include "esphome/components/light/light_output.h" +#include "esphome/components/output/float_output.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace rgbct { + +class RGBCTLightOutput : public light::LightOutput { + public: + void set_red(output::FloatOutput *red) { red_ = red; } + void set_green(output::FloatOutput *green) { green_ = green; } + void set_blue(output::FloatOutput *blue) { blue_ = blue; } + + void set_color_temperature(output::FloatOutput *color_temperature) { color_temperature_ = color_temperature; } + void set_white_brightness(output::FloatOutput *white_brightness) { white_brightness_ = white_brightness; } + + void set_cold_white_temperature(float cold_white_temperature) { cold_white_temperature_ = cold_white_temperature; } + void set_warm_white_temperature(float warm_white_temperature) { warm_white_temperature_ = warm_white_temperature; } + void set_color_interlock(bool color_interlock) { color_interlock_ = color_interlock; } + + light::LightTraits get_traits() override { + auto traits = light::LightTraits(); + if (this->color_interlock_) + traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::COLOR_TEMPERATURE}); + else + traits.set_supported_color_modes({light::ColorMode::RGB_COLOR_TEMPERATURE, light::ColorMode::COLOR_TEMPERATURE}); + traits.set_min_mireds(this->cold_white_temperature_); + traits.set_max_mireds(this->warm_white_temperature_); + return traits; + } + void write_state(light::LightState *state) override { + float red, green, blue, color_temperature, white_brightness; + + state->current_values_as_rgbct(&red, &green, &blue, &color_temperature, &white_brightness); + + this->red_->set_level(red); + this->green_->set_level(green); + this->blue_->set_level(blue); + this->color_temperature_->set_level(color_temperature); + this->white_brightness_->set_level(white_brightness); + } + + protected: + output::FloatOutput *red_; + output::FloatOutput *green_; + output::FloatOutput *blue_; + output::FloatOutput *color_temperature_; + output::FloatOutput *white_brightness_; + float cold_white_temperature_; + float warm_white_temperature_; + bool color_interlock_{true}; +}; + +} // namespace rgbct +} // namespace esphome diff --git a/esphome/components/rgbww/light.py b/esphome/components/rgbww/light.py index 37bc668215..c0ce85e267 100644 --- a/esphome/components/rgbww/light.py +++ b/esphome/components/rgbww/light.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome.components import light, output from esphome.const import ( CONF_BLUE, + CONF_CONSTANT_BRIGHTNESS, CONF_GREEN, CONF_RED, CONF_OUTPUT_ID, @@ -15,7 +16,6 @@ from esphome.const import ( rgbww_ns = cg.esphome_ns.namespace("rgbww") RGBWWLightOutput = rgbww_ns.class_("RGBWWLightOutput", light.LightOutput) -CONF_CONSTANT_BRIGHTNESS = "constant_brightness" CONF_COLOR_INTERLOCK = "color_interlock" CONFIG_SCHEMA = cv.All( diff --git a/esphome/const.py b/esphome/const.py index a2f49fe3ca..9f3ce13b46 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -134,6 +134,7 @@ CONF_COMPONENTS = "components" CONF_CONDITION = "condition" CONF_CONDITION_ID = "condition_id" CONF_CONDUCTIVITY = "conductivity" +CONF_CONSTANT_BRIGHTNESS = "constant_brightness" CONF_CONTRAST = "contrast" CONF_COOL_ACTION = "cool_action" CONF_COOL_DEADBAND = "cool_deadband" diff --git a/tests/test1.yaml b/tests/test1.yaml index 2bc767c885..9b7139de3a 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1429,6 +1429,16 @@ light: cold_white_color_temperature: 153 mireds warm_white_color_temperature: 500 mireds color_interlock: true + - platform: rgbct + name: 'Living Room Lights 2' + red: pca_3 + green: pca_4 + blue: pca_5 + color_temperature: pca_6 + white_brightness: pca_6 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true - platform: cwww name: 'Living Room Lights 2' cold_white: pca_6 @@ -1436,6 +1446,12 @@ light: cold_white_color_temperature: 153 mireds warm_white_color_temperature: 500 mireds constant_brightness: true + - platform: color_temperature + name: 'Living Room Lights 2' + color_temperature: pca_6 + brightness: pca_6 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds - platform: fastled_clockless id: addr1 chipset: WS2811 From 1771e673d2169ad05041b4c3000c376aa95d0b8c Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 10 Aug 2021 13:14:42 +0100 Subject: [PATCH 1173/1841] Warn if underscore character is used in hostname (#2079) * Prevent underscore character being used in 'name'. * Restrict underscores in hostnames, not all names. * Use hostname validator for node name. * Allow underscore in hostname but warn once. * Add renaming instructions link to warning. * Point underscore warning to FAQ section Co-authored-by: Otto Winter Co-authored-by: Otto Winter --- esphome/config_validation.py | 13 +++++++++++-- esphome/core/config.py | 2 +- tests/unit_tests/test_config_validation.py | 22 ++++++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 84452839ad..3aebca81b8 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -891,11 +891,20 @@ def validate_bytes(value): def hostname(value): value = string(value) + warned_underscore = False if len(value) > 63: raise Invalid("Hostnames can only be 63 characters long") for c in value: - if not (c.isalnum() or c in "_-"): - raise Invalid("Hostname can only have alphanumeric characters and _ or -") + if not (c.isalnum() or c in "-_"): + raise Invalid("Hostname can only have alphanumeric characters and -") + if c in "_" and not warned_underscore: + _LOGGER.warning( + "'%s': Using the '_' (underscore) character in the hostname is discouraged " + "as it can cause problems with some DHCP and local name services. " + "For more information, see https://esphome.io/guides/faq.html#why-shouldn-t-i-use-underscores-in-my-device-name", + value, + ) + warned_underscore = True return value diff --git a/esphome/core/config.py b/esphome/core/config.py index 97748d71d8..45b8809f1e 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -158,7 +158,7 @@ def valid_project_name(value: str): CONFIG_SCHEMA = cv.Schema( { - cv.Required(CONF_NAME): cv.valid_name, + cv.Required(CONF_NAME): cv.hostname, cv.Required(CONF_PLATFORM): cv.one_of("ESP8266", "ESP32", upper=True), cv.Required(CONF_BOARD): validate_board, cv.Optional(CONF_COMMENT): cv.string, diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index 16cfb16e94..e34c7064fa 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -40,6 +40,28 @@ def test_valid_name__invalid(value): config_validation.valid_name(value) +@pytest.mark.parametrize("value", ("foo", "bar123", "foo-bar")) +def test_hostname__valid(value): + actual = config_validation.hostname(value) + + assert actual == value + + +@pytest.mark.parametrize("value", ("foo bar", "foobar ", "foo#bar")) +def test_hostname__invalid(value): + with pytest.raises(Invalid): + config_validation.hostname(value) + + +def test_hostname__warning(caplog): + actual = config_validation.hostname("foo_bar") + assert actual == "foo_bar" + assert ( + "Using the '_' (underscore) character in the hostname is discouraged" + in caplog.text + ) + + @given(one_of(integers(), text())) def test_string__valid(value): actual = config_validation.string(value) From 6144ce1fe04d8d6c3e9664d303ed750e35e9bde5 Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Tue, 10 Aug 2021 14:44:31 -0500 Subject: [PATCH 1174/1841] Break the Tuya set_datapoint_value method into separate methods per datapoint type (#2059) Co-authored-by: Chris Nussbaum Co-authored-by: Trevor North --- .../components/tuya/climate/tuya_climate.cpp | 6 +- esphome/components/tuya/fan/tuya_fan.cpp | 8 +- esphome/components/tuya/light/tuya_light.cpp | 12 +-- .../components/tuya/switch/tuya_switch.cpp | 2 +- esphome/components/tuya/tuya.cpp | 85 +++++++++++++------ esphome/components/tuya/tuya.h | 12 ++- 6 files changed, 83 insertions(+), 42 deletions(-) diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index 5f214d3bfe..a4ce06476d 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -54,14 +54,14 @@ void TuyaClimate::control(const climate::ClimateCall &call) { if (call.get_mode().has_value()) { const bool switch_state = *call.get_mode() != climate::CLIMATE_MODE_OFF; ESP_LOGV(TAG, "Setting switch: %s", ONOFF(switch_state)); - this->parent_->set_datapoint_value(*this->switch_id_, switch_state); + this->parent_->set_boolean_datapoint_value(*this->switch_id_, switch_state); } if (call.get_target_temperature().has_value()) { const float target_temperature = *call.get_target_temperature(); ESP_LOGV(TAG, "Setting target temperature: %.1f", target_temperature); - this->parent_->set_datapoint_value(*this->target_temperature_id_, - (int) (target_temperature / this->target_temperature_multiplier_)); + this->parent_->set_integer_datapoint_value(*this->target_temperature_id_, + (int) (target_temperature / this->target_temperature_multiplier_)); } } diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp index ed92713e52..8738b7f4a0 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -67,20 +67,20 @@ void TuyaFan::dump_config() { void TuyaFan::write_state() { if (this->switch_id_.has_value()) { ESP_LOGV(TAG, "Setting switch: %s", ONOFF(this->fan_->state)); - this->parent_->set_datapoint_value(*this->switch_id_, this->fan_->state); + this->parent_->set_boolean_datapoint_value(*this->switch_id_, this->fan_->state); } if (this->oscillation_id_.has_value()) { ESP_LOGV(TAG, "Setting oscillating: %s", ONOFF(this->fan_->oscillating)); - this->parent_->set_datapoint_value(*this->oscillation_id_, this->fan_->oscillating); + this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, this->fan_->oscillating); } if (this->direction_id_.has_value()) { bool enable = this->fan_->direction == fan::FAN_DIRECTION_REVERSE; ESP_LOGV(TAG, "Setting reverse direction: %s", ONOFF(enable)); - this->parent_->set_datapoint_value(*this->direction_id_, enable); + this->parent_->set_boolean_datapoint_value(*this->direction_id_, enable); } if (this->speed_id_.has_value()) { ESP_LOGV(TAG, "Setting speed: %d", this->fan_->speed); - this->parent_->set_datapoint_value(*this->speed_id_, this->fan_->speed - 1); + this->parent_->set_integer_datapoint_value(*this->speed_id_, this->fan_->speed - 1); } } diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index e0ca026248..8ad78aacea 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -31,7 +31,7 @@ void TuyaLight::setup() { }); } if (min_value_datapoint_id_.has_value()) { - parent_->set_datapoint_value(*this->min_value_datapoint_id_, this->min_value_); + parent_->set_integer_datapoint_value(*this->min_value_datapoint_id_, this->min_value_); } } @@ -66,9 +66,9 @@ void TuyaLight::write_state(light::LightState *state) { if (brightness == 0.0f) { // turning off, first try via switch (if exists), then dimmer if (switch_id_.has_value()) { - parent_->set_datapoint_value(*this->switch_id_, false); + parent_->set_boolean_datapoint_value(*this->switch_id_, false); } else if (dimmer_id_.has_value()) { - parent_->set_datapoint_value(*this->dimmer_id_, 0); + parent_->set_integer_datapoint_value(*this->dimmer_id_, 0); } return; } @@ -78,17 +78,17 @@ void TuyaLight::write_state(light::LightState *state) { static_cast(this->color_temperature_max_value_ * (state->current_values.get_color_temperature() - this->cold_white_temperature_) / (this->warm_white_temperature_ - this->cold_white_temperature_)); - parent_->set_datapoint_value(*this->color_temperature_id_, color_temp_int); + parent_->set_integer_datapoint_value(*this->color_temperature_id_, color_temp_int); } auto brightness_int = static_cast(brightness * this->max_value_); brightness_int = std::max(brightness_int, this->min_value_); if (this->dimmer_id_.has_value()) { - parent_->set_datapoint_value(*this->dimmer_id_, brightness_int); + parent_->set_integer_datapoint_value(*this->dimmer_id_, brightness_int); } if (this->switch_id_.has_value()) { - parent_->set_datapoint_value(*this->switch_id_, true); + parent_->set_boolean_datapoint_value(*this->switch_id_, true); } } diff --git a/esphome/components/tuya/switch/tuya_switch.cpp b/esphome/components/tuya/switch/tuya_switch.cpp index 8cd09fb01c..cbd794b001 100644 --- a/esphome/components/tuya/switch/tuya_switch.cpp +++ b/esphome/components/tuya/switch/tuya_switch.cpp @@ -15,7 +15,7 @@ void TuyaSwitch::setup() { void TuyaSwitch::write_state(bool state) { ESP_LOGV(TAG, "Setting switch %u: %s", this->switch_id_, ONOFF(state)); - this->parent_->set_datapoint_value(this->switch_id_, state); + this->parent_->set_boolean_datapoint_value(this->switch_id_, state); this->publish_state(state); } diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 9e036feda9..bbab4f04ce 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -437,42 +437,38 @@ void Tuya::send_local_time_() { } #endif -void Tuya::set_datapoint_value(uint8_t datapoint_id, uint32_t value) { - ESP_LOGD(TAG, "Setting datapoint %u to %u", datapoint_id, value); +void Tuya::set_raw_datapoint_value(uint8_t datapoint_id, const std::vector &value) { + ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, hexencode(value).c_str()); optional datapoint = this->get_datapoint_(datapoint_id); if (!datapoint.has_value()) { - ESP_LOGE(TAG, "Attempt to set unknown datapoint %u", datapoint_id); + ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); + } else if (datapoint->type != TuyaDatapointType::RAW) { + ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); return; - } - if (datapoint->value_uint == value) { + } else if (datapoint->value_raw == value) { ESP_LOGV(TAG, "Not sending unchanged value"); return; } - - std::vector data; - switch (datapoint->len) { - case 4: - data.push_back(value >> 24); - data.push_back(value >> 16); - case 2: - data.push_back(value >> 8); - case 1: - data.push_back(value >> 0); - break; - default: - ESP_LOGE(TAG, "Unexpected datapoint length %zu", datapoint->len); - return; - } - this->send_datapoint_command_(datapoint->id, datapoint->type, data); + this->send_datapoint_command_(datapoint_id, TuyaDatapointType::RAW, value); } -void Tuya::set_datapoint_value(uint8_t datapoint_id, const std::string &value) { +void Tuya::set_boolean_datapoint_value(uint8_t datapoint_id, bool value) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1); +} + +void Tuya::set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4); +} + +void Tuya::set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) { ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, value.c_str()); optional datapoint = this->get_datapoint_(datapoint_id); if (!datapoint.has_value()) { - ESP_LOGE(TAG, "Attempt to set unknown datapoint %u", datapoint_id); - } - if (datapoint->value_string == value) { + ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); + } else if (datapoint->type != TuyaDatapointType::STRING) { + ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); + return; + } else if (datapoint->value_string == value) { ESP_LOGV(TAG, "Not sending unchanged value"); return; } @@ -483,6 +479,14 @@ void Tuya::set_datapoint_value(uint8_t datapoint_id, const std::string &value) { this->send_datapoint_command_(datapoint->id, datapoint->type, data); } +void Tuya::set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1); +} + +void Tuya::set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length); +} + optional Tuya::get_datapoint_(uint8_t datapoint_id) { for (auto &datapoint : this->datapoints_) if (datapoint.id == datapoint_id) @@ -490,6 +494,37 @@ optional Tuya::get_datapoint_(uint8_t datapoint_id) { return {}; } +void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, const uint32_t value, + uint8_t length) { + ESP_LOGD(TAG, "Setting datapoint %u to %u", datapoint_id, value); + optional datapoint = this->get_datapoint_(datapoint_id); + if (!datapoint.has_value()) { + ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); + } else if (datapoint->type != datapoint_type) { + ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); + return; + } else if (datapoint->value_uint == value) { + ESP_LOGV(TAG, "Not sending unchanged value"); + return; + } + + std::vector data; + switch (length) { + case 4: + data.push_back(value >> 24); + data.push_back(value >> 16); + case 2: + data.push_back(value >> 8); + case 1: + data.push_back(value >> 0); + break; + default: + ESP_LOGE(TAG, "Unexpected datapoint length %u", length); + return; + } + this->send_datapoint_command_(datapoint_id, datapoint_type, data); +} + void Tuya::send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector data) { std::vector buffer; buffer.push_back(datapoint_id); diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 7ce4be4315..785399502b 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -17,7 +17,7 @@ enum class TuyaDatapointType : uint8_t { INTEGER = 0x02, // 4 byte STRING = 0x03, // variable length ENUM = 0x04, // 1 byte - BITMASK = 0x05, // 2 bytes + BITMASK = 0x05, // 1/2/4 bytes }; struct TuyaDatapoint { @@ -75,8 +75,12 @@ class Tuya : public Component, public uart::UARTDevice { void loop() override; void dump_config() override; void register_listener(uint8_t datapoint_id, const std::function &func); - void set_datapoint_value(uint8_t datapoint_id, uint32_t value); - void set_datapoint_value(uint8_t datapoint_id, const std::string &value); + void set_raw_datapoint_value(uint8_t datapoint_id, const std::vector &value); + void set_boolean_datapoint_value(uint8_t datapoint_id, bool value); + void set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value); + void set_string_datapoint_value(uint8_t datapoint_id, const std::string &value); + void set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value); + void set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length); #ifdef USE_TIME void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; } #endif @@ -95,6 +99,8 @@ class Tuya : public Component, public uart::UARTDevice { void process_command_queue_(); void send_command_(const TuyaCommand &command); void send_empty_command_(TuyaCommandType command); + void set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, uint32_t value, + uint8_t length); void send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector data); void send_wifi_status_(); From d3375193a9106e9da0ca739179f4e16a7b7bb3d3 Mon Sep 17 00:00:00 2001 From: Andreas Hergert <36455093+andreashergert1984@users.noreply.github.com> Date: Tue, 10 Aug 2021 21:48:32 +0200 Subject: [PATCH 1175/1841] Feature pipsolar anh (#1664) Co-authored-by: Andreas Hergert Co-authored-by: Otto Winter Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/pipsolar/__init__.py | 32 + .../pipsolar/binary_sensor/__init__.py | 144 +++ .../components/pipsolar/output/__init__.py | 106 ++ .../pipsolar/output/pipsolar_output.cpp | 22 + .../pipsolar/output/pipsolar_output.h | 40 + esphome/components/pipsolar/pipsolar.cpp | 922 ++++++++++++++++++ esphome/components/pipsolar/pipsolar.h | 223 +++++ .../components/pipsolar/sensor/__init__.py | 220 +++++ .../components/pipsolar/switch/__init__.py | 60 ++ .../pipsolar/switch/pipsolar_switch.cpp | 24 + .../pipsolar/switch/pipsolar_switch.h | 25 + .../pipsolar/text_sensor/__init__.py | 52 + .../text_sensor/pipsolar_textsensor.cpp | 13 + .../text_sensor/pipsolar_textsensor.h | 20 + tests/test4.yaml | 228 +++++ 16 files changed, 2132 insertions(+) create mode 100644 esphome/components/pipsolar/__init__.py create mode 100644 esphome/components/pipsolar/binary_sensor/__init__.py create mode 100644 esphome/components/pipsolar/output/__init__.py create mode 100644 esphome/components/pipsolar/output/pipsolar_output.cpp create mode 100644 esphome/components/pipsolar/output/pipsolar_output.h create mode 100644 esphome/components/pipsolar/pipsolar.cpp create mode 100644 esphome/components/pipsolar/pipsolar.h create mode 100644 esphome/components/pipsolar/sensor/__init__.py create mode 100644 esphome/components/pipsolar/switch/__init__.py create mode 100644 esphome/components/pipsolar/switch/pipsolar_switch.cpp create mode 100644 esphome/components/pipsolar/switch/pipsolar_switch.h create mode 100644 esphome/components/pipsolar/text_sensor/__init__.py create mode 100644 esphome/components/pipsolar/text_sensor/pipsolar_textsensor.cpp create mode 100644 esphome/components/pipsolar/text_sensor/pipsolar_textsensor.h diff --git a/CODEOWNERS b/CODEOWNERS index 714ef58538..605222df63 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -87,6 +87,7 @@ esphome/components/number/* @esphome/core esphome/components/ota/* @esphome/core esphome/components/output/* @esphome/core esphome/components/pid/* @OttoWinter +esphome/components/pipsolar/* @andreashergert1984 esphome/components/pmsa003i/* @sjtrny esphome/components/pn532/* @OttoWinter @jesserockz esphome/components/pn532_i2c/* @OttoWinter @jesserockz diff --git a/esphome/components/pipsolar/__init__.py b/esphome/components/pipsolar/__init__.py new file mode 100644 index 0000000000..20e4672125 --- /dev/null +++ b/esphome/components/pipsolar/__init__.py @@ -0,0 +1,32 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ID +from esphome.components import uart + +DEPENDENCIES = ["uart"] +CODEOWNERS = ["@andreashergert1984"] +AUTO_LOAD = ["binary_sensor", "text_sensor", "sensor", "switch", "output"] +MULTI_CONF = True + +CONF_PIPSOLAR_ID = "pipsolar_id" + +pipsolar_ns = cg.esphome_ns.namespace("pipsolar") +PipsolarComponent = pipsolar_ns.class_("Pipsolar", cg.Component) + +PIPSOLAR_COMPONENT_SCHEMA = cv.COMPONENT_SCHEMA.extend( + { + cv.Required(CONF_PIPSOLAR_ID): cv.use_id(PipsolarComponent), + } +) + +CONFIG_SCHEMA = cv.All( + cv.Schema({cv.GenerateID(): cv.declare_id(PipsolarComponent)}) + .extend(cv.polling_component_schema("1s")) + .extend(uart.UART_DEVICE_SCHEMA) +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield uart.register_uart_device(var, config) diff --git a/esphome/components/pipsolar/binary_sensor/__init__.py b/esphome/components/pipsolar/binary_sensor/__init__.py new file mode 100644 index 0000000000..5c6af3bffc --- /dev/null +++ b/esphome/components/pipsolar/binary_sensor/__init__.py @@ -0,0 +1,144 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import ( + CONF_ID, +) +from .. import PIPSOLAR_COMPONENT_SCHEMA, CONF_PIPSOLAR_ID + +DEPENDENCIES = ["uart"] + +CONF_ADD_SBU_PRIORITY_VERSION = "add_sbu_priority_version" +CONF_CONFIGURATION_STATUS = "configuration_status" +CONF_SCC_FIRMWARE_VERSION = "scc_firmware_version" +CONF_LOAD_STATUS = "load_status" +CONF_BATTERY_VOLTAGE_TO_STEADY_WHILE_CHARGING = ( + "battery_voltage_to_steady_while_charging" +) +CONF_CHARGING_STATUS = "charging_status" +CONF_SCC_CHARGING_STATUS = "scc_charging_status" +CONF_AC_CHARGING_STATUS = "ac_charging_status" +CONF_CHARGING_TO_FLOATING_MODE = "charging_to_floating_mode" +CONF_SWITCH_ON = "switch_on" +CONF_DUSTPROOF_INSTALLED = "dustproof_installed" +CONF_SILENCE_BUZZER_OPEN_BUZZER = "silence_buzzer_open_buzzer" +CONF_OVERLOAD_BYPASS_FUNCTION = "overload_bypass_function" +CONF_LCD_ESCAPE_TO_DEFAULT = "lcd_escape_to_default" +CONF_OVERLOAD_RESTART_FUNCTION = "overload_restart_function" +CONF_OVER_TEMPERATURE_RESTART_FUNCTION = "over_temperature_restart_function" +CONF_BACKLIGHT_ON = "backlight_on" +CONF_ALARM_ON_WHEN_PRIMARY_SOURCE_INTERRUPT = "alarm_on_when_primary_source_interrupt" +CONF_FAULT_CODE_RECORD = "fault_code_record" +CONF_POWER_SAVING = "power_saving" + +CONF_WARNINGS_PRESENT = "warnings_present" +CONF_FAULTS_PRESENT = "faults_present" +CONF_WARNING_POWER_LOSS = "warning_power_loss" +CONF_FAULT_INVERTER_FAULT = "fault_inverter_fault" +CONF_FAULT_BUS_OVER = "fault_bus_over" +CONF_FAULT_BUS_UNDER = "fault_bus_under" +CONF_FAULT_BUS_SOFT_FAIL = "fault_bus_soft_fail" +CONF_WARNING_LINE_FAIL = "warning_line_fail" +CONF_FAULT_OPVSHORT = "fault_opvshort" +CONF_FAULT_INVERTER_VOLTAGE_TOO_LOW = "fault_inverter_voltage_too_low" +CONF_FAULT_INVERTER_VOLTAGE_TOO_HIGH = "fault_inverter_voltage_too_high" +CONF_WARNING_OVER_TEMPERATURE = "warning_over_temperature" +CONF_WARNING_FAN_LOCK = "warning_fan_lock" +CONF_WARNING_BATTERY_VOLTAGE_HIGH = "warning_battery_voltage_high" +CONF_WARNING_BATTERY_LOW_ALARM = "warning_battery_low_alarm" +CONF_WARNING_BATTERY_UNDER_SHUTDOWN = "warning_battery_under_shutdown" +CONF_WARNING_BATTERY_DERATING = "warning_battery_derating" +CONF_WARNING_OVER_LOAD = "warning_over_load" +CONF_WARNING_EEPROM_FAILED = "warning_eeprom_failed" +CONF_FAULT_INVERTER_OVER_CURRENT = "fault_inverter_over_current" +CONF_FAULT_INVERTER_SOFT_FAILED = "fault_inverter_soft_failed" +CONF_FAULT_SELF_TEST_FAILED = "fault_self_test_failed" +CONF_FAULT_OP_DC_VOLTAGE_OVER = "fault_op_dc_voltage_over" +CONF_FAULT_BATTERY_OPEN = "fault_battery_open" +CONF_FAULT_CURRENT_SENSOR_FAILED = "fault_current_sensor_failed" +CONF_FAULT_BATTERY_SHORT = "fault_battery_short" +CONF_WARNING_POWER_LIMIT = "warning_power_limit" +CONF_WARNING_PV_VOLTAGE_HIGH = "warning_pv_voltage_high" +CONF_FAULT_MPPT_OVERLOAD = "fault_mppt_overload" +CONF_WARNING_MPPT_OVERLOAD = "warning_mppt_overload" +CONF_WARNING_BATTERY_TOO_LOW_TO_CHARGE = "warning_battery_too_low_to_charge" +CONF_FAULT_DC_DC_OVER_CURRENT = "fault_dc_dc_over_current" +CONF_FAULT_CODE = "fault_code" +CONF_WARNUNG_LOW_PV_ENERGY = "warnung_low_pv_energy" +CONF_WARNING_HIGH_AC_INPUT_DURING_BUS_SOFT_START = ( + "warning_high_ac_input_during_bus_soft_start" +) +CONF_WARNING_BATTERY_EQUALIZATION = "warning_battery_equalization" + +TYPES = [ + CONF_ADD_SBU_PRIORITY_VERSION, + CONF_CONFIGURATION_STATUS, + CONF_SCC_FIRMWARE_VERSION, + CONF_LOAD_STATUS, + CONF_BATTERY_VOLTAGE_TO_STEADY_WHILE_CHARGING, + CONF_CHARGING_STATUS, + CONF_SCC_CHARGING_STATUS, + CONF_AC_CHARGING_STATUS, + CONF_CHARGING_TO_FLOATING_MODE, + CONF_SWITCH_ON, + CONF_DUSTPROOF_INSTALLED, + CONF_SILENCE_BUZZER_OPEN_BUZZER, + CONF_OVERLOAD_BYPASS_FUNCTION, + CONF_LCD_ESCAPE_TO_DEFAULT, + CONF_OVERLOAD_RESTART_FUNCTION, + CONF_OVER_TEMPERATURE_RESTART_FUNCTION, + CONF_BACKLIGHT_ON, + CONF_ALARM_ON_WHEN_PRIMARY_SOURCE_INTERRUPT, + CONF_FAULT_CODE_RECORD, + CONF_POWER_SAVING, + CONF_WARNINGS_PRESENT, + CONF_FAULTS_PRESENT, + CONF_WARNING_POWER_LOSS, + CONF_FAULT_INVERTER_FAULT, + CONF_FAULT_BUS_OVER, + CONF_FAULT_BUS_UNDER, + CONF_FAULT_BUS_SOFT_FAIL, + CONF_WARNING_LINE_FAIL, + CONF_FAULT_OPVSHORT, + CONF_FAULT_INVERTER_VOLTAGE_TOO_LOW, + CONF_FAULT_INVERTER_VOLTAGE_TOO_HIGH, + CONF_WARNING_OVER_TEMPERATURE, + CONF_WARNING_FAN_LOCK, + CONF_WARNING_BATTERY_VOLTAGE_HIGH, + CONF_WARNING_BATTERY_LOW_ALARM, + CONF_WARNING_BATTERY_UNDER_SHUTDOWN, + CONF_WARNING_BATTERY_DERATING, + CONF_WARNING_OVER_LOAD, + CONF_WARNING_EEPROM_FAILED, + CONF_FAULT_INVERTER_OVER_CURRENT, + CONF_FAULT_INVERTER_SOFT_FAILED, + CONF_FAULT_SELF_TEST_FAILED, + CONF_FAULT_OP_DC_VOLTAGE_OVER, + CONF_FAULT_BATTERY_OPEN, + CONF_FAULT_CURRENT_SENSOR_FAILED, + CONF_FAULT_BATTERY_SHORT, + CONF_WARNING_POWER_LIMIT, + CONF_WARNING_PV_VOLTAGE_HIGH, + CONF_FAULT_MPPT_OVERLOAD, + CONF_WARNING_MPPT_OVERLOAD, + CONF_WARNING_BATTERY_TOO_LOW_TO_CHARGE, + CONF_FAULT_DC_DC_OVER_CURRENT, + CONF_FAULT_CODE, + CONF_WARNUNG_LOW_PV_ENERGY, + CONF_WARNING_HIGH_AC_INPUT_DURING_BUS_SOFT_START, + CONF_WARNING_BATTERY_EQUALIZATION, +] + +CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend( + {cv.Optional(type): binary_sensor.BINARY_SENSOR_SCHEMA for type in TYPES} +) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_PIPSOLAR_ID]) + for type in TYPES: + if type in config: + conf = config[type] + sens = cg.new_Pvariable(conf[CONF_ID]) + await binary_sensor.register_binary_sensor(sens, conf) + cg.add(getattr(paren, f"set_{type}")(sens)) diff --git a/esphome/components/pipsolar/output/__init__.py b/esphome/components/pipsolar/output/__init__.py new file mode 100644 index 0000000000..b518d485e7 --- /dev/null +++ b/esphome/components/pipsolar/output/__init__.py @@ -0,0 +1,106 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.components import output +from esphome.const import CONF_ID, CONF_VALUE +from .. import PIPSOLAR_COMPONENT_SCHEMA, CONF_PIPSOLAR_ID, pipsolar_ns + +DEPENDENCIES = ["pipsolar"] + +PipsolarOutput = pipsolar_ns.class_("PipsolarOutput", output.FloatOutput) +SetOutputAction = pipsolar_ns.class_("SetOutputAction", automation.Action) + +CONF_POSSIBLE_VALUES = "possible_values" + +# 3.11 PCVV: Setting battery C.V. (constant voltage) charging voltage 48.0V ~ 58.4V for 48V unit +# battery_bulk_voltage; +# battery_recharge_voltage; 12V unit: 11V/11.3V/11.5V/11.8V/12V/12.3V/12.5V/12.8V +# 24V unit: 22V/22.5V/23V/23.5V/24V/24.5V/25V/25.5V +# 48V unit: 44V/45V/46V/47V/48V/49V/50V/51V +# battery_under_voltage; 40.0V ~ 48.0V for 48V unit +# battery_float_voltage; 48.0V ~ 58.4V for 48V unit +# battery_type; 00 for AGM, 01 for Flooded battery +# current_max_ac_charging_current; +# output_source_priority; 00 / 01 / 02 +# charger_source_priority; For HS: 00 for utility first, 01 for solar first, 02 for solar and utility, 03 for only solar charging +# For MS/MSX: 00 for utility first, 01 for solar first, 03 for only solar charging +# battery_redischarge_voltage; 12V unit: 00.0V12V/12.3V/12.5V/12.8V/13V/13.3V/13.5V/13.8V/14V/14.3V/14.5 +# 24V unit: 00.0V/24V/24.5V/25V/25.5V/26V/26.5V/27V/27.5V/28V/28.5V/29V +# 48V unit: 00.0V48V/49V/50V/51V/52V/53V/54V/55V/56V/57V/58V + +CONF_BATTERY_RECHARGE_VOLTAGE = "battery_recharge_voltage" +CONF_BATTERY_UNDER_VOLTAGE = "battery_under_voltage" +CONF_BATTERY_FLOAT_VOLTAGE = "battery_float_voltage" +CONF_BATTERY_TYPE = "battery_type" +CONF_CURRENT_MAX_AC_CHARGING_CURRENT = "current_max_ac_charging_current" +CONF_CURRENT_MAX_CHARGING_CURRENT = "current_max_charging_current" +CONF_OUTPUT_SOURCE_PRIORITY = "output_source_priority" +CONF_CHARGER_SOURCE_PRIORITY = "charger_source_priority" +CONF_BATTERY_REDISCHARGE_VOLTAGE = "battery_redischarge_voltage" + +TYPES = { + CONF_BATTERY_RECHARGE_VOLTAGE: ( + [44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 51.0], + "PBCV%02.1f", + ), + CONF_BATTERY_UNDER_VOLTAGE: ( + [40.0, 40.1, 42, 43, 44, 45, 46, 47, 48.0], + "PSDV%02.1f", + ), + CONF_BATTERY_FLOAT_VOLTAGE: ([48.0, 49.0, 50.0, 51.0], "PBFT%02.1f"), + CONF_BATTERY_TYPE: ([0, 1, 2], "PBT%02.0f"), + CONF_CURRENT_MAX_AC_CHARGING_CURRENT: ([2, 10, 20], "MUCHGC0%02.0f"), + CONF_CURRENT_MAX_CHARGING_CURRENT: ([10, 20, 30, 40], "MCHGC0%02.0f"), + CONF_OUTPUT_SOURCE_PRIORITY: ([0, 1, 2], "POP%02.0f"), + CONF_CHARGER_SOURCE_PRIORITY: ([0, 1, 2, 3], "PCP%02.0f"), + CONF_BATTERY_REDISCHARGE_VOLTAGE: ( + [0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58], + "PBDV%02.1f", + ), +} + +CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend( + { + cv.Optional(type): output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(PipsolarOutput), + cv.Optional(CONF_POSSIBLE_VALUES, default=values): cv.All( + cv.ensure_list(cv.positive_float), cv.Length(min=1) + ), + } + ) + for type, (values, _) in TYPES.items() + } +) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_PIPSOLAR_ID]) + + for type, (_, command) in TYPES.items(): + if type in config: + conf = config[type] + var = cg.new_Pvariable(conf[CONF_ID]) + await output.register_output(var, conf) + cg.add(var.set_parent(paren)) + cg.add(var.set_set_command(command)) + if (CONF_POSSIBLE_VALUES) in conf: + cg.add(var.set_possible_values(conf[CONF_POSSIBLE_VALUES])) + + +@automation.register_action( + "output.pipsolar.set_level", + SetOutputAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(CONF_ID), + cv.Required(CONF_VALUE): cv.templatable(cv.positive_float), + } + ), +) +def output_pipsolar_set_level_to_code(config, action_id, template_arg, args): + paren = yield cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = yield cg.templatable(config[CONF_VALUE], args, float) + cg.add(var.set_level(template_)) + yield var diff --git a/esphome/components/pipsolar/output/pipsolar_output.cpp b/esphome/components/pipsolar/output/pipsolar_output.cpp new file mode 100644 index 0000000000..b843f1f3e6 --- /dev/null +++ b/esphome/components/pipsolar/output/pipsolar_output.cpp @@ -0,0 +1,22 @@ +#include "pipsolar_output.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace pipsolar { + +static const char *const TAG = "pipsolar.output"; + +void PipsolarOutput::write_state(float state) { + char tmp[10]; + sprintf(tmp, this->set_command_.c_str(), state); + + if (std::find(this->possible_values_.begin(), this->possible_values_.end(), state) != this->possible_values_.end()) { + ESP_LOGD(TAG, "Will write: %s out of value %f / %02.0f", tmp, state, state); + this->parent_->switch_command(std::string(tmp)); + } else { + ESP_LOGD(TAG, "Will not write: %s as it is not in list of allowed values", tmp); + } +} +} // namespace pipsolar +} // namespace esphome diff --git a/esphome/components/pipsolar/output/pipsolar_output.h b/esphome/components/pipsolar/output/pipsolar_output.h new file mode 100644 index 0000000000..932efe01c2 --- /dev/null +++ b/esphome/components/pipsolar/output/pipsolar_output.h @@ -0,0 +1,40 @@ +#pragma once + +#include "../pipsolar.h" +#include "esphome/components/output/float_output.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace pipsolar { + +class Pipsolar; + +class PipsolarOutput : public output::FloatOutput { + public: + PipsolarOutput() {} + void set_parent(Pipsolar *parent) { this->parent_ = parent; } + void set_set_command(std::string command) { this->set_command_ = std::move(command); }; + void set_possible_values(std::vector possible_values) { this->possible_values_ = std::move(possible_values); } + void set_value(float value) { this->write_state(value); }; + + protected: + void write_state(float state) override; + std::string set_command_; + Pipsolar *parent_; + std::vector possible_values_; +}; + +template class SetOutputAction : public Action { + public: + SetOutputAction(PipsolarOutput *output) : output_(output) {} + + TEMPLATABLE_VALUE(float, level) + + void play(Ts... x) override { this->output_->set_value(this->level_.value(x...)); } + + protected: + PipsolarOutput *output_; +}; + +} // namespace pipsolar +} // namespace esphome diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp new file mode 100644 index 0000000000..9c7adc13c1 --- /dev/null +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -0,0 +1,922 @@ +#include "pipsolar.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pipsolar { + +static const char *const TAG = "pipsolar"; + +void Pipsolar::setup() { + this->state_ = STATE_IDLE; + this->command_start_millis_ = 0; +} + +void Pipsolar::empty_uart_buffer_() { + uint8_t byte; + while (this->available()) { + this->read_byte(&byte); + } +} + +void Pipsolar::loop() { + // Read message + if (this->state_ == STATE_IDLE) { + this->empty_uart_buffer_(); + switch (this->send_next_command_()) { + case 0: + // no command send (empty queue) time to poll + if (millis() - this->last_poll_ > this->update_interval_) { + this->send_next_poll_(); + this->last_poll_ = millis(); + } + return; + break; + case 1: + // command send + return; + break; + } + } + if (this->state_ == STATE_COMMAND_COMPLETE) { + if (this->check_incoming_length_(4)) { + ESP_LOGD(TAG, "response length for command OK"); + if (this->check_incoming_crc_()) { + // crc ok + if (this->read_buffer_[1] == 'A' && this->read_buffer_[2] == 'C' && this->read_buffer_[3] == 'K') { + ESP_LOGD(TAG, "command successful"); + } else { + ESP_LOGD(TAG, "command not successful"); + } + this->command_queue_[this->command_queue_position_] = std::string(""); + this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH; + this->state_ = STATE_IDLE; + + } else { + // crc failed + this->command_queue_[this->command_queue_position_] = std::string(""); + this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH; + this->state_ = STATE_IDLE; + } + } else { + ESP_LOGD(TAG, "response length for command %s not OK: with length %zu", + this->command_queue_[this->command_queue_position_].c_str(), this->read_pos_); + this->command_queue_[this->command_queue_position_] = std::string(""); + this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH; + this->state_ = STATE_IDLE; + } + } + + if (this->state_ == STATE_POLL_DECODED) { + std::string mode; + switch (this->used_polling_commands_[this->last_polling_command_].identifier) { + case POLLING_QPIRI: + if (this->grid_rating_voltage_) { + this->grid_rating_voltage_->publish_state(value_grid_rating_voltage_); + } + if (this->grid_rating_current_) { + this->grid_rating_current_->publish_state(value_grid_rating_current_); + } + if (this->ac_output_rating_voltage_) { + this->ac_output_rating_voltage_->publish_state(value_ac_output_rating_voltage_); + } + if (this->ac_output_rating_frequency_) { + this->ac_output_rating_frequency_->publish_state(value_ac_output_rating_frequency_); + } + if (this->ac_output_rating_current_) { + this->ac_output_rating_current_->publish_state(value_ac_output_rating_current_); + } + if (this->ac_output_rating_apparent_power_) { + this->ac_output_rating_apparent_power_->publish_state(value_ac_output_rating_apparent_power_); + } + if (this->ac_output_rating_active_power_) { + this->ac_output_rating_active_power_->publish_state(value_ac_output_rating_active_power_); + } + if (this->battery_rating_voltage_) { + this->battery_rating_voltage_->publish_state(value_battery_rating_voltage_); + } + if (this->battery_recharge_voltage_) { + this->battery_recharge_voltage_->publish_state(value_battery_recharge_voltage_); + } + if (this->battery_under_voltage_) { + this->battery_under_voltage_->publish_state(value_battery_under_voltage_); + } + if (this->battery_bulk_voltage_) { + this->battery_bulk_voltage_->publish_state(value_battery_bulk_voltage_); + } + if (this->battery_float_voltage_) { + this->battery_float_voltage_->publish_state(value_battery_float_voltage_); + } + if (this->battery_type_) { + this->battery_type_->publish_state(value_battery_type_); + } + if (this->current_max_ac_charging_current_) { + this->current_max_ac_charging_current_->publish_state(value_current_max_ac_charging_current_); + } + if (this->current_max_charging_current_) { + this->current_max_charging_current_->publish_state(value_current_max_charging_current_); + } + if (this->input_voltage_range_) { + this->input_voltage_range_->publish_state(value_input_voltage_range_); + } + // special for input voltage range switch + if (this->input_voltage_range_switch_) { + this->input_voltage_range_switch_->publish_state(value_input_voltage_range_ == 1); + } + if (this->output_source_priority_) { + this->output_source_priority_->publish_state(value_output_source_priority_); + } + // special for output source priority switches + if (this->output_source_priority_utility_switch_) { + this->output_source_priority_utility_switch_->publish_state(value_output_source_priority_ == 0); + } + if (this->output_source_priority_solar_switch_) { + this->output_source_priority_solar_switch_->publish_state(value_output_source_priority_ == 1); + } + if (this->output_source_priority_battery_switch_) { + this->output_source_priority_battery_switch_->publish_state(value_output_source_priority_ == 2); + } + if (this->charger_source_priority_) { + this->charger_source_priority_->publish_state(value_charger_source_priority_); + } + if (this->parallel_max_num_) { + this->parallel_max_num_->publish_state(value_parallel_max_num_); + } + if (this->machine_type_) { + this->machine_type_->publish_state(value_machine_type_); + } + if (this->topology_) { + this->topology_->publish_state(value_topology_); + } + if (this->output_mode_) { + this->output_mode_->publish_state(value_output_mode_); + } + if (this->battery_redischarge_voltage_) { + this->battery_redischarge_voltage_->publish_state(value_battery_redischarge_voltage_); + } + if (this->pv_ok_condition_for_parallel_) { + this->pv_ok_condition_for_parallel_->publish_state(value_pv_ok_condition_for_parallel_); + } + // special for pv ok condition switch + if (this->pv_ok_condition_for_parallel_switch_) { + this->pv_ok_condition_for_parallel_switch_->publish_state(value_pv_ok_condition_for_parallel_ == 1); + } + if (this->pv_power_balance_) { + this->pv_power_balance_->publish_state(value_pv_power_balance_ == 1); + } + // special for power balance switch + if (this->pv_power_balance_switch_) { + this->pv_power_balance_switch_->publish_state(value_pv_power_balance_ == 1); + } + this->state_ = STATE_IDLE; + break; + case POLLING_QPIGS: + if (this->grid_voltage_) { + this->grid_voltage_->publish_state(value_grid_voltage_); + } + if (this->grid_frequency_) { + this->grid_frequency_->publish_state(value_grid_frequency_); + } + if (this->ac_output_voltage_) { + this->ac_output_voltage_->publish_state(value_ac_output_voltage_); + } + if (this->ac_output_frequency_) { + this->ac_output_frequency_->publish_state(value_ac_output_frequency_); + } + if (this->ac_output_apparent_power_) { + this->ac_output_apparent_power_->publish_state(value_ac_output_apparent_power_); + } + if (this->ac_output_active_power_) { + this->ac_output_active_power_->publish_state(value_ac_output_active_power_); + } + if (this->output_load_percent_) { + this->output_load_percent_->publish_state(value_output_load_percent_); + } + if (this->bus_voltage_) { + this->bus_voltage_->publish_state(value_bus_voltage_); + } + if (this->battery_voltage_) { + this->battery_voltage_->publish_state(value_battery_voltage_); + } + if (this->battery_charging_current_) { + this->battery_charging_current_->publish_state(value_battery_charging_current_); + } + if (this->battery_capacity_percent_) { + this->battery_capacity_percent_->publish_state(value_battery_capacity_percent_); + } + if (this->inverter_heat_sink_temperature_) { + this->inverter_heat_sink_temperature_->publish_state(value_inverter_heat_sink_temperature_); + } + if (this->pv_input_current_for_battery_) { + this->pv_input_current_for_battery_->publish_state(value_pv_input_current_for_battery_); + } + if (this->pv_input_voltage_) { + this->pv_input_voltage_->publish_state(value_pv_input_voltage_); + } + if (this->battery_voltage_scc_) { + this->battery_voltage_scc_->publish_state(value_battery_voltage_scc_); + } + if (this->battery_discharge_current_) { + this->battery_discharge_current_->publish_state(value_battery_discharge_current_); + } + if (this->add_sbu_priority_version_) { + this->add_sbu_priority_version_->publish_state(value_add_sbu_priority_version_); + } + if (this->configuration_status_) { + this->configuration_status_->publish_state(value_configuration_status_); + } + if (this->scc_firmware_version_) { + this->scc_firmware_version_->publish_state(value_scc_firmware_version_); + } + if (this->load_status_) { + this->load_status_->publish_state(value_load_status_); + } + if (this->battery_voltage_to_steady_while_charging_) { + this->battery_voltage_to_steady_while_charging_->publish_state( + value_battery_voltage_to_steady_while_charging_); + } + if (this->charging_status_) { + this->charging_status_->publish_state(value_charging_status_); + } + if (this->scc_charging_status_) { + this->scc_charging_status_->publish_state(value_scc_charging_status_); + } + if (this->ac_charging_status_) { + this->ac_charging_status_->publish_state(value_ac_charging_status_); + } + if (this->battery_voltage_offset_for_fans_on_) { + this->battery_voltage_offset_for_fans_on_->publish_state(value_battery_voltage_offset_for_fans_on_ / 10.0f); + } //.1 scale + if (this->eeprom_version_) { + this->eeprom_version_->publish_state(value_eeprom_version_); + } + if (this->pv_charging_power_) { + this->pv_charging_power_->publish_state(value_pv_charging_power_); + } + if (this->charging_to_floating_mode_) { + this->charging_to_floating_mode_->publish_state(value_charging_to_floating_mode_); + } + if (this->switch_on_) { + this->switch_on_->publish_state(value_switch_on_); + } + if (this->dustproof_installed_) { + this->dustproof_installed_->publish_state(value_dustproof_installed_); + } + this->state_ = STATE_IDLE; + break; + case POLLING_QMOD: + if (this->device_mode_) { + mode = value_device_mode_; + this->device_mode_->publish_state(mode); + } + this->state_ = STATE_IDLE; + break; + case POLLING_QFLAG: + if (this->silence_buzzer_open_buzzer_) { + this->silence_buzzer_open_buzzer_->publish_state(value_silence_buzzer_open_buzzer_); + } + if (this->overload_bypass_function_) { + this->overload_bypass_function_->publish_state(value_overload_bypass_function_); + } + if (this->lcd_escape_to_default_) { + this->lcd_escape_to_default_->publish_state(value_lcd_escape_to_default_); + } + if (this->overload_restart_function_) { + this->overload_restart_function_->publish_state(value_overload_restart_function_); + } + if (this->over_temperature_restart_function_) { + this->over_temperature_restart_function_->publish_state(value_over_temperature_restart_function_); + } + if (this->backlight_on_) { + this->backlight_on_->publish_state(value_backlight_on_); + } + if (this->alarm_on_when_primary_source_interrupt_) { + this->alarm_on_when_primary_source_interrupt_->publish_state(value_alarm_on_when_primary_source_interrupt_); + } + if (this->fault_code_record_) { + this->fault_code_record_->publish_state(value_fault_code_record_); + } + if (this->power_saving_) { + this->power_saving_->publish_state(value_power_saving_); + } + this->state_ = STATE_IDLE; + break; + case POLLING_QPIWS: + if (this->warnings_present_) { + this->warnings_present_->publish_state(value_warnings_present_); + } + if (this->faults_present_) { + this->faults_present_->publish_state(value_faults_present_); + } + if (this->warning_power_loss_) { + this->warning_power_loss_->publish_state(value_warning_power_loss_); + } + if (this->fault_inverter_fault_) { + this->fault_inverter_fault_->publish_state(value_fault_inverter_fault_); + } + if (this->fault_bus_over_) { + this->fault_bus_over_->publish_state(value_fault_bus_over_); + } + if (this->fault_bus_under_) { + this->fault_bus_under_->publish_state(value_fault_bus_under_); + } + if (this->fault_bus_soft_fail_) { + this->fault_bus_soft_fail_->publish_state(value_fault_bus_soft_fail_); + } + if (this->warning_line_fail_) { + this->warning_line_fail_->publish_state(value_warning_line_fail_); + } + if (this->fault_opvshort_) { + this->fault_opvshort_->publish_state(value_fault_opvshort_); + } + if (this->fault_inverter_voltage_too_low_) { + this->fault_inverter_voltage_too_low_->publish_state(value_fault_inverter_voltage_too_low_); + } + if (this->fault_inverter_voltage_too_high_) { + this->fault_inverter_voltage_too_high_->publish_state(value_fault_inverter_voltage_too_high_); + } + if (this->warning_over_temperature_) { + this->warning_over_temperature_->publish_state(value_warning_over_temperature_); + } + if (this->warning_fan_lock_) { + this->warning_fan_lock_->publish_state(value_warning_fan_lock_); + } + if (this->warning_battery_voltage_high_) { + this->warning_battery_voltage_high_->publish_state(value_warning_battery_voltage_high_); + } + if (this->warning_battery_low_alarm_) { + this->warning_battery_low_alarm_->publish_state(value_warning_battery_low_alarm_); + } + if (this->warning_battery_under_shutdown_) { + this->warning_battery_under_shutdown_->publish_state(value_warning_battery_under_shutdown_); + } + if (this->warning_battery_derating_) { + this->warning_battery_derating_->publish_state(value_warning_battery_derating_); + } + if (this->warning_over_load_) { + this->warning_over_load_->publish_state(value_warning_over_load_); + } + if (this->warning_eeprom_failed_) { + this->warning_eeprom_failed_->publish_state(value_warning_eeprom_failed_); + } + if (this->fault_inverter_over_current_) { + this->fault_inverter_over_current_->publish_state(value_fault_inverter_over_current_); + } + if (this->fault_inverter_soft_failed_) { + this->fault_inverter_soft_failed_->publish_state(value_fault_inverter_soft_failed_); + } + if (this->fault_self_test_failed_) { + this->fault_self_test_failed_->publish_state(value_fault_self_test_failed_); + } + if (this->fault_op_dc_voltage_over_) { + this->fault_op_dc_voltage_over_->publish_state(value_fault_op_dc_voltage_over_); + } + if (this->fault_battery_open_) { + this->fault_battery_open_->publish_state(value_fault_battery_open_); + } + if (this->fault_current_sensor_failed_) { + this->fault_current_sensor_failed_->publish_state(value_fault_current_sensor_failed_); + } + if (this->fault_battery_short_) { + this->fault_battery_short_->publish_state(value_fault_battery_short_); + } + if (this->warning_power_limit_) { + this->warning_power_limit_->publish_state(value_warning_power_limit_); + } + if (this->warning_pv_voltage_high_) { + this->warning_pv_voltage_high_->publish_state(value_warning_pv_voltage_high_); + } + if (this->fault_mppt_overload_) { + this->fault_mppt_overload_->publish_state(value_fault_mppt_overload_); + } + if (this->warning_mppt_overload_) { + this->warning_mppt_overload_->publish_state(value_warning_mppt_overload_); + } + if (this->warning_battery_too_low_to_charge_) { + this->warning_battery_too_low_to_charge_->publish_state(value_warning_battery_too_low_to_charge_); + } + if (this->fault_dc_dc_over_current_) { + this->fault_dc_dc_over_current_->publish_state(value_fault_dc_dc_over_current_); + } + if (this->fault_code_) { + this->fault_code_->publish_state(value_fault_code_); + } + if (this->warnung_low_pv_energy_) { + this->warnung_low_pv_energy_->publish_state(value_warnung_low_pv_energy_); + } + if (this->warning_high_ac_input_during_bus_soft_start_) { + this->warning_high_ac_input_during_bus_soft_start_->publish_state( + value_warning_high_ac_input_during_bus_soft_start_); + } + if (this->warning_battery_equalization_) { + this->warning_battery_equalization_->publish_state(value_warning_battery_equalization_); + } + this->state_ = STATE_IDLE; + break; + case POLLING_QT: + this->state_ = STATE_IDLE; + break; + case POLLING_QMN: + this->state_ = STATE_IDLE; + break; + } + } + + if (this->state_ == STATE_POLL_CHECKED) { + bool enabled = true; + std::string fc; + char tmp[PIPSOLAR_READ_BUFFER_LENGTH]; + sprintf(tmp, "%s", this->read_buffer_); + switch (this->used_polling_commands_[this->last_polling_command_].identifier) { + case POLLING_QPIRI: + ESP_LOGD(TAG, "Decode QPIRI"); + sscanf(tmp, "(%f %f %f %f %f %d %d %f %f %f %f %f %d %d %d %d %d %d %d %d %d %d %f %d %d", // NOLINT + &value_grid_rating_voltage_, &value_grid_rating_current_, &value_ac_output_rating_voltage_, // NOLINT + &value_ac_output_rating_frequency_, &value_ac_output_rating_current_, // NOLINT + &value_ac_output_rating_apparent_power_, &value_ac_output_rating_active_power_, // NOLINT + &value_battery_rating_voltage_, &value_battery_recharge_voltage_, // NOLINT + &value_battery_under_voltage_, &value_battery_bulk_voltage_, &value_battery_float_voltage_, // NOLINT + &value_battery_type_, &value_current_max_ac_charging_current_, // NOLINT + &value_current_max_charging_current_, &value_input_voltage_range_, // NOLINT + &value_output_source_priority_, &value_charger_source_priority_, &value_parallel_max_num_, // NOLINT + &value_machine_type_, &value_topology_, &value_output_mode_, // NOLINT + &value_battery_redischarge_voltage_, &value_pv_ok_condition_for_parallel_, // NOLINT + &value_pv_power_balance_); // NOLINT + if (this->last_qpiri_) { + this->last_qpiri_->publish_state(tmp); + } + this->state_ = STATE_POLL_DECODED; + break; + case POLLING_QPIGS: + ESP_LOGD(TAG, "Decode QPIGS"); + sscanf( // NOLINT + tmp, // NOLINT + "(%f %f %f %f %d %d %d %d %f %d %d %d %d %f %f %d %1d%1d%1d%1d%1d%1d%1d%1d %d %d %d %1d%1d%1d", // NOLINT + &value_grid_voltage_, &value_grid_frequency_, &value_ac_output_voltage_, // NOLINT + &value_ac_output_frequency_, // NOLINT + &value_ac_output_apparent_power_, &value_ac_output_active_power_, &value_output_load_percent_, // NOLINT + &value_bus_voltage_, &value_battery_voltage_, &value_battery_charging_current_, // NOLINT + &value_battery_capacity_percent_, &value_inverter_heat_sink_temperature_, // NOLINT + &value_pv_input_current_for_battery_, &value_pv_input_voltage_, &value_battery_voltage_scc_, // NOLINT + &value_battery_discharge_current_, &value_add_sbu_priority_version_, // NOLINT + &value_configuration_status_, &value_scc_firmware_version_, &value_load_status_, // NOLINT + &value_battery_voltage_to_steady_while_charging_, &value_charging_status_, // NOLINT + &value_scc_charging_status_, &value_ac_charging_status_, // NOLINT + &value_battery_voltage_offset_for_fans_on_, &value_eeprom_version_, &value_pv_charging_power_, // NOLINT + &value_charging_to_floating_mode_, &value_switch_on_, // NOLINT + &value_dustproof_installed_); // NOLINT + if (this->last_qpigs_) { + this->last_qpigs_->publish_state(tmp); + } + this->state_ = STATE_POLL_DECODED; + break; + case POLLING_QMOD: + ESP_LOGD(TAG, "Decode QMOD"); + this->value_device_mode_ = char(this->read_buffer_[1]); + if (this->last_qmod_) { + this->last_qmod_->publish_state(tmp); + } + this->state_ = STATE_POLL_DECODED; + break; + case POLLING_QFLAG: + ESP_LOGD(TAG, "Decode QFLAG"); + // result like:"(EbkuvxzDajy" + // get through all char: ignore first "(" Enable flag on 'E', Disable on 'D') else set the corresponding value + for (int i = 1; i < strlen(tmp); i++) { + switch (tmp[i]) { + case 'E': + enabled = true; + break; + case 'D': + enabled = false; + break; + case 'a': + this->value_silence_buzzer_open_buzzer_ = enabled; + break; + case 'b': + this->value_overload_bypass_function_ = enabled; + break; + case 'k': + this->value_lcd_escape_to_default_ = enabled; + break; + case 'u': + this->value_overload_restart_function_ = enabled; + break; + case 'v': + this->value_over_temperature_restart_function_ = enabled; + break; + case 'x': + this->value_backlight_on_ = enabled; + break; + case 'y': + this->value_alarm_on_when_primary_source_interrupt_ = enabled; + break; + case 'z': + this->value_fault_code_record_ = enabled; + break; + case 'j': + this->value_power_saving_ = enabled; + break; + } + } + if (this->last_qflag_) { + this->last_qflag_->publish_state(tmp); + } + this->state_ = STATE_POLL_DECODED; + break; + case POLLING_QPIWS: + ESP_LOGD(TAG, "Decode QPIWS"); + // '(00000000000000000000000000000000' + // iterate over all available flag (as not all models have all flags, but at least in the same order) + this->value_warnings_present_ = false; + this->value_faults_present_ = true; + + for (int i = 1; i < strlen(tmp); i++) { + enabled = tmp[i] == '1'; + switch (i) { + case 1: + this->value_warning_power_loss_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 2: + this->value_fault_inverter_fault_ = enabled; + this->value_faults_present_ += enabled; + break; + case 3: + this->value_fault_bus_over_ = enabled; + this->value_faults_present_ += enabled; + break; + case 4: + this->value_fault_bus_under_ = enabled; + this->value_faults_present_ += enabled; + break; + case 5: + this->value_fault_bus_soft_fail_ = enabled; + this->value_faults_present_ += enabled; + break; + case 6: + this->value_warning_line_fail_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 7: + this->value_fault_opvshort_ = enabled; + this->value_faults_present_ += enabled; + break; + case 8: + this->value_fault_inverter_voltage_too_low_ = enabled; + this->value_faults_present_ += enabled; + break; + case 9: + this->value_fault_inverter_voltage_too_high_ = enabled; + this->value_faults_present_ += enabled; + break; + case 10: + this->value_warning_over_temperature_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 11: + this->value_warning_fan_lock_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 12: + this->value_warning_battery_voltage_high_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 13: + this->value_warning_battery_low_alarm_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 15: + this->value_warning_battery_under_shutdown_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 16: + this->value_warning_battery_derating_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 17: + this->value_warning_over_load_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 18: + this->value_warning_eeprom_failed_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 19: + this->value_fault_inverter_over_current_ = enabled; + this->value_faults_present_ += enabled; + break; + case 20: + this->value_fault_inverter_soft_failed_ = enabled; + this->value_faults_present_ += enabled; + break; + case 21: + this->value_fault_self_test_failed_ = enabled; + this->value_faults_present_ += enabled; + break; + case 22: + this->value_fault_op_dc_voltage_over_ = enabled; + this->value_faults_present_ += enabled; + break; + case 23: + this->value_fault_battery_open_ = enabled; + this->value_faults_present_ += enabled; + break; + case 24: + this->value_fault_current_sensor_failed_ = enabled; + this->value_faults_present_ += enabled; + break; + case 25: + this->value_fault_battery_short_ = enabled; + this->value_faults_present_ += enabled; + break; + case 26: + this->value_warning_power_limit_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 27: + this->value_warning_pv_voltage_high_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 28: + this->value_fault_mppt_overload_ = enabled; + this->value_faults_present_ += enabled; + break; + case 29: + this->value_warning_mppt_overload_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 30: + this->value_warning_battery_too_low_to_charge_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 31: + this->value_fault_dc_dc_over_current_ = enabled; + this->value_faults_present_ += enabled; + break; + case 32: + fc = tmp[i]; + fc += tmp[i + 1]; + this->value_fault_code_ = strtol(fc.c_str(), nullptr, 10); + break; + case 34: + this->value_warnung_low_pv_energy_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 35: + this->value_warning_high_ac_input_during_bus_soft_start_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 36: + this->value_warning_battery_equalization_ = enabled; + this->value_warnings_present_ += enabled; + break; + } + } + if (this->last_qpiws_) { + this->last_qpiws_->publish_state(tmp); + } + this->state_ = STATE_POLL_DECODED; + break; + case POLLING_QT: + ESP_LOGD(TAG, "Decode QT"); + if (this->last_qt_) { + this->last_qt_->publish_state(tmp); + } + this->state_ = STATE_POLL_DECODED; + break; + case POLLING_QMN: + ESP_LOGD(TAG, "Decode QMN"); + if (this->last_qmn_) { + this->last_qmn_->publish_state(tmp); + } + this->state_ = STATE_POLL_DECODED; + break; + default: + this->state_ = STATE_IDLE; + break; + } + return; + } + + if (this->state_ == STATE_POLL_COMPLETE) { + if (this->check_incoming_crc_()) { + if (this->read_buffer_[0] == '(' && this->read_buffer_[1] == 'N' && this->read_buffer_[2] == 'A' && + this->read_buffer_[3] == 'K') { + this->state_ = STATE_IDLE; + return; + } + // crc ok + this->state_ = STATE_POLL_CHECKED; + return; + } else { + this->state_ = STATE_IDLE; + } + } + + if (this->state_ == STATE_COMMAND || this->state_ == STATE_POLL) { + while (this->available()) { + uint8_t byte; + this->read_byte(&byte); + + if (this->read_pos_ == PIPSOLAR_READ_BUFFER_LENGTH) { + this->read_pos_ = 0; + this->empty_uart_buffer_(); + } + this->read_buffer_[this->read_pos_] = byte; + this->read_pos_++; + + // end of answer + if (byte == 0x0D) { + this->read_buffer_[this->read_pos_] = 0; + this->empty_uart_buffer_(); + if (this->state_ == STATE_POLL) { + this->state_ = STATE_POLL_COMPLETE; + } + if (this->state_ == STATE_COMMAND) { + this->state_ = STATE_COMMAND_COMPLETE; + } + } + } // available + } + if (this->state_ == STATE_COMMAND) { + if (millis() - this->command_start_millis_ > esphome::pipsolar::Pipsolar::COMMAND_TIMEOUT) { + // command timeout + const char *command = this->command_queue_[this->command_queue_position_].c_str(); + this->command_start_millis_ = millis(); + ESP_LOGD(TAG, "timeout command from queue: %s", command); + this->command_queue_[this->command_queue_position_] = std::string(""); + this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH; + this->state_ = STATE_IDLE; + return; + } else { + } + } + if (this->state_ == STATE_POLL) { + if (millis() - this->command_start_millis_ > esphome::pipsolar::Pipsolar::COMMAND_TIMEOUT) { + // command timeout + ESP_LOGD(TAG, "timeout command to poll: %s", this->used_polling_commands_[this->last_polling_command_].command); + this->state_ = STATE_IDLE; + } else { + } + } +} + +uint8_t Pipsolar::check_incoming_length_(uint8_t length) { + if (this->read_pos_ - 3 == length) { + return 1; + } + return 0; +} + +uint8_t Pipsolar::check_incoming_crc_() { + uint16_t crc16; + crc16 = calc_crc_(read_buffer_, read_pos_ - 3); + ESP_LOGD(TAG, "checking crc on incoming message"); + if (((uint8_t)((crc16) >> 8)) == read_buffer_[read_pos_ - 3] && + ((uint8_t)((crc16) &0xff)) == read_buffer_[read_pos_ - 2]) { + ESP_LOGD(TAG, "CRC OK"); + read_buffer_[read_pos_ - 1] = 0; + read_buffer_[read_pos_ - 2] = 0; + read_buffer_[read_pos_ - 3] = 0; + return 1; + } + ESP_LOGD(TAG, "CRC NOK expected: %X %X but got: %X %X", ((uint8_t)((crc16) >> 8)), ((uint8_t)((crc16) &0xff)), + read_buffer_[read_pos_ - 3], read_buffer_[read_pos_ - 2]); + return 0; +} + +// send next command used +uint8_t Pipsolar::send_next_command_() { + uint16_t crc16; + if (this->command_queue_[this->command_queue_position_].length() != 0) { + const char *command = this->command_queue_[this->command_queue_position_].c_str(); + uint8_t byte_command[16]; + uint8_t length = this->command_queue_[this->command_queue_position_].length(); + for (uint8_t i = 0; i < length; i++) { + byte_command[i] = (uint8_t) this->command_queue_[this->command_queue_position_].at(i); + } + this->state_ = STATE_COMMAND; + this->command_start_millis_ = millis(); + this->empty_uart_buffer_(); + this->read_pos_ = 0; + crc16 = calc_crc_(byte_command, length); + this->write_str(command); + // checksum + this->write(((uint8_t)((crc16) >> 8))); // highbyte + this->write(((uint8_t)((crc16) &0xff))); // lowbyte + // end Byte + this->write(0x0D); + ESP_LOGD(TAG, "Sending command from queue: %s with length %d", command, length); + return 1; + } + return 0; +} + +void Pipsolar::send_next_poll_() { + uint16_t crc16; + this->last_polling_command_ = (this->last_polling_command_ + 1) % 15; + if (this->used_polling_commands_[this->last_polling_command_].length == 0) { + this->last_polling_command_ = 0; + } + if (this->used_polling_commands_[this->last_polling_command_].length == 0) { + // no command specified + return; + } + this->state_ = STATE_POLL; + this->command_start_millis_ = millis(); + this->empty_uart_buffer_(); + this->read_pos_ = 0; + crc16 = calc_crc_(this->used_polling_commands_[this->last_polling_command_].command, + this->used_polling_commands_[this->last_polling_command_].length); + this->write_array(this->used_polling_commands_[this->last_polling_command_].command, + this->used_polling_commands_[this->last_polling_command_].length); + // checksum + this->write(((uint8_t)((crc16) >> 8))); // highbyte + this->write(((uint8_t)((crc16) &0xff))); // lowbyte + // end Byte + this->write(0x0D); + ESP_LOGD(TAG, "Sending polling command : %s with length %d", + this->used_polling_commands_[this->last_polling_command_].command, + this->used_polling_commands_[this->last_polling_command_].length); +} + +void Pipsolar::queue_command_(const char *command, byte length) { + uint8_t next_position = command_queue_position_; + for (uint8_t i = 0; i < COMMAND_QUEUE_LENGTH; i++) { + uint8_t testposition = (next_position + i) % COMMAND_QUEUE_LENGTH; + if (command_queue_[testposition].length() == 0) { + command_queue_[testposition] = command; + ESP_LOGD(TAG, "Command queued successfully: %s with length %u at position %d", command, + command_queue_[testposition].length(), testposition); + return; + } + } + ESP_LOGD(TAG, "Command queue full dropping command: %s", command); +} + +void Pipsolar::switch_command(const std::string &command) { + ESP_LOGD(TAG, "got command: %s", command.c_str()); + queue_command_(command.c_str(), command.length()); +} +void Pipsolar::dump_config() { + ESP_LOGCONFIG(TAG, "Pipsolar:"); + ESP_LOGCONFIG(TAG, "used commands:"); + for (auto &used_polling_command : this->used_polling_commands_) { + if (used_polling_command.length != 0) { + ESP_LOGCONFIG(TAG, "%s", used_polling_command.command); + } + } +} +void Pipsolar::update() {} + +void Pipsolar::add_polling_command_(const char *command, ENUMPollingCommand polling_command) { + for (auto &used_polling_command : this->used_polling_commands_) { + if (used_polling_command.length == strlen(command)) { + uint8_t len = strlen(command); + if (memcmp(used_polling_command.command, command, len) == 0) { + return; + } + } + if (used_polling_command.length == 0) { + size_t length = strlen(command) + 1; + const char *beg = command; + const char *end = command + length; + used_polling_command.command = new uint8_t[length]; + size_t i = 0; + for (; beg != end; ++beg, ++i) { + used_polling_command.command[i] = (uint8_t)(*beg); + } + used_polling_command.errors = 0; + used_polling_command.identifier = polling_command; + used_polling_command.length = length - 1; + return; + } + } +} + +uint16_t Pipsolar::calc_crc_(uint8_t *msg, int n) { + // Initial value. xmodem uses 0xFFFF but this example + // requires an initial value of zero. + uint16_t x = 0; + while (n--) { + x = crc_xmodem_update_(x, (uint16_t) *msg++); + } + return (x); +} + +// See bottom of this page: http://www.nongnu.org/avr-libc/user-manual/group__util__crc.html +// Polynomial: x^16 + x^12 + x^5 + 1 (0x1021) +uint16_t Pipsolar::crc_xmodem_update_(uint16_t crc, uint8_t data) { + int i; + crc = crc ^ ((uint16_t) data << 8); + for (i = 0; i < 8; i++) { + if (crc & 0x8000) + crc = (crc << 1) ^ 0x1021; //(polynomial = 0x1021) + else + crc <<= 1; + } + return crc; +} + +} // namespace pipsolar +} // namespace esphome diff --git a/esphome/components/pipsolar/pipsolar.h b/esphome/components/pipsolar/pipsolar.h new file mode 100644 index 0000000000..508036c7de --- /dev/null +++ b/esphome/components/pipsolar/pipsolar.h @@ -0,0 +1,223 @@ +#pragma once + +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/switch/switch.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/components/uart/uart.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace pipsolar { + +enum ENUMPollingCommand { + POLLING_QPIRI = 0, + POLLING_QPIGS = 1, + POLLING_QMOD = 2, + POLLING_QFLAG = 3, + POLLING_QPIWS = 4, + POLLING_QT = 5, + POLLING_QMN = 6, +}; +struct PollingCommand { + uint8_t *command; + uint8_t length = 0; + uint8_t errors; + ENUMPollingCommand identifier; +}; + +#define PIPSOLAR_VALUED_ENTITY_(type, name, polling_command, value_type) \ + protected: \ + value_type value_##name##_; \ + PIPSOLAR_ENTITY_(type, name, polling_command) + +#define PIPSOLAR_ENTITY_(type, name, polling_command) \ + protected: \ + type *name##_{}; /* NOLINT */ \ +\ + public: \ + void set_##name(type *name) { /* NOLINT */ \ + this->name##_ = name; \ + this->add_polling_command_(#polling_command, POLLING_##polling_command); \ + } + +#define PIPSOLAR_SENSOR(name, polling_command, value_type) \ + PIPSOLAR_VALUED_ENTITY_(sensor::Sensor, name, polling_command, value_type) +#define PIPSOLAR_SWITCH(name, polling_command) PIPSOLAR_ENTITY_(switch_::Switch, name, polling_command) +#define PIPSOLAR_BINARY_SENSOR(name, polling_command, value_type) \ + PIPSOLAR_VALUED_ENTITY_(binary_sensor::BinarySensor, name, polling_command, value_type) +#define PIPSOLAR_VALUED_TEXT_SENSOR(name, polling_command, value_type) \ + PIPSOLAR_VALUED_ENTITY_(text_sensor::TextSensor, name, polling_command, value_type) +#define PIPSOLAR_TEXT_SENSOR(name, polling_command) PIPSOLAR_ENTITY_(text_sensor::TextSensor, name, polling_command) + +class Pipsolar : public uart::UARTDevice, public PollingComponent { + // QPIGS values + PIPSOLAR_SENSOR(grid_voltage, QPIGS, float) + PIPSOLAR_SENSOR(grid_frequency, QPIGS, float) + PIPSOLAR_SENSOR(ac_output_voltage, QPIGS, float) + PIPSOLAR_SENSOR(ac_output_frequency, QPIGS, float) + PIPSOLAR_SENSOR(ac_output_apparent_power, QPIGS, int) + PIPSOLAR_SENSOR(ac_output_active_power, QPIGS, int) + PIPSOLAR_SENSOR(output_load_percent, QPIGS, int) + PIPSOLAR_SENSOR(bus_voltage, QPIGS, int) + PIPSOLAR_SENSOR(battery_voltage, QPIGS, float) + PIPSOLAR_SENSOR(battery_charging_current, QPIGS, int) + PIPSOLAR_SENSOR(battery_capacity_percent, QPIGS, int) + PIPSOLAR_SENSOR(inverter_heat_sink_temperature, QPIGS, int) + PIPSOLAR_SENSOR(pv_input_current_for_battery, QPIGS, int) + PIPSOLAR_SENSOR(pv_input_voltage, QPIGS, float) + PIPSOLAR_SENSOR(battery_voltage_scc, QPIGS, float) + PIPSOLAR_SENSOR(battery_discharge_current, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(add_sbu_priority_version, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(configuration_status, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(scc_firmware_version, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(load_status, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(battery_voltage_to_steady_while_charging, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(charging_status, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(scc_charging_status, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(ac_charging_status, QPIGS, int) + PIPSOLAR_SENSOR(battery_voltage_offset_for_fans_on, QPIGS, int) //.1 scale + PIPSOLAR_SENSOR(eeprom_version, QPIGS, int) + PIPSOLAR_SENSOR(pv_charging_power, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(charging_to_floating_mode, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(switch_on, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(dustproof_installed, QPIGS, int) + + // QPIRI values + PIPSOLAR_SENSOR(grid_rating_voltage, QPIRI, float) + PIPSOLAR_SENSOR(grid_rating_current, QPIRI, float) + PIPSOLAR_SENSOR(ac_output_rating_voltage, QPIRI, float) + PIPSOLAR_SENSOR(ac_output_rating_frequency, QPIRI, float) + PIPSOLAR_SENSOR(ac_output_rating_current, QPIRI, float) + PIPSOLAR_SENSOR(ac_output_rating_apparent_power, QPIRI, int) + PIPSOLAR_SENSOR(ac_output_rating_active_power, QPIRI, int) + PIPSOLAR_SENSOR(battery_rating_voltage, QPIRI, float) + PIPSOLAR_SENSOR(battery_recharge_voltage, QPIRI, float) + PIPSOLAR_SENSOR(battery_under_voltage, QPIRI, float) + PIPSOLAR_SENSOR(battery_bulk_voltage, QPIRI, float) + PIPSOLAR_SENSOR(battery_float_voltage, QPIRI, float) + PIPSOLAR_SENSOR(battery_type, QPIRI, int) + PIPSOLAR_SENSOR(current_max_ac_charging_current, QPIRI, int) + PIPSOLAR_SENSOR(current_max_charging_current, QPIRI, int) + PIPSOLAR_SENSOR(input_voltage_range, QPIRI, int) + PIPSOLAR_SENSOR(output_source_priority, QPIRI, int) + PIPSOLAR_SENSOR(charger_source_priority, QPIRI, int) + PIPSOLAR_SENSOR(parallel_max_num, QPIRI, int) + PIPSOLAR_SENSOR(machine_type, QPIRI, int) + PIPSOLAR_SENSOR(topology, QPIRI, int) + PIPSOLAR_SENSOR(output_mode, QPIRI, int) + PIPSOLAR_SENSOR(battery_redischarge_voltage, QPIRI, float) + PIPSOLAR_SENSOR(pv_ok_condition_for_parallel, QPIRI, int) + PIPSOLAR_SENSOR(pv_power_balance, QPIRI, int) + + // QMOD values + PIPSOLAR_VALUED_TEXT_SENSOR(device_mode, QMOD, char) + + // QFLAG values + PIPSOLAR_BINARY_SENSOR(silence_buzzer_open_buzzer, QFLAG, int) + PIPSOLAR_BINARY_SENSOR(overload_bypass_function, QFLAG, int) + PIPSOLAR_BINARY_SENSOR(lcd_escape_to_default, QFLAG, int) + PIPSOLAR_BINARY_SENSOR(overload_restart_function, QFLAG, int) + PIPSOLAR_BINARY_SENSOR(over_temperature_restart_function, QFLAG, int) + PIPSOLAR_BINARY_SENSOR(backlight_on, QFLAG, int) + PIPSOLAR_BINARY_SENSOR(alarm_on_when_primary_source_interrupt, QFLAG, int) + PIPSOLAR_BINARY_SENSOR(fault_code_record, QFLAG, int) + PIPSOLAR_BINARY_SENSOR(power_saving, QFLAG, int) + + // QPIWS values + PIPSOLAR_BINARY_SENSOR(warnings_present, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(faults_present, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_power_loss, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_inverter_fault, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_bus_over, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_bus_under, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_bus_soft_fail, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_line_fail, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_opvshort, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_inverter_voltage_too_low, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_inverter_voltage_too_high, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_over_temperature, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_fan_lock, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_battery_voltage_high, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_battery_low_alarm, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_battery_under_shutdown, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_battery_derating, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_over_load, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_eeprom_failed, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_inverter_over_current, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_inverter_soft_failed, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_self_test_failed, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_op_dc_voltage_over, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_battery_open, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_current_sensor_failed, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_battery_short, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_power_limit, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_pv_voltage_high, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_mppt_overload, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_mppt_overload, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_battery_too_low_to_charge, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_dc_dc_over_current, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_code, QPIWS, int) + PIPSOLAR_BINARY_SENSOR(warnung_low_pv_energy, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_high_ac_input_during_bus_soft_start, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_battery_equalization, QPIWS, bool) + + PIPSOLAR_TEXT_SENSOR(last_qpigs, QPIGS) + PIPSOLAR_TEXT_SENSOR(last_qpiri, QPIRI) + PIPSOLAR_TEXT_SENSOR(last_qmod, QMOD) + PIPSOLAR_TEXT_SENSOR(last_qflag, QFLAG) + PIPSOLAR_TEXT_SENSOR(last_qpiws, QPIWS) + PIPSOLAR_TEXT_SENSOR(last_qt, QT) + PIPSOLAR_TEXT_SENSOR(last_qmn, QMN) + + PIPSOLAR_SWITCH(output_source_priority_utility_switch, QPIRI) + PIPSOLAR_SWITCH(output_source_priority_solar_switch, QPIRI) + PIPSOLAR_SWITCH(output_source_priority_battery_switch, QPIRI) + PIPSOLAR_SWITCH(input_voltage_range_switch, QPIRI) + PIPSOLAR_SWITCH(pv_ok_condition_for_parallel_switch, QPIRI) + PIPSOLAR_SWITCH(pv_power_balance_switch, QPIRI) + + void switch_command(const std::string &command); + void setup() override; + void loop() override; + void dump_config() override; + void update() override; + + protected: + static const size_t PIPSOLAR_READ_BUFFER_LENGTH = 110; // maximum supported answer length + static const size_t COMMAND_QUEUE_LENGTH = 10; + static const size_t COMMAND_TIMEOUT = 5000; + uint32_t last_poll_ = 0; + void add_polling_command_(const char *command, ENUMPollingCommand polling_command); + void empty_uart_buffer_(); + uint8_t check_incoming_crc_(); + uint8_t check_incoming_length_(uint8_t length); + uint16_t calc_crc_(uint8_t *msg, int n); + uint16_t crc_xmodem_update_(uint16_t crc, uint8_t data); + uint8_t send_next_command_(); + void send_next_poll_(); + void queue_command_(const char *command, byte length); + std::string command_queue_[COMMAND_QUEUE_LENGTH]; + uint8_t command_queue_position_ = 0; + uint8_t read_buffer_[PIPSOLAR_READ_BUFFER_LENGTH]; + size_t read_pos_{0}; + + uint32_t command_start_millis_ = 0; + uint8_t state_; + enum State { + STATE_IDLE = 0, + STATE_POLL = 1, + STATE_COMMAND = 2, + STATE_POLL_COMPLETE = 3, + STATE_COMMAND_COMPLETE = 4, + STATE_POLL_CHECKED = 5, + STATE_POLL_DECODED = 6, + }; + + uint8_t last_polling_command_ = 0; + PollingCommand used_polling_commands_[15]; +}; + +} // namespace pipsolar +} // namespace esphome diff --git a/esphome/components/pipsolar/sensor/__init__.py b/esphome/components/pipsolar/sensor/__init__.py new file mode 100644 index 0000000000..5e4dd6c40c --- /dev/null +++ b/esphome/components/pipsolar/sensor/__init__.py @@ -0,0 +1,220 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, + ICON_CURRENT_AC, + ICON_EMPTY, + UNIT_AMPERE, + UNIT_CELSIUS, + UNIT_HERTZ, + UNIT_PERCENT, + UNIT_VOLT, + UNIT_EMPTY, + UNIT_VOLT_AMPS, + UNIT_WATT, + CONF_BUS_VOLTAGE, + CONF_BATTERY_VOLTAGE, +) +from .. import PIPSOLAR_COMPONENT_SCHEMA, CONF_PIPSOLAR_ID + +DEPENDENCIES = ["uart"] + +# QPIRI sensors +CONF_GRID_RATING_VOLTAGE = "grid_rating_voltage" +CONF_GRID_RATING_CURRENT = "grid_rating_current" +CONF_AC_OUTPUT_RATING_VOLTAGE = "ac_output_rating_voltage" +CONF_AC_OUTPUT_RATING_FREQUENCY = "ac_output_rating_frequency" +CONF_AC_OUTPUT_RATING_CURRENT = "ac_output_rating_current" +CONF_AC_OUTPUT_RATING_APPARENT_POWER = "ac_output_rating_apparent_power" +CONF_AC_OUTPUT_RATING_ACTIVE_POWER = "ac_output_rating_active_power" +CONF_BATTERY_RATING_VOLTAGE = "battery_rating_voltage" +CONF_BATTERY_RECHARGE_VOLTAGE = "battery_recharge_voltage" +CONF_BATTERY_UNDER_VOLTAGE = "battery_under_voltage" +CONF_BATTERY_BULK_VOLTAGE = "battery_bulk_voltage" +CONF_BATTERY_FLOAT_VOLTAGE = "battery_float_voltage" +CONF_BATTERY_TYPE = "battery_type" +CONF_CURRENT_MAX_AC_CHARGING_CURRENT = "current_max_ac_charging_current" +CONF_CURRENT_MAX_CHARGING_CURRENT = "current_max_charging_current" +CONF_INPUT_VOLTAGE_RANGE = "input_voltage_range" +CONF_OUTPUT_SOURCE_PRIORITY = "output_source_priority" +CONF_CHARGER_SOURCE_PRIORITY = "charger_source_priority" +CONF_PARALLEL_MAX_NUM = "parallel_max_num" +CONF_MACHINE_TYPE = "machine_type" +CONF_TOPOLOGY = "topology" +CONF_OUTPUT_MODE = "output_mode" +CONF_BATTERY_REDISCHARGE_VOLTAGE = "battery_redischarge_voltage" +CONF_PV_OK_CONDITION_FOR_PARALLEL = "pv_ok_condition_for_parallel" +CONF_PV_POWER_BALANCE = "pv_power_balance" + +CONF_GRID_VOLTAGE = "grid_voltage" +CONF_GRID_FREQUENCY = "grid_frequency" +CONF_AC_OUTPUT_VOLTAGE = "ac_output_voltage" +CONF_AC_OUTPUT_FREQUENCY = "ac_output_frequency" +CONF_AC_OUTPUT_APPARENT_POWER = "ac_output_apparent_power" +CONF_AC_OUTPUT_ACTIVE_POWER = "ac_output_active_power" +CONF_OUTPUT_LOAD_PERCENT = "output_load_percent" +CONF_BATTERY_CHARGING_CURRENT = "battery_charging_current" +CONF_BATTERY_CAPACITY_PERCENT = "battery_capacity_percent" +CONF_INVERTER_HEAT_SINK_TEMPERATURE = "inverter_heat_sink_temperature" +CONF_PV_INPUT_CURRENT_FOR_BATTERY = "pv_input_current_for_battery" +CONF_PV_INPUT_VOLTAGE = "pv_input_voltage" +CONF_BATTERY_VOLTAGE_SCC = "battery_voltage_scc" +CONF_BATTERY_DISCHARGE_CURRENT = "battery_discharge_current" +CONF_ADD_SBU_PRIORITY_VERSION = "add_sbu_priority_version" +CONF_CONFIGURATION_STATUS = "configuration_status" +CONF_SCC_FIRMWARE_VERSION = "scc_firmware_version" +CONF_BATTERY_VOLTAGE_OFFSET_FOR_FANS_ON = "battery_voltage_offset_for_fans_on" +CONF_EEPROM_VERSION = "eeprom_version" +CONF_PV_CHARGING_POWER = "pv_charging_power" + +TYPES = { + CONF_GRID_RATING_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_GRID_RATING_CURRENT: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + ), + CONF_AC_OUTPUT_RATING_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_AC_OUTPUT_RATING_FREQUENCY: sensor.sensor_schema( + UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY + ), + CONF_AC_OUTPUT_RATING_CURRENT: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + ), + CONF_AC_OUTPUT_RATING_APPARENT_POWER: sensor.sensor_schema( + UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_POWER + ), + CONF_AC_OUTPUT_RATING_ACTIVE_POWER: sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER + ), + CONF_BATTERY_RATING_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_BATTERY_RECHARGE_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_BATTERY_UNDER_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_BATTERY_BULK_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_BATTERY_FLOAT_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_BATTERY_TYPE: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_CURRENT_MAX_AC_CHARGING_CURRENT: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + ), + CONF_CURRENT_MAX_CHARGING_CURRENT: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + ), + CONF_INPUT_VOLTAGE_RANGE: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_OUTPUT_SOURCE_PRIORITY: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_CHARGER_SOURCE_PRIORITY: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_PARALLEL_MAX_NUM: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_MACHINE_TYPE: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_TOPOLOGY: sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY), + CONF_OUTPUT_MODE: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_BATTERY_REDISCHARGE_VOLTAGE: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_PV_OK_CONDITION_FOR_PARALLEL: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_PV_POWER_BALANCE: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_GRID_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_GRID_FREQUENCY: sensor.sensor_schema( + UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY + ), + CONF_AC_OUTPUT_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_AC_OUTPUT_FREQUENCY: sensor.sensor_schema( + UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY + ), + CONF_AC_OUTPUT_APPARENT_POWER: sensor.sensor_schema( + UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_POWER + ), + CONF_AC_OUTPUT_ACTIVE_POWER: sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER + ), + CONF_OUTPUT_LOAD_PERCENT: sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_BUS_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_BATTERY_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_BATTERY_CHARGING_CURRENT: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + ), + CONF_BATTERY_CAPACITY_PERCENT: sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_INVERTER_HEAT_SINK_TEMPERATURE: sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + CONF_PV_INPUT_CURRENT_FOR_BATTERY: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + ), + CONF_PV_INPUT_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_BATTERY_VOLTAGE_SCC: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_BATTERY_DISCHARGE_CURRENT: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + ), + CONF_BATTERY_VOLTAGE_OFFSET_FOR_FANS_ON: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_EEPROM_VERSION: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_PV_CHARGING_POWER: sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER + ), +} + +CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend( + {cv.Optional(type): schema for type, schema in TYPES.items()} +) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_PIPSOLAR_ID]) + + for type, _ in TYPES.items(): + if type in config: + conf = config[type] + sens = await sensor.new_sensor(conf) + cg.add(getattr(paren, f"set_{type}")(sens)) diff --git a/esphome/components/pipsolar/switch/__init__.py b/esphome/components/pipsolar/switch/__init__.py new file mode 100644 index 0000000000..5ff33b10ff --- /dev/null +++ b/esphome/components/pipsolar/switch/__init__.py @@ -0,0 +1,60 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch +from esphome.const import ( + CONF_ID, + CONF_INVERTED, + CONF_ICON, + ICON_POWER, +) +from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA, pipsolar_ns + +DEPENDENCIES = ["uart"] + +CONF_OUTPUT_SOURCE_PRIORITY_UTILITY = "output_source_priority_utility" +CONF_OUTPUT_SOURCE_PRIORITY_SOLAR = "output_source_priority_solar" +CONF_OUTPUT_SOURCE_PRIORITY_BATTERY = "output_source_priority_battery" +CONF_INPUT_VOLTAGE_RANGE = "input_voltage_range" +CONF_PV_OK_CONDITION_FOR_PARALLEL = "pv_ok_condition_for_parallel" +CONF_PV_POWER_BALANCE = "pv_power_balance" + +TYPES = { + CONF_OUTPUT_SOURCE_PRIORITY_UTILITY: ("POP00", None), + CONF_OUTPUT_SOURCE_PRIORITY_SOLAR: ("POP01", None), + CONF_OUTPUT_SOURCE_PRIORITY_BATTERY: ("POP02", None), + CONF_INPUT_VOLTAGE_RANGE: ("PGR01", "PGR00"), + CONF_PV_OK_CONDITION_FOR_PARALLEL: ("PPVOKC1", "PPVOKC0"), + CONF_PV_POWER_BALANCE: ("PSPB1", "PSPB0"), +} + +PipsolarSwitch = pipsolar_ns.class_("PipsolarSwitch", switch.Switch, cg.Component) + +PIPSWITCH_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(PipsolarSwitch), + cv.Optional(CONF_INVERTED): cv.invalid( + "Pipsolar switches do not support inverted mode!" + ), + cv.Optional(CONF_ICON, default=ICON_POWER): switch.icon, + } +).extend(cv.COMPONENT_SCHEMA) + +CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend( + {cv.Optional(type): PIPSWITCH_SCHEMA for type in TYPES} +) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_PIPSOLAR_ID]) + + for type, (on, off) in TYPES.items(): + if type in config: + conf = config[type] + var = cg.new_Pvariable(conf[CONF_ID]) + await cg.register_component(var, conf) + await switch.register_switch(var, conf) + cg.add(getattr(paren, f"set_{type}_switch")(var)) + cg.add(var.set_parent(paren)) + cg.add(var.set_on_command(on)) + if off is not None: + cg.add(var.set_off_command(off)) diff --git a/esphome/components/pipsolar/switch/pipsolar_switch.cpp b/esphome/components/pipsolar/switch/pipsolar_switch.cpp new file mode 100644 index 0000000000..7eaeac1c2d --- /dev/null +++ b/esphome/components/pipsolar/switch/pipsolar_switch.cpp @@ -0,0 +1,24 @@ +#include "pipsolar_switch.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace pipsolar { + +static const char *const TAG = "pipsolar.switch"; + +void PipsolarSwitch::dump_config() { LOG_SWITCH("", "Pipsolar Switch", this); } +void PipsolarSwitch::write_state(bool state) { + if (state) { + if (this->on_command_.length() > 0) { + this->parent_->switch_command(this->on_command_); + } + } else { + if (this->off_command_.length() > 0) { + this->parent_->switch_command(this->off_command_); + } + } +} + +} // namespace pipsolar +} // namespace esphome diff --git a/esphome/components/pipsolar/switch/pipsolar_switch.h b/esphome/components/pipsolar/switch/pipsolar_switch.h new file mode 100644 index 0000000000..3fe4c7dfa1 --- /dev/null +++ b/esphome/components/pipsolar/switch/pipsolar_switch.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../pipsolar.h" +#include "esphome/components/switch/switch.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace pipsolar { +class Pipsolar; +class PipsolarSwitch : public switch_::Switch, public Component { + public: + void set_parent(Pipsolar *parent) { this->parent_ = parent; }; + void set_on_command(std::string command) { this->on_command_ = std::move(command); }; + void set_off_command(std::string command) { this->off_command_ = std::move(command); }; + void dump_config() override; + + protected: + void write_state(bool state) override; + std::string on_command_; + std::string off_command_; + Pipsolar *parent_; +}; + +} // namespace pipsolar +} // namespace esphome diff --git a/esphome/components/pipsolar/text_sensor/__init__.py b/esphome/components/pipsolar/text_sensor/__init__.py new file mode 100644 index 0000000000..fe6c4979f3 --- /dev/null +++ b/esphome/components/pipsolar/text_sensor/__init__.py @@ -0,0 +1,52 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import CONF_ID +from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA, pipsolar_ns + +DEPENDENCIES = ["uart"] + +CONF_DEVICE_MODE = "device_mode" +CONF_LAST_QPIGS = "last_qpigs" +CONF_LAST_QPIRI = "last_qpiri" +CONF_LAST_QMOD = "last_qmod" +CONF_LAST_QFLAG = "last_qflag" +CONF_LAST_QPIWS = "last_qpiws" +CONF_LAST_QT = "last_qt" +CONF_LAST_QMN = "last_qmn" + +PipsolarTextSensor = pipsolar_ns.class_( + "PipsolarTextSensor", text_sensor.TextSensor, cg.Component +) + +TYPES = [ + CONF_DEVICE_MODE, + CONF_LAST_QPIGS, + CONF_LAST_QPIRI, + CONF_LAST_QMOD, + CONF_LAST_QFLAG, + CONF_LAST_QPIWS, + CONF_LAST_QT, + CONF_LAST_QMN, +] + +CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend( + { + cv.Optional(type): text_sensor.TEXT_SENSOR_SCHEMA.extend( + {cv.GenerateID(): cv.declare_id(PipsolarTextSensor)} + ) + for type in TYPES + } +) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_PIPSOLAR_ID]) + + for type in TYPES: + if type in config: + conf = config[type] + var = cg.new_Pvariable(conf[CONF_ID]) + await text_sensor.register_text_sensor(var, conf) + await cg.register_component(var, conf) + cg.add(getattr(paren, f"set_{type}")(var)) diff --git a/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.cpp b/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.cpp new file mode 100644 index 0000000000..ee1fe2d1d8 --- /dev/null +++ b/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.cpp @@ -0,0 +1,13 @@ +#include "pipsolar_textsensor.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace pipsolar { + +static const char *const TAG = "pipsolar.text_sensor"; + +void PipsolarTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Pipsolar TextSensor", this); } + +} // namespace pipsolar +} // namespace esphome diff --git a/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.h b/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.h new file mode 100644 index 0000000000..871f6d8dee --- /dev/null +++ b/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../pipsolar.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace pipsolar { +class Pipsolar; +class PipsolarTextSensor : public Component, public text_sensor::TextSensor { + public: + void set_parent(Pipsolar *parent) { this->parent_ = parent; }; + void dump_config() override; + + protected: + Pipsolar *parent_; +}; + +} // namespace pipsolar +} // namespace esphome diff --git a/tests/test4.yaml b/tests/test4.yaml index 2857e8ca5f..e52e8cc33c 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -56,6 +56,9 @@ time: tuya: time_id: sntp_time +pipsolar: + id: inverter0 + sensor: - platform: homeassistant entity_id: sensor.hello_world @@ -63,6 +66,140 @@ sensor: - platform: tuya id: tuya_sensor sensor_datapoint: 1 + - platform: pipsolar + pipsolar_id: inverter0 + grid_rating_voltage: + id: inverter0_grid_rating_voltage + name: inverter0_grid_rating_voltage + grid_rating_current: + id: inverter0_grid_rating_current + name: inverter0_grid_rating_current + ac_output_rating_voltage: + id: inverter0_ac_output_rating_voltage + name: inverter0_ac_output_rating_voltage + ac_output_rating_frequency: + id: inverter0_ac_output_rating_frequency + name: inverter0_ac_output_rating_frequency + ac_output_rating_current: + id: inverter0_ac_output_rating_current + name: inverter0_ac_output_rating_current + ac_output_rating_apparent_power: + id: inverter0_ac_output_rating_apparent_power + name: inverter0_ac_output_rating_apparent_power + ac_output_rating_active_power: + id: inverter0_ac_output_rating_active_power + name: inverter0_ac_output_rating_active_power + battery_rating_voltage: + id: inverter0_battery_rating_voltage + name: inverter0_battery_rating_voltage + battery_recharge_voltage: + id: inverter0_battery_recharge_voltage + name: inverter0_battery_recharge_voltage + battery_under_voltage: + id: inverter0_battery_under_voltage + name: inverter0_battery_under_voltage + battery_bulk_voltage: + id: inverter0_battery_bulk_voltage + name: inverter0_battery_bulk_voltage + battery_float_voltage: + id: inverter0_battery_float_voltage + name: inverter0_battery_float_voltage + battery_type: + id: inverter0_battery_type + name: inverter0_battery_type + current_max_ac_charging_current: + id: inverter0_current_max_ac_charging_current + name: inverter0_current_max_ac_charging_current + current_max_charging_current: + id: inverter0_current_max_charging_current + name: inverter0_current_max_charging_current + input_voltage_range: + id: inverter0_input_voltage_range + name: inverter0_input_voltage_range + output_source_priority: + id: inverter0_output_source_priority + name: inverter0_output_source_priority + charger_source_priority: + id: inverter0_charger_source_priority + name: inverter0_charger_source_priority + parallel_max_num: + id: inverter0_parallel_max_num + name: inverter0_parallel_max_num + machine_type: + id: inverter0_machine_type + name: inverter0_machine_type + topology: + id: inverter0_topology + name: inverter0_topology + output_mode: + id: inverter0_output_mode + name: inverter0_output_mode + battery_redischarge_voltage: + id: inverter0_battery_redischarge_voltage + name: inverter0_battery_redischarge_voltage + pv_ok_condition_for_parallel: + id: inverter0_pv_ok_condition_for_parallel + name: inverter0_pv_ok_condition_for_parallel + pv_power_balance: + id: inverter0_pv_power_balance + name: inverter0_pv_power_balance + grid_voltage: + id: inverter0_grid_voltage + name: inverter0_grid_voltage + grid_frequency: + id: inverter0_grid_frequency + name: inverter0_grid_frequency + ac_output_voltage: + id: inverter0_ac_output_voltage + name: inverter0_ac_output_voltage + ac_output_frequency: + id: inverter0_ac_output_frequency + name: inverter0_ac_output_frequency + ac_output_apparent_power: + id: inverter0_ac_output_apparent_power + name: inverter0_ac_output_apparent_power + ac_output_active_power: + id: inverter0_ac_output_active_power + name: inverter0_ac_output_active_power + output_load_percent: + id: inverter0_output_load_percent + name: inverter0_output_load_percent + bus_voltage: + id: inverter0_bus_voltage + name: inverter0_bus_voltage + battery_voltage: + id: inverter0_battery_voltage + name: inverter0_battery_voltage + battery_charging_current: + id: inverter0_battery_charging_current + name: inverter0_battery_charging_current + battery_capacity_percent: + id: inverter0_battery_capacity_percent + name: inverter0_battery_capacity_percent + inverter_heat_sink_temperature: + id: inverter0_inverter_heat_sink_temperature + name: inverter0_inverter_heat_sink_temperature + pv_input_current_for_battery: + id: inverter0_pv_input_current_for_battery + name: inverter0_pv_input_current_for_battery + pv_input_voltage: + id: inverter0_pv_input_voltage + name: inverter0_pv_input_voltage + battery_voltage_scc: + id: inverter0_battery_voltage_scc + name: inverter0_battery_voltage_scc + battery_discharge_current: + id: inverter0_battery_discharge_current + name: inverter0_battery_discharge_current + battery_voltage_offset_for_fans_on: + id: inverter0_battery_voltage_offset_for_fans_on + name: inverter0_battery_voltage_offset_for_fans_on + eeprom_version: + id: inverter0_eeprom_version + name: inverter0_eeprom_version + pv_charging_power: + id: inverter0_pv_charging_power + name: inverter0_pv_charging_power - platform: "hrxl_maxsonar_wr" name: "Rainwater Tank Level" filters: @@ -95,6 +232,59 @@ binary_sensor: - platform: tuya id: tuya_binary_sensor sensor_datapoint: 1 + - platform: pipsolar + pipsolar_id: inverter0 + add_sbu_priority_version: + id: inverter0_add_sbu_priority_version + name: inverter0_add_sbu_priority_version + configuration_status: + id: inverter0_configuration_status + name: inverter0_configuration_status + scc_firmware_version: + id: inverter0_scc_firmware_version + name: inverter0_scc_firmware_version + load_status: + id: inverter0_load_status + name: inverter0_load_status + battery_voltage_to_steady_while_charging: + id: inverter0_battery_voltage_to_steady_while_charging + name: inverter0_battery_voltage_to_steady_while_charging + charging_status: + id: inverter0_charging_status + name: inverter0_charging_status + scc_charging_status: + id: inverter0_scc_charging_status + name: inverter0_scc_charging_status + ac_charging_status: + id: inverter0_ac_charging_status + name: inverter0_ac_charging_status + charging_to_floating_mode: + id: inverter0_charging_to_floating_mode + name: inverter0_charging_to_floating_mode + switch_on: + id: inverter0_switch_on + name: inverter0_switch_on + dustproof_installed: + id: inverter0_dustproof_installed + name: inverter0_dustproof_installed + silence_buzzer_open_buzzer: + id: inverter0_silence_buzzer_open_buzzer + name: inverter0_silence_buzzer_open_buzzer + overload_bypass_function: + id: inverter0_overload_bypass_function + name: inverter0_overload_bypass_function + lcd_escape_to_default: + id: inverter0_lcd_escape_to_default + name: inverter0_lcd_escape_to_default + overload_restart_function: + id: inverter0_overload_restart_function + name: inverter0_overload_restart_function + over_temperature_restart_function: + id: inverter0_over_temperature_restart_function + name: inverter0_over_temperature_restart_function + backlight_on: + id: inverter0_backlight_on + name: inverter0_backlight_on - platform: template id: ar1 lambda: 'return {};' @@ -131,6 +321,20 @@ switch: - platform: tuya id: tuya_switch switch_datapoint: 1 + - platform: pipsolar + pipsolar_id: inverter0 + output_source_priority_utility: + name: inverter0_output_source_priority_utility + output_source_priority_solar: + name: inverter0_output_source_priority_solar + output_source_priority_battery: + name: inverter0_output_source_priority_battery + input_voltage_range: + name: inverter0_input_voltage_range + pv_ok_condition_for_parallel: + name: inverter0_pv_ok_condition_for_parallel + pv_power_balance: + name: inverter0_pv_power_balance light: - platform: fastled_clockless @@ -204,6 +408,30 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); +text_sensor: + - platform: pipsolar + pipsolar_id: inverter0 + device_mode: + id: inverter0_device_mode + name: inverter0_device_mode + last_qpigs: + id: inverter0_last_qpigs + name: inverter0_last_qpigs + last_qpiri: + id: inverter0_last_qpiri + name: inverter0_last_qpiri + last_qmod: + id: inverter0_last_qmod + name: inverter0_last_qmod + last_qflag: + id: inverter0_last_qflag + name: inverter0_last_qflag + +output: + - platform: pipsolar + pipsolar_id: inverter0 + battery_recharge_voltage: + id: inverter0_battery_recharge_voltage_out esp32_camera: name: ESP-32 Camera data_pins: [GPIO17, GPIO35, GPIO34, GPIO5, GPIO39, GPIO18, GPIO36, GPIO19] From e5366dbbe73ce69e68e2b2c545bfa26302c79c53 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Tue, 10 Aug 2021 21:55:36 +0200 Subject: [PATCH 1176/1841] Add deassert_rts_dtr option to force RTS/DTR low when using miniterm (#2089) --- esphome/__main__.py | 14 +++++++++++++- esphome/components/logger/__init__.py | 2 ++ esphome/const.py | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 614bc12bae..b4770914a7 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -11,6 +11,7 @@ from esphome.config import iter_components, read_config, strip_default_ids from esphome.const import ( CONF_BAUD_RATE, CONF_BROKER, + CONF_DEASSERT_RTS_DTR, CONF_LOGGER, CONF_OTA, CONF_PASSWORD, @@ -99,10 +100,21 @@ def run_miniterm(config, port): baud_rate = config["logger"][CONF_BAUD_RATE] if baud_rate == 0: _LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.") + return _LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate) backtrace_state = False - with serial.Serial(port, baudrate=baud_rate) as ser: + ser = serial.Serial() + ser.baudrate = baud_rate + ser.port = port + + # We can't set to False by default since it leads to toggling and hence + # ESP32 resets on some platforms. + if config["logger"][CONF_DEASSERT_RTS_DTR]: + ser.dtr = False + ser.rts = False + + with ser: while True: try: raw = ser.readline() diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 8d79c96f63..55178941ec 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -7,6 +7,7 @@ from esphome.automation import LambdaAction from esphome.const import ( CONF_ARGS, CONF_BAUD_RATE, + CONF_DEASSERT_RTS_DTR, CONF_FORMAT, CONF_HARDWARE_UART, CONF_ID, @@ -104,6 +105,7 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(): cv.declare_id(Logger), cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int, cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes, + cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean, cv.Optional(CONF_HARDWARE_UART, default="UART0"): uart_selection, cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level, cv.Optional(CONF_LOGS, default={}): cv.Schema( diff --git a/esphome/const.py b/esphome/const.py index 9f3ce13b46..cd095677b3 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -163,6 +163,7 @@ CONF_DATA_TEMPLATE = "data_template" CONF_DAYS_OF_MONTH = "days_of_month" CONF_DAYS_OF_WEEK = "days_of_week" CONF_DC_PIN = "dc_pin" +CONF_DEASSERT_RTS_DTR = "deassert_rts_dtr" CONF_DEBOUNCE = "debounce" CONF_DECELERATION = "deceleration" CONF_DEFAULT_MODE = "default_mode" From 947c104eff56ae8ed5e242f1276cebc81aeb98c2 Mon Sep 17 00:00:00 2001 From: buxtronix Date: Wed, 11 Aug 2021 14:07:10 +1000 Subject: [PATCH 1177/1841] Support for AM43 BLE blind motors (#1744) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ben Buxton Co-authored-by: Otto Winter Co-authored-by: Stefan Agner Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: René Klomp Co-authored-by: Oxan van Leeuwen Co-authored-by: Geoff Davis Co-authored-by: Paulus Schoutsen Co-authored-by: dentra Co-authored-by: Guillermo Ruffino Co-authored-by: Franck Nijhof Co-authored-by: Barry Loong Co-authored-by: Sergey V. DUDANOV Co-authored-by: Balazs Scheidler --- CODEOWNERS | 2 + esphome/components/am43/__init__.py | 0 esphome/components/am43/am43.cpp | 115 ++++++++++++++ esphome/components/am43/am43.h | 45 ++++++ esphome/components/am43/am43_base.cpp | 142 ++++++++++++++++++ esphome/components/am43/am43_base.h | 78 ++++++++++ esphome/components/am43/cover/__init__.py | 36 +++++ esphome/components/am43/cover/am43_cover.cpp | 149 +++++++++++++++++++ esphome/components/am43/cover/am43_cover.h | 45 ++++++ esphome/components/am43/sensor.py | 46 ++++++ esphome/components/ble_client/ble_client.h | 2 +- tests/test1.yaml | 4 + 12 files changed, 663 insertions(+), 1 deletion(-) create mode 100644 esphome/components/am43/__init__.py create mode 100644 esphome/components/am43/am43.cpp create mode 100644 esphome/components/am43/am43.h create mode 100644 esphome/components/am43/am43_base.cpp create mode 100644 esphome/components/am43/am43_base.h create mode 100644 esphome/components/am43/cover/__init__.py create mode 100644 esphome/components/am43/cover/am43_cover.cpp create mode 100644 esphome/components/am43/cover/am43_cover.h create mode 100644 esphome/components/am43/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 605222df63..1298d4d43d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -14,6 +14,8 @@ esphome/core/* @esphome/core esphome/components/ac_dimmer/* @glmnet esphome/components/adc/* @esphome/core esphome/components/addressable_light/* @justfalter +esphome/components/am43/* @buxtronix +esphome/components/am43/cover/* @buxtronix esphome/components/animation/* @syndlex esphome/components/anova/* @buxtronix esphome/components/api/* @OttoWinter diff --git a/esphome/components/am43/__init__.py b/esphome/components/am43/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/am43/am43.cpp b/esphome/components/am43/am43.cpp new file mode 100644 index 0000000000..e60bb2474c --- /dev/null +++ b/esphome/components/am43/am43.cpp @@ -0,0 +1,115 @@ +#include "am43.h" +#include "esphome/core/log.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace am43 { + +static const char *TAG = "am43"; + +void Am43::dump_config() { + ESP_LOGCONFIG(TAG, "AM43"); + LOG_SENSOR(" ", "Battery", this->battery_); + LOG_SENSOR(" ", "Illuminance", this->illuminance_); +} + +void Am43::setup() { + this->encoder_ = new Am43Encoder(); + this->decoder_ = new Am43Decoder(); + this->logged_in_ = false; + this->last_battery_update_ = 0; + this->current_sensor_ = 0; +} + +void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { + switch (event) { + case ESP_GATTC_OPEN_EVT: { + this->logged_in_ = false; + break; + } + case ESP_GATTC_DISCONNECT_EVT: { + this->logged_in_ = false; + this->node_state = espbt::ClientState::Idle; + if (this->battery_ != nullptr) + this->battery_->publish_state(NAN); + if (this->illuminance_ != nullptr) + this->illuminance_->publish_state(NAN); + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: { + auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID); + if (chr == nullptr) { + if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) { + ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", + this->parent_->address_str().c_str()); + } else { + ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?", + this->parent_->address_str().c_str()); + } + break; + } + this->char_handle_ = chr->handle; + break; + } + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + this->node_state = espbt::ClientState::Established; + this->update(); + break; + } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.handle != this->char_handle_) + break; + this->decoder_->decode(param->notify.value, param->notify.value_len); + + if (this->battery_ != nullptr && this->decoder_->has_battery_level() && + millis() - this->last_battery_update_ > 10000) { + this->battery_->publish_state(this->decoder_->battery_level_); + this->last_battery_update_ = millis(); + } + + if (this->illuminance_ != nullptr && this->decoder_->has_light_level()) { + this->illuminance_->publish_state(this->decoder_->light_level_); + } + + if (this->current_sensor_ > 0) { + if (this->illuminance_ != nullptr) { + auto packet = this->encoder_->get_light_level_request(); + auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, + packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, + ESP_GATT_AUTH_REQ_NONE); + if (status) + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), + status); + } + this->current_sensor_ = 0; + } + break; + } + default: + break; + } +} + +void Am43::update() { + if (this->node_state != espbt::ClientState::Established) { + ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str()); + return; + } + if (this->current_sensor_ == 0) { + if (this->battery_ != nullptr) { + auto packet = this->encoder_->get_battery_level_request(); + auto status = + esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, + packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + } + this->current_sensor_++; + } +} + +} // namespace am43 +} // namespace esphome + +#endif diff --git a/esphome/components/am43/am43.h b/esphome/components/am43/am43.h new file mode 100644 index 0000000000..460bb601b2 --- /dev/null +++ b/esphome/components/am43/am43.h @@ -0,0 +1,45 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/ble_client/ble_client.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/am43/am43_base.h" + +#ifdef ARDUINO_ARCH_ESP32 + +#include + +namespace esphome { +namespace am43 { + +namespace espbt = esphome::esp32_ble_tracker; + +class Am43 : public esphome::ble_client::BLEClientNode, public PollingComponent { + public: + void setup() override; + void update() override; + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void set_battery(sensor::Sensor *battery) { battery_ = battery; } + void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; } + + protected: + uint16_t char_handle_; + Am43Encoder *encoder_; + Am43Decoder *decoder_; + bool logged_in_; + sensor::Sensor *battery_{nullptr}; + sensor::Sensor *illuminance_{nullptr}; + uint8_t current_sensor_; + // The AM43 often gets into a state where it spams loads of battery update + // notifications. Here we will limit to no more than every 10s. + uint8_t last_battery_update_; +}; + +} // namespace am43 +} // namespace esphome + +#endif diff --git a/esphome/components/am43/am43_base.cpp b/esphome/components/am43/am43_base.cpp new file mode 100644 index 0000000000..a17df55571 --- /dev/null +++ b/esphome/components/am43/am43_base.cpp @@ -0,0 +1,142 @@ +#include "am43_base.h" + +namespace esphome { +namespace am43 { + +const uint8_t START_PACKET[5] = {0x00, 0xff, 0x00, 0x00, 0x9a}; + +std::string pkt_to_hex(const uint8_t *data, uint16_t len) { + char buf[64]; + memset(buf, 0, 64); + for (int i = 0; i < len; i++) + sprintf(&buf[i * 2], "%02x", data[i]); + std::string ret = buf; + return ret; +} + +Am43Packet *Am43Encoder::get_battery_level_request() { + uint8_t data = 0x1; + return this->encode_(0xA2, &data, 1); +} + +Am43Packet *Am43Encoder::get_light_level_request() { + uint8_t data = 0x1; + return this->encode_(0xAA, &data, 1); +} + +Am43Packet *Am43Encoder::get_position_request() { + uint8_t data = 0x1; + return this->encode_(CMD_GET_POSITION, &data, 1); +} + +Am43Packet *Am43Encoder::get_send_pin_request(uint16_t pin) { + uint8_t data[2]; + data[0] = (pin & 0xFF00) >> 8; + data[1] = pin & 0xFF; + return this->encode_(CMD_SEND_PIN, data, 2); +} + +Am43Packet *Am43Encoder::get_open_request() { + uint8_t data = 0xDD; + return this->encode_(CMD_SET_STATE, &data, 1); +} + +Am43Packet *Am43Encoder::get_close_request() { + uint8_t data = 0xEE; + return this->encode_(CMD_SET_STATE, &data, 1); +} + +Am43Packet *Am43Encoder::get_stop_request() { + uint8_t data = 0xCC; + return this->encode_(CMD_SET_STATE, &data, 1); +} + +Am43Packet *Am43Encoder::get_set_position_request(uint8_t position) { + return this->encode_(CMD_SET_POSITION, &position, 1); +} + +void Am43Encoder::checksum_() { + uint8_t checksum = 0; + int i = 0; + for (i = 0; i < this->packet_.length; i++) + checksum = checksum ^ this->packet_.data[i]; + this->packet_.data[i] = checksum ^ 0xff; + this->packet_.length++; +} + +Am43Packet *Am43Encoder::encode_(uint8_t command, uint8_t *data, uint8_t length) { + memcpy(this->packet_.data, START_PACKET, 5); + this->packet_.data[5] = command; + this->packet_.data[6] = length; + memcpy(&this->packet_.data[7], data, length); + this->packet_.length = length + 7; + this->checksum_(); + ESP_LOGV("am43", "ENC(%d): 0x%s", packet_.length, pkt_to_hex(packet_.data, packet_.length).c_str()); + return &this->packet_; +} + +#define VERIFY_MIN_LENGTH(x) \ + if (length < (x)) \ + return; + +void Am43Decoder::decode(const uint8_t *data, uint16_t length) { + this->has_battery_level_ = false; + this->has_light_level_ = false; + this->has_set_position_response_ = false; + this->has_set_state_response_ = false; + this->has_position_ = false; + this->has_pin_response_ = false; + ESP_LOGV("am43", "DEC(%d): 0x%s", length, pkt_to_hex(data, length).c_str()); + + if (length < 2 || data[0] != 0x9a) + return; + switch (data[1]) { + case CMD_GET_BATTERY_LEVEL: { + VERIFY_MIN_LENGTH(8); + this->battery_level_ = data[7]; + this->has_battery_level_ = true; + break; + } + case CMD_GET_LIGHT_LEVEL: { + VERIFY_MIN_LENGTH(5); + this->light_level_ = 100 * ((float) data[4] / 9); + this->has_light_level_ = true; + break; + } + case CMD_GET_POSITION: { + VERIFY_MIN_LENGTH(6); + this->position_ = data[5]; + this->has_position_ = true; + break; + } + case CMD_NOTIFY_POSITION: { + VERIFY_MIN_LENGTH(5); + this->position_ = data[4]; + this->has_position_ = true; + break; + } + case CMD_SEND_PIN: { + VERIFY_MIN_LENGTH(4); + this->pin_ok_ = data[3] == RESPONSE_ACK; + this->has_pin_response_ = true; + break; + } + case CMD_SET_POSITION: { + VERIFY_MIN_LENGTH(4); + this->set_position_ok_ = data[3] == RESPONSE_ACK; + this->has_set_position_response_ = true; + break; + } + case CMD_SET_STATE: { + VERIFY_MIN_LENGTH(4); + this->set_state_ok_ = data[3] == RESPONSE_ACK; + this->has_set_state_response_ = true; + break; + } + default: + break; + } +}; + +} // namespace am43 +} // namespace esphome diff --git a/esphome/components/am43/am43_base.h b/esphome/components/am43/am43_base.h new file mode 100644 index 0000000000..e817f161fe --- /dev/null +++ b/esphome/components/am43/am43_base.h @@ -0,0 +1,78 @@ +#pragma once + +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace am43 { + +static const uint16_t AM43_SERVICE_UUID = 0xFE50; +static const uint16_t AM43_CHARACTERISTIC_UUID = 0xFE51; +// +// Tuya identifiers, only to detect and warn users as they are incompatible. +static const uint16_t AM43_TUYA_SERVICE_UUID = 0x1910; +static const uint16_t AM43_TUYA_CHARACTERISTIC_UUID = 0x2b11; + +struct Am43Packet { + uint8_t length; + uint8_t data[24]; +}; + +static const uint8_t CMD_GET_BATTERY_LEVEL = 0xA2; +static const uint8_t CMD_GET_LIGHT_LEVEL = 0xAA; +static const uint8_t CMD_GET_POSITION = 0xA7; +static const uint8_t CMD_SEND_PIN = 0x17; +static const uint8_t CMD_SET_STATE = 0x0A; +static const uint8_t CMD_SET_POSITION = 0x0D; +static const uint8_t CMD_NOTIFY_POSITION = 0xA1; + +static const uint8_t RESPONSE_ACK = 0x5A; +static const uint8_t RESPONSE_NACK = 0xA5; + +class Am43Encoder { + public: + Am43Packet *get_battery_level_request(); + Am43Packet *get_light_level_request(); + Am43Packet *get_position_request(); + Am43Packet *get_send_pin_request(uint16_t pin); + Am43Packet *get_open_request(); + Am43Packet *get_close_request(); + Am43Packet *get_stop_request(); + Am43Packet *get_set_position_request(uint8_t position); + + protected: + void checksum_(); + Am43Packet *encode_(uint8_t command, uint8_t *data, uint8_t length); + Am43Packet packet_; +}; + +class Am43Decoder { + public: + void decode(const uint8_t *data, uint16_t length); + bool has_battery_level() { return this->has_battery_level_; } + bool has_light_level() { return this->has_light_level_; } + bool has_set_position_response() { return this->has_set_position_response_; } + bool has_set_state_response() { return this->has_set_state_response_; } + bool has_position() { return this->has_position_; } + bool has_pin_response() { return this->has_pin_response_; } + + union { + uint8_t position_; + uint8_t battery_level_; + float light_level_; + uint8_t set_position_ok_; + uint8_t set_state_ok_; + uint8_t pin_ok_; + }; + + protected: + bool has_battery_level_; + bool has_light_level_; + bool has_set_position_response_; + bool has_set_state_response_; + bool has_position_; + bool has_pin_response_; +}; + +} // namespace am43 +} // namespace esphome diff --git a/esphome/components/am43/cover/__init__.py b/esphome/components/am43/cover/__init__.py new file mode 100644 index 0000000000..1ab0edbe78 --- /dev/null +++ b/esphome/components/am43/cover/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import cover, ble_client +from esphome.const import CONF_ID, CONF_PIN + +CODEOWNERS = ["@buxtronix"] +DEPENDENCIES = ["ble_client"] +AUTO_LOAD = ["am43"] + +CONF_INVERT_POSITION = "invert_position" + +am43_ns = cg.esphome_ns.namespace("am43") +Am43Component = am43_ns.class_( + "Am43Component", cover.Cover, ble_client.BLEClientNode, cg.Component +) + +CONFIG_SCHEMA = ( + cover.COVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(Am43Component), + cv.Optional(CONF_PIN, default=8888): cv.int_range(min=0, max=0xFFFF), + cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, + } + ) + .extend(ble_client.BLE_CLIENT_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_pin(config[CONF_PIN])) + cg.add(var.set_invert_position(config[CONF_INVERT_POSITION])) + yield cg.register_component(var, config) + yield cover.register_cover(var, config) + yield ble_client.register_ble_node(var, config) diff --git a/esphome/components/am43/cover/am43_cover.cpp b/esphome/components/am43/cover/am43_cover.cpp new file mode 100644 index 0000000000..3ae7fe8f8c --- /dev/null +++ b/esphome/components/am43/cover/am43_cover.cpp @@ -0,0 +1,149 @@ +#include "am43_cover.h" +#include "esphome/core/log.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace am43 { + +static const char *TAG = "am43_cover"; + +using namespace esphome::cover; + +void Am43Component::dump_config() { + LOG_COVER("", "AM43 Cover", this); + ESP_LOGCONFIG(TAG, " Device Pin: %d", this->pin_); + ESP_LOGCONFIG(TAG, " Invert Position: %d", (int) this->invert_position_); +} + +void Am43Component::setup() { + this->position = COVER_OPEN; + this->encoder_ = new Am43Encoder(); + this->decoder_ = new Am43Decoder(); + this->logged_in_ = false; +} + +void Am43Component::loop() { + if (this->node_state == espbt::ClientState::Established && !this->logged_in_) { + auto packet = this->encoder_->get_send_pin_request(this->pin_); + auto status = + esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, + packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str()); + if (status) + ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status); + else + this->logged_in_ = true; + } +} + +CoverTraits Am43Component::get_traits() { + auto traits = CoverTraits(); + traits.set_supports_position(true); + traits.set_supports_tilt(false); + traits.set_is_assumed_state(false); + return traits; +} + +void Am43Component::control(const CoverCall &call) { + if (this->node_state != espbt::ClientState::Established) { + ESP_LOGW(TAG, "[%s] Cannot send cover control, not connected", this->get_name().c_str()); + return; + } + if (call.get_stop()) { + auto packet = this->encoder_->get_stop_request(); + auto status = + esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, + packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) + ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status); + } + if (call.get_position().has_value()) { + auto pos = *call.get_position(); + + if (this->invert_position_) + pos = 1 - pos; + auto packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100)); + auto status = + esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, + packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) + ESP_LOGW(TAG, "[%s] Error writing set_position command to device, error = %d", this->get_name().c_str(), status); + } +} + +void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) { + switch (event) { + case ESP_GATTC_DISCONNECT_EVT: { + this->logged_in_ = false; + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: { + auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID); + if (chr == nullptr) { + if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) { + ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", this->get_name().c_str()); + } else { + ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?", this->get_name().c_str()); + } + break; + } + this->char_handle_ = chr->handle; + + auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle); + if (status) { + ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status); + } + break; + } + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + this->node_state = espbt::ClientState::Established; + break; + } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.handle != this->char_handle_) + break; + this->decoder_->decode(param->notify.value, param->notify.value_len); + + if (this->decoder_->has_position()) { + this->position = ((float) this->decoder_->position_ / 100.0); + if (!this->invert_position_) + this->position = 1 - this->position; + if (this->position > 0.97) + this->position = 1.0; + if (this->position < 0.02) + this->position = 0.0; + this->publish_state(); + } + + if (this->decoder_->has_pin_response()) { + if (this->decoder_->pin_ok_) { + ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str()); + auto packet = this->encoder_->get_position_request(); + auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, + packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, + ESP_GATT_AUTH_REQ_NONE); + if (status) + ESP_LOGW(TAG, "[%s] Error writing set_position to device, error = %d", this->get_name().c_str(), status); + } else { + ESP_LOGW(TAG, "[%s] AM43 pin rejected!", this->get_name().c_str()); + } + } + + if (this->decoder_->has_set_position_response() && !this->decoder_->set_position_ok_) + ESP_LOGW(TAG, "[%s] Got nack after sending set_position. Bad pin?", this->get_name().c_str()); + + if (this->decoder_->has_set_state_response() && !this->decoder_->set_state_ok_) + ESP_LOGW(TAG, "[%s] Got nack after sending set_state. Bad pin?", this->get_name().c_str()); + break; + } + default: + break; + } +} + +} // namespace am43 +} // namespace esphome + +#endif diff --git a/esphome/components/am43/cover/am43_cover.h b/esphome/components/am43/cover/am43_cover.h new file mode 100644 index 0000000000..5557da49e7 --- /dev/null +++ b/esphome/components/am43/cover/am43_cover.h @@ -0,0 +1,45 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/ble_client/ble_client.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/cover/cover.h" +#include "esphome/components/am43/am43_base.h" + +#ifdef ARDUINO_ARCH_ESP32 + +#include + +namespace esphome { +namespace am43 { + +namespace espbt = esphome::esp32_ble_tracker; + +class Am43Component : public cover::Cover, public esphome::ble_client::BLEClientNode, public Component { + public: + void setup() override; + void loop() override; + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + cover::CoverTraits get_traits() override; + void set_pin(uint16_t pin) { this->pin_ = pin; } + void set_invert_position(bool invert_position) { this->invert_position_ = invert_position; } + + protected: + void control(const cover::CoverCall &call) override; + uint16_t char_handle_; + uint16_t pin_; + bool invert_position_; + Am43Encoder *encoder_; + Am43Decoder *decoder_; + bool logged_in_; + + float position_; +}; + +} // namespace am43 +} // namespace esphome + +#endif diff --git a/esphome/components/am43/sensor.py b/esphome/components/am43/sensor.py new file mode 100644 index 0000000000..c88e529a0c --- /dev/null +++ b/esphome/components/am43/sensor.py @@ -0,0 +1,46 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, ble_client +from esphome.const import ( + CONF_ID, + CONF_BATTERY_LEVEL, + ICON_BATTERY, + CONF_ILLUMINANCE, + ICON_BRIGHTNESS_5, + UNIT_PERCENT, +) + +CODEOWNERS = ["@buxtronix"] + +am43_ns = cg.esphome_ns.namespace("am43") +Am43 = am43_ns.class_("Am43", ble_client.BLEClientNode, cg.PollingComponent) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(Am43), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + UNIT_PERCENT, ICON_BATTERY, 0 + ), + cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( + UNIT_PERCENT, ICON_BRIGHTNESS_5, 0 + ), + } + ) + .extend(ble_client.BLE_CLIENT_SCHEMA) + .extend(cv.polling_component_schema("120s")) +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield ble_client.register_ble_node(var, config) + + if CONF_BATTERY_LEVEL in config: + sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) + cg.add(var.set_battery(sens)) + + if CONF_ILLUMINANCE in config: + sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE]) + cg.add(var.set_illuminance(sens)) diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index 203acc181f..b9d16ddacd 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -25,7 +25,7 @@ class BLEClientNode { public: virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) = 0; - virtual void loop() = 0; + virtual void loop(){}; void set_address(uint64_t address) { address_ = address; } espbt::ESPBTClient *client; // This should be transitioned to Established once the node no longer needs diff --git a/tests/test1.yaml b/tests/test1.yaml index 9b7139de3a..bcf5f932a8 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2145,6 +2145,10 @@ cover: id: template_cover state: CLOSED assumed_state: no + - platform: am43 + name: 'Test AM43' + id: am43_test + ble_client_id: ble_foo debug: From 11477dbc033ffb2603c6acd3062f46a267c1531a Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 11 Aug 2021 06:50:05 +0200 Subject: [PATCH 1178/1841] Fix format warning in Tuya component (#1954) --- esphome/components/tuya/tuya.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index bbab4f04ce..e42c74005e 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -297,7 +297,7 @@ void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) { ESP_LOGD(TAG, "Datapoint %u update to %#08X", datapoint.id, datapoint.value_bitmask); break; default: - ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, datapoint.type); + ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, static_cast(datapoint.type)); return; } From 46f17bea66855f5ca0e96815ac5ca587f320de39 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 11 Aug 2021 06:51:35 +0200 Subject: [PATCH 1179/1841] Modular light transformers (#2124) --- .../components/light/addressable_light.cpp | 91 ++++++++------- esphome/components/light/addressable_light.h | 16 +++ esphome/components/light/light_output.cpp | 12 ++ esphome/components/light/light_output.h | 4 + esphome/components/light/light_state.cpp | 38 +++---- esphome/components/light/light_state.h | 3 - esphome/components/light/light_transformer.h | 106 ++++-------------- esphome/components/light/transformers.h | 75 +++++++++++++ 8 files changed, 195 insertions(+), 150 deletions(-) create mode 100644 esphome/components/light/light_output.cpp create mode 100644 esphome/components/light/transformers.h diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index 4eb9124a7b..12eab6a685 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -24,6 +24,10 @@ void AddressableLight::call_setup() { #endif } +std::unique_ptr AddressableLight::create_default_transition() { + return make_unique(*this); +} + Color esp_color_from_light_color_values(LightColorValues val) { auto r = to_uint8_scale(val.get_color_brightness() * val.get_red()); auto g = to_uint8_scale(val.get_color_brightness() * val.get_green()); @@ -37,66 +41,67 @@ void AddressableLight::write_state(LightState *state) { auto max_brightness = to_uint8_scale(val.get_brightness() * val.get_state()); this->correction_.set_local_brightness(max_brightness); - this->last_transition_progress_ = 0.0f; - this->accumulated_alpha_ = 0.0f; - if (this->is_effect_active()) return; // don't use LightState helper, gamma correction+brightness is handled by ESPColorView + this->all() = esp_color_from_light_color_values(val); +} - if (state->transformer_ == nullptr || !state->transformer_->is_transition()) { - // no transformer active or non-transition one - this->all() = esp_color_from_light_color_values(val); - } else { - // transition transformer active, activate specialized transition for addressable effects - // instead of using a unified transition for all LEDs, we use the current state each LED as the - // start. Warning: ugly +void AddressableLightTransformer::start() { + auto end_values = this->target_values_; + this->target_color_ = esp_color_from_light_color_values(end_values); - // We can't use a direct lerp smoothing here though - that would require creating a copy of the original - // state of each LED at the start of the transition - // Instead, we "fake" the look of the LERP by using an exponential average over time and using - // dynamically-calculated alpha values to match the look of the + // our transition will handle brightness, disable brightness in correction. + this->light_.correction_.set_local_brightness(255); + this->target_color_ *= to_uint8_scale(end_values.get_brightness() * end_values.get_state()); +} - float new_progress = state->transformer_->get_progress(); - float prev_smoothed = LightTransitionTransformer::smoothed_progress(last_transition_progress_); - float new_smoothed = LightTransitionTransformer::smoothed_progress(new_progress); - this->last_transition_progress_ = new_progress; +optional AddressableLightTransformer::apply() { + // Don't try to transition over running effects, instead immediately use the target values. write_state() and the + // effects pick up the change from current_values. + if (this->light_.is_effect_active()) + return this->target_values_; - auto end_values = state->transformer_->get_end_values(); - Color target_color = esp_color_from_light_color_values(end_values); + // Use a specialized transition for addressable lights: instead of using a unified transition for + // all LEDs, we use the current state of each LED as the start. - // our transition will handle brightness, disable brightness in correction. - this->correction_.set_local_brightness(255); - target_color *= to_uint8_scale(end_values.get_brightness() * end_values.get_state()); + // We can't use a direct lerp smoothing here though - that would require creating a copy of the original + // state of each LED at the start of the transition. + // Instead, we "fake" the look of the LERP by using an exponential average over time and using + // dynamically-calculated alpha values to match the look. - float denom = (1.0f - new_smoothed); - float alpha = denom == 0.0f ? 0.0f : (new_smoothed - prev_smoothed) / denom; + float smoothed_progress = LightTransitionTransformer::smoothed_progress(this->get_progress_()); - // We need to use a low-resolution alpha here which makes the transition set in only after ~half of the length - // We solve this by accumulating the fractional part of the alpha over time. - float alpha255 = alpha * 255.0f; - float alpha255int = floorf(alpha255); - float alpha255remainder = alpha255 - alpha255int; + float denom = (1.0f - smoothed_progress); + float alpha = denom == 0.0f ? 0.0f : (smoothed_progress - this->last_transition_progress_) / denom; - this->accumulated_alpha_ += alpha255remainder; - float alpha_add = floorf(this->accumulated_alpha_); - this->accumulated_alpha_ -= alpha_add; + // We need to use a low-resolution alpha here which makes the transition set in only after ~half of the length + // We solve this by accumulating the fractional part of the alpha over time. + float alpha255 = alpha * 255.0f; + float alpha255int = floorf(alpha255); + float alpha255remainder = alpha255 - alpha255int; - alpha255 += alpha_add; - alpha255 = clamp(alpha255, 0.0f, 255.0f); - auto alpha8 = static_cast(alpha255); + this->accumulated_alpha_ += alpha255remainder; + float alpha_add = floorf(this->accumulated_alpha_); + this->accumulated_alpha_ -= alpha_add; - if (alpha8 != 0) { - uint8_t inv_alpha8 = 255 - alpha8; - Color add = target_color * alpha8; + alpha255 += alpha_add; + alpha255 = clamp(alpha255, 0.0f, 255.0f); + auto alpha8 = static_cast(alpha255); - for (auto led : *this) - led = add + led.get() * inv_alpha8; - } + if (alpha8 != 0) { + uint8_t inv_alpha8 = 255 - alpha8; + Color add = this->target_color_ * alpha8; + + for (auto led : this->light_) + led.set(add + led.get() * inv_alpha8); } - this->schedule_show(); + this->last_transition_progress_ = smoothed_progress; + this->light_.schedule_show(); + + return {}; } } // namespace light diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index 54a1e3c6b4..ab1efdf160 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -8,6 +8,7 @@ #include "esp_range_view.h" #include "light_output.h" #include "light_state.h" +#include "transformers.h" #ifdef USE_POWER_SUPPLY #include "esphome/components/power_supply/power_supply.h" @@ -53,6 +54,7 @@ class AddressableLight : public LightOutput, public Component { bool is_effect_active() const { return this->effect_active_; } void set_effect_active(bool effect_active) { this->effect_active_ = effect_active; } void write_state(LightState *state) override; + std::unique_ptr create_default_transition() override; void set_correction(float red, float green, float blue, float white = 1.0f) { this->correction_.set_max_brightness( Color(to_uint8_scale(red), to_uint8_scale(green), to_uint8_scale(blue), to_uint8_scale(white))); @@ -70,6 +72,8 @@ class AddressableLight : public LightOutput, public Component { void call_setup() override; protected: + friend class AddressableLightTransformer; + bool should_show_() const { return this->effect_active_ || this->next_show_; } void mark_shown_() { this->next_show_ = false; @@ -92,6 +96,18 @@ class AddressableLight : public LightOutput, public Component { power_supply::PowerSupplyRequester power_; #endif LightState *state_parent_{nullptr}; +}; + +class AddressableLightTransformer : public LightTransitionTransformer { + public: + AddressableLightTransformer(AddressableLight &light) : light_(light) {} + + void start() override; + optional apply() override; + + protected: + AddressableLight &light_; + Color target_color_{}; float last_transition_progress_{0.0f}; float accumulated_alpha_{0.0f}; }; diff --git a/esphome/components/light/light_output.cpp b/esphome/components/light/light_output.cpp new file mode 100644 index 0000000000..e805a0b694 --- /dev/null +++ b/esphome/components/light/light_output.cpp @@ -0,0 +1,12 @@ +#include "light_output.h" +#include "transformers.h" + +namespace esphome { +namespace light { + +std::unique_ptr LightOutput::create_default_transition() { + return make_unique(); +} + +} // namespace light +} // namespace esphome diff --git a/esphome/components/light/light_output.h b/esphome/components/light/light_output.h index d05cbf9436..7568ea6831 100644 --- a/esphome/components/light/light_output.h +++ b/esphome/components/light/light_output.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "light_traits.h" #include "light_state.h" +#include "light_transformer.h" namespace esphome { namespace light { @@ -13,6 +14,9 @@ class LightOutput { /// Return the LightTraits of this LightOutput. virtual LightTraits get_traits() = 0; + /// Return the default transformer used for transitions. + virtual std::unique_ptr create_default_transition(); + virtual void setup_state(LightState *state) {} virtual void write_state(LightState *state) = 0; diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 3c4a10c88e..278229fbd1 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -1,6 +1,7 @@ -#include "light_state.h" #include "esphome/core/log.h" +#include "light_state.h" #include "light_output.h" +#include "transformers.h" namespace esphome { namespace light { @@ -105,19 +106,19 @@ void LightState::loop() { // Apply transformer (if any) if (this->transformer_ != nullptr) { + auto values = this->transformer_->apply(); + this->next_write_ = values.has_value(); // don't write if transformer doesn't want us to + if (values.has_value()) + this->current_values = *values; + if (this->transformer_->is_finished()) { - this->remote_values = this->current_values = this->transformer_->get_end_values(); - this->target_state_reached_callback_.call(); - if (this->transformer_->publish_at_end()) - this->publish_state(); + this->transformer_->stop(); this->transformer_ = nullptr; - } else { - this->current_values = this->transformer_->get_values(); - this->remote_values = this->transformer_->get_remote_values(); + this->target_state_reached_callback_.call(); } - this->next_write_ = true; } + // Write state to the light if (this->next_write_) { this->output_->write_state(this); this->next_write_ = false; @@ -217,18 +218,21 @@ void LightState::stop_effect_() { } void LightState::start_transition_(const LightColorValues &target, uint32_t length) { - this->transformer_ = make_unique(millis(), length, this->current_values, target); - this->remote_values = this->transformer_->get_remote_values(); + this->transformer_ = this->output_->create_default_transition(); + this->transformer_->setup(this->current_values, target, length); + this->remote_values = target; } void LightState::start_flash_(const LightColorValues &target, uint32_t length) { - LightColorValues end_colors = this->current_values; + LightColorValues end_colors = this->remote_values; // If starting a flash if one is already happening, set end values to end values of current flash // Hacky but works if (this->transformer_ != nullptr) - end_colors = this->transformer_->get_end_values(); - this->transformer_ = make_unique(millis(), length, end_colors, target); - this->remote_values = this->transformer_->get_remote_values(); + end_colors = this->transformer_->get_target_values(); + + this->transformer_ = make_unique(*this); + this->transformer_->setup(end_colors, target, length); + this->remote_values = target; } void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) { @@ -240,10 +244,6 @@ void LightState::set_immediately_(const LightColorValues &target, bool set_remot this->next_write_ = true; } -void LightState::set_transformer_(std::unique_ptr transformer) { - this->transformer_ = std::move(transformer); -} - void LightState::save_remote_values_() { LightStateRTCState saved; saved.color_mode = this->remote_values.get_color_mode(); diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index 23527e8a47..dfea9a15f4 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -157,9 +157,6 @@ class LightState : public Nameable, public Component { /// Internal method to set the color values to target immediately (with no transition). void set_immediately_(const LightColorValues &target, bool set_remote_values); - /// Internal method to start a transformer. - void set_transformer_(std::unique_ptr transformer); - /// Internal method to save the current remote_values to the preferences void save_remote_values_(); diff --git a/esphome/components/light/light_transformer.h b/esphome/components/light/light_transformer.h index 7db6265a89..c5181abd4f 100644 --- a/esphome/components/light/light_transformer.h +++ b/esphome/components/light/light_transformer.h @@ -1,42 +1,42 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "light_color_values.h" namespace esphome { namespace light { -/// Base-class for all light color transformers, such as transitions or flashes. +/// Base class for all light color transformers, such as transitions or flashes. class LightTransformer { public: - LightTransformer(uint32_t start_time, uint32_t length, const LightColorValues &start_values, - const LightColorValues &target_values) - : start_time_(start_time), length_(length), start_values_(start_values), target_values_(target_values) {} + void setup(const LightColorValues &start_values, const LightColorValues &target_values, uint32_t length) { + this->start_time_ = millis(); + this->length_ = length; + this->start_values_ = start_values; + this->target_values_ = target_values; + this->start(); + } - LightTransformer() = delete; + /// Indicates whether this transformation is finished. + virtual bool is_finished() { return this->get_progress_() >= 1.0f; } - /// Whether this transformation is finished - virtual bool is_finished() { return this->get_progress() >= 1.0f; } + /// This will be called before the transition is started. + virtual void start() {} - /// This will be called to get the current values for output. - virtual LightColorValues get_values() = 0; + /// This will be called while the transformer is active to apply the transition to the light. Can either write to the + /// light directly, or return LightColorValues that will be applied. + virtual optional apply() = 0; - /// The values that should be reported to the front-end. - virtual LightColorValues get_remote_values() { return this->get_target_values_(); } + /// This will be called after transition is finished. + virtual void stop() {} - /// The values that should be set after this transformation is complete. - virtual LightColorValues get_end_values() { return this->get_target_values_(); } + const LightColorValues &get_start_values() const { return this->start_values_; } - virtual bool publish_at_end() = 0; - virtual bool is_transition() = 0; - - float get_progress() { return clamp((millis() - this->start_time_) / float(this->length_), 0.0f, 1.0f); } + const LightColorValues &get_target_values() const { return this->target_values_; } protected: - const LightColorValues &get_start_values_() const { return this->start_values_; } - - const LightColorValues &get_target_values_() const { return this->target_values_; } + /// The progress of this transition, on a scale of 0 to 1. + float get_progress_() { return clamp((millis() - this->start_time_) / float(this->length_), 0.0f, 1.0f); } uint32_t start_time_; uint32_t length_; @@ -44,69 +44,5 @@ class LightTransformer { LightColorValues target_values_; }; -class LightTransitionTransformer : public LightTransformer { - public: - LightTransitionTransformer(uint32_t start_time, uint32_t length, const LightColorValues &start_values, - const LightColorValues &target_values) - : LightTransformer(start_time, length, start_values, target_values) { - // When turning light on from off state, use colors from new. - if (!this->start_values_.is_on() && this->target_values_.is_on()) { - this->start_values_ = LightColorValues(target_values); - this->start_values_.set_brightness(0.0f); - } - - // When changing color mode, go through off state, as color modes are orthogonal and there can't be two active. - if (this->start_values_.get_color_mode() != this->target_values_.get_color_mode()) { - this->changing_color_mode_ = true; - this->intermediate_values_ = LightColorValues(this->get_start_values_()); - this->intermediate_values_.set_state(false); - } - } - - LightColorValues get_values() override { - float p = this->get_progress(); - - // Halfway through, when intermediate state (off) is reached, flip it to the target, but remain off. - if (this->changing_color_mode_ && p > 0.5f && - this->intermediate_values_.get_color_mode() != this->target_values_.get_color_mode()) { - this->intermediate_values_ = LightColorValues(this->get_end_values()); - this->intermediate_values_.set_state(false); - } - - LightColorValues &start = this->changing_color_mode_ && p > 0.5f ? this->intermediate_values_ : this->start_values_; - LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->target_values_; - if (this->changing_color_mode_) - p = p < 0.5f ? p * 2 : (p - 0.5) * 2; - - float v = LightTransitionTransformer::smoothed_progress(p); - return LightColorValues::lerp(start, end, v); - } - - bool publish_at_end() override { return false; } - bool is_transition() override { return true; } - - // This looks crazy, but it reduces to 6x^5 - 15x^4 + 10x^3 which is just a smooth sigmoid-like - // transition from 0 to 1 on x = [0, 1] - static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); } - - protected: - bool changing_color_mode_{false}; - LightColorValues intermediate_values_{}; -}; - -class LightFlashTransformer : public LightTransformer { - public: - LightFlashTransformer(uint32_t start_time, uint32_t length, const LightColorValues &start_values, - const LightColorValues &target_values) - : LightTransformer(start_time, length, start_values, target_values) {} - - LightColorValues get_values() override { return this->get_target_values_(); } - - LightColorValues get_end_values() override { return this->get_start_values_(); } - - bool publish_at_end() override { return true; } - bool is_transition() override { return false; } -}; - } // namespace light } // namespace esphome diff --git a/esphome/components/light/transformers.h b/esphome/components/light/transformers.h new file mode 100644 index 0000000000..fd0bfd20f3 --- /dev/null +++ b/esphome/components/light/transformers.h @@ -0,0 +1,75 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "light_color_values.h" +#include "light_state.h" +#include "light_transformer.h" + +namespace esphome { +namespace light { + +class LightTransitionTransformer : public LightTransformer { + public: + void start() override { + // When turning light on from off state, use colors from target state. + if (!this->start_values_.is_on() && this->target_values_.is_on()) { + this->start_values_ = LightColorValues(this->target_values_); + this->start_values_.set_brightness(0.0f); + } + + // When changing color mode, go through off state, as color modes are orthogonal and there can't be two active. + if (this->start_values_.get_color_mode() != this->target_values_.get_color_mode()) { + this->changing_color_mode_ = true; + this->intermediate_values_ = this->start_values_; + this->intermediate_values_.set_state(false); + } + } + + optional apply() override { + float p = this->get_progress_(); + + // Halfway through, when intermediate state (off) is reached, flip it to the target, but remain off. + if (this->changing_color_mode_ && p > 0.5f && + this->intermediate_values_.get_color_mode() != this->target_values_.get_color_mode()) { + this->intermediate_values_ = this->target_values_; + this->intermediate_values_.set_state(false); + } + + LightColorValues &start = this->changing_color_mode_ && p > 0.5f ? this->intermediate_values_ : this->start_values_; + LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->target_values_; + if (this->changing_color_mode_) + p = p < 0.5f ? p * 2 : (p - 0.5) * 2; + + float v = LightTransitionTransformer::smoothed_progress(p); + return LightColorValues::lerp(start, end, v); + } + + protected: + // This looks crazy, but it reduces to 6x^5 - 15x^4 + 10x^3 which is just a smooth sigmoid-like + // transition from 0 to 1 on x = [0, 1] + static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); } + + bool changing_color_mode_{false}; + LightColorValues intermediate_values_{}; +}; + +class LightFlashTransformer : public LightTransformer { + public: + LightFlashTransformer(LightState &state) : state_(state) {} + + optional apply() override { return this->get_target_values(); } + + // Restore the original values after the flash. + void stop() override { + this->state_.current_values = this->get_start_values(); + this->state_.remote_values = this->get_start_values(); + this->state_.publish_state(); + } + + protected: + LightState &state_; +}; + +} // namespace light +} // namespace esphome From 8c41fc2b1dd22fb6e574d6982f88b32a60204b49 Mon Sep 17 00:00:00 2001 From: Branimir Lambov Date: Wed, 11 Aug 2021 06:14:17 +0100 Subject: [PATCH 1180/1841] Support for the DKE screen version of LilyGo-TTGO-T5 V2.3 (#1969) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .../components/waveshare_epaper/display.py | 8 +- .../waveshare_epaper/waveshare_epaper.cpp | 131 ++++++++++++++++++ .../waveshare_epaper/waveshare_epaper.h | 28 ++++ 3 files changed, 165 insertions(+), 2 deletions(-) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 5a40b92ce2..e825456c36 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -43,6 +43,9 @@ WaveshareEPaper7P5In = waveshare_epaper_ns.class_( WaveshareEPaper7P5InV2 = waveshare_epaper_ns.class_( "WaveshareEPaper7P5InV2", WaveshareEPaper ) +WaveshareEPaper2P13InDKE = waveshare_epaper_ns.class_( + "WaveshareEPaper2P13InDKE", WaveshareEPaper +) WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel") WaveshareEPaperTypeBModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeBModel") @@ -64,13 +67,14 @@ MODELS = { "5.83in": ("b", WaveshareEPaper5P8In), "7.50in": ("b", WaveshareEPaper7P5In), "7.50inv2": ("b", WaveshareEPaper7P5InV2), + "2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE), } def validate_full_update_every_only_type_a(value): if CONF_FULL_UPDATE_EVERY not in value: return value - if MODELS[value[CONF_MODEL]][0] != "a": + if MODELS[value[CONF_MODEL]][0] == "b": raise cv.Invalid( "The 'full_update_every' option is only available for models " "'1.54in', '1.54inV2', '2.13in', '2.90in', and '2.90inV2'." @@ -101,7 +105,7 @@ async def to_code(config): if model_type == "a": rhs = WaveshareEPaperTypeA.new(model) var = cg.Pvariable(config[CONF_ID], rhs, WaveshareEPaperTypeA) - elif model_type == "b": + elif model_type in ("b", "c"): rhs = model.new() var = cg.Pvariable(config[CONF_ID], rhs, model) else: diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 17d04b556b..c32e7d27a0 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -1080,5 +1080,136 @@ void WaveshareEPaper7P5InV2::dump_config() { LOG_PIN(" Busy Pin: ", this->busy_pin_); LOG_UPDATE_INTERVAL(this); } + +static const uint8_t LUT_SIZE_TTGO_DKE_PART = 153; + +static const uint8_t PART_UPDATE_LUT_TTGO_DKE[LUT_SIZE_TTGO_DKE_PART] = { + 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, + // 0x22, 0x17, 0x41, 0x0, 0x32, 0x32 +}; + +void WaveshareEPaper2P13InDKE::initialize() {} +void HOT WaveshareEPaper2P13InDKE::display() { + bool partial = this->at_update_ != 0; + this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; + + if (partial) + ESP_LOGI(TAG, "Performing partial e-paper update."); + else + ESP_LOGI(TAG, "Performing full e-paper update."); + + // start and set up data format + this->command(0x12); + this->wait_until_idle_(); + + this->command(0x11); + this->data(0x03); + this->command(0x44); + this->data(1); + this->data(this->get_width_internal() / 8); + this->command(0x45); + this->data(0); + this->data(0); + this->data(this->get_height_internal()); + this->data(0); + this->command(0x4e); + this->data(1); + this->command(0x4f); + this->data(0); + this->data(0); + + if (!partial) { + // send data + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // commit + this->command(0x20); + this->wait_until_idle_(); + } else { + // set up partial update + this->command(0x32); + for (uint8_t v : PART_UPDATE_LUT_TTGO_DKE) + this->data(v); + this->command(0x3F); + this->data(0x22); + + this->command(0x03); + this->data(0x17); + this->command(0x04); + this->data(0x41); + this->data(0x00); + this->data(0x32); + this->command(0x2C); + this->data(0x32); + + this->command(0x37); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x40); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + + this->command(0x3C); + this->data(0x80); + this->command(0x22); + this->data(0xC0); + this->command(0x20); + this->wait_until_idle_(); + + // send data + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // commit as partial + this->command(0x22); + this->data(0xCF); + this->command(0x20); + this->wait_until_idle_(); + + // data must be sent again on partial update + delay(300); // NOLINT + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + delay(300); // NOLINT + } + + ESP_LOGI(TAG, "Completed e-paper update."); +} + +int WaveshareEPaper2P13InDKE::get_width_internal() { return 128; } +int WaveshareEPaper2P13InDKE::get_height_internal() { return 250; } +int WaveshareEPaper2P13InDKE::idle_timeout_() { return 5000; } +void WaveshareEPaper2P13InDKE::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.13inDKE"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void WaveshareEPaper2P13InDKE::set_full_update_every(uint32_t full_update_every) { + this->full_update_every_ = full_update_every; +} + } // namespace waveshare_epaper } // namespace esphome diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 228753d1b7..f7603c5af0 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -301,5 +301,33 @@ class WaveshareEPaper7P5InV2 : public WaveshareEPaper { int get_height_internal() override; }; +class WaveshareEPaper2P13InDKE : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND POWER DOWN + this->command(0x10); + this->data(0x01); + // cannot wait until idle here, the device no longer responds + } + + void set_full_update_every(uint32_t full_update_every); + + protected: + int get_width_internal() override; + + int get_height_internal() override; + + int idle_timeout_() override; + + uint32_t full_update_every_{30}; + uint32_t at_update_{0}; +}; + } // namespace waveshare_epaper } // namespace esphome From d43640915364e1410d5c62d9132dc4d0c4a9ff9b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 11 Aug 2021 07:21:57 +0200 Subject: [PATCH 1181/1841] Support multiple configuration directories for update-all subcommand (#1925) --- esphome/__main__.py | 9 +++------ esphome/dashboard/dashboard.py | 2 +- esphome/util.py | 19 +++++++++++++------ esphome/vscode.py | 2 +- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index b4770914a7..8d6f2b8f89 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -296,7 +296,6 @@ def command_vscode(args): logging.disable(logging.INFO) logging.disable(logging.WARNING) - CORE.config_path = args.configuration vscode.read_config(args) @@ -406,7 +405,7 @@ def command_update_all(args): import click success = {} - files = list_yaml_files(args.configuration[0]) + files = list_yaml_files(args.configuration) twidth = 60 def print_bar(middle_text): @@ -694,14 +693,12 @@ def parse_args(argv): ) parser_vscode = subparsers.add_parser("vscode") - parser_vscode.add_argument( - "configuration", help="Your YAML configuration file.", nargs=1 - ) + parser_vscode.add_argument("configuration", help="Your YAML configuration file.") parser_vscode.add_argument("--ace", action="store_true") parser_update = subparsers.add_parser("update-all") parser_update.add_argument( - "configuration", help="Your YAML configuration file directory.", nargs=1 + "configuration", help="Your YAML configuration file directories.", nargs="+" ) return parser.parse_args(argv[1:]) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index d10e5ff002..ea55ea3b18 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -98,7 +98,7 @@ class DashboardSettings: return os.path.join(self.config_dir, *args) def list_yaml_files(self): - return util.list_yaml_files(self.config_dir) + return util.list_yaml_files([self.config_dir]) settings = DashboardSettings() diff --git a/esphome/util.py b/esphome/util.py index 10f9923c44..56bc97ca71 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -247,17 +247,24 @@ class OrderedDict(collections.OrderedDict): return dict(self).__repr__() -def list_yaml_files(folder): - files = filter_yaml_files([os.path.join(folder, p) for p in os.listdir(folder)]) +def list_yaml_files(folders): + files = filter_yaml_files( + [os.path.join(folder, p) for folder in folders for p in os.listdir(folder)] + ) files.sort() return files def filter_yaml_files(files): - files = [f for f in files if os.path.splitext(f)[1] == ".yaml"] - files = [f for f in files if os.path.basename(f) != "secrets.yaml"] - files = [f for f in files if not os.path.basename(f).startswith(".")] - return files + return [ + f + for f in files + if ( + os.path.splitext(f)[1] == ".yaml" + and os.path.basename(f) != "secrets.yaml" + and not os.path.basename(f).startswith(".") + ) + ] class SerialPort: diff --git a/esphome/vscode.py b/esphome/vscode.py index 6e1a0270be..68d59abd02 100644 --- a/esphome/vscode.py +++ b/esphome/vscode.py @@ -67,7 +67,7 @@ def read_config(args): CORE.ace = args.ace f = data["file"] if CORE.ace: - CORE.config_path = os.path.join(args.configuration[0], f) + CORE.config_path = os.path.join(args.configuration, f) else: CORE.config_path = data["file"] vs = VSCodeResult() From 5edebaf4686ceb30a4d0440b52d63811e9be6efd Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 11 Aug 2021 19:48:46 +1200 Subject: [PATCH 1182/1841] Bump version to v1.22.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index cd095677b3..50bac3ca8d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.21.0-dev" +__version__ = "1.22.0-dev" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From b0bc898278810d4cc8267ee03dc9df36a0476fa0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 11 Aug 2021 21:27:39 +1200 Subject: [PATCH 1183/1841] Bump version to v1.21.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 50bac3ca8d..2b9418a81c 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.22.0-dev" +__version__ = "1.21.0b1" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 6847645782d1e1993fd9bc269d7ec9100d588d00 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 11 Aug 2021 21:45:04 +1200 Subject: [PATCH 1184/1841] Fix bad merge conflict --- esphome/components/sensor/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index ebd4fcc26c..26ddac3464 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -258,11 +258,11 @@ def sensor_schema( ): validate_last_reset_type } ) - if last_reset_type_ != LAST_RESET_TYPE_NONE: + if last_reset_type != LAST_RESET_TYPE_NONE: schema = schema.extend( { cv.Optional( - CONF_LAST_RESET_TYPE, default=last_reset_type_ + CONF_LAST_RESET_TYPE, default=last_reset_type ): validate_last_reset_type } ) From 2735f96516ed94fbe10b44c79be21e82d7fbabfe Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 11 Aug 2021 21:54:24 +1200 Subject: [PATCH 1185/1841] Fix bad merge again --- esphome/components/sensor/__init__.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 26ddac3464..19f2a9f0e6 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -258,14 +258,6 @@ def sensor_schema( ): validate_last_reset_type } ) - if last_reset_type != LAST_RESET_TYPE_NONE: - schema = schema.extend( - { - cv.Optional( - CONF_LAST_RESET_TYPE, default=last_reset_type - ): validate_last_reset_type - } - ) return schema From 9173da04162beef7f89becf03107d3b24ad1f2f0 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 15 Aug 2021 21:40:34 +0200 Subject: [PATCH 1186/1841] Always send all light state values in API (#2150) --- esphome/components/api/api_connection.cpp | 25 ++++++++--------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 5fba549a57..f644ec62e0 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -310,22 +310,15 @@ bool APIConnection::send_light_state(light::LightState *light) { resp.key = light->get_object_id_hash(); resp.state = values.is_on(); resp.color_mode = static_cast(color_mode); - if (color_mode & light::ColorCapability::BRIGHTNESS) - resp.brightness = values.get_brightness(); - if (color_mode & light::ColorCapability::RGB) { - resp.color_brightness = values.get_color_brightness(); - resp.red = values.get_red(); - resp.green = values.get_green(); - resp.blue = values.get_blue(); - } - if (color_mode & light::ColorCapability::WHITE) - resp.white = values.get_white(); - if (color_mode & light::ColorCapability::COLOR_TEMPERATURE) - resp.color_temperature = values.get_color_temperature(); - if (color_mode & light::ColorCapability::COLD_WARM_WHITE) { - resp.cold_white = values.get_cold_white(); - resp.warm_white = values.get_warm_white(); - } + resp.brightness = values.get_brightness(); + resp.color_brightness = values.get_color_brightness(); + resp.red = values.get_red(); + resp.green = values.get_green(); + resp.blue = values.get_blue(); + resp.white = values.get_white(); + resp.color_temperature = values.get_color_temperature(); + resp.cold_white = values.get_cold_white(); + resp.warm_white = values.get_warm_white(); if (light->supports_effects()) resp.effect = light->get_effect_name(); return this->send_light_state_response(resp); From 303b6990057d271d43d72c8cf2017ad94e1ca443 Mon Sep 17 00:00:00 2001 From: puuu Date: Mon, 16 Aug 2021 04:59:29 +0900 Subject: [PATCH 1187/1841] let sensors announce its state_class via mqtt (#2155) --- esphome/components/mqtt/mqtt_sensor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index c106a95902..ce7e89c584 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -61,6 +61,9 @@ void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo if (this->sensor_->get_force_update()) root["force_update"] = true; + if (this->sensor_->state_class == sensor::STATE_CLASS_MEASUREMENT) + root["state_class"] = "measurement"; + config.command_topic = false; } bool MQTTSensorComponent::send_initial_state() { From 117b58ebe6c5a93e1f646df5964fe46e529fb027 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sun, 15 Aug 2021 16:31:48 -0500 Subject: [PATCH 1188/1841] Thermostat delayed fan mode fix (#2158) --- .../thermostat/thermostat_climate.cpp | 24 +++++++++++-------- .../thermostat/thermostat_climate.h | 7 ++---- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index d9d8b106ea..a75713cbb9 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -57,29 +57,34 @@ void ThermostatClimate::refresh() { } bool ThermostatClimate::climate_action_change_delayed() { + bool state_mismatch = this->action != this->compute_action_(true); + switch (this->compute_action_(true)) { case climate::CLIMATE_ACTION_OFF: case climate::CLIMATE_ACTION_IDLE: - return !this->idle_action_ready_(); + return state_mismatch && (!this->idle_action_ready_()); case climate::CLIMATE_ACTION_COOLING: - return !this->cooling_action_ready_(); + return state_mismatch && (!this->cooling_action_ready_()); case climate::CLIMATE_ACTION_HEATING: - return !this->heating_action_ready_(); + return state_mismatch && (!this->heating_action_ready_()); case climate::CLIMATE_ACTION_FAN: - return !this->fanning_action_ready_(); + return state_mismatch && (!this->fanning_action_ready_()); case climate::CLIMATE_ACTION_DRYING: - return !this->drying_action_ready_(); + return state_mismatch && (!this->drying_action_ready_()); default: break; } return false; } -bool ThermostatClimate::fan_mode_change_delayed() { return !this->fan_mode_ready_(); } +bool ThermostatClimate::fan_mode_change_delayed() { + bool state_mismatch = this->fan_mode.value_or(climate::CLIMATE_FAN_ON) != this->prev_fan_mode_; + return state_mismatch && (!this->fan_mode_ready_()); +} climate::ClimateAction ThermostatClimate::delayed_climate_action() { return this->compute_action_(true); } -climate::ClimateFanMode ThermostatClimate::delayed_fan_mode() { return this->desired_fan_mode_; } +climate::ClimateFanMode ThermostatClimate::locked_fan_mode() { return this->prev_fan_mode_; } bool ThermostatClimate::hysteresis_valid() { if ((this->supports_cool_ || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) && @@ -510,7 +515,7 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { // already in target mode return; - this->desired_fan_mode_ = fan_mode; // needed for timer callback + this->fan_mode = fan_mode; if (this->fan_mode_ready_()) { Trigger<> *trig = this->fan_mode_auto_trigger_; @@ -564,7 +569,6 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { this->start_timer_(thermostat::TIMER_FAN_MODE); assert(trig != nullptr); trig->trigger(); - this->fan_mode = fan_mode; this->prev_fan_mode_ = fan_mode; this->prev_fan_mode_trigger_ = trig; } @@ -733,7 +737,7 @@ void ThermostatClimate::cooling_on_timer_callback_() { void ThermostatClimate::fan_mode_timer_callback_() { ESP_LOGVV(TAG, "fan_mode timer expired"); this->timer_[thermostat::TIMER_FAN_MODE].active = false; - this->switch_to_fan_mode_(this->desired_fan_mode_); + this->switch_to_fan_mode_(this->fan_mode.value_or(climate::CLIMATE_FAN_ON)); if (this->supports_fan_only_action_uses_fan_mode_timer_) this->switch_to_action_(this->compute_action_()); } diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 1a5fd82ac0..60777e7c81 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -136,8 +136,8 @@ class ThermostatClimate : public climate::Climate, public Component { bool fan_mode_change_delayed(); /// Returns the climate action that is being delayed (check climate_action_change_delayed(), first!) climate::ClimateAction delayed_climate_action(); - /// Returns the fan mode that is being delayed (check fan_mode_change_delayed(), first!) - climate::ClimateFanMode delayed_fan_mode(); + /// Returns the fan mode that is locked in (check fan_mode_change_delayed(), first!) + climate::ClimateFanMode locked_fan_mode(); /// Set point and hysteresis validation bool hysteresis_valid(); // returns true if valid void validate_target_temperature(); @@ -377,9 +377,6 @@ class ThermostatClimate : public climate::Climate, public Component { Trigger<> *prev_mode_trigger_{nullptr}; Trigger<> *prev_swing_mode_trigger_{nullptr}; - /// Desired fan_mode -- used to store desired mode for callback when switching is delayed - climate::ClimateFanMode desired_fan_mode_{climate::CLIMATE_FAN_ON}; - /// Store previously-known states /// /// These are used to determine when a trigger/action needs to be called From 9b48ff5775750cc08c9fae2288b78e35da5a7db9 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 16 Aug 2021 01:57:50 +0200 Subject: [PATCH 1189/1841] Fix native API log level enum values (#2151) --- esphome/components/api/api.proto | 8 ++++---- esphome/components/api/api_connection.cpp | 2 -- esphome/components/api/api_pb2.cpp | 11 ++--------- esphome/components/api/api_pb2.h | 8 ++++---- 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index b9b56d3f8e..d21e82f50c 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -555,9 +555,10 @@ enum LogLevel { LOG_LEVEL_ERROR = 1; LOG_LEVEL_WARN = 2; LOG_LEVEL_INFO = 3; - LOG_LEVEL_DEBUG = 4; - LOG_LEVEL_VERBOSE = 5; - LOG_LEVEL_VERY_VERBOSE = 6; + LOG_LEVEL_CONFIG = 4; + LOG_LEVEL_DEBUG = 5; + LOG_LEVEL_VERBOSE = 6; + LOG_LEVEL_VERY_VERBOSE = 7; } message SubscribeLogsRequest { option (id) = 28; @@ -572,7 +573,6 @@ message SubscribeLogsResponse { option (no_delay) = false; LogLevel level = 1; - string tag = 2; string message = 3; bool send_failed = 4; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index f644ec62e0..4a31f15e77 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -694,8 +694,6 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin auto buffer = this->create_buffer(); // LogLevel level = 1; buffer.encode_uint32(1, static_cast(level)); - // string tag = 2; - // buffer.encode_string(2, tag, strlen(tag)); // string message = 3; buffer.encode_string(3, line, strlen(line)); // SubscribeLogsResponse - 29 diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index eecba7a68e..aaeef57324 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -120,6 +120,8 @@ template<> const char *proto_enum_to_string(enums::LogLevel val return "LOG_LEVEL_WARN"; case enums::LOG_LEVEL_INFO: return "LOG_LEVEL_INFO"; + case enums::LOG_LEVEL_CONFIG: + return "LOG_LEVEL_CONFIG"; case enums::LOG_LEVEL_DEBUG: return "LOG_LEVEL_DEBUG"; case enums::LOG_LEVEL_VERBOSE: @@ -2334,10 +2336,6 @@ bool SubscribeLogsResponse::decode_varint(uint32_t field_id, ProtoVarInt value) } bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 2: { - this->tag = value.as_string(); - return true; - } case 3: { this->message = value.as_string(); return true; @@ -2348,7 +2346,6 @@ bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimite } void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(1, this->level); - buffer.encode_string(2, this->tag); buffer.encode_string(3, this->message); buffer.encode_bool(4, this->send_failed); } @@ -2360,10 +2357,6 @@ void SubscribeLogsResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->level)); out.append("\n"); - out.append(" tag: "); - out.append("'").append(this->tag).append("'"); - out.append("\n"); - out.append(" message: "); out.append("'").append(this->message).append("'"); out.append("\n"); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index be32488391..2395a4bec3 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -58,9 +58,10 @@ enum LogLevel : uint32_t { LOG_LEVEL_ERROR = 1, LOG_LEVEL_WARN = 2, LOG_LEVEL_INFO = 3, - LOG_LEVEL_DEBUG = 4, - LOG_LEVEL_VERBOSE = 5, - LOG_LEVEL_VERY_VERBOSE = 6, + LOG_LEVEL_CONFIG = 4, + LOG_LEVEL_DEBUG = 5, + LOG_LEVEL_VERBOSE = 6, + LOG_LEVEL_VERY_VERBOSE = 7, }; enum ServiceArgType : uint32_t { SERVICE_ARG_TYPE_BOOL = 0, @@ -627,7 +628,6 @@ class SubscribeLogsRequest : public ProtoMessage { class SubscribeLogsResponse : public ProtoMessage { public: enums::LogLevel level{}; - std::string tag{}; std::string message{}; bool send_failed{false}; void encode(ProtoWriteBuffer buffer) const override; From 9efeea14f2560a93ea3d3f0cc3d0cd268f1e0edc Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 15 Aug 2021 21:40:34 +0200 Subject: [PATCH 1190/1841] Always send all light state values in API (#2150) --- esphome/components/api/api_connection.cpp | 25 ++++++++--------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 5fba549a57..f644ec62e0 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -310,22 +310,15 @@ bool APIConnection::send_light_state(light::LightState *light) { resp.key = light->get_object_id_hash(); resp.state = values.is_on(); resp.color_mode = static_cast(color_mode); - if (color_mode & light::ColorCapability::BRIGHTNESS) - resp.brightness = values.get_brightness(); - if (color_mode & light::ColorCapability::RGB) { - resp.color_brightness = values.get_color_brightness(); - resp.red = values.get_red(); - resp.green = values.get_green(); - resp.blue = values.get_blue(); - } - if (color_mode & light::ColorCapability::WHITE) - resp.white = values.get_white(); - if (color_mode & light::ColorCapability::COLOR_TEMPERATURE) - resp.color_temperature = values.get_color_temperature(); - if (color_mode & light::ColorCapability::COLD_WARM_WHITE) { - resp.cold_white = values.get_cold_white(); - resp.warm_white = values.get_warm_white(); - } + resp.brightness = values.get_brightness(); + resp.color_brightness = values.get_color_brightness(); + resp.red = values.get_red(); + resp.green = values.get_green(); + resp.blue = values.get_blue(); + resp.white = values.get_white(); + resp.color_temperature = values.get_color_temperature(); + resp.cold_white = values.get_cold_white(); + resp.warm_white = values.get_warm_white(); if (light->supports_effects()) resp.effect = light->get_effect_name(); return this->send_light_state_response(resp); From bd457f64d88d717fbf9dba9bca8d7fba131c0a27 Mon Sep 17 00:00:00 2001 From: puuu Date: Mon, 16 Aug 2021 04:59:29 +0900 Subject: [PATCH 1191/1841] let sensors announce its state_class via mqtt (#2155) --- esphome/components/mqtt/mqtt_sensor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index c106a95902..ce7e89c584 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -61,6 +61,9 @@ void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo if (this->sensor_->get_force_update()) root["force_update"] = true; + if (this->sensor_->state_class == sensor::STATE_CLASS_MEASUREMENT) + root["state_class"] = "measurement"; + config.command_topic = false; } bool MQTTSensorComponent::send_initial_state() { From 02b5a3efb8144ad982b722267be6b88a216d74ee Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sun, 15 Aug 2021 16:31:48 -0500 Subject: [PATCH 1192/1841] Thermostat delayed fan mode fix (#2158) --- .../thermostat/thermostat_climate.cpp | 24 +++++++++++-------- .../thermostat/thermostat_climate.h | 7 ++---- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index d9d8b106ea..a75713cbb9 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -57,29 +57,34 @@ void ThermostatClimate::refresh() { } bool ThermostatClimate::climate_action_change_delayed() { + bool state_mismatch = this->action != this->compute_action_(true); + switch (this->compute_action_(true)) { case climate::CLIMATE_ACTION_OFF: case climate::CLIMATE_ACTION_IDLE: - return !this->idle_action_ready_(); + return state_mismatch && (!this->idle_action_ready_()); case climate::CLIMATE_ACTION_COOLING: - return !this->cooling_action_ready_(); + return state_mismatch && (!this->cooling_action_ready_()); case climate::CLIMATE_ACTION_HEATING: - return !this->heating_action_ready_(); + return state_mismatch && (!this->heating_action_ready_()); case climate::CLIMATE_ACTION_FAN: - return !this->fanning_action_ready_(); + return state_mismatch && (!this->fanning_action_ready_()); case climate::CLIMATE_ACTION_DRYING: - return !this->drying_action_ready_(); + return state_mismatch && (!this->drying_action_ready_()); default: break; } return false; } -bool ThermostatClimate::fan_mode_change_delayed() { return !this->fan_mode_ready_(); } +bool ThermostatClimate::fan_mode_change_delayed() { + bool state_mismatch = this->fan_mode.value_or(climate::CLIMATE_FAN_ON) != this->prev_fan_mode_; + return state_mismatch && (!this->fan_mode_ready_()); +} climate::ClimateAction ThermostatClimate::delayed_climate_action() { return this->compute_action_(true); } -climate::ClimateFanMode ThermostatClimate::delayed_fan_mode() { return this->desired_fan_mode_; } +climate::ClimateFanMode ThermostatClimate::locked_fan_mode() { return this->prev_fan_mode_; } bool ThermostatClimate::hysteresis_valid() { if ((this->supports_cool_ || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) && @@ -510,7 +515,7 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { // already in target mode return; - this->desired_fan_mode_ = fan_mode; // needed for timer callback + this->fan_mode = fan_mode; if (this->fan_mode_ready_()) { Trigger<> *trig = this->fan_mode_auto_trigger_; @@ -564,7 +569,6 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { this->start_timer_(thermostat::TIMER_FAN_MODE); assert(trig != nullptr); trig->trigger(); - this->fan_mode = fan_mode; this->prev_fan_mode_ = fan_mode; this->prev_fan_mode_trigger_ = trig; } @@ -733,7 +737,7 @@ void ThermostatClimate::cooling_on_timer_callback_() { void ThermostatClimate::fan_mode_timer_callback_() { ESP_LOGVV(TAG, "fan_mode timer expired"); this->timer_[thermostat::TIMER_FAN_MODE].active = false; - this->switch_to_fan_mode_(this->desired_fan_mode_); + this->switch_to_fan_mode_(this->fan_mode.value_or(climate::CLIMATE_FAN_ON)); if (this->supports_fan_only_action_uses_fan_mode_timer_) this->switch_to_action_(this->compute_action_()); } diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 1a5fd82ac0..60777e7c81 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -136,8 +136,8 @@ class ThermostatClimate : public climate::Climate, public Component { bool fan_mode_change_delayed(); /// Returns the climate action that is being delayed (check climate_action_change_delayed(), first!) climate::ClimateAction delayed_climate_action(); - /// Returns the fan mode that is being delayed (check fan_mode_change_delayed(), first!) - climate::ClimateFanMode delayed_fan_mode(); + /// Returns the fan mode that is locked in (check fan_mode_change_delayed(), first!) + climate::ClimateFanMode locked_fan_mode(); /// Set point and hysteresis validation bool hysteresis_valid(); // returns true if valid void validate_target_temperature(); @@ -377,9 +377,6 @@ class ThermostatClimate : public climate::Climate, public Component { Trigger<> *prev_mode_trigger_{nullptr}; Trigger<> *prev_swing_mode_trigger_{nullptr}; - /// Desired fan_mode -- used to store desired mode for callback when switching is delayed - climate::ClimateFanMode desired_fan_mode_{climate::CLIMATE_FAN_ON}; - /// Store previously-known states /// /// These are used to determine when a trigger/action needs to be called From 1be9bac3a97c824e7ad222caebd39b2c570f2d82 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 16 Aug 2021 01:57:50 +0200 Subject: [PATCH 1193/1841] Fix native API log level enum values (#2151) --- esphome/components/api/api.proto | 8 ++++---- esphome/components/api/api_connection.cpp | 2 -- esphome/components/api/api_pb2.cpp | 11 ++--------- esphome/components/api/api_pb2.h | 8 ++++---- 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index b9b56d3f8e..d21e82f50c 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -555,9 +555,10 @@ enum LogLevel { LOG_LEVEL_ERROR = 1; LOG_LEVEL_WARN = 2; LOG_LEVEL_INFO = 3; - LOG_LEVEL_DEBUG = 4; - LOG_LEVEL_VERBOSE = 5; - LOG_LEVEL_VERY_VERBOSE = 6; + LOG_LEVEL_CONFIG = 4; + LOG_LEVEL_DEBUG = 5; + LOG_LEVEL_VERBOSE = 6; + LOG_LEVEL_VERY_VERBOSE = 7; } message SubscribeLogsRequest { option (id) = 28; @@ -572,7 +573,6 @@ message SubscribeLogsResponse { option (no_delay) = false; LogLevel level = 1; - string tag = 2; string message = 3; bool send_failed = 4; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index f644ec62e0..4a31f15e77 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -694,8 +694,6 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin auto buffer = this->create_buffer(); // LogLevel level = 1; buffer.encode_uint32(1, static_cast(level)); - // string tag = 2; - // buffer.encode_string(2, tag, strlen(tag)); // string message = 3; buffer.encode_string(3, line, strlen(line)); // SubscribeLogsResponse - 29 diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index eecba7a68e..aaeef57324 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -120,6 +120,8 @@ template<> const char *proto_enum_to_string(enums::LogLevel val return "LOG_LEVEL_WARN"; case enums::LOG_LEVEL_INFO: return "LOG_LEVEL_INFO"; + case enums::LOG_LEVEL_CONFIG: + return "LOG_LEVEL_CONFIG"; case enums::LOG_LEVEL_DEBUG: return "LOG_LEVEL_DEBUG"; case enums::LOG_LEVEL_VERBOSE: @@ -2334,10 +2336,6 @@ bool SubscribeLogsResponse::decode_varint(uint32_t field_id, ProtoVarInt value) } bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 2: { - this->tag = value.as_string(); - return true; - } case 3: { this->message = value.as_string(); return true; @@ -2348,7 +2346,6 @@ bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimite } void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(1, this->level); - buffer.encode_string(2, this->tag); buffer.encode_string(3, this->message); buffer.encode_bool(4, this->send_failed); } @@ -2360,10 +2357,6 @@ void SubscribeLogsResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->level)); out.append("\n"); - out.append(" tag: "); - out.append("'").append(this->tag).append("'"); - out.append("\n"); - out.append(" message: "); out.append("'").append(this->message).append("'"); out.append("\n"); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index be32488391..2395a4bec3 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -58,9 +58,10 @@ enum LogLevel : uint32_t { LOG_LEVEL_ERROR = 1, LOG_LEVEL_WARN = 2, LOG_LEVEL_INFO = 3, - LOG_LEVEL_DEBUG = 4, - LOG_LEVEL_VERBOSE = 5, - LOG_LEVEL_VERY_VERBOSE = 6, + LOG_LEVEL_CONFIG = 4, + LOG_LEVEL_DEBUG = 5, + LOG_LEVEL_VERBOSE = 6, + LOG_LEVEL_VERY_VERBOSE = 7, }; enum ServiceArgType : uint32_t { SERVICE_ARG_TYPE_BOOL = 0, @@ -627,7 +628,6 @@ class SubscribeLogsRequest : public ProtoMessage { class SubscribeLogsResponse : public ProtoMessage { public: enums::LogLevel level{}; - std::string tag{}; std::string message{}; bool send_failed{false}; void encode(ProtoWriteBuffer buffer) const override; From 14e04eb2313f0c4f6daefd6d8a428f13e41a97b7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 16 Aug 2021 12:32:48 +1200 Subject: [PATCH 1194/1841] Bump version to v1.21.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 2b9418a81c..89be4b7fd7 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.21.0b1" +__version__ = "1.21.0b2" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 0c370d589766235e6f816587e743194714aec056 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 17 Aug 2021 04:02:38 +0200 Subject: [PATCH 1195/1841] Initialize color temperature to value within range if possible (#2168) --- esphome/components/light/light_state.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 278229fbd1..030cf4b7a2 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -39,6 +39,13 @@ void LightState::setup() { effect->init_internal(this); } + // When supported color temperature range is known, initialize color temperature setting within bounds. + float min_mireds = this->get_traits().get_min_mireds(); + if (min_mireds > 0) { + this->remote_values.set_color_temperature(min_mireds); + this->current_values.set_color_temperature(min_mireds); + } + auto call = this->make_call(); LightStateRTCState recovered{}; switch (this->restore_mode_) { From 3b52a306cd86dcbb95a892ac680ac843ab84c142 Mon Sep 17 00:00:00 2001 From: Daniel Hyles Date: Tue, 17 Aug 2021 18:05:37 +1000 Subject: [PATCH 1196/1841] Add a dummy color temp (#2161) --- esphome/components/hbridge/hbridge_light_output.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/hbridge/hbridge_light_output.h b/esphome/components/hbridge/hbridge_light_output.h index 2f4f87134c..c309154852 100644 --- a/esphome/components/hbridge/hbridge_light_output.h +++ b/esphome/components/hbridge/hbridge_light_output.h @@ -19,6 +19,8 @@ class HBridgeLightOutput : public PollingComponent, public light::LightOutput { light::LightTraits get_traits() override { auto traits = light::LightTraits(); traits.set_supported_color_modes({light::ColorMode::COLD_WARM_WHITE}); + traits.set_min_mireds(153); + traits.set_max_mireds(500); return traits; } From 607e1f823d66e907f6a6a53ed40ea3cb004863f0 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Tue, 17 Aug 2021 10:12:29 +0200 Subject: [PATCH 1197/1841] Minor code cleanup in light components (#2162) Co-authored-by: Maurice Makaay --- esphome/components/light/addressable_light.cpp | 2 +- esphome/components/light/base_light_effects.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index 12eab6a685..9a34dde6be 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -19,7 +19,7 @@ void AddressableLight::call_setup() { ESP_LOGVV(TAG, " [%2d] Color: R=%3u G=%3u B=%3u W=%3u", i, color.get_red_raw(), color.get_green_raw(), color.get_blue_raw(), color.get_white_raw()); } - ESP_LOGVV(TAG, ""); + ESP_LOGVV(TAG, " "); }); #endif } diff --git a/esphome/components/light/base_light_effects.h b/esphome/components/light/base_light_effects.h index f66b90f665..7826b2eecb 100644 --- a/esphome/components/light/base_light_effects.h +++ b/esphome/components/light/base_light_effects.h @@ -196,7 +196,6 @@ class FlickerLightEffect : public LightEffect { out.set_warm_white(remote.get_warm_white() * beta + current.get_warm_white() * alpha + (random_cubic_float() * this->intensity_)); - auto traits = this->state_->get_traits(); auto call = this->state_->make_call(); call.set_publish(false); call.set_save(false); From ebabf0e7d83dee43d8f335b3a2057cccb2f6d6e8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 17 Aug 2021 11:43:51 +0200 Subject: [PATCH 1198/1841] Add Gas device class to DSMR component (#2169) --- esphome/components/dsmr/sensor.py | 5 +++-- esphome/components/sensor/__init__.py | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 05c568e21a..84b263c2d5 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -5,6 +5,7 @@ from esphome.const import ( DEVICE_CLASS_CURRENT, DEVICE_CLASS_EMPTY, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ICON_EMPTY, @@ -178,7 +179,7 @@ CONFIG_SCHEMA = cv.Schema( "m³", ICON_EMPTY, 3, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_GAS, STATE_CLASS_MEASUREMENT, LAST_RESET_TYPE_NEVER, ), @@ -186,7 +187,7 @@ CONFIG_SCHEMA = cv.Schema( "m³", ICON_EMPTY, 3, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_GAS, STATE_CLASS_MEASUREMENT, LAST_RESET_TYPE_NEVER, ), diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 19f2a9f0e6..390cfcf551 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -40,6 +40,7 @@ from esphome.const import ( DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_MONETARY, @@ -62,6 +63,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_MONETARY, From 8eb18995cb70f502896f381c6af723d08e376ee6 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 17 Aug 2021 11:44:05 +0200 Subject: [PATCH 1199/1841] Add device class update to binary sensor (#2170) --- esphome/components/binary_sensor/__init__.py | 2 ++ esphome/const.py | 1 + 2 files changed, 3 insertions(+) diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 3c2169a922..9cd2a045ff 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -48,6 +48,7 @@ from esphome.const import ( DEVICE_CLASS_SAFETY, DEVICE_CLASS_SMOKE, DEVICE_CLASS_SOUND, + DEVICE_CLASS_UPDATE, DEVICE_CLASS_VIBRATION, DEVICE_CLASS_WINDOW, ) @@ -79,6 +80,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_SAFETY, DEVICE_CLASS_SMOKE, DEVICE_CLASS_SOUND, + DEVICE_CLASS_UPDATE, DEVICE_CLASS_VIBRATION, DEVICE_CLASS_WINDOW, ] diff --git a/esphome/const.py b/esphome/const.py index 50bac3ca8d..b0f11d1c1a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -813,6 +813,7 @@ DEVICE_CLASS_PROBLEM = "problem" DEVICE_CLASS_SAFETY = "safety" DEVICE_CLASS_SMOKE = "smoke" DEVICE_CLASS_SOUND = "sound" +DEVICE_CLASS_UPDATE = "update" DEVICE_CLASS_VIBRATION = "vibration" DEVICE_CLASS_WINDOW = "window" # device classes of both binary_sensor and sensor component From ebaa84617feb8b07e54c6bac012c285e4c077d01 Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Tue, 17 Aug 2021 14:16:02 -0500 Subject: [PATCH 1200/1841] Total daily energy methods (#2163) Co-authored-by: Chris Nussbaum --- .../components/total_daily_energy/sensor.py | 11 ++++++++++ .../total_daily_energy/total_daily_energy.cpp | 21 ++++++++++++++++++- .../total_daily_energy/total_daily_energy.h | 9 ++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index ec38daaf57..f6e92149d6 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -7,6 +7,7 @@ from esphome.const import ( DEVICE_CLASS_ENERGY, LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, + CONF_METHOD, ) DEPENDENCIES = ["time"] @@ -14,6 +15,12 @@ DEPENDENCIES = ["time"] CONF_POWER_ID = "power_id" CONF_MIN_SAVE_INTERVAL = "min_save_interval" total_daily_energy_ns = cg.esphome_ns.namespace("total_daily_energy") +TotalDailyEnergyMethod = total_daily_energy_ns.enum("TotalDailyEnergyMethod") +TOTAL_DAILY_ENERGY_METHODS = { + "trapezoid": TotalDailyEnergyMethod.TOTAL_DAILY_ENERGY_METHOD_TRAPEZOID, + "left": TotalDailyEnergyMethod.TOTAL_DAILY_ENERGY_METHOD_LEFT, + "right": TotalDailyEnergyMethod.TOTAL_DAILY_ENERGY_METHOD_RIGHT, +} TotalDailyEnergy = total_daily_energy_ns.class_( "TotalDailyEnergy", sensor.Sensor, cg.Component ) @@ -33,6 +40,9 @@ CONFIG_SCHEMA = ( cv.Optional( CONF_MIN_SAVE_INTERVAL, default="0s" ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_METHOD, default="right"): cv.enum( + TOTAL_DAILY_ENERGY_METHODS, lower=True + ), } ) .extend(cv.COMPONENT_SCHEMA) @@ -50,3 +60,4 @@ async def to_code(config): time_ = await cg.get_variable(config[CONF_TIME_ID]) cg.add(var.set_time(time_)) cg.add(var.set_min_save_interval(config[CONF_MIN_SAVE_INTERVAL])) + cg.add(var.set_method(config[CONF_METHOD])) diff --git a/esphome/components/total_daily_energy/total_daily_energy.cpp b/esphome/components/total_daily_energy/total_daily_energy.cpp index 1e60442ae7..83333acab7 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.cpp +++ b/esphome/components/total_daily_energy/total_daily_energy.cpp @@ -20,7 +20,9 @@ void TotalDailyEnergy::setup() { this->parent_->add_on_state_callback([this](float state) { this->process_new_state_(state); }); } + void TotalDailyEnergy::dump_config() { LOG_SENSOR("", "Total Daily Energy", this); } + void TotalDailyEnergy::loop() { auto t = this->time_->now(); if (!t.is_valid()) @@ -37,6 +39,7 @@ void TotalDailyEnergy::loop() { this->publish_state_and_save(0); } } + void TotalDailyEnergy::publish_state_and_save(float state) { this->total_energy_ = state; this->publish_state(state); @@ -47,13 +50,29 @@ void TotalDailyEnergy::publish_state_and_save(float state) { this->last_save_ = now; this->pref_.save(&state); } + void TotalDailyEnergy::process_new_state_(float state) { if (isnan(state)) return; const uint32_t now = millis(); + const float old_state = this->last_power_state_; + const float new_state = state; float delta_hours = (now - this->last_update_) / 1000.0f / 60.0f / 60.0f; + float delta_energy = 0.0f; + switch (this->method_) { + case TOTAL_DAILY_ENERGY_METHOD_TRAPEZOID: + delta_energy = delta_hours * (old_state + new_state) / 2.0; + break; + case TOTAL_DAILY_ENERGY_METHOD_LEFT: + delta_energy = delta_hours * old_state; + break; + case TOTAL_DAILY_ENERGY_METHOD_RIGHT: + delta_energy = delta_hours * new_state; + break; + } + this->last_power_state_ = new_state; this->last_update_ = now; - this->publish_state_and_save(this->total_energy_ + state * delta_hours); + this->publish_state_and_save(this->total_energy_ + delta_energy); } } // namespace total_daily_energy diff --git a/esphome/components/total_daily_energy/total_daily_energy.h b/esphome/components/total_daily_energy/total_daily_energy.h index 123446c534..fd71b8decc 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.h +++ b/esphome/components/total_daily_energy/total_daily_energy.h @@ -8,11 +8,18 @@ namespace esphome { namespace total_daily_energy { +enum TotalDailyEnergyMethod { + TOTAL_DAILY_ENERGY_METHOD_TRAPEZOID = 0, + TOTAL_DAILY_ENERGY_METHOD_LEFT, + TOTAL_DAILY_ENERGY_METHOD_RIGHT, +}; + class TotalDailyEnergy : public sensor::Sensor, public Component { public: void set_min_save_interval(uint32_t min_interval) { this->min_save_interval_ = min_interval; } void set_time(time::RealTimeClock *time) { time_ = time; } void set_parent(Sensor *parent) { parent_ = parent; } + void set_method(TotalDailyEnergyMethod method) { method_ = method; } void setup() override; void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } @@ -29,11 +36,13 @@ class TotalDailyEnergy : public sensor::Sensor, public Component { ESPPreferenceObject pref_; time::RealTimeClock *time_; Sensor *parent_; + TotalDailyEnergyMethod method_; uint16_t last_day_of_year_{}; uint32_t last_update_{0}; uint32_t last_save_{0}; uint32_t min_save_interval_{0}; float total_energy_{0.0f}; + float last_power_state_{0.0f}; }; } // namespace total_daily_energy From edb3b77916069d378ec5fb442a5c578663666d97 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 10:22:00 +1200 Subject: [PATCH 1201/1841] Send dirty states when screen wakes up (#2167) --- esphome/components/nextion/nextion.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 14c7b34a4c..3f44fe4075 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -543,6 +543,7 @@ void Nextion::process_nextion_commands_() { ESP_LOGVV(TAG, "Received Nextion leaves sleep automatically"); this->is_sleeping_ = false; this->wake_callback_.call(); + this->all_components_send_state_(false); break; } case 0x88: // system successful start up From fbd9e87b513c20ea30faad76c290b6c94a73c6c5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 10:32:19 +1200 Subject: [PATCH 1202/1841] Remove specified accuracy_decimals from total_daily_energy (#2174) --- esphome/components/total_daily_energy/sensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index f6e92149d6..f1449432a0 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -27,7 +27,6 @@ TotalDailyEnergy = total_daily_energy_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_MEASUREMENT, last_reset_type=LAST_RESET_TYPE_AUTO, From f0b14055b69cc5248655a2c13c06b75c6cb6b1c0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 11:04:13 +1200 Subject: [PATCH 1203/1841] Add new total_increasing state-class for Home Assistant 2021.9+ (#2166) --- esphome/components/api/api.proto | 1 + esphome/components/api/api_pb2.cpp | 2 ++ esphome/components/api/api_pb2.h | 1 + esphome/components/sensor/__init__.py | 1 + esphome/components/sensor/sensor.cpp | 4 ++++ esphome/components/sensor/sensor.h | 1 + esphome/const.py | 3 +++ 7 files changed, 13 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index d21e82f50c..e3ef2d7c9e 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -448,6 +448,7 @@ message LightCommandRequest { enum SensorStateClass { STATE_CLASS_NONE = 0; STATE_CLASS_MEASUREMENT = 1; + STATE_CLASS_TOTAL_INCREASING = 2; } enum SensorLastResetType { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index aaeef57324..f5860bee64 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -94,6 +94,8 @@ template<> const char *proto_enum_to_string(enums::Sens return "STATE_CLASS_NONE"; case enums::STATE_CLASS_MEASUREMENT: return "STATE_CLASS_MEASUREMENT"; + case enums::STATE_CLASS_TOTAL_INCREASING: + return "STATE_CLASS_TOTAL_INCREASING"; default: return "UNKNOWN"; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 2395a4bec3..93bfcd9b55 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -47,6 +47,7 @@ enum ColorMode : uint32_t { enum SensorStateClass : uint32_t { STATE_CLASS_NONE = 0, STATE_CLASS_MEASUREMENT = 1, + STATE_CLASS_TOTAL_INCREASING = 2, }; enum SensorLastResetType : uint32_t { LAST_RESET_NONE = 0, diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 390cfcf551..1bb4e25a17 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -81,6 +81,7 @@ StateClasses = sensor_ns.enum("StateClass") STATE_CLASSES = { "": StateClasses.STATE_CLASS_NONE, "measurement": StateClasses.STATE_CLASS_MEASUREMENT, + "total_increasing": StateClasses.STATE_CLASS_TOTAL_INCREASING, } validate_state_class = cv.enum(STATE_CLASSES, lower=True, space="_") diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 6e8765a8df..1a5c76db51 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -10,6 +10,8 @@ const char *state_class_to_string(StateClass state_class) { switch (state_class) { case STATE_CLASS_MEASUREMENT: return "measurement"; + case STATE_CLASS_TOTAL_INCREASING: + return "total_increasing"; case STATE_CLASS_NONE: default: return ""; @@ -72,6 +74,8 @@ void Sensor::set_state_class(StateClass state_class) { this->state_class = state void Sensor::set_state_class(const std::string &state_class) { if (str_equals_case_insensitive(state_class, "measurement")) { this->state_class = STATE_CLASS_MEASUREMENT; + } else if (str_equals_case_insensitive(state_class, "total_increasing")) { + this->state_class = STATE_CLASS_TOTAL_INCREASING; } else { ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str()); } diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index b9908b6cbe..f0d7ba4887 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -37,6 +37,7 @@ namespace sensor { enum StateClass : uint8_t { STATE_CLASS_NONE = 0, STATE_CLASS_MEASUREMENT = 1, + STATE_CLASS_TOTAL_INCREASING = 2, }; const char *state_class_to_string(StateClass state_class); diff --git a/esphome/const.py b/esphome/const.py index b0f11d1c1a..d7cd57e05d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -841,6 +841,9 @@ STATE_CLASS_NONE = "" # The state represents a measurement in present time STATE_CLASS_MEASUREMENT = "measurement" +# The state represents a total that only increases, a decrease is considered a reset. +STATE_CLASS_TOTAL_INCREASING = "total_increasing" + # This sensor does not support resetting. ie, it is not accumulative LAST_RESET_TYPE_NONE = "" # This sensor is expected to never reset its value From 9ee3463d07ed0243a6bb827dc7dbd1115ec4bb1f Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 17 Aug 2021 04:02:38 +0200 Subject: [PATCH 1204/1841] Initialize color temperature to value within range if possible (#2168) --- esphome/components/light/light_state.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 278229fbd1..030cf4b7a2 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -39,6 +39,13 @@ void LightState::setup() { effect->init_internal(this); } + // When supported color temperature range is known, initialize color temperature setting within bounds. + float min_mireds = this->get_traits().get_min_mireds(); + if (min_mireds > 0) { + this->remote_values.set_color_temperature(min_mireds); + this->current_values.set_color_temperature(min_mireds); + } + auto call = this->make_call(); LightStateRTCState recovered{}; switch (this->restore_mode_) { From cbdb96f1058e33201972b97d9b16e0180c539512 Mon Sep 17 00:00:00 2001 From: Daniel Hyles Date: Tue, 17 Aug 2021 18:05:37 +1000 Subject: [PATCH 1205/1841] Add a dummy color temp (#2161) --- esphome/components/hbridge/hbridge_light_output.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/hbridge/hbridge_light_output.h b/esphome/components/hbridge/hbridge_light_output.h index 2f4f87134c..c309154852 100644 --- a/esphome/components/hbridge/hbridge_light_output.h +++ b/esphome/components/hbridge/hbridge_light_output.h @@ -19,6 +19,8 @@ class HBridgeLightOutput : public PollingComponent, public light::LightOutput { light::LightTraits get_traits() override { auto traits = light::LightTraits(); traits.set_supported_color_modes({light::ColorMode::COLD_WARM_WHITE}); + traits.set_min_mireds(153); + traits.set_max_mireds(500); return traits; } From 44bb5a89c8ee5756bea37f1a425ba6d87120cf8f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 17 Aug 2021 11:43:51 +0200 Subject: [PATCH 1206/1841] Add Gas device class to DSMR component (#2169) --- esphome/components/dsmr/sensor.py | 5 +++-- esphome/components/sensor/__init__.py | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 05c568e21a..84b263c2d5 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -5,6 +5,7 @@ from esphome.const import ( DEVICE_CLASS_CURRENT, DEVICE_CLASS_EMPTY, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ICON_EMPTY, @@ -178,7 +179,7 @@ CONFIG_SCHEMA = cv.Schema( "m³", ICON_EMPTY, 3, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_GAS, STATE_CLASS_MEASUREMENT, LAST_RESET_TYPE_NEVER, ), @@ -186,7 +187,7 @@ CONFIG_SCHEMA = cv.Schema( "m³", ICON_EMPTY, 3, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_GAS, STATE_CLASS_MEASUREMENT, LAST_RESET_TYPE_NEVER, ), diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 19f2a9f0e6..390cfcf551 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -40,6 +40,7 @@ from esphome.const import ( DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_MONETARY, @@ -62,6 +63,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_MONETARY, From 9c605f2d46571a327bab46d4e9b4a3e9f62c8eb4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 10:22:00 +1200 Subject: [PATCH 1207/1841] Send dirty states when screen wakes up (#2167) --- esphome/components/nextion/nextion.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 14c7b34a4c..3f44fe4075 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -543,6 +543,7 @@ void Nextion::process_nextion_commands_() { ESP_LOGVV(TAG, "Received Nextion leaves sleep automatically"); this->is_sleeping_ = false; this->wake_callback_.call(); + this->all_components_send_state_(false); break; } case 0x88: // system successful start up From 61a9c9fa3327499437349311e9a9c35bf355c7cf Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 10:32:19 +1200 Subject: [PATCH 1208/1841] Remove specified accuracy_decimals from total_daily_energy (#2174) --- esphome/components/total_daily_energy/sensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index ec38daaf57..6e75feae48 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -20,7 +20,6 @@ TotalDailyEnergy = total_daily_energy_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_MEASUREMENT, last_reset_type=LAST_RESET_TYPE_AUTO, From 4e3b95d120afc0c0d247131ae6db27531e480628 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 11:04:13 +1200 Subject: [PATCH 1209/1841] Add new total_increasing state-class for Home Assistant 2021.9+ (#2166) --- esphome/components/api/api.proto | 1 + esphome/components/api/api_pb2.cpp | 2 ++ esphome/components/api/api_pb2.h | 1 + esphome/components/sensor/__init__.py | 1 + esphome/components/sensor/sensor.cpp | 4 ++++ esphome/components/sensor/sensor.h | 1 + esphome/const.py | 3 +++ 7 files changed, 13 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index d21e82f50c..e3ef2d7c9e 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -448,6 +448,7 @@ message LightCommandRequest { enum SensorStateClass { STATE_CLASS_NONE = 0; STATE_CLASS_MEASUREMENT = 1; + STATE_CLASS_TOTAL_INCREASING = 2; } enum SensorLastResetType { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index aaeef57324..f5860bee64 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -94,6 +94,8 @@ template<> const char *proto_enum_to_string(enums::Sens return "STATE_CLASS_NONE"; case enums::STATE_CLASS_MEASUREMENT: return "STATE_CLASS_MEASUREMENT"; + case enums::STATE_CLASS_TOTAL_INCREASING: + return "STATE_CLASS_TOTAL_INCREASING"; default: return "UNKNOWN"; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 2395a4bec3..93bfcd9b55 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -47,6 +47,7 @@ enum ColorMode : uint32_t { enum SensorStateClass : uint32_t { STATE_CLASS_NONE = 0, STATE_CLASS_MEASUREMENT = 1, + STATE_CLASS_TOTAL_INCREASING = 2, }; enum SensorLastResetType : uint32_t { LAST_RESET_NONE = 0, diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 390cfcf551..1bb4e25a17 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -81,6 +81,7 @@ StateClasses = sensor_ns.enum("StateClass") STATE_CLASSES = { "": StateClasses.STATE_CLASS_NONE, "measurement": StateClasses.STATE_CLASS_MEASUREMENT, + "total_increasing": StateClasses.STATE_CLASS_TOTAL_INCREASING, } validate_state_class = cv.enum(STATE_CLASSES, lower=True, space="_") diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 6e8765a8df..1a5c76db51 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -10,6 +10,8 @@ const char *state_class_to_string(StateClass state_class) { switch (state_class) { case STATE_CLASS_MEASUREMENT: return "measurement"; + case STATE_CLASS_TOTAL_INCREASING: + return "total_increasing"; case STATE_CLASS_NONE: default: return ""; @@ -72,6 +74,8 @@ void Sensor::set_state_class(StateClass state_class) { this->state_class = state void Sensor::set_state_class(const std::string &state_class) { if (str_equals_case_insensitive(state_class, "measurement")) { this->state_class = STATE_CLASS_MEASUREMENT; + } else if (str_equals_case_insensitive(state_class, "total_increasing")) { + this->state_class = STATE_CLASS_TOTAL_INCREASING; } else { ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str()); } diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index b9908b6cbe..f0d7ba4887 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -37,6 +37,7 @@ namespace sensor { enum StateClass : uint8_t { STATE_CLASS_NONE = 0, STATE_CLASS_MEASUREMENT = 1, + STATE_CLASS_TOTAL_INCREASING = 2, }; const char *state_class_to_string(StateClass state_class); diff --git a/esphome/const.py b/esphome/const.py index 89be4b7fd7..010ea1290a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -840,6 +840,9 @@ STATE_CLASS_NONE = "" # The state represents a measurement in present time STATE_CLASS_MEASUREMENT = "measurement" +# The state represents a total that only increases, a decrease is considered a reset. +STATE_CLASS_TOTAL_INCREASING = "total_increasing" + # This sensor does not support resetting. ie, it is not accumulative LAST_RESET_TYPE_NONE = "" # This sensor is expected to never reset its value From 409d4b9d475c6abf142a451fe0a740dfe6c853df Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 11:11:39 +1200 Subject: [PATCH 1210/1841] Bump version to v1.21.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 010ea1290a..a17152a64b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.21.0b2" +__version__ = "1.21.0b3" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From d4c2a85f9c79ba7aebc9d99aed584ad54460f7a7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 14:25:10 +1200 Subject: [PATCH 1211/1841] Bump version to v2021.8.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a17152a64b..e25ba7e046 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.21.0b3" +__version__ = "2021.8.0" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 2e59ad90cc0ef374c7360e0bde1c9ce44d271363 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 15:10:44 +1200 Subject: [PATCH 1212/1841] Fix docker release for new tags without `v` --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1b15b540f6..3dc4952422 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: id: tag run: | if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then - TAG="${GITHUB_REF#refs/tags/v}" + TAG="${GITHUB_REF#refs/tags/}" else TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p") today="$(date --utc '+%Y%m%d')" @@ -138,7 +138,7 @@ jobs: - env: TOKEN: ${{ secrets.DEPLOY_HASSIO_TOKEN }} run: | - TAG="${GITHUB_REF#refs/tags/v}" + TAG="${GITHUB_REF#refs/tags/}" curl \ -u ":$TOKEN" \ -X POST \ From 2100ef63a93ecbc18d56a7012954268ef5538909 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 15:19:25 +1200 Subject: [PATCH 1213/1841] Bump version to 2021.9.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 5ba5efb66d..d8134bc45f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.8.0" +__version__ = "2021.9.0-dev" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 21f8fd9fa54ac667ffc5124cea06df22bd3760db Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 15:39:57 +1200 Subject: [PATCH 1214/1841] Fix pypi download url (#2177) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 44a5965887..967eadd70f 100755 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ PYPI_URL = "https://pypi.python.org/pypi/{}".format(PROJECT_PACKAGE_NAME) GITHUB_PATH = "{}/{}".format(PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY) GITHUB_URL = "https://github.com/{}".format(GITHUB_PATH) -DOWNLOAD_URL = "{}/archive/v{}.zip".format(GITHUB_URL, const.__version__) +DOWNLOAD_URL = "{}/archive/{}.zip".format(GITHUB_URL, const.__version__) here = os.path.abspath(os.path.dirname(__file__)) From 5cb56bc6774a52664703941796fa80f5de000877 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 19 Aug 2021 21:28:58 +1200 Subject: [PATCH 1215/1841] Set SDM voltage state class to measurement (#2181) --- esphome/components/sdm_meter/sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/sdm_meter/sensor.py b/esphome/components/sdm_meter/sensor.py index 13cc94786c..c3af47a664 100644 --- a/esphome/components/sdm_meter/sensor.py +++ b/esphome/components/sdm_meter/sensor.py @@ -47,6 +47,7 @@ PHASE_SENSORS = { unit_of_measurement=UNIT_VOLT, accuracy_decimals=2, device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_CURRENT: sensor.sensor_schema( unit_of_measurement=UNIT_AMPERE, From b0fa317302df4f6adeab56034e0b6099f58aa4c4 Mon Sep 17 00:00:00 2001 From: puuu Date: Sat, 21 Aug 2021 19:26:24 +0900 Subject: [PATCH 1216/1841] Light: include ON_OFF capability to BRIGHTNESS ColorMode (#2186) --- esphome/components/light/color_mode.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/color_mode.h b/esphome/components/light/color_mode.h index 0f5b7b4b93..77c377d39e 100644 --- a/esphome/components/light/color_mode.h +++ b/esphome/components/light/color_mode.h @@ -52,7 +52,7 @@ enum class ColorMode : uint8_t { /// Only on/off control. ON_OFF = (uint8_t) ColorCapability::ON_OFF, /// Dimmable light. - BRIGHTNESS = (uint8_t) ColorCapability::BRIGHTNESS, + BRIGHTNESS = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS), /// White output only (use only if the light also has another color mode such as RGB). WHITE = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::WHITE), /// Controllable color temperature output. From 8cc3cbb22e6f0b2d25d7233e54bb7f51f2ea5efb Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 23 Aug 2021 09:19:21 +0200 Subject: [PATCH 1217/1841] Add macros header with more usable Arduino version defines (#2145) --- .../components/esp8266_pwm/esp8266_pwm.cpp | 5 +- .../neopixelbus/neopixelbus_light.h | 5 +- esphome/components/wifi/wifi_component.h | 3 +- .../wifi/wifi_component_esp8266.cpp | 15 ++--- esphome/core/esphal.cpp | 3 +- esphome/core/macros.h | 56 +++++++++++++++++++ 6 files changed, 72 insertions(+), 15 deletions(-) create mode 100644 esphome/core/macros.h diff --git a/esphome/components/esp8266_pwm/esp8266_pwm.cpp b/esphome/components/esp8266_pwm/esp8266_pwm.cpp index 37a9f3efbf..d725e90edc 100644 --- a/esphome/components/esp8266_pwm/esp8266_pwm.cpp +++ b/esphome/components/esp8266_pwm/esp8266_pwm.cpp @@ -1,9 +1,10 @@ #include "esp8266_pwm.h" +#include "esphome/core/macros.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" -#ifdef ARDUINO_ESP8266_RELEASE_2_3_0 -#error ESP8266 PWM requires at least arduino_core_version 2.4.0 +#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) +#error ESP8266 PWM requires at least arduino_version 2.4.0 #endif #include diff --git a/esphome/components/neopixelbus/neopixelbus_light.h b/esphome/components/neopixelbus/neopixelbus_light.h index c7f7badc5a..1f2cde0bd2 100644 --- a/esphome/components/neopixelbus/neopixelbus_light.h +++ b/esphome/components/neopixelbus/neopixelbus_light.h @@ -1,13 +1,14 @@ #pragma once +#include "esphome/core/macros.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/color.h" #include "esphome/components/light/light_output.h" #include "esphome/components/light/addressable_light.h" -#ifdef ARDUINO_ESP8266_RELEASE_2_3_0 -#error The NeoPixelBus library requires at least arduino_core_version 2.4.x +#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) +#error The NeoPixelBus library requires at least arduino_version 2.4.x #endif #include "NeoPixelBus.h" diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index f698e09d93..d3b086553c 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -1,5 +1,6 @@ #pragma once +#include "esphome/core/macros.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/automation.h" @@ -17,7 +18,7 @@ #include #include -#ifdef ARDUINO_ESP8266_RELEASE_2_3_0 +#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) extern "C" { #include }; diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 2f6c32aec6..c743d253a6 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -1,4 +1,5 @@ #include "wifi_component.h" +#include "esphome/core/macros.h" #ifdef ARDUINO_ARCH_ESP8266 @@ -10,10 +11,6 @@ #include #endif -#ifdef WIFI_IS_OFF_AT_BOOT // Identifies ESP8266 Arduino 3.0.0 -#define ARDUINO_ESP8266_RELEASE_3 -#endif - extern "C" { #include "lwip/err.h" #include "lwip/dns.h" @@ -22,7 +19,7 @@ extern "C" { #if LWIP_IPV6 #include "lwip/netif.h" // struct netif #endif -#ifdef ARDUINO_ESP8266_RELEASE_3 +#if ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) #include "LwipDhcpServer.h" #define wifi_softap_set_dhcps_lease(lease) dhcpSoftAP.set_dhcps_lease(lease) #define wifi_softap_set_dhcps_lease_time(time) dhcpSoftAP.set_dhcps_lease_time(time) @@ -229,7 +226,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { conf.bssid_set = 0; } -#ifndef ARDUINO_ESP8266_RELEASE_2_3_0 +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) if (ap.get_password().empty()) { conf.threshold.authmode = AUTH_OPEN; } else { @@ -495,7 +492,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { ESP_LOGVV(TAG, "Event: AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi); break; } -#ifndef ARDUINO_ESP8266_RELEASE_2_3_0 +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) case EVENT_OPMODE_CHANGED: { auto it = event->event_info.opmode_changed; ESP_LOGV(TAG, "Event: Changed Mode old=%s new=%s", get_op_mode_str(it.old_opmode), @@ -580,7 +577,7 @@ bool WiFiComponent::wifi_scan_start_() { config.bssid = nullptr; config.channel = 0; config.show_hidden = 1; -#ifndef ARDUINO_ESP8266_RELEASE_2_3_0 +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) config.scan_type = WIFI_SCAN_TYPE_ACTIVE; if (FIRST_SCAN) { config.scan_time.active.min = 100; @@ -659,7 +656,7 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { return false; } -#ifdef ARDUINO_ESP8266_RELEASE_3 +#if ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) dhcpSoftAP.begin(&info); #endif diff --git a/esphome/core/esphal.cpp b/esphome/core/esphal.cpp index 6b8350991e..7851ba01b6 100644 --- a/esphome/core/esphal.cpp +++ b/esphome/core/esphal.cpp @@ -1,4 +1,5 @@ #include "esphome/core/esphal.h" +#include "esphome/core/macros.h" #include "esphome/core/helpers.h" #include "esphome/core/defines.h" #include "esphome/core/log.h" @@ -298,7 +299,7 @@ void force_link_symbols() { } // namespace esphome -#ifdef ARDUINO_ESP8266_RELEASE_2_3_0 +#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) // Fix 2.3.0 std missing memchr extern "C" { void *memchr(const void *s, int c, size_t n) { diff --git a/esphome/core/macros.h b/esphome/core/macros.h new file mode 100644 index 0000000000..59b52bf7a1 --- /dev/null +++ b/esphome/core/macros.h @@ -0,0 +1,56 @@ +#pragma once + +#define VERSION_CODE(major, minor, patch) ((major) << 16 | (minor) << 8 | (patch)) + +#if defined(ARDUINO_ARCH_ESP8266) + +#include +#if defined(ARDUINO_ESP8266_MAJOR) && defined(ARDUINO_ESP8266_MINOR) && defined(ARDUINO_ESP8266_REVISION) // v3.0.1+ +#define ARDUINO_VERSION_CODE VERSION_CODE(ARDUINO_ESP8266_MAJOR, ARDUINO_ESP8266_MINOR, ARDUINO_ESP8266_REVISION) +#elif ARDUINO_ESP8266_GIT_VER == 0xefb0341a // version defines were screwed up in v3.0.0 +#define ARDUINO_VERSION_CODE VERSION_CODE(3, 0, 0) +#elif defined(ARDUINO_ESP8266_RELEASE_2_7_4) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 4) +#elif defined(ARDUINO_ESP8266_RELEASE_2_7_3) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 3) +#elif defined(ARDUINO_ESP8266_RELEASE_2_7_2) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 2) +#elif defined(ARDUINO_ESP8266_RELEASE_2_7_1) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 1) +#elif defined(ARDUINO_ESP8266_RELEASE_2_7_0) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 0) +#elif defined(ARDUINO_ESP8266_RELEASE_2_6_3) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 6, 3) +#elif defined(ARDUINO_ESP8266_RELEASE_2_6_2) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 6, 2) +#elif defined(ARDUINO_ESP8266_RELEASE_2_6_1) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 6, 1) +#elif defined(ARDUINO_ESP8266_RELEASE_2_5_2) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 5, 2) +#elif defined(ARDUINO_ESP8266_RELEASE_2_5_1) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 5, 1) +#elif defined(ARDUINO_ESP8266_RELEASE_2_5_0) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 5, 0) +#elif defined(ARDUINO_ESP8266_RELEASE_2_4_2) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 4, 2) +#elif defined(ARDUINO_ESP8266_RELEASE_2_4_1) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 4, 1) +#elif defined(ARDUINO_ESP8266_RELEASE_2_4_0) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 4, 0) +#elif defined(ARDUINO_ESP8266_RELEASE_2_3_0) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 3, 0) +#else +#warning "Could not determine Arduino framework version, update esphome/core/macros.h!" +#endif + +#elif defined(ARDUINO_ARCH_ESP32) + +#if defined(IDF_VER) // identifies v2, needed since v1 doesn't have the esp_arduino_version.h header +#include +#define ARDUINO_VERSION_CODE \ + VERSION_CODE(ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATH) +#else +#define ARDUINO_VERSION_CODE VERSION_CODE(1, 0, 0) // there are no defines identifying minor/patch version +#endif + +#endif From 5ec9bb0fb5cc446ea5d9a8de9c22a583eeb52700 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 23 Aug 2021 09:21:30 +0200 Subject: [PATCH 1218/1841] Clean-up constant definitions (#2148) --- esphome/components/api/api_connection.cpp | 4 +- .../components/bme680_bsec/bme680_bsec.cpp | 4 +- esphome/components/bme680_bsec/bme680_bsec.h | 2 +- .../esp32_ble_server/ble_server.cpp | 4 +- esphome/components/mqtt/mqtt_component.cpp | 4 +- esphome/components/wifi/__init__.py | 2 +- esphome/components/wifi/wifi_component.cpp | 14 ++--- esphome/components/wifi/wifi_component.h | 16 ++--- .../components/wifi/wifi_component_esp32.cpp | 8 +-- .../wifi/wifi_component_esp8266.cpp | 6 +- esphome/core/defines.h | 61 +++++++++++++------ esphome/core/version.h | 8 ++- 12 files changed, 77 insertions(+), 56 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 4a31f15e77..05dc14269b 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -745,9 +745,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { resp.mac_address = get_mac_address_pretty(); resp.esphome_version = ESPHOME_VERSION; resp.compilation_time = App.get_compilation_time(); -#ifdef ARDUINO_BOARD - resp.model = ARDUINO_BOARD; -#endif + resp.model = ESPHOME_BOARD; #ifdef USE_DEEP_SLEEP resp.has_deep_sleep = deep_sleep::global_has_deep_sleep; #endif diff --git a/esphome/components/bme680_bsec/bme680_bsec.cpp b/esphome/components/bme680_bsec/bme680_bsec.cpp index 8f53180296..e2cb7491a6 100644 --- a/esphome/components/bme680_bsec/bme680_bsec.cpp +++ b/esphome/components/bme680_bsec/bme680_bsec.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "bme680_bsec.sensor"; static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"}; -BME680BSECComponent *BME680BSECComponent::instance; +BME680BSECComponent *BME680BSECComponent::instance; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) void BME680BSECComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up BME680 via BSEC..."); @@ -359,7 +359,7 @@ void BME680BSECComponent::publish_sensor_state_(sensor::Sensor *sensor, float va sensor->publish_state(value); } -void BME680BSECComponent::publish_sensor_state_(text_sensor::TextSensor *sensor, std::string value) { +void BME680BSECComponent::publish_sensor_state_(text_sensor::TextSensor *sensor, const std::string &value) { if (!sensor || (sensor->has_state() && sensor->state == value)) { return; } diff --git a/esphome/components/bme680_bsec/bme680_bsec.h b/esphome/components/bme680_bsec/bme680_bsec.h index 73994b7541..365aec725e 100644 --- a/esphome/components/bme680_bsec/bme680_bsec.h +++ b/esphome/components/bme680_bsec/bme680_bsec.h @@ -70,7 +70,7 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice { int64_t get_time_ns_(); void publish_sensor_state_(sensor::Sensor *sensor, float value, bool change_only = false); - void publish_sensor_state_(text_sensor::TextSensor *sensor, std::string value); + void publish_sensor_state_(text_sensor::TextSensor *sensor, const std::string &value); void load_state_(); void save_state_(uint8_t accuracy); diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index db83eb6bee..f1eebf3c8a 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -82,11 +82,9 @@ bool BLEServer::create_device_characteristics_() { this->device_information_service_->create_characteristic(MODEL_UUID, BLECharacteristic::PROPERTY_READ); model->set_value(this->model_.value()); } else { -#ifdef ARDUINO_BOARD BLECharacteristic *model = this->device_information_service_->create_characteristic(MODEL_UUID, BLECharacteristic::PROPERTY_READ); - model->set_value(ARDUINO_BOARD); -#endif + model->set_value(ESPHOME_BOARD); } BLECharacteristic *version = diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 13acdcacd8..6ddc080b53 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -102,9 +102,7 @@ bool MQTTComponent::send_discovery_() { device_info["identifiers"] = get_mac_address(); device_info["name"] = node_name; device_info["sw_version"] = "esphome v" ESPHOME_VERSION " " + App.get_compilation_time(); -#ifdef ARDUINO_BOARD - device_info["model"] = ARDUINO_BOARD; -#endif + device_info["model"] = ESPHOME_BOARD; device_info["manufacturer"] = "espressif"; }, 0, discovery_info.retain); diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index d066570cc8..c2943d0645 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -305,7 +305,7 @@ def wifi_network(config, static_ip): cg.add(ap.set_password(config[CONF_PASSWORD])) if CONF_EAP in config: cg.add(ap.set_eap(eap_auth(config[CONF_EAP]))) - cg.add_define("ESPHOME_WIFI_WPA2_EAP") + cg.add_define("USE_WIFI_WPA2_EAP") if CONF_BSSID in config: cg.add(ap.set_bssid([HexInt(i) for i in config[CONF_BSSID].parts])) if CONF_HIDDEN in config: diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index e99cd0e1b1..50feeb6cad 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -258,7 +258,7 @@ void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) { ESP_LOGV(TAG, " BSSID: Not Set"); } -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP if (ap.get_eap().has_value()) { ESP_LOGV(TAG, " WPA2 Enterprise authentication configured:"); EAPAuth eap_config = ap.get_eap().value(); @@ -274,7 +274,7 @@ void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) { } else { #endif ESP_LOGV(TAG, " Password: " LOG_SECRET("'%s'"), ap.get_password().c_str()); -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP } #endif if (ap.get_channel().has_value()) { @@ -478,7 +478,7 @@ void WiFiComponent::check_scanning_finished() { // copy manual IP (if set) connect_params.set_manual_ip(config.get_manual_ip()); -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP // copy EAP parameters (if set) connect_params.set_eap(config.get_eap()); #endif @@ -638,8 +638,8 @@ void WiFiAP::set_ssid(const std::string &ssid) { this->ssid_ = ssid; } void WiFiAP::set_bssid(bssid_t bssid) { this->bssid_ = bssid; } void WiFiAP::set_bssid(optional bssid) { this->bssid_ = bssid; } void WiFiAP::set_password(const std::string &password) { this->password_ = password; } -#ifdef ESPHOME_WIFI_WPA2_EAP -void WiFiAP::set_eap(optional eap_auth) { this->eap_ = eap_auth; } +#ifdef USE_WIFI_WPA2_EAP +void WiFiAP::set_eap(optional eap_auth) { this->eap_ = std::move(eap_auth); } #endif void WiFiAP::set_channel(optional channel) { this->channel_ = channel; } void WiFiAP::set_manual_ip(optional manual_ip) { this->manual_ip_ = std::move(manual_ip); } @@ -647,7 +647,7 @@ void WiFiAP::set_hidden(bool hidden) { this->hidden_ = hidden; } const std::string &WiFiAP::get_ssid() const { return this->ssid_; } const optional &WiFiAP::get_bssid() const { return this->bssid_; } const std::string &WiFiAP::get_password() const { return this->password_; } -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP const optional &WiFiAP::get_eap() const { return this->eap_; } #endif const optional &WiFiAP::get_channel() const { return this->channel_; } @@ -679,7 +679,7 @@ bool WiFiScanResult::matches(const WiFiAP &config) { if (config.get_bssid().has_value() && *config.get_bssid() != this->bssid_) return false; -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP // BSSID requires auth but no PSK or EAP credentials given if (this->with_auth_ && (config.get_password().empty() && !config.get_eap().has_value())) return false; diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index d3b086553c..3a4213c93c 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -63,7 +63,7 @@ struct ManualIP { IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default. }; -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP struct EAPAuth { std::string identity; // required for all auth types std::string username; @@ -73,7 +73,7 @@ struct EAPAuth { const char *client_cert; const char *client_key; }; -#endif // ESPHOME_WIFI_WPA2_EAP +#endif // USE_WIFI_WPA2_EAP using bssid_t = std::array; @@ -83,9 +83,9 @@ class WiFiAP { void set_bssid(bssid_t bssid); void set_bssid(optional bssid); void set_password(const std::string &password); -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP void set_eap(optional eap_auth); -#endif // ESPHOME_WIFI_WPA2_EAP +#endif // USE_WIFI_WPA2_EAP void set_channel(optional channel); void set_priority(float priority) { priority_ = priority; } void set_manual_ip(optional manual_ip); @@ -93,9 +93,9 @@ class WiFiAP { const std::string &get_ssid() const; const optional &get_bssid() const; const std::string &get_password() const; -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP const optional &get_eap() const; -#endif // ESPHOME_WIFI_WPA2_EAP +#endif // USE_WIFI_WPA2_EAP const optional &get_channel() const; float get_priority() const { return priority_; } const optional &get_manual_ip() const; @@ -105,9 +105,9 @@ class WiFiAP { std::string ssid_; optional bssid_; std::string password_; -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP optional eap_; -#endif // ESPHOME_WIFI_WPA2_EAP +#endif // USE_WIFI_WPA2_EAP optional channel_; float priority_{0}; optional manual_ip_; diff --git a/esphome/components/wifi/wifi_component_esp32.cpp b/esphome/components/wifi/wifi_component_esp32.cpp index 1bccf08a7f..57c4efcdd5 100644 --- a/esphome/components/wifi/wifi_component_esp32.cpp +++ b/esphome/components/wifi/wifi_component_esp32.cpp @@ -6,7 +6,7 @@ #include #include -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP #include #endif #include "lwip/err.h" @@ -163,7 +163,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { conf.sta.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK; } -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP if (ap.get_eap().has_value()) { conf.sta.threshold.authmode = WIFI_AUTH_WPA2_ENTERPRISE; } @@ -220,7 +220,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { } // setup enterprise authentication if required -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP if (ap.get_eap().has_value()) { // note: all certificates and keys have to be null terminated. Lengths are appended by +1 to include \0. EAPAuth eap = ap.get_eap().value(); @@ -264,7 +264,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_enable failed! %d", err); } } -#endif // ESPHOME_WIFI_WPA2_EAP +#endif // USE_WIFI_WPA2_EAP this->wifi_apply_hostname_(); diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index c743d253a6..ab68f421a8 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -7,7 +7,7 @@ #include #include -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP #include #endif @@ -250,7 +250,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { } // setup enterprise authentication if required -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP if (ap.get_eap().has_value()) { // note: all certificates and keys have to be null terminated. Lengths are appended by +1 to include \0. EAPAuth eap = ap.get_eap().value(); @@ -293,7 +293,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_enable failed! %d", ret); } } -#endif // ESPHOME_WIFI_WPA2_EAP +#endif // USE_WIFI_WPA2_EAP this->wifi_apply_hostname_(); diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 5c176d1b33..ec10e86586 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -1,31 +1,52 @@ #pragma once -// This file is auto-generated! Do not edit! +// This file is not used by the runtime, instead, a version is generated during +// compilation with only the relevant feature flags for the current build. +// +// This file is only used by static analyzers and IDEs. + +// Informative flags +#define ESPHOME_BOARD "dummy_board" +#define ESPHOME_PROJECT_NAME "dummy project" +#define ESPHOME_PROJECT_VERSION "v2" + +// Feature flags +#define USE_ADC_SENSOR_VCC #define USE_API -#define USE_LOGGER #define USE_BINARY_SENSOR -#define USE_SENSOR -#define USE_SWITCH -#define USE_WIFI -#define USE_STATUS_LED -#define USE_TEXT_SENSOR -#define USE_FAN -#define USE_COVER -#define USE_LIGHT +#define USE_CAPTIVE_PORTAL #define USE_CLIMATE -#define USE_NUMBER -#define USE_SELECT -#define USE_MQTT -#define USE_POWER_SUPPLY +#define USE_COVER +#define USE_DEEP_SLEEP +#define USE_ESP8266_PREFERENCES_FLASH +#define USE_FAN #define USE_HOMEASSISTANT_TIME +#define USE_I2C_MULTIPLEXER #define USE_JSON +#define USE_LIGHT +#define USE_LOGGER +#define USE_MDNS +#define USE_MQTT +#define USE_NUMBER +#define USE_OTA_STATE_CALLBACK +#define USE_POWER_SUPPLY +#define USE_PROMETHEUS +#define USE_SELECT +#define USE_SENSOR +#define USE_STATUS_LED +#define USE_SWITCH +#define USE_TEXT_SENSOR +#define USE_TFT_UPLOAD +#define USE_TIME +#define USE_WIFI +#define USE_WIFI_WPA2_EAP + #ifdef ARDUINO_ARCH_ESP32 -#define USE_ESP32_CAMERA #define USE_ESP32_BLE_SERVER +#define USE_ESP32_CAMERA +#define USE_ETHERNET #define USE_IMPROV #endif -#define USE_TIME -#define USE_DEEP_SLEEP -#define USE_CAPTIVE_PORTAL -#define ESPHOME_BOARD "dummy_board" -#define USE_MDNS + +// Disabled feature flags +//#define USE_BSEC // Requires a library with proprietary license. diff --git a/esphome/core/version.h b/esphome/core/version.h index 0942c3e52f..b64f581b25 100644 --- a/esphome/core/version.h +++ b/esphome/core/version.h @@ -1,3 +1,9 @@ #pragma once -// This file is auto-generated! Do not edit! + +// This file is not used by the runtime, instead, a version is generated during +// compilation with only the version for the current build. This is kept in its +// own file so that not all files have to be recompiled for each new release. +// +// This file is only used by static analyzers and IDEs. + #define ESPHOME_VERSION "dev" From 2f33cd2db544b098141ea1476b58d5569e8824ac Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 23 Aug 2021 10:00:38 +0200 Subject: [PATCH 1219/1841] Remove double scheduling from addressable lights (#1963) --- .../adalight/adalight_light_effect.cpp | 2 + .../e131/e131_addressable_light_effect.cpp | 1 + .../components/fastled_base/fastled_light.cpp | 9 ++--- .../components/fastled_base/fastled_light.h | 2 +- .../components/light/addressable_light.cpp | 5 +-- esphome/components/light/addressable_light.h | 8 ++-- .../light/addressable_light_effect.h | 37 ++++++++++++------- esphome/components/light/light_output.h | 7 ++++ esphome/components/light/light_state.cpp | 14 +++---- .../neopixelbus/neopixelbus_light.h | 5 +-- .../components/partition/light_partition.h | 10 ++--- esphome/components/wled/wled_light_effect.cpp | 2 + 12 files changed, 57 insertions(+), 45 deletions(-) diff --git a/esphome/components/adalight/adalight_light_effect.cpp b/esphome/components/adalight/adalight_light_effect.cpp index 5f60fbe0b2..d9c2892d21 100644 --- a/esphome/components/adalight/adalight_light_effect.cpp +++ b/esphome/components/adalight/adalight_light_effect.cpp @@ -44,6 +44,7 @@ void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) { for (int led = it.size(); led-- > 0;) { it[led].set(Color::BLACK); } + it.schedule_show(); } void AdalightLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) { @@ -133,6 +134,7 @@ AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableL it[led].set(Color(led_data[0], led_data[1], led_data[2], white)); } + it.schedule_show(); return CONSUMED; } diff --git a/esphome/components/e131/e131_addressable_light_effect.cpp b/esphome/components/e131/e131_addressable_light_effect.cpp index f280b5bc94..f0f165b25f 100644 --- a/esphome/components/e131/e131_addressable_light_effect.cpp +++ b/esphome/components/e131/e131_addressable_light_effect.cpp @@ -84,6 +84,7 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet break; } + it->schedule_show(); return true; } diff --git a/esphome/components/fastled_base/fastled_light.cpp b/esphome/components/fastled_base/fastled_light.cpp index 4d791f5709..edfeb401f1 100644 --- a/esphome/components/fastled_base/fastled_light.cpp +++ b/esphome/components/fastled_base/fastled_light.cpp @@ -20,13 +20,12 @@ void FastLEDLightOutput::dump_config() { ESP_LOGCONFIG(TAG, " Num LEDs: %u", this->num_leds_); ESP_LOGCONFIG(TAG, " Max refresh rate: %u", *this->max_refresh_rate_); } -void FastLEDLightOutput::loop() { - if (!this->should_show_()) - return; - - uint32_t now = micros(); +void FastLEDLightOutput::write_state(light::LightState *state) { // protect from refreshing too often + uint32_t now = micros(); if (*this->max_refresh_rate_ != 0 && (now - this->last_refresh_) < *this->max_refresh_rate_) { + // try again next loop iteration, so that this change won't get lost + this->schedule_show(); return; } this->last_refresh_ = now; diff --git a/esphome/components/fastled_base/fastled_light.h b/esphome/components/fastled_base/fastled_light.h index ac6acc95a5..ee85735dea 100644 --- a/esphome/components/fastled_base/fastled_light.h +++ b/esphome/components/fastled_base/fastled_light.h @@ -213,7 +213,7 @@ class FastLEDLightOutput : public light::AddressableLight { } void setup() override; void dump_config() override; - void loop() override; + void write_state(light::LightState *state) override; float get_setup_priority() const override { return setup_priority::HARDWARE; } void clear_effect_data() override { diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index 9a34dde6be..1b1bc88a6f 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -12,8 +12,7 @@ void AddressableLight::call_setup() { #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE this->set_interval(5000, [this]() { const char *name = this->state_parent_ == nullptr ? "" : this->state_parent_->get_name().c_str(); - ESP_LOGVV(TAG, "Addressable Light '%s' (effect_active=%s next_show=%s)", name, YESNO(this->effect_active_), - YESNO(this->next_show_)); + ESP_LOGVV(TAG, "Addressable Light '%s' (effect_active=%s)", name, YESNO(this->effect_active_)); for (int i = 0; i < this->size(); i++) { auto color = this->get(i); ESP_LOGVV(TAG, " [%2d] Color: R=%3u G=%3u B=%3u W=%3u", i, color.get_red_raw(), color.get_green_raw(), @@ -36,7 +35,7 @@ Color esp_color_from_light_color_values(LightColorValues val) { return Color(r, g, b, w); } -void AddressableLight::write_state(LightState *state) { +void AddressableLight::update_state(LightState *state) { auto val = state->current_values; auto max_brightness = to_uint8_scale(val.get_brightness() * val.get_state()); this->correction_.set_local_brightness(max_brightness); diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index ab1efdf160..bba2158457 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -51,9 +51,9 @@ class AddressableLight : public LightOutput, public Component { amnt = this->size(); this->range(amnt, this->size()) = this->range(0, -amnt); } + // Indicates whether an effect that directly updates the output buffer is active to prevent overwriting bool is_effect_active() const { return this->effect_active_; } void set_effect_active(bool effect_active) { this->effect_active_ = effect_active; } - void write_state(LightState *state) override; std::unique_ptr create_default_transition() override; void set_correction(float red, float green, float blue, float white = 1.0f) { this->correction_.set_max_brightness( @@ -63,7 +63,8 @@ class AddressableLight : public LightOutput, public Component { this->correction_.calculate_gamma_table(state->get_gamma_correct()); this->state_parent_ = state; } - void schedule_show() { this->next_show_ = true; } + void update_state(LightState *state) override; + void schedule_show() { this->state_parent_->next_write_ = true; } #ifdef USE_POWER_SUPPLY void set_power_supply(power_supply::PowerSupply *power_supply) { this->power_.set_parent(power_supply); } @@ -74,9 +75,7 @@ class AddressableLight : public LightOutput, public Component { protected: friend class AddressableLightTransformer; - bool should_show_() const { return this->effect_active_ || this->next_show_; } void mark_shown_() { - this->next_show_ = false; #ifdef USE_POWER_SUPPLY for (auto c : *this) { if (c.get().is_on()) { @@ -90,7 +89,6 @@ class AddressableLight : public LightOutput, public Component { virtual ESPColorView get_view_internal(int32_t index) const = 0; bool effect_active_{false}; - bool next_show_{true}; ESPColorCorrection correction_{}; #ifdef USE_POWER_SUPPLY power_supply::PowerSupplyRequester power_; diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index 3a2ba66845..1cb29dfa4e 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -63,6 +63,7 @@ class AddressableLambdaLightEffect : public AddressableLightEffect { this->last_run_ = now; this->f_(it, current_color, this->initial_run_); this->initial_run_ = false; + it.schedule_show(); } } @@ -87,6 +88,7 @@ class AddressableRainbowLightEffect : public AddressableLightEffect { var = hsv; hue += add; } + it.schedule_show(); } void set_speed(uint32_t speed) { this->speed_ = speed; } void set_width(uint16_t width) { this->width_ = width; } @@ -134,6 +136,7 @@ class AddressableColorWipeEffect : public AddressableLightEffect { new_color.b = c.b; } } + it.schedule_show(); } protected: @@ -151,25 +154,27 @@ class AddressableScanEffect : public AddressableLightEffect { void set_move_interval(uint32_t move_interval) { this->move_interval_ = move_interval; } void set_scan_width(uint32_t scan_width) { this->scan_width_ = scan_width; } void apply(AddressableLight &it, const Color ¤t_color) override { - it.all() = Color::BLACK; + const uint32_t now = millis(); + if (now - this->last_move_ < this->move_interval_) + return; + if (direction_) { + this->at_led_++; + if (this->at_led_ == it.size() - this->scan_width_) + this->direction_ = false; + } else { + this->at_led_--; + if (this->at_led_ == 0) + this->direction_ = true; + } + this->last_move_ = now; + + it.all() = Color::BLACK; for (auto i = 0; i < this->scan_width_; i++) { it[this->at_led_ + i] = current_color; } - const uint32_t now = millis(); - if (now - this->last_move_ > this->move_interval_) { - if (direction_) { - this->at_led_++; - if (this->at_led_ == it.size() - this->scan_width_) - this->direction_ = false; - } else { - this->at_led_--; - if (this->at_led_ == 0) - this->direction_ = true; - } - this->last_move_ = now; - } + it.schedule_show(); } protected: @@ -210,6 +215,7 @@ class AddressableTwinkleEffect : public AddressableLightEffect { continue; addressable[pos].set_effect_data(1); } + addressable.schedule_show(); } void set_twinkle_probability(float twinkle_probability) { this->twinkle_probability_ = twinkle_probability; } void set_progress_interval(uint32_t progress_interval) { this->progress_interval_ = progress_interval; } @@ -257,6 +263,7 @@ class AddressableRandomTwinkleEffect : public AddressableLightEffect { const uint8_t color = random_uint32() & 0b111; it[pos].set_effect_data(0b1000 | color); } + it.schedule_show(); } void set_twinkle_probability(float twinkle_probability) { this->twinkle_probability_ = twinkle_probability; } void set_progress_interval(uint32_t progress_interval) { this->progress_interval_ = progress_interval; } @@ -301,6 +308,7 @@ class AddressableFireworksEffect : public AddressableLightEffect { it[pos] = current_color; } } + it.schedule_show(); } void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; } void set_spark_probability(float spark_probability) { this->spark_probability_ = spark_probability; } @@ -335,6 +343,7 @@ class AddressableFlickerEffect : public AddressableLightEffect { // slowly fade back to "real" value var = (var.get() * inv_intensity) + (current_color * intensity); } + it.schedule_show(); } void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; } void set_intensity(float intensity) { this->intensity_ = to_uint8_scale(intensity); } diff --git a/esphome/components/light/light_output.h b/esphome/components/light/light_output.h index 7568ea6831..73ba0371cd 100644 --- a/esphome/components/light/light_output.h +++ b/esphome/components/light/light_output.h @@ -19,6 +19,13 @@ class LightOutput { virtual void setup_state(LightState *state) {} + /// Called on every update of the current values of the associated LightState, + /// can optionally be used to do processing of this change. + virtual void update_state(LightState *state) {} + + /// Called from loop() every time the light state has changed, and should + /// should write the new state to hardware. Every call to write_state() is + /// preceded by (at least) one call to update_state(). virtual void write_state(LightState *state) = 0; }; diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 030cf4b7a2..4c4eefdc30 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -114,9 +114,11 @@ void LightState::loop() { // Apply transformer (if any) if (this->transformer_ != nullptr) { auto values = this->transformer_->apply(); - this->next_write_ = values.has_value(); // don't write if transformer doesn't want us to - if (values.has_value()) + if (values.has_value()) { this->current_values = *values; + this->output_->update_state(this); + this->next_write_ = true; + } if (this->transformer_->is_finished()) { this->transformer_->stop(); @@ -127,18 +129,15 @@ void LightState::loop() { // Write state to the light if (this->next_write_) { - this->output_->write_state(this); this->next_write_ = false; + this->output_->write_state(this); } } float LightState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; } uint32_t LightState::hash_base() { return 1114400283; } -void LightState::publish_state() { - this->remote_values_callback_.call(); - this->next_write_ = true; -} +void LightState::publish_state() { this->remote_values_callback_.call(); } LightOutput *LightState::get_output() const { return this->output_; } std::string LightState::get_effect_name() { @@ -248,6 +247,7 @@ void LightState::set_immediately_(const LightColorValues &target, bool set_remot if (set_remote_values) { this->remote_values = target; } + this->output_->update_state(this); this->next_write_ = true; } diff --git a/esphome/components/neopixelbus/neopixelbus_light.h b/esphome/components/neopixelbus/neopixelbus_light.h index 1f2cde0bd2..6fa3fb3cd9 100644 --- a/esphome/components/neopixelbus/neopixelbus_light.h +++ b/esphome/components/neopixelbus/neopixelbus_light.h @@ -83,10 +83,7 @@ class NeoPixelBusLightOutputBase : public light::AddressableLight { this->controller_->Begin(); } - void loop() override { - if (!this->should_show_()) - return; - + void write_state(light::LightState *state) override { this->mark_shown_(); this->controller_->Dirty(); diff --git a/esphome/components/partition/light_partition.h b/esphome/components/partition/light_partition.h index 687fe562d1..f74001cf75 100644 --- a/esphome/components/partition/light_partition.h +++ b/esphome/components/partition/light_partition.h @@ -50,13 +50,11 @@ class PartitionLightOutput : public light::AddressableLight { } } light::LightTraits get_traits() override { return this->segments_[0].get_src()->get_traits(); } - void loop() override { - if (this->should_show_()) { - for (auto seg : this->segments_) { - seg.get_src()->schedule_show(); - } - this->mark_shown_(); + void write_state(light::LightState *state) override { + for (auto seg : this->segments_) { + seg.get_src()->schedule_show(); } + this->mark_shown_(); } protected: diff --git a/esphome/components/wled/wled_light_effect.cpp b/esphome/components/wled/wled_light_effect.cpp index 690d2f3b00..915d1c6cc2 100644 --- a/esphome/components/wled/wled_light_effect.cpp +++ b/esphome/components/wled/wled_light_effect.cpp @@ -42,6 +42,7 @@ void WLEDLightEffect::blank_all_leds_(light::AddressableLight &it) { for (int led = it.size(); led-- > 0;) { it[led].set(Color::BLACK); } + it.schedule_show(); } void WLEDLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) { @@ -134,6 +135,7 @@ bool WLEDLightEffect::parse_frame_(light::AddressableLight &it, const uint8_t *p blank_at_ = millis() + DEFAULT_BLANK_TIME; } + it.schedule_show(); return true; } From d71996e58d57d70446d9eb4d6cc023263e9c779c Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 23 Aug 2021 10:43:54 +0200 Subject: [PATCH 1220/1841] Reduce static RAM usage (#2140) --- esphome/components/light/light_call.cpp | 37 +++--- esphome/components/ota/ota_component.cpp | 11 +- esphome/components/sntp/sntp_component.cpp | 5 +- esphome/components/time/real_time_clock.cpp | 5 +- .../wifi/wifi_component_esp8266.cpp | 112 ++++++++++-------- esphome/core/log.h | 25 ++++ 6 files changed, 113 insertions(+), 82 deletions(-) diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 6945d37ded..d979b13368 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -8,26 +8,23 @@ namespace light { static const char *const TAG = "light"; static const char *color_mode_to_human(ColorMode color_mode) { - switch (color_mode) { - case ColorMode::UNKNOWN: - return "Unknown"; - case ColorMode::WHITE: - return "White"; - case ColorMode::COLOR_TEMPERATURE: - return "Color temperature"; - case ColorMode::COLD_WARM_WHITE: - return "Cold/warm white"; - case ColorMode::RGB: - return "RGB"; - case ColorMode::RGB_WHITE: - return "RGBW"; - case ColorMode::RGB_COLD_WARM_WHITE: - return "RGB + cold/warm white"; - case ColorMode::RGB_COLOR_TEMPERATURE: - return "RGB + color temperature"; - default: - return ""; - } + if (color_mode == ColorMode::UNKNOWN) + return "Unknown"; + if (color_mode == ColorMode::WHITE) + return "White"; + if (color_mode == ColorMode::COLOR_TEMPERATURE) + return "Color temperature"; + if (color_mode == ColorMode::COLD_WARM_WHITE) + return "Cold/warm white"; + if (color_mode == ColorMode::RGB) + return "RGB"; + if (color_mode == ColorMode::RGB_WHITE) + return "RGBW"; + if (color_mode == ColorMode::RGB_COLD_WARM_WHITE) + return "RGB + cold/warm white"; + if (color_mode == ColorMode::RGB_COLOR_TEMPERATURE) + return "RGB + color temperature"; + return ""; } void LightCall::perform() { diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 71f8101704..ac72befb9e 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -178,28 +178,29 @@ void OTAComponent::handle_() { #endif if (!Update.begin(ota_size, U_FLASH)) { + uint8_t error = Update.getError(); StreamString ss; Update.printError(ss); #ifdef ARDUINO_ARCH_ESP8266 - if (ss.indexOf("Invalid bootstrapping") != -1) { + if (error == UPDATE_ERROR_BOOTSTRAP) { error_code = OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; goto error; } - if (ss.indexOf("new Flash config wrong") != -1 || ss.indexOf("new Flash config wsong") != -1) { + if (error == UPDATE_ERROR_NEW_FLASH_CONFIG) { error_code = OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG; goto error; } - if (ss.indexOf("Flash config wrong real") != -1 || ss.indexOf("Flash config wsong real") != -1) { + if (error == UPDATE_ERROR_FLASH_CONFIG) { error_code = OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; goto error; } - if (ss.indexOf("Not Enough Space") != -1) { + if (error == UPDATE_ERROR_SPACE) { error_code = OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE; goto error; } #endif #ifdef ARDUINO_ARCH_ESP32 - if (ss.indexOf("Bad Size Given") != -1) { + if (error == UPDATE_ERROR_SIZE) { error_code = OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; goto error; } diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index ff176b1d4e..895c775b19 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -56,9 +56,8 @@ void SNTPComponent::loop() { if (!time.is_valid()) return; - char buf[128]; - time.strftime(buf, sizeof(buf), "%c"); - ESP_LOGD(TAG, "Synchronized time: %s", buf); + ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%d:%d", time.year, time.month, time.day_of_month, time.hour, + time.minute, time.second); this->time_sync_callback_.call(); this->has_time_ = true; } diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index c2a93b5191..27a2d84da6 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -35,9 +35,8 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) { } auto time = this->now(); - char buf[128]; - time.strftime(buf, sizeof(buf), "%c"); - ESP_LOGD(TAG, "Synchronized time: %s", buf); + ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%d:%d", time.year, time.month, time.day_of_month, time.hour, + time.minute, time.second); this->time_sync_callback_.call(); } diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index ab68f421a8..ad1a64d1f4 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -366,65 +366,75 @@ const char *get_op_mode_str(uint8_t mode) { return "UNKNOWN"; } } +// Note that this method returns PROGMEM strings, so use LOG_STR_ARG() to access them. const char *get_disconnect_reason_str(uint8_t reason) { + /* If this were one big switch statement, GCC would generate a lookup table for it. However, the values of the + * REASON_* constants aren't continuous, and GCC will fill in the gap with the default value -- wasting 4 bytes of RAM + * per entry. As there's ~175 default entries, this wastes 700 bytes of RAM. + */ + if (reason <= REASON_CIPHER_SUITE_REJECTED) { // This must be the last constant with a value <200 + switch (reason) { + case REASON_AUTH_EXPIRE: + return LOG_STR("Auth Expired"); + case REASON_AUTH_LEAVE: + return LOG_STR("Auth Leave"); + case REASON_ASSOC_EXPIRE: + return LOG_STR("Association Expired"); + case REASON_ASSOC_TOOMANY: + return LOG_STR("Too Many Associations"); + case REASON_NOT_AUTHED: + return LOG_STR("Not Authenticated"); + case REASON_NOT_ASSOCED: + return LOG_STR("Not Associated"); + case REASON_ASSOC_LEAVE: + return LOG_STR("Association Leave"); + case REASON_ASSOC_NOT_AUTHED: + return LOG_STR("Association not Authenticated"); + case REASON_DISASSOC_PWRCAP_BAD: + return LOG_STR("Disassociate Power Cap Bad"); + case REASON_DISASSOC_SUPCHAN_BAD: + return LOG_STR("Disassociate Supported Channel Bad"); + case REASON_IE_INVALID: + return LOG_STR("IE Invalid"); + case REASON_MIC_FAILURE: + return LOG_STR("Mic Failure"); + case REASON_4WAY_HANDSHAKE_TIMEOUT: + return LOG_STR("4-Way Handshake Timeout"); + case REASON_GROUP_KEY_UPDATE_TIMEOUT: + return LOG_STR("Group Key Update Timeout"); + case REASON_IE_IN_4WAY_DIFFERS: + return LOG_STR("IE In 4-Way Handshake Differs"); + case REASON_GROUP_CIPHER_INVALID: + return LOG_STR("Group Cipher Invalid"); + case REASON_PAIRWISE_CIPHER_INVALID: + return LOG_STR("Pairwise Cipher Invalid"); + case REASON_AKMP_INVALID: + return LOG_STR("AKMP Invalid"); + case REASON_UNSUPP_RSN_IE_VERSION: + return LOG_STR("Unsupported RSN IE version"); + case REASON_INVALID_RSN_IE_CAP: + return LOG_STR("Invalid RSN IE Cap"); + case REASON_802_1X_AUTH_FAILED: + return LOG_STR("802.1x Authentication Failed"); + case REASON_CIPHER_SUITE_REJECTED: + return LOG_STR("Cipher Suite Rejected"); + } + } + switch (reason) { - case REASON_AUTH_EXPIRE: - return "Auth Expired"; - case REASON_AUTH_LEAVE: - return "Auth Leave"; - case REASON_ASSOC_EXPIRE: - return "Association Expired"; - case REASON_ASSOC_TOOMANY: - return "Too Many Associations"; - case REASON_NOT_AUTHED: - return "Not Authenticated"; - case REASON_NOT_ASSOCED: - return "Not Associated"; - case REASON_ASSOC_LEAVE: - return "Association Leave"; - case REASON_ASSOC_NOT_AUTHED: - return "Association not Authenticated"; - case REASON_DISASSOC_PWRCAP_BAD: - return "Disassociate Power Cap Bad"; - case REASON_DISASSOC_SUPCHAN_BAD: - return "Disassociate Supported Channel Bad"; - case REASON_IE_INVALID: - return "IE Invalid"; - case REASON_MIC_FAILURE: - return "Mic Failure"; - case REASON_4WAY_HANDSHAKE_TIMEOUT: - return "4-Way Handshake Timeout"; - case REASON_GROUP_KEY_UPDATE_TIMEOUT: - return "Group Key Update Timeout"; - case REASON_IE_IN_4WAY_DIFFERS: - return "IE In 4-Way Handshake Differs"; - case REASON_GROUP_CIPHER_INVALID: - return "Group Cipher Invalid"; - case REASON_PAIRWISE_CIPHER_INVALID: - return "Pairwise Cipher Invalid"; - case REASON_AKMP_INVALID: - return "AKMP Invalid"; - case REASON_UNSUPP_RSN_IE_VERSION: - return "Unsupported RSN IE version"; - case REASON_INVALID_RSN_IE_CAP: - return "Invalid RSN IE Cap"; - case REASON_802_1X_AUTH_FAILED: - return "802.1x Authentication Failed"; - case REASON_CIPHER_SUITE_REJECTED: - return "Cipher Suite Rejected"; case REASON_BEACON_TIMEOUT: - return "Beacon Timeout"; + return LOG_STR("Beacon Timeout"); case REASON_NO_AP_FOUND: - return "AP Not Found"; + return LOG_STR("AP Not Found"); case REASON_AUTH_FAIL: - return "Authentication Failed"; + return LOG_STR("Authentication Failed"); case REASON_ASSOC_FAIL: - return "Association Failed"; + return LOG_STR("Association Failed"); case REASON_HANDSHAKE_TIMEOUT: - return "Handshake Failed"; + return LOG_STR("Handshake Failed"); case REASON_UNSPECIFIED: default: - return "Unspecified"; + return LOG_STR("Unspecified"); } } @@ -448,7 +458,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { ESP_LOGW(TAG, "Event: Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); } else { ESP_LOGW(TAG, "Event: Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, - format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); + format_mac_addr(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason))); } break; } diff --git a/esphome/core/log.h b/esphome/core/log.h index 0eec28101f..fbaaf14408 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -7,6 +7,7 @@ #include "WString.h" #endif +#include "esphome/core/macros.h" // avoid esp-idf redefining our macros #include "esphome/core/esphal.h" @@ -162,4 +163,28 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT #define ONOFF(b) ((b) ? "ON" : "OFF") #define TRUEFALSE(b) ((b) ? "TRUE" : "FALSE") +#ifdef USE_STORE_LOG_STR_IN_FLASH +#define LOG_STR(s) PSTR(s) + +// From Arduino 2.5 onwards, we can pass a PSTR() to printf(). For previous versions, emulate support +// by copying the message to a local buffer first. String length is limited to 63 characters. +// https://github.com/esp8266/Arduino/commit/6280e98b0360f85fdac2b8f10707fffb4f6e6e31 +#include +#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 5, 0) +#define LOG_STR_ARG(s) \ + ({ \ + char __buf[64]; \ + __buf[63] = '\0'; \ + strncpy_P(__buf, s, 63); \ + __buf; \ + }) +#else +#define LOG_STR_ARG(s) (s) +#endif + +#else +#define LOG_STR(s) (s) +#define LOG_STR_ARG(s) (s) +#endif + } // namespace esphome From 518c271eba008568593642d7d06747f6e095e866 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 23 Aug 2021 10:44:24 +0200 Subject: [PATCH 1221/1841] Fix addressable light control without transitions & effects with transitions (#2187) --- esphome/components/light/addressable_light.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index 1b1bc88a6f..a8fa2cd7ac 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -45,9 +45,14 @@ void AddressableLight::update_state(LightState *state) { // don't use LightState helper, gamma correction+brightness is handled by ESPColorView this->all() = esp_color_from_light_color_values(val); + this->schedule_show(); } void AddressableLightTransformer::start() { + // don't try to transition over running effects. + if (this->light_.is_effect_active()) + return; + auto end_values = this->target_values_; this->target_color_ = esp_color_from_light_color_values(end_values); From 71237e2f76f4f7f9e2eef84bb24a01c2326becbd Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 23 Aug 2021 21:21:30 +1200 Subject: [PATCH 1222/1841] Fix template select log message mentioning number (#2194) --- esphome/components/template/select/template_select.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/template/select/template_select.cpp b/esphome/components/template/select/template_select.cpp index 782c0ee6f9..8695880856 100644 --- a/esphome/components/template/select/template_select.cpp +++ b/esphome/components/template/select/template_select.cpp @@ -11,7 +11,7 @@ void TemplateSelect::setup() { return; std::string value; - ESP_LOGD(TAG, "Setting up Template Number"); + ESP_LOGD(TAG, "Setting up Template Select"); if (!this->restore_value_) { value = this->initial_option_; ESP_LOGD(TAG, "State from initial: %s", value.c_str()); From 1c1ad3261031062dacc532f6a5fd6a54b2c1d0b2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 23 Aug 2021 20:48:12 +0200 Subject: [PATCH 1223/1841] Add deprecated attribute to some deprecated types/methods (#2185) --- esphome/components/api/api_connection.cpp | 1 + esphome/components/cover/cover.h | 3 +++ esphome/components/fan/fan_helpers.cpp | 4 +++- esphome/components/fan/fan_state.h | 3 ++- esphome/components/mqtt/mqtt_fan.cpp | 10 ++++++---- esphome/components/web_server/web_server.cpp | 8 ++++---- 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 05dc14269b..2bf3af5f65 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -289,6 +289,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { // Prefer level call.set_speed(msg.speed_level); } else if (msg.has_speed) { + // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) call.set_speed(fan::speed_enum_to_level(static_cast(msg.speed), traits.supported_speed_count())); } if (msg.has_direction) diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index 8f30750fbd..77a53f11c5 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -125,16 +125,19 @@ class Cover : public Nameable { * * This is a legacy method and may be removed later, please use `.make_call()` instead. */ + ESPDEPRECATED("open() is deprecated, use make_call().set_command_open() instead.", "2021.9") void open(); /** Close the cover. * * This is a legacy method and may be removed later, please use `.make_call()` instead. */ + ESPDEPRECATED("close() is deprecated, use make_call().set_command_close() instead.", "2021.9") void close(); /** Stop the cover. * * This is a legacy method and may be removed later, please use `.make_call()` instead. */ + ESPDEPRECATED("stop() is deprecated, use make_call().set_command_stop() instead.", "2021.9") void stop(); void add_on_state_callback(std::function &&f); diff --git a/esphome/components/fan/fan_helpers.cpp b/esphome/components/fan/fan_helpers.cpp index 09be20991b..5d923a1b15 100644 --- a/esphome/components/fan/fan_helpers.cpp +++ b/esphome/components/fan/fan_helpers.cpp @@ -4,12 +4,14 @@ namespace esphome { namespace fan { +// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels) { const auto speed_ratio = static_cast(speed_level) / (supported_speed_levels + 1); const auto legacy_level = clamp(static_cast(ceilf(speed_ratio * 3)), 1, 3); - return static_cast(legacy_level - 1); + return static_cast(legacy_level - 1); // NOLINT(clang-diagnostic-deprecated-declarations) } +// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) int speed_enum_to_level(FanSpeed speed, int supported_speed_levels) { const auto enum_level = static_cast(speed) + 1; const auto speed_level = roundf(enum_level / 3.0f * supported_speed_levels); diff --git a/esphome/components/fan/fan_state.h b/esphome/components/fan/fan_state.h index a0dda4083a..af00275df0 100644 --- a/esphome/components/fan/fan_state.h +++ b/esphome/components/fan/fan_state.h @@ -10,7 +10,7 @@ namespace esphome { namespace fan { /// Simple enum to represent the speed of a fan. - DEPRECATED - Will be deleted soon -enum FanSpeed { +enum ESPDEPRECATED("FanSpeed is deprecated.", "2021.9") FanSpeed { FAN_SPEED_LOW = 0, ///< The fan is running on low speed. FAN_SPEED_MEDIUM = 1, ///< The fan is running on medium speed. FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed. @@ -45,6 +45,7 @@ class FanStateCall { this->speed_ = speed; return *this; } + ESPDEPRECATED("set_speed() with string argument is deprecated, use integer argument instead.", "2021.9") FanStateCall &set_speed(const char *legacy_speed); FanStateCall &set_direction(FanDirection direction) { this->direction_ = direction; diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index 4171dae04c..ba9121bc5d 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -65,7 +65,9 @@ void MQTTFanComponent::setup() { if (this->state_->get_traits().supports_speed()) { this->subscribe(this->get_speed_command_topic(), [this](const std::string &topic, const std::string &payload) { - this->state_->make_call().set_speed(payload.c_str()).perform(); + this->state_->make_call() + .set_speed(payload.c_str()) // NOLINT(clang-diagnostic-deprecated-declarations) + .perform(); }); } @@ -99,16 +101,16 @@ bool MQTTFanComponent::publish_state() { if (traits.supports_speed()) { const char *payload; switch (fan::speed_level_to_enum(this->state_->speed, traits.supported_speed_count())) { - case FAN_SPEED_LOW: { + case FAN_SPEED_LOW: { // NOLINT(clang-diagnostic-deprecated-declarations) payload = "low"; break; } - case FAN_SPEED_MEDIUM: { + case FAN_SPEED_MEDIUM: { // NOLINT(clang-diagnostic-deprecated-declarations) payload = "medium"; break; } default: - case FAN_SPEED_HIGH: { + case FAN_SPEED_HIGH: { // NOLINT(clang-diagnostic-deprecated-declarations) payload = "high"; break; } diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 9dad61bb5b..56c75a1c58 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -398,13 +398,13 @@ std::string WebServer::fan_json(fan::FanState *obj) { if (traits.supports_speed()) { root["speed_level"] = obj->speed; switch (fan::speed_level_to_enum(obj->speed, traits.supported_speed_count())) { - case fan::FAN_SPEED_LOW: + case fan::FAN_SPEED_LOW: // NOLINT(clang-diagnostic-deprecated-declarations) root["speed"] = "low"; break; - case fan::FAN_SPEED_MEDIUM: + case fan::FAN_SPEED_MEDIUM: // NOLINT(clang-diagnostic-deprecated-declarations) root["speed"] = "medium"; break; - case fan::FAN_SPEED_HIGH: + case fan::FAN_SPEED_HIGH: // NOLINT(clang-diagnostic-deprecated-declarations) root["speed"] = "high"; break; } @@ -430,7 +430,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc auto call = obj->turn_on(); if (request->hasParam("speed")) { String speed = request->getParam("speed")->value(); - call.set_speed(speed.c_str()); + call.set_speed(speed.c_str()); // NOLINT(clang-diagnostic-deprecated-declarations) } if (request->hasParam("speed_level")) { String speed_level = request->getParam("speed_level")->value(); From 1b89174558c87841a568e89abbea351fe35fcf17 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 23 Aug 2021 20:49:19 +0200 Subject: [PATCH 1224/1841] Store source package in Component for debugging (#2070) --- esphome/core/application.cpp | 14 +++++------- esphome/core/component.cpp | 21 +++++++++++++++++- esphome/core/component.h | 22 +++++++++++++++++++ esphome/core/scheduler.cpp | 5 ++++- esphome/cpp_helpers.py | 33 ++++++++++++++++++++++++++++ tests/unit_tests/test_core.py | 6 ++++- tests/unit_tests/test_cpp_helpers.py | 4 ++-- 7 files changed, 91 insertions(+), 14 deletions(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 1a3158e4ce..fac17a8271 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -19,7 +19,7 @@ void Application::register_component_(Component *comp) { for (auto *c : this->components_) { if (comp == c) { - ESP_LOGW(TAG, "Component already registered! (%p)", c); + ESP_LOGW(TAG, "Component %s already registered! (%p)", c->get_component_source(), c); return; } } @@ -66,23 +66,19 @@ void Application::setup() { } void Application::loop() { uint32_t new_app_state = 0; - const uint32_t start = millis(); this->scheduler.call(); for (Component *component : this->looping_components_) { - component->call(); + { + WarnIfComponentBlockingGuard guard{component}; + component->call(); + } new_app_state |= component->get_component_state(); this->app_state_ |= new_app_state; this->feed_wdt(); } this->app_state_ = new_app_state; - const uint32_t end = millis(); - if (end - start > 200) { - ESP_LOGV(TAG, "A component took a long time in a loop() cycle (%.2f s).", (end - start) / 1e3f); - ESP_LOGV(TAG, "Components should block for at most 20-30ms in loop()."); - } - const uint32_t now = millis(); if (HighFrequencyLoopRequester::is_high_frequency()) { diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index f6b15b1977..e4535e77d9 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -92,8 +92,13 @@ void Component::call() { break; } } +const char *Component::get_component_source() const { + if (this->component_source_ == nullptr) + return ""; + return this->component_source_; +} void Component::mark_failed() { - ESP_LOGE(TAG, "Component was marked as failed."); + ESP_LOGE(TAG, "Component %s was marked as failed.", this->get_component_source()); this->component_state_ &= ~COMPONENT_STATE_MASK; this->component_state_ |= COMPONENT_STATE_FAILED; this->status_set_error(); @@ -190,4 +195,18 @@ uint32_t Nameable::get_object_id_hash() { return this->object_id_hash_; } bool Nameable::is_disabled_by_default() const { return this->disabled_by_default_; } void Nameable::set_disabled_by_default(bool disabled_by_default) { this->disabled_by_default_ = disabled_by_default; } +WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component) { + component_ = component; + started_ = millis(); +} +WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() { + uint32_t now = millis(); + if (now - started_ > 50) { + const char *src = component_ == nullptr ? "" : component_->get_component_source(); + ESP_LOGV(TAG, "Component %s took a long time for an operation (%.2f s).", src, (now - started_) / 1e3f); + ESP_LOGV(TAG, "Components should block for at most 20-30ms."); + ; + } +} + } // namespace esphome diff --git a/esphome/core/component.h b/esphome/core/component.h index a4a945ef2a..b9a22c240e 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -130,6 +130,17 @@ class Component { bool has_overridden_loop() const; + /** Set where this component was loaded from for some debug messages. + * + * This is set by the ESPHome core, and should not be called manually. + */ + void set_component_source(const char *source) { component_source_ = source; } + /** Get the integration where this component was declared as a string. + * + * Returns "" if source not set + */ + const char *get_component_source() const; + protected: virtual void call_loop(); virtual void call_setup(); @@ -201,6 +212,7 @@ class Component { uint32_t component_state_{0x0000}; ///< State of this component. float setup_priority_override_{NAN}; + const char *component_source_ = nullptr; }; /** This class simplifies creating components that periodically check a state. @@ -276,4 +288,14 @@ class Nameable { bool disabled_by_default_{false}; }; +class WarnIfComponentBlockingGuard { + public: + WarnIfComponentBlockingGuard(Component *component); + ~WarnIfComponentBlockingGuard(); + + protected: + uint32_t started_; + Component *component_; +}; + } // namespace esphome diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 410c68052f..60e0d4e9bd 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -155,7 +155,10 @@ void ICACHE_RAM_ATTR HOT Scheduler::call() { // Warning: During f(), a lot of stuff can happen, including: // - timeouts/intervals get added, potentially invalidating vector pointers // - timeouts/intervals get cancelled - item->f(); + { + WarnIfComponentBlockingGuard guard{item->component}; + item->f(); + } } { diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 1d66eabf6c..7912e4ae06 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -1,3 +1,5 @@ +import logging + from esphome.const import ( CONF_INVERTED, CONF_MODE, @@ -15,6 +17,9 @@ from esphome.cpp_types import App, GPIOPin from esphome.util import Registry, RegistryEntry +_LOGGER = logging.getLogger(__name__) + + async def gpio_pin_expression(conf): """Generate an expression for the given pin option. @@ -42,6 +47,8 @@ async def register_component(var, config): :param var: The variable representing the component. :param config: The configuration for the component. """ + import inspect + id_ = str(var.base) if id_ not in CORE.component_ids: raise ValueError( @@ -54,6 +61,32 @@ async def register_component(var, config): add(var.set_setup_priority(config[CONF_SETUP_PRIORITY])) if CONF_UPDATE_INTERVAL in config: add(var.set_update_interval(config[CONF_UPDATE_INTERVAL])) + + # Set component source by inspecting the stack and getting the callee module + # https://stackoverflow.com/a/1095621 + name = None + try: + for frm in inspect.stack()[1:]: + mod = inspect.getmodule(frm[0]) + if mod is None: + continue + name = mod.__name__ + if name.startswith("esphome.components."): + name = name[len("esphome.components.") :] + break + if name == "esphome.automation": + name = "automation" + # continue looking further up in stack in case we find a better one + if name == "esphome.coroutine": + # Only works for async-await coroutine syntax + break + except (KeyError, AttributeError, IndexError) as e: + _LOGGER.warning( + "Error while finding name of component, please report this", exc_info=e + ) + if name is not None: + add(var.set_component_source(name)) + add(App.register_component(var)) return var diff --git a/tests/unit_tests/test_core.py b/tests/unit_tests/test_core.py index 9e4ad3d79d..37b4d6db57 100644 --- a/tests/unit_tests/test_core.py +++ b/tests/unit_tests/test_core.py @@ -473,7 +473,11 @@ 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__", + core.Library(name="libbar", version=None, repository="file:///test"), + False, + ), ("__eq__", 1000, NotImplemented), ("__eq__", "1000", NotImplemented), ("__eq__", True, NotImplemented), diff --git a/tests/unit_tests/test_cpp_helpers.py b/tests/unit_tests/test_cpp_helpers.py index 3e317589a9..ae7a61e01f 100644 --- a/tests/unit_tests/test_cpp_helpers.py +++ b/tests/unit_tests/test_cpp_helpers.py @@ -38,7 +38,7 @@ async def test_register_component(monkeypatch): actual = await ch.register_component(var, {}) assert actual is var - add_mock.assert_called_once() + assert add_mock.call_count == 2 app_mock.register_component.assert_called_with(var) assert core_mock.component_ids == [] @@ -77,6 +77,6 @@ async def test_register_component__with_setup_priority(monkeypatch): assert actual is var add_mock.assert_called() - assert add_mock.call_count == 3 + assert add_mock.call_count == 4 app_mock.register_component.assert_called_with(var) assert core_mock.component_ids == [] From ce29a3b07a71a7ba822c2c8cc09905a2c3784a31 Mon Sep 17 00:00:00 2001 From: puuu Date: Tue, 24 Aug 2021 10:18:40 +0900 Subject: [PATCH 1225/1841] mqtt_light: remove legacy API config that is not compatible with HA 2021.8 (#2183) --- esphome/components/mqtt/mqtt_light.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index be662867cf..b702e6e425 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -54,12 +54,6 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover // legacy API if (traits.supports_color_capability(ColorCapability::BRIGHTNESS)) root["brightness"] = true; - if (traits.supports_color_capability(ColorCapability::RGB)) - root["rgb"] = true; - if (traits.supports_color_capability(ColorCapability::COLOR_TEMPERATURE)) - root["color_temp"] = true; - if (traits.supports_color_capability(ColorCapability::WHITE)) - root["white_value"] = true; if (this->state_->supports_effects()) { root["effect"] = true; From eff626248f8be84601b3120514d972d3f27670a6 Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Mon, 23 Aug 2021 20:20:39 -0500 Subject: [PATCH 1226/1841] Tuya fan component uses enum datapoint type for speed instead of integer (#2182) Co-authored-by: Chris Nussbaum --- esphome/components/tuya/fan/tuya_fan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp index 8738b7f4a0..e9f8ce8e96 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -80,7 +80,7 @@ void TuyaFan::write_state() { } if (this->speed_id_.has_value()) { ESP_LOGV(TAG, "Setting speed: %d", this->fan_->speed); - this->parent_->set_integer_datapoint_value(*this->speed_id_, this->fan_->speed - 1); + this->parent_->set_enum_datapoint_value(*this->speed_id_, this->fan_->speed - 1); } } From e2640c8368684fef71f343c15498bf15198ddf3f Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Mon, 23 Aug 2021 18:26:59 -0700 Subject: [PATCH 1227/1841] Fix template select lambda (#2198) --- esphome/components/template/select/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/template/select/__init__.py b/esphome/components/template/select/__init__.py index 4044a407f3..3a707628a8 100644 --- a/esphome/components/template/select/__init__.py +++ b/esphome/components/template/select/__init__.py @@ -55,7 +55,7 @@ async def to_code(config): if CONF_LAMBDA in config: template_ = await cg.process_lambda( - config[CONF_LAMBDA], [], return_type=cg.optional.template(str) + config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string) ) cg.add(var.set_template(template_)) From ed68a0e7734e8ff2841ac6ffdf438a35960ae774 Mon Sep 17 00:00:00 2001 From: mtl010957 Date: Mon, 23 Aug 2021 21:38:59 -0400 Subject: [PATCH 1228/1841] Internally all temperature units are Celsius so just send it directly (#1840) --- esphome/components/mqtt/mqtt_climate.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 5809b6616c..be9dbb0a08 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -60,6 +60,8 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC root["max_temp"] = traits.get_visual_max_temperature(); // temp_step root["temp_step"] = traits.get_visual_temperature_step(); + // temperature units are always coerced to Celsius internally + root["temp_unit"] = "C"; if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { // away_mode_command_topic From 63d87b17aa892413dd3e336745b3f446e69c4ede Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 15:39:57 +1200 Subject: [PATCH 1229/1841] Fix pypi download url (#2177) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 44a5965887..967eadd70f 100755 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ PYPI_URL = "https://pypi.python.org/pypi/{}".format(PROJECT_PACKAGE_NAME) GITHUB_PATH = "{}/{}".format(PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY) GITHUB_URL = "https://github.com/{}".format(GITHUB_PATH) -DOWNLOAD_URL = "{}/archive/v{}.zip".format(GITHUB_URL, const.__version__) +DOWNLOAD_URL = "{}/archive/{}.zip".format(GITHUB_URL, const.__version__) here = os.path.abspath(os.path.dirname(__file__)) From 3869e56521724d0df7eb2b2b6bcc141eb62bb0d1 Mon Sep 17 00:00:00 2001 From: puuu Date: Sat, 21 Aug 2021 19:26:24 +0900 Subject: [PATCH 1230/1841] Light: include ON_OFF capability to BRIGHTNESS ColorMode (#2186) --- esphome/components/light/color_mode.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/color_mode.h b/esphome/components/light/color_mode.h index 0f5b7b4b93..77c377d39e 100644 --- a/esphome/components/light/color_mode.h +++ b/esphome/components/light/color_mode.h @@ -52,7 +52,7 @@ enum class ColorMode : uint8_t { /// Only on/off control. ON_OFF = (uint8_t) ColorCapability::ON_OFF, /// Dimmable light. - BRIGHTNESS = (uint8_t) ColorCapability::BRIGHTNESS, + BRIGHTNESS = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS), /// White output only (use only if the light also has another color mode such as RGB). WHITE = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::WHITE), /// Controllable color temperature output. From ad953f02d11c4b5faa85706a75366324c2b04293 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 23 Aug 2021 10:44:24 +0200 Subject: [PATCH 1231/1841] Fix addressable light control without transitions & effects with transitions (#2187) --- esphome/components/light/addressable_light.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index 12eab6a685..80a1e14ffd 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -46,9 +46,14 @@ void AddressableLight::write_state(LightState *state) { // don't use LightState helper, gamma correction+brightness is handled by ESPColorView this->all() = esp_color_from_light_color_values(val); + this->schedule_show(); } void AddressableLightTransformer::start() { + // don't try to transition over running effects. + if (this->light_.is_effect_active()) + return; + auto end_values = this->target_values_; this->target_color_ = esp_color_from_light_color_values(end_values); From 9de40c26ebb6f4d312bbbffcb16f1c86fb268a23 Mon Sep 17 00:00:00 2001 From: puuu Date: Tue, 24 Aug 2021 10:18:40 +0900 Subject: [PATCH 1232/1841] mqtt_light: remove legacy API config that is not compatible with HA 2021.8 (#2183) --- esphome/components/mqtt/mqtt_light.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index be662867cf..b702e6e425 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -54,12 +54,6 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover // legacy API if (traits.supports_color_capability(ColorCapability::BRIGHTNESS)) root["brightness"] = true; - if (traits.supports_color_capability(ColorCapability::RGB)) - root["rgb"] = true; - if (traits.supports_color_capability(ColorCapability::COLOR_TEMPERATURE)) - root["color_temp"] = true; - if (traits.supports_color_capability(ColorCapability::WHITE)) - root["white_value"] = true; if (this->state_->supports_effects()) { root["effect"] = true; From 481e0e98f877e186a1dc9c5790c0d512ddd2cec2 Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Mon, 23 Aug 2021 20:20:39 -0500 Subject: [PATCH 1233/1841] Tuya fan component uses enum datapoint type for speed instead of integer (#2182) Co-authored-by: Chris Nussbaum --- esphome/components/tuya/fan/tuya_fan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp index 8738b7f4a0..e9f8ce8e96 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -80,7 +80,7 @@ void TuyaFan::write_state() { } if (this->speed_id_.has_value()) { ESP_LOGV(TAG, "Setting speed: %d", this->fan_->speed); - this->parent_->set_integer_datapoint_value(*this->speed_id_, this->fan_->speed - 1); + this->parent_->set_enum_datapoint_value(*this->speed_id_, this->fan_->speed - 1); } } From 44f8dcfb6e602748a3a2619c84ac9f7cf1177d75 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Mon, 23 Aug 2021 18:26:59 -0700 Subject: [PATCH 1234/1841] Fix template select lambda (#2198) --- esphome/components/template/select/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/template/select/__init__.py b/esphome/components/template/select/__init__.py index 4044a407f3..3a707628a8 100644 --- a/esphome/components/template/select/__init__.py +++ b/esphome/components/template/select/__init__.py @@ -55,7 +55,7 @@ async def to_code(config): if CONF_LAMBDA in config: template_ = await cg.process_lambda( - config[CONF_LAMBDA], [], return_type=cg.optional.template(str) + config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string) ) cg.add(var.set_template(template_)) From e7404183a026758ca55b2228eecc948b505bdb42 Mon Sep 17 00:00:00 2001 From: mtl010957 Date: Mon, 23 Aug 2021 21:38:59 -0400 Subject: [PATCH 1235/1841] Internally all temperature units are Celsius so just send it directly (#1840) --- esphome/components/mqtt/mqtt_climate.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 5809b6616c..be9dbb0a08 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -60,6 +60,8 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC root["max_temp"] = traits.get_visual_max_temperature(); // temp_step root["temp_step"] = traits.get_visual_temperature_step(); + // temperature units are always coerced to Celsius internally + root["temp_unit"] = "C"; if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { // away_mode_command_topic From 0a4837c1f02931387ae624c1420491e8905e2037 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 24 Aug 2021 14:26:28 +1200 Subject: [PATCH 1236/1841] Bump version to 2021.8.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index e25ba7e046..823cbd5924 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.8.0" +__version__ = "2021.8.1" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 565473c90c4eaaeaec22fbccf34938c788edd7f0 Mon Sep 17 00:00:00 2001 From: Stephan Peijnik-Steinwender Date: Tue, 24 Aug 2021 12:57:53 +0200 Subject: [PATCH 1237/1841] ST7789V: Make backlight_pin optional (#2180) --- esphome/components/st7789v/display.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/components/st7789v/display.py b/esphome/components/st7789v/display.py index a053d00ea2..7b38b1d2c5 100644 --- a/esphome/components/st7789v/display.py +++ b/esphome/components/st7789v/display.py @@ -29,7 +29,7 @@ CONFIG_SCHEMA = ( cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_CS_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_BACKLIGHT_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_BACKLIGHT_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, } ) @@ -49,8 +49,9 @@ async def to_code(config): reset = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) cg.add(var.set_reset_pin(reset)) - bl = await cg.gpio_pin_expression(config[CONF_BACKLIGHT_PIN]) - cg.add(var.set_backlight_pin(bl)) + if CONF_BACKLIGHT_PIN in config: + bl = await cg.gpio_pin_expression(config[CONF_BACKLIGHT_PIN]) + cg.add(var.set_backlight_pin(bl)) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( From 39cd2838dfed9fcd3af5995f0c19bc0c444328b8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 25 Aug 2021 19:38:51 +1200 Subject: [PATCH 1238/1841] Revert "Light: include ON_OFF capability to BRIGHTNESS ColorMode (#2186)" (#2202) This reverts commit b0fa317302df4f6adeab56034e0b6099f58aa4c4. --- esphome/components/light/color_mode.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/color_mode.h b/esphome/components/light/color_mode.h index 77c377d39e..0f5b7b4b93 100644 --- a/esphome/components/light/color_mode.h +++ b/esphome/components/light/color_mode.h @@ -52,7 +52,7 @@ enum class ColorMode : uint8_t { /// Only on/off control. ON_OFF = (uint8_t) ColorCapability::ON_OFF, /// Dimmable light. - BRIGHTNESS = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS), + BRIGHTNESS = (uint8_t) ColorCapability::BRIGHTNESS, /// White output only (use only if the light also has another color mode such as RGB). WHITE = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::WHITE), /// Controllable color temperature output. From 877a5fda41f1b47eb3fd3437278b5c7e9c43256a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 25 Aug 2021 19:38:51 +1200 Subject: [PATCH 1239/1841] Revert "Light: include ON_OFF capability to BRIGHTNESS ColorMode (#2186)" (#2202) This reverts commit b0fa317302df4f6adeab56034e0b6099f58aa4c4. --- esphome/components/light/color_mode.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/color_mode.h b/esphome/components/light/color_mode.h index 77c377d39e..0f5b7b4b93 100644 --- a/esphome/components/light/color_mode.h +++ b/esphome/components/light/color_mode.h @@ -52,7 +52,7 @@ enum class ColorMode : uint8_t { /// Only on/off control. ON_OFF = (uint8_t) ColorCapability::ON_OFF, /// Dimmable light. - BRIGHTNESS = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS), + BRIGHTNESS = (uint8_t) ColorCapability::BRIGHTNESS, /// White output only (use only if the light also has another color mode such as RGB). WHITE = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::WHITE), /// Controllable color temperature output. From 4937af0cd9311f1e45c149395c5b21ebd8e64d5f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 25 Aug 2021 19:46:55 +1200 Subject: [PATCH 1240/1841] Bump version to 2021.8.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 823cbd5924..ce3b95b731 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.8.1" +__version__ = "2021.8.2" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 3be56fd50297fc461c2d5f7a9b19ccc850a016c8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 26 Aug 2021 09:27:00 +1200 Subject: [PATCH 1241/1841] Fix SDM energy units to be KILO... (#2206) --- esphome/components/sdm_meter/sensor.py | 12 ++++++------ esphome/const.py | 2 ++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/esphome/components/sdm_meter/sensor.py b/esphome/components/sdm_meter/sensor.py index c3af47a664..ffb88af5ac 100644 --- a/esphome/components/sdm_meter/sensor.py +++ b/esphome/components/sdm_meter/sensor.py @@ -28,12 +28,12 @@ from esphome.const import ( UNIT_AMPERE, UNIT_DEGREES, UNIT_HERTZ, + UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + UNIT_KILOWATT_HOURS, UNIT_VOLT, UNIT_VOLT_AMPS, UNIT_VOLT_AMPS_REACTIVE, - UNIT_VOLT_AMPS_REACTIVE_HOURS, UNIT_WATT, - UNIT_WATT_HOURS, ) AUTO_LOAD = ["modbus"] @@ -101,28 +101,28 @@ CONFIG_SCHEMA = ( state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_IMPORT_ACTIVE_ENERGY): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT_HOURS, + unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_MEASUREMENT, last_reset_type=LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_EXPORT_ACTIVE_ENERGY): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT_HOURS, + unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_MEASUREMENT, last_reset_type=LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_IMPORT_REACTIVE_ENERGY): sensor.sensor_schema( - unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE_HOURS, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_MEASUREMENT, last_reset_type=LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_EXPORT_REACTIVE_ENERGY): sensor.sensor_schema( - unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE_HOURS, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_MEASUREMENT, diff --git a/esphome/const.py b/esphome/const.py index d8134bc45f..932e7e8c61 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -768,6 +768,8 @@ UNIT_KELVIN = "K" UNIT_KILOGRAM = "kg" UNIT_KILOMETER = "km" UNIT_KILOMETER_PER_HOUR = "km/h" +UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kVArh" +UNIT_KILOWATT_HOURS = "kWh" UNIT_LUX = "lx" UNIT_METER = "m" UNIT_METER_PER_SECOND_SQUARED = "m/s²" From de871862a84a9c56627e00ddcb281b6e31216b43 Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Thu, 26 Aug 2021 08:25:24 +1000 Subject: [PATCH 1242/1841] Optionally set direction on fan.turn_on action (#2171) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/fan/__init__.py | 13 +++++++++++++ esphome/components/fan/automation.h | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 9db2e9ed12..27dee3271f 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -18,6 +18,7 @@ from esphome.const import ( CONF_ON_TURN_OFF, CONF_ON_TURN_ON, CONF_TRIGGER_ID, + CONF_DIRECTION, ) from esphome.core import CORE, coroutine_with_priority @@ -27,6 +28,12 @@ fan_ns = cg.esphome_ns.namespace("fan") FanState = fan_ns.class_("FanState", cg.Nameable, cg.Component) MakeFan = cg.Application.struct("MakeFan") +FanDirection = fan_ns.enum("FanDirection") +FAN_DIRECTION_ENUM = { + "FORWARD": FanDirection.FAN_DIRECTION_FORWARD, + "REVERSE": FanDirection.FAN_DIRECTION_REVERSE, +} + # Actions TurnOnAction = fan_ns.class_("TurnOnAction", automation.Action) TurnOffAction = fan_ns.class_("TurnOffAction", automation.Action) @@ -143,6 +150,9 @@ async def fan_turn_off_to_code(config, action_id, template_arg, args): cv.Required(CONF_ID): cv.use_id(FanState), cv.Optional(CONF_OSCILLATING): cv.templatable(cv.boolean), cv.Optional(CONF_SPEED): cv.templatable(cv.int_range(1)), + cv.Optional(CONF_DIRECTION): cv.templatable( + cv.enum(FAN_DIRECTION_ENUM, upper=True) + ), } ), ) @@ -155,6 +165,9 @@ async def fan_turn_on_to_code(config, action_id, template_arg, args): if CONF_SPEED in config: template_ = await cg.templatable(config[CONF_SPEED], args, int) cg.add(var.set_speed(template_)) + if CONF_DIRECTION in config: + template_ = await cg.templatable(config[CONF_DIRECTION], args, FanDirection) + cg.add(var.set_direction(template_)) return var diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index fbfc71c720..29a8e8d992 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -13,6 +13,7 @@ template class TurnOnAction : public Action { TEMPLATABLE_VALUE(bool, oscillating) TEMPLATABLE_VALUE(int, speed) + TEMPLATABLE_VALUE(FanDirection, direction) void play(Ts... x) override { auto call = this->state_->turn_on(); @@ -22,6 +23,9 @@ template class TurnOnAction : public Action { if (this->speed_.has_value()) { call.set_speed(this->speed_.value(x...)); } + if (this->direction_.has_value()) { + call.set_direction(this->direction_.value(x...)); + } call.perform(); } From 94b28102f56d7bee37f87b0282a76dbd8c918b51 Mon Sep 17 00:00:00 2001 From: marsjan155 Date: Thu, 26 Aug 2021 04:33:03 +0200 Subject: [PATCH 1243/1841] Add st7920 display, (#1440) Co-authored-by: Oxan van Leeuwen Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/st7920/__init__.py | 0 esphome/components/st7920/display.py | 42 ++++++++ esphome/components/st7920/st7920.cpp | 146 ++++++++++++++++++++++++++ esphome/components/st7920/st7920.h | 50 +++++++++ tests/test1.yaml | 8 ++ 6 files changed, 247 insertions(+) create mode 100644 esphome/components/st7920/__init__.py create mode 100644 esphome/components/st7920/display.py create mode 100644 esphome/components/st7920/st7920.cpp create mode 100644 esphome/components/st7920/st7920.h diff --git a/CODEOWNERS b/CODEOWNERS index 1298d4d43d..eebaf02671 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -129,6 +129,7 @@ esphome/components/ssd1351_base/* @kbx81 esphome/components/ssd1351_spi/* @kbx81 esphome/components/st7735/* @SenexCrenshaw esphome/components/st7789v/* @kbx81 +esphome/components/st7920/* @marsjan155 esphome/components/substitutions/* @esphome/core esphome/components/sun/* @OttoWinter esphome/components/switch/* @esphome/core diff --git a/esphome/components/st7920/__init__.py b/esphome/components/st7920/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/st7920/display.py b/esphome/components/st7920/display.py new file mode 100644 index 0000000000..9b544fa644 --- /dev/null +++ b/esphome/components/st7920/display.py @@ -0,0 +1,42 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import display, spi +from esphome.const import CONF_ID, CONF_LAMBDA, CONF_WIDTH, CONF_HEIGHT + +AUTO_LOAD = ["display"] +CODEOWNERS = ["@marsjan155"] +DEPENDENCIES = ["spi"] + +st7920_ns = cg.esphome_ns.namespace("st7920") +ST7920 = st7920_ns.class_( + "ST7920", cg.PollingComponent, display.DisplayBuffer, spi.SPIDevice +) +ST7920Ref = ST7920.operator("ref") + +CONFIG_SCHEMA = ( + display.FULL_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ST7920), + cv.Required(CONF_WIDTH): cv.int_, + cv.Required(CONF_HEIGHT): cv.int_, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(spi.spi_device_schema()) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await spi.register_spi_device(var, config) + + if CONF_LAMBDA in config: + lambda_ = await cg.process_lambda( + config[CONF_LAMBDA], [(ST7920Ref, "it")], return_type=cg.void + ) + cg.add(var.set_writer(lambda_)) + cg.add(var.set_width(config[CONF_WIDTH])) + cg.add(var.set_height(config[CONF_HEIGHT])) + + await display.register_display(var, config) diff --git a/esphome/components/st7920/st7920.cpp b/esphome/components/st7920/st7920.cpp new file mode 100644 index 0000000000..d985b0a426 --- /dev/null +++ b/esphome/components/st7920/st7920.cpp @@ -0,0 +1,146 @@ +#include "st7920.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" +#include "esphome/components/display/display_buffer.h" + +namespace esphome { +namespace st7920 { + +static const char *const TAG = "st7920"; + +// ST7920 COMMANDS +static const uint8_t LCD_DATA = 0xFA; +static const uint8_t LCD_COMMAND = 0xF8; +static const uint8_t LCD_CLS = 0x01; +static const uint8_t LCD_HOME = 0x02; +static const uint8_t LCD_ADDRINC = 0x06; +static const uint8_t LCD_DISPLAYON = 0x0C; +static const uint8_t LCD_DISPLAYOFF = 0x08; +static const uint8_t LCD_CURSORON = 0x0E; +static const uint8_t LCD_CURSORBLINK = 0x0F; +static const uint8_t LCD_BASIC = 0x30; +static const uint8_t LCD_GFXMODE = 0x36; +static const uint8_t LCD_EXTEND = 0x34; +static const uint8_t LCD_TXTMODE = 0x34; +static const uint8_t LCD_STANDBY = 0x01; +static const uint8_t LCD_SCROLL = 0x03; +static const uint8_t LCD_SCROLLADDR = 0x40; +static const uint8_t LCD_ADDR = 0x80; +static const uint8_t LCD_LINE0 = 0x80; +static const uint8_t LCD_LINE1 = 0x90; +static const uint8_t LCD_LINE2 = 0x88; +static const uint8_t LCD_LINE3 = 0x98; + +void ST7920::setup() { + ESP_LOGCONFIG(TAG, "Setting up ST7920..."); + this->dump_config(); + this->spi_setup(); + this->init_internal_(this->get_buffer_length_()); + display_init_(); +} + +void ST7920::command_(uint8_t value) { + this->enable(); + this->send_(LCD_COMMAND, value); + this->disable(); +} + +void ST7920::data_(uint8_t value) { + this->enable(); + this->send_(LCD_DATA, value); + this->disable(); +} + +void ST7920::send_(uint8_t type, uint8_t value) { + this->write_byte(type); + this->write_byte(value & 0xF0); + this->write_byte(value << 4); +} + +void ST7920::goto_xy_(uint16_t x, uint16_t y) { + if (y >= 32 && y < 64) { + y -= 32; + x += 8; + } else if (y >= 64 && y < 64 + 32) { + y -= 32; + x += 0; + } else if (y >= 64 + 32 && y < 64 + 64) { + y -= 64; + x += 8; + } + this->command_(LCD_ADDR | y); // 6-bit (0..63) + this->command_(LCD_ADDR | x); // 4-bit (0..15) +} + +void HOT ST7920::write_display_data() { + uint8_t i, j, b; + for (j = 0; j < this->get_height_internal() / 2; j++) { + this->goto_xy_(0, j); + this->enable(); + for (i = 0; i < 16; i++) { // 16 bytes from line #0+ + b = this->buffer_[i + j * 16]; + this->send_(LCD_DATA, b); + } + for (i = 0; i < 16; i++) { // 16 bytes from line #32+ + b = this->buffer_[i + (j + 32) * 16]; + this->send_(LCD_DATA, b); + } + this->disable(); + App.feed_wdt(); + } +} + +void ST7920::fill(Color color) { memset(this->buffer_, color.is_on() ? 0xFF : 0x00, this->get_buffer_length_()); } + +void ST7920::dump_config() { + LOG_DISPLAY("", "ST7920", this); + LOG_PIN(" CS Pin: ", this->cs_); + ESP_LOGCONFIG(TAG, " Height: %d", this->height_); + ESP_LOGCONFIG(TAG, " Width: %d", this->width_); +} + +float ST7920::get_setup_priority() const { return setup_priority::PROCESSOR; } + +void ST7920::update() { + this->clear(); + if (this->writer_local_.has_value()) // call lambda function if available + (*this->writer_local_)(*this); + this->write_display_data(); +} + +int ST7920::get_width_internal() { return this->width_; } + +int ST7920::get_height_internal() { return this->height_; } + +size_t ST7920::get_buffer_length_() { + return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u; +} + +void HOT ST7920::draw_absolute_pixel_internal(int x, int y, Color color) { + if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) { + ESP_LOGW(TAG, "Position out of area: %dx%d", x, y); + return; + } + int width = this->get_width_internal() / 8u; + if (color.is_on()) { + this->buffer_[y * width + x / 8] |= (0x80 >> (x & 7)); + } else { + this->buffer_[y * width + x / 8] &= ~(0x80 >> (x & 7)); + } +} + +void ST7920::display_init_() { + ESP_LOGD(TAG, "Initializing display..."); + this->command_(LCD_BASIC); // 8bit mode + this->command_(LCD_BASIC); // 8bit mode + this->command_(LCD_CLS); // clear screen + delay(12); // >10 ms delay + this->command_(LCD_ADDRINC); // cursor increment right no shift + this->command_(LCD_DISPLAYON); // D=1, C=0, B=0 + this->command_(LCD_EXTEND); // LCD_EXTEND); + this->command_(LCD_GFXMODE); // LCD_GFXMODE); + this->write_display_data(); +} + +} // namespace st7920 +} // namespace esphome diff --git a/esphome/components/st7920/st7920.h b/esphome/components/st7920/st7920.h new file mode 100644 index 0000000000..d0258d922c --- /dev/null +++ b/esphome/components/st7920/st7920.h @@ -0,0 +1,50 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/display/display_buffer.h" +#include "esphome/components/spi/spi.h" + +namespace esphome { +namespace st7920 { + +class ST7920; + +using st7920_writer_t = std::function; + +class ST7920 : public PollingComponent, + public display::DisplayBuffer, + public spi::SPIDevice { + public: + void set_writer(st7920_writer_t &&writer) { this->writer_local_ = writer; } + void set_height(uint16_t height) { this->height_ = height; } + void set_width(uint16_t width) { this->width_ = width; } + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + void fill(Color color) override; + void write_display_data(); + + protected: + void draw_absolute_pixel_internal(int x, int y, Color color) override; + int get_height_internal() override; + int get_width_internal() override; + size_t get_buffer_length_(); + void display_init_(); + void command_(uint8_t value); + void data_(uint8_t value); + void send_(uint8_t type, uint8_t value); + void goto_xy_(uint16_t x, uint16_t y); + void start_transaction_(); + void end_transaction_(); + + int16_t width_ = 128, height_ = 64; + optional writer_local_{}; +}; + +} // namespace st7920 +} // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index bcf5f932a8..a3f7a97281 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2038,6 +2038,14 @@ display: backlight_pin: GPIO4 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: st7920 + width: 128 + height: 64 + cs_pin: + number: GPIO23 + inverted: true + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: st7735 model: 'INITR_BLACKTAB' cs_pin: GPIO5 From b955527f6ce3e6a7d8066112beb03b4b2e1b1e87 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 26 Aug 2021 15:34:39 +1200 Subject: [PATCH 1244/1841] Fix css/js file loading for webserver when esphome not executed form config directory (#2207) --- esphome/components/web_server/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index a181f83c64..ca3a60f43f 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -13,7 +13,7 @@ from esphome.const import ( CONF_USERNAME, CONF_PASSWORD, ) -from esphome.core import coroutine_with_priority +from esphome.core import CORE, coroutine_with_priority AUTO_LOAD = ["json", "web_server_base"] @@ -61,9 +61,11 @@ async def to_code(config): cg.add(var.set_password(config[CONF_AUTH][CONF_PASSWORD])) if CONF_CSS_INCLUDE in config: cg.add_define("WEBSERVER_CSS_INCLUDE") - with open(config[CONF_CSS_INCLUDE], "r") as myfile: + path = CORE.relative_config_path(config[CONF_CSS_INCLUDE]) + with open(path, "r") as myfile: cg.add(var.set_css_include(myfile.read())) if CONF_JS_INCLUDE in config: cg.add_define("WEBSERVER_JS_INCLUDE") - with open(config[CONF_JS_INCLUDE], "r") as myfile: + path = CORE.relative_config_path(config[CONF_JS_INCLUDE]) + with open(path, "r") as myfile: cg.add(var.set_js_include(myfile.read())) From b5de43b2255e95d7e1348012755ed7b799e44e58 Mon Sep 17 00:00:00 2001 From: Alessandro Campolo Date: Sun, 29 Aug 2021 23:07:06 +0200 Subject: [PATCH 1245/1841] cs_pin made optional for ili9341 (#2219) --- esphome/components/ili9341/display.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/ili9341/display.py b/esphome/components/ili9341/display.py index 450a958c56..157e8212bd 100644 --- a/esphome/components/ili9341/display.py +++ b/esphome/components/ili9341/display.py @@ -42,7 +42,7 @@ CONFIG_SCHEMA = cv.All( } ) .extend(cv.polling_component_schema("1s")) - .extend(spi.spi_device_schema()), + .extend(spi.spi_device_schema(False)), cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), ) From 56225701f9cdb13b052763190031f255fb909274 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Mon, 30 Aug 2021 09:27:38 +1200 Subject: [PATCH 1246/1841] Fix Packages when using MQTT (#2210) --- esphome/config_validation.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 3aebca81b8..61ef7d2f9f 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1457,7 +1457,11 @@ class OnlyWith(Optional): if self._component in CORE.raw_config or ( CONF_PACKAGES in CORE.raw_config and self._component - in {list(x.keys())[0] for x in CORE.raw_config[CONF_PACKAGES].values()} + in [ + k + for package in CORE.raw_config[CONF_PACKAGES].values() + for k in package.keys() + ] ): return self._default return vol.UNDEFINED From fac49896dfb372e939f4bccade9a77be888a7faa Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 29 Aug 2021 23:41:05 +0200 Subject: [PATCH 1247/1841] Update known boards (#2190) --- esphome/boards.py | 277 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) diff --git a/esphome/boards.py b/esphome/boards.py index 220d440a37..ba6fe889ea 100644 --- a/esphome/boards.py +++ b/esphome/boards.py @@ -55,6 +55,7 @@ ESP8266_BOARD_PINS = { "espectro": {"LED": 15, "BUTTON": 2}, "espino": {"LED": 2, "LED_RED": 2, "LED_GREEN": 4, "LED_BLUE": 5, "BUTTON": 0}, "espinotee": {"LED": 16}, + "espmxdevkit": {}, "espresso_lite_v1": {"LED": 16}, "espresso_lite_v2": {"LED": 2}, "gen4iod": {}, @@ -105,6 +106,10 @@ ESP8266_BOARD_PINS = { }, "phoenix_v1": {"LED": 16}, "phoenix_v2": {"LED": 2}, + "sonoff_basic": {}, + "sonoff_s20": {}, + "sonoff_sv": {}, + "sonoff_th": {}, "sparkfunBlynk": "thing", "thing": {"LED": 5, "SDA": 2, "SCL": 14}, "thingdev": "thing", @@ -166,6 +171,7 @@ ESP8266_FLASH_SIZES = { "espectro": FLASH_SIZE_4_MB, "espino": FLASH_SIZE_4_MB, "espinotee": FLASH_SIZE_4_MB, + "espmxdevkit": FLASH_SIZE_1_MB, "espresso_lite_v1": FLASH_SIZE_4_MB, "espresso_lite_v2": FLASH_SIZE_4_MB, "gen4iod": FLASH_SIZE_512_KB, @@ -178,6 +184,10 @@ ESP8266_FLASH_SIZES = { "oak": FLASH_SIZE_4_MB, "phoenix_v1": FLASH_SIZE_4_MB, "phoenix_v2": FLASH_SIZE_4_MB, + "sonoff_basic": FLASH_SIZE_1_MB, + "sonoff_s20": FLASH_SIZE_1_MB, + "sonoff_sv": FLASH_SIZE_1_MB, + "sonoff_th": FLASH_SIZE_1_MB, "sparkfunBlynk": FLASH_SIZE_4_MB, "thing": FLASH_SIZE_512_KB, "thingdev": FLASH_SIZE_512_KB, @@ -291,6 +301,7 @@ ESP32_BOARD_PINS = { "SW2": 2, "SW3": 0, }, + "az-delivery-devkit-v4": {}, "bpi-bit": { "BUTTON_A": 35, "BUTTON_B": 27, @@ -320,6 +331,8 @@ ESP32_BOARD_PINS = { "RGB_LED": 4, "TEMPERATURE_SENSOR": 34, }, + "briki_abc_esp32": {}, + "briki_mbc-wb_esp32": {}, "d-duino-32": { "D1": 5, "D10": 1, @@ -380,11 +393,58 @@ ESP32_BOARD_PINS = { "esp32cam": {}, "esp32dev": {}, "esp32doit-devkit-v1": {"LED": 2}, + "esp32doit-espduino": {"TX0": 1, "RX0": 3, "CMD": 11, "CLK": 6, "SD0": 7, "SD1": 8}, "esp32thing": {"BUTTON": 0, "LED": 5, "SS": 2}, + "esp32thing_plus": { + "SDA": 23, + "SCL": 22, + "SS": 33, + "MOSI": 18, + "MISO": 19, + "SCK": 5, + "A0": 26, + "A1": 25, + "A2": 34, + "A3": 39, + "A4": 36, + "A5": 4, + "A6": 14, + "A7": 32, + "A8": 15, + "A9": 33, + "A10": 27, + "A11": 12, + "A12": 13, + }, "esp32vn-iot-uno": {}, "espea32": {"BUTTON": 0, "LED": 5}, "espectro32": {"LED": 15, "SD_SS": 33}, "espino32": {"BUTTON": 0, "LED": 16}, + "etboard": { + "LED_BUILTIN": 5, + "TX": 34, + "RX": 35, + "SS": 29, + "MOSI": 37, + "MISO": 31, + "SCK": 30, + "A0": 36, + "A1": 39, + "A2": 32, + "A3": 33, + "A4": 34, + "A5": 35, + "A6": 25, + "A7": 26, + "D2": 27, + "D3": 14, + "D4": 12, + "D5": 13, + "D6": 15, + "D7": 16, + "D8": 17, + "D9": 4, + }, "featheresp32": { "A0": 26, "A1": 25, @@ -434,6 +494,18 @@ ESP32_BOARD_PINS = { "SW4": 21, }, "frogboard": {}, + "healtypi4": { + "KEY_BUILTIN": 17, + "ADS1292_DRDY_PIN": 26, + "ADS1292_CS_PIN": 13, + "ADS1292_START_PIN": 14, + "ADS1292_PWDN_PIN": 27, + "AFE4490_CS_PIN": 21, + "AFE4490_DRDY_PIN": 39, + "AFE4490_PWDN_PIN": 4, + "PUSH_BUTTON": 17, + "SLIDE_SWITCH": 16, + }, "heltec_wifi_kit_32": { "A1": 37, "A2": 38, @@ -444,6 +516,7 @@ ESP32_BOARD_PINS = { "SDA_OLED": 4, "Vext": 21, }, + "heltec_wifi_kit_32_v2": "heltec_wifi_kit_32", "heltec_wifi_lora_32": { "BUTTON": 0, "DIO0": 26, @@ -489,8 +562,68 @@ ESP32_BOARD_PINS = { "SS": 18, "Vext": 21, }, + "heltec_wireless_stick_lite": { + "LED_BUILTIN": 25, + "KEY_BUILTIN": 0, + "SS": 18, + "MOSI": 27, + "MISO": 19, + "SCK": 5, + "Vext": 21, + "LED": 25, + "RST_LoRa": 14, + "DIO0": 26, + "DIO1": 35, + "DIO2": 34, + }, + "honeylemon": { + "LED_BUILTIN": 2, + "BUILTIN_KEY": 0, + }, "hornbill32dev": {"BUTTON": 0, "LED": 13}, "hornbill32minima": {"SS": 2}, + "imbrios-logsens-v1p1": { + "LED_BUILTIN": 33, + "UART2_TX": 17, + "UART2_RX": 16, + "UART2_RTS": 4, + "CAN_TX": 17, + "CAN_RX": 16, + "CAN_TXDE": 4, + "SS": 15, + "MOSI": 13, + "MISO": 12, + "SCK": 14, + "SPI_SS1": 23, + "BUZZER_CTRL": 19, + "SD_CARD_DETECT": 35, + "SW2_BUILDIN": 0, + "SW3_BUILDIN": 36, + "SW4_BUILDIN": 34, + "LED1_BUILDIN": 32, + "LED2_BUILDIN": 33, + }, + "inex_openkb": { + "LED_BUILTIN": 16, + "LDR_PIN": 36, + "SW1": 16, + "SW2": 14, + "BT_LED": 17, + "WIFI_LED": 2, + "NTP_LED": 15, + "IOT_LED": 12, + "BUZZER": 13, + "INPUT1": 32, + "INPUT2": 33, + "INPUT3": 34, + "INPUT4": 35, + "OUTPUT1": 26, + "OUTPUT2": 27, + "SDA0": 21, + "SCL0": 22, + "SDA1": 4, + "SCL1": 5, + }, "intorobot": { "A1": 39, "A2": 35, @@ -528,6 +661,40 @@ ESP32_BOARD_PINS = { "iotaap_magnolia": {}, "iotbusio": {}, "iotbusproteus": {}, + "kits-edu": {}, + "labplus_mpython": { + "SDA": 23, + "SCL": 22, + "P0": 33, + "P1": 32, + "P2": 35, + "P3": 34, + "P4": 39, + "P5": 0, + "P6": 16, + "P7": 17, + "P8": 26, + "P9": 25, + "P10": 36, + "P11": 2, + "P13": 18, + "P14": 19, + "P15": 21, + "P16": 5, + "P19": 22, + "P20": 23, + "P": 27, + "Y": 14, + "T": 12, + "H": 13, + "O": 15, + "N": 4, + "BTN_A": 0, + "BTN_B": 2, + "SOUND": 36, + "LIGHT": 39, + "BUZZER": 16, + }, "lolin32": {"LED": 5}, "lolin32_lite": {"LED": 22}, "lolin_d32": {"LED": 5, "_VBAT": 35}, @@ -554,6 +721,16 @@ ESP32_BOARD_PINS = { "SDA": 12, "SS": 18, }, + "m5stack-atom": { + "SDA": 26, + "SCL": 32, + "ADC1": 35, + "ADC2": 36, + "SS": 19, + "MOSI": 33, + "MISO": 23, + "SCK": 22, + }, "m5stack-core-esp32": { "ADC1": 35, "ADC2": 36, @@ -580,6 +757,26 @@ ESP32_BOARD_PINS = { "RXD2": 16, "TXD2": 17, }, + "m5stack-core2": { + "SDA": 32, + "SCL": 33, + "SS": 5, + "MOSI": 23, + "MISO": 38, + "SCK": 18, + "ADC1": 35, + "ADC2": 36, + }, + "m5stack-coreink": { + "SDA": 32, + "SCL": 33, + "SS": 9, + "MOSI": 23, + "MISO": 34, + "SCK": 18, + "ADC1": 35, + "ADC2": 36, + }, "m5stack-fire": { "ADC1": 35, "ADC2": 36, @@ -630,6 +827,17 @@ ESP32_BOARD_PINS = { "RXD2": 16, "TXD2": 17, }, + "m5stack-timer-cam": { + "LED_BUILTIN": 2, + "SDA": 4, + "SCL": 13, + "SS": 5, + "MOSI": 23, + "MISO": 19, + "SCK": 18, + "ADC1": 35, + "ADC2": 36, + }, "m5stick-c": { "ADC1": 35, "ADC2": 36, @@ -664,6 +872,17 @@ ESP32_BOARD_PINS = { "RIGHT_PUTTON": 34, "YELLOW_LED": 18, }, + "mgbot-iotik32a": { + "LED_BUILTIN": 4, + "TX2": 17, + "RX2": 16, + }, + "mgbot-iotik32b": { + "LED_BUILTIN": 18, + "IR": 27, + "TX2": 17, + "RX2": 16, + }, "mhetesp32devkit": {"LED": 2}, "mhetesp32minikit": {"LED": 2}, "microduino-core-esp32": { @@ -740,6 +959,7 @@ ESP32_BOARD_PINS = { }, "node32s": {}, "nodemcu-32s": {"BUTTON": 0, "LED": 2}, + "nscreen-32": {}, "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": { @@ -766,6 +986,10 @@ ESP32_BOARD_PINS = { "VBAT": 35, }, "pico32": {}, + "piranha_esp32": { + "LED_BUILTIN": 2, + "KEY_BUILTIN": 0, + }, "pocket_32": {"LED": 16}, "pycom_gpy": { "A1": 37, @@ -778,7 +1002,14 @@ ESP32_BOARD_PINS = { "SDA": 12, "SS": 17, }, + "qchip": "heltec_wifi_kit_32", "quantum": {}, + "s_odi_ultra": { + "LED_BUILTIN": 2, + "LED_BUILTINB": 4, + }, + "sensesiot_weizen": {}, + "sg-o_airMon": {}, "sparkfun_lora_gateway_1-channel": {"MISO": 12, "MOSI": 13, "SCK": 14, "SS": 16}, "tinypico": {}, "ttgo-lora32-v1": { @@ -790,6 +1021,26 @@ ESP32_BOARD_PINS = { "SCK": 5, "SS": 18, }, + "ttgo-lora32-v2": { + "LED_BUILTIN": 22, + "KEY_BUILTIN": 0, + "SS": 18, + "MOSI": 27, + "MISO": 19, + "SCK": 5, + "A1": 37, + "A2": 38, + }, + "ttgo-lora32-v21": { + "LED_BUILTIN": 25, + "KEY_BUILTIN": 0, + "SS": 18, + "MOSI": 27, + "MISO": 19, + "SCK": 5, + "A1": 37, + "A2": 38, + }, "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}, @@ -855,6 +1106,32 @@ ESP32_BOARD_PINS = { "T5": 5, "T6": 4, }, + "wifiduino32": { + "LED_BUILTIN": 2, + "KEY_BUILTIN": 0, + "SDA": 5, + "SCL": 16, + "A0": 27, + "A1": 14, + "A2": 12, + "A3": 35, + "A4": 13, + "A5": 4, + "D0": 3, + "D1": 1, + "D2": 17, + "D3": 15, + "D4": 32, + "D5": 33, + "D6": 25, + "D7": 26, + "D8": 23, + "D9": 22, + "D10": 21, + "D11": 19, + "D12": 18, + "D13": 2, + }, "xinabox_cw02": {"LED": 27}, } From f923ba87c0c55e219e55993e13596b2a60724d37 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 30 Aug 2021 09:41:14 +1200 Subject: [PATCH 1248/1841] Bump dashboard to 20210826.0 (#2211) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b4d557f06e..79fefbfcaa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==3.1 click==7.1.2 -esphome-dashboard==20210728.0 +esphome-dashboard==20210826.0 From 9218e85bd68c1fab554f8515cec172a9f2faec38 Mon Sep 17 00:00:00 2001 From: Marcio Granzotto Rodrigues Date: Mon, 30 Aug 2021 15:03:30 -0300 Subject: [PATCH 1249/1841] Remove footer validation for fujitsu_general (#2196) --- esphome/components/fujitsu_general/fujitsu_general.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/esphome/components/fujitsu_general/fujitsu_general.cpp b/esphome/components/fujitsu_general/fujitsu_general.cpp index 8d789bbcfc..9e58f672c7 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.cpp +++ b/esphome/components/fujitsu_general/fujitsu_general.cpp @@ -297,12 +297,6 @@ bool FujitsuGeneralClimate::on_receive(remote_base::RemoteReceiveData data) { } } - // Validate footer - if (!data.expect_mark(FUJITSU_GENERAL_BIT_MARK)) { - ESP_LOGV(TAG, "Footer fail"); - return false; - } - for (uint8_t byte = 0; byte < recv_message_length; ++byte) { ESP_LOGVV(TAG, "%02X", recv_message[byte]); } From 37f322585e16438e95da8ce9bde6d65c2be54103 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Mon, 30 Aug 2021 15:48:19 -0300 Subject: [PATCH 1250/1841] Glmnet schema 202105 (#2220) --- script/build_jsonschema.py | 45 +++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/script/build_jsonschema.py b/script/build_jsonschema.py index 1ab0ffa015..7a3257411c 100644 --- a/script/build_jsonschema.py +++ b/script/build_jsonschema.py @@ -25,6 +25,11 @@ JSC_DESCRIPTION = "description" JSC_ONEOF = "oneOf" JSC_PROPERTIES = "properties" JSC_REF = "$ref" + +# this should be required, but YAML Language server completion does not work properly if required are specified. +# still needed for other features / checks +JSC_REQUIRED = "required_" + SIMPLE_AUTOMATION = "simple_automation" schema_names = {} @@ -295,9 +300,17 @@ def get_automation_schema(name, vschema): # * an object with automation's schema and a then key # with again a single action or an array of actions + if len(extra_jschema[JSC_PROPERTIES]) == 0: + return get_ref(SIMPLE_AUTOMATION) + extra_jschema[JSC_PROPERTIES]["then"] = add_definition_array_or_single_object( get_ref(JSC_ACTION) ) + # if there is a required element in extra_jschema then this automation does not support + # directly a list of actions + if JSC_REQUIRED in extra_jschema: + return create_ref(name, extra_vschema, extra_jschema) + jschema = add_definition_array_or_single_object(get_ref(JSC_ACTION)) jschema[JSC_ANYOF].append(extra_jschema) @@ -370,9 +383,14 @@ def get_entry(parent_key, vschema): # everything else just accept string and let ESPHome validate try: from esphome.core import ID + from esphome.automation import Trigger, Automation v = vschema(None) if isinstance(v, ID): + if v.type.base != "script::Script" and ( + v.type.inherits_from(Trigger) or v.type == Automation + ): + return None entry = {"type": "string", "id_type": v.type.base} elif isinstance(v, str): entry = {"type": "string"} @@ -494,9 +512,11 @@ def convert_schema(path, vschema, un_extend=True): output = {} if str(vschema) in ejs.hidden_schemas: - # this can get another think twist. When adding this I've already figured out - # interval and script in other way - if path not in ["interval", "script"]: + if ejs.hidden_schemas[str(vschema)] == "automation": + vschema = vschema(ejs.jschema_extractor) + jschema = get_jschema(path, vschema, True) + return add_definition_array_or_single_object(jschema) + else: vschema = vschema(ejs.jschema_extractor) if un_extend: @@ -515,9 +535,8 @@ def convert_schema(path, vschema, un_extend=True): return rhs # merge - if JSC_ALLOF in lhs and JSC_ALLOF in rhs: - output = lhs[JSC_ALLOF] + output = lhs for k in rhs[JSC_ALLOF]: merge(output[JSC_ALLOF], k) elif JSC_ALLOF in lhs: @@ -574,6 +593,7 @@ def convert_schema(path, vschema, un_extend=True): return output props = output[JSC_PROPERTIES] = {} + required = [] output["type"] = ["object", "null"] if DUMP_COMMENTS: @@ -616,13 +636,21 @@ def convert_schema(path, vschema, un_extend=True): if prop: # Deprecated (cv.Invalid) properties not added props[str(k)] = prop # TODO: see required, sometimes completions doesn't show up because of this... - # if isinstance(k, cv.Required): - # required.append(str(k)) + if isinstance(k, cv.Required): + required.append(str(k)) try: if str(k.default) != "...": - prop["default"] = k.default() + default_value = k.default() + # Yaml validator fails if `"default": null` ends up in the json schema + if default_value is not None: + if prop["type"] == "string": + default_value = str(default_value) + prop["default"] = default_value except: pass + + if len(required) > 0: + output[JSC_REQUIRED] = required return output @@ -648,6 +676,7 @@ def add_pin_registry(): internal = definitions[schema_name] definitions[schema_name]["additionalItems"] = False definitions[f"PIN.{mode}_INTERNAL"] = internal + internal[JSC_PROPERTIES]["number"] = {"type": ["number", "string"]} schemas = [get_ref(f"PIN.{mode}_INTERNAL")] schemas[0]["required"] = ["number"] # accept string and object, for internal shorthand pin IO: From 03190611bbf399b32f911c0f5444f3fb97bda9dc Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Tue, 31 Aug 2021 08:10:22 +1000 Subject: [PATCH 1251/1841] Add H-Bridge fan component (#2212) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 2 + esphome/components/hbridge/__init__.py | 3 + esphome/components/hbridge/fan/__init__.py | 70 +++++++++++++++ .../components/hbridge/fan/hbridge_fan.cpp | 85 +++++++++++++++++++ esphome/components/hbridge/fan/hbridge_fan.h | 58 +++++++++++++ .../hbridge/{light.py => light/__init__.py} | 4 +- .../{ => light}/hbridge_light_output.h | 0 esphome/const.py | 1 + 8 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 esphome/components/hbridge/fan/__init__.py create mode 100644 esphome/components/hbridge/fan/hbridge_fan.cpp create mode 100644 esphome/components/hbridge/fan/hbridge_fan.h rename esphome/components/hbridge/{light.py => light/__init__.py} (94%) rename esphome/components/hbridge/{ => light}/hbridge_light_output.h (100%) diff --git a/CODEOWNERS b/CODEOWNERS index eebaf02671..53669b8b99 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -52,6 +52,8 @@ esphome/components/globals/* @esphome/core esphome/components/gpio/* @esphome/core esphome/components/gps/* @coogle esphome/components/havells_solar/* @sourabhjaiswal +esphome/components/hbridge/fan/* @WeekendWarrior +esphome/components/hbridge/light/* @DotNetDann esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/homeassistant/* @OttoWinter esphome/components/hrxl_maxsonar_wr/* @netmikey diff --git a/esphome/components/hbridge/__init__.py b/esphome/components/hbridge/__init__.py index e69de29bb2..7eae863ff5 100644 --- a/esphome/components/hbridge/__init__.py +++ b/esphome/components/hbridge/__init__.py @@ -0,0 +1,3 @@ +import esphome.codegen as cg + +hbridge_ns = cg.esphome_ns.namespace("hbridge") diff --git a/esphome/components/hbridge/fan/__init__.py b/esphome/components/hbridge/fan/__init__.py new file mode 100644 index 0000000000..b169978acd --- /dev/null +++ b/esphome/components/hbridge/fan/__init__.py @@ -0,0 +1,70 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.automation import maybe_simple_id +from esphome.components import fan, output +from esphome.const import ( + CONF_ID, + CONF_DECAY_MODE, + CONF_SPEED_COUNT, + CONF_PIN_A, + CONF_PIN_B, + CONF_ENABLE_PIN, +) +from .. import hbridge_ns + + +CODEOWNERS = ["@WeekendWarrior"] + + +HBridgeFan = hbridge_ns.class_("HBridgeFan", fan.FanState) + +DecayMode = hbridge_ns.enum("DecayMode") +DECAY_MODE_OPTIONS = { + "SLOW": DecayMode.DECAY_MODE_SLOW, + "FAST": DecayMode.DECAY_MODE_FAST, +} + +# Actions +BrakeAction = hbridge_ns.class_("BrakeAction", automation.Action) + + +CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( + { + cv.GenerateID(CONF_ID): cv.declare_id(HBridgeFan), + cv.Required(CONF_PIN_A): cv.use_id(output.FloatOutput), + cv.Required(CONF_PIN_B): cv.use_id(output.FloatOutput), + cv.Optional(CONF_DECAY_MODE, default="SLOW"): cv.enum( + DECAY_MODE_OPTIONS, upper=True + ), + cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1), + cv.Optional(CONF_ENABLE_PIN): cv.use_id(output.FloatOutput), + } +).extend(cv.COMPONENT_SCHEMA) + + +@automation.register_action( + "fan.hbridge.brake", + BrakeAction, + maybe_simple_id({cv.Required(CONF_ID): cv.use_id(HBridgeFan)}), +) +async def fan_hbridge_brake_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) + + +async def to_code(config): + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_SPEED_COUNT], + config[CONF_DECAY_MODE], + ) + await fan.register_fan(var, config) + pin_a_ = await cg.get_variable(config[CONF_PIN_A]) + cg.add(var.set_pin_a(pin_a_)) + pin_b_ = await cg.get_variable(config[CONF_PIN_B]) + cg.add(var.set_pin_b(pin_b_)) + + if CONF_ENABLE_PIN in config: + enable_pin = await cg.get_variable(config[CONF_ENABLE_PIN]) + cg.add(var.set_enable_pin(enable_pin)) diff --git a/esphome/components/hbridge/fan/hbridge_fan.cpp b/esphome/components/hbridge/fan/hbridge_fan.cpp new file mode 100644 index 0000000000..a4e5429ff4 --- /dev/null +++ b/esphome/components/hbridge/fan/hbridge_fan.cpp @@ -0,0 +1,85 @@ +#include "hbridge_fan.h" +#include "esphome/components/fan/fan_helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace hbridge { + +static const char *const TAG = "fan.hbridge"; + +void HBridgeFan::set_hbridge_levels_(float a_level, float b_level) { + this->pin_a_->set_level(a_level); + this->pin_b_->set_level(b_level); + ESP_LOGD(TAG, "Setting speed: a: %.2f, b: %.2f", a_level, b_level); +} + +// constant IN1/IN2, PWM on EN => power control, fast current decay +// constant IN1/EN, PWM on IN2 => power control, slow current decay +void HBridgeFan::set_hbridge_levels_(float a_level, float b_level, float enable) { + this->pin_a_->set_level(a_level); + this->pin_b_->set_level(b_level); + this->enable_->set_level(enable); + ESP_LOGD(TAG, "Setting speed: a: %.2f, b: %.2f, enable: %.2f", a_level, b_level, enable); +} + +fan::FanStateCall HBridgeFan::brake() { + ESP_LOGD(TAG, "Braking"); + (this->enable_ == nullptr) ? this->set_hbridge_levels_(1.0f, 1.0f) : this->set_hbridge_levels_(1.0f, 1.0f, 1.0f); + return this->make_call().set_state(false); +} + +void HBridgeFan::dump_config() { + ESP_LOGCONFIG(TAG, "Fan '%s':", this->get_name().c_str()); + if (this->get_traits().supports_oscillation()) { + ESP_LOGCONFIG(TAG, " Oscillation: YES"); + } + if (this->get_traits().supports_direction()) { + ESP_LOGCONFIG(TAG, " Direction: YES"); + } + if (this->decay_mode_ == DECAY_MODE_SLOW) { + ESP_LOGCONFIG(TAG, " Decay Mode: Slow"); + } else { + ESP_LOGCONFIG(TAG, " Decay Mode: Fast"); + } +} +void HBridgeFan::setup() { + auto traits = fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_); + this->set_traits(traits); + this->add_on_state_callback([this]() { this->next_update_ = true; }); +} +void HBridgeFan::loop() { + if (!this->next_update_) { + return; + } + this->next_update_ = false; + + float speed = 0.0f; + if (this->state) { + speed = static_cast(this->speed) / static_cast(this->speed_count_); + } + if (speed == 0.0f) { // off means idle + (this->enable_ == nullptr) ? this->set_hbridge_levels_(speed, speed) + : this->set_hbridge_levels_(speed, speed, speed); + return; + } + if (this->direction == fan::FAN_DIRECTION_FORWARD) { + if (this->decay_mode_ == DECAY_MODE_SLOW) { + (this->enable_ == nullptr) ? this->set_hbridge_levels_(1.0f - speed, 1.0f) + : this->set_hbridge_levels_(1.0f - speed, 1.0f, 1.0f); + } else { // DECAY_MODE_FAST + (this->enable_ == nullptr) ? this->set_hbridge_levels_(0.0f, speed) + : this->set_hbridge_levels_(0.0f, 1.0f, speed); + } + } else { // fan::FAN_DIRECTION_REVERSE + if (this->decay_mode_ == DECAY_MODE_SLOW) { + (this->enable_ == nullptr) ? this->set_hbridge_levels_(1.0f, 1.0f - speed) + : this->set_hbridge_levels_(1.0f, 1.0f - speed, 1.0f); + } else { // DECAY_MODE_FAST + (this->enable_ == nullptr) ? this->set_hbridge_levels_(speed, 0.0f) + : this->set_hbridge_levels_(1.0f, 0.0f, speed); + } + } +} + +} // namespace hbridge +} // namespace esphome diff --git a/esphome/components/hbridge/fan/hbridge_fan.h b/esphome/components/hbridge/fan/hbridge_fan.h new file mode 100644 index 0000000000..984318c8d6 --- /dev/null +++ b/esphome/components/hbridge/fan/hbridge_fan.h @@ -0,0 +1,58 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/components/output/binary_output.h" +#include "esphome/components/output/float_output.h" +#include "esphome/components/fan/fan_state.h" + +namespace esphome { +namespace hbridge { + +enum DecayMode { + DECAY_MODE_SLOW = 0, + DECAY_MODE_FAST = 1, +}; + +class HBridgeFan : public fan::FanState { + public: + HBridgeFan(int speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {} + + void set_pin_a(output::FloatOutput *pin_a) { pin_a_ = pin_a; } + void set_pin_b(output::FloatOutput *pin_b) { pin_b_ = pin_b; } + void set_enable_pin(output::FloatOutput *enable) { enable_ = enable; } + + void setup() override; + void loop() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + fan::FanStateCall brake(); + + int get_speed_count() { return this->speed_count_; } + // update Hbridge without a triggered FanState change, eg. for acceleration/deceleration ramping + void internal_update() { this->next_update_ = true; } + + protected: + output::FloatOutput *pin_a_; + output::FloatOutput *pin_b_; + output::FloatOutput *enable_{nullptr}; + output::BinaryOutput *oscillating_{nullptr}; + bool next_update_{true}; + int speed_count_{}; + DecayMode decay_mode_{DECAY_MODE_SLOW}; + + void set_hbridge_levels_(float a_level, float b_level); + void set_hbridge_levels_(float a_level, float b_level, float enable); +}; + +template class BrakeAction : public Action { + public: + explicit BrakeAction(HBridgeFan *parent) : parent_(parent) {} + + void play(Ts... x) override { this->parent_->brake(); } + + HBridgeFan *parent_; +}; + +} // namespace hbridge +} // namespace esphome diff --git a/esphome/components/hbridge/light.py b/esphome/components/hbridge/light/__init__.py similarity index 94% rename from esphome/components/hbridge/light.py rename to esphome/components/hbridge/light/__init__.py index b4ae45977a..fe5c3e9845 100644 --- a/esphome/components/hbridge/light.py +++ b/esphome/components/hbridge/light/__init__.py @@ -2,8 +2,10 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import light, output from esphome.const import CONF_OUTPUT_ID, CONF_PIN_A, CONF_PIN_B +from .. import hbridge_ns + +CODEOWNERS = ["@DotNetDann"] -hbridge_ns = cg.esphome_ns.namespace("hbridge") HBridgeLightOutput = hbridge_ns.class_( "HBridgeLightOutput", cg.PollingComponent, light.LightOutput ) diff --git a/esphome/components/hbridge/hbridge_light_output.h b/esphome/components/hbridge/light/hbridge_light_output.h similarity index 100% rename from esphome/components/hbridge/hbridge_light_output.h rename to esphome/components/hbridge/light/hbridge_light_output.h diff --git a/esphome/const.py b/esphome/const.py index 932e7e8c61..011b5deddb 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -165,6 +165,7 @@ CONF_DAYS_OF_WEEK = "days_of_week" CONF_DC_PIN = "dc_pin" CONF_DEASSERT_RTS_DTR = "deassert_rts_dtr" CONF_DEBOUNCE = "debounce" +CONF_DECAY_MODE = "decay_mode" CONF_DECELERATION = "deceleration" CONF_DEFAULT_MODE = "default_mode" CONF_DEFAULT_TARGET_TEMPERATURE_HIGH = "default_target_temperature_high" From f186ff8b46137744fd4892ef24716c37cb52c2db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Aug 2021 10:40:06 +1200 Subject: [PATCH 1252/1841] Bump black from 21.7b0 to 21.8b0 (#2222) Bumps [black](https://github.com/psf/black) from 21.7b0 to 21.8b0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits) --- updated-dependencies: - dependency-name: black dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 684582bd4c..005131f315 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==2.9.6 flake8==3.9.2 -black==21.7b0 +black==21.8b0 pexpect==4.8.0 pre-commit From 58350b6c996c7e99bdfd73b7c4a07454fcdbadfd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Aug 2021 11:10:52 +1200 Subject: [PATCH 1253/1841] Bump pytest from 6.2.4 to 6.2.5 (#2223) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 005131f315..b0de4b727b 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ pexpect==4.8.0 pre-commit # Unit tests -pytest==6.2.4 +pytest==6.2.5 pytest-cov==2.12.1 pytest-mock==3.6.1 pytest-asyncio==0.15.1 From 140ef791aa255ff2349bee8667b16d71cac4fb3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Mon, 30 Aug 2021 22:00:30 -0400 Subject: [PATCH 1254/1841] Support for the AirThings Wave Plus (#1656) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 2 + esphome/components/airthings_ble/__init__.py | 23 +++ .../airthings_ble/airthings_listener.cpp | 33 ++++ .../airthings_ble/airthings_listener.h | 20 +++ .../airthings_wave_plus/__init__.py | 1 + .../airthings_wave_plus.cpp | 142 ++++++++++++++++++ .../airthings_wave_plus/airthings_wave_plus.h | 79 ++++++++++ .../components/airthings_wave_plus/sensor.py | 116 ++++++++++++++ esphome/const.py | 4 + tests/test2.yaml | 25 +++ 10 files changed, 445 insertions(+) create mode 100644 esphome/components/airthings_ble/__init__.py create mode 100644 esphome/components/airthings_ble/airthings_listener.cpp create mode 100644 esphome/components/airthings_ble/airthings_listener.h create mode 100644 esphome/components/airthings_wave_plus/__init__.py create mode 100644 esphome/components/airthings_wave_plus/airthings_wave_plus.cpp create mode 100644 esphome/components/airthings_wave_plus/airthings_wave_plus.h create mode 100644 esphome/components/airthings_wave_plus/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 53669b8b99..40bf27aa43 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -14,6 +14,8 @@ esphome/core/* @esphome/core esphome/components/ac_dimmer/* @glmnet esphome/components/adc/* @esphome/core esphome/components/addressable_light/* @justfalter +esphome/components/airthings_ble/* @jeromelaban +esphome/components/airthings_wave_plus/* @jeromelaban esphome/components/am43/* @buxtronix esphome/components/am43/cover/* @buxtronix esphome/components/animation/* @syndlex diff --git a/esphome/components/airthings_ble/__init__.py b/esphome/components/airthings_ble/__init__.py new file mode 100644 index 0000000000..ca94069703 --- /dev/null +++ b/esphome/components/airthings_ble/__init__.py @@ -0,0 +1,23 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import esp32_ble_tracker +from esphome.const import CONF_ID + +DEPENDENCIES = ["esp32_ble_tracker"] +CODEOWNERS = ["@jeromelaban"] + +airthings_ble_ns = cg.esphome_ns.namespace("airthings_ble") +AirthingsListener = airthings_ble_ns.class_( + "AirthingsListener", esp32_ble_tracker.ESPBTDeviceListener +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(AirthingsListener), + } +).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield esp32_ble_tracker.register_ble_device(var, config) diff --git a/esphome/components/airthings_ble/airthings_listener.cpp b/esphome/components/airthings_ble/airthings_listener.cpp new file mode 100644 index 0000000000..921e42c498 --- /dev/null +++ b/esphome/components/airthings_ble/airthings_listener.cpp @@ -0,0 +1,33 @@ +#include "airthings_listener.h" +#include "esphome/core/log.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace airthings_ble { + +static const char *TAG = "airthings_ble"; + +bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + for (auto &it : device.get_manufacturer_datas()) { + if (it.uuid == esp32_ble_tracker::ESPBTUUID::from_uint32(0x0334)) { + if (it.data.size() < 4) + continue; + + uint32_t sn = it.data[0]; + sn |= ((uint32_t) it.data[1] << 8); + sn |= ((uint32_t) it.data[2] << 16); + sn |= ((uint32_t) it.data[3] << 24); + + ESP_LOGD(TAG, "Found AirThings device Serial:%u (MAC: %s)", sn, device.address_str().c_str()); + return true; + } + } + + return false; +} + +} // namespace airthings_ble +} // namespace esphome + +#endif diff --git a/esphome/components/airthings_ble/airthings_listener.h b/esphome/components/airthings_ble/airthings_listener.h new file mode 100644 index 0000000000..cd240ac1ba --- /dev/null +++ b/esphome/components/airthings_ble/airthings_listener.h @@ -0,0 +1,20 @@ +#pragma once + +#ifdef ARDUINO_ARCH_ESP32 + +#include "esphome/core/component.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include + +namespace esphome { +namespace airthings_ble { + +class AirthingsListener : public esp32_ble_tracker::ESPBTDeviceListener { + public: + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; +}; + +} // namespace airthings_ble +} // namespace esphome + +#endif diff --git a/esphome/components/airthings_wave_plus/__init__.py b/esphome/components/airthings_wave_plus/__init__.py new file mode 100644 index 0000000000..1aff461edd --- /dev/null +++ b/esphome/components/airthings_wave_plus/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@jeromelaban"] diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp new file mode 100644 index 0000000000..6b2e807e0b --- /dev/null +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -0,0 +1,142 @@ +#include "airthings_wave_plus.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace airthings_wave_plus { + +void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) { + switch (event) { + case ESP_GATTC_OPEN_EVT: { + if (param->open.status == ESP_GATT_OK) { + ESP_LOGI(TAG, "Connected successfully!"); + } + break; + } + + case ESP_GATTC_DISCONNECT_EVT: { + ESP_LOGW(TAG, "Disconnected!"); + break; + } + + case ESP_GATTC_SEARCH_CMPL_EVT: { + this->handle = 0; + auto chr = this->parent()->get_characteristic(service_uuid, sensors_data_characteristic_uuid); + if (chr == nullptr) { + ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid.to_string().c_str(), + sensors_data_characteristic_uuid.to_string().c_str()); + break; + } + this->handle = chr->handle; + this->node_state = espbt::ClientState::Established; + + request_read_values_(); + break; + } + + case ESP_GATTC_READ_CHAR_EVT: { + if (param->read.conn_id != this->parent()->conn_id) + break; + if (param->read.status != ESP_GATT_OK) { + ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); + break; + } + if (param->read.handle == this->handle) { + read_sensors_(param->read.value, param->read.value_len); + } + break; + } + + default: + break; + } +} + +void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) { + auto value = (WavePlusReadings *) raw_value; + + if (sizeof(WavePlusReadings) <= value_len) { + ESP_LOGD(TAG, "version = %d", value->version); + + if (value->version == 1) { + ESP_LOGD(TAG, "ambient light = %d", value->ambientLight); + + this->humidity_sensor_->publish_state(value->humidity / 2.0f); + if (is_valid_radon_value_(value->radon)) { + this->radon_sensor_->publish_state(value->radon); + } + if (is_valid_radon_value_(value->radon_lt)) { + this->radon_long_term_sensor_->publish_state(value->radon_lt); + } + this->temperature_sensor_->publish_state(value->temperature / 100.0f); + this->pressure_sensor_->publish_state(value->pressure / 50.0f); + if (is_valid_co2_value_(value->co2)) { + this->co2_sensor_->publish_state(value->co2); + } + if (is_valid_voc_value_(value->voc)) { + this->tvoc_sensor_->publish_state(value->voc); + } + + // This instance must not stay connected + // so other clients can connect to it (e.g. the + // mobile app). + parent()->set_enabled(false); + } else { + ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version); + } + } +} + +bool AirthingsWavePlus::is_valid_radon_value_(short radon) { return 0 <= radon && radon <= 16383; } + +bool AirthingsWavePlus::is_valid_voc_value_(short voc) { return 0 <= voc && voc <= 16383; } + +bool AirthingsWavePlus::is_valid_co2_value_(short co2) { return 0 <= co2 && co2 <= 16383; } + +void AirthingsWavePlus::loop() {} + +void AirthingsWavePlus::update() { + if (this->node_state != espbt::ClientState::Established) { + if (!parent()->enabled) { + ESP_LOGW(TAG, "Reconnecting to device"); + parent()->set_enabled(true); + parent()->connect(); + } else { + ESP_LOGW(TAG, "Connection in progress"); + } + } +} + +void AirthingsWavePlus::request_read_values_() { + auto status = + esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); + } +} + +void AirthingsWavePlus::dump_config() { + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); + LOG_SENSOR(" ", "Radon", this->radon_sensor_); + LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); + LOG_SENSOR(" ", "CO2", this->co2_sensor_); + LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); +} + +AirthingsWavePlus::AirthingsWavePlus() : PollingComponent(10000) { + auto service_bt = *BLEUUID::fromString(std::string("b42e1c08-ade7-11e4-89d3-123b93f75cba")).getNative(); + auto characteristic_bt = *BLEUUID::fromString(std::string("b42e2a68-ade7-11e4-89d3-123b93f75cba")).getNative(); + + service_uuid = espbt::ESPBTUUID::from_uuid(service_bt); + sensors_data_characteristic_uuid = espbt::ESPBTUUID::from_uuid(characteristic_bt); +} + +void AirthingsWavePlus::setup() {} + +} // namespace airthings_wave_plus +} // namespace esphome + +#endif // ARDUINO_ARCH_ESP32 diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.h b/esphome/components/airthings_wave_plus/airthings_wave_plus.h new file mode 100644 index 0000000000..18d7fe60d2 --- /dev/null +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.h @@ -0,0 +1,79 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/ble_client/ble_client.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/log.h" +#include +#include + +#ifdef ARDUINO_ARCH_ESP32 +#include +#include + +using namespace esphome::ble_client; + +namespace esphome { +namespace airthings_wave_plus { + +static const char *TAG = "airthings_wave_plus"; + +class AirthingsWavePlus : public PollingComponent, public BLEClientNode { + public: + AirthingsWavePlus(); + + void setup() override; + void dump_config() override; + void update() override; + void loop() override; + + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + + void set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; } + void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; } + void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; } + void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } + void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; } + void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; } + void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } + + protected: + bool is_valid_radon_value_(short radon); + bool is_valid_voc_value_(short voc); + bool is_valid_co2_value_(short co2); + + void read_sensors_(uint8_t *value, uint16_t value_len); + void request_read_values_(); + + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *radon_sensor_{nullptr}; + sensor::Sensor *radon_long_term_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; + sensor::Sensor *co2_sensor_{nullptr}; + sensor::Sensor *tvoc_sensor_{nullptr}; + + uint16_t handle; + espbt::ESPBTUUID service_uuid; + espbt::ESPBTUUID sensors_data_characteristic_uuid; + + struct WavePlusReadings { + uint8_t version; + uint8_t humidity; + uint8_t ambientLight; + uint8_t unused01; + uint16_t radon; + uint16_t radon_lt; + uint16_t temperature; + uint16_t pressure; + uint16_t co2; + uint16_t voc; + }; +}; + +} // namespace airthings_wave_plus +} // namespace esphome + +#endif // ARDUINO_ARCH_ESP32 diff --git a/esphome/components/airthings_wave_plus/sensor.py b/esphome/components/airthings_wave_plus/sensor.py new file mode 100644 index 0000000000..4109fca700 --- /dev/null +++ b/esphome/components/airthings_wave_plus/sensor.py @@ -0,0 +1,116 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, ble_client + +from esphome.const import ( + DEVICE_CLASS_CARBON_DIOXIDE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + STATE_CLASS_MEASUREMENT, + UNIT_PERCENT, + UNIT_CELSIUS, + UNIT_HECTOPASCAL, + ICON_RADIOACTIVE, + CONF_ID, + CONF_RADON, + CONF_RADON_LONG_TERM, + CONF_HUMIDITY, + CONF_TVOC, + CONF_CO2, + CONF_PRESSURE, + CONF_TEMPERATURE, + UNIT_BECQUEREL_PER_CUBIC_METER, + UNIT_PARTS_PER_MILLION, + UNIT_PARTS_PER_BILLION, + ICON_RADIATOR, +) + +DEPENDENCIES = ["ble_client"] + +airthings_wave_plus_ns = cg.esphome_ns.namespace("airthings_wave_plus") +AirthingsWavePlus = airthings_wave_plus_ns.class_( + "AirthingsWavePlus", cg.PollingComponent, ble_client.BLEClientNode +) + + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(AirthingsWavePlus), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=0, + ), + cv.Optional(CONF_RADON): sensor.sensor_schema( + unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, + icon=ICON_RADIOACTIVE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema( + unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, + icon=ICON_RADIOACTIVE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PRESSURE): sensor.sensor_schema( + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CO2): sensor.sensor_schema( + unit_of_measurement=UNIT_PARTS_PER_MILLION, + accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TVOC): sensor.sensor_schema( + unit_of_measurement=UNIT_PARTS_PER_BILLION, + icon=ICON_RADIATOR, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("5mins")) + .extend(ble_client.BLE_CLIENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + await ble_client.register_ble_node(var, config) + + if CONF_HUMIDITY in config: + sens = await sensor.new_sensor(config[CONF_HUMIDITY]) + cg.add(var.set_humidity(sens)) + if CONF_RADON in config: + sens = await sensor.new_sensor(config[CONF_RADON]) + cg.add(var.set_radon(sens)) + if CONF_RADON_LONG_TERM in config: + sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM]) + cg.add(var.set_radon_long_term(sens)) + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature(sens)) + if CONF_PRESSURE in config: + sens = await sensor.new_sensor(config[CONF_PRESSURE]) + cg.add(var.set_pressure(sens)) + if CONF_CO2 in config: + sens = await sensor.new_sensor(config[CONF_CO2]) + cg.add(var.set_co2(sens)) + if CONF_TVOC in config: + sens = await sensor.new_sensor(config[CONF_TVOC]) + cg.add(var.set_tvoc(sens)) diff --git a/esphome/const.py b/esphome/const.py index 011b5deddb..579d8ef4c6 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -507,6 +507,8 @@ CONF_PROTOCOL = "protocol" CONF_PULL_MODE = "pull_mode" CONF_PULSE_LENGTH = "pulse_length" CONF_QOS = "qos" +CONF_RADON = "radon" +CONF_RADON_LONG_TERM = "radon_long_term" CONF_RANDOM = "random" CONF_RANGE = "range" CONF_RANGE_FROM = "range_from" @@ -732,6 +734,7 @@ ICON_PERCENT = "mdi:percent" ICON_POWER = "mdi:power" ICON_PULSE = "mdi:pulse" ICON_RADIATOR = "mdi:radiator" +ICON_RADIOACTIVE = "mdi:radioactive" ICON_RESTART = "mdi:restart" ICON_ROTATE_RIGHT = "mdi:rotate-right" ICON_RULER = "mdi:ruler" @@ -753,6 +756,7 @@ ICON_WEATHER_WINDY = "mdi:weather-windy" ICON_WIFI = "mdi:wifi" UNIT_AMPERE = "A" +UNIT_BECQUEREL_PER_CUBIC_METER = "Bq/m³" UNIT_CELSIUS = "°C" UNIT_COUNT_DECILITRE = "/dL" UNIT_COUNTS_PER_CUBIC_METER = "#/m³" diff --git a/tests/test2.yaml b/tests/test2.yaml index 6807278c0d..0db47965fe 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -246,6 +246,24 @@ sensor: id: freezer_temp_source reference_voltage: 3.19 number: 0 + - platform: airthings_wave_plus + ble_client_id: airthings01 + update_interval: 5min + temperature: + name: "Wave Plus Temperature" + radon: + name: "Wave Plus Radon" + radon_long_term: + name: "Wave Plus Radon Long Term" + pressure: + name: "Wave Plus Pressure" + humidity: + name: "Wave Plus Humidity" + co2: + name: "Wave Plus CO2" + tvoc: + name: "Wave Plus VOC" + time: - platform: homeassistant on_time: @@ -334,6 +352,12 @@ esp32_ble_tracker: - lambda: !lambda |- ESP_LOGD("main", "Length of manufacturer data is %i", x.size()); +ble_client: + - mac_address: 01:02:03:04:05:06 + id: airthings01 + +airthings_ble: + #esp32_ble_beacon: # type: iBeacon # uuid: 'c29ce823-e67a-4e71-bff2-abaa32e77a98' @@ -431,3 +455,4 @@ interval: - logger.log: 'Interval Run' display: + From 54337befc262f7935b61c2b9f970db0c66c8d74f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 31 Aug 2021 14:00:58 +1200 Subject: [PATCH 1255/1841] Fix some lint errors in pylint 2.10.2 (#2226) --- esphome/components/mcp23xxx_base/__init__.py | 3 ++- esphome/components/web_server/__init__.py | 4 ++-- esphome/config_validation.py | 7 ++++--- esphome/dashboard/dashboard.py | 6 ++++-- esphome/helpers.py | 10 +++++----- esphome/writer.py | 2 +- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/esphome/components/mcp23xxx_base/__init__.py b/esphome/components/mcp23xxx_base/__init__.py index 019b7c7e64..c22d377b3c 100644 --- a/esphome/components/mcp23xxx_base/__init__.py +++ b/esphome/components/mcp23xxx_base/__init__.py @@ -91,7 +91,7 @@ async def mcp23xxx_pin_to_code(config): # BEGIN Removed pin schemas below to show error in configuration -# TODO remove in 1.19.0 +# TODO remove in 2022.5.0 for id in ["mcp23008", "mcp23s08", "mcp23017", "mcp23s17"]: PIN_SCHEMA = cv.Schema( @@ -110,6 +110,7 @@ for id in ["mcp23008", "mcp23s08", "mcp23017", "mcp23s17"]: } ) + # pylint: disable=cell-var-from-loop @pins.PIN_SCHEMA_REGISTRY.register(id, (PIN_SCHEMA, PIN_SCHEMA)) def pin_to_code(config): pass diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index ca3a60f43f..7f17767657 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -62,10 +62,10 @@ async def to_code(config): if CONF_CSS_INCLUDE in config: cg.add_define("WEBSERVER_CSS_INCLUDE") path = CORE.relative_config_path(config[CONF_CSS_INCLUDE]) - with open(path, "r") as myfile: + with open(file=path, mode="r", encoding="utf-8") as myfile: cg.add(var.set_css_include(myfile.read())) if CONF_JS_INCLUDE in config: cg.add_define("WEBSERVER_JS_INCLUDE") path = CORE.relative_config_path(config[CONF_JS_INCLUDE]) - with open(path, "r") as myfile: + with open(file=path, mode="r", encoding="utf-8") as myfile: cg.add(var.set_js_include(myfile.read())) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 61ef7d2f9f..4df65a38e3 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -836,10 +836,11 @@ pressure = float_with_unit("pressure", "(bar|Bar)", optional_unit=True) def temperature(value): + err = None try: return _temperature_c(value) - except Invalid as orig_err: # noqa - pass + except Invalid as orig_err: + err = orig_err try: kelvin = _temperature_k(value) @@ -853,7 +854,7 @@ def temperature(value): except Invalid: pass - raise orig_err # noqa + raise err _color_temperature_mireds = float_with_unit("Color Temperature", r"(mireds|Mireds)") diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index ea55ea3b18..8016bf7bbd 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -600,7 +600,7 @@ class EditRequestHandler(BaseHandler): content = "" if os.path.isfile(filename): # pylint: disable=no-value-for-parameter - with open(filename, "r") as f: + with open(file=filename, mode="r", encoding="utf-8") as f: content = f.read() self.write(content) @@ -608,7 +608,9 @@ class EditRequestHandler(BaseHandler): @bind_config def post(self, configuration=None): # pylint: disable=no-value-for-parameter - with open(settings.rel_path(configuration), "wb") as f: + with open( + file=settings.rel_path(configuration), mode="wb", encoding="utf-8" + ) as f: f.write(self.request.body) self.set_status(200) diff --git a/esphome/helpers.py b/esphome/helpers.py index ad7b8272b2..c766cc1ac7 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -276,11 +276,11 @@ def file_compare(path1: os.PathLike, path2: os.PathLike) -> bool: # A dict of types that need to be converted to heaptypes before a class can be added # to the object _TYPE_OVERLOADS = { - int: type("EInt", (int,), dict()), - float: type("EFloat", (float,), dict()), - str: type("EStr", (str,), dict()), - dict: type("EDict", (str,), dict()), - list: type("EList", (list,), dict()), + int: type("EInt", (int,), {}), + float: type("EFloat", (float,), {}), + str: type("EStr", (str,), {}), + dict: type("EDict", (str,), {}), + list: type("EList", (list,), {}), } # cache created classes here diff --git a/esphome/writer.py b/esphome/writer.py index 641ae9b3cc..09ed284173 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -481,5 +481,5 @@ GITIGNORE_CONTENT = """# Gitignore settings for ESPHome def write_gitignore(): path = CORE.relative_config_path(".gitignore") if not os.path.isfile(path): - with open(path, "w") as f: + with open(file=path, mode="w", encoding="utf-8") as f: f.write(GITIGNORE_CONTENT) From ea1b5e19f048d4cc50ad81d14390395fc31c4d9a Mon Sep 17 00:00:00 2001 From: Alex <33379584+alexyao2015@users.noreply.github.com> Date: Mon, 30 Aug 2021 21:18:16 -0500 Subject: [PATCH 1256/1841] Add transitions to light flash (#2201) --- esphome/components/light/__init__.py | 8 ++++ esphome/components/light/light_state.cpp | 7 ++- esphome/components/light/light_state.h | 7 +++ esphome/components/light/transformers.h | 43 ++++++++++++++++++- .../components/power_supply/power_supply.cpp | 2 +- esphome/const.py | 1 + 6 files changed, 65 insertions(+), 3 deletions(-) diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index 52e8984545..69cb87e539 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -7,6 +7,7 @@ from esphome.const import ( CONF_DEFAULT_TRANSITION_LENGTH, CONF_DISABLED_BY_DEFAULT, CONF_EFFECTS, + CONF_FLASH_TRANSITION_LENGTH, CONF_GAMMA_CORRECT, CONF_ID, CONF_INTERNAL, @@ -85,6 +86,9 @@ BRIGHTNESS_ONLY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend( cv.Optional( CONF_DEFAULT_TRANSITION_LENGTH, default="1s" ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_FLASH_TRANSITION_LENGTH, default="0s" + ): cv.positive_time_period_milliseconds, cv.Optional(CONF_EFFECTS): validate_effects(MONOCHROMATIC_EFFECTS), } ) @@ -132,6 +136,10 @@ async def setup_light_core_(light_var, output_var, config): config[CONF_DEFAULT_TRANSITION_LENGTH] ) ) + if CONF_FLASH_TRANSITION_LENGTH in config: + cg.add( + light_var.set_flash_transition_length(config[CONF_FLASH_TRANSITION_LENGTH]) + ) if CONF_GAMMA_CORRECT in config: cg.add(light_var.set_gamma_correct(config[CONF_GAMMA_CORRECT])) effects = await cg.build_registry_list( diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 4c4eefdc30..945d3910d5 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -157,6 +157,11 @@ void LightState::add_new_target_state_reached_callback(std::function &&s void LightState::set_default_transition_length(uint32_t default_transition_length) { this->default_transition_length_ = default_transition_length; } +uint32_t LightState::get_default_transition_length() const { return this->default_transition_length_; } +void LightState::set_flash_transition_length(uint32_t flash_transition_length) { + this->flash_transition_length_ = flash_transition_length; +} +uint32_t LightState::get_flash_transition_length() const { return this->flash_transition_length_; } void LightState::set_gamma_correct(float gamma_correct) { this->gamma_correct_ = gamma_correct; } void LightState::set_restore_mode(LightRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } bool LightState::supports_effects() { return !this->effects_.empty(); } @@ -234,7 +239,7 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length) { // If starting a flash if one is already happening, set end values to end values of current flash // Hacky but works if (this->transformer_ != nullptr) - end_colors = this->transformer_->get_target_values(); + end_colors = this->transformer_->get_start_values(); this->transformer_ = make_unique(*this); this->transformer_->setup(end_colors, target, length); diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index dfea9a15f4..dd42aa76db 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -99,6 +99,11 @@ class LightState : public Nameable, public Component { /// Set the default transition length, i.e. the transition length when no transition is provided. void set_default_transition_length(uint32_t default_transition_length); + uint32_t get_default_transition_length() const; + + /// Set the flash transition length + void set_flash_transition_length(uint32_t flash_transition_length); + uint32_t get_flash_transition_length() const; /// Set the gamma correction factor void set_gamma_correct(float gamma_correct); @@ -188,6 +193,8 @@ class LightState : public Nameable, public Component { /// Default transition length for all transitions in ms. uint32_t default_transition_length_{}; + /// Transition length to use for flash transitions. + uint32_t flash_transition_length_{}; /// Gamma correction factor for the light. float gamma_correct_{}; /// Restore mode of the light. diff --git a/esphome/components/light/transformers.h b/esphome/components/light/transformers.h index fd0bfd20f3..adcaebe0c8 100644 --- a/esphome/components/light/transformers.h +++ b/esphome/components/light/transformers.h @@ -58,7 +58,45 @@ class LightFlashTransformer : public LightTransformer { public: LightFlashTransformer(LightState &state) : state_(state) {} - optional apply() override { return this->get_target_values(); } + void start() override { + this->transition_length_ = this->state_.get_flash_transition_length(); + if (this->transition_length_ * 2 > this->length_) + this->transition_length_ = this->length_ / 2; + + // do not create transition if length is 0 + if (this->transition_length_ == 0) + return; + + // first transition to original target + this->transformer_ = this->state_.get_output()->create_default_transition(); + this->transformer_->setup(this->state_.current_values, this->target_values_, this->transition_length_); + } + + optional apply() override { + // transition transformer does not handle 0 length as progress returns nan + if (this->transition_length_ == 0) + return this->target_values_; + + if (this->transformer_ != nullptr) { + if (!this->transformer_->is_finished()) { + return this->transformer_->apply(); + } else { + this->transformer_->stop(); + this->transformer_ = nullptr; + } + } + + if (millis() > this->start_time_ + this->length_ - this->transition_length_ && + !this->secondary_transition_occurred_) { + // second transition back to start value + this->transformer_ = this->state_.get_output()->create_default_transition(); + this->transformer_->setup(this->state_.current_values, this->get_start_values(), this->transition_length_); + this->secondary_transition_occurred_ = true; + } + + // once transition is complete, don't change states until next transition + return optional(); + } // Restore the original values after the flash. void stop() override { @@ -69,6 +107,9 @@ class LightFlashTransformer : public LightTransformer { protected: LightState &state_; + uint32_t transition_length_; + bool secondary_transition_occurred_{false}; + std::unique_ptr transformer_{nullptr}; }; } // namespace light diff --git a/esphome/components/power_supply/power_supply.cpp b/esphome/components/power_supply/power_supply.cpp index f50adac6f9..a492919202 100644 --- a/esphome/components/power_supply/power_supply.cpp +++ b/esphome/components/power_supply/power_supply.cpp @@ -42,7 +42,7 @@ void PowerSupply::request_high_power() { void PowerSupply::unrequest_high_power() { this->active_requests_--; if (this->active_requests_ < 0) { - // we're just going to use 0 as our now counter. + // we're just going to use 0 as our new counter. this->active_requests_ = 0; } diff --git a/esphome/const.py b/esphome/const.py index 579d8ef4c6..0ddc5da706 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -241,6 +241,7 @@ CONF_FILTERS = "filters" CONF_FINGER_ID = "finger_id" CONF_FINGERPRINT_COUNT = "fingerprint_count" CONF_FLASH_LENGTH = "flash_length" +CONF_FLASH_TRANSITION_LENGTH = "flash_transition_length" CONF_FLOW_CONTROL_PIN = "flow_control_pin" CONF_FOR = "for" CONF_FORCE_UPDATE = "force_update" From edcd88123d37e7484160331a21a1a4f0673d0a98 Mon Sep 17 00:00:00 2001 From: Petko Bordjukov Date: Thu, 2 Sep 2021 02:46:15 +0300 Subject: [PATCH 1257/1841] iBeacon support for ble_presence (#1627) --- .../components/ble_presence/binary_sensor.py | 36 ++++++++- .../ble_presence/ble_presence_device.h | 79 +++++++++++++++---- esphome/components/ble_rssi/sensor.py | 2 +- .../components/esp32_ble_tracker/__init__.py | 14 +++- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 8 ++ esphome/const.py | 3 + tests/test2.yaml | 5 ++ 7 files changed, 125 insertions(+), 22 deletions(-) diff --git a/esphome/components/ble_presence/binary_sensor.py b/esphome/components/ble_presence/binary_sensor.py index c58d29e6be..2a242c3aca 100644 --- a/esphome/components/ble_presence/binary_sensor.py +++ b/esphome/components/ble_presence/binary_sensor.py @@ -1,7 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor, esp32_ble_tracker -from esphome.const import CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_ID +from esphome.const import ( + CONF_MAC_ADDRESS, + CONF_SERVICE_UUID, + CONF_IBEACON_MAJOR, + CONF_IBEACON_MINOR, + CONF_IBEACON_UUID, + CONF_ID, +) DEPENDENCIES = ["esp32_ble_tracker"] @@ -13,17 +20,30 @@ BLEPresenceDevice = ble_presence_ns.class_( esp32_ble_tracker.ESPBTDeviceListener, ) + +def _validate(config): + if CONF_IBEACON_MAJOR in config and CONF_IBEACON_UUID not in config: + raise cv.Invalid("iBeacon major identifier requires iBeacon UUID") + if CONF_IBEACON_MINOR in config and CONF_IBEACON_UUID not in config: + raise cv.Invalid("iBeacon minor identifier requires iBeacon UUID") + return config + + CONFIG_SCHEMA = cv.All( binary_sensor.BINARY_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(BLEPresenceDevice), cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, + cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t, + cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t, + cv.Optional(CONF_IBEACON_UUID): cv.uuid, } ) .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) .extend(cv.COMPONENT_SCHEMA), - cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID), + cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_IBEACON_UUID), + _validate, ) @@ -50,5 +70,15 @@ async def to_code(config): ) ) elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): - uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID]) + uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) cg.add(var.set_service_uuid128(uuid128)) + + if CONF_IBEACON_UUID in config: + ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(config[CONF_IBEACON_UUID])) + cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) + + if CONF_IBEACON_MAJOR in config: + cg.add(var.set_ibeacon_major(config[CONF_IBEACON_MAJOR])) + + if CONF_IBEACON_MINOR in config: + cg.add(var.set_ibeacon_minor(config[CONF_IBEACON_MINOR])) diff --git a/esphome/components/ble_presence/ble_presence_device.h b/esphome/components/ble_presence/ble_presence_device.h index bce6a9cf98..dfc36d68cb 100644 --- a/esphome/components/ble_presence/ble_presence_device.h +++ b/esphome/components/ble_presence/ble_presence_device.h @@ -14,41 +14,78 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, public Component { public: void set_address(uint64_t address) { - this->by_address_ = true; + this->match_by_ = MATCH_BY_MAC_ADDRESS; this->address_ = address; } void set_service_uuid16(uint16_t uuid) { - this->by_address_ = false; + this->match_by_ = MATCH_BY_SERVICE_UUID; this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint16(uuid); } void set_service_uuid32(uint32_t uuid) { - this->by_address_ = false; + this->match_by_ = MATCH_BY_SERVICE_UUID; this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint32(uuid); } void set_service_uuid128(uint8_t *uuid) { - this->by_address_ = false; + this->match_by_ = MATCH_BY_SERVICE_UUID; this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid); } + void set_ibeacon_uuid(uint8_t *uuid) { + this->match_by_ = MATCH_BY_IBEACON_UUID; + this->ibeacon_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid); + } + void set_ibeacon_major(uint16_t major) { + this->check_ibeacon_major_ = true; + this->ibeacon_major_ = major; + } + void set_ibeacon_minor(uint16_t minor) { + this->check_ibeacon_minor_ = true; + this->ibeacon_minor_ = minor; + } void on_scan_end() override { if (!this->found_) this->publish_state(false); this->found_ = false; } bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { - if (this->by_address_) { - if (device.address_uint64() == this->address_) { - this->publish_state(true); - this->found_ = true; - return true; - } - } else { - for (auto uuid : device.get_service_uuids()) { - if (this->uuid_ == uuid) { - this->publish_state(device.get_rssi()); + switch (this->match_by_) { + case MATCH_BY_MAC_ADDRESS: + if (device.address_uint64() == this->address_) { + this->publish_state(true); this->found_ = true; return true; } - } + break; + case MATCH_BY_SERVICE_UUID: + for (auto uuid : device.get_service_uuids()) { + if (this->uuid_ == uuid) { + this->publish_state(device.get_rssi()); + this->found_ = true; + return true; + } + } + break; + case MATCH_BY_IBEACON_UUID: + if (!device.get_ibeacon().has_value()) { + return false; + } + + auto ibeacon = device.get_ibeacon().value(); + + if (this->ibeacon_uuid_ != ibeacon.get_uuid()) { + return false; + } + + if (this->check_ibeacon_major_ && this->ibeacon_major_ != ibeacon.get_major()) { + return false; + } + + if (this->check_ibeacon_minor_ && this->ibeacon_minor_ != ibeacon.get_minor()) { + return false; + } + + this->publish_state(device.get_rssi()); + this->found_ = true; + return true; } return false; } @@ -56,10 +93,20 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, float get_setup_priority() const override { return setup_priority::DATA; } protected: + enum MATCH_TYPE { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID }; + MATCH_TYPE match_by_; + bool found_{false}; - bool by_address_{false}; + uint64_t address_; + esp32_ble_tracker::ESPBTUUID uuid_; + + esp32_ble_tracker::ESPBTUUID ibeacon_uuid_; + uint16_t ibeacon_major_; + bool check_ibeacon_major_; + uint16_t ibeacon_minor_; + bool check_ibeacon_minor_; }; } // namespace ble_presence diff --git a/esphome/components/ble_rssi/sensor.py b/esphome/components/ble_rssi/sensor.py index bca73328f9..0c4308b11a 100644 --- a/esphome/components/ble_rssi/sensor.py +++ b/esphome/components/ble_rssi/sensor.py @@ -60,5 +60,5 @@ async def to_code(config): ) ) elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): - uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID]) + uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) cg.add(var.set_service_uuid128(uuid128)) diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 18f1c46ff2..fec0f6dcfb 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -108,6 +108,16 @@ def as_hex(value): def as_hex_array(value): + value = value.replace("-", "") + cpp_array = [ + f"0x{part}" for part in [value[i : i + 2] for i in range(0, len(value), 2)] + ] + return cg.RawExpression( + "(uint8_t*)(const uint8_t[16]){{{}}}".format(",".join(cpp_array)) + ) + + +def as_reversed_hex_array(value): value = value.replace("-", "") cpp_array = [ f"0x{part}" for part in [value[i : i + 2] for i in range(0, len(value), 2)] @@ -193,7 +203,7 @@ async def to_code(config): elif len(conf[CONF_SERVICE_UUID]) == len(bt_uuid32_format): cg.add(trigger.set_service_uuid32(as_hex(conf[CONF_SERVICE_UUID]))) elif len(conf[CONF_SERVICE_UUID]) == len(bt_uuid128_format): - uuid128 = as_hex_array(conf[CONF_SERVICE_UUID]) + uuid128 = as_reversed_hex_array(conf[CONF_SERVICE_UUID]) cg.add(trigger.set_service_uuid128(uuid128)) if CONF_MAC_ADDRESS in conf: cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) @@ -205,7 +215,7 @@ async def to_code(config): elif len(conf[CONF_MANUFACTURER_ID]) == len(bt_uuid32_format): cg.add(trigger.set_manufacturer_uuid32(as_hex(conf[CONF_MANUFACTURER_ID]))) elif len(conf[CONF_MANUFACTURER_ID]) == len(bt_uuid128_format): - uuid128 = as_hex_array(conf[CONF_MANUFACTURER_ID]) + uuid128 = as_reversed_hex_array(conf[CONF_MANUFACTURER_ID]) cg.add(trigger.set_manufacturer_uuid128(uuid128)) if CONF_MAC_ADDRESS in conf: cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index b3db651655..e1cd3975e8 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -434,6 +434,14 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e } for (auto &data : this->manufacturer_datas_) { ESP_LOGVV(TAG, " Manufacturer data: %s", hexencode(data.data).c_str()); + if (this->get_ibeacon().has_value()) { + auto ibeacon = this->get_ibeacon().value(); + ESP_LOGVV(TAG, " iBeacon data:"); + ESP_LOGVV(TAG, " UUID: %s", ibeacon.get_uuid().to_string().c_str()); + ESP_LOGVV(TAG, " Major: %u", ibeacon.get_major()); + ESP_LOGVV(TAG, " Minor: %u", ibeacon.get_minor()); + ESP_LOGVV(TAG, " TXPower: %d", ibeacon.get_signal_power()); + } } for (auto &data : this->service_datas_) { ESP_LOGVV(TAG, " Service data:"); diff --git a/esphome/const.py b/esphome/const.py index 0ddc5da706..06ce736a85 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -279,6 +279,9 @@ CONF_HUMIDITY = "humidity" CONF_HYSTERESIS = "hysteresis" CONF_I2C = "i2c" CONF_I2C_ID = "i2c_id" +CONF_IBEACON_MAJOR = "ibeacon_major" +CONF_IBEACON_MINOR = "ibeacon_minor" +CONF_IBEACON_UUID = "ibeacon_uuid" CONF_ICON = "icon" CONF_ID = "id" CONF_IDENTITY = "identity" diff --git a/tests/test2.yaml b/tests/test2.yaml index 0db47965fe..54932953d5 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -294,6 +294,11 @@ binary_sensor: - platform: ble_presence service_uuid: '11223344-5566-7788-99aa-bbccddeeff00' name: 'BLE Test Service 128 Presence' + - platform: ble_presence + ibeacon_uuid: '11223344-5566-7788-99aa-bbccddeeff00' + ibeacon_major: 100 + ibeacon_minor: 1 + name: 'BLE Test iBeacon Presence' - platform: esp32_touch name: 'ESP32 Touch Pad GPIO27' pin: GPIO27 From 9937ad7fa03cbf999ed82976cb9c5b921f749f9f Mon Sep 17 00:00:00 2001 From: Alex <33379584+alexyao2015@users.noreply.github.com> Date: Wed, 1 Sep 2021 18:56:40 -0500 Subject: [PATCH 1258/1841] Cleanup flash transitions (#2227) --- esphome/components/light/transformers.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/esphome/components/light/transformers.h b/esphome/components/light/transformers.h index adcaebe0c8..d501d53f72 100644 --- a/esphome/components/light/transformers.h +++ b/esphome/components/light/transformers.h @@ -86,12 +86,10 @@ class LightFlashTransformer : public LightTransformer { } } - if (millis() > this->start_time_ + this->length_ - this->transition_length_ && - !this->secondary_transition_occurred_) { + if (millis() > this->start_time_ + this->length_ - this->transition_length_) { // second transition back to start value this->transformer_ = this->state_.get_output()->create_default_transition(); this->transformer_->setup(this->state_.current_values, this->get_start_values(), this->transition_length_); - this->secondary_transition_occurred_ = true; } // once transition is complete, don't change states until next transition @@ -108,7 +106,6 @@ class LightFlashTransformer : public LightTransformer { protected: LightState &state_; uint32_t transition_length_; - bool secondary_transition_occurred_{false}; std::unique_ptr transformer_{nullptr}; }; From a4d024f43d20b93e2ec69935f5703be1ca128446 Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Wed, 1 Sep 2021 19:16:11 -0500 Subject: [PATCH 1259/1841] Add is_on and is_off conditions for the fan component (#2225) Co-authored-by: Chris Nussbaum --- esphome/components/fan/__init__.py | 26 ++++++++++++++++++++++++++ esphome/components/fan/automation.h | 17 +++++++++++++++++ tests/test1.yaml | 3 +++ 3 files changed, 46 insertions(+) diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 27dee3271f..6bf0d1ca1a 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -42,6 +42,9 @@ ToggleAction = fan_ns.class_("ToggleAction", automation.Action) FanTurnOnTrigger = fan_ns.class_("FanTurnOnTrigger", automation.Trigger.template()) FanTurnOffTrigger = fan_ns.class_("FanTurnOffTrigger", automation.Trigger.template()) +FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template()) +FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.template()) + FAN_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(FanState), @@ -171,6 +174,29 @@ async def fan_turn_on_to_code(config, action_id, template_arg, args): return var +@automation.register_condition( + "fan.is_on", + FanIsOnCondition, + automation.maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(FanState), + } + ), +) +@automation.register_condition( + "fan.is_off", + FanIsOffCondition, + automation.maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(FanState), + } + ), +) +async def fan_is_on_off_to_code(config, condition_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(condition_id, template_arg, paren) + + @coroutine_with_priority(100.0) async def to_code(config): cg.add_define("USE_FAN") diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index 29a8e8d992..abcad82569 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -50,6 +50,23 @@ template class ToggleAction : public Action { FanState *state_; }; +template class FanIsOnCondition : public Condition { + public: + explicit FanIsOnCondition(FanState *state) : state_(state) {} + bool check(Ts... x) override { return this->state_->state; } + + protected: + FanState *state_; +}; +template class FanIsOffCondition : public Condition { + public: + explicit FanIsOffCondition(FanState *state) : state_(state) {} + bool check(Ts... x) override { return !this->state_->state; } + + protected: + FanState *state_; +}; + class FanTurnOnTrigger : public Trigger<> { public: FanTurnOnTrigger(FanState *state) { diff --git a/tests/test1.yaml b/tests/test1.yaml index a3f7a97281..f26e441192 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -129,6 +129,8 @@ mqtt: - mqtt.connected: - light.is_on: kitchen - light.is_off: kitchen + - fan.is_on: fan_speed + - fan.is_off: fan_speed then: - lambda: |- int data = x["my_data"]; @@ -1868,6 +1870,7 @@ fan: oscillation_output: gpio_19 direction_output: gpio_26 - platform: speed + id: fan_speed output: pca_6 speed_count: 10 name: 'Living Room Fan 2' From 910f812737174f9c2c85097248c856f9deedcf8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Sep 2021 16:20:42 +1200 Subject: [PATCH 1260/1841] Bump pylint from 2.9.6 to 2.10.2 (#2197) Bumps [pylint](https://github.com/PyCQA/pylint) from 2.9.6 to 2.10.2. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - [Commits](https://github.com/PyCQA/pylint/compare/v2.9.6...v2.10.2) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index b0de4b727b..59085b33e2 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.9.6 +pylint==2.10.2 flake8==3.9.2 black==21.8b0 pexpect==4.8.0 From b01bc76dc5a48f24c575db4ae4b0019058399ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Fri, 3 Sep 2021 06:37:18 +0200 Subject: [PATCH 1261/1841] mqtt_sensor: properly send state_class via MQTT (#2228) --- esphome/components/mqtt/mqtt_sensor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index ce7e89c584..d440e30fc4 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -61,8 +61,8 @@ void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo if (this->sensor_->get_force_update()) root["force_update"] = true; - if (this->sensor_->state_class == sensor::STATE_CLASS_MEASUREMENT) - root["state_class"] = "measurement"; + if (this->sensor_->state_class != STATE_CLASS_NONE) + root["state_class"] = state_class_to_string(this->sensor_->state_class); config.command_topic = false; } From 00aaf84c37a748ddf3744236c3c3100ad4206fab Mon Sep 17 00:00:00 2001 From: DAVe3283 Date: Thu, 2 Sep 2021 22:58:30 -0600 Subject: [PATCH 1262/1841] Fix uptime's state_class (esphome/issues#2337) (#2205) --- esphome/components/uptime/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/uptime/sensor.py b/esphome/components/uptime/sensor.py index 6ea3cca189..7989f3befc 100644 --- a/esphome/components/uptime/sensor.py +++ b/esphome/components/uptime/sensor.py @@ -3,7 +3,7 @@ import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( CONF_ID, - STATE_CLASS_NONE, + STATE_CLASS_TOTAL_INCREASING, UNIT_SECOND, ICON_TIMER, ) @@ -16,7 +16,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_SECOND, icon=ICON_TIMER, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, + state_class=STATE_CLASS_TOTAL_INCREASING, ) .extend( { From f364788c035174debd5b3a627358910fbc1792a9 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sat, 4 Sep 2021 04:32:33 +0200 Subject: [PATCH 1263/1841] Expose WHITE/CWWW/RGBCT color modes over MQTT (#2231) --- esphome/components/mqtt/mqtt_light.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index b702e6e425..f53be9c010 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -38,18 +38,23 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover root["color_mode"] = true; JsonArray &color_modes = root.createNestedArray("supported_color_modes"); - if (traits.supports_color_mode(ColorMode::COLOR_TEMPERATURE)) + if (traits.supports_color_mode(ColorMode::ON_OFF)) + color_modes.add("onoff"); + if (traits.supports_color_mode(ColorMode::BRIGHTNESS)) + color_modes.add("brightness"); + if (traits.supports_color_mode(ColorMode::WHITE)) + color_modes.add("white"); + if (traits.supports_color_mode(ColorMode::COLOR_TEMPERATURE) || + traits.supports_color_mode(ColorMode::COLD_WARM_WHITE)) color_modes.add("color_temp"); if (traits.supports_color_mode(ColorMode::RGB)) color_modes.add("rgb"); - if (traits.supports_color_mode(ColorMode::RGB_WHITE)) + if (traits.supports_color_mode(ColorMode::RGB_WHITE) || + // HA doesn't support RGBCT, and there's no CWWW->CT emulation in ESPHome yet, so ignore CT control for now + traits.supports_color_mode(ColorMode::RGB_COLOR_TEMPERATURE)) color_modes.add("rgbw"); if (traits.supports_color_mode(ColorMode::RGB_COLD_WARM_WHITE)) color_modes.add("rgbww"); - if (traits.supports_color_mode(ColorMode::BRIGHTNESS)) - color_modes.add("brightness"); - if (traits.supports_color_mode(ColorMode::ON_OFF)) - color_modes.add("onoff"); // legacy API if (traits.supports_color_capability(ColorCapability::BRIGHTNESS)) From 54de0ca0da91c2abc8fb6fd4d5144bf0af811656 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sat, 4 Sep 2021 04:46:53 +0200 Subject: [PATCH 1264/1841] Reject template select/number/switches that don't handle user input (#2230) --- .../components/template/number/__init__.py | 11 +++-- .../components/template/select/__init__.py | 22 ++++++++-- .../components/template/switch/__init__.py | 43 ++++++++++++++----- tests/test1.yaml | 1 + 4 files changed, 58 insertions(+), 19 deletions(-) diff --git a/esphome/components/template/number/__init__.py b/esphome/components/template/number/__init__.py index 22bbaacc15..887f6b15ad 100644 --- a/esphome/components/template/number/__init__.py +++ b/esphome/components/template/number/__init__.py @@ -29,12 +29,16 @@ def validate_min_max(config): def validate(config): if CONF_LAMBDA in config: - if CONF_OPTIMISTIC in config: + if config[CONF_OPTIMISTIC]: raise cv.Invalid("optimistic cannot be used with lambda") if CONF_INITIAL_VALUE in config: raise cv.Invalid("initial_value cannot be used with lambda") if CONF_RESTORE_VALUE in config: raise cv.Invalid("restore_value cannot be used with lambda") + if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config: + raise cv.Invalid( + "Either optimistic mode must be enabled, or set_action must be set, to handle the number being set." + ) return config @@ -46,7 +50,7 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_MIN_VALUE): cv.float_, cv.Required(CONF_STEP): cv.positive_float, cv.Optional(CONF_LAMBDA): cv.returning_lambda, - cv.Optional(CONF_OPTIMISTIC): cv.boolean, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_INITIAL_VALUE): cv.float_, cv.Optional(CONF_RESTORE_VALUE): cv.boolean, @@ -75,8 +79,7 @@ async def to_code(config): cg.add(var.set_template(template_)) else: - if CONF_OPTIMISTIC in config: - cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) if CONF_INITIAL_VALUE in config: cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) if CONF_RESTORE_VALUE in config: diff --git a/esphome/components/template/select/__init__.py b/esphome/components/template/select/__init__.py index 3a707628a8..6562beb7f8 100644 --- a/esphome/components/template/select/__init__.py +++ b/esphome/components/template/select/__init__.py @@ -30,6 +30,21 @@ def validate_initial_value_in_options(config): return config +def validate(config): + if CONF_LAMBDA in config: + if config[CONF_OPTIMISTIC]: + raise cv.Invalid("optimistic cannot be used with lambda") + if CONF_INITIAL_OPTION in config: + raise cv.Invalid("initial_value cannot be used with lambda") + if CONF_RESTORE_VALUE in config: + raise cv.Invalid("restore_value cannot be used with lambda") + if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config: + raise cv.Invalid( + "Either optimistic mode must be enabled, or set_action must be set, to handle the option being set." + ) + return config + + CONFIG_SCHEMA = cv.All( select.SELECT_SCHEMA.extend( { @@ -38,13 +53,14 @@ CONFIG_SCHEMA = cv.All( cv.ensure_list(cv.string_strict), cv.Length(min=1) ), cv.Optional(CONF_LAMBDA): cv.returning_lambda, - cv.Optional(CONF_OPTIMISTIC): cv.boolean, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_INITIAL_OPTION): cv.string_strict, cv.Optional(CONF_RESTORE_VALUE): cv.boolean, } ).extend(cv.polling_component_schema("60s")), validate_initial_value_in_options, + validate, ) @@ -60,9 +76,7 @@ async def to_code(config): cg.add(var.set_template(template_)) else: - if CONF_OPTIMISTIC in config: - cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) - + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) cg.add(var.set_initial_option(config[CONF_INITIAL_OPTION])) if CONF_RESTORE_VALUE in config: diff --git a/esphome/components/template/switch/__init__.py b/esphome/components/template/switch/__init__.py index b00710dfb7..6095a7c561 100644 --- a/esphome/components/template/switch/__init__.py +++ b/esphome/components/template/switch/__init__.py @@ -16,17 +16,38 @@ from .. import template_ns TemplateSwitch = template_ns.class_("TemplateSwitch", switch.Switch, cg.Component) -CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(TemplateSwitch), - cv.Optional(CONF_LAMBDA): cv.returning_lambda, - cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, - cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean, - cv.Optional(CONF_TURN_OFF_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_TURN_ON_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_RESTORE_STATE, default=False): cv.boolean, - } -).extend(cv.COMPONENT_SCHEMA) + +def validate(config): + if ( + not config[CONF_OPTIMISTIC] + and CONF_TURN_ON_ACTION not in config + and CONF_TURN_OFF_ACTION not in config + ): + raise cv.Invalid( + "Either optimistic mode must be enabled, or turn_on_action or turn_off_action must be set, " + "to handle the switch being set." + ) + return config + + +CONFIG_SCHEMA = cv.All( + switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TemplateSwitch), + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean, + cv.Optional(CONF_TURN_OFF_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_TURN_ON_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_RESTORE_STATE, default=False): cv.boolean, + } + ).extend(cv.COMPONENT_SCHEMA), + validate, +) async def to_code(config): diff --git a/tests/test1.yaml b/tests/test1.yaml index f26e441192..bfdf9c3bea 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1862,6 +1862,7 @@ switch: inverted: False - platform: template id: ble1_status + optimistic: true fan: - platform: binary From 77508f7e44c56b4e64291ead4cd33b79d92c4d52 Mon Sep 17 00:00:00 2001 From: Christian Ferbar <5595808+ferbar@users.noreply.github.com> Date: Sat, 4 Sep 2021 04:49:34 +0200 Subject: [PATCH 1265/1841] Fix UARTComponent hardware vs software UART0 conflict (#2229) Co-authored-by: Oxan van Leeuwen --- esphome/components/uart/uart.h | 5 +++++ esphome/components/uart/uart_esp8266.cpp | 28 +++++++++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index 1dc1e18412..8ac00658f4 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -118,6 +118,11 @@ class UARTComponent : public Component, public Stream { uint8_t stop_bits_; uint8_t data_bits_; UARTParityOptions parity_; + + private: +#ifdef ARDUINO_ARCH_ESP8266 + static bool serial0InUse; +#endif }; #ifdef ARDUINO_ARCH_ESP32 diff --git a/esphome/components/uart/uart_esp8266.cpp b/esphome/components/uart/uart_esp8266.cpp index c45f48644c..5cb625f2ff 100644 --- a/esphome/components/uart/uart_esp8266.cpp +++ b/esphome/components/uart/uart_esp8266.cpp @@ -4,11 +4,17 @@ #include "esphome/core/helpers.h" #include "esphome/core/application.h" #include "esphome/core/defines.h" -# + +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#endif + namespace esphome { namespace uart { static const char *const TAG = "uart_esp8266"; +bool UARTComponent::serial0InUse = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + uint32_t UARTComponent::get_config() { uint32_t config = 0; @@ -49,15 +55,31 @@ void UARTComponent::setup() { // is 1 we still want to use Serial. SerialConfig config = static_cast(get_config()); - if (this->tx_pin_.value_or(1) == 1 && this->rx_pin_.value_or(3) == 3) { + if (!UARTComponent::serial0InUse && this->tx_pin_.value_or(1) == 1 && + this->rx_pin_.value_or(3) == 3 +#ifdef USE_LOGGER + // we will use UART0 if logger isn't using it in swapped mode + && (logger::global_logger->get_hw_serial() == nullptr || + logger::global_logger->get_uart() != logger::UART_SELECTION_UART0_SWAP) +#endif + ) { this->hw_serial_ = &Serial; this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); - } else if (this->tx_pin_.value_or(15) == 15 && this->rx_pin_.value_or(13) == 13) { + UARTComponent::serial0InUse = true; + } else if (!UARTComponent::serial0InUse && this->tx_pin_.value_or(15) == 15 && + this->rx_pin_.value_or(13) == 13 +#ifdef USE_LOGGER + // we will use UART0 swapped if logger isn't using it in regular mode + && (logger::global_logger->get_hw_serial() == nullptr || + logger::global_logger->get_uart() != logger::UART_SELECTION_UART0) +#endif + ) { this->hw_serial_ = &Serial; this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); this->hw_serial_->swap(); + UARTComponent::serial0InUse = true; } else if (this->tx_pin_.value_or(2) == 2 && this->rx_pin_.value_or(8) == 8) { this->hw_serial_ = &Serial1; this->hw_serial_->begin(this->baud_rate_, config); From ca12b8aa5644cc551508650ce7e7c4bef34c7888 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Sep 2021 08:22:15 +1200 Subject: [PATCH 1266/1841] Move to use zeroconf library instead of inline copy (#2192) --- esphome/dashboard/dashboard.py | 4 +- esphome/helpers.py | 4 +- esphome/zeroconf.py | 772 +++------------------------------ 3 files changed, 53 insertions(+), 727 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 8016bf7bbd..a5e9766eea 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -41,7 +41,7 @@ from .util import password_hash # pylint: disable=unused-import, wrong-import-order from typing import Optional # noqa -from esphome.zeroconf import DashboardStatus, Zeroconf +from esphome.zeroconf import DashboardStatus, EsphomeZeroconf _LOGGER = logging.getLogger(__name__) @@ -501,7 +501,7 @@ def _ping_func(filename, address): class MDNSStatusThread(threading.Thread): def run(self): - zc = Zeroconf() + zc = EsphomeZeroconf() def on_update(dat): for key, b in dat.items(): diff --git a/esphome/helpers.py b/esphome/helpers.py index c766cc1ac7..a1cb4367c5 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -97,10 +97,10 @@ def is_ip_address(host): def _resolve_with_zeroconf(host): from esphome.core import EsphomeError - from esphome.zeroconf import Zeroconf + from esphome.zeroconf import EsphomeZeroconf try: - zc = Zeroconf() + zc = EsphomeZeroconf() except Exception as err: raise EsphomeError( "Cannot start mDNS sockets, is this a docker container without " diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index a44c7c9114..bc3f905261 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -1,520 +1,27 @@ -# Custom zeroconf implementation based on python-zeroconf -# (https://github.com/jstasiak/python-zeroconf) that supports Python 2 - -import errno -import logging -import select import socket -import struct -import sys import threading import time - -import ifaddr - -log = logging.getLogger(__name__) - -# Some timing constants - -_LISTENER_TIME = 200 - -# Some DNS constants - -_MDNS_ADDR = "224.0.0.251" -_MDNS_PORT = 5353 - -_MAX_MSG_ABSOLUTE = 8966 - -_FLAGS_QR_MASK = 0x8000 # query response mask -_FLAGS_QR_QUERY = 0x0000 # query -_FLAGS_QR_RESPONSE = 0x8000 # response - -_FLAGS_AA = 0x0400 # Authoritative answer -_FLAGS_TC = 0x0200 # Truncated -_FLAGS_RD = 0x0100 # Recursion desired -_FLAGS_RA = 0x8000 # Recursion available - -_FLAGS_Z = 0x0040 # Zero -_FLAGS_AD = 0x0020 # Authentic data -_FLAGS_CD = 0x0010 # Checking disabled - -_CLASS_IN = 1 -_CLASS_CS = 2 -_CLASS_CH = 3 -_CLASS_HS = 4 -_CLASS_NONE = 254 -_CLASS_ANY = 255 -_CLASS_MASK = 0x7FFF -_CLASS_UNIQUE = 0x8000 - -_TYPE_A = 1 -_TYPE_NS = 2 -_TYPE_MD = 3 -_TYPE_MF = 4 -_TYPE_CNAME = 5 -_TYPE_SOA = 6 -_TYPE_MB = 7 -_TYPE_MG = 8 -_TYPE_MR = 9 -_TYPE_NULL = 10 -_TYPE_WKS = 11 -_TYPE_PTR = 12 -_TYPE_HINFO = 13 -_TYPE_MINFO = 14 -_TYPE_MX = 15 -_TYPE_TXT = 16 -_TYPE_AAAA = 28 -_TYPE_SRV = 33 -_TYPE_ANY = 255 - -# Mapping constants to names -int2byte = struct.Struct(">B").pack - - -# Exceptions -class Error(Exception): - pass - - -class IncomingDecodeError(Error): - pass - - -# pylint: disable=no-init -class QuietLogger: - _seen_logs = {} - - @classmethod - def log_exception_warning(cls, logger_data=None): - exc_info = sys.exc_info() - exc_str = str(exc_info[1]) - if exc_str not in cls._seen_logs: - # log at warning level the first time this is seen - cls._seen_logs[exc_str] = exc_info - logger = log.warning - else: - logger = log.debug - if logger_data is not None: - logger(*logger_data) - logger("Exception occurred:", exc_info=True) - - @classmethod - def log_warning_once(cls, *args): - msg_str = args[0] - if msg_str not in cls._seen_logs: - cls._seen_logs[msg_str] = 0 - logger = log.warning - else: - logger = log.debug - cls._seen_logs[msg_str] += 1 - logger(*args) - - -class DNSEntry: - """A DNS entry""" - - def __init__(self, name, type_, class_): - self.key = name.lower() - self.name = name - self.type = type_ - self.class_ = class_ & _CLASS_MASK - self.unique = (class_ & _CLASS_UNIQUE) != 0 - - -class DNSQuestion(DNSEntry): - """A DNS question entry""" - - def __init__(self, name, type_, class_): - DNSEntry.__init__(self, name, type_, class_) - - def answered_by(self, rec): - """Returns true if the question is answered by the record""" - return ( - self.class_ == rec.class_ - and (self.type == rec.type or self.type == _TYPE_ANY) - and self.name == rec.name - ) - - -class DNSRecord(DNSEntry): - """A DNS record - like a DNS entry, but has a TTL""" - - def __init__(self, name, type_, class_, ttl): - DNSEntry.__init__(self, name, type_, class_) - self.ttl = 15 - self.created = time.time() - - def write(self, out): - """Abstract method""" - raise NotImplementedError - - def is_expired(self, now): - return self.created + self.ttl <= now - - def is_removable(self, now): - return self.created + self.ttl * 2 <= now - - -class DNSAddress(DNSRecord): - """A DNS address record""" - - def __init__(self, name, type_, class_, ttl, address): - DNSRecord.__init__(self, name, type_, class_, ttl) - self.address = address - - def write(self, out): - """Used in constructing an outgoing packet""" - out.write_string(self.address) - - -class DNSText(DNSRecord): - """A DNS text record""" - - def __init__(self, name, type_, class_, ttl, text): - assert isinstance(text, (bytes, type(None))) - DNSRecord.__init__(self, name, type_, class_, ttl) - self.text = text - - def write(self, out): - """Used in constructing an outgoing packet""" - out.write_string(self.text) - - -class DNSIncoming(QuietLogger): - """Object representation of an incoming DNS packet""" - - def __init__(self, data): - """Constructor from string holding bytes of packet""" - self.offset = 0 - self.data = data - self.questions = [] - self.answers = [] - self.id = 0 - self.flags = 0 # type: int - self.num_questions = 0 - self.num_answers = 0 - self.num_authorities = 0 - self.num_additionals = 0 - self.valid = False - - try: - self.read_header() - self.read_questions() - self.read_others() - self.valid = True - - except (IndexError, struct.error, IncomingDecodeError): - self.log_exception_warning( - ("Choked at offset %d while unpacking %r", self.offset, data) - ) - - def unpack(self, format_): - length = struct.calcsize(format_) - info = struct.unpack(format_, self.data[self.offset : self.offset + length]) - self.offset += length - return info - - def read_header(self): - """Reads header portion of packet""" - ( - self.id, - self.flags, - self.num_questions, - self.num_answers, - self.num_authorities, - self.num_additionals, - ) = self.unpack(b"!6H") - - def read_questions(self): - """Reads questions section of packet""" - for _ in range(self.num_questions): - name = self.read_name() - type_, class_ = self.unpack(b"!HH") - - question = DNSQuestion(name, type_, class_) - self.questions.append(question) - - def read_character_string(self): - """Reads a character string from the packet""" - length = self.data[self.offset] - self.offset += 1 - return self.read_string(length) - - def read_string(self, length): - """Reads a string of a given length from the packet""" - info = self.data[self.offset : self.offset + length] - self.offset += length - return info - - def read_unsigned_short(self): - """Reads an unsigned short from the packet""" - return self.unpack(b"!H")[0] - - def read_others(self): - """Reads the answers, authorities and additionals section of the - packet""" - n = self.num_answers + self.num_authorities + self.num_additionals - for _ in range(n): - domain = self.read_name() - type_, class_, ttl, length = self.unpack(b"!HHiH") - - rec = None - if type_ == _TYPE_A: - rec = DNSAddress(domain, type_, class_, ttl, self.read_string(4)) - elif type_ == _TYPE_TXT: - rec = DNSText(domain, type_, class_, ttl, self.read_string(length)) - elif type_ == _TYPE_AAAA: - rec = DNSAddress(domain, type_, class_, ttl, self.read_string(16)) - else: - # Try to ignore types we don't know about - # Skip the payload for the resource record so the next - # records can be parsed correctly - self.offset += length - - if rec is not None: - self.answers.append(rec) - - def is_query(self): - """Returns true if this is a query""" - return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY - - def is_response(self): - """Returns true if this is a response""" - return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE - - def read_utf(self, offset, length): - """Reads a UTF-8 string of a given length from the packet""" - return str(self.data[offset : offset + length], "utf-8", "replace") - - def read_name(self): - """Reads a domain name from the packet""" - result = "" - off = self.offset - next_ = -1 - first = off - - while True: - length = self.data[off] - off += 1 - if length == 0: - break - t = length & 0xC0 - if t == 0x00: - result = "".join((result, self.read_utf(off, length) + ".")) - off += length - elif t == 0xC0: - if next_ < 0: - next_ = off + 1 - off = ((length & 0x3F) << 8) | self.data[off] - if off >= first: - raise IncomingDecodeError(f"Bad domain name (circular) at {off}") - first = off - else: - raise IncomingDecodeError(f"Bad domain name at {off}") - - if next_ >= 0: - self.offset = next_ - else: - self.offset = off - - return result - - -class DNSOutgoing: - """Object representation of an outgoing packet""" - - def __init__(self, flags): - self.finished = False - self.id = 0 - self.flags = flags - self.names = {} - self.data = [] - self.size = 12 - self.state = False - - self.questions = [] - self.answers = [] - - def add_question(self, record): - """Adds a question""" - self.questions.append(record) - - def pack(self, format_, value): - self.data.append(struct.pack(format_, value)) - self.size += struct.calcsize(format_) - - def write_byte(self, value): - """Writes a single byte to the packet""" - self.pack(b"!c", int2byte(value)) - - def insert_short(self, index, value): - """Inserts an unsigned short in a certain position in the packet""" - self.data.insert(index, struct.pack(b"!H", value)) - self.size += 2 - - def write_short(self, value): - """Writes an unsigned short to the packet""" - self.pack(b"!H", value) - - def write_int(self, value): - """Writes an unsigned integer to the packet""" - self.pack(b"!I", int(value)) - - def write_string(self, value): - """Writes a string to the packet""" - assert isinstance(value, bytes) - self.data.append(value) - self.size += len(value) - - def write_utf(self, s): - """Writes a UTF-8 string of a given length to the packet""" - utfstr = s.encode("utf-8") - length = len(utfstr) - self.write_byte(length) - self.write_string(utfstr) - - def write_character_string(self, value): - assert isinstance(value, bytes) - length = len(value) - self.write_byte(length) - self.write_string(value) - - def write_name(self, name): - # split name into each label - parts = name.split(".") - if not parts[-1]: - parts.pop() - - # construct each suffix - name_suffices = [".".join(parts[i:]) for i in range(len(parts))] - - # look for an existing name or suffix - for count, sub_name in enumerate(name_suffices): - if sub_name in self.names: - break - else: - count = len(name_suffices) - - # note the new names we are saving into the packet - name_length = len(name.encode("utf-8")) - for suffix in name_suffices[:count]: - self.names[suffix] = ( - self.size + name_length - len(suffix.encode("utf-8")) - 1 - ) - - # write the new names out. - for part in parts[:count]: - self.write_utf(part) - - # if we wrote part of the name, create a pointer to the rest - if count != len(name_suffices): - # Found substring in packet, create pointer - index = self.names[name_suffices[count]] - self.write_byte((index >> 8) | 0xC0) - self.write_byte(index & 0xFF) - else: - # this is the end of a name - self.write_byte(0) - - def write_question(self, question): - self.write_name(question.name) - self.write_short(question.type) - self.write_short(question.class_) - - def packet(self): - if not self.state: - for question in self.questions: - self.write_question(question) - self.state = True - - self.insert_short(0, 0) # num additionals - self.insert_short(0, 0) # num authorities - self.insert_short(0, 0) # num answers - self.insert_short(0, len(self.questions)) - self.insert_short(0, self.flags) # _FLAGS_QR_QUERY - self.insert_short(0, 0) - return b"".join(self.data) - - -class Engine(threading.Thread): - def __init__(self, zc): - threading.Thread.__init__(self, name="zeroconf-Engine") - self.daemon = True - self.zc = zc - self.readers = {} - self.timeout = 5 - self.condition = threading.Condition() - self.start() - - def run(self): - while not self.zc.done: - # pylint: disable=len-as-condition - with self.condition: - rs = self.readers.keys() - if len(rs) == 0: - # No sockets to manage, but we wait for the timeout - # or addition of a socket - self.condition.wait(self.timeout) - - if len(rs) != 0: - try: - rr, _, _ = select.select(rs, [], [], self.timeout) - if not self.zc.done: - for socket_ in rr: - reader = self.readers.get(socket_) - if reader: - reader.handle_read(socket_) - - except OSError as e: - # If the socket was closed by another thread, during - # shutdown, ignore it and exit - if e.args[0] != socket.EBADF or not self.zc.done: - raise - - def add_reader(self, reader, socket_): - with self.condition: - self.readers[socket_] = reader - self.condition.notify() - - def del_reader(self, socket_): - with self.condition: - del self.readers[socket_] - self.condition.notify() - - -class Listener(QuietLogger): - def __init__(self, zc): - self.zc = zc - self.data = None - - def handle_read(self, socket_): - try: - data, (addr, port) = socket_.recvfrom(_MAX_MSG_ABSOLUTE) - except Exception: # pylint: disable=broad-except - self.log_exception_warning() - return - - log.debug("Received from %r:%r: %r ", addr, port, data) - - self.data = data - msg = DNSIncoming(data) - if not msg.valid or msg.is_query(): - pass - else: - self.zc.handle_response(msg) - - -class RecordUpdateListener: - def update_record(self, zc, now, record): - raise NotImplementedError() +from typing import Optional + +from zeroconf import ( + _CLASS_IN, + _FLAGS_QR_QUERY, + _TYPE_A, + DNSAddress, + DNSOutgoing, + DNSRecord, + DNSQuestion, + RecordUpdateListener, + Zeroconf, +) class HostResolver(RecordUpdateListener): - def __init__(self, name): + def __init__(self, name: str): self.name = name - self.address = None + self.address: Optional[bytes] = None - def update_record(self, zc, now, record): + def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None: if record is None: return if record.type == _TYPE_A: @@ -522,14 +29,14 @@ class HostResolver(RecordUpdateListener): if record.name == self.name: self.address = record.address - def request(self, zc, timeout): + def request(self, zc: Zeroconf, timeout: float) -> bool: now = time.time() delay = 0.2 next_ = now + delay last = now + timeout try: - zc.add_listener(self) + zc.add_listener(self, None) while self.address is None: if last <= now: # Timeout @@ -550,56 +57,52 @@ class HostResolver(RecordUpdateListener): class DashboardStatus(RecordUpdateListener, threading.Thread): - def __init__(self, zc, on_update): + PING_AFTER = 15 * 1000 # Send new mDNS request after 15 seconds + OFFLINE_AFTER = PING_AFTER * 2 # Offline if no mDNS response after 30 seconds + + def __init__(self, zc: Zeroconf, on_update) -> None: threading.Thread.__init__(self) self.zc = zc - self.query_hosts = set() - self.key_to_host = {} - self.cache = {} + self.query_hosts: set[str] = set() + self.key_to_host: dict[str, str] = {} self.stop_event = threading.Event() self.query_event = threading.Event() self.on_update = on_update - def update_record(self, zc, now, record): - if record is None: - return - if record.type in (_TYPE_A, _TYPE_AAAA, _TYPE_TXT): - assert isinstance(record, DNSEntry) - if record.name in self.query_hosts: - self.cache.setdefault(record.name, []).insert(0, record) - self.purge_cache() + def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None: + pass - def purge_cache(self): - new_cache = {} - for host, records in self.cache.items(): - if host not in self.query_hosts: - continue - new_records = [rec for rec in records if not rec.is_removable(time.time())] - if new_records: - new_cache[host] = new_records - self.cache = new_cache - self.on_update({key: self.host_status(key) for key in self.key_to_host}) - - def request_query(self, hosts): + def request_query(self, hosts: dict[str, str]) -> None: self.query_hosts = set(hosts.values()) self.key_to_host = hosts self.query_event.set() - def stop(self): + def stop(self) -> None: self.stop_event.set() self.query_event.set() - def host_status(self, key): - return self.key_to_host.get(key) in self.cache + def host_status(self, key: str) -> bool: + entries = self.zc.cache.entries_with_name(key) + if not entries: + return False + now = time.time() * 1000 - def run(self): - self.zc.add_listener(self) + return any( + (entry.created + DashboardStatus.OFFLINE_AFTER) >= now for entry in entries + ) + + def run(self) -> None: + self.zc.add_listener(self, None) while not self.stop_event.is_set(): - self.purge_cache() + self.on_update( + {key: self.host_status(host) for key, host in self.key_to_host.items()} + ) + now = time.time() * 1000 for host in self.query_hosts: - if all( - record.is_expired(time.time()) - for record in self.cache.get(host, []) + entries = self.zc.cache.entries_with_name(host) + if not entries or all( + (entry.created + DashboardStatus.PING_AFTER) <= now + for entry in entries ): out = DNSOutgoing(_FLAGS_QR_QUERY) out.add_question(DNSQuestion(host, _TYPE_A, _CLASS_IN)) @@ -609,186 +112,9 @@ class DashboardStatus(RecordUpdateListener, threading.Thread): self.zc.remove_listener(self) -def get_all_addresses(): - return list( - { - addr.ip - for iface in ifaddr.get_adapters() - for addr in iface.ips - if addr.is_IPv4 - and addr.network_prefix != 32 # Host only netmask 255.255.255.255 - } - ) - - -def new_socket(): - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - - # SO_REUSEADDR should be equivalent to SO_REUSEPORT for - # multicast UDP sockets (p 731, "TCP/IP Illustrated, - # Volume 2"), but some BSD-derived systems require - # SO_REUSEPORT to be specified explicitly. Also, not all - # versions of Python have SO_REUSEPORT available. - # Catch OSError and socket.error for kernel versions <3.9 because lacking - # SO_REUSEPORT support. - try: - reuseport = socket.SO_REUSEPORT - except AttributeError: - pass - else: - try: - s.setsockopt(socket.SOL_SOCKET, reuseport, 1) - except OSError as err: - # OSError on python 3, socket.error on python 2 - if err.errno != errno.ENOPROTOOPT: - raise - - # OpenBSD needs the ttl and loop values for the IP_MULTICAST_TTL and - # IP_MULTICAST_LOOP socket options as an unsigned char. - ttl = struct.pack(b"B", 255) - s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl) - loop = struct.pack(b"B", 1) - s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, loop) - - s.bind(("", _MDNS_PORT)) - return s - - -class Zeroconf(QuietLogger): - def __init__(self): - # hook for threads - self._GLOBAL_DONE = False - - self._listen_socket = new_socket() - interfaces = get_all_addresses() - - self._respond_sockets = [] - - for i in interfaces: - try: - _value = socket.inet_aton(_MDNS_ADDR) + socket.inet_aton(i) - self._listen_socket.setsockopt( - socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, _value - ) - except OSError as e: - _errno = e.args[0] - if _errno == errno.EADDRINUSE: - log.info( - "Address in use when adding %s to multicast group, " - "it is expected to happen on some systems", - i, - ) - elif _errno == errno.EADDRNOTAVAIL: - log.info( - "Address not available when adding %s to multicast " - "group, it is expected to happen on some systems", - i, - ) - continue - elif _errno == errno.EINVAL: - log.info( - "Interface of %s does not support multicast, " - "it is expected in WSL", - i, - ) - continue - - else: - raise - - respond_socket = new_socket() - respond_socket.setsockopt( - socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(i) - ) - - self._respond_sockets.append(respond_socket) - - self.listeners = [] - - self.condition = threading.Condition() - - self.engine = Engine(self) - self.listener = Listener(self) - self.engine.add_reader(self.listener, self._listen_socket) - - @property - def done(self): - return self._GLOBAL_DONE - - def wait(self, timeout): - """Calling thread waits for a given number of milliseconds or - until notified.""" - with self.condition: - self.condition.wait(timeout) - - def notify_all(self): - """Notifies all waiting threads""" - with self.condition: - self.condition.notify_all() - - def resolve_host(self, host, timeout=3.0): +class EsphomeZeroconf(Zeroconf): + def resolve_host(self, host: str, timeout=3.0): info = HostResolver(host) if info.request(self, timeout): return socket.inet_ntoa(info.address) return None - - def add_listener(self, listener): - self.listeners.append(listener) - self.notify_all() - - def remove_listener(self, listener): - """Removes a listener.""" - try: - self.listeners.remove(listener) - self.notify_all() - except Exception as e: # pylint: disable=broad-except - log.exception("Unknown error, possibly benign: %r", e) - - def update_record(self, now, rec): - """Used to notify listeners of new information that has updated - a record.""" - for listener in self.listeners: - listener.update_record(self, now, rec) - self.notify_all() - - def handle_response(self, msg): - """Deal with incoming response packets. All answers - are held in the cache, and listeners are notified.""" - now = time.time() - for record in msg.answers: - self.update_record(now, record) - - def send(self, out): - """Sends an outgoing packet.""" - packet = out.packet() - log.debug("Sending %r (%d bytes) as %r...", out, len(packet), packet) - for s in self._respond_sockets: - if self._GLOBAL_DONE: - return - try: - bytes_sent = s.sendto(packet, 0, (_MDNS_ADDR, _MDNS_PORT)) - except Exception: # pylint: disable=broad-except - # on send errors, log the exception and keep going - self.log_exception_warning() - else: - if bytes_sent != len(packet): - self.log_warning_once( - "!!! sent %d out of %d bytes to %r" - % (bytes_sent, len(packet), s) - ) - - def close(self): - """Ends the background threads, and prevent this instance from - servicing further queries.""" - if not self._GLOBAL_DONE: - self._GLOBAL_DONE = True - # shutdown recv socket and thread - self.engine.del_reader(self._listen_socket) - self._listen_socket.close() - self.engine.join() - - # shutdown the rest - self.notify_all() - for s in self._respond_sockets: - s.close() From f9b0666adf760591444301d60662ea2d302d96e5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Sep 2021 08:23:06 +1200 Subject: [PATCH 1267/1841] Allow using a git source for a package (#2193) --- .../external_components/__init__.py | 91 ++----------- esphome/components/packages/__init__.py | 125 +++++++++++++++++- esphome/config.py | 4 + esphome/config_validation.py | 25 ++-- esphome/const.py | 3 + esphome/git.py | 74 +++++++++++ 6 files changed, 231 insertions(+), 91 deletions(-) create mode 100644 esphome/git.py diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index 8f6e2bece4..110a8d95ed 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -1,13 +1,12 @@ import re import logging from pathlib import Path -import subprocess -import hashlib -import datetime import esphome.config_validation as cv from esphome.const import ( CONF_COMPONENTS, + CONF_REF, + CONF_REFRESH, CONF_SOURCE, CONF_URL, CONF_TYPE, @@ -15,7 +14,7 @@ from esphome.const import ( CONF_PATH, ) from esphome.core import CORE -from esphome import loader +from esphome import git, loader _LOGGER = logging.getLogger(__name__) @@ -23,19 +22,11 @@ DOMAIN = CONF_EXTERNAL_COMPONENTS TYPE_GIT = "git" TYPE_LOCAL = "local" -CONF_REFRESH = "refresh" -CONF_REF = "ref" - - -def validate_git_ref(value): - if re.match(r"[a-zA-Z0-9\-_.\./]+", value) is None: - raise cv.Invalid("Not a valid git ref") - return value GIT_SCHEMA = { cv.Required(CONF_URL): cv.url, - cv.Optional(CONF_REF): validate_git_ref, + cv.Optional(CONF_REF): cv.git_ref, } LOCAL_SCHEMA = { cv.Required(CONF_PATH): cv.directory, @@ -68,14 +59,6 @@ def validate_source_shorthand(value): return SOURCE_SCHEMA(conf) -def validate_refresh(value: str): - if value.lower() == "always": - return validate_refresh("0s") - if value.lower() == "never": - return validate_refresh("1000y") - return cv.positive_time_period_seconds(value) - - SOURCE_SCHEMA = cv.Any( validate_source_shorthand, cv.typed_schema( @@ -90,7 +73,7 @@ SOURCE_SCHEMA = cv.Any( CONFIG_SCHEMA = cv.ensure_list( { cv.Required(CONF_SOURCE): SOURCE_SCHEMA, - cv.Optional(CONF_REFRESH, default="1d"): cv.All(cv.string, validate_refresh), + cv.Optional(CONF_REFRESH, default="1d"): cv.All(cv.string, cv.source_refresh), cv.Optional(CONF_COMPONENTS, default="all"): cv.Any( "all", cv.ensure_list(cv.string) ), @@ -102,65 +85,13 @@ async def to_code(config): pass -def _compute_destination_path(key: str) -> Path: - base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN - h = hashlib.new("sha256") - h.update(key.encode()) - return base_dir / h.hexdigest()[:8] - - -def _run_git_command(cmd, cwd=None): - try: - ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False) - except FileNotFoundError as err: - raise cv.Invalid( - "git is not installed but required for external_components.\n" - "Please see https://git-scm.com/book/en/v2/Getting-Started-Installing-Git for installing git" - ) from err - - if ret.returncode != 0 and ret.stderr: - err_str = ret.stderr.decode("utf-8") - lines = [x.strip() for x in err_str.splitlines()] - if lines[-1].startswith("fatal:"): - raise cv.Invalid(lines[-1][len("fatal: ") :]) - raise cv.Invalid(err_str) - - def _process_git_config(config: dict, refresh) -> str: - key = f"{config[CONF_URL]}@{config.get(CONF_REF)}" - repo_dir = _compute_destination_path(key) - if not repo_dir.is_dir(): - _LOGGER.info("Cloning %s", key) - _LOGGER.debug("Location: %s", repo_dir) - cmd = ["git", "clone", "--depth=1"] - if CONF_REF in config: - cmd += ["--branch", config[CONF_REF]] - cmd += ["--", config[CONF_URL], str(repo_dir)] - _run_git_command(cmd) - - else: - # Check refresh needed - file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD") - # On first clone, FETCH_HEAD does not exists - if not file_timestamp.exists(): - file_timestamp = Path(repo_dir / ".git" / "HEAD") - age = datetime.datetime.now() - datetime.datetime.fromtimestamp( - file_timestamp.stat().st_mtime - ) - if age.total_seconds() > refresh.total_seconds: - _LOGGER.info("Updating %s", key) - _LOGGER.debug("Location: %s", repo_dir) - # Stash local changes (if any) - _run_git_command( - ["git", "stash", "push", "--include-untracked"], str(repo_dir) - ) - # Fetch remote ref - cmd = ["git", "fetch", "--", "origin"] - if CONF_REF in config: - cmd.append(config[CONF_REF]) - _run_git_command(cmd, str(repo_dir)) - # Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch) - _run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir)) + repo_dir = git.clone_or_update( + url=config[CONF_URL], + ref=config.get(CONF_REF), + refresh=refresh, + domain=DOMAIN, + ) if (repo_dir / "esphome" / "components").is_dir(): components_dir = repo_dir / "esphome" / "components" diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index 8c5c9a0144..330ffc2bf2 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -1,6 +1,19 @@ +import re +from pathlib import Path +from esphome.core import EsphomeError + +from esphome import git, yaml_util +from esphome.const import ( + CONF_FILE, + CONF_FILES, + CONF_PACKAGES, + CONF_REF, + CONF_REFRESH, + CONF_URL, +) import esphome.config_validation as cv -from esphome.const import CONF_PACKAGES +DOMAIN = CONF_PACKAGES def _merge_package(full_old, full_new): @@ -23,11 +36,119 @@ def _merge_package(full_old, full_new): return merge(full_old, full_new) +def validate_git_package(config: dict): + new_config = config + for key, conf in config.items(): + if CONF_URL in conf: + try: + conf = BASE_SCHEMA(conf) + if CONF_FILE in conf: + new_config[key][CONF_FILES] = [conf[CONF_FILE]] + del new_config[key][CONF_FILE] + except cv.MultipleInvalid as e: + with cv.prepend_path([key]): + raise e + except cv.Invalid as e: + raise cv.Invalid( + "Extra keys not allowed in git based package", + path=[key] + e.path, + ) from e + return new_config + + +def validate_yaml_filename(value): + value = cv.string(value) + + if not (value.endswith(".yaml") or value.endswith(".yml")): + raise cv.Invalid("Only YAML (.yaml / .yml) files are supported.") + + return value + + +def validate_source_shorthand(value): + if not isinstance(value, str): + raise cv.Invalid("Shorthand only for strings") + + m = re.match( + r"github://([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)/([a-zA-Z0-9\-_.\./]+)(?:@([a-zA-Z0-9\-_.\./]+))?", + value, + ) + if m is None: + raise cv.Invalid( + "Source is not a file system path or in expected github://username/name/[sub-folder/]file-path.yml[@branch-or-tag] format!" + ) + + conf = { + CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git", + CONF_FILE: m.group(3), + } + if m.group(4): + conf[CONF_REF] = m.group(4) + + # print(conf) + return BASE_SCHEMA(conf) + + +BASE_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_URL): cv.url, + cv.Exclusive(CONF_FILE, "files"): validate_yaml_filename, + cv.Exclusive(CONF_FILES, "files"): cv.All( + cv.ensure_list(validate_yaml_filename), + cv.Length(min=1), + ), + cv.Optional(CONF_REF): cv.git_ref, + cv.Optional(CONF_REFRESH, default="1d"): cv.All( + cv.string, cv.source_refresh + ), + } + ), + cv.has_at_least_one_key(CONF_FILE, CONF_FILES), +) + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + str: cv.Any(validate_source_shorthand, BASE_SCHEMA, dict), + } + ), + validate_git_package, +) + + +def _process_base_package(config: dict) -> dict: + repo_dir = git.clone_or_update( + url=config[CONF_URL], + ref=config.get(CONF_REF), + refresh=config[CONF_REFRESH], + domain=DOMAIN, + ) + files: str = config[CONF_FILES] + + packages = {} + for file in files: + yaml_file: Path = repo_dir / file + + if not yaml_file.is_file(): + raise cv.Invalid(f"{file} does not exist in repository", path=[CONF_FILES]) + + try: + packages[file] = yaml_util.load_yaml(yaml_file) + except EsphomeError as e: + raise cv.Invalid( + f"{file} is not a valid YAML file. Please check the file contents." + ) from e + return {"packages": packages} + + def do_packages_pass(config: dict): if CONF_PACKAGES not in config: return config packages = config[CONF_PACKAGES] with cv.prepend_path(CONF_PACKAGES): + packages = CONFIG_SCHEMA(packages) if not isinstance(packages, dict): raise cv.Invalid( "Packages must be a key to value mapping, got {} instead" @@ -37,6 +158,8 @@ def do_packages_pass(config: dict): for package_name, package_config in packages.items(): with cv.prepend_path(package_name): recursive_package = package_config + if CONF_URL in package_config: + package_config = _process_base_package(package_config) if isinstance(package_config, dict): recursive_package = do_packages_pass(package_config) config = _merge_package(recursive_package, config) diff --git a/esphome/config.py b/esphome/config.py index 93413a009c..de261f7eba 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -333,6 +333,8 @@ def validate_config(config, command_line_substitutions): result.add_error(err) return result + CORE.raw_config = config + # 1. Load substitutions if CONF_SUBSTITUTIONS in config: from esphome.components import substitutions @@ -348,6 +350,8 @@ def validate_config(config, command_line_substitutions): result.add_error(err) return result + CORE.raw_config = config + # 1.1. Check for REPLACEME special value try: recursive_check_replaceme(config) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 4df65a38e3..fb659c41ea 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -33,7 +33,6 @@ from esphome.const import ( CONF_UPDATE_INTERVAL, CONF_TYPE_ID, CONF_TYPE, - CONF_PACKAGES, ) from esphome.core import ( CORE, @@ -1455,15 +1454,7 @@ class OnlyWith(Optional): @property def default(self): # pylint: disable=unsupported-membership-test - if self._component in CORE.raw_config or ( - CONF_PACKAGES in CORE.raw_config - and self._component - in [ - k - for package in CORE.raw_config[CONF_PACKAGES].values() - for k in package.keys() - ] - ): + if self._component in CORE.raw_config: return self._default return vol.UNDEFINED @@ -1633,3 +1624,17 @@ def url(value): if not parsed.scheme or not parsed.netloc: raise Invalid("Expected a URL scheme and host") return parsed.geturl() + + +def git_ref(value): + if re.match(r"[a-zA-Z0-9\-_.\./]+", value) is None: + raise Invalid("Not a valid git ref") + return value + + +def source_refresh(value: str): + if value.lower() == "always": + return source_refresh("0s") + if value.lower() == "never": + return source_refresh("1000y") + return positive_time_period_seconds(value) diff --git a/esphome/const.py b/esphome/const.py index 06ce736a85..7132cb1d1d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -235,6 +235,7 @@ CONF_FAN_WITH_COOLING = "fan_with_cooling" CONF_FAN_WITH_HEATING = "fan_with_heating" CONF_FAST_CONNECT = "fast_connect" CONF_FILE = "file" +CONF_FILES = "files" CONF_FILTER = "filter" CONF_FILTER_OUT = "filter_out" CONF_FILTERS = "filters" @@ -525,8 +526,10 @@ CONF_REACTIVE_POWER = "reactive_power" CONF_REBOOT_TIMEOUT = "reboot_timeout" CONF_RECEIVE_TIMEOUT = "receive_timeout" CONF_RED = "red" +CONF_REF = "ref" CONF_REFERENCE_RESISTANCE = "reference_resistance" CONF_REFERENCE_TEMPERATURE = "reference_temperature" +CONF_REFRESH = "refresh" CONF_REPEAT = "repeat" CONF_REPOSITORY = "repository" CONF_RESET_PIN = "reset_pin" diff --git a/esphome/git.py b/esphome/git.py new file mode 100644 index 0000000000..12c6b41648 --- /dev/null +++ b/esphome/git.py @@ -0,0 +1,74 @@ +from pathlib import Path +import subprocess +import hashlib +import logging + +from datetime import datetime + +from esphome.core import CORE, TimePeriodSeconds +import esphome.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + + +def run_git_command(cmd, cwd=None): + try: + ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False) + except FileNotFoundError as err: + raise cv.Invalid( + "git is not installed but required for external_components.\n" + "Please see https://git-scm.com/book/en/v2/Getting-Started-Installing-Git for installing git" + ) from err + + if ret.returncode != 0 and ret.stderr: + err_str = ret.stderr.decode("utf-8") + lines = [x.strip() for x in err_str.splitlines()] + if lines[-1].startswith("fatal:"): + raise cv.Invalid(lines[-1][len("fatal: ") :]) + raise cv.Invalid(err_str) + + +def _compute_destination_path(key: str, domain: str) -> Path: + base_dir = Path(CORE.config_dir) / ".esphome" / domain + h = hashlib.new("sha256") + h.update(key.encode()) + return base_dir / h.hexdigest()[:8] + + +def clone_or_update( + *, url: str, ref: str = None, refresh: TimePeriodSeconds, domain: str +) -> Path: + key = f"{url}@{ref}" + repo_dir = _compute_destination_path(key, domain) + if not repo_dir.is_dir(): + _LOGGER.info("Cloning %s", key) + _LOGGER.debug("Location: %s", repo_dir) + cmd = ["git", "clone", "--depth=1"] + if ref is not None: + cmd += ["--branch", ref] + cmd += ["--", url, str(repo_dir)] + run_git_command(cmd) + + else: + # Check refresh needed + file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD") + # On first clone, FETCH_HEAD does not exists + if not file_timestamp.exists(): + file_timestamp = Path(repo_dir / ".git" / "HEAD") + age = datetime.now() - datetime.fromtimestamp(file_timestamp.stat().st_mtime) + if age.total_seconds() > refresh.total_seconds: + _LOGGER.info("Updating %s", key) + _LOGGER.debug("Location: %s", repo_dir) + # Stash local changes (if any) + run_git_command( + ["git", "stash", "push", "--include-untracked"], str(repo_dir) + ) + # Fetch remote ref + cmd = ["git", "fetch", "--", "origin"] + if ref is not None: + cmd.append(ref) + run_git_command(cmd, str(repo_dir)) + # Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch) + run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir)) + + return repo_dir From ff6bed54c66a01942c8881aeb849521959c91f0e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Sep 2021 08:30:47 +1200 Subject: [PATCH 1268/1841] Remove last_reset_type and convert all those sensors to TOTAL_INCREASING (#2233) --- esphome/components/api/api.proto | 3 +- esphome/components/api/api_connection.cpp | 1 - esphome/components/api/api_pb2.cpp | 8 +-- esphome/components/api/api_pb2.h | 2 +- esphome/components/atm90e32/sensor.py | 8 +-- esphome/components/demo/__init__.py | 6 +- esphome/components/demo/demo_sensor.h | 4 +- esphome/components/dsmr/sensor.py | 58 +++---------------- esphome/components/havells_solar/sensor.py | 13 ++--- esphome/components/hlw8012/sensor.py | 5 +- esphome/components/pulse_counter/sensor.py | 4 +- esphome/components/pulse_meter/sensor.py | 5 +- esphome/components/pzem004t/sensor.py | 5 +- esphome/components/pzemac/sensor.py | 5 +- esphome/components/sdm_meter/sensor.py | 14 ++--- esphome/components/selec_meter/sensor.py | 23 +++----- esphome/components/sensor/__init__.py | 28 +-------- esphome/components/sensor/sensor.cpp | 13 ----- esphome/components/sensor/sensor.h | 24 -------- .../components/total_daily_energy/sensor.py | 6 +- esphome/const.py | 8 --- script/api_protobuf/api_protobuf.py | 8 +-- 22 files changed, 60 insertions(+), 191 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index e3ef2d7c9e..7648ffeaa2 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -473,7 +473,8 @@ message ListEntitiesSensorResponse { bool force_update = 8; string device_class = 9; SensorStateClass state_class = 10; - SensorLastResetType last_reset_type = 11; + // Last reset type removed in 2021.9.0 + SensorLastResetType legacy_last_reset_type = 11; bool disabled_by_default = 12; } message SensorStateResponse { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 2bf3af5f65..99e611be10 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -418,7 +418,6 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { msg.force_update = sensor->get_force_update(); msg.device_class = sensor->get_device_class(); msg.state_class = static_cast(sensor->state_class); - msg.last_reset_type = static_cast(sensor->last_reset_type); msg.disabled_by_default = sensor->is_disabled_by_default(); return this->send_list_entities_sensor_response(msg); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index f5860bee64..d6b85d257c 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1817,7 +1817,7 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va return true; } case 11: { - this->last_reset_type = value.as_enum(); + this->legacy_last_reset_type = value.as_enum(); return true; } case 12: { @@ -1879,7 +1879,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(8, this->force_update); buffer.encode_string(9, this->device_class); buffer.encode_enum(10, this->state_class); - buffer.encode_enum(11, this->last_reset_type); + buffer.encode_enum(11, this->legacy_last_reset_type); buffer.encode_bool(12, this->disabled_by_default); } #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1928,8 +1928,8 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->state_class)); out.append("\n"); - out.append(" last_reset_type: "); - out.append(proto_enum_to_string(this->last_reset_type)); + out.append(" legacy_last_reset_type: "); + out.append(proto_enum_to_string(this->legacy_last_reset_type)); out.append("\n"); out.append(" disabled_by_default: "); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 93bfcd9b55..1371ab5248 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -510,7 +510,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { bool force_update{false}; std::string device_class{}; enums::SensorStateClass state_class{}; - enums::SensorLastResetType last_reset_type{}; + enums::SensorLastResetType legacy_last_reset_type{}; bool disabled_by_default{false}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/components/atm90e32/sensor.py b/esphome/components/atm90e32/sensor.py index 28b49604ff..05e5250d89 100644 --- a/esphome/components/atm90e32/sensor.py +++ b/esphome/components/atm90e32/sensor.py @@ -19,8 +19,8 @@ from esphome.const import ( DEVICE_CLASS_VOLTAGE, ICON_LIGHTBULB, ICON_CURRENT_AC, - LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_HERTZ, UNIT_VOLT, UNIT_AMPERE, @@ -94,15 +94,13 @@ ATM90E32_PHASE_SCHEMA = cv.Schema( unit_of_measurement=UNIT_WATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema( unit_of_measurement=UNIT_WATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t, cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t, diff --git a/esphome/components/demo/__init__.py b/esphome/components/demo/__init__.py index b3ea47d869..fae8a2b07d 100644 --- a/esphome/components/demo/__init__.py +++ b/esphome/components/demo/__init__.py @@ -19,7 +19,6 @@ from esphome.const import ( CONF_ICON, CONF_ID, CONF_INVERTED, - CONF_LAST_RESET_TYPE, CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_NAME, @@ -40,8 +39,8 @@ from esphome.const import ( ICON_BLUR, ICON_EMPTY, ICON_THERMOMETER, - LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_CELSIUS, UNIT_EMPTY, UNIT_PERCENT, @@ -336,8 +335,7 @@ CONFIG_SCHEMA = cv.Schema( CONF_UNIT_OF_MEASUREMENT: UNIT_WATT_HOURS, CONF_ACCURACY_DECIMALS: 0, CONF_DEVICE_CLASS: DEVICE_CLASS_ENERGY, - CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT, - CONF_LAST_RESET_TYPE: LAST_RESET_TYPE_AUTO, + CONF_STATE_CLASS: STATE_CLASS_TOTAL_INCREASING, }, ], ): [ diff --git a/esphome/components/demo/demo_sensor.h b/esphome/components/demo/demo_sensor.h index 117468793b..344aaf26f8 100644 --- a/esphome/components/demo/demo_sensor.h +++ b/esphome/components/demo/demo_sensor.h @@ -11,8 +11,8 @@ class DemoSensor : public sensor::Sensor, public PollingComponent { public: void update() override { float val = random_float(); - bool is_auto = this->last_reset_type == sensor::LAST_RESET_TYPE_AUTO; - if (is_auto) { + bool increasing = this->state_class == sensor::STATE_CLASS_TOTAL_INCREASING; + if (increasing) { float base = isnan(this->state) ? 0.0f : this->state; this->publish_state(base + val * 10); } else { diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 84b263c2d5..2c05651d67 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -9,9 +9,9 @@ from esphome.const import ( DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ICON_EMPTY, - LAST_RESET_TYPE_NEVER, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, + STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, UNIT_EMPTY, UNIT_VOLT, @@ -26,52 +26,22 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_DSMR_ID): cv.use_id(Dsmr), cv.Optional("energy_delivered_lux"): sensor.sensor_schema( - "kWh", - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_NEVER, + "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING ), cv.Optional("energy_delivered_tariff1"): sensor.sensor_schema( - "kWh", - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_NEVER, + "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING ), cv.Optional("energy_delivered_tariff2"): sensor.sensor_schema( - "kWh", - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_NEVER, + "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING ), cv.Optional("energy_returned_lux"): sensor.sensor_schema( - "kWh", - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_NEVER, + "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING ), cv.Optional("energy_returned_tariff1"): sensor.sensor_schema( - "kWh", - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_NEVER, + "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING ), cv.Optional("energy_returned_tariff2"): sensor.sensor_schema( - "kWh", - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_NEVER, + "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING ), cv.Optional("total_imported_energy"): sensor.sensor_schema( "kvarh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE @@ -176,20 +146,10 @@ CONFIG_SCHEMA = cv.Schema( UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE ), cv.Optional("gas_delivered"): sensor.sensor_schema( - "m³", - ICON_EMPTY, - 3, - DEVICE_CLASS_GAS, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_NEVER, + "m³", ICON_EMPTY, 3, DEVICE_CLASS_GAS, STATE_CLASS_TOTAL_INCREASING ), cv.Optional("gas_delivered_be"): sensor.sensor_schema( - "m³", - ICON_EMPTY, - 3, - DEVICE_CLASS_GAS, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_NEVER, + "m³", ICON_EMPTY, 3, DEVICE_CLASS_GAS, STATE_CLASS_TOTAL_INCREASING ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/havells_solar/sensor.py b/esphome/components/havells_solar/sensor.py index 1d685b9b2e..3ec12d5b83 100644 --- a/esphome/components/havells_solar/sensor.py +++ b/esphome/components/havells_solar/sensor.py @@ -13,9 +13,8 @@ from esphome.const import ( DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ICON_CURRENT_AC, - LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, + STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, UNIT_DEGREES, UNIT_HERTZ, @@ -143,25 +142,23 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_TOTAL_GENERATION_TIME): sensor.sensor_schema( unit_of_measurement=UNIT_HOURS, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_TODAY_GENERATION_TIME): sensor.sensor_schema( unit_of_measurement=UNIT_MINUTE, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_INVERTER_MODULE_TEMP): sensor.sensor_schema( unit_of_measurement=UNIT_DEGREES, diff --git a/esphome/components/hlw8012/sensor.py b/esphome/components/hlw8012/sensor.py index 75590f8572..11e9c8e4d4 100644 --- a/esphome/components/hlw8012/sensor.py +++ b/esphome/components/hlw8012/sensor.py @@ -18,8 +18,8 @@ from esphome.const import ( DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, - LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, @@ -78,8 +78,7 @@ CONFIG_SCHEMA = cv.Schema( unit_of_measurement=UNIT_WATT_HOURS, accuracy_decimals=1, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance, cv.Optional(CONF_VOLTAGE_DIVIDER, default=2351): cv.positive_float, diff --git a/esphome/components/pulse_counter/sensor.py b/esphome/components/pulse_counter/sensor.py index 767728fc80..c7b89d41b0 100644 --- a/esphome/components/pulse_counter/sensor.py +++ b/esphome/components/pulse_counter/sensor.py @@ -13,7 +13,7 @@ from esphome.const import ( CONF_TOTAL, ICON_PULSE, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, + STATE_CLASS_TOTAL_INCREASING, UNIT_PULSES_PER_MINUTE, UNIT_PULSES, ) @@ -95,7 +95,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_PULSES, icon=ICON_PULSE, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, + state_class=STATE_CLASS_TOTAL_INCREASING, ), } ) diff --git a/esphome/components/pulse_meter/sensor.py b/esphome/components/pulse_meter/sensor.py index 18da842bad..454cb3a69d 100644 --- a/esphome/components/pulse_meter/sensor.py +++ b/esphome/components/pulse_meter/sensor.py @@ -11,8 +11,8 @@ from esphome.const import ( CONF_TOTAL, CONF_VALUE, ICON_PULSE, - LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_PULSES, UNIT_PULSES_PER_MINUTE, ) @@ -64,8 +64,7 @@ CONFIG_SCHEMA = sensor.sensor_schema( unit_of_measurement=UNIT_PULSES, icon=ICON_PULSE, accuracy_decimals=0, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), } ) diff --git a/esphome/components/pzem004t/sensor.py b/esphome/components/pzem004t/sensor.py index 23502e849a..70dec82c3f 100644 --- a/esphome/components/pzem004t/sensor.py +++ b/esphome/components/pzem004t/sensor.py @@ -11,8 +11,8 @@ from esphome.const import ( DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, - LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, @@ -50,8 +50,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_WATT_HOURS, accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), } ) diff --git a/esphome/components/pzemac/sensor.py b/esphome/components/pzemac/sensor.py index 1616bf0ace..b6697e3d19 100644 --- a/esphome/components/pzemac/sensor.py +++ b/esphome/components/pzemac/sensor.py @@ -15,8 +15,8 @@ from esphome.const import ( DEVICE_CLASS_POWER, DEVICE_CLASS_ENERGY, ICON_CURRENT_AC, - LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_HERTZ, UNIT_VOLT, UNIT_AMPERE, @@ -55,8 +55,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_WATT_HOURS, accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( unit_of_measurement=UNIT_HERTZ, diff --git a/esphome/components/sdm_meter/sensor.py b/esphome/components/sdm_meter/sensor.py index ffb88af5ac..8a0d9674a7 100644 --- a/esphome/components/sdm_meter/sensor.py +++ b/esphome/components/sdm_meter/sensor.py @@ -23,8 +23,8 @@ from esphome.const import ( DEVICE_CLASS_VOLTAGE, ICON_CURRENT_AC, ICON_FLASH, - LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, UNIT_DEGREES, UNIT_HERTZ, @@ -104,29 +104,25 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_EXPORT_ACTIVE_ENERGY): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_IMPORT_REACTIVE_ENERGY): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_EXPORT_REACTIVE_ENERGY): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), } ) diff --git a/esphome/components/selec_meter/sensor.py b/esphome/components/selec_meter/sensor.py index 2d05d00380..168d3a3db2 100644 --- a/esphome/components/selec_meter/sensor.py +++ b/esphome/components/selec_meter/sensor.py @@ -20,8 +20,8 @@ from esphome.const import ( DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_VOLTAGE, ICON_CURRENT_AC, - LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, UNIT_HERTZ, UNIT_VOLT, @@ -54,50 +54,43 @@ SENSORS = { unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_IMPORT_ACTIVE_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_EXPORT_ACTIVE_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_TOTAL_REACTIVE_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_IMPORT_REACTIVE_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_EXPORT_REACTIVE_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_APPARENT_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_ACTIVE_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_WATT, diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 1bb4e25a17..bf0dbf62c4 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -17,7 +17,6 @@ from esphome.const import ( CONF_ICON, CONF_ID, CONF_INTERNAL, - CONF_LAST_RESET_TYPE, CONF_ON_RAW_VALUE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, @@ -31,9 +30,6 @@ from esphome.const import ( CONF_NAME, CONF_MQTT_ID, CONF_FORCE_UPDATE, - LAST_RESET_TYPE_AUTO, - LAST_RESET_TYPE_NEVER, - LAST_RESET_TYPE_NONE, DEVICE_CLASS_EMPTY, DEVICE_CLASS_BATTERY, DEVICE_CLASS_CARBON_MONOXIDE, @@ -85,15 +81,6 @@ STATE_CLASSES = { } validate_state_class = cv.enum(STATE_CLASSES, lower=True, space="_") -LastResetTypes = sensor_ns.enum("LastResetType") -LAST_RESET_TYPES = { - LAST_RESET_TYPE_NONE: LastResetTypes.LAST_RESET_TYPE_NONE, - LAST_RESET_TYPE_NEVER: LastResetTypes.LAST_RESET_TYPE_NEVER, - LAST_RESET_TYPE_AUTO: LastResetTypes.LAST_RESET_TYPE_AUTO, -} -validate_last_reset_type = cv.enum(LAST_RESET_TYPES, lower=True, space="_") - - IS_PLATFORM_COMPONENT = True @@ -183,7 +170,9 @@ SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( cv.Optional(CONF_ACCURACY_DECIMALS): validate_accuracy_decimals, cv.Optional(CONF_DEVICE_CLASS): validate_device_class, cv.Optional(CONF_STATE_CLASS): validate_state_class, - cv.Optional(CONF_LAST_RESET_TYPE): validate_last_reset_type, + cv.Optional("last_reset_type"): cv.invalid( + "last_reset_type has been removed since 2021.9.0. state_class: total_increasing should be used for total values." + ), cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean, cv.Optional(CONF_EXPIRE_AFTER): cv.All( cv.requires_component("mqtt"), @@ -220,7 +209,6 @@ def sensor_schema( accuracy_decimals: int = _UNDEF, device_class: str = _UNDEF, state_class: str = _UNDEF, - last_reset_type: str = _UNDEF, ) -> cv.Schema: schema = SENSOR_SCHEMA if unit_of_measurement is not _UNDEF: @@ -253,14 +241,6 @@ def sensor_schema( schema = schema.extend( {cv.Optional(CONF_STATE_CLASS, default=state_class): validate_state_class} ) - if last_reset_type is not _UNDEF: - schema = schema.extend( - { - cv.Optional( - CONF_LAST_RESET_TYPE, default=last_reset_type - ): validate_last_reset_type - } - ) return schema @@ -511,8 +491,6 @@ async def setup_sensor_core_(var, config): cg.add(var.set_icon(config[CONF_ICON])) if CONF_ACCURACY_DECIMALS in config: cg.add(var.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS])) - if CONF_LAST_RESET_TYPE in config: - cg.add(var.set_last_reset_type(config[CONF_LAST_RESET_TYPE])) cg.add(var.set_force_update(config[CONF_FORCE_UPDATE])) if config.get(CONF_FILTERS): # must exist and not be empty filters = await build_filters(config[CONF_FILTERS]) diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 1a5c76db51..1dbc1c901a 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -18,18 +18,6 @@ const char *state_class_to_string(StateClass state_class) { } } -const char *last_reset_type_to_string(LastResetType last_reset_type) { - switch (last_reset_type) { - case LAST_RESET_TYPE_NEVER: - return "never"; - case LAST_RESET_TYPE_AUTO: - return "auto"; - case LAST_RESET_TYPE_NONE: - default: - return ""; - } -} - void Sensor::publish_state(float state) { this->raw_state = state; this->raw_callback_.call(state); @@ -80,7 +68,6 @@ void Sensor::set_state_class(const std::string &state_class) { ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str()); } } -void Sensor::set_last_reset_type(LastResetType last_reset_type) { this->last_reset_type = last_reset_type; } std::string Sensor::get_unit_of_measurement() { if (this->unit_of_measurement_.has_value()) return *this->unit_of_measurement_; diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index f0d7ba4887..34b8b26a54 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -14,10 +14,6 @@ namespace sensor { ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ } \ ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->state_class)); \ - if ((obj)->state_class == sensor::STATE_CLASS_MEASUREMENT && \ - (obj)->last_reset_type != sensor::LAST_RESET_TYPE_NONE) { \ - ESP_LOGCONFIG(TAG, "%s Last Reset Type: '%s'", prefix, last_reset_type_to_string((obj)->last_reset_type)); \ - } \ ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \ ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \ if (!(obj)->get_icon().empty()) { \ @@ -42,20 +38,6 @@ enum StateClass : uint8_t { const char *state_class_to_string(StateClass state_class); -/** - * Sensor last reset types - */ -enum LastResetType : uint8_t { - /// This sensor does not support resetting. ie, it is not accumulative - LAST_RESET_TYPE_NONE = 0, - /// This sensor is expected to never reset its value - LAST_RESET_TYPE_NEVER = 1, - /// This sensor may reset and Home Assistant will watch for this - LAST_RESET_TYPE_AUTO = 2, -}; - -const char *last_reset_type_to_string(LastResetType last_reset_type); - /** Base-class for all sensors. * * A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy. @@ -174,12 +156,6 @@ class Sensor : public Nameable { */ virtual std::string device_class(); - // The Last reset type of this sensor - LastResetType last_reset_type{LAST_RESET_TYPE_NONE}; - - /// Manually set the Home Assistant last reset type for this sensor. - void set_last_reset_type(LastResetType last_reset_type); - /** A unique ID for this sensor, empty for no unique id. See unique ID requirements: * https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements * diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index f1449432a0..46eaac98eb 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -5,9 +5,8 @@ from esphome.const import ( CONF_ID, CONF_TIME_ID, DEVICE_CLASS_ENERGY, - LAST_RESET_TYPE_AUTO, - STATE_CLASS_MEASUREMENT, CONF_METHOD, + STATE_CLASS_TOTAL_INCREASING, ) DEPENDENCIES = ["time"] @@ -28,8 +27,7 @@ TotalDailyEnergy = total_daily_energy_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ) .extend( { diff --git a/esphome/const.py b/esphome/const.py index 7132cb1d1d..5c5037ceb2 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -323,7 +323,6 @@ CONF_KEY = "key" CONF_LAMBDA = "lambda" CONF_LAST_CONFIDENCE = "last_confidence" CONF_LAST_FINGER_ID = "last_finger_id" -CONF_LAST_RESET_TYPE = "last_reset_type" CONF_LATITUDE = "latitude" CONF_LENGTH = "length" CONF_LEVEL = "level" @@ -857,10 +856,3 @@ STATE_CLASS_MEASUREMENT = "measurement" # The state represents a total that only increases, a decrease is considered a reset. STATE_CLASS_TOTAL_INCREASING = "total_increasing" - -# This sensor does not support resetting. ie, it is not accumulative -LAST_RESET_TYPE_NONE = "" -# This sensor is expected to never reset its value -LAST_RESET_TYPE_NEVER = "never" -# This sensor may reset and Home Assistant will watch for this -LAST_RESET_TYPE_AUTO = "auto" diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 6983090fd9..7ccdc5a24e 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -778,9 +778,9 @@ def build_service_message_type(mt): hout += f"bool {func}(const {mt.name} &msg);\n" cout += f"bool {class_name}::{func}(const {mt.name} &msg) {{\n" if log: - cout += f'#ifdef HAS_PROTO_MESSAGE_DUMP\n' + cout += f"#ifdef HAS_PROTO_MESSAGE_DUMP\n" cout += f' ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n' - cout += f'#endif\n' + cout += f"#endif\n" # cout += f' this->set_nodelay({str(nodelay).lower()});\n' cout += f" return this->send_message_<{mt.name}>(msg, {id_});\n" cout += f"}}\n" @@ -794,9 +794,9 @@ def build_service_message_type(mt): case += f"{mt.name} msg;\n" case += f"msg.decode(msg_data, msg_size);\n" if log: - case += f'#ifdef HAS_PROTO_MESSAGE_DUMP\n' + case += f"#ifdef HAS_PROTO_MESSAGE_DUMP\n" case += f'ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n' - case += f'#endif\n' + case += f"#endif\n" case += f"this->{func}(msg);\n" if ifdef is not None: case += f"#endif\n" From 97eba1eecc7d1ee1aac8f3904315375dfd442b51 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Sep 2021 08:36:55 +1200 Subject: [PATCH 1269/1841] Dont dump legacy fields (#2241) --- esphome/components/api/api_pb2.cpp | 41 ----------------------------- script/api_protobuf/api_protobuf.py | 2 ++ 2 files changed, 2 insertions(+), 41 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index d6b85d257c..dab0b29127 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -793,10 +793,6 @@ void CoverStateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" legacy_state: "); - out.append(proto_enum_to_string(this->legacy_state)); - out.append("\n"); - out.append(" position: "); sprintf(buffer, "%g", this->position); out.append(buffer); @@ -880,10 +876,6 @@ void CoverCommandRequest::dump_to(std::string &out) const { out.append(YESNO(this->has_legacy_command)); out.append("\n"); - out.append(" legacy_command: "); - out.append(proto_enum_to_string(this->legacy_command)); - out.append("\n"); - out.append(" has_position: "); out.append(YESNO(this->has_position)); out.append("\n"); @@ -1330,22 +1322,6 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append("\n"); } - out.append(" legacy_supports_brightness: "); - out.append(YESNO(this->legacy_supports_brightness)); - out.append("\n"); - - out.append(" legacy_supports_rgb: "); - out.append(YESNO(this->legacy_supports_rgb)); - out.append("\n"); - - out.append(" legacy_supports_white_value: "); - out.append(YESNO(this->legacy_supports_white_value)); - out.append("\n"); - - out.append(" legacy_supports_color_temperature: "); - out.append(YESNO(this->legacy_supports_color_temperature)); - out.append("\n"); - out.append(" min_mireds: "); sprintf(buffer, "%g", this->min_mireds); out.append(buffer); @@ -2760,11 +2736,6 @@ void ExecuteServiceArgument::dump_to(std::string &out) const { out.append(YESNO(this->bool_)); out.append("\n"); - out.append(" legacy_int: "); - sprintf(buffer, "%d", this->legacy_int); - out.append(buffer); - out.append("\n"); - out.append(" float_: "); sprintf(buffer, "%g", this->float_); out.append(buffer); @@ -3180,10 +3151,6 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" legacy_supports_away: "); - out.append(YESNO(this->legacy_supports_away)); - out.append("\n"); - out.append(" supports_action: "); out.append(YESNO(this->supports_action)); out.append("\n"); @@ -3342,10 +3309,6 @@ void ClimateStateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" legacy_away: "); - out.append(YESNO(this->legacy_away)); - out.append("\n"); - out.append(" action: "); out.append(proto_enum_to_string(this->action)); out.append("\n"); @@ -3545,10 +3508,6 @@ void ClimateCommandRequest::dump_to(std::string &out) const { out.append(YESNO(this->has_legacy_away)); out.append("\n"); - out.append(" legacy_away: "); - out.append(YESNO(this->legacy_away)); - out.append("\n"); - out.append(" has_fan_mode: "); out.append(YESNO(this->has_fan_mode)); out.append("\n"); diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 7ccdc5a24e..7b9e7941ae 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -183,6 +183,8 @@ class TypeInfo: @property def dump_content(self): + if self.name.startswith("legacy_"): + return None o = f'out.append(" {self.name}: ");\n' o += self.dump(f"this->{self.field_name}") + "\n" o += f'out.append("\\n");\n' From 2a653642f50c580d59e3fc7b87c6eca4400b9537 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Sep 2021 08:57:37 +1200 Subject: [PATCH 1270/1841] Fix encoding bug (#2242) --- esphome/dashboard/dashboard.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index a5e9766eea..ab9dd39735 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -608,9 +608,7 @@ class EditRequestHandler(BaseHandler): @bind_config def post(self, configuration=None): # pylint: disable=no-value-for-parameter - with open( - file=settings.rel_path(configuration), mode="wb", encoding="utf-8" - ) as f: + with open(file=settings.rel_path(configuration), mode="wb") as f: f.write(self.request.body) self.set_status(200) From e2d97b6f36d1a06e5e9620a855240b34a2b508af Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Sep 2021 08:57:58 +1200 Subject: [PATCH 1271/1841] Light: include ON_OFF capability to BRIGHTNESS ColorMode (#2204) --- esphome/components/light/color_mode.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/color_mode.h b/esphome/components/light/color_mode.h index 0f5b7b4b93..77c377d39e 100644 --- a/esphome/components/light/color_mode.h +++ b/esphome/components/light/color_mode.h @@ -52,7 +52,7 @@ enum class ColorMode : uint8_t { /// Only on/off control. ON_OFF = (uint8_t) ColorCapability::ON_OFF, /// Dimmable light. - BRIGHTNESS = (uint8_t) ColorCapability::BRIGHTNESS, + BRIGHTNESS = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS), /// White output only (use only if the light also has another color mode such as RGB). WHITE = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::WHITE), /// Controllable color temperature output. From 7f76f3726fe3cb5ea403b52d2fc190feaa2326a4 Mon Sep 17 00:00:00 2001 From: Peter van Dijk Date: Mon, 6 Sep 2021 04:47:13 +0200 Subject: [PATCH 1272/1841] LOG_UPDATE_INTERVAL: correctly report "never" (#2240) --- esphome/core/component.h | 6 +++++- esphome/core/scheduler.cpp | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/core/component.h b/esphome/core/component.h index b9a22c240e..a84f612dd9 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -38,8 +38,12 @@ extern const float LATE; } // namespace setup_priority +static const uint32_t SCHEDULER_DONT_RUN = 4294967295UL; + #define LOG_UPDATE_INTERVAL(this) \ - if (this->get_update_interval() < 100) { \ + if (this->get_update_interval() == SCHEDULER_DONT_RUN) { \ + ESP_LOGCONFIG(TAG, " Update Interval: never"); \ + } else if (this->get_update_interval() < 100) { \ ESP_LOGCONFIG(TAG, " Update Interval: %.3fs", this->get_update_interval() / 1000.0f); \ } else { \ ESP_LOGCONFIG(TAG, " Update Interval: %.1fs", this->get_update_interval() / 1000.0f); \ diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 60e0d4e9bd..5718e3b396 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -7,7 +7,6 @@ namespace esphome { static const char *const TAG = "scheduler"; -static const uint32_t SCHEDULER_DONT_RUN = 4294967295UL; static const uint32_t MAX_LOGICALLY_DELETED_ITEMS = 10; // Uncomment to debug scheduler From 4a6f1f150a837e654c7b6af8f26dec64c6bd5dc1 Mon Sep 17 00:00:00 2001 From: Alex <33379584+alexyao2015@users.noreply.github.com> Date: Sun, 5 Sep 2021 21:48:28 -0500 Subject: [PATCH 1273/1841] Fix runtime exception due to dict typing (#2243) --- esphome/zeroconf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index bc3f905261..e94b59d3ae 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -1,7 +1,7 @@ import socket import threading import time -from typing import Optional +from typing import Dict, Optional from zeroconf import ( _CLASS_IN, @@ -64,7 +64,7 @@ class DashboardStatus(RecordUpdateListener, threading.Thread): threading.Thread.__init__(self) self.zc = zc self.query_hosts: set[str] = set() - self.key_to_host: dict[str, str] = {} + self.key_to_host: Dict[str, str] = {} self.stop_event = threading.Event() self.query_event = threading.Event() self.on_update = on_update @@ -72,7 +72,7 @@ class DashboardStatus(RecordUpdateListener, threading.Thread): def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None: pass - def request_query(self, hosts: dict[str, str]) -> None: + def request_query(self, hosts: Dict[str, str]) -> None: self.query_hosts = set(hosts.values()) self.key_to_host = hosts self.query_event.set() From 2d91e6b97760974ae6e5d3a02018b83a67ffd3f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Mon, 6 Sep 2021 22:00:08 +0200 Subject: [PATCH 1274/1841] template: select: fix initial_value cannot be used with lambda (#2244) --- .../components/template/select/__init__.py | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/esphome/components/template/select/__init__.py b/esphome/components/template/select/__init__.py index 6562beb7f8..4eba77119d 100644 --- a/esphome/components/template/select/__init__.py +++ b/esphome/components/template/select/__init__.py @@ -19,17 +19,6 @@ TemplateSelect = template_ns.class_( CONF_SET_ACTION = "set_action" -def validate_initial_value_in_options(config): - if CONF_INITIAL_OPTION in config: - if config[CONF_INITIAL_OPTION] not in config[CONF_OPTIONS]: - raise cv.Invalid( - f"initial_option '{config[CONF_INITIAL_OPTION]}' is not a valid option [{', '.join(config[CONF_OPTIONS])}]" - ) - else: - config[CONF_INITIAL_OPTION] = config[CONF_OPTIONS][0] - return config - - def validate(config): if CONF_LAMBDA in config: if config[CONF_OPTIMISTIC]: @@ -38,6 +27,14 @@ def validate(config): raise cv.Invalid("initial_value cannot be used with lambda") if CONF_RESTORE_VALUE in config: raise cv.Invalid("restore_value cannot be used with lambda") + elif CONF_INITIAL_OPTION in config: + if config[CONF_INITIAL_OPTION] not in config[CONF_OPTIONS]: + raise cv.Invalid( + f"initial_option '{config[CONF_INITIAL_OPTION]}' is not a valid option [{', '.join(config[CONF_OPTIONS])}]" + ) + else: + config[CONF_INITIAL_OPTION] = config[CONF_OPTIONS][0] + if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config: raise cv.Invalid( "Either optimistic mode must be enabled, or set_action must be set, to handle the option being set." @@ -59,7 +56,6 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_RESTORE_VALUE): cv.boolean, } ).extend(cv.polling_component_schema("60s")), - validate_initial_value_in_options, validate, ) From d9cb64b893701695d48c95e6114d83dacfa9bb7c Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 7 Sep 2021 00:12:26 +0200 Subject: [PATCH 1275/1841] Add device classes new in HA 2021.9 (#2248) --- esphome/components/ccs811/sensor.py | 4 ++++ esphome/components/hm3301/sensor.py | 8 +++++++ esphome/components/mhz19/sensor.py | 2 ++ esphome/components/pm1006/sensor.py | 2 ++ esphome/components/pmsa003i/sensor.py | 9 ++++--- esphome/components/pmsx003/sensor.py | 9 ++++--- esphome/components/sds011/sensor.py | 4 ++++ esphome/components/senseair/sensor.py | 2 ++ esphome/components/sensor/__init__.py | 34 +++++++++++++++++++++------ esphome/components/sgp30/sensor.py | 4 ++++ esphome/components/sgp40/sensor.py | 2 ++ esphome/components/sm300d2/sensor.py | 8 +++++++ esphome/components/sps30/sensor.py | 6 +++++ esphome/components/zyaura/sensor.py | 2 ++ esphome/const.py | 18 ++++++++++---- 15 files changed, 97 insertions(+), 17 deletions(-) diff --git a/esphome/components/ccs811/sensor.py b/esphome/components/ccs811/sensor.py index 4c09a14c3e..c177ed6b5c 100644 --- a/esphome/components/ccs811/sensor.py +++ b/esphome/components/ccs811/sensor.py @@ -4,6 +4,8 @@ from esphome.components import i2c, sensor from esphome.const import ( CONF_ID, ICON_RADIATOR, + DEVICE_CLASS_CARBON_DIOXIDE, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, UNIT_PARTS_PER_BILLION, @@ -30,12 +32,14 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_PARTS_PER_MILLION, icon=ICON_MOLECULE_CO2, accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Required(CONF_TVOC): sensor.sensor_schema( unit_of_measurement=UNIT_PARTS_PER_BILLION, icon=ICON_RADIATOR, accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BASELINE): cv.hex_uint16_t, diff --git a/esphome/components/hm3301/sensor.py b/esphome/components/hm3301/sensor.py index fe1c6008d4..7cd81fec1d 100644 --- a/esphome/components/hm3301/sensor.py +++ b/esphome/components/hm3301/sensor.py @@ -6,6 +6,10 @@ from esphome.const import ( CONF_PM_2_5, CONF_PM_10_0, CONF_PM_1_0, + DEVICE_CLASS_AQI, + DEVICE_CLASS_PM1, + DEVICE_CLASS_PM10, + DEVICE_CLASS_PM25, STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, @@ -45,24 +49,28 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + device_class=DEVICE_CLASS_PM1, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_2_5): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + device_class=DEVICE_CLASS_PM25, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + device_class=DEVICE_CLASS_PM10, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_AQI): sensor.sensor_schema( unit_of_measurement=UNIT_INDEX, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + device_class=DEVICE_CLASS_AQI, state_class=STATE_CLASS_MEASUREMENT, ).extend( { diff --git a/esphome/components/mhz19/sensor.py b/esphome/components/mhz19/sensor.py index 1a111f7891..0081f42952 100644 --- a/esphome/components/mhz19/sensor.py +++ b/esphome/components/mhz19/sensor.py @@ -8,6 +8,7 @@ from esphome.const import ( CONF_ID, CONF_TEMPERATURE, DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_CARBON_DIOXIDE, ICON_MOLECULE_CO2, STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, @@ -34,6 +35,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_PARTS_PER_MILLION, icon=ICON_MOLECULE_CO2, accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( diff --git a/esphome/components/pm1006/sensor.py b/esphome/components/pm1006/sensor.py index 8ea0e303f3..0423be61e2 100644 --- a/esphome/components/pm1006/sensor.py +++ b/esphome/components/pm1006/sensor.py @@ -4,6 +4,7 @@ from esphome.components import sensor, uart from esphome.const import ( CONF_ID, CONF_PM_2_5, + DEVICE_CLASS_PM25, STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_BLUR, @@ -23,6 +24,7 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_BLUR, accuracy_decimals=0, + device_class=DEVICE_CLASS_PM25, state_class=STATE_CLASS_MEASUREMENT, ), } diff --git a/esphome/components/pmsa003i/sensor.py b/esphome/components/pmsa003i/sensor.py index ac26270cfc..a9586d98cd 100644 --- a/esphome/components/pmsa003i/sensor.py +++ b/esphome/components/pmsa003i/sensor.py @@ -13,6 +13,9 @@ from esphome.const import ( UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, ICON_COUNTER, + DEVICE_CLASS_PM1, + DEVICE_CLASS_PM10, + DEVICE_CLASS_PM25, DEVICE_CLASS_EMPTY, ) @@ -39,19 +42,19 @@ CONFIG_SCHEMA = ( UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 2, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_PM1, ), cv.Optional(CONF_PM_2_5): sensor.sensor_schema( UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 2, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_PM25, ), cv.Optional(CONF_PM_10_0): sensor.sensor_schema( UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 2, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_PM10, ), cv.Optional(CONF_PMC_0_3): sensor.sensor_schema( UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index c3dd7d5a97..8b9e5c9af2 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -19,6 +19,9 @@ from esphome.const import ( CONF_PM_10_0UM, CONF_TEMPERATURE, CONF_TYPE, + DEVICE_CLASS_PM1, + DEVICE_CLASS_PM10, + DEVICE_CLASS_PM25, DEVICE_CLASS_EMPTY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, @@ -75,19 +78,19 @@ CONFIG_SCHEMA = ( UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_PM1, ), cv.Optional(CONF_PM_2_5_STD): sensor.sensor_schema( UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_PM25, ), cv.Optional(CONF_PM_10_0_STD): sensor.sensor_schema( UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_PM10, ), cv.Optional(CONF_PM_1_0): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, diff --git a/esphome/components/sds011/sensor.py b/esphome/components/sds011/sensor.py index 0997b47ef6..456d47ee91 100644 --- a/esphome/components/sds011/sensor.py +++ b/esphome/components/sds011/sensor.py @@ -7,6 +7,8 @@ from esphome.const import ( CONF_PM_2_5, CONF_RX_ONLY, CONF_UPDATE_INTERVAL, + DEVICE_CLASS_PM25, + DEVICE_CLASS_PM10, STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, @@ -41,12 +43,14 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=1, + device_class=DEVICE_CLASS_PM25, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=1, + device_class=DEVICE_CLASS_PM10, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_RX_ONLY, default=False): cv.boolean, diff --git a/esphome/components/senseair/sensor.py b/esphome/components/senseair/sensor.py index 739a8ada50..d423793873 100644 --- a/esphome/components/senseair/sensor.py +++ b/esphome/components/senseair/sensor.py @@ -7,6 +7,7 @@ from esphome.const import ( CONF_CO2, CONF_ID, ICON_MOLECULE_CO2, + DEVICE_CLASS_CARBON_DIOXIDE, STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, ) @@ -41,6 +42,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_PARTS_PER_MILLION, icon=ICON_MOLECULE_CO2, accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, state_class=STATE_CLASS_MEASUREMENT, ), } diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index bf0dbf62c4..fd278be51e 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -31,21 +31,31 @@ from esphome.const import ( CONF_MQTT_ID, CONF_FORCE_UPDATE, DEVICE_CLASS_EMPTY, + DEVICE_CLASS_AQI, DEVICE_CLASS_BATTERY, - DEVICE_CLASS_CARBON_MONOXIDE, DEVICE_CLASS_CARBON_DIOXIDE, + DEVICE_CLASS_CARBON_MONOXIDE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_MONETARY, - DEVICE_CLASS_SIGNAL_STRENGTH, - DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_NITROGEN_DIOXIDE, + DEVICE_CLASS_NITROGEN_MONOXIDE, + DEVICE_CLASS_NITROUS_OXIDE, + DEVICE_CLASS_OZONE, + DEVICE_CLASS_PM1, + DEVICE_CLASS_PM10, + DEVICE_CLASS_PM25, DEVICE_CLASS_POWER, DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_SULPHUR_DIOXIDE, + DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TIMESTAMP, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, DEVICE_CLASS_VOLTAGE, ) from esphome.core import CORE, coroutine_with_priority @@ -54,21 +64,31 @@ from esphome.util import Registry CODEOWNERS = ["@esphome/core"] DEVICE_CLASSES = [ DEVICE_CLASS_EMPTY, + DEVICE_CLASS_AQI, DEVICE_CLASS_BATTERY, - DEVICE_CLASS_CARBON_MONOXIDE, DEVICE_CLASS_CARBON_DIOXIDE, + DEVICE_CLASS_CARBON_MONOXIDE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_MONETARY, - DEVICE_CLASS_SIGNAL_STRENGTH, - DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_TIMESTAMP, + DEVICE_CLASS_NITROGEN_DIOXIDE, + DEVICE_CLASS_NITROGEN_MONOXIDE, + DEVICE_CLASS_NITROUS_OXIDE, + DEVICE_CLASS_OZONE, + DEVICE_CLASS_PM1, + DEVICE_CLASS_PM10, + DEVICE_CLASS_PM25, DEVICE_CLASS_POWER, DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_SULPHUR_DIOXIDE, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_TIMESTAMP, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, DEVICE_CLASS_VOLTAGE, ] diff --git a/esphome/components/sgp30/sensor.py b/esphome/components/sgp30/sensor.py index 3e33af3b4a..2596e0065d 100644 --- a/esphome/components/sgp30/sensor.py +++ b/esphome/components/sgp30/sensor.py @@ -7,6 +7,8 @@ from esphome.const import ( CONF_ECO2, CONF_TVOC, ICON_RADIATOR, + DEVICE_CLASS_CARBON_DIOXIDE, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, UNIT_PARTS_PER_BILLION, @@ -34,12 +36,14 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_PARTS_PER_MILLION, icon=ICON_MOLECULE_CO2, accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Required(CONF_TVOC): sensor.sensor_schema( unit_of_measurement=UNIT_PARTS_PER_BILLION, icon=ICON_RADIATOR, accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ECO2_BASELINE): sensor.sensor_schema( diff --git a/esphome/components/sgp40/sensor.py b/esphome/components/sgp40/sensor.py index 0f562048ac..7b96f867af 100644 --- a/esphome/components/sgp40/sensor.py +++ b/esphome/components/sgp40/sensor.py @@ -4,6 +4,7 @@ from esphome.components import i2c, sensor from esphome.const import ( CONF_ID, ICON_RADIATOR, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, STATE_CLASS_MEASUREMENT, ) @@ -26,6 +27,7 @@ CONFIG_SCHEMA = ( sensor.sensor_schema( icon=ICON_RADIATOR, accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, state_class=STATE_CLASS_MEASUREMENT, ) .extend( diff --git a/esphome/components/sm300d2/sensor.py b/esphome/components/sm300d2/sensor.py index 73cada0eb3..8452ee81f2 100644 --- a/esphome/components/sm300d2/sensor.py +++ b/esphome/components/sm300d2/sensor.py @@ -10,6 +10,10 @@ from esphome.const import ( CONF_PM_10_0, CONF_TEMPERATURE, CONF_HUMIDITY, + DEVICE_CLASS_CARBON_DIOXIDE, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, + DEVICE_CLASS_PM25, + DEVICE_CLASS_PM10, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY, STATE_CLASS_MEASUREMENT, @@ -36,6 +40,7 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_PARTS_PER_MILLION, icon=ICON_MOLECULE_CO2, accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_FORMALDEHYDE): sensor.sensor_schema( @@ -48,18 +53,21 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_2_5): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_GRAIN, accuracy_decimals=0, + device_class=DEVICE_CLASS_PM25, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_GRAIN, accuracy_decimals=0, + device_class=DEVICE_CLASS_PM10, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( diff --git a/esphome/components/sps30/sensor.py b/esphome/components/sps30/sensor.py index 959b427861..27264cf942 100644 --- a/esphome/components/sps30/sensor.py +++ b/esphome/components/sps30/sensor.py @@ -13,6 +13,9 @@ from esphome.const import ( CONF_PMC_4_0, CONF_PMC_10_0, CONF_PM_SIZE, + DEVICE_CLASS_PM1, + DEVICE_CLASS_PM10, + DEVICE_CLASS_PM25, STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, UNIT_COUNTS_PER_CUBIC_METER, @@ -35,12 +38,14 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=2, + device_class=DEVICE_CLASS_PM1, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_2_5): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=2, + device_class=DEVICE_CLASS_PM25, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_4_0): sensor.sensor_schema( @@ -53,6 +58,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=2, + device_class=DEVICE_CLASS_PM10, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_0_5): sensor.sensor_schema( diff --git a/esphome/components/zyaura/sensor.py b/esphome/components/zyaura/sensor.py index f2273afa9e..74f1f9ec61 100644 --- a/esphome/components/zyaura/sensor.py +++ b/esphome/components/zyaura/sensor.py @@ -9,6 +9,7 @@ from esphome.const import ( CONF_CO2, CONF_TEMPERATURE, CONF_HUMIDITY, + DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, @@ -35,6 +36,7 @@ CONFIG_SCHEMA = cv.Schema( unit_of_measurement=UNIT_PARTS_PER_MILLION, icon=ICON_MOLECULE_CO2, accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( diff --git a/esphome/const.py b/esphome/const.py index 5c5037ceb2..6342e2a848 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -811,7 +811,6 @@ DEVICE_CLASS_COLD = "cold" DEVICE_CLASS_CONNECTIVITY = "connectivity" DEVICE_CLASS_DOOR = "door" DEVICE_CLASS_GARAGE_DOOR = "garage_door" -DEVICE_CLASS_GAS = "gas" DEVICE_CLASS_HEAT = "heat" DEVICE_CLASS_LIGHT = "light" DEVICE_CLASS_LOCK = "lock" @@ -832,20 +831,31 @@ DEVICE_CLASS_WINDOW = "window" # device classes of both binary_sensor and sensor component DEVICE_CLASS_EMPTY = "" DEVICE_CLASS_BATTERY = "battery" +DEVICE_CLASS_GAS = "gas" DEVICE_CLASS_POWER = "power" # device classes of sensor component -DEVICE_CLASS_CARBON_MONOXIDE = "carbon_monoxide" +DEVICE_CLASS_AQI = "aqi" DEVICE_CLASS_CARBON_DIOXIDE = "carbon_dioxide" +DEVICE_CLASS_CARBON_MONOXIDE = "carbon_monoxide" DEVICE_CLASS_CURRENT = "current" DEVICE_CLASS_ENERGY = "energy" DEVICE_CLASS_HUMIDITY = "humidity" DEVICE_CLASS_ILLUMINANCE = "illuminance" DEVICE_CLASS_MONETARY = "monetary" -DEVICE_CLASS_SIGNAL_STRENGTH = "signal_strength" -DEVICE_CLASS_TEMPERATURE = "temperature" +DEVICE_CLASS_NITROGEN_DIOXIDE = "nitrogen_dioxide" +DEVICE_CLASS_NITROGEN_MONOXIDE = "nitrogen_monoxide" +DEVICE_CLASS_NITROUS_OXIDE = "nitrous_oxide" +DEVICE_CLASS_OZONE = "ozone" +DEVICE_CLASS_PM1 = "pm1" +DEVICE_CLASS_PM10 = "pm10" +DEVICE_CLASS_PM25 = "pm25" DEVICE_CLASS_POWER_FACTOR = "power_factor" DEVICE_CLASS_PRESSURE = "pressure" +DEVICE_CLASS_SIGNAL_STRENGTH = "signal_strength" +DEVICE_CLASS_SULPHUR_DIOXIDE = "sulphur_dioxide" +DEVICE_CLASS_TEMPERATURE = "temperature" DEVICE_CLASS_TIMESTAMP = "timestamp" +DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds" DEVICE_CLASS_VOLTAGE = "voltage" # state classes From dba502c75611a0f43fcd6a778f239e93c1ff3625 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 7 Sep 2021 22:57:20 +0200 Subject: [PATCH 1276/1841] Logger prevent recursive logging (#2251) --- esphome/components/logger/logger.cpp | 8 ++++++-- esphome/components/logger/logger.h | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index ce82a51b94..9d79037087 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -43,21 +43,24 @@ void Logger::write_header_(int level, const char *tag, int line) { } void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT - if (level > this->level_for(tag)) + if (level > this->level_for(tag) || recursion_guard_) return; + recursion_guard_ = true; this->reset_buffer_(); this->write_header_(level, tag, line); this->vprintf_to_buffer_(format, args); this->write_footer_(); this->log_message_(level, tag); + recursion_guard_ = false; } #ifdef USE_STORE_LOG_STR_IN_FLASH void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format, va_list args) { // NOLINT - if (level > this->level_for(tag)) + if (level > this->level_for(tag) || recursion_guard_) return; + recursion_guard_ = true; this->reset_buffer_(); // copy format string const char *format_pgm_p = (PGM_P) format; @@ -78,6 +81,7 @@ void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStr this->vprintf_to_buffer_(this->tx_buffer_, args); this->write_footer_(); this->log_message_(level, tag, offset); + recursion_guard_ = false; } #endif diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 1724875229..365261cb91 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -113,6 +113,8 @@ class Logger : public Component { }; std::vector log_levels_; CallbackManager log_callback_{}; + /// Prevents recursive log calls, if true a log message is already being processed. + bool recursion_guard_ = false; }; extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) From b0533db2eb80143a40c650d52edeec7a9123ceb7 Mon Sep 17 00:00:00 2001 From: dgtal1 <27864579+dgtal1@users.noreply.github.com> Date: Wed, 8 Sep 2021 05:15:57 +0200 Subject: [PATCH 1277/1841] Add new trigger to fan component `on_speed_set` (#2246) --- esphome/components/fan/__init__.py | 10 ++++++++++ esphome/components/fan/automation.h | 18 ++++++++++++++++++ esphome/const.py | 1 + tests/test1.yaml | 3 +++ 4 files changed, 32 insertions(+) diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 6bf0d1ca1a..46ff0c2d53 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -15,6 +15,7 @@ from esphome.const import ( CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC, CONF_NAME, + CONF_ON_SPEED_SET, CONF_ON_TURN_OFF, CONF_ON_TURN_ON, CONF_TRIGGER_ID, @@ -41,6 +42,7 @@ ToggleAction = fan_ns.class_("ToggleAction", automation.Action) FanTurnOnTrigger = fan_ns.class_("FanTurnOnTrigger", automation.Trigger.template()) FanTurnOffTrigger = fan_ns.class_("FanTurnOffTrigger", automation.Trigger.template()) +FanSpeedSetTrigger = fan_ns.class_("FanSpeedSetTrigger", automation.Trigger.template()) FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template()) FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.template()) @@ -71,6 +73,11 @@ FAN_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOffTrigger), } ), + cv.Optional(CONF_ON_SPEED_SET): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanSpeedSetTrigger), + } + ), } ) @@ -110,6 +117,9 @@ async def setup_fan_core_(var, config): for conf in config.get(CONF_ON_TURN_OFF, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_SPEED_SET, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) async def register_fan(var, config): diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index abcad82569..7ff7c720df 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -103,5 +103,23 @@ class FanTurnOffTrigger : public Trigger<> { bool last_on_; }; +class FanSpeedSetTrigger : public Trigger<> { + public: + FanSpeedSetTrigger(FanState *state) { + state->add_on_state_callback([this, state]() { + auto speed = state->speed; + auto should_trigger = speed != !this->last_speed_; + this->last_speed_ = speed; + if (should_trigger) { + this->trigger(); + } + }); + this->last_speed_ = state->speed; + } + + protected: + int last_speed_; +}; + } // namespace fan } // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 6342e2a848..aff03b244b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -425,6 +425,7 @@ CONF_ON_PRESS = "on_press" CONF_ON_RAW_VALUE = "on_raw_value" CONF_ON_RELEASE = "on_release" CONF_ON_SHUTDOWN = "on_shutdown" +CONF_ON_SPEED_SET = "on_speed_set" CONF_ON_STATE = "on_state" CONF_ON_TAG = "on_tag" CONF_ON_TAG_REMOVED = "on_tag_removed" diff --git a/tests/test1.yaml b/tests/test1.yaml index bfdf9c3bea..da3289843d 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1881,6 +1881,9 @@ fan: oscillation_command_topic: oscillation/command/topic speed_state_topic: speed/state/topic speed_command_topic: speed/command/topic + on_speed_set: + then: + - logger.log: "Fan speed was changed!" interval: - interval: 10s From 1be106c0b5f9dfa3b6df668a36975d3f7a7421ab Mon Sep 17 00:00:00 2001 From: wifwucite <74489218+wifwucite@users.noreply.github.com> Date: Wed, 8 Sep 2021 05:30:17 +0200 Subject: [PATCH 1278/1841] Fix fan speed restore issue on boot (#1867) --- esphome/components/binary/fan/binary_fan.cpp | 5 ++++- esphome/components/fan/fan_state.cpp | 2 +- esphome/components/speed/fan/speed_fan.cpp | 5 ++++- esphome/components/tuya/fan/tuya_fan.cpp | 4 ++++ esphome/components/tuya/fan/tuya_fan.h | 1 + 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/esphome/components/binary/fan/binary_fan.cpp b/esphome/components/binary/fan/binary_fan.cpp index eaf41829bb..2201fe576e 100644 --- a/esphome/components/binary/fan/binary_fan.cpp +++ b/esphome/components/binary/fan/binary_fan.cpp @@ -55,7 +55,10 @@ void BinaryFan::loop() { ESP_LOGD(TAG, "Setting reverse direction: %s", ONOFF(enable)); } } -float BinaryFan::get_setup_priority() const { return setup_priority::DATA; } + +// We need a higher priority than the FanState component to make sure that the traits are set +// when that component sets itself up. +float BinaryFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; } } // namespace binary } // namespace esphome diff --git a/esphome/components/fan/fan_state.cpp b/esphome/components/fan/fan_state.cpp index 9b4ae53937..a4883c5e2c 100644 --- a/esphome/components/fan/fan_state.cpp +++ b/esphome/components/fan/fan_state.cpp @@ -39,7 +39,7 @@ void FanState::setup() { call.set_direction(recovered.direction); call.perform(); } -float FanState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; } +float FanState::get_setup_priority() const { return setup_priority::DATA - 1.0f; } uint32_t FanState::hash_base() { return 418001110UL; } void FanStateCall::perform() const { diff --git a/esphome/components/speed/fan/speed_fan.cpp b/esphome/components/speed/fan/speed_fan.cpp index 8c6ec54d4c..cb10db4ed4 100644 --- a/esphome/components/speed/fan/speed_fan.cpp +++ b/esphome/components/speed/fan/speed_fan.cpp @@ -56,7 +56,10 @@ void SpeedFan::loop() { ESP_LOGD(TAG, "Setting reverse direction: %s", ONOFF(enable)); } } -float SpeedFan::get_setup_priority() const { return setup_priority::DATA; } + +// We need a higher priority than the FanState component to make sure that the traits are set +// when that component sets itself up. +float SpeedFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; } } // namespace speed } // namespace esphome diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp index e9f8ce8e96..f060b18eba 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -84,5 +84,9 @@ void TuyaFan::write_state() { } } +// We need a higher priority than the FanState component to make sure that the traits are set +// when that component sets itself up. +float TuyaFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; } + } // namespace tuya } // namespace esphome diff --git a/esphome/components/tuya/fan/tuya_fan.h b/esphome/components/tuya/fan/tuya_fan.h index a24e7a218e..e96770d8c3 100644 --- a/esphome/components/tuya/fan/tuya_fan.h +++ b/esphome/components/tuya/fan/tuya_fan.h @@ -11,6 +11,7 @@ class TuyaFan : public Component { public: TuyaFan(Tuya *parent, fan::FanState *fan, int speed_count) : parent_(parent), fan_(fan), speed_count_(speed_count) {} void setup() override; + float get_setup_priority() const override; void dump_config() override; void set_speed_id(uint8_t speed_id) { this->speed_id_ = speed_id; } void set_switch_id(uint8_t switch_id) { this->switch_id_ = switch_id; } From 6180ee8065db319a475692a1dbcbe02b5132e9a3 Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Tue, 7 Sep 2021 22:36:49 -0500 Subject: [PATCH 1279/1841] Template sensors always publish on update interval (#2224) Co-authored-by: Chris Nussbaum --- .../components/template/sensor/template_sensor.cpp | 13 +++++++------ .../template/text_sensor/template_text_sensor.cpp | 13 +++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/esphome/components/template/sensor/template_sensor.cpp b/esphome/components/template/sensor/template_sensor.cpp index 9324cb5dea..63cbd70db0 100644 --- a/esphome/components/template/sensor/template_sensor.cpp +++ b/esphome/components/template/sensor/template_sensor.cpp @@ -7,12 +7,13 @@ namespace template_ { static const char *const TAG = "template.sensor"; void TemplateSensor::update() { - if (!this->f_.has_value()) - return; - - auto val = (*this->f_)(); - if (val.has_value()) { - this->publish_state(*val); + if (this->f_.has_value()) { + auto val = (*this->f_)(); + if (val.has_value()) { + this->publish_state(*val); + } + } else if (!isnan(this->get_raw_state())) { + this->publish_state(this->get_raw_state()); } } float TemplateSensor::get_setup_priority() const { return setup_priority::HARDWARE; } diff --git a/esphome/components/template/text_sensor/template_text_sensor.cpp b/esphome/components/template/text_sensor/template_text_sensor.cpp index 885ad47bbf..83bebb5bcf 100644 --- a/esphome/components/template/text_sensor/template_text_sensor.cpp +++ b/esphome/components/template/text_sensor/template_text_sensor.cpp @@ -7,12 +7,13 @@ namespace template_ { static const char *const TAG = "template.text_sensor"; void TemplateTextSensor::update() { - if (!this->f_.has_value()) - return; - - auto val = (*this->f_)(); - if (val.has_value()) { - this->publish_state(*val); + if (this->f_.has_value()) { + auto val = (*this->f_)(); + if (val.has_value()) { + this->publish_state(*val); + } + } else if (this->has_state()) { + this->publish_state(this->state); } } float TemplateTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; } From f924e80f431a26f1c49c8eedd203b2f12317e4d0 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 8 Sep 2021 05:41:42 +0200 Subject: [PATCH 1280/1841] Socket component (#2250) --- CODEOWNERS | 1 + esphome/components/socket/__init__.py | 28 ++ .../components/socket/bsd_sockets_impl.cpp | 105 ++++ esphome/components/socket/headers.h | 117 +++++ .../components/socket/lwip_raw_tcp_impl.cpp | 475 ++++++++++++++++++ esphome/components/socket/socket.h | 42 ++ esphome/core/defines.h | 6 + script/ci-custom.py | 7 +- 8 files changed, 779 insertions(+), 2 deletions(-) create mode 100644 esphome/components/socket/__init__.py create mode 100644 esphome/components/socket/bsd_sockets_impl.cpp create mode 100644 esphome/components/socket/headers.h create mode 100644 esphome/components/socket/lwip_raw_tcp_impl.cpp create mode 100644 esphome/components/socket/socket.h diff --git a/CODEOWNERS b/CODEOWNERS index 40bf27aa43..ad670ede14 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -119,6 +119,7 @@ esphome/components/sht4x/* @sjtrny esphome/components/shutdown/* @esphome/core esphome/components/sim800l/* @glmnet esphome/components/sm2135/* @BoukeHaarsma23 +esphome/components/socket/* @esphome/core esphome/components/spi/* @esphome/core esphome/components/ssd1322_base/* @kbx81 esphome/components/ssd1322_spi/* @kbx81 diff --git a/esphome/components/socket/__init__.py b/esphome/components/socket/__init__.py new file mode 100644 index 0000000000..8e9502be6d --- /dev/null +++ b/esphome/components/socket/__init__.py @@ -0,0 +1,28 @@ +import esphome.config_validation as cv +import esphome.codegen as cg + +CODEOWNERS = ["@esphome/core"] + +CONF_IMPLEMENTATION = "implementation" +IMPLEMENTATION_LWIP_TCP = "lwip_tcp" +IMPLEMENTATION_BSD_SOCKETS = "bsd_sockets" + +CONFIG_SCHEMA = cv.Schema( + { + cv.SplitDefault( + CONF_IMPLEMENTATION, + esp8266=IMPLEMENTATION_LWIP_TCP, + esp32=IMPLEMENTATION_BSD_SOCKETS, + ): cv.one_of( + IMPLEMENTATION_LWIP_TCP, IMPLEMENTATION_BSD_SOCKETS, lower=True, space="_" + ), + } +) + + +async def to_code(config): + impl = config[CONF_IMPLEMENTATION] + if impl == IMPLEMENTATION_LWIP_TCP: + cg.add_define("USE_SOCKET_IMPL_LWIP_TCP") + elif impl == IMPLEMENTATION_BSD_SOCKETS: + cg.add_define("USE_SOCKET_IMPL_BSD_SOCKETS") diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp new file mode 100644 index 0000000000..a0cdb6ec42 --- /dev/null +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -0,0 +1,105 @@ +#include "socket.h" +#include "esphome/core/defines.h" + +#ifdef USE_SOCKET_IMPL_BSD_SOCKETS + +#include + +namespace esphome { +namespace socket { + +std::string format_sockaddr(const struct sockaddr_storage &storage) { + if (storage.ss_family == AF_INET) { + const struct sockaddr_in *addr = reinterpret_cast(&storage); + char buf[INET_ADDRSTRLEN]; + const char *ret = inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf)); + if (ret == NULL) + return {}; + return std::string{buf}; + } else if (storage.ss_family == AF_INET6) { + const struct sockaddr_in6 *addr = reinterpret_cast(&storage); + char buf[INET6_ADDRSTRLEN]; + const char *ret = inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof(buf)); + if (ret == NULL) + return {}; + return std::string{buf}; + } + return {}; +} + +class BSDSocketImpl : public Socket { + public: + BSDSocketImpl(int fd) : Socket(), fd_(fd) {} + ~BSDSocketImpl() override { + if (!closed_) { + close(); + } + } + std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) override { + int fd = ::accept(fd_, addr, addrlen); + if (fd == -1) + return {}; + return std::unique_ptr{new BSDSocketImpl(fd)}; + } + int bind(const struct sockaddr *addr, socklen_t addrlen) override { return ::bind(fd_, addr, addrlen); } + int close() override { + int ret = ::close(fd_); + closed_ = true; + return ret; + } + int shutdown(int how) override { return ::shutdown(fd_, how); } + + int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { return ::getpeername(fd_, addr, addrlen); } + std::string getpeername() override { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + int err = this->getpeername((struct sockaddr *) &storage, &len); + if (err != 0) + return {}; + return format_sockaddr(storage); + } + int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { return ::getsockname(fd_, addr, addrlen); } + std::string getsockname() override { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + int err = this->getsockname((struct sockaddr *) &storage, &len); + if (err != 0) + return {}; + return format_sockaddr(storage); + } + int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { + return ::getsockopt(fd_, level, optname, optval, optlen); + } + int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override { + return ::setsockopt(fd_, level, optname, optval, optlen); + } + int listen(int backlog) override { return ::listen(fd_, backlog); } + ssize_t read(void *buf, size_t len) override { return ::read(fd_, buf, len); } + ssize_t write(const void *buf, size_t len) override { return ::write(fd_, buf, len); } + int setblocking(bool blocking) override { + int fl = ::fcntl(fd_, F_GETFL, 0); + if (blocking) { + fl &= ~O_NONBLOCK; + } else { + fl |= O_NONBLOCK; + } + ::fcntl(fd_, F_SETFL, fl); + return 0; + } + + protected: + int fd_; + bool closed_ = false; +}; + +std::unique_ptr socket(int domain, int type, int protocol) { + int ret = ::socket(domain, type, protocol); + if (ret == -1) + return nullptr; + return std::unique_ptr{new BSDSocketImpl(ret)}; +} + +} // namespace socket +} // namespace esphome + +#endif // USE_SOCKET_IMPL_BSD_SOCKETS diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h new file mode 100644 index 0000000000..a084823bdf --- /dev/null +++ b/esphome/components/socket/headers.h @@ -0,0 +1,117 @@ +#pragma once +#include "esphome/core/defines.h" + +// Helper file to include all socket-related system headers (or use our own +// definitions where system ones don't exist) + +#ifdef USE_SOCKET_IMPL_LWIP_TCP + +#define LWIP_INTERNAL +#include +#include "lwip/inet.h" +#include +#include + +/* Address families. */ +#define AF_UNSPEC 0 +#define AF_INET 2 +#define AF_INET6 10 +#define PF_INET AF_INET +#define PF_INET6 AF_INET6 +#define PF_UNSPEC AF_UNSPEC +#define IPPROTO_IP 0 +#define IPPROTO_TCP 6 +#define IPPROTO_IPV6 41 +#define IPPROTO_ICMPV6 58 + +#define TCP_NODELAY 0x01 + +#define F_GETFL 3 +#define F_SETFL 4 +#define O_NONBLOCK 1 + +#define SHUT_RD 0 +#define SHUT_WR 1 +#define SHUT_RDWR 2 + +/* Socket protocol types (TCP/UDP/RAW) */ +#define SOCK_STREAM 1 +#define SOCK_DGRAM 2 +#define SOCK_RAW 3 + +#define SO_REUSEADDR 0x0004 /* Allow local address reuse */ +#define SO_KEEPALIVE 0x0008 /* keep connections alive */ +#define SO_BROADCAST 0x0020 /* permit to send and to receive broadcast messages (see IP_SOF_BROADCAST option) */ + +#define SOL_SOCKET 0xfff /* options for socket level */ + +typedef uint8_t sa_family_t; +typedef uint16_t in_port_t; + +struct sockaddr_in { + uint8_t sin_len; + sa_family_t sin_family; + in_port_t sin_port; + struct in_addr sin_addr; +#define SIN_ZERO_LEN 8 + char sin_zero[SIN_ZERO_LEN]; +}; + +struct sockaddr_in6 { + uint8_t sin6_len; /* length of this structure */ + sa_family_t sin6_family; /* AF_INET6 */ + in_port_t sin6_port; /* Transport layer port # */ + uint32_t sin6_flowinfo; /* IPv6 flow information */ + struct in6_addr sin6_addr; /* IPv6 address */ + uint32_t sin6_scope_id; /* Set of interfaces for scope */ +}; + +struct sockaddr { + uint8_t sa_len; + sa_family_t sa_family; + char sa_data[14]; +}; + +struct sockaddr_storage { + uint8_t s2_len; + sa_family_t ss_family; + char s2_data1[2]; + uint32_t s2_data2[3]; + uint32_t s2_data3[3]; +}; +typedef uint32_t socklen_t; + +#ifdef ARDUINO_ARCH_ESP8266 +// arduino-esp8266 declares a global vars called INADDR_NONE/ANY which are invalid with the define +#ifdef INADDR_ANY +#undef INADDR_ANY +#endif +#ifdef INADDR_NONE +#undef INADDR_NONE +#endif + +#define INADDR_ANY ((uint32_t) 0x00000000UL) +#endif + +#endif // USE_SOCKET_IMPL_LWIP_TCP + +#ifdef USE_SOCKET_IMPL_BSD_SOCKETS + +#include +#include +#include +#include +#include +#include + +#ifdef ARDUINO_ARCH_ESP32 +// arduino-esp32 declares a global var called INADDR_NONE which is replaced +// by the define +#ifdef INADDR_NONE +#undef INADDR_NONE +#endif +// not defined for ESP32 +typedef uint32_t socklen_t; +#endif // ARDUINO_ARCH_ESP32 + +#endif // USE_SOCKET_IMPL_BSD_SOCKETS diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp new file mode 100644 index 0000000000..a5afea072e --- /dev/null +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -0,0 +1,475 @@ +#include "socket.h" +#include "esphome/core/defines.h" + +#ifdef USE_SOCKET_IMPL_LWIP_TCP + +#include "lwip/ip.h" +#include "lwip/netif.h" +#include "lwip/opt.h" +#include "lwip/tcp.h" +#include +#include +#include + +#include "esphome/core/log.h" + +namespace esphome { +namespace socket { + +static const char *const TAG = "lwip"; + +class LWIPRawImpl : public Socket { + public: + LWIPRawImpl(struct tcp_pcb *pcb) : pcb_(pcb) {} + ~LWIPRawImpl() override { + if (pcb_ != nullptr) { + tcp_abort(pcb_); + pcb_ = nullptr; + } + } + + void init() { + tcp_arg(pcb_, this); + tcp_accept(pcb_, LWIPRawImpl::s_accept_fn); + tcp_recv(pcb_, LWIPRawImpl::s_recv_fn); + tcp_err(pcb_, LWIPRawImpl::s_err_fn); + } + + std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) override { + if (pcb_ == nullptr) { + errno = EBADF; + return nullptr; + } + if (accepted_sockets_.empty()) { + errno = EWOULDBLOCK; + return nullptr; + } + std::unique_ptr sock = std::move(accepted_sockets_.front()); + accepted_sockets_.pop(); + if (addr != nullptr) { + sock->getpeername(addr, addrlen); + } + sock->init(); + return std::unique_ptr(std::move(sock)); + } + int bind(const struct sockaddr *name, socklen_t addrlen) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + if (name == nullptr) { + errno = EINVAL; + return 0; + } + ip_addr_t ip; + in_port_t port; + auto family = name->sa_family; +#if LWIP_IPV6 + if (family == AF_INET) { + if (addrlen < sizeof(sockaddr_in6)) { + errno = EINVAL; + return -1; + } + auto *addr4 = reinterpret_cast(name); + port = ntohs(addr4->sin_port); + ip.type = IPADDR_TYPE_V4; + ip.u_addr.ip4.addr = addr4->sin_addr.s_addr; + + } else if (family == AF_INET6) { + if (addrlen < sizeof(sockaddr_in)) { + errno = EINVAL; + return -1; + } + auto *addr6 = reinterpret_cast(name); + port = ntohs(addr6->sin6_port); + ip.type = IPADDR_TYPE_V6; + memcpy(&ip.u_addr.ip6.addr, &addr6->sin6_addr.un.u8_addr, 16); + } else { + errno = EINVAL; + return -1; + } +#else + if (family != AF_INET) { + errno = EINVAL; + return -1; + } + auto *addr4 = reinterpret_cast(name); + port = ntohs(addr4->sin_port); + ip.addr = addr4->sin_addr.s_addr; +#endif + err_t err = tcp_bind(pcb_, &ip, port); + if (err == ERR_USE) { + errno = EADDRINUSE; + return -1; + } + if (err == ERR_VAL) { + errno = EINVAL; + return -1; + } + if (err != ERR_OK) { + errno = EIO; + return -1; + } + return 0; + } + int close() override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + err_t err = tcp_close(pcb_); + if (err != ERR_OK) { + tcp_abort(pcb_); + pcb_ = nullptr; + errno = err == ERR_MEM ? ENOMEM : EIO; + return -1; + } + pcb_ = nullptr; + return 0; + } + int shutdown(int how) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + bool shut_rx = false, shut_tx = false; + if (how == SHUT_RD) { + shut_rx = true; + } else if (how == SHUT_WR) { + shut_tx = true; + } else if (how == SHUT_RDWR) { + shut_rx = shut_tx = true; + } else { + errno = EINVAL; + return -1; + } + err_t err = tcp_shutdown(pcb_, shut_rx, shut_tx); + if (err != ERR_OK) { + errno = err == ERR_MEM ? ENOMEM : EIO; + return -1; + } + return 0; + } + + int getpeername(struct sockaddr *name, socklen_t *addrlen) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + if (name == nullptr || addrlen == nullptr) { + errno = EINVAL; + return -1; + } + if (*addrlen < sizeof(struct sockaddr_in)) { + errno = EINVAL; + return -1; + } + struct sockaddr_in *addr = reinterpret_cast(name); + addr->sin_family = AF_INET; + *addrlen = addr->sin_len = sizeof(struct sockaddr_in); + addr->sin_port = pcb_->remote_port; + addr->sin_addr.s_addr = pcb_->remote_ip.addr; + return 0; + } + std::string getpeername() override { + if (pcb_ == nullptr) { + errno = EBADF; + return ""; + } + char buffer[24]; + uint32_t ip4 = pcb_->remote_ip.addr; + snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", (ip4 >> 24) & 0xFF, (ip4 >> 16) & 0xFF, (ip4 >> 8) & 0xFF, + (ip4 >> 0) & 0xFF); + return std::string(buffer); + } + int getsockname(struct sockaddr *name, socklen_t *addrlen) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + if (name == nullptr || addrlen == nullptr) { + errno = EINVAL; + return -1; + } + if (*addrlen < sizeof(struct sockaddr_in)) { + errno = EINVAL; + return -1; + } + struct sockaddr_in *addr = reinterpret_cast(name); + addr->sin_family = AF_INET; + *addrlen = addr->sin_len = sizeof(struct sockaddr_in); + addr->sin_port = pcb_->local_port; + addr->sin_addr.s_addr = pcb_->local_ip.addr; + return 0; + } + std::string getsockname() override { + if (pcb_ == nullptr) { + errno = EBADF; + return ""; + } + char buffer[24]; + uint32_t ip4 = pcb_->local_ip.addr; + snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", (ip4 >> 24) & 0xFF, (ip4 >> 16) & 0xFF, (ip4 >> 8) & 0xFF, + (ip4 >> 0) & 0xFF); + return std::string(buffer); + } + int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + if (optlen == nullptr || optval == nullptr) { + errno = EINVAL; + return -1; + } + if (level == SOL_SOCKET && optname == SO_REUSEADDR) { + if (*optlen < 4) { + errno = EINVAL; + return -1; + } + + // lwip doesn't seem to have this feature. Don't send an error + // to prevent warnings + *reinterpret_cast(optval) = 1; + *optlen = 4; + return 0; + } + if (level == IPPROTO_TCP && optname == TCP_NODELAY) { + if (*optlen < 4) { + errno = EINVAL; + return -1; + } + *reinterpret_cast(optval) = tcp_nagle_disabled(pcb_); + *optlen = 4; + return 0; + } + + errno = EINVAL; + return -1; + } + int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + if (level == SOL_SOCKET && optname == SO_REUSEADDR) { + if (optlen != 4) { + errno = EINVAL; + return -1; + } + + // lwip doesn't seem to have this feature. Don't send an error + // to prevent warnings + return 0; + } + if (level == IPPROTO_TCP && optname == TCP_NODELAY) { + if (optlen != 4) { + errno = EINVAL; + return -1; + } + int val = *reinterpret_cast(optval); + if (val != 0) { + tcp_nagle_disable(pcb_); + } else { + tcp_nagle_enable(pcb_); + } + return 0; + } + + errno = EINVAL; + return -1; + } + int listen(int backlog) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + struct tcp_pcb *listen_pcb = tcp_listen_with_backlog(pcb_, backlog); + if (listen_pcb == nullptr) { + tcp_abort(pcb_); + pcb_ = nullptr; + errno = EOPNOTSUPP; + return -1; + } + // tcp_listen reallocates the pcb, replace ours + pcb_ = listen_pcb; + // set callbacks on new pcb + tcp_arg(pcb_, this); + tcp_accept(pcb_, LWIPRawImpl::s_accept_fn); + return 0; + } + ssize_t read(void *buf, size_t len) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + if (rx_closed_ && rx_buf_ == nullptr) { + errno = ECONNRESET; + return -1; + } + if (len == 0) { + return 0; + } + if (rx_buf_ == nullptr) { + errno = EWOULDBLOCK; + return -1; + } + + size_t read = 0; + uint8_t *buf8 = reinterpret_cast(buf); + while (len && rx_buf_ != nullptr) { + size_t pb_len = rx_buf_->len; + size_t pb_left = pb_len - rx_buf_offset_; + if (pb_left == 0) + break; + size_t copysize = std::min(len, pb_left); + memcpy(buf8, reinterpret_cast(rx_buf_->payload) + rx_buf_offset_, copysize); + + if (pb_left == copysize) { + // full pb copied, free it + if (rx_buf_->next == nullptr) { + // last buffer in chain + pbuf_free(rx_buf_); + rx_buf_ = nullptr; + rx_buf_offset_ = 0; + } else { + auto *old_buf = rx_buf_; + rx_buf_ = rx_buf_->next; + pbuf_ref(rx_buf_); + pbuf_free(old_buf); + rx_buf_offset_ = 0; + } + } else { + rx_buf_offset_ += copysize; + } + tcp_recved(pcb_, copysize); + + buf8 += copysize; + len -= copysize; + read += copysize; + } + + return read; + } + ssize_t write(const void *buf, size_t len) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + if (len == 0) + return 0; + if (buf == nullptr) { + errno = EINVAL; + return 0; + } + auto space = tcp_sndbuf(pcb_); + if (space == 0) { + errno = EWOULDBLOCK; + return -1; + } + size_t to_send = std::min((size_t) space, len); + err_t err = tcp_write(pcb_, buf, to_send, TCP_WRITE_FLAG_COPY); + if (err == ERR_MEM) { + errno = EWOULDBLOCK; + return -1; + } + if (err != ERR_OK) { + errno = EIO; + return -1; + } + err = tcp_output(pcb_); + if (err != ERR_OK) { + errno = EIO; + return -1; + } + return to_send; + } + int setblocking(bool blocking) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + if (blocking) { + // blocking operation not supported + errno = EINVAL; + return -1; + } + return 0; + } + + err_t accept_fn(struct tcp_pcb *newpcb, err_t err) { + if (err != ERR_OK || newpcb == nullptr) { + // "An error code if there has been an error accepting. Only return ERR_ABRT if you have + // called tcp_abort from within the callback function!" + // https://www.nongnu.org/lwip/2_1_x/tcp_8h.html#a00517abce6856d6c82f0efebdafb734d + // nothing to do here, we just don't push it to the queue + return ERR_OK; + } + accepted_sockets_.emplace(new LWIPRawImpl(newpcb)); + return ERR_OK; + } + void err_fn(err_t err) { + // "If a connection is aborted because of an error, the application is alerted of this event by + // the err callback." + // pcb is already freed when this callback is called + // ERR_RST: connection was reset by remote host + // ERR_ABRT: aborted through tcp_abort or TCP timer + pcb_ = nullptr; + } + err_t recv_fn(struct pbuf *pb, err_t err) { + if (err != 0) { + // "An error code if there has been an error receiving Only return ERR_ABRT if you have + // called tcp_abort from within the callback function!" + rx_closed_ = true; + return ERR_OK; + } + if (pb == nullptr) { + rx_closed_ = true; + return ERR_OK; + } + if (rx_buf_ == nullptr) { + // no need to copy because lwIP gave control of it to us + rx_buf_ = pb; + rx_buf_offset_ = 0; + } else { + pbuf_cat(rx_buf_, pb); + } + return ERR_OK; + } + + static err_t s_accept_fn(void *arg, struct tcp_pcb *newpcb, err_t err) { + LWIPRawImpl *arg_this = reinterpret_cast(arg); + return arg_this->accept_fn(newpcb, err); + } + + static void s_err_fn(void *arg, err_t err) { + LWIPRawImpl *arg_this = reinterpret_cast(arg); + return arg_this->err_fn(err); + } + + static err_t s_recv_fn(void *arg, struct tcp_pcb *pcb, struct pbuf *pb, err_t err) { + LWIPRawImpl *arg_this = reinterpret_cast(arg); + return arg_this->recv_fn(pb, err); + } + + protected: + struct tcp_pcb *pcb_; + std::queue> accepted_sockets_; + bool rx_closed_ = false; + pbuf *rx_buf_ = nullptr; + size_t rx_buf_offset_ = 0; +}; + +std::unique_ptr socket(int domain, int type, int protocol) { + auto *pcb = tcp_new(); + if (pcb == nullptr) + return nullptr; + auto *sock = new LWIPRawImpl(pcb); + sock->init(); + return std::unique_ptr{sock}; +} + +} // namespace socket +} // namespace esphome + +#endif // USE_SOCKET_IMPL_LWIP_TCP diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h new file mode 100644 index 0000000000..7a5ce79161 --- /dev/null +++ b/esphome/components/socket/socket.h @@ -0,0 +1,42 @@ +#pragma once +#include +#include + +#include "headers.h" +#include "esphome/core/optional.h" + +namespace esphome { +namespace socket { + +class Socket { + public: + Socket() = default; + virtual ~Socket() = default; + Socket(const Socket &) = delete; + Socket &operator=(const Socket &) = delete; + + virtual std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) = 0; + virtual int bind(const struct sockaddr *addr, socklen_t addrlen) = 0; + virtual int close() = 0; + // not supported yet: + // virtual int connect(const std::string &address) = 0; + // virtual int connect(const struct sockaddr *addr, socklen_t addrlen) = 0; + virtual int shutdown(int how) = 0; + + virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0; + virtual std::string getpeername() = 0; + virtual int getsockname(struct sockaddr *addr, socklen_t *addrlen) = 0; + virtual std::string getsockname() = 0; + virtual int getsockopt(int level, int optname, void *optval, socklen_t *optlen) = 0; + virtual int setsockopt(int level, int optname, const void *optval, socklen_t optlen) = 0; + virtual int listen(int backlog) = 0; + virtual ssize_t read(void *buf, size_t len) = 0; + virtual ssize_t write(const void *buf, size_t len) = 0; + virtual int setblocking(bool blocking) = 0; + virtual int loop() { return 0; }; +}; + +std::unique_ptr socket(int domain, int type, int protocol); + +} // namespace socket +} // namespace esphome diff --git a/esphome/core/defines.h b/esphome/core/defines.h index ec10e86586..d73f7e9d00 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -48,5 +48,11 @@ #define USE_IMPROV #endif +#ifdef ARDUINO_ARCH_ESP8266 +#define USE_SOCKET_IMPL_LWIP_TCP +#else +#define USE_SOCKET_IMPL_BSD_SOCKETS +#endif + // Disabled feature flags //#define USE_BSEC // Requires a library with proprietary license. diff --git a/script/ci-custom.py b/script/ci-custom.py index 5dad3e2445..cdc450a96b 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -261,7 +261,7 @@ def highlight(s): @lint_re_check( r"^#define\s+([a-zA-Z0-9_]+)\s+([0-9bx]+)" + CPP_RE_EOL, include=cpp_include, - exclude=["esphome/core/log.h"], + exclude=["esphome/core/log.h", "esphome/components/socket/headers.h"], ) def lint_no_defines(fname, match): s = highlight( @@ -493,7 +493,10 @@ def lint_relative_py_import(fname): "esphome/components/*.h", "esphome/components/*.cpp", "esphome/components/*.tcc", - ] + ], + exclude=[ + "esphome/components/socket/headers.h", + ], ) def lint_namespace(fname, content): expected_name = re.match( From fa2eb46cd6d3e5e38ebd60c65055aa836f2a4aab Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 8 Sep 2021 16:11:09 +1200 Subject: [PATCH 1281/1841] Allow .yml files to show up in the dashboard (#2257) --- esphome/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/util.py b/esphome/util.py index 56bc97ca71..527e370ad8 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -260,8 +260,8 @@ def filter_yaml_files(files): f for f in files if ( - os.path.splitext(f)[1] == ".yaml" - and os.path.basename(f) != "secrets.yaml" + os.path.splitext(f)[1] in (".yaml", ".yml") + and os.path.basename(f) not in ("secrets.yaml", "secrets.yml") and not os.path.basename(f).startswith(".") ) ] From f87a701b28ac7164895e0968a4dfef4736fbbf4a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 8 Sep 2021 17:14:12 +1200 Subject: [PATCH 1282/1841] Bump dashboard to 20210908.0 and fix card names for yml (#2258) --- esphome/dashboard/dashboard.py | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index ab9dd39735..e44ee64770 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -431,7 +431,7 @@ class DashboardEntry: @property def name(self): if self.storage is None: - return self.filename[: -len(".yaml")] + return self.filename.removesuffix(".yml").removesuffix(".yaml") return self.storage.name @property diff --git a/requirements.txt b/requirements.txt index 79fefbfcaa..daaf86e641 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==3.1 click==7.1.2 -esphome-dashboard==20210826.0 +esphome-dashboard==20210908.0 From 4356581db0d0e11d34f194961d3b823de5b198bf Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 8 Sep 2021 19:51:20 +1200 Subject: [PATCH 1283/1841] Remove removesuffix --- esphome/dashboard/dashboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index e44ee64770..97f9d60693 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -431,7 +431,7 @@ class DashboardEntry: @property def name(self): if self.storage is None: - return self.filename.removesuffix(".yml").removesuffix(".yaml") + return self.filename.replace(".yml", "").replace(".yaml", "") return self.storage.name @property From e44f447d8520833930677892e34842e084715753 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 8 Sep 2021 12:02:32 +0200 Subject: [PATCH 1284/1841] Fix socket not setting callbacks early enough (#2260) --- .../components/socket/lwip_raw_tcp_impl.cpp | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index a5afea072e..aaeee7268a 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -16,19 +16,28 @@ namespace esphome { namespace socket { -static const char *const TAG = "lwip"; +static const char *const TAG = "socket.lwip"; + +// set to 1 to enable verbose lwip logging +#if 0 +#define LWIP_LOG(msg, ...) ESP_LOGVV(TAG, "socket %p: " msg, this, ##__VA_ARGS__) +#else +#define LWIP_LOG(msg, ...) +#endif class LWIPRawImpl : public Socket { public: LWIPRawImpl(struct tcp_pcb *pcb) : pcb_(pcb) {} ~LWIPRawImpl() override { if (pcb_ != nullptr) { + LWIP_LOG("tcp_abort(%p)", pcb_); tcp_abort(pcb_); pcb_ = nullptr; } } void init() { + LWIP_LOG("init(%p)", pcb_); tcp_arg(pcb_, this); tcp_accept(pcb_, LWIPRawImpl::s_accept_fn); tcp_recv(pcb_, LWIPRawImpl::s_recv_fn); @@ -49,7 +58,7 @@ class LWIPRawImpl : public Socket { if (addr != nullptr) { sock->getpeername(addr, addrlen); } - sock->init(); + LWIP_LOG("accept(%p)", sock.get()); return std::unique_ptr(std::move(sock)); } int bind(const struct sockaddr *name, socklen_t addrlen) override { @@ -97,6 +106,7 @@ class LWIPRawImpl : public Socket { port = ntohs(addr4->sin_port); ip.addr = addr4->sin_addr.s_addr; #endif + LWIP_LOG("tcp_bind(%p ip=%u port=%u)", pcb_, ip.addr, port); err_t err = tcp_bind(pcb_, &ip, port); if (err == ERR_USE) { errno = EADDRINUSE; @@ -117,6 +127,7 @@ class LWIPRawImpl : public Socket { errno = EBADF; return -1; } + LWIP_LOG("tcp_close(%p)", pcb_); err_t err = tcp_close(pcb_); if (err != ERR_OK) { tcp_abort(pcb_); @@ -143,6 +154,7 @@ class LWIPRawImpl : public Socket { errno = EINVAL; return -1; } + LWIP_LOG("tcp_shutdown(%p shut_rx=%d shut_tx=%d)", pcb_, shut_rx ? 1 : 0, shut_tx ? 1 : 0); err_t err = tcp_shutdown(pcb_, shut_rx, shut_tx); if (err != ERR_OK) { errno = err == ERR_MEM ? ENOMEM : EIO; @@ -178,8 +190,8 @@ class LWIPRawImpl : public Socket { } char buffer[24]; uint32_t ip4 = pcb_->remote_ip.addr; - snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", (ip4 >> 24) & 0xFF, (ip4 >> 16) & 0xFF, (ip4 >> 8) & 0xFF, - (ip4 >> 0) & 0xFF); + snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", (ip4 >> 0) & 0xFF, (ip4 >> 8) & 0xFF, (ip4 >> 16) & 0xFF, + (ip4 >> 24) & 0xFF); return std::string(buffer); } int getsockname(struct sockaddr *name, socklen_t *addrlen) override { @@ -209,8 +221,8 @@ class LWIPRawImpl : public Socket { } char buffer[24]; uint32_t ip4 = pcb_->local_ip.addr; - snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", (ip4 >> 24) & 0xFF, (ip4 >> 16) & 0xFF, (ip4 >> 8) & 0xFF, - (ip4 >> 0) & 0xFF); + snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", (ip4 >> 0) & 0xFF, (ip4 >> 8) & 0xFF, (ip4 >> 16) & 0xFF, + (ip4 >> 24) & 0xFF); return std::string(buffer); } int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { @@ -284,6 +296,7 @@ class LWIPRawImpl : public Socket { errno = EBADF; return -1; } + LWIP_LOG("tcp_listen_with_backlog(%p backlog=%d)", pcb_, backlog); struct tcp_pcb *listen_pcb = tcp_listen_with_backlog(pcb_, backlog); if (listen_pcb == nullptr) { tcp_abort(pcb_); @@ -294,6 +307,7 @@ class LWIPRawImpl : public Socket { // tcp_listen reallocates the pcb, replace ours pcb_ = listen_pcb; // set callbacks on new pcb + LWIP_LOG("tcp_arg(%p)", pcb_); tcp_arg(pcb_, this); tcp_accept(pcb_, LWIPRawImpl::s_accept_fn); return 0; @@ -342,6 +356,7 @@ class LWIPRawImpl : public Socket { } else { rx_buf_offset_ += copysize; } + LWIP_LOG("tcp_recved(%p %u)", pcb_, copysize); tcp_recved(pcb_, copysize); buf8 += copysize; @@ -368,6 +383,7 @@ class LWIPRawImpl : public Socket { return -1; } size_t to_send = std::min((size_t) space, len); + LWIP_LOG("tcp_write(%p buf=%p %u)", pcb_, buf, to_send); err_t err = tcp_write(pcb_, buf, to_send, TCP_WRITE_FLAG_COPY); if (err == ERR_MEM) { errno = EWOULDBLOCK; @@ -377,6 +393,7 @@ class LWIPRawImpl : public Socket { errno = EIO; return -1; } + LWIP_LOG("tcp_output(%p)", pcb_); err = tcp_output(pcb_); if (err != ERR_OK) { errno = EIO; @@ -398,6 +415,7 @@ class LWIPRawImpl : public Socket { } err_t accept_fn(struct tcp_pcb *newpcb, err_t err) { + LWIP_LOG("accept(newpcb=%p err=%d)", newpcb, err); if (err != ERR_OK || newpcb == nullptr) { // "An error code if there has been an error accepting. Only return ERR_ABRT if you have // called tcp_abort from within the callback function!" @@ -405,10 +423,13 @@ class LWIPRawImpl : public Socket { // nothing to do here, we just don't push it to the queue return ERR_OK; } - accepted_sockets_.emplace(new LWIPRawImpl(newpcb)); + auto *sock = new LWIPRawImpl(newpcb); + sock->init(); + accepted_sockets_.emplace(sock); return ERR_OK; } void err_fn(err_t err) { + LWIP_LOG("err(err=%d)", err); // "If a connection is aborted because of an error, the application is alerted of this event by // the err callback." // pcb is already freed when this callback is called @@ -417,6 +438,7 @@ class LWIPRawImpl : public Socket { pcb_ = nullptr; } err_t recv_fn(struct pbuf *pb, err_t err) { + LWIP_LOG("recv(pb=%p err=%d)", pb, err); if (err != 0) { // "An error code if there has been an error receiving Only return ERR_ABRT if you have // called tcp_abort from within the callback function!" From 2790d72bff8e0ede8649e861e11fbe33a8a07be0 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 8 Sep 2021 12:52:57 +0200 Subject: [PATCH 1285/1841] Convert API to use sockets (#2253) * Socket component * Lint * Lint * Fix esp8266 missing INADDR_ANY * API convert to sockets and frame helper * Fix compile error Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/api/__init__.py | 2 +- esphome/components/api/api_connection.cpp | 200 +++++-------- esphome/components/api/api_connection.h | 35 +-- esphome/components/api/api_frame_helper.cpp | 294 ++++++++++++++++++++ esphome/components/api/api_frame_helper.h | 103 +++++++ esphome/components/api/api_server.cpp | 70 ++++- esphome/components/api/api_server.h | 11 +- esphome/components/socket/headers.h | 14 +- 8 files changed, 563 insertions(+), 166 deletions(-) create mode 100644 esphome/components/api/api_frame_helper.cpp create mode 100644 esphome/components/api/api_frame_helper.h diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 559f8f649c..fc140dc7d2 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -19,7 +19,7 @@ from esphome.const import ( from esphome.core import coroutine_with_priority DEPENDENCIES = ["network"] -AUTO_LOAD = ["async_tcp"] +AUTO_LOAD = ["socket"] CODEOWNERS = ["@OttoWinter"] api_ns = cg.esphome_ns.namespace("api") diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 99e611be10..bce0b0bab8 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -2,6 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/util.h" #include "esphome/core/version.h" +#include #ifdef USE_DEEP_SLEEP #include "esphome/components/deep_sleep/deep_sleep_component.h" @@ -18,74 +19,27 @@ namespace api { static const char *const TAG = "api.connection"; -APIConnection::APIConnection(AsyncClient *client, APIServer *parent) - : client_(client), parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) { - this->client_->onError([](void *s, AsyncClient *c, int8_t error) { ((APIConnection *) s)->on_error_(error); }, this); - this->client_->onDisconnect([](void *s, AsyncClient *c) { ((APIConnection *) s)->on_disconnect_(); }, this); - this->client_->onTimeout([](void *s, AsyncClient *c, uint32_t time) { ((APIConnection *) s)->on_timeout_(time); }, - this); - this->client_->onData([](void *s, AsyncClient *c, void *buf, - size_t len) { ((APIConnection *) s)->on_data_(reinterpret_cast(buf), len); }, - this); +APIConnection::APIConnection(std::unique_ptr sock, APIServer *parent) + : parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) { + this->proto_write_buffer_.reserve(64); - this->send_buffer_.reserve(64); - this->recv_buffer_.reserve(32); - this->client_info_ = this->client_->remoteIP().toString().c_str(); + helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock))}; +} +void APIConnection::start() { this->last_traffic_ = millis(); -} -APIConnection::~APIConnection() { delete this->client_; } -void APIConnection::on_error_(int8_t error) { this->remove_ = true; } -void APIConnection::on_disconnect_() { this->remove_ = true; } -void APIConnection::on_timeout_(uint32_t time) { this->on_fatal_error(); } -void APIConnection::on_data_(uint8_t *buf, size_t len) { - if (len == 0 || buf == nullptr) + + APIError err = helper_->init(); + if (err != APIError::OK) { + ESP_LOGW(TAG, "Helper init failed: %d errno=%d", (int) err, errno); + remove_ = true; return; - this->recv_buffer_.insert(this->recv_buffer_.end(), buf, buf + len); -} -void APIConnection::parse_recv_buffer_() { - if (this->recv_buffer_.empty() || this->remove_) - return; - - while (!this->recv_buffer_.empty()) { - if (this->recv_buffer_[0] != 0x00) { - ESP_LOGW(TAG, "Invalid preamble from %s", this->client_info_.c_str()); - this->on_fatal_error(); - return; - } - uint32_t i = 1; - const uint32_t size = this->recv_buffer_.size(); - uint32_t consumed; - auto msg_size_varint = ProtoVarInt::parse(&this->recv_buffer_[i], size - i, &consumed); - if (!msg_size_varint.has_value()) - // not enough data there yet - return; - i += consumed; - uint32_t msg_size = msg_size_varint->as_uint32(); - - auto msg_type_varint = ProtoVarInt::parse(&this->recv_buffer_[i], size - i, &consumed); - if (!msg_type_varint.has_value()) - // not enough data there yet - return; - i += consumed; - uint32_t msg_type = msg_type_varint->as_uint32(); - - if (size - i < msg_size) - // message body not fully received - return; - - uint8_t *msg = &this->recv_buffer_[i]; - this->read_message(msg_size, msg_type, msg); - if (this->remove_) - return; - // pop front - uint32_t total = i + msg_size; - this->recv_buffer_.erase(this->recv_buffer_.begin(), this->recv_buffer_.begin() + total); - this->last_traffic_ = millis(); } + client_info_ = helper_->getpeername(); + helper_->set_log_info(client_info_); } -void APIConnection::disconnect_client() { - this->client_->close(); +void APIConnection::force_disconnect_client() { + this->helper_->close(); this->remove_ = true; } @@ -93,61 +47,74 @@ void APIConnection::loop() { if (this->remove_) return; - if (this->next_close_) { - this->disconnect_client(); - return; - } - if (!network_is_connected()) { // when network is disconnected force disconnect immediately // don't wait for timeout this->on_fatal_error(); return; } - if (this->client_->disconnected()) { - // failsafe for disconnect logic - this->on_disconnect_(); + if (this->next_close_) { + this->helper_->close(); + this->remove_ = true; return; } - this->parse_recv_buffer_(); + + APIError err = helper_->loop(); + if (err != APIError::OK) { + on_fatal_error(); + ESP_LOGW(TAG, "%s: Socket operation failed: %d", client_info_.c_str(), (int) err); + return; + } + ReadPacketBuffer buffer; + err = helper_->read_packet(&buffer); + if (err == APIError::WOULD_BLOCK) { + // pass + } else if (err != APIError::OK) { + on_fatal_error(); + ESP_LOGW(TAG, "%s: Reading failed: %d", client_info_.c_str(), (int) err); + return; + } else { + this->last_traffic_ = millis(); + // read a packet + this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]); + if (this->remove_) + return; + } this->list_entities_iterator_.advance(); this->initial_state_iterator_.advance(); const uint32_t keepalive = 60000; + const uint32_t now = millis(); if (this->sent_ping_) { // Disconnect if not responded within 2.5*keepalive - if (millis() - this->last_traffic_ > (keepalive * 5) / 2) { + if (now - this->last_traffic_ > (keepalive * 5) / 2) { + this->force_disconnect_client(); ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str()); - this->disconnect_client(); } - } else if (millis() - this->last_traffic_ > keepalive) { + } else if (now - this->last_traffic_ > keepalive) { this->sent_ping_ = true; this->send_ping_request(PingRequest()); } #ifdef USE_ESP32_CAMERA - if (this->image_reader_.available()) { - uint32_t space = this->client_->space(); - // reserve 15 bytes for metadata, and at least 64 bytes of data - if (space >= 15 + 64) { - uint32_t to_send = std::min(space - 15, this->image_reader_.available()); - auto buffer = this->create_buffer(); - // fixed32 key = 1; - buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash()); - // bytes data = 2; - buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send); - // bool done = 3; - bool done = this->image_reader_.available() == to_send; - buffer.encode_bool(3, done); - bool success = this->send_buffer(buffer, 44); + if (this->image_reader_.available() && this->helper_->can_write_without_blocking()) { + uint32_t to_send = std::min((size_t) 1024, this->image_reader_.available()); + auto buffer = this->create_buffer(); + // fixed32 key = 1; + buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash()); + // bytes data = 2; + buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send); + // bool done = 3; + bool done = this->image_reader_.available() == to_send; + buffer.encode_bool(3, done); + bool success = this->send_buffer(buffer, 44); - if (success) { - this->image_reader_.consume_data(to_send); - } - if (success && done) { - this->image_reader_.return_image(); - } + if (success) { + this->image_reader_.consume_data(to_send); + } + if (success && done) { + this->image_reader_.return_image(); } } #endif @@ -709,8 +676,8 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin } HelloResponse APIConnection::hello(const HelloRequest &msg) { - this->client_info_ = msg.client_info + " (" + this->client_->remoteIP().toString().c_str(); - this->client_info_ += ")"; + this->client_info_ = msg.client_info + " (" + this->helper_->getpeername() + ")"; + this->helper_->set_log_info(client_info_); ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str()); HelloResponse resp; @@ -786,44 +753,31 @@ void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistant bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) { if (this->remove_) return false; + if (!this->helper_->can_write_without_blocking()) + return false; - std::vector header; - header.push_back(0x00); - ProtoVarInt(buffer.get_buffer()->size()).encode(header); - ProtoVarInt(message_type).encode(header); - - size_t needed_space = buffer.get_buffer()->size() + header.size(); - - if (needed_space > this->client_->space()) { - delay(0); - if (needed_space > this->client_->space()) { - // SubscribeLogsResponse - if (message_type != 29) { - ESP_LOGV(TAG, "Cannot send message because of TCP buffer space"); - } - delay(0); - return false; - } + APIError err = this->helper_->write_packet(message_type, buffer.get_buffer()->data(), buffer.get_buffer()->size()); + if (err == APIError::WOULD_BLOCK) + return false; + if (err != APIError::OK) { + on_fatal_error(); + ESP_LOGW(TAG, "%s: Packet write failed %d errno=%d", client_info_.c_str(), (int) err, errno); + return false; } - - this->client_->add(reinterpret_cast(header.data()), header.size(), - ASYNC_WRITE_FLAG_COPY | ASYNC_WRITE_FLAG_MORE); - this->client_->add(reinterpret_cast(buffer.get_buffer()->data()), buffer.get_buffer()->size(), - ASYNC_WRITE_FLAG_COPY); - bool ret = this->client_->send(); - return ret; + this->last_traffic_ = millis(); + return true; } void APIConnection::on_unauthenticated_access() { - ESP_LOGD(TAG, "'%s' tried to access without authentication.", this->client_info_.c_str()); this->on_fatal_error(); + ESP_LOGD(TAG, "'%s' tried to access without authentication.", this->client_info_.c_str()); } void APIConnection::on_no_setup_connection() { - ESP_LOGD(TAG, "'%s' tried to access without full connection.", this->client_info_.c_str()); this->on_fatal_error(); + ESP_LOGD(TAG, "'%s' tried to access without full connection.", this->client_info_.c_str()); } void APIConnection::on_fatal_error() { ESP_LOGV(TAG, "Error: Disconnecting %s", this->client_info_.c_str()); - this->client_->close(); + this->helper_->close(); this->remove_ = true; } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index bc9839a423..a1788bbede 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -5,16 +5,18 @@ #include "api_pb2.h" #include "api_pb2_service.h" #include "api_server.h" +#include "api_frame_helper.h" namespace esphome { namespace api { class APIConnection : public APIServerConnection { public: - APIConnection(AsyncClient *client, APIServer *parent); - virtual ~APIConnection(); + APIConnection(std::unique_ptr socket, APIServer *parent); + virtual ~APIConnection() = default; - void disconnect_client(); + void start(); + void force_disconnect_client(); void loop(); bool send_list_info_done() { @@ -87,8 +89,8 @@ class APIConnection : public APIServerConnection { #endif void on_disconnect_response(const DisconnectResponse &value) override { - // we initiated disconnect_client - this->next_close_ = true; + this->helper_->close(); + this->remove_ = true; } void on_ping_response(const PingResponse &value) override { // we initiated ping @@ -102,6 +104,8 @@ class APIConnection : public APIServerConnection { ConnectResponse connect(const ConnectRequest &msg) override; DisconnectResponse disconnect(const DisconnectRequest &msg) override { // remote initiated disconnect_client + // don't close yet, we still need to send the disconnect response + // close will happen on next loop this->next_close_ = true; DisconnectResponse resp; return resp; @@ -135,19 +139,16 @@ class APIConnection : public APIServerConnection { void on_unauthenticated_access() override; void on_no_setup_connection() override; ProtoWriteBuffer create_buffer() override { - this->send_buffer_.clear(); - return {&this->send_buffer_}; + // FIXME: ensure no recursive writes can happen + this->proto_write_buffer_.clear(); + return {&this->proto_write_buffer_}; } bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override; protected: friend APIServer; - void on_error_(int8_t error); - void on_disconnect_(); - void on_timeout_(uint32_t time); - void on_data_(uint8_t *buf, size_t len); - void parse_recv_buffer_(); + bool send_(const void *buf, size_t len, bool force); enum class ConnectionState { WAITING_FOR_HELLO, @@ -157,8 +158,10 @@ class APIConnection : public APIServerConnection { bool remove_{false}; - std::vector send_buffer_; - std::vector recv_buffer_; + // Buffer used to encode proto messages + // Re-use to prevent allocations + std::vector proto_write_buffer_; + std::unique_ptr helper_; std::string client_info_; #ifdef USE_ESP32_CAMERA @@ -170,9 +173,7 @@ class APIConnection : public APIServerConnection { uint32_t last_traffic_; bool sent_ping_{false}; bool service_call_subscription_{false}; - bool current_nodelay_{false}; - bool next_close_{false}; - AsyncClient *client_; + bool next_close_ = false; APIServer *parent_; InitialStateIterator initial_state_iterator_; ListEntitiesIterator list_entities_iterator_; diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp new file mode 100644 index 0000000000..f903ab8656 --- /dev/null +++ b/esphome/components/api/api_frame_helper.cpp @@ -0,0 +1,294 @@ +#include "api_frame_helper.h" + +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "proto.h" + +namespace esphome { +namespace api { + +static const char *const TAG = "api.socket"; + +/// Is the given return value (from read/write syscalls) a wouldblock error? +bool is_would_block(ssize_t ret) { + if (ret == -1) { + return errno == EWOULDBLOCK || errno == EAGAIN; + } + return ret == 0; +} + +#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__) + +/// Initialize the frame helper, returns OK if successful. +APIError APIPlaintextFrameHelper::init() { + if (state_ != State::INITIALIZE || socket_ == nullptr) { + HELPER_LOG("Bad state for init %d", (int) state_); + return APIError::BAD_STATE; + } + int err = socket_->setblocking(false); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("Setting nonblocking failed with errno %d", errno); + return APIError::TCP_NONBLOCKING_FAILED; + } + int enable = 1; + err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("Setting nodelay failed with errno %d", errno); + return APIError::TCP_NODELAY_FAILED; + } + + state_ = State::DATA; + return APIError::OK; +} +/// Not used for plaintext +APIError APIPlaintextFrameHelper::loop() { + if (state_ != State::DATA) { + return APIError::BAD_STATE; + } + // try send pending TX data + if (!tx_buf_.empty()) { + APIError err = try_send_tx_buf_(); + if (err != APIError::OK) { + return err; + } + } + return APIError::OK; +} + +/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter + * + * @param frame: The struct to hold the frame information in. + * msg: store the parsed frame in that struct + * + * @return See APIError + * + * error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame. + */ +APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { + int err; + APIError aerr; + + if (frame == nullptr) { + HELPER_LOG("Bad argument for try_read_frame_"); + return APIError::BAD_ARG; + } + + // read header + while (!rx_header_parsed_) { + uint8_t data; + ssize_t received = socket_->read(&data, 1); + if (is_would_block(received)) { + return APIError::WOULD_BLOCK; + } else if (received == -1) { + state_ = State::FAILED; + HELPER_LOG("Socket read failed with errno %d", errno); + return APIError::SOCKET_READ_FAILED; + } + rx_header_buf_.push_back(data); + + // try parse header + if (rx_header_buf_[0] != 0x00) { + state_ = State::FAILED; + HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]); + return APIError::BAD_INDICATOR; + } + + size_t i = 1; + size_t consumed = 0; + auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed); + if (!msg_size_varint.has_value()) { + // not enough data there yet + continue; + } + + i += consumed; + rx_header_parsed_len_ = msg_size_varint->as_uint32(); + + auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed); + if (!msg_type_varint.has_value()) { + // not enough data there yet + continue; + } + rx_header_parsed_type_ = msg_type_varint->as_uint32(); + rx_header_parsed_ = true; + } + // header reading done + + // reserve space for body + if (rx_buf_.size() != rx_header_parsed_len_) { + rx_buf_.resize(rx_header_parsed_len_); + } + + if (rx_buf_len_ < rx_header_parsed_len_) { + // more data to read + size_t to_read = rx_header_parsed_len_ - rx_buf_len_; + ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read); + if (is_would_block(received)) { + return APIError::WOULD_BLOCK; + } else if (received == -1) { + state_ = State::FAILED; + HELPER_LOG("Socket read failed with errno %d", errno); + return APIError::SOCKET_READ_FAILED; + } + rx_buf_len_ += received; + if (received != to_read) { + // not all read + return APIError::WOULD_BLOCK; + } + } + + // uncomment for even more debugging + // ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); + frame->msg = std::move(rx_buf_); + // consume msg + rx_buf_ = {}; + rx_buf_len_ = 0; + rx_header_buf_.clear(); + rx_header_parsed_ = false; + return APIError::OK; +} + +APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { + int err; + APIError aerr; + + if (state_ != State::DATA) { + return APIError::WOULD_BLOCK; + } + + ParsedFrame frame; + aerr = try_read_frame_(&frame); + if (aerr != APIError::OK) + return aerr; + + buffer->container = std::move(frame.msg); + buffer->data_offset = 0; + buffer->data_len = rx_header_parsed_len_; + buffer->type = rx_header_parsed_type_; + return APIError::OK; +} +bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } +APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) { + int err; + APIError aerr; + + if (state_ != State::DATA) { + return APIError::BAD_STATE; + } + + std::vector header; + header.push_back(0x00); + ProtoVarInt(payload_len).encode(header); + ProtoVarInt(type).encode(header); + + aerr = write_raw_(&header[0], header.size()); + if (aerr != APIError::OK) { + return aerr; + } + aerr = write_raw_(payload, payload_len); + if (aerr != APIError::OK) { + return aerr; + } + return APIError::OK; +} +APIError APIPlaintextFrameHelper::try_send_tx_buf_() { + // try send from tx_buf + while (state_ != State::CLOSED && !tx_buf_.empty()) { + ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size()); + if (sent == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) + break; + state_ = State::FAILED; + HELPER_LOG("Socket write failed with errno %d", errno); + return APIError::SOCKET_WRITE_FAILED; + } else if (sent == 0) { + break; + } + // TODO: inefficient if multiple packets in txbuf + // replace with deque of buffers + tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent); + } + + return APIError::OK; +} +/** Write the data to the socket, or buffer it a write would block + * + * @param data The data to write + * @param len The length of data + */ +APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) { + if (len == 0) + return APIError::OK; + int err; + APIError aerr; + + // uncomment for even more debugging + // ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); + + if (!tx_buf_.empty()) { + // try to empty tx_buf_ first + aerr = try_send_tx_buf_(); + if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK) + return aerr; + } + + if (!tx_buf_.empty()) { + // tx buf not empty, can't write now because then stream would be inconsistent + tx_buf_.insert(tx_buf_.end(), data, data + len); + return APIError::OK; + } + + ssize_t sent = socket_->write(data, len); + if (is_would_block(sent)) { + // operation would block, add buffer to tx_buf + tx_buf_.insert(tx_buf_.end(), data, data + len); + return APIError::OK; + } else if (sent == -1) { + // an error occured + state_ = State::FAILED; + HELPER_LOG("Socket write failed with errno %d", errno); + return APIError::SOCKET_WRITE_FAILED; + } else if (sent != len) { + // partially sent, add end to tx_buf + tx_buf_.insert(tx_buf_.end(), data + sent, data + len); + return APIError::OK; + } + // fully sent + return APIError::OK; +} +APIError APIPlaintextFrameHelper::write_frame_(const uint8_t *data, size_t len) { + APIError aerr; + + uint8_t header[3]; + header[0] = 0x01; // indicator + header[1] = (uint8_t)(len >> 8); + header[2] = (uint8_t) len; + + aerr = write_raw_(header, 3); + if (aerr != APIError::OK) + return aerr; + aerr = write_raw_(data, len); + return aerr; +} + +APIError APIPlaintextFrameHelper::close() { + state_ = State::CLOSED; + int err = socket_->close(); + if (err == -1) + return APIError::CLOSE_FAILED; + return APIError::OK; +} +APIError APIPlaintextFrameHelper::shutdown(int how) { + int err = socket_->shutdown(how); + if (err == -1) + return APIError::SHUTDOWN_FAILED; + if (how == SHUT_RDWR) { + state_ = State::CLOSED; + } + return APIError::OK; +} + +} // namespace api +} // namespace esphome diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h new file mode 100644 index 0000000000..14a0760c25 --- /dev/null +++ b/esphome/components/api/api_frame_helper.h @@ -0,0 +1,103 @@ +#pragma once +#include +#include +#include + +#include "esphome/core/defines.h" + +#include "esphome/components/socket/socket.h" + +namespace esphome { +namespace api { + +struct ReadPacketBuffer { + std::vector container; + uint16_t type; + size_t data_offset; + size_t data_len; +}; + +struct PacketBuffer { + const std::vector container; + uint16_t type; + uint8_t data_offset; + uint8_t data_len; +}; + +enum class APIError : int { + OK = 0, + WOULD_BLOCK = 1001, + BAD_INDICATOR = 1003, + BAD_DATA_PACKET = 1004, + TCP_NODELAY_FAILED = 1005, + TCP_NONBLOCKING_FAILED = 1006, + CLOSE_FAILED = 1007, + SHUTDOWN_FAILED = 1008, + BAD_STATE = 1009, + BAD_ARG = 1010, + SOCKET_READ_FAILED = 1011, + SOCKET_WRITE_FAILED = 1012, + OUT_OF_MEMORY = 1018, +}; + +class APIFrameHelper { + public: + virtual APIError init() = 0; + virtual APIError loop() = 0; + virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; + virtual bool can_write_without_blocking() = 0; + virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0; + virtual std::string getpeername() = 0; + virtual APIError close() = 0; + virtual APIError shutdown(int how) = 0; + // Give this helper a name for logging + virtual void set_log_info(std::string info) = 0; +}; +class APIPlaintextFrameHelper : public APIFrameHelper { + public: + APIPlaintextFrameHelper(std::unique_ptr socket) : socket_(std::move(socket)) {} + ~APIPlaintextFrameHelper() = default; + APIError init() override; + APIError loop() override; + APIError read_packet(ReadPacketBuffer *buffer) override; + bool can_write_without_blocking() override; + APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override; + std::string getpeername() override { return socket_->getpeername(); } + APIError close() override; + APIError shutdown(int how) override; + // Give this helper a name for logging + void set_log_info(std::string info) override { info_ = std::move(info); } + + protected: + struct ParsedFrame { + std::vector msg; + }; + + APIError try_read_frame_(ParsedFrame *frame); + APIError try_send_tx_buf_(); + APIError write_frame_(const uint8_t *data, size_t len); + APIError write_raw_(const uint8_t *data, size_t len); + + std::unique_ptr socket_; + + std::string info_; + std::vector rx_header_buf_; + bool rx_header_parsed_ = false; + uint32_t rx_header_parsed_type_ = 0; + uint32_t rx_header_parsed_len_ = 0; + + std::vector rx_buf_; + size_t rx_buf_len_ = 0; + + std::vector tx_buf_; + + enum class State { + INITIALIZE = 1, + DATA = 2, + CLOSED = 3, + FAILED = 4, + } state_ = State::INITIALIZE; +}; + +} // namespace api +} // namespace esphome diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index d48c0a4fd8..c4c193b389 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -1,10 +1,11 @@ #include "api_server.h" #include "api_connection.h" -#include "esphome/core/log.h" #include "esphome/core/application.h" -#include "esphome/core/util.h" #include "esphome/core/defines.h" +#include "esphome/core/log.h" +#include "esphome/core/util.h" #include "esphome/core/version.h" +#include #ifdef USE_LOGGER #include "esphome/components/logger/logger.h" @@ -21,20 +22,45 @@ static const char *const TAG = "api"; void APIServer::setup() { ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server..."); this->setup_controller(); - this->server_ = AsyncServer(this->port_); - this->server_.setNoDelay(false); - this->server_.begin(); - this->server_.onClient( - [](void *s, AsyncClient *client) { - if (client == nullptr) - return; + socket_ = socket::socket(AF_INET, SOCK_STREAM, 0); + if (socket_ == nullptr) { + ESP_LOGW(TAG, "Could not create socket."); + this->mark_failed(); + return; + } + int enable = 1; + int err = socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + if (err != 0) { + ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err); + // we can still continue + } + err = socket_->setblocking(false); + if (err != 0) { + ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err); + this->mark_failed(); + return; + } + + struct sockaddr_in server; + memset(&server, 0, sizeof(server)); + server.sin_family = AF_INET; + server.sin_addr.s_addr = ESPHOME_INADDR_ANY; + server.sin_port = htons(this->port_); + + err = socket_->bind((struct sockaddr *) &server, sizeof(server)); + if (err != 0) { + ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno); + this->mark_failed(); + return; + } + + err = socket_->listen(4); + if (err != 0) { + ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno); + this->mark_failed(); + return; + } - // can't print here because in lwIP thread - // ESP_LOGD(TAG, "New client connected from %s", client->remoteIP().toString().c_str()); - auto *a_this = (APIServer *) s; - a_this->clients_.push_back(new APIConnection(client, a_this)); - }, - this); #ifdef USE_LOGGER if (logger::global_logger != nullptr) { logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { @@ -59,6 +85,20 @@ void APIServer::setup() { #endif } void APIServer::loop() { + // Accept new clients + while (true) { + struct sockaddr_storage source_addr; + socklen_t addr_len = sizeof(source_addr); + auto sock = socket_->accept((struct sockaddr *) &source_addr, &addr_len); + if (!sock) + break; + ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str()); + + auto *conn = new APIConnection(std::move(sock), this); + clients_.push_back(conn); + conn->start(); + } + // Partition clients into remove and active auto new_end = std::partition(this->clients_.begin(), this->clients_.end(), [](APIConnection *conn) { return !conn->remove_; }); diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 96b3192e9e..7c42fe7dd5 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -4,6 +4,7 @@ #include "esphome/core/controller.h" #include "esphome/core/defines.h" #include "esphome/core/log.h" +#include "esphome/components/socket/socket.h" #include "api_pb2.h" #include "api_pb2_service.h" #include "util.h" @@ -11,13 +12,6 @@ #include "subscribe_state.h" #include "user_services.h" -#ifdef ARDUINO_ARCH_ESP32 -#include -#endif -#ifdef ARDUINO_ARCH_ESP8266 -#include -#endif - namespace esphome { namespace api { @@ -35,6 +29,7 @@ class APIServer : public Component, public Controller { void set_port(uint16_t port); void set_password(const std::string &password); void set_reboot_timeout(uint32_t reboot_timeout); + void handle_disconnect(APIConnection *conn); #ifdef USE_BINARY_SENSOR void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override; @@ -86,7 +81,7 @@ class APIServer : public Component, public Controller { const std::vector &get_user_services() const { return this->user_services_; } protected: - AsyncServer server_{0}; + std::unique_ptr socket_ = nullptr; uint16_t port_{6053}; uint32_t reboot_timeout_{300000}; uint32_t last_connected_{0}; diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h index a084823bdf..da710b760e 100644 --- a/esphome/components/socket/headers.h +++ b/esphome/components/socket/headers.h @@ -90,7 +90,11 @@ typedef uint32_t socklen_t; #undef INADDR_NONE #endif -#define INADDR_ANY ((uint32_t) 0x00000000UL) +#define ESPHOME_INADDR_ANY ((uint32_t) 0x00000000UL) +#define ESPHOME_INADDR_NONE ((uint32_t) 0xFFFFFFFFUL) +#else // !ARDUINO_ARCH_ESP8266 +#define ESPHOME_INADDR_ANY INADDR_ANY +#define ESPHOME_INADDR_NONE INADDR_NONE #endif #endif // USE_SOCKET_IMPL_LWIP_TCP @@ -112,6 +116,12 @@ typedef uint32_t socklen_t; #endif // not defined for ESP32 typedef uint32_t socklen_t; -#endif // ARDUINO_ARCH_ESP32 + +#define ESPHOME_INADDR_ANY ((uint32_t) 0x00000000UL) +#define ESPHOME_INADDR_NONE ((uint32_t) 0xFFFFFFFFUL) +#else // !ARDUINO_ARCH_ESP32 +#define ESPHOME_INADDR_ANY INADDR_ANY +#define ESPHOME_INADDR_NONE INADDR_NONE +#endif #endif // USE_SOCKET_IMPL_BSD_SOCKETS From 4e120a291eb4ca444dfd6a8a488fa6700c554579 Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Thu, 9 Sep 2021 01:10:02 +0400 Subject: [PATCH 1286/1841] Midea support v2 (#2188) --- CODEOWNERS | 3 +- esphome/components/climate/__init__.py | 1 + esphome/components/climate/climate.cpp | 69 +++++ esphome/components/climate/climate.h | 13 + esphome/components/climate/climate_traits.h | 2 + esphome/components/midea/__init__.py | 0 esphome/components/midea/adapter.cpp | 173 +++++++++++ esphome/components/midea/adapter.h | 42 +++ esphome/components/midea/air_conditioner.cpp | 152 ++++++++++ esphome/components/midea/air_conditioner.h | 41 +++ esphome/components/midea/appliance_base.h | 76 +++++ esphome/components/midea/automations.h | 56 ++++ esphome/components/midea/climate.py | 284 ++++++++++++++++++ esphome/components/midea/midea_ir.h | 42 +++ esphome/components/midea_ac/climate.py | 114 +------ esphome/components/midea_ac/midea_climate.cpp | 208 ------------- esphome/components/midea_ac/midea_climate.h | 68 ----- esphome/components/midea_ac/midea_frame.cpp | 238 --------------- esphome/components/midea_ac/midea_frame.h | 165 ---------- esphome/components/midea_dongle/__init__.py | 30 -- .../components/midea_dongle/midea_dongle.cpp | 98 ------ .../components/midea_dongle/midea_dongle.h | 56 ---- .../components/midea_dongle/midea_frame.cpp | 95 ------ esphome/components/midea_dongle/midea_frame.h | 104 ------- esphome/components/remote_base/__init__.py | 42 +++ .../components/remote_base/midea_protocol.cpp | 99 ++++++ .../components/remote_base/midea_protocol.h | 105 +++++++ esphome/const.py | 6 + esphome/core/component.cpp | 1 + esphome/core/component.h | 2 + platformio.ini | 1 + tests/test1.yaml | 79 ++++- tests/test3.yaml | 37 --- 33 files changed, 1276 insertions(+), 1226 deletions(-) create mode 100644 esphome/components/midea/__init__.py create mode 100644 esphome/components/midea/adapter.cpp create mode 100644 esphome/components/midea/adapter.h create mode 100644 esphome/components/midea/air_conditioner.cpp create mode 100644 esphome/components/midea/air_conditioner.h create mode 100644 esphome/components/midea/appliance_base.h create mode 100644 esphome/components/midea/automations.h create mode 100644 esphome/components/midea/climate.py create mode 100644 esphome/components/midea/midea_ir.h delete mode 100644 esphome/components/midea_ac/midea_climate.cpp delete mode 100644 esphome/components/midea_ac/midea_climate.h delete mode 100644 esphome/components/midea_ac/midea_frame.cpp delete mode 100644 esphome/components/midea_ac/midea_frame.h delete mode 100644 esphome/components/midea_dongle/__init__.py delete mode 100644 esphome/components/midea_dongle/midea_dongle.cpp delete mode 100644 esphome/components/midea_dongle/midea_dongle.h delete mode 100644 esphome/components/midea_dongle/midea_frame.cpp delete mode 100644 esphome/components/midea_dongle/midea_frame.h create mode 100644 esphome/components/remote_base/midea_protocol.cpp create mode 100644 esphome/components/remote_base/midea_protocol.h diff --git a/CODEOWNERS b/CODEOWNERS index ad670ede14..dedd8acc1e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -79,8 +79,7 @@ esphome/components/mcp23x17_base/* @jesserockz esphome/components/mcp23xxx_base/* @jesserockz esphome/components/mcp2515/* @danielschramm @mvturnho esphome/components/mcp9808/* @k7hpn -esphome/components/midea_ac/* @dudanov -esphome/components/midea_dongle/* @dudanov +esphome/components/midea/* @dudanov esphome/components/mitsubishi/* @RubyBailey esphome/components/network/* @esphome/core esphome/components/nextion/* @senexcrenshaw diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index f6a9fa2927..c2f07ce423 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -63,6 +63,7 @@ validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True) ClimatePreset = climate_ns.enum("ClimatePreset") CLIMATE_PRESETS = { + "NONE": ClimatePreset.CLIMATE_PRESET_NONE, "ECO": ClimatePreset.CLIMATE_PRESET_ECO, "AWAY": ClimatePreset.CLIMATE_PRESET_AWAY, "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 8da2206f37..4861e7b8cb 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -494,5 +494,74 @@ void ClimateDeviceRestoreState::apply(Climate *climate) { climate->publish_state(); } +template bool set_alternative(optional &dst, optional &alt, const T1 &src) { + bool is_changed = alt.has_value(); + alt.reset(); + if (is_changed || dst != src) { + dst = src; + is_changed = true; + } + return is_changed; +} + +bool Climate::set_fan_mode_(ClimateFanMode mode) { + return set_alternative(this->fan_mode, this->custom_fan_mode, mode); +} + +bool Climate::set_custom_fan_mode_(const std::string &mode) { + return set_alternative(this->custom_fan_mode, this->fan_mode, mode); +} + +bool Climate::set_preset_(ClimatePreset preset) { return set_alternative(this->preset, this->custom_preset, preset); } + +bool Climate::set_custom_preset_(const std::string &preset) { + return set_alternative(this->custom_preset, this->preset, preset); +} + +void Climate::dump_traits_(const char *tag) { + auto traits = this->get_traits(); + ESP_LOGCONFIG(tag, "ClimateTraits:"); + ESP_LOGCONFIG(tag, " [x] Visual settings:"); + ESP_LOGCONFIG(tag, " - Min: %.1f", traits.get_visual_min_temperature()); + ESP_LOGCONFIG(tag, " - Max: %.1f", traits.get_visual_max_temperature()); + ESP_LOGCONFIG(tag, " - Step: %.1f", traits.get_visual_temperature_step()); + if (traits.get_supports_current_temperature()) + ESP_LOGCONFIG(tag, " [x] Supports current temperature"); + if (traits.get_supports_two_point_target_temperature()) + ESP_LOGCONFIG(tag, " [x] Supports two-point target temperature"); + if (traits.get_supports_action()) + ESP_LOGCONFIG(tag, " [x] Supports action"); + if (!traits.get_supported_modes().empty()) { + ESP_LOGCONFIG(tag, " [x] Supported modes:"); + for (ClimateMode m : traits.get_supported_modes()) + ESP_LOGCONFIG(tag, " - %s", climate_mode_to_string(m)); + } + if (!traits.get_supported_fan_modes().empty()) { + ESP_LOGCONFIG(tag, " [x] Supported fan modes:"); + for (ClimateFanMode m : traits.get_supported_fan_modes()) + ESP_LOGCONFIG(tag, " - %s", climate_fan_mode_to_string(m)); + } + if (!traits.get_supported_custom_fan_modes().empty()) { + ESP_LOGCONFIG(tag, " [x] Supported custom fan modes:"); + for (const std::string &s : traits.get_supported_custom_fan_modes()) + ESP_LOGCONFIG(tag, " - %s", s.c_str()); + } + if (!traits.get_supported_presets().empty()) { + ESP_LOGCONFIG(tag, " [x] Supported presets:"); + for (ClimatePreset p : traits.get_supported_presets()) + ESP_LOGCONFIG(tag, " - %s", climate_preset_to_string(p)); + } + if (!traits.get_supported_custom_presets().empty()) { + ESP_LOGCONFIG(tag, " [x] Supported custom presets:"); + for (const std::string &s : traits.get_supported_custom_presets()) + ESP_LOGCONFIG(tag, " - %s", s.c_str()); + } + if (!traits.get_supported_swing_modes().empty()) { + ESP_LOGCONFIG(tag, " [x] Supported swing modes:"); + for (ClimateSwingMode m : traits.get_supported_swing_modes()) + ESP_LOGCONFIG(tag, " - %s", climate_swing_mode_to_string(m)); + } +} + } // namespace climate } // namespace esphome diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index b208e5946a..46d0fb1d77 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -245,6 +245,18 @@ class Climate : public Nameable { protected: friend ClimateCall; + /// Set fan mode. Reset custom fan mode. Return true if fan mode has been changed. + bool set_fan_mode_(ClimateFanMode mode); + + /// Set custom fan mode. Reset primary fan mode. Return true if fan mode has been changed. + bool set_custom_fan_mode_(const std::string &mode); + + /// Set preset. Reset custom preset. Return true if preset has been changed. + bool set_preset_(ClimatePreset preset); + + /// Set custom preset. Reset primary preset. Return true if preset has been changed. + bool set_custom_preset_(const std::string &preset); + /** Get the default traits of this climate device. * * Traits are static data that encode the capabilities and static data for a climate device such as supported @@ -270,6 +282,7 @@ class Climate : public Nameable { void save_state_(); uint32_t hash_base() override; + void dump_traits_(const char *tag); CallbackManager state_callback_{}; ESPPreferenceObject rtc_; diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index 48493b500c..903ce085d8 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -72,6 +72,7 @@ class ClimateTraits { void set_supported_fan_modes(std::set modes) { supported_fan_modes_ = std::move(modes); } void add_supported_fan_mode(ClimateFanMode mode) { supported_fan_modes_.insert(mode); } + void add_supported_custom_fan_mode(const std::string &mode) { supported_custom_fan_modes_.insert(mode); } ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20") void set_supports_fan_mode_on(bool supported) { set_fan_mode_support_(CLIMATE_FAN_ON, supported); } ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20") @@ -104,6 +105,7 @@ class ClimateTraits { void set_supported_presets(std::set presets) { supported_presets_ = std::move(presets); } void add_supported_preset(ClimatePreset preset) { supported_presets_.insert(preset); } + void add_supported_custom_preset(const std::string &preset) { supported_custom_presets_.insert(preset); } bool supports_preset(ClimatePreset preset) const { return supported_presets_.count(preset); } bool get_supports_presets() const { return !supported_presets_.empty(); } const std::set &get_supported_presets() const { return supported_presets_; } diff --git a/esphome/components/midea/__init__.py b/esphome/components/midea/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/midea/adapter.cpp b/esphome/components/midea/adapter.cpp new file mode 100644 index 0000000000..bd5b289095 --- /dev/null +++ b/esphome/components/midea/adapter.cpp @@ -0,0 +1,173 @@ +#include "esphome/core/log.h" +#include "adapter.h" + +namespace esphome { +namespace midea { + +const char *const Constants::TAG = "midea"; +const std::string Constants::FREEZE_PROTECTION = "freeze protection"; +const std::string Constants::SILENT = "silent"; +const std::string Constants::TURBO = "turbo"; + +ClimateMode Converters::to_climate_mode(MideaMode mode) { + switch (mode) { + case MideaMode::MODE_AUTO: + return ClimateMode::CLIMATE_MODE_HEAT_COOL; + case MideaMode::MODE_COOL: + return ClimateMode::CLIMATE_MODE_COOL; + case MideaMode::MODE_DRY: + return ClimateMode::CLIMATE_MODE_DRY; + case MideaMode::MODE_FAN_ONLY: + return ClimateMode::CLIMATE_MODE_FAN_ONLY; + case MideaMode::MODE_HEAT: + return ClimateMode::CLIMATE_MODE_HEAT; + default: + return ClimateMode::CLIMATE_MODE_OFF; + } +} + +MideaMode Converters::to_midea_mode(ClimateMode mode) { + switch (mode) { + case ClimateMode::CLIMATE_MODE_HEAT_COOL: + return MideaMode::MODE_AUTO; + case ClimateMode::CLIMATE_MODE_COOL: + return MideaMode::MODE_COOL; + case ClimateMode::CLIMATE_MODE_DRY: + return MideaMode::MODE_DRY; + case ClimateMode::CLIMATE_MODE_FAN_ONLY: + return MideaMode::MODE_FAN_ONLY; + case ClimateMode::CLIMATE_MODE_HEAT: + return MideaMode::MODE_HEAT; + default: + return MideaMode::MODE_OFF; + } +} + +ClimateSwingMode Converters::to_climate_swing_mode(MideaSwingMode mode) { + switch (mode) { + case MideaSwingMode::SWING_VERTICAL: + return ClimateSwingMode::CLIMATE_SWING_VERTICAL; + case MideaSwingMode::SWING_HORIZONTAL: + return ClimateSwingMode::CLIMATE_SWING_HORIZONTAL; + case MideaSwingMode::SWING_BOTH: + return ClimateSwingMode::CLIMATE_SWING_BOTH; + default: + return ClimateSwingMode::CLIMATE_SWING_OFF; + } +} + +MideaSwingMode Converters::to_midea_swing_mode(ClimateSwingMode mode) { + switch (mode) { + case ClimateSwingMode::CLIMATE_SWING_VERTICAL: + return MideaSwingMode::SWING_VERTICAL; + case ClimateSwingMode::CLIMATE_SWING_HORIZONTAL: + return MideaSwingMode::SWING_HORIZONTAL; + case ClimateSwingMode::CLIMATE_SWING_BOTH: + return MideaSwingMode::SWING_BOTH; + default: + return MideaSwingMode::SWING_OFF; + } +} + +MideaFanMode Converters::to_midea_fan_mode(ClimateFanMode mode) { + switch (mode) { + case ClimateFanMode::CLIMATE_FAN_LOW: + return MideaFanMode::FAN_LOW; + case ClimateFanMode::CLIMATE_FAN_MEDIUM: + return MideaFanMode::FAN_MEDIUM; + case ClimateFanMode::CLIMATE_FAN_HIGH: + return MideaFanMode::FAN_HIGH; + default: + return MideaFanMode::FAN_AUTO; + } +} + +ClimateFanMode Converters::to_climate_fan_mode(MideaFanMode mode) { + switch (mode) { + case MideaFanMode::FAN_LOW: + return ClimateFanMode::CLIMATE_FAN_LOW; + case MideaFanMode::FAN_MEDIUM: + return ClimateFanMode::CLIMATE_FAN_MEDIUM; + case MideaFanMode::FAN_HIGH: + return ClimateFanMode::CLIMATE_FAN_HIGH; + default: + return ClimateFanMode::CLIMATE_FAN_AUTO; + } +} + +bool Converters::is_custom_midea_fan_mode(MideaFanMode mode) { + switch (mode) { + case MideaFanMode::FAN_SILENT: + case MideaFanMode::FAN_TURBO: + return true; + default: + return false; + } +} + +const std::string &Converters::to_custom_climate_fan_mode(MideaFanMode mode) { + switch (mode) { + case MideaFanMode::FAN_SILENT: + return Constants::SILENT; + default: + return Constants::TURBO; + } +} + +MideaFanMode Converters::to_midea_fan_mode(const std::string &mode) { + if (mode == Constants::SILENT) + return MideaFanMode::FAN_SILENT; + return MideaFanMode::FAN_TURBO; +} + +MideaPreset Converters::to_midea_preset(ClimatePreset preset) { + switch (preset) { + case ClimatePreset::CLIMATE_PRESET_SLEEP: + return MideaPreset::PRESET_SLEEP; + case ClimatePreset::CLIMATE_PRESET_ECO: + return MideaPreset::PRESET_ECO; + case ClimatePreset::CLIMATE_PRESET_BOOST: + return MideaPreset::PRESET_TURBO; + default: + return MideaPreset::PRESET_NONE; + } +} + +ClimatePreset Converters::to_climate_preset(MideaPreset preset) { + switch (preset) { + case MideaPreset::PRESET_SLEEP: + return ClimatePreset::CLIMATE_PRESET_SLEEP; + case MideaPreset::PRESET_ECO: + return ClimatePreset::CLIMATE_PRESET_ECO; + case MideaPreset::PRESET_TURBO: + return ClimatePreset::CLIMATE_PRESET_BOOST; + default: + return ClimatePreset::CLIMATE_PRESET_NONE; + } +} + +bool Converters::is_custom_midea_preset(MideaPreset preset) { return preset == MideaPreset::PRESET_FREEZE_PROTECTION; } + +const std::string &Converters::to_custom_climate_preset(MideaPreset preset) { return Constants::FREEZE_PROTECTION; } + +MideaPreset Converters::to_midea_preset(const std::string &preset) { return MideaPreset::PRESET_FREEZE_PROTECTION; } + +void Converters::to_climate_traits(ClimateTraits &traits, const dudanov::midea::ac::Capabilities &capabilities) { + if (capabilities.supportAutoMode()) + traits.add_supported_mode(ClimateMode::CLIMATE_MODE_HEAT_COOL); + if (capabilities.supportCoolMode()) + traits.add_supported_mode(ClimateMode::CLIMATE_MODE_COOL); + if (capabilities.supportHeatMode()) + traits.add_supported_mode(ClimateMode::CLIMATE_MODE_HEAT); + if (capabilities.supportDryMode()) + traits.add_supported_mode(ClimateMode::CLIMATE_MODE_DRY); + if (capabilities.supportTurboPreset()) + traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_BOOST); + if (capabilities.supportEcoPreset()) + traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_ECO); + if (capabilities.supportFrostProtectionPreset()) + traits.add_supported_custom_preset(Constants::FREEZE_PROTECTION); +} + +} // namespace midea +} // namespace esphome diff --git a/esphome/components/midea/adapter.h b/esphome/components/midea/adapter.h new file mode 100644 index 0000000000..8d8d57e8f9 --- /dev/null +++ b/esphome/components/midea/adapter.h @@ -0,0 +1,42 @@ +#pragma once +#include +#include "esphome/components/climate/climate_traits.h" +#include "appliance_base.h" + +namespace esphome { +namespace midea { + +using MideaMode = dudanov::midea::ac::Mode; +using MideaSwingMode = dudanov::midea::ac::SwingMode; +using MideaFanMode = dudanov::midea::ac::FanMode; +using MideaPreset = dudanov::midea::ac::Preset; + +class Constants { + public: + static const char *const TAG; + static const std::string FREEZE_PROTECTION; + static const std::string SILENT; + static const std::string TURBO; +}; + +class Converters { + public: + static MideaMode to_midea_mode(ClimateMode mode); + static ClimateMode to_climate_mode(MideaMode mode); + static MideaSwingMode to_midea_swing_mode(ClimateSwingMode mode); + static ClimateSwingMode to_climate_swing_mode(MideaSwingMode mode); + static MideaPreset to_midea_preset(ClimatePreset preset); + static MideaPreset to_midea_preset(const std::string &preset); + static bool is_custom_midea_preset(MideaPreset preset); + static ClimatePreset to_climate_preset(MideaPreset preset); + static const std::string &to_custom_climate_preset(MideaPreset preset); + static MideaFanMode to_midea_fan_mode(ClimateFanMode fan_mode); + static MideaFanMode to_midea_fan_mode(const std::string &fan_mode); + static bool is_custom_midea_fan_mode(MideaFanMode fan_mode); + static ClimateFanMode to_climate_fan_mode(MideaFanMode fan_mode); + static const std::string &to_custom_climate_fan_mode(MideaFanMode fan_mode); + static void to_climate_traits(ClimateTraits &traits, const dudanov::midea::ac::Capabilities &capabilities); +}; + +} // namespace midea +} // namespace esphome diff --git a/esphome/components/midea/air_conditioner.cpp b/esphome/components/midea/air_conditioner.cpp new file mode 100644 index 0000000000..a71f1dbdfb --- /dev/null +++ b/esphome/components/midea/air_conditioner.cpp @@ -0,0 +1,152 @@ +#include "esphome/core/log.h" +#include "air_conditioner.h" +#include "adapter.h" +#ifdef USE_REMOTE_TRANSMITTER +#include "midea_ir.h" +#endif + +namespace esphome { +namespace midea { + +static void set_sensor(Sensor *sensor, float value) { + if (sensor != nullptr && (!sensor->has_state() || sensor->get_raw_state() != value)) + sensor->publish_state(value); +} + +template void update_property(T &property, const T &value, bool &flag) { + if (property != value) { + property = value; + flag = true; + } +} + +void AirConditioner::on_status_change() { + bool need_publish = false; + update_property(this->target_temperature, this->base_.getTargetTemp(), need_publish); + update_property(this->current_temperature, this->base_.getIndoorTemp(), need_publish); + auto mode = Converters::to_climate_mode(this->base_.getMode()); + update_property(this->mode, mode, need_publish); + auto swing_mode = Converters::to_climate_swing_mode(this->base_.getSwingMode()); + update_property(this->swing_mode, swing_mode, need_publish); + // Preset + auto preset = this->base_.getPreset(); + if (Converters::is_custom_midea_preset(preset)) { + if (this->set_custom_preset_(Converters::to_custom_climate_preset(preset))) + need_publish = true; + } else if (this->set_preset_(Converters::to_climate_preset(preset))) { + need_publish = true; + } + // Fan mode + auto fan_mode = this->base_.getFanMode(); + if (Converters::is_custom_midea_fan_mode(fan_mode)) { + if (this->set_custom_fan_mode_(Converters::to_custom_climate_fan_mode(fan_mode))) + need_publish = true; + } else if (this->set_fan_mode_(Converters::to_climate_fan_mode(fan_mode))) { + need_publish = true; + } + if (need_publish) + this->publish_state(); + set_sensor(this->outdoor_sensor_, this->base_.getOutdoorTemp()); + set_sensor(this->power_sensor_, this->base_.getPowerUsage()); + set_sensor(this->humidity_sensor_, this->base_.getIndoorHum()); +} + +void AirConditioner::control(const ClimateCall &call) { + dudanov::midea::ac::Control ctrl{}; + if (call.get_target_temperature().has_value()) + ctrl.targetTemp = call.get_target_temperature().value(); + if (call.get_swing_mode().has_value()) + ctrl.swingMode = Converters::to_midea_swing_mode(call.get_swing_mode().value()); + if (call.get_mode().has_value()) + ctrl.mode = Converters::to_midea_mode(call.get_mode().value()); + if (call.get_preset().has_value()) + ctrl.preset = Converters::to_midea_preset(call.get_preset().value()); + else if (call.get_custom_preset().has_value()) + ctrl.preset = Converters::to_midea_preset(call.get_custom_preset().value()); + if (call.get_fan_mode().has_value()) + ctrl.fanMode = Converters::to_midea_fan_mode(call.get_fan_mode().value()); + else if (call.get_custom_fan_mode().has_value()) + ctrl.fanMode = Converters::to_midea_fan_mode(call.get_custom_fan_mode().value()); + this->base_.control(ctrl); +} + +ClimateTraits AirConditioner::traits() { + auto traits = ClimateTraits(); + traits.set_supports_current_temperature(true); + traits.set_visual_min_temperature(17); + traits.set_visual_max_temperature(30); + traits.set_visual_temperature_step(0.5); + traits.set_supported_modes(this->supported_modes_); + traits.set_supported_swing_modes(this->supported_swing_modes_); + traits.set_supported_presets(this->supported_presets_); + traits.set_supported_custom_presets(this->supported_custom_presets_); + traits.set_supported_custom_fan_modes(this->supported_custom_fan_modes_); + /* + MINIMAL SET OF CAPABILITIES */ + traits.add_supported_mode(ClimateMode::CLIMATE_MODE_OFF); + traits.add_supported_mode(ClimateMode::CLIMATE_MODE_FAN_ONLY); + traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_AUTO); + traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_LOW); + traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_MEDIUM); + traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_HIGH); + traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_OFF); + traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_VERTICAL); + traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_NONE); + traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_SLEEP); + if (this->base_.getAutoconfStatus() == dudanov::midea::AUTOCONF_OK) + Converters::to_climate_traits(traits, this->base_.getCapabilities()); + return traits; +} + +void AirConditioner::dump_config() { + ESP_LOGCONFIG(Constants::TAG, "MideaDongle:"); + ESP_LOGCONFIG(Constants::TAG, " [x] Period: %dms", this->base_.getPeriod()); + ESP_LOGCONFIG(Constants::TAG, " [x] Response timeout: %dms", this->base_.getTimeout()); + ESP_LOGCONFIG(Constants::TAG, " [x] Request attempts: %d", this->base_.getNumAttempts()); +#ifdef USE_REMOTE_TRANSMITTER + ESP_LOGCONFIG(Constants::TAG, " [x] Using RemoteTransmitter"); +#endif + if (this->base_.getAutoconfStatus() == dudanov::midea::AUTOCONF_OK) { + this->base_.getCapabilities().dump(); + } else if (this->base_.getAutoconfStatus() == dudanov::midea::AUTOCONF_ERROR) { + ESP_LOGW(Constants::TAG, + "Failed to get 0xB5 capabilities report. Suggest to disable it in config and manually set your " + "appliance options."); + } + this->dump_traits_(Constants::TAG); +} + +/* ACTIONS */ + +void AirConditioner::do_follow_me(float temperature, bool beeper) { +#ifdef USE_REMOTE_TRANSMITTER + IrFollowMeData data(static_cast(lroundf(temperature)), beeper); + this->transmit_ir(data); +#else + ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); +#endif +} + +void AirConditioner::do_swing_step() { +#ifdef USE_REMOTE_TRANSMITTER + IrSpecialData data(0x01); + this->transmit_ir(data); +#else + ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); +#endif +} + +void AirConditioner::do_display_toggle() { + if (this->base_.getCapabilities().supportLightControl()) { + this->base_.displayToggle(); + } else { +#ifdef USE_REMOTE_TRANSMITTER + IrSpecialData data(0x08); + this->transmit_ir(data); +#else + ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); +#endif + } +} + +} // namespace midea +} // namespace esphome diff --git a/esphome/components/midea/air_conditioner.h b/esphome/components/midea/air_conditioner.h new file mode 100644 index 0000000000..895b6412f3 --- /dev/null +++ b/esphome/components/midea/air_conditioner.h @@ -0,0 +1,41 @@ +#pragma once +#include +#include "appliance_base.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace midea { + +using sensor::Sensor; +using climate::ClimateCall; + +class AirConditioner : public ApplianceBase { + public: + void dump_config() override; + void set_outdoor_temperature_sensor(Sensor *sensor) { this->outdoor_sensor_ = sensor; } + void set_humidity_setpoint_sensor(Sensor *sensor) { this->humidity_sensor_ = sensor; } + void set_power_sensor(Sensor *sensor) { this->power_sensor_ = sensor; } + void on_status_change() override; + + /* ############### */ + /* ### ACTIONS ### */ + /* ############### */ + + void do_follow_me(float temperature, bool beeper = false); + void do_display_toggle(); + void do_swing_step(); + void do_beeper_on() { this->set_beeper_feedback(true); } + void do_beeper_off() { this->set_beeper_feedback(false); } + void do_power_on() { this->base_.setPowerState(true); } + void do_power_off() { this->base_.setPowerState(false); } + + protected: + void control(const ClimateCall &call) override; + ClimateTraits traits() override; + Sensor *outdoor_sensor_{nullptr}; + Sensor *humidity_sensor_{nullptr}; + Sensor *power_sensor_{nullptr}; +}; + +} // namespace midea +} // namespace esphome diff --git a/esphome/components/midea/appliance_base.h b/esphome/components/midea/appliance_base.h new file mode 100644 index 0000000000..aa616ced36 --- /dev/null +++ b/esphome/components/midea/appliance_base.h @@ -0,0 +1,76 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/core/log.h" +#include "esphome/components/uart/uart.h" +#include "esphome/components/climate/climate.h" +#ifdef USE_REMOTE_TRANSMITTER +#include "esphome/components/remote_base/midea_protocol.h" +#include "esphome/components/remote_transmitter/remote_transmitter.h" +#endif +#include +#include + +namespace esphome { +namespace midea { + +using climate::ClimatePreset; +using climate::ClimateTraits; +using climate::ClimateMode; +using climate::ClimateSwingMode; +using climate::ClimateFanMode; + +template class ApplianceBase : public Component, public uart::UARTDevice, public climate::Climate { + static_assert(std::is_base_of::value, + "T must derive from dudanov::midea::ApplianceBase class"); + + public: + ApplianceBase() { + this->base_.setStream(this); + this->base_.addOnStateCallback(std::bind(&ApplianceBase::on_status_change, this)); + dudanov::midea::ApplianceBase::setLogger([](int level, const char *tag, int line, String format, va_list args) { + esp_log_vprintf_(level, tag, line, format.c_str(), args); + }); + } + bool can_proceed() override { + return this->base_.getAutoconfStatus() != dudanov::midea::AutoconfStatus::AUTOCONF_PROGRESS; + } + float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; } + void setup() override { this->base_.setup(); } + void loop() override { this->base_.loop(); } + void set_period(uint32_t ms) { this->base_.setPeriod(ms); } + void set_response_timeout(uint32_t ms) { this->base_.setTimeout(ms); } + void set_request_attempts(uint32_t attempts) { this->base_.setNumAttempts(attempts); } + void set_beeper_feedback(bool state) { this->base_.setBeeper(state); } + void set_autoconf(bool value) { this->base_.setAutoconf(value); } + void set_supported_modes(std::set modes) { this->supported_modes_ = std::move(modes); } + void set_supported_swing_modes(std::set modes) { this->supported_swing_modes_ = std::move(modes); } + void set_supported_presets(std::set presets) { this->supported_presets_ = std::move(presets); } + void set_custom_presets(std::set presets) { this->supported_custom_presets_ = std::move(presets); } + void set_custom_fan_modes(std::set modes) { this->supported_custom_fan_modes_ = std::move(modes); } + virtual void on_status_change() = 0; +#ifdef USE_REMOTE_TRANSMITTER + void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) { + this->transmitter_ = transmitter; + } + void transmit_ir(remote_base::MideaData &data) { + data.finalize(); + auto transmit = this->transmitter_->transmit(); + remote_base::MideaProtocol().encode(transmit.get_data(), data); + transmit.perform(); + } +#endif + + protected: + T base_; + std::set supported_modes_{}; + std::set supported_swing_modes_{}; + std::set supported_presets_{}; + std::set supported_custom_presets_{}; + std::set supported_custom_fan_modes_{}; +#ifdef USE_REMOTE_TRANSMITTER + remote_transmitter::RemoteTransmitterComponent *transmitter_{nullptr}; +#endif +}; + +} // namespace midea +} // namespace esphome diff --git a/esphome/components/midea/automations.h b/esphome/components/midea/automations.h new file mode 100644 index 0000000000..1f026c0c15 --- /dev/null +++ b/esphome/components/midea/automations.h @@ -0,0 +1,56 @@ +#pragma once +#include "esphome/core/automation.h" +#include "air_conditioner.h" + +namespace esphome { +namespace midea { + +template class MideaActionBase : public Action { + public: + void set_parent(AirConditioner *parent) { this->parent_ = parent; } + + protected: + AirConditioner *parent_; +}; + +template class FollowMeAction : public MideaActionBase { + TEMPLATABLE_VALUE(float, temperature) + TEMPLATABLE_VALUE(bool, beeper) + + void play(Ts... x) override { + this->parent_->do_follow_me(this->temperature_.value(x...), this->beeper_.value(x...)); + } +}; + +template class SwingStepAction : public MideaActionBase { + public: + void play(Ts... x) override { this->parent_->do_swing_step(); } +}; + +template class DisplayToggleAction : public MideaActionBase { + public: + void play(Ts... x) override { this->parent_->do_display_toggle(); } +}; + +template class BeeperOnAction : public MideaActionBase { + public: + void play(Ts... x) override { this->parent_->do_beeper_on(); } +}; + +template class BeeperOffAction : public MideaActionBase { + public: + void play(Ts... x) override { this->parent_->do_beeper_off(); } +}; + +template class PowerOnAction : public MideaActionBase { + public: + void play(Ts... x) override { this->parent_->do_power_on(); } +}; + +template class PowerOffAction : public MideaActionBase { + public: + void play(Ts... x) override { this->parent_->do_power_off(); } +}; + +} // namespace midea +} // namespace esphome diff --git a/esphome/components/midea/climate.py b/esphome/components/midea/climate.py new file mode 100644 index 0000000000..137fcdd607 --- /dev/null +++ b/esphome/components/midea/climate.py @@ -0,0 +1,284 @@ +from esphome.core import coroutine +from esphome import automation +from esphome.components import climate, sensor, uart, remote_transmitter +from esphome.components.remote_base import CONF_TRANSMITTER_ID +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import ( + CONF_AUTOCONF, + CONF_BEEPER, + CONF_CUSTOM_FAN_MODES, + CONF_CUSTOM_PRESETS, + CONF_ID, + CONF_NUM_ATTEMPTS, + CONF_PERIOD, + CONF_SUPPORTED_MODES, + CONF_SUPPORTED_PRESETS, + CONF_SUPPORTED_SWING_MODES, + CONF_TIMEOUT, + CONF_TEMPERATURE, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + ICON_POWER, + ICON_THERMOMETER, + ICON_WATER_PERCENT, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PERCENT, + UNIT_WATT, +) +from esphome.components.climate import ( + ClimateMode, + ClimatePreset, + ClimateSwingMode, +) + +CODEOWNERS = ["@dudanov"] +DEPENDENCIES = ["climate", "uart", "wifi"] +AUTO_LOAD = ["sensor"] +CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" +CONF_POWER_USAGE = "power_usage" +CONF_HUMIDITY_SETPOINT = "humidity_setpoint" +midea_ns = cg.esphome_ns.namespace("midea") +AirConditioner = midea_ns.class_("AirConditioner", climate.Climate, cg.Component) +Capabilities = midea_ns.namespace("Constants") + + +def templatize(value): + if isinstance(value, cv.Schema): + value = value.schema + ret = {} + for key, val in value.items(): + ret[key] = cv.templatable(val) + return cv.Schema(ret) + + +def register_action(name, type_, schema): + validator = templatize(schema).extend(MIDEA_ACTION_BASE_SCHEMA) + registerer = automation.register_action(f"midea_ac.{name}", type_, validator) + + def decorator(func): + async def new_func(config, action_id, template_arg, args): + ac_ = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg) + cg.add(var.set_parent(ac_)) + await coroutine(func)(var, config, args) + return var + + return registerer(new_func) + + return decorator + + +ALLOWED_CLIMATE_MODES = { + "HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL, + "COOL": ClimateMode.CLIMATE_MODE_COOL, + "HEAT": ClimateMode.CLIMATE_MODE_HEAT, + "DRY": ClimateMode.CLIMATE_MODE_DRY, + "FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY, +} + +ALLOWED_CLIMATE_PRESETS = { + "ECO": ClimatePreset.CLIMATE_PRESET_ECO, + "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, + "SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP, +} + +ALLOWED_CLIMATE_SWING_MODES = { + "BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH, + "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL, + "HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, +} + +CUSTOM_FAN_MODES = { + "SILENT": Capabilities.SILENT, + "TURBO": Capabilities.TURBO, +} + +CUSTOM_PRESETS = { + "FREEZE_PROTECTION": Capabilities.FREEZE_PROTECTION, +} + +validate_modes = cv.enum(ALLOWED_CLIMATE_MODES, upper=True) +validate_presets = cv.enum(ALLOWED_CLIMATE_PRESETS, upper=True) +validate_swing_modes = cv.enum(ALLOWED_CLIMATE_SWING_MODES, upper=True) +validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True) +validate_custom_presets = cv.enum(CUSTOM_PRESETS, upper=True) + +CONFIG_SCHEMA = cv.All( + climate.CLIMATE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(AirConditioner), + cv.Optional(CONF_PERIOD, default="1s"): cv.time_period, + cv.Optional(CONF_TIMEOUT, default="2s"): cv.time_period, + cv.Optional(CONF_NUM_ATTEMPTS, default=3): cv.int_range(min=1, max=5), + cv.Optional(CONF_TRANSMITTER_ID): cv.use_id( + remote_transmitter.RemoteTransmitterComponent + ), + cv.Optional(CONF_BEEPER, default=False): cv.boolean, + cv.Optional(CONF_AUTOCONF, default=True): cv.boolean, + cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(validate_modes), + cv.Optional(CONF_SUPPORTED_SWING_MODES): cv.ensure_list( + validate_swing_modes + ), + cv.Optional(CONF_SUPPORTED_PRESETS): cv.ensure_list(validate_presets), + cv.Optional(CONF_CUSTOM_PRESETS): cv.ensure_list(validate_custom_presets), + cv.Optional(CONF_CUSTOM_FAN_MODES): cv.ensure_list( + validate_custom_fan_modes + ), + cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER_USAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + icon=ICON_POWER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HUMIDITY_SETPOINT): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_WATER_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + +# Actions +FollowMeAction = midea_ns.class_("FollowMeAction", automation.Action) +DisplayToggleAction = midea_ns.class_("DisplayToggleAction", automation.Action) +SwingStepAction = midea_ns.class_("SwingStepAction", automation.Action) +BeeperOnAction = midea_ns.class_("BeeperOnAction", automation.Action) +BeeperOffAction = midea_ns.class_("BeeperOffAction", automation.Action) +PowerOnAction = midea_ns.class_("PowerOnAction", automation.Action) +PowerOffAction = midea_ns.class_("PowerOffAction", automation.Action) + +MIDEA_ACTION_BASE_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_ID): cv.use_id(AirConditioner), + } +) + +# FollowMe action +MIDEA_FOLLOW_ME_MIN = 0 +MIDEA_FOLLOW_ME_MAX = 37 +MIDEA_FOLLOW_ME_SCHEMA = cv.Schema( + { + cv.Required(CONF_TEMPERATURE): cv.templatable(cv.temperature), + cv.Optional(CONF_BEEPER, default=False): cv.templatable(cv.boolean), + } +) + + +@register_action("follow_me", FollowMeAction, MIDEA_FOLLOW_ME_SCHEMA) +async def follow_me_to_code(var, config, args): + template_ = await cg.templatable(config[CONF_BEEPER], args, cg.bool_) + cg.add(var.set_beeper(template_)) + template_ = await cg.templatable(config[CONF_TEMPERATURE], args, cg.float_) + cg.add(var.set_temperature(template_)) + + +# Toggle Display action +@register_action( + "display_toggle", + DisplayToggleAction, + cv.Schema({}), +) +async def display_toggle_to_code(var, config, args): + pass + + +# Swing Step action +@register_action( + "swing_step", + SwingStepAction, + cv.Schema({}), +) +async def swing_step_to_code(var, config, args): + pass + + +# Beeper On action +@register_action( + "beeper_on", + BeeperOnAction, + cv.Schema({}), +) +async def beeper_on_to_code(var, config, args): + pass + + +# Beeper Off action +@register_action( + "beeper_off", + BeeperOffAction, + cv.Schema({}), +) +async def beeper_off_to_code(var, config, args): + pass + + +# Power On action +@register_action( + "power_on", + PowerOnAction, + cv.Schema({}), +) +async def power_on_to_code(var, config, args): + pass + + +# Power Off action +@register_action( + "power_off", + PowerOffAction, + cv.Schema({}), +) +async def power_off_to_code(var, config, args): + pass + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + await climate.register_climate(var, config) + cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds)) + cg.add(var.set_response_timeout(config[CONF_TIMEOUT].total_milliseconds)) + cg.add(var.set_request_attempts(config[CONF_NUM_ATTEMPTS])) + if CONF_TRANSMITTER_ID in config: + cg.add_define("USE_REMOTE_TRANSMITTER") + transmitter_ = await cg.get_variable(config[CONF_TRANSMITTER_ID]) + cg.add(var.set_transmitter(transmitter_)) + cg.add(var.set_beeper_feedback(config[CONF_BEEPER])) + cg.add(var.set_autoconf(config[CONF_AUTOCONF])) + if CONF_SUPPORTED_MODES in config: + cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) + if CONF_SUPPORTED_SWING_MODES in config: + cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES])) + if CONF_SUPPORTED_PRESETS in config: + cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS])) + if CONF_CUSTOM_PRESETS in config: + cg.add(var.set_custom_presets(config[CONF_CUSTOM_PRESETS])) + if CONF_CUSTOM_FAN_MODES in config: + cg.add(var.set_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES])) + if CONF_OUTDOOR_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_OUTDOOR_TEMPERATURE]) + cg.add(var.set_outdoor_temperature_sensor(sens)) + if CONF_POWER_USAGE in config: + sens = await sensor.new_sensor(config[CONF_POWER_USAGE]) + cg.add(var.set_power_sensor(sens)) + if CONF_HUMIDITY_SETPOINT in config: + sens = await sensor.new_sensor(config[CONF_HUMIDITY_SETPOINT]) + cg.add(var.set_humidity_setpoint_sensor(sens)) + cg.add_library("dudanov/MideaUART", "1.1.5") diff --git a/esphome/components/midea/midea_ir.h b/esphome/components/midea/midea_ir.h new file mode 100644 index 0000000000..2459d844a1 --- /dev/null +++ b/esphome/components/midea/midea_ir.h @@ -0,0 +1,42 @@ +#pragma once +#ifdef USE_REMOTE_TRANSMITTER +#include "esphome/components/remote_base/midea_protocol.h" + +namespace esphome { +namespace midea { + +using IrData = remote_base::MideaData; + +class IrFollowMeData : public IrData { + public: + // Default constructor (temp: 30C, beeper: off) + IrFollowMeData() : IrData({MIDEA_TYPE_FOLLOW_ME, 0x82, 0x48, 0x7F, 0x1F}) {} + // Copy from Base + IrFollowMeData(const IrData &data) : IrData(data) {} + // Direct from temperature and beeper values + IrFollowMeData(uint8_t temp, bool beeper = false) : IrFollowMeData() { + this->set_temp(temp); + this->set_beeper(beeper); + } + + /* TEMPERATURE */ + uint8_t temp() const { return this->data_[4] - 1; } + void set_temp(uint8_t val) { this->data_[4] = std::min(MAX_TEMP, val) + 1; } + + /* BEEPER */ + bool beeper() const { return this->data_[3] & 128; } + void set_beeper(bool val) { this->set_value_(3, 1, 7, val); } + + protected: + static const uint8_t MAX_TEMP = 37; +}; + +class IrSpecialData : public IrData { + public: + IrSpecialData(uint8_t code) : IrData({MIDEA_TYPE_SPECIAL, code, 0xFF, 0xFF, 0xFF}) {} +}; + +} // namespace midea +} // namespace esphome + +#endif diff --git a/esphome/components/midea_ac/climate.py b/esphome/components/midea_ac/climate.py index 741741fd03..f336f84787 100644 --- a/esphome/components/midea_ac/climate.py +++ b/esphome/components/midea_ac/climate.py @@ -1,115 +1,3 @@ -from esphome.components import climate, sensor import esphome.config_validation as cv -import esphome.codegen as cg -from esphome.const import ( - CONF_CUSTOM_FAN_MODES, - CONF_CUSTOM_PRESETS, - CONF_ID, - CONF_PRESET_BOOST, - CONF_PRESET_ECO, - CONF_PRESET_SLEEP, - STATE_CLASS_MEASUREMENT, - UNIT_CELSIUS, - UNIT_PERCENT, - UNIT_WATT, - ICON_THERMOMETER, - ICON_POWER, - DEVICE_CLASS_POWER, - DEVICE_CLASS_TEMPERATURE, - ICON_WATER_PERCENT, - DEVICE_CLASS_HUMIDITY, -) -from esphome.components.midea_dongle import CONF_MIDEA_DONGLE_ID, MideaDongle -AUTO_LOAD = ["climate", "sensor", "midea_dongle"] -CODEOWNERS = ["@dudanov"] -CONF_BEEPER = "beeper" -CONF_SWING_HORIZONTAL = "swing_horizontal" -CONF_SWING_BOTH = "swing_both" -CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" -CONF_POWER_USAGE = "power_usage" -CONF_HUMIDITY_SETPOINT = "humidity_setpoint" -midea_ac_ns = cg.esphome_ns.namespace("midea_ac") -MideaAC = midea_ac_ns.class_("MideaAC", climate.Climate, cg.Component) - -CLIMATE_CUSTOM_FAN_MODES = { - "SILENT": "silent", - "TURBO": "turbo", -} - -validate_climate_custom_fan_mode = cv.enum(CLIMATE_CUSTOM_FAN_MODES, upper=True) - -CLIMATE_CUSTOM_PRESETS = { - "FREEZE_PROTECTION": "freeze protection", -} - -validate_climate_custom_preset = cv.enum(CLIMATE_CUSTOM_PRESETS, upper=True) - -CONFIG_SCHEMA = cv.All( - climate.CLIMATE_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(MideaAC), - cv.GenerateID(CONF_MIDEA_DONGLE_ID): cv.use_id(MideaDongle), - cv.Optional(CONF_BEEPER, default=False): cv.boolean, - cv.Optional(CONF_CUSTOM_FAN_MODES): cv.ensure_list( - validate_climate_custom_fan_mode - ), - cv.Optional(CONF_CUSTOM_PRESETS): cv.ensure_list( - validate_climate_custom_preset - ), - cv.Optional(CONF_SWING_HORIZONTAL, default=False): cv.boolean, - cv.Optional(CONF_SWING_BOTH, default=False): cv.boolean, - cv.Optional(CONF_PRESET_ECO, default=False): cv.boolean, - cv.Optional(CONF_PRESET_SLEEP, default=False): cv.boolean, - cv.Optional(CONF_PRESET_BOOST, default=False): cv.boolean, - cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_THERMOMETER, - accuracy_decimals=0, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_POWER_USAGE): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT, - icon=ICON_POWER, - accuracy_decimals=0, - device_class=DEVICE_CLASS_POWER, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_HUMIDITY_SETPOINT): sensor.sensor_schema( - unit_of_measurement=UNIT_PERCENT, - icon=ICON_WATER_PERCENT, - accuracy_decimals=0, - device_class=DEVICE_CLASS_HUMIDITY, - state_class=STATE_CLASS_MEASUREMENT, - ), - } - ).extend(cv.COMPONENT_SCHEMA) -) - - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await climate.register_climate(var, config) - paren = await cg.get_variable(config[CONF_MIDEA_DONGLE_ID]) - cg.add(var.set_midea_dongle_parent(paren)) - cg.add(var.set_beeper_feedback(config[CONF_BEEPER])) - if CONF_CUSTOM_FAN_MODES in config: - cg.add(var.set_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES])) - if CONF_CUSTOM_PRESETS in config: - cg.add(var.set_custom_presets(config[CONF_CUSTOM_PRESETS])) - cg.add(var.set_swing_horizontal(config[CONF_SWING_HORIZONTAL])) - cg.add(var.set_swing_both(config[CONF_SWING_BOTH])) - cg.add(var.set_preset_eco(config[CONF_PRESET_ECO])) - cg.add(var.set_preset_sleep(config[CONF_PRESET_SLEEP])) - cg.add(var.set_preset_boost(config[CONF_PRESET_BOOST])) - if CONF_OUTDOOR_TEMPERATURE in config: - sens = await sensor.new_sensor(config[CONF_OUTDOOR_TEMPERATURE]) - cg.add(var.set_outdoor_temperature_sensor(sens)) - if CONF_POWER_USAGE in config: - sens = await sensor.new_sensor(config[CONF_POWER_USAGE]) - cg.add(var.set_power_sensor(sens)) - if CONF_HUMIDITY_SETPOINT in config: - sens = await sensor.new_sensor(config[CONF_HUMIDITY_SETPOINT]) - cg.add(var.set_humidity_setpoint_sensor(sens)) +CONFIG_SCHEMA = cv.invalid("This platform has been renamed to midea in 2021.9") diff --git a/esphome/components/midea_ac/midea_climate.cpp b/esphome/components/midea_ac/midea_climate.cpp deleted file mode 100644 index 72f7d23404..0000000000 --- a/esphome/components/midea_ac/midea_climate.cpp +++ /dev/null @@ -1,208 +0,0 @@ -#include "esphome/core/log.h" -#include "midea_climate.h" - -namespace esphome { -namespace midea_ac { - -static const char *const TAG = "midea_ac"; - -static void set_sensor(sensor::Sensor *sensor, float value) { - if (sensor != nullptr && (!sensor->has_state() || sensor->get_raw_state() != value)) - sensor->publish_state(value); -} - -template void set_property(T &property, T value, bool &flag) { - if (property != value) { - property = value; - flag = true; - } -} - -void MideaAC::on_frame(const midea_dongle::Frame &frame) { - const auto p = frame.as(); - if (p.has_power_info()) { - set_sensor(this->power_sensor_, p.get_power_usage()); - return; - } else if (!p.has_properties()) { - ESP_LOGW(TAG, "RX: frame has unknown type"); - return; - } - if (p.get_type() == midea_dongle::MideaMessageType::DEVICE_CONTROL) { - ESP_LOGD(TAG, "RX: control frame"); - this->ctrl_request_ = false; - } else { - ESP_LOGD(TAG, "RX: query frame"); - } - if (this->ctrl_request_) - return; - this->cmd_frame_.set_properties(p); // copy properties from response - bool need_publish = false; - set_property(this->mode, p.get_mode(), need_publish); - set_property(this->target_temperature, p.get_target_temp(), need_publish); - set_property(this->current_temperature, p.get_indoor_temp(), need_publish); - if (p.is_custom_fan_mode()) { - this->fan_mode.reset(); - optional mode = p.get_custom_fan_mode(); - set_property(this->custom_fan_mode, mode, need_publish); - } else { - this->custom_fan_mode.reset(); - optional mode = p.get_fan_mode(); - set_property(this->fan_mode, mode, need_publish); - } - set_property(this->swing_mode, p.get_swing_mode(), need_publish); - if (p.is_custom_preset()) { - this->preset.reset(); - optional preset = p.get_custom_preset(); - set_property(this->custom_preset, preset, need_publish); - } else { - this->custom_preset.reset(); - set_property(this->preset, p.get_preset(), need_publish); - } - if (need_publish) - this->publish_state(); - set_sensor(this->outdoor_sensor_, p.get_outdoor_temp()); - set_sensor(this->humidity_sensor_, p.get_humidity_setpoint()); -} - -void MideaAC::on_update() { - if (this->ctrl_request_) { - ESP_LOGD(TAG, "TX: control"); - this->parent_->write_frame(this->cmd_frame_); - } else { - ESP_LOGD(TAG, "TX: query"); - if (this->power_sensor_ == nullptr || this->request_num_++ % 32) - this->parent_->write_frame(this->query_frame_); - else - this->parent_->write_frame(this->power_frame_); - } -} - -bool MideaAC::allow_preset(climate::ClimatePreset preset) const { - switch (preset) { - case climate::CLIMATE_PRESET_ECO: - if (this->mode == climate::CLIMATE_MODE_COOL) { - return true; - } else { - ESP_LOGD(TAG, "ECO preset is only available in COOL mode"); - } - break; - case climate::CLIMATE_PRESET_SLEEP: - if (this->mode == climate::CLIMATE_MODE_FAN_ONLY || this->mode == climate::CLIMATE_MODE_DRY) { - ESP_LOGD(TAG, "SLEEP preset is not available in FAN_ONLY or DRY mode"); - } else { - return true; - } - break; - case climate::CLIMATE_PRESET_BOOST: - if (this->mode == climate::CLIMATE_MODE_HEAT || this->mode == climate::CLIMATE_MODE_COOL) { - return true; - } else { - ESP_LOGD(TAG, "BOOST preset is only available in HEAT or COOL mode"); - } - break; - case climate::CLIMATE_PRESET_NONE: - return true; - default: - break; - } - return false; -} - -bool MideaAC::allow_custom_preset(const std::string &custom_preset) const { - if (custom_preset == MIDEA_FREEZE_PROTECTION_PRESET) { - if (this->mode == climate::CLIMATE_MODE_HEAT) { - return true; - } else { - ESP_LOGD(TAG, "%s is only available in HEAT mode", MIDEA_FREEZE_PROTECTION_PRESET.c_str()); - } - } - return false; -} - -void MideaAC::control(const climate::ClimateCall &call) { - if (call.get_mode().has_value() && call.get_mode().value() != this->mode) { - this->cmd_frame_.set_mode(call.get_mode().value()); - this->ctrl_request_ = true; - } - if (call.get_target_temperature().has_value() && call.get_target_temperature().value() != this->target_temperature) { - this->cmd_frame_.set_target_temp(call.get_target_temperature().value()); - this->ctrl_request_ = true; - } - if (call.get_fan_mode().has_value() && - (!this->fan_mode.has_value() || this->fan_mode.value() != call.get_fan_mode().value())) { - this->custom_fan_mode.reset(); - this->cmd_frame_.set_fan_mode(call.get_fan_mode().value()); - this->ctrl_request_ = true; - } - if (call.get_custom_fan_mode().has_value() && - (!this->custom_fan_mode.has_value() || this->custom_fan_mode.value() != call.get_custom_fan_mode().value())) { - this->fan_mode.reset(); - this->cmd_frame_.set_custom_fan_mode(call.get_custom_fan_mode().value()); - this->ctrl_request_ = true; - } - if (call.get_swing_mode().has_value() && call.get_swing_mode().value() != this->swing_mode) { - this->cmd_frame_.set_swing_mode(call.get_swing_mode().value()); - this->ctrl_request_ = true; - } - if (call.get_preset().has_value() && this->allow_preset(call.get_preset().value()) && - (!this->preset.has_value() || this->preset.value() != call.get_preset().value())) { - this->custom_preset.reset(); - this->cmd_frame_.set_preset(call.get_preset().value()); - this->ctrl_request_ = true; - } - if (call.get_custom_preset().has_value() && this->allow_custom_preset(call.get_custom_preset().value()) && - (!this->custom_preset.has_value() || this->custom_preset.value() != call.get_custom_preset().value())) { - this->preset.reset(); - this->cmd_frame_.set_custom_preset(call.get_custom_preset().value()); - this->ctrl_request_ = true; - } - if (this->ctrl_request_) { - this->cmd_frame_.set_beeper_feedback(this->beeper_feedback_); - this->cmd_frame_.finalize(); - } -} - -climate::ClimateTraits MideaAC::traits() { - auto traits = climate::ClimateTraits(); - traits.set_visual_min_temperature(17); - traits.set_visual_max_temperature(30); - traits.set_visual_temperature_step(0.5); - traits.set_supported_modes({ - climate::CLIMATE_MODE_OFF, - climate::CLIMATE_MODE_HEAT_COOL, - climate::CLIMATE_MODE_COOL, - climate::CLIMATE_MODE_DRY, - climate::CLIMATE_MODE_HEAT, - climate::CLIMATE_MODE_FAN_ONLY, - }); - traits.set_supported_fan_modes({ - climate::CLIMATE_FAN_AUTO, - climate::CLIMATE_FAN_LOW, - climate::CLIMATE_FAN_MEDIUM, - climate::CLIMATE_FAN_HIGH, - }); - traits.set_supported_custom_fan_modes(this->traits_custom_fan_modes_); - traits.set_supported_swing_modes({ - climate::CLIMATE_SWING_OFF, - climate::CLIMATE_SWING_VERTICAL, - }); - if (traits_swing_horizontal_) - traits.add_supported_swing_mode(climate::CLIMATE_SWING_HORIZONTAL); - if (traits_swing_both_) - traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH); - traits.set_supported_presets({ - climate::CLIMATE_PRESET_NONE, - }); - if (traits_preset_eco_) - traits.add_supported_preset(climate::CLIMATE_PRESET_ECO); - if (traits_preset_sleep_) - traits.add_supported_preset(climate::CLIMATE_PRESET_SLEEP); - if (traits_preset_boost_) - traits.add_supported_preset(climate::CLIMATE_PRESET_BOOST); - traits.set_supported_custom_presets(this->traits_custom_presets_); - traits.set_supports_current_temperature(true); - return traits; -} - -} // namespace midea_ac -} // namespace esphome diff --git a/esphome/components/midea_ac/midea_climate.h b/esphome/components/midea_ac/midea_climate.h deleted file mode 100644 index 62bd4c339e..0000000000 --- a/esphome/components/midea_ac/midea_climate.h +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include - -#include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/components/midea_dongle/midea_dongle.h" -#include "esphome/components/climate/climate.h" -#include "esphome/components/midea_dongle/midea_dongle.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/core/component.h" -#include "midea_frame.h" - -namespace esphome { -namespace midea_ac { - -class MideaAC : public midea_dongle::MideaAppliance, public climate::Climate, public Component { - public: - float get_setup_priority() const override { return setup_priority::LATE; } - void on_frame(const midea_dongle::Frame &frame) override; - void on_update() override; - void setup() override { this->parent_->set_appliance(this); } - void set_midea_dongle_parent(midea_dongle::MideaDongle *parent) { this->parent_ = parent; } - void set_outdoor_temperature_sensor(sensor::Sensor *sensor) { this->outdoor_sensor_ = sensor; } - void set_humidity_setpoint_sensor(sensor::Sensor *sensor) { this->humidity_sensor_ = sensor; } - void set_power_sensor(sensor::Sensor *sensor) { this->power_sensor_ = sensor; } - void set_beeper_feedback(bool state) { this->beeper_feedback_ = state; } - void set_swing_horizontal(bool state) { this->traits_swing_horizontal_ = state; } - void set_swing_both(bool state) { this->traits_swing_both_ = state; } - void set_preset_eco(bool state) { this->traits_preset_eco_ = state; } - void set_preset_sleep(bool state) { this->traits_preset_sleep_ = state; } - void set_preset_boost(bool state) { this->traits_preset_boost_ = state; } - bool allow_preset(climate::ClimatePreset preset) const; - void set_custom_fan_modes(std::set custom_fan_modes) { - this->traits_custom_fan_modes_ = std::move(custom_fan_modes); - } - void set_custom_presets(std::set custom_presets) { - this->traits_custom_presets_ = std::move(custom_presets); - } - bool allow_custom_preset(const std::string &custom_preset) const; - - protected: - /// Override control to change settings of the climate device. - void control(const climate::ClimateCall &call) override; - /// Return the traits of this controller. - climate::ClimateTraits traits() override; - - const QueryFrame query_frame_; - const PowerQueryFrame power_frame_; - CommandFrame cmd_frame_; - midea_dongle::MideaDongle *parent_{nullptr}; - sensor::Sensor *outdoor_sensor_{nullptr}; - sensor::Sensor *humidity_sensor_{nullptr}; - sensor::Sensor *power_sensor_{nullptr}; - uint8_t request_num_{0}; - bool ctrl_request_{false}; - bool beeper_feedback_{false}; - bool traits_swing_horizontal_{false}; - bool traits_swing_both_{false}; - bool traits_preset_eco_{false}; - bool traits_preset_sleep_{false}; - bool traits_preset_boost_{false}; - std::set traits_custom_fan_modes_{{}}; - std::set traits_custom_presets_{{}}; -}; - -} // namespace midea_ac -} // namespace esphome diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp deleted file mode 100644 index c0a5ce4b55..0000000000 --- a/esphome/components/midea_ac/midea_frame.cpp +++ /dev/null @@ -1,238 +0,0 @@ -#include "midea_frame.h" - -namespace esphome { -namespace midea_ac { - -static const char *const TAG = "midea_ac"; -const std::string MIDEA_SILENT_FAN_MODE = "silent"; -const std::string MIDEA_TURBO_FAN_MODE = "turbo"; -const std::string MIDEA_FREEZE_PROTECTION_PRESET = "freeze protection"; - -const uint8_t QueryFrame::INIT[] = {0xAA, 0x21, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x81, - 0x00, 0xFF, 0x03, 0xFF, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x37, 0x31}; - -const uint8_t PowerQueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x21, - 0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x17, 0x6A}; - -const uint8_t CommandFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x40, 0x00, - 0x00, 0x00, 0x7F, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - -float PropertiesFrame::get_target_temp() const { - float temp = static_cast((this->pbuf_[12] & 0x0F) + 16); - if (this->pbuf_[12] & 0x10) - temp += 0.5; - return temp; -} - -void PropertiesFrame::set_target_temp(float temp) { - uint8_t tmp = static_cast(temp * 16.0) + 4; - tmp = ((tmp & 8) << 1) | (tmp >> 4); - this->pbuf_[12] &= ~0x1F; - this->pbuf_[12] |= tmp; -} - -static float i16tof(int16_t in) { return static_cast(in - 50) / 2.0; } -float PropertiesFrame::get_indoor_temp() const { return i16tof(this->pbuf_[21]); } -float PropertiesFrame::get_outdoor_temp() const { return i16tof(this->pbuf_[22]); } -float PropertiesFrame::get_humidity_setpoint() const { return static_cast(this->pbuf_[29] & 0x7F); } - -climate::ClimateMode PropertiesFrame::get_mode() const { - if (!this->get_power_()) - return climate::CLIMATE_MODE_OFF; - switch (this->pbuf_[12] >> 5) { - case MIDEA_MODE_AUTO: - return climate::CLIMATE_MODE_HEAT_COOL; - case MIDEA_MODE_COOL: - return climate::CLIMATE_MODE_COOL; - case MIDEA_MODE_DRY: - return climate::CLIMATE_MODE_DRY; - case MIDEA_MODE_HEAT: - return climate::CLIMATE_MODE_HEAT; - case MIDEA_MODE_FAN_ONLY: - return climate::CLIMATE_MODE_FAN_ONLY; - default: - return climate::CLIMATE_MODE_OFF; - } -} - -void PropertiesFrame::set_mode(climate::ClimateMode mode) { - uint8_t m; - switch (mode) { - case climate::CLIMATE_MODE_HEAT_COOL: - m = MIDEA_MODE_AUTO; - break; - case climate::CLIMATE_MODE_COOL: - m = MIDEA_MODE_COOL; - break; - case climate::CLIMATE_MODE_DRY: - m = MIDEA_MODE_DRY; - break; - case climate::CLIMATE_MODE_HEAT: - m = MIDEA_MODE_HEAT; - break; - case climate::CLIMATE_MODE_FAN_ONLY: - m = MIDEA_MODE_FAN_ONLY; - break; - default: - this->set_power_(false); - return; - } - this->set_power_(true); - this->pbuf_[12] &= ~0xE0; - this->pbuf_[12] |= m << 5; -} - -optional PropertiesFrame::get_preset() const { - if (this->get_eco_mode()) - return climate::CLIMATE_PRESET_ECO; - if (this->get_sleep_mode()) - return climate::CLIMATE_PRESET_SLEEP; - if (this->get_turbo_mode()) - return climate::CLIMATE_PRESET_BOOST; - return climate::CLIMATE_PRESET_NONE; -} - -void PropertiesFrame::set_preset(climate::ClimatePreset preset) { - this->clear_presets(); - switch (preset) { - case climate::CLIMATE_PRESET_ECO: - this->set_eco_mode(true); - break; - case climate::CLIMATE_PRESET_SLEEP: - this->set_sleep_mode(true); - break; - case climate::CLIMATE_PRESET_BOOST: - this->set_turbo_mode(true); - break; - default: - break; - } -} - -void PropertiesFrame::clear_presets() { - this->set_eco_mode(false); - this->set_sleep_mode(false); - this->set_turbo_mode(false); - this->set_freeze_protection_mode(false); -} - -bool PropertiesFrame::is_custom_preset() const { return this->get_freeze_protection_mode(); } - -const std::string &PropertiesFrame::get_custom_preset() const { return midea_ac::MIDEA_FREEZE_PROTECTION_PRESET; }; - -void PropertiesFrame::set_custom_preset(const std::string &preset) { - this->clear_presets(); - if (preset == MIDEA_FREEZE_PROTECTION_PRESET) - this->set_freeze_protection_mode(true); -} - -bool PropertiesFrame::is_custom_fan_mode() const { - switch (this->pbuf_[13]) { - case MIDEA_FAN_SILENT: - case MIDEA_FAN_TURBO: - return true; - default: - return false; - } -} - -climate::ClimateFanMode PropertiesFrame::get_fan_mode() const { - switch (this->pbuf_[13]) { - case MIDEA_FAN_LOW: - return climate::CLIMATE_FAN_LOW; - case MIDEA_FAN_MEDIUM: - return climate::CLIMATE_FAN_MEDIUM; - case MIDEA_FAN_HIGH: - return climate::CLIMATE_FAN_HIGH; - default: - return climate::CLIMATE_FAN_AUTO; - } -} - -void PropertiesFrame::set_fan_mode(climate::ClimateFanMode mode) { - uint8_t m; - switch (mode) { - case climate::CLIMATE_FAN_LOW: - m = MIDEA_FAN_LOW; - break; - case climate::CLIMATE_FAN_MEDIUM: - m = MIDEA_FAN_MEDIUM; - break; - case climate::CLIMATE_FAN_HIGH: - m = MIDEA_FAN_HIGH; - break; - default: - m = MIDEA_FAN_AUTO; - break; - } - this->pbuf_[13] = m; -} - -const std::string &PropertiesFrame::get_custom_fan_mode() const { - switch (this->pbuf_[13]) { - case MIDEA_FAN_SILENT: - return MIDEA_SILENT_FAN_MODE; - default: - return MIDEA_TURBO_FAN_MODE; - } -} - -void PropertiesFrame::set_custom_fan_mode(const std::string &mode) { - uint8_t m; - if (mode == MIDEA_SILENT_FAN_MODE) { - m = MIDEA_FAN_SILENT; - } else { - m = MIDEA_FAN_TURBO; - } - this->pbuf_[13] = m; -} - -climate::ClimateSwingMode PropertiesFrame::get_swing_mode() const { - switch (this->pbuf_[17] & 0x0F) { - case MIDEA_SWING_VERTICAL: - return climate::CLIMATE_SWING_VERTICAL; - case MIDEA_SWING_HORIZONTAL: - return climate::CLIMATE_SWING_HORIZONTAL; - case MIDEA_SWING_BOTH: - return climate::CLIMATE_SWING_BOTH; - default: - return climate::CLIMATE_SWING_OFF; - } -} - -void PropertiesFrame::set_swing_mode(climate::ClimateSwingMode mode) { - uint8_t m; - switch (mode) { - case climate::CLIMATE_SWING_VERTICAL: - m = MIDEA_SWING_VERTICAL; - break; - case climate::CLIMATE_SWING_HORIZONTAL: - m = MIDEA_SWING_HORIZONTAL; - break; - case climate::CLIMATE_SWING_BOTH: - m = MIDEA_SWING_BOTH; - break; - default: - m = MIDEA_SWING_OFF; - break; - } - this->pbuf_[17] = 0x30 | m; -} - -float PropertiesFrame::get_power_usage() const { - uint32_t power = 0; - const uint8_t *ptr = this->pbuf_ + 28; - for (uint32_t weight = 1;; weight *= 10, ptr--) { - power += (*ptr % 16) * weight; - weight *= 10; - power += (*ptr / 16) * weight; - if (weight == 100000) - return static_cast(power) * 0.1; - } -} - -} // namespace midea_ac -} // namespace esphome diff --git a/esphome/components/midea_ac/midea_frame.h b/esphome/components/midea_ac/midea_frame.h deleted file mode 100644 index e1d6fed49d..0000000000 --- a/esphome/components/midea_ac/midea_frame.h +++ /dev/null @@ -1,165 +0,0 @@ -#pragma once -#include "esphome/components/climate/climate.h" -#include "esphome/components/midea_dongle/midea_frame.h" - -namespace esphome { -namespace midea_ac { - -extern const std::string MIDEA_SILENT_FAN_MODE; -extern const std::string MIDEA_TURBO_FAN_MODE; -extern const std::string MIDEA_FREEZE_PROTECTION_PRESET; - -/// Enum for all modes a Midea device can be in. -enum MideaMode : uint8_t { - /// The Midea device is set to automatically change the heating/cooling cycle - MIDEA_MODE_AUTO = 1, - /// The Midea device is manually set to cool mode (not in auto mode!) - MIDEA_MODE_COOL = 2, - /// The Midea device is manually set to dry mode - MIDEA_MODE_DRY = 3, - /// The Midea device is manually set to heat mode (not in auto mode!) - MIDEA_MODE_HEAT = 4, - /// The Midea device is manually set to fan only mode - MIDEA_MODE_FAN_ONLY = 5, -}; - -/// Enum for all modes a Midea fan can be in -enum MideaFanMode : uint8_t { - /// The fan mode is set to Auto - MIDEA_FAN_AUTO = 102, - /// The fan mode is set to Silent - MIDEA_FAN_SILENT = 20, - /// The fan mode is set to Low - MIDEA_FAN_LOW = 40, - /// The fan mode is set to Medium - MIDEA_FAN_MEDIUM = 60, - /// The fan mode is set to High - MIDEA_FAN_HIGH = 80, - /// The fan mode is set to Turbo - MIDEA_FAN_TURBO = 100, -}; - -/// Enum for all modes a Midea swing can be in -enum MideaSwingMode : uint8_t { - /// The sing mode is set to Off - MIDEA_SWING_OFF = 0b0000, - /// The fan mode is set to Both - MIDEA_SWING_BOTH = 0b1111, - /// The fan mode is set to Vertical - MIDEA_SWING_VERTICAL = 0b1100, - /// The fan mode is set to Horizontal - MIDEA_SWING_HORIZONTAL = 0b0011, -}; - -class PropertiesFrame : public midea_dongle::BaseFrame { - public: - PropertiesFrame() = delete; - PropertiesFrame(uint8_t *data) : BaseFrame(data) {} - PropertiesFrame(const Frame &frame) : BaseFrame(frame) {} - - bool has_properties() const { - return this->has_response_type(0xC0) && (this->has_type(0x03) || this->has_type(0x02)); - } - - bool has_power_info() const { return this->has_response_type(0xC1); } - - /* TARGET TEMPERATURE */ - - float get_target_temp() const; - void set_target_temp(float temp); - - /* MODE */ - climate::ClimateMode get_mode() const; - void set_mode(climate::ClimateMode mode); - - /* FAN SPEED */ - bool is_custom_fan_mode() const; - climate::ClimateFanMode get_fan_mode() const; - void set_fan_mode(climate::ClimateFanMode mode); - - const std::string &get_custom_fan_mode() const; - void set_custom_fan_mode(const std::string &mode); - - /* SWING MODE */ - climate::ClimateSwingMode get_swing_mode() const; - void set_swing_mode(climate::ClimateSwingMode mode); - - /* INDOOR TEMPERATURE */ - float get_indoor_temp() const; - - /* OUTDOOR TEMPERATURE */ - float get_outdoor_temp() const; - - /* HUMIDITY SETPOINT */ - float get_humidity_setpoint() const; - - /* ECO MODE */ - bool get_eco_mode() const { return this->pbuf_[19] & 0x10; } - void set_eco_mode(bool state) { this->set_bytemask_(19, 0x80, state); } - - /* SLEEP MODE */ - bool get_sleep_mode() const { return this->pbuf_[20] & 0x01; } - void set_sleep_mode(bool state) { this->set_bytemask_(20, 0x01, state); } - - /* TURBO MODE */ - bool get_turbo_mode() const { return this->pbuf_[18] & 0x20 || this->pbuf_[20] & 0x02; } - void set_turbo_mode(bool state) { - this->set_bytemask_(18, 0x20, state); - this->set_bytemask_(20, 0x02, state); - } - - /* FREEZE PROTECTION */ - bool get_freeze_protection_mode() const { return this->pbuf_[31] & 0x80; } - void set_freeze_protection_mode(bool state) { this->set_bytemask_(31, 0x80, state); } - - /* PRESET */ - optional get_preset() const; - void set_preset(climate::ClimatePreset preset); - void clear_presets(); - - bool is_custom_preset() const; - const std::string &get_custom_preset() const; - void set_custom_preset(const std::string &preset); - - /* POWER USAGE */ - float get_power_usage() const; - - /// Set properties from another frame - void set_properties(const PropertiesFrame &p) { memcpy(this->pbuf_ + 11, p.data() + 11, 10); } - - protected: - /* POWER */ - bool get_power_() const { return this->pbuf_[11] & 0x01; } - void set_power_(bool state) { this->set_bytemask_(11, 0x01, state); } -}; - -// Query state frame (read-only) -class QueryFrame : public midea_dongle::StaticFrame { - public: - QueryFrame() : StaticFrame(FPSTR(this->INIT)) {} - - private: - static const uint8_t PROGMEM INIT[]; -}; - -// Power query state frame (read-only) -class PowerQueryFrame : public midea_dongle::StaticFrame { - public: - PowerQueryFrame() : StaticFrame(FPSTR(this->INIT)) {} - - private: - static const uint8_t PROGMEM INIT[]; -}; - -// Command frame -class CommandFrame : public midea_dongle::StaticFrame { - public: - CommandFrame() : StaticFrame(FPSTR(this->INIT)) {} - void set_beeper_feedback(bool state) { this->set_bytemask_(11, 0x40, state); } - - private: - static const uint8_t PROGMEM INIT[]; -}; - -} // namespace midea_ac -} // namespace esphome diff --git a/esphome/components/midea_dongle/__init__.py b/esphome/components/midea_dongle/__init__.py deleted file mode 100644 index daa8ea6657..0000000000 --- a/esphome/components/midea_dongle/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components import uart -from esphome.const import CONF_ID - -DEPENDENCIES = ["wifi", "uart"] -CODEOWNERS = ["@dudanov"] - -midea_dongle_ns = cg.esphome_ns.namespace("midea_dongle") -MideaDongle = midea_dongle_ns.class_("MideaDongle", cg.Component, uart.UARTDevice) - -CONF_MIDEA_DONGLE_ID = "midea_dongle_id" -CONF_STRENGTH_ICON = "strength_icon" -CONFIG_SCHEMA = ( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(MideaDongle), - cv.Optional(CONF_STRENGTH_ICON, default=False): cv.boolean, - } - ) - .extend(cv.COMPONENT_SCHEMA) - .extend(uart.UART_DEVICE_SCHEMA) -) - - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await uart.register_uart_device(var, config) - cg.add(var.use_strength_icon(config[CONF_STRENGTH_ICON])) diff --git a/esphome/components/midea_dongle/midea_dongle.cpp b/esphome/components/midea_dongle/midea_dongle.cpp deleted file mode 100644 index 7e3683a964..0000000000 --- a/esphome/components/midea_dongle/midea_dongle.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "midea_dongle.h" -#include "esphome/core/log.h" -#include "esphome/core/helpers.h" - -namespace esphome { -namespace midea_dongle { - -static const char *const TAG = "midea_dongle"; - -void MideaDongle::loop() { - while (this->available()) { - const uint8_t rx = this->read(); - if (this->idx_ <= OFFSET_LENGTH) { - if (this->idx_ == OFFSET_LENGTH) { - if (rx <= OFFSET_BODY || rx >= sizeof(this->buf_)) { - this->reset_(); - continue; - } - this->cnt_ = rx; - } else if (rx != SYNC_BYTE) { - continue; - } - } - this->buf_[this->idx_++] = rx; - if (--this->cnt_) - continue; - this->reset_(); - const BaseFrame frame(this->buf_); - ESP_LOGD(TAG, "RX: %s", frame.to_string().c_str()); - if (!frame.is_valid()) { - ESP_LOGW(TAG, "RX: frame check failed!"); - continue; - } - if (frame.get_type() == QUERY_NETWORK) { - this->notify_.set_type(QUERY_NETWORK); - this->need_notify_ = true; - continue; - } - if (this->appliance_ != nullptr) - this->appliance_->on_frame(frame); - } -} - -void MideaDongle::update() { - const bool is_conn = WiFi.isConnected(); - uint8_t wifi_strength = 0; - if (!this->rssi_timer_) { - if (is_conn) - wifi_strength = 4; - } else if (is_conn) { - if (--this->rssi_timer_) { - wifi_strength = this->notify_.get_signal_strength(); - } else { - this->rssi_timer_ = 60; - const int32_t dbm = WiFi.RSSI(); - if (dbm > -63) - wifi_strength = 4; - else if (dbm > -75) - wifi_strength = 3; - else if (dbm > -88) - wifi_strength = 2; - else if (dbm > -100) - wifi_strength = 1; - } - } else { - this->rssi_timer_ = 1; - } - if (this->notify_.is_connected() != is_conn) { - this->notify_.set_connected(is_conn); - this->need_notify_ = true; - } - if (this->notify_.get_signal_strength() != wifi_strength) { - this->notify_.set_signal_strength(wifi_strength); - this->need_notify_ = true; - } - if (!--this->notify_timer_) { - this->notify_.set_type(NETWORK_NOTIFY); - this->need_notify_ = true; - } - if (this->need_notify_) { - ESP_LOGD(TAG, "TX: notify WiFi STA %s, signal strength %d", is_conn ? "connected" : "not connected", wifi_strength); - this->need_notify_ = false; - this->notify_timer_ = 600; - this->notify_.finalize(); - this->write_frame(this->notify_); - return; - } - if (this->appliance_ != nullptr) - this->appliance_->on_update(); -} - -void MideaDongle::write_frame(const Frame &frame) { - this->write_array(frame.data(), frame.size()); - ESP_LOGD(TAG, "TX: %s", frame.to_string().c_str()); -} - -} // namespace midea_dongle -} // namespace esphome diff --git a/esphome/components/midea_dongle/midea_dongle.h b/esphome/components/midea_dongle/midea_dongle.h deleted file mode 100644 index a7dfb9cf25..0000000000 --- a/esphome/components/midea_dongle/midea_dongle.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once -#include "esphome/core/component.h" -#include "esphome/components/wifi/wifi_component.h" -#include "esphome/components/uart/uart.h" -#include "midea_frame.h" - -namespace esphome { -namespace midea_dongle { - -enum MideaApplianceType : uint8_t { DEHUMIDIFIER = 0xA1, AIR_CONDITIONER = 0xAC, BROADCAST = 0xFF }; -enum MideaMessageType : uint8_t { - DEVICE_CONTROL = 0x02, - DEVICE_QUERY = 0x03, - NETWORK_NOTIFY = 0x0D, - QUERY_NETWORK = 0x63, -}; - -struct MideaAppliance { - /// Calling on update event - virtual void on_update() = 0; - /// Calling on frame receive event - virtual void on_frame(const Frame &frame) = 0; -}; - -class MideaDongle : public PollingComponent, public uart::UARTDevice { - public: - MideaDongle() : PollingComponent(1000) {} - float get_setup_priority() const override { return setup_priority::LATE; } - void update() override; - void loop() override; - void set_appliance(MideaAppliance *app) { this->appliance_ = app; } - void use_strength_icon(bool state) { this->rssi_timer_ = state; } - void write_frame(const Frame &frame); - - protected: - MideaAppliance *appliance_{nullptr}; - NotifyFrame notify_; - unsigned notify_timer_{1}; - // Buffer - uint8_t buf_[36]; - // Index - uint8_t idx_{0}; - // Reverse receive counter - uint8_t cnt_{2}; - uint8_t rssi_timer_{0}; - bool need_notify_{false}; - - // Reset receiver state - void reset_() { - this->idx_ = 0; - this->cnt_ = 2; - } -}; - -} // namespace midea_dongle -} // namespace esphome diff --git a/esphome/components/midea_dongle/midea_frame.cpp b/esphome/components/midea_dongle/midea_frame.cpp deleted file mode 100644 index acb3feee5f..0000000000 --- a/esphome/components/midea_dongle/midea_frame.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include "midea_frame.h" - -namespace esphome { -namespace midea_dongle { - -const uint8_t BaseFrame::CRC_TABLE[] = { - 0x00, 0x5E, 0xBC, 0xE2, 0x61, 0x3F, 0xDD, 0x83, 0xC2, 0x9C, 0x7E, 0x20, 0xA3, 0xFD, 0x1F, 0x41, 0x9D, 0xC3, 0x21, - 0x7F, 0xFC, 0xA2, 0x40, 0x1E, 0x5F, 0x01, 0xE3, 0xBD, 0x3E, 0x60, 0x82, 0xDC, 0x23, 0x7D, 0x9F, 0xC1, 0x42, 0x1C, - 0xFE, 0xA0, 0xE1, 0xBF, 0x5D, 0x03, 0x80, 0xDE, 0x3C, 0x62, 0xBE, 0xE0, 0x02, 0x5C, 0xDF, 0x81, 0x63, 0x3D, 0x7C, - 0x22, 0xC0, 0x9E, 0x1D, 0x43, 0xA1, 0xFF, 0x46, 0x18, 0xFA, 0xA4, 0x27, 0x79, 0x9B, 0xC5, 0x84, 0xDA, 0x38, 0x66, - 0xE5, 0xBB, 0x59, 0x07, 0xDB, 0x85, 0x67, 0x39, 0xBA, 0xE4, 0x06, 0x58, 0x19, 0x47, 0xA5, 0xFB, 0x78, 0x26, 0xC4, - 0x9A, 0x65, 0x3B, 0xD9, 0x87, 0x04, 0x5A, 0xB8, 0xE6, 0xA7, 0xF9, 0x1B, 0x45, 0xC6, 0x98, 0x7A, 0x24, 0xF8, 0xA6, - 0x44, 0x1A, 0x99, 0xC7, 0x25, 0x7B, 0x3A, 0x64, 0x86, 0xD8, 0x5B, 0x05, 0xE7, 0xB9, 0x8C, 0xD2, 0x30, 0x6E, 0xED, - 0xB3, 0x51, 0x0F, 0x4E, 0x10, 0xF2, 0xAC, 0x2F, 0x71, 0x93, 0xCD, 0x11, 0x4F, 0xAD, 0xF3, 0x70, 0x2E, 0xCC, 0x92, - 0xD3, 0x8D, 0x6F, 0x31, 0xB2, 0xEC, 0x0E, 0x50, 0xAF, 0xF1, 0x13, 0x4D, 0xCE, 0x90, 0x72, 0x2C, 0x6D, 0x33, 0xD1, - 0x8F, 0x0C, 0x52, 0xB0, 0xEE, 0x32, 0x6C, 0x8E, 0xD0, 0x53, 0x0D, 0xEF, 0xB1, 0xF0, 0xAE, 0x4C, 0x12, 0x91, 0xCF, - 0x2D, 0x73, 0xCA, 0x94, 0x76, 0x28, 0xAB, 0xF5, 0x17, 0x49, 0x08, 0x56, 0xB4, 0xEA, 0x69, 0x37, 0xD5, 0x8B, 0x57, - 0x09, 0xEB, 0xB5, 0x36, 0x68, 0x8A, 0xD4, 0x95, 0xCB, 0x29, 0x77, 0xF4, 0xAA, 0x48, 0x16, 0xE9, 0xB7, 0x55, 0x0B, - 0x88, 0xD6, 0x34, 0x6A, 0x2B, 0x75, 0x97, 0xC9, 0x4A, 0x14, 0xF6, 0xA8, 0x74, 0x2A, 0xC8, 0x96, 0x15, 0x4B, 0xA9, - 0xF7, 0xB6, 0xE8, 0x0A, 0x54, 0xD7, 0x89, 0x6B, 0x35}; - -const uint8_t NotifyFrame::INIT[] = {0xAA, 0x1F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0D, 0x01, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - -bool BaseFrame::is_valid() const { return /*this->has_valid_crc_() &&*/ this->has_valid_cs_(); } - -void BaseFrame::finalize() { - this->update_crc_(); - this->update_cs_(); -} - -void BaseFrame::update_crc_() { - uint8_t crc = 0; - uint8_t *ptr = this->pbuf_ + OFFSET_BODY; - uint8_t len = this->length_() - OFFSET_BODY; - while (--len) - crc = pgm_read_byte(BaseFrame::CRC_TABLE + (crc ^ *ptr++)); - *ptr = crc; -} - -void BaseFrame::update_cs_() { - uint8_t cs = 0; - uint8_t *ptr = this->pbuf_ + OFFSET_LENGTH; - uint8_t len = this->length_(); - while (--len) - cs -= *ptr++; - *ptr = cs; -} - -bool BaseFrame::has_valid_crc_() const { - uint8_t crc = 0; - uint8_t len = this->length_() - OFFSET_BODY; - const uint8_t *ptr = this->pbuf_ + OFFSET_BODY; - for (; len; ptr++, len--) - crc = pgm_read_byte(BaseFrame::CRC_TABLE + (crc ^ *ptr)); - return !crc; -} - -bool BaseFrame::has_valid_cs_() const { - uint8_t cs = 0; - uint8_t len = this->length_(); - const uint8_t *ptr = this->pbuf_ + OFFSET_LENGTH; - for (; len; ptr++, len--) - cs -= *ptr; - return !cs; -} - -void BaseFrame::set_bytemask_(uint8_t idx, uint8_t mask, bool state) { - uint8_t *dst = this->pbuf_ + idx; - if (state) - *dst |= mask; - else - *dst &= ~mask; -} - -static char u4hex(uint8_t num) { return num + ((num < 10) ? '0' : ('A' - 10)); } - -String Frame::to_string() const { - String ret; - char buf[4]; - buf[2] = ' '; - buf[3] = '\0'; - ret.reserve(3 * 36); - const uint8_t *it = this->data(); - for (size_t i = 0; i < this->size(); i++, it++) { - buf[0] = u4hex(*it >> 4); - buf[1] = u4hex(*it & 15); - ret.concat(buf); - } - return ret; -} - -} // namespace midea_dongle -} // namespace esphome diff --git a/esphome/components/midea_dongle/midea_frame.h b/esphome/components/midea_dongle/midea_frame.h deleted file mode 100644 index ce89cc636e..0000000000 --- a/esphome/components/midea_dongle/midea_frame.h +++ /dev/null @@ -1,104 +0,0 @@ -#pragma once -#include "esphome/core/component.h" - -namespace esphome { -namespace midea_dongle { - -static const uint8_t OFFSET_START = 0; -static const uint8_t OFFSET_LENGTH = 1; -static const uint8_t OFFSET_APPTYPE = 2; -static const uint8_t OFFSET_BODY = 10; -static const uint8_t SYNC_BYTE = 0xAA; - -class Frame { - public: - Frame() = delete; - Frame(uint8_t *data) : pbuf_(data) {} - Frame(const Frame &frame) : pbuf_(frame.data()) {} - - // Frame buffer - uint8_t *data() const { return this->pbuf_; } - // Frame size - uint8_t size() const { return this->length_() + OFFSET_LENGTH; } - uint8_t app_type() const { return this->pbuf_[OFFSET_APPTYPE]; } - - template typename std::enable_if::value, T>::type as() const { - return T(*this); - } - String to_string() const; - - protected: - uint8_t *pbuf_; - uint8_t length_() const { return this->pbuf_[OFFSET_LENGTH]; } -}; - -class BaseFrame : public Frame { - public: - BaseFrame() = delete; - BaseFrame(uint8_t *data) : Frame(data) {} - BaseFrame(const Frame &frame) : Frame(frame) {} - - // Check for valid - bool is_valid() const; - // Prepare for sending to device - void finalize(); - uint8_t get_type() const { return this->pbuf_[9]; } - void set_type(uint8_t value) { this->pbuf_[9] = value; } - bool has_response_type(uint8_t type) const { return this->resp_type_() == type; } - bool has_type(uint8_t type) const { return this->get_type() == type; } - - protected: - static const uint8_t PROGMEM CRC_TABLE[256]; - void set_bytemask_(uint8_t idx, uint8_t mask, bool state); - uint8_t resp_type_() const { return this->pbuf_[OFFSET_BODY]; } - bool has_valid_crc_() const; - bool has_valid_cs_() const; - void update_crc_(); - void update_cs_(); -}; - -template class StaticFrame : public T { - public: - // Default constructor - StaticFrame() : T(this->buf_) {} - // Copy constructor - StaticFrame(const Frame &src) : T(this->buf_) { - if (src.length_() < sizeof(this->buf_)) { - memcpy(this->buf_, src.data(), src.length_() + OFFSET_LENGTH); - } - } - // Constructor for RAM data - StaticFrame(const uint8_t *src) : T(this->buf_) { - const uint8_t len = src[OFFSET_LENGTH]; - if (len < sizeof(this->buf_)) { - memcpy(this->buf_, src, len + OFFSET_LENGTH); - } - } - // Constructor for PROGMEM data - StaticFrame(const __FlashStringHelper *pgm) : T(this->buf_) { - const uint8_t *src = reinterpret_cast(pgm); - const uint8_t len = pgm_read_byte(src + OFFSET_LENGTH); - if (len < sizeof(this->buf_)) { - memcpy_P(this->buf_, src, len + OFFSET_LENGTH); - } - } - - protected: - uint8_t buf_[buf_size]; -}; - -// Device network notification frame -class NotifyFrame : public midea_dongle::StaticFrame { - public: - NotifyFrame() : StaticFrame(FPSTR(NotifyFrame::INIT)) {} - void set_signal_strength(uint8_t value) { this->pbuf_[12] = value; } - uint8_t get_signal_strength() const { return this->pbuf_[12]; } - void set_connected(bool state) { this->pbuf_[18] = state ? 0 : 1; } - bool is_connected() const { return !this->pbuf_[18]; } - - private: - static const uint8_t PROGMEM INIT[]; -}; - -} // namespace midea_dongle -} // namespace esphome diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index c9f1c611a8..d76dc6bc34 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -1085,3 +1085,45 @@ async def panasonic_action(var, config, args): cg.add(var.set_address(template_)) template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint32) cg.add(var.set_command(template_)) + + +# Midea +MideaData, MideaBinarySensor, MideaTrigger, MideaAction, MideaDumper = declare_protocol( + "Midea" +) +MideaAction = ns.class_("MideaAction", RemoteTransmitterActionBase) +MIDEA_SCHEMA = cv.Schema( + { + cv.Required(CONF_CODE): cv.All( + [cv.Any(cv.hex_uint8_t, cv.uint8_t)], + cv.Length(min=5, max=5), + ), + cv.GenerateID(CONF_CODE_STORAGE_ID): cv.declare_id(cg.uint8), + } +) + + +@register_binary_sensor("midea", MideaBinarySensor, MIDEA_SCHEMA) +def midea_binary_sensor(var, config): + arr_ = cg.progmem_array(config[CONF_CODE_STORAGE_ID], config[CONF_CODE]) + cg.add(var.set_code(arr_)) + + +@register_trigger("midea", MideaTrigger, MideaData) +def midea_trigger(var, config): + pass + + +@register_dumper("midea", MideaDumper) +def midea_dumper(var, config): + pass + + +@register_action( + "midea", + MideaAction, + MIDEA_SCHEMA, +) +async def midea_action(var, config, args): + arr_ = cg.progmem_array(config[CONF_CODE_STORAGE_ID], config[CONF_CODE]) + cg.add(var.set_code(arr_)) diff --git a/esphome/components/remote_base/midea_protocol.cpp b/esphome/components/remote_base/midea_protocol.cpp new file mode 100644 index 0000000000..baf64f246f --- /dev/null +++ b/esphome/components/remote_base/midea_protocol.cpp @@ -0,0 +1,99 @@ +#include "midea_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.midea"; + +uint8_t MideaData::calc_cs_() const { + uint8_t cs = 0; + for (const uint8_t *it = this->data(); it != this->data() + OFFSET_CS; ++it) + cs -= reverse_bits_8(*it); + return reverse_bits_8(cs); +} + +bool MideaData::check_compliment(const MideaData &rhs) const { + const uint8_t *it0 = rhs.data(); + for (const uint8_t *it1 = this->data(); it1 != this->data() + this->size(); ++it0, ++it1) { + if (*it0 != ~(*it1)) + return false; + } + return true; +} + +void MideaProtocol::data(RemoteTransmitData *dst, const MideaData &src, bool compliment) { + for (const uint8_t *it = src.data(); it != src.data() + src.size(); ++it) { + const uint8_t data = compliment ? ~(*it) : *it; + for (uint8_t mask = 128; mask; mask >>= 1) { + if (data & mask) + one(dst); + else + zero(dst); + } + } +} + +void MideaProtocol::encode(RemoteTransmitData *dst, const MideaData &data) { + dst->set_carrier_frequency(38000); + dst->reserve(2 + 48 * 2 + 2 + 2 + 48 * 2 + 2); + MideaProtocol::header(dst); + MideaProtocol::data(dst, data); + MideaProtocol::footer(dst); + MideaProtocol::header(dst); + MideaProtocol::data(dst, data, true); + MideaProtocol::footer(dst); +} + +bool MideaProtocol::expect_one(RemoteReceiveData &src) { + if (!src.peek_item(BIT_HIGH_US, BIT_ONE_LOW_US)) + return false; + src.advance(2); + return true; +} + +bool MideaProtocol::expect_zero(RemoteReceiveData &src) { + if (!src.peek_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) + return false; + src.advance(2); + return true; +} + +bool MideaProtocol::expect_header(RemoteReceiveData &src) { + if (!src.peek_item(HEADER_HIGH_US, HEADER_LOW_US)) + return false; + src.advance(2); + return true; +} + +bool MideaProtocol::expect_footer(RemoteReceiveData &src) { + if (!src.peek_item(BIT_HIGH_US, MIN_GAP_US)) + return false; + src.advance(2); + return true; +} + +bool MideaProtocol::expect_data(RemoteReceiveData &src, MideaData &out) { + for (uint8_t *dst = out.data(); dst != out.data() + out.size(); ++dst) { + for (uint8_t mask = 128; mask; mask >>= 1) { + if (MideaProtocol::expect_one(src)) + *dst |= mask; + else if (!MideaProtocol::expect_zero(src)) + return false; + } + } + return true; +} + +optional MideaProtocol::decode(RemoteReceiveData src) { + MideaData out, inv; + if (MideaProtocol::expect_header(src) && MideaProtocol::expect_data(src, out) && MideaProtocol::expect_footer(src) && + out.is_valid() && MideaProtocol::expect_data(src, inv) && out.check_compliment(inv)) + return out; + return {}; +} + +void MideaProtocol::dump(const MideaData &data) { ESP_LOGD(TAG, "Received Midea: %s", data.to_string().c_str()); } + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h new file mode 100644 index 0000000000..9b0d156617 --- /dev/null +++ b/esphome/components/remote_base/midea_protocol.h @@ -0,0 +1,105 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +class MideaData { + public: + // Make zero-filled + MideaData() { memset(this->data_, 0, sizeof(this->data_)); } + // Make from initializer_list + MideaData(std::initializer_list data) { std::copy(data.begin(), data.end(), this->data()); } + // Make from vector + MideaData(const std::vector &data) { + memcpy(this->data_, data.data(), std::min(data.size(), sizeof(this->data_))); + } + // Make 40-bit copy from PROGMEM array + MideaData(const uint8_t *data) { memcpy_P(this->data_, data, OFFSET_CS); } + // Default copy constructor + MideaData(const MideaData &) = default; + + uint8_t *data() { return this->data_; } + const uint8_t *data() const { return this->data_; } + uint8_t size() const { return sizeof(this->data_); } + bool is_valid() const { return this->data_[OFFSET_CS] == this->calc_cs_(); } + void finalize() { this->data_[OFFSET_CS] = this->calc_cs_(); } + bool check_compliment(const MideaData &rhs) const; + std::string to_string() const { return hexencode(*this); } + // compare only 40-bits + bool operator==(const MideaData &rhs) const { return !memcmp(this->data_, rhs.data_, OFFSET_CS); } + enum MideaDataType : uint8_t { + MIDEA_TYPE_COMMAND = 0xA1, + MIDEA_TYPE_SPECIAL = 0xA2, + MIDEA_TYPE_FOLLOW_ME = 0xA4, + }; + MideaDataType type() const { return static_cast(this->data_[0]); } + template T to() const { return T(*this); } + + protected: + void set_value_(uint8_t offset, uint8_t val_mask, uint8_t shift, uint8_t val) { + data_[offset] &= ~(val_mask << shift); + data_[offset] |= (val << shift); + } + static const uint8_t OFFSET_CS = 5; + // 48-bits data + uint8_t data_[6]; + // Calculate checksum + uint8_t calc_cs_() const; +}; + +class MideaProtocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const MideaData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const MideaData &data) override; + + protected: + static const int32_t TICK_US = 560; + static const int32_t HEADER_HIGH_US = 8 * TICK_US; + static const int32_t HEADER_LOW_US = 8 * TICK_US; + static const int32_t BIT_HIGH_US = 1 * TICK_US; + static const int32_t BIT_ONE_LOW_US = 3 * TICK_US; + static const int32_t BIT_ZERO_LOW_US = 1 * TICK_US; + static const int32_t MIN_GAP_US = 10 * TICK_US; + static void one(RemoteTransmitData *dst) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); } + static void zero(RemoteTransmitData *dst) { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); } + static void header(RemoteTransmitData *dst) { dst->item(HEADER_HIGH_US, HEADER_LOW_US); } + static void footer(RemoteTransmitData *dst) { dst->item(BIT_HIGH_US, MIN_GAP_US); } + static void data(RemoteTransmitData *dst, const MideaData &src, bool compliment = false); + static bool expect_one(RemoteReceiveData &src); + static bool expect_zero(RemoteReceiveData &src); + static bool expect_header(RemoteReceiveData &src); + static bool expect_footer(RemoteReceiveData &src); + static bool expect_data(RemoteReceiveData &src, MideaData &out); +}; + +class MideaBinarySensor : public RemoteReceiverBinarySensorBase { + public: + bool matches(RemoteReceiveData src) override { + auto data = MideaProtocol().decode(src); + return data.has_value() && data.value() == this->data_; + } + void set_code(const uint8_t *code) { this->data_ = code; } + + protected: + MideaData data_; +}; + +using MideaTrigger = RemoteReceiverTrigger; +using MideaDumper = RemoteReceiverDumper; + +template class MideaAction : public RemoteTransmitterActionBase { + TEMPLATABLE_VALUE(const uint8_t *, code) + void encode(RemoteTransmitData *dst, Ts... x) override { + MideaData data = this->code_.value(x...); + data.finalize(); + MideaProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index aff03b244b..07d0079172 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -69,6 +69,7 @@ CONF_ATTENUATION = "attenuation" CONF_ATTRIBUTE = "attribute" CONF_AUTH = "auth" CONF_AUTO_MODE = "auto_mode" +CONF_AUTOCONF = "autoconf" CONF_AUTOMATION_ID = "automation_id" CONF_AVAILABILITY = "availability" CONF_AWAY = "away" @@ -78,6 +79,7 @@ CONF_BASELINE = "baseline" CONF_BATTERY_LEVEL = "battery_level" CONF_BATTERY_VOLTAGE = "battery_voltage" CONF_BAUD_RATE = "baud_rate" +CONF_BEEPER = "beeper" CONF_BELOW = "below" CONF_BINARY = "binary" CONF_BINARY_SENSOR = "binary_sensor" @@ -615,6 +617,10 @@ CONF_SUPPLEMENTAL_COOLING_ACTION = "supplemental_cooling_action" CONF_SUPPLEMENTAL_COOLING_DELTA = "supplemental_cooling_delta" CONF_SUPPLEMENTAL_HEATING_ACTION = "supplemental_heating_action" CONF_SUPPLEMENTAL_HEATING_DELTA = "supplemental_heating_delta" +CONF_SUPPORTED_FAN_MODES = "supported_fan_modes" +CONF_SUPPORTED_MODES = "supported_modes" +CONF_SUPPORTED_PRESETS = "supported_presets" +CONF_SUPPORTED_SWING_MODES = "supported_swing_modes" CONF_SUPPORTS_COOL = "supports_cool" CONF_SUPPORTS_HEAT = "supports_heat" CONF_SWING_BOTH_ACTION = "swing_both_action" diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index e4535e77d9..cd3081998b 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -20,6 +20,7 @@ const float PROCESSOR = 400.0; const float BLUETOOTH = 350.0f; const float AFTER_BLUETOOTH = 300.0f; const float WIFI = 250.0f; +const float BEFORE_CONNECTION = 220.0f; const float AFTER_WIFI = 200.0f; const float AFTER_CONNECTION = 100.0f; const float LATE = -100.0f; diff --git a/esphome/core/component.h b/esphome/core/component.h index a84f612dd9..ea87ebcdfe 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -29,6 +29,8 @@ extern const float PROCESSOR; extern const float BLUETOOTH; extern const float AFTER_BLUETOOTH; extern const float WIFI; +/// For components that should be initialized after WiFi and before API is connected. +extern const float BEFORE_CONNECTION; /// For components that should be initialized after WiFi is connected. extern const float AFTER_WIFI; /// For components that should be initialized after a data connection (API/MQTT) is connected. diff --git a/platformio.ini b/platformio.ini index c280c54a21..88b1000d1d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -36,6 +36,7 @@ lib_deps = 6306@1.0.3 ; HM3301 glmnet/Dsmr@0.3 ; used by dsmr rweather/Crypto@0.2.0 ; used by dsmr + dudanov/MideaUART@1.1.0 ; used by midea build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE diff --git a/tests/test1.yaml b/tests/test1.yaml index da3289843d..cd4179f394 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1608,29 +1608,84 @@ climate: name: Toshiba Climate - platform: hitachi_ac344 name: Hitachi Climate - - platform: midea_ac + - platform: midea + id: midea_unit + uart_id: uart0 + name: Midea Climate + transmitter_id: + period: 1s + num_attempts: 5 + timeout: 2s + beeper: false + autoconf: true visual: - min_temperature: 18 °C - max_temperature: 25 °C - temperature_step: 0.1 °C - name: 'Electrolux EACS' - beeper: true + min_temperature: 17 °C + max_temperature: 30 °C + temperature_step: 0.5 °C + supported_modes: + - FAN_ONLY + - HEAT_COOL + - COOL + - HEAT + - DRY + custom_fan_modes: + - SILENT + - TURBO + supported_presets: + - ECO + - BOOST + - SLEEP + custom_presets: + - FREEZE_PROTECTION + supported_swing_modes: + - VERTICAL + - HORIZONTAL + - BOTH outdoor_temperature: - name: 'Temp' + name: "Temp" power_usage: - name: 'Power' + name: "Power" humidity_setpoint: - name: 'Hum' + name: "Humidity" - platform: anova name: Anova cooker ble_client_id: ble_blah unit_of_measurement: c -midea_dongle: - uart_id: uart0 - strength_icon: true +script: + - id: climate_custom + then: + - climate.control: + id: midea_unit + custom_preset: FREEZE_PROTECTION + custom_fan_mode: SILENT + - id: climate_preset + then: + - climate.control: + id: midea_unit + preset: SLEEP switch: + - platform: template + name: MIDEA_AC_TOGGLE_LIGHT + turn_on_action: + midea_ac.display_toggle: + - platform: template + name: MIDEA_AC_SWING_STEP + turn_on_action: + midea_ac.swing_step: + - platform: template + name: MIDEA_AC_BEEPER_CONTROL + optimistic: true + turn_on_action: + midea_ac.beeper_on: + turn_off_action: + midea_ac.beeper_off: + - platform: template + name: MIDEA_RAW + turn_on_action: + remote_transmitter.transmit_midea: + code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF] - platform: gpio name: 'MCP23S08 Pin #0' pin: diff --git a/tests/test3.yaml b/tests/test3.yaml index e35c1e611c..c012871125 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -748,17 +748,6 @@ 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 @@ -949,32 +938,6 @@ 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 From 9e5cd0da513ab0dd650afe13bbfdff3880c73d24 Mon Sep 17 00:00:00 2001 From: Peter van Dijk Date: Wed, 8 Sep 2021 23:19:43 +0200 Subject: [PATCH 1287/1841] ccs811: publish firmware version; log bootloader and HW version; fix a bug (#2006) --- CODEOWNERS | 1 + esphome/components/ccs811/ccs811.cpp | 47 +++++++++++++++++++++++----- esphome/components/ccs811/ccs811.h | 5 ++- esphome/components/ccs811/sensor.py | 18 ++++++++++- 4 files changed, 62 insertions(+), 9 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index dedd8acc1e..3ae9f71ead 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -31,6 +31,7 @@ esphome/components/ble_client/* @buxtronix esphome/components/bme680_bsec/* @trvrnrth esphome/components/canbus/* @danielschramm @mvturnho esphome/components/captive_portal/* @OttoWinter +esphome/components/ccs811/* @habbie esphome/components/climate/* @esphome/core esphome/components/climate_ir/* @glmnet esphome/components/color_temperature/* @jesserockz diff --git a/esphome/components/ccs811/ccs811.cpp b/esphome/components/ccs811/ccs811.cpp index dec070a9b2..08df6f7774 100644 --- a/esphome/components/ccs811/ccs811.cpp +++ b/esphome/components/ccs811/ccs811.cpp @@ -16,7 +16,7 @@ static const char *const TAG = "ccs811"; return; \ } -#define CHECKED_IO(f) CHECK_TRUE(f, COMMUNICAITON_FAILED) +#define CHECKED_IO(f) CHECK_TRUE(f, COMMUNICATION_FAILED) void CCS811Component::setup() { // page 9 programming guide - hwid is always 0x81 @@ -38,12 +38,14 @@ void CCS811Component::setup() { // set MEAS_MODE (page 5) uint8_t meas_mode = 0; uint32_t interval = this->get_update_interval(); - if (interval <= 1000) - meas_mode = 1 << 4; - else if (interval <= 10000) - meas_mode = 2 << 4; + if (interval >= 60 * 1000) + meas_mode = 3 << 4; // sensor takes a reading every 60 seconds + else if (interval >= 10 * 1000) + meas_mode = 2 << 4; // sensor takes a reading every 10 seconds + else if (interval >= 1 * 1000) + meas_mode = 1 << 4; // sensor takes a reading every second else - meas_mode = 3 << 4; + meas_mode = 4 << 4; // sensor takes a reading every 250ms CHECKED_IO(this->write_byte(0x01, meas_mode)) @@ -51,6 +53,36 @@ void CCS811Component::setup() { // baseline available, write to sensor this->write_bytes(0x11, decode_uint16(*this->baseline_)); } + + auto hardware_version_data = this->read_bytes<1>(0x21); + auto bootloader_version_data = this->read_bytes<2>(0x23); + auto application_version_data = this->read_bytes<2>(0x24); + + uint8_t hardware_version = 0; + uint16_t bootloader_version = 0; + uint16_t application_version = 0; + + if (hardware_version_data.has_value()) { + hardware_version = (*hardware_version_data)[0]; + } + + if (bootloader_version_data.has_value()) { + bootloader_version = encode_uint16((*bootloader_version_data)[0], (*bootloader_version_data)[1]); + } + + if (application_version_data.has_value()) { + application_version = encode_uint16((*application_version_data)[0], (*application_version_data)[1]); + } + + ESP_LOGD(TAG, "hardware_version=0x%x bootloader_version=0x%x application_version=0x%x\n", hardware_version, + bootloader_version, application_version); + if (this->version_ != nullptr) { + char version[20]; // "15.15.15 (0xffff)" is 17 chars, plus NUL, plus wiggle room + sprintf(version, "%d.%d.%d (0x%02x)", (application_version >> 12 & 15), (application_version >> 8 & 15), + (application_version >> 4 & 15), application_version); + ESP_LOGD(TAG, "publishing version state: %s", version); + this->version_->publish_state(version); + } } void CCS811Component::update() { if (!this->status_has_data_()) @@ -117,6 +149,7 @@ void CCS811Component::dump_config() { LOG_UPDATE_INTERVAL(this) LOG_SENSOR(" ", "CO2 Sensor", this->co2_) LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_) + LOG_TEXT_SENSOR(" ", "Firmware Version Sensor", this->version_) if (this->baseline_) { ESP_LOGCONFIG(TAG, " Baseline: %04X", *this->baseline_); } else { @@ -124,7 +157,7 @@ void CCS811Component::dump_config() { } if (this->is_failed()) { switch (this->error_code_) { - case COMMUNICAITON_FAILED: + case COMMUNICATION_FAILED: ESP_LOGW(TAG, "Communication failed! Is the sensor connected?"); break; case INVALID_ID: diff --git a/esphome/components/ccs811/ccs811.h b/esphome/components/ccs811/ccs811.h index cea919c9a5..8a0d60d002 100644 --- a/esphome/components/ccs811/ccs811.h +++ b/esphome/components/ccs811/ccs811.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/preferences.h" #include "esphome/components/sensor/sensor.h" +#include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/i2c/i2c.h" namespace esphome { @@ -12,6 +13,7 @@ class CCS811Component : public PollingComponent, public i2c::I2CDevice { public: void set_co2(sensor::Sensor *co2) { co2_ = co2; } void set_tvoc(sensor::Sensor *tvoc) { tvoc_ = tvoc; } + void set_version(text_sensor::TextSensor *version) { version_ = version; } void set_baseline(uint16_t baseline) { baseline_ = baseline; } void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } @@ -34,7 +36,7 @@ class CCS811Component : public PollingComponent, public i2c::I2CDevice { enum ErrorCode { UNKNOWN, - COMMUNICAITON_FAILED, + COMMUNICATION_FAILED, INVALID_ID, SENSOR_REPORTED_ERROR, APP_INVALID, @@ -43,6 +45,7 @@ class CCS811Component : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *co2_{nullptr}; sensor::Sensor *tvoc_{nullptr}; + text_sensor::TextSensor *version_{nullptr}; optional baseline_{}; /// Input sensor for humidity reading. sensor::Sensor *humidity_{nullptr}; diff --git a/esphome/components/ccs811/sensor.py b/esphome/components/ccs811/sensor.py index c177ed6b5c..bb8200273d 100644 --- a/esphome/components/ccs811/sensor.py +++ b/esphome/components/ccs811/sensor.py @@ -1,9 +1,11 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import i2c, sensor +from esphome.components import i2c, sensor, text_sensor from esphome.const import ( + CONF_ICON, CONF_ID, ICON_RADIATOR, + ICON_RESTART, DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, STATE_CLASS_MEASUREMENT, @@ -14,9 +16,12 @@ from esphome.const import ( CONF_TEMPERATURE, CONF_TVOC, CONF_HUMIDITY, + CONF_VERSION, ICON_MOLECULE_CO2, ) +AUTO_LOAD = ["text_sensor"] +CODEOWNERS = ["@habbie"] DEPENDENCIES = ["i2c"] ccs811_ns = cg.esphome_ns.namespace("ccs811") @@ -42,6 +47,12 @@ CONFIG_SCHEMA = ( device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_VERSION): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + cv.Optional(CONF_ICON, default=ICON_RESTART): cv.icon, + } + ), cv.Optional(CONF_BASELINE): cv.hex_uint16_t, cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor), cv.Optional(CONF_HUMIDITY): cv.use_id(sensor.Sensor), @@ -62,6 +73,11 @@ async def to_code(config): sens = await sensor.new_sensor(config[CONF_TVOC]) cg.add(var.set_tvoc(sens)) + if CONF_VERSION in config: + sens = cg.new_Pvariable(config[CONF_VERSION][CONF_ID]) + await text_sensor.register_text_sensor(sens, config[CONF_VERSION]) + cg.add(var.set_version(sens)) + if CONF_BASELINE in config: cg.add(var.set_baseline(config[CONF_BASELINE])) From e5051eefbcf1fecf2f02904c40b862bf899ec8f4 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 8 Sep 2021 23:22:47 +0200 Subject: [PATCH 1288/1841] API encryption (#2254) --- esphome/components/api/__init__.py | 33 ++ esphome/components/api/api_connection.cpp | 6 + esphome/components/api/api_frame_helper.cpp | 612 ++++++++++++++++++++ esphome/components/api/api_frame_helper.h | 76 +++ esphome/components/api/api_noise_context.h | 23 + esphome/components/api/api_server.h | 10 + esphome/core/defines.h | 3 + esphome/core/helpers.cpp | 9 + esphome/core/helpers.h | 2 + platformio.ini | 1 + tests/test3.yaml | 2 + 11 files changed, 777 insertions(+) create mode 100644 esphome/components/api/api_noise_context.h diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index fc140dc7d2..3705f0d7ca 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -1,3 +1,5 @@ +import base64 + import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation @@ -6,6 +8,7 @@ from esphome.const import ( CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, + CONF_KEY, CONF_PASSWORD, CONF_PORT, CONF_REBOOT_TIMEOUT, @@ -41,6 +44,22 @@ SERVICE_ARG_NATIVE_TYPES = { "float[]": cg.std_vector.template(float), "string[]": cg.std_vector.template(cg.std_string), } +CONF_ENCRYPTION = "encryption" + + +def validate_encryption_key(value): + value = cv.string_strict(value) + try: + decoded = base64.b64decode(value, validate=True) + except ValueError as err: + raise cv.Invalid("Invalid key format, please check it's using base64") from err + + if len(decoded) != 32: + raise cv.Invalid("Encryption key must be base64 and 32 bytes long") + + # Return original data for roundtrip conversion + return value + CONFIG_SCHEMA = cv.Schema( { @@ -63,6 +82,11 @@ CONFIG_SCHEMA = cv.Schema( ), } ), + cv.Optional(CONF_ENCRYPTION): cv.Schema( + { + cv.Required(CONF_KEY): validate_encryption_key, + } + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -92,6 +116,15 @@ async def to_code(config): cg.add(var.register_user_service(trigger)) await automation.build_automation(trigger, func_args, conf) + if CONF_ENCRYPTION in config: + conf = config[CONF_ENCRYPTION] + decoded = base64.b64decode(conf[CONF_KEY]) + cg.add(var.set_noise_psk(list(decoded))) + cg.add_define("USE_API_NOISE") + cg.add_library("esphome/noise-c", "0.1.1") + else: + cg.add_define("USE_API_PLAINTEXT") + cg.add_define("USE_API") cg.add_global(api_ns.using) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index bce0b0bab8..650f4f6f6e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -23,7 +23,13 @@ APIConnection::APIConnection(std::unique_ptr sock, APIServer *pa : parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) { this->proto_write_buffer_.reserve(64); +#if defined(USE_API_PLAINTEXT) helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock))}; +#elif defined(USE_API_NOISE) + helper_ = std::unique_ptr{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())}; +#else +#error "No frame helper defined" +#endif } void APIConnection::start() { this->last_traffic_ = millis(); diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index f903ab8656..26fbf1269f 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -19,6 +19,617 @@ bool is_would_block(ssize_t ret) { #define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__) +#ifdef USE_API_NOISE +static const char *const PROLOGUE_INIT = "NoiseAPIInit"; + +/// Convert a noise error code to a readable error +std::string noise_err_to_str(int err) { + if (err == NOISE_ERROR_NO_MEMORY) + return "NO_MEMORY"; + if (err == NOISE_ERROR_UNKNOWN_ID) + return "UNKNOWN_ID"; + if (err == NOISE_ERROR_UNKNOWN_NAME) + return "UNKNOWN_NAME"; + if (err == NOISE_ERROR_MAC_FAILURE) + return "MAC_FAILURE"; + if (err == NOISE_ERROR_NOT_APPLICABLE) + return "NOT_APPLICABLE"; + if (err == NOISE_ERROR_SYSTEM) + return "SYSTEM"; + if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED) + return "REMOTE_KEY_REQUIRED"; + if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED) + return "LOCAL_KEY_REQUIRED"; + if (err == NOISE_ERROR_PSK_REQUIRED) + return "PSK_REQUIRED"; + if (err == NOISE_ERROR_INVALID_LENGTH) + return "INVALID_LENGTH"; + if (err == NOISE_ERROR_INVALID_PARAM) + return "INVALID_PARAM"; + if (err == NOISE_ERROR_INVALID_STATE) + return "INVALID_STATE"; + if (err == NOISE_ERROR_INVALID_NONCE) + return "INVALID_NONCE"; + if (err == NOISE_ERROR_INVALID_PRIVATE_KEY) + return "INVALID_PRIVATE_KEY"; + if (err == NOISE_ERROR_INVALID_PUBLIC_KEY) + return "INVALID_PUBLIC_KEY"; + if (err == NOISE_ERROR_INVALID_FORMAT) + return "INVALID_FORMAT"; + if (err == NOISE_ERROR_INVALID_SIGNATURE) + return "INVALID_SIGNATURE"; + return to_string(err); +} + +/// Initialize the frame helper, returns OK if successful. +APIError APINoiseFrameHelper::init() { + if (state_ != State::INITIALIZE || socket_ == nullptr) { + HELPER_LOG("Bad state for init %d", (int) state_); + return APIError::BAD_STATE; + } + int err = socket_->setblocking(false); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("Setting nonblocking failed with errno %d", errno); + return APIError::TCP_NONBLOCKING_FAILED; + } + int enable = 1; + err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("Setting nodelay failed with errno %d", errno); + return APIError::TCP_NODELAY_FAILED; + } + + // init prologue + prologue_.insert(prologue_.end(), PROLOGUE_INIT, PROLOGUE_INIT + strlen(PROLOGUE_INIT)); + + state_ = State::CLIENT_HELLO; + return APIError::OK; +} +/// Run through handshake messages (if in that phase) +APIError APINoiseFrameHelper::loop() { + APIError err = state_action_(); + if (err == APIError::WOULD_BLOCK) + return APIError::OK; + if (err != APIError::OK) + return err; + if (!tx_buf_.empty()) { + err = try_send_tx_buf_(); + if (err != APIError::OK) { + return err; + } + } + return APIError::OK; +} + +/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter + * + * @param frame: The struct to hold the frame information in. + * msg_start: points to the start of the payload - this pointer is only valid until the next + * try_receive_raw_ call + * + * @return 0 if a full packet is in rx_buf_ + * @return -1 if error, check errno. + * + * errno EWOULDBLOCK: Packet could not be read without blocking. Try again later. + * errno ENOMEM: Not enough memory for reading packet. + * errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame. + * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase. + */ +APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { + int err; + APIError aerr; + + if (frame == nullptr) { + HELPER_LOG("Bad argument for try_read_frame_"); + return APIError::BAD_ARG; + } + + // read header + if (rx_header_buf_len_ < 3) { + // no header information yet + size_t to_read = 3 - rx_header_buf_len_; + ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read); + if (is_would_block(received)) { + return APIError::WOULD_BLOCK; + } else if (received == -1) { + state_ = State::FAILED; + HELPER_LOG("Socket read failed with errno %d", errno); + return APIError::SOCKET_READ_FAILED; + } + rx_header_buf_len_ += received; + if (received != to_read) { + // not a full read + return APIError::WOULD_BLOCK; + } + + // header reading done + } + + // read body + uint8_t indicator = rx_header_buf_[0]; + if (indicator != 0x01) { + state_ = State::FAILED; + HELPER_LOG("Bad indicator byte %u", indicator); + return APIError::BAD_INDICATOR; + } + + uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2]; + + if (state_ != State::DATA && msg_size > 128) { + // for handshake message only permit up to 128 bytes + state_ = State::FAILED; + HELPER_LOG("Bad packet len for handshake: %d", msg_size); + return APIError::BAD_HANDSHAKE_PACKET_LEN; + } + + // reserve space for body + if (rx_buf_.size() != msg_size) { + rx_buf_.resize(msg_size); + } + + if (rx_buf_len_ < msg_size) { + // more data to read + size_t to_read = msg_size - rx_buf_len_; + ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read); + if (is_would_block(received)) { + return APIError::WOULD_BLOCK; + } else if (received == -1) { + state_ = State::FAILED; + HELPER_LOG("Socket read failed with errno %d", errno); + return APIError::SOCKET_READ_FAILED; + } + rx_buf_len_ += received; + if (received != to_read) { + // not all read + return APIError::WOULD_BLOCK; + } + } + + // uncomment for even more debugging + // ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); + frame->msg = std::move(rx_buf_); + // consume msg + rx_buf_ = {}; + rx_buf_len_ = 0; + rx_header_buf_len_ = 0; + return APIError::OK; +} + +/** To be called from read/write methods. + * + * This method runs through the internal handshake methods, if in that state. + * + * If the handshake is still active when this method returns and a read/write can't take place at + * the moment, returns WOULD_BLOCK. + * If an error occured, returns that error. Only returns OK if the transport is ready for data + * traffic. + */ +APIError APINoiseFrameHelper::state_action_() { + int err; + APIError aerr; + if (state_ == State::INITIALIZE) { + HELPER_LOG("Bad state for method: %d", (int) state_); + return APIError::BAD_STATE; + } + if (state_ == State::CLIENT_HELLO) { + // waiting for client hello + ParsedFrame frame; + aerr = try_read_frame_(&frame); + if (aerr != APIError::OK) + return aerr; + // ignore contents, may be used in future for flags + prologue_.push_back((uint8_t)(frame.msg.size() >> 8)); + prologue_.push_back((uint8_t) frame.msg.size()); + prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end()); + + state_ = State::SERVER_HELLO; + } + if (state_ == State::SERVER_HELLO) { + // send server hello + uint8_t msg[1]; + msg[0] = 0x01; // chosen proto + aerr = write_frame_(msg, 1); + if (aerr != APIError::OK) + return aerr; + + // start handshake + aerr = init_handshake_(); + if (aerr != APIError::OK) + return aerr; + + state_ = State::HANDSHAKE; + } + if (state_ == State::HANDSHAKE) { + int action = noise_handshakestate_get_action(handshake_); + if (action == NOISE_ACTION_READ_MESSAGE) { + // waiting for handshake msg + ParsedFrame frame; + aerr = try_read_frame_(&frame); + if (aerr == APIError::BAD_INDICATOR) { + send_explicit_handshake_reject_("Bad indicator byte"); + return aerr; + } + if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) { + send_explicit_handshake_reject_("Bad handshake packet len"); + return aerr; + } + if (aerr != APIError::OK) + return aerr; + + if (frame.msg.empty()) { + send_explicit_handshake_reject_("Empty handshake message"); + return APIError::BAD_HANDSHAKE_PACKET_LEN; + } else if (frame.msg[0] != 0x00) { + HELPER_LOG("Bad handshake error byte: %u", frame.msg[0]); + send_explicit_handshake_reject_("Bad handshake error byte"); + return APIError::BAD_HANDSHAKE_PACKET_LEN; + } + + NoiseBuffer mbuf; + noise_buffer_init(mbuf); + noise_buffer_set_input(mbuf, frame.msg.data() + 1, frame.msg.size() - 1); + err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr); + if (err != 0) { + // TODO: explicit rejection + state_ = State::FAILED; + HELPER_LOG("noise_handshakestate_read_message failed: %s", noise_err_to_str(err).c_str()); + if (err == NOISE_ERROR_MAC_FAILURE) { + send_explicit_handshake_reject_("Handshake MAC failure"); + } else { + send_explicit_handshake_reject_("Handshake error"); + } + return APIError::HANDSHAKESTATE_READ_FAILED; + } + + aerr = check_handshake_finished_(); + if (aerr != APIError::OK) + return aerr; + } else if (action == NOISE_ACTION_WRITE_MESSAGE) { + uint8_t buffer[65]; + NoiseBuffer mbuf; + noise_buffer_init(mbuf); + noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1); + + err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("noise_handshakestate_write_message failed: %s", noise_err_to_str(err).c_str()); + return APIError::HANDSHAKESTATE_WRITE_FAILED; + } + buffer[0] = 0x00; // success + + aerr = write_frame_(buffer, mbuf.size + 1); + if (aerr != APIError::OK) + return aerr; + aerr = check_handshake_finished_(); + if (aerr != APIError::OK) + return aerr; + } else { + // bad state for action + state_ = State::FAILED; + HELPER_LOG("Bad action for handshake: %d", action); + return APIError::HANDSHAKESTATE_BAD_STATE; + } + } + if (state_ == State::CLOSED || state_ == State::FAILED) { + return APIError::BAD_STATE; + } + return APIError::OK; +} +void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) { + std::vector data; + data.reserve(reason.size() + 1); + data[0] = 0x01; // failure + for (size_t i = 0; i < reason.size(); i++) { + data[i + 1] = (uint8_t) reason[i]; + } + write_frame_(data.data(), data.size()); +} + +APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { + int err; + APIError aerr; + aerr = state_action_(); + if (aerr != APIError::OK) { + return aerr; + } + + if (state_ != State::DATA) { + return APIError::WOULD_BLOCK; + } + + ParsedFrame frame; + aerr = try_read_frame_(&frame); + if (aerr != APIError::OK) + return aerr; + + NoiseBuffer mbuf; + noise_buffer_init(mbuf); + noise_buffer_set_inout(mbuf, frame.msg.data(), frame.msg.size(), frame.msg.size()); + err = noise_cipherstate_decrypt(recv_cipher_, &mbuf); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("noise_cipherstate_decrypt failed: %s", noise_err_to_str(err).c_str()); + return APIError::CIPHERSTATE_DECRYPT_FAILED; + } + + size_t msg_size = mbuf.size; + uint8_t *msg_data = frame.msg.data(); + if (msg_size < 4) { + state_ = State::FAILED; + HELPER_LOG("Bad data packet: size %d too short", msg_size); + return APIError::BAD_DATA_PACKET; + } + + // uint16_t type; + // uint16_t data_len; + // uint8_t *data; + // uint8_t *padding; zero or more bytes to fill up the rest of the packet + uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1]; + uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3]; + if (data_len > msg_size - 4) { + state_ = State::FAILED; + HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size); + return APIError::BAD_DATA_PACKET; + } + + buffer->container = std::move(frame.msg); + buffer->data_offset = 4; + buffer->data_len = data_len; + buffer->type = type; + return APIError::OK; +} +bool APINoiseFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } +APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) { + int err; + APIError aerr; + aerr = state_action_(); + if (aerr != APIError::OK) { + return aerr; + } + + if (state_ != State::DATA) { + return APIError::WOULD_BLOCK; + } + + size_t padding = 0; + size_t msg_len = 4 + payload_len + padding; + size_t frame_len = 3 + msg_len + noise_cipherstate_get_mac_length(send_cipher_); + auto tmpbuf = std::unique_ptr{new (std::nothrow) uint8_t[frame_len]}; + if (tmpbuf == nullptr) { + HELPER_LOG("Could not allocate for writing packet"); + return APIError::OUT_OF_MEMORY; + } + + tmpbuf[0] = 0x01; // indicator + // tmpbuf[1], tmpbuf[2] to be set later + const uint8_t msg_offset = 3; + const uint8_t payload_offset = msg_offset + 4; + tmpbuf[msg_offset + 0] = (uint8_t)(type >> 8); // type + tmpbuf[msg_offset + 1] = (uint8_t) type; + tmpbuf[msg_offset + 2] = (uint8_t)(payload_len >> 8); // data_len + tmpbuf[msg_offset + 3] = (uint8_t) payload_len; + // copy data + std::copy(payload, payload + payload_len, &tmpbuf[payload_offset]); + // fill padding with zeros + std::fill(&tmpbuf[payload_offset + payload_len], &tmpbuf[frame_len], 0); + + NoiseBuffer mbuf; + noise_buffer_init(mbuf); + noise_buffer_set_inout(mbuf, &tmpbuf[msg_offset], msg_len, frame_len - msg_offset); + err = noise_cipherstate_encrypt(send_cipher_, &mbuf); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("noise_cipherstate_encrypt failed: %s", noise_err_to_str(err).c_str()); + return APIError::CIPHERSTATE_ENCRYPT_FAILED; + } + + size_t total_len = 3 + mbuf.size; + tmpbuf[1] = (uint8_t)(mbuf.size >> 8); + tmpbuf[2] = (uint8_t) mbuf.size; + // write raw to not have two packets sent if NAGLE disabled + aerr = write_raw_(&tmpbuf[0], total_len); + if (aerr != APIError::OK) { + return aerr; + } + return APIError::OK; +} +APIError APINoiseFrameHelper::try_send_tx_buf_() { + // try send from tx_buf + while (state_ != State::CLOSED && !tx_buf_.empty()) { + ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size()); + if (sent == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) + break; + state_ = State::FAILED; + HELPER_LOG("Socket write failed with errno %d", errno); + return APIError::SOCKET_WRITE_FAILED; + } else if (sent == 0) { + break; + } + // TODO: inefficient if multiple packets in txbuf + // replace with deque of buffers + tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent); + } + + return APIError::OK; +} +/** Write the data to the socket, or buffer it a write would block + * + * @param data The data to write + * @param len The length of data + */ +APIError APINoiseFrameHelper::write_raw_(const uint8_t *data, size_t len) { + if (len == 0) + return APIError::OK; + int err; + APIError aerr; + + // uncomment for even more debugging + // ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); + + if (!tx_buf_.empty()) { + // try to empty tx_buf_ first + aerr = try_send_tx_buf_(); + if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK) + return aerr; + } + + if (!tx_buf_.empty()) { + // tx buf not empty, can't write now because then stream would be inconsistent + tx_buf_.insert(tx_buf_.end(), data, data + len); + return APIError::OK; + } + + ssize_t sent = socket_->write(data, len); + if (is_would_block(sent)) { + // operation would block, add buffer to tx_buf + tx_buf_.insert(tx_buf_.end(), data, data + len); + return APIError::OK; + } else if (sent == -1) { + // an error occured + state_ = State::FAILED; + HELPER_LOG("Socket write failed with errno %d", errno); + return APIError::SOCKET_WRITE_FAILED; + } else if (sent != len) { + // partially sent, add end to tx_buf + tx_buf_.insert(tx_buf_.end(), data + sent, data + len); + return APIError::OK; + } + // fully sent + return APIError::OK; +} +APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) { + APIError aerr; + + uint8_t header[3]; + header[0] = 0x01; // indicator + header[1] = (uint8_t)(len >> 8); + header[2] = (uint8_t) len; + + aerr = write_raw_(header, 3); + if (aerr != APIError::OK) + return aerr; + aerr = write_raw_(data, len); + return aerr; +} + +/** Initiate the data structures for the handshake. + * + * @return 0 on success, -1 on error (check errno) + */ +APIError APINoiseFrameHelper::init_handshake_() { + int err; + memset(&nid_, 0, sizeof(nid_)); + // const char *proto = "Noise_NNpsk0_25519_ChaChaPoly_SHA256"; + // err = noise_protocol_name_to_id(&nid_, proto, strlen(proto)); + nid_.pattern_id = NOISE_PATTERN_NN; + nid_.cipher_id = NOISE_CIPHER_CHACHAPOLY; + nid_.dh_id = NOISE_DH_CURVE25519; + nid_.prefix_id = NOISE_PREFIX_STANDARD; + nid_.hybrid_id = NOISE_DH_NONE; + nid_.hash_id = NOISE_HASH_SHA256; + nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0; + + err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("noise_handshakestate_new_by_id failed: %s", noise_err_to_str(err).c_str()); + return APIError::HANDSHAKESTATE_SETUP_FAILED; + } + + const auto &psk = ctx_->get_psk(); + err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size()); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("noise_handshakestate_set_pre_shared_key failed: %s", noise_err_to_str(err).c_str()); + return APIError::HANDSHAKESTATE_SETUP_FAILED; + } + + err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size()); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("noise_handshakestate_set_prologue failed: %s", noise_err_to_str(err).c_str()); + return APIError::HANDSHAKESTATE_SETUP_FAILED; + } + // set_prologue copies it into handshakestate, so we can get rid of it now + prologue_ = {}; + + err = noise_handshakestate_start(handshake_); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("noise_handshakestate_start failed: %s", noise_err_to_str(err).c_str()); + return APIError::HANDSHAKESTATE_SETUP_FAILED; + } + return APIError::OK; +} + +APIError APINoiseFrameHelper::check_handshake_finished_() { + assert(state_ == State::HANDSHAKE); + + int action = noise_handshakestate_get_action(handshake_); + if (action == NOISE_ACTION_READ_MESSAGE || action == NOISE_ACTION_WRITE_MESSAGE) + return APIError::OK; + if (action != NOISE_ACTION_SPLIT) { + state_ = State::FAILED; + HELPER_LOG("Bad action for handshake: %d", action); + return APIError::HANDSHAKESTATE_BAD_STATE; + } + int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("noise_handshakestate_split failed: %s", noise_err_to_str(err).c_str()); + return APIError::HANDSHAKESTATE_SPLIT_FAILED; + } + + HELPER_LOG("Handshake complete!"); + noise_handshakestate_free(handshake_); + handshake_ = nullptr; + state_ = State::DATA; + return APIError::OK; +} + +APINoiseFrameHelper::~APINoiseFrameHelper() { + if (handshake_ != nullptr) { + noise_handshakestate_free(handshake_); + handshake_ = nullptr; + } + if (send_cipher_ != nullptr) { + noise_cipherstate_free(send_cipher_); + send_cipher_ = nullptr; + } + if (recv_cipher_ != nullptr) { + noise_cipherstate_free(recv_cipher_); + recv_cipher_ = nullptr; + } +} + +APIError APINoiseFrameHelper::close() { + state_ = State::CLOSED; + int err = socket_->close(); + if (err == -1) + return APIError::CLOSE_FAILED; + return APIError::OK; +} +APIError APINoiseFrameHelper::shutdown(int how) { + int err = socket_->shutdown(how); + if (err == -1) + return APIError::SHUTDOWN_FAILED; + if (how == SHUT_RDWR) { + state_ = State::CLOSED; + } + return APIError::OK; +} +extern "C" { +// declare how noise generates random bytes (here with a good HWRNG based on the RF system) +void noise_rand_bytes(void *output, size_t len) { esphome::fill_random(reinterpret_cast(output), len); } +} +#endif // USE_API_NOISE + +#ifdef USE_API_PLAINTEXT + /// Initialize the frame helper, returns OK if successful. APIError APIPlaintextFrameHelper::init() { if (state_ != State::INITIALIZE || socket_ == nullptr) { @@ -289,6 +900,7 @@ APIError APIPlaintextFrameHelper::shutdown(int how) { } return APIError::OK; } +#endif // USE_API_PLAINTEXT } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 14a0760c25..7189bc4b4b 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -5,7 +5,12 @@ #include "esphome/core/defines.h" +#ifdef USE_API_NOISE +#include "noise/protocol.h" +#endif + #include "esphome/components/socket/socket.h" +#include "api_noise_context.h" namespace esphome { namespace api { @@ -27,6 +32,7 @@ struct PacketBuffer { enum class APIError : int { OK = 0, WOULD_BLOCK = 1001, + BAD_HANDSHAKE_PACKET_LEN = 1002, BAD_INDICATOR = 1003, BAD_DATA_PACKET = 1004, TCP_NODELAY_FAILED = 1005, @@ -37,7 +43,14 @@ enum class APIError : int { BAD_ARG = 1010, SOCKET_READ_FAILED = 1011, SOCKET_WRITE_FAILED = 1012, + HANDSHAKESTATE_READ_FAILED = 1013, + HANDSHAKESTATE_WRITE_FAILED = 1014, + HANDSHAKESTATE_BAD_STATE = 1015, + CIPHERSTATE_DECRYPT_FAILED = 1016, + CIPHERSTATE_ENCRYPT_FAILED = 1017, OUT_OF_MEMORY = 1018, + HANDSHAKESTATE_SETUP_FAILED = 1019, + HANDSHAKESTATE_SPLIT_FAILED = 1020, }; class APIFrameHelper { @@ -53,6 +66,68 @@ class APIFrameHelper { // Give this helper a name for logging virtual void set_log_info(std::string info) = 0; }; + +#ifdef USE_API_NOISE +class APINoiseFrameHelper : public APIFrameHelper { + public: + APINoiseFrameHelper(std::unique_ptr socket, std::shared_ptr ctx) + : socket_(std::move(socket)), ctx_(ctx) {} + ~APINoiseFrameHelper(); + APIError init() override; + APIError loop() override; + APIError read_packet(ReadPacketBuffer *buffer) override; + bool can_write_without_blocking() override; + APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override; + std::string getpeername() override { return socket_->getpeername(); } + APIError close() override; + APIError shutdown(int how) override; + // Give this helper a name for logging + void set_log_info(std::string info) override { info_ = std::move(info); } + + protected: + struct ParsedFrame { + std::vector msg; + }; + + APIError state_action_(); + APIError try_read_frame_(ParsedFrame *frame); + APIError try_send_tx_buf_(); + APIError write_frame_(const uint8_t *data, size_t len); + APIError write_raw_(const uint8_t *data, size_t len); + APIError init_handshake_(); + APIError check_handshake_finished_(); + void send_explicit_handshake_reject_(const std::string &reason); + + std::unique_ptr socket_; + + std::string info_; + uint8_t rx_header_buf_[3]; + size_t rx_header_buf_len_ = 0; + std::vector rx_buf_; + size_t rx_buf_len_ = 0; + + std::vector tx_buf_; + std::vector prologue_; + + std::shared_ptr ctx_; + NoiseHandshakeState *handshake_ = nullptr; + NoiseCipherState *send_cipher_ = nullptr; + NoiseCipherState *recv_cipher_ = nullptr; + NoiseProtocolId nid_; + + enum class State { + INITIALIZE = 1, + CLIENT_HELLO = 2, + SERVER_HELLO = 3, + HANDSHAKE = 4, + DATA = 5, + CLOSED = 6, + FAILED = 7, + } state_ = State::INITIALIZE; +}; +#endif // USE_API_NOISE + +#ifdef USE_API_PLAINTEXT class APIPlaintextFrameHelper : public APIFrameHelper { public: APIPlaintextFrameHelper(std::unique_ptr socket) : socket_(std::move(socket)) {} @@ -98,6 +173,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper { FAILED = 4, } state_ = State::INITIALIZE; }; +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_noise_context.h b/esphome/components/api/api_noise_context.h new file mode 100644 index 0000000000..fba6b65a26 --- /dev/null +++ b/esphome/components/api/api_noise_context.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include +#include "esphome/core/defines.h" + +namespace esphome { +namespace api { + +#ifdef USE_API_NOISE +using psk_t = std::array; + +class APINoiseContext { + public: + void set_psk(psk_t psk) { psk_ = std::move(psk); } + const psk_t &get_psk() const { return psk_; } + + protected: + psk_t psk_; +}; +#endif // USE_API_NOISE + +} // namespace api +} // namespace esphome diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 7c42fe7dd5..e3fa6b18c9 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -11,6 +11,7 @@ #include "list_entities.h" #include "subscribe_state.h" #include "user_services.h" +#include "api_noise_context.h" namespace esphome { namespace api { @@ -30,6 +31,11 @@ class APIServer : public Component, public Controller { void set_password(const std::string &password); void set_reboot_timeout(uint32_t reboot_timeout); +#ifdef USE_API_NOISE + void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(std::move(psk)); } + std::shared_ptr get_noise_ctx() { return noise_ctx_; } +#endif // USE_API_NOISE + void handle_disconnect(APIConnection *conn); #ifdef USE_BINARY_SENSOR void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override; @@ -89,6 +95,10 @@ class APIServer : public Component, public Controller { std::string password_; std::vector state_subs_; std::vector user_services_; + +#ifdef USE_API_NOISE + std::shared_ptr noise_ctx_ = std::make_shared(); +#endif // USE_API_NOISE }; extern APIServer *global_api_server; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index d73f7e9d00..3cca6445b5 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -54,5 +54,8 @@ #define USE_SOCKET_IMPL_BSD_SOCKETS #endif +#define USE_API_PLAINTEXT +#define USE_API_NOISE + // Disabled feature flags //#define USE_BSEC // Requires a library with proprietary license. diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 9e9c775899..c5ff0102c3 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -55,6 +55,15 @@ double random_double() { return random_uint32() / double(UINT32_MAX); } float random_float() { return float(random_double()); } +void fill_random(uint8_t *data, size_t len) { +#ifdef ARDUINO_ARCH_ESP32 + esp_fill_random(data, len); +#else + int err = os_get_random(data, len); + assert(err == 0); +#endif +} + static uint32_t fast_random_seed = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) void fast_random_set_seed(uint32_t seed) { fast_random_seed = seed; } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 5868918cd6..60bc7a9ad3 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -109,6 +109,8 @@ double random_double(); /// Returns a random float between 0 and 1. Essentially just casts random_double() to a float. float random_float(); +void fill_random(uint8_t *data, size_t len); + void fast_random_set_seed(uint32_t seed); uint32_t fast_random_32(); uint16_t fast_random_16(); diff --git a/platformio.ini b/platformio.ini index 88b1000d1d..f4dea3fcb9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -36,6 +36,7 @@ lib_deps = 6306@1.0.3 ; HM3301 glmnet/Dsmr@0.3 ; used by dsmr rweather/Crypto@0.2.0 ; used by dsmr + esphome/noise-c@0.1.1 ; used by api dudanov/MideaUART@1.1.0 ; used by midea build_flags = diff --git a/tests/test3.yaml b/tests/test3.yaml index c012871125..5602481c36 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -22,6 +22,8 @@ api: port: 8000 password: 'pwd' reboot_timeout: 0min + encryption: + key: 'bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU=' services: - service: hello_world variables: From cc52f379335659eddd2a0a087d61bf9d7bb4ed2f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 9 Sep 2021 09:29:08 +1200 Subject: [PATCH 1289/1841] Revert "Dont dump legacy fields (#2241)" (#2259) This reverts commit 97eba1eecc7d1ee1aac8f3904315375dfd442b51. --- esphome/components/api/api_pb2.cpp | 41 +++++++++++++++++++++++++++++ script/api_protobuf/api_protobuf.py | 2 -- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index dab0b29127..d6b85d257c 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -793,6 +793,10 @@ void CoverStateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); + out.append(" legacy_state: "); + out.append(proto_enum_to_string(this->legacy_state)); + out.append("\n"); + out.append(" position: "); sprintf(buffer, "%g", this->position); out.append(buffer); @@ -876,6 +880,10 @@ void CoverCommandRequest::dump_to(std::string &out) const { out.append(YESNO(this->has_legacy_command)); out.append("\n"); + out.append(" legacy_command: "); + out.append(proto_enum_to_string(this->legacy_command)); + out.append("\n"); + out.append(" has_position: "); out.append(YESNO(this->has_position)); out.append("\n"); @@ -1322,6 +1330,22 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append("\n"); } + out.append(" legacy_supports_brightness: "); + out.append(YESNO(this->legacy_supports_brightness)); + out.append("\n"); + + out.append(" legacy_supports_rgb: "); + out.append(YESNO(this->legacy_supports_rgb)); + out.append("\n"); + + out.append(" legacy_supports_white_value: "); + out.append(YESNO(this->legacy_supports_white_value)); + out.append("\n"); + + out.append(" legacy_supports_color_temperature: "); + out.append(YESNO(this->legacy_supports_color_temperature)); + out.append("\n"); + out.append(" min_mireds: "); sprintf(buffer, "%g", this->min_mireds); out.append(buffer); @@ -2736,6 +2760,11 @@ void ExecuteServiceArgument::dump_to(std::string &out) const { out.append(YESNO(this->bool_)); out.append("\n"); + out.append(" legacy_int: "); + sprintf(buffer, "%d", this->legacy_int); + out.append(buffer); + out.append("\n"); + out.append(" float_: "); sprintf(buffer, "%g", this->float_); out.append(buffer); @@ -3151,6 +3180,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); + out.append(" legacy_supports_away: "); + out.append(YESNO(this->legacy_supports_away)); + out.append("\n"); + out.append(" supports_action: "); out.append(YESNO(this->supports_action)); out.append("\n"); @@ -3309,6 +3342,10 @@ void ClimateStateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); + out.append(" legacy_away: "); + out.append(YESNO(this->legacy_away)); + out.append("\n"); + out.append(" action: "); out.append(proto_enum_to_string(this->action)); out.append("\n"); @@ -3508,6 +3545,10 @@ void ClimateCommandRequest::dump_to(std::string &out) const { out.append(YESNO(this->has_legacy_away)); out.append("\n"); + out.append(" legacy_away: "); + out.append(YESNO(this->legacy_away)); + out.append("\n"); + out.append(" has_fan_mode: "); out.append(YESNO(this->has_fan_mode)); out.append("\n"); diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 7b9e7941ae..7ccdc5a24e 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -183,8 +183,6 @@ class TypeInfo: @property def dump_content(self): - if self.name.startswith("legacy_"): - return None o = f'out.append(" {self.name}: ");\n' o += self.dump(f"this->{self.field_name}") + "\n" o += f'out.append("\\n");\n' From f09aca48658dc6708099778df5c77a54f358c14f Mon Sep 17 00:00:00 2001 From: Peter van Dijk Date: Wed, 8 Sep 2021 23:35:00 +0200 Subject: [PATCH 1290/1841] pm1006: add support for sending a measurement request (#2214) --- CODEOWNERS | 1 + esphome/components/pm1006/pm1006.cpp | 7 +++++++ esphome/components/pm1006/pm1006.h | 3 ++- esphome/components/pm1006/sensor.py | 23 ++++++++++++++++++++++- 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 3ae9f71ead..8aa96d14af 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -94,6 +94,7 @@ esphome/components/ota/* @esphome/core esphome/components/output/* @esphome/core esphome/components/pid/* @OttoWinter esphome/components/pipsolar/* @andreashergert1984 +esphome/components/pm1006/* @habbie esphome/components/pmsa003i/* @sjtrny esphome/components/pn532/* @OttoWinter @jesserockz esphome/components/pn532_i2c/* @OttoWinter @jesserockz diff --git a/esphome/components/pm1006/pm1006.cpp b/esphome/components/pm1006/pm1006.cpp index 9bedb3cfc0..1a89307eee 100644 --- a/esphome/components/pm1006/pm1006.cpp +++ b/esphome/components/pm1006/pm1006.cpp @@ -7,6 +7,7 @@ namespace pm1006 { static const char *const TAG = "pm1006"; static const uint8_t PM1006_RESPONSE_HEADER[] = {0x16, 0x11, 0x0B}; +static const uint8_t PM1006_REQUEST[] = {0x11, 0x02, 0x0B, 0x01, 0xE1}; void PM1006Component::setup() { // because this implementation is currently rx-only, there is nothing to setup @@ -15,9 +16,15 @@ void PM1006Component::setup() { void PM1006Component::dump_config() { ESP_LOGCONFIG(TAG, "PM1006:"); LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_); + LOG_UPDATE_INTERVAL(this); this->check_uart_settings(9600); } +void PM1006Component::update() { + ESP_LOGV(TAG, "sending measurement request"); + this->write_array(PM1006_REQUEST, sizeof(PM1006_REQUEST)); +} + void PM1006Component::loop() { while (this->available() != 0) { this->read_byte(&this->data_[this->data_index_]); diff --git a/esphome/components/pm1006/pm1006.h b/esphome/components/pm1006/pm1006.h index 66f4cf0311..238ac67006 100644 --- a/esphome/components/pm1006/pm1006.h +++ b/esphome/components/pm1006/pm1006.h @@ -7,7 +7,7 @@ namespace esphome { namespace pm1006 { -class PM1006Component : public Component, public uart::UARTDevice { +class PM1006Component : public PollingComponent, public uart::UARTDevice { public: PM1006Component() = default; @@ -15,6 +15,7 @@ class PM1006Component : public Component, public uart::UARTDevice { void setup() override; void dump_config() override; void loop() override; + void update() override; float get_setup_priority() const override; diff --git a/esphome/components/pm1006/sensor.py b/esphome/components/pm1006/sensor.py index 0423be61e2..1e648be199 100644 --- a/esphome/components/pm1006/sensor.py +++ b/esphome/components/pm1006/sensor.py @@ -4,12 +4,15 @@ from esphome.components import sensor, uart from esphome.const import ( CONF_ID, CONF_PM_2_5, + CONF_UPDATE_INTERVAL, DEVICE_CLASS_PM25, STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_BLUR, ) +from esphome.core import TimePeriodMilliseconds +CODEOWNERS = ["@habbie"] DEPENDENCIES = ["uart"] pm1006_ns = cg.esphome_ns.namespace("pm1006") @@ -30,10 +33,28 @@ CONFIG_SCHEMA = cv.All( } ) .extend(cv.COMPONENT_SCHEMA) - .extend(uart.UART_DEVICE_SCHEMA), + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.polling_component_schema("never")), ) +def validate_interval_uart(config): + require_tx = False + + interval = config.get(CONF_UPDATE_INTERVAL) + + if isinstance(interval, TimePeriodMilliseconds): + # 'never' is encoded as a very large int, not as a TimePeriodMilliseconds objects + require_tx = True + + uart.final_validate_device_schema( + "pm1006", baud_rate=9600, require_rx=True, require_tx=require_tx + )(config) + + +FINAL_VALIDATE_SCHEMA = validate_interval_uart + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) From faf1c8bee8aff24ab331397529a75355f18011e8 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Wed, 8 Sep 2021 16:42:35 -0500 Subject: [PATCH 1291/1841] SGP40 sensor start-up fix (#2178) --- esphome/components/sgp40/sgp40.cpp | 17 ++++++++++++----- esphome/components/sgp40/sgp40.h | 1 + 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp index 8d93b3e1b1..a911c107b9 100644 --- a/esphome/components/sgp40/sgp40.cpp +++ b/esphome/components/sgp40/sgp40.cpp @@ -78,27 +78,28 @@ void SGP40Component::setup() { } void SGP40Component::self_test_() { - ESP_LOGD(TAG, "selfTest started"); + ESP_LOGD(TAG, "Self-test started"); if (!this->write_command_(SGP40_CMD_SELF_TEST)) { this->error_code_ = COMMUNICATION_FAILED; - ESP_LOGD(TAG, "selfTest communicatin failed"); + ESP_LOGD(TAG, "Self-test communication failed"); this->mark_failed(); } this->set_timeout(250, [this]() { uint16_t reply[1]; if (!this->read_data_(reply, 1)) { - ESP_LOGD(TAG, "selfTest read_data_ failed"); + ESP_LOGD(TAG, "Self-test read_data_ failed"); this->mark_failed(); return; } if (reply[0] == 0xD400) { - ESP_LOGD(TAG, "selfTest completed"); + this->self_test_complete_ = true; + ESP_LOGD(TAG, "Self-test completed"); return; } - ESP_LOGD(TAG, "selfTest failed"); + ESP_LOGD(TAG, "Self-test failed"); this->mark_failed(); }); } @@ -154,6 +155,12 @@ int32_t SGP40Component::measure_voc_index_() { */ uint16_t SGP40Component::measure_raw_() { float humidity = NAN; + + if (!this->self_test_complete_) { + ESP_LOGD(TAG, "Self-test not yet complete"); + return UINT16_MAX; + } + if (this->humidity_sensor_ != nullptr) { humidity = this->humidity_sensor_->state; } diff --git a/esphome/components/sgp40/sgp40.h b/esphome/components/sgp40/sgp40.h index b9ea365169..62936102e7 100644 --- a/esphome/components/sgp40/sgp40.h +++ b/esphome/components/sgp40/sgp40.h @@ -68,6 +68,7 @@ class SGP40Component : public PollingComponent, public sensor::Sensor, public i2 int32_t seconds_since_last_store_; SGP40Baselines baselines_storage_; VocAlgorithmParams voc_algorithm_params_; + bool self_test_complete_; bool store_baseline_; int32_t state0_; int32_t state1_; From d2616cbdfc5e14df20c6ecbb1d324d1bcf6b0ec9 Mon Sep 17 00:00:00 2001 From: Stephen Tierney Date: Thu, 9 Sep 2021 08:14:08 +1000 Subject: [PATCH 1292/1841] PMSA003i Update state_class and async (#2216) * Update component (state_class and async) * No need to specify empty device class * Remove unused import Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/pmsa003i/sensor.py | 67 ++++++++++++++++++--------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/esphome/components/pmsa003i/sensor.py b/esphome/components/pmsa003i/sensor.py index a9586d98cd..ceca791cd6 100644 --- a/esphome/components/pmsa003i/sensor.py +++ b/esphome/components/pmsa003i/sensor.py @@ -16,7 +16,7 @@ from esphome.const import ( DEVICE_CLASS_PM1, DEVICE_CLASS_PM10, DEVICE_CLASS_PM25, - DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, ) CODEOWNERS = ["@sjtrny"] @@ -39,40 +39,61 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(PMSA003IComponent), cv.Optional(CONF_STANDARD_UNITS, default=True): cv.boolean, cv.Optional(CONF_PM_1_0): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 2, - DEVICE_CLASS_PM1, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + device_class=DEVICE_CLASS_PM1, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_2_5): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 2, - DEVICE_CLASS_PM25, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + device_class=DEVICE_CLASS_PM25, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 2, - DEVICE_CLASS_PM10, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + device_class=DEVICE_CLASS_PM10, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_0_3): sensor.sensor_schema( - UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_COUNTS_PER_100ML, + icon=ICON_COUNTER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_0_5): sensor.sensor_schema( - UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_COUNTS_PER_100ML, + icon=ICON_COUNTER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_1_0): sensor.sensor_schema( - UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_COUNTS_PER_100ML, + icon=ICON_COUNTER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_2_5): sensor.sensor_schema( - UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_COUNTS_PER_100ML, + icon=ICON_COUNTER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_5_0): sensor.sensor_schema( - UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_COUNTS_PER_100ML, + icon=ICON_COUNTER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_10_0): sensor.sensor_schema( - UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_COUNTS_PER_100ML, + icon=ICON_COUNTER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), } ) @@ -93,15 +114,15 @@ TYPES = { } -def to_code(config): +async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - yield cg.register_component(var, config) - yield i2c.register_i2c_device(var, config) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) cg.add(var.set_standard_units(config[CONF_STANDARD_UNITS])) for key, funcName in TYPES.items(): if key in config: - sens = yield sensor.new_sensor(config[key]) + sens = await sensor.new_sensor(config[key]) cg.add(getattr(var, funcName)(sens)) From 09a6fdf1c7358767909e56ef1bbfb92638119933 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 9 Sep 2021 10:28:34 +1200 Subject: [PATCH 1293/1841] Bump version to 2021.9.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 07d0079172..44e3c09870 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.0-dev" +__version__ = "2021.9.0b1" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From bad161a5c1d87d335ab88b7ad3ddc0297f2520a6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 9 Sep 2021 10:28:34 +1200 Subject: [PATCH 1294/1841] Bump version to 2021.10.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 07d0079172..f342609538 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.0-dev" +__version__ = "2021.10.0-dev" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From affaaf7d2c86c5b356886098876ad4b970ba848c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 10 Sep 2021 12:10:28 +1200 Subject: [PATCH 1295/1841] Fix a few ESP32-C3 compiler issues (#2265) * Fix using Serial when using ESP32-C3 standard pins * Force type for std::min in pn532 * Fix variable size where size_t is different on exp32-c3 --- esphome/components/api/api_frame_helper.cpp | 2 +- esphome/components/pn532/pn532.cpp | 2 +- esphome/components/uart/uart_esp32.cpp | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 26fbf1269f..520a5c2caf 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -707,7 +707,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { } size_t i = 1; - size_t consumed = 0; + uint32_t consumed = 0; auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed); if (!msg_size_varint.has_value()) { // not enough data there yet diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index fc84f30078..fcab9872c4 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -49,7 +49,7 @@ void PN532::setup() { } // Set up SAM (secure access module) - uint8_t sam_timeout = std::min(255u, this->update_interval_ / 50); + uint8_t sam_timeout = std::min(255u, this->update_interval_ / 50); if (!this->write_command_({ PN532_COMMAND_SAMCONFIGURATION, 0x01, // normal mode diff --git a/esphome/components/uart/uart_esp32.cpp b/esphome/components/uart/uart_esp32.cpp index 16d683e4a6..c672a34c36 100644 --- a/esphome/components/uart/uart_esp32.cpp +++ b/esphome/components/uart/uart_esp32.cpp @@ -73,7 +73,11 @@ void UARTComponent::setup() { // Use Arduino HardwareSerial UARTs if all used pins match the ones // preconfigured by the platform. For example if RX disabled but TX pin // is 1 we still want to use Serial. +#ifdef CONFIG_IDF_TARGET_ESP32C3 + if (this->tx_pin_.value_or(21) == 21 && this->rx_pin_.value_or(20) == 20) { +#else if (this->tx_pin_.value_or(1) == 1 && this->rx_pin_.value_or(3) == 3) { +#endif this->hw_serial_ = &Serial; } else { this->hw_serial_ = new HardwareSerial(next_uart_num++); From 3d71e2e1890a94d0caee3dda2403184b98fdbfde Mon Sep 17 00:00:00 2001 From: poptix Date: Fri, 10 Sep 2021 04:05:25 -0500 Subject: [PATCH 1296/1841] sm300d2: Accept (undocumented) 0x80 checksum offset. (#2263) Co-authored-by: Matt Hallacy --- esphome/components/sm300d2/sm300d2.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/sm300d2/sm300d2.cpp b/esphome/components/sm300d2/sm300d2.cpp index 34d80349f9..b1787581ae 100644 --- a/esphome/components/sm300d2/sm300d2.cpp +++ b/esphome/components/sm300d2/sm300d2.cpp @@ -27,7 +27,8 @@ void SM300D2Sensor::update() { } uint16_t calculated_checksum = this->sm300d2_checksum_(response); - if (calculated_checksum != response[SM300D2_RESPONSE_LENGTH - 1]) { + if ((calculated_checksum != response[SM300D2_RESPONSE_LENGTH - 1]) && + (calculated_checksum - 0x80 != response[SM300D2_RESPONSE_LENGTH - 1])) { ESP_LOGW(TAG, "SM300D2 Checksum doesn't match: 0x%02X!=0x%02X", response[SM300D2_RESPONSE_LENGTH - 1], calculated_checksum); this->status_set_warning(); From 7dd40e2014a04a3af560c212815cd77ca604b667 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 10 Sep 2021 12:10:28 +1200 Subject: [PATCH 1297/1841] Fix a few ESP32-C3 compiler issues (#2265) * Fix using Serial when using ESP32-C3 standard pins * Force type for std::min in pn532 * Fix variable size where size_t is different on exp32-c3 --- esphome/components/api/api_frame_helper.cpp | 2 +- esphome/components/pn532/pn532.cpp | 2 +- esphome/components/uart/uart_esp32.cpp | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 26fbf1269f..520a5c2caf 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -707,7 +707,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { } size_t i = 1; - size_t consumed = 0; + uint32_t consumed = 0; auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed); if (!msg_size_varint.has_value()) { // not enough data there yet diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index fc84f30078..fcab9872c4 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -49,7 +49,7 @@ void PN532::setup() { } // Set up SAM (secure access module) - uint8_t sam_timeout = std::min(255u, this->update_interval_ / 50); + uint8_t sam_timeout = std::min(255u, this->update_interval_ / 50); if (!this->write_command_({ PN532_COMMAND_SAMCONFIGURATION, 0x01, // normal mode diff --git a/esphome/components/uart/uart_esp32.cpp b/esphome/components/uart/uart_esp32.cpp index 16d683e4a6..c672a34c36 100644 --- a/esphome/components/uart/uart_esp32.cpp +++ b/esphome/components/uart/uart_esp32.cpp @@ -73,7 +73,11 @@ void UARTComponent::setup() { // Use Arduino HardwareSerial UARTs if all used pins match the ones // preconfigured by the platform. For example if RX disabled but TX pin // is 1 we still want to use Serial. +#ifdef CONFIG_IDF_TARGET_ESP32C3 + if (this->tx_pin_.value_or(21) == 21 && this->rx_pin_.value_or(20) == 20) { +#else if (this->tx_pin_.value_or(1) == 1 && this->rx_pin_.value_or(3) == 3) { +#endif this->hw_serial_ = &Serial; } else { this->hw_serial_ = new HardwareSerial(next_uart_num++); From 87842e097bcb02893d2dea286f4af7970fa3492f Mon Sep 17 00:00:00 2001 From: poptix Date: Fri, 10 Sep 2021 04:05:25 -0500 Subject: [PATCH 1298/1841] sm300d2: Accept (undocumented) 0x80 checksum offset. (#2263) Co-authored-by: Matt Hallacy --- esphome/components/sm300d2/sm300d2.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/sm300d2/sm300d2.cpp b/esphome/components/sm300d2/sm300d2.cpp index 34d80349f9..b1787581ae 100644 --- a/esphome/components/sm300d2/sm300d2.cpp +++ b/esphome/components/sm300d2/sm300d2.cpp @@ -27,7 +27,8 @@ void SM300D2Sensor::update() { } uint16_t calculated_checksum = this->sm300d2_checksum_(response); - if (calculated_checksum != response[SM300D2_RESPONSE_LENGTH - 1]) { + if ((calculated_checksum != response[SM300D2_RESPONSE_LENGTH - 1]) && + (calculated_checksum - 0x80 != response[SM300D2_RESPONSE_LENGTH - 1])) { ESP_LOGW(TAG, "SM300D2 Checksum doesn't match: 0x%02X!=0x%02X", response[SM300D2_RESPONSE_LENGTH - 1], calculated_checksum); this->status_set_warning(); From 9821a3442bc7e10654af3b26b6c8785efe9a6de4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 10 Sep 2021 21:34:38 +1200 Subject: [PATCH 1299/1841] Bump version to 2021.9.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 44e3c09870..100d89594b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.0b1" +__version__ = "2021.9.0b2" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From ede1de9021c2ffe9533ad7f05c929554834a6c13 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 12 Sep 2021 22:04:35 +0200 Subject: [PATCH 1300/1841] Drop obsolete comments from CONTRIBUTING.md (#2271) --- CONTRIBUTING.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 43d5e79074..e96bb5745b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,10 +1,6 @@ # Contributing to ESPHome -This python project is responsible for reading in YAML configuration files, -converting them to C++ code. This code is then converted to a platformio project and compiled -with [esphome-core](https://github.com/esphome/esphome-core), the C++ framework behind the project. - -For a detailed guide, please see https://esphome.io/guides/contributing.html#contributing-to-esphomeyaml +For a detailed guide, please see https://esphome.io/guides/contributing.html#contributing-to-esphome Things to note when contributing: From 97a18717e6d3b39a281cd1403d03a304404321a0 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 02:44:39 +0200 Subject: [PATCH 1301/1841] Disable automatic usage of SNTP servers from DHCP (#2273) --- esphome/components/wifi/wifi_component_esp32.cpp | 6 ++++++ esphome/components/wifi/wifi_component_esp8266.cpp | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/esphome/components/wifi/wifi_component_esp32.cpp b/esphome/components/wifi/wifi_component_esp32.cpp index 57c4efcdd5..b56030db56 100644 --- a/esphome/components/wifi/wifi_component_esp32.cpp +++ b/esphome/components/wifi/wifi_component_esp32.cpp @@ -11,6 +11,7 @@ #endif #include "lwip/err.h" #include "lwip/dns.h" +#include "lwip/apps/sntp.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -92,6 +93,11 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { tcpip_adapter_dhcp_status_t dhcp_status; tcpip_adapter_dhcpc_get_status(TCPIP_ADAPTER_IF_STA, &dhcp_status); if (!manual_ip.has_value()) { + // lwIP starts the SNTP client if it gets an SNTP server from DHCP. We don't need the time, and more importantly, + // the built-in SNTP client has a memory leak in certain situations. Disable this feature. + // https://github.com/esphome/issues/issues/2299 + sntp_servermode_dhcp(false); + // Use DHCP client if (dhcp_status != TCPIP_ADAPTER_DHCP_STARTED) { esp_err_t err = tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index ad1a64d1f4..de529ee3aa 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -16,6 +16,7 @@ extern "C" { #include "lwip/dns.h" #include "lwip/dhcp.h" #include "lwip/init.h" // LWIP_VERSION_ +#include "lwip/apps/sntp.h" #if LWIP_IPV6 #include "lwip/netif.h" // struct netif #endif @@ -112,6 +113,11 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { enum dhcp_status dhcp_status = wifi_station_dhcpc_status(); if (!manual_ip.has_value()) { + // lwIP starts the SNTP client if it gets an SNTP server from DHCP. We don't need the time, and more importantly, + // the built-in SNTP client has a memory leak in certain situations. Disable this feature. + // https://github.com/esphome/issues/issues/2299 + sntp_servermode_dhcp(false); + // Use DHCP client if (dhcp_status != DHCP_STARTED) { bool ret = wifi_station_dhcpc_start(); From f0b6aabc964722d3d0da3e49c84cec4125d9e7d4 Mon Sep 17 00:00:00 2001 From: irtimaled Date: Mon, 13 Sep 2021 00:33:20 -0700 Subject: [PATCH 1302/1841] Support inverting color temperature on tuya lights (#2277) --- esphome/components/tuya/light/__init__.py | 4 ++++ esphome/components/tuya/light/tuya_light.cpp | 9 ++++++++- esphome/components/tuya/light/tuya_light.h | 4 ++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/esphome/components/tuya/light/__init__.py b/esphome/components/tuya/light/__init__.py index 979082d636..f43cc570ca 100644 --- a/esphome/components/tuya/light/__init__.py +++ b/esphome/components/tuya/light/__init__.py @@ -18,6 +18,7 @@ DEPENDENCIES = ["tuya"] CONF_DIMMER_DATAPOINT = "dimmer_datapoint" CONF_MIN_VALUE_DATAPOINT = "min_value_datapoint" CONF_COLOR_TEMPERATURE_DATAPOINT = "color_temperature_datapoint" +CONF_COLOR_TEMPERATURE_INVERT = "color_temperature_invert" CONF_COLOR_TEMPERATURE_MAX_VALUE = "color_temperature_max_value" TuyaLight = tuya_ns.class_("TuyaLight", light.LightOutput, cg.Component) @@ -33,6 +34,7 @@ CONFIG_SCHEMA = cv.All( cv.Inclusive( CONF_COLOR_TEMPERATURE_DATAPOINT, "color_temperature" ): cv.uint8_t, + cv.Optional(CONF_COLOR_TEMPERATURE_INVERT, default=False): cv.boolean, cv.Optional(CONF_MIN_VALUE): cv.int_, cv.Optional(CONF_MAX_VALUE): cv.int_, cv.Optional(CONF_COLOR_TEMPERATURE_MAX_VALUE): cv.int_, @@ -67,6 +69,8 @@ async def to_code(config): cg.add(var.set_switch_id(config[CONF_SWITCH_DATAPOINT])) if CONF_COLOR_TEMPERATURE_DATAPOINT in config: cg.add(var.set_color_temperature_id(config[CONF_COLOR_TEMPERATURE_DATAPOINT])) + cg.add(var.set_color_temperature_invert(config[CONF_COLOR_TEMPERATURE_INVERT])) + cg.add( var.set_cold_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE]) ) diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index 8ad78aacea..6f3adfcdfd 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -9,10 +9,14 @@ static const char *const TAG = "tuya.light"; void TuyaLight::setup() { if (this->color_temperature_id_.has_value()) { this->parent_->register_listener(*this->color_temperature_id_, [this](const TuyaDatapoint &datapoint) { + auto datapoint_value = datapoint.value_uint; + if (this->color_temperature_invert_) { + datapoint_value = this->color_temperature_max_value_ - datapoint_value; + } auto call = this->state_->make_call(); call.set_color_temperature(this->cold_white_temperature_ + (this->warm_white_temperature_ - this->cold_white_temperature_) * - (float(datapoint.value_uint) / float(this->color_temperature_max_value_))); + (float(datapoint_value) / this->color_temperature_max_value_)); call.perform(); }); } @@ -78,6 +82,9 @@ void TuyaLight::write_state(light::LightState *state) { static_cast(this->color_temperature_max_value_ * (state->current_values.get_color_temperature() - this->cold_white_temperature_) / (this->warm_white_temperature_ - this->cold_white_temperature_)); + if (this->color_temperature_invert_) { + color_temp_int = this->color_temperature_max_value_ - color_temp_int; + } parent_->set_integer_datapoint_value(*this->color_temperature_id_, color_temp_int); } diff --git a/esphome/components/tuya/light/tuya_light.h b/esphome/components/tuya/light/tuya_light.h index 72422bc9e7..20753fa90b 100644 --- a/esphome/components/tuya/light/tuya_light.h +++ b/esphome/components/tuya/light/tuya_light.h @@ -17,6 +17,9 @@ class TuyaLight : public Component, public light::LightOutput { } void set_switch_id(uint8_t switch_id) { this->switch_id_ = switch_id; } void set_color_temperature_id(uint8_t color_temperature_id) { this->color_temperature_id_ = color_temperature_id; } + void set_color_temperature_invert(bool color_temperature_invert) { + this->color_temperature_invert_ = color_temperature_invert; + } void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } void set_min_value(uint32_t min_value) { min_value_ = min_value; } void set_max_value(uint32_t max_value) { max_value_ = max_value; } @@ -47,6 +50,7 @@ class TuyaLight : public Component, public light::LightOutput { uint32_t color_temperature_max_value_ = 255; float cold_white_temperature_; float warm_white_temperature_; + bool color_temperature_invert_{false}; light::LightState *state_{nullptr}; }; From f31e0532c4e502de7913f086452a3ac4eebb3473 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 09:33:29 +0200 Subject: [PATCH 1303/1841] Untangle core headers (part 1) (#2276) --- esphome/components/ade7953/ade7953.h | 1 + esphome/components/am43/am43_base.cpp | 2 ++ esphome/components/anova/anova_base.cpp | 2 ++ esphome/components/as3935/as3935.h | 1 + esphome/components/climate/climate_traits.cpp | 2 +- esphome/components/deep_sleep/deep_sleep_component.h | 1 + esphome/components/dht/dht.h | 1 + esphome/components/esp32_ble/ble_uuid.h | 1 + .../gpio/binary_sensor/gpio_binary_sensor.h | 1 + esphome/components/gpio/switch/gpio_switch.h | 1 + esphome/components/nfc/nfc.cpp | 1 + esphome/components/rc522/rc522.h | 1 + .../remote_receiver/remote_receiver_esp8266.cpp | 1 + esphome/components/sx1509/sx1509.h | 1 + esphome/components/ttp229_bsf/ttp229_bsf.h | 1 + esphome/components/tx20/tx20.h | 1 + esphome/components/vl53l0x/vl53l0x_sensor.h | 1 + esphome/core/esphal.h | 4 +--- esphome/core/helpers.h | 5 ++++- esphome/core/log.h | 12 +++++++----- esphome/core/preferences.h | 2 +- 21 files changed, 32 insertions(+), 11 deletions(-) diff --git a/esphome/components/ade7953/ade7953.h b/esphome/components/ade7953/ade7953.h index e0fadf37c3..5a582a991a 100644 --- a/esphome/components/ade7953/ade7953.h +++ b/esphome/components/ade7953/ade7953.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/components/i2c/i2c.h" #include "esphome/components/sensor/sensor.h" diff --git a/esphome/components/am43/am43_base.cpp b/esphome/components/am43/am43_base.cpp index a17df55571..af474dcb79 100644 --- a/esphome/components/am43/am43_base.cpp +++ b/esphome/components/am43/am43_base.cpp @@ -1,4 +1,6 @@ #include "am43_base.h" +#include +#include namespace esphome { namespace am43 { diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp index ad581b1e6c..811a34a27a 100644 --- a/esphome/components/anova/anova_base.cpp +++ b/esphome/components/anova/anova_base.cpp @@ -1,4 +1,6 @@ #include "anova_base.h" +#include +#include namespace esphome { namespace anova { diff --git a/esphome/components/as3935/as3935.h b/esphome/components/as3935/as3935.h index d0e53e7832..76c7697d38 100644 --- a/esphome/components/as3935/as3935.h +++ b/esphome/components/as3935/as3935.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h" diff --git a/esphome/components/climate/climate_traits.cpp b/esphome/components/climate/climate_traits.cpp index c871552360..16c9cd05be 100644 --- a/esphome/components/climate/climate_traits.cpp +++ b/esphome/components/climate/climate_traits.cpp @@ -1,5 +1,5 @@ #include "climate_traits.h" -#include "esphome/core/log.h" +#include namespace esphome { namespace climate { diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index e163cf7709..5050bd6b4d 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/automation.h" +#include "esphome/core/esphal.h" namespace esphome { namespace deep_sleep { diff --git a/esphome/components/dht/dht.h b/esphome/components/dht/dht.h index 8826a3d2b7..9b848cb119 100644 --- a/esphome/components/dht/dht.h +++ b/esphome/components/dht/dht.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/components/sensor/sensor.h" namespace esphome { diff --git a/esphome/components/esp32_ble/ble_uuid.h b/esphome/components/esp32_ble/ble_uuid.h index 0e3c9b0d0a..89082e19fa 100644 --- a/esphome/components/esp32_ble/ble_uuid.h +++ b/esphome/components/esp32_ble/ble_uuid.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/helpers.h" +#include "esphome/core/esphal.h" #ifdef ARDUINO_ARCH_ESP32 diff --git a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h index cfe49b3c94..f655207243 100644 --- a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h +++ b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/components/binary_sensor/binary_sensor.h" namespace esphome { diff --git a/esphome/components/gpio/switch/gpio_switch.h b/esphome/components/gpio/switch/gpio_switch.h index c0036b20e9..b39e070c85 100644 --- a/esphome/components/gpio/switch/gpio_switch.h +++ b/esphome/components/gpio/switch/gpio_switch.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/components/switch/switch.h" namespace esphome { diff --git a/esphome/components/nfc/nfc.cpp b/esphome/components/nfc/nfc.cpp index 01f63e6ec9..706c09a5aa 100644 --- a/esphome/components/nfc/nfc.cpp +++ b/esphome/components/nfc/nfc.cpp @@ -1,4 +1,5 @@ #include "nfc.h" +#include #include "esphome/core/log.h" namespace esphome { diff --git a/esphome/components/rc522/rc522.h b/esphome/components/rc522/rc522.h index 6880651ac4..e4e3387ff6 100644 --- a/esphome/components/rc522/rc522.h +++ b/esphome/components/rc522/rc522.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/core/automation.h" #include "esphome/components/binary_sensor/binary_sensor.h" diff --git a/esphome/components/remote_receiver/remote_receiver_esp8266.cpp b/esphome/components/remote_receiver/remote_receiver_esp8266.cpp index 3fb68caca2..d509eea925 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp8266.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp8266.cpp @@ -1,4 +1,5 @@ #include "remote_receiver.h" +#include "esphome/core/esphal.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" diff --git a/esphome/components/sx1509/sx1509.h b/esphome/components/sx1509/sx1509.h index 55d5e54091..1390059675 100644 --- a/esphome/components/sx1509/sx1509.h +++ b/esphome/components/sx1509/sx1509.h @@ -2,6 +2,7 @@ #include "esphome/components/i2c/i2c.h" #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "sx1509_gpio_pin.h" #include "sx1509_registers.h" diff --git a/esphome/components/ttp229_bsf/ttp229_bsf.h b/esphome/components/ttp229_bsf/ttp229_bsf.h index 94dd014d8e..d73e4fb185 100644 --- a/esphome/components/ttp229_bsf/ttp229_bsf.h +++ b/esphome/components/ttp229_bsf/ttp229_bsf.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/components/binary_sensor/binary_sensor.h" namespace esphome { diff --git a/esphome/components/tx20/tx20.h b/esphome/components/tx20/tx20.h index 8b79deffbc..56cb723fa1 100644 --- a/esphome/components/tx20/tx20.h +++ b/esphome/components/tx20/tx20.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/components/sensor/sensor.h" namespace esphome { diff --git a/esphome/components/vl53l0x/vl53l0x_sensor.h b/esphome/components/vl53l0x/vl53l0x_sensor.h index 0c37df67a2..83a6322dcd 100644 --- a/esphome/components/vl53l0x/vl53l0x_sensor.h +++ b/esphome/components/vl53l0x/vl53l0x_sensor.h @@ -3,6 +3,7 @@ #include #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" diff --git a/esphome/core/esphal.h b/esphome/core/esphal.h index 809c7d91b5..08fbfb09f5 100644 --- a/esphome/core/esphal.h +++ b/esphome/core/esphal.h @@ -1,9 +1,7 @@ #pragma once #include "Arduino.h" -#ifdef ARDUINO_ARCH_ESP32 -#include -#endif + // Fix some arduino defs #ifdef round #undef round diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 60bc7a9ad3..a63c627e5a 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -6,8 +6,11 @@ #include #include +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-psram.h" +#endif + #include "esphome/core/optional.h" -#include "esphome/core/esphal.h" #ifdef CLANG_TIDY #undef ICACHE_RAM_ATTR diff --git a/esphome/core/log.h b/esphome/core/log.h index fbaaf14408..0b0911c3ac 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -3,18 +3,20 @@ #include #include #include + #ifdef USE_STORE_LOG_STR_IN_FLASH #include "WString.h" #endif -#include "esphome/core/macros.h" -// avoid esp-idf redefining our macros -#include "esphome/core/esphal.h" - +// Both the ESP-IDF and Arduino also define ESP_LOG* macros. Include them here, so that they won't +// be reincluded later on and redefine our macros. #ifdef ARDUINO_ARCH_ESP32 -#include "esp_err.h" +#include +#include #endif +#include "esphome/core/macros.h" + namespace esphome { #define ESPHOME_LOG_LEVEL_NONE 0 diff --git a/esphome/core/preferences.h b/esphome/core/preferences.h index 43d0f023e3..699871ca00 100644 --- a/esphome/core/preferences.h +++ b/esphome/core/preferences.h @@ -1,8 +1,8 @@ #pragma once #include +#include -#include "esphome/core/esphal.h" #include "esphome/core/defines.h" namespace esphome { From 0cd24c629a727914711c7e9e257713d079a0245a Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 09:35:55 +0200 Subject: [PATCH 1304/1841] Compatibility with clang-tidy v14 (#2272) --- .clang-tidy | 4 ++++ esphome/components/bme280/bme280.cpp | 20 +++++++++---------- esphome/components/bmp280/bmp280.cpp | 10 +++++----- esphome/components/display/display_buffer.cpp | 4 +--- esphome/components/i2c/i2c.cpp | 2 +- esphome/components/mcp3008/mcp3008.cpp | 3 +-- esphome/components/mqtt/mqtt_client.cpp | 2 +- .../components/remote_base/rc5_protocol.cpp | 6 +++--- esphome/components/st7735/st7735.cpp | 17 ++++++++-------- esphome/components/sun/sun.cpp | 6 ++---- esphome/core/component.cpp | 6 ++---- esphome/core/preferences.cpp | 6 +++--- 12 files changed, 41 insertions(+), 45 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 473529a9b1..936100e9e6 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -2,9 +2,11 @@ Checks: >- *, -abseil-*, + -altera-*, -android-*, -boost-*, -bugprone-branch-clone, + -bugprone-easily-swappable-parameters, -bugprone-narrowing-conversions, -bugprone-signed-char-misuse, -bugprone-too-small-loop-variable, @@ -20,6 +22,7 @@ Checks: >- -clang-diagnostic-sign-compare, -clang-diagnostic-unused-variable, -clang-diagnostic-unused-const-variable, + -concurrency-*, -cppcoreguidelines-avoid-c-arrays, -cppcoreguidelines-avoid-goto, -cppcoreguidelines-avoid-magic-numbers, @@ -72,6 +75,7 @@ Checks: >- -readability-const-return-type, -readability-convert-member-functions-to-static, -readability-else-after-return, + -readability-function-cognitive-complexity, -readability-implicit-bool-conversion, -readability-isolate-declaration, -readability-magic-numbers, diff --git a/esphome/components/bme280/bme280.cpp b/esphome/components/bme280/bme280.cpp index 097974da7a..fe11b69945 100644 --- a/esphome/components/bme280/bme280.cpp +++ b/esphome/components/bme280/bme280.cpp @@ -113,14 +113,14 @@ void BME280Component::setup() { this->calibration_.h5 = read_u8_(BME280_REGISTER_DIG_H5 + 1) << 4 | (read_u8_(BME280_REGISTER_DIG_H5) >> 4); this->calibration_.h6 = read_u8_(BME280_REGISTER_DIG_H6); - uint8_t humid_register = 0; - if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_register)) { + uint8_t humid_control_val = 0; + if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) { this->mark_failed(); return; } - humid_register &= ~0b00000111; - humid_register |= this->humidity_oversampling_ & 0b111; - if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_register)) { + humid_control_val &= ~0b00000111; + humid_control_val |= this->humidity_oversampling_ & 0b111; + if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) { this->mark_failed(); return; } @@ -169,11 +169,11 @@ inline uint8_t oversampling_to_time(BME280Oversampling over_sampling) { return ( void BME280Component::update() { // Enable sensor ESP_LOGV(TAG, "Sending conversion request..."); - uint8_t meas_register = 0; - meas_register |= (this->temperature_oversampling_ & 0b111) << 5; - meas_register |= (this->pressure_oversampling_ & 0b111) << 2; - meas_register |= BME280_MODE_FORCED; - if (!this->write_byte(BME280_REGISTER_CONTROL, meas_register)) { + uint8_t meas_value = 0; + meas_value |= (this->temperature_oversampling_ & 0b111) << 5; + meas_value |= (this->pressure_oversampling_ & 0b111) << 2; + meas_value |= BME280_MODE_FORCED; + if (!this->write_byte(BME280_REGISTER_CONTROL, meas_value)) { this->status_set_warning(); return; } diff --git a/esphome/components/bmp280/bmp280.cpp b/esphome/components/bmp280/bmp280.cpp index 4048e40077..d04a357ca2 100644 --- a/esphome/components/bmp280/bmp280.cpp +++ b/esphome/components/bmp280/bmp280.cpp @@ -123,11 +123,11 @@ inline uint8_t oversampling_to_time(BMP280Oversampling over_sampling) { return ( void BMP280Component::update() { // Enable sensor ESP_LOGV(TAG, "Sending conversion request..."); - uint8_t meas_register = 0; - meas_register |= (this->temperature_oversampling_ & 0b111) << 5; - meas_register |= (this->pressure_oversampling_ & 0b111) << 2; - meas_register |= 0b01; // Forced mode - if (!this->write_byte(BMP280_REGISTER_CONTROL, meas_register)) { + uint8_t meas_value = 0; + meas_value |= (this->temperature_oversampling_ & 0b111) << 5; + meas_value |= (this->pressure_oversampling_ & 0b111) << 2; + meas_value |= 0b01; // Forced mode + if (!this->write_byte(BMP280_REGISTER_CONTROL, meas_value)) { this->status_set_warning(); return; } diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 29f86dbd5f..c330d00ce5 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -514,9 +514,7 @@ Color Animation::get_grayscale_pixel(int x, int y) const { return Color(gray | gray << 8 | gray << 16 | gray << 24); } Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type) - : Image(data_start, width, height, type), animation_frame_count_(animation_frame_count) { - current_frame_ = 0; -} + : Image(data_start, width, height, type), current_frame_(0), animation_frame_count_(animation_frame_count) {} int Animation::get_animation_frame_count() const { return this->animation_frame_count_; } int Animation::get_current_frame() const { return this->current_frame_; } void Animation::next_frame() { diff --git a/esphome/components/i2c/i2c.cpp b/esphome/components/i2c/i2c.cpp index 77dfcedc04..f1980e1f1d 100644 --- a/esphome/components/i2c/i2c.cpp +++ b/esphome/components/i2c/i2c.cpp @@ -16,7 +16,7 @@ I2CComponent::I2CComponent() { this->wire_ = new TwoWire(next_i2c_bus_num_); next_i2c_bus_num_++; #else - this->wire_ = &Wire; + this->wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer) #endif } diff --git a/esphome/components/mcp3008/mcp3008.cpp b/esphome/components/mcp3008/mcp3008.cpp index 909a6f4708..bea24b5c6a 100644 --- a/esphome/components/mcp3008/mcp3008.cpp +++ b/esphome/components/mcp3008/mcp3008.cpp @@ -38,9 +38,8 @@ float MCP3008::read_data(uint8_t pin) { } MCP3008Sensor::MCP3008Sensor(MCP3008 *parent, const std::string &name, uint8_t pin, float reference_voltage) - : PollingComponent(1000), parent_(parent), pin_(pin) { + : PollingComponent(1000), parent_(parent), pin_(pin), reference_voltage_(reference_voltage) { this->set_name(name); - this->reference_voltage_ = reference_voltage; } float MCP3008Sensor::get_setup_priority() const { return setup_priority::DATA; } diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index afb67d36ed..be2176df09 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -25,7 +25,7 @@ MQTTClientComponent::MQTTClientComponent() { // Connection void MQTTClientComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up MQTT..."); - this->mqtt_client_.onMessage([this](char *topic, char *payload, AsyncMqttClientMessageProperties properties, + this->mqtt_client_.onMessage([this](char const *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { if (index == 0) this->payload_buffer_.reserve(total); diff --git a/esphome/components/remote_base/rc5_protocol.cpp b/esphome/components/remote_base/rc5_protocol.cpp index 80af2f9ad8..a8b608ec9e 100644 --- a/esphome/components/remote_base/rc5_protocol.cpp +++ b/esphome/components/remote_base/rc5_protocol.cpp @@ -41,7 +41,7 @@ optional RC5Protocol::decode(RemoteReceiveData src) { .address = 0, .command = 0, }; - int field_bit = 0; + uint8_t field_bit; if (src.expect_space(BIT_TIME_US) && src.expect_mark(BIT_TIME_US)) { field_bit = 1; @@ -60,7 +60,7 @@ optional RC5Protocol::decode(RemoteReceiveData src) { return {}; } - uint64_t out_data = 0; + uint32_t out_data = 0; for (int bit = NBITS - 4; bit >= 1; bit--) { if ((src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) { @@ -78,7 +78,7 @@ optional RC5Protocol::decode(RemoteReceiveData src) { out_data |= 1; } - out.command = (out_data & 0x3F) + (1 - field_bit) * 64; + out.command = (uint8_t)(out_data & 0x3F) + (1 - field_bit) * 64u; out.address = (out_data >> 6) & 0x1F; return out; } diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index 0467ed83db..ac8802739b 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -220,15 +220,14 @@ static const uint8_t PROGMEM // clang-format on static const char *const TAG = "st7735"; -ST7735::ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr) { - model_ = model; - this->width_ = width; - this->height_ = height; - this->colstart_ = colstart; - this->rowstart_ = rowstart; - this->eightbitcolor_ = eightbitcolor; - this->usebgr_ = usebgr; -} +ST7735::ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr) + : model_(model), + colstart_(colstart), + rowstart_(rowstart), + eightbitcolor_(eightbitcolor), + usebgr_(usebgr), + width_(width), + height_(height) {} void ST7735::setup() { ESP_LOGCONFIG(TAG, "Setting up ST7735..."); diff --git a/esphome/components/sun/sun.cpp b/esphome/components/sun/sun.cpp index 4fd034ce7d..113c14d431 100644 --- a/esphome/components/sun/sun.cpp +++ b/esphome/components/sun/sun.cpp @@ -79,10 +79,8 @@ struct SunAtTime { num_t jde; num_t t; - SunAtTime(num_t jde) : jde(jde) { - // eq 25.1, p. 163; julian centuries from the epoch J2000.0 - t = (jde - 2451545) / 36525.0; - } + // eq 25.1, p. 163; julian centuries from the epoch J2000.0 + SunAtTime(num_t jde) : jde(jde), t((jde - 2451545) / 36525.0) {} num_t mean_obliquity() const { // eq. 22.2, p. 147; mean obliquity of the ecliptic diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index cd3081998b..7682c083a5 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -196,10 +196,8 @@ uint32_t Nameable::get_object_id_hash() { return this->object_id_hash_; } bool Nameable::is_disabled_by_default() const { return this->disabled_by_default_; } void Nameable::set_disabled_by_default(bool disabled_by_default) { this->disabled_by_default_ = disabled_by_default; } -WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component) { - component_ = component; - started_ = millis(); -} +WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component) + : started_(millis()), component_(component) {} WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() { uint32_t now = millis(); if (now - started_ > 50) { diff --git a/esphome/core/preferences.cpp b/esphome/core/preferences.cpp index 68030a2d59..bfc5d9f875 100644 --- a/esphome/core/preferences.cpp +++ b/esphome/core/preferences.cpp @@ -20,7 +20,7 @@ static const char *const TAG = "preferences"; ESPPreferenceObject::ESPPreferenceObject() : offset_(0), length_words_(0), type_(0), data_(nullptr) {} ESPPreferenceObject::ESPPreferenceObject(size_t offset, size_t length, uint32_t type) : offset_(offset), length_words_(length), type_(type) { - this->data_ = new uint32_t[this->length_words_ + 1]; + this->data_ = new uint32_t[this->length_words_ + 1]; // NOLINT(cppcoreguidelines-prefer-member-initializer) for (uint32_t i = 0; i < this->length_words_ + 1; i++) this->data_[i] = 0; } @@ -69,7 +69,7 @@ static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) { if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) { return false; } - *dest = ESP_RTC_USER_MEM[index]; + *dest = ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr) return true; } @@ -83,7 +83,7 @@ static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) { return false; } - auto *ptr = &ESP_RTC_USER_MEM[index]; + auto *ptr = &ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr) *ptr = value; return true; } From 3aa107142bdca8a9d1b54ea9073033dfcfaf99d6 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 09:37:11 +0200 Subject: [PATCH 1305/1841] Only try compat parsing after regular parsing fails (#2269) --- esphome/__main__.py | 146 +++++++++++++++++++++++--------------------- 1 file changed, 78 insertions(+), 68 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 8d6f2b8f89..97b2988c2e 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -483,75 +483,9 @@ def parse_args(argv): metavar=("key", "value"), ) - # Keep backward compatibility with the old command line format of - # esphome . - # - # Unfortunately this can't be done by adding another configuration argument to the - # main config parser, as argparse is greedy when parsing arguments, so in regular - # usage it'll eat the command as the configuration argument and error out out - # because it can't parse the configuration as a command. - # - # Instead, construct an ad-hoc parser for the old format that doesn't actually - # process the arguments, but parses them enough to let us figure out if the old - # format is used. In that case, swap the command and configuration in the arguments - # and continue on with the normal parser (after raising a deprecation warning). - # - # Disable argparse's built-in help option and add it manually to prevent this - # parser from printing the help messagefor the old format when invoked with -h. - compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False) - compat_parser.add_argument("-h", "--help") - compat_parser.add_argument("configuration", nargs="*") - compat_parser.add_argument( - "command", - choices=[ - "config", - "compile", - "upload", - "logs", - "run", - "clean-mqtt", - "wizard", - "mqtt-fingerprint", - "version", - "clean", - "dashboard", - "vscode", - "update-all", - ], - ) - - # on Python 3.9+ we can simply set exit_on_error=False in the constructor - def _raise(x): - raise argparse.ArgumentError(None, x) - - compat_parser.error = _raise - - deprecated_argv_suggestion = None - - if ["dashboard", "config"] == argv[1:3] or ["version"] == argv[1:2]: - # this is most likely meant in new-style arg format. do not try compat parsing - pass - else: - try: - result, unparsed = compat_parser.parse_known_args(argv[1:]) - last_option = len(argv) - len(unparsed) - 1 - len(result.configuration) - unparsed = [ - "--device" if arg in ("--upload-port", "--serial-port") else arg - for arg in unparsed - ] - argv = ( - argv[0:last_option] + [result.command] + result.configuration + unparsed - ) - deprecated_argv_suggestion = argv - except argparse.ArgumentError: - # This is not an old-style command line, so we don't have to do anything. - pass - - # And continue on with regular parsing parser = argparse.ArgumentParser( description=f"ESPHome v{const.__version__}", parents=[options_parser] ) - parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion) mqtt_options = argparse.ArgumentParser(add_help=False) mqtt_options.add_argument("--topic", help="Manually set the MQTT topic.") @@ -701,7 +635,83 @@ def parse_args(argv): "configuration", help="Your YAML configuration file directories.", nargs="+" ) - return parser.parse_args(argv[1:]) + # Keep backward compatibility with the old command line format of + # esphome . + # + # Unfortunately this can't be done by adding another configuration argument to the + # main config parser, as argparse is greedy when parsing arguments, so in regular + # usage it'll eat the command as the configuration argument and error out out + # because it can't parse the configuration as a command. + # + # Instead, if parsing using the current format fails, construct an ad-hoc parser + # that doesn't actually process the arguments, but parses them enough to let us + # figure out if the old format is used. In that case, swap the command and + # configuration in the arguments and retry with the normal parser (and raise + # a deprecation warning). + arguments = argv[1:] + + # On Python 3.9+ we can simply set exit_on_error=False in the constructor + def _raise(x): + raise argparse.ArgumentError(None, x) + + # First, try new-style parsing, but don't exit in case of failure + try: + # duplicate parser so that we can use the original one to raise errors later on + current_parser = argparse.ArgumentParser(add_help=False, parents=[parser]) + current_parser.set_defaults(deprecated_argv_suggestion=None) + current_parser.error = _raise + return current_parser.parse_args(arguments) + except argparse.ArgumentError: + pass + + # Second, try compat parsing and rearrange the command-line if it succeeds + # Disable argparse's built-in help option and add it manually to prevent this + # parser from printing the help messagefor the old format when invoked with -h. + compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False) + compat_parser.add_argument("-h", "--help", action="store_true") + compat_parser.add_argument("configuration", nargs="*") + compat_parser.add_argument( + "command", + choices=[ + "config", + "compile", + "upload", + "logs", + "run", + "clean-mqtt", + "wizard", + "mqtt-fingerprint", + "version", + "clean", + "dashboard", + "vscode", + "update-all", + ], + ) + + try: + compat_parser.error = _raise + result, unparsed = compat_parser.parse_known_args(argv[1:]) + last_option = len(arguments) - len(unparsed) - 1 - len(result.configuration) + unparsed = [ + "--device" if arg in ("--upload-port", "--serial-port") else arg + for arg in unparsed + ] + arguments = ( + arguments[0:last_option] + + [result.command] + + result.configuration + + unparsed + ) + deprecated_argv_suggestion = arguments + except argparse.ArgumentError: + # old-style parsing failed, don't suggest any argument + deprecated_argv_suggestion = None + + # Finally, run the new-style parser again with the possibly swapped arguments, + # and let it error out if the command is unparsable. + parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion) + return parser.parse_args(arguments) def run_esphome(argv): @@ -715,7 +725,7 @@ def run_esphome(argv): "and will be removed in the future. " ) _LOGGER.warning("Please instead use:") - _LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion[1:])) + _LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion)) if sys.version_info < (3, 7, 0): _LOGGER.error( From e18dfdd6569a22ef16c5439b8e29404f4e2dc62b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 09:39:18 +0200 Subject: [PATCH 1306/1841] Suppress excessive warnings about deprecated Fan interfaces (#2270) --- esphome/components/api/api_connection.cpp | 5 ++++- esphome/components/fan/fan_helpers.cpp | 7 ++++--- esphome/components/fan/fan_helpers.h | 8 ++++++++ esphome/components/fan/fan_state.cpp | 2 ++ esphome/components/mqtt/mqtt_fan.cpp | 1 + esphome/components/web_server/web_server.cpp | 1 + 6 files changed, 20 insertions(+), 4 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 650f4f6f6e..ea3be42275 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -214,6 +214,9 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) { #endif #ifdef USE_FAN +// Shut-up about usage of deprecated speed_level_to_enum/speed_enum_to_level functions for a bit. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" bool APIConnection::send_fan_state(fan::FanState *fan) { if (!this->state_subscription_) return false; @@ -262,13 +265,13 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { // Prefer level call.set_speed(msg.speed_level); } else if (msg.has_speed) { - // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) call.set_speed(fan::speed_enum_to_level(static_cast(msg.speed), traits.supported_speed_count())); } if (msg.has_direction) call.set_direction(static_cast(msg.direction)); call.perform(); } +#pragma GCC diagnostic pop #endif #ifdef USE_LIGHT diff --git a/esphome/components/fan/fan_helpers.cpp b/esphome/components/fan/fan_helpers.cpp index 5d923a1b15..34883617e6 100644 --- a/esphome/components/fan/fan_helpers.cpp +++ b/esphome/components/fan/fan_helpers.cpp @@ -4,14 +4,15 @@ namespace esphome { namespace fan { -// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) +// This whole file is deprecated, don't warn about usage of deprecated types in here. +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels) { const auto speed_ratio = static_cast(speed_level) / (supported_speed_levels + 1); const auto legacy_level = clamp(static_cast(ceilf(speed_ratio * 3)), 1, 3); - return static_cast(legacy_level - 1); // NOLINT(clang-diagnostic-deprecated-declarations) + return static_cast(legacy_level - 1); } -// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) int speed_enum_to_level(FanSpeed speed, int supported_speed_levels) { const auto enum_level = static_cast(speed) + 1; const auto speed_level = roundf(enum_level / 3.0f * supported_speed_levels); diff --git a/esphome/components/fan/fan_helpers.h b/esphome/components/fan/fan_helpers.h index 138aa5bca3..009505601e 100644 --- a/esphome/components/fan/fan_helpers.h +++ b/esphome/components/fan/fan_helpers.h @@ -4,8 +4,16 @@ namespace esphome { namespace fan { +// Shut-up about usage of deprecated FanSpeed for a bit. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +ESPDEPRECATED("FanSpeed and speed_level_to_enum() are deprecated.", "2021.9") FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels); +ESPDEPRECATED("FanSpeed and speed_enum_to_level() are deprecated.", "2021.9") int speed_enum_to_level(FanSpeed speed, int supported_speed_levels); +#pragma GCC diagnostic pop + } // namespace fan } // namespace esphome diff --git a/esphome/components/fan/fan_state.cpp b/esphome/components/fan/fan_state.cpp index a4883c5e2c..a57115beb4 100644 --- a/esphome/components/fan/fan_state.cpp +++ b/esphome/components/fan/fan_state.cpp @@ -67,6 +67,8 @@ void FanStateCall::perform() const { this->state_->state_callback_.call(); } +// This whole method is deprecated, don't warn about usage of deprecated methods inside of it. +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" FanStateCall &FanStateCall::set_speed(const char *legacy_speed) { const auto supported_speed_count = this->state_->get_traits().supported_speed_count(); if (strcasecmp(legacy_speed, "low") == 0) { diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index ba9121bc5d..b8eecf0ff3 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -100,6 +100,7 @@ bool MQTTFanComponent::publish_state() { auto traits = this->state_->get_traits(); if (traits.supports_speed()) { const char *payload; + // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) switch (fan::speed_level_to_enum(this->state_->speed, traits.supported_speed_count())) { case FAN_SPEED_LOW: { // NOLINT(clang-diagnostic-deprecated-declarations) payload = "low"; diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 56c75a1c58..dc97bcd5c2 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -397,6 +397,7 @@ std::string WebServer::fan_json(fan::FanState *obj) { const auto traits = obj->get_traits(); if (traits.supports_speed()) { root["speed_level"] = obj->speed; + // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) switch (fan::speed_level_to_enum(obj->speed, traits.supported_speed_count())) { case fan::FAN_SPEED_LOW: // NOLINT(clang-diagnostic-deprecated-declarations) root["speed"] = "low"; From d594a6fcbca53472b58649fcc13d897fb3edd3e4 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 09:48:52 +0200 Subject: [PATCH 1307/1841] Store strings only used for logging in flash (#2274) Co-authored-by: Otto winter --- .../components/binary_sensor/binary_sensor.h | 2 +- esphome/components/climate/climate.cpp | 44 +++++---- esphome/components/climate/climate.h | 2 +- esphome/components/climate/climate_mode.cpp | 98 +++++++++---------- esphome/components/climate/climate_mode.h | 11 ++- esphome/components/cover/cover.h | 2 +- .../components/gpio/switch/gpio_switch.cpp | 16 +-- .../hitachi_ac344/hitachi_ac344.cpp | 2 +- .../hitachi_ac424/hitachi_ac424.cpp | 2 +- esphome/components/light/light_call.cpp | 33 ++++--- esphome/components/mqtt/mqtt_client.cpp | 24 ++--- esphome/components/number/number.h | 2 +- esphome/components/select/select.h | 2 +- esphome/components/sensor/sensor.cpp | 8 +- esphome/components/sensor/sensor.h | 7 +- esphome/components/switch/switch.h | 2 +- esphome/components/text_sensor/text_sensor.h | 2 +- esphome/components/uart/uart.cpp | 14 +-- esphome/components/uart/uart.h | 3 +- esphome/components/uart/uart_esp8266.cpp | 2 +- esphome/components/wifi/wifi_component.cpp | 67 ++++++------- .../wifi/wifi_component_esp8266.cpp | 38 +++---- esphome/core/esphal.cpp | 69 +++++-------- esphome/core/esphal.h | 7 +- esphome/core/log.h | 33 ++++--- 25 files changed, 241 insertions(+), 251 deletions(-) diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index 62e8031cb5..195badd798 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -10,7 +10,7 @@ namespace binary_sensor { #define LOG_BINARY_SENSOR(prefix, type, obj) \ if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ if (!(obj)->get_device_class().empty()) { \ ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ } \ diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 4861e7b8cb..383103b014 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -9,8 +9,8 @@ void ClimateCall::perform() { ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); this->validate_(); if (this->mode_.has_value()) { - const char *mode_s = climate_mode_to_string(*this->mode_); - ESP_LOGD(TAG, " Mode: %s", mode_s); + const LogString *mode_s = climate_mode_to_string(*this->mode_); + ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(mode_s)); } if (this->custom_fan_mode_.has_value()) { this->fan_mode_.reset(); @@ -18,8 +18,8 @@ void ClimateCall::perform() { } if (this->fan_mode_.has_value()) { this->custom_fan_mode_.reset(); - const char *fan_mode_s = climate_fan_mode_to_string(*this->fan_mode_); - ESP_LOGD(TAG, " Fan: %s", fan_mode_s); + const LogString *fan_mode_s = climate_fan_mode_to_string(*this->fan_mode_); + ESP_LOGD(TAG, " Fan: %s", LOG_STR_ARG(fan_mode_s)); } if (this->custom_preset_.has_value()) { this->preset_.reset(); @@ -27,12 +27,12 @@ void ClimateCall::perform() { } if (this->preset_.has_value()) { this->custom_preset_.reset(); - const char *preset_s = climate_preset_to_string(*this->preset_); - ESP_LOGD(TAG, " Preset: %s", preset_s); + const LogString *preset_s = climate_preset_to_string(*this->preset_); + ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(preset_s)); } if (this->swing_mode_.has_value()) { - const char *swing_mode_s = climate_swing_mode_to_string(*this->swing_mode_); - ESP_LOGD(TAG, " Swing: %s", swing_mode_s); + const LogString *swing_mode_s = climate_swing_mode_to_string(*this->swing_mode_); + ESP_LOGD(TAG, " Swing: %s", LOG_STR_ARG(swing_mode_s)); } if (this->target_temperature_.has_value()) { ESP_LOGD(TAG, " Target Temperature: %.2f", *this->target_temperature_); @@ -50,7 +50,7 @@ void ClimateCall::validate_() { if (this->mode_.has_value()) { auto mode = *this->mode_; if (!traits.supports_mode(mode)) { - ESP_LOGW(TAG, " Mode %s is not supported by this device!", climate_mode_to_string(mode)); + ESP_LOGW(TAG, " Mode %s is not supported by this device!", LOG_STR_ARG(climate_mode_to_string(mode))); this->mode_.reset(); } } @@ -63,7 +63,8 @@ void ClimateCall::validate_() { } else if (this->fan_mode_.has_value()) { auto fan_mode = *this->fan_mode_; if (!traits.supports_fan_mode(fan_mode)) { - ESP_LOGW(TAG, " Fan Mode %s is not supported by this device!", climate_fan_mode_to_string(fan_mode)); + ESP_LOGW(TAG, " Fan Mode %s is not supported by this device!", + LOG_STR_ARG(climate_fan_mode_to_string(fan_mode))); this->fan_mode_.reset(); } } @@ -76,14 +77,15 @@ void ClimateCall::validate_() { } else if (this->preset_.has_value()) { auto preset = *this->preset_; if (!traits.supports_preset(preset)) { - ESP_LOGW(TAG, " Preset %s is not supported by this device!", climate_preset_to_string(preset)); + ESP_LOGW(TAG, " Preset %s is not supported by this device!", LOG_STR_ARG(climate_preset_to_string(preset))); this->preset_.reset(); } } if (this->swing_mode_.has_value()) { auto swing_mode = *this->swing_mode_; if (!traits.supports_swing_mode(swing_mode)) { - ESP_LOGW(TAG, " Swing Mode %s is not supported by this device!", climate_swing_mode_to_string(swing_mode)); + ESP_LOGW(TAG, " Swing Mode %s is not supported by this device!", + LOG_STR_ARG(climate_swing_mode_to_string(swing_mode))); this->swing_mode_.reset(); } } @@ -373,24 +375,24 @@ void Climate::publish_state() { ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str()); auto traits = this->get_traits(); - ESP_LOGD(TAG, " Mode: %s", climate_mode_to_string(this->mode)); + ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode))); if (traits.get_supports_action()) { - ESP_LOGD(TAG, " Action: %s", climate_action_to_string(this->action)); + ESP_LOGD(TAG, " Action: %s", LOG_STR_ARG(climate_action_to_string(this->action))); } if (traits.get_supports_fan_modes() && this->fan_mode.has_value()) { - ESP_LOGD(TAG, " Fan Mode: %s", climate_fan_mode_to_string(this->fan_mode.value())); + ESP_LOGD(TAG, " Fan Mode: %s", LOG_STR_ARG(climate_fan_mode_to_string(this->fan_mode.value()))); } if (!traits.get_supported_custom_fan_modes().empty() && this->custom_fan_mode.has_value()) { ESP_LOGD(TAG, " Custom Fan Mode: %s", this->custom_fan_mode.value().c_str()); } if (traits.get_supports_presets() && this->preset.has_value()) { - ESP_LOGD(TAG, " Preset: %s", climate_preset_to_string(this->preset.value())); + ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(climate_preset_to_string(this->preset.value()))); } if (!traits.get_supported_custom_presets().empty() && this->custom_preset.has_value()) { ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset.value().c_str()); } if (traits.get_supports_swing_modes()) { - ESP_LOGD(TAG, " Swing Mode: %s", climate_swing_mode_to_string(this->swing_mode)); + ESP_LOGD(TAG, " Swing Mode: %s", LOG_STR_ARG(climate_swing_mode_to_string(this->swing_mode))); } if (traits.get_supports_current_temperature()) { ESP_LOGD(TAG, " Current Temperature: %.2f°C", this->current_temperature); @@ -534,12 +536,12 @@ void Climate::dump_traits_(const char *tag) { if (!traits.get_supported_modes().empty()) { ESP_LOGCONFIG(tag, " [x] Supported modes:"); for (ClimateMode m : traits.get_supported_modes()) - ESP_LOGCONFIG(tag, " - %s", climate_mode_to_string(m)); + ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_mode_to_string(m))); } if (!traits.get_supported_fan_modes().empty()) { ESP_LOGCONFIG(tag, " [x] Supported fan modes:"); for (ClimateFanMode m : traits.get_supported_fan_modes()) - ESP_LOGCONFIG(tag, " - %s", climate_fan_mode_to_string(m)); + ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_fan_mode_to_string(m))); } if (!traits.get_supported_custom_fan_modes().empty()) { ESP_LOGCONFIG(tag, " [x] Supported custom fan modes:"); @@ -549,7 +551,7 @@ void Climate::dump_traits_(const char *tag) { if (!traits.get_supported_presets().empty()) { ESP_LOGCONFIG(tag, " [x] Supported presets:"); for (ClimatePreset p : traits.get_supported_presets()) - ESP_LOGCONFIG(tag, " - %s", climate_preset_to_string(p)); + ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_preset_to_string(p))); } if (!traits.get_supported_custom_presets().empty()) { ESP_LOGCONFIG(tag, " [x] Supported custom presets:"); @@ -559,7 +561,7 @@ void Climate::dump_traits_(const char *tag) { if (!traits.get_supported_swing_modes().empty()) { ESP_LOGCONFIG(tag, " [x] Supported swing modes:"); for (ClimateSwingMode m : traits.get_supported_swing_modes()) - ESP_LOGCONFIG(tag, " - %s", climate_swing_mode_to_string(m)); + ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_swing_mode_to_string(m))); } } diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 46d0fb1d77..418db02485 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -12,7 +12,7 @@ namespace climate { #define LOG_CLIMATE(prefix, type, obj) \ if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ } class Climate; diff --git a/esphome/components/climate/climate_mode.cpp b/esphome/components/climate/climate_mode.cpp index 7a626942eb..e46159a750 100644 --- a/esphome/components/climate/climate_mode.cpp +++ b/esphome/components/climate/climate_mode.cpp @@ -3,105 +3,105 @@ namespace esphome { namespace climate { -const char *climate_mode_to_string(ClimateMode mode) { +const LogString *climate_mode_to_string(ClimateMode mode) { switch (mode) { case CLIMATE_MODE_OFF: - return "OFF"; - case CLIMATE_MODE_AUTO: - return "AUTO"; - case CLIMATE_MODE_COOL: - return "COOL"; - case CLIMATE_MODE_HEAT: - return "HEAT"; - case CLIMATE_MODE_FAN_ONLY: - return "FAN_ONLY"; - case CLIMATE_MODE_DRY: - return "DRY"; + return LOG_STR("OFF"); case CLIMATE_MODE_HEAT_COOL: - return "HEAT_COOL"; + return LOG_STR("HEAT_COOL"); + case CLIMATE_MODE_AUTO: + return LOG_STR("AUTO"); + case CLIMATE_MODE_COOL: + return LOG_STR("COOL"); + case CLIMATE_MODE_HEAT: + return LOG_STR("HEAT"); + case CLIMATE_MODE_FAN_ONLY: + return LOG_STR("FAN_ONLY"); + case CLIMATE_MODE_DRY: + return LOG_STR("DRY"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } -const char *climate_action_to_string(ClimateAction action) { +const LogString *climate_action_to_string(ClimateAction action) { switch (action) { case CLIMATE_ACTION_OFF: - return "OFF"; + return LOG_STR("OFF"); case CLIMATE_ACTION_COOLING: - return "COOLING"; + return LOG_STR("COOLING"); case CLIMATE_ACTION_HEATING: - return "HEATING"; + return LOG_STR("HEATING"); case CLIMATE_ACTION_IDLE: - return "IDLE"; + return LOG_STR("IDLE"); case CLIMATE_ACTION_DRYING: - return "DRYING"; + return LOG_STR("DRYING"); case CLIMATE_ACTION_FAN: - return "FAN"; + return LOG_STR("FAN"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } -const char *climate_fan_mode_to_string(ClimateFanMode fan_mode) { +const LogString *climate_fan_mode_to_string(ClimateFanMode fan_mode) { switch (fan_mode) { case climate::CLIMATE_FAN_ON: - return "ON"; + return LOG_STR("ON"); case climate::CLIMATE_FAN_OFF: - return "OFF"; + return LOG_STR("OFF"); case climate::CLIMATE_FAN_AUTO: - return "AUTO"; + return LOG_STR("AUTO"); case climate::CLIMATE_FAN_LOW: - return "LOW"; + return LOG_STR("LOW"); case climate::CLIMATE_FAN_MEDIUM: - return "MEDIUM"; + return LOG_STR("MEDIUM"); case climate::CLIMATE_FAN_HIGH: - return "HIGH"; + return LOG_STR("HIGH"); case climate::CLIMATE_FAN_MIDDLE: - return "MIDDLE"; + return LOG_STR("MIDDLE"); case climate::CLIMATE_FAN_FOCUS: - return "FOCUS"; + return LOG_STR("FOCUS"); case climate::CLIMATE_FAN_DIFFUSE: - return "DIFFUSE"; + return LOG_STR("DIFFUSE"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } -const char *climate_swing_mode_to_string(ClimateSwingMode swing_mode) { +const LogString *climate_swing_mode_to_string(ClimateSwingMode swing_mode) { switch (swing_mode) { case climate::CLIMATE_SWING_OFF: - return "OFF"; + return LOG_STR("OFF"); case climate::CLIMATE_SWING_BOTH: - return "BOTH"; + return LOG_STR("BOTH"); case climate::CLIMATE_SWING_VERTICAL: - return "VERTICAL"; + return LOG_STR("VERTICAL"); case climate::CLIMATE_SWING_HORIZONTAL: - return "HORIZONTAL"; + return LOG_STR("HORIZONTAL"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } -const char *climate_preset_to_string(ClimatePreset preset) { +const LogString *climate_preset_to_string(ClimatePreset preset) { switch (preset) { case climate::CLIMATE_PRESET_NONE: - return "NONE"; + return LOG_STR("NONE"); case climate::CLIMATE_PRESET_HOME: - return "HOME"; + return LOG_STR("HOME"); case climate::CLIMATE_PRESET_ECO: - return "ECO"; + return LOG_STR("ECO"); case climate::CLIMATE_PRESET_AWAY: - return "AWAY"; + return LOG_STR("AWAY"); case climate::CLIMATE_PRESET_BOOST: - return "BOOST"; + return LOG_STR("BOOST"); case climate::CLIMATE_PRESET_COMFORT: - return "COMFORT"; + return LOG_STR("COMFORT"); case climate::CLIMATE_PRESET_SLEEP: - return "SLEEP"; + return LOG_STR("SLEEP"); case climate::CLIMATE_PRESET_ACTIVITY: - return "ACTIVITY"; + return LOG_STR("ACTIVITY"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } diff --git a/esphome/components/climate/climate_mode.h b/esphome/components/climate/climate_mode.h index 476cf5bd84..3e5626919c 100644 --- a/esphome/components/climate/climate_mode.h +++ b/esphome/components/climate/climate_mode.h @@ -1,6 +1,7 @@ #pragma once #include +#include "esphome/core/log.h" namespace esphome { namespace climate { @@ -96,19 +97,19 @@ enum ClimatePreset : uint8_t { }; /// Convert the given ClimateMode to a human-readable string. -const char *climate_mode_to_string(ClimateMode mode); +const LogString *climate_mode_to_string(ClimateMode mode); /// Convert the given ClimateAction to a human-readable string. -const char *climate_action_to_string(ClimateAction action); +const LogString *climate_action_to_string(ClimateAction action); /// Convert the given ClimateFanMode to a human-readable string. -const char *climate_fan_mode_to_string(ClimateFanMode mode); +const LogString *climate_fan_mode_to_string(ClimateFanMode mode); /// Convert the given ClimateSwingMode to a human-readable string. -const char *climate_swing_mode_to_string(ClimateSwingMode mode); +const LogString *climate_swing_mode_to_string(ClimateSwingMode mode); /// Convert the given ClimateSwingMode to a human-readable string. -const char *climate_preset_to_string(ClimatePreset preset); +const LogString *climate_preset_to_string(ClimatePreset preset); } // namespace climate } // namespace esphome diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index 77a53f11c5..72ec15a459 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -13,7 +13,7 @@ const extern float COVER_CLOSED; #define LOG_COVER(prefix, type, obj) \ if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ auto traits_ = (obj)->get_traits(); \ if (traits_.get_is_assumed_state()) { \ ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \ diff --git a/esphome/components/gpio/switch/gpio_switch.cpp b/esphome/components/gpio/switch/gpio_switch.cpp index 410a3818d6..56e0087eae 100644 --- a/esphome/components/gpio/switch/gpio_switch.cpp +++ b/esphome/components/gpio/switch/gpio_switch.cpp @@ -47,28 +47,28 @@ void GPIOSwitch::setup() { void GPIOSwitch::dump_config() { LOG_SWITCH("", "GPIO Switch", this); LOG_PIN(" Pin: ", this->pin_); - const char *restore_mode = ""; + const LogString *restore_mode = LOG_STR(""); switch (this->restore_mode_) { case GPIO_SWITCH_RESTORE_DEFAULT_OFF: - restore_mode = "Restore (Defaults to OFF)"; + restore_mode = LOG_STR("Restore (Defaults to OFF)"); break; case GPIO_SWITCH_RESTORE_DEFAULT_ON: - restore_mode = "Restore (Defaults to ON)"; + restore_mode = LOG_STR("Restore (Defaults to ON)"); break; case GPIO_SWITCH_RESTORE_INVERTED_DEFAULT_ON: - restore_mode = "Restore inverted (Defaults to ON)"; + restore_mode = LOG_STR("Restore inverted (Defaults to ON)"); break; case GPIO_SWITCH_RESTORE_INVERTED_DEFAULT_OFF: - restore_mode = "Restore inverted (Defaults to OFF)"; + restore_mode = LOG_STR("Restore inverted (Defaults to OFF)"); break; case GPIO_SWITCH_ALWAYS_OFF: - restore_mode = "Always OFF"; + restore_mode = LOG_STR("Always OFF"); break; case GPIO_SWITCH_ALWAYS_ON: - restore_mode = "Always ON"; + restore_mode = LOG_STR("Always ON"); break; } - ESP_LOGCONFIG(TAG, " Restore Mode: %s", restore_mode); + ESP_LOGCONFIG(TAG, " Restore Mode: %s", LOG_STR_ARG(restore_mode)); if (!this->interlock_.empty()) { ESP_LOGCONFIG(TAG, " Interlocks:"); for (auto *lock : this->interlock_) { diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.cpp b/esphome/components/hitachi_ac344/hitachi_ac344.cpp index 1e5bca1396..067ea39d07 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.cpp +++ b/esphome/components/hitachi_ac344/hitachi_ac344.cpp @@ -165,7 +165,7 @@ void HitachiClimate::transmit_state() { set_power_(false); break; default: - ESP_LOGW(TAG, "Unsupported mode: %s", climate_mode_to_string(this->mode)); + ESP_LOGW(TAG, "Unsupported mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode))); } set_temp_(static_cast(this->target_temperature)); diff --git a/esphome/components/hitachi_ac424/hitachi_ac424.cpp b/esphome/components/hitachi_ac424/hitachi_ac424.cpp index 10b83cbd58..2e5423a37a 100644 --- a/esphome/components/hitachi_ac424/hitachi_ac424.cpp +++ b/esphome/components/hitachi_ac424/hitachi_ac424.cpp @@ -166,7 +166,7 @@ void HitachiClimate::transmit_state() { set_power_(false); break; default: - ESP_LOGW(TAG, "Unsupported mode: %s", climate_mode_to_string(this->mode)); + ESP_LOGW(TAG, "Unsupported mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode))); } set_temp_(static_cast(this->target_temperature)); diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index d979b13368..178a78a94c 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -7,24 +7,24 @@ namespace light { static const char *const TAG = "light"; -static const char *color_mode_to_human(ColorMode color_mode) { +static const LogString *color_mode_to_human(ColorMode color_mode) { if (color_mode == ColorMode::UNKNOWN) - return "Unknown"; + return LOG_STR("Unknown"); if (color_mode == ColorMode::WHITE) - return "White"; + return LOG_STR("White"); if (color_mode == ColorMode::COLOR_TEMPERATURE) - return "Color temperature"; + return LOG_STR("Color temperature"); if (color_mode == ColorMode::COLD_WARM_WHITE) - return "Cold/warm white"; + return LOG_STR("Cold/warm white"); if (color_mode == ColorMode::RGB) - return "RGB"; + return LOG_STR("RGB"); if (color_mode == ColorMode::RGB_WHITE) - return "RGBW"; + return LOG_STR("RGBW"); if (color_mode == ColorMode::RGB_COLD_WARM_WHITE) - return "RGB + cold/warm white"; + return LOG_STR("RGB + cold/warm white"); if (color_mode == ColorMode::RGB_COLOR_TEMPERATURE) - return "RGB + color temperature"; - return ""; + return LOG_STR("RGB + color temperature"); + return LOG_STR(""); } void LightCall::perform() { @@ -37,7 +37,7 @@ void LightCall::perform() { // Only print color mode when it's being changed ColorMode current_color_mode = this->parent_->remote_values.get_color_mode(); if (this->color_mode_.value_or(current_color_mode) != current_color_mode) { - ESP_LOGD(TAG, " Color mode: %s", color_mode_to_human(v.get_color_mode())); + ESP_LOGD(TAG, " Color mode: %s", LOG_STR_ARG(color_mode_to_human(v.get_color_mode()))); } // Only print state when it's being changed @@ -135,7 +135,7 @@ LightColorValues LightCall::validate_() { // Color mode check if (this->color_mode_.has_value() && !traits.supports_color_mode(this->color_mode_.value())) { ESP_LOGW(TAG, "'%s' - This light does not support color mode %s!", name, - color_mode_to_human(this->color_mode_.value())); + LOG_STR_ARG(color_mode_to_human(this->color_mode_.value()))); this->color_mode_.reset(); } @@ -206,7 +206,8 @@ LightColorValues LightCall::validate_() { if (name_##_.has_value()) { \ auto val = *name_##_; \ if (val < (min) || val > (max)) { \ - ESP_LOGW(TAG, "'%s' - %s value %.2f is out of range [%.1f - %.1f]!", name, upper_name, val, (min), (max)); \ + ESP_LOGW(TAG, "'%s' - %s value %.2f is out of range [%.1f - %.1f]!", name, LOG_STR_LITERAL(upper_name), val, \ + (min), (max)); \ name_##_ = clamp(val, (min), (max)); \ } \ } @@ -387,7 +388,7 @@ ColorMode LightCall::compute_color_mode_() { // Don't change if the current mode is suitable. if (suitable_modes.count(current_mode) > 0) { ESP_LOGI(TAG, "'%s' - Keeping current color mode %s for call without color mode.", - this->parent_->get_name().c_str(), color_mode_to_human(current_mode)); + this->parent_->get_name().c_str(), LOG_STR_ARG(color_mode_to_human(current_mode))); return current_mode; } @@ -397,7 +398,7 @@ ColorMode LightCall::compute_color_mode_() { continue; ESP_LOGI(TAG, "'%s' - Using color mode %s for call without color mode.", this->parent_->get_name().c_str(), - color_mode_to_human(mode)); + LOG_STR_ARG(color_mode_to_human(mode))); return mode; } @@ -405,7 +406,7 @@ ColorMode LightCall::compute_color_mode_() { // out whatever we don't support. auto color_mode = current_mode != ColorMode::UNKNOWN ? current_mode : *supported_modes.begin(); ESP_LOGW(TAG, "'%s' - No color mode suitable for this call supported, defaulting to %s!", - this->parent_->get_name().c_str(), color_mode_to_human(color_mode)); + this->parent_->get_name().c_str(), LOG_STR_ARG(color_mode_to_human(color_mode))); return color_mode; } std::set LightCall::get_suitable_color_modes_() { diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index be2176df09..129597cc00 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -221,40 +221,40 @@ void MQTTClientComponent::check_connected() { void MQTTClientComponent::loop() { if (this->disconnect_reason_.has_value()) { - const char *reason_s = nullptr; + const LogString *reason_s; switch (*this->disconnect_reason_) { case AsyncMqttClientDisconnectReason::TCP_DISCONNECTED: - reason_s = "TCP disconnected"; + reason_s = LOG_STR("TCP disconnected"); break; case AsyncMqttClientDisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION: - reason_s = "Unacceptable Protocol Version"; + reason_s = LOG_STR("Unacceptable Protocol Version"); break; case AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED: - reason_s = "Identifier Rejected"; + reason_s = LOG_STR("Identifier Rejected"); break; case AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE: - reason_s = "Server Unavailable"; + reason_s = LOG_STR("Server Unavailable"); break; case AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS: - reason_s = "Malformed Credentials"; + reason_s = LOG_STR("Malformed Credentials"); break; case AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED: - reason_s = "Not Authorized"; + reason_s = LOG_STR("Not Authorized"); break; case AsyncMqttClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE: - reason_s = "Not Enough Space"; + reason_s = LOG_STR("Not Enough Space"); break; case AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT: - reason_s = "TLS Bad Fingerprint"; + reason_s = LOG_STR("TLS Bad Fingerprint"); break; default: - reason_s = "Unknown"; + reason_s = LOG_STR("Unknown"); break; } if (!network_is_connected()) { - reason_s = "WiFi disconnected"; + reason_s = LOG_STR("WiFi disconnected"); } - ESP_LOGW(TAG, "MQTT Disconnected: %s.", reason_s); + ESP_LOGW(TAG, "MQTT Disconnected: %s.", LOG_STR_ARG(reason_s)); this->disconnect_reason_.reset(); } diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index e32b53187b..945f174510 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -8,7 +8,7 @@ namespace number { #define LOG_NUMBER(prefix, type, obj) \ if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ if (!(obj)->traits.get_icon().empty()) { \ ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->traits.get_icon().c_str()); \ } \ diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index 414a8daabb..0dec74b627 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -10,7 +10,7 @@ namespace select { #define LOG_SELECT(prefix, type, obj) \ if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ if (!(obj)->traits.get_icon().empty()) { \ ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->traits.get_icon().c_str()); \ } \ diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 1dbc1c901a..34f72a4508 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -6,15 +6,15 @@ namespace sensor { static const char *const TAG = "sensor"; -const char *state_class_to_string(StateClass state_class) { +const LogString *state_class_to_string(StateClass state_class) { switch (state_class) { case STATE_CLASS_MEASUREMENT: - return "measurement"; + return LOG_STR("measurement"); case STATE_CLASS_TOTAL_INCREASING: - return "total_increasing"; + return LOG_STR("total_increasing"); case STATE_CLASS_NONE: default: - return ""; + return LOG_STR(""); } } diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 34b8b26a54..6322c12027 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -1,5 +1,6 @@ #pragma once +#include "esphome/core/log.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/components/sensor/filter.h" @@ -9,11 +10,11 @@ namespace sensor { #define LOG_SENSOR(prefix, type, obj) \ if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ if (!(obj)->get_device_class().empty()) { \ ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ } \ - ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->state_class)); \ + ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, LOG_STR_ARG(state_class_to_string((obj)->state_class))); \ ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \ ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \ if (!(obj)->get_icon().empty()) { \ @@ -36,7 +37,7 @@ enum StateClass : uint8_t { STATE_CLASS_TOTAL_INCREASING = 2, }; -const char *state_class_to_string(StateClass state_class); +const LogString *state_class_to_string(StateClass state_class); /** Base-class for all sensors. * diff --git a/esphome/components/switch/switch.h b/esphome/components/switch/switch.h index 966119a29f..8cfae3b6f8 100644 --- a/esphome/components/switch/switch.h +++ b/esphome/components/switch/switch.h @@ -9,7 +9,7 @@ namespace switch_ { #define LOG_SWITCH(prefix, type, obj) \ if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ if (!(obj)->get_icon().empty()) { \ ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ } \ diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index 5c6e5be51a..5293f0d216 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -8,7 +8,7 @@ namespace text_sensor { #define LOG_TEXT_SENSOR(prefix, type, obj) \ if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ if (!(obj)->get_icon().empty()) { \ ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ } \ diff --git a/esphome/components/uart/uart.cpp b/esphome/components/uart/uart.cpp index 08e3395a7a..8cc8a47b14 100644 --- a/esphome/components/uart/uart.cpp +++ b/esphome/components/uart/uart.cpp @@ -58,21 +58,21 @@ void UARTDevice::check_uart_settings(uint32_t baud_rate, uint8_t stop_bits, UART this->parent_->data_bits_); } if (this->parent_->parity_ != parity) { - ESP_LOGE(TAG, " Invalid parity: Integration requested parity %s but you have %s!", parity_to_str(parity), - parity_to_str(this->parent_->parity_)); + ESP_LOGE(TAG, " Invalid parity: Integration requested parity %s but you have %s!", + LOG_STR_ARG(parity_to_str(parity)), LOG_STR_ARG(parity_to_str(this->parent_->parity_))); } } -const char *parity_to_str(UARTParityOptions parity) { +const LogString *parity_to_str(UARTParityOptions parity) { switch (parity) { case UART_CONFIG_PARITY_NONE: - return "NONE"; + return LOG_STR("NONE"); case UART_CONFIG_PARITY_EVEN: - return "EVEN"; + return LOG_STR("EVEN"); case UART_CONFIG_PARITY_ODD: - return "ODD"; + return LOG_STR("ODD"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index 8ac00658f4..041cdaecdf 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -4,6 +4,7 @@ #include #include "esphome/core/esphal.h" #include "esphome/core/component.h" +#include "esphome/core/log.h" namespace esphome { namespace uart { @@ -14,7 +15,7 @@ enum UARTParityOptions { UART_CONFIG_PARITY_ODD, }; -const char *parity_to_str(UARTParityOptions parity); +const LogString *parity_to_str(UARTParityOptions parity); #ifdef ARDUINO_ARCH_ESP8266 class ESP8266SoftwareSerial { diff --git a/esphome/components/uart/uart_esp8266.cpp b/esphome/components/uart/uart_esp8266.cpp index 5cb625f2ff..bc6b0f72d3 100644 --- a/esphome/components/uart/uart_esp8266.cpp +++ b/esphome/components/uart/uart_esp8266.cpp @@ -104,7 +104,7 @@ void UARTComponent::dump_config() { } ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_); ESP_LOGCONFIG(TAG, " Data Bits: %u", this->data_bits_); - ESP_LOGCONFIG(TAG, " Parity: %s", parity_to_str(this->parity_)); + ESP_LOGCONFIG(TAG, " Parity: %s", LOG_STR_ARG(parity_to_str(this->parity_))); ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_); if (this->hw_serial_ != nullptr) { ESP_LOGCONFIG(TAG, " Using hardware serial interface."); diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 50feeb6cad..c5ba9b01af 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -307,7 +307,7 @@ void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) { this->action_started_ = millis(); } -void print_signal_bars(int8_t rssi, char *buf) { +const LogString *get_signal_bars(int8_t rssi) { // LOWER ONE QUARTER BLOCK // Unicode: U+2582, UTF-8: E2 96 82 // LOWER HALF BLOCK @@ -317,36 +317,36 @@ void print_signal_bars(int8_t rssi, char *buf) { // FULL BLOCK // Unicode: U+2588, UTF-8: E2 96 88 if (rssi >= -50) { - sprintf(buf, "\033[0;32m" // green - "\xe2\x96\x82" - "\xe2\x96\x84" - "\xe2\x96\x86" - "\xe2\x96\x88" - "\033[0m"); + return LOG_STR("\033[0;32m" // green + "\xe2\x96\x82" + "\xe2\x96\x84" + "\xe2\x96\x86" + "\xe2\x96\x88" + "\033[0m"); } else if (rssi >= -65) { - sprintf(buf, "\033[0;33m" // yellow - "\xe2\x96\x82" - "\xe2\x96\x84" - "\xe2\x96\x86" - "\033[0;37m" - "\xe2\x96\x88" - "\033[0m"); + return LOG_STR("\033[0;33m" // yellow + "\xe2\x96\x82" + "\xe2\x96\x84" + "\xe2\x96\x86" + "\033[0;37m" + "\xe2\x96\x88" + "\033[0m"); } else if (rssi >= -85) { - sprintf(buf, "\033[0;33m" // yellow - "\xe2\x96\x82" - "\xe2\x96\x84" - "\033[0;37m" - "\xe2\x96\x86" - "\xe2\x96\x88" - "\033[0m"); + return LOG_STR("\033[0;33m" // yellow + "\xe2\x96\x82" + "\xe2\x96\x84" + "\033[0;37m" + "\xe2\x96\x86" + "\xe2\x96\x88" + "\033[0m"); } else { - sprintf(buf, "\033[0;31m" // red - "\xe2\x96\x82" - "\033[0;37m" - "\xe2\x96\x84" - "\xe2\x96\x86" - "\xe2\x96\x88" - "\033[0m"); + return LOG_STR("\033[0;31m" // red + "\xe2\x96\x82" + "\033[0;37m" + "\xe2\x96\x84" + "\xe2\x96\x86" + "\xe2\x96\x88" + "\033[0m"); } } @@ -361,10 +361,8 @@ void WiFiComponent::print_connect_params_() { ESP_LOGCONFIG(TAG, " BSSID: " LOG_SECRET("%02X:%02X:%02X:%02X:%02X:%02X"), bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); ESP_LOGCONFIG(TAG, " Hostname: '%s'", App.get_name().c_str()); - char signal_bars[50]; int8_t rssi = WiFi.RSSI(); - print_signal_bars(rssi, signal_bars); - ESP_LOGCONFIG(TAG, " Signal strength: %d dB %s", rssi, signal_bars); + ESP_LOGCONFIG(TAG, " Signal strength: %d dB %s", rssi, LOG_STR_ARG(get_signal_bars(rssi))); if (this->selected_ap_.get_bssid().has_value()) { ESP_LOGV(TAG, " Priority: %.1f", this->get_sta_priority(*this->selected_ap_.get_bssid())); } @@ -433,16 +431,15 @@ void WiFiComponent::check_scanning_finished() { char bssid_s[18]; auto bssid = res.get_bssid(); sprintf(bssid_s, "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); - char signal_bars[50]; - print_signal_bars(res.get_rssi(), signal_bars); if (res.get_matches()) { ESP_LOGI(TAG, "- '%s' %s" LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), - res.get_is_hidden() ? "(HIDDEN) " : "", bssid_s, signal_bars); + res.get_is_hidden() ? "(HIDDEN) " : "", bssid_s, LOG_STR_ARG(get_signal_bars(res.get_rssi()))); ESP_LOGD(TAG, " Channel: %u", res.get_channel()); ESP_LOGD(TAG, " RSSI: %d dB", res.get_rssi()); } else { - ESP_LOGD(TAG, "- " LOG_SECRET("'%s'") " " LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), bssid_s, signal_bars); + ESP_LOGD(TAG, "- " LOG_SECRET("'%s'") " " LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), bssid_s, + LOG_STR_ARG(get_signal_bars(res.get_rssi()))); } } diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index de529ee3aa..842b44b705 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -327,20 +327,20 @@ class WiFiMockClass : public ESP8266WiFiGenericClass { static void _event_callback(void *event) { ESP8266WiFiGenericClass::_eventCallback(event); } // NOLINT }; -const char *get_auth_mode_str(uint8_t mode) { +const LogString *get_auth_mode_str(uint8_t mode) { switch (mode) { case AUTH_OPEN: - return "OPEN"; + return LOG_STR("OPEN"); case AUTH_WEP: - return "WEP"; + return LOG_STR("WEP"); case AUTH_WPA_PSK: - return "WPA PSK"; + return LOG_STR("WPA PSK"); case AUTH_WPA2_PSK: - return "WPA2 PSK"; + return LOG_STR("WPA2 PSK"); case AUTH_WPA_WPA2_PSK: - return "WPA/WPA2 PSK"; + return LOG_STR("WPA/WPA2 PSK"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } #ifdef ipv4_addr @@ -358,22 +358,22 @@ std::string format_ip_addr(struct ip_addr ip) { return buf; } #endif -const char *get_op_mode_str(uint8_t mode) { +const LogString *get_op_mode_str(uint8_t mode) { switch (mode) { case WIFI_OFF: - return "OFF"; + return LOG_STR("OFF"); case WIFI_STA: - return "STA"; + return LOG_STR("STA"); case WIFI_AP: - return "AP"; + return LOG_STR("AP"); case WIFI_AP_STA: - return "AP+STA"; + return LOG_STR("AP+STA"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } -// Note that this method returns PROGMEM strings, so use LOG_STR_ARG() to access them. -const char *get_disconnect_reason_str(uint8_t reason) { + +const LogString *get_disconnect_reason_str(uint8_t reason) { /* If this were one big switch statement, GCC would generate a lookup table for it. However, the values of the * REASON_* constants aren't continuous, and GCC will fill in the gap with the default value -- wasting 4 bytes of RAM * per entry. As there's ~175 default entries, this wastes 700 bytes of RAM. @@ -470,8 +470,8 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { } case EVENT_STAMODE_AUTHMODE_CHANGE: { auto it = event->event_info.auth_change; - ESP_LOGV(TAG, "Event: Changed AuthMode old=%s new=%s", get_auth_mode_str(it.old_mode), - get_auth_mode_str(it.new_mode)); + ESP_LOGV(TAG, "Event: Changed AuthMode old=%s new=%s", LOG_STR_ARG(get_auth_mode_str(it.old_mode)), + LOG_STR_ARG(get_auth_mode_str(it.new_mode))); // Mitigate CVE-2020-12638 // https://lbsfilm.at/blog/wpa2-authenticationmode-downgrade-in-espressif-microprocessors if (it.old_mode != AUTH_OPEN && it.new_mode == AUTH_OPEN) { @@ -511,8 +511,8 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { #if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) case EVENT_OPMODE_CHANGED: { auto it = event->event_info.opmode_changed; - ESP_LOGV(TAG, "Event: Changed Mode old=%s new=%s", get_op_mode_str(it.old_opmode), - get_op_mode_str(it.new_opmode)); + ESP_LOGV(TAG, "Event: Changed Mode old=%s new=%s", LOG_STR_ARG(get_op_mode_str(it.old_opmode)), + LOG_STR_ARG(get_op_mode_str(it.new_opmode))); break; } case EVENT_SOFTAPMODE_DISTRIBUTE_STA_IP: { diff --git a/esphome/core/esphal.cpp b/esphome/core/esphal.cpp index 7851ba01b6..8fb1dd86c5 100644 --- a/esphome/core/esphal.cpp +++ b/esphome/core/esphal.cpp @@ -43,81 +43,56 @@ GPIOPin::GPIOPin(uint8_t pin, uint8_t mode, bool inverted) { } -const char *GPIOPin::get_pin_mode_name() const { +const LogString *GPIOPin::get_pin_mode_name() const { const char *mode_s; switch (this->mode_) { case INPUT: - mode_s = "INPUT"; - break; + return LOG_STR("INPUT"); case OUTPUT: - mode_s = "OUTPUT"; - break; + return LOG_STR("OUTPUT"); case INPUT_PULLUP: - mode_s = "INPUT_PULLUP"; - break; + return LOG_STR("INPUT_PULLUP"); case OUTPUT_OPEN_DRAIN: - mode_s = "OUTPUT_OPEN_DRAIN"; - break; + return LOG_STR("OUTPUT_OPEN_DRAIN"); case SPECIAL: - mode_s = "SPECIAL"; - break; + return LOG_STR("SPECIAL"); case FUNCTION_1: - mode_s = "FUNCTION_1"; - break; + return LOG_STR("FUNCTION_1"); case FUNCTION_2: - mode_s = "FUNCTION_2"; - break; + return LOG_STR("FUNCTION_2"); case FUNCTION_3: - mode_s = "FUNCTION_3"; - break; + return LOG_STR("FUNCTION_3"); case FUNCTION_4: - mode_s = "FUNCTION_4"; - break; - + return LOG_STR("FUNCTION_4"); #ifdef ARDUINO_ARCH_ESP32 case PULLUP: - mode_s = "PULLUP"; - break; + return LOG_STR("PULLUP"); case PULLDOWN: - mode_s = "PULLDOWN"; - break; + return LOG_STR("PULLDOWN"); case INPUT_PULLDOWN: - mode_s = "INPUT_PULLDOWN"; - break; + return LOG_STR("INPUT_PULLDOWN"); case OPEN_DRAIN: - mode_s = "OPEN_DRAIN"; - break; + return LOG_STR("OPEN_DRAIN"); case FUNCTION_5: - mode_s = "FUNCTION_5"; - break; + return LOG_STR("FUNCTION_5"); case FUNCTION_6: - mode_s = "FUNCTION_6"; - break; + return LOG_STR("FUNCTION_6"); case ANALOG: - mode_s = "ANALOG"; - break; + return LOG_STR("ANALOG"); #endif #ifdef ARDUINO_ARCH_ESP8266 case FUNCTION_0: - mode_s = "FUNCTION_0"; - break; + return LOG_STR("FUNCTION_0"); case WAKEUP_PULLUP: - mode_s = "WAKEUP_PULLUP"; - break; + return LOG_STR("WAKEUP_PULLUP"); case WAKEUP_PULLDOWN: - mode_s = "WAKEUP_PULLDOWN"; - break; + return LOG_STR("WAKEUP_PULLDOWN"); case INPUT_PULLDOWN_16: - mode_s = "INPUT_PULLDOWN_16"; - break; + return LOG_STR("INPUT_PULLDOWN_16"); #endif - default: - mode_s = "UNKNOWN"; - break; + return LOG_STR("UNKNOWN"); } - - return mode_s; } unsigned char GPIOPin::get_pin() const { return this->pin_; } diff --git a/esphome/core/esphal.h b/esphome/core/esphal.h index 08fbfb09f5..1b92f816b1 100644 --- a/esphome/core/esphal.h +++ b/esphome/core/esphal.h @@ -25,6 +25,8 @@ #undef abs #endif +#include "esphome/core/log.h" + namespace esphome { #define LOG_PIN(prefix, pin) \ @@ -32,7 +34,8 @@ namespace esphome { ESP_LOGCONFIG(TAG, prefix LOG_PIN_PATTERN, LOG_PIN_ARGS(pin)); \ } #define LOG_PIN_PATTERN "GPIO%u (Mode: %s%s)" -#define LOG_PIN_ARGS(pin) (pin)->get_pin(), (pin)->get_pin_mode_name(), ((pin)->is_inverted() ? ", INVERTED" : "") +#define LOG_PIN_ARGS(pin) \ + (pin)->get_pin(), LOG_STR_ARG((pin)->get_pin_mode_name()), ((pin)->is_inverted() ? ", INVERTED" : "") /// Copy of GPIOPin that is safe to use from ISRs (with no virtual functions) class ISRInternalGPIOPin { @@ -85,7 +88,7 @@ class GPIOPin { /// Get the GPIO pin number. uint8_t get_pin() const; - const char *get_pin_mode_name() const; + const LogString *get_pin_mode_name() const; /// Get the pinMode of this pin. uint8_t get_mode() const; /// Return whether this pin shall be treated as inverted. (for example active-low) diff --git a/esphome/core/log.h b/esphome/core/log.h index 0b0911c3ac..3c2f1e6999 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -165,28 +165,37 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT #define ONOFF(b) ((b) ? "ON" : "OFF") #define TRUEFALSE(b) ((b) ? "TRUE" : "FALSE") -#ifdef USE_STORE_LOG_STR_IN_FLASH -#define LOG_STR(s) PSTR(s) +// Helper class that identifies strings that may be stored in flash storage (similar to Arduino's __FlashStringHelper) +struct LogString; -// From Arduino 2.5 onwards, we can pass a PSTR() to printf(). For previous versions, emulate support -// by copying the message to a local buffer first. String length is limited to 63 characters. +#ifdef USE_STORE_LOG_STR_IN_FLASH + +#include + +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 0) +#define LOG_STR_ARG(s) ((PGM_P)(s)) +#else +// Pre-Arduino 2.5, we can't pass a PSTR() to printf(). Emulate support by copying the message to a +// local buffer first. String length is limited to 63 characters. // https://github.com/esp8266/Arduino/commit/6280e98b0360f85fdac2b8f10707fffb4f6e6e31 -#include -#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 5, 0) #define LOG_STR_ARG(s) \ ({ \ char __buf[64]; \ __buf[63] = '\0'; \ - strncpy_P(__buf, s, 63); \ + strncpy_P(__buf, (PGM_P)(s), 63); \ __buf; \ }) -#else -#define LOG_STR_ARG(s) (s) #endif -#else -#define LOG_STR(s) (s) -#define LOG_STR_ARG(s) (s) +#define LOG_STR(s) (reinterpret_cast(PSTR(s))) +#define LOG_STR_LITERAL(s) LOG_STR_ARG(LOG_STR(s)) + +#else // !USE_STORE_LOG_STR_IN_FLASH + +#define LOG_STR(s) (reinterpret_cast(s)) +#define LOG_STR_ARG(s) (reinterpret_cast(s)) +#define LOG_STR_LITERAL(s) (s) + #endif } // namespace esphome From 63a186bdf96e3b8db5f7291e2cd8972375a49a4c Mon Sep 17 00:00:00 2001 From: Jas Strong Date: Mon, 13 Sep 2021 00:54:48 -0700 Subject: [PATCH 1308/1841] t6615: tolerate sensor dropping commands (#2255) The Amphenol T6615 has a built-in calibration system which means that the sensor could go away for a couple of seconds to figure itself out. While this is happening, commands are silently dropped. This caused the previous version of this code to lock up completely, since there was no way for the command_ state machine to tick back to the NONE state. Instead of just breaking the state machine, which might be harmful on a multi-core or multi-threaded device, add a timestamp and only break the lock if it's been more than a second since the command was issued. The command usually doesn't take more than a few milliseconds to complete, so this should not affect things unduly. While we're at it, rewrite the rx side to be more robust against bytes going missing. Instead of reading in the data essentially inline, read into a buffer and process it when enough has been read to make progress. If data stops coming when we expect it to, or the data is malformed, have a timeout that sends a new command. Co-authored-by: jas --- esphome/components/t6615/t6615.cpp | 64 ++++++++++++++++++------------ esphome/components/t6615/t6615.h | 2 + 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/esphome/components/t6615/t6615.cpp b/esphome/components/t6615/t6615.cpp index 09ff61827c..c139c56ce4 100644 --- a/esphome/components/t6615/t6615.cpp +++ b/esphome/components/t6615/t6615.cpp @@ -6,7 +6,7 @@ namespace t6615 { static const char *const TAG = "t6615"; -static const uint8_t T6615_RESPONSE_BUFFER_LENGTH = 32; +static const uint32_t T6615_TIMEOUT = 1000; static const uint8_t T6615_MAGIC = 0xFF; static const uint8_t T6615_ADDR_HOST = 0xFA; static const uint8_t T6615_ADDR_SENSOR = 0xFE; @@ -19,31 +19,49 @@ static const uint8_t T6615_COMMAND_ENABLE_ABC[] = {0xB7, 0x01}; static const uint8_t T6615_COMMAND_DISABLE_ABC[] = {0xB7, 0x02}; static const uint8_t T6615_COMMAND_SET_ELEVATION[] = {0x03, 0x0F}; -void T6615Component::loop() { - if (!this->available()) - return; +void T6615Component::send_ppm_command_() { + this->command_time_ = millis(); + this->command_ = T6615Command::GET_PPM; + this->write_byte(T6615_MAGIC); + this->write_byte(T6615_ADDR_SENSOR); + this->write_byte(sizeof(T6615_COMMAND_GET_PPM)); + this->write_array(T6615_COMMAND_GET_PPM, sizeof(T6615_COMMAND_GET_PPM)); +} - // Read header - uint8_t header[3]; - this->read_array(header, 3); - if (header[0] != T6615_MAGIC || header[1] != T6615_ADDR_HOST) { - ESP_LOGW(TAG, "Reading data from T6615 failed!"); - while (this->available()) - this->read(); // Clear the incoming buffer - this->status_set_warning(); +void T6615Component::loop() { + if (this->available() < 5) { + if (this->command_ == T6615Command::GET_PPM && millis() - this->command_time_ > T6615_TIMEOUT) { + /* command got eaten, clear the buffer and fire another */ + while (this->available()) + this->read(); + this->send_ppm_command_(); + } return; } - // Read body - uint8_t length = header[2]; - uint8_t response[T6615_RESPONSE_BUFFER_LENGTH]; - this->read_array(response, length); + uint8_t response_buffer[6]; + + /* by the time we get here, we know we have at least five bytes in the buffer */ + this->read_array(response_buffer, 5); + + // Read header + if (response_buffer[0] != T6615_MAGIC || response_buffer[1] != T6615_ADDR_HOST) { + ESP_LOGW(TAG, "Got bad data from T6615! Magic was %02X and address was %02X", response_buffer[0], + response_buffer[1]); + /* make sure the buffer is empty */ + while (this->available()) + this->read(); + /* try again to read the sensor */ + this->send_ppm_command_(); + this->status_set_warning(); + return; + } this->status_clear_warning(); switch (this->command_) { case T6615Command::GET_PPM: { - const uint16_t ppm = encode_uint16(response[0], response[1]); + const uint16_t ppm = encode_uint16(response_buffer[3], response_buffer[4]); ESP_LOGD(TAG, "T6615 Received CO₂=%uppm", ppm); this->co2_sensor_->publish_state(ppm); break; @@ -51,23 +69,19 @@ void T6615Component::loop() { default: break; } - + this->command_time_ = 0; this->command_ = T6615Command::NONE; } void T6615Component::update() { this->query_ppm_(); } void T6615Component::query_ppm_() { - if (this->co2_sensor_ == nullptr || this->command_ != T6615Command::NONE) { + if (this->co2_sensor_ == nullptr || + (this->command_ != T6615Command::NONE && millis() - this->command_time_ < T6615_TIMEOUT)) { return; } - this->command_ = T6615Command::GET_PPM; - - this->write_byte(T6615_MAGIC); - this->write_byte(T6615_ADDR_SENSOR); - this->write_byte(sizeof(T6615_COMMAND_GET_PPM)); - this->write_array(T6615_COMMAND_GET_PPM, sizeof(T6615_COMMAND_GET_PPM)); + this->send_ppm_command_(); } float T6615Component::get_setup_priority() const { return setup_priority::DATA; } diff --git a/esphome/components/t6615/t6615.h b/esphome/components/t6615/t6615.h index a7da3b4cf6..a075685023 100644 --- a/esphome/components/t6615/t6615.h +++ b/esphome/components/t6615/t6615.h @@ -32,8 +32,10 @@ class T6615Component : public PollingComponent, public uart::UARTDevice { protected: void query_ppm_(); + void send_ppm_command_(); T6615Command command_ = T6615Command::NONE; + unsigned long command_time_ = 0; sensor::Sensor *co2_sensor_{nullptr}; }; From 8a2b1d9359decb113e58959b7cbbfa75bc519f29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Mon, 13 Sep 2021 10:06:28 +0200 Subject: [PATCH 1309/1841] Expose select on Frontend `web_server:` (#2245) --- esphome/components/web_server/web_server.cpp | 43 +++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index dc97bcd5c2..f82a8893eb 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -25,7 +25,8 @@ namespace web_server { static const char *const TAG = "web_server"; -void write_row(AsyncResponseStream *stream, Nameable *obj, const std::string &klass, const std::string &action) { +void write_row(AsyncResponseStream *stream, Nameable *obj, const std::string &klass, const std::string &action, + const std::function &action_func = nullptr) { if (obj->is_internal()) return; stream->print("print(obj->get_name().c_str()); stream->print(""); stream->print(action.c_str()); + if (action_func) { + action_func(*stream, obj); + } stream->print(""); stream->print(""); } @@ -219,7 +223,17 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { #ifdef USE_SELECT for (auto *obj : App.get_selects()) - write_row(stream, obj, "select", ""); + write_row(stream, obj, "select", "", [](AsyncResponseStream &stream, Nameable *obj) { + select::Select *select = (select::Select *) obj; + stream.print(""); + }); #endif stream->print(F("

    See ESPHome Web API for " @@ -648,8 +662,27 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM continue; if (obj->get_object_id() != match.id) continue; - std::string data = this->select_json(obj, obj->state); - request->send(200, "text/json", data.c_str()); + + if (request->method() == HTTP_GET) { + std::string data = this->select_json(obj, obj->state); + request->send(200, "text/json", data.c_str()); + return; + } + + if (match.method != "set") { + request->send(404); + return; + } + + auto call = obj->make_call(); + + if (request->hasParam("option")) { + String option = request->getParam("option")->value(); + call.set_option(option.c_str()); // NOLINT(clang-diagnostic-deprecated-declarations) + } + + this->defer([call]() mutable { call.perform(); }); + request->send(200); return; } request->send(404); @@ -721,7 +754,7 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { #endif #ifdef USE_SELECT - if (request->method() == HTTP_GET && match.domain == "select") + if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "select") return true; #endif From 4e308f551cf543d721f0f394a51764fcee35b062 Mon Sep 17 00:00:00 2001 From: Alex <33379584+alexyao2015@users.noreply.github.com> Date: Mon, 13 Sep 2021 03:08:06 -0500 Subject: [PATCH 1310/1841] Fix devcontainer scripts on Windows (#2239) --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..94f480de94 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file From c6109024aaff83c9a44ccd579f5994fafe4a3078 Mon Sep 17 00:00:00 2001 From: James Braid <251628+jamesbraid@users.noreply.github.com> Date: Mon, 13 Sep 2021 02:23:59 -0700 Subject: [PATCH 1311/1841] Fix SM300D2 sensor component routines so they correctly read the sensor output (#2159) --- esphome/components/sm300d2/sensor.py | 4 ++-- esphome/components/sm300d2/sm300d2.cpp | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/sm300d2/sensor.py b/esphome/components/sm300d2/sensor.py index 8452ee81f2..0c3c54f200 100644 --- a/esphome/components/sm300d2/sensor.py +++ b/esphome/components/sm300d2/sensor.py @@ -72,13 +72,13 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, - accuracy_decimals=0, + accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, - accuracy_decimals=0, + accuracy_decimals=1, device_class=DEVICE_CLASS_HUMIDITY, state_class=STATE_CLASS_MEASUREMENT, ), diff --git a/esphome/components/sm300d2/sm300d2.cpp b/esphome/components/sm300d2/sm300d2.cpp index b1787581ae..d542582fcb 100644 --- a/esphome/components/sm300d2/sm300d2.cpp +++ b/esphome/components/sm300d2/sm300d2.cpp @@ -9,10 +9,12 @@ static const uint8_t SM300D2_RESPONSE_LENGTH = 17; void SM300D2Sensor::update() { uint8_t response[SM300D2_RESPONSE_LENGTH]; + uint8_t peeked; + + while (this->available() > 0 && this->peek_byte(&peeked) && peeked != 0x3C) + this->read(); - flush(); bool read_success = read_array(response, SM300D2_RESPONSE_LENGTH); - flush(); if (!read_success) { ESP_LOGW(TAG, "Reading data from SM300D2 failed!"); @@ -63,7 +65,7 @@ void SM300D2Sensor::update() { if (this->pm_2_5_sensor_ != nullptr) this->pm_2_5_sensor_->publish_state(pm_2_5); - ESP_LOGD(TAG, "Received pm_10_0: %u µg/m³", pm_10_0); + ESP_LOGD(TAG, "Received PM10: %u µg/m³", pm_10_0); if (this->pm_10_0_sensor_ != nullptr) this->pm_10_0_sensor_->publish_state(pm_10_0); From e0cff214b215ab2cd4dfb221b37d0f56dbce141b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Sep 2021 11:25:28 +0200 Subject: [PATCH 1312/1841] Bump tzlocal from 2.1 to 3.0 (#2154) Bumps [tzlocal](https://github.com/regebro/tzlocal) from 2.1 to 3.0. - [Release notes](https://github.com/regebro/tzlocal/releases) - [Changelog](https://github.com/regebro/tzlocal/blob/master/CHANGES.txt) - [Commits](https://github.com/regebro/tzlocal/compare/2.1...3.0) --- updated-dependencies: - dependency-name: tzlocal dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index daaf86e641..7de02d8ccb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ paho-mqtt==1.5.1 colorama==0.4.4 tornado==6.1 protobuf==3.17.3 -tzlocal==2.1 +tzlocal==3.0 pytz==2021.1 pyserial==3.5 ifaddr==0.1.7 From a4867a00ea6d7170d22dda00705d010bfd0ed756 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 13 Sep 2021 11:31:02 +0200 Subject: [PATCH 1313/1841] Activate owning-memory clang-tidy check (#1891) * Activate owning-memory clang-tidy check * Lint * Lint * Fix issue with new NfcTag constructor * Update pointers for number and select * Add back the NOLINT to display buffer * Fix merge * DSMR fixes * Nextion fixes * Fix pipsolar * Fix lwip socket * Format * Change socket fix Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .clang-tidy | 1 - esphome/components/api/api_server.cpp | 42 +++++------ esphome/components/api/api_server.h | 2 +- esphome/components/api/custom_api_device.h | 4 +- .../captive_portal/captive_portal.cpp | 2 +- .../captive_portal/captive_portal.h | 5 +- .../components/dallas/dallas_component.cpp | 16 +---- esphome/components/dallas/dallas_component.h | 6 +- esphome/components/dallas/sensor.py | 13 ++-- esphome/components/display/display_buffer.cpp | 4 +- esphome/components/dsmr/dsmr.cpp | 2 +- esphome/components/e131/e131.cpp | 2 +- .../components/fastled_base/fastled_light.cpp | 2 +- .../components/fastled_base/fastled_light.h | 2 +- esphome/components/hm3301/hm3301.cpp | 2 +- esphome/components/hm3301/hm3301.h | 2 +- .../components/http_request/http_request.cpp | 6 +- .../components/http_request/http_request.h | 7 +- esphome/components/json/json_util.cpp | 30 +++----- esphome/components/json/json_util.h | 2 + esphome/components/lcd_base/lcd_display.cpp | 2 +- esphome/components/logger/logger.cpp | 2 +- esphome/components/max7219/max7219.cpp | 2 +- esphome/components/mqtt/mqtt_component.cpp | 3 +- esphome/components/mqtt/mqtt_component.h | 4 +- .../neopixelbus/neopixelbus_light.h | 2 +- .../binary_sensor/nextion_binarysensor.cpp | 4 +- .../binary_sensor/nextion_binarysensor.h | 3 +- esphome/components/nextion/nextion.cpp | 69 ++++++++----------- esphome/components/nextion/nextion.h | 11 +-- esphome/components/nextion/nextion_base.h | 9 +-- .../nextion/nextion_component_base.h | 3 +- esphome/components/nextion/nextion_upload.cpp | 10 +-- .../nextion/sensor/nextion_sensor.cpp | 8 +-- .../nextion/sensor/nextion_sensor.h | 5 +- .../nextion/switch/nextion_switch.cpp | 4 +- .../nextion/switch/nextion_switch.h | 5 +- .../text_sensor/nextion_textsensor.cpp | 4 +- .../nextion/text_sensor/nextion_textsensor.h | 5 +- esphome/components/nfc/ndef_message.cpp | 14 ++-- esphome/components/nfc/ndef_message.h | 16 +++-- esphome/components/nfc/ndef_record.h | 6 +- esphome/components/nfc/nfc_tag.h | 21 ++++-- esphome/components/ota/ota_component.cpp | 2 +- esphome/components/ota/ota_component.h | 4 +- esphome/components/pca9685/output.py | 5 +- esphome/components/pca9685/pca9685_output.cpp | 10 +-- esphome/components/pca9685/pca9685_output.h | 9 +-- esphome/components/pipsolar/pipsolar.cpp | 2 +- esphome/components/pn532/pn532.cpp | 20 +++--- esphome/components/pn532/pn532.h | 8 +-- .../components/pn532/pn532_mifare_classic.cpp | 10 +-- .../pn532/pn532_mifare_ultralight.cpp | 12 ++-- esphome/components/scd30/scd30.cpp | 7 +- esphome/components/sgp30/sgp30.cpp | 7 +- esphome/components/sht3xd/sht3xd.cpp | 7 +- esphome/components/shtcx/shtcx.cpp | 7 +- .../components/socket/lwip_raw_tcp_impl.cpp | 6 +- esphome/components/sps30/sps30.cpp | 7 +- esphome/components/sts3x/sts3x.cpp | 7 +- esphome/components/tlc59208f/output.py | 5 +- .../components/tlc59208f/tlc59208f_output.cpp | 10 +-- .../components/tlc59208f/tlc59208f_output.h | 9 +-- esphome/components/tm1651/tm1651.cpp | 3 +- esphome/components/tm1651/tm1651.h | 4 +- esphome/components/uart/uart_esp8266.cpp | 4 +- .../web_server_base/web_server_base.cpp | 4 +- .../web_server_base/web_server_base.h | 8 +-- esphome/components/wled/wled_light_effect.cpp | 3 +- esphome/core/esphal.cpp | 5 +- esphome/core/helpers.cpp | 15 ---- esphome/core/helpers.h | 2 +- esphome/core/preferences.cpp | 15 ++-- esphome/core/preferences.h | 23 ++++--- tests/dummy_main.cpp | 10 +-- 75 files changed, 293 insertions(+), 321 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 936100e9e6..597065d50b 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -30,7 +30,6 @@ Checks: >- -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, -cppcoreguidelines-pro-bounds-pointer-arithmetic, diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index c4c193b389..70f82c59db 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -64,7 +64,7 @@ void APIServer::setup() { #ifdef USE_LOGGER if (logger::global_logger != nullptr) { logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { - for (auto *c : this->clients_) { + for (auto &c : this->clients_) { if (!c->remove_) c->send_log_message(level, tag, message); } @@ -77,7 +77,7 @@ void APIServer::setup() { #ifdef USE_ESP32_CAMERA if (esp32_camera::global_esp32_camera != nullptr) { esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr image) { - for (auto *c : this->clients_) + for (auto &c : this->clients_) if (!c->remove_) c->send_camera_state(image); }); @@ -95,25 +95,21 @@ void APIServer::loop() { ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str()); auto *conn = new APIConnection(std::move(sock), this); - clients_.push_back(conn); + clients_.emplace_back(conn); conn->start(); } // Partition clients into remove and active - auto new_end = - std::partition(this->clients_.begin(), this->clients_.end(), [](APIConnection *conn) { return !conn->remove_; }); + auto new_end = std::partition(this->clients_.begin(), this->clients_.end(), + [](const std::unique_ptr &conn) { return !conn->remove_; }); // print disconnection messages for (auto it = new_end; it != this->clients_.end(); ++it) { ESP_LOGD(TAG, "Disconnecting %s", (*it)->client_info_.c_str()); } - // only then delete the pointers, otherwise log routine - // would access freed memory - for (auto it = new_end; it != this->clients_.end(); ++it) - delete *it; // resize vector this->clients_.erase(new_end, this->clients_.end()); - for (auto *client : this->clients_) { + for (auto &client : this->clients_) { client->loop(); } @@ -169,7 +165,7 @@ void APIServer::handle_disconnect(APIConnection *conn) {} void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_binary_sensor_state(obj, state); } #endif @@ -178,7 +174,7 @@ void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool s void APIServer::on_cover_update(cover::Cover *obj) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_cover_state(obj); } #endif @@ -187,7 +183,7 @@ void APIServer::on_cover_update(cover::Cover *obj) { void APIServer::on_fan_update(fan::FanState *obj) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_fan_state(obj); } #endif @@ -196,7 +192,7 @@ void APIServer::on_fan_update(fan::FanState *obj) { void APIServer::on_light_update(light::LightState *obj) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_light_state(obj); } #endif @@ -205,7 +201,7 @@ void APIServer::on_light_update(light::LightState *obj) { void APIServer::on_sensor_update(sensor::Sensor *obj, float state) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_sensor_state(obj, state); } #endif @@ -214,7 +210,7 @@ void APIServer::on_sensor_update(sensor::Sensor *obj, float state) { void APIServer::on_switch_update(switch_::Switch *obj, bool state) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_switch_state(obj, state); } #endif @@ -223,7 +219,7 @@ void APIServer::on_switch_update(switch_::Switch *obj, bool state) { void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_text_sensor_state(obj, state); } #endif @@ -232,7 +228,7 @@ void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::s void APIServer::on_climate_update(climate::Climate *obj) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_climate_state(obj); } #endif @@ -241,7 +237,7 @@ void APIServer::on_climate_update(climate::Climate *obj) { void APIServer::on_number_update(number::Number *obj, float state) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_number_state(obj, state); } #endif @@ -250,7 +246,7 @@ void APIServer::on_number_update(number::Number *obj, float state) { void APIServer::on_select_update(select::Select *obj, const std::string &state) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_select_state(obj, state); } #endif @@ -261,7 +257,7 @@ APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-c void APIServer::set_password(const std::string &password) { this->password_ = password; } void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) { - for (auto *client : this->clients_) { + for (auto &client : this->clients_) { client->send_homeassistant_service_call(call); } } @@ -281,7 +277,7 @@ uint16_t APIServer::get_port() const { return this->port_; } void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; } #ifdef USE_HOMEASSISTANT_TIME void APIServer::request_time() { - for (auto *client : this->clients_) { + for (auto &client : this->clients_) { if (!client->remove_ && client->connection_state_ == APIConnection::ConnectionState::CONNECTED) client->send_time_request(); } @@ -289,7 +285,7 @@ void APIServer::request_time() { #endif bool APIServer::is_connected() const { return !this->clients_.empty(); } void APIServer::on_shutdown() { - for (auto *c : this->clients_) { + for (auto &c : this->clients_) { c->send_disconnect_request(DisconnectRequest()); } delay(10); diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index e3fa6b18c9..d659f24358 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -91,7 +91,7 @@ class APIServer : public Component, public Controller { uint16_t port_{6053}; uint32_t reboot_timeout_{300000}; uint32_t last_connected_{0}; - std::vector clients_; + std::vector> clients_; std::string password_; std::vector state_subs_; std::vector user_services_; diff --git a/esphome/components/api/custom_api_device.h b/esphome/components/api/custom_api_device.h index 74343904eb..9f125a6149 100644 --- a/esphome/components/api/custom_api_device.h +++ b/esphome/components/api/custom_api_device.h @@ -49,7 +49,7 @@ class CustomAPIDevice { template void register_service(void (T::*callback)(Ts...), const std::string &name, const std::array &arg_names) { - auto *service = new CustomAPIDeviceService(name, arg_names, (T *) this, callback); + auto *service = new CustomAPIDeviceService(name, arg_names, (T *) this, callback); // NOLINT global_api_server->register_user_service(service); } @@ -72,7 +72,7 @@ class CustomAPIDevice { * @param name The name of the arguments for the service, must match the arguments of the function. */ template void register_service(void (T::*callback)(), const std::string &name) { - auto *service = new CustomAPIDeviceService(name, {}, (T *) this, callback); + auto *service = new CustomAPIDeviceService(name, {}, (T *) this, callback); // NOLINT global_api_server->register_user_service(service); } diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index 746029e011..3341769956 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -76,7 +76,7 @@ void CaptivePortal::start() { this->base_->add_ota_handler(); } - this->dns_server_ = new DNSServer(); + this->dns_server_ = make_unique(); this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); this->dns_server_->start(53, "*", ip); diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h index 44b1050f45..399ffaabd3 100644 --- a/esphome/components/captive_portal/captive_portal.h +++ b/esphome/components/captive_portal/captive_portal.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "esphome/core/component.h" #include "esphome/core/helpers.h" @@ -26,7 +27,7 @@ class CaptivePortal : public AsyncWebHandler, public Component { this->active_ = false; this->base_->deinit(); this->dns_server_->stop(); - delete this->dns_server_; + this->dns_server_ = nullptr; } bool canHandle(AsyncWebServerRequest *request) override { @@ -65,7 +66,7 @@ class CaptivePortal : public AsyncWebHandler, public Component { web_server_base::WebServerBase *base_; bool initialized_{false}; bool active_{false}; - DNSServer *dns_server_{nullptr}; + std::unique_ptr dns_server_{nullptr}; }; extern CaptivePortal *global_captive_portal; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index 7e34546078..847d630e7c 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -97,16 +97,7 @@ void DallasComponent::dump_config() { } } -DallasTemperatureSensor *DallasComponent::get_sensor_by_address(uint64_t address, uint8_t resolution) { - auto s = new DallasTemperatureSensor(address, resolution, this); - this->sensors_.push_back(s); - return s; -} -DallasTemperatureSensor *DallasComponent::get_sensor_by_index(uint8_t index, uint8_t resolution) { - auto s = this->get_sensor_by_address(0, resolution); - s->set_index(index); - return s; -} +void DallasComponent::register_sensor(DallasTemperatureSensor *sensor) { this->sensors_.push_back(sensor); } void DallasComponent::update() { this->status_clear_warning(); @@ -157,11 +148,6 @@ void DallasComponent::update() { } DallasComponent::DallasComponent(ESPOneWire *one_wire) : one_wire_(one_wire) {} -DallasTemperatureSensor::DallasTemperatureSensor(uint64_t address, uint8_t resolution, DallasComponent *parent) - : parent_(parent) { - this->set_address(address); - this->set_resolution(resolution); -} void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; } uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; } void DallasTemperatureSensor::set_resolution(uint8_t resolution) { this->resolution_ = resolution; } diff --git a/esphome/components/dallas/dallas_component.h b/esphome/components/dallas/dallas_component.h index d32aec1758..8d405f6eab 100644 --- a/esphome/components/dallas/dallas_component.h +++ b/esphome/components/dallas/dallas_component.h @@ -13,8 +13,7 @@ class DallasComponent : public PollingComponent { public: explicit DallasComponent(ESPOneWire *one_wire); - DallasTemperatureSensor *get_sensor_by_address(uint64_t address, uint8_t resolution); - DallasTemperatureSensor *get_sensor_by_index(uint8_t index, uint8_t resolution); + void register_sensor(DallasTemperatureSensor *sensor); void setup() override; void dump_config() override; @@ -33,8 +32,7 @@ class DallasComponent : public PollingComponent { /// Internal class that helps us create multiple sensors for one Dallas hub. class DallasTemperatureSensor : public sensor::Sensor { public: - DallasTemperatureSensor(uint64_t address, uint8_t resolution, DallasComponent *parent); - + void set_parent(DallasComponent *parent) { parent_ = parent; } /// Helper to get a pointer to the address as uint8_t. uint8_t *get_address8(); /// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29". diff --git a/esphome/components/dallas/sensor.py b/esphome/components/dallas/sensor.py index 5e09701bae..05c2c8b90c 100644 --- a/esphome/components/dallas/sensor.py +++ b/esphome/components/dallas/sensor.py @@ -36,10 +36,15 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): hub = await cg.get_variable(config[CONF_DALLAS_ID]) + var = cg.new_Pvariable(config[CONF_ID]) + if CONF_ADDRESS in config: - address = config[CONF_ADDRESS] - rhs = hub.Pget_sensor_by_address(address, config.get(CONF_RESOLUTION)) + cg.add(var.set_address(config[CONF_ADDRESS])) else: - rhs = hub.Pget_sensor_by_index(config[CONF_INDEX], config.get(CONF_RESOLUTION)) - var = cg.Pvariable(config[CONF_ID], rhs) + cg.add(var.set_index(config[CONF_INDEX])) + + if CONF_RESOLUTION in config: + cg.add(var.set_resolution(config[CONF_RESOLUTION])) + + cg.add(hub.register_sensor(var)) await sensor.register_sensor(var, config) diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index c330d00ce5..9c4ee3189f 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -1,9 +1,9 @@ #include "display_buffer.h" +#include #include "esphome/core/application.h" #include "esphome/core/color.h" #include "esphome/core/log.h" -#include namespace esphome { namespace display { @@ -14,7 +14,7 @@ const Color COLOR_OFF(0, 0, 0, 0); const Color COLOR_ON(255, 255, 255, 255); void DisplayBuffer::init_internal_(uint32_t buffer_length) { - this->buffer_ = new (std::nothrow) uint8_t[buffer_length]; + this->buffer_ = new (std::nothrow) uint8_t[buffer_length]; // NOLINT if (this->buffer_ == nullptr) { ESP_LOGE(TAG, "Could not allocate buffer for display!"); return; diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index 9bce7a382f..39f05a28d1 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -105,7 +105,7 @@ void Dsmr::receive_encrypted_() { &buffer[18], // cipher size buffer_length - 17); - delete gcmaes128; + delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory) telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_)); ESP_LOGV(TAG, "Decrypted data length: %d", telegram_len_); diff --git a/esphome/components/e131/e131.cpp b/esphome/components/e131/e131.cpp index bd749683c5..7694d039e5 100644 --- a/esphome/components/e131/e131.cpp +++ b/esphome/components/e131/e131.cpp @@ -26,7 +26,7 @@ E131Component::~E131Component() { } void E131Component::setup() { - udp_.reset(new WiFiUDP()); + udp_ = make_unique(); if (!udp_->begin(PORT)) { ESP_LOGE(TAG, "Cannot bind E131 to %d.", PORT); diff --git a/esphome/components/fastled_base/fastled_light.cpp b/esphome/components/fastled_base/fastled_light.cpp index edfeb401f1..15a5c8984c 100644 --- a/esphome/components/fastled_base/fastled_light.cpp +++ b/esphome/components/fastled_base/fastled_light.cpp @@ -10,7 +10,7 @@ void FastLEDLightOutput::setup() { ESP_LOGCONFIG(TAG, "Setting up FastLED light..."); this->controller_->init(); this->controller_->setLeds(this->leds_, this->num_leds_); - this->effect_data_ = new uint8_t[this->num_leds_]; + this->effect_data_ = new uint8_t[this->num_leds_]; // NOLINT if (!this->max_refresh_rate_.has_value()) { this->set_max_refresh_rate(this->controller_->getMaxRefreshRate()); } diff --git a/esphome/components/fastled_base/fastled_light.h b/esphome/components/fastled_base/fastled_light.h index ee85735dea..d1c470599d 100644 --- a/esphome/components/fastled_base/fastled_light.h +++ b/esphome/components/fastled_base/fastled_light.h @@ -30,7 +30,7 @@ class FastLEDLightOutput : public light::AddressableLight { CLEDController &add_leds(CLEDController *controller, int num_leds) { this->controller_ = controller; this->num_leds_ = num_leds; - this->leds_ = new CRGB[num_leds]; + this->leds_ = new CRGB[num_leds]; // NOLINT for (int i = 0; i < this->num_leds_; i++) this->leds_[i] = CRGB::Black; diff --git a/esphome/components/hm3301/hm3301.cpp b/esphome/components/hm3301/hm3301.cpp index 5612867d1b..96c1ec0ee9 100644 --- a/esphome/components/hm3301/hm3301.cpp +++ b/esphome/components/hm3301/hm3301.cpp @@ -12,7 +12,7 @@ static const uint8_t PM_10_0_VALUE_INDEX = 7; void HM3301Component::setup() { ESP_LOGCONFIG(TAG, "Setting up HM3301..."); - hm3301_ = new HM330X(); + hm3301_ = make_unique(); error_code_ = hm3301_->init(); if (error_code_ != NO_ERROR) { this->mark_failed(); diff --git a/esphome/components/hm3301/hm3301.h b/esphome/components/hm3301/hm3301.h index 5594f1719c..b024f93719 100644 --- a/esphome/components/hm3301/hm3301.h +++ b/esphome/components/hm3301/hm3301.h @@ -27,7 +27,7 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice { void update() override; protected: - HM330X *hm3301_; + std::unique_ptr hm3301_; HM330XErrorCode error_code_{NO_ERROR}; diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index 0fafa8cd86..bf5b24c4f2 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -75,10 +75,10 @@ void HttpRequestComponent::send(const std::vector } #ifdef ARDUINO_ARCH_ESP8266 -WiFiClient *HttpRequestComponent::get_wifi_client_() { +std::shared_ptr HttpRequestComponent::get_wifi_client_() { if (this->secure_) { if (this->wifi_client_secure_ == nullptr) { - this->wifi_client_secure_ = new BearSSL::WiFiClientSecure(); + this->wifi_client_secure_ = std::make_shared(); this->wifi_client_secure_->setInsecure(); this->wifi_client_secure_->setBufferSizes(512, 512); } @@ -86,7 +86,7 @@ WiFiClient *HttpRequestComponent::get_wifi_client_() { } if (this->wifi_client_ == nullptr) { - this->wifi_client_ = new WiFiClient(); + this->wifi_client_ = std::make_shared(); } return this->wifi_client_; } diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index 27919fe75d..740a4580b4 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -6,6 +6,7 @@ #include #include #include +#include #ifdef ARDUINO_ARCH_ESP32 #include @@ -51,9 +52,9 @@ class HttpRequestComponent : public Component { std::string body_; std::list

    headers_; #ifdef ARDUINO_ARCH_ESP8266 - WiFiClient *wifi_client_{nullptr}; - BearSSL::WiFiClientSecure *wifi_client_secure_{nullptr}; - WiFiClient *get_wifi_client_(); + std::shared_ptr wifi_client_; + std::shared_ptr wifi_client_secure_; + std::shared_ptr get_wifi_client_(); #endif }; diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 4720c8f9c9..aab5b1ce9b 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -6,21 +6,7 @@ namespace json { static const char *const TAG = "json"; -static char *global_json_build_buffer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static size_t global_json_build_buffer_size = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - -void reserve_global_json_build_buffer(size_t required_size) { - if (global_json_build_buffer_size == 0 || global_json_build_buffer_size < required_size) { - delete[] global_json_build_buffer; - global_json_build_buffer_size = std::max(required_size, global_json_build_buffer_size * 2); - - size_t remainder = global_json_build_buffer_size % 16U; - if (remainder != 0) - global_json_build_buffer_size += 16 - remainder; - - global_json_build_buffer = new char[global_json_build_buffer_size]; - } -} +static std::vector global_json_build_buffer; // NOLINT const char *build_json(const json_build_t &f, size_t *length) { global_json_buffer.clear(); @@ -35,16 +21,16 @@ const char *build_json(const json_build_t &f, size_t *length) { // Discovery | 372 | 356 | // Discovery | 336 | 311 | // Discovery | 408 | 393 | - reserve_global_json_build_buffer(global_json_buffer.size()); - size_t bytes_written = root.printTo(global_json_build_buffer, global_json_build_buffer_size); + global_json_build_buffer.reserve(global_json_buffer.size() + 1); + size_t bytes_written = root.printTo(global_json_build_buffer.data(), global_json_build_buffer.capacity()); - if (bytes_written >= global_json_build_buffer_size - 1) { - reserve_global_json_build_buffer(root.measureLength() + 1); - bytes_written = root.printTo(global_json_build_buffer, global_json_build_buffer_size); + if (bytes_written >= global_json_build_buffer.capacity() - 1) { + global_json_build_buffer.reserve(root.measureLength() + 1); + bytes_written = root.printTo(global_json_build_buffer.data(), global_json_build_buffer.capacity()); } *length = bytes_written; - return global_json_build_buffer; + return global_json_build_buffer.data(); } void parse_json(const std::string &data, const json_parse_t &f) { global_json_buffer.clear(); @@ -113,7 +99,7 @@ void VectorJsonBuffer::reserve(size_t size) { // NOLINT target_capacity *= 2; char *old_buffer = this->buffer_; - this->buffer_ = new char[target_capacity]; + this->buffer_ = new char[target_capacity]; // NOLINT if (old_buffer != nullptr && this->capacity_ != 0) { this->free_blocks_.push_back(old_buffer); memcpy(this->buffer_, old_buffer, this->capacity_); diff --git a/esphome/components/json/json_util.h b/esphome/components/json/json_util.h index aafd039b9a..65b2496b85 100644 --- a/esphome/components/json/json_util.h +++ b/esphome/components/json/json_util.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esphome/core/helpers.h" #include diff --git a/esphome/components/lcd_base/lcd_display.cpp b/esphome/components/lcd_base/lcd_display.cpp index 95b349b9c1..4c0d0ab3ea 100644 --- a/esphome/components/lcd_base/lcd_display.cpp +++ b/esphome/components/lcd_base/lcd_display.cpp @@ -29,7 +29,7 @@ static const uint8_t LCD_DISPLAY_FUNCTION_2_LINE = 0x08; static const uint8_t LCD_DISPLAY_FUNCTION_5X10_DOTS = 0x04; void LCDDisplay::setup() { - this->buffer_ = new uint8_t[this->rows_ * this->columns_]; + this->buffer_ = new uint8_t[this->rows_ * this->columns_]; // NOLINT for (uint8_t i = 0; i < this->rows_ * this->columns_; i++) this->buffer_[i] = ' '; diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 9d79037087..59dfa93429 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -123,7 +123,7 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) { Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart) : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size), uart_(uart) { // add 1 to buffer size for null terminator - this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; + this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT } void Logger::pre_setup() { diff --git a/esphome/components/max7219/max7219.cpp b/esphome/components/max7219/max7219.cpp index 91bea22e46..97886e53fa 100644 --- a/esphome/components/max7219/max7219.cpp +++ b/esphome/components/max7219/max7219.cpp @@ -117,7 +117,7 @@ float MAX7219Component::get_setup_priority() const { return setup_priority::PROC void MAX7219Component::setup() { ESP_LOGCONFIG(TAG, "Setting up MAX7219..."); this->spi_setup(); - this->buffer_ = new uint8_t[this->num_chips_ * 8]; + this->buffer_ = new uint8_t[this->num_chips_ * 8]; // NOLINT for (uint8_t i = 0; i < this->num_chips_ * 8; i++) this->buffer_[i] = 0; diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 6ddc080b53..6e376d0fef 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -139,8 +139,7 @@ void MQTTComponent::set_custom_command_topic(const std::string &custom_command_t void MQTTComponent::set_availability(std::string topic, std::string payload_available, std::string payload_not_available) { - delete this->availability_; - this->availability_ = new Availability(); + this->availability_ = make_unique(); this->availability_->topic = std::move(topic); this->availability_->payload_available = std::move(payload_available); this->availability_->payload_not_available = std::move(payload_not_available); diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 4d7a522d5f..83a0c06644 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esphome/core/component.h" #include "mqtt_client.h" @@ -171,7 +173,7 @@ class MQTTComponent : public Component { std::string custom_command_topic_{}; bool retain_{true}; bool discovery_enabled_{true}; - Availability *availability_{nullptr}; + std::unique_ptr availability_; bool resend_state_{false}; }; diff --git a/esphome/components/neopixelbus/neopixelbus_light.h b/esphome/components/neopixelbus/neopixelbus_light.h index 6fa3fb3cd9..5359bac61b 100644 --- a/esphome/components/neopixelbus/neopixelbus_light.h +++ b/esphome/components/neopixelbus/neopixelbus_light.h @@ -79,7 +79,7 @@ class NeoPixelBusLightOutputBase : public light::AddressableLight { (*this)[i] = Color(0, 0, 0, 0); } - this->effect_data_ = new uint8_t[this->size()]; + this->effect_data_ = new uint8_t[this->size()]; // NOLINT this->controller_->Begin(); } diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp index bf6e74cb38..c5bfa78efe 100644 --- a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp @@ -33,7 +33,7 @@ void NextionBinarySensor::update() { if (this->variable_name_.empty()) // This is a touch component return; - this->nextion_->add_to_get_queue(this); + this->nextion_->add_to_get_queue(shared_from_this()); } void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nextion) { @@ -48,7 +48,7 @@ void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nexti this->needs_to_send_update_ = true; } else { this->needs_to_send_update_ = false; - this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), (int) state); } } diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.h b/esphome/components/nextion/binary_sensor/nextion_binarysensor.h index b6b23ada85..b86ee74013 100644 --- a/esphome/components/nextion/binary_sensor/nextion_binarysensor.h +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.h @@ -10,7 +10,8 @@ class NextionBinarySensor; class NextionBinarySensor : public NextionComponent, public binary_sensor::BinarySensorInitiallyOff, - public PollingComponent { + public PollingComponent, + public std::enable_shared_from_this { public: NextionBinarySensor(NextionBase *nextion) { this->nextion_ = nextion; } diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 3f44fe4075..d56c370412 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -196,7 +196,7 @@ void Nextion::print_queue_members_() { ESP_LOGN(TAG, "print_queue_members_ (top 10) size %zu", this->nextion_queue_.size()); ESP_LOGN(TAG, "*******************************************"); int count = 0; - for (auto *i : this->nextion_queue_) { + for (auto &i : this->nextion_queue_) { if (count++ == 10) break; @@ -257,8 +257,9 @@ bool Nextion::remove_from_q_(bool report_empty) { return false; } - NextionQueue *nb = this->nextion_queue_.front(); - NextionComponentBase *component = nb->component; + auto nb = std::move(this->nextion_queue_.front()); + this->nextion_queue_.pop_front(); + auto &component = nb->component; ESP_LOGN(TAG, "Removing %s from the queue", component->get_variable_name().c_str()); @@ -266,10 +267,8 @@ bool Nextion::remove_from_q_(bool report_empty) { if (component->get_variable_name() == "sleep_wake") { this->is_sleeping_ = false; } - delete component; } - delete nb; - this->nextion_queue_.pop_front(); + return true; } @@ -358,7 +357,7 @@ void Nextion::process_nextion_commands_() { int index = 0; int found = -1; for (auto &nb : this->nextion_queue_) { - NextionComponentBase *component = nb->component; + auto &component = nb->component; if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { ESP_LOGW(TAG, "Nextion reported invalid Waveform ID %d or Channel # %d was used!", @@ -369,9 +368,6 @@ void Nextion::process_nextion_commands_() { found = index; - delete component; - delete nb; - break; } ++index; @@ -468,8 +464,9 @@ void Nextion::process_nextion_commands_() { break; } - NextionQueue *nb = this->nextion_queue_.front(); - NextionComponentBase *component = nb->component; + auto nb = std::move(this->nextion_queue_.front()); + this->nextion_queue_.pop_front(); + auto &component = nb->component; if (component->get_queue_type() != NextionQueueType::TEXT_SENSOR) { ESP_LOGE(TAG, "ERROR: Received string return but next in queue \"%s\" is not a text sensor", @@ -480,9 +477,6 @@ void Nextion::process_nextion_commands_() { component->set_state_from_string(to_process, true, false); } - delete nb; - this->nextion_queue_.pop_front(); - break; } // 0x71 0x01 0x02 0x03 0x04 0xFF 0xFF 0xFF @@ -511,8 +505,9 @@ void Nextion::process_nextion_commands_() { ++dataindex; } - NextionQueue *nb = this->nextion_queue_.front(); - NextionComponentBase *component = nb->component; + auto nb = std::move(this->nextion_queue_.front()); + this->nextion_queue_.pop_front(); + auto &component = nb->component; if (component->get_queue_type() != NextionQueueType::SENSOR && component->get_queue_type() != NextionQueueType::BINARY_SENSOR && @@ -526,9 +521,6 @@ void Nextion::process_nextion_commands_() { component->set_state_from_int(value, true, false); } - delete nb; - this->nextion_queue_.pop_front(); - break; } @@ -690,7 +682,7 @@ void Nextion::process_nextion_commands_() { int index = 0; int found = -1; for (auto &nb : this->nextion_queue_) { - auto component = nb->component; + auto &component = nb->component; if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { size_t buffer_to_send = component->get_wave_buffer().size() < 255 ? component->get_wave_buffer().size() : 255; // ADDT command can only send 255 @@ -707,8 +699,6 @@ void Nextion::process_nextion_commands_() { component->get_wave_buffer().begin() + buffer_to_send); } found = index; - delete component; - delete nb; break; } ++index; @@ -737,7 +727,7 @@ void Nextion::process_nextion_commands_() { if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) { for (int i = 0; i < this->nextion_queue_.size(); i++) { - NextionComponentBase *component = this->nextion_queue_[i]->component; + auto &component = this->nextion_queue_[i]->component; if (this->nextion_queue_[i]->queue_time + this->max_q_age_ms_ < ms) { if (this->nextion_queue_[i]->queue_time == 0) ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\" queue_time 0", @@ -754,12 +744,10 @@ void Nextion::process_nextion_commands_() { if (component->get_variable_name() == "sleep_wake") { this->is_sleeping_ = false; } - delete component; } - delete this->nextion_queue_[i]; - this->nextion_queue_.erase(this->nextion_queue_.begin() + i); + i--; } else { break; @@ -911,16 +899,16 @@ uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool * @param variable_name Name for the queue */ void Nextion::add_no_result_to_queue_(const std::string &variable_name) { - nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; + auto nextion_queue = make_unique(); - nextion_queue->component = new nextion::NextionComponentBase; + nextion_queue->component = make_unique(); nextion_queue->component->set_variable_name(variable_name); nextion_queue->queue_time = millis(); - this->nextion_queue_.push_back(nextion_queue); - ESP_LOGN(TAG, "Add to queue type: NORESULT component %s", nextion_queue->component->get_variable_name().c_str()); + + this->nextion_queue_.push_back(std::move(nextion_queue)); } /** @@ -991,7 +979,7 @@ bool Nextion::add_no_result_to_queue_with_printf_(const std::string &variable_na * @param is_sleep_safe The command is safe to send when the Nextion is sleeping */ -void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) { +void Nextion::add_no_result_to_queue_with_set(std::shared_ptr component, int state_value) { this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(), state_value); } @@ -1019,7 +1007,8 @@ void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &varia * @param state_value String value to set * @param is_sleep_safe The command is safe to send when the Nextion is sleeping */ -void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) { +void Nextion::add_no_result_to_queue_with_set(std::shared_ptr component, + const std::string &state_value) { this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(), state_value); } @@ -1039,11 +1028,11 @@ void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &varia state_value.c_str()); } -void Nextion::add_to_get_queue(NextionComponentBase *component) { +void Nextion::add_to_get_queue(std::shared_ptr component) { if ((!this->is_setup() && !this->ignore_is_setup_)) return; - nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; + auto nextion_queue = make_unique(); nextion_queue->component = component; nextion_queue->queue_time = millis(); @@ -1054,7 +1043,7 @@ void Nextion::add_to_get_queue(NextionComponentBase *component) { std::string command = "get " + component->get_variable_name_to_send(); if (this->send_command_(command)) { - this->nextion_queue_.push_back(nextion_queue); + this->nextion_queue_.push_back(std::move(nextion_queue)); } } @@ -1066,13 +1055,13 @@ void Nextion::add_to_get_queue(NextionComponentBase *component) { * @param buffer_to_send The buffer size * @param buffer_size The buffer data */ -void Nextion::add_addt_command_to_queue(NextionComponentBase *component) { +void Nextion::add_addt_command_to_queue(std::shared_ptr component) { if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) return; - nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; + auto nextion_queue = make_unique(); - nextion_queue->component = new nextion::NextionComponentBase; + nextion_queue->component = std::make_shared(); nextion_queue->queue_time = millis(); size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() @@ -1081,7 +1070,7 @@ void Nextion::add_addt_command_to_queue(NextionComponentBase *component) { std::string command = "addt " + to_string(component->get_component_id()) + "," + to_string(component->get_wave_channel_id()) + "," + to_string(buffer_to_send); if (this->send_command_(command)) { - this->nextion_queue_.push_back(nextion_queue); + this->nextion_queue_.push_back(std::move(nextion_queue)); } } diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 3a43a51975..45c4a629c4 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -707,17 +707,18 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state); void set_nextion_text_state(const std::string &name, const std::string &state); - void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) override; + void add_no_result_to_queue_with_set(std::shared_ptr component, int state_value) override; void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, int state_value) override; - void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) override; + void add_no_result_to_queue_with_set(std::shared_ptr component, + const std::string &state_value) override; void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, const std::string &state_value) override; - void add_to_get_queue(NextionComponentBase *component) override; + void add_to_get_queue(std::shared_ptr component) override; - void add_addt_command_to_queue(NextionComponentBase *component) override; + void add_addt_command_to_queue(std::shared_ptr component) override; void update_components_by_prefix(const std::string &prefix); @@ -728,7 +729,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; } protected: - std::deque nextion_queue_; + std::deque> nextion_queue_; uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag); void all_components_send_state_(bool force_update = false); uint64_t comok_sent_ = 0; diff --git a/esphome/components/nextion/nextion_base.h b/esphome/components/nextion/nextion_base.h index a24fd74060..d91c70c960 100644 --- a/esphome/components/nextion/nextion_base.h +++ b/esphome/components/nextion/nextion_base.h @@ -24,18 +24,19 @@ class NextionBase; class NextionBase { public: - virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) = 0; + virtual void add_no_result_to_queue_with_set(std::shared_ptr component, int state_value) = 0; virtual void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, int state_value) = 0; - virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) = 0; + virtual void add_no_result_to_queue_with_set(std::shared_ptr component, + const std::string &state_value) = 0; virtual void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, const std::string &state_value) = 0; - virtual void add_addt_command_to_queue(NextionComponentBase *component) = 0; + virtual void add_addt_command_to_queue(std::shared_ptr component) = 0; - virtual void add_to_get_queue(NextionComponentBase *component) = 0; + virtual void add_to_get_queue(std::shared_ptr component) = 0; virtual void set_component_background_color(const char *component, Color color) = 0; virtual void set_component_pressed_background_color(const char *component, Color color) = 0; diff --git a/esphome/components/nextion/nextion_component_base.h b/esphome/components/nextion/nextion_component_base.h index 71ad803bc4..2725d5a30c 100644 --- a/esphome/components/nextion/nextion_component_base.h +++ b/esphome/components/nextion/nextion_component_base.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include "esphome/core/defines.h" namespace esphome { @@ -22,7 +23,7 @@ class NextionComponentBase; class NextionQueue { public: virtual ~NextionQueue() = default; - NextionComponentBase *component; + std::shared_ptr component; uint32_t queue_time = 0; }; diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index 43c8867e56..a83618e888 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -275,12 +275,12 @@ void Nextion::upload_tft() { } else { #endif ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap()); - this->transfer_buffer_ = new (std::nothrow) uint8_t[chunk_size]; - if (this->transfer_buffer_ == nullptr) { // Try a smaller size + this->transfer_buffer_ = new (std::nothrow) uint8_t[chunk_size]; // NOLINT(cppcoreguidelines-owning-memory) + if (this->transfer_buffer_ == nullptr) { // Try a smaller size ESP_LOGD(TAG, "Could not allocate buffer size: %d trying 4096 instead", chunk_size); chunk_size = 4096; ESP_LOGD(TAG, "Allocating %d buffer", chunk_size); - this->transfer_buffer_ = new uint8_t[chunk_size]; + this->transfer_buffer_ = new (std::nothrow) uint8_t[chunk_size]; // NOLINT(cppcoreguidelines-owning-memory) if (!this->transfer_buffer_) this->upload_end_(); @@ -322,7 +322,7 @@ void Nextion::upload_end_() { WiFiClient *Nextion::get_wifi_client_() { if (this->tft_url_.compare(0, 6, "https:") == 0) { if (this->wifi_client_secure_ == nullptr) { - this->wifi_client_secure_ = new BearSSL::WiFiClientSecure(); + this->wifi_client_secure_ = new BearSSL::WiFiClientSecure(); // NOLINT(cppcoreguidelines-owning-memory) this->wifi_client_secure_->setInsecure(); this->wifi_client_secure_->setBufferSizes(512, 512); } @@ -330,7 +330,7 @@ WiFiClient *Nextion::get_wifi_client_() { } if (this->wifi_client_ == nullptr) { - this->wifi_client_ = new WiFiClient(); + this->wifi_client_ = new WiFiClient(); // NOLINT(cppcoreguidelines-owning-memory) } return this->wifi_client_; } diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp index bbcb465d85..1e79164546 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.cpp +++ b/esphome/components/nextion/sensor/nextion_sensor.cpp @@ -34,7 +34,7 @@ void NextionSensor::update() { return; if (this->wave_chan_id_ == UINT8_MAX) { - this->nextion_->add_to_get_queue(this); + this->nextion_->add_to_get_queue(shared_from_this()); } else { if (this->send_last_value_) { this->add_to_wave_buffer(this->last_value_); @@ -62,9 +62,9 @@ void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) { double to_multiply = pow(10, this->precision_); int state_value = (int) (state * to_multiply); - this->nextion_->add_no_result_to_queue_with_set(this, (int) state_value); + this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), (int) state_value); } else { - this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), (int) state); } } } @@ -103,7 +103,7 @@ void NextionSensor::wave_update_() { buffer_to_send, this->wave_buffer_.size(), this->component_id_, this->wave_chan_id_); #endif - this->nextion_->add_addt_command_to_queue(this); + this->nextion_->add_addt_command_to_queue(shared_from_this()); } } // namespace nextion diff --git a/esphome/components/nextion/sensor/nextion_sensor.h b/esphome/components/nextion/sensor/nextion_sensor.h index e4dde9a513..068ff0451b 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.h +++ b/esphome/components/nextion/sensor/nextion_sensor.h @@ -8,7 +8,10 @@ namespace esphome { namespace nextion { class NextionSensor; -class NextionSensor : public NextionComponent, public sensor::Sensor, public PollingComponent { +class NextionSensor : public NextionComponent, + public sensor::Sensor, + public PollingComponent, + public std::enable_shared_from_this { public: NextionSensor(NextionBase *nextion) { this->nextion_ = nextion; } void send_state_to_nextion() override { this->set_state(this->state, false, true); }; diff --git a/esphome/components/nextion/switch/nextion_switch.cpp b/esphome/components/nextion/switch/nextion_switch.cpp index 1f32ad3425..0bd958e0d8 100644 --- a/esphome/components/nextion/switch/nextion_switch.cpp +++ b/esphome/components/nextion/switch/nextion_switch.cpp @@ -20,7 +20,7 @@ void NextionSwitch::process_bool(const std::string &variable_name, bool on) { void NextionSwitch::update() { if (!this->nextion_->is_setup()) return; - this->nextion_->add_to_get_queue(this); + this->nextion_->add_to_get_queue(shared_from_this()); } void NextionSwitch::set_state(bool state, bool publish, bool send_to_nextion) { @@ -32,7 +32,7 @@ void NextionSwitch::set_state(bool state, bool publish, bool send_to_nextion) { this->needs_to_send_update_ = true; } else { this->needs_to_send_update_ = false; - this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), (int) state); } } if (publish) { diff --git a/esphome/components/nextion/switch/nextion_switch.h b/esphome/components/nextion/switch/nextion_switch.h index 1548287473..d7783e5c51 100644 --- a/esphome/components/nextion/switch/nextion_switch.h +++ b/esphome/components/nextion/switch/nextion_switch.h @@ -8,7 +8,10 @@ namespace esphome { namespace nextion { class NextionSwitch; -class NextionSwitch : public NextionComponent, public switch_::Switch, public PollingComponent { +class NextionSwitch : public NextionComponent, + public switch_::Switch, + public PollingComponent, + public std::enable_shared_from_this { public: NextionSwitch(NextionBase *nextion) { this->nextion_ = nextion; } diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.cpp b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp index 08f032df74..fa7cb35025 100644 --- a/esphome/components/nextion/text_sensor/nextion_textsensor.cpp +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp @@ -18,7 +18,7 @@ void NextionTextSensor::process_text(const std::string &variable_name, const std void NextionTextSensor::update() { if (!this->nextion_->is_setup()) return; - this->nextion_->add_to_get_queue(this); + this->nextion_->add_to_get_queue(shared_from_this()); } void NextionTextSensor::set_state(const std::string &state, bool publish, bool send_to_nextion) { @@ -29,7 +29,7 @@ void NextionTextSensor::set_state(const std::string &state, bool publish, bool s if (this->nextion_->is_sleeping() || !this->visible_) { this->needs_to_send_update_ = true; } else { - this->nextion_->add_no_result_to_queue_with_set(this, state); + this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), state); } } diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.h b/esphome/components/nextion/text_sensor/nextion_textsensor.h index 5716d0a008..762797727d 100644 --- a/esphome/components/nextion/text_sensor/nextion_textsensor.h +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.h @@ -8,7 +8,10 @@ namespace esphome { namespace nextion { class NextionTextSensor; -class NextionTextSensor : public NextionComponent, public text_sensor::TextSensor, public PollingComponent { +class NextionTextSensor : public NextionComponent, + public text_sensor::TextSensor, + public PollingComponent, + public std::enable_shared_from_this { public: NextionTextSensor(NextionBase *nextion) { this->nextion_ = nextion; } void update() override; diff --git a/esphome/components/nfc/ndef_message.cpp b/esphome/components/nfc/ndef_message.cpp index 4e295d4469..b1554f41ae 100644 --- a/esphome/components/nfc/ndef_message.cpp +++ b/esphome/components/nfc/ndef_message.cpp @@ -17,7 +17,7 @@ NdefMessage::NdefMessage(std::vector &data) { ESP_LOGVV(TAG, "me=%s, sr=%s, il=%s, tnf=%d", YESNO(me), YESNO(sr), YESNO(il), tnf); - auto record = new NdefRecord(); + auto record = make_unique(); record->set_tnf(tnf); uint8_t type_length = data[index++]; @@ -62,20 +62,20 @@ NdefMessage::NdefMessage(std::vector &data) { record->set_payload(payload_str); index += payload_length; - this->add_record(record); ESP_LOGV(TAG, "Adding record type %s = %s", record->get_type().c_str(), record->get_payload().c_str()); + this->add_record(std::move(record)); if (me) break; } } -bool NdefMessage::add_record(NdefRecord *record) { +bool NdefMessage::add_record(std::unique_ptr record) { if (this->records_.size() >= MAX_NDEF_RECORDS) { ESP_LOGE(TAG, "Too many records. Max: %d", MAX_NDEF_RECORDS); return false; } - this->records_.push_back(record); + this->records_.emplace_back(std::move(record)); return true; } @@ -83,13 +83,11 @@ bool NdefMessage::add_text_record(const std::string &text) { return this->add_te bool NdefMessage::add_text_record(const std::string &text, const std::string &encoding) { std::string payload = to_string(text.length()) + encoding + text; - auto r = new NdefRecord(TNF_WELL_KNOWN, "T", payload); - return this->add_record(r); + return this->add_record(make_unique(TNF_WELL_KNOWN, "T", payload)); } bool NdefMessage::add_uri_record(const std::string &uri) { - auto r = new NdefRecord(TNF_WELL_KNOWN, "U", uri); - return this->add_record(r); + return this->add_record(make_unique(TNF_WELL_KNOWN, "U", uri)); } std::vector NdefMessage::encode() { diff --git a/esphome/components/nfc/ndef_message.h b/esphome/components/nfc/ndef_message.h index e47a7b992a..d2ffa9582e 100644 --- a/esphome/components/nfc/ndef_message.h +++ b/esphome/components/nfc/ndef_message.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "ndef_record.h" @@ -11,12 +13,18 @@ static const uint8_t MAX_NDEF_RECORDS = 4; class NdefMessage { public: - NdefMessage(){}; + NdefMessage() = default; NdefMessage(std::vector &data); + NdefMessage(const NdefMessage &msg) { + records_.reserve(msg.records_.size()); + for (const auto &r : msg.records_) { + records_.emplace_back(make_unique(*r)); + } + } - std::vector get_records() { return this->records_; }; + const std::vector> &get_records() { return this->records_; }; - bool add_record(NdefRecord *record); + bool add_record(std::unique_ptr record); bool add_text_record(const std::string &text); bool add_text_record(const std::string &text, const std::string &encoding); bool add_uri_record(const std::string &uri); @@ -24,7 +32,7 @@ class NdefMessage { std::vector encode(); protected: - std::vector records_; + std::vector> records_; }; } // namespace nfc diff --git a/esphome/components/nfc/ndef_record.h b/esphome/components/nfc/ndef_record.h index 2059444882..680fe20986 100644 --- a/esphome/components/nfc/ndef_record.h +++ b/esphome/components/nfc/ndef_record.h @@ -85,9 +85,9 @@ class NdefRecord { std::vector encode(bool first, bool last); uint8_t get_tnf_byte(bool first, bool last); - const std::string &get_type() { return this->type_; }; - const std::string &get_id() { return this->id_; }; - const std::string &get_payload() { return this->payload_; }; + const std::string &get_type() const { return this->type_; }; + const std::string &get_id() const { return this->id_; }; + const std::string &get_payload() const { return this->payload_; }; protected: uint8_t tnf_; diff --git a/esphome/components/nfc/nfc_tag.h b/esphome/components/nfc/nfc_tag.h index ff0d1c39b4..ab6ca650e4 100644 --- a/esphome/components/nfc/nfc_tag.h +++ b/esphome/components/nfc/nfc_tag.h @@ -1,6 +1,9 @@ #pragma once +#include + #include "esphome/core/log.h" +#include "esphome/core/helpers.h" #include "ndef_message.h" namespace esphome { @@ -20,27 +23,33 @@ class NfcTag { this->uid_ = uid; this->tag_type_ = tag_type; }; - NfcTag(std::vector &uid, const std::string &tag_type, nfc::NdefMessage *ndef_message) { + NfcTag(std::vector &uid, const std::string &tag_type, std::unique_ptr ndef_message) { this->uid_ = uid; this->tag_type_ = tag_type; - this->ndef_message_ = ndef_message; + this->ndef_message_ = std::move(ndef_message); }; NfcTag(std::vector &uid, const std::string &tag_type, std::vector &ndef_data) { this->uid_ = uid; this->tag_type_ = tag_type; - this->ndef_message_ = new NdefMessage(ndef_data); + this->ndef_message_ = make_unique(ndef_data); }; + NfcTag(const NfcTag &rhs) { + uid_ = rhs.uid_; + tag_type_ = rhs.tag_type_; + if (rhs.ndef_message_ != nullptr) + ndef_message_ = make_unique(*rhs.ndef_message_); + } std::vector &get_uid() { return this->uid_; }; const std::string &get_tag_type() { return this->tag_type_; }; bool has_ndef_message() { return this->ndef_message_ != nullptr; }; - NdefMessage *get_ndef_message() { return this->ndef_message_; }; - void set_ndef_message(NdefMessage *ndef_message) { this->ndef_message_ = ndef_message; }; + const std::unique_ptr &get_ndef_message() { return this->ndef_message_; }; + void set_ndef_message(std::unique_ptr ndef_message) { this->ndef_message_ = std::move(ndef_message); }; protected: std::vector uid_; std::string tag_type_; - NdefMessage *ndef_message_{nullptr}; + std::unique_ptr ndef_message_; }; } // namespace nfc diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index ac72befb9e..e8ac1b9bcd 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -19,7 +19,7 @@ static const char *const TAG = "ota"; static const uint8_t OTA_VERSION_1_0 = 1; void OTAComponent::setup() { - this->server_ = new WiFiServer(this->port_); + this->server_ = make_unique(this->port_); this->server_->begin(); this->dump_config(); diff --git a/esphome/components/ota/ota_component.h b/esphome/components/ota/ota_component.h index 8b5830295e..04cea56ec2 100644 --- a/esphome/components/ota/ota_component.h +++ b/esphome/components/ota/ota_component.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esphome/core/component.h" #include "esphome/core/preferences.h" #include "esphome/core/helpers.h" @@ -80,7 +82,7 @@ class OTAComponent : public Component { uint16_t port_; - WiFiServer *server_{nullptr}; + std::unique_ptr server_{nullptr}; WiFiClient client_{}; bool has_safe_mode_{false}; ///< stores whether safe mode can be enabled. diff --git a/esphome/components/pca9685/output.py b/esphome/components/pca9685/output.py index 40f7b3cd74..b7681f9ba0 100644 --- a/esphome/components/pca9685/output.py +++ b/esphome/components/pca9685/output.py @@ -20,6 +20,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( async def to_code(config): paren = await cg.get_variable(config[CONF_PCA9685_ID]) - rhs = paren.create_channel(config[CONF_CHANNEL]) - var = cg.Pvariable(config[CONF_ID], rhs) + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_channel(config[CONF_CHANNEL])) + cg.add(paren.register_channel(var)) await output.register_output(var, config) diff --git a/esphome/components/pca9685/pca9685_output.cpp b/esphome/components/pca9685/pca9685_output.cpp index c3fd2b60e4..1ad6f4a665 100644 --- a/esphome/components/pca9685/pca9685_output.cpp +++ b/esphome/components/pca9685/pca9685_output.cpp @@ -123,11 +123,11 @@ void PCA9685Output::loop() { this->update_ = false; } -PCA9685Channel *PCA9685Output::create_channel(uint8_t channel) { - this->min_channel_ = std::min(this->min_channel_, channel); - this->max_channel_ = std::max(this->max_channel_, channel); - auto *c = new PCA9685Channel(this, channel); - return c; +void PCA9685Output::register_channel(PCA9685Channel *channel) { + auto c = channel->channel_; + this->min_channel_ = std::min(this->min_channel_, c); + this->max_channel_ = std::max(this->max_channel_, c); + channel->set_parent(this); } void PCA9685Channel::write_state(float state) { diff --git a/esphome/components/pca9685/pca9685_output.h b/esphome/components/pca9685/pca9685_output.h index 55b52fc660..bd3f7f15f9 100644 --- a/esphome/components/pca9685/pca9685_output.h +++ b/esphome/components/pca9685/pca9685_output.h @@ -20,14 +20,15 @@ extern const uint8_t PCA9685_MODE_OUTNE_LOW; class PCA9685Output; -class PCA9685Channel : public output::FloatOutput { +class PCA9685Channel : public output::FloatOutput, public Parented { public: - PCA9685Channel(PCA9685Output *parent, uint8_t channel) : parent_(parent), channel_(channel) {} + void set_channel(uint8_t channel) { channel_ = channel; } protected: + friend class PCA9685Output; + void write_state(float state) override; - PCA9685Output *parent_; uint8_t channel_; }; @@ -37,7 +38,7 @@ class PCA9685Output : public Component, public i2c::I2CDevice { PCA9685Output(float frequency, uint8_t mode = PCA9685_MODE_OUTPUT_ONACK | PCA9685_MODE_OUTPUT_TOTEM_POLE) : frequency_(frequency), mode_(mode) {} - PCA9685Channel *create_channel(uint8_t channel); + void register_channel(PCA9685Channel *channel); void setup() override; void dump_config() override; diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index 9c7adc13c1..8603adfbf4 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -881,7 +881,7 @@ void Pipsolar::add_polling_command_(const char *command, ENUMPollingCommand poll size_t length = strlen(command) + 1; const char *beg = command; const char *end = command + length; - used_polling_command.command = new uint8_t[length]; + used_polling_command.command = new uint8_t[length]; // NOLINT(cppcoreguidelines-owning-memory) size_t i = 0; for (; beg != end; ++beg, ++i) { used_polling_command.command[i] = (uint8_t)(*beg); diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index fcab9872c4..8fda19cba3 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -104,7 +104,7 @@ void PN532::loop() { if (!success) { // Something failed if (!this->current_uid_.empty()) { - auto tag = new nfc::NfcTag(this->current_uid_); + auto tag = make_unique(this->current_uid_); for (auto *trigger : this->triggers_ontagremoved_) trigger->process(tag); } @@ -117,7 +117,7 @@ void PN532::loop() { if (num_targets != 1) { // no tags found or too many if (!this->current_uid_.empty()) { - auto tag = new nfc::NfcTag(this->current_uid_); + auto tag = make_unique(this->current_uid_); for (auto *trigger : this->triggers_ontagremoved_) trigger->process(tag); } @@ -158,10 +158,10 @@ void PN532::loop() { if (report) { ESP_LOGD(TAG, "Found new tag '%s'", nfc::format_uid(nfcid).c_str()); if (tag->has_ndef_message()) { - auto message = tag->get_ndef_message(); - auto records = message->get_records(); + const auto &message = tag->get_ndef_message(); + const auto &records = message->get_records(); ESP_LOGD(TAG, " NDEF formatted records:"); - for (auto &record : records) { + for (const auto &record : records) { ESP_LOGD(TAG, " %s - %s", record->get_type().c_str(), record->get_payload().c_str()); } } @@ -270,7 +270,7 @@ void PN532::turn_off_rf_() { }); } -nfc::NfcTag *PN532::read_tag_(std::vector &uid) { +std::unique_ptr PN532::read_tag_(std::vector &uid) { uint8_t type = nfc::guess_tag_type(uid.size()); if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) { @@ -281,9 +281,9 @@ nfc::NfcTag *PN532::read_tag_(std::vector &uid) { return this->read_mifare_ultralight_tag_(uid); } else if (type == nfc::TAG_TYPE_UNKNOWN) { ESP_LOGV(TAG, "Cannot determine tag type"); - return new nfc::NfcTag(uid); + return make_unique(uid); } else { - return new nfc::NfcTag(uid); + return make_unique(uid); } } @@ -373,7 +373,9 @@ bool PN532BinarySensor::process(std::vector &data) { this->found_ = true; return true; } -void PN532OnTagTrigger::process(nfc::NfcTag *tag) { this->trigger(nfc::format_uid(tag->get_uid()), *tag); } +void PN532OnTagTrigger::process(const std::unique_ptr &tag) { + this->trigger(nfc::format_uid(tag->get_uid()), *tag); +} } // namespace pn532 } // namespace esphome diff --git a/esphome/components/pn532/pn532.h b/esphome/components/pn532/pn532.h index c4854cf7f2..692a5011e6 100644 --- a/esphome/components/pn532/pn532.h +++ b/esphome/components/pn532/pn532.h @@ -54,13 +54,13 @@ class PN532 : public PollingComponent { virtual bool read_data(std::vector &data, uint8_t len) = 0; virtual bool read_response(uint8_t command, std::vector &data) = 0; - nfc::NfcTag *read_tag_(std::vector &uid); + std::unique_ptr read_tag_(std::vector &uid); bool format_tag_(std::vector &uid); bool clean_tag_(std::vector &uid); bool write_tag_(std::vector &uid, nfc::NdefMessage *message); - nfc::NfcTag *read_mifare_classic_tag_(std::vector &uid); + std::unique_ptr read_mifare_classic_tag_(std::vector &uid); bool read_mifare_classic_block_(uint8_t block_num, std::vector &data); bool write_mifare_classic_block_(uint8_t block_num, std::vector &data); bool auth_mifare_classic_block_(std::vector &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key); @@ -68,7 +68,7 @@ class PN532 : public PollingComponent { bool format_mifare_classic_ndef_(std::vector &uid); bool write_mifare_classic_tag_(std::vector &uid, nfc::NdefMessage *message); - nfc::NfcTag *read_mifare_ultralight_tag_(std::vector &uid); + std::unique_ptr read_mifare_ultralight_tag_(std::vector &uid); bool read_mifare_ultralight_page_(uint8_t page_num, std::vector &data); bool is_mifare_ultralight_formatted_(); uint16_t read_mifare_ultralight_capacity_(); @@ -117,7 +117,7 @@ class PN532BinarySensor : public binary_sensor::BinarySensor { class PN532OnTagTrigger : public Trigger { public: - void process(nfc::NfcTag *tag); + void process(const std::unique_ptr &tag); }; class PN532OnFinishedWriteTrigger : public Trigger<> { diff --git a/esphome/components/pn532/pn532_mifare_classic.cpp b/esphome/components/pn532/pn532_mifare_classic.cpp index 119d1ceef6..fa99e5cfab 100644 --- a/esphome/components/pn532/pn532_mifare_classic.cpp +++ b/esphome/components/pn532/pn532_mifare_classic.cpp @@ -6,7 +6,7 @@ namespace pn532 { static const char *const TAG = "pn532.mifare_classic"; -nfc::NfcTag *PN532::read_mifare_classic_tag_(std::vector &uid) { +std::unique_ptr PN532::read_mifare_classic_tag_(std::vector &uid) { uint8_t current_block = 4; uint8_t message_start_index = 0; uint32_t message_length = 0; @@ -15,15 +15,15 @@ nfc::NfcTag *PN532::read_mifare_classic_tag_(std::vector &uid) { std::vector data; if (this->read_mifare_classic_block_(current_block, data)) { if (!nfc::decode_mifare_classic_tlv(data, message_length, message_start_index)) { - return new nfc::NfcTag(uid, nfc::ERROR); + return make_unique(uid, nfc::ERROR); } } else { ESP_LOGE(TAG, "Failed to read block %d", current_block); - return new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC); + return make_unique(uid, nfc::MIFARE_CLASSIC); } } else { ESP_LOGV(TAG, "Tag is not NDEF formatted"); - return new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC); + return make_unique(uid, nfc::MIFARE_CLASSIC); } uint32_t index = 0; @@ -51,7 +51,7 @@ nfc::NfcTag *PN532::read_mifare_classic_tag_(std::vector &uid) { } } buffer.erase(buffer.begin(), buffer.begin() + message_start_index); - return new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC, buffer); + return make_unique(uid, nfc::MIFARE_CLASSIC, buffer); } bool PN532::read_mifare_classic_block_(uint8_t block_num, std::vector &data) { diff --git a/esphome/components/pn532/pn532_mifare_ultralight.cpp b/esphome/components/pn532/pn532_mifare_ultralight.cpp index 1787a3c225..6f118419ba 100644 --- a/esphome/components/pn532/pn532_mifare_ultralight.cpp +++ b/esphome/components/pn532/pn532_mifare_ultralight.cpp @@ -6,28 +6,28 @@ namespace pn532 { static const char *const TAG = "pn532.mifare_ultralight"; -nfc::NfcTag *PN532::read_mifare_ultralight_tag_(std::vector &uid) { +std::unique_ptr PN532::read_mifare_ultralight_tag_(std::vector &uid) { if (!this->is_mifare_ultralight_formatted_()) { ESP_LOGD(TAG, "Not NDEF formatted"); - return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2); + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } uint8_t message_length; uint8_t message_start_index; if (!this->find_mifare_ultralight_ndef_(message_length, message_start_index)) { - return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2); + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } ESP_LOGVV(TAG, "message length: %d, start: %d", message_length, message_start_index); if (message_length == 0) { - return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2); + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } std::vector data; for (uint8_t page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; page < nfc::MIFARE_ULTRALIGHT_MAX_PAGE; page++) { std::vector page_data; if (!this->read_mifare_ultralight_page_(page, page_data)) { ESP_LOGE(TAG, "Error reading page %d", page); - return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2); + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } data.insert(data.end(), page_data.begin(), page_data.end()); @@ -38,7 +38,7 @@ nfc::NfcTag *PN532::read_mifare_ultralight_tag_(std::vector &uid) { data.erase(data.begin(), data.begin() + message_start_index); data.erase(data.begin() + message_length, data.end()); - return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2, data); + return make_unique(uid, nfc::NFC_FORUM_TYPE_2, data); } bool PN532::read_mifare_ultralight_page_(uint8_t page_num, std::vector &data) { diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index 838c92f04e..8dfd992abe 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -221,10 +221,9 @@ uint8_t SCD30Component::sht_crc_(uint8_t data1, uint8_t data2) { bool SCD30Component::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; - auto *buf = new uint8_t[num_bytes]; + std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf, num_bytes)) { - delete[](buf); + if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { return false; } @@ -233,13 +232,11 @@ bool SCD30Component::read_data_(uint16_t *data, uint8_t len) { uint8_t crc = sht_crc_(buf[j], buf[j + 1]); if (crc != buf[j + 2]) { ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - delete[](buf); return false; } data[i] = (buf[j] << 8) | buf[j + 1]; } - delete[](buf); return true; } diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index e30d6d3adc..5a6f438429 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -332,10 +332,9 @@ uint8_t SGP30Component::sht_crc_(uint8_t data1, uint8_t data2) { bool SGP30Component::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; - auto *buf = new uint8_t[num_bytes]; + std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf, num_bytes)) { - delete[](buf); + if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { return false; } @@ -344,13 +343,11 @@ bool SGP30Component::read_data_(uint16_t *data, uint8_t len) { uint8_t crc = sht_crc_(buf[j], buf[j + 1]); if (crc != buf[j + 2]) { ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - delete[](buf); return false; } data[i] = (buf[j] << 8) | buf[j + 1]; } - delete[](buf); return true; } diff --git a/esphome/components/sht3xd/sht3xd.cpp b/esphome/components/sht3xd/sht3xd.cpp index 0bf2cc1a81..be5b93c124 100644 --- a/esphome/components/sht3xd/sht3xd.cpp +++ b/esphome/components/sht3xd/sht3xd.cpp @@ -101,10 +101,9 @@ uint8_t sht_crc(uint8_t data1, uint8_t data2) { bool SHT3XDComponent::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; - auto *buf = new uint8_t[num_bytes]; + std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf, num_bytes)) { - delete[](buf); + if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { return false; } @@ -113,13 +112,11 @@ bool SHT3XDComponent::read_data_(uint16_t *data, uint8_t len) { uint8_t crc = sht_crc(buf[j], buf[j + 1]); if (crc != buf[j + 2]) { ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - delete[](buf); return false; } data[i] = (buf[j] << 8) | buf[j + 1]; } - delete[](buf); return true; } diff --git a/esphome/components/shtcx/shtcx.cpp b/esphome/components/shtcx/shtcx.cpp index 848da8d7d7..db3a3bf803 100644 --- a/esphome/components/shtcx/shtcx.cpp +++ b/esphome/components/shtcx/shtcx.cpp @@ -130,10 +130,9 @@ uint8_t sht_crc(uint8_t data1, uint8_t data2) { bool SHTCXComponent::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; - auto *buf = new uint8_t[num_bytes]; + std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf, num_bytes)) { - delete[](buf); + if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { return false; } @@ -142,13 +141,11 @@ bool SHTCXComponent::read_data_(uint16_t *data, uint8_t len) { uint8_t crc = sht_crc(buf[j], buf[j + 1]); if (crc != buf[j + 2]) { ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - delete[](buf); return false; } data[i] = (buf[j] << 8) | buf[j + 1]; } - delete[](buf); return true; } diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index aaeee7268a..29e1b23823 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -423,9 +423,9 @@ class LWIPRawImpl : public Socket { // nothing to do here, we just don't push it to the queue return ERR_OK; } - auto *sock = new LWIPRawImpl(newpcb); + auto sock = std::unique_ptr(new LWIPRawImpl(newpcb)); sock->init(); - accepted_sockets_.emplace(sock); + accepted_sockets_.push(std::move(sock)); return ERR_OK; } void err_fn(err_t err) { @@ -486,7 +486,7 @@ std::unique_ptr socket(int domain, int type, int protocol) { auto *pcb = tcp_new(); if (pcb == nullptr) return nullptr; - auto *sock = new LWIPRawImpl(pcb); + auto *sock = new LWIPRawImpl(pcb); // NOLINT(cppcoreguidelines-owning-memory) sock->init(); return std::unique_ptr{sock}; } diff --git a/esphome/components/sps30/sps30.cpp b/esphome/components/sps30/sps30.cpp index 30bd134626..3a31f4d607 100644 --- a/esphome/components/sps30/sps30.cpp +++ b/esphome/components/sps30/sps30.cpp @@ -242,10 +242,9 @@ bool SPS30Component::start_continuous_measurement_() { bool SPS30Component::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; - auto *buf = new uint8_t[num_bytes]; + std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf, num_bytes)) { - delete[](buf); + if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { return false; } @@ -254,13 +253,11 @@ bool SPS30Component::read_data_(uint16_t *data, uint8_t len) { uint8_t crc = sht_crc_(buf[j], buf[j + 1]); if (crc != buf[j + 2]) { ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - delete[](buf); return false; } data[i] = (buf[j] << 8) | buf[j + 1]; } - delete[](buf); return true; } diff --git a/esphome/components/sts3x/sts3x.cpp b/esphome/components/sts3x/sts3x.cpp index 5d6d725a17..77383c6daa 100644 --- a/esphome/components/sts3x/sts3x.cpp +++ b/esphome/components/sts3x/sts3x.cpp @@ -97,10 +97,9 @@ uint8_t sts3x_crc(uint8_t data1, uint8_t data2) { bool STS3XComponent::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; - auto *buf = new uint8_t[num_bytes]; + std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf, num_bytes)) { - delete[](buf); + if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { return false; } @@ -109,13 +108,11 @@ bool STS3XComponent::read_data_(uint16_t *data, uint8_t len) { uint8_t crc = sts3x_crc(buf[j], buf[j + 1]); if (crc != buf[j + 2]) { ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - delete[](buf); return false; } data[i] = (buf[j] << 8) | buf[j + 1]; } - delete[](buf); return true; } diff --git a/esphome/components/tlc59208f/output.py b/esphome/components/tlc59208f/output.py index f7cc89b252..ac45909673 100644 --- a/esphome/components/tlc59208f/output.py +++ b/esphome/components/tlc59208f/output.py @@ -20,6 +20,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( async def to_code(config): paren = await cg.get_variable(config[CONF_TLC59208F_ID]) - rhs = paren.create_channel(config[CONF_CHANNEL]) - var = cg.Pvariable(config[CONF_ID], rhs) + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_channel(config[CONF_CHANNEL])) + cg.add(paren.register_channel(var)) await output.register_output(var, config) diff --git a/esphome/components/tlc59208f/tlc59208f_output.cpp b/esphome/components/tlc59208f/tlc59208f_output.cpp index 784a0947ed..e416a5c62b 100644 --- a/esphome/components/tlc59208f/tlc59208f_output.cpp +++ b/esphome/components/tlc59208f/tlc59208f_output.cpp @@ -137,11 +137,11 @@ void TLC59208FOutput::loop() { this->update_ = false; } -TLC59208FChannel *TLC59208FOutput::create_channel(uint8_t channel) { - this->min_channel_ = std::min(this->min_channel_, channel); - this->max_channel_ = std::max(this->max_channel_, channel); - auto *c = new TLC59208FChannel(this, channel); - return c; +void TLC59208FOutput::register_channel(TLC59208FChannel *channel) { + auto c = channel->channel_; + this->min_channel_ = std::min(this->min_channel_, c); + this->max_channel_ = std::max(this->max_channel_, c); + channel->set_parent(this); } void TLC59208FChannel::write_state(float state) { diff --git a/esphome/components/tlc59208f/tlc59208f_output.h b/esphome/components/tlc59208f/tlc59208f_output.h index 06b7adc882..79fccc97f3 100644 --- a/esphome/components/tlc59208f/tlc59208f_output.h +++ b/esphome/components/tlc59208f/tlc59208f_output.h @@ -21,14 +21,15 @@ extern const uint8_t TLC59208F_MODE2_WDT_35MS; class TLC59208FOutput; -class TLC59208FChannel : public output::FloatOutput { +class TLC59208FChannel : public output::FloatOutput, public Parented { public: - TLC59208FChannel(TLC59208FOutput *parent, uint8_t channel) : parent_(parent), channel_(channel) {} + void set_channel(uint8_t channel) { channel_ = channel; } protected: + friend class TLC59208FOutput; + void write_state(float state) override; - TLC59208FOutput *parent_; uint8_t channel_; }; @@ -37,7 +38,7 @@ class TLC59208FOutput : public Component, public i2c::I2CDevice { public: TLC59208FOutput(uint8_t mode = TLC59208F_MODE2_OCH) : mode_(mode) {} - TLC59208FChannel *create_channel(uint8_t channel); + void register_channel(TLC59208FChannel *channel); void setup() override; void dump_config() override; diff --git a/esphome/components/tm1651/tm1651.cpp b/esphome/components/tm1651/tm1651.cpp index bb37e9f4c9..3689ef5894 100644 --- a/esphome/components/tm1651/tm1651.cpp +++ b/esphome/components/tm1651/tm1651.cpp @@ -1,5 +1,6 @@ #include "tm1651.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" namespace esphome { namespace tm1651 { @@ -19,7 +20,7 @@ void TM1651Display::setup() { uint8_t clk = clk_pin_->get_pin(); uint8_t dio = dio_pin_->get_pin(); - battery_display_ = new TM1651(clk, dio); + battery_display_ = make_unique(clk, dio); battery_display_->init(); battery_display_->clearDisplay(); } diff --git a/esphome/components/tm1651/tm1651.h b/esphome/components/tm1651/tm1651.h index 6eab24687c..a18519bf41 100644 --- a/esphome/components/tm1651/tm1651.h +++ b/esphome/components/tm1651/tm1651.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esphome/core/component.h" #include "esphome/core/esphal.h" #include "esphome/core/automation.h" @@ -25,7 +27,7 @@ class TM1651Display : public Component { void turn_off(); protected: - TM1651 *battery_display_; + std::unique_ptr battery_display_; GPIOPin *clk_pin_; GPIOPin *dio_pin_; bool is_on_ = true; diff --git a/esphome/components/uart/uart_esp8266.cpp b/esphome/components/uart/uart_esp8266.cpp index bc6b0f72d3..6d207fde6c 100644 --- a/esphome/components/uart/uart_esp8266.cpp +++ b/esphome/components/uart/uart_esp8266.cpp @@ -85,7 +85,7 @@ void UARTComponent::setup() { this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); } else { - this->sw_serial_ = new ESP8266SoftwareSerial(); + this->sw_serial_ = new ESP8266SoftwareSerial(); // NOLINT int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1; int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1; this->sw_serial_->setup(tx, rx, this->baud_rate_, this->stop_bits_, this->data_bits_, this->parity_, @@ -227,7 +227,7 @@ void ESP8266SoftwareSerial::setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_ra pin.setup(); this->gpio_rx_pin_ = &pin; this->rx_pin_ = pin.to_isr(); - this->rx_buffer_ = new uint8_t[this->rx_buffer_size_]; + this->rx_buffer_ = new uint8_t[this->rx_buffer_size_]; // NOLINT pin.attach_interrupt(ESP8266SoftwareSerial::gpio_intr, this, FALLING); } } diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index 85711704b9..9f04c3b7bf 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -86,7 +86,9 @@ void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) { request->send(response); } -void WebServerBase::add_ota_handler() { this->add_handler(new OTARequestHandler(this)); } +void WebServerBase::add_ota_handler() { + this->add_handler(new OTARequestHandler(this)); // NOLINT +} float WebServerBase::get_setup_priority() const { // Before WiFi (captive portal) return setup_priority::WIFI + 2.0f; diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index b6024ceafa..158006ae40 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -1,5 +1,6 @@ #pragma once +#include #include "esphome/core/component.h" #include @@ -14,7 +15,7 @@ class WebServerBase : public Component { this->initialized_++; return; } - this->server_ = new AsyncWebServer(this->port_); + this->server_ = std::make_shared(this->port_); this->server_->begin(); for (auto *handler : this->handlers_) @@ -25,11 +26,10 @@ class WebServerBase : public Component { void deinit() { this->initialized_--; if (this->initialized_ == 0) { - delete this->server_; this->server_ = nullptr; } } - AsyncWebServer *get_server() const { return server_; } + std::shared_ptr get_server() const { return server_; } float get_setup_priority() const override; void add_handler(AsyncWebHandler *handler) { @@ -50,7 +50,7 @@ class WebServerBase : public Component { int initialized_{0}; uint16_t port_{80}; - AsyncWebServer *server_{nullptr}; + std::shared_ptr server_{nullptr}; std::vector handlers_; }; diff --git a/esphome/components/wled/wled_light_effect.cpp b/esphome/components/wled/wled_light_effect.cpp index 915d1c6cc2..500b9bbad2 100644 --- a/esphome/components/wled/wled_light_effect.cpp +++ b/esphome/components/wled/wled_light_effect.cpp @@ -1,5 +1,6 @@ #include "wled_light_effect.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" #ifdef ARDUINO_ARCH_ESP32 #include @@ -48,7 +49,7 @@ void WLEDLightEffect::blank_all_leds_(light::AddressableLight &it) { void WLEDLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) { // Init UDP lazily if (!udp_) { - udp_.reset(new WiFiUDP()); + udp_ = make_unique(); if (!udp_->begin(port_)) { ESP_LOGW(TAG, "Cannot bind WLEDLightEffect to %d.", port_); diff --git a/esphome/core/esphal.cpp b/esphome/core/esphal.cpp index 8fb1dd86c5..849fdf95ad 100644 --- a/esphome/core/esphal.cpp +++ b/esphome/core/esphal.cpp @@ -230,10 +230,11 @@ void GPIOPin::attach_interrupt_(void (*func)(void *), void *arg, int mode) const } } #ifdef ARDUINO_ARCH_ESP8266 - ArgStructure *as = new ArgStructure; + ArgStructure *as = new ArgStructure; // NOLINT as->interruptInfo = nullptr; as->functionInfo = new ESPHomeInterruptFuncInfo{ + // NOLINT .func = func, .arg = arg, }; @@ -249,7 +250,7 @@ void GPIOPin::attach_interrupt_(void (*func)(void *), void *arg, int mode) const } ISRInternalGPIOPin *GPIOPin::to_isr() const { - return new ISRInternalGPIOPin(this->pin_, + return new ISRInternalGPIOPin(this->pin_, // NOLINT #ifdef ARDUINO_ARCH_ESP32 this->gpio_clear_, this->gpio_set_, #endif diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index c5ff0102c3..53c7d1de43 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -141,21 +141,6 @@ std::string uint32_to_string(uint32_t num) { snprintf(buffer, sizeof(buffer), "%04X%04X", address16[1], address16[0]); return std::string(buffer); } -static char *global_json_build_buffer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static size_t global_json_build_buffer_size = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - -void reserve_global_json_build_buffer(size_t required_size) { - if (global_json_build_buffer_size == 0 || global_json_build_buffer_size < required_size) { - delete[] global_json_build_buffer; - global_json_build_buffer_size = std::max(required_size, global_json_build_buffer_size * 2); - - size_t remainder = global_json_build_buffer_size % 16U; - if (remainder != 0) - global_json_build_buffer_size += 16 - remainder; - - global_json_build_buffer = new char[global_json_build_buffer_size]; - } -} ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { if (on == nullptr && strcasecmp(str, "on") == 0) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index a63c627e5a..044de6c8b0 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -338,7 +338,7 @@ template T *new_buffer(size_t length) { buffer = new T[length]; } #else - buffer = new T[length]; + buffer = new T[length]; // NOLINT #endif return buffer; diff --git a/esphome/core/preferences.cpp b/esphome/core/preferences.cpp index bfc5d9f875..f051ce0e8a 100644 --- a/esphome/core/preferences.cpp +++ b/esphome/core/preferences.cpp @@ -17,13 +17,8 @@ namespace esphome { static const char *const TAG = "preferences"; -ESPPreferenceObject::ESPPreferenceObject() : offset_(0), length_words_(0), type_(0), data_(nullptr) {} ESPPreferenceObject::ESPPreferenceObject(size_t offset, size_t length, uint32_t type) - : offset_(offset), length_words_(length), type_(type) { - this->data_ = new uint32_t[this->length_words_ + 1]; // NOLINT(cppcoreguidelines-prefer-member-initializer) - for (uint32_t i = 0; i < this->length_words_ + 1; i++) - this->data_[i] = 0; -} + : offset_(offset), length_words_(length), type_(type), data_(length + 1) {} bool ESPPreferenceObject::load_() { if (!this->is_initialized()) { ESP_LOGV(TAG, "Load Pref Not initialized!"); @@ -173,7 +168,7 @@ ESPPreferences::ESPPreferences() : current_offset_(0) {} void ESPPreferences::begin() { - this->flash_storage_ = new uint32_t[ESP8266_FLASH_STORAGE_SIZE]; + this->flash_storage_ = new uint32_t[ESP8266_FLASH_STORAGE_SIZE]; // NOLINT ESP_LOGVV(TAG, "Loading preferences from flash..."); { @@ -234,7 +229,7 @@ bool ESPPreferenceObject::save_internal_() { char key[32]; sprintf(key, "%u", this->offset_); uint32_t len = (this->length_words_ + 1) * 4; - esp_err_t err = nvs_set_blob(global_preferences.nvs_handle_, key, this->data_, len); + esp_err_t err = nvs_set_blob(global_preferences.nvs_handle_, key, this->data_.data(), len); if (err) { ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", key, len, esp_err_to_name(err)); return false; @@ -264,7 +259,7 @@ bool ESPPreferenceObject::load_internal_() { ESP_LOGVV(TAG, "NVS length does not match. Assuming key changed (%u!=%u)", actual_len, len); return false; } - err = nvs_get_blob(global_preferences.nvs_handle_, key, this->data_, &len); + err = nvs_get_blob(global_preferences.nvs_handle_, key, this->data_.data(), &len); if (err) { ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key, esp_err_to_name(err)); return false; @@ -301,7 +296,7 @@ uint32_t ESPPreferenceObject::calculate_crc_() const { } return crc; } -bool ESPPreferenceObject::is_initialized() const { return this->data_ != nullptr; } +bool ESPPreferenceObject::is_initialized() const { return !this->data_.empty(); } ESPPreferences global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/core/preferences.h b/esphome/core/preferences.h index 699871ca00..7ed9a2c8c5 100644 --- a/esphome/core/preferences.h +++ b/esphome/core/preferences.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include #include "esphome/core/defines.h" @@ -9,7 +11,7 @@ namespace esphome { class ESPPreferenceObject { public: - ESPPreferenceObject(); + ESPPreferenceObject() = default; ESPPreferenceObject(size_t offset, size_t length, uint32_t type); template bool save(T *src); @@ -28,12 +30,12 @@ class ESPPreferenceObject { uint32_t calculate_crc_() const; - size_t offset_; - size_t length_words_; - uint32_t type_; - uint32_t *data_; + size_t offset_ = 0; + size_t length_words_ = 0; + uint32_t type_ = 0; + std::vector data_; #ifdef ARDUINO_ARCH_ESP8266 - bool in_flash_{false}; + bool in_flash_ = false; #endif }; @@ -92,17 +94,18 @@ template ESPPreferenceObject ESPPreferences::make_preference(uint32_ template bool ESPPreferenceObject::save(T *src) { if (!this->is_initialized()) return false; - memset(this->data_, 0, this->length_words_ * 4); - memcpy(this->data_, src, sizeof(T)); + // ensure all bytes are 0 (in case sizeof(T) is not multiple of 4) + std::fill_n(data_.begin(), length_words_, 0); + memcpy(data_.data(), src, sizeof(T)); return this->save_(); } template bool ESPPreferenceObject::load(T *dest) { - memset(this->data_, 0, this->length_words_ * 4); + std::fill_n(data_.begin(), length_words_, 0); if (!this->load_()) return false; - memcpy(dest, this->data_, sizeof(T)); + memcpy(dest, data_.data(), sizeof(T)); return true; } diff --git a/tests/dummy_main.cpp b/tests/dummy_main.cpp index c3b192d15f..f9ba868096 100644 --- a/tests/dummy_main.cpp +++ b/tests/dummy_main.cpp @@ -13,23 +13,23 @@ using namespace esphome; void setup() { App.pre_setup("livingroom", __DATE__ ", " __TIME__, false); - auto *log = new logger::Logger(115200, 512, logger::UART_SELECTION_UART0); + auto *log = new logger::Logger(115200, 512, logger::UART_SELECTION_UART0); // NOLINT log->pre_setup(); App.register_component(log); - auto *wifi = new wifi::WiFiComponent(); + auto *wifi = new wifi::WiFiComponent(); // NOLINT App.register_component(wifi); wifi::WiFiAP ap; ap.set_ssid("Test SSID"); ap.set_password("password1"); wifi->add_sta(ap); - auto *ota = new ota::OTAComponent(); + auto *ota = new ota::OTAComponent(); // NOLINT ota->set_port(8266); - auto *gpio = new gpio::GPIOSwitch(); + auto *gpio = new gpio::GPIOSwitch(); // NOLINT gpio->set_name("GPIO Switch"); - gpio->set_pin(new GPIOPin(8, OUTPUT, false)); + gpio->set_pin(new GPIOPin(8, OUTPUT, false)); // NOLINT App.register_component(gpio); App.register_switch(gpio); From aad03f1bf59e4e812109a6311422411cf748affd Mon Sep 17 00:00:00 2001 From: Tercio Filho Date: Mon, 13 Sep 2021 10:36:01 -0300 Subject: [PATCH 1314/1841] Fix issue #2054. PZEM004T Component doesn't set the module address. (#1784) --- esphome/components/pzem004t/pzem004t.cpp | 8 ++++++++ esphome/components/pzem004t/pzem004t.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/esphome/components/pzem004t/pzem004t.cpp b/esphome/components/pzem004t/pzem004t.cpp index 969ce8fa10..e5418765bd 100644 --- a/esphome/components/pzem004t/pzem004t.cpp +++ b/esphome/components/pzem004t/pzem004t.cpp @@ -6,6 +6,14 @@ namespace pzem004t { static const char *const TAG = "pzem004t"; +void PZEM004T::setup() { + // Clear UART buffer + while (this->available()) + this->read(); + // Set module address + this->write_state_(SET_ADDRESS); +} + void PZEM004T::loop() { const uint32_t now = millis(); if (now - this->last_read_ > 500 && this->available() < 7) { diff --git a/esphome/components/pzem004t/pzem004t.h b/esphome/components/pzem004t/pzem004t.h index 517b81eb21..f4f9f29b4d 100644 --- a/esphome/components/pzem004t/pzem004t.h +++ b/esphome/components/pzem004t/pzem004t.h @@ -14,6 +14,8 @@ class PZEM004T : public PollingComponent, public uart::UARTDevice { void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } + void setup() override; + void loop() override; void update() override; From fe47ddc27a295da15e8c6df9c262ba1fba5d27e0 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 16:39:35 +0200 Subject: [PATCH 1315/1841] Convert st7735.h to use LF line endings (#2287) --- esphome/components/st7735/st7735.h | 174 ++++++++++++++--------------- 1 file changed, 87 insertions(+), 87 deletions(-) diff --git a/esphome/components/st7735/st7735.h b/esphome/components/st7735/st7735.h index 737170e99b..0eb4ccadd8 100644 --- a/esphome/components/st7735/st7735.h +++ b/esphome/components/st7735/st7735.h @@ -1,87 +1,87 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/components/spi/spi.h" -#include "esphome/components/display/display_buffer.h" - -namespace esphome { -namespace st7735 { - -static const uint8_t ST7735_TFTWIDTH_128 = 128; // for 1.44 and mini^M -static const uint8_t ST7735_TFTWIDTH_80 = 80; // for mini^M -static const uint8_t ST7735_TFTHEIGHT_128 = 128; // for 1.44" display^M -static const uint8_t ST7735_TFTHEIGHT_160 = 160; // for 1.8" and mini display^M - -// some flags for initR() :( -static const uint8_t INITR_GREENTAB = 0x00; -static const uint8_t INITR_REDTAB = 0x01; -static const uint8_t INITR_BLACKTAB = 0x02; -static const uint8_t INITR_144GREENTAB = 0x01; -static const uint8_t INITR_MINI_160X80 = 0x04; -static const uint8_t INITR_HALLOWING = 0x05; -static const uint8_t INITR_18GREENTAB = INITR_GREENTAB; -static const uint8_t INITR_18REDTAB = INITR_REDTAB; -static const uint8_t INITR_18BLACKTAB = INITR_BLACKTAB; - -enum ST7735Model { - ST7735_INITR_GREENTAB = INITR_GREENTAB, - ST7735_INITR_REDTAB = INITR_REDTAB, - ST7735_INITR_BLACKTAB = INITR_BLACKTAB, - ST7735_INITR_MINI_160X80 = INITR_MINI_160X80, - ST7735_INITR_18BLACKTAB = INITR_18BLACKTAB, - ST7735_INITR_18REDTAB = INITR_18REDTAB -}; - -class ST7735 : public PollingComponent, - public display::DisplayBuffer, - public spi::SPIDevice { - public: - ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr); - void dump_config() override; - void setup() override; - - void display(); - - void update() override; - - void set_model(ST7735Model model) { this->model_ = model; } - float get_setup_priority() const override { return setup_priority::PROCESSOR; } - - void set_reset_pin(GPIOPin *value) { this->reset_pin_ = value; } - void set_dc_pin(GPIOPin *value) { dc_pin_ = value; } - size_t get_buffer_length(); - - protected: - void sendcommand_(uint8_t cmd, const uint8_t *data_bytes, uint8_t num_data_bytes); - void senddata_(const uint8_t *data_bytes, uint8_t num_data_bytes); - - void writecommand_(uint8_t value); - void writedata_(uint8_t value); - - void write_display_data_(); - - void init_reset_(); - void display_init_(const uint8_t *addr); - void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h); - void draw_absolute_pixel_internal(int x, int y, Color color) override; - void spi_master_write_addr_(uint16_t addr1, uint16_t addr2); - void spi_master_write_color_(uint16_t color, uint16_t size); - - int get_width_internal() override; - int get_height_internal() override; - - const char *model_str_(); - - ST7735Model model_{ST7735_INITR_18BLACKTAB}; - uint8_t colstart_ = 0, rowstart_ = 0; - bool eightbitcolor_ = false; - bool usebgr_ = false; - int16_t width_ = 80, height_ = 80; // Watch heap size - - GPIOPin *reset_pin_{nullptr}; - GPIOPin *dc_pin_{nullptr}; -}; - -} // namespace st7735 -} // namespace esphome +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/spi/spi.h" +#include "esphome/components/display/display_buffer.h" + +namespace esphome { +namespace st7735 { + +static const uint8_t ST7735_TFTWIDTH_128 = 128; // for 1.44 and mini^M +static const uint8_t ST7735_TFTWIDTH_80 = 80; // for mini^M +static const uint8_t ST7735_TFTHEIGHT_128 = 128; // for 1.44" display^M +static const uint8_t ST7735_TFTHEIGHT_160 = 160; // for 1.8" and mini display^M + +// some flags for initR() :( +static const uint8_t INITR_GREENTAB = 0x00; +static const uint8_t INITR_REDTAB = 0x01; +static const uint8_t INITR_BLACKTAB = 0x02; +static const uint8_t INITR_144GREENTAB = 0x01; +static const uint8_t INITR_MINI_160X80 = 0x04; +static const uint8_t INITR_HALLOWING = 0x05; +static const uint8_t INITR_18GREENTAB = INITR_GREENTAB; +static const uint8_t INITR_18REDTAB = INITR_REDTAB; +static const uint8_t INITR_18BLACKTAB = INITR_BLACKTAB; + +enum ST7735Model { + ST7735_INITR_GREENTAB = INITR_GREENTAB, + ST7735_INITR_REDTAB = INITR_REDTAB, + ST7735_INITR_BLACKTAB = INITR_BLACKTAB, + ST7735_INITR_MINI_160X80 = INITR_MINI_160X80, + ST7735_INITR_18BLACKTAB = INITR_18BLACKTAB, + ST7735_INITR_18REDTAB = INITR_18REDTAB +}; + +class ST7735 : public PollingComponent, + public display::DisplayBuffer, + public spi::SPIDevice { + public: + ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr); + void dump_config() override; + void setup() override; + + void display(); + + void update() override; + + void set_model(ST7735Model model) { this->model_ = model; } + float get_setup_priority() const override { return setup_priority::PROCESSOR; } + + void set_reset_pin(GPIOPin *value) { this->reset_pin_ = value; } + void set_dc_pin(GPIOPin *value) { dc_pin_ = value; } + size_t get_buffer_length(); + + protected: + void sendcommand_(uint8_t cmd, const uint8_t *data_bytes, uint8_t num_data_bytes); + void senddata_(const uint8_t *data_bytes, uint8_t num_data_bytes); + + void writecommand_(uint8_t value); + void writedata_(uint8_t value); + + void write_display_data_(); + + void init_reset_(); + void display_init_(const uint8_t *addr); + void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h); + void draw_absolute_pixel_internal(int x, int y, Color color) override; + void spi_master_write_addr_(uint16_t addr1, uint16_t addr2); + void spi_master_write_color_(uint16_t color, uint16_t size); + + int get_width_internal() override; + int get_height_internal() override; + + const char *model_str_(); + + ST7735Model model_{ST7735_INITR_18BLACKTAB}; + uint8_t colstart_ = 0, rowstart_ = 0; + bool eightbitcolor_ = false; + bool usebgr_ = false; + int16_t width_ = 80, height_ = 80; // Watch heap size + + GPIOPin *reset_pin_{nullptr}; + GPIOPin *dc_pin_{nullptr}; +}; + +} // namespace st7735 +} // namespace esphome From 133a17d6eb3b8b94d6bc0d6d3313d7f7103d3585 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 16:55:01 +0200 Subject: [PATCH 1316/1841] Add esphal.h include to inkplate6 component (#2286) --- esphome/components/inkplate6/inkplate.h | 1 + tests/test5.yaml | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/esphome/components/inkplate6/inkplate.h b/esphome/components/inkplate6/inkplate.h index 94d14b4f6e..f2821b22a9 100644 --- a/esphome/components/inkplate6/inkplate.h +++ b/esphome/components/inkplate6/inkplate.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/components/i2c/i2c.h" #include "esphome/components/display/display_buffer.h" diff --git a/tests/test5.yaml b/tests/test5.yaml index 5d4ff025d9..84d4a5a44e 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -28,6 +28,8 @@ uart: rx_pin: 16 baud_rate: 19200 +i2c: + modbus: uart_id: uart1 @@ -149,3 +151,20 @@ sensor: uart_id: uart2 co2: name: CO2 Sensor + +display: + - platform: inkplate6 + id: inkplate_display + greyscale: false + partial_updating: false + update_interval: 60s + + ckv_pin: GPIO1 + sph_pin: GPIO1 + gmod_pin: GPIO1 + gpio0_enable_pin: GPIO1 + oe_pin: GPIO1 + spv_pin: GPIO1 + powerup_pin: GPIO1 + wakeup_pin: GPIO1 + vcom_pin: GPIO1 From a2d2863c72edf6d5f04a8291fdb5a47349149d63 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 13 Sep 2021 17:22:24 +0200 Subject: [PATCH 1317/1841] Revert "Bump tzlocal from 2.1 to 3.0 (#2154)" (#2289) This reverts commit e0cff214b215ab2cd4dfb221b37d0f56dbce141b. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7de02d8ccb..daaf86e641 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ paho-mqtt==1.5.1 colorama==0.4.4 tornado==6.1 protobuf==3.17.3 -tzlocal==3.0 +tzlocal==2.1 pytz==2021.1 pyserial==3.5 ifaddr==0.1.7 From 40c474cd832f736b191361e01a20165fbb0fc36b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 18:11:27 +0200 Subject: [PATCH 1318/1841] Run clang-tidy against ESP32 (#2147) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Otto winter --- .clang-tidy | 2 +- .github/workflows/ci.yml | 21 ++++++---- esphome/components/ac_dimmer/ac_dimmer.cpp | 2 +- .../airthings_ble/airthings_listener.cpp | 2 +- .../airthings_wave_plus.cpp | 30 ++++++------- .../airthings_wave_plus/airthings_wave_plus.h | 32 +++++++------- esphome/components/am43/am43.cpp | 6 +-- esphome/components/am43/am43.h | 4 +- esphome/components/am43/cover/am43_cover.cpp | 6 +-- esphome/components/am43/cover/am43_cover.h | 4 +- esphome/components/anova/anova.cpp | 6 +-- esphome/components/anova/anova.h | 2 +- esphome/components/api/api_connection.cpp | 2 +- esphome/components/api/api_server.cpp | 11 ++--- .../atc_mithermometer/atc_mithermometer.cpp | 12 ++---- .../atc_mithermometer/atc_mithermometer.h | 1 - esphome/components/ble_client/ble_client.cpp | 16 +++---- esphome/components/esp32_ble/ble.cpp | 18 ++++---- .../components/esp32_ble/ble_advertising.cpp | 1 + esphome/components/esp32_ble/ble_uuid.cpp | 42 +++++++++---------- .../esp32_ble_beacon/esp32_ble_beacon.cpp | 7 ++-- .../esp32_ble_server/ble_characteristic.cpp | 2 +- .../esp32_ble_server/ble_descriptor.cpp | 4 +- .../esp32_ble_server/ble_server.cpp | 4 +- .../esp32_ble_server/ble_service.cpp | 3 +- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 12 +++--- .../components/esp32_camera/esp32_camera.cpp | 4 +- .../esp32_improv/esp32_improv_component.cpp | 10 ++--- .../components/esp8266_pwm/esp8266_pwm.cpp | 4 ++ esphome/components/esp8266_pwm/esp8266_pwm.h | 4 ++ .../ethernet/ethernet_component.cpp | 8 ++-- .../components/ethernet/ethernet_component.h | 2 +- esphome/components/i2c/i2c.cpp | 11 ++--- esphome/components/i2c/i2c.h | 4 -- .../inkbird_ibsth1_mini.cpp | 24 +++++------ esphome/components/inkplate6/inkplate.cpp | 16 ++++--- esphome/components/ledc/ledc_output.cpp | 2 +- esphome/components/mqtt/mqtt_client.cpp | 4 +- esphome/components/nextion/nextion_upload.cpp | 8 +--- .../pulse_counter/pulse_counter_sensor.cpp | 7 +--- .../pulse_counter/pulse_counter_sensor.h | 4 -- .../pvvx_mithermometer/pvvx_mithermometer.cpp | 14 ++----- .../pvvx_mithermometer/pvvx_mithermometer.h | 1 - .../components/remote_base/rc5_protocol.cpp | 6 +-- .../components/socket/bsd_sockets_impl.cpp | 8 ++-- esphome/components/spi/spi.cpp | 2 +- esphome/components/st7735/st7735.cpp | 20 ++++----- esphome/components/st7789v/st7789v.cpp | 20 ++++----- esphome/components/uart/uart.h | 4 -- esphome/components/uart/uart_esp32.cpp | 6 +-- .../components/wifi/wifi_component_esp32.cpp | 36 ++++++++-------- .../wifi/wifi_component_esp8266.cpp | 6 +-- esphome/components/xiaomi_ble/xiaomi_ble.cpp | 16 +++---- .../components/xiaomi_cgd1/xiaomi_cgd1.cpp | 8 +--- .../components/xiaomi_cgdk2/xiaomi_cgdk2.cpp | 8 +--- .../components/xiaomi_cgg1/xiaomi_cgg1.cpp | 8 +--- .../components/xiaomi_cgpr1/xiaomi_cgpr1.cpp | 10 ++--- .../xiaomi_gcls002/xiaomi_gcls002.cpp | 6 +-- .../xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp | 6 +-- .../xiaomi_hhccpot002/xiaomi_hhccpot002.cpp | 6 +-- .../xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp | 6 +-- .../xiaomi_lywsd02/xiaomi_lywsd02.cpp | 6 +-- .../xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp | 8 +--- .../xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp | 6 +-- .../xiaomi_mhoc401/xiaomi_mhoc401.cpp | 8 +--- .../xiaomi_miscale2/xiaomi_miscale2.cpp | 10 ++--- .../xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp | 8 +--- .../xiaomi_mue4094rt/xiaomi_mue4094rt.cpp | 6 +-- .../xiaomi_wx08zm/xiaomi_wx08zm.cpp | 6 +-- esphome/core/application.cpp | 6 +-- esphome/core/defines.h | 5 +-- platformio.ini | 1 - script/clang-tidy | 14 +++++-- script/helpers.py | 10 +++++ 74 files changed, 291 insertions(+), 364 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 597065d50b..8451a2bdd4 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -124,7 +124,7 @@ CheckOptions: - key: readability-identifier-naming.StaticConstantCase value: 'UPPER_CASE' - key: readability-identifier-naming.StaticVariableCase - value: 'UPPER_CASE' + value: 'lower_case' - key: readability-identifier-naming.GlobalConstantCase value: 'UPPER_CASE' - key: readability-identifier-naming.ParameterCase diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7a841f84c..eb93c36d19 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,17 +19,20 @@ jobs: - id: clang-format name: Run script/clang-format - id: clang-tidy - name: Run script/clang-tidy 1/4 - split: 1 + name: Run script/clang-tidy for ESP8266 + options: --environment esp8266-tidy --grep ARDUINO_ARCH_ESP8266 - id: clang-tidy - name: Run script/clang-tidy 2/4 - split: 2 + name: Run script/clang-tidy for ESP32 1/4 + options: --environment esp32-tidy --split-num 4 --split-at 1 - id: clang-tidy - name: Run script/clang-tidy 3/4 - split: 3 + name: Run script/clang-tidy for ESP32 2/4 + options: --environment esp32-tidy --split-num 4 --split-at 2 - id: clang-tidy - name: Run script/clang-tidy 4/4 - split: 4 + name: Run script/clang-tidy for ESP32 3/4 + options: --environment esp32-tidy --split-num 4 --split-at 3 + - id: clang-tidy + name: Run script/clang-tidy for ESP32 4/4 + options: --environment esp32-tidy --split-num 4 --split-at 4 # cpp lint job runs with esphome-lint docker image so that clang-format-* # doesn't have to be installed @@ -51,7 +54,7 @@ jobs: if: ${{ matrix.id == 'clang-format' }} - name: Run clang-tidy - run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }} + run: script/clang-tidy --all-headers --fix ${{ matrix.options }} if: ${{ matrix.id == 'clang-tidy' }} - name: Suggested changes diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index 7e9251ddf3..9a71f0e224 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -141,7 +141,7 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store #ifdef ARDUINO_ARCH_ESP32 // ESP32 implementation, uses basically the same code but needs to wrap // timer_interrupt() function to auto-reschedule -static hw_timer_t *dimmer_timer = nullptr; +static hw_timer_t *dimmer_timer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); } #endif diff --git a/esphome/components/airthings_ble/airthings_listener.cpp b/esphome/components/airthings_ble/airthings_listener.cpp index 921e42c498..a843bcb145 100644 --- a/esphome/components/airthings_ble/airthings_listener.cpp +++ b/esphome/components/airthings_ble/airthings_listener.cpp @@ -6,7 +6,7 @@ namespace esphome { namespace airthings_ble { -static const char *TAG = "airthings_ble"; +static const char *const TAG = "airthings_ble"; bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { for (auto &it : device.get_manufacturer_datas()) { diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp index 6b2e807e0b..6b4780726d 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -5,6 +5,8 @@ namespace esphome { namespace airthings_wave_plus { +static const char *const TAG = "airthings_wave_plus"; + void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { switch (event) { @@ -21,15 +23,15 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt } case ESP_GATTC_SEARCH_CMPL_EVT: { - this->handle = 0; - auto chr = this->parent()->get_characteristic(service_uuid, sensors_data_characteristic_uuid); + this->handle_ = 0; + auto chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_); if (chr == nullptr) { - ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid.to_string().c_str(), - sensors_data_characteristic_uuid.to_string().c_str()); + ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(), + sensors_data_characteristic_uuid_.to_string().c_str()); break; } - this->handle = chr->handle; - this->node_state = espbt::ClientState::Established; + this->handle_ = chr->handle; + this->node_state = esp32_ble_tracker::ClientState::Established; request_read_values_(); break; @@ -42,7 +44,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); break; } - if (param->read.handle == this->handle) { + if (param->read.handle == this->handle_) { read_sensors_(param->read.value, param->read.value_len); } break; @@ -88,16 +90,16 @@ void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) { } } -bool AirthingsWavePlus::is_valid_radon_value_(short radon) { return 0 <= radon && radon <= 16383; } +bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return 0 <= radon && radon <= 16383; } -bool AirthingsWavePlus::is_valid_voc_value_(short voc) { return 0 <= voc && voc <= 16383; } +bool AirthingsWavePlus::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; } -bool AirthingsWavePlus::is_valid_co2_value_(short co2) { return 0 <= co2 && co2 <= 16383; } +bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && co2 <= 16383; } void AirthingsWavePlus::loop() {} void AirthingsWavePlus::update() { - if (this->node_state != espbt::ClientState::Established) { + if (this->node_state != esp32_ble_tracker::ClientState::Established) { if (!parent()->enabled) { ESP_LOGW(TAG, "Reconnecting to device"); parent()->set_enabled(true); @@ -110,7 +112,7 @@ void AirthingsWavePlus::update() { void AirthingsWavePlus::request_read_values_() { auto status = - esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); } @@ -130,8 +132,8 @@ AirthingsWavePlus::AirthingsWavePlus() : PollingComponent(10000) { auto service_bt = *BLEUUID::fromString(std::string("b42e1c08-ade7-11e4-89d3-123b93f75cba")).getNative(); auto characteristic_bt = *BLEUUID::fromString(std::string("b42e2a68-ade7-11e4-89d3-123b93f75cba")).getNative(); - service_uuid = espbt::ESPBTUUID::from_uuid(service_bt); - sensors_data_characteristic_uuid = espbt::ESPBTUUID::from_uuid(characteristic_bt); + service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_uuid(service_bt); + sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_uuid(characteristic_bt); } void AirthingsWavePlus::setup() {} diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.h b/esphome/components/airthings_wave_plus/airthings_wave_plus.h index 18d7fe60d2..88d7a7a01b 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.h +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.h @@ -1,25 +1,21 @@ #pragma once +#ifdef ARDUINO_ARCH_ESP32 + +#include +#include +#include +#include #include "esphome/core/component.h" +#include "esphome/core/log.h" #include "esphome/components/ble_client/ble_client.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/core/log.h" -#include -#include - -#ifdef ARDUINO_ARCH_ESP32 -#include -#include - -using namespace esphome::ble_client; namespace esphome { namespace airthings_wave_plus { -static const char *TAG = "airthings_wave_plus"; - -class AirthingsWavePlus : public PollingComponent, public BLEClientNode { +class AirthingsWavePlus : public PollingComponent, public ble_client::BLEClientNode { public: AirthingsWavePlus(); @@ -40,9 +36,9 @@ class AirthingsWavePlus : public PollingComponent, public BLEClientNode { void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } protected: - bool is_valid_radon_value_(short radon); - bool is_valid_voc_value_(short voc); - bool is_valid_co2_value_(short co2); + bool is_valid_radon_value_(uint16_t radon); + bool is_valid_voc_value_(uint16_t voc); + bool is_valid_co2_value_(uint16_t co2); void read_sensors_(uint8_t *value, uint16_t value_len); void request_read_values_(); @@ -55,9 +51,9 @@ class AirthingsWavePlus : public PollingComponent, public BLEClientNode { sensor::Sensor *co2_sensor_{nullptr}; sensor::Sensor *tvoc_sensor_{nullptr}; - uint16_t handle; - espbt::ESPBTUUID service_uuid; - espbt::ESPBTUUID sensors_data_characteristic_uuid; + uint16_t handle_; + esp32_ble_tracker::ESPBTUUID service_uuid_; + esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_; struct WavePlusReadings { uint8_t version; diff --git a/esphome/components/am43/am43.cpp b/esphome/components/am43/am43.cpp index e60bb2474c..94be194a1d 100644 --- a/esphome/components/am43/am43.cpp +++ b/esphome/components/am43/am43.cpp @@ -6,7 +6,7 @@ namespace esphome { namespace am43 { -static const char *TAG = "am43"; +static const char *const TAG = "am43"; void Am43::dump_config() { ESP_LOGCONFIG(TAG, "AM43"); @@ -15,8 +15,8 @@ void Am43::dump_config() { } void Am43::setup() { - this->encoder_ = new Am43Encoder(); - this->decoder_ = new Am43Decoder(); + this->encoder_ = make_unique(); + this->decoder_ = make_unique(); this->logged_in_ = false; this->last_battery_update_ = 0; this->current_sensor_ = 0; diff --git a/esphome/components/am43/am43.h b/esphome/components/am43/am43.h index 460bb601b2..025d075b68 100644 --- a/esphome/components/am43/am43.h +++ b/esphome/components/am43/am43.h @@ -28,8 +28,8 @@ class Am43 : public esphome::ble_client::BLEClientNode, public PollingComponent protected: uint16_t char_handle_; - Am43Encoder *encoder_; - Am43Decoder *decoder_; + std::unique_ptr encoder_; + std::unique_ptr decoder_; bool logged_in_; sensor::Sensor *battery_{nullptr}; sensor::Sensor *illuminance_{nullptr}; diff --git a/esphome/components/am43/cover/am43_cover.cpp b/esphome/components/am43/cover/am43_cover.cpp index 3ae7fe8f8c..1067ce9ebb 100644 --- a/esphome/components/am43/cover/am43_cover.cpp +++ b/esphome/components/am43/cover/am43_cover.cpp @@ -6,7 +6,7 @@ namespace esphome { namespace am43 { -static const char *TAG = "am43_cover"; +static const char *const TAG = "am43_cover"; using namespace esphome::cover; @@ -18,8 +18,8 @@ void Am43Component::dump_config() { void Am43Component::setup() { this->position = COVER_OPEN; - this->encoder_ = new Am43Encoder(); - this->decoder_ = new Am43Decoder(); + this->encoder_ = make_unique(); + this->decoder_ = make_unique(); this->logged_in_ = false; } diff --git a/esphome/components/am43/cover/am43_cover.h b/esphome/components/am43/cover/am43_cover.h index 5557da49e7..dba1391b63 100644 --- a/esphome/components/am43/cover/am43_cover.h +++ b/esphome/components/am43/cover/am43_cover.h @@ -32,8 +32,8 @@ class Am43Component : public cover::Cover, public esphome::ble_client::BLEClient uint16_t char_handle_; uint16_t pin_; bool invert_position_; - Am43Encoder *encoder_; - Am43Decoder *decoder_; + std::unique_ptr encoder_; + std::unique_ptr decoder_; bool logged_in_; float position_; diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index f330969c33..d0ea818669 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -6,14 +6,14 @@ namespace esphome { namespace anova { -static const char *TAG = "anova"; +static const char *const TAG = "anova"; using namespace esphome::climate; void Anova::dump_config() { LOG_CLIMATE("", "Anova BLE Cooker", this); } void Anova::setup() { - this->codec_ = new AnovaCodec(); + this->codec_ = make_unique(); this->current_request_ = 0; } @@ -135,7 +135,7 @@ void Anova::update() { if (this->current_request_ < 2) { auto pkt = this->codec_->get_read_device_status_request(); if (this->current_request_ == 0) - auto pkt = this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c'); + this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c'); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) diff --git a/esphome/components/anova/anova.h b/esphome/components/anova/anova.h index 42bdbcaed0..e033513013 100644 --- a/esphome/components/anova/anova.h +++ b/esphome/components/anova/anova.h @@ -39,7 +39,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode void set_unit_of_measurement(const char *); protected: - AnovaCodec *codec_; + std::unique_ptr codec_; void control(const climate::ClimateCall &call) override; uint16_t char_handle_; uint8_t current_request_; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index ea3be42275..a530554155 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -633,7 +633,7 @@ void APIConnection::send_camera_state(std::shared_ptr return; if (this->image_reader_.available()) return; - this->image_reader_.set_image(image); + this->image_reader_.set_image(std::move(image)); } bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { ListEntitiesCameraResponse msg; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 70f82c59db..1ef567ab57 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -76,11 +76,12 @@ void APIServer::setup() { #ifdef USE_ESP32_CAMERA if (esp32_camera::global_esp32_camera != nullptr) { - esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr image) { - for (auto &c : this->clients_) - if (!c->remove_) - c->send_camera_state(image); - }); + esp32_camera::global_esp32_camera->add_image_callback( + [this](const std::shared_ptr &image) { + for (auto &c : this->clients_) + if (!c->remove_) + c->send_camera_state(image); + }); } #endif } diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.cpp b/esphome/components/atc_mithermometer/atc_mithermometer.cpp index 5656cdf430..1dba259143 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.cpp +++ b/esphome/components/atc_mithermometer/atc_mithermometer.cpp @@ -26,7 +26,7 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device bool success = false; for (auto &service_data : device.get_service_datas()) { auto res = parse_header(service_data); - if (res->is_duplicate) { + if (!res.has_value()) { continue; } if (!(parse_message(service_data.data, *res))) { @@ -46,11 +46,7 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device success = true; } - if (!success) { - return false; - } - - return true; + return success; } optional ATCMiThermometer::parse_header(const esp32_ble_tracker::ServiceData &service_data) { @@ -64,12 +60,10 @@ optional ATCMiThermometer::parse_header(const esp32_ble_tracker::Se static uint8_t last_frame_count = 0; if (last_frame_count == raw[12]) { - ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%d).", static_cast(last_frame_count)); - result.is_duplicate = true; + ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%hhu).", last_frame_count); return {}; } last_frame_count = raw[12]; - result.is_duplicate = false; return result; } diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.h b/esphome/components/atc_mithermometer/atc_mithermometer.h index 203dca3200..eff14811be 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.h +++ b/esphome/components/atc_mithermometer/atc_mithermometer.h @@ -14,7 +14,6 @@ struct ParseResult { optional humidity; optional battery_level; optional battery_voltage; - bool is_duplicate; int raw_offset; }; diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 956848b73d..0398113f83 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -130,7 +130,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es break; } ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu); - esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, NULL); + esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); break; } case ESP_GATTC_DISCONNECT_EVT: { @@ -139,13 +139,13 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es } ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT", this->address_str().c_str()); for (auto &svc : this->services_) - delete svc; + delete svc; // NOLINT(cppcoreguidelines-owning-memory) this->services_.clear(); this->set_states(espbt::ClientState::Idle); break; } case ESP_GATTC_SEARCH_RES_EVT: { - BLEService *ble_service = new BLEService(); + BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory) ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid); ble_service->start_handle = param->search_res.start_handle; ble_service->end_handle = param->search_res.end_handle; @@ -194,7 +194,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es // Delete characteristics after clients have used them to save RAM. if (!all_established && this->all_nodes_established()) { for (auto &svc : this->services_) - delete svc; + delete svc; // NOLINT(cppcoreguidelines-owning-memory) this->services_.clear(); } } @@ -307,7 +307,7 @@ BLEDescriptor *BLEClient::get_descriptor(uint16_t service, uint16_t chr, uint16_ BLEService::~BLEService() { for (auto &chr : this->characteristics) - delete chr; + delete chr; // NOLINT(cppcoreguidelines-owning-memory) } void BLEService::parse_characteristics() { @@ -329,7 +329,7 @@ void BLEService::parse_characteristics() { break; } - BLECharacteristic *characteristic = new BLECharacteristic(); + BLECharacteristic *characteristic = new BLECharacteristic(); // NOLINT(cppcoreguidelines-owning-memory) characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid); characteristic->properties = result.properties; characteristic->handle = result.char_handle; @@ -344,7 +344,7 @@ void BLEService::parse_characteristics() { BLECharacteristic::~BLECharacteristic() { for (auto &desc : this->descriptors) - delete desc; + delete desc; // NOLINT(cppcoreguidelines-owning-memory) } void BLECharacteristic::parse_descriptors() { @@ -366,7 +366,7 @@ void BLECharacteristic::parse_descriptors() { break; } - BLEDescriptor *desc = new BLEDescriptor(); + BLEDescriptor *desc = new BLEDescriptor(); // NOLINT(cppcoreguidelines-owning-memory) desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid); desc->handle = result.handle; desc->characteristic = this; diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index a54ee48b30..2d710a6ef7 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -27,7 +27,7 @@ void ESP32BLE::setup() { return; } - this->advertising_ = new BLEAdvertising(); + this->advertising_ = new BLEAdvertising(); // NOLINT(cppcoreguidelines-owning-memory) this->advertising_->set_scan_response(true); this->advertising_->set_min_preferred_interval(0x06); @@ -123,25 +123,25 @@ void ESP32BLE::loop() { BLEEvent *ble_event = this->ble_events_.pop(); while (ble_event != nullptr) { switch (ble_event->type_) { - case ble_event->GATTS: + case BLEEvent::GATTS: this->real_gatts_event_handler_(ble_event->event_.gatts.gatts_event, ble_event->event_.gatts.gatts_if, &ble_event->event_.gatts.gatts_param); break; - case ble_event->GAP: + case BLEEvent::GAP: this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param); break; default: break; } - delete ble_event; + delete ble_event; // NOLINT(cppcoreguidelines-owning-memory) ble_event = this->ble_events_.pop(); } } void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { - BLEEvent *new_event = new BLEEvent(event, param); + BLEEvent *new_event = new BLEEvent(event, param); // NOLINT(cppcoreguidelines-owning-memory) global_ble->ble_events_.push(new_event); -} +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { ESP_LOGV(TAG, "(BLE) gap_event_handler - %d", event); @@ -153,9 +153,9 @@ void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { - BLEEvent *new_event = new BLEEvent(event, gatts_if, param); + BLEEvent *new_event = new BLEEvent(event, gatts_if, param); // NOLINT(cppcoreguidelines-owning-memory) global_ble->ble_events_.push(new_event); -} +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { @@ -174,7 +174,7 @@ float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; } void ESP32BLE::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE:"); } -ESP32BLE *global_ble = nullptr; +ESP32BLE *global_ble = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esp32_ble } // namespace esphome diff --git a/esphome/components/esp32_ble/ble_advertising.cpp b/esphome/components/esp32_ble/ble_advertising.cpp index 92270124dd..bcfb0d1a1b 100644 --- a/esphome/components/esp32_ble/ble_advertising.cpp +++ b/esphome/components/esp32_ble/ble_advertising.cpp @@ -45,6 +45,7 @@ void BLEAdvertising::start() { this->advertising_data_.service_uuid_len = 0; } else { this->advertising_data_.service_uuid_len = 16 * num_services; + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) this->advertising_data_.p_service_uuid = new uint8_t[this->advertising_data_.service_uuid_len]; uint8_t *p = this->advertising_data_.p_service_uuid; for (int i = 0; i < num_services; i++) { diff --git a/esphome/components/esp32_ble/ble_uuid.cpp b/esphome/components/esp32_ble/ble_uuid.cpp index 0de938d9a8..b33275110d 100644 --- a/esphome/components/esp32_ble/ble_uuid.cpp +++ b/esphome/components/esp32_ble/ble_uuid.cpp @@ -33,28 +33,28 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { ret.uuid_.len = ESP_UUID_LEN_16; ret.uuid_.uuid.uuid16 = 0; for (int i = 0; i < data.length();) { - uint8_t MSB = data.c_str()[i]; - uint8_t LSB = data.c_str()[i + 1]; + uint8_t msb = data.c_str()[i]; + uint8_t lsb = data.c_str()[i + 1]; - if (MSB > '9') - MSB -= 7; - if (LSB > '9') - LSB -= 7; - ret.uuid_.uuid.uuid16 += (((MSB & 0x0F) << 4) | (LSB & 0x0F)) << (2 - i) * 4; + if (msb > '9') + msb -= 7; + if (lsb > '9') + lsb -= 7; + ret.uuid_.uuid.uuid16 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (2 - i) * 4; i += 2; } } else if (data.length() == 8) { ret.uuid_.len = ESP_UUID_LEN_32; ret.uuid_.uuid.uuid32 = 0; for (int i = 0; i < data.length();) { - uint8_t MSB = data.c_str()[i]; - uint8_t LSB = data.c_str()[i + 1]; + uint8_t msb = data.c_str()[i]; + uint8_t lsb = data.c_str()[i + 1]; - if (MSB > '9') - MSB -= 7; - if (LSB > '9') - LSB -= 7; - ret.uuid_.uuid.uuid32 += (((MSB & 0x0F) << 4) | (LSB & 0x0F)) << (6 - i) * 4; + if (msb > '9') + msb -= 7; + if (lsb > '9') + lsb -= 7; + ret.uuid_.uuid.uuid32 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (6 - i) * 4; i += 2; } } else if (data.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be @@ -69,14 +69,14 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { for (int i = 0; i < data.length();) { if (data.c_str()[i] == '-') i++; - uint8_t MSB = data.c_str()[i]; - uint8_t LSB = data.c_str()[i + 1]; + uint8_t msb = data.c_str()[i]; + uint8_t lsb = data.c_str()[i + 1]; - if (MSB > '9') - MSB -= 7; - if (LSB > '9') - LSB -= 7; - ret.uuid_.uuid.uuid128[15 - n++] = ((MSB & 0x0F) << 4) | (LSB & 0x0F); + if (msb > '9') + msb -= 7; + if (lsb > '9') + lsb -= 7; + ret.uuid_.uuid.uuid128[15 - n++] = ((msb & 0x0F) << 4) | (lsb & 0x0F); i += 2; } } else { diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp index 1b06fd787e..c9e00a1093 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp @@ -15,6 +15,7 @@ namespace esp32_ble_beacon { static const char *const TAG = "esp32_ble_beacon"; +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) static esp_ble_adv_params_t ble_adv_params = { .adv_int_min = 0x20, .adv_int_max = 0x40, @@ -28,7 +29,7 @@ static esp_ble_adv_params_t ble_adv_params = { #define ENDIAN_CHANGE_U16(x) ((((x) &0xFF00) >> 8) + (((x) &0xFF) << 8)) -static esp_ble_ibeacon_head_t ibeacon_common_head = { +static const esp_ble_ibeacon_head_t IBEACON_COMMON_HEAD = { .flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = 0x004C, .beacon_type = 0x1502}; void ESP32BLEBeacon::dump_config() { @@ -90,7 +91,7 @@ void ESP32BLEBeacon::ble_setup() { } esp_ble_ibeacon_t ibeacon_adv_data; - memcpy(&ibeacon_adv_data.ibeacon_head, &ibeacon_common_head, sizeof(esp_ble_ibeacon_head_t)); + memcpy(&ibeacon_adv_data.ibeacon_head, &IBEACON_COMMON_HEAD, sizeof(esp_ble_ibeacon_head_t)); memcpy(&ibeacon_adv_data.ibeacon_vendor.proximity_uuid, global_esp32_ble_beacon->uuid_.data(), sizeof(ibeacon_adv_data.ibeacon_vendor.proximity_uuid)); ibeacon_adv_data.ibeacon_vendor.minor = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->minor_); @@ -131,7 +132,7 @@ void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap } } -ESP32BLEBeacon *global_esp32_ble_beacon = nullptr; +ESP32BLEBeacon *global_esp32_ble_beacon = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esp32_ble_beacon } // namespace esphome diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index 62775ab05f..d8ff39cc94 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -27,7 +27,7 @@ BLECharacteristic::BLECharacteristic(const ESPBTUUID uuid, uint32_t properties) void BLECharacteristic::set_value(std::vector value) { xSemaphoreTake(this->set_value_lock_, 0L); - this->value_ = value; + this->value_ = std::move(value); xSemaphoreGive(this->set_value_lock_); } void BLECharacteristic::set_value(const std::string &value) { diff --git a/esphome/components/esp32_ble_server/ble_descriptor.cpp b/esphome/components/esp32_ble_server/ble_descriptor.cpp index a04343da3a..5262087ad1 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.cpp +++ b/esphome/components/esp32_ble_server/ble_descriptor.cpp @@ -15,10 +15,10 @@ BLEDescriptor::BLEDescriptor(ESPBTUUID uuid, uint16_t max_len) { this->uuid_ = uuid; this->value_.attr_len = 0; this->value_.attr_max_len = max_len; - this->value_.attr_value = (uint8_t *) malloc(max_len); + this->value_.attr_value = (uint8_t *) malloc(max_len); // NOLINT } -BLEDescriptor::~BLEDescriptor() { free(this->value_.attr_value); } +BLEDescriptor::~BLEDescriptor() { free(this->value_.attr_value); } // NOLINT void BLEDescriptor::do_create(BLECharacteristic *characteristic) { this->characteristic_ = characteristic; diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index f1eebf3c8a..5777e99e23 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -109,7 +109,7 @@ BLEService *BLEServer::create_service(const std::string &uuid, bool advertise) { } BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles, uint8_t inst_id) { ESP_LOGV(TAG, "Creating service - %s", uuid.to_string().c_str()); - BLEService *service = new BLEService(uuid, num_handles, inst_id); + BLEService *service = new BLEService(uuid, num_handles, inst_id); // NOLINT(cppcoreguidelines-owning-memory) this->services_.push_back(service); if (advertise) { esp32_ble::global_ble->get_advertising()->add_service_uuid(uuid); @@ -158,7 +158,7 @@ float BLEServer::get_setup_priority() const { return setup_priority::BLUETOOTH - void BLEServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Server:"); } -BLEServer *global_ble_server = nullptr; +BLEServer *global_ble_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esp32_ble_server } // namespace esphome diff --git a/esphome/components/esp32_ble_server/ble_service.cpp b/esphome/components/esp32_ble_server/ble_service.cpp index b7c88a436c..5cfc53a397 100644 --- a/esphome/components/esp32_ble_server/ble_service.cpp +++ b/esphome/components/esp32_ble_server/ble_service.cpp @@ -14,7 +14,7 @@ BLEService::BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id) BLEService::~BLEService() { for (auto &chr : this->characteristics_) - delete chr; + delete chr; // NOLINT(cppcoreguidelines-owning-memory) } BLECharacteristic *BLEService::get_characteristic(ESPBTUUID uuid) { @@ -34,6 +34,7 @@ BLECharacteristic *BLEService::create_characteristic(const std::string &uuid, es return create_characteristic(ESPBTUUID::from_raw(uuid), properties); } BLECharacteristic *BLEService::create_characteristic(ESPBTUUID uuid, esp_gatt_char_prop_t properties) { + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) BLECharacteristic *characteristic = new BLECharacteristic(uuid, properties); this->characteristics_.push_back(characteristic); return characteristic; diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index e1cd3975e8..358fe50605 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -21,7 +21,7 @@ namespace esp32_ble_tracker { static const char *const TAG = "esp32_ble_tracker"; -ESP32BLETracker *global_esp32_ble_tracker = nullptr; +ESP32BLETracker *global_esp32_ble_tracker = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) { uint64_t u = 0; @@ -55,7 +55,7 @@ void ESP32BLETracker::loop() { &ble_event->event_.gattc.gattc_param); else this->real_gap_event_handler(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param); - delete ble_event; + delete ble_event; // NOLINT(cppcoreguidelines-owning-memory) ble_event = this->ble_events_.pop(); } @@ -204,9 +204,9 @@ void ESP32BLETracker::register_client(ESPBTClient *client) { } void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { - BLEEvent *gap_event = new BLEEvent(event, param); + BLEEvent *gap_event = new BLEEvent(event, param); // NOLINT(cppcoreguidelines-owning-memory) global_esp32_ble_tracker->ble_events_.push(gap_event); -} +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) void ESP32BLETracker::real_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch (event) { @@ -254,9 +254,9 @@ void ESP32BLETracker::gap_scan_result(const esp_ble_gap_cb_param_t::ble_scan_res void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { - BLEEvent *gattc_event = new BLEEvent(event, gattc_if, param); + BLEEvent *gattc_event = new BLEEvent(event, gattc_if, param); // NOLINT(cppcoreguidelines-owning-memory) global_esp32_ble_tracker->ble_events_.push(gattc_event); -} +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) void ESP32BLETracker::real_gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 500ddd67c9..072cf41460 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -275,10 +275,10 @@ void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) { } void ESP32Camera::set_test_pattern(bool test_pattern) { this->test_pattern_ = test_pattern; } -ESP32Camera *global_esp32_camera; +ESP32Camera *global_esp32_camera; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) void CameraImageReader::set_image(std::shared_ptr image) { - this->image_ = image; + this->image_ = std::move(image); this->offset_ = 0; } size_t CameraImageReader::available() const { diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 65d253078c..b584d2e8b9 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -32,7 +32,7 @@ void ESP32ImprovComponent::setup_characteristics() { this->rpc_ = this->service_->create_characteristic(improv::RPC_COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE); this->rpc_->on_write([this](const std::vector &data) { - if (data.size() > 0) { + if (!data.empty()) { this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end()); } }); @@ -56,7 +56,7 @@ void ESP32ImprovComponent::setup_characteristics() { } void ESP32ImprovComponent::loop() { - if (this->incoming_data_.size() > 0) + if (!this->incoming_data_.empty()) this->process_incoming_data_(); uint32_t now = millis(); @@ -162,7 +162,7 @@ bool ESP32ImprovComponent::check_identify_() { void ESP32ImprovComponent::set_state_(improv::State state) { ESP_LOGV(TAG, "Setting state: %d", state); this->state_ = state; - if (this->status_->get_value().size() == 0 || this->status_->get_value()[0] != state) { + if (this->status_->get_value().empty() || this->status_->get_value()[0] != state) { uint8_t data[1]{state}; this->status_->set_value(data, 1); if (state != improv::STATE_STOPPED) @@ -173,7 +173,7 @@ void ESP32ImprovComponent::set_state_(improv::State state) { void ESP32ImprovComponent::set_error_(improv::Error error) { if (error != improv::ERROR_NONE) ESP_LOGE(TAG, "Error: %d", error); - if (this->error_->get_value().size() == 0 || this->error_->get_value()[0] != error) { + if (this->error_->get_value().empty() || this->error_->get_value()[0] != error) { uint8_t data[1]{error}; this->error_->set_value(data, 1); if (this->state_ != improv::STATE_STOPPED) @@ -274,7 +274,7 @@ void ESP32ImprovComponent::on_wifi_connect_timeout_() { void ESP32ImprovComponent::on_client_disconnect() { this->set_error_(improv::ERROR_NONE); }; -ESP32ImprovComponent *global_improv_component = nullptr; +ESP32ImprovComponent *global_improv_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esp32_improv } // namespace esphome diff --git a/esphome/components/esp8266_pwm/esp8266_pwm.cpp b/esphome/components/esp8266_pwm/esp8266_pwm.cpp index d725e90edc..186653acd1 100644 --- a/esphome/components/esp8266_pwm/esp8266_pwm.cpp +++ b/esphome/components/esp8266_pwm/esp8266_pwm.cpp @@ -1,3 +1,5 @@ +#ifdef ARDUINO_ARCH_ESP8266 + #include "esp8266_pwm.h" #include "esphome/core/macros.h" #include "esphome/core/log.h" @@ -55,3 +57,5 @@ void HOT ESP8266PWM::write_state(float state) { } // namespace esp8266_pwm } // namespace esphome + +#endif diff --git a/esphome/components/esp8266_pwm/esp8266_pwm.h b/esphome/components/esp8266_pwm/esp8266_pwm.h index 661db6611f..1bf875dc43 100644 --- a/esphome/components/esp8266_pwm/esp8266_pwm.h +++ b/esphome/components/esp8266_pwm/esp8266_pwm.h @@ -1,5 +1,7 @@ #pragma once +#ifdef ARDUINO_ARCH_ESP8266 + #include "esphome/core/component.h" #include "esphome/core/esphal.h" #include "esphome/core/automation.h" @@ -48,3 +50,5 @@ template class SetFrequencyAction : public Action { } // namespace esp8266_pwm } // namespace esphome + +#endif diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index b22f8c97d1..cad76ae476 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -16,17 +16,17 @@ // Defined in WiFiGeneric.cpp, sets global initialized flag, starts network event task queue and calls // tcpip_adapter_init() -extern void tcpipInit(); +extern void tcpipInit(); // NOLINT(readability-identifier-naming) namespace esphome { namespace ethernet { static const char *const TAG = "ethernet"; -EthernetComponent *global_eth_component; +EthernetComponent *global_eth_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) #define ESPHL_ERROR_CHECK(err, message) \ - if (err != ESP_OK) { \ + if ((err) != ESP_OK) { \ ESP_LOGE(TAG, message ": (%d) %s", err, esp_err_to_name(err)); \ this->mark_failed(); \ return; \ @@ -258,7 +258,7 @@ void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; } void EthernetComponent::set_type(EthernetType type) { this->type_ = type; } void EthernetComponent::set_clk_mode(eth_clock_mode_t clk_mode) { this->clk_mode_ = clk_mode; } -void EthernetComponent::set_manual_ip(ManualIP manual_ip) { this->manual_ip_ = manual_ip; } +void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; } std::string EthernetComponent::get_use_address() const { if (this->use_address_.empty()) { return App.get_name() + ".local"; diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 326cd1edea..dc8816ef86 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -48,7 +48,7 @@ class EthernetComponent : public Component { void set_mdio_pin(uint8_t mdio_pin); void set_type(EthernetType type); void set_clk_mode(eth_clock_mode_t clk_mode); - void set_manual_ip(ManualIP manual_ip); + void set_manual_ip(const ManualIP &manual_ip); IPAddress get_ip_address(); std::string get_use_address() const; diff --git a/esphome/components/i2c/i2c.cpp b/esphome/components/i2c/i2c.cpp index f1980e1f1d..a3a3f7f82a 100644 --- a/esphome/components/i2c/i2c.cpp +++ b/esphome/components/i2c/i2c.cpp @@ -10,11 +10,12 @@ static const char *const TAG = "i2c"; I2CComponent::I2CComponent() { #ifdef ARDUINO_ARCH_ESP32 - if (next_i2c_bus_num_ == 0) + static uint8_t next_i2c_bus_num = 0; + if (next_i2c_bus_num == 0) this->wire_ = &Wire; else - this->wire_ = new TwoWire(next_i2c_bus_num_); - next_i2c_bus_num_++; + this->wire_ = new TwoWire(next_i2c_bus_num); // NOLINT(cppcoreguidelines-owning-memory) + next_i2c_bus_num++; #else this->wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer) #endif @@ -273,10 +274,6 @@ bool I2CDevice::write_byte_16(uint8_t a_register, uint16_t data) { // NOLINT } void I2CDevice::set_i2c_parent(I2CComponent *parent) { this->parent_ = parent; } -#ifdef ARDUINO_ARCH_ESP32 -uint8_t next_i2c_bus_num_ = 0; -#endif - I2CRegister &I2CRegister::operator=(uint8_t value) { this->parent_->write_byte(this->register_, value); return *this; diff --git a/esphome/components/i2c/i2c.h b/esphome/components/i2c/i2c.h index da791ec633..6f93d4c0e4 100644 --- a/esphome/components/i2c/i2c.h +++ b/esphome/components/i2c/i2c.h @@ -131,10 +131,6 @@ class I2CComponent : public Component { bool scan_; }; -#ifdef ARDUINO_ARCH_ESP32 -extern uint8_t next_i2c_bus_num_; -#endif - class I2CDevice; class I2CMultiplexer; class I2CRegister { diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp index a940a77148..81693c6d96 100644 --- a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp @@ -37,25 +37,25 @@ bool InkbirdIBSTH1_MINI::parse_device(const esp32_ble_tracker::ESPBTDevice &devi ESP_LOGVV(TAG, "parse_device(): address is not public"); return false; } - if (device.get_service_datas().size() != 0) { + if (!device.get_service_datas().empty()) { ESP_LOGVV(TAG, "parse_device(): service_data is expected to be empty"); return false; } - auto mnfDatas = device.get_manufacturer_datas(); - if (mnfDatas.size() != 1) { + auto mnf_datas = device.get_manufacturer_datas(); + if (mnf_datas.size() != 1) { ESP_LOGVV(TAG, "parse_device(): manufacturer_datas is expected to have a single element"); return false; } - auto mnfData = mnfDatas[0]; - if (mnfData.uuid.get_uuid().len != ESP_UUID_LEN_16) { + auto mnf_data = mnf_datas[0]; + if (mnf_data.uuid.get_uuid().len != ESP_UUID_LEN_16) { ESP_LOGVV(TAG, "parse_device(): manufacturer data element is expected to have uuid of length 16"); return false; } - if (mnfData.data.size() != 7) { + if (mnf_data.data.size() != 7) { ESP_LOGVV(TAG, "parse_device(): manufacturer data element length is expected to be of length 7"); return false; } - if (mnfData.data[6] != 8) { + if (mnf_data.data[6] != 8) { ESP_LOGVV(TAG, "parse_device(): unexpected data"); return false; } @@ -72,20 +72,20 @@ bool InkbirdIBSTH1_MINI::parse_device(const esp32_ble_tracker::ESPBTDevice &devi auto external_temperature = NAN; // Read bluetooth data into variable - auto measured_temperature = mnfData.uuid.get_uuid().uuid.uuid16 / 100.0f; + auto measured_temperature = mnf_data.uuid.get_uuid().uuid.uuid16 / 100.0f; // Set temperature or external_temperature based on which sensor is in use - if (mnfData.data[2] == 0) { + if (mnf_data.data[2] == 0) { temperature = measured_temperature; - } else if (mnfData.data[2] == 1) { + } else if (mnf_data.data[2] == 1) { external_temperature = measured_temperature; } else { ESP_LOGVV(TAG, "parse_device(): unknown sensor type"); return false; } - auto battery_level = mnfData.data[5]; - auto humidity = ((mnfData.data[1] << 8) + mnfData.data[0]) / 100.0f; + auto battery_level = mnf_data.data[5]; + auto humidity = ((mnf_data.data[1] << 8) + mnf_data.data[0]) / 100.0f; // Send temperature only if the value is set if (!isnan(temperature) && this->temperature_ != nullptr) { diff --git a/esphome/components/inkplate6/inkplate.cpp b/esphome/components/inkplate6/inkplate.cpp index 089b4791a6..b5cec6cf9e 100644 --- a/esphome/components/inkplate6/inkplate.cpp +++ b/esphome/components/inkplate6/inkplate.cpp @@ -150,7 +150,6 @@ void Inkplate6::dump_config() { } void Inkplate6::eink_off_() { ESP_LOGV(TAG, "Eink off called"); - unsigned long start_time = millis(); if (panel_on_ == 0) return; panel_on_ = 0; @@ -170,7 +169,6 @@ void Inkplate6::eink_off_() { } void Inkplate6::eink_on_() { ESP_LOGV(TAG, "Eink on called"); - unsigned long start_time = millis(); if (panel_on_ == 1) return; panel_on_ = 1; @@ -200,7 +198,7 @@ void Inkplate6::eink_on_() { } void Inkplate6::fill(Color color) { ESP_LOGV(TAG, "Fill called"); - unsigned long start_time = millis(); + uint32_t start_time = millis(); if (this->greyscale_) { uint8_t fill = ((color.red * 2126 / 10000) + (color.green * 7152 / 10000) + (color.blue * 722 / 10000)) >> 5; @@ -214,7 +212,7 @@ void Inkplate6::fill(Color color) { } void Inkplate6::display() { ESP_LOGV(TAG, "Display called"); - unsigned long start_time = millis(); + uint32_t start_time = millis(); if (this->greyscale_) { this->display3b_(); @@ -229,7 +227,7 @@ void Inkplate6::display() { } void Inkplate6::display1b_() { ESP_LOGV(TAG, "Display1b called"); - unsigned long start_time = millis(); + uint32_t start_time = millis(); memcpy(this->buffer_, this->partial_buffer_, this->get_buffer_length_()); @@ -348,7 +346,7 @@ void Inkplate6::display1b_() { } void Inkplate6::display3b_() { ESP_LOGV(TAG, "Display3b called"); - unsigned long start_time = millis(); + uint32_t start_time = millis(); eink_on_(); clean_fast_(0, 1); @@ -424,7 +422,7 @@ void Inkplate6::display3b_() { } bool Inkplate6::partial_update_() { ESP_LOGV(TAG, "Partial update called"); - unsigned long start_time = millis(); + uint32_t start_time = millis(); if (this->greyscale_) return false; if (this->block_partial_) @@ -531,7 +529,7 @@ void Inkplate6::vscan_end_() { } void Inkplate6::clean() { ESP_LOGV(TAG, "Clean called"); - unsigned long start_time = millis(); + uint32_t start_time = millis(); eink_on_(); clean_fast_(0, 1); // White @@ -544,7 +542,7 @@ void Inkplate6::clean() { } void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) { ESP_LOGV(TAG, "Clean fast called with: (%d, %d)", c, rep); - unsigned long start_time = millis(); + uint32_t start_time = millis(); eink_on_(); uint8_t data = 0; diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp index 0575dbee6a..45bee5b871 100644 --- a/esphome/components/ledc/ledc_output.cpp +++ b/esphome/components/ledc/ledc_output.cpp @@ -63,7 +63,7 @@ void LEDCOutput::update_frequency(float frequency) { this->write_state(this->duty_); } -uint8_t next_ledc_channel = 0; +uint8_t next_ledc_channel = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace ledc } // namespace esphome diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 129597cc00..237a8146e6 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -88,8 +88,8 @@ void MQTTClientComponent::start_dnslookup_() { this->dns_resolved_ = false; ip_addr_t addr; #ifdef ARDUINO_ARCH_ESP32 - err_t err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr, this->dns_found_callback, this, - LWIP_DNS_ADDRTYPE_IPV4); + err_t err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr, + MQTTClientComponent::dns_found_callback, this, LWIP_DNS_ADDRTYPE_IPV4); #endif #ifdef ARDUINO_ARCH_ESP8266 err_t err = dns_gethostbyname(this->credentials_.address.c_str(), &addr, diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index a83618e888..35b904357b 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -25,15 +25,10 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { if (range_end > this->tft_size_) range_end = this->tft_size_; - bool begin_status = false; -#ifdef ARDUINO_ARCH_ESP32 - begin_status = http->begin(this->tft_url_.c_str()); -#endif #ifdef ARDUINO_ARCH_ESP8266 #ifndef CLANG_TIDY http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); http->setRedirectLimit(3); - begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str()); #endif #endif @@ -44,6 +39,7 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { int tries = 1; int code = 0; + bool begin_status = false; while (tries <= 5) { #ifdef ARDUINO_ARCH_ESP32 begin_status = http->begin(this->tft_url_.c_str()); @@ -156,7 +152,7 @@ void Nextion::upload_tft() { ESP_LOGD(TAG, "connection failed"); #ifdef ARDUINO_ARCH_ESP32 if (psramFound()) - free(this->transfer_buffer_); + free(this->transfer_buffer_); // NOLINT else #endif delete this->transfer_buffer_; diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.cpp b/esphome/components/pulse_counter/pulse_counter_sensor.cpp index 9a0cf568a9..602c0bdd67 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.cpp +++ b/esphome/components/pulse_counter/pulse_counter_sensor.cpp @@ -45,10 +45,11 @@ pulse_counter_t PulseCounterStorage::read_raw_value() { #ifdef ARDUINO_ARCH_ESP32 bool PulseCounterStorage::pulse_counter_setup(GPIOPin *pin) { + static pcnt_unit_t next_pcnt_unit = PCNT_UNIT_0; this->pin = pin; this->pin->setup(); this->pcnt_unit = next_pcnt_unit; - next_pcnt_unit = pcnt_unit_t(int(next_pcnt_unit) + 1); // NOLINT + next_pcnt_unit = pcnt_unit_t(int(next_pcnt_unit) + 1); ESP_LOGCONFIG(TAG, " PCNT Unit Number: %u", this->pcnt_unit); @@ -166,9 +167,5 @@ void PulseCounterSensor::update() { } } -#ifdef ARDUINO_ARCH_ESP32 -pcnt_unit_t next_pcnt_unit = PCNT_UNIT_0; -#endif - } // namespace pulse_counter } // namespace esphome diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.h b/esphome/components/pulse_counter/pulse_counter_sensor.h index b3e3f42c01..3203ab81fd 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.h +++ b/esphome/components/pulse_counter/pulse_counter_sensor.h @@ -69,9 +69,5 @@ class PulseCounterSensor : public sensor::Sensor, public PollingComponent { sensor::Sensor *total_sensor_; }; -#ifdef ARDUINO_ARCH_ESP32 -extern pcnt_unit_t next_pcnt_unit; -#endif - } // namespace pulse_counter } // namespace esphome diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp index 34f190e45e..9edcd9166c 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp @@ -6,7 +6,7 @@ namespace esphome { namespace pvvx_mithermometer { -static const char *TAG = "pvvx_mithermometer"; +static const char *const TAG = "pvvx_mithermometer"; void PVVXMiThermometer::dump_config() { ESP_LOGCONFIG(TAG, "PVVX MiThermometer"); @@ -26,7 +26,7 @@ bool PVVXMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &devic bool success = false; for (auto &service_data : device.get_service_datas()) { auto res = parse_header(service_data); - if (res->is_duplicate) { + if (!res.has_value()) { continue; } if (!(parse_message(service_data.data, *res))) { @@ -46,11 +46,7 @@ bool PVVXMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &devic success = true; } - if (!success) { - return false; - } - - return true; + return success; } optional PVVXMiThermometer::parse_header(const esp32_ble_tracker::ServiceData &service_data) { @@ -64,12 +60,10 @@ optional PVVXMiThermometer::parse_header(const esp32_ble_tracker::S static uint8_t last_frame_count = 0; if (last_frame_count == raw[13]) { - ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%d).", static_cast(last_frame_count)); - result.is_duplicate = true; + ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%hhu).", last_frame_count); return {}; } last_frame_count = raw[13]; - result.is_duplicate = false; return result; } diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h index 42a40d4200..4132954983 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h @@ -14,7 +14,6 @@ struct ParseResult { optional humidity; optional battery_level; optional battery_voltage; - bool is_duplicate; int raw_offset; }; diff --git a/esphome/components/remote_base/rc5_protocol.cpp b/esphome/components/remote_base/rc5_protocol.cpp index a8b608ec9e..47a85cda57 100644 --- a/esphome/components/remote_base/rc5_protocol.cpp +++ b/esphome/components/remote_base/rc5_protocol.cpp @@ -10,7 +10,7 @@ static const uint32_t BIT_TIME_US = 889; static const uint8_t NBITS = 14; void RC5Protocol::encode(RemoteTransmitData *dst, const RC5Data &data) { - static bool TOGGLE = false; + static bool toggle = false; dst->set_carrier_frequency(36000); uint64_t out_data = 0; @@ -21,7 +21,7 @@ void RC5Protocol::encode(RemoteTransmitData *dst, const RC5Data &data) { } else { out_data |= 0b11 << 12; } - out_data |= TOGGLE << 11; + out_data |= toggle << 11; out_data |= data.address << 6; out_data |= command; @@ -34,7 +34,7 @@ void RC5Protocol::encode(RemoteTransmitData *dst, const RC5Data &data) { dst->space(BIT_TIME_US); } } - TOGGLE = !TOGGLE; + toggle = !toggle; } optional RC5Protocol::decode(RemoteReceiveData src) { RC5Data out{ diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index a0cdb6ec42..1ad520a099 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -3,7 +3,7 @@ #ifdef USE_SOCKET_IMPL_BSD_SOCKETS -#include +#include namespace esphome { namespace socket { @@ -13,14 +13,14 @@ std::string format_sockaddr(const struct sockaddr_storage &storage) { const struct sockaddr_in *addr = reinterpret_cast(&storage); char buf[INET_ADDRSTRLEN]; const char *ret = inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf)); - if (ret == NULL) + if (ret == nullptr) return {}; return std::string{buf}; } else if (storage.ss_family == AF_INET6) { const struct sockaddr_in6 *addr = reinterpret_cast(&storage); char buf[INET6_ADDRSTRLEN]; const char *ret = inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof(buf)); - if (ret == NULL) + if (ret == nullptr) return {}; return std::string{buf}; } @@ -32,7 +32,7 @@ class BSDSocketImpl : public Socket { BSDSocketImpl(int fd) : Socket(), fd_(fd) {} ~BSDSocketImpl() override { if (!closed_) { - close(); + close(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) } } std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) override { diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index a5d7fba30a..609b4df456 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -61,7 +61,7 @@ void SPIComponent::setup() { if (spi_bus_num == 0) { this->hw_spi_ = &SPI; } else { - this->hw_spi_ = new SPIClass(VSPI); + this->hw_spi_ = new SPIClass(VSPI); // NOLINT(cppcoreguidelines-owning-memory) } spi_bus_num++; this->hw_spi_->begin(clk_pin, miso_pin, mosi_pin); diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index ac8802739b..b44f76a9e6 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -458,26 +458,26 @@ void HOT ST7735::write_display_data_() { } void ST7735::spi_master_write_addr_(uint16_t addr1, uint16_t addr2) { - static uint8_t BYTE[4]; - BYTE[0] = (addr1 >> 8) & 0xFF; - BYTE[1] = addr1 & 0xFF; - BYTE[2] = (addr2 >> 8) & 0xFF; - BYTE[3] = addr2 & 0xFF; + static uint8_t byte[4]; + byte[0] = (addr1 >> 8) & 0xFF; + byte[1] = addr1 & 0xFF; + byte[2] = (addr2 >> 8) & 0xFF; + byte[3] = addr2 & 0xFF; this->dc_pin_->digital_write(true); - this->write_array(BYTE, 4); + this->write_array(byte, 4); } void ST7735::spi_master_write_color_(uint16_t color, uint16_t size) { - static uint8_t BYTE[1024]; + static uint8_t byte[1024]; int index = 0; for (int i = 0; i < size; i++) { - BYTE[index++] = (color >> 8) & 0xFF; - BYTE[index++] = color & 0xFF; + byte[index++] = (color >> 8) & 0xFF; + byte[index++] = color & 0xFF; } this->dc_pin_->digital_write(true); - return write_array(BYTE, size * 2); + return write_array(byte, size * 2); } } // namespace st7735 diff --git a/esphome/components/st7789v/st7789v.cpp b/esphome/components/st7789v/st7789v.cpp index c84153970d..471ad6664c 100644 --- a/esphome/components/st7789v/st7789v.cpp +++ b/esphome/components/st7789v/st7789v.cpp @@ -197,26 +197,26 @@ void ST7789V::write_data_(uint8_t value) { } void ST7789V::write_addr_(uint16_t addr1, uint16_t addr2) { - static uint8_t BYTE[4]; - BYTE[0] = (addr1 >> 8) & 0xFF; - BYTE[1] = addr1 & 0xFF; - BYTE[2] = (addr2 >> 8) & 0xFF; - BYTE[3] = addr2 & 0xFF; + static uint8_t byte[4]; + byte[0] = (addr1 >> 8) & 0xFF; + byte[1] = addr1 & 0xFF; + byte[2] = (addr2 >> 8) & 0xFF; + byte[3] = addr2 & 0xFF; this->dc_pin_->digital_write(true); - this->write_array(BYTE, 4); + this->write_array(byte, 4); } void ST7789V::write_color_(uint16_t color, uint16_t size) { - static uint8_t BYTE[1024]; + static uint8_t byte[1024]; int index = 0; for (int i = 0; i < size; i++) { - BYTE[index++] = (color >> 8) & 0xFF; - BYTE[index++] = color & 0xFF; + byte[index++] = (color >> 8) & 0xFF; + byte[index++] = color & 0xFF; } this->dc_pin_->digital_write(true); - return write_array(BYTE, size * 2); + return write_array(byte, size * 2); } int ST7789V::get_height_internal() { diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index 041cdaecdf..a79b7b841e 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -126,10 +126,6 @@ class UARTComponent : public Component, public Stream { #endif }; -#ifdef ARDUINO_ARCH_ESP32 -extern uint8_t next_uart_num; -#endif - class UARTDevice : public Stream { public: UARTDevice() = default; diff --git a/esphome/components/uart/uart_esp32.cpp b/esphome/components/uart/uart_esp32.cpp index c672a34c36..db2757780e 100644 --- a/esphome/components/uart/uart_esp32.cpp +++ b/esphome/components/uart/uart_esp32.cpp @@ -8,7 +8,6 @@ namespace esphome { namespace uart { static const char *const TAG = "uart_esp32"; -uint8_t next_uart_num = 1; static const uint32_t UART_PARITY_EVEN = 0 << 0; static const uint32_t UART_PARITY_ODD = 1 << 0; @@ -80,7 +79,8 @@ void UARTComponent::setup() { #endif this->hw_serial_ = &Serial; } else { - this->hw_serial_ = new HardwareSerial(next_uart_num++); + static uint8_t next_uart_num = 1; + this->hw_serial_ = new HardwareSerial(next_uart_num++); // NOLINT(cppcoreguidelines-owning-memory) } int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1; int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1; @@ -99,7 +99,7 @@ void UARTComponent::dump_config() { } ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_); ESP_LOGCONFIG(TAG, " Data Bits: %u", this->data_bits_); - ESP_LOGCONFIG(TAG, " Parity: %s", parity_to_str(this->parity_)); + ESP_LOGCONFIG(TAG, " Parity: %s", LOG_STR_ARG(parity_to_str(this->parity_))); ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_); this->check_logger_conflict_(); } diff --git a/esphome/components/wifi/wifi_component_esp32.cpp b/esphome/components/wifi/wifi_component_esp32.cpp index b56030db56..38c9e12bec 100644 --- a/esphome/components/wifi/wifi_component_esp32.cpp +++ b/esphome/components/wifi/wifi_component_esp32.cpp @@ -25,31 +25,31 @@ namespace wifi { static const char *const TAG = "wifi_esp32"; bool WiFiComponent::wifi_mode_(optional sta, optional ap) { - uint8_t current_mode = WiFi.getMode(); + uint8_t current_mode = WiFiClass::getMode(); bool current_sta = current_mode & 0b01; bool current_ap = current_mode & 0b10; - bool sta_ = sta.value_or(current_sta); - bool ap_ = ap.value_or(current_ap); - if (current_sta == sta_ && current_ap == ap_) + bool enable_sta = sta.value_or(current_sta); + bool enable_ap = ap.value_or(current_ap); + if (current_sta == enable_sta && current_ap == enable_ap) return true; - if (sta_ && !current_sta) { + if (enable_sta && !current_sta) { ESP_LOGV(TAG, "Enabling STA."); - } else if (!sta_ && current_sta) { + } else if (!enable_sta && current_sta) { ESP_LOGV(TAG, "Disabling STA."); } - if (ap_ && !current_ap) { + if (enable_ap && !current_ap) { ESP_LOGV(TAG, "Enabling AP."); - } else if (!ap_ && current_ap) { + } else if (!enable_ap && current_ap) { ESP_LOGV(TAG, "Disabling AP."); } uint8_t mode = 0; - if (sta_) + if (enable_sta) mode |= 0b01; - if (ap_) + if (enable_ap) mode |= 0b10; - bool ret = WiFi.mode(static_cast(mode)); + bool ret = WiFiClass::mode(static_cast(mode)); if (!ret) { ESP_LOGW(TAG, "Setting WiFi mode failed!"); @@ -159,8 +159,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv417wifi_sta_config_t wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - strcpy(reinterpret_cast(conf.sta.ssid), ap.get_ssid().c_str()); - strcpy(reinterpret_cast(conf.sta.password), ap.get_password().c_str()); + strlcpy(reinterpret_cast(conf.sta.ssid), ap.get_ssid().c_str(), sizeof(conf.sta.ssid)); + strlcpy(reinterpret_cast(conf.sta.password), ap.get_password().c_str(), sizeof(conf.sta.password)); // The weakest authmode to accept in the fast scan mode if (ap.get_password().empty()) { @@ -176,10 +176,10 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { #endif if (ap.get_bssid().has_value()) { - conf.sta.bssid_set = 1; + conf.sta.bssid_set = true; memcpy(conf.sta.bssid, ap.get_bssid()->data(), 6); } else { - conf.sta.bssid_set = 0; + conf.sta.bssid_set = false; } if (ap.get_channel().has_value()) { conf.sta.channel = *ap.get_channel(); @@ -559,7 +559,7 @@ void WiFiComponent::wifi_pre_setup_() { // Make sure WiFi is in clean state before anything starts this->wifi_mode_(false, false); } -wl_status_t WiFiComponent::wifi_sta_status_() { return WiFi.status(); } +wl_status_t WiFiComponent::wifi_sta_status_() { return WiFiClass::status(); } bool WiFiComponent::wifi_scan_start_() { // enable STA if (!this->wifi_mode_(true, {})) @@ -660,7 +660,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - strcpy(reinterpret_cast(conf.ap.ssid), ap.get_ssid().c_str()); + strlcpy(reinterpret_cast(conf.ap.ssid), ap.get_ssid().c_str(), sizeof(conf.ap.ssid)); conf.ap.channel = ap.get_channel().value_or(1); conf.ap.ssid_hidden = ap.get_ssid().size(); conf.ap.max_connection = 5; @@ -671,7 +671,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { *conf.ap.password = 0; } else { conf.ap.authmode = WIFI_AUTH_WPA2_PSK; - strcpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str()); + strlcpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.ssid)); } #if ESP_IDF_VERSION_MAJOR >= 4 diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 842b44b705..5a0ba92b09 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -581,7 +581,7 @@ wl_status_t WiFiComponent::wifi_sta_status_() { } } bool WiFiComponent::wifi_scan_start_() { - static bool FIRST_SCAN = false; + static bool first_scan = false; // enable STA if (!this->wifi_mode_(true, {})) @@ -595,7 +595,7 @@ bool WiFiComponent::wifi_scan_start_() { config.show_hidden = 1; #if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) config.scan_type = WIFI_SCAN_TYPE_ACTIVE; - if (FIRST_SCAN) { + if (first_scan) { config.scan_time.active.min = 100; config.scan_time.active.max = 200; } else { @@ -603,7 +603,7 @@ bool WiFiComponent::wifi_scan_start_() { config.scan_time.active.max = 500; } #endif - FIRST_SCAN = false; + first_scan = false; bool ret = wifi_station_scan(&config, &WiFiComponent::s_wifi_scan_done_callback); if (!ret) { ESP_LOGV(TAG, "wifi_station_scan failed!"); diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index f86dc44eeb..3e7e591f9a 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -15,7 +15,7 @@ static const char *const TAG = "xiaomi_ble"; bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result) { // motion detection, 1 byte, 8-bit unsigned integer if ((value_type == 0x03) && (value_length == 1)) { - result.has_motion = (data[0]) ? true : false; + result.has_motion = data[0]; } // temperature, 2 bytes, 16-bit signed integer (LE), 0.1 °C else if ((value_type == 0x04) && (value_length == 2)) { @@ -31,7 +31,7 @@ bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_l else if (((value_type == 0x07) || (value_type == 0x0F)) && (value_length == 3)) { const uint32_t illuminance = uint32_t(data[0]) | (uint32_t(data[1]) << 8) | (uint32_t(data[2]) << 16); result.illuminance = illuminance; - result.is_light = (illuminance == 100) ? true : false; + result.is_light = illuminance == 100; if (value_type == 0x0F) result.has_motion = true; } @@ -62,7 +62,7 @@ bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_l } // on/off state, 1 byte, 8-bit unsigned integer else if ((value_type == 0x12) && (value_length == 1)) { - result.is_active = (data[0]) ? true : false; + result.is_active = data[0]; } // mosquito tablet, 1 byte, 8-bit unsigned integer, 1 % else if ((value_type == 0x13) && (value_length == 1)) { @@ -72,7 +72,7 @@ bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_l else if ((value_type == 0x17) && (value_length == 4)) { const uint32_t idle_time = encode_uint32(data[3], data[2], data[1], data[0]); result.idle_time = idle_time / 60.0f; - result.has_motion = (idle_time) ? false : true; + result.has_motion = !idle_time; } else { return false; } @@ -81,7 +81,7 @@ bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_l } bool parse_xiaomi_message(const std::vector &message, XiaomiParseResult &result) { - result.has_encryption = (message[0] & 0x08) ? true : false; // update encryption status + result.has_encryption = message[0] & 0x08; // update encryption status if (result.has_encryption) { ESP_LOGVV(TAG, "parse_xiaomi_message(): payload is encrypted, stop reading message."); return false; @@ -136,9 +136,9 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service } auto raw = service_data.data; - result.has_data = (raw[0] & 0x40) ? true : false; - result.has_capability = (raw[0] & 0x20) ? true : false; - result.has_encryption = (raw[0] & 0x08) ? true : false; + result.has_data = raw[0] & 0x40; + result.has_capability = raw[0] & 0x20; + result.has_encryption = raw[0] & 0x08; if (!result.has_data) { ESP_LOGVV(TAG, "parse_xiaomi_header(): service data has no DATA flag."); diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp index c6e7a3f962..cfbe986fff 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp @@ -52,11 +52,7 @@ bool XiaomiCGD1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { success = true; } - if (!success) { - return false; - } - - return true; + return success; } void XiaomiCGD1::set_bindkey(const std::string &bindkey) { @@ -67,7 +63,7 @@ void XiaomiCGD1::set_bindkey(const std::string &bindkey) { char temp[3] = {0}; for (int i = 0; i < 16; i++) { strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, NULL, 16); + bindkey_[i] = std::strtoul(temp, nullptr, 16); } } diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp index 2ba2ac4c0a..8f42eaecaf 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp @@ -52,11 +52,7 @@ bool XiaomiCGDK2::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { success = true; } - if (!success) { - return false; - } - - return true; + return success; } void XiaomiCGDK2::set_bindkey(const std::string &bindkey) { @@ -67,7 +63,7 @@ void XiaomiCGDK2::set_bindkey(const std::string &bindkey) { char temp[3] = {0}; for (int i = 0; i < 16; i++) { strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, NULL, 16); + bindkey_[i] = std::strtoul(temp, nullptr, 16); } } diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp index 86192fb028..d0a91d23f0 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp @@ -52,11 +52,7 @@ bool XiaomiCGG1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { success = true; } - if (!success) { - return false; - } - - return true; + return success; } void XiaomiCGG1::set_bindkey(const std::string &bindkey) { @@ -67,7 +63,7 @@ void XiaomiCGG1::set_bindkey(const std::string &bindkey) { char temp[3] = {0}; for (int i = 0; i < 16; i++) { strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, NULL, 16); + bindkey_[i] = std::strtoul(temp, nullptr, 16); } } diff --git a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp index 2ed1024076..75ed947c25 100644 --- a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp +++ b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp @@ -6,7 +6,7 @@ namespace esphome { namespace xiaomi_cgpr1 { -static const char *TAG = "xiaomi_cgpr1"; +static const char *const TAG = "xiaomi_cgpr1"; void XiaomiCGPR1::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi CGPR1"); @@ -54,11 +54,7 @@ bool XiaomiCGPR1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { success = true; } - if (!success) { - return false; - } - - return true; + return success; } void XiaomiCGPR1::set_bindkey(const std::string &bindkey) { @@ -69,7 +65,7 @@ void XiaomiCGPR1::set_bindkey(const std::string &bindkey) { char temp[3] = {0}; for (int i = 0; i < 16; i++) { strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, NULL, 16); + bindkey_[i] = std::strtoul(temp, nullptr, 16); } } diff --git a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp index 5f7d67b85a..f626daad88 100644 --- a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp +++ b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp @@ -53,11 +53,7 @@ bool XiaomiGCLS002::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { success = true; } - if (!success) { - return false; - } - - return true; + return success; } } // namespace xiaomi_gcls002 diff --git a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp index 103201d511..3f2cbf963a 100644 --- a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp +++ b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp @@ -56,11 +56,7 @@ bool XiaomiHHCCJCY01::parse_device(const esp32_ble_tracker::ESPBTDevice &device) success = true; } - if (!success) { - return false; - } - - return true; + return success; } } // namespace xiaomi_hhccjcy01 diff --git a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp index efc83cb6cc..92b7171004 100644 --- a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp +++ b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp @@ -47,11 +47,7 @@ bool XiaomiHHCCPOT002::parse_device(const esp32_ble_tracker::ESPBTDevice &device success = true; } - if (!success) { - return false; - } - - return true; + return success; } } // namespace xiaomi_hhccpot002 diff --git a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp index c88a3c3b61..646796525e 100644 --- a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp +++ b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp @@ -53,11 +53,7 @@ bool XiaomiJQJCY01YM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) success = true; } - if (!success) { - return false; - } - - return true; + return success; } } // namespace xiaomi_jqjcy01ym diff --git a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp index 6b8ecdeaff..1ecf0606a2 100644 --- a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp +++ b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp @@ -50,11 +50,7 @@ bool XiaomiLYWSD02::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { success = true; } - if (!success) { - return false; - } - - return true; + return success; } } // namespace xiaomi_lywsd02 diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp index f0a2cee8d4..3eb0b68318 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp @@ -56,11 +56,7 @@ bool XiaomiLYWSD03MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device success = true; } - if (!success) { - return false; - } - - return true; + return success; } void XiaomiLYWSD03MMC::set_bindkey(const std::string &bindkey) { @@ -71,7 +67,7 @@ void XiaomiLYWSD03MMC::set_bindkey(const std::string &bindkey) { char temp[3] = {0}; for (int i = 0; i < 16; i++) { strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, NULL, 16); + bindkey_[i] = std::strtoul(temp, nullptr, 16); } } diff --git a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp index 39bcd9df03..f16fce296b 100644 --- a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp +++ b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp @@ -50,11 +50,7 @@ bool XiaomiLYWSDCGQ::parse_device(const esp32_ble_tracker::ESPBTDevice &device) success = true; } - if (!success) { - return false; - } - - return true; + return success; } } // namespace xiaomi_lywsdcgq diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp index e93a4b91ae..fdbb799ea6 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp @@ -56,11 +56,7 @@ bool XiaomiMHOC401::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { success = true; } - if (!success) { - return false; - } - - return true; + return success; } void XiaomiMHOC401::set_bindkey(const std::string &bindkey) { @@ -71,7 +67,7 @@ void XiaomiMHOC401::set_bindkey(const std::string &bindkey) { char temp[3] = {0}; for (int i = 0; i < 16; i++) { strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, NULL, 16); + bindkey_[i] = std::strtoul(temp, nullptr, 16); } } diff --git a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp index cc63e46573..34c5a975c7 100644 --- a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp +++ b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp @@ -71,8 +71,8 @@ bool XiaomiMiscale2::parse_message(const std::vector &message, ParseRes return false; } - bool is_Stabilized = ((data[1] & (1 << 5)) != 0) ? true : false; - bool loadRemoved = ((data[1] & (1 << 7)) != 0) ? true : false; + bool is_stabilized = ((data[1] & (1 << 5)) != 0); + bool load_removed = ((data[1] & (1 << 7)) != 0); // weight, 2 bytes, 16-bit unsigned integer, 1 kg const int16_t weight = uint16_t(data[11]) | (uint16_t(data[12]) << 8); @@ -85,11 +85,7 @@ bool XiaomiMiscale2::parse_message(const std::vector &message, ParseRes const int16_t impedance = uint16_t(data[9]) | (uint16_t(data[10]) << 8); result.impedance = impedance; - if (!is_Stabilized || loadRemoved || impedance == 0 || impedance >= 3000) { - return false; - } - - return true; + return is_stabilized && !load_removed && impedance != 0 && impedance < 3000; } bool XiaomiMiscale2::report_results(const optional &result, const std::string &address) { diff --git a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp index 739543ddb6..df1cede068 100644 --- a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp +++ b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp @@ -57,11 +57,7 @@ bool XiaomiMJYD02YLA::parse_device(const esp32_ble_tracker::ESPBTDevice &device) success = true; } - if (!success) { - return false; - } - - return true; + return success; } void XiaomiMJYD02YLA::set_bindkey(const std::string &bindkey) { @@ -72,7 +68,7 @@ void XiaomiMJYD02YLA::set_bindkey(const std::string &bindkey) { char temp[3] = {0}; for (int i = 0; i < 16; i++) { strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, NULL, 16); + bindkey_[i] = std::strtoul(temp, nullptr, 16); } } diff --git a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp index c1eff2e066..53429c1806 100644 --- a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp +++ b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp @@ -46,11 +46,7 @@ bool XiaomiMUE4094RT::parse_device(const esp32_ble_tracker::ESPBTDevice &device) success = true; } - if (!success) { - return false; - } - - return true; + return success; } } // namespace xiaomi_mue4094rt diff --git a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp index d6e2ebe5b2..7529bb7431 100644 --- a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp +++ b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp @@ -51,11 +51,7 @@ bool XiaomiWX08ZM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { success = true; } - if (!success) { - return false; - } - - return true; + return success; } } // namespace xiaomi_wx08zm diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index fac17a8271..c0f68d80d6 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -111,11 +111,11 @@ void Application::loop() { } void ICACHE_RAM_ATTR HOT Application::feed_wdt() { - static uint32_t LAST_FEED = 0; + static uint32_t last_feed = 0; uint32_t now = millis(); - if (now - LAST_FEED > 3) { + if (now - last_feed > 3) { this->feed_wdt_arch_(); - LAST_FEED = now; + last_feed = now; #ifdef USE_STATUS_LED if (status_led::global_status_led != nullptr) { status_led::global_status_led->call(); diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 3cca6445b5..45bb3b82a5 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -11,7 +11,6 @@ #define ESPHOME_PROJECT_VERSION "v2" // Feature flags -#define USE_ADC_SENSOR_VCC #define USE_API #define USE_BINARY_SENSOR #define USE_CAPTIVE_PORTAL @@ -46,12 +45,12 @@ #define USE_ESP32_CAMERA #define USE_ETHERNET #define USE_IMPROV +#define USE_SOCKET_IMPL_BSD_SOCKETS #endif #ifdef ARDUINO_ARCH_ESP8266 +#define USE_ADC_SENSOR_VCC #define USE_SOCKET_IMPL_LWIP_TCP -#else -#define USE_SOCKET_IMPL_BSD_SOCKETS #endif #define USE_API_PLAINTEXT diff --git a/platformio.ini b/platformio.ini index f4dea3fcb9..a35e0c10fe 100644 --- a/platformio.ini +++ b/platformio.ini @@ -69,7 +69,6 @@ lib_deps = Update build_flags = ${common.build_flags} - -DUSE_ETHERNET src_filter = ${common.src_filter} - diff --git a/script/clang-tidy b/script/clang-tidy index c2f47bad11..a63ebc674a 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -16,7 +16,7 @@ import click import pexpect sys.path.append(os.path.dirname(__file__)) -from helpers import shlex_quote, get_output, \ +from helpers import shlex_quote, get_output, filter_grep, \ build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata @@ -27,13 +27,15 @@ def clang_options(idedata): # disable built-in include directories from the host '-nostdinc', '-nostdinc++', + # pretend we're an Xtensa compiler, which gates some features in the headers + '-D__XTENSA__', # allow to condition code on the presence of clang-tidy '-DCLANG_TIDY' ] # copy compiler flags, except those clang doesn't understand. cmd.extend(flag for flag in idedata['cxx_flags'].split(' ') - if flag not in ('-free', '-fipa-pta', '-mlongcalls', '-mtext-section-literals')) + if flag not in ('-free', '-fipa-pta', '-fstrict-volatile-bitfields', '-mlongcalls', '-mtext-section-literals')) # defines cmd.extend(f'-D{define}' for define in idedata['defines']) @@ -97,6 +99,8 @@ def main(): parser.add_argument('-j', '--jobs', type=int, default=multiprocessing.cpu_count(), help='number of tidy instances to be run in parallel.') + parser.add_argument('-e', '--environment', default='esp8266-tidy', + help='the PlatformIO environment to run against (esp8266-tidy or esp32-tidy)') parser.add_argument('files', nargs='*', default=[], help='files to be processed (regex on path)') parser.add_argument('--fix', action='store_true', help='apply fix-its') @@ -104,6 +108,7 @@ def main(): help='run clang-tidy in quiet mode') parser.add_argument('-c', '--changed', action='store_true', help='only run on changed files') + parser.add_argument('-g', '--grep', help='only run on files containing value') parser.add_argument('--split-num', type=int, help='split the files into X jobs.', default=None) parser.add_argument('--split-at', type=int, help='which split is this? starts at 1', @@ -126,7 +131,7 @@ def main(): """) return 1 - idedata = load_idedata("esp8266-tidy") + idedata = load_idedata(args.environment) options = clang_options(idedata) files = [] @@ -141,6 +146,9 @@ def main(): if args.changed: files = filter_changed(files) + if args.grep: + files = filter_grep(files, args.grep) + files.sort() if args.split_num: diff --git a/script/helpers.py b/script/helpers.py index 7200078822..d32e0eafbe 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -92,6 +92,16 @@ def filter_changed(files): return files +def filter_grep(files, value): + matched = [] + for file in files: + with open(file, "r") as handle: + contents = handle.read() + if value in contents: + matched.append(file) + return matched + + def git_ls_files(patterns=None): command = ["git", "ls-files", "-s"] if patterns is not None: From ed7983af41e16e8af56b39913bd62918dfb6bc9f Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 13 Sep 2021 18:52:53 +0200 Subject: [PATCH 1319/1841] Fix API socket issues (#2288) * Fix API socket issues * Fix compile error against beta * Format --- esphome/components/api/api_connection.cpp | 74 ++++++++++++------- esphome/components/api/api_connection.h | 16 +--- esphome/components/api/api_frame_helper.cpp | 70 +++++++++++++----- esphome/components/api/api_frame_helper.h | 3 +- esphome/components/api/api_server.cpp | 2 +- .../components/socket/lwip_raw_tcp_impl.cpp | 54 +++++++++----- 6 files changed, 142 insertions(+), 77 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index a530554155..58a5662055 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -36,19 +36,14 @@ void APIConnection::start() { APIError err = helper_->init(); if (err != APIError::OK) { - ESP_LOGW(TAG, "Helper init failed: %d errno=%d", (int) err, errno); - remove_ = true; + on_fatal_error(); + ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); return; } client_info_ = helper_->getpeername(); helper_->set_log_info(client_info_); } -void APIConnection::force_disconnect_client() { - this->helper_->close(); - this->remove_ = true; -} - void APIConnection::loop() { if (this->remove_) return; @@ -57,9 +52,11 @@ void APIConnection::loop() { // when network is disconnected force disconnect immediately // don't wait for timeout this->on_fatal_error(); + ESP_LOGW(TAG, "%s: Network unavailable, disconnecting", client_info_.c_str()); return; } if (this->next_close_) { + // requested a disconnect this->helper_->close(); this->remove_ = true; return; @@ -68,7 +65,7 @@ void APIConnection::loop() { APIError err = helper_->loop(); if (err != APIError::OK) { on_fatal_error(); - ESP_LOGW(TAG, "%s: Socket operation failed: %d", client_info_.c_str(), (int) err); + ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); return; } ReadPacketBuffer buffer; @@ -77,7 +74,11 @@ void APIConnection::loop() { // pass } else if (err != APIError::OK) { on_fatal_error(); - ESP_LOGW(TAG, "%s: Reading failed: %d", client_info_.c_str(), (int) err); + if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { + ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str()); + } else { + ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); + } return; } else { this->last_traffic_ = millis(); @@ -95,8 +96,8 @@ void APIConnection::loop() { if (this->sent_ping_) { // Disconnect if not responded within 2.5*keepalive if (now - this->last_traffic_ > (keepalive * 5) / 2) { - this->force_disconnect_client(); - ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str()); + on_fatal_error(); + ESP_LOGW(TAG, "%s didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str()); } } else if (now - this->last_traffic_ > keepalive) { this->sent_ping_ = true; @@ -124,12 +125,40 @@ void APIConnection::loop() { } } #endif + + if (state_subs_at_ != -1) { + const auto &subs = this->parent_->get_state_subs(); + if (state_subs_at_ >= subs.size()) { + state_subs_at_ = -1; + } else { + auto &it = subs[state_subs_at_]; + SubscribeHomeAssistantStateResponse resp; + resp.entity_id = it.entity_id; + resp.attribute = it.attribute.value(); + if (this->send_subscribe_home_assistant_state_response(resp)) { + state_subs_at_++; + } + } + } } std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) { return App.get_name() + component_type + nameable->get_object_id(); } +DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) { + // remote initiated disconnect_client + // don't close yet, we still need to send the disconnect response + // close will happen on next loop + ESP_LOGD(TAG, "%s requested disconnected", client_info_.c_str()); + this->next_close_ = true; + DisconnectResponse resp; + return resp; +} +void APIConnection::on_disconnect_response(const DisconnectResponse &value) { + // pass +} + #ifdef USE_BINARY_SENSOR bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state) { if (!this->state_subscription_) @@ -703,7 +732,7 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) { // bool invalid_password = 1; resp.invalid_password = !correct; if (correct) { - ESP_LOGD(TAG, "Client '%s' connected successfully!", this->client_info_.c_str()); + ESP_LOGD(TAG, "%s: Connected successfully", this->client_info_.c_str()); this->connection_state_ = ConnectionState::AUTHENTICATED; #ifdef USE_HOMEASSISTANT_TIME @@ -749,15 +778,7 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) { } } void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) { - for (auto &it : this->parent_->get_state_subs()) { - SubscribeHomeAssistantStateResponse resp; - resp.entity_id = it.entity_id; - resp.attribute = it.attribute.value(); - if (!this->send_subscribe_home_assistant_state_response(resp)) { - this->on_fatal_error(); - return; - } - } + state_subs_at_ = 0; } bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) { if (this->remove_) @@ -770,7 +791,11 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) return false; if (err != APIError::OK) { on_fatal_error(); - ESP_LOGW(TAG, "%s: Packet write failed %d errno=%d", client_info_.c_str(), (int) err, errno); + if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) { + ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str()); + } else { + ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); + } return false; } this->last_traffic_ = millis(); @@ -778,14 +803,13 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) } void APIConnection::on_unauthenticated_access() { this->on_fatal_error(); - ESP_LOGD(TAG, "'%s' tried to access without authentication.", this->client_info_.c_str()); + ESP_LOGD(TAG, "%s: tried to access without authentication.", this->client_info_.c_str()); } void APIConnection::on_no_setup_connection() { this->on_fatal_error(); - ESP_LOGD(TAG, "'%s' tried to access without full connection.", this->client_info_.c_str()); + ESP_LOGD(TAG, "%s: tried to access without full connection.", this->client_info_.c_str()); } void APIConnection::on_fatal_error() { - ESP_LOGV(TAG, "Error: Disconnecting %s", this->client_info_.c_str()); this->helper_->close(); this->remove_ = true; } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index a1788bbede..a1f1769a19 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -16,7 +16,6 @@ class APIConnection : public APIServerConnection { virtual ~APIConnection() = default; void start(); - void force_disconnect_client(); void loop(); bool send_list_info_done() { @@ -88,10 +87,7 @@ class APIConnection : public APIServerConnection { } #endif - void on_disconnect_response(const DisconnectResponse &value) override { - this->helper_->close(); - this->remove_ = true; - } + void on_disconnect_response(const DisconnectResponse &value) override; void on_ping_response(const PingResponse &value) override { // we initiated ping this->sent_ping_ = false; @@ -102,14 +98,7 @@ class APIConnection : public APIServerConnection { #endif HelloResponse hello(const HelloRequest &msg) override; ConnectResponse connect(const ConnectRequest &msg) override; - DisconnectResponse disconnect(const DisconnectRequest &msg) override { - // remote initiated disconnect_client - // don't close yet, we still need to send the disconnect response - // close will happen on next loop - this->next_close_ = true; - DisconnectResponse resp; - return resp; - } + DisconnectResponse disconnect(const DisconnectRequest &msg) override; PingResponse ping(const PingRequest &msg) override { return {}; } DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override; void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); } @@ -177,6 +166,7 @@ class APIConnection : public APIServerConnection { APIServer *parent_; InitialStateIterator initial_state_iterator_; ListEntitiesIterator list_entities_iterator_; + int state_subs_at_ = -1; }; } // namespace api diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 520a5c2caf..c064c7278f 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -17,6 +17,54 @@ bool is_would_block(ssize_t ret) { return ret == 0; } +const char *api_error_to_str(APIError err) { + // not using switch to ensure compiler doesn't try to build a big table out of it + if (err == APIError::OK) { + return "OK"; + } else if (err == APIError::WOULD_BLOCK) { + return "WOULD_BLOCK"; + } else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) { + return "BAD_HANDSHAKE_PACKET_LEN"; + } else if (err == APIError::BAD_INDICATOR) { + return "BAD_INDICATOR"; + } else if (err == APIError::BAD_DATA_PACKET) { + return "BAD_DATA_PACKET"; + } else if (err == APIError::TCP_NODELAY_FAILED) { + return "TCP_NODELAY_FAILED"; + } else if (err == APIError::TCP_NONBLOCKING_FAILED) { + return "TCP_NONBLOCKING_FAILED"; + } else if (err == APIError::CLOSE_FAILED) { + return "CLOSE_FAILED"; + } else if (err == APIError::SHUTDOWN_FAILED) { + return "SHUTDOWN_FAILED"; + } else if (err == APIError::BAD_STATE) { + return "BAD_STATE"; + } else if (err == APIError::BAD_ARG) { + return "BAD_ARG"; + } else if (err == APIError::SOCKET_READ_FAILED) { + return "SOCKET_READ_FAILED"; + } else if (err == APIError::SOCKET_WRITE_FAILED) { + return "SOCKET_WRITE_FAILED"; + } else if (err == APIError::HANDSHAKESTATE_READ_FAILED) { + return "HANDSHAKESTATE_READ_FAILED"; + } else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) { + return "HANDSHAKESTATE_WRITE_FAILED"; + } else if (err == APIError::HANDSHAKESTATE_BAD_STATE) { + return "HANDSHAKESTATE_BAD_STATE"; + } else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) { + return "CIPHERSTATE_DECRYPT_FAILED"; + } else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) { + return "CIPHERSTATE_ENCRYPT_FAILED"; + } else if (err == APIError::OUT_OF_MEMORY) { + return "OUT_OF_MEMORY"; + } else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) { + return "HANDSHAKESTATE_SETUP_FAILED"; + } else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) { + return "HANDSHAKESTATE_SPLIT_FAILED"; + } + return "UNKNOWN"; +} + #define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__) #ifdef USE_API_NOISE @@ -808,14 +856,12 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() { // try send from tx_buf while (state_ != State::CLOSED && !tx_buf_.empty()) { ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size()); - if (sent == -1) { - if (errno == EWOULDBLOCK || errno == EAGAIN) - break; + if (is_would_block(sent)) { + break; + } else if (sent == -1) { state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); return APIError::SOCKET_WRITE_FAILED; - } else if (sent == 0) { - break; } // TODO: inefficient if multiple packets in txbuf // replace with deque of buffers @@ -869,20 +915,6 @@ APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) { // fully sent return APIError::OK; } -APIError APIPlaintextFrameHelper::write_frame_(const uint8_t *data, size_t len) { - APIError aerr; - - uint8_t header[3]; - header[0] = 0x01; // indicator - header[1] = (uint8_t)(len >> 8); - header[2] = (uint8_t) len; - - aerr = write_raw_(header, 3); - if (aerr != APIError::OK) - return aerr; - aerr = write_raw_(data, len); - return aerr; -} APIError APIPlaintextFrameHelper::close() { state_ = State::CLOSED; diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 7189bc4b4b..a8974cd25f 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -53,6 +53,8 @@ enum class APIError : int { HANDSHAKESTATE_SPLIT_FAILED = 1020, }; +const char *api_error_to_str(APIError err); + class APIFrameHelper { public: virtual APIError init() = 0; @@ -150,7 +152,6 @@ class APIPlaintextFrameHelper : public APIFrameHelper { APIError try_read_frame_(ParsedFrame *frame); APIError try_send_tx_buf_(); - APIError write_frame_(const uint8_t *data, size_t len); APIError write_raw_(const uint8_t *data, size_t len); std::unique_ptr socket_; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 1ef567ab57..d84a35747c 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -105,7 +105,7 @@ void APIServer::loop() { [](const std::unique_ptr &conn) { return !conn->remove_; }); // print disconnection messages for (auto it = new_end; it != this->clients_.end(); ++it) { - ESP_LOGD(TAG, "Disconnecting %s", (*it)->client_info_.c_str()); + ESP_LOGV(TAG, "Removing connection to %s", (*it)->client_info_.c_str()); } // resize vector this->clients_.erase(new_end, this->clients_.end()); diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 29e1b23823..2147e36632 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -109,14 +109,17 @@ class LWIPRawImpl : public Socket { LWIP_LOG("tcp_bind(%p ip=%u port=%u)", pcb_, ip.addr, port); err_t err = tcp_bind(pcb_, &ip, port); if (err == ERR_USE) { + LWIP_LOG(" -> err ERR_USE"); errno = EADDRINUSE; return -1; } if (err == ERR_VAL) { + LWIP_LOG(" -> err ERR_VAL"); errno = EINVAL; return -1; } if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); errno = EIO; return -1; } @@ -124,12 +127,13 @@ class LWIPRawImpl : public Socket { } int close() override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } LWIP_LOG("tcp_close(%p)", pcb_); err_t err = tcp_close(pcb_); if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); tcp_abort(pcb_); pcb_ = nullptr; errno = err == ERR_MEM ? ENOMEM : EIO; @@ -140,7 +144,7 @@ class LWIPRawImpl : public Socket { } int shutdown(int how) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } bool shut_rx = false, shut_tx = false; @@ -157,6 +161,7 @@ class LWIPRawImpl : public Socket { LWIP_LOG("tcp_shutdown(%p shut_rx=%d shut_tx=%d)", pcb_, shut_rx ? 1 : 0, shut_tx ? 1 : 0); err_t err = tcp_shutdown(pcb_, shut_rx, shut_tx); if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); errno = err == ERR_MEM ? ENOMEM : EIO; return -1; } @@ -165,7 +170,7 @@ class LWIPRawImpl : public Socket { int getpeername(struct sockaddr *name, socklen_t *addrlen) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (name == nullptr || addrlen == nullptr) { @@ -185,7 +190,7 @@ class LWIPRawImpl : public Socket { } std::string getpeername() override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return ""; } char buffer[24]; @@ -196,7 +201,7 @@ class LWIPRawImpl : public Socket { } int getsockname(struct sockaddr *name, socklen_t *addrlen) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (name == nullptr || addrlen == nullptr) { @@ -216,7 +221,7 @@ class LWIPRawImpl : public Socket { } std::string getsockname() override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return ""; } char buffer[24]; @@ -227,7 +232,7 @@ class LWIPRawImpl : public Socket { } int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (optlen == nullptr || optval == nullptr) { @@ -261,7 +266,7 @@ class LWIPRawImpl : public Socket { } int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (level == SOL_SOCKET && optname == SO_REUSEADDR) { @@ -314,7 +319,7 @@ class LWIPRawImpl : public Socket { } ssize_t read(void *buf, size_t len) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (rx_closed_ && rx_buf_ == nullptr) { @@ -368,7 +373,7 @@ class LWIPRawImpl : public Socket { } ssize_t write(const void *buf, size_t len) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (len == 0) @@ -386,24 +391,37 @@ class LWIPRawImpl : public Socket { LWIP_LOG("tcp_write(%p buf=%p %u)", pcb_, buf, to_send); err_t err = tcp_write(pcb_, buf, to_send, TCP_WRITE_FLAG_COPY); if (err == ERR_MEM) { + LWIP_LOG(" -> err ERR_MEM"); errno = EWOULDBLOCK; return -1; } if (err != ERR_OK) { - errno = EIO; + LWIP_LOG(" -> err %d", err); + errno = ECONNRESET; return -1; } - LWIP_LOG("tcp_output(%p)", pcb_); - err = tcp_output(pcb_); - if (err != ERR_OK) { - errno = EIO; - return -1; + if (tcp_nagle_disabled(pcb_)) { + LWIP_LOG("tcp_output(%p)", pcb_); + err = tcp_output(pcb_); + if (err == ERR_ABRT) { + LWIP_LOG(" -> err ERR_ABRT"); + // sometimes lwip returns ERR_ABRT for no apparent reason + // the connection works fine afterwards, and back with ESPAsyncTCP we + // indirectly also ignored this error + // FIXME: figure out where this is returned and what it means in this context + return to_send; + } + if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); + errno = ECONNRESET; + return -1; + } } return to_send; } int setblocking(bool blocking) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (blocking) { @@ -466,7 +484,7 @@ class LWIPRawImpl : public Socket { static void s_err_fn(void *arg, err_t err) { LWIPRawImpl *arg_this = reinterpret_cast(arg); - return arg_this->err_fn(err); + arg_this->err_fn(err); } static err_t s_recv_fn(void *arg, struct tcp_pcb *pcb, struct pbuf *pb, err_t err) { From 924df1e7de1a6f11d5ab75ed1e94ed3dcd739134 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 18:55:04 +0200 Subject: [PATCH 1320/1841] Run clang-tidy against Arduino 3 (#2146) * Add macros header with more usable Arduino version defines * Change Arduino version checking to use our version defines * Add missing ESP8266 check * Rename Arduino version macro to ARDUINO_VERSION_CODE * Upgrade clang-tidy to use Arduino 3 * Fix clang-tidy warnings * Upgrade NeoPixelBus to upstream 2.6.7 * Use Arduino-version-appropriate API to set redirect flags * Remove now unnecessary CLANG_TIDY ifdefs * Add preprocessor hackery to avoid including pgmspace.h * Bump base image to 4.1.1 and update lint * Fix nfctag * Fix make_unique ambiguous * Fix ignore name * Fix ambiguous v2 * Remove unused begin * Cast time_t to prevent issues on platforms where time_t is 32bit Co-authored-by: Otto winter --- .clang-tidy | 5 +++- .github/workflows/ci.yml | 2 +- .github/workflows/docker-lint-build.yml | 4 +-- docker/Dockerfile.dev | 2 +- docker/build.py | 2 +- esphome/components/adc/adc_sensor.cpp | 2 +- esphome/components/debug/debug_component.cpp | 11 ++++---- .../deep_sleep/deep_sleep_component.cpp | 2 +- .../components/http_request/http_request.cpp | 11 +++++--- esphome/components/light/light_color_values.h | 1 + esphome/components/neopixelbus/light.py | 2 +- esphome/components/nextion/nextion_upload.cpp | 25 +++++++++++++------ esphome/components/nfc/ndef_message.cpp | 4 +-- esphome/components/nfc/nfc_tag.h | 4 +-- esphome/components/pn532/pn532.cpp | 8 +++--- .../components/pn532/pn532_mifare_classic.cpp | 8 +++--- .../pn532/pn532_mifare_ultralight.cpp | 10 ++++---- esphome/components/sgp40/sgp40.cpp | 1 + .../components/shutdown/shutdown_switch.cpp | 2 +- esphome/components/spi/spi.cpp | 6 ++--- esphome/components/time/automation.cpp | 4 +-- esphome/components/uart/uart_esp8266.cpp | 6 ++--- .../web_server_base/web_server_base.cpp | 1 + esphome/core/application.cpp | 4 +-- esphome/core/application_esp8266.cpp | 4 ++- esphome/core/helpers.h | 7 ------ esphome/core/util.cpp | 4 +-- esphome/core/util.h | 2 +- platformio.ini | 5 ++-- script/clang-tidy | 15 +++++++++++ 30 files changed, 98 insertions(+), 66 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 8451a2bdd4..890eb18608 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -63,11 +63,15 @@ Checks: >- -misc-no-recursion, -misc-unused-parameters, -modernize-avoid-c-arrays, + -modernize-avoid-bind, + -modernize-concat-nested-namespaces, -modernize-return-braced-init-list, -modernize-use-auto, -modernize-use-default-member-init, -modernize-use-equals-default, -modernize-use-trailing-return-type, + -modernize-make-unique, + -modernize-use-nodiscard, -mpi-*, -objc-*, -readability-braces-around-statements, @@ -86,7 +90,6 @@ Checks: >- -readability-redundant-string-init, -readability-uppercase-literal-suffix, -readability-use-anyofallof, - -warnings-as-errors WarningsAsErrors: '*' AnalyzeTemporaryDtors: false FormatStyle: google diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb93c36d19..b00e1b3835 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: # cpp lint job runs with esphome-lint docker image so that clang-format-* # doesn't have to be installed - container: ghcr.io/esphome/esphome-lint:1.1 + container: ghcr.io/esphome/esphome-lint:1.2 steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/docker-lint-build.yml b/.github/workflows/docker-lint-build.yml index 32aec87cdd..8350d4c719 100644 --- a/.github/workflows/docker-lint-build.yml +++ b/.github/workflows/docker-lint-build.yml @@ -29,7 +29,7 @@ jobs: python-version: '3.9' - name: Set TAG run: | - echo "TAG=1.1" >> $GITHUB_ENV + echo "TAG=1.2" >> $GITHUB_ENV - name: Run build run: | @@ -74,7 +74,7 @@ jobs: python-version: '3.9' - name: Set TAG run: | - echo "TAG=1.1" >> $GITHUB_ENV + echo "TAG=1.2" >> $GITHUB_ENV - name: Enable experimental manifest support run: | mkdir -p ~/.docker diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index 27eaa18da9..c646ce989b 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -1 +1 @@ -FROM esphome/esphome-lint:1.1 +FROM esphome/esphome-lint:1.2 diff --git a/docker/build.py b/docker/build.py index 54a279f845..039d7dc15b 100755 --- a/docker/build.py +++ b/docker/build.py @@ -24,7 +24,7 @@ TYPE_LINT = 'lint' TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT] -BASE_VERSION = "3.6.0" +BASE_VERSION = "4.1.1" parser = argparse.ArgumentParser() diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 78f12a6b9e..4106a0c49b 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -148,7 +148,7 @@ float ADCSensor::sample() { #ifdef ARDUINO_ARCH_ESP8266 #ifdef USE_ADC_SENSOR_VCC - return ESP.getVcc() / 1024.0f; + return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance) #else return analogRead(this->pin_) / 1024.0f; // NOLINT #endif diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index 668792c7b1..11590a0978 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -21,11 +21,11 @@ void DebugComponent::dump_config() { #endif ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION); - this->free_heap_ = ESP.getFreeHeap(); + this->free_heap_ = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_); const char *flash_mode; - switch (ESP.getFlashChipMode()) { + switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance) case FM_QIO: flash_mode = "QIO"; break; @@ -49,6 +49,7 @@ void DebugComponent::dump_config() { default: flash_mode = "UNKNOWN"; } + // NOLINTNEXTLINE(readability-static-accessed-through-instance) ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", ESP.getFlashChipSize() / 1024, ESP.getFlashChipSpeed() / 1000000, flash_mode); @@ -87,7 +88,7 @@ void DebugComponent::dump_config() { ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version()); - std::string mac = uint64_to_string(ESP.getEfuseMac()); + std::string mac = uint64_to_string(ESP.getEfuseMac()); // NOLINT(readability-static-accessed-through-instance) ESP_LOGD(TAG, "EFuse MAC: %s", mac.c_str()); const char *reset_reason; @@ -186,7 +187,7 @@ void DebugComponent::dump_config() { ESP_LOGD(TAG, "Wakeup Reason: %s", wakeup_reason); #endif -#ifdef ARDUINO_ARCH_ESP8266 +#if defined(ARDUINO_ARCH_ESP8266) && !defined(CLANG_TIDY) ESP_LOGD(TAG, "Chip ID: 0x%08X", ESP.getChipId()); ESP_LOGD(TAG, "SDK Version: %s", ESP.getSdkVersion()); ESP_LOGD(TAG, "Core Version: %s", ESP.getCoreVersion().c_str()); @@ -198,7 +199,7 @@ void DebugComponent::dump_config() { #endif } void DebugComponent::loop() { - uint32_t new_free_heap = ESP.getFreeHeap(); + uint32_t new_free_heap = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) if (new_free_heap < this->free_heap_ / 2) { this->free_heap_ = new_free_heap; ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_); diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index de5672759a..00f660f41a 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -84,7 +84,7 @@ void DeepSleepComponent::begin_sleep(bool manual) { #endif #ifdef ARDUINO_ARCH_ESP8266 - ESP.deepSleep(*this->sleep_duration_); + ESP.deepSleep(*this->sleep_duration_); // NOLINT(readability-static-accessed-through-instance) #endif } float DeepSleepComponent::get_setup_priority() const { return setup_priority::LATE; } diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index bf5b24c4f2..0d4e425147 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -1,4 +1,5 @@ #include "http_request.h" +#include "esphome/core/macros.h" #include "esphome/core/log.h" namespace esphome { @@ -31,11 +32,15 @@ void HttpRequestComponent::send(const std::vector begin_status = this->client_.begin(url); #endif #ifdef ARDUINO_ARCH_ESP8266 -#ifndef CLANG_TIDY +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) + this->client_.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); +#elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) this->client_.setFollowRedirects(true); - this->client_.setRedirectLimit(3); - begin_status = this->client_.begin(*this->get_wifi_client_(), url); #endif +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) + this->client_.setRedirectLimit(3); +#endif + begin_status = this->client_.begin(*this->get_wifi_client_(), url); #endif if (!begin_status) { diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index dd74c396c6..ffbe378ee3 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -2,6 +2,7 @@ #include "esphome/core/helpers.h" #include "color_mode.h" +#include namespace esphome { namespace light { diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index 59d784a614..575f69b731 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -205,4 +205,4 @@ async def to_code(config): cg.add(var.set_pixel_order(getattr(ESPNeoPixelOrder, config[CONF_TYPE]))) # https://github.com/Makuna/NeoPixelBus/blob/master/library.json - cg.add_library("NeoPixelBus-esphome", "2.6.2") + cg.add_library("NeoPixelBus", "2.6.7") diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index 35b904357b..f864a397cc 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -1,6 +1,7 @@ #include "nextion.h" #include "esphome/core/application.h" +#include "esphome/core/macros.h" #include "esphome/core/util.h" #include "esphome/core/log.h" @@ -26,8 +27,12 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { range_end = this->tft_size_; #ifdef ARDUINO_ARCH_ESP8266 -#ifndef CLANG_TIDY +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); +#elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) + http->setFollowRedirects(true); +#endif +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) http->setRedirectLimit(3); #endif #endif @@ -44,10 +49,8 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { #ifdef ARDUINO_ARCH_ESP32 begin_status = http->begin(this->tft_url_.c_str()); #endif -#ifndef CLANG_TIDY #ifdef ARDUINO_ARCH_ESP8266 begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str()); -#endif #endif ++tries; @@ -140,11 +143,15 @@ void Nextion::upload_tft() { begin_status = http.begin(this->tft_url_.c_str()); #endif #ifdef ARDUINO_ARCH_ESP8266 -#ifndef CLANG_TIDY +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); - http.setRedirectLimit(3); - begin_status = http.begin(*this->get_wifi_client_(), this->tft_url_.c_str()); +#elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) + http.setFollowRedirects(true); #endif +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) + http.setRedirectLimit(3); +#endif + begin_status = http.begin(*this->get_wifi_client_(), this->tft_url_.c_str()); #endif if (!begin_status) { @@ -256,6 +263,7 @@ void Nextion::upload_tft() { } } #else + // NOLINTNEXTLINE(readability-static-accessed-through-instance) uint32_t chunk_size = ESP.getFreeHeap() < 10240 ? 4096 : 8192; #endif @@ -270,6 +278,7 @@ void Nextion::upload_tft() { } } else { #endif + // NOLINTNEXTLINE(readability-static-accessed-through-instance) ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap()); this->transfer_buffer_ = new (std::nothrow) uint8_t[chunk_size]; // NOLINT(cppcoreguidelines-owning-memory) if (this->transfer_buffer_ == nullptr) { // Try a smaller size @@ -288,6 +297,7 @@ void Nextion::upload_tft() { this->transfer_buffer_size_ = chunk_size; } + // NOLINTNEXTLINE(readability-static-accessed-through-instance) ESP_LOGD(TAG, "Updating tft from \"%s\" with a file size of %d using %zu chunksize, Heap Size %d", this->tft_url_.c_str(), this->content_length_, this->transfer_buffer_size_, ESP.getFreeHeap()); @@ -299,6 +309,7 @@ void Nextion::upload_tft() { this->upload_end_(); } App.feed_wdt(); + // NOLINTNEXTLINE(readability-static-accessed-through-instance) ESP_LOGD(TAG, "Heap Size %d, Bytes left %d", ESP.getFreeHeap(), this->content_length_); } ESP_LOGD(TAG, "Successfully updated Nextion!"); @@ -311,7 +322,7 @@ void Nextion::upload_end_() { this->soft_reset(); delay(1500); // NOLINT ESP_LOGD(TAG, "Restarting esphome"); - ESP.restart(); + ESP.restart(); // NOLINT(readability-static-accessed-through-instance) } #ifdef ARDUINO_ARCH_ESP8266 diff --git a/esphome/components/nfc/ndef_message.cpp b/esphome/components/nfc/ndef_message.cpp index b1554f41ae..147392940c 100644 --- a/esphome/components/nfc/ndef_message.cpp +++ b/esphome/components/nfc/ndef_message.cpp @@ -83,11 +83,11 @@ bool NdefMessage::add_text_record(const std::string &text) { return this->add_te bool NdefMessage::add_text_record(const std::string &text, const std::string &encoding) { std::string payload = to_string(text.length()) + encoding + text; - return this->add_record(make_unique(TNF_WELL_KNOWN, "T", payload)); + return this->add_record(std::unique_ptr{new NdefRecord(TNF_WELL_KNOWN, "T", payload)}); } bool NdefMessage::add_uri_record(const std::string &uri) { - return this->add_record(make_unique(TNF_WELL_KNOWN, "U", uri)); + return this->add_record(std::unique_ptr{new NdefRecord(TNF_WELL_KNOWN, "U", uri)}); } std::vector NdefMessage::encode() { diff --git a/esphome/components/nfc/nfc_tag.h b/esphome/components/nfc/nfc_tag.h index ab6ca650e4..2c8b0a5f21 100644 --- a/esphome/components/nfc/nfc_tag.h +++ b/esphome/components/nfc/nfc_tag.h @@ -31,13 +31,13 @@ class NfcTag { NfcTag(std::vector &uid, const std::string &tag_type, std::vector &ndef_data) { this->uid_ = uid; this->tag_type_ = tag_type; - this->ndef_message_ = make_unique(ndef_data); + this->ndef_message_ = std::unique_ptr(new NdefMessage(ndef_data)); }; NfcTag(const NfcTag &rhs) { uid_ = rhs.uid_; tag_type_ = rhs.tag_type_; if (rhs.ndef_message_ != nullptr) - ndef_message_ = make_unique(*rhs.ndef_message_); + ndef_message_ = std::unique_ptr(new NdefMessage(*rhs.ndef_message_)); } std::vector &get_uid() { return this->uid_; }; diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index 8fda19cba3..c28ce8d503 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -104,7 +104,7 @@ void PN532::loop() { if (!success) { // Something failed if (!this->current_uid_.empty()) { - auto tag = make_unique(this->current_uid_); + auto tag = std::unique_ptr{new nfc::NfcTag(this->current_uid_)}; for (auto *trigger : this->triggers_ontagremoved_) trigger->process(tag); } @@ -117,7 +117,7 @@ void PN532::loop() { if (num_targets != 1) { // no tags found or too many if (!this->current_uid_.empty()) { - auto tag = make_unique(this->current_uid_); + auto tag = std::unique_ptr{new nfc::NfcTag(this->current_uid_)}; for (auto *trigger : this->triggers_ontagremoved_) trigger->process(tag); } @@ -281,9 +281,9 @@ std::unique_ptr PN532::read_tag_(std::vector &uid) { return this->read_mifare_ultralight_tag_(uid); } else if (type == nfc::TAG_TYPE_UNKNOWN) { ESP_LOGV(TAG, "Cannot determine tag type"); - return make_unique(uid); + return std::unique_ptr{new nfc::NfcTag(uid)}; } else { - return make_unique(uid); + return std::unique_ptr{new nfc::NfcTag(uid)}; } } diff --git a/esphome/components/pn532/pn532_mifare_classic.cpp b/esphome/components/pn532/pn532_mifare_classic.cpp index fa99e5cfab..f4bd11d49f 100644 --- a/esphome/components/pn532/pn532_mifare_classic.cpp +++ b/esphome/components/pn532/pn532_mifare_classic.cpp @@ -15,15 +15,15 @@ std::unique_ptr PN532::read_mifare_classic_tag_(std::vector data; if (this->read_mifare_classic_block_(current_block, data)) { if (!nfc::decode_mifare_classic_tlv(data, message_length, message_start_index)) { - return make_unique(uid, nfc::ERROR); + return std::unique_ptr{new nfc::NfcTag(uid, nfc::ERROR)}; } } else { ESP_LOGE(TAG, "Failed to read block %d", current_block); - return make_unique(uid, nfc::MIFARE_CLASSIC); + return std::unique_ptr{new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC)}; } } else { ESP_LOGV(TAG, "Tag is not NDEF formatted"); - return make_unique(uid, nfc::MIFARE_CLASSIC); + return std::unique_ptr{new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC)}; } uint32_t index = 0; @@ -51,7 +51,7 @@ std::unique_ptr PN532::read_mifare_classic_tag_(std::vector(uid, nfc::MIFARE_CLASSIC, buffer); + return std::unique_ptr{new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC, buffer)}; } bool PN532::read_mifare_classic_block_(uint8_t block_num, std::vector &data) { diff --git a/esphome/components/pn532/pn532_mifare_ultralight.cpp b/esphome/components/pn532/pn532_mifare_ultralight.cpp index 6f118419ba..24e97ab95c 100644 --- a/esphome/components/pn532/pn532_mifare_ultralight.cpp +++ b/esphome/components/pn532/pn532_mifare_ultralight.cpp @@ -9,25 +9,25 @@ static const char *const TAG = "pn532.mifare_ultralight"; std::unique_ptr PN532::read_mifare_ultralight_tag_(std::vector &uid) { if (!this->is_mifare_ultralight_formatted_()) { ESP_LOGD(TAG, "Not NDEF formatted"); - return make_unique(uid, nfc::NFC_FORUM_TYPE_2); + return std::unique_ptr{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2)}; } uint8_t message_length; uint8_t message_start_index; if (!this->find_mifare_ultralight_ndef_(message_length, message_start_index)) { - return make_unique(uid, nfc::NFC_FORUM_TYPE_2); + return std::unique_ptr{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2)}; } ESP_LOGVV(TAG, "message length: %d, start: %d", message_length, message_start_index); if (message_length == 0) { - return make_unique(uid, nfc::NFC_FORUM_TYPE_2); + return std::unique_ptr{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2)}; } std::vector data; for (uint8_t page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; page < nfc::MIFARE_ULTRALIGHT_MAX_PAGE; page++) { std::vector page_data; if (!this->read_mifare_ultralight_page_(page, page_data)) { ESP_LOGE(TAG, "Error reading page %d", page); - return make_unique(uid, nfc::NFC_FORUM_TYPE_2); + return std::unique_ptr{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2)}; } data.insert(data.end(), page_data.begin(), page_data.end()); @@ -38,7 +38,7 @@ std::unique_ptr PN532::read_mifare_ultralight_tag_(std::vector(uid, nfc::NFC_FORUM_TYPE_2, data); + return std::unique_ptr{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2, data)}; } bool PN532::read_mifare_ultralight_page_(uint8_t page_num, std::vector &data) { diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp index a911c107b9..1a1909eeb0 100644 --- a/esphome/components/sgp40/sgp40.cpp +++ b/esphome/components/sgp40/sgp40.cpp @@ -1,5 +1,6 @@ #include "esphome/core/log.h" #include "sgp40.h" +#include namespace esphome { namespace sgp40 { diff --git a/esphome/components/shutdown/shutdown_switch.cpp b/esphome/components/shutdown/shutdown_switch.cpp index 3cc8ba9e1b..87b755cab6 100644 --- a/esphome/components/shutdown/shutdown_switch.cpp +++ b/esphome/components/shutdown/shutdown_switch.cpp @@ -18,7 +18,7 @@ void ShutdownSwitch::write_state(bool state) { App.run_safe_shutdown_hooks(); #ifdef ARDUINO_ARCH_ESP8266 - ESP.deepSleep(0); + ESP.deepSleep(0); // NOLINT(readability-static-accessed-through-instance) #endif #ifdef ARDUINO_ARCH_ESP32 esp_deep_sleep_start(); diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index 609b4df456..3180447711 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -95,12 +95,12 @@ void SPIComponent::debug_rx(uint8_t value) { void SPIComponent::debug_enable(uint8_t pin) { ESP_LOGVV(TAG, "Enabling SPI Chip on pin %u...", pin); } void SPIComponent::cycle_clock_(bool value) { - uint32_t start = ESP.getCycleCount(); - while (start - ESP.getCycleCount() < this->wait_cycle_) + uint32_t start = ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) + while (start - ESP.getCycleCount() < this->wait_cycle_) // NOLINT(readability-static-accessed-through-instance) ; this->clk_->digital_write(value); start += this->wait_cycle_; - while (start - ESP.getCycleCount() < this->wait_cycle_) + while (start - ESP.getCycleCount() < this->wait_cycle_) // NOLINT(readability-static-accessed-through-instance) ; } diff --git a/esphome/components/time/automation.cpp b/esphome/components/time/automation.cpp index 84cc76a762..f133ab021c 100644 --- a/esphome/components/time/automation.cpp +++ b/esphome/components/time/automation.cpp @@ -43,9 +43,9 @@ void CronTrigger::loop() { this->last_check_ = time; if (!time.fields_in_range()) { ESP_LOGW(TAG, "Time is out of range!"); - ESP_LOGD(TAG, "Second=%02u Minute=%02u Hour=%02u DayOfWeek=%u DayOfMonth=%u DayOfYear=%u Month=%u time=%ld", + ESP_LOGD(TAG, "Second=%02u Minute=%02u Hour=%02u DayOfWeek=%u DayOfMonth=%u DayOfYear=%u Month=%u time=%" PRId64, time.second, time.minute, time.hour, time.day_of_week, time.day_of_month, time.day_of_year, time.month, - time.timestamp); + (int64_t) time.timestamp); } if (this->matches(time)) diff --git a/esphome/components/uart/uart_esp8266.cpp b/esphome/components/uart/uart_esp8266.cpp index 6d207fde6c..a74f2601e4 100644 --- a/esphome/components/uart/uart_esp8266.cpp +++ b/esphome/components/uart/uart_esp8266.cpp @@ -233,7 +233,7 @@ void ESP8266SoftwareSerial::setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_ra } void ICACHE_RAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg) { uint32_t wait = arg->bit_time_ + arg->bit_time_ / 3 - 500; - const uint32_t start = ESP.getCycleCount(); + const uint32_t start = ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) uint8_t rec = 0; // Manually unroll the loop for (int i = 0; i < arg->data_bits_; i++) @@ -273,7 +273,7 @@ void ICACHE_RAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { { InterruptLock lock; uint32_t wait = this->bit_time_; - const uint32_t start = ESP.getCycleCount(); + const uint32_t start = ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) // Start bit this->write_bit_(false, &wait, start); for (int i = 0; i < this->data_bits_; i++) { @@ -291,7 +291,7 @@ void ICACHE_RAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { } } void ICACHE_RAM_ATTR ESP8266SoftwareSerial::wait_(uint32_t *wait, const uint32_t &start) { - while (ESP.getCycleCount() - start < *wait) + while (ESP.getCycleCount() - start < *wait) // NOLINT(readability-static-accessed-through-instance) ; *wait += this->bit_time_; } diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index 9f04c3b7bf..6351941aee 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -29,6 +29,7 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin this->ota_read_length_ = 0; #ifdef ARDUINO_ARCH_ESP8266 Update.runAsync(true); + // NOLINTNEXTLINE(readability-static-accessed-through-instance) success = Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000); #endif #ifdef ARDUINO_ARCH_ESP32 diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index c0f68d80d6..5ab1d973f4 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -127,7 +127,7 @@ void Application::reboot() { ESP_LOGI(TAG, "Forcing a reboot..."); for (auto *comp : this->components_) comp->on_shutdown(); - ESP.restart(); + ESP.restart(); // NOLINT(readability-static-accessed-through-instance) // restart() doesn't always end execution while (true) { yield(); @@ -139,7 +139,7 @@ void Application::safe_reboot() { comp->on_safe_shutdown(); for (auto *comp : this->components_) comp->on_shutdown(); - ESP.restart(); + ESP.restart(); // NOLINT(readability-static-accessed-through-instance) // restart() doesn't always end execution while (true) { yield(); diff --git a/esphome/core/application_esp8266.cpp b/esphome/core/application_esp8266.cpp index 95139ca112..ee32417634 100644 --- a/esphome/core/application_esp8266.cpp +++ b/esphome/core/application_esp8266.cpp @@ -6,7 +6,9 @@ namespace esphome { static const char *const TAG = "app_esp8266"; -void ICACHE_RAM_ATTR HOT Application::feed_wdt_arch_() { ESP.wdtFeed(); } +void ICACHE_RAM_ATTR HOT Application::feed_wdt_arch_() { + ESP.wdtFeed(); // NOLINT(readability-static-accessed-through-instance) +} } // namespace esphome #endif diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 044de6c8b0..86c70088d4 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -12,13 +12,6 @@ #include "esphome/core/optional.h" -#ifdef CLANG_TIDY -#undef ICACHE_RAM_ATTR -#define ICACHE_RAM_ATTR -#undef ICACHE_RODATA_ATTR -#define ICACHE_RODATA_ATTR -#endif - #define HOT __attribute__((hot)) #define ESPDEPRECATED(msg, when) __attribute__((deprecated(msg))) #define ALWAYS_INLINE __attribute__((always_inline)) diff --git a/esphome/core/util.cpp b/esphome/core/util.cpp index 67e575e1c5..e0132e2e4a 100644 --- a/esphome/core/util.cpp +++ b/esphome/core/util.cpp @@ -75,12 +75,12 @@ static const uint8_t WEBSERVER_PORT = 80; #ifdef USE_MDNS #ifdef ARDUINO_ARCH_ESP8266 -void network_setup_mdns(IPAddress address, int interface) { +void network_setup_mdns(const IPAddress &address, int interface) { // Latest arduino framework breaks mDNS for AP interface // see https://github.com/esp8266/Arduino/issues/6114 if (interface == 1) return; - MDNS.begin(App.get_name().c_str(), std::move(address)); + MDNS.begin(App.get_name().c_str(), address); mdns_setup = true; #endif #ifdef ARDUINO_ARCH_ESP32 diff --git a/esphome/core/util.h b/esphome/core/util.h index 2d58eff893..764c6aaf03 100644 --- a/esphome/core/util.h +++ b/esphome/core/util.h @@ -21,7 +21,7 @@ bool remote_is_connected(); /// Manually set up the network stack (outside of the App.setup() loop, for example in OTA safe mode) #ifdef ARDUINO_ARCH_ESP8266 -void network_setup_mdns(IPAddress address, int interface); +void network_setup_mdns(const IPAddress &address, int interface); #endif #ifdef ARDUINO_ARCH_ESP32 void network_setup_mdns(); diff --git a/platformio.ini b/platformio.ini index a35e0c10fe..3b1241f282 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,7 +30,7 @@ lib_deps = ArduinoJson-esphomelib@5.13.3 esphome/ESPAsyncWebServer-esphome@1.3.0 FastLED@3.3.2 - NeoPixelBus-esphome@2.6.2 + NeoPixelBus@2.6.7 1655@1.0.2 ; TinyGPSPlus (has name conflict) 6865@1.0.0 ; TM1651 Battery Display 6306@1.0.3 ; HM3301 @@ -47,8 +47,7 @@ src_filter = +<.temp/all-include.cpp> [common:esp8266] -; 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 +platform = platformio/espressif8266@3.1.0 framework = arduino board = nodemcuv2 lib_deps = diff --git a/script/clang-tidy b/script/clang-tidy index a63ebc674a..463959fa5d 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -27,6 +27,21 @@ def clang_options(idedata): # disable built-in include directories from the host '-nostdinc', '-nostdinc++', + # replace pgmspace.h, as it uses GNU extensions clang doesn't support + # https://github.com/earlephilhower/newlib-xtensa/pull/18 + '-D_PGMSPACE_H_', + '-Dpgm_read_byte(s)=(*(const uint8_t *)(s))', + '-Dpgm_read_byte_near(s)=(*(const uint8_t *)(s))', + '-Dpgm_read_dword(s)=(*(const uint32_t *)(s))', + '-DPROGMEM=', + '-DPGM_P=const char *', + '-DPSTR(s)=(s)', + # this next one is also needed with upstream pgmspace.h + # suppress warning about identifier naming in expansion of this macro + '-DPSTRN(s, n)=(s)', + # suppress warning about attribute cannot be applied to type + # https://github.com/esp8266/Arduino/pull/8258 + '-Ddeprecated(x)=', # pretend we're an Xtensa compiler, which gates some features in the headers '-D__XTENSA__', # allow to condition code on the presence of clang-tidy From e6b0a0ca2b9fbf329d8233c322257078d2f5a7b6 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 18:58:49 +0200 Subject: [PATCH 1321/1841] Clean-up sensor integration (#2275) --- esphome/components/api/api_connection.cpp | 2 +- esphome/components/demo/demo_sensor.h | 2 +- esphome/components/mqtt/mqtt_sensor.cpp | 15 +-- esphome/components/sensor/filter.cpp | 28 ----- esphome/components/sensor/filter.h | 19 --- esphome/components/sensor/sensor.cpp | 106 +++++++--------- esphome/components/sensor/sensor.h | 140 ++++++++-------------- 7 files changed, 102 insertions(+), 210 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 58a5662055..31a530c04d 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -422,7 +422,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { msg.accuracy_decimals = sensor->get_accuracy_decimals(); msg.force_update = sensor->get_force_update(); msg.device_class = sensor->get_device_class(); - msg.state_class = static_cast(sensor->state_class); + msg.state_class = static_cast(sensor->get_state_class()); msg.disabled_by_default = sensor->is_disabled_by_default(); return this->send_list_entities_sensor_response(msg); diff --git a/esphome/components/demo/demo_sensor.h b/esphome/components/demo/demo_sensor.h index 344aaf26f8..9a35674124 100644 --- a/esphome/components/demo/demo_sensor.h +++ b/esphome/components/demo/demo_sensor.h @@ -11,7 +11,7 @@ class DemoSensor : public sensor::Sensor, public PollingComponent { public: void update() override { float val = random_float(); - bool increasing = this->state_class == sensor::STATE_CLASS_TOTAL_INCREASING; + bool increasing = this->get_state_class() == sensor::STATE_CLASS_TOTAL_INCREASING; if (increasing) { float base = isnan(this->state) ? 0.0f : this->state; this->publish_state(base + val * 10); diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index d440e30fc4..e921056167 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -31,16 +31,9 @@ void MQTTSensorComponent::dump_config() { std::string MQTTSensorComponent::component_type() const { return "sensor"; } uint32_t MQTTSensorComponent::get_expire_after() const { - if (this->expire_after_.has_value()) { + if (this->expire_after_.has_value()) return *this->expire_after_; - } else { -#ifdef USE_DEEP_SLEEP - if (deep_sleep::global_has_deep_sleep) { - return 0; - } -#endif - return this->sensor_->calculate_expected_filter_update_interval() * 5; - } + return 0; } void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire_after_ = expire_after; } void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } @@ -61,8 +54,8 @@ void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo if (this->sensor_->get_force_update()) root["force_update"] = true; - if (this->sensor_->state_class != STATE_CLASS_NONE) - root["state_class"] = state_class_to_string(this->sensor_->state_class); + if (this->sensor_->get_state_class() != STATE_CLASS_NONE) + root["state_class"] = state_class_to_string(this->sensor_->get_state_class()); config.command_topic = false; } diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index bbe47b43ec..f048189959 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -8,7 +8,6 @@ namespace sensor { static const char *const TAG = "sensor.filter"; // Filter -uint32_t Filter::expected_interval(uint32_t input) { return input; } void Filter::input(float value) { ESP_LOGVV(TAG, "Filter(%p)::input(%f)", this, value); optional out = this->new_value(value); @@ -29,15 +28,6 @@ void Filter::initialize(Sensor *parent, Filter *next) { this->parent_ = parent; this->next_ = next; } -uint32_t Filter::calculate_remaining_interval(uint32_t input) { - uint32_t this_interval = this->expected_interval(input); - ESP_LOGVV(TAG, "Filter(%p)::calculate_remaining_interval(%u) -> %u", this, input, this_interval); - if (this->next_ == nullptr) { - return this_interval; - } else { - return this->next_->calculate_remaining_interval(this_interval); - } -} // MedianFilter MedianFilter::MedianFilter(size_t window_size, size_t send_every, size_t send_first_at) @@ -75,8 +65,6 @@ optional MedianFilter::new_value(float value) { return {}; } -uint32_t MedianFilter::expected_interval(uint32_t input) { return input * this->send_every_; } - // MinFilter MinFilter::MinFilter(size_t window_size, size_t send_every, size_t send_first_at) : send_every_(send_every), send_at_(send_every - send_first_at), window_size_(window_size) {} @@ -106,8 +94,6 @@ optional MinFilter::new_value(float value) { return {}; } -uint32_t MinFilter::expected_interval(uint32_t input) { return input * this->send_every_; } - // MaxFilter MaxFilter::MaxFilter(size_t window_size, size_t send_every, size_t send_first_at) : send_every_(send_every), send_at_(send_every - send_first_at), window_size_(window_size) {} @@ -137,8 +123,6 @@ optional MaxFilter::new_value(float value) { return {}; } -uint32_t MaxFilter::expected_interval(uint32_t input) { return input * this->send_every_; } - // SlidingWindowMovingAverageFilter SlidingWindowMovingAverageFilter::SlidingWindowMovingAverageFilter(size_t window_size, size_t send_every, size_t send_first_at) @@ -177,8 +161,6 @@ optional SlidingWindowMovingAverageFilter::new_value(float value) { return {}; } -uint32_t SlidingWindowMovingAverageFilter::expected_interval(uint32_t input) { return input * this->send_every_; } - // ExponentialMovingAverageFilter ExponentialMovingAverageFilter::ExponentialMovingAverageFilter(float alpha, size_t send_every) : send_every_(send_every), send_at_(send_every - 1), alpha_(alpha) {} @@ -203,7 +185,6 @@ optional ExponentialMovingAverageFilter::new_value(float value) { } void ExponentialMovingAverageFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; } void ExponentialMovingAverageFilter::set_alpha(float alpha) { this->alpha_ = alpha; } -uint32_t ExponentialMovingAverageFilter::expected_interval(uint32_t input) { return input * this->send_every_; } // LambdaFilter LambdaFilter::LambdaFilter(lambda_filter_t lambda_filter) : lambda_filter_(std::move(lambda_filter)) {} @@ -296,14 +277,6 @@ void OrFilter::initialize(Sensor *parent, Filter *next) { this->phi_.initialize(parent, nullptr); } -uint32_t OrFilter::expected_interval(uint32_t input) { - uint32_t min_interval = UINT32_MAX; - for (Filter *filter : this->filters_) { - min_interval = std::min(min_interval, filter->calculate_remaining_interval(input)); - } - - return min_interval; -} // DebounceFilter optional DebounceFilter::new_value(float value) { this->set_timeout("debounce", this->time_period_, [this, value]() { this->output(value); }); @@ -324,7 +297,6 @@ optional HeartbeatFilter::new_value(float value) { return {}; } -uint32_t HeartbeatFilter::expected_interval(uint32_t input) { return this->time_period_; } void HeartbeatFilter::setup() { this->set_interval("heartbeat", this->time_period_, [this]() { ESP_LOGVV(TAG, "HeartbeatFilter(%p)::interval(has_value=%s, last_input=%f)", this, YESNO(this->has_value_), diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 270a91ccff..29a6813ea9 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -33,11 +33,6 @@ class Filter { void input(float value); - /// Return the amount of time that this filter is expected to take based on the input time interval. - virtual uint32_t expected_interval(uint32_t input); - - uint32_t calculate_remaining_interval(uint32_t input); - void output(float value); protected: @@ -68,8 +63,6 @@ class MedianFilter : public Filter { void set_send_every(size_t send_every); void set_window_size(size_t window_size); - uint32_t expected_interval(uint32_t input) override; - protected: std::deque queue_; size_t send_every_; @@ -98,8 +91,6 @@ class MinFilter : public Filter { void set_send_every(size_t send_every); void set_window_size(size_t window_size); - uint32_t expected_interval(uint32_t input) override; - protected: std::deque queue_; size_t send_every_; @@ -128,8 +119,6 @@ class MaxFilter : public Filter { void set_send_every(size_t send_every); void set_window_size(size_t window_size); - uint32_t expected_interval(uint32_t input) override; - protected: std::deque queue_; size_t send_every_; @@ -159,8 +148,6 @@ class SlidingWindowMovingAverageFilter : public Filter { void set_send_every(size_t send_every); void set_window_size(size_t window_size); - uint32_t expected_interval(uint32_t input) override; - protected: float sum_{0.0}; std::deque queue_; @@ -183,8 +170,6 @@ class ExponentialMovingAverageFilter : public Filter { void set_send_every(size_t send_every); void set_alpha(float alpha); - uint32_t expected_interval(uint32_t input) override; - protected: bool first_value_{true}; float accumulator_{0.0f}; @@ -279,8 +264,6 @@ class HeartbeatFilter : public Filter, public Component { optional new_value(float value) override; - uint32_t expected_interval(uint32_t input) override; - float get_setup_priority() const override; protected: @@ -306,8 +289,6 @@ class OrFilter : public Filter { void initialize(Sensor *parent, Filter *next) override; - uint32_t expected_interval(uint32_t input) override; - optional new_value(float value) override; protected: diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 34f72a4508..128f36fc93 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -18,6 +18,51 @@ const LogString *state_class_to_string(StateClass state_class) { } } +Sensor::Sensor(const std::string &name) : Nameable(name), state(NAN), raw_state(NAN) {} +Sensor::Sensor() : Sensor("") {} + +std::string Sensor::get_unit_of_measurement() { + if (this->unit_of_measurement_.has_value()) + return *this->unit_of_measurement_; + return this->unit_of_measurement(); +} +void Sensor::set_unit_of_measurement(const std::string &unit_of_measurement) { + this->unit_of_measurement_ = unit_of_measurement; +} +std::string Sensor::unit_of_measurement() { return ""; } + +std::string Sensor::get_icon() { + if (this->icon_.has_value()) + return *this->icon_; + return this->icon(); +} +void Sensor::set_icon(const std::string &icon) { this->icon_ = icon; } +std::string Sensor::icon() { return ""; } + +int8_t Sensor::get_accuracy_decimals() { + if (this->accuracy_decimals_.has_value()) + return *this->accuracy_decimals_; + return this->accuracy_decimals(); +} +void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; } +int8_t Sensor::accuracy_decimals() { return 0; } + +std::string Sensor::get_device_class() { + if (this->device_class_.has_value()) + return *this->device_class_; + return this->device_class(); +} +void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } +std::string Sensor::device_class() { return ""; } + +void Sensor::set_state_class(StateClass state_class) { this->state_class_ = state_class; } +StateClass Sensor::get_state_class() { + if (this->state_class_.has_value()) + return *this->state_class_; + return this->state_class(); +} +StateClass Sensor::state_class() { return StateClass::STATE_CLASS_NONE; } + void Sensor::publish_state(float state) { this->raw_state = state; this->raw_callback_.call(state); @@ -30,54 +75,12 @@ void Sensor::publish_state(float state) { this->filter_list_->input(state); } } -std::string Sensor::unit_of_measurement() { return ""; } -std::string Sensor::icon() { return ""; } -uint32_t Sensor::update_interval() { return 0; } -int8_t Sensor::accuracy_decimals() { return 0; } -Sensor::Sensor(const std::string &name) : Nameable(name), state(NAN), raw_state(NAN) {} -Sensor::Sensor() : Sensor("") {} -void Sensor::set_unit_of_measurement(const std::string &unit_of_measurement) { - this->unit_of_measurement_ = unit_of_measurement; -} -void Sensor::set_icon(const std::string &icon) { this->icon_ = icon; } -void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; } void Sensor::add_on_state_callback(std::function &&callback) { this->callback_.add(std::move(callback)); } void Sensor::add_on_raw_state_callback(std::function &&callback) { this->raw_callback_.add(std::move(callback)); } -std::string Sensor::get_icon() { - if (this->icon_.has_value()) - return *this->icon_; - return this->icon(); -} -void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } -std::string Sensor::get_device_class() { - if (this->device_class_.has_value()) - return *this->device_class_; - return this->device_class(); -} -std::string Sensor::device_class() { return ""; } -void Sensor::set_state_class(StateClass state_class) { this->state_class = state_class; } -void Sensor::set_state_class(const std::string &state_class) { - if (str_equals_case_insensitive(state_class, "measurement")) { - this->state_class = STATE_CLASS_MEASUREMENT; - } else if (str_equals_case_insensitive(state_class, "total_increasing")) { - this->state_class = STATE_CLASS_TOTAL_INCREASING; - } else { - ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str()); - } -} -std::string Sensor::get_unit_of_measurement() { - if (this->unit_of_measurement_.has_value()) - return *this->unit_of_measurement_; - return this->unit_of_measurement(); -} -int8_t Sensor::get_accuracy_decimals() { - if (this->accuracy_decimals_.has_value()) - return *this->accuracy_decimals_; - return this->accuracy_decimals(); -} + void Sensor::add_filter(Filter *filter) { // inefficient, but only happens once on every sensor setup and nobody's going to have massive amounts of // filters @@ -119,24 +122,7 @@ void Sensor::internal_send_state_to_frontend(float state) { this->callback_.call(state); } bool Sensor::has_state() const { return this->has_state_; } -uint32_t Sensor::calculate_expected_filter_update_interval() { - uint32_t interval = this->update_interval(); - if (interval == 4294967295UL) - // update_interval: never - return 0; - - if (this->filter_list_ == nullptr) { - return interval; - } - - return this->filter_list_->calculate_remaining_interval(interval); -} uint32_t Sensor::hash_base() { return 2455723294UL; } -PollingSensorComponent::PollingSensorComponent(const std::string &name, uint32_t update_interval) - : PollingComponent(update_interval), Sensor(name) {} - -uint32_t PollingSensorComponent::update_interval() { return this->get_update_interval(); } - } // namespace sensor } // namespace esphome diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 6322c12027..3fc993cb2c 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -14,7 +14,7 @@ namespace sensor { if (!(obj)->get_device_class().empty()) { \ ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ } \ - ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, LOG_STR_ARG(state_class_to_string((obj)->state_class))); \ + ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, LOG_STR_ARG(state_class_to_string((obj)->get_state_class()))); \ ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \ ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \ if (!(obj)->get_icon().empty()) { \ @@ -48,26 +48,42 @@ class Sensor : public Nameable { explicit Sensor(); explicit Sensor(const std::string &name); - /** Manually set the unit of measurement of this sensor. By default the sensor's default defined by - * unit_of_measurement() is used. - * - * @param unit_of_measurement The unit of measurement, "" to disable. - */ + /// Get the unit of measurement, using the manual override if set. + std::string get_unit_of_measurement(); + /// Manually set the unit of measurement. void set_unit_of_measurement(const std::string &unit_of_measurement); - /** Manually set the icon of this sensor. By default the sensor's default defined by icon() is used. - * - * @param icon The icon, for example "mdi:flash". "" to disable. - */ + /// Get the icon. Uses the manual override if specified or the default value instead. + std::string get_icon(); + /// Manually set the icon, for example "mdi:flash". void set_icon(const std::string &icon); - /** Manually set the accuracy in decimals for this sensor. By default, the sensor's default defined by - * accuracy_decimals() is used. - * - * @param accuracy_decimals The accuracy decimal that should be used. - */ + /// Get the accuracy in decimals, using the manual override if set. + int8_t get_accuracy_decimals(); + /// Manually set the accuracy in decimals. void set_accuracy_decimals(int8_t accuracy_decimals); + /// Get the device class, using the manual override if set. + std::string get_device_class(); + /// Manually set the device class. + void set_device_class(const std::string &device_class); + + /// Get the state class, using the manual override if set. + StateClass get_state_class(); + /// Manually set the state class. + void set_state_class(StateClass state_class); + + /** + * Get whether force update mode is enabled. + * + * If the sensor is in force_update mode, the frontend is required to save all + * state changes to the database when they are published, even if the state is the + * same as before. + */ + bool get_force_update() const { return force_update_; } + /// Set force update mode. + void set_force_update(bool force_update) { force_update_ = force_update; } + /// Add a filter to the filter chain. Will be appended to the back. void add_filter(Filter *filter); @@ -94,15 +110,6 @@ class Sensor : public Nameable { /// Getter-syntax for .raw_state float get_raw_state() const; - /// Get the accuracy in decimals. Uses the manual override if specified or the default value instead. - int8_t get_accuracy_decimals(); - - /// Get the unit of measurement. Uses the manual override if specified or the default value instead. - std::string get_unit_of_measurement(); - - /// Get the Home Assistant Icon. Uses the manual override if specified or the default value instead. - std::string get_icon(); - /** Publish a new state to the front-end. * * First, the new state will be assigned to the raw_value. Then it's passed through all filters @@ -128,35 +135,15 @@ class Sensor : public Nameable { */ float state; - /// Manually set the Home Assistant device class (see sensor::device_class) - void set_device_class(const std::string &device_class); - - /// Get the device class for this sensor, using the manual override if specified. - std::string get_device_class(); - - /** This member variable stores the current raw state of the sensor. Unlike .state, - * this will be updated immediately when publish_state is called. + /** This member variable stores the current raw state of the sensor, without any filters applied. + * + * Unlike .state,this will be updated immediately when publish_state is called. */ float raw_state; /// Return whether this sensor has gotten a full state (that passed through all filters) yet. bool has_state() const; - // The state class of this sensor state - StateClass state_class{STATE_CLASS_NONE}; - - /// Manually set the Home Assistant state class (see sensor::state_class) - void set_state_class(StateClass state_class); - void set_state_class(const std::string &state_class); - - /** Override this to set the Home Assistant device class for this sensor. - * - * Return "" to disable this feature. - * - * @return The device class of this sensor, for example "temperature". - */ - virtual std::string device_class(); - /** A unique ID for this sensor, empty for no unique id. See unique ID requirements: * https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements * @@ -164,65 +151,38 @@ class Sensor : public Nameable { */ virtual std::string unique_id(); - /// Return with which interval the sensor is polled. Return 0 for non-polling mode. - virtual uint32_t update_interval(); - - /// Calculate the expected update interval for values that pass through all filters. - uint32_t calculate_expected_filter_update_interval(); - void internal_send_state_to_frontend(float state); - bool get_force_update() const { return force_update_; } - /** Set this sensor's force_update mode. - * - * If the sensor is in force_update mode, the frontend is required to save all - * state changes to the database when they are published, even if the state is the - * same as before. - */ - void set_force_update(bool force_update) { force_update_ = force_update; } - protected: - /** Override this to set the Home Assistant unit of measurement for this sensor. - * - * Return "" to disable this feature. - * - * @return The icon of this sensor, for example "°C". - */ + /// Override this to set the default unit of measurement. virtual std::string unit_of_measurement(); // NOLINT - /** Override this to set the Home Assistant icon for this sensor. - * - * Return "" to disable this feature. - * - * @return The icon of this sensor, for example "mdi:battery". - */ + /// Override this to set the default icon. virtual std::string icon(); // NOLINT - /// Return the accuracy in decimals for this sensor. + /// Override this to set the default accuracy in decimals. virtual int8_t accuracy_decimals(); // NOLINT - optional device_class_{}; ///< Stores the override of the device class + /// Override this to set the default device class. + virtual std::string device_class(); // NOLINT + + /// Override this to set the default state class. + virtual StateClass state_class(); // NOLINT uint32_t hash_base() override; CallbackManager raw_callback_; ///< Storage for raw state callbacks. CallbackManager callback_; ///< Storage for filtered state callbacks. - /// Override the unit of measurement - optional unit_of_measurement_; - /// Override the icon advertised to Home Assistant, otherwise sensor's icon will be used. - optional icon_; - /// Override the accuracy in decimals, otherwise the sensor's values will be used. - optional accuracy_decimals_; - Filter *filter_list_{nullptr}; ///< Store all active filters. + bool has_state_{false}; - bool force_update_{false}; -}; + Filter *filter_list_{nullptr}; ///< Store all active filters. -class PollingSensorComponent : public PollingComponent, public Sensor { - public: - explicit PollingSensorComponent(const std::string &name, uint32_t update_interval); - - uint32_t update_interval() override; + optional unit_of_measurement_; ///< Unit of measurement override + optional icon_; ///< Icon override + optional accuracy_decimals_; ///< Accuracy in decimals override + optional device_class_; ///< Device class override + optional state_class_{STATE_CLASS_NONE}; ///< State class override + bool force_update_{false}; ///< Force update mode }; } // namespace sensor From 4eb51ab4d6c48301ff7073aeae4c4ba5c6753faa Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 02:44:39 +0200 Subject: [PATCH 1322/1841] Disable automatic usage of SNTP servers from DHCP (#2273) --- esphome/components/wifi/wifi_component_esp32.cpp | 6 ++++++ esphome/components/wifi/wifi_component_esp8266.cpp | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/esphome/components/wifi/wifi_component_esp32.cpp b/esphome/components/wifi/wifi_component_esp32.cpp index 57c4efcdd5..b56030db56 100644 --- a/esphome/components/wifi/wifi_component_esp32.cpp +++ b/esphome/components/wifi/wifi_component_esp32.cpp @@ -11,6 +11,7 @@ #endif #include "lwip/err.h" #include "lwip/dns.h" +#include "lwip/apps/sntp.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -92,6 +93,11 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { tcpip_adapter_dhcp_status_t dhcp_status; tcpip_adapter_dhcpc_get_status(TCPIP_ADAPTER_IF_STA, &dhcp_status); if (!manual_ip.has_value()) { + // lwIP starts the SNTP client if it gets an SNTP server from DHCP. We don't need the time, and more importantly, + // the built-in SNTP client has a memory leak in certain situations. Disable this feature. + // https://github.com/esphome/issues/issues/2299 + sntp_servermode_dhcp(false); + // Use DHCP client if (dhcp_status != TCPIP_ADAPTER_DHCP_STARTED) { esp_err_t err = tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index ad1a64d1f4..de529ee3aa 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -16,6 +16,7 @@ extern "C" { #include "lwip/dns.h" #include "lwip/dhcp.h" #include "lwip/init.h" // LWIP_VERSION_ +#include "lwip/apps/sntp.h" #if LWIP_IPV6 #include "lwip/netif.h" // struct netif #endif @@ -112,6 +113,11 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { enum dhcp_status dhcp_status = wifi_station_dhcpc_status(); if (!manual_ip.has_value()) { + // lwIP starts the SNTP client if it gets an SNTP server from DHCP. We don't need the time, and more importantly, + // the built-in SNTP client has a memory leak in certain situations. Disable this feature. + // https://github.com/esphome/issues/issues/2299 + sntp_servermode_dhcp(false); + // Use DHCP client if (dhcp_status != DHCP_STARTED) { bool ret = wifi_station_dhcpc_start(); From e92a9d1d9e4b6dbab9f68b570f87cc17a404d27a Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 13 Sep 2021 18:52:53 +0200 Subject: [PATCH 1323/1841] Fix API socket issues (#2288) * Fix API socket issues * Fix compile error against beta * Format --- esphome/components/api/api_connection.cpp | 74 ++++++++++++------- esphome/components/api/api_connection.h | 16 +--- esphome/components/api/api_frame_helper.cpp | 70 +++++++++++++----- esphome/components/api/api_frame_helper.h | 3 +- esphome/components/api/api_server.cpp | 2 +- .../components/socket/lwip_raw_tcp_impl.cpp | 54 +++++++++----- 6 files changed, 142 insertions(+), 77 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 650f4f6f6e..1a365bc0b0 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -36,19 +36,14 @@ void APIConnection::start() { APIError err = helper_->init(); if (err != APIError::OK) { - ESP_LOGW(TAG, "Helper init failed: %d errno=%d", (int) err, errno); - remove_ = true; + on_fatal_error(); + ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); return; } client_info_ = helper_->getpeername(); helper_->set_log_info(client_info_); } -void APIConnection::force_disconnect_client() { - this->helper_->close(); - this->remove_ = true; -} - void APIConnection::loop() { if (this->remove_) return; @@ -57,9 +52,11 @@ void APIConnection::loop() { // when network is disconnected force disconnect immediately // don't wait for timeout this->on_fatal_error(); + ESP_LOGW(TAG, "%s: Network unavailable, disconnecting", client_info_.c_str()); return; } if (this->next_close_) { + // requested a disconnect this->helper_->close(); this->remove_ = true; return; @@ -68,7 +65,7 @@ void APIConnection::loop() { APIError err = helper_->loop(); if (err != APIError::OK) { on_fatal_error(); - ESP_LOGW(TAG, "%s: Socket operation failed: %d", client_info_.c_str(), (int) err); + ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); return; } ReadPacketBuffer buffer; @@ -77,7 +74,11 @@ void APIConnection::loop() { // pass } else if (err != APIError::OK) { on_fatal_error(); - ESP_LOGW(TAG, "%s: Reading failed: %d", client_info_.c_str(), (int) err); + if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { + ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str()); + } else { + ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); + } return; } else { this->last_traffic_ = millis(); @@ -95,8 +96,8 @@ void APIConnection::loop() { if (this->sent_ping_) { // Disconnect if not responded within 2.5*keepalive if (now - this->last_traffic_ > (keepalive * 5) / 2) { - this->force_disconnect_client(); - ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str()); + on_fatal_error(); + ESP_LOGW(TAG, "%s didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str()); } } else if (now - this->last_traffic_ > keepalive) { this->sent_ping_ = true; @@ -124,12 +125,40 @@ void APIConnection::loop() { } } #endif + + if (state_subs_at_ != -1) { + const auto &subs = this->parent_->get_state_subs(); + if (state_subs_at_ >= subs.size()) { + state_subs_at_ = -1; + } else { + auto &it = subs[state_subs_at_]; + SubscribeHomeAssistantStateResponse resp; + resp.entity_id = it.entity_id; + resp.attribute = it.attribute.value(); + if (this->send_subscribe_home_assistant_state_response(resp)) { + state_subs_at_++; + } + } + } } std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) { return App.get_name() + component_type + nameable->get_object_id(); } +DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) { + // remote initiated disconnect_client + // don't close yet, we still need to send the disconnect response + // close will happen on next loop + ESP_LOGD(TAG, "%s requested disconnected", client_info_.c_str()); + this->next_close_ = true; + DisconnectResponse resp; + return resp; +} +void APIConnection::on_disconnect_response(const DisconnectResponse &value) { + // pass +} + #ifdef USE_BINARY_SENSOR bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state) { if (!this->state_subscription_) @@ -700,7 +729,7 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) { // bool invalid_password = 1; resp.invalid_password = !correct; if (correct) { - ESP_LOGD(TAG, "Client '%s' connected successfully!", this->client_info_.c_str()); + ESP_LOGD(TAG, "%s: Connected successfully", this->client_info_.c_str()); this->connection_state_ = ConnectionState::AUTHENTICATED; #ifdef USE_HOMEASSISTANT_TIME @@ -746,15 +775,7 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) { } } void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) { - for (auto &it : this->parent_->get_state_subs()) { - SubscribeHomeAssistantStateResponse resp; - resp.entity_id = it.entity_id; - resp.attribute = it.attribute.value(); - if (!this->send_subscribe_home_assistant_state_response(resp)) { - this->on_fatal_error(); - return; - } - } + state_subs_at_ = 0; } bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) { if (this->remove_) @@ -767,7 +788,11 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) return false; if (err != APIError::OK) { on_fatal_error(); - ESP_LOGW(TAG, "%s: Packet write failed %d errno=%d", client_info_.c_str(), (int) err, errno); + if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) { + ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str()); + } else { + ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); + } return false; } this->last_traffic_ = millis(); @@ -775,14 +800,13 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) } void APIConnection::on_unauthenticated_access() { this->on_fatal_error(); - ESP_LOGD(TAG, "'%s' tried to access without authentication.", this->client_info_.c_str()); + ESP_LOGD(TAG, "%s: tried to access without authentication.", this->client_info_.c_str()); } void APIConnection::on_no_setup_connection() { this->on_fatal_error(); - ESP_LOGD(TAG, "'%s' tried to access without full connection.", this->client_info_.c_str()); + ESP_LOGD(TAG, "%s: tried to access without full connection.", this->client_info_.c_str()); } void APIConnection::on_fatal_error() { - ESP_LOGV(TAG, "Error: Disconnecting %s", this->client_info_.c_str()); this->helper_->close(); this->remove_ = true; } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index a1788bbede..a1f1769a19 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -16,7 +16,6 @@ class APIConnection : public APIServerConnection { virtual ~APIConnection() = default; void start(); - void force_disconnect_client(); void loop(); bool send_list_info_done() { @@ -88,10 +87,7 @@ class APIConnection : public APIServerConnection { } #endif - void on_disconnect_response(const DisconnectResponse &value) override { - this->helper_->close(); - this->remove_ = true; - } + void on_disconnect_response(const DisconnectResponse &value) override; void on_ping_response(const PingResponse &value) override { // we initiated ping this->sent_ping_ = false; @@ -102,14 +98,7 @@ class APIConnection : public APIServerConnection { #endif HelloResponse hello(const HelloRequest &msg) override; ConnectResponse connect(const ConnectRequest &msg) override; - DisconnectResponse disconnect(const DisconnectRequest &msg) override { - // remote initiated disconnect_client - // don't close yet, we still need to send the disconnect response - // close will happen on next loop - this->next_close_ = true; - DisconnectResponse resp; - return resp; - } + DisconnectResponse disconnect(const DisconnectRequest &msg) override; PingResponse ping(const PingRequest &msg) override { return {}; } DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override; void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); } @@ -177,6 +166,7 @@ class APIConnection : public APIServerConnection { APIServer *parent_; InitialStateIterator initial_state_iterator_; ListEntitiesIterator list_entities_iterator_; + int state_subs_at_ = -1; }; } // namespace api diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 520a5c2caf..c064c7278f 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -17,6 +17,54 @@ bool is_would_block(ssize_t ret) { return ret == 0; } +const char *api_error_to_str(APIError err) { + // not using switch to ensure compiler doesn't try to build a big table out of it + if (err == APIError::OK) { + return "OK"; + } else if (err == APIError::WOULD_BLOCK) { + return "WOULD_BLOCK"; + } else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) { + return "BAD_HANDSHAKE_PACKET_LEN"; + } else if (err == APIError::BAD_INDICATOR) { + return "BAD_INDICATOR"; + } else if (err == APIError::BAD_DATA_PACKET) { + return "BAD_DATA_PACKET"; + } else if (err == APIError::TCP_NODELAY_FAILED) { + return "TCP_NODELAY_FAILED"; + } else if (err == APIError::TCP_NONBLOCKING_FAILED) { + return "TCP_NONBLOCKING_FAILED"; + } else if (err == APIError::CLOSE_FAILED) { + return "CLOSE_FAILED"; + } else if (err == APIError::SHUTDOWN_FAILED) { + return "SHUTDOWN_FAILED"; + } else if (err == APIError::BAD_STATE) { + return "BAD_STATE"; + } else if (err == APIError::BAD_ARG) { + return "BAD_ARG"; + } else if (err == APIError::SOCKET_READ_FAILED) { + return "SOCKET_READ_FAILED"; + } else if (err == APIError::SOCKET_WRITE_FAILED) { + return "SOCKET_WRITE_FAILED"; + } else if (err == APIError::HANDSHAKESTATE_READ_FAILED) { + return "HANDSHAKESTATE_READ_FAILED"; + } else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) { + return "HANDSHAKESTATE_WRITE_FAILED"; + } else if (err == APIError::HANDSHAKESTATE_BAD_STATE) { + return "HANDSHAKESTATE_BAD_STATE"; + } else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) { + return "CIPHERSTATE_DECRYPT_FAILED"; + } else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) { + return "CIPHERSTATE_ENCRYPT_FAILED"; + } else if (err == APIError::OUT_OF_MEMORY) { + return "OUT_OF_MEMORY"; + } else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) { + return "HANDSHAKESTATE_SETUP_FAILED"; + } else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) { + return "HANDSHAKESTATE_SPLIT_FAILED"; + } + return "UNKNOWN"; +} + #define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__) #ifdef USE_API_NOISE @@ -808,14 +856,12 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() { // try send from tx_buf while (state_ != State::CLOSED && !tx_buf_.empty()) { ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size()); - if (sent == -1) { - if (errno == EWOULDBLOCK || errno == EAGAIN) - break; + if (is_would_block(sent)) { + break; + } else if (sent == -1) { state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); return APIError::SOCKET_WRITE_FAILED; - } else if (sent == 0) { - break; } // TODO: inefficient if multiple packets in txbuf // replace with deque of buffers @@ -869,20 +915,6 @@ APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) { // fully sent return APIError::OK; } -APIError APIPlaintextFrameHelper::write_frame_(const uint8_t *data, size_t len) { - APIError aerr; - - uint8_t header[3]; - header[0] = 0x01; // indicator - header[1] = (uint8_t)(len >> 8); - header[2] = (uint8_t) len; - - aerr = write_raw_(header, 3); - if (aerr != APIError::OK) - return aerr; - aerr = write_raw_(data, len); - return aerr; -} APIError APIPlaintextFrameHelper::close() { state_ = State::CLOSED; diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 7189bc4b4b..a8974cd25f 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -53,6 +53,8 @@ enum class APIError : int { HANDSHAKESTATE_SPLIT_FAILED = 1020, }; +const char *api_error_to_str(APIError err); + class APIFrameHelper { public: virtual APIError init() = 0; @@ -150,7 +152,6 @@ class APIPlaintextFrameHelper : public APIFrameHelper { APIError try_read_frame_(ParsedFrame *frame); APIError try_send_tx_buf_(); - APIError write_frame_(const uint8_t *data, size_t len); APIError write_raw_(const uint8_t *data, size_t len); std::unique_ptr socket_; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index c4c193b389..33843f384b 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -104,7 +104,7 @@ void APIServer::loop() { std::partition(this->clients_.begin(), this->clients_.end(), [](APIConnection *conn) { return !conn->remove_; }); // print disconnection messages for (auto it = new_end; it != this->clients_.end(); ++it) { - ESP_LOGD(TAG, "Disconnecting %s", (*it)->client_info_.c_str()); + ESP_LOGV(TAG, "Removing connection to %s", (*it)->client_info_.c_str()); } // only then delete the pointers, otherwise log routine // would access freed memory diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index aaeee7268a..39741ea7ec 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -109,14 +109,17 @@ class LWIPRawImpl : public Socket { LWIP_LOG("tcp_bind(%p ip=%u port=%u)", pcb_, ip.addr, port); err_t err = tcp_bind(pcb_, &ip, port); if (err == ERR_USE) { + LWIP_LOG(" -> err ERR_USE"); errno = EADDRINUSE; return -1; } if (err == ERR_VAL) { + LWIP_LOG(" -> err ERR_VAL"); errno = EINVAL; return -1; } if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); errno = EIO; return -1; } @@ -124,12 +127,13 @@ class LWIPRawImpl : public Socket { } int close() override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } LWIP_LOG("tcp_close(%p)", pcb_); err_t err = tcp_close(pcb_); if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); tcp_abort(pcb_); pcb_ = nullptr; errno = err == ERR_MEM ? ENOMEM : EIO; @@ -140,7 +144,7 @@ class LWIPRawImpl : public Socket { } int shutdown(int how) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } bool shut_rx = false, shut_tx = false; @@ -157,6 +161,7 @@ class LWIPRawImpl : public Socket { LWIP_LOG("tcp_shutdown(%p shut_rx=%d shut_tx=%d)", pcb_, shut_rx ? 1 : 0, shut_tx ? 1 : 0); err_t err = tcp_shutdown(pcb_, shut_rx, shut_tx); if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); errno = err == ERR_MEM ? ENOMEM : EIO; return -1; } @@ -165,7 +170,7 @@ class LWIPRawImpl : public Socket { int getpeername(struct sockaddr *name, socklen_t *addrlen) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (name == nullptr || addrlen == nullptr) { @@ -185,7 +190,7 @@ class LWIPRawImpl : public Socket { } std::string getpeername() override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return ""; } char buffer[24]; @@ -196,7 +201,7 @@ class LWIPRawImpl : public Socket { } int getsockname(struct sockaddr *name, socklen_t *addrlen) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (name == nullptr || addrlen == nullptr) { @@ -216,7 +221,7 @@ class LWIPRawImpl : public Socket { } std::string getsockname() override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return ""; } char buffer[24]; @@ -227,7 +232,7 @@ class LWIPRawImpl : public Socket { } int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (optlen == nullptr || optval == nullptr) { @@ -261,7 +266,7 @@ class LWIPRawImpl : public Socket { } int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (level == SOL_SOCKET && optname == SO_REUSEADDR) { @@ -314,7 +319,7 @@ class LWIPRawImpl : public Socket { } ssize_t read(void *buf, size_t len) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (rx_closed_ && rx_buf_ == nullptr) { @@ -368,7 +373,7 @@ class LWIPRawImpl : public Socket { } ssize_t write(const void *buf, size_t len) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (len == 0) @@ -386,24 +391,37 @@ class LWIPRawImpl : public Socket { LWIP_LOG("tcp_write(%p buf=%p %u)", pcb_, buf, to_send); err_t err = tcp_write(pcb_, buf, to_send, TCP_WRITE_FLAG_COPY); if (err == ERR_MEM) { + LWIP_LOG(" -> err ERR_MEM"); errno = EWOULDBLOCK; return -1; } if (err != ERR_OK) { - errno = EIO; + LWIP_LOG(" -> err %d", err); + errno = ECONNRESET; return -1; } - LWIP_LOG("tcp_output(%p)", pcb_); - err = tcp_output(pcb_); - if (err != ERR_OK) { - errno = EIO; - return -1; + if (tcp_nagle_disabled(pcb_)) { + LWIP_LOG("tcp_output(%p)", pcb_); + err = tcp_output(pcb_); + if (err == ERR_ABRT) { + LWIP_LOG(" -> err ERR_ABRT"); + // sometimes lwip returns ERR_ABRT for no apparent reason + // the connection works fine afterwards, and back with ESPAsyncTCP we + // indirectly also ignored this error + // FIXME: figure out where this is returned and what it means in this context + return to_send; + } + if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); + errno = ECONNRESET; + return -1; + } } return to_send; } int setblocking(bool blocking) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (blocking) { @@ -466,7 +484,7 @@ class LWIPRawImpl : public Socket { static void s_err_fn(void *arg, err_t err) { LWIPRawImpl *arg_this = reinterpret_cast(arg); - return arg_this->err_fn(err); + arg_this->err_fn(err); } static err_t s_recv_fn(void *arg, struct tcp_pcb *pcb, struct pbuf *pb, err_t err) { From 91f12a50cf371ce797310aab06b69e7c95d7b066 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 14 Sep 2021 07:13:00 +1200 Subject: [PATCH 1324/1841] Bump version to 2021.9.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 100d89594b..aa52a28ba8 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.0b2" +__version__ = "2021.9.0b3" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From b9767bdcbcb00aca39ea8f0cf63f3fbcbe142649 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 13 Sep 2021 21:16:13 +0200 Subject: [PATCH 1325/1841] Bump platformio to 5.2.0 (#2291) --- docker/build.py | 2 +- esphome/zeroconf.py | 7 ++++--- requirements.txt | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docker/build.py b/docker/build.py index 039d7dc15b..c926b3653b 100755 --- a/docker/build.py +++ b/docker/build.py @@ -24,7 +24,7 @@ TYPE_LINT = 'lint' TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT] -BASE_VERSION = "4.1.1" +BASE_VERSION = "4.2.0" parser = argparse.ArgumentParser() diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index e94b59d3ae..443ed6a33a 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -4,9 +4,6 @@ import time from typing import Dict, Optional from zeroconf import ( - _CLASS_IN, - _FLAGS_QR_QUERY, - _TYPE_A, DNSAddress, DNSOutgoing, DNSRecord, @@ -15,6 +12,10 @@ from zeroconf import ( Zeroconf, ) +_CLASS_IN = 1 +_FLAGS_QR_QUERY = 0x0000 # query +_TYPE_A = 1 + class HostResolver(RecordUpdateListener): def __init__(self, name: str): diff --git a/requirements.txt b/requirements.txt index daaf86e641..2d354d5f04 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ tzlocal==2.1 pytz==2021.1 pyserial==3.5 ifaddr==0.1.7 -platformio==5.1.1 +platformio==5.2.0 esptool==3.1 click==7.1.2 esphome-dashboard==20210908.0 From 855112dfc3b3005b55f28c2e79859b072e86c3c8 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 14 Sep 2021 09:53:37 +0200 Subject: [PATCH 1326/1841] API Noise logging (#2298) --- esphome/__main__.py | 2 +- esphome/api/__init__.py | 0 esphome/api/api_pb2.py | 3997 ------------------------------ esphome/api/client.py | 518 ---- esphome/components/api/client.py | 73 + requirements.txt | 3 +- 6 files changed, 75 insertions(+), 4518 deletions(-) delete mode 100644 esphome/api/__init__.py delete mode 100644 esphome/api/api_pb2.py delete mode 100644 esphome/api/client.py create mode 100644 esphome/components/api/client.py diff --git a/esphome/__main__.py b/esphome/__main__.py index 97b2988c2e..121fa7cc9e 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -256,7 +256,7 @@ def show_logs(config, args, port): run_miniterm(config, port) return 0 if get_port_type(port) == "NETWORK" and "api" in config: - from esphome.api.client import run_logs + from esphome.components.api.client import run_logs return run_logs(config, port) if get_port_type(port) == "MQTT" and "mqtt" in config: diff --git a/esphome/api/__init__.py b/esphome/api/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/esphome/api/api_pb2.py b/esphome/api/api_pb2.py deleted file mode 100644 index 6262b752c6..0000000000 --- a/esphome/api/api_pb2.py +++ /dev/null @@ -1,3997 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: api.proto - -import sys - -_b = sys.version_info[0] < 3 and (lambda x: x) or (lambda x: x.encode("latin1")) -from google.protobuf.internal import enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database - -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -DESCRIPTOR = _descriptor.FileDescriptor( - name="api.proto", - package="", - syntax="proto3", - serialized_options=None, - serialized_pb=_b( - '\n\tapi.proto"#\n\x0cHelloRequest\x12\x13\n\x0b\x63lient_info\x18\x01 \x01(\t"Z\n\rHelloResponse\x12\x19\n\x11\x61pi_version_major\x18\x01 \x01(\r\x12\x19\n\x11\x61pi_version_minor\x18\x02 \x01(\r\x12\x13\n\x0bserver_info\x18\x03 \x01(\t""\n\x0e\x43onnectRequest\x12\x10\n\x08password\x18\x01 \x01(\t"+\n\x0f\x43onnectResponse\x12\x18\n\x10invalid_password\x18\x01 \x01(\x08"\x13\n\x11\x44isconnectRequest"\x14\n\x12\x44isconnectResponse"\r\n\x0bPingRequest"\x0e\n\x0cPingResponse"\x13\n\x11\x44\x65viceInfoRequest"\xad\x01\n\x12\x44\x65viceInfoResponse\x12\x15\n\ruses_password\x18\x01 \x01(\x08\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0bmac_address\x18\x03 \x01(\t\x12\x1c\n\x14\x65sphome_core_version\x18\x04 \x01(\t\x12\x18\n\x10\x63ompilation_time\x18\x05 \x01(\t\x12\r\n\x05model\x18\x06 \x01(\t\x12\x16\n\x0ehas_deep_sleep\x18\x07 \x01(\x08"\x15\n\x13ListEntitiesRequest"\x9a\x01\n ListEntitiesBinarySensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x14\n\x0c\x64\x65vice_class\x18\x05 \x01(\t\x12\x1f\n\x17is_status_binary_sensor\x18\x06 \x01(\x08"s\n\x19ListEntitiesCoverResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x15\n\ris_optimistic\x18\x05 \x01(\x08"\x90\x01\n\x17ListEntitiesFanResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x1c\n\x14supports_oscillation\x18\x05 \x01(\x08\x12\x16\n\x0esupports_speed\x18\x06 \x01(\x08"\x8a\x02\n\x19ListEntitiesLightResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x1b\n\x13supports_brightness\x18\x05 \x01(\x08\x12\x14\n\x0csupports_rgb\x18\x06 \x01(\x08\x12\x1c\n\x14supports_white_value\x18\x07 \x01(\x08\x12"\n\x1asupports_color_temperature\x18\x08 \x01(\x08\x12\x12\n\nmin_mireds\x18\t \x01(\x02\x12\x12\n\nmax_mireds\x18\n \x01(\x02\x12\x0f\n\x07\x65\x66\x66\x65\x63ts\x18\x0b \x03(\t"\xa3\x01\n\x1aListEntitiesSensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\x12\x1b\n\x13unit_of_measurement\x18\x06 \x01(\t\x12\x19\n\x11\x61\x63\x63uracy_decimals\x18\x07 \x01(\x05"\x7f\n\x1aListEntitiesSwitchResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\x12\x12\n\noptimistic\x18\x06 \x01(\x08"o\n\x1eListEntitiesTextSensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t"\x1a\n\x18ListEntitiesDoneResponse"\x18\n\x16SubscribeStatesRequest"7\n\x19\x42inarySensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08"t\n\x12\x43overStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12-\n\x05state\x18\x02 \x01(\x0e\x32\x1e.CoverStateResponse.CoverState""\n\nCoverState\x12\x08\n\x04OPEN\x10\x00\x12\n\n\x06\x43LOSED\x10\x01"]\n\x10\x46\x61nStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\x12\x13\n\x0boscillating\x18\x03 \x01(\x08\x12\x18\n\x05speed\x18\x04 \x01(\x0e\x32\t.FanSpeed"\xa8\x01\n\x12LightStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\x12\x12\n\nbrightness\x18\x03 \x01(\x02\x12\x0b\n\x03red\x18\x04 \x01(\x02\x12\r\n\x05green\x18\x05 \x01(\x02\x12\x0c\n\x04\x62lue\x18\x06 \x01(\x02\x12\r\n\x05white\x18\x07 \x01(\x02\x12\x19\n\x11\x63olor_temperature\x18\x08 \x01(\x02\x12\x0e\n\x06\x65\x66\x66\x65\x63t\x18\t \x01(\t"1\n\x13SensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x02"1\n\x13SwitchStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08"5\n\x17TextSensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\t"\x98\x01\n\x13\x43overCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\x32\n\x07\x63ommand\x18\x03 \x01(\x0e\x32!.CoverCommandRequest.CoverCommand"-\n\x0c\x43overCommand\x12\x08\n\x04OPEN\x10\x00\x12\t\n\x05\x43LOSE\x10\x01\x12\x08\n\x04STOP\x10\x02"\x9d\x01\n\x11\x46\x61nCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\r\n\x05state\x18\x03 \x01(\x08\x12\x11\n\thas_speed\x18\x04 \x01(\x08\x12\x18\n\x05speed\x18\x05 \x01(\x0e\x32\t.FanSpeed\x12\x17\n\x0fhas_oscillating\x18\x06 \x01(\x08\x12\x13\n\x0boscillating\x18\x07 \x01(\x08"\x95\x03\n\x13LightCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\r\n\x05state\x18\x03 \x01(\x08\x12\x16\n\x0ehas_brightness\x18\x04 \x01(\x08\x12\x12\n\nbrightness\x18\x05 \x01(\x02\x12\x0f\n\x07has_rgb\x18\x06 \x01(\x08\x12\x0b\n\x03red\x18\x07 \x01(\x02\x12\r\n\x05green\x18\x08 \x01(\x02\x12\x0c\n\x04\x62lue\x18\t \x01(\x02\x12\x11\n\thas_white\x18\n \x01(\x08\x12\r\n\x05white\x18\x0b \x01(\x02\x12\x1d\n\x15has_color_temperature\x18\x0c \x01(\x08\x12\x19\n\x11\x63olor_temperature\x18\r \x01(\x02\x12\x1d\n\x15has_transition_length\x18\x0e \x01(\x08\x12\x19\n\x11transition_length\x18\x0f \x01(\r\x12\x18\n\x10has_flash_length\x18\x10 \x01(\x08\x12\x14\n\x0c\x66lash_length\x18\x11 \x01(\r\x12\x12\n\nhas_effect\x18\x12 \x01(\x08\x12\x0e\n\x06\x65\x66\x66\x65\x63t\x18\x13 \x01(\t"2\n\x14SwitchCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08"E\n\x14SubscribeLogsRequest\x12\x18\n\x05level\x18\x01 \x01(\x0e\x32\t.LogLevel\x12\x13\n\x0b\x64ump_config\x18\x02 \x01(\x08"d\n\x15SubscribeLogsResponse\x12\x18\n\x05level\x18\x01 \x01(\x0e\x32\t.LogLevel\x12\x0b\n\x03tag\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12\x13\n\x0bsend_failed\x18\x04 \x01(\x08"\x1e\n\x1cSubscribeServiceCallsRequest"\xdf\x02\n\x13ServiceCallResponse\x12\x0f\n\x07service\x18\x01 \x01(\t\x12,\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32\x1e.ServiceCallResponse.DataEntry\x12=\n\rdata_template\x18\x03 \x03(\x0b\x32&.ServiceCallResponse.DataTemplateEntry\x12\x36\n\tvariables\x18\x04 \x03(\x0b\x32#.ServiceCallResponse.VariablesEntry\x1a+\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x33\n\x11\x44\x61taTemplateEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x30\n\x0eVariablesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01"%\n#SubscribeHomeAssistantStatesRequest"8\n#SubscribeHomeAssistantStateResponse\x12\x11\n\tentity_id\x18\x01 \x01(\t">\n\x1aHomeAssistantStateResponse\x12\x11\n\tentity_id\x18\x01 \x01(\t\x12\r\n\x05state\x18\x02 \x01(\t"\x10\n\x0eGetTimeRequest"(\n\x0fGetTimeResponse\x12\x15\n\repoch_seconds\x18\x01 \x01(\x07*)\n\x08\x46\x61nSpeed\x12\x07\n\x03LOW\x10\x00\x12\n\n\x06MEDIUM\x10\x01\x12\x08\n\x04HIGH\x10\x02*]\n\x08LogLevel\x12\x08\n\x04NONE\x10\x00\x12\t\n\x05\x45RROR\x10\x01\x12\x08\n\x04WARN\x10\x02\x12\x08\n\x04INFO\x10\x03\x12\t\n\x05\x44\x45\x42UG\x10\x04\x12\x0b\n\x07VERBOSE\x10\x05\x12\x10\n\x0cVERY_VERBOSE\x10\x06\x62\x06proto3' - ), -) - -_FANSPEED = _descriptor.EnumDescriptor( - name="FanSpeed", - full_name="FanSpeed", - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name="LOW", index=0, number=0, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="MEDIUM", index=1, number=1, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="HIGH", index=2, number=2, serialized_options=None, type=None - ), - ], - containing_type=None, - serialized_options=None, - serialized_start=3822, - serialized_end=3863, -) -_sym_db.RegisterEnumDescriptor(_FANSPEED) - -FanSpeed = enum_type_wrapper.EnumTypeWrapper(_FANSPEED) -_LOGLEVEL = _descriptor.EnumDescriptor( - name="LogLevel", - full_name="LogLevel", - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name="NONE", index=0, number=0, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="ERROR", index=1, number=1, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="WARN", index=2, number=2, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="INFO", index=3, number=3, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="DEBUG", index=4, number=4, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="VERBOSE", index=5, number=5, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="VERY_VERBOSE", index=6, number=6, serialized_options=None, type=None - ), - ], - containing_type=None, - serialized_options=None, - serialized_start=3865, - serialized_end=3958, -) -_sym_db.RegisterEnumDescriptor(_LOGLEVEL) - -LogLevel = enum_type_wrapper.EnumTypeWrapper(_LOGLEVEL) -LOW = 0 -MEDIUM = 1 -HIGH = 2 -NONE = 0 -ERROR = 1 -WARN = 2 -INFO = 3 -DEBUG = 4 -VERBOSE = 5 -VERY_VERBOSE = 6 - - -_COVERSTATERESPONSE_COVERSTATE = _descriptor.EnumDescriptor( - name="CoverState", - full_name="CoverStateResponse.CoverState", - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name="OPEN", index=0, number=0, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="CLOSED", index=1, number=1, serialized_options=None, type=None - ), - ], - containing_type=None, - serialized_options=None, - serialized_start=1808, - serialized_end=1842, -) -_sym_db.RegisterEnumDescriptor(_COVERSTATERESPONSE_COVERSTATE) - -_COVERCOMMANDREQUEST_COVERCOMMAND = _descriptor.EnumDescriptor( - name="CoverCommand", - full_name="CoverCommandRequest.CoverCommand", - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name="OPEN", index=0, number=0, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="CLOSE", index=1, number=1, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="STOP", index=2, number=2, serialized_options=None, type=None - ), - ], - containing_type=None, - serialized_options=None, - serialized_start=2375, - serialized_end=2420, -) -_sym_db.RegisterEnumDescriptor(_COVERCOMMANDREQUEST_COVERCOMMAND) - - -_HELLOREQUEST = _descriptor.Descriptor( - name="HelloRequest", - full_name="HelloRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="client_info", - full_name="HelloRequest.client_info", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=13, - serialized_end=48, -) - - -_HELLORESPONSE = _descriptor.Descriptor( - name="HelloResponse", - full_name="HelloResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="api_version_major", - full_name="HelloResponse.api_version_major", - index=0, - number=1, - type=13, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="api_version_minor", - full_name="HelloResponse.api_version_minor", - index=1, - number=2, - type=13, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="server_info", - full_name="HelloResponse.server_info", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=50, - serialized_end=140, -) - - -_CONNECTREQUEST = _descriptor.Descriptor( - name="ConnectRequest", - full_name="ConnectRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="password", - full_name="ConnectRequest.password", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=142, - serialized_end=176, -) - - -_CONNECTRESPONSE = _descriptor.Descriptor( - name="ConnectResponse", - full_name="ConnectResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="invalid_password", - full_name="ConnectResponse.invalid_password", - index=0, - number=1, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=178, - serialized_end=221, -) - - -_DISCONNECTREQUEST = _descriptor.Descriptor( - name="DisconnectRequest", - full_name="DisconnectRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=223, - serialized_end=242, -) - - -_DISCONNECTRESPONSE = _descriptor.Descriptor( - name="DisconnectResponse", - full_name="DisconnectResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=244, - serialized_end=264, -) - - -_PINGREQUEST = _descriptor.Descriptor( - name="PingRequest", - full_name="PingRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=266, - serialized_end=279, -) - - -_PINGRESPONSE = _descriptor.Descriptor( - name="PingResponse", - full_name="PingResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=281, - serialized_end=295, -) - - -_DEVICEINFOREQUEST = _descriptor.Descriptor( - name="DeviceInfoRequest", - full_name="DeviceInfoRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=297, - serialized_end=316, -) - - -_DEVICEINFORESPONSE = _descriptor.Descriptor( - name="DeviceInfoResponse", - full_name="DeviceInfoResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="uses_password", - full_name="DeviceInfoResponse.uses_password", - index=0, - number=1, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="DeviceInfoResponse.name", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="mac_address", - full_name="DeviceInfoResponse.mac_address", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="esphome_core_version", - full_name="DeviceInfoResponse.esphome_core_version", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="compilation_time", - full_name="DeviceInfoResponse.compilation_time", - index=4, - number=5, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="model", - full_name="DeviceInfoResponse.model", - index=5, - number=6, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_deep_sleep", - full_name="DeviceInfoResponse.has_deep_sleep", - index=6, - number=7, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=319, - serialized_end=492, -) - - -_LISTENTITIESREQUEST = _descriptor.Descriptor( - name="ListEntitiesRequest", - full_name="ListEntitiesRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=494, - serialized_end=515, -) - - -_LISTENTITIESBINARYSENSORRESPONSE = _descriptor.Descriptor( - name="ListEntitiesBinarySensorResponse", - full_name="ListEntitiesBinarySensorResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesBinarySensorResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesBinarySensorResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesBinarySensorResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesBinarySensorResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="device_class", - full_name="ListEntitiesBinarySensorResponse.device_class", - index=4, - number=5, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="is_status_binary_sensor", - full_name="ListEntitiesBinarySensorResponse.is_status_binary_sensor", - index=5, - number=6, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=518, - serialized_end=672, -) - - -_LISTENTITIESCOVERRESPONSE = _descriptor.Descriptor( - name="ListEntitiesCoverResponse", - full_name="ListEntitiesCoverResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesCoverResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesCoverResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesCoverResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesCoverResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="is_optimistic", - full_name="ListEntitiesCoverResponse.is_optimistic", - index=4, - number=5, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=674, - serialized_end=789, -) - - -_LISTENTITIESFANRESPONSE = _descriptor.Descriptor( - name="ListEntitiesFanResponse", - full_name="ListEntitiesFanResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesFanResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesFanResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesFanResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesFanResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="supports_oscillation", - full_name="ListEntitiesFanResponse.supports_oscillation", - index=4, - number=5, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="supports_speed", - full_name="ListEntitiesFanResponse.supports_speed", - index=5, - number=6, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=792, - serialized_end=936, -) - - -_LISTENTITIESLIGHTRESPONSE = _descriptor.Descriptor( - name="ListEntitiesLightResponse", - full_name="ListEntitiesLightResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesLightResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesLightResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesLightResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesLightResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="supports_brightness", - full_name="ListEntitiesLightResponse.supports_brightness", - index=4, - number=5, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="supports_rgb", - full_name="ListEntitiesLightResponse.supports_rgb", - index=5, - number=6, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="supports_white_value", - full_name="ListEntitiesLightResponse.supports_white_value", - index=6, - number=7, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="supports_color_temperature", - full_name="ListEntitiesLightResponse.supports_color_temperature", - index=7, - number=8, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="min_mireds", - full_name="ListEntitiesLightResponse.min_mireds", - index=8, - number=9, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="max_mireds", - full_name="ListEntitiesLightResponse.max_mireds", - index=9, - number=10, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="effects", - full_name="ListEntitiesLightResponse.effects", - index=10, - number=11, - type=9, - cpp_type=9, - label=3, - has_default_value=False, - default_value=[], - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=939, - serialized_end=1205, -) - - -_LISTENTITIESSENSORRESPONSE = _descriptor.Descriptor( - name="ListEntitiesSensorResponse", - full_name="ListEntitiesSensorResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesSensorResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesSensorResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesSensorResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesSensorResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="icon", - full_name="ListEntitiesSensorResponse.icon", - index=4, - number=5, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unit_of_measurement", - full_name="ListEntitiesSensorResponse.unit_of_measurement", - index=5, - number=6, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="accuracy_decimals", - full_name="ListEntitiesSensorResponse.accuracy_decimals", - index=6, - number=7, - type=5, - cpp_type=1, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1208, - serialized_end=1371, -) - - -_LISTENTITIESSWITCHRESPONSE = _descriptor.Descriptor( - name="ListEntitiesSwitchResponse", - full_name="ListEntitiesSwitchResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesSwitchResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesSwitchResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesSwitchResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesSwitchResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="icon", - full_name="ListEntitiesSwitchResponse.icon", - index=4, - number=5, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="optimistic", - full_name="ListEntitiesSwitchResponse.optimistic", - index=5, - number=6, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1373, - serialized_end=1500, -) - - -_LISTENTITIESTEXTSENSORRESPONSE = _descriptor.Descriptor( - name="ListEntitiesTextSensorResponse", - full_name="ListEntitiesTextSensorResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesTextSensorResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesTextSensorResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesTextSensorResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesTextSensorResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="icon", - full_name="ListEntitiesTextSensorResponse.icon", - index=4, - number=5, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1502, - serialized_end=1613, -) - - -_LISTENTITIESDONERESPONSE = _descriptor.Descriptor( - name="ListEntitiesDoneResponse", - full_name="ListEntitiesDoneResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1615, - serialized_end=1641, -) - - -_SUBSCRIBESTATESREQUEST = _descriptor.Descriptor( - name="SubscribeStatesRequest", - full_name="SubscribeStatesRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1643, - serialized_end=1667, -) - - -_BINARYSENSORSTATERESPONSE = _descriptor.Descriptor( - name="BinarySensorStateResponse", - full_name="BinarySensorStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="BinarySensorStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="BinarySensorStateResponse.state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1669, - serialized_end=1724, -) - - -_COVERSTATERESPONSE = _descriptor.Descriptor( - name="CoverStateResponse", - full_name="CoverStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="CoverStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="CoverStateResponse.state", - index=1, - number=2, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[ - _COVERSTATERESPONSE_COVERSTATE, - ], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1726, - serialized_end=1842, -) - - -_FANSTATERESPONSE = _descriptor.Descriptor( - name="FanStateResponse", - full_name="FanStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="FanStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="FanStateResponse.state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="oscillating", - full_name="FanStateResponse.oscillating", - index=2, - number=3, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="speed", - full_name="FanStateResponse.speed", - index=3, - number=4, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1844, - serialized_end=1937, -) - - -_LIGHTSTATERESPONSE = _descriptor.Descriptor( - name="LightStateResponse", - full_name="LightStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="LightStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="LightStateResponse.state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="brightness", - full_name="LightStateResponse.brightness", - index=2, - number=3, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="red", - full_name="LightStateResponse.red", - index=3, - number=4, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="green", - full_name="LightStateResponse.green", - index=4, - number=5, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="blue", - full_name="LightStateResponse.blue", - index=5, - number=6, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="white", - full_name="LightStateResponse.white", - index=6, - number=7, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="color_temperature", - full_name="LightStateResponse.color_temperature", - index=7, - number=8, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="effect", - full_name="LightStateResponse.effect", - index=8, - number=9, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1940, - serialized_end=2108, -) - - -_SENSORSTATERESPONSE = _descriptor.Descriptor( - name="SensorStateResponse", - full_name="SensorStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="SensorStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="SensorStateResponse.state", - index=1, - number=2, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2110, - serialized_end=2159, -) - - -_SWITCHSTATERESPONSE = _descriptor.Descriptor( - name="SwitchStateResponse", - full_name="SwitchStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="SwitchStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="SwitchStateResponse.state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2161, - serialized_end=2210, -) - - -_TEXTSENSORSTATERESPONSE = _descriptor.Descriptor( - name="TextSensorStateResponse", - full_name="TextSensorStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="TextSensorStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="TextSensorStateResponse.state", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2212, - serialized_end=2265, -) - - -_COVERCOMMANDREQUEST = _descriptor.Descriptor( - name="CoverCommandRequest", - full_name="CoverCommandRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="CoverCommandRequest.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_state", - full_name="CoverCommandRequest.has_state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="command", - full_name="CoverCommandRequest.command", - index=2, - number=3, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[ - _COVERCOMMANDREQUEST_COVERCOMMAND, - ], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2268, - serialized_end=2420, -) - - -_FANCOMMANDREQUEST = _descriptor.Descriptor( - name="FanCommandRequest", - full_name="FanCommandRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="FanCommandRequest.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_state", - full_name="FanCommandRequest.has_state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="FanCommandRequest.state", - index=2, - number=3, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_speed", - full_name="FanCommandRequest.has_speed", - index=3, - number=4, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="speed", - full_name="FanCommandRequest.speed", - index=4, - number=5, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_oscillating", - full_name="FanCommandRequest.has_oscillating", - index=5, - number=6, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="oscillating", - full_name="FanCommandRequest.oscillating", - index=6, - number=7, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2423, - serialized_end=2580, -) - - -_LIGHTCOMMANDREQUEST = _descriptor.Descriptor( - name="LightCommandRequest", - full_name="LightCommandRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="LightCommandRequest.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_state", - full_name="LightCommandRequest.has_state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="LightCommandRequest.state", - index=2, - number=3, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_brightness", - full_name="LightCommandRequest.has_brightness", - index=3, - number=4, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="brightness", - full_name="LightCommandRequest.brightness", - index=4, - number=5, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_rgb", - full_name="LightCommandRequest.has_rgb", - index=5, - number=6, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="red", - full_name="LightCommandRequest.red", - index=6, - number=7, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="green", - full_name="LightCommandRequest.green", - index=7, - number=8, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="blue", - full_name="LightCommandRequest.blue", - index=8, - number=9, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_white", - full_name="LightCommandRequest.has_white", - index=9, - number=10, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="white", - full_name="LightCommandRequest.white", - index=10, - number=11, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_color_temperature", - full_name="LightCommandRequest.has_color_temperature", - index=11, - number=12, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="color_temperature", - full_name="LightCommandRequest.color_temperature", - index=12, - number=13, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_transition_length", - full_name="LightCommandRequest.has_transition_length", - index=13, - number=14, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="transition_length", - full_name="LightCommandRequest.transition_length", - index=14, - number=15, - type=13, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_flash_length", - full_name="LightCommandRequest.has_flash_length", - index=15, - number=16, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="flash_length", - full_name="LightCommandRequest.flash_length", - index=16, - number=17, - type=13, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_effect", - full_name="LightCommandRequest.has_effect", - index=17, - number=18, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="effect", - full_name="LightCommandRequest.effect", - index=18, - number=19, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2583, - serialized_end=2988, -) - - -_SWITCHCOMMANDREQUEST = _descriptor.Descriptor( - name="SwitchCommandRequest", - full_name="SwitchCommandRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="SwitchCommandRequest.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="SwitchCommandRequest.state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2990, - serialized_end=3040, -) - - -_SUBSCRIBELOGSREQUEST = _descriptor.Descriptor( - name="SubscribeLogsRequest", - full_name="SubscribeLogsRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="level", - full_name="SubscribeLogsRequest.level", - index=0, - number=1, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="dump_config", - full_name="SubscribeLogsRequest.dump_config", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3042, - serialized_end=3111, -) - - -_SUBSCRIBELOGSRESPONSE = _descriptor.Descriptor( - name="SubscribeLogsResponse", - full_name="SubscribeLogsResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="level", - full_name="SubscribeLogsResponse.level", - index=0, - number=1, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="tag", - full_name="SubscribeLogsResponse.tag", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="message", - full_name="SubscribeLogsResponse.message", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="send_failed", - full_name="SubscribeLogsResponse.send_failed", - index=3, - number=4, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3113, - serialized_end=3213, -) - - -_SUBSCRIBESERVICECALLSREQUEST = _descriptor.Descriptor( - name="SubscribeServiceCallsRequest", - full_name="SubscribeServiceCallsRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3215, - serialized_end=3245, -) - - -_SERVICECALLRESPONSE_DATAENTRY = _descriptor.Descriptor( - name="DataEntry", - full_name="ServiceCallResponse.DataEntry", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="ServiceCallResponse.DataEntry.key", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="value", - full_name="ServiceCallResponse.DataEntry.value", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=_b("8\001"), - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3453, - serialized_end=3496, -) - -_SERVICECALLRESPONSE_DATATEMPLATEENTRY = _descriptor.Descriptor( - name="DataTemplateEntry", - full_name="ServiceCallResponse.DataTemplateEntry", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="ServiceCallResponse.DataTemplateEntry.key", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="value", - full_name="ServiceCallResponse.DataTemplateEntry.value", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=_b("8\001"), - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3498, - serialized_end=3549, -) - -_SERVICECALLRESPONSE_VARIABLESENTRY = _descriptor.Descriptor( - name="VariablesEntry", - full_name="ServiceCallResponse.VariablesEntry", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="ServiceCallResponse.VariablesEntry.key", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="value", - full_name="ServiceCallResponse.VariablesEntry.value", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=_b("8\001"), - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3551, - serialized_end=3599, -) - -_SERVICECALLRESPONSE = _descriptor.Descriptor( - name="ServiceCallResponse", - full_name="ServiceCallResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="service", - full_name="ServiceCallResponse.service", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="data", - full_name="ServiceCallResponse.data", - index=1, - number=2, - type=11, - cpp_type=10, - label=3, - has_default_value=False, - default_value=[], - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="data_template", - full_name="ServiceCallResponse.data_template", - index=2, - number=3, - type=11, - cpp_type=10, - label=3, - has_default_value=False, - default_value=[], - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="variables", - full_name="ServiceCallResponse.variables", - index=3, - number=4, - type=11, - cpp_type=10, - label=3, - has_default_value=False, - default_value=[], - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[ - _SERVICECALLRESPONSE_DATAENTRY, - _SERVICECALLRESPONSE_DATATEMPLATEENTRY, - _SERVICECALLRESPONSE_VARIABLESENTRY, - ], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3248, - serialized_end=3599, -) - - -_SUBSCRIBEHOMEASSISTANTSTATESREQUEST = _descriptor.Descriptor( - name="SubscribeHomeAssistantStatesRequest", - full_name="SubscribeHomeAssistantStatesRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3601, - serialized_end=3638, -) - - -_SUBSCRIBEHOMEASSISTANTSTATERESPONSE = _descriptor.Descriptor( - name="SubscribeHomeAssistantStateResponse", - full_name="SubscribeHomeAssistantStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="entity_id", - full_name="SubscribeHomeAssistantStateResponse.entity_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3640, - serialized_end=3696, -) - - -_HOMEASSISTANTSTATERESPONSE = _descriptor.Descriptor( - name="HomeAssistantStateResponse", - full_name="HomeAssistantStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="entity_id", - full_name="HomeAssistantStateResponse.entity_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="HomeAssistantStateResponse.state", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3698, - serialized_end=3760, -) - - -_GETTIMEREQUEST = _descriptor.Descriptor( - name="GetTimeRequest", - full_name="GetTimeRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3762, - serialized_end=3778, -) - - -_GETTIMERESPONSE = _descriptor.Descriptor( - name="GetTimeResponse", - full_name="GetTimeResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="epoch_seconds", - full_name="GetTimeResponse.epoch_seconds", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3780, - serialized_end=3820, -) - -_COVERSTATERESPONSE.fields_by_name["state"].enum_type = _COVERSTATERESPONSE_COVERSTATE -_COVERSTATERESPONSE_COVERSTATE.containing_type = _COVERSTATERESPONSE -_FANSTATERESPONSE.fields_by_name["speed"].enum_type = _FANSPEED -_COVERCOMMANDREQUEST.fields_by_name[ - "command" -].enum_type = _COVERCOMMANDREQUEST_COVERCOMMAND -_COVERCOMMANDREQUEST_COVERCOMMAND.containing_type = _COVERCOMMANDREQUEST -_FANCOMMANDREQUEST.fields_by_name["speed"].enum_type = _FANSPEED -_SUBSCRIBELOGSREQUEST.fields_by_name["level"].enum_type = _LOGLEVEL -_SUBSCRIBELOGSRESPONSE.fields_by_name["level"].enum_type = _LOGLEVEL -_SERVICECALLRESPONSE_DATAENTRY.containing_type = _SERVICECALLRESPONSE -_SERVICECALLRESPONSE_DATATEMPLATEENTRY.containing_type = _SERVICECALLRESPONSE -_SERVICECALLRESPONSE_VARIABLESENTRY.containing_type = _SERVICECALLRESPONSE -_SERVICECALLRESPONSE.fields_by_name[ - "data" -].message_type = _SERVICECALLRESPONSE_DATAENTRY -_SERVICECALLRESPONSE.fields_by_name[ - "data_template" -].message_type = _SERVICECALLRESPONSE_DATATEMPLATEENTRY -_SERVICECALLRESPONSE.fields_by_name[ - "variables" -].message_type = _SERVICECALLRESPONSE_VARIABLESENTRY -DESCRIPTOR.message_types_by_name["HelloRequest"] = _HELLOREQUEST -DESCRIPTOR.message_types_by_name["HelloResponse"] = _HELLORESPONSE -DESCRIPTOR.message_types_by_name["ConnectRequest"] = _CONNECTREQUEST -DESCRIPTOR.message_types_by_name["ConnectResponse"] = _CONNECTRESPONSE -DESCRIPTOR.message_types_by_name["DisconnectRequest"] = _DISCONNECTREQUEST -DESCRIPTOR.message_types_by_name["DisconnectResponse"] = _DISCONNECTRESPONSE -DESCRIPTOR.message_types_by_name["PingRequest"] = _PINGREQUEST -DESCRIPTOR.message_types_by_name["PingResponse"] = _PINGRESPONSE -DESCRIPTOR.message_types_by_name["DeviceInfoRequest"] = _DEVICEINFOREQUEST -DESCRIPTOR.message_types_by_name["DeviceInfoResponse"] = _DEVICEINFORESPONSE -DESCRIPTOR.message_types_by_name["ListEntitiesRequest"] = _LISTENTITIESREQUEST -DESCRIPTOR.message_types_by_name[ - "ListEntitiesBinarySensorResponse" -] = _LISTENTITIESBINARYSENSORRESPONSE -DESCRIPTOR.message_types_by_name[ - "ListEntitiesCoverResponse" -] = _LISTENTITIESCOVERRESPONSE -DESCRIPTOR.message_types_by_name["ListEntitiesFanResponse"] = _LISTENTITIESFANRESPONSE -DESCRIPTOR.message_types_by_name[ - "ListEntitiesLightResponse" -] = _LISTENTITIESLIGHTRESPONSE -DESCRIPTOR.message_types_by_name[ - "ListEntitiesSensorResponse" -] = _LISTENTITIESSENSORRESPONSE -DESCRIPTOR.message_types_by_name[ - "ListEntitiesSwitchResponse" -] = _LISTENTITIESSWITCHRESPONSE -DESCRIPTOR.message_types_by_name[ - "ListEntitiesTextSensorResponse" -] = _LISTENTITIESTEXTSENSORRESPONSE -DESCRIPTOR.message_types_by_name["ListEntitiesDoneResponse"] = _LISTENTITIESDONERESPONSE -DESCRIPTOR.message_types_by_name["SubscribeStatesRequest"] = _SUBSCRIBESTATESREQUEST -DESCRIPTOR.message_types_by_name[ - "BinarySensorStateResponse" -] = _BINARYSENSORSTATERESPONSE -DESCRIPTOR.message_types_by_name["CoverStateResponse"] = _COVERSTATERESPONSE -DESCRIPTOR.message_types_by_name["FanStateResponse"] = _FANSTATERESPONSE -DESCRIPTOR.message_types_by_name["LightStateResponse"] = _LIGHTSTATERESPONSE -DESCRIPTOR.message_types_by_name["SensorStateResponse"] = _SENSORSTATERESPONSE -DESCRIPTOR.message_types_by_name["SwitchStateResponse"] = _SWITCHSTATERESPONSE -DESCRIPTOR.message_types_by_name["TextSensorStateResponse"] = _TEXTSENSORSTATERESPONSE -DESCRIPTOR.message_types_by_name["CoverCommandRequest"] = _COVERCOMMANDREQUEST -DESCRIPTOR.message_types_by_name["FanCommandRequest"] = _FANCOMMANDREQUEST -DESCRIPTOR.message_types_by_name["LightCommandRequest"] = _LIGHTCOMMANDREQUEST -DESCRIPTOR.message_types_by_name["SwitchCommandRequest"] = _SWITCHCOMMANDREQUEST -DESCRIPTOR.message_types_by_name["SubscribeLogsRequest"] = _SUBSCRIBELOGSREQUEST -DESCRIPTOR.message_types_by_name["SubscribeLogsResponse"] = _SUBSCRIBELOGSRESPONSE -DESCRIPTOR.message_types_by_name[ - "SubscribeServiceCallsRequest" -] = _SUBSCRIBESERVICECALLSREQUEST -DESCRIPTOR.message_types_by_name["ServiceCallResponse"] = _SERVICECALLRESPONSE -DESCRIPTOR.message_types_by_name[ - "SubscribeHomeAssistantStatesRequest" -] = _SUBSCRIBEHOMEASSISTANTSTATESREQUEST -DESCRIPTOR.message_types_by_name[ - "SubscribeHomeAssistantStateResponse" -] = _SUBSCRIBEHOMEASSISTANTSTATERESPONSE -DESCRIPTOR.message_types_by_name[ - "HomeAssistantStateResponse" -] = _HOMEASSISTANTSTATERESPONSE -DESCRIPTOR.message_types_by_name["GetTimeRequest"] = _GETTIMEREQUEST -DESCRIPTOR.message_types_by_name["GetTimeResponse"] = _GETTIMERESPONSE -DESCRIPTOR.enum_types_by_name["FanSpeed"] = _FANSPEED -DESCRIPTOR.enum_types_by_name["LogLevel"] = _LOGLEVEL -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -HelloRequest = _reflection.GeneratedProtocolMessageType( - "HelloRequest", - (_message.Message,), - dict( - DESCRIPTOR=_HELLOREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:HelloRequest) - ), -) -_sym_db.RegisterMessage(HelloRequest) - -HelloResponse = _reflection.GeneratedProtocolMessageType( - "HelloResponse", - (_message.Message,), - dict( - DESCRIPTOR=_HELLORESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:HelloResponse) - ), -) -_sym_db.RegisterMessage(HelloResponse) - -ConnectRequest = _reflection.GeneratedProtocolMessageType( - "ConnectRequest", - (_message.Message,), - dict( - DESCRIPTOR=_CONNECTREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ConnectRequest) - ), -) -_sym_db.RegisterMessage(ConnectRequest) - -ConnectResponse = _reflection.GeneratedProtocolMessageType( - "ConnectResponse", - (_message.Message,), - dict( - DESCRIPTOR=_CONNECTRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ConnectResponse) - ), -) -_sym_db.RegisterMessage(ConnectResponse) - -DisconnectRequest = _reflection.GeneratedProtocolMessageType( - "DisconnectRequest", - (_message.Message,), - dict( - DESCRIPTOR=_DISCONNECTREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:DisconnectRequest) - ), -) -_sym_db.RegisterMessage(DisconnectRequest) - -DisconnectResponse = _reflection.GeneratedProtocolMessageType( - "DisconnectResponse", - (_message.Message,), - dict( - DESCRIPTOR=_DISCONNECTRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:DisconnectResponse) - ), -) -_sym_db.RegisterMessage(DisconnectResponse) - -PingRequest = _reflection.GeneratedProtocolMessageType( - "PingRequest", - (_message.Message,), - dict( - DESCRIPTOR=_PINGREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:PingRequest) - ), -) -_sym_db.RegisterMessage(PingRequest) - -PingResponse = _reflection.GeneratedProtocolMessageType( - "PingResponse", - (_message.Message,), - dict( - DESCRIPTOR=_PINGRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:PingResponse) - ), -) -_sym_db.RegisterMessage(PingResponse) - -DeviceInfoRequest = _reflection.GeneratedProtocolMessageType( - "DeviceInfoRequest", - (_message.Message,), - dict( - DESCRIPTOR=_DEVICEINFOREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:DeviceInfoRequest) - ), -) -_sym_db.RegisterMessage(DeviceInfoRequest) - -DeviceInfoResponse = _reflection.GeneratedProtocolMessageType( - "DeviceInfoResponse", - (_message.Message,), - dict( - DESCRIPTOR=_DEVICEINFORESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:DeviceInfoResponse) - ), -) -_sym_db.RegisterMessage(DeviceInfoResponse) - -ListEntitiesRequest = _reflection.GeneratedProtocolMessageType( - "ListEntitiesRequest", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesRequest) - ), -) -_sym_db.RegisterMessage(ListEntitiesRequest) - -ListEntitiesBinarySensorResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesBinarySensorResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESBINARYSENSORRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesBinarySensorResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesBinarySensorResponse) - -ListEntitiesCoverResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesCoverResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESCOVERRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesCoverResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesCoverResponse) - -ListEntitiesFanResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesFanResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESFANRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesFanResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesFanResponse) - -ListEntitiesLightResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesLightResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESLIGHTRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesLightResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesLightResponse) - -ListEntitiesSensorResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesSensorResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESSENSORRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesSensorResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesSensorResponse) - -ListEntitiesSwitchResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesSwitchResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESSWITCHRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesSwitchResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesSwitchResponse) - -ListEntitiesTextSensorResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesTextSensorResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESTEXTSENSORRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesTextSensorResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesTextSensorResponse) - -ListEntitiesDoneResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesDoneResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESDONERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesDoneResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesDoneResponse) - -SubscribeStatesRequest = _reflection.GeneratedProtocolMessageType( - "SubscribeStatesRequest", - (_message.Message,), - dict( - DESCRIPTOR=_SUBSCRIBESTATESREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SubscribeStatesRequest) - ), -) -_sym_db.RegisterMessage(SubscribeStatesRequest) - -BinarySensorStateResponse = _reflection.GeneratedProtocolMessageType( - "BinarySensorStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_BINARYSENSORSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:BinarySensorStateResponse) - ), -) -_sym_db.RegisterMessage(BinarySensorStateResponse) - -CoverStateResponse = _reflection.GeneratedProtocolMessageType( - "CoverStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_COVERSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:CoverStateResponse) - ), -) -_sym_db.RegisterMessage(CoverStateResponse) - -FanStateResponse = _reflection.GeneratedProtocolMessageType( - "FanStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_FANSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:FanStateResponse) - ), -) -_sym_db.RegisterMessage(FanStateResponse) - -LightStateResponse = _reflection.GeneratedProtocolMessageType( - "LightStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LIGHTSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:LightStateResponse) - ), -) -_sym_db.RegisterMessage(LightStateResponse) - -SensorStateResponse = _reflection.GeneratedProtocolMessageType( - "SensorStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_SENSORSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SensorStateResponse) - ), -) -_sym_db.RegisterMessage(SensorStateResponse) - -SwitchStateResponse = _reflection.GeneratedProtocolMessageType( - "SwitchStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_SWITCHSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SwitchStateResponse) - ), -) -_sym_db.RegisterMessage(SwitchStateResponse) - -TextSensorStateResponse = _reflection.GeneratedProtocolMessageType( - "TextSensorStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_TEXTSENSORSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:TextSensorStateResponse) - ), -) -_sym_db.RegisterMessage(TextSensorStateResponse) - -CoverCommandRequest = _reflection.GeneratedProtocolMessageType( - "CoverCommandRequest", - (_message.Message,), - dict( - DESCRIPTOR=_COVERCOMMANDREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:CoverCommandRequest) - ), -) -_sym_db.RegisterMessage(CoverCommandRequest) - -FanCommandRequest = _reflection.GeneratedProtocolMessageType( - "FanCommandRequest", - (_message.Message,), - dict( - DESCRIPTOR=_FANCOMMANDREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:FanCommandRequest) - ), -) -_sym_db.RegisterMessage(FanCommandRequest) - -LightCommandRequest = _reflection.GeneratedProtocolMessageType( - "LightCommandRequest", - (_message.Message,), - dict( - DESCRIPTOR=_LIGHTCOMMANDREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:LightCommandRequest) - ), -) -_sym_db.RegisterMessage(LightCommandRequest) - -SwitchCommandRequest = _reflection.GeneratedProtocolMessageType( - "SwitchCommandRequest", - (_message.Message,), - dict( - DESCRIPTOR=_SWITCHCOMMANDREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SwitchCommandRequest) - ), -) -_sym_db.RegisterMessage(SwitchCommandRequest) - -SubscribeLogsRequest = _reflection.GeneratedProtocolMessageType( - "SubscribeLogsRequest", - (_message.Message,), - dict( - DESCRIPTOR=_SUBSCRIBELOGSREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SubscribeLogsRequest) - ), -) -_sym_db.RegisterMessage(SubscribeLogsRequest) - -SubscribeLogsResponse = _reflection.GeneratedProtocolMessageType( - "SubscribeLogsResponse", - (_message.Message,), - dict( - DESCRIPTOR=_SUBSCRIBELOGSRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SubscribeLogsResponse) - ), -) -_sym_db.RegisterMessage(SubscribeLogsResponse) - -SubscribeServiceCallsRequest = _reflection.GeneratedProtocolMessageType( - "SubscribeServiceCallsRequest", - (_message.Message,), - dict( - DESCRIPTOR=_SUBSCRIBESERVICECALLSREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SubscribeServiceCallsRequest) - ), -) -_sym_db.RegisterMessage(SubscribeServiceCallsRequest) - -ServiceCallResponse = _reflection.GeneratedProtocolMessageType( - "ServiceCallResponse", - (_message.Message,), - dict( - DataEntry=_reflection.GeneratedProtocolMessageType( - "DataEntry", - (_message.Message,), - dict( - DESCRIPTOR=_SERVICECALLRESPONSE_DATAENTRY, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ServiceCallResponse.DataEntry) - ), - ), - DataTemplateEntry=_reflection.GeneratedProtocolMessageType( - "DataTemplateEntry", - (_message.Message,), - dict( - DESCRIPTOR=_SERVICECALLRESPONSE_DATATEMPLATEENTRY, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ServiceCallResponse.DataTemplateEntry) - ), - ), - VariablesEntry=_reflection.GeneratedProtocolMessageType( - "VariablesEntry", - (_message.Message,), - dict( - DESCRIPTOR=_SERVICECALLRESPONSE_VARIABLESENTRY, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ServiceCallResponse.VariablesEntry) - ), - ), - DESCRIPTOR=_SERVICECALLRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ServiceCallResponse) - ), -) -_sym_db.RegisterMessage(ServiceCallResponse) -_sym_db.RegisterMessage(ServiceCallResponse.DataEntry) -_sym_db.RegisterMessage(ServiceCallResponse.DataTemplateEntry) -_sym_db.RegisterMessage(ServiceCallResponse.VariablesEntry) - -SubscribeHomeAssistantStatesRequest = _reflection.GeneratedProtocolMessageType( - "SubscribeHomeAssistantStatesRequest", - (_message.Message,), - dict( - DESCRIPTOR=_SUBSCRIBEHOMEASSISTANTSTATESREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SubscribeHomeAssistantStatesRequest) - ), -) -_sym_db.RegisterMessage(SubscribeHomeAssistantStatesRequest) - -SubscribeHomeAssistantStateResponse = _reflection.GeneratedProtocolMessageType( - "SubscribeHomeAssistantStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_SUBSCRIBEHOMEASSISTANTSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SubscribeHomeAssistantStateResponse) - ), -) -_sym_db.RegisterMessage(SubscribeHomeAssistantStateResponse) - -HomeAssistantStateResponse = _reflection.GeneratedProtocolMessageType( - "HomeAssistantStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_HOMEASSISTANTSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:HomeAssistantStateResponse) - ), -) -_sym_db.RegisterMessage(HomeAssistantStateResponse) - -GetTimeRequest = _reflection.GeneratedProtocolMessageType( - "GetTimeRequest", - (_message.Message,), - dict( - DESCRIPTOR=_GETTIMEREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:GetTimeRequest) - ), -) -_sym_db.RegisterMessage(GetTimeRequest) - -GetTimeResponse = _reflection.GeneratedProtocolMessageType( - "GetTimeResponse", - (_message.Message,), - dict( - DESCRIPTOR=_GETTIMERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:GetTimeResponse) - ), -) -_sym_db.RegisterMessage(GetTimeResponse) - - -_SERVICECALLRESPONSE_DATAENTRY._options = None -_SERVICECALLRESPONSE_DATATEMPLATEENTRY._options = None -_SERVICECALLRESPONSE_VARIABLESENTRY._options = None -# @@protoc_insertion_point(module_scope) diff --git a/esphome/api/client.py b/esphome/api/client.py deleted file mode 100644 index dd11f79922..0000000000 --- a/esphome/api/client.py +++ /dev/null @@ -1,518 +0,0 @@ -from datetime import datetime -import functools -import logging -import socket -import threading -import time - -# pylint: disable=unused-import -from typing import Optional # noqa -from google.protobuf import message # noqa - -from esphome import const -import esphome.api.api_pb2 as pb -from esphome.const import CONF_PASSWORD, CONF_PORT -from esphome.core import EsphomeError -from esphome.helpers import resolve_ip_address, indent -from esphome.log import color, Fore -from esphome.util import safe_print - -_LOGGER = logging.getLogger(__name__) - - -class APIConnectionError(EsphomeError): - pass - - -MESSAGE_TYPE_TO_PROTO = { - 1: pb.HelloRequest, - 2: pb.HelloResponse, - 3: pb.ConnectRequest, - 4: pb.ConnectResponse, - 5: pb.DisconnectRequest, - 6: pb.DisconnectResponse, - 7: pb.PingRequest, - 8: pb.PingResponse, - 9: pb.DeviceInfoRequest, - 10: pb.DeviceInfoResponse, - 11: pb.ListEntitiesRequest, - 12: pb.ListEntitiesBinarySensorResponse, - 13: pb.ListEntitiesCoverResponse, - 14: pb.ListEntitiesFanResponse, - 15: pb.ListEntitiesLightResponse, - 16: pb.ListEntitiesSensorResponse, - 17: pb.ListEntitiesSwitchResponse, - 18: pb.ListEntitiesTextSensorResponse, - 19: pb.ListEntitiesDoneResponse, - 20: pb.SubscribeStatesRequest, - 21: pb.BinarySensorStateResponse, - 22: pb.CoverStateResponse, - 23: pb.FanStateResponse, - 24: pb.LightStateResponse, - 25: pb.SensorStateResponse, - 26: pb.SwitchStateResponse, - 27: pb.TextSensorStateResponse, - 28: pb.SubscribeLogsRequest, - 29: pb.SubscribeLogsResponse, - 30: pb.CoverCommandRequest, - 31: pb.FanCommandRequest, - 32: pb.LightCommandRequest, - 33: pb.SwitchCommandRequest, - 34: pb.SubscribeServiceCallsRequest, - 35: pb.ServiceCallResponse, - 36: pb.GetTimeRequest, - 37: pb.GetTimeResponse, -} - - -def _varuint_to_bytes(value): - if value <= 0x7F: - return bytes([value]) - - ret = bytes() - while value: - temp = value & 0x7F - value >>= 7 - if value: - ret += bytes([temp | 0x80]) - else: - ret += bytes([temp]) - - return ret - - -def _bytes_to_varuint(value): - result = 0 - bitpos = 0 - for val in value: - result |= (val & 0x7F) << bitpos - bitpos += 7 - if (val & 0x80) == 0: - return result - return None - - -# pylint: disable=too-many-instance-attributes,not-callable -class APIClient(threading.Thread): - def __init__(self, address, port, password): - threading.Thread.__init__(self) - self._address = address # type: str - self._port = port # type: int - self._password = password # type: Optional[str] - self._socket = None # type: Optional[socket.socket] - self._socket_open_event = threading.Event() - self._socket_write_lock = threading.Lock() - self._connected = False - self._authenticated = False - self._message_handlers = [] - self._keepalive = 5 - self._ping_timer = None - - self.on_disconnect = None - self.on_connect = None - self.on_login = None - self.auto_reconnect = False - self._running_event = threading.Event() - self._stop_event = threading.Event() - - @property - def stopped(self): - return self._stop_event.is_set() - - def _refresh_ping(self): - if self._ping_timer is not None: - self._ping_timer.cancel() - self._ping_timer = None - - def func(): - self._ping_timer = None - - if self._connected: - try: - self.ping() - except APIConnectionError as err: - self._fatal_error(err) - else: - self._refresh_ping() - - self._ping_timer = threading.Timer(self._keepalive, func) - self._ping_timer.start() - - def _cancel_ping(self): - if self._ping_timer is not None: - self._ping_timer.cancel() - self._ping_timer = None - - def _close_socket(self): - self._cancel_ping() - if self._socket is not None: - self._socket.close() - self._socket = None - self._socket_open_event.clear() - self._connected = False - self._authenticated = False - self._message_handlers = [] - - def stop(self, force=False): - if self.stopped: - raise ValueError - - if self._connected and not force: - try: - self.disconnect() - except APIConnectionError: - pass - self._close_socket() - - self._stop_event.set() - if not force: - self.join() - - def connect(self): - if not self._running_event.wait(0.1): - raise APIConnectionError("You need to call start() first!") - - if self._connected: - self.disconnect(on_disconnect=False) - - try: - ip = resolve_ip_address(self._address) - except EsphomeError as err: - _LOGGER.warning( - "Error resolving IP address of %s. Is it connected to WiFi?", - self._address, - ) - _LOGGER.warning( - "(If this error persists, please set a static IP address: " - "https://esphome.io/components/wifi.html#manual-ips)" - ) - raise APIConnectionError(err) from err - - _LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip) - self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self._socket.settimeout(10.0) - self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - try: - self._socket.connect((ip, self._port)) - except OSError as err: - err = APIConnectionError(f"Error connecting to {ip}: {err}") - self._fatal_error(err) - raise err - self._socket.settimeout(0.1) - - self._socket_open_event.set() - - hello = pb.HelloRequest() - hello.client_info = f"ESPHome v{const.__version__}" - try: - resp = self._send_message_await_response(hello, pb.HelloResponse) - except APIConnectionError as err: - self._fatal_error(err) - raise err - _LOGGER.debug( - "Successfully connected to %s ('%s' API=%s.%s)", - self._address, - resp.server_info, - resp.api_version_major, - resp.api_version_minor, - ) - self._connected = True - self._refresh_ping() - if self.on_connect is not None: - self.on_connect() - - def _check_connected(self): - if not self._connected: - err = APIConnectionError("Must be connected!") - self._fatal_error(err) - raise err - - def login(self): - self._check_connected() - if self._authenticated: - raise APIConnectionError("Already logged in!") - - connect = pb.ConnectRequest() - if self._password is not None: - connect.password = self._password - resp = self._send_message_await_response(connect, pb.ConnectResponse) - if resp.invalid_password: - raise APIConnectionError("Invalid password!") - - self._authenticated = True - if self.on_login is not None: - self.on_login() - - def _fatal_error(self, err): - was_connected = self._connected - - self._close_socket() - - if was_connected and self.on_disconnect is not None: - self.on_disconnect(err) - - def _write(self, data): # type: (bytes) -> None - if self._socket is None: - raise APIConnectionError("Socket closed") - - # _LOGGER.debug("Write: %s", format_bytes(data)) - with self._socket_write_lock: - try: - self._socket.sendall(data) - except OSError as err: - err = APIConnectionError(f"Error while writing data: {err}") - self._fatal_error(err) - raise err - - def _send_message(self, msg): - # type: (message.Message) -> None - for message_type, klass in MESSAGE_TYPE_TO_PROTO.items(): - if isinstance(msg, klass): - break - else: - raise ValueError - - encoded = msg.SerializeToString() - _LOGGER.debug("Sending %s:\n%s", type(msg), indent(str(msg))) - req = bytes([0]) - req += _varuint_to_bytes(len(encoded)) - req += _varuint_to_bytes(message_type) - req += encoded - self._write(req) - - def _send_message_await_response_complex( - self, send_msg, do_append, do_stop, timeout=5 - ): - event = threading.Event() - responses = [] - - def on_message(resp): - if do_append(resp): - responses.append(resp) - if do_stop(resp): - event.set() - - self._message_handlers.append(on_message) - self._send_message(send_msg) - ret = event.wait(timeout) - try: - self._message_handlers.remove(on_message) - except ValueError: - pass - if not ret: - raise APIConnectionError("Timeout while waiting for message response!") - return responses - - def _send_message_await_response(self, send_msg, response_type, timeout=5): - def is_response(msg): - return isinstance(msg, response_type) - - return self._send_message_await_response_complex( - send_msg, is_response, is_response, timeout - )[0] - - def device_info(self): - self._check_connected() - return self._send_message_await_response( - pb.DeviceInfoRequest(), pb.DeviceInfoResponse - ) - - def ping(self): - self._check_connected() - return self._send_message_await_response(pb.PingRequest(), pb.PingResponse) - - def disconnect(self, on_disconnect=True): - self._check_connected() - - try: - self._send_message_await_response( - pb.DisconnectRequest(), pb.DisconnectResponse - ) - except APIConnectionError: - pass - self._close_socket() - - if self.on_disconnect is not None and on_disconnect: - self.on_disconnect(None) - - def _check_authenticated(self): - if not self._authenticated: - raise APIConnectionError("Must login first!") - - def subscribe_logs(self, on_log, log_level=7, dump_config=False): - self._check_authenticated() - - def on_msg(msg): - if isinstance(msg, pb.SubscribeLogsResponse): - on_log(msg) - - self._message_handlers.append(on_msg) - req = pb.SubscribeLogsRequest(dump_config=dump_config) - req.level = log_level - self._send_message(req) - - def _recv(self, amount): - ret = bytes() - if amount == 0: - return ret - - while len(ret) < amount: - if self.stopped: - raise APIConnectionError("Stopped!") - if not self._socket_open_event.is_set(): - raise APIConnectionError("No socket!") - try: - val = self._socket.recv(amount - len(ret)) - except AttributeError as err: - raise APIConnectionError("Socket was closed") from err - except socket.timeout: - continue - except OSError as err: - raise APIConnectionError(f"Error while receiving data: {err}") from err - ret += val - return ret - - def _recv_varint(self): - raw = bytes() - while not raw or raw[-1] & 0x80: - raw += self._recv(1) - return _bytes_to_varuint(raw) - - def _run_once(self): - if not self._socket_open_event.wait(0.1): - return - - # Preamble - if self._recv(1)[0] != 0x00: - raise APIConnectionError("Invalid preamble") - - length = self._recv_varint() - msg_type = self._recv_varint() - - raw_msg = self._recv(length) - if msg_type not in MESSAGE_TYPE_TO_PROTO: - _LOGGER.debug("Skipping message type %s", msg_type) - return - - msg = MESSAGE_TYPE_TO_PROTO[msg_type]() - msg.ParseFromString(raw_msg) - _LOGGER.debug("Got message: %s:\n%s", type(msg), indent(str(msg))) - for msg_handler in self._message_handlers[:]: - msg_handler(msg) - self._handle_internal_messages(msg) - - def run(self): - self._running_event.set() - while not self.stopped: - try: - self._run_once() - except APIConnectionError as err: - if self.stopped: - break - if self._connected: - _LOGGER.error("Error while reading incoming messages: %s", err) - self._fatal_error(err) - self._running_event.clear() - - def _handle_internal_messages(self, msg): - if isinstance(msg, pb.DisconnectRequest): - self._send_message(pb.DisconnectResponse()) - if self._socket is not None: - self._socket.close() - self._socket = None - self._connected = False - if self.on_disconnect is not None: - self.on_disconnect(None) - elif isinstance(msg, pb.PingRequest): - self._send_message(pb.PingResponse()) - elif isinstance(msg, pb.GetTimeRequest): - resp = pb.GetTimeResponse() - resp.epoch_seconds = int(time.time()) - self._send_message(resp) - - -def run_logs(config, address): - conf = config["api"] - port = conf[CONF_PORT] - password = conf[CONF_PASSWORD] - _LOGGER.info("Starting log output from %s using esphome API", address) - - cli = APIClient(address, port, password) - stopping = False - retry_timer = [] - - has_connects = [] - - def try_connect(err, tries=0): - if stopping: - return - - if err: - _LOGGER.warning("Disconnected from API: %s", err) - - while retry_timer: - retry_timer.pop(0).cancel() - - error = None - try: - cli.connect() - cli.login() - except APIConnectionError as err2: # noqa - error = err2 - - if error is None: - _LOGGER.info("Successfully connected to %s", address) - return - - wait_time = int(min(1.5 ** min(tries, 100), 30)) - if not has_connects: - _LOGGER.warning( - "Initial connection failed. The ESP might not be connected " - "to WiFi yet (%s). Re-Trying in %s seconds", - error, - wait_time, - ) - else: - _LOGGER.warning( - "Couldn't connect to API (%s). Trying to reconnect in %s seconds", - error, - wait_time, - ) - timer = threading.Timer( - wait_time, functools.partial(try_connect, None, tries + 1) - ) - timer.start() - retry_timer.append(timer) - - def on_log(msg): - time_ = datetime.now().time().strftime("[%H:%M:%S]") - text = msg.message - if msg.send_failed: - text = color( - Fore.WHITE, - "(Message skipped because it was too big to fit in " - "TCP buffer - This is only cosmetic)", - ) - safe_print(time_ + text) - - def on_login(): - try: - cli.subscribe_logs(on_log, dump_config=not has_connects) - has_connects.append(True) - except APIConnectionError: - cli.disconnect() - - cli.on_disconnect = try_connect - cli.on_login = on_login - cli.start() - - try: - try_connect(None) - while True: - time.sleep(1) - except KeyboardInterrupt: - stopping = True - cli.stop(True) - while retry_timer: - retry_timer.pop(0).cancel() - return 0 diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py new file mode 100644 index 0000000000..d8192eb88f --- /dev/null +++ b/esphome/components/api/client.py @@ -0,0 +1,73 @@ +import asyncio +import logging +from datetime import datetime +from typing import Optional + +from aioesphomeapi import APIClient, ReconnectLogic, APIConnectionError, LogLevel +import zeroconf + +from esphome.const import CONF_KEY, CONF_PORT, CONF_PASSWORD, __version__ +from esphome.util import safe_print +from . import CONF_ENCRYPTION + +_LOGGER = logging.getLogger(__name__) + + +async def async_run_logs(config, address): + conf = config["api"] + port: int = conf[CONF_PORT] + password: str = conf[CONF_PASSWORD] + noise_psk: Optional[str] = None + if CONF_ENCRYPTION in conf: + noise_psk = conf[CONF_ENCRYPTION][CONF_KEY] + _LOGGER.info("Starting log output from %s using esphome API", address) + zc = zeroconf.Zeroconf() + cli = APIClient( + asyncio.get_event_loop(), + address, + port, + password, + client_info=f"ESPHome Logs {__version__}", + noise_psk=noise_psk, + ) + first_connect = True + + def on_log(msg): + time_ = datetime.now().time().strftime("[%H:%M:%S]") + text = msg.message.decode("utf8", "backslashreplace") + safe_print(time_ + text) + + async def on_connect(): + nonlocal first_connect + try: + await cli.subscribe_logs( + on_log, + log_level=LogLevel.LOG_LEVEL_VERY_VERBOSE, + dump_config=first_connect, + ) + first_connect = False + except APIConnectionError: + cli.disconnect() + + async def on_disconnect(): + _LOGGER.warning("Disconnected from API") + + zc = zeroconf.Zeroconf() + reconnect = ReconnectLogic( + client=cli, + on_connect=on_connect, + on_disconnect=on_disconnect, + zeroconf_instance=zc, + ) + await reconnect.start() + + try: + while True: + await asyncio.sleep(60) + except KeyboardInterrupt: + await reconnect.stop() + zc.close() + + +def run_logs(config, address): + asyncio.run(async_run_logs(config, address)) diff --git a/requirements.txt b/requirements.txt index 2d354d5f04..18752e16a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,12 +3,11 @@ PyYAML==5.4.1 paho-mqtt==1.5.1 colorama==0.4.4 tornado==6.1 -protobuf==3.17.3 tzlocal==2.1 pytz==2021.1 pyserial==3.5 -ifaddr==0.1.7 platformio==5.2.0 esptool==3.1 click==7.1.2 esphome-dashboard==20210908.0 +aioesphomeapi==9.0.0 From c5d26a5b4acdb61cf36de228f0e5f480aac42ab3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Sep 2021 10:26:49 +0200 Subject: [PATCH 1327/1841] Bump click from 7.1.2 to 8.0.1 (#1824) Bumps [click](https://github.com/pallets/click) from 7.1.2 to 8.0.1. - [Release notes](https://github.com/pallets/click/releases) - [Changelog](https://github.com/pallets/click/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/click/compare/7.1.2...8.0.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 18752e16a3..5ab68beb2e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,6 @@ pytz==2021.1 pyserial==3.5 platformio==5.2.0 esptool==3.1 -click==7.1.2 +click==8.0.1 esphome-dashboard==20210908.0 aioesphomeapi==9.0.0 From 5f61897becd2ec2c5d495431df6ffc5c519eeb45 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 14 Sep 2021 10:35:37 +0200 Subject: [PATCH 1328/1841] Add stale/lock bots (#2299) --- .github/workflows/lock.yml | 21 +++++++++++++++++++++ .github/workflows/stale.yml | 30 ++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 .github/workflows/lock.yml create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml new file mode 100644 index 0000000000..aedb9dd9b3 --- /dev/null +++ b/.github/workflows/lock.yml @@ -0,0 +1,21 @@ +name: Lock + +on: + schedule: + - cron: "0 * * * *" + workflow_dispatch: + +permissions: + issues: write + pull-requests: write + +jobs: + lock: + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@v2 + with: + github-token: ${{ github.token }} + pr-lock-inactive-days: "1" + pr-lock-reason: "" + process-only: prs diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000000..f26dd32929 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,30 @@ +name: Stale + +on: + schedule: + - cron: "0 * * * *" + workflow_dispatch: + +permissions: + issues: write + pull-requests: write + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v4 + with: + repo-token: ${{ github.token }} + days-before-pr-stale: 90 + days-before-pr-close: 7 + days-before-issue-stale: -1 + days-before-issue-close: -1 + remove-stale-when-updated: true + stale-pr-label: "stale" + exempt-pr-labels: "no-stale" + stale-pr-message: > + There hasn't been any activity on this pull request recently. This + pull request has been automatically marked as stale because of that + and will be closed if no further activity occurs within 7 days. + Thank you for your contributions. From dd3f2f6c7ebca5eb1bc2561d651b989154596ef9 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 14 Sep 2021 11:53:49 +0200 Subject: [PATCH 1329/1841] Fix api noise explicit reject (#2297) --- esphome/components/api/api_frame_helper.cpp | 41 ++++++++++++++++----- esphome/components/api/api_frame_helper.h | 2 + 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index c064c7278f..e68831e594 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -61,11 +61,15 @@ const char *api_error_to_str(APIError err) { return "HANDSHAKESTATE_SETUP_FAILED"; } else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) { return "HANDSHAKESTATE_SPLIT_FAILED"; + } else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) { + return "BAD_HANDSHAKE_ERROR_BYTE"; } return "UNKNOWN"; } #define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__) +// uncomment to log raw packets +//#define HELPER_LOG_PACKETS #ifdef USE_API_NOISE static const char *const PROLOGUE_INIT = "NoiseAPIInit"; @@ -236,7 +240,9 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { } // uncomment for even more debugging - // ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); +#ifdef HELPER_LOG_PACKETS + ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); +#endif frame->msg = std::move(rx_buf_); // consume msg rx_buf_ = {}; @@ -265,6 +271,14 @@ APIError APINoiseFrameHelper::state_action_() { // waiting for client hello ParsedFrame frame; aerr = try_read_frame_(&frame); + if (aerr == APIError::BAD_INDICATOR) { + send_explicit_handshake_reject_("Bad indicator byte"); + return aerr; + } + if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) { + send_explicit_handshake_reject_("Bad handshake packet len"); + return aerr; + } if (aerr != APIError::OK) return aerr; // ignore contents, may be used in future for flags @@ -308,11 +322,11 @@ APIError APINoiseFrameHelper::state_action_() { if (frame.msg.empty()) { send_explicit_handshake_reject_("Empty handshake message"); - return APIError::BAD_HANDSHAKE_PACKET_LEN; + return APIError::BAD_HANDSHAKE_ERROR_BYTE; } else if (frame.msg[0] != 0x00) { HELPER_LOG("Bad handshake error byte: %u", frame.msg[0]); send_explicit_handshake_reject_("Bad handshake error byte"); - return APIError::BAD_HANDSHAKE_PACKET_LEN; + return APIError::BAD_HANDSHAKE_ERROR_BYTE; } NoiseBuffer mbuf; @@ -320,7 +334,6 @@ APIError APINoiseFrameHelper::state_action_() { noise_buffer_set_input(mbuf, frame.msg.data() + 1, frame.msg.size() - 1); err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr); if (err != 0) { - // TODO: explicit rejection state_ = State::FAILED; HELPER_LOG("noise_handshakestate_read_message failed: %s", noise_err_to_str(err).c_str()); if (err == NOISE_ERROR_MAC_FAILURE) { @@ -368,12 +381,16 @@ APIError APINoiseFrameHelper::state_action_() { } void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) { std::vector data; - data.reserve(reason.size() + 1); + data.resize(reason.length() + 1); data[0] = 0x01; // failure - for (size_t i = 0; i < reason.size(); i++) { + for (size_t i = 0; i < reason.length(); i++) { data[i + 1] = (uint8_t) reason[i]; } + // temporarily remove failed state + auto orig_state = state_; + state_ = State::EXPLICIT_REJECT; write_frame_(data.data(), data.size()); + state_ = orig_state; } APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { @@ -516,7 +533,9 @@ APIError APINoiseFrameHelper::write_raw_(const uint8_t *data, size_t len) { APIError aerr; // uncomment for even more debugging - // ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); +#ifdef HELPER_LOG_PACKETS + ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); +#endif if (!tx_buf_.empty()) { // try to empty tx_buf_ first @@ -799,7 +818,9 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { } // uncomment for even more debugging - // ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); +#ifdef HELPER_LOG_PACKETS + ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); +#endif frame->msg = std::move(rx_buf_); // consume msg rx_buf_ = {}; @@ -882,7 +903,9 @@ APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) { APIError aerr; // uncomment for even more debugging - // ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); +#ifdef HELPER_LOG_PACKETS + ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); +#endif if (!tx_buf_.empty()) { // try to empty tx_buf_ first diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index a8974cd25f..a9a653cf4f 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -51,6 +51,7 @@ enum class APIError : int { OUT_OF_MEMORY = 1018, HANDSHAKESTATE_SETUP_FAILED = 1019, HANDSHAKESTATE_SPLIT_FAILED = 1020, + BAD_HANDSHAKE_ERROR_BYTE = 1021, }; const char *api_error_to_str(APIError err); @@ -125,6 +126,7 @@ class APINoiseFrameHelper : public APIFrameHelper { DATA = 5, CLOSED = 6, FAILED = 7, + EXPLICIT_REJECT = 8, } state_ = State::INITIALIZE; }; #endif // USE_API_NOISE From d437cc915c9fc10644299b3c189f1aedd826ceb3 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 14 Sep 2021 22:40:45 +1200 Subject: [PATCH 1330/1841] Allow simple hostname for sntp servers (#2300) --- esphome/components/sntp/time.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sntp/time.py b/esphome/components/sntp/time.py index 5475dc0a1f..b1362f5421 100644 --- a/esphome/components/sntp/time.py +++ b/esphome/components/sntp/time.py @@ -16,7 +16,7 @@ CONFIG_SCHEMA = time_.TIME_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(SNTPComponent), cv.Optional(CONF_SERVERS, default=DEFAULT_SERVERS): cv.All( - cv.ensure_list(cv.domain), cv.Length(min=1, max=3) + cv.ensure_list(cv.Any(cv.domain, cv.hostname)), cv.Length(min=1, max=3) ), } ).extend(cv.COMPONENT_SCHEMA) From 4cc2817fcd42d0d208a00561dcca0dc9b7377a4d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 14 Sep 2021 22:59:15 +1200 Subject: [PATCH 1331/1841] Fix binary strobe (#2301) --- esphome/components/light/base_light_effects.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/base_light_effects.h b/esphome/components/light/base_light_effects.h index 7826b2eecb..5ab9f66ce4 100644 --- a/esphome/components/light/base_light_effects.h +++ b/esphome/components/light/base_light_effects.h @@ -156,7 +156,7 @@ class StrobeLightEffect : public LightEffect { if (!color.is_on()) { // Don't turn the light off, otherwise the light effect will be stopped - call.set_brightness_if_supported(0.0f); + call.set_brightness(0.0f); call.set_state(true); } call.set_publish(false); From 1b5f11bbee25a3eea1d65b8ba440b8e0bdc7d0d4 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 09:37:11 +0200 Subject: [PATCH 1332/1841] Only try compat parsing after regular parsing fails (#2269) --- esphome/__main__.py | 146 +++++++++++++++++++++++--------------------- 1 file changed, 78 insertions(+), 68 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 8d6f2b8f89..97b2988c2e 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -483,75 +483,9 @@ def parse_args(argv): metavar=("key", "value"), ) - # Keep backward compatibility with the old command line format of - # esphome . - # - # Unfortunately this can't be done by adding another configuration argument to the - # main config parser, as argparse is greedy when parsing arguments, so in regular - # usage it'll eat the command as the configuration argument and error out out - # because it can't parse the configuration as a command. - # - # Instead, construct an ad-hoc parser for the old format that doesn't actually - # process the arguments, but parses them enough to let us figure out if the old - # format is used. In that case, swap the command and configuration in the arguments - # and continue on with the normal parser (after raising a deprecation warning). - # - # Disable argparse's built-in help option and add it manually to prevent this - # parser from printing the help messagefor the old format when invoked with -h. - compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False) - compat_parser.add_argument("-h", "--help") - compat_parser.add_argument("configuration", nargs="*") - compat_parser.add_argument( - "command", - choices=[ - "config", - "compile", - "upload", - "logs", - "run", - "clean-mqtt", - "wizard", - "mqtt-fingerprint", - "version", - "clean", - "dashboard", - "vscode", - "update-all", - ], - ) - - # on Python 3.9+ we can simply set exit_on_error=False in the constructor - def _raise(x): - raise argparse.ArgumentError(None, x) - - compat_parser.error = _raise - - deprecated_argv_suggestion = None - - if ["dashboard", "config"] == argv[1:3] or ["version"] == argv[1:2]: - # this is most likely meant in new-style arg format. do not try compat parsing - pass - else: - try: - result, unparsed = compat_parser.parse_known_args(argv[1:]) - last_option = len(argv) - len(unparsed) - 1 - len(result.configuration) - unparsed = [ - "--device" if arg in ("--upload-port", "--serial-port") else arg - for arg in unparsed - ] - argv = ( - argv[0:last_option] + [result.command] + result.configuration + unparsed - ) - deprecated_argv_suggestion = argv - except argparse.ArgumentError: - # This is not an old-style command line, so we don't have to do anything. - pass - - # And continue on with regular parsing parser = argparse.ArgumentParser( description=f"ESPHome v{const.__version__}", parents=[options_parser] ) - parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion) mqtt_options = argparse.ArgumentParser(add_help=False) mqtt_options.add_argument("--topic", help="Manually set the MQTT topic.") @@ -701,7 +635,83 @@ def parse_args(argv): "configuration", help="Your YAML configuration file directories.", nargs="+" ) - return parser.parse_args(argv[1:]) + # Keep backward compatibility with the old command line format of + # esphome . + # + # Unfortunately this can't be done by adding another configuration argument to the + # main config parser, as argparse is greedy when parsing arguments, so in regular + # usage it'll eat the command as the configuration argument and error out out + # because it can't parse the configuration as a command. + # + # Instead, if parsing using the current format fails, construct an ad-hoc parser + # that doesn't actually process the arguments, but parses them enough to let us + # figure out if the old format is used. In that case, swap the command and + # configuration in the arguments and retry with the normal parser (and raise + # a deprecation warning). + arguments = argv[1:] + + # On Python 3.9+ we can simply set exit_on_error=False in the constructor + def _raise(x): + raise argparse.ArgumentError(None, x) + + # First, try new-style parsing, but don't exit in case of failure + try: + # duplicate parser so that we can use the original one to raise errors later on + current_parser = argparse.ArgumentParser(add_help=False, parents=[parser]) + current_parser.set_defaults(deprecated_argv_suggestion=None) + current_parser.error = _raise + return current_parser.parse_args(arguments) + except argparse.ArgumentError: + pass + + # Second, try compat parsing and rearrange the command-line if it succeeds + # Disable argparse's built-in help option and add it manually to prevent this + # parser from printing the help messagefor the old format when invoked with -h. + compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False) + compat_parser.add_argument("-h", "--help", action="store_true") + compat_parser.add_argument("configuration", nargs="*") + compat_parser.add_argument( + "command", + choices=[ + "config", + "compile", + "upload", + "logs", + "run", + "clean-mqtt", + "wizard", + "mqtt-fingerprint", + "version", + "clean", + "dashboard", + "vscode", + "update-all", + ], + ) + + try: + compat_parser.error = _raise + result, unparsed = compat_parser.parse_known_args(argv[1:]) + last_option = len(arguments) - len(unparsed) - 1 - len(result.configuration) + unparsed = [ + "--device" if arg in ("--upload-port", "--serial-port") else arg + for arg in unparsed + ] + arguments = ( + arguments[0:last_option] + + [result.command] + + result.configuration + + unparsed + ) + deprecated_argv_suggestion = arguments + except argparse.ArgumentError: + # old-style parsing failed, don't suggest any argument + deprecated_argv_suggestion = None + + # Finally, run the new-style parser again with the possibly swapped arguments, + # and let it error out if the command is unparsable. + parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion) + return parser.parse_args(arguments) def run_esphome(argv): @@ -715,7 +725,7 @@ def run_esphome(argv): "and will be removed in the future. " ) _LOGGER.warning("Please instead use:") - _LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion[1:])) + _LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion)) if sys.version_info < (3, 7, 0): _LOGGER.error( From 23ead416d53dc71c764eabcc50b41980a369b03a Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 09:39:18 +0200 Subject: [PATCH 1333/1841] Suppress excessive warnings about deprecated Fan interfaces (#2270) --- esphome/components/api/api_connection.cpp | 5 ++++- esphome/components/fan/fan_helpers.cpp | 7 ++++--- esphome/components/fan/fan_helpers.h | 8 ++++++++ esphome/components/fan/fan_state.cpp | 2 ++ esphome/components/mqtt/mqtt_fan.cpp | 1 + esphome/components/web_server/web_server.cpp | 1 + 6 files changed, 20 insertions(+), 4 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 1a365bc0b0..786fc28d68 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -243,6 +243,9 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) { #endif #ifdef USE_FAN +// Shut-up about usage of deprecated speed_level_to_enum/speed_enum_to_level functions for a bit. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" bool APIConnection::send_fan_state(fan::FanState *fan) { if (!this->state_subscription_) return false; @@ -291,13 +294,13 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { // Prefer level call.set_speed(msg.speed_level); } else if (msg.has_speed) { - // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) call.set_speed(fan::speed_enum_to_level(static_cast(msg.speed), traits.supported_speed_count())); } if (msg.has_direction) call.set_direction(static_cast(msg.direction)); call.perform(); } +#pragma GCC diagnostic pop #endif #ifdef USE_LIGHT diff --git a/esphome/components/fan/fan_helpers.cpp b/esphome/components/fan/fan_helpers.cpp index 5d923a1b15..34883617e6 100644 --- a/esphome/components/fan/fan_helpers.cpp +++ b/esphome/components/fan/fan_helpers.cpp @@ -4,14 +4,15 @@ namespace esphome { namespace fan { -// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) +// This whole file is deprecated, don't warn about usage of deprecated types in here. +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels) { const auto speed_ratio = static_cast(speed_level) / (supported_speed_levels + 1); const auto legacy_level = clamp(static_cast(ceilf(speed_ratio * 3)), 1, 3); - return static_cast(legacy_level - 1); // NOLINT(clang-diagnostic-deprecated-declarations) + return static_cast(legacy_level - 1); } -// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) int speed_enum_to_level(FanSpeed speed, int supported_speed_levels) { const auto enum_level = static_cast(speed) + 1; const auto speed_level = roundf(enum_level / 3.0f * supported_speed_levels); diff --git a/esphome/components/fan/fan_helpers.h b/esphome/components/fan/fan_helpers.h index 138aa5bca3..009505601e 100644 --- a/esphome/components/fan/fan_helpers.h +++ b/esphome/components/fan/fan_helpers.h @@ -4,8 +4,16 @@ namespace esphome { namespace fan { +// Shut-up about usage of deprecated FanSpeed for a bit. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +ESPDEPRECATED("FanSpeed and speed_level_to_enum() are deprecated.", "2021.9") FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels); +ESPDEPRECATED("FanSpeed and speed_enum_to_level() are deprecated.", "2021.9") int speed_enum_to_level(FanSpeed speed, int supported_speed_levels); +#pragma GCC diagnostic pop + } // namespace fan } // namespace esphome diff --git a/esphome/components/fan/fan_state.cpp b/esphome/components/fan/fan_state.cpp index a4883c5e2c..a57115beb4 100644 --- a/esphome/components/fan/fan_state.cpp +++ b/esphome/components/fan/fan_state.cpp @@ -67,6 +67,8 @@ void FanStateCall::perform() const { this->state_->state_callback_.call(); } +// This whole method is deprecated, don't warn about usage of deprecated methods inside of it. +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" FanStateCall &FanStateCall::set_speed(const char *legacy_speed) { const auto supported_speed_count = this->state_->get_traits().supported_speed_count(); if (strcasecmp(legacy_speed, "low") == 0) { diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index ba9121bc5d..b8eecf0ff3 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -100,6 +100,7 @@ bool MQTTFanComponent::publish_state() { auto traits = this->state_->get_traits(); if (traits.supports_speed()) { const char *payload; + // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) switch (fan::speed_level_to_enum(this->state_->speed, traits.supported_speed_count())) { case FAN_SPEED_LOW: { // NOLINT(clang-diagnostic-deprecated-declarations) payload = "low"; diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 56c75a1c58..dc97bcd5c2 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -397,6 +397,7 @@ std::string WebServer::fan_json(fan::FanState *obj) { const auto traits = obj->get_traits(); if (traits.supports_speed()) { root["speed_level"] = obj->speed; + // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) switch (fan::speed_level_to_enum(obj->speed, traits.supported_speed_count())) { case fan::FAN_SPEED_LOW: // NOLINT(clang-diagnostic-deprecated-declarations) root["speed"] = "low"; From 460a144ca8be65825e28efbfbebedd65ef0c9935 Mon Sep 17 00:00:00 2001 From: Jas Strong Date: Mon, 13 Sep 2021 00:54:48 -0700 Subject: [PATCH 1334/1841] t6615: tolerate sensor dropping commands (#2255) The Amphenol T6615 has a built-in calibration system which means that the sensor could go away for a couple of seconds to figure itself out. While this is happening, commands are silently dropped. This caused the previous version of this code to lock up completely, since there was no way for the command_ state machine to tick back to the NONE state. Instead of just breaking the state machine, which might be harmful on a multi-core or multi-threaded device, add a timestamp and only break the lock if it's been more than a second since the command was issued. The command usually doesn't take more than a few milliseconds to complete, so this should not affect things unduly. While we're at it, rewrite the rx side to be more robust against bytes going missing. Instead of reading in the data essentially inline, read into a buffer and process it when enough has been read to make progress. If data stops coming when we expect it to, or the data is malformed, have a timeout that sends a new command. Co-authored-by: jas --- esphome/components/t6615/t6615.cpp | 64 ++++++++++++++++++------------ esphome/components/t6615/t6615.h | 2 + 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/esphome/components/t6615/t6615.cpp b/esphome/components/t6615/t6615.cpp index 09ff61827c..c139c56ce4 100644 --- a/esphome/components/t6615/t6615.cpp +++ b/esphome/components/t6615/t6615.cpp @@ -6,7 +6,7 @@ namespace t6615 { static const char *const TAG = "t6615"; -static const uint8_t T6615_RESPONSE_BUFFER_LENGTH = 32; +static const uint32_t T6615_TIMEOUT = 1000; static const uint8_t T6615_MAGIC = 0xFF; static const uint8_t T6615_ADDR_HOST = 0xFA; static const uint8_t T6615_ADDR_SENSOR = 0xFE; @@ -19,31 +19,49 @@ static const uint8_t T6615_COMMAND_ENABLE_ABC[] = {0xB7, 0x01}; static const uint8_t T6615_COMMAND_DISABLE_ABC[] = {0xB7, 0x02}; static const uint8_t T6615_COMMAND_SET_ELEVATION[] = {0x03, 0x0F}; -void T6615Component::loop() { - if (!this->available()) - return; +void T6615Component::send_ppm_command_() { + this->command_time_ = millis(); + this->command_ = T6615Command::GET_PPM; + this->write_byte(T6615_MAGIC); + this->write_byte(T6615_ADDR_SENSOR); + this->write_byte(sizeof(T6615_COMMAND_GET_PPM)); + this->write_array(T6615_COMMAND_GET_PPM, sizeof(T6615_COMMAND_GET_PPM)); +} - // Read header - uint8_t header[3]; - this->read_array(header, 3); - if (header[0] != T6615_MAGIC || header[1] != T6615_ADDR_HOST) { - ESP_LOGW(TAG, "Reading data from T6615 failed!"); - while (this->available()) - this->read(); // Clear the incoming buffer - this->status_set_warning(); +void T6615Component::loop() { + if (this->available() < 5) { + if (this->command_ == T6615Command::GET_PPM && millis() - this->command_time_ > T6615_TIMEOUT) { + /* command got eaten, clear the buffer and fire another */ + while (this->available()) + this->read(); + this->send_ppm_command_(); + } return; } - // Read body - uint8_t length = header[2]; - uint8_t response[T6615_RESPONSE_BUFFER_LENGTH]; - this->read_array(response, length); + uint8_t response_buffer[6]; + + /* by the time we get here, we know we have at least five bytes in the buffer */ + this->read_array(response_buffer, 5); + + // Read header + if (response_buffer[0] != T6615_MAGIC || response_buffer[1] != T6615_ADDR_HOST) { + ESP_LOGW(TAG, "Got bad data from T6615! Magic was %02X and address was %02X", response_buffer[0], + response_buffer[1]); + /* make sure the buffer is empty */ + while (this->available()) + this->read(); + /* try again to read the sensor */ + this->send_ppm_command_(); + this->status_set_warning(); + return; + } this->status_clear_warning(); switch (this->command_) { case T6615Command::GET_PPM: { - const uint16_t ppm = encode_uint16(response[0], response[1]); + const uint16_t ppm = encode_uint16(response_buffer[3], response_buffer[4]); ESP_LOGD(TAG, "T6615 Received CO₂=%uppm", ppm); this->co2_sensor_->publish_state(ppm); break; @@ -51,23 +69,19 @@ void T6615Component::loop() { default: break; } - + this->command_time_ = 0; this->command_ = T6615Command::NONE; } void T6615Component::update() { this->query_ppm_(); } void T6615Component::query_ppm_() { - if (this->co2_sensor_ == nullptr || this->command_ != T6615Command::NONE) { + if (this->co2_sensor_ == nullptr || + (this->command_ != T6615Command::NONE && millis() - this->command_time_ < T6615_TIMEOUT)) { return; } - this->command_ = T6615Command::GET_PPM; - - this->write_byte(T6615_MAGIC); - this->write_byte(T6615_ADDR_SENSOR); - this->write_byte(sizeof(T6615_COMMAND_GET_PPM)); - this->write_array(T6615_COMMAND_GET_PPM, sizeof(T6615_COMMAND_GET_PPM)); + this->send_ppm_command_(); } float T6615Component::get_setup_priority() const { return setup_priority::DATA; } diff --git a/esphome/components/t6615/t6615.h b/esphome/components/t6615/t6615.h index a7da3b4cf6..a075685023 100644 --- a/esphome/components/t6615/t6615.h +++ b/esphome/components/t6615/t6615.h @@ -32,8 +32,10 @@ class T6615Component : public PollingComponent, public uart::UARTDevice { protected: void query_ppm_(); + void send_ppm_command_(); T6615Command command_ = T6615Command::NONE; + unsigned long command_time_ = 0; sensor::Sensor *co2_sensor_{nullptr}; }; From 39a18fb35831c1fa15f3fc7c56ba79ff8b16d5ef Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 13 Sep 2021 21:16:13 +0200 Subject: [PATCH 1335/1841] Bump platformio to 5.2.0 (#2291) --- docker/build.py | 2 +- esphome/zeroconf.py | 7 ++++--- requirements.txt | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docker/build.py b/docker/build.py index 54a279f845..c926b3653b 100755 --- a/docker/build.py +++ b/docker/build.py @@ -24,7 +24,7 @@ TYPE_LINT = 'lint' TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT] -BASE_VERSION = "3.6.0" +BASE_VERSION = "4.2.0" parser = argparse.ArgumentParser() diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index e94b59d3ae..443ed6a33a 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -4,9 +4,6 @@ import time from typing import Dict, Optional from zeroconf import ( - _CLASS_IN, - _FLAGS_QR_QUERY, - _TYPE_A, DNSAddress, DNSOutgoing, DNSRecord, @@ -15,6 +12,10 @@ from zeroconf import ( Zeroconf, ) +_CLASS_IN = 1 +_FLAGS_QR_QUERY = 0x0000 # query +_TYPE_A = 1 + class HostResolver(RecordUpdateListener): def __init__(self, name: str): diff --git a/requirements.txt b/requirements.txt index daaf86e641..2d354d5f04 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ tzlocal==2.1 pytz==2021.1 pyserial==3.5 ifaddr==0.1.7 -platformio==5.1.1 +platformio==5.2.0 esptool==3.1 click==7.1.2 esphome-dashboard==20210908.0 From 233783c76c9638d7338ba53dbda50868c4332ea6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 14 Sep 2021 09:53:37 +0200 Subject: [PATCH 1336/1841] API Noise logging (#2298) --- esphome/__main__.py | 2 +- esphome/api/__init__.py | 0 esphome/api/api_pb2.py | 3997 ------------------------------ esphome/api/client.py | 518 ---- esphome/components/api/client.py | 73 + requirements.txt | 3 +- 6 files changed, 75 insertions(+), 4518 deletions(-) delete mode 100644 esphome/api/__init__.py delete mode 100644 esphome/api/api_pb2.py delete mode 100644 esphome/api/client.py create mode 100644 esphome/components/api/client.py diff --git a/esphome/__main__.py b/esphome/__main__.py index 97b2988c2e..121fa7cc9e 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -256,7 +256,7 @@ def show_logs(config, args, port): run_miniterm(config, port) return 0 if get_port_type(port) == "NETWORK" and "api" in config: - from esphome.api.client import run_logs + from esphome.components.api.client import run_logs return run_logs(config, port) if get_port_type(port) == "MQTT" and "mqtt" in config: diff --git a/esphome/api/__init__.py b/esphome/api/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/esphome/api/api_pb2.py b/esphome/api/api_pb2.py deleted file mode 100644 index 6262b752c6..0000000000 --- a/esphome/api/api_pb2.py +++ /dev/null @@ -1,3997 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: api.proto - -import sys - -_b = sys.version_info[0] < 3 and (lambda x: x) or (lambda x: x.encode("latin1")) -from google.protobuf.internal import enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database - -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -DESCRIPTOR = _descriptor.FileDescriptor( - name="api.proto", - package="", - syntax="proto3", - serialized_options=None, - serialized_pb=_b( - '\n\tapi.proto"#\n\x0cHelloRequest\x12\x13\n\x0b\x63lient_info\x18\x01 \x01(\t"Z\n\rHelloResponse\x12\x19\n\x11\x61pi_version_major\x18\x01 \x01(\r\x12\x19\n\x11\x61pi_version_minor\x18\x02 \x01(\r\x12\x13\n\x0bserver_info\x18\x03 \x01(\t""\n\x0e\x43onnectRequest\x12\x10\n\x08password\x18\x01 \x01(\t"+\n\x0f\x43onnectResponse\x12\x18\n\x10invalid_password\x18\x01 \x01(\x08"\x13\n\x11\x44isconnectRequest"\x14\n\x12\x44isconnectResponse"\r\n\x0bPingRequest"\x0e\n\x0cPingResponse"\x13\n\x11\x44\x65viceInfoRequest"\xad\x01\n\x12\x44\x65viceInfoResponse\x12\x15\n\ruses_password\x18\x01 \x01(\x08\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0bmac_address\x18\x03 \x01(\t\x12\x1c\n\x14\x65sphome_core_version\x18\x04 \x01(\t\x12\x18\n\x10\x63ompilation_time\x18\x05 \x01(\t\x12\r\n\x05model\x18\x06 \x01(\t\x12\x16\n\x0ehas_deep_sleep\x18\x07 \x01(\x08"\x15\n\x13ListEntitiesRequest"\x9a\x01\n ListEntitiesBinarySensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x14\n\x0c\x64\x65vice_class\x18\x05 \x01(\t\x12\x1f\n\x17is_status_binary_sensor\x18\x06 \x01(\x08"s\n\x19ListEntitiesCoverResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x15\n\ris_optimistic\x18\x05 \x01(\x08"\x90\x01\n\x17ListEntitiesFanResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x1c\n\x14supports_oscillation\x18\x05 \x01(\x08\x12\x16\n\x0esupports_speed\x18\x06 \x01(\x08"\x8a\x02\n\x19ListEntitiesLightResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x1b\n\x13supports_brightness\x18\x05 \x01(\x08\x12\x14\n\x0csupports_rgb\x18\x06 \x01(\x08\x12\x1c\n\x14supports_white_value\x18\x07 \x01(\x08\x12"\n\x1asupports_color_temperature\x18\x08 \x01(\x08\x12\x12\n\nmin_mireds\x18\t \x01(\x02\x12\x12\n\nmax_mireds\x18\n \x01(\x02\x12\x0f\n\x07\x65\x66\x66\x65\x63ts\x18\x0b \x03(\t"\xa3\x01\n\x1aListEntitiesSensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\x12\x1b\n\x13unit_of_measurement\x18\x06 \x01(\t\x12\x19\n\x11\x61\x63\x63uracy_decimals\x18\x07 \x01(\x05"\x7f\n\x1aListEntitiesSwitchResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\x12\x12\n\noptimistic\x18\x06 \x01(\x08"o\n\x1eListEntitiesTextSensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t"\x1a\n\x18ListEntitiesDoneResponse"\x18\n\x16SubscribeStatesRequest"7\n\x19\x42inarySensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08"t\n\x12\x43overStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12-\n\x05state\x18\x02 \x01(\x0e\x32\x1e.CoverStateResponse.CoverState""\n\nCoverState\x12\x08\n\x04OPEN\x10\x00\x12\n\n\x06\x43LOSED\x10\x01"]\n\x10\x46\x61nStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\x12\x13\n\x0boscillating\x18\x03 \x01(\x08\x12\x18\n\x05speed\x18\x04 \x01(\x0e\x32\t.FanSpeed"\xa8\x01\n\x12LightStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\x12\x12\n\nbrightness\x18\x03 \x01(\x02\x12\x0b\n\x03red\x18\x04 \x01(\x02\x12\r\n\x05green\x18\x05 \x01(\x02\x12\x0c\n\x04\x62lue\x18\x06 \x01(\x02\x12\r\n\x05white\x18\x07 \x01(\x02\x12\x19\n\x11\x63olor_temperature\x18\x08 \x01(\x02\x12\x0e\n\x06\x65\x66\x66\x65\x63t\x18\t \x01(\t"1\n\x13SensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x02"1\n\x13SwitchStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08"5\n\x17TextSensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\t"\x98\x01\n\x13\x43overCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\x32\n\x07\x63ommand\x18\x03 \x01(\x0e\x32!.CoverCommandRequest.CoverCommand"-\n\x0c\x43overCommand\x12\x08\n\x04OPEN\x10\x00\x12\t\n\x05\x43LOSE\x10\x01\x12\x08\n\x04STOP\x10\x02"\x9d\x01\n\x11\x46\x61nCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\r\n\x05state\x18\x03 \x01(\x08\x12\x11\n\thas_speed\x18\x04 \x01(\x08\x12\x18\n\x05speed\x18\x05 \x01(\x0e\x32\t.FanSpeed\x12\x17\n\x0fhas_oscillating\x18\x06 \x01(\x08\x12\x13\n\x0boscillating\x18\x07 \x01(\x08"\x95\x03\n\x13LightCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\r\n\x05state\x18\x03 \x01(\x08\x12\x16\n\x0ehas_brightness\x18\x04 \x01(\x08\x12\x12\n\nbrightness\x18\x05 \x01(\x02\x12\x0f\n\x07has_rgb\x18\x06 \x01(\x08\x12\x0b\n\x03red\x18\x07 \x01(\x02\x12\r\n\x05green\x18\x08 \x01(\x02\x12\x0c\n\x04\x62lue\x18\t \x01(\x02\x12\x11\n\thas_white\x18\n \x01(\x08\x12\r\n\x05white\x18\x0b \x01(\x02\x12\x1d\n\x15has_color_temperature\x18\x0c \x01(\x08\x12\x19\n\x11\x63olor_temperature\x18\r \x01(\x02\x12\x1d\n\x15has_transition_length\x18\x0e \x01(\x08\x12\x19\n\x11transition_length\x18\x0f \x01(\r\x12\x18\n\x10has_flash_length\x18\x10 \x01(\x08\x12\x14\n\x0c\x66lash_length\x18\x11 \x01(\r\x12\x12\n\nhas_effect\x18\x12 \x01(\x08\x12\x0e\n\x06\x65\x66\x66\x65\x63t\x18\x13 \x01(\t"2\n\x14SwitchCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08"E\n\x14SubscribeLogsRequest\x12\x18\n\x05level\x18\x01 \x01(\x0e\x32\t.LogLevel\x12\x13\n\x0b\x64ump_config\x18\x02 \x01(\x08"d\n\x15SubscribeLogsResponse\x12\x18\n\x05level\x18\x01 \x01(\x0e\x32\t.LogLevel\x12\x0b\n\x03tag\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12\x13\n\x0bsend_failed\x18\x04 \x01(\x08"\x1e\n\x1cSubscribeServiceCallsRequest"\xdf\x02\n\x13ServiceCallResponse\x12\x0f\n\x07service\x18\x01 \x01(\t\x12,\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32\x1e.ServiceCallResponse.DataEntry\x12=\n\rdata_template\x18\x03 \x03(\x0b\x32&.ServiceCallResponse.DataTemplateEntry\x12\x36\n\tvariables\x18\x04 \x03(\x0b\x32#.ServiceCallResponse.VariablesEntry\x1a+\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x33\n\x11\x44\x61taTemplateEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x30\n\x0eVariablesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01"%\n#SubscribeHomeAssistantStatesRequest"8\n#SubscribeHomeAssistantStateResponse\x12\x11\n\tentity_id\x18\x01 \x01(\t">\n\x1aHomeAssistantStateResponse\x12\x11\n\tentity_id\x18\x01 \x01(\t\x12\r\n\x05state\x18\x02 \x01(\t"\x10\n\x0eGetTimeRequest"(\n\x0fGetTimeResponse\x12\x15\n\repoch_seconds\x18\x01 \x01(\x07*)\n\x08\x46\x61nSpeed\x12\x07\n\x03LOW\x10\x00\x12\n\n\x06MEDIUM\x10\x01\x12\x08\n\x04HIGH\x10\x02*]\n\x08LogLevel\x12\x08\n\x04NONE\x10\x00\x12\t\n\x05\x45RROR\x10\x01\x12\x08\n\x04WARN\x10\x02\x12\x08\n\x04INFO\x10\x03\x12\t\n\x05\x44\x45\x42UG\x10\x04\x12\x0b\n\x07VERBOSE\x10\x05\x12\x10\n\x0cVERY_VERBOSE\x10\x06\x62\x06proto3' - ), -) - -_FANSPEED = _descriptor.EnumDescriptor( - name="FanSpeed", - full_name="FanSpeed", - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name="LOW", index=0, number=0, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="MEDIUM", index=1, number=1, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="HIGH", index=2, number=2, serialized_options=None, type=None - ), - ], - containing_type=None, - serialized_options=None, - serialized_start=3822, - serialized_end=3863, -) -_sym_db.RegisterEnumDescriptor(_FANSPEED) - -FanSpeed = enum_type_wrapper.EnumTypeWrapper(_FANSPEED) -_LOGLEVEL = _descriptor.EnumDescriptor( - name="LogLevel", - full_name="LogLevel", - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name="NONE", index=0, number=0, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="ERROR", index=1, number=1, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="WARN", index=2, number=2, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="INFO", index=3, number=3, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="DEBUG", index=4, number=4, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="VERBOSE", index=5, number=5, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="VERY_VERBOSE", index=6, number=6, serialized_options=None, type=None - ), - ], - containing_type=None, - serialized_options=None, - serialized_start=3865, - serialized_end=3958, -) -_sym_db.RegisterEnumDescriptor(_LOGLEVEL) - -LogLevel = enum_type_wrapper.EnumTypeWrapper(_LOGLEVEL) -LOW = 0 -MEDIUM = 1 -HIGH = 2 -NONE = 0 -ERROR = 1 -WARN = 2 -INFO = 3 -DEBUG = 4 -VERBOSE = 5 -VERY_VERBOSE = 6 - - -_COVERSTATERESPONSE_COVERSTATE = _descriptor.EnumDescriptor( - name="CoverState", - full_name="CoverStateResponse.CoverState", - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name="OPEN", index=0, number=0, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="CLOSED", index=1, number=1, serialized_options=None, type=None - ), - ], - containing_type=None, - serialized_options=None, - serialized_start=1808, - serialized_end=1842, -) -_sym_db.RegisterEnumDescriptor(_COVERSTATERESPONSE_COVERSTATE) - -_COVERCOMMANDREQUEST_COVERCOMMAND = _descriptor.EnumDescriptor( - name="CoverCommand", - full_name="CoverCommandRequest.CoverCommand", - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name="OPEN", index=0, number=0, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="CLOSE", index=1, number=1, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="STOP", index=2, number=2, serialized_options=None, type=None - ), - ], - containing_type=None, - serialized_options=None, - serialized_start=2375, - serialized_end=2420, -) -_sym_db.RegisterEnumDescriptor(_COVERCOMMANDREQUEST_COVERCOMMAND) - - -_HELLOREQUEST = _descriptor.Descriptor( - name="HelloRequest", - full_name="HelloRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="client_info", - full_name="HelloRequest.client_info", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=13, - serialized_end=48, -) - - -_HELLORESPONSE = _descriptor.Descriptor( - name="HelloResponse", - full_name="HelloResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="api_version_major", - full_name="HelloResponse.api_version_major", - index=0, - number=1, - type=13, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="api_version_minor", - full_name="HelloResponse.api_version_minor", - index=1, - number=2, - type=13, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="server_info", - full_name="HelloResponse.server_info", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=50, - serialized_end=140, -) - - -_CONNECTREQUEST = _descriptor.Descriptor( - name="ConnectRequest", - full_name="ConnectRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="password", - full_name="ConnectRequest.password", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=142, - serialized_end=176, -) - - -_CONNECTRESPONSE = _descriptor.Descriptor( - name="ConnectResponse", - full_name="ConnectResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="invalid_password", - full_name="ConnectResponse.invalid_password", - index=0, - number=1, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=178, - serialized_end=221, -) - - -_DISCONNECTREQUEST = _descriptor.Descriptor( - name="DisconnectRequest", - full_name="DisconnectRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=223, - serialized_end=242, -) - - -_DISCONNECTRESPONSE = _descriptor.Descriptor( - name="DisconnectResponse", - full_name="DisconnectResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=244, - serialized_end=264, -) - - -_PINGREQUEST = _descriptor.Descriptor( - name="PingRequest", - full_name="PingRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=266, - serialized_end=279, -) - - -_PINGRESPONSE = _descriptor.Descriptor( - name="PingResponse", - full_name="PingResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=281, - serialized_end=295, -) - - -_DEVICEINFOREQUEST = _descriptor.Descriptor( - name="DeviceInfoRequest", - full_name="DeviceInfoRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=297, - serialized_end=316, -) - - -_DEVICEINFORESPONSE = _descriptor.Descriptor( - name="DeviceInfoResponse", - full_name="DeviceInfoResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="uses_password", - full_name="DeviceInfoResponse.uses_password", - index=0, - number=1, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="DeviceInfoResponse.name", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="mac_address", - full_name="DeviceInfoResponse.mac_address", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="esphome_core_version", - full_name="DeviceInfoResponse.esphome_core_version", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="compilation_time", - full_name="DeviceInfoResponse.compilation_time", - index=4, - number=5, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="model", - full_name="DeviceInfoResponse.model", - index=5, - number=6, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_deep_sleep", - full_name="DeviceInfoResponse.has_deep_sleep", - index=6, - number=7, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=319, - serialized_end=492, -) - - -_LISTENTITIESREQUEST = _descriptor.Descriptor( - name="ListEntitiesRequest", - full_name="ListEntitiesRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=494, - serialized_end=515, -) - - -_LISTENTITIESBINARYSENSORRESPONSE = _descriptor.Descriptor( - name="ListEntitiesBinarySensorResponse", - full_name="ListEntitiesBinarySensorResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesBinarySensorResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesBinarySensorResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesBinarySensorResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesBinarySensorResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="device_class", - full_name="ListEntitiesBinarySensorResponse.device_class", - index=4, - number=5, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="is_status_binary_sensor", - full_name="ListEntitiesBinarySensorResponse.is_status_binary_sensor", - index=5, - number=6, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=518, - serialized_end=672, -) - - -_LISTENTITIESCOVERRESPONSE = _descriptor.Descriptor( - name="ListEntitiesCoverResponse", - full_name="ListEntitiesCoverResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesCoverResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesCoverResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesCoverResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesCoverResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="is_optimistic", - full_name="ListEntitiesCoverResponse.is_optimistic", - index=4, - number=5, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=674, - serialized_end=789, -) - - -_LISTENTITIESFANRESPONSE = _descriptor.Descriptor( - name="ListEntitiesFanResponse", - full_name="ListEntitiesFanResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesFanResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesFanResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesFanResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesFanResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="supports_oscillation", - full_name="ListEntitiesFanResponse.supports_oscillation", - index=4, - number=5, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="supports_speed", - full_name="ListEntitiesFanResponse.supports_speed", - index=5, - number=6, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=792, - serialized_end=936, -) - - -_LISTENTITIESLIGHTRESPONSE = _descriptor.Descriptor( - name="ListEntitiesLightResponse", - full_name="ListEntitiesLightResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesLightResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesLightResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesLightResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesLightResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="supports_brightness", - full_name="ListEntitiesLightResponse.supports_brightness", - index=4, - number=5, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="supports_rgb", - full_name="ListEntitiesLightResponse.supports_rgb", - index=5, - number=6, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="supports_white_value", - full_name="ListEntitiesLightResponse.supports_white_value", - index=6, - number=7, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="supports_color_temperature", - full_name="ListEntitiesLightResponse.supports_color_temperature", - index=7, - number=8, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="min_mireds", - full_name="ListEntitiesLightResponse.min_mireds", - index=8, - number=9, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="max_mireds", - full_name="ListEntitiesLightResponse.max_mireds", - index=9, - number=10, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="effects", - full_name="ListEntitiesLightResponse.effects", - index=10, - number=11, - type=9, - cpp_type=9, - label=3, - has_default_value=False, - default_value=[], - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=939, - serialized_end=1205, -) - - -_LISTENTITIESSENSORRESPONSE = _descriptor.Descriptor( - name="ListEntitiesSensorResponse", - full_name="ListEntitiesSensorResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesSensorResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesSensorResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesSensorResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesSensorResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="icon", - full_name="ListEntitiesSensorResponse.icon", - index=4, - number=5, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unit_of_measurement", - full_name="ListEntitiesSensorResponse.unit_of_measurement", - index=5, - number=6, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="accuracy_decimals", - full_name="ListEntitiesSensorResponse.accuracy_decimals", - index=6, - number=7, - type=5, - cpp_type=1, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1208, - serialized_end=1371, -) - - -_LISTENTITIESSWITCHRESPONSE = _descriptor.Descriptor( - name="ListEntitiesSwitchResponse", - full_name="ListEntitiesSwitchResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesSwitchResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesSwitchResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesSwitchResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesSwitchResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="icon", - full_name="ListEntitiesSwitchResponse.icon", - index=4, - number=5, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="optimistic", - full_name="ListEntitiesSwitchResponse.optimistic", - index=5, - number=6, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1373, - serialized_end=1500, -) - - -_LISTENTITIESTEXTSENSORRESPONSE = _descriptor.Descriptor( - name="ListEntitiesTextSensorResponse", - full_name="ListEntitiesTextSensorResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesTextSensorResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesTextSensorResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesTextSensorResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesTextSensorResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="icon", - full_name="ListEntitiesTextSensorResponse.icon", - index=4, - number=5, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1502, - serialized_end=1613, -) - - -_LISTENTITIESDONERESPONSE = _descriptor.Descriptor( - name="ListEntitiesDoneResponse", - full_name="ListEntitiesDoneResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1615, - serialized_end=1641, -) - - -_SUBSCRIBESTATESREQUEST = _descriptor.Descriptor( - name="SubscribeStatesRequest", - full_name="SubscribeStatesRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1643, - serialized_end=1667, -) - - -_BINARYSENSORSTATERESPONSE = _descriptor.Descriptor( - name="BinarySensorStateResponse", - full_name="BinarySensorStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="BinarySensorStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="BinarySensorStateResponse.state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1669, - serialized_end=1724, -) - - -_COVERSTATERESPONSE = _descriptor.Descriptor( - name="CoverStateResponse", - full_name="CoverStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="CoverStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="CoverStateResponse.state", - index=1, - number=2, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[ - _COVERSTATERESPONSE_COVERSTATE, - ], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1726, - serialized_end=1842, -) - - -_FANSTATERESPONSE = _descriptor.Descriptor( - name="FanStateResponse", - full_name="FanStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="FanStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="FanStateResponse.state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="oscillating", - full_name="FanStateResponse.oscillating", - index=2, - number=3, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="speed", - full_name="FanStateResponse.speed", - index=3, - number=4, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1844, - serialized_end=1937, -) - - -_LIGHTSTATERESPONSE = _descriptor.Descriptor( - name="LightStateResponse", - full_name="LightStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="LightStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="LightStateResponse.state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="brightness", - full_name="LightStateResponse.brightness", - index=2, - number=3, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="red", - full_name="LightStateResponse.red", - index=3, - number=4, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="green", - full_name="LightStateResponse.green", - index=4, - number=5, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="blue", - full_name="LightStateResponse.blue", - index=5, - number=6, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="white", - full_name="LightStateResponse.white", - index=6, - number=7, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="color_temperature", - full_name="LightStateResponse.color_temperature", - index=7, - number=8, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="effect", - full_name="LightStateResponse.effect", - index=8, - number=9, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1940, - serialized_end=2108, -) - - -_SENSORSTATERESPONSE = _descriptor.Descriptor( - name="SensorStateResponse", - full_name="SensorStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="SensorStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="SensorStateResponse.state", - index=1, - number=2, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2110, - serialized_end=2159, -) - - -_SWITCHSTATERESPONSE = _descriptor.Descriptor( - name="SwitchStateResponse", - full_name="SwitchStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="SwitchStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="SwitchStateResponse.state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2161, - serialized_end=2210, -) - - -_TEXTSENSORSTATERESPONSE = _descriptor.Descriptor( - name="TextSensorStateResponse", - full_name="TextSensorStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="TextSensorStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="TextSensorStateResponse.state", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2212, - serialized_end=2265, -) - - -_COVERCOMMANDREQUEST = _descriptor.Descriptor( - name="CoverCommandRequest", - full_name="CoverCommandRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="CoverCommandRequest.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_state", - full_name="CoverCommandRequest.has_state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="command", - full_name="CoverCommandRequest.command", - index=2, - number=3, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[ - _COVERCOMMANDREQUEST_COVERCOMMAND, - ], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2268, - serialized_end=2420, -) - - -_FANCOMMANDREQUEST = _descriptor.Descriptor( - name="FanCommandRequest", - full_name="FanCommandRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="FanCommandRequest.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_state", - full_name="FanCommandRequest.has_state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="FanCommandRequest.state", - index=2, - number=3, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_speed", - full_name="FanCommandRequest.has_speed", - index=3, - number=4, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="speed", - full_name="FanCommandRequest.speed", - index=4, - number=5, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_oscillating", - full_name="FanCommandRequest.has_oscillating", - index=5, - number=6, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="oscillating", - full_name="FanCommandRequest.oscillating", - index=6, - number=7, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2423, - serialized_end=2580, -) - - -_LIGHTCOMMANDREQUEST = _descriptor.Descriptor( - name="LightCommandRequest", - full_name="LightCommandRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="LightCommandRequest.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_state", - full_name="LightCommandRequest.has_state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="LightCommandRequest.state", - index=2, - number=3, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_brightness", - full_name="LightCommandRequest.has_brightness", - index=3, - number=4, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="brightness", - full_name="LightCommandRequest.brightness", - index=4, - number=5, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_rgb", - full_name="LightCommandRequest.has_rgb", - index=5, - number=6, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="red", - full_name="LightCommandRequest.red", - index=6, - number=7, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="green", - full_name="LightCommandRequest.green", - index=7, - number=8, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="blue", - full_name="LightCommandRequest.blue", - index=8, - number=9, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_white", - full_name="LightCommandRequest.has_white", - index=9, - number=10, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="white", - full_name="LightCommandRequest.white", - index=10, - number=11, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_color_temperature", - full_name="LightCommandRequest.has_color_temperature", - index=11, - number=12, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="color_temperature", - full_name="LightCommandRequest.color_temperature", - index=12, - number=13, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_transition_length", - full_name="LightCommandRequest.has_transition_length", - index=13, - number=14, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="transition_length", - full_name="LightCommandRequest.transition_length", - index=14, - number=15, - type=13, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_flash_length", - full_name="LightCommandRequest.has_flash_length", - index=15, - number=16, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="flash_length", - full_name="LightCommandRequest.flash_length", - index=16, - number=17, - type=13, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_effect", - full_name="LightCommandRequest.has_effect", - index=17, - number=18, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="effect", - full_name="LightCommandRequest.effect", - index=18, - number=19, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2583, - serialized_end=2988, -) - - -_SWITCHCOMMANDREQUEST = _descriptor.Descriptor( - name="SwitchCommandRequest", - full_name="SwitchCommandRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="SwitchCommandRequest.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="SwitchCommandRequest.state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2990, - serialized_end=3040, -) - - -_SUBSCRIBELOGSREQUEST = _descriptor.Descriptor( - name="SubscribeLogsRequest", - full_name="SubscribeLogsRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="level", - full_name="SubscribeLogsRequest.level", - index=0, - number=1, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="dump_config", - full_name="SubscribeLogsRequest.dump_config", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3042, - serialized_end=3111, -) - - -_SUBSCRIBELOGSRESPONSE = _descriptor.Descriptor( - name="SubscribeLogsResponse", - full_name="SubscribeLogsResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="level", - full_name="SubscribeLogsResponse.level", - index=0, - number=1, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="tag", - full_name="SubscribeLogsResponse.tag", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="message", - full_name="SubscribeLogsResponse.message", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="send_failed", - full_name="SubscribeLogsResponse.send_failed", - index=3, - number=4, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3113, - serialized_end=3213, -) - - -_SUBSCRIBESERVICECALLSREQUEST = _descriptor.Descriptor( - name="SubscribeServiceCallsRequest", - full_name="SubscribeServiceCallsRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3215, - serialized_end=3245, -) - - -_SERVICECALLRESPONSE_DATAENTRY = _descriptor.Descriptor( - name="DataEntry", - full_name="ServiceCallResponse.DataEntry", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="ServiceCallResponse.DataEntry.key", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="value", - full_name="ServiceCallResponse.DataEntry.value", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=_b("8\001"), - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3453, - serialized_end=3496, -) - -_SERVICECALLRESPONSE_DATATEMPLATEENTRY = _descriptor.Descriptor( - name="DataTemplateEntry", - full_name="ServiceCallResponse.DataTemplateEntry", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="ServiceCallResponse.DataTemplateEntry.key", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="value", - full_name="ServiceCallResponse.DataTemplateEntry.value", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=_b("8\001"), - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3498, - serialized_end=3549, -) - -_SERVICECALLRESPONSE_VARIABLESENTRY = _descriptor.Descriptor( - name="VariablesEntry", - full_name="ServiceCallResponse.VariablesEntry", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="ServiceCallResponse.VariablesEntry.key", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="value", - full_name="ServiceCallResponse.VariablesEntry.value", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=_b("8\001"), - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3551, - serialized_end=3599, -) - -_SERVICECALLRESPONSE = _descriptor.Descriptor( - name="ServiceCallResponse", - full_name="ServiceCallResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="service", - full_name="ServiceCallResponse.service", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="data", - full_name="ServiceCallResponse.data", - index=1, - number=2, - type=11, - cpp_type=10, - label=3, - has_default_value=False, - default_value=[], - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="data_template", - full_name="ServiceCallResponse.data_template", - index=2, - number=3, - type=11, - cpp_type=10, - label=3, - has_default_value=False, - default_value=[], - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="variables", - full_name="ServiceCallResponse.variables", - index=3, - number=4, - type=11, - cpp_type=10, - label=3, - has_default_value=False, - default_value=[], - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[ - _SERVICECALLRESPONSE_DATAENTRY, - _SERVICECALLRESPONSE_DATATEMPLATEENTRY, - _SERVICECALLRESPONSE_VARIABLESENTRY, - ], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3248, - serialized_end=3599, -) - - -_SUBSCRIBEHOMEASSISTANTSTATESREQUEST = _descriptor.Descriptor( - name="SubscribeHomeAssistantStatesRequest", - full_name="SubscribeHomeAssistantStatesRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3601, - serialized_end=3638, -) - - -_SUBSCRIBEHOMEASSISTANTSTATERESPONSE = _descriptor.Descriptor( - name="SubscribeHomeAssistantStateResponse", - full_name="SubscribeHomeAssistantStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="entity_id", - full_name="SubscribeHomeAssistantStateResponse.entity_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3640, - serialized_end=3696, -) - - -_HOMEASSISTANTSTATERESPONSE = _descriptor.Descriptor( - name="HomeAssistantStateResponse", - full_name="HomeAssistantStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="entity_id", - full_name="HomeAssistantStateResponse.entity_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="HomeAssistantStateResponse.state", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3698, - serialized_end=3760, -) - - -_GETTIMEREQUEST = _descriptor.Descriptor( - name="GetTimeRequest", - full_name="GetTimeRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3762, - serialized_end=3778, -) - - -_GETTIMERESPONSE = _descriptor.Descriptor( - name="GetTimeResponse", - full_name="GetTimeResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="epoch_seconds", - full_name="GetTimeResponse.epoch_seconds", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3780, - serialized_end=3820, -) - -_COVERSTATERESPONSE.fields_by_name["state"].enum_type = _COVERSTATERESPONSE_COVERSTATE -_COVERSTATERESPONSE_COVERSTATE.containing_type = _COVERSTATERESPONSE -_FANSTATERESPONSE.fields_by_name["speed"].enum_type = _FANSPEED -_COVERCOMMANDREQUEST.fields_by_name[ - "command" -].enum_type = _COVERCOMMANDREQUEST_COVERCOMMAND -_COVERCOMMANDREQUEST_COVERCOMMAND.containing_type = _COVERCOMMANDREQUEST -_FANCOMMANDREQUEST.fields_by_name["speed"].enum_type = _FANSPEED -_SUBSCRIBELOGSREQUEST.fields_by_name["level"].enum_type = _LOGLEVEL -_SUBSCRIBELOGSRESPONSE.fields_by_name["level"].enum_type = _LOGLEVEL -_SERVICECALLRESPONSE_DATAENTRY.containing_type = _SERVICECALLRESPONSE -_SERVICECALLRESPONSE_DATATEMPLATEENTRY.containing_type = _SERVICECALLRESPONSE -_SERVICECALLRESPONSE_VARIABLESENTRY.containing_type = _SERVICECALLRESPONSE -_SERVICECALLRESPONSE.fields_by_name[ - "data" -].message_type = _SERVICECALLRESPONSE_DATAENTRY -_SERVICECALLRESPONSE.fields_by_name[ - "data_template" -].message_type = _SERVICECALLRESPONSE_DATATEMPLATEENTRY -_SERVICECALLRESPONSE.fields_by_name[ - "variables" -].message_type = _SERVICECALLRESPONSE_VARIABLESENTRY -DESCRIPTOR.message_types_by_name["HelloRequest"] = _HELLOREQUEST -DESCRIPTOR.message_types_by_name["HelloResponse"] = _HELLORESPONSE -DESCRIPTOR.message_types_by_name["ConnectRequest"] = _CONNECTREQUEST -DESCRIPTOR.message_types_by_name["ConnectResponse"] = _CONNECTRESPONSE -DESCRIPTOR.message_types_by_name["DisconnectRequest"] = _DISCONNECTREQUEST -DESCRIPTOR.message_types_by_name["DisconnectResponse"] = _DISCONNECTRESPONSE -DESCRIPTOR.message_types_by_name["PingRequest"] = _PINGREQUEST -DESCRIPTOR.message_types_by_name["PingResponse"] = _PINGRESPONSE -DESCRIPTOR.message_types_by_name["DeviceInfoRequest"] = _DEVICEINFOREQUEST -DESCRIPTOR.message_types_by_name["DeviceInfoResponse"] = _DEVICEINFORESPONSE -DESCRIPTOR.message_types_by_name["ListEntitiesRequest"] = _LISTENTITIESREQUEST -DESCRIPTOR.message_types_by_name[ - "ListEntitiesBinarySensorResponse" -] = _LISTENTITIESBINARYSENSORRESPONSE -DESCRIPTOR.message_types_by_name[ - "ListEntitiesCoverResponse" -] = _LISTENTITIESCOVERRESPONSE -DESCRIPTOR.message_types_by_name["ListEntitiesFanResponse"] = _LISTENTITIESFANRESPONSE -DESCRIPTOR.message_types_by_name[ - "ListEntitiesLightResponse" -] = _LISTENTITIESLIGHTRESPONSE -DESCRIPTOR.message_types_by_name[ - "ListEntitiesSensorResponse" -] = _LISTENTITIESSENSORRESPONSE -DESCRIPTOR.message_types_by_name[ - "ListEntitiesSwitchResponse" -] = _LISTENTITIESSWITCHRESPONSE -DESCRIPTOR.message_types_by_name[ - "ListEntitiesTextSensorResponse" -] = _LISTENTITIESTEXTSENSORRESPONSE -DESCRIPTOR.message_types_by_name["ListEntitiesDoneResponse"] = _LISTENTITIESDONERESPONSE -DESCRIPTOR.message_types_by_name["SubscribeStatesRequest"] = _SUBSCRIBESTATESREQUEST -DESCRIPTOR.message_types_by_name[ - "BinarySensorStateResponse" -] = _BINARYSENSORSTATERESPONSE -DESCRIPTOR.message_types_by_name["CoverStateResponse"] = _COVERSTATERESPONSE -DESCRIPTOR.message_types_by_name["FanStateResponse"] = _FANSTATERESPONSE -DESCRIPTOR.message_types_by_name["LightStateResponse"] = _LIGHTSTATERESPONSE -DESCRIPTOR.message_types_by_name["SensorStateResponse"] = _SENSORSTATERESPONSE -DESCRIPTOR.message_types_by_name["SwitchStateResponse"] = _SWITCHSTATERESPONSE -DESCRIPTOR.message_types_by_name["TextSensorStateResponse"] = _TEXTSENSORSTATERESPONSE -DESCRIPTOR.message_types_by_name["CoverCommandRequest"] = _COVERCOMMANDREQUEST -DESCRIPTOR.message_types_by_name["FanCommandRequest"] = _FANCOMMANDREQUEST -DESCRIPTOR.message_types_by_name["LightCommandRequest"] = _LIGHTCOMMANDREQUEST -DESCRIPTOR.message_types_by_name["SwitchCommandRequest"] = _SWITCHCOMMANDREQUEST -DESCRIPTOR.message_types_by_name["SubscribeLogsRequest"] = _SUBSCRIBELOGSREQUEST -DESCRIPTOR.message_types_by_name["SubscribeLogsResponse"] = _SUBSCRIBELOGSRESPONSE -DESCRIPTOR.message_types_by_name[ - "SubscribeServiceCallsRequest" -] = _SUBSCRIBESERVICECALLSREQUEST -DESCRIPTOR.message_types_by_name["ServiceCallResponse"] = _SERVICECALLRESPONSE -DESCRIPTOR.message_types_by_name[ - "SubscribeHomeAssistantStatesRequest" -] = _SUBSCRIBEHOMEASSISTANTSTATESREQUEST -DESCRIPTOR.message_types_by_name[ - "SubscribeHomeAssistantStateResponse" -] = _SUBSCRIBEHOMEASSISTANTSTATERESPONSE -DESCRIPTOR.message_types_by_name[ - "HomeAssistantStateResponse" -] = _HOMEASSISTANTSTATERESPONSE -DESCRIPTOR.message_types_by_name["GetTimeRequest"] = _GETTIMEREQUEST -DESCRIPTOR.message_types_by_name["GetTimeResponse"] = _GETTIMERESPONSE -DESCRIPTOR.enum_types_by_name["FanSpeed"] = _FANSPEED -DESCRIPTOR.enum_types_by_name["LogLevel"] = _LOGLEVEL -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -HelloRequest = _reflection.GeneratedProtocolMessageType( - "HelloRequest", - (_message.Message,), - dict( - DESCRIPTOR=_HELLOREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:HelloRequest) - ), -) -_sym_db.RegisterMessage(HelloRequest) - -HelloResponse = _reflection.GeneratedProtocolMessageType( - "HelloResponse", - (_message.Message,), - dict( - DESCRIPTOR=_HELLORESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:HelloResponse) - ), -) -_sym_db.RegisterMessage(HelloResponse) - -ConnectRequest = _reflection.GeneratedProtocolMessageType( - "ConnectRequest", - (_message.Message,), - dict( - DESCRIPTOR=_CONNECTREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ConnectRequest) - ), -) -_sym_db.RegisterMessage(ConnectRequest) - -ConnectResponse = _reflection.GeneratedProtocolMessageType( - "ConnectResponse", - (_message.Message,), - dict( - DESCRIPTOR=_CONNECTRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ConnectResponse) - ), -) -_sym_db.RegisterMessage(ConnectResponse) - -DisconnectRequest = _reflection.GeneratedProtocolMessageType( - "DisconnectRequest", - (_message.Message,), - dict( - DESCRIPTOR=_DISCONNECTREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:DisconnectRequest) - ), -) -_sym_db.RegisterMessage(DisconnectRequest) - -DisconnectResponse = _reflection.GeneratedProtocolMessageType( - "DisconnectResponse", - (_message.Message,), - dict( - DESCRIPTOR=_DISCONNECTRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:DisconnectResponse) - ), -) -_sym_db.RegisterMessage(DisconnectResponse) - -PingRequest = _reflection.GeneratedProtocolMessageType( - "PingRequest", - (_message.Message,), - dict( - DESCRIPTOR=_PINGREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:PingRequest) - ), -) -_sym_db.RegisterMessage(PingRequest) - -PingResponse = _reflection.GeneratedProtocolMessageType( - "PingResponse", - (_message.Message,), - dict( - DESCRIPTOR=_PINGRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:PingResponse) - ), -) -_sym_db.RegisterMessage(PingResponse) - -DeviceInfoRequest = _reflection.GeneratedProtocolMessageType( - "DeviceInfoRequest", - (_message.Message,), - dict( - DESCRIPTOR=_DEVICEINFOREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:DeviceInfoRequest) - ), -) -_sym_db.RegisterMessage(DeviceInfoRequest) - -DeviceInfoResponse = _reflection.GeneratedProtocolMessageType( - "DeviceInfoResponse", - (_message.Message,), - dict( - DESCRIPTOR=_DEVICEINFORESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:DeviceInfoResponse) - ), -) -_sym_db.RegisterMessage(DeviceInfoResponse) - -ListEntitiesRequest = _reflection.GeneratedProtocolMessageType( - "ListEntitiesRequest", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesRequest) - ), -) -_sym_db.RegisterMessage(ListEntitiesRequest) - -ListEntitiesBinarySensorResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesBinarySensorResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESBINARYSENSORRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesBinarySensorResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesBinarySensorResponse) - -ListEntitiesCoverResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesCoverResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESCOVERRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesCoverResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesCoverResponse) - -ListEntitiesFanResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesFanResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESFANRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesFanResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesFanResponse) - -ListEntitiesLightResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesLightResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESLIGHTRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesLightResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesLightResponse) - -ListEntitiesSensorResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesSensorResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESSENSORRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesSensorResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesSensorResponse) - -ListEntitiesSwitchResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesSwitchResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESSWITCHRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesSwitchResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesSwitchResponse) - -ListEntitiesTextSensorResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesTextSensorResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESTEXTSENSORRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesTextSensorResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesTextSensorResponse) - -ListEntitiesDoneResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesDoneResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESDONERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesDoneResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesDoneResponse) - -SubscribeStatesRequest = _reflection.GeneratedProtocolMessageType( - "SubscribeStatesRequest", - (_message.Message,), - dict( - DESCRIPTOR=_SUBSCRIBESTATESREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SubscribeStatesRequest) - ), -) -_sym_db.RegisterMessage(SubscribeStatesRequest) - -BinarySensorStateResponse = _reflection.GeneratedProtocolMessageType( - "BinarySensorStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_BINARYSENSORSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:BinarySensorStateResponse) - ), -) -_sym_db.RegisterMessage(BinarySensorStateResponse) - -CoverStateResponse = _reflection.GeneratedProtocolMessageType( - "CoverStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_COVERSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:CoverStateResponse) - ), -) -_sym_db.RegisterMessage(CoverStateResponse) - -FanStateResponse = _reflection.GeneratedProtocolMessageType( - "FanStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_FANSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:FanStateResponse) - ), -) -_sym_db.RegisterMessage(FanStateResponse) - -LightStateResponse = _reflection.GeneratedProtocolMessageType( - "LightStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LIGHTSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:LightStateResponse) - ), -) -_sym_db.RegisterMessage(LightStateResponse) - -SensorStateResponse = _reflection.GeneratedProtocolMessageType( - "SensorStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_SENSORSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SensorStateResponse) - ), -) -_sym_db.RegisterMessage(SensorStateResponse) - -SwitchStateResponse = _reflection.GeneratedProtocolMessageType( - "SwitchStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_SWITCHSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SwitchStateResponse) - ), -) -_sym_db.RegisterMessage(SwitchStateResponse) - -TextSensorStateResponse = _reflection.GeneratedProtocolMessageType( - "TextSensorStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_TEXTSENSORSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:TextSensorStateResponse) - ), -) -_sym_db.RegisterMessage(TextSensorStateResponse) - -CoverCommandRequest = _reflection.GeneratedProtocolMessageType( - "CoverCommandRequest", - (_message.Message,), - dict( - DESCRIPTOR=_COVERCOMMANDREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:CoverCommandRequest) - ), -) -_sym_db.RegisterMessage(CoverCommandRequest) - -FanCommandRequest = _reflection.GeneratedProtocolMessageType( - "FanCommandRequest", - (_message.Message,), - dict( - DESCRIPTOR=_FANCOMMANDREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:FanCommandRequest) - ), -) -_sym_db.RegisterMessage(FanCommandRequest) - -LightCommandRequest = _reflection.GeneratedProtocolMessageType( - "LightCommandRequest", - (_message.Message,), - dict( - DESCRIPTOR=_LIGHTCOMMANDREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:LightCommandRequest) - ), -) -_sym_db.RegisterMessage(LightCommandRequest) - -SwitchCommandRequest = _reflection.GeneratedProtocolMessageType( - "SwitchCommandRequest", - (_message.Message,), - dict( - DESCRIPTOR=_SWITCHCOMMANDREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SwitchCommandRequest) - ), -) -_sym_db.RegisterMessage(SwitchCommandRequest) - -SubscribeLogsRequest = _reflection.GeneratedProtocolMessageType( - "SubscribeLogsRequest", - (_message.Message,), - dict( - DESCRIPTOR=_SUBSCRIBELOGSREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SubscribeLogsRequest) - ), -) -_sym_db.RegisterMessage(SubscribeLogsRequest) - -SubscribeLogsResponse = _reflection.GeneratedProtocolMessageType( - "SubscribeLogsResponse", - (_message.Message,), - dict( - DESCRIPTOR=_SUBSCRIBELOGSRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SubscribeLogsResponse) - ), -) -_sym_db.RegisterMessage(SubscribeLogsResponse) - -SubscribeServiceCallsRequest = _reflection.GeneratedProtocolMessageType( - "SubscribeServiceCallsRequest", - (_message.Message,), - dict( - DESCRIPTOR=_SUBSCRIBESERVICECALLSREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SubscribeServiceCallsRequest) - ), -) -_sym_db.RegisterMessage(SubscribeServiceCallsRequest) - -ServiceCallResponse = _reflection.GeneratedProtocolMessageType( - "ServiceCallResponse", - (_message.Message,), - dict( - DataEntry=_reflection.GeneratedProtocolMessageType( - "DataEntry", - (_message.Message,), - dict( - DESCRIPTOR=_SERVICECALLRESPONSE_DATAENTRY, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ServiceCallResponse.DataEntry) - ), - ), - DataTemplateEntry=_reflection.GeneratedProtocolMessageType( - "DataTemplateEntry", - (_message.Message,), - dict( - DESCRIPTOR=_SERVICECALLRESPONSE_DATATEMPLATEENTRY, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ServiceCallResponse.DataTemplateEntry) - ), - ), - VariablesEntry=_reflection.GeneratedProtocolMessageType( - "VariablesEntry", - (_message.Message,), - dict( - DESCRIPTOR=_SERVICECALLRESPONSE_VARIABLESENTRY, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ServiceCallResponse.VariablesEntry) - ), - ), - DESCRIPTOR=_SERVICECALLRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ServiceCallResponse) - ), -) -_sym_db.RegisterMessage(ServiceCallResponse) -_sym_db.RegisterMessage(ServiceCallResponse.DataEntry) -_sym_db.RegisterMessage(ServiceCallResponse.DataTemplateEntry) -_sym_db.RegisterMessage(ServiceCallResponse.VariablesEntry) - -SubscribeHomeAssistantStatesRequest = _reflection.GeneratedProtocolMessageType( - "SubscribeHomeAssistantStatesRequest", - (_message.Message,), - dict( - DESCRIPTOR=_SUBSCRIBEHOMEASSISTANTSTATESREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SubscribeHomeAssistantStatesRequest) - ), -) -_sym_db.RegisterMessage(SubscribeHomeAssistantStatesRequest) - -SubscribeHomeAssistantStateResponse = _reflection.GeneratedProtocolMessageType( - "SubscribeHomeAssistantStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_SUBSCRIBEHOMEASSISTANTSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SubscribeHomeAssistantStateResponse) - ), -) -_sym_db.RegisterMessage(SubscribeHomeAssistantStateResponse) - -HomeAssistantStateResponse = _reflection.GeneratedProtocolMessageType( - "HomeAssistantStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_HOMEASSISTANTSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:HomeAssistantStateResponse) - ), -) -_sym_db.RegisterMessage(HomeAssistantStateResponse) - -GetTimeRequest = _reflection.GeneratedProtocolMessageType( - "GetTimeRequest", - (_message.Message,), - dict( - DESCRIPTOR=_GETTIMEREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:GetTimeRequest) - ), -) -_sym_db.RegisterMessage(GetTimeRequest) - -GetTimeResponse = _reflection.GeneratedProtocolMessageType( - "GetTimeResponse", - (_message.Message,), - dict( - DESCRIPTOR=_GETTIMERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:GetTimeResponse) - ), -) -_sym_db.RegisterMessage(GetTimeResponse) - - -_SERVICECALLRESPONSE_DATAENTRY._options = None -_SERVICECALLRESPONSE_DATATEMPLATEENTRY._options = None -_SERVICECALLRESPONSE_VARIABLESENTRY._options = None -# @@protoc_insertion_point(module_scope) diff --git a/esphome/api/client.py b/esphome/api/client.py deleted file mode 100644 index dd11f79922..0000000000 --- a/esphome/api/client.py +++ /dev/null @@ -1,518 +0,0 @@ -from datetime import datetime -import functools -import logging -import socket -import threading -import time - -# pylint: disable=unused-import -from typing import Optional # noqa -from google.protobuf import message # noqa - -from esphome import const -import esphome.api.api_pb2 as pb -from esphome.const import CONF_PASSWORD, CONF_PORT -from esphome.core import EsphomeError -from esphome.helpers import resolve_ip_address, indent -from esphome.log import color, Fore -from esphome.util import safe_print - -_LOGGER = logging.getLogger(__name__) - - -class APIConnectionError(EsphomeError): - pass - - -MESSAGE_TYPE_TO_PROTO = { - 1: pb.HelloRequest, - 2: pb.HelloResponse, - 3: pb.ConnectRequest, - 4: pb.ConnectResponse, - 5: pb.DisconnectRequest, - 6: pb.DisconnectResponse, - 7: pb.PingRequest, - 8: pb.PingResponse, - 9: pb.DeviceInfoRequest, - 10: pb.DeviceInfoResponse, - 11: pb.ListEntitiesRequest, - 12: pb.ListEntitiesBinarySensorResponse, - 13: pb.ListEntitiesCoverResponse, - 14: pb.ListEntitiesFanResponse, - 15: pb.ListEntitiesLightResponse, - 16: pb.ListEntitiesSensorResponse, - 17: pb.ListEntitiesSwitchResponse, - 18: pb.ListEntitiesTextSensorResponse, - 19: pb.ListEntitiesDoneResponse, - 20: pb.SubscribeStatesRequest, - 21: pb.BinarySensorStateResponse, - 22: pb.CoverStateResponse, - 23: pb.FanStateResponse, - 24: pb.LightStateResponse, - 25: pb.SensorStateResponse, - 26: pb.SwitchStateResponse, - 27: pb.TextSensorStateResponse, - 28: pb.SubscribeLogsRequest, - 29: pb.SubscribeLogsResponse, - 30: pb.CoverCommandRequest, - 31: pb.FanCommandRequest, - 32: pb.LightCommandRequest, - 33: pb.SwitchCommandRequest, - 34: pb.SubscribeServiceCallsRequest, - 35: pb.ServiceCallResponse, - 36: pb.GetTimeRequest, - 37: pb.GetTimeResponse, -} - - -def _varuint_to_bytes(value): - if value <= 0x7F: - return bytes([value]) - - ret = bytes() - while value: - temp = value & 0x7F - value >>= 7 - if value: - ret += bytes([temp | 0x80]) - else: - ret += bytes([temp]) - - return ret - - -def _bytes_to_varuint(value): - result = 0 - bitpos = 0 - for val in value: - result |= (val & 0x7F) << bitpos - bitpos += 7 - if (val & 0x80) == 0: - return result - return None - - -# pylint: disable=too-many-instance-attributes,not-callable -class APIClient(threading.Thread): - def __init__(self, address, port, password): - threading.Thread.__init__(self) - self._address = address # type: str - self._port = port # type: int - self._password = password # type: Optional[str] - self._socket = None # type: Optional[socket.socket] - self._socket_open_event = threading.Event() - self._socket_write_lock = threading.Lock() - self._connected = False - self._authenticated = False - self._message_handlers = [] - self._keepalive = 5 - self._ping_timer = None - - self.on_disconnect = None - self.on_connect = None - self.on_login = None - self.auto_reconnect = False - self._running_event = threading.Event() - self._stop_event = threading.Event() - - @property - def stopped(self): - return self._stop_event.is_set() - - def _refresh_ping(self): - if self._ping_timer is not None: - self._ping_timer.cancel() - self._ping_timer = None - - def func(): - self._ping_timer = None - - if self._connected: - try: - self.ping() - except APIConnectionError as err: - self._fatal_error(err) - else: - self._refresh_ping() - - self._ping_timer = threading.Timer(self._keepalive, func) - self._ping_timer.start() - - def _cancel_ping(self): - if self._ping_timer is not None: - self._ping_timer.cancel() - self._ping_timer = None - - def _close_socket(self): - self._cancel_ping() - if self._socket is not None: - self._socket.close() - self._socket = None - self._socket_open_event.clear() - self._connected = False - self._authenticated = False - self._message_handlers = [] - - def stop(self, force=False): - if self.stopped: - raise ValueError - - if self._connected and not force: - try: - self.disconnect() - except APIConnectionError: - pass - self._close_socket() - - self._stop_event.set() - if not force: - self.join() - - def connect(self): - if not self._running_event.wait(0.1): - raise APIConnectionError("You need to call start() first!") - - if self._connected: - self.disconnect(on_disconnect=False) - - try: - ip = resolve_ip_address(self._address) - except EsphomeError as err: - _LOGGER.warning( - "Error resolving IP address of %s. Is it connected to WiFi?", - self._address, - ) - _LOGGER.warning( - "(If this error persists, please set a static IP address: " - "https://esphome.io/components/wifi.html#manual-ips)" - ) - raise APIConnectionError(err) from err - - _LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip) - self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self._socket.settimeout(10.0) - self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - try: - self._socket.connect((ip, self._port)) - except OSError as err: - err = APIConnectionError(f"Error connecting to {ip}: {err}") - self._fatal_error(err) - raise err - self._socket.settimeout(0.1) - - self._socket_open_event.set() - - hello = pb.HelloRequest() - hello.client_info = f"ESPHome v{const.__version__}" - try: - resp = self._send_message_await_response(hello, pb.HelloResponse) - except APIConnectionError as err: - self._fatal_error(err) - raise err - _LOGGER.debug( - "Successfully connected to %s ('%s' API=%s.%s)", - self._address, - resp.server_info, - resp.api_version_major, - resp.api_version_minor, - ) - self._connected = True - self._refresh_ping() - if self.on_connect is not None: - self.on_connect() - - def _check_connected(self): - if not self._connected: - err = APIConnectionError("Must be connected!") - self._fatal_error(err) - raise err - - def login(self): - self._check_connected() - if self._authenticated: - raise APIConnectionError("Already logged in!") - - connect = pb.ConnectRequest() - if self._password is not None: - connect.password = self._password - resp = self._send_message_await_response(connect, pb.ConnectResponse) - if resp.invalid_password: - raise APIConnectionError("Invalid password!") - - self._authenticated = True - if self.on_login is not None: - self.on_login() - - def _fatal_error(self, err): - was_connected = self._connected - - self._close_socket() - - if was_connected and self.on_disconnect is not None: - self.on_disconnect(err) - - def _write(self, data): # type: (bytes) -> None - if self._socket is None: - raise APIConnectionError("Socket closed") - - # _LOGGER.debug("Write: %s", format_bytes(data)) - with self._socket_write_lock: - try: - self._socket.sendall(data) - except OSError as err: - err = APIConnectionError(f"Error while writing data: {err}") - self._fatal_error(err) - raise err - - def _send_message(self, msg): - # type: (message.Message) -> None - for message_type, klass in MESSAGE_TYPE_TO_PROTO.items(): - if isinstance(msg, klass): - break - else: - raise ValueError - - encoded = msg.SerializeToString() - _LOGGER.debug("Sending %s:\n%s", type(msg), indent(str(msg))) - req = bytes([0]) - req += _varuint_to_bytes(len(encoded)) - req += _varuint_to_bytes(message_type) - req += encoded - self._write(req) - - def _send_message_await_response_complex( - self, send_msg, do_append, do_stop, timeout=5 - ): - event = threading.Event() - responses = [] - - def on_message(resp): - if do_append(resp): - responses.append(resp) - if do_stop(resp): - event.set() - - self._message_handlers.append(on_message) - self._send_message(send_msg) - ret = event.wait(timeout) - try: - self._message_handlers.remove(on_message) - except ValueError: - pass - if not ret: - raise APIConnectionError("Timeout while waiting for message response!") - return responses - - def _send_message_await_response(self, send_msg, response_type, timeout=5): - def is_response(msg): - return isinstance(msg, response_type) - - return self._send_message_await_response_complex( - send_msg, is_response, is_response, timeout - )[0] - - def device_info(self): - self._check_connected() - return self._send_message_await_response( - pb.DeviceInfoRequest(), pb.DeviceInfoResponse - ) - - def ping(self): - self._check_connected() - return self._send_message_await_response(pb.PingRequest(), pb.PingResponse) - - def disconnect(self, on_disconnect=True): - self._check_connected() - - try: - self._send_message_await_response( - pb.DisconnectRequest(), pb.DisconnectResponse - ) - except APIConnectionError: - pass - self._close_socket() - - if self.on_disconnect is not None and on_disconnect: - self.on_disconnect(None) - - def _check_authenticated(self): - if not self._authenticated: - raise APIConnectionError("Must login first!") - - def subscribe_logs(self, on_log, log_level=7, dump_config=False): - self._check_authenticated() - - def on_msg(msg): - if isinstance(msg, pb.SubscribeLogsResponse): - on_log(msg) - - self._message_handlers.append(on_msg) - req = pb.SubscribeLogsRequest(dump_config=dump_config) - req.level = log_level - self._send_message(req) - - def _recv(self, amount): - ret = bytes() - if amount == 0: - return ret - - while len(ret) < amount: - if self.stopped: - raise APIConnectionError("Stopped!") - if not self._socket_open_event.is_set(): - raise APIConnectionError("No socket!") - try: - val = self._socket.recv(amount - len(ret)) - except AttributeError as err: - raise APIConnectionError("Socket was closed") from err - except socket.timeout: - continue - except OSError as err: - raise APIConnectionError(f"Error while receiving data: {err}") from err - ret += val - return ret - - def _recv_varint(self): - raw = bytes() - while not raw or raw[-1] & 0x80: - raw += self._recv(1) - return _bytes_to_varuint(raw) - - def _run_once(self): - if not self._socket_open_event.wait(0.1): - return - - # Preamble - if self._recv(1)[0] != 0x00: - raise APIConnectionError("Invalid preamble") - - length = self._recv_varint() - msg_type = self._recv_varint() - - raw_msg = self._recv(length) - if msg_type not in MESSAGE_TYPE_TO_PROTO: - _LOGGER.debug("Skipping message type %s", msg_type) - return - - msg = MESSAGE_TYPE_TO_PROTO[msg_type]() - msg.ParseFromString(raw_msg) - _LOGGER.debug("Got message: %s:\n%s", type(msg), indent(str(msg))) - for msg_handler in self._message_handlers[:]: - msg_handler(msg) - self._handle_internal_messages(msg) - - def run(self): - self._running_event.set() - while not self.stopped: - try: - self._run_once() - except APIConnectionError as err: - if self.stopped: - break - if self._connected: - _LOGGER.error("Error while reading incoming messages: %s", err) - self._fatal_error(err) - self._running_event.clear() - - def _handle_internal_messages(self, msg): - if isinstance(msg, pb.DisconnectRequest): - self._send_message(pb.DisconnectResponse()) - if self._socket is not None: - self._socket.close() - self._socket = None - self._connected = False - if self.on_disconnect is not None: - self.on_disconnect(None) - elif isinstance(msg, pb.PingRequest): - self._send_message(pb.PingResponse()) - elif isinstance(msg, pb.GetTimeRequest): - resp = pb.GetTimeResponse() - resp.epoch_seconds = int(time.time()) - self._send_message(resp) - - -def run_logs(config, address): - conf = config["api"] - port = conf[CONF_PORT] - password = conf[CONF_PASSWORD] - _LOGGER.info("Starting log output from %s using esphome API", address) - - cli = APIClient(address, port, password) - stopping = False - retry_timer = [] - - has_connects = [] - - def try_connect(err, tries=0): - if stopping: - return - - if err: - _LOGGER.warning("Disconnected from API: %s", err) - - while retry_timer: - retry_timer.pop(0).cancel() - - error = None - try: - cli.connect() - cli.login() - except APIConnectionError as err2: # noqa - error = err2 - - if error is None: - _LOGGER.info("Successfully connected to %s", address) - return - - wait_time = int(min(1.5 ** min(tries, 100), 30)) - if not has_connects: - _LOGGER.warning( - "Initial connection failed. The ESP might not be connected " - "to WiFi yet (%s). Re-Trying in %s seconds", - error, - wait_time, - ) - else: - _LOGGER.warning( - "Couldn't connect to API (%s). Trying to reconnect in %s seconds", - error, - wait_time, - ) - timer = threading.Timer( - wait_time, functools.partial(try_connect, None, tries + 1) - ) - timer.start() - retry_timer.append(timer) - - def on_log(msg): - time_ = datetime.now().time().strftime("[%H:%M:%S]") - text = msg.message - if msg.send_failed: - text = color( - Fore.WHITE, - "(Message skipped because it was too big to fit in " - "TCP buffer - This is only cosmetic)", - ) - safe_print(time_ + text) - - def on_login(): - try: - cli.subscribe_logs(on_log, dump_config=not has_connects) - has_connects.append(True) - except APIConnectionError: - cli.disconnect() - - cli.on_disconnect = try_connect - cli.on_login = on_login - cli.start() - - try: - try_connect(None) - while True: - time.sleep(1) - except KeyboardInterrupt: - stopping = True - cli.stop(True) - while retry_timer: - retry_timer.pop(0).cancel() - return 0 diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py new file mode 100644 index 0000000000..d8192eb88f --- /dev/null +++ b/esphome/components/api/client.py @@ -0,0 +1,73 @@ +import asyncio +import logging +from datetime import datetime +from typing import Optional + +from aioesphomeapi import APIClient, ReconnectLogic, APIConnectionError, LogLevel +import zeroconf + +from esphome.const import CONF_KEY, CONF_PORT, CONF_PASSWORD, __version__ +from esphome.util import safe_print +from . import CONF_ENCRYPTION + +_LOGGER = logging.getLogger(__name__) + + +async def async_run_logs(config, address): + conf = config["api"] + port: int = conf[CONF_PORT] + password: str = conf[CONF_PASSWORD] + noise_psk: Optional[str] = None + if CONF_ENCRYPTION in conf: + noise_psk = conf[CONF_ENCRYPTION][CONF_KEY] + _LOGGER.info("Starting log output from %s using esphome API", address) + zc = zeroconf.Zeroconf() + cli = APIClient( + asyncio.get_event_loop(), + address, + port, + password, + client_info=f"ESPHome Logs {__version__}", + noise_psk=noise_psk, + ) + first_connect = True + + def on_log(msg): + time_ = datetime.now().time().strftime("[%H:%M:%S]") + text = msg.message.decode("utf8", "backslashreplace") + safe_print(time_ + text) + + async def on_connect(): + nonlocal first_connect + try: + await cli.subscribe_logs( + on_log, + log_level=LogLevel.LOG_LEVEL_VERY_VERBOSE, + dump_config=first_connect, + ) + first_connect = False + except APIConnectionError: + cli.disconnect() + + async def on_disconnect(): + _LOGGER.warning("Disconnected from API") + + zc = zeroconf.Zeroconf() + reconnect = ReconnectLogic( + client=cli, + on_connect=on_connect, + on_disconnect=on_disconnect, + zeroconf_instance=zc, + ) + await reconnect.start() + + try: + while True: + await asyncio.sleep(60) + except KeyboardInterrupt: + await reconnect.stop() + zc.close() + + +def run_logs(config, address): + asyncio.run(async_run_logs(config, address)) diff --git a/requirements.txt b/requirements.txt index 2d354d5f04..18752e16a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,12 +3,11 @@ PyYAML==5.4.1 paho-mqtt==1.5.1 colorama==0.4.4 tornado==6.1 -protobuf==3.17.3 tzlocal==2.1 pytz==2021.1 pyserial==3.5 -ifaddr==0.1.7 platformio==5.2.0 esptool==3.1 click==7.1.2 esphome-dashboard==20210908.0 +aioesphomeapi==9.0.0 From a328fff5a77eae1e03b8b7c73be78312eb880d48 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 14 Sep 2021 11:53:49 +0200 Subject: [PATCH 1337/1841] Fix api noise explicit reject (#2297) --- esphome/components/api/api_frame_helper.cpp | 41 ++++++++++++++++----- esphome/components/api/api_frame_helper.h | 2 + 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index c064c7278f..e68831e594 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -61,11 +61,15 @@ const char *api_error_to_str(APIError err) { return "HANDSHAKESTATE_SETUP_FAILED"; } else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) { return "HANDSHAKESTATE_SPLIT_FAILED"; + } else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) { + return "BAD_HANDSHAKE_ERROR_BYTE"; } return "UNKNOWN"; } #define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__) +// uncomment to log raw packets +//#define HELPER_LOG_PACKETS #ifdef USE_API_NOISE static const char *const PROLOGUE_INIT = "NoiseAPIInit"; @@ -236,7 +240,9 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { } // uncomment for even more debugging - // ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); +#ifdef HELPER_LOG_PACKETS + ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); +#endif frame->msg = std::move(rx_buf_); // consume msg rx_buf_ = {}; @@ -265,6 +271,14 @@ APIError APINoiseFrameHelper::state_action_() { // waiting for client hello ParsedFrame frame; aerr = try_read_frame_(&frame); + if (aerr == APIError::BAD_INDICATOR) { + send_explicit_handshake_reject_("Bad indicator byte"); + return aerr; + } + if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) { + send_explicit_handshake_reject_("Bad handshake packet len"); + return aerr; + } if (aerr != APIError::OK) return aerr; // ignore contents, may be used in future for flags @@ -308,11 +322,11 @@ APIError APINoiseFrameHelper::state_action_() { if (frame.msg.empty()) { send_explicit_handshake_reject_("Empty handshake message"); - return APIError::BAD_HANDSHAKE_PACKET_LEN; + return APIError::BAD_HANDSHAKE_ERROR_BYTE; } else if (frame.msg[0] != 0x00) { HELPER_LOG("Bad handshake error byte: %u", frame.msg[0]); send_explicit_handshake_reject_("Bad handshake error byte"); - return APIError::BAD_HANDSHAKE_PACKET_LEN; + return APIError::BAD_HANDSHAKE_ERROR_BYTE; } NoiseBuffer mbuf; @@ -320,7 +334,6 @@ APIError APINoiseFrameHelper::state_action_() { noise_buffer_set_input(mbuf, frame.msg.data() + 1, frame.msg.size() - 1); err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr); if (err != 0) { - // TODO: explicit rejection state_ = State::FAILED; HELPER_LOG("noise_handshakestate_read_message failed: %s", noise_err_to_str(err).c_str()); if (err == NOISE_ERROR_MAC_FAILURE) { @@ -368,12 +381,16 @@ APIError APINoiseFrameHelper::state_action_() { } void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) { std::vector data; - data.reserve(reason.size() + 1); + data.resize(reason.length() + 1); data[0] = 0x01; // failure - for (size_t i = 0; i < reason.size(); i++) { + for (size_t i = 0; i < reason.length(); i++) { data[i + 1] = (uint8_t) reason[i]; } + // temporarily remove failed state + auto orig_state = state_; + state_ = State::EXPLICIT_REJECT; write_frame_(data.data(), data.size()); + state_ = orig_state; } APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { @@ -516,7 +533,9 @@ APIError APINoiseFrameHelper::write_raw_(const uint8_t *data, size_t len) { APIError aerr; // uncomment for even more debugging - // ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); +#ifdef HELPER_LOG_PACKETS + ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); +#endif if (!tx_buf_.empty()) { // try to empty tx_buf_ first @@ -799,7 +818,9 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { } // uncomment for even more debugging - // ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); +#ifdef HELPER_LOG_PACKETS + ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); +#endif frame->msg = std::move(rx_buf_); // consume msg rx_buf_ = {}; @@ -882,7 +903,9 @@ APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) { APIError aerr; // uncomment for even more debugging - // ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); +#ifdef HELPER_LOG_PACKETS + ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); +#endif if (!tx_buf_.empty()) { // try to empty tx_buf_ first diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index a8974cd25f..a9a653cf4f 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -51,6 +51,7 @@ enum class APIError : int { OUT_OF_MEMORY = 1018, HANDSHAKESTATE_SETUP_FAILED = 1019, HANDSHAKESTATE_SPLIT_FAILED = 1020, + BAD_HANDSHAKE_ERROR_BYTE = 1021, }; const char *api_error_to_str(APIError err); @@ -125,6 +126,7 @@ class APINoiseFrameHelper : public APIFrameHelper { DATA = 5, CLOSED = 6, FAILED = 7, + EXPLICIT_REJECT = 8, } state_ = State::INITIALIZE; }; #endif // USE_API_NOISE From a32ad33b4eb885193852029edca361f6ed8529d0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 14 Sep 2021 22:40:45 +1200 Subject: [PATCH 1338/1841] Allow simple hostname for sntp servers (#2300) --- esphome/components/sntp/time.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sntp/time.py b/esphome/components/sntp/time.py index 5475dc0a1f..b1362f5421 100644 --- a/esphome/components/sntp/time.py +++ b/esphome/components/sntp/time.py @@ -16,7 +16,7 @@ CONFIG_SCHEMA = time_.TIME_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(SNTPComponent), cv.Optional(CONF_SERVERS, default=DEFAULT_SERVERS): cv.All( - cv.ensure_list(cv.domain), cv.Length(min=1, max=3) + cv.ensure_list(cv.Any(cv.domain, cv.hostname)), cv.Length(min=1, max=3) ), } ).extend(cv.COMPONENT_SCHEMA) From 89f2ea572562cc41de5cf36f0f99209614bea0be Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 14 Sep 2021 22:59:15 +1200 Subject: [PATCH 1339/1841] Fix binary strobe (#2301) --- esphome/components/light/base_light_effects.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/base_light_effects.h b/esphome/components/light/base_light_effects.h index 7826b2eecb..5ab9f66ce4 100644 --- a/esphome/components/light/base_light_effects.h +++ b/esphome/components/light/base_light_effects.h @@ -156,7 +156,7 @@ class StrobeLightEffect : public LightEffect { if (!color.is_on()) { // Don't turn the light off, otherwise the light effect will be stopped - call.set_brightness_if_supported(0.0f); + call.set_brightness(0.0f); call.set_state(true); } call.set_publish(false); From 5fad38f65f397cb0e8b18841441c40908cd404fb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 14 Sep 2021 23:07:08 +1200 Subject: [PATCH 1340/1841] Bump version to 2021.9.0b4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index aa52a28ba8..c5c08b74d4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.0b3" +__version__ = "2021.9.0b4" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 716039e452b435ea15fcfc5b94c59cb45e277b99 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 14 Sep 2021 14:27:35 +0200 Subject: [PATCH 1341/1841] Use standard version of make_unique when available (#2292) --- .clang-tidy | 5 ++++- esphome/components/nfc/ndef_message.cpp | 4 ++-- esphome/components/nfc/nfc_tag.h | 4 ++-- esphome/components/pn532/pn532.cpp | 10 ++++++---- esphome/components/pn532/pn532_mifare_classic.cpp | 10 ++++++---- esphome/components/pn532/pn532_mifare_ultralight.cpp | 12 +++++++----- esphome/components/socket/bsd_sockets_impl.cpp | 3 ++- esphome/core/helpers.h | 7 ++++++- 8 files changed, 35 insertions(+), 20 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 890eb18608..98a1568e5a 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -70,7 +70,6 @@ Checks: >- -modernize-use-default-member-init, -modernize-use-equals-default, -modernize-use-trailing-return-type, - -modernize-make-unique, -modernize-use-nodiscard, -mpi-*, -objc-*, @@ -114,6 +113,10 @@ CheckOptions: value: llvm - key: modernize-use-nullptr.NullMacros value: 'NULL' + - key: modernize-make-unique.MakeSmartPtrFunction + value: 'make_unique' + - key: modernize-make-unique.MakeSmartPtrFunctionHeader + value: 'esphome/core/helpers.h' - key: readability-identifier-naming.LocalVariableCase value: 'lower_case' - key: readability-identifier-naming.ClassCase diff --git a/esphome/components/nfc/ndef_message.cpp b/esphome/components/nfc/ndef_message.cpp index 147392940c..b1554f41ae 100644 --- a/esphome/components/nfc/ndef_message.cpp +++ b/esphome/components/nfc/ndef_message.cpp @@ -83,11 +83,11 @@ bool NdefMessage::add_text_record(const std::string &text) { return this->add_te bool NdefMessage::add_text_record(const std::string &text, const std::string &encoding) { std::string payload = to_string(text.length()) + encoding + text; - return this->add_record(std::unique_ptr{new NdefRecord(TNF_WELL_KNOWN, "T", payload)}); + return this->add_record(make_unique(TNF_WELL_KNOWN, "T", payload)); } bool NdefMessage::add_uri_record(const std::string &uri) { - return this->add_record(std::unique_ptr{new NdefRecord(TNF_WELL_KNOWN, "U", uri)}); + return this->add_record(make_unique(TNF_WELL_KNOWN, "U", uri)); } std::vector NdefMessage::encode() { diff --git a/esphome/components/nfc/nfc_tag.h b/esphome/components/nfc/nfc_tag.h index 2c8b0a5f21..ab6ca650e4 100644 --- a/esphome/components/nfc/nfc_tag.h +++ b/esphome/components/nfc/nfc_tag.h @@ -31,13 +31,13 @@ class NfcTag { NfcTag(std::vector &uid, const std::string &tag_type, std::vector &ndef_data) { this->uid_ = uid; this->tag_type_ = tag_type; - this->ndef_message_ = std::unique_ptr(new NdefMessage(ndef_data)); + this->ndef_message_ = make_unique(ndef_data); }; NfcTag(const NfcTag &rhs) { uid_ = rhs.uid_; tag_type_ = rhs.tag_type_; if (rhs.ndef_message_ != nullptr) - ndef_message_ = std::unique_ptr(new NdefMessage(*rhs.ndef_message_)); + ndef_message_ = make_unique(*rhs.ndef_message_); } std::vector &get_uid() { return this->uid_; }; diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index c28ce8d503..1c4160539a 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -1,4 +1,6 @@ #include "pn532.h" + +#include #include "esphome/core/log.h" // Based on: @@ -104,7 +106,7 @@ void PN532::loop() { if (!success) { // Something failed if (!this->current_uid_.empty()) { - auto tag = std::unique_ptr{new nfc::NfcTag(this->current_uid_)}; + auto tag = make_unique(this->current_uid_); for (auto *trigger : this->triggers_ontagremoved_) trigger->process(tag); } @@ -117,7 +119,7 @@ void PN532::loop() { if (num_targets != 1) { // no tags found or too many if (!this->current_uid_.empty()) { - auto tag = std::unique_ptr{new nfc::NfcTag(this->current_uid_)}; + auto tag = make_unique(this->current_uid_); for (auto *trigger : this->triggers_ontagremoved_) trigger->process(tag); } @@ -281,9 +283,9 @@ std::unique_ptr PN532::read_tag_(std::vector &uid) { return this->read_mifare_ultralight_tag_(uid); } else if (type == nfc::TAG_TYPE_UNKNOWN) { ESP_LOGV(TAG, "Cannot determine tag type"); - return std::unique_ptr{new nfc::NfcTag(uid)}; + return make_unique(uid); } else { - return std::unique_ptr{new nfc::NfcTag(uid)}; + return make_unique(uid); } } diff --git a/esphome/components/pn532/pn532_mifare_classic.cpp b/esphome/components/pn532/pn532_mifare_classic.cpp index f4bd11d49f..81d135d8e6 100644 --- a/esphome/components/pn532/pn532_mifare_classic.cpp +++ b/esphome/components/pn532/pn532_mifare_classic.cpp @@ -1,3 +1,5 @@ +#include + #include "pn532.h" #include "esphome/core/log.h" @@ -15,15 +17,15 @@ std::unique_ptr PN532::read_mifare_classic_tag_(std::vector data; if (this->read_mifare_classic_block_(current_block, data)) { if (!nfc::decode_mifare_classic_tlv(data, message_length, message_start_index)) { - return std::unique_ptr{new nfc::NfcTag(uid, nfc::ERROR)}; + return make_unique(uid, nfc::ERROR); } } else { ESP_LOGE(TAG, "Failed to read block %d", current_block); - return std::unique_ptr{new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC)}; + return make_unique(uid, nfc::MIFARE_CLASSIC); } } else { ESP_LOGV(TAG, "Tag is not NDEF formatted"); - return std::unique_ptr{new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC)}; + return make_unique(uid, nfc::MIFARE_CLASSIC); } uint32_t index = 0; @@ -51,7 +53,7 @@ std::unique_ptr PN532::read_mifare_classic_tag_(std::vector{new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC, buffer)}; + return make_unique(uid, nfc::MIFARE_CLASSIC, buffer); } bool PN532::read_mifare_classic_block_(uint8_t block_num, std::vector &data) { diff --git a/esphome/components/pn532/pn532_mifare_ultralight.cpp b/esphome/components/pn532/pn532_mifare_ultralight.cpp index 24e97ab95c..1b91ae919e 100644 --- a/esphome/components/pn532/pn532_mifare_ultralight.cpp +++ b/esphome/components/pn532/pn532_mifare_ultralight.cpp @@ -1,3 +1,5 @@ +#include + #include "pn532.h" #include "esphome/core/log.h" @@ -9,25 +11,25 @@ static const char *const TAG = "pn532.mifare_ultralight"; std::unique_ptr PN532::read_mifare_ultralight_tag_(std::vector &uid) { if (!this->is_mifare_ultralight_formatted_()) { ESP_LOGD(TAG, "Not NDEF formatted"); - return std::unique_ptr{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2)}; + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } uint8_t message_length; uint8_t message_start_index; if (!this->find_mifare_ultralight_ndef_(message_length, message_start_index)) { - return std::unique_ptr{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2)}; + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } ESP_LOGVV(TAG, "message length: %d, start: %d", message_length, message_start_index); if (message_length == 0) { - return std::unique_ptr{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2)}; + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } std::vector data; for (uint8_t page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; page < nfc::MIFARE_ULTRALIGHT_MAX_PAGE; page++) { std::vector page_data; if (!this->read_mifare_ultralight_page_(page, page_data)) { ESP_LOGE(TAG, "Error reading page %d", page); - return std::unique_ptr{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2)}; + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } data.insert(data.end(), page_data.begin(), page_data.end()); @@ -38,7 +40,7 @@ std::unique_ptr PN532::read_mifare_ultralight_tag_(std::vector{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2, data)}; + return make_unique(uid, nfc::NFC_FORUM_TYPE_2, data); } bool PN532::read_mifare_ultralight_page_(uint8_t page_num, std::vector &data) { diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index 1ad520a099..6fb00ce22d 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -1,5 +1,6 @@ #include "socket.h" #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" #ifdef USE_SOCKET_IMPL_BSD_SOCKETS @@ -39,7 +40,7 @@ class BSDSocketImpl : public Socket { int fd = ::accept(fd_, addr, addrlen); if (fd == -1) return {}; - return std::unique_ptr{new BSDSocketImpl(fd)}; + return make_unique(fd); } int bind(const struct sockaddr *addr, socklen_t addrlen) override { return ::bind(fd_, addr, addrlen); } int close() override { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 86c70088d4..6c0ee8399e 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -88,10 +88,15 @@ template T clamp(T val, T min, T max); */ float lerp(float completion, float start, float end); -/// std::make_unique +// Not all platforms we support target C++14 yet, so we can't unconditionally use std::make_unique. Provide our own +// implementation if needed, and otherwise pull std::make_unique into scope so that we have a uniform API. +#if __cplusplus >= 201402L +using std::make_unique; +#else template std::unique_ptr make_unique(Args &&...args) { return std::unique_ptr(new T(std::forward(args)...)); } +#endif /// Return a random 32 bit unsigned integer. uint32_t random_uint32(); From 5a90b83f6385f4d8b154bdb3a0c396d37c3385e2 Mon Sep 17 00:00:00 2001 From: jsuanet <75206491+jsuanet@users.noreply.github.com> Date: Tue, 14 Sep 2021 23:22:45 +0200 Subject: [PATCH 1342/1841] Fix unit of measurement fields for DSMR power consumed/delivered fields (#2304) Co-authored-by: Jos Suanet --- esphome/components/dsmr/sensor.py | 134 +++++++++++++++++++++++------- esphome/const.py | 2 + 2 files changed, 107 insertions(+), 29 deletions(-) diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 2c05651d67..9d531293e9 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -13,9 +13,13 @@ from esphome.const import ( STATE_CLASS_NONE, STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, + UNIT_CUBIC_METER, UNIT_EMPTY, + UNIT_KILOWATT, + UNIT_KILOWATT_HOURS, + UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + UNIT_KILOVOLT_AMPS_REACTIVE, UNIT_VOLT, - UNIT_WATT, ) from . import Dsmr, CONF_DSMR_ID @@ -26,40 +30,80 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_DSMR_ID): cv.use_id(Dsmr), cv.Optional("energy_delivered_lux"): sensor.sensor_schema( - "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_delivered_tariff1"): sensor.sensor_schema( - "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_delivered_tariff2"): sensor.sensor_schema( - "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_returned_lux"): sensor.sensor_schema( - "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_returned_tariff1"): sensor.sensor_schema( - "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_returned_tariff2"): sensor.sensor_schema( - "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("total_imported_energy"): sensor.sensor_schema( - "kvarh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_NONE, ), cv.Optional("total_exported_energy"): sensor.sensor_schema( - "kvarh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_NONE, ), cv.Optional("power_delivered"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("power_returned"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("reactive_power_delivered"): sensor.sensor_schema( - "kvar", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned"): sensor.sensor_schema( - "kvar", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_threshold"): sensor.sensor_schema( UNIT_EMPTY, ICON_EMPTY, 3, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE @@ -77,13 +121,13 @@ CONFIG_SCHEMA = cv.Schema( UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE ), cv.Optional("electricity_sags_l2"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE ), cv.Optional("electricity_sags_l3"): sensor.sensor_schema( UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE ), cv.Optional("electricity_swells_l1"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE ), cv.Optional("electricity_swells_l2"): sensor.sensor_schema( UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE @@ -101,40 +145,64 @@ CONFIG_SCHEMA = cv.Schema( UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT ), cv.Optional("power_delivered_l1"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("power_delivered_l2"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("power_delivered_l3"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("power_returned_l1"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("power_returned_l2"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("power_returned_l3"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("reactive_power_delivered_l1"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l2"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l3"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l1"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l2"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l3"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l1"): sensor.sensor_schema( UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE @@ -146,10 +214,18 @@ CONFIG_SCHEMA = cv.Schema( UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE ), cv.Optional("gas_delivered"): sensor.sensor_schema( - "m³", ICON_EMPTY, 3, DEVICE_CLASS_GAS, STATE_CLASS_TOTAL_INCREASING + UNIT_CUBIC_METER, + ICON_EMPTY, + 3, + DEVICE_CLASS_GAS, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("gas_delivered_be"): sensor.sensor_schema( - "m³", ICON_EMPTY, 3, DEVICE_CLASS_GAS, STATE_CLASS_TOTAL_INCREASING + UNIT_CUBIC_METER, + ICON_EMPTY, + 3, + DEVICE_CLASS_GAS, + STATE_CLASS_TOTAL_INCREASING, ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/const.py b/esphome/const.py index f342609538..92d18cde3a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -786,7 +786,9 @@ UNIT_KELVIN = "K" UNIT_KILOGRAM = "kg" UNIT_KILOMETER = "km" UNIT_KILOMETER_PER_HOUR = "km/h" +UNIT_KILOVOLT_AMPS_REACTIVE = "kVAr" UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kVArh" +UNIT_KILOWATT = "kW" UNIT_KILOWATT_HOURS = "kWh" UNIT_LUX = "lx" UNIT_METER = "m" From 103ba4c696361d1f34a9b071c7175e5be63f205d Mon Sep 17 00:00:00 2001 From: JonasEr <8407728+JonasEr@users.noreply.github.com> Date: Wed, 15 Sep 2021 01:06:43 +0300 Subject: [PATCH 1343/1841] Bug fix of NFC message & records becoming inaccessible in on_tag lambdas (#2309) --- esphome/components/nfc/ndef_message.h | 4 ++-- esphome/components/nfc/nfc_tag.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/nfc/ndef_message.h b/esphome/components/nfc/ndef_message.h index d2ffa9582e..20140e8c1a 100644 --- a/esphome/components/nfc/ndef_message.h +++ b/esphome/components/nfc/ndef_message.h @@ -22,7 +22,7 @@ class NdefMessage { } } - const std::vector> &get_records() { return this->records_; }; + const std::vector> &get_records() { return this->records_; }; bool add_record(std::unique_ptr record); bool add_text_record(const std::string &text); @@ -32,7 +32,7 @@ class NdefMessage { std::vector encode(); protected: - std::vector> records_; + std::vector> records_; }; } // namespace nfc diff --git a/esphome/components/nfc/nfc_tag.h b/esphome/components/nfc/nfc_tag.h index ab6ca650e4..2dfc431428 100644 --- a/esphome/components/nfc/nfc_tag.h +++ b/esphome/components/nfc/nfc_tag.h @@ -43,13 +43,13 @@ class NfcTag { std::vector &get_uid() { return this->uid_; }; const std::string &get_tag_type() { return this->tag_type_; }; bool has_ndef_message() { return this->ndef_message_ != nullptr; }; - const std::unique_ptr &get_ndef_message() { return this->ndef_message_; }; + const std::shared_ptr &get_ndef_message() { return this->ndef_message_; }; void set_ndef_message(std::unique_ptr ndef_message) { this->ndef_message_ = std::move(ndef_message); }; protected: std::vector uid_; std::string tag_type_; - std::unique_ptr ndef_message_; + std::shared_ptr ndef_message_; }; } // namespace nfc From de33cbd7e7c37670d5a3c4262af024ea222b986e Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Tue, 14 Sep 2021 22:14:49 -0300 Subject: [PATCH 1344/1841] Dsmr updates (#2157) * add option to use check_crc * ignore newline before ( in parsing * add gas delivered text for raw sensor * fix compile issue when not listing any sensor * make gas_mbus_id configurable * update dsmr lib for clang --- esphome/components/dsmr/__init__.py | 10 ++++++++-- esphome/components/dsmr/dsmr.cpp | 10 ++++++++-- esphome/components/dsmr/dsmr.h | 3 ++- esphome/components/dsmr/sensor.py | 5 ++++- esphome/components/dsmr/text_sensor.py | 13 ++++++++++--- platformio.ini | 2 +- 6 files changed, 33 insertions(+), 10 deletions(-) diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py index df11a14ee8..1f1a2f980e 100644 --- a/esphome/components/dsmr/__init__.py +++ b/esphome/components/dsmr/__init__.py @@ -13,6 +13,8 @@ AUTO_LOAD = ["sensor", "text_sensor"] CONF_DSMR_ID = "dsmr_id" CONF_DECRYPTION_KEY = "decryption_key" +CONF_CRC_CHECK = "crc_check" +CONF_GAS_MBUS_ID = "gas_mbus_id" # Hack to prevent compile error due to ambiguity with lib namespace dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr") @@ -41,19 +43,23 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(Dsmr), cv.Optional(CONF_DECRYPTION_KEY): _validate_key, + cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean, + cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_, } ).extend(uart.UART_DEVICE_SCHEMA) async def to_code(config): uart_component = await cg.get_variable(config[CONF_UART_ID]) - var = cg.new_Pvariable(config[CONF_ID], uart_component) + var = cg.new_Pvariable(config[CONF_ID], uart_component, config[CONF_CRC_CHECK]) if CONF_DECRYPTION_KEY in config: cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY])) await cg.register_component(var, config) + cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID]) + # DSMR Parser - cg.add_library("glmnet/Dsmr", "0.3") + cg.add_library("glmnet/Dsmr", "0.5") # Crypto cg.add_library("rweather/Crypto", "0.2.0") diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index 39f05a28d1..7c1b406d42 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -37,6 +37,12 @@ void Dsmr::receive_telegram_() { return; } + // Some v2.2 or v3 meters will send a new value which starts with '(' + // in a new line while the value belongs to the previous ObisId. For + // proper parsing remove these new line characters + while (c == '(' && (telegram_[telegram_len_ - 1] == '\n' || telegram_[telegram_len_ - 1] == '\r')) + telegram_len_--; + telegram_[telegram_len_] = c; telegram_len_++; if (c == '!') { // footer: exclamation mark @@ -130,8 +136,8 @@ bool Dsmr::parse_telegram() { MyData data; ESP_LOGV(TAG, "Trying to parse"); ::dsmr::ParseResult res = - ::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, - false); // Parse telegram according to data definition. Ignore unknown values. + ::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false, + this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. if (res.err) { // Parsing error, show it auto err_str = res.fullError(telegram_, telegram_ + telegram_len_); diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index 984f2596db..dcb8f5f73d 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -48,7 +48,7 @@ using MyData = ::dsmr::ParsedData decryption_key_{}; + bool crc_check_; }; } // namespace dsmr } // namespace esphome diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 9d531293e9..761009c766 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -244,4 +244,7 @@ async def to_code(config): cg.add(getattr(hub, f"set_{key}")(s)) sensors.append(f"F({key})") - cg.add_define("DSMR_SENSOR_LIST(F, sep)", cg.RawExpression(" sep ".join(sensors))) + if sensors: + cg.add_define( + "DSMR_SENSOR_LIST(F, sep)", cg.RawExpression(" sep ".join(sensors)) + ) diff --git a/esphome/components/dsmr/text_sensor.py b/esphome/components/dsmr/text_sensor.py index 821b07dc6b..339eea711f 100644 --- a/esphome/components/dsmr/text_sensor.py +++ b/esphome/components/dsmr/text_sensor.py @@ -71,6 +71,11 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), } ), + cv.Optional("gas_delivered_text"): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + } + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -89,6 +94,8 @@ async def to_code(config): cg.add(getattr(hub, f"set_{key}")(var)) text_sensors.append(f"F({key})") - cg.add_define( - "DSMR_TEXT_SENSOR_LIST(F, sep)", cg.RawExpression(" sep ".join(text_sensors)) - ) + if text_sensors: + cg.add_define( + "DSMR_TEXT_SENSOR_LIST(F, sep)", + cg.RawExpression(" sep ".join(text_sensors)), + ) diff --git a/platformio.ini b/platformio.ini index 3b1241f282..174832b067 100644 --- a/platformio.ini +++ b/platformio.ini @@ -34,7 +34,7 @@ lib_deps = 1655@1.0.2 ; TinyGPSPlus (has name conflict) 6865@1.0.0 ; TM1651 Battery Display 6306@1.0.3 ; HM3301 - glmnet/Dsmr@0.3 ; used by dsmr + glmnet/Dsmr@0.5 ; used by dsmr rweather/Crypto@0.2.0 ; used by dsmr esphome/noise-c@0.1.1 ; used by api dudanov/MideaUART@1.1.0 ; used by midea From b276ac0588d665351ed621ddfa0ad8f5de945a77 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 15 Sep 2021 16:39:13 +1200 Subject: [PATCH 1345/1841] Simple time.sleep in place of threading wait due to upgraded zeroconf (#2307) --- esphome/zeroconf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index 443ed6a33a..e6853531f2 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -49,7 +49,7 @@ class HostResolver(RecordUpdateListener): next_ = now + delay delay *= 2 - zc.wait(min(next_, last) - now) + time.sleep(min(next_, last) - now) now = time.time() finally: zc.remove_listener(self) From 19014331d8225157cfa3390e36641c1c876b5db5 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 15 Sep 2021 08:48:27 +0200 Subject: [PATCH 1346/1841] Fix aioesphomeapi API logger with explicit api.port in the YAML. (#2310) --- esphome/components/api/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index d8192eb88f..4a3944d33e 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__) async def async_run_logs(config, address): conf = config["api"] - port: int = conf[CONF_PORT] + port: int = int(conf[CONF_PORT]) password: str = conf[CONF_PASSWORD] noise_psk: Optional[str] = None if CONF_ENCRYPTION in conf: From 0ea77de98cfa8af51fcf2cef5f7745a7bdad7b94 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 15 Sep 2021 19:00:51 +1200 Subject: [PATCH 1347/1841] Start a wifi scan after saving station details (#2315) --- esphome/components/wifi/wifi_component.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index c5ba9b01af..0a1e347e8f 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -244,6 +244,8 @@ void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &pa sta.set_ssid(ssid); sta.set_password(password); this->set_sta(sta); + + this->start_scanning(); } void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) { From 3b9d1263229f65b11f21c253b7687d6472f3a20f Mon Sep 17 00:00:00 2001 From: jsuanet <75206491+jsuanet@users.noreply.github.com> Date: Tue, 14 Sep 2021 23:22:45 +0200 Subject: [PATCH 1348/1841] Fix unit of measurement fields for DSMR power consumed/delivered fields (#2304) Co-authored-by: Jos Suanet --- esphome/components/dsmr/sensor.py | 134 +++++++++++++++++++++++------- esphome/const.py | 2 + 2 files changed, 107 insertions(+), 29 deletions(-) diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 2c05651d67..9d531293e9 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -13,9 +13,13 @@ from esphome.const import ( STATE_CLASS_NONE, STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, + UNIT_CUBIC_METER, UNIT_EMPTY, + UNIT_KILOWATT, + UNIT_KILOWATT_HOURS, + UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + UNIT_KILOVOLT_AMPS_REACTIVE, UNIT_VOLT, - UNIT_WATT, ) from . import Dsmr, CONF_DSMR_ID @@ -26,40 +30,80 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_DSMR_ID): cv.use_id(Dsmr), cv.Optional("energy_delivered_lux"): sensor.sensor_schema( - "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_delivered_tariff1"): sensor.sensor_schema( - "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_delivered_tariff2"): sensor.sensor_schema( - "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_returned_lux"): sensor.sensor_schema( - "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_returned_tariff1"): sensor.sensor_schema( - "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_returned_tariff2"): sensor.sensor_schema( - "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("total_imported_energy"): sensor.sensor_schema( - "kvarh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_NONE, ), cv.Optional("total_exported_energy"): sensor.sensor_schema( - "kvarh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_NONE, ), cv.Optional("power_delivered"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("power_returned"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("reactive_power_delivered"): sensor.sensor_schema( - "kvar", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned"): sensor.sensor_schema( - "kvar", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_threshold"): sensor.sensor_schema( UNIT_EMPTY, ICON_EMPTY, 3, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE @@ -77,13 +121,13 @@ CONFIG_SCHEMA = cv.Schema( UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE ), cv.Optional("electricity_sags_l2"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE ), cv.Optional("electricity_sags_l3"): sensor.sensor_schema( UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE ), cv.Optional("electricity_swells_l1"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE ), cv.Optional("electricity_swells_l2"): sensor.sensor_schema( UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE @@ -101,40 +145,64 @@ CONFIG_SCHEMA = cv.Schema( UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT ), cv.Optional("power_delivered_l1"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("power_delivered_l2"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("power_delivered_l3"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("power_returned_l1"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("power_returned_l2"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("power_returned_l3"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("reactive_power_delivered_l1"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l2"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l3"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l1"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l2"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l3"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l1"): sensor.sensor_schema( UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE @@ -146,10 +214,18 @@ CONFIG_SCHEMA = cv.Schema( UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE ), cv.Optional("gas_delivered"): sensor.sensor_schema( - "m³", ICON_EMPTY, 3, DEVICE_CLASS_GAS, STATE_CLASS_TOTAL_INCREASING + UNIT_CUBIC_METER, + ICON_EMPTY, + 3, + DEVICE_CLASS_GAS, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("gas_delivered_be"): sensor.sensor_schema( - "m³", ICON_EMPTY, 3, DEVICE_CLASS_GAS, STATE_CLASS_TOTAL_INCREASING + UNIT_CUBIC_METER, + ICON_EMPTY, + 3, + DEVICE_CLASS_GAS, + STATE_CLASS_TOTAL_INCREASING, ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/const.py b/esphome/const.py index c5c08b74d4..587309d213 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -786,7 +786,9 @@ UNIT_KELVIN = "K" UNIT_KILOGRAM = "kg" UNIT_KILOMETER = "km" UNIT_KILOMETER_PER_HOUR = "km/h" +UNIT_KILOVOLT_AMPS_REACTIVE = "kVAr" UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kVArh" +UNIT_KILOWATT = "kW" UNIT_KILOWATT_HOURS = "kWh" UNIT_LUX = "lx" UNIT_METER = "m" From 2d79d21c5007fdb874f9d029c3575c2da2429273 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 15 Sep 2021 16:39:13 +1200 Subject: [PATCH 1349/1841] Simple time.sleep in place of threading wait due to upgraded zeroconf (#2307) --- esphome/zeroconf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index 443ed6a33a..e6853531f2 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -49,7 +49,7 @@ class HostResolver(RecordUpdateListener): next_ = now + delay delay *= 2 - zc.wait(min(next_, last) - now) + time.sleep(min(next_, last) - now) now = time.time() finally: zc.remove_listener(self) From efae363739635aaf28e7e62f0aa2e72c302292fc Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 15 Sep 2021 08:48:27 +0200 Subject: [PATCH 1350/1841] Fix aioesphomeapi API logger with explicit api.port in the YAML. (#2310) --- esphome/components/api/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index d8192eb88f..4a3944d33e 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__) async def async_run_logs(config, address): conf = config["api"] - port: int = conf[CONF_PORT] + port: int = int(conf[CONF_PORT]) password: str = conf[CONF_PASSWORD] noise_psk: Optional[str] = None if CONF_ENCRYPTION in conf: From ad5f2cd7481ac625fd995ac541e7d27028f52eeb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 15 Sep 2021 19:00:51 +1200 Subject: [PATCH 1351/1841] Start a wifi scan after saving station details (#2315) --- esphome/components/wifi/wifi_component.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 50feeb6cad..282900260d 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -244,6 +244,8 @@ void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &pa sta.set_ssid(ssid); sta.set_password(password); this->set_sta(sta); + + this->start_scanning(); } void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) { From b422a63b2a4c066f2ba1e3f65a1c887a757608d2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 15 Sep 2021 19:01:54 +1200 Subject: [PATCH 1352/1841] Bump version to 2021.9.0b5 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 587309d213..2cd6d05da5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.0b4" +__version__ = "2021.9.0b5" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 2db8c42e1daeb47b4ee161446bdac38933e60078 Mon Sep 17 00:00:00 2001 From: Stefan Rado Date: Wed, 15 Sep 2021 10:23:35 +0200 Subject: [PATCH 1353/1841] Support direct relay state feedback for tuya climate component (#1668) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/tuya/climate/__init__.py | 59 ++++++++++++++----- .../components/tuya/climate/tuya_climate.cpp | 47 +++++++++++++++ .../components/tuya/climate/tuya_climate.h | 7 +++ 3 files changed, 98 insertions(+), 15 deletions(-) diff --git a/esphome/components/tuya/climate/__init__.py b/esphome/components/tuya/climate/__init__.py index 06c80964ee..471a8146e1 100644 --- a/esphome/components/tuya/climate/__init__.py +++ b/esphome/components/tuya/climate/__init__.py @@ -1,3 +1,4 @@ +from esphome import pins from esphome.components import climate import esphome.config_validation as cv import esphome.codegen as cg @@ -15,6 +16,8 @@ CODEOWNERS = ["@jesserockz"] CONF_ACTIVE_STATE_DATAPOINT = "active_state_datapoint" CONF_ACTIVE_STATE_HEATING_VALUE = "active_state_heating_value" CONF_ACTIVE_STATE_COOLING_VALUE = "active_state_cooling_value" +CONF_HEATING_STATE_PIN = "heating_state_pin" +CONF_COOLING_STATE_PIN = "cooling_state_pin" CONF_TARGET_TEMPERATURE_DATAPOINT = "target_temperature_datapoint" CONF_CURRENT_TEMPERATURE_DATAPOINT = "current_temperature_datapoint" CONF_TEMPERATURE_MULTIPLIER = "temperature_multiplier" @@ -69,14 +72,21 @@ def validate_temperature_multipliers(value): def validate_active_state_values(value): if CONF_ACTIVE_STATE_DATAPOINT not in value: - return value - if value[CONF_SUPPORTS_COOL] and CONF_ACTIVE_STATE_COOLING_VALUE not in value: - raise cv.Invalid( - ( - f"{CONF_ACTIVE_STATE_COOLING_VALUE} required if using " - f"{CONF_ACTIVE_STATE_DATAPOINT} and device supports cooling" + if CONF_ACTIVE_STATE_COOLING_VALUE in value: + raise cv.Invalid( + ( + f"{CONF_ACTIVE_STATE_DATAPOINT} required if using " + f"{CONF_ACTIVE_STATE_COOLING_VALUE}" + ) + ) + else: + if value[CONF_SUPPORTS_COOL] and CONF_ACTIVE_STATE_COOLING_VALUE not in value: + raise cv.Invalid( + ( + f"{CONF_ACTIVE_STATE_COOLING_VALUE} required if using " + f"{CONF_ACTIVE_STATE_DATAPOINT} and device supports cooling" + ) ) - ) return value @@ -91,6 +101,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_ACTIVE_STATE_DATAPOINT): cv.uint8_t, cv.Optional(CONF_ACTIVE_STATE_HEATING_VALUE, default=1): cv.uint8_t, cv.Optional(CONF_ACTIVE_STATE_COOLING_VALUE): cv.uint8_t, + cv.Optional(CONF_HEATING_STATE_PIN): pins.gpio_input_pin_schema, + cv.Optional(CONF_COOLING_STATE_PIN): pins.gpio_input_pin_schema, cv.Optional(CONF_TARGET_TEMPERATURE_DATAPOINT): cv.uint8_t, cv.Optional(CONF_CURRENT_TEMPERATURE_DATAPOINT): cv.uint8_t, cv.Optional(CONF_TEMPERATURE_MULTIPLIER): cv.positive_float, @@ -101,6 +113,8 @@ CONFIG_SCHEMA = cv.All( cv.has_at_least_one_key(CONF_TARGET_TEMPERATURE_DATAPOINT, CONF_SWITCH_DATAPOINT), validate_temperature_multipliers, validate_active_state_values, + cv.has_at_most_one_key(CONF_ACTIVE_STATE_DATAPOINT, CONF_HEATING_STATE_PIN), + cv.has_at_most_one_key(CONF_ACTIVE_STATE_DATAPOINT, CONF_COOLING_STATE_PIN), ) @@ -118,14 +132,29 @@ async def to_code(config): cg.add(var.set_switch_id(config[CONF_SWITCH_DATAPOINT])) if CONF_ACTIVE_STATE_DATAPOINT in config: cg.add(var.set_active_state_id(config[CONF_ACTIVE_STATE_DATAPOINT])) - if CONF_ACTIVE_STATE_HEATING_VALUE in config: - cg.add( - var.set_active_state_heating_value(config[CONF_ACTIVE_STATE_HEATING_VALUE]) - ) - if CONF_ACTIVE_STATE_COOLING_VALUE in config: - cg.add( - var.set_active_state_cooling_value(config[CONF_ACTIVE_STATE_COOLING_VALUE]) - ) + if CONF_ACTIVE_STATE_HEATING_VALUE in config: + cg.add( + var.set_active_state_heating_value( + config[CONF_ACTIVE_STATE_HEATING_VALUE] + ) + ) + if CONF_ACTIVE_STATE_COOLING_VALUE in config: + cg.add( + var.set_active_state_cooling_value( + config[CONF_ACTIVE_STATE_COOLING_VALUE] + ) + ) + else: + if CONF_HEATING_STATE_PIN in config: + heating_state_pin = await cg.gpio_pin_expression( + config[CONF_HEATING_STATE_PIN] + ) + cg.add(var.set_heating_state_pin(heating_state_pin)) + if CONF_COOLING_STATE_PIN in config: + cooling_state_pin = await cg.gpio_pin_expression( + config[CONF_COOLING_STATE_PIN] + ) + cg.add(var.set_cooling_state_pin(cooling_state_pin)) if CONF_TARGET_TEMPERATURE_DATAPOINT in config: cg.add(var.set_target_temperature_id(config[CONF_TARGET_TEMPERATURE_DATAPOINT])) if CONF_CURRENT_TEMPERATURE_DATAPOINT in config: diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index a4ce06476d..ae4424e438 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -31,6 +31,15 @@ void TuyaClimate::setup() { this->compute_state_(); this->publish_state(); }); + } else { + if (this->heating_state_pin_ != nullptr) { + this->heating_state_pin_->setup(); + this->heating_state_ = this->heating_state_pin_->digital_read(); + } + if (this->cooling_state_pin_ != nullptr) { + this->cooling_state_pin_->setup(); + this->cooling_state_ = this->cooling_state_pin_->digital_read(); + } } if (this->target_temperature_id_.has_value()) { this->parent_->register_listener(*this->target_temperature_id_, [this](const TuyaDatapoint &datapoint) { @@ -50,6 +59,34 @@ void TuyaClimate::setup() { } } +void TuyaClimate::loop() { + if (this->active_state_id_.has_value()) + return; + + bool state_changed = false; + if (this->heating_state_pin_ != nullptr) { + bool heating_state = this->heating_state_pin_->digital_read(); + if (heating_state != this->heating_state_) { + ESP_LOGV(TAG, "Heating state pin changed to: %s", ONOFF(heating_state)); + this->heating_state_ = heating_state; + state_changed = true; + } + } + if (this->cooling_state_pin_ != nullptr) { + bool cooling_state = this->cooling_state_pin_->digital_read(); + if (cooling_state != this->cooling_state_) { + ESP_LOGV(TAG, "Cooling state pin changed to: %s", ONOFF(cooling_state)); + this->cooling_state_ = cooling_state; + state_changed = true; + } + } + + if (state_changed) { + this->compute_state_(); + this->publish_state(); + } +} + void TuyaClimate::control(const climate::ClimateCall &call) { if (call.get_mode().has_value()) { const bool switch_state = *call.get_mode() != climate::CLIMATE_MODE_OFF; @@ -86,6 +123,8 @@ void TuyaClimate::dump_config() { ESP_LOGCONFIG(TAG, " Target Temperature has datapoint ID %u", *this->target_temperature_id_); if (this->current_temperature_id_.has_value()) ESP_LOGCONFIG(TAG, " Current Temperature has datapoint ID %u", *this->current_temperature_id_); + LOG_PIN(" Heating State Pin: ", this->heating_state_pin_); + LOG_PIN(" Cooling State Pin: ", this->cooling_state_pin_); } void TuyaClimate::compute_state_() { @@ -102,6 +141,7 @@ void TuyaClimate::compute_state_() { climate::ClimateAction target_action = climate::CLIMATE_ACTION_IDLE; if (this->active_state_id_.has_value()) { + // Use state from MCU datapoint if (this->supports_heat_ && this->active_state_heating_value_.has_value() && this->active_state_ == this->active_state_heating_value_) { target_action = climate::CLIMATE_ACTION_HEATING; @@ -109,6 +149,13 @@ void TuyaClimate::compute_state_() { this->active_state_ == this->active_state_cooling_value_) { target_action = climate::CLIMATE_ACTION_COOLING; } + } else if (this->heating_state_pin_ != nullptr || this->cooling_state_pin_ != nullptr) { + // Use state from input pins + if (this->heating_state_) { + target_action = climate::CLIMATE_ACTION_HEATING; + } else if (this->cooling_state_) { + target_action = climate::CLIMATE_ACTION_COOLING; + } } else { // Fallback to active state calc based on temp and hysteresis const float temp_diff = this->target_temperature - this->current_temperature; diff --git a/esphome/components/tuya/climate/tuya_climate.h b/esphome/components/tuya/climate/tuya_climate.h index f015bc337c..f1a0c13a77 100644 --- a/esphome/components/tuya/climate/tuya_climate.h +++ b/esphome/components/tuya/climate/tuya_climate.h @@ -10,6 +10,7 @@ namespace tuya { class TuyaClimate : public climate::Climate, public Component { public: void setup() override; + void loop() override; void dump_config() override; void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } @@ -17,6 +18,8 @@ class TuyaClimate : public climate::Climate, public Component { void set_active_state_id(uint8_t state_id) { this->active_state_id_ = state_id; } void set_active_state_heating_value(uint8_t value) { this->active_state_heating_value_ = value; } void set_active_state_cooling_value(uint8_t value) { this->active_state_cooling_value_ = value; } + void set_heating_state_pin(GPIOPin *pin) { this->heating_state_pin_ = pin; } + void set_cooling_state_pin(GPIOPin *pin) { this->cooling_state_pin_ = pin; } void set_target_temperature_id(uint8_t target_temperature_id) { this->target_temperature_id_ = target_temperature_id; } @@ -51,12 +54,16 @@ class TuyaClimate : public climate::Climate, public Component { optional active_state_id_{}; optional active_state_heating_value_{}; optional active_state_cooling_value_{}; + GPIOPin *heating_state_pin_{nullptr}; + GPIOPin *cooling_state_pin_{nullptr}; optional target_temperature_id_{}; optional current_temperature_id_{}; float current_temperature_multiplier_{1.0f}; float target_temperature_multiplier_{1.0f}; float hysteresis_{1.0f}; uint8_t active_state_; + bool heating_state_{false}; + bool cooling_state_{false}; }; } // namespace tuya From d281e59f3a317019dbf41d742458c4e42f3b3800 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Wed, 15 Sep 2021 08:40:52 -0300 Subject: [PATCH 1354/1841] ac_dimmer increase gate time for robotdyn (#1708) * ac_dimmer increate gate time for robotdyn * add explanation on longer gate enable time --- esphome/components/ac_dimmer/ac_dimmer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index 9a71f0e224..e86703bfe1 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -17,7 +17,10 @@ static AcDimmerDataStore *all_dimmers[32]; // NOLINT(cppcoreguidelines-avoid-no /// Time in microseconds the gate should be held high /// 10µs should be long enough for most triacs /// For reference: BT136 datasheet says 2µs nominal (page 7) -static const uint32_t GATE_ENABLE_TIME = 10; +/// However other factors like gate driver propagation time +/// are also considered and a really low value is not important +/// See also: https://github.com/esphome/issues/issues/1632 +static const uint32_t GATE_ENABLE_TIME = 50; /// Function called from timer interrupt /// Input is current time in microseconds (micros()) From 607ddaa632790b75d6a26f2bf5ceb17855ca51f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Sep 2021 18:02:28 +0200 Subject: [PATCH 1355/1841] Bump aioesphomeapi from 9.0.0 to 9.1.0 (#2306) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5ab68beb2e..64ffd366e1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,4 @@ platformio==5.2.0 esptool==3.1 click==8.0.1 esphome-dashboard==20210908.0 -aioesphomeapi==9.0.0 +aioesphomeapi==9.1.0 From 6366ff6421d7cf807df66bc508891df33137786c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Sep 2021 18:02:41 +0200 Subject: [PATCH 1356/1841] Bump black from 21.8b0 to 21.9b0 (#2305) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 59085b33e2..bb735193f0 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==2.10.2 flake8==3.9.2 -black==21.8b0 +black==21.9b0 pexpect==4.8.0 pre-commit From c6dc8a11e24f9332bc6557c8733fd12d45ed3dc2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 15 Sep 2021 19:01:31 +0200 Subject: [PATCH 1357/1841] Add namespace to all PlatformIO library references (#2296) * Remove unnecessary duplication in platformio.ini * Add namespace to all platformio library references * Add cmake-build-* to gitignore They're generated by the CLion add-on for each PlatformIO environment. Listing them all separately seems nonsensical. --- .gitignore | 5 +-- esphome/components/async_tcp/__init__.py | 2 +- esphome/components/fastled_base/__init__.py | 2 +- esphome/components/gps/__init__.py | 2 +- esphome/components/hm3301/sensor.py | 2 +- esphome/components/json/__init__.py | 2 +- esphome/components/mqtt/__init__.py | 2 +- esphome/components/neopixelbus/light.py | 2 +- esphome/components/tm1651/__init__.py | 2 +- platformio.ini | 46 +++++++++------------ 10 files changed, 29 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index 92d92e4b3b..cd7822bded 100644 --- a/.gitignore +++ b/.gitignore @@ -102,10 +102,7 @@ CMakeLists.txt .idea/**/dynamic.xml # CMake -cmake-build-debug/ -cmake-build-livingroom8266/ -cmake-build-livingroom32/ -cmake-build-release/ +cmake-build-*/ CMakeCache.txt CMakeFiles diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index 8938dc4671..22d4bb1fcb 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -12,4 +12,4 @@ async def to_code(config): cg.add_library("esphome/AsyncTCP-esphome", "1.2.2") elif CORE.is_esp8266: # https://github.com/OttoWinter/ESPAsyncTCP - cg.add_library("ESPAsyncTCP-esphome", "1.2.3") + cg.add_library("ottowinter/ESPAsyncTCP-esphome", "1.2.3") diff --git a/esphome/components/fastled_base/__init__.py b/esphome/components/fastled_base/__init__.py index f2d0bb1f38..62de036e62 100644 --- a/esphome/components/fastled_base/__init__.py +++ b/esphome/components/fastled_base/__init__.py @@ -44,5 +44,5 @@ async def new_fastled_light(config): # https://github.com/FastLED/FastLED/blob/master/library.json # 3.3.3 has an issue on ESP32 with RMT and fastled_clockless: # https://github.com/esphome/issues/issues/1375 - cg.add_library("FastLED", "3.3.2") + cg.add_library("fastled/FastLED", "3.3.2") return var diff --git a/esphome/components/gps/__init__.py b/esphome/components/gps/__init__.py index 0d5c3f3f3d..9d323e6e4d 100644 --- a/esphome/components/gps/__init__.py +++ b/esphome/components/gps/__init__.py @@ -99,4 +99,4 @@ async def to_code(config): cg.add(var.set_satellites_sensor(sens)) # https://platformio.org/lib/show/1655/TinyGPSPlus - cg.add_library("1655", "1.0.2") # TinyGPSPlus, has name conflict + cg.add_library("mikalhart/TinyGPSPlus", "1.0.2") diff --git a/esphome/components/hm3301/sensor.py b/esphome/components/hm3301/sensor.py index 7cd81fec1d..2c3c2f7221 100644 --- a/esphome/components/hm3301/sensor.py +++ b/esphome/components/hm3301/sensor.py @@ -110,4 +110,4 @@ async def to_code(config): cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE])) # https://platformio.org/lib/show/6306/Grove%20-%20Laser%20PM2.5%20Sensor%20HM3301 - cg.add_library("6306", "1.0.3") + cg.add_library("seeed-studio/Grove - Laser PM2.5 Sensor HM3301", "1.0.3") diff --git a/esphome/components/json/__init__.py b/esphome/components/json/__init__.py index 7bdef6ea0e..9879754d9e 100644 --- a/esphome/components/json/__init__.py +++ b/esphome/components/json/__init__.py @@ -7,6 +7,6 @@ json_ns = cg.esphome_ns.namespace("json") @coroutine_with_priority(1.0) async def to_code(config): - cg.add_library("ArduinoJson-esphomelib", "5.13.3") + cg.add_library("ottowinter/ArduinoJson-esphomelib", "5.13.3") cg.add_define("USE_JSON") cg.add_global(json_ns.using) diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 56ea9027af..19f78641df 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -214,7 +214,7 @@ async def to_code(config): await cg.register_component(var, config) # https://github.com/OttoWinter/async-mqtt-client/blob/master/library.json - cg.add_library("AsyncMqttClient-esphome", "0.8.4") + cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.4") cg.add_define("USE_MQTT") cg.add_global(mqtt_ns.using) diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index 575f69b731..5eee5210f9 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -205,4 +205,4 @@ async def to_code(config): cg.add(var.set_pixel_order(getattr(ESPNeoPixelOrder, config[CONF_TYPE]))) # https://github.com/Makuna/NeoPixelBus/blob/master/library.json - cg.add_library("NeoPixelBus", "2.6.7") + cg.add_library("makuna/NeoPixelBus", "2.6.7") diff --git a/esphome/components/tm1651/__init__.py b/esphome/components/tm1651/__init__.py index f67a9f4512..d06c1bedde 100644 --- a/esphome/components/tm1651/__init__.py +++ b/esphome/components/tm1651/__init__.py @@ -50,7 +50,7 @@ async def to_code(config): cg.add(var.set_dio_pin(dio_pin)) # https://platformio.org/lib/show/6865/TM1651 - cg.add_library("6865", "1.0.1") + cg.add_library("freekode/TM1651", "1.0.1") BINARY_OUTPUT_ACTION_SCHEMA = maybe_simple_id( diff --git a/platformio.ini b/platformio.ini index 174832b067..89e9ce9374 100644 --- a/platformio.ini +++ b/platformio.ini @@ -4,7 +4,7 @@ ; It's *not* used during runtime. [platformio] -default_envs = esp8266 +default_envs = esp8266, esp32 src_dir = . include_dir = @@ -26,19 +26,18 @@ build_flags = [common] lib_deps = - AsyncMqttClient-esphome@0.8.4 - ArduinoJson-esphomelib@5.13.3 - esphome/ESPAsyncWebServer-esphome@1.3.0 - FastLED@3.3.2 - NeoPixelBus@2.6.7 - 1655@1.0.2 ; TinyGPSPlus (has name conflict) - 6865@1.0.0 ; TM1651 Battery Display - 6306@1.0.3 ; HM3301 - glmnet/Dsmr@0.5 ; used by dsmr - rweather/Crypto@0.2.0 ; used by dsmr - esphome/noise-c@0.1.1 ; used by api - dudanov/MideaUART@1.1.0 ; used by midea - + ottowinter/AsyncMqttClient-esphome@0.8.4 ; mqtt + ottowinter/ArduinoJson-esphomelib@5.13.3 ; json + esphome/ESPAsyncWebServer-esphome@1.3.0 ; web_server_base + fastled/FastLED@3.3.2 ; fastled_base + makuna/NeoPixelBus@2.6.7 ; neopixelbus + mikalhart/TinyGPSPlus@1.0.2 ; gps + freekode/TM1651@1.0.1 ; tm1651 + seeed-studio/Grove - Laser PM2.5 Sensor HM3301@1.0.3 ; hm3301 + glmnet/Dsmr@0.5 ; dsmr + rweather/Crypto@0.2.0 ; dsmr + esphome/noise-c@0.1.1 ; api + dudanov/MideaUART@1.1.0 ; midea build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = @@ -47,30 +46,25 @@ src_filter = +<.temp/all-include.cpp> [common:esp8266] +extends = common platform = platformio/espressif8266@3.1.0 framework = arduino board = nodemcuv2 lib_deps = ${common.lib_deps} - ESP8266WiFi - ESPAsyncTCP-esphome@1.2.3 - Update -build_flags = ${common.build_flags} -src_filter = ${common.src_filter} + ESP8266WiFi ; wifi (Arduino built-in) + Update ; ota (Arduino built-in) + ottowinter/ESPAsyncTCP-esphome@1.2.3 ; async_tcp [common:esp32] +extends = common platform = platformio/espressif32@3.2.0 framework = arduino board = nodemcu-32s lib_deps = ${common.lib_deps} - esphome/AsyncTCP-esphome@1.2.2 - Update -build_flags = - ${common.build_flags} -src_filter = - ${common.src_filter} - - + Hash ; ota (Arduino built-in) + esphome/AsyncTCP-esphome@1.2.2 ; async_tcp [env:esp8266] extends = common:esp8266 From c69b88bb554694c4059057717c10ee45f46580df Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 15 Sep 2021 19:10:42 +0200 Subject: [PATCH 1358/1841] Fix platformio.ini parser used by container build --- docker/platformio_install_deps.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/docker/platformio_install_deps.py b/docker/platformio_install_deps.py index 6f3e9f28d5..6b5860ffc5 100755 --- a/docker/platformio_install_deps.py +++ b/docker/platformio_install_deps.py @@ -3,18 +3,11 @@ # all platformio libraries in the global storage import configparser -import re import subprocess import sys -config = configparser.ConfigParser() +config = configparser.ConfigParser(inline_comment_prefixes=(';', )) config.read(sys.argv[1]) -libs = [] -for line in config['common']['lib_deps'].splitlines(): - # Format: '1655@1.0.2 ; TinyGPSPlus (has name conflict)' (includes comment) - m = re.search(r'([a-zA-Z0-9-_/]+@[0-9\.]+)', line) - if m is None: - continue - libs.append(m.group(1)) +libs = [x for x in config['common']['lib_deps'].splitlines() if len(x) != 0] -subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs]) +subprocess.check_call(['platformio', 'lib', '-g', 'uninstall', *libs]) From aed140d80208032ecb6d3d3358b6c48b7dee95be Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 15 Sep 2021 19:13:30 +0200 Subject: [PATCH 1359/1841] Fix typo --- docker/platformio_install_deps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/platformio_install_deps.py b/docker/platformio_install_deps.py index 6b5860ffc5..5625bd4d01 100755 --- a/docker/platformio_install_deps.py +++ b/docker/platformio_install_deps.py @@ -10,4 +10,4 @@ config = configparser.ConfigParser(inline_comment_prefixes=(';', )) config.read(sys.argv[1]) libs = [x for x in config['common']['lib_deps'].splitlines() if len(x) != 0] -subprocess.check_call(['platformio', 'lib', '-g', 'uninstall', *libs]) +subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs]) From 8f3a739da77e06b5ef18115248ee469ed0caa95f Mon Sep 17 00:00:00 2001 From: Matthew Mazzanti Date: Wed, 15 Sep 2021 13:59:58 -0400 Subject: [PATCH 1360/1841] Allow transforms and flashes to not update remote_values (#2313) --- esphome/components/light/light_call.cpp | 4 ++-- esphome/components/light/light_state.cpp | 14 ++++++++++---- esphome/components/light/light_state.h | 4 ++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 178a78a94c..dc1e7d39fb 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -77,7 +77,7 @@ void LightCall::perform() { ESP_LOGD(TAG, " Flash length: %.1fs", *this->flash_length_ / 1e3f); } - this->parent_->start_flash_(v, *this->flash_length_); + this->parent_->start_flash_(v, *this->flash_length_, this->publish_); } else if (this->has_transition_()) { // TRANSITION if (this->publish_) { @@ -92,7 +92,7 @@ void LightCall::perform() { this->parent_->stop_effect_(); } - this->parent_->start_transition_(v, *this->transition_length_); + this->parent_->start_transition_(v, *this->transition_length_, this->publish_); } else if (this->has_effect_()) { // EFFECT diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 945d3910d5..d7f539a8d8 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -228,13 +228,16 @@ void LightState::stop_effect_() { this->active_effect_index_ = 0; } -void LightState::start_transition_(const LightColorValues &target, uint32_t length) { +void LightState::start_transition_(const LightColorValues &target, uint32_t length, bool set_remote_values) { this->transformer_ = this->output_->create_default_transition(); this->transformer_->setup(this->current_values, target, length); - this->remote_values = target; + + if (set_remote_values) { + this->remote_values = target; + } } -void LightState::start_flash_(const LightColorValues &target, uint32_t length) { +void LightState::start_flash_(const LightColorValues &target, uint32_t length, bool set_remote_values) { LightColorValues end_colors = this->remote_values; // If starting a flash if one is already happening, set end values to end values of current flash // Hacky but works @@ -243,7 +246,10 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length) { this->transformer_ = make_unique(*this); this->transformer_->setup(end_colors, target, length); - this->remote_values = target; + + if (set_remote_values) { + this->remote_values = target; + }; } void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) { diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index dd42aa76db..f73a4c3b17 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -154,10 +154,10 @@ class LightState : public Nameable, public Component { /// Internal method to stop the current effect (if one is active). void stop_effect_(); /// Internal method to start a transition to the target color with the given length. - void start_transition_(const LightColorValues &target, uint32_t length); + void start_transition_(const LightColorValues &target, uint32_t length, bool set_remote_values); /// Internal method to start a flash for the specified amount of time. - void start_flash_(const LightColorValues &target, uint32_t length); + void start_flash_(const LightColorValues &target, uint32_t length, bool set_remote_values); /// Internal method to set the color values to target immediately (with no transition). void set_immediately_(const LightColorValues &target, bool set_remote_values); From 0f4a7bf1f548f8fe338e8aee188e93d9da064578 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 16 Sep 2021 09:12:01 +1200 Subject: [PATCH 1361/1841] Bump version to 2021.9.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 2cd6d05da5..5a5351f1b0 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.0b5" +__version__ = "2021.9.0" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 2e49039c0187dac827292dae23d1c73786f8b0f7 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 19 Sep 2021 14:20:35 +0200 Subject: [PATCH 1362/1841] Reduce stale/lock gh actions interval (#2341) --- .github/stale.yml | 59 ------------------------------------- .github/workflows/lock.yml | 2 +- .github/workflows/stale.yml | 2 +- 3 files changed, 2 insertions(+), 61 deletions(-) delete mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 225c029bd9..0000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,59 +0,0 @@ -# Configuration for probot-stale - https://github.com/probot/stale - -# Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 60 - -# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. -# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: 7 - -# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) -onlyLabels: [] - -# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable -exemptLabels: - - not-stale - -# Set to true to ignore issues in a project (defaults to false) -exemptProjects: false - -# Set to true to ignore issues in a milestone (defaults to false) -exemptMilestones: true - -# Set to true to ignore issues with an assignee (defaults to false) -exemptAssignees: false - -# Label to use when marking as stale -staleLabel: stale - -# Comment to post when marking as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. - -# Comment to post when removing the stale label. -# unmarkComment: > -# Your comment here. - -# Comment to post when closing a stale Issue or Pull Request. -# closeComment: > -# Your comment here. - -# Limit the number of actions per hour, from 1-30. Default is 30 -limitPerRun: 10 - -# Limit to only `issues` or `pulls` -only: pulls - -# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': -# pulls: -# daysUntilStale: 30 -# markComment: > -# This pull request has been automatically marked as stale because it has not had -# recent activity. It will be closed if no further activity occurs. Thank you -# for your contributions. - -# issues: -# exemptLabels: -# - confirmed diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index aedb9dd9b3..375b8f1db4 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -2,7 +2,7 @@ name: Lock on: schedule: - - cron: "0 * * * *" + - cron: '30 0 * * *' workflow_dispatch: permissions: diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index f26dd32929..712ae1a289 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -2,7 +2,7 @@ name: Stale on: schedule: - - cron: "0 * * * *" + - cron: '30 0 * * *' workflow_dispatch: permissions: From 64341d1d1887f16df87b88266402c15a109ac4e1 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Mon, 20 Sep 2021 04:30:41 +1200 Subject: [PATCH 1363/1841] Fix MQTT discovery for sensor state_class (#2331) --- esphome/components/sensor/sensor.cpp | 8 ++++---- esphome/components/sensor/sensor.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 128f36fc93..0dc0275715 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -6,15 +6,15 @@ namespace sensor { static const char *const TAG = "sensor"; -const LogString *state_class_to_string(StateClass state_class) { +std::string state_class_to_string(StateClass state_class) { switch (state_class) { case STATE_CLASS_MEASUREMENT: - return LOG_STR("measurement"); + return "measurement"; case STATE_CLASS_TOTAL_INCREASING: - return LOG_STR("total_increasing"); + return "total_increasing"; case STATE_CLASS_NONE: default: - return LOG_STR(""); + return ""; } } diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 3fc993cb2c..d284f931b1 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -14,7 +14,7 @@ namespace sensor { if (!(obj)->get_device_class().empty()) { \ ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ } \ - ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, LOG_STR_ARG(state_class_to_string((obj)->get_state_class()))); \ + ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->get_state_class()).c_str()); \ ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \ ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \ if (!(obj)->get_icon().empty()) { \ @@ -37,7 +37,7 @@ enum StateClass : uint8_t { STATE_CLASS_TOTAL_INCREASING = 2, }; -const LogString *state_class_to_string(StateClass state_class); +std::string state_class_to_string(StateClass state_class); /** Base-class for all sensors. * From 68d547595ee2177319af3e2572e07da4798331c6 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 19 Sep 2021 18:31:20 +0200 Subject: [PATCH 1364/1841] Light transition fixes (#2320) --- esphome/components/light/addressable_light.cpp | 11 ++++++----- esphome/components/light/light_state.cpp | 3 +++ esphome/components/light/transformers.h | 8 +++++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index a8fa2cd7ac..599d43d8b1 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -62,10 +62,13 @@ void AddressableLightTransformer::start() { } optional AddressableLightTransformer::apply() { - // Don't try to transition over running effects, instead immediately use the target values. write_state() and the - // effects pick up the change from current_values. + float smoothed_progress = LightTransitionTransformer::smoothed_progress(this->get_progress_()); + + // When running an output-buffer modifying effect, don't try to transition individual LEDs, but instead just fade the + // LightColorValues. write_state() then picks up the change in brightness, and the color change is picked up by the + // effects which respect it. if (this->light_.is_effect_active()) - return this->target_values_; + return LightColorValues::lerp(this->get_start_values(), this->get_target_values(), smoothed_progress); // Use a specialized transition for addressable lights: instead of using a unified transition for // all LEDs, we use the current state of each LED as the start. @@ -75,8 +78,6 @@ optional AddressableLightTransformer::apply() { // Instead, we "fake" the look of the LERP by using an exponential average over time and using // dynamically-calculated alpha values to match the look. - float smoothed_progress = LightTransitionTransformer::smoothed_progress(this->get_progress_()); - float denom = (1.0f - smoothed_progress); float alpha = denom == 0.0f ? 0.0f : (smoothed_progress - this->last_transition_progress_) / denom; diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index d7f539a8d8..398546a09f 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -121,6 +121,9 @@ void LightState::loop() { } if (this->transformer_->is_finished()) { + // if the transition has written directly to the output, current_values is outdated, so update it + this->current_values = this->transformer_->get_target_values(); + this->transformer_->stop(); this->transformer_ = nullptr; this->target_state_reached_callback_.call(); diff --git a/esphome/components/light/transformers.h b/esphome/components/light/transformers.h index d501d53f72..90646f4e61 100644 --- a/esphome/components/light/transformers.h +++ b/esphome/components/light/transformers.h @@ -12,12 +12,18 @@ namespace light { class LightTransitionTransformer : public LightTransformer { public: void start() override { - // When turning light on from off state, use colors from target state. + // When turning light on from off state, use target state and only increase brightness from zero. if (!this->start_values_.is_on() && this->target_values_.is_on()) { this->start_values_ = LightColorValues(this->target_values_); this->start_values_.set_brightness(0.0f); } + // When turning light off from on state, use source state and only decrease brightness to zero. + if (this->start_values_.is_on() && !this->target_values_.is_on()) { + this->target_values_ = LightColorValues(this->start_values_); + this->target_values_.set_brightness(0.0f); + } + // When changing color mode, go through off state, as color modes are orthogonal and there can't be two active. if (this->start_values_.get_color_mode() != this->target_values_.get_color_mode()) { this->changing_color_mode_ = true; From f76685fccf50f5bf64de80dd5b2a8295353f1798 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 19 Sep 2021 18:31:31 +0200 Subject: [PATCH 1365/1841] Cease using deprecated Cover methods in automations (#2326) --- esphome/components/cover/automation.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index 0092f987f2..0b364e1e09 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -11,7 +11,7 @@ template class OpenAction : public Action { public: explicit OpenAction(Cover *cover) : cover_(cover) {} - void play(Ts... x) override { this->cover_->open(); } + void play(Ts... x) override { this->cover_->make_call().set_command_open().perform(); } protected: Cover *cover_; @@ -21,7 +21,7 @@ template class CloseAction : public Action { public: explicit CloseAction(Cover *cover) : cover_(cover) {} - void play(Ts... x) override { this->cover_->close(); } + void play(Ts... x) override { this->cover_->make_call().set_command_close().perform(); } protected: Cover *cover_; @@ -31,7 +31,7 @@ template class StopAction : public Action { public: explicit StopAction(Cover *cover) : cover_(cover) {} - void play(Ts... x) override { this->cover_->stop(); } + void play(Ts... x) override { this->cover_->make_call().set_command_stop().perform(); } protected: Cover *cover_; From 30eca885c9c55c92fa2d3b62dbe627b762443e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Sun, 19 Sep 2021 18:46:17 +0200 Subject: [PATCH 1366/1841] Add `esp8266_disable_ssl_support:` config option (#2236) --- esphome/components/http_request/__init__.py | 6 ++++++ esphome/components/http_request/http_request.cpp | 2 ++ esphome/components/http_request/http_request.h | 5 +++++ esphome/const.py | 1 + esphome/core/defines.h | 1 + 5 files changed, 15 insertions(+) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index 7dffdae27f..eea2f0d407 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_METHOD, CONF_TRIGGER_ID, CONF_URL, + CONF_ESP8266_DISABLE_SSL_SUPPORT, ) from esphome.core import CORE, Lambda @@ -71,6 +72,9 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(): cv.declare_id(HttpRequestComponent), cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string, cv.Optional(CONF_TIMEOUT, default="5s"): cv.positive_time_period_milliseconds, + cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All( + cv.only_on_esp8266, cv.boolean + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -98,6 +102,8 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_timeout(config[CONF_TIMEOUT])) cg.add(var.set_useragent(config[CONF_USERAGENT])) + if CORE.is_esp8266 and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]: + cg.add_define("USE_HTTP_REQUEST_ESP8266_HTTPS") await cg.register_component(var, config) diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index 0d4e425147..3f5bf6da87 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -81,6 +81,7 @@ void HttpRequestComponent::send(const std::vector #ifdef ARDUINO_ARCH_ESP8266 std::shared_ptr HttpRequestComponent::get_wifi_client_() { +#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS if (this->secure_) { if (this->wifi_client_secure_ == nullptr) { this->wifi_client_secure_ = std::make_shared(); @@ -89,6 +90,7 @@ std::shared_ptr HttpRequestComponent::get_wifi_client_() { } return this->wifi_client_secure_; } +#endif if (this->wifi_client_ == nullptr) { this->wifi_client_ = std::make_shared(); diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index 740a4580b4..fa29f8aa3a 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -3,6 +3,7 @@ #include "esphome/components/json/json_util.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include #include #include @@ -13,8 +14,10 @@ #endif #ifdef ARDUINO_ARCH_ESP8266 #include +#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS #include #endif +#endif namespace esphome { namespace http_request { @@ -53,7 +56,9 @@ class HttpRequestComponent : public Component { std::list
    headers_; #ifdef ARDUINO_ARCH_ESP8266 std::shared_ptr wifi_client_; +#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS std::shared_ptr wifi_client_secure_; +#endif std::shared_ptr get_wifi_client_(); #endif }; diff --git a/esphome/const.py b/esphome/const.py index 92d18cde3a..a74068ab77 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -208,6 +208,7 @@ CONF_ENABLE_PIN = "enable_pin" CONF_ENABLE_TIME = "enable_time" CONF_ENERGY = "energy" CONF_ENTITY_ID = "entity_id" +CONF_ESP8266_DISABLE_SSL_SUPPORT = "esp8266_disable_ssl_support" CONF_ESP8266_RESTORE_FROM_FLASH = "esp8266_restore_from_flash" CONF_ESPHOME = "esphome" CONF_ETHERNET = "ethernet" diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 45bb3b82a5..e3976d378e 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -20,6 +20,7 @@ #define USE_ESP8266_PREFERENCES_FLASH #define USE_FAN #define USE_HOMEASSISTANT_TIME +#define USE_HTTP_REQUEST_ESP8266_HTTPS #define USE_I2C_MULTIPLEXER #define USE_JSON #define USE_LIGHT From 50da6308110d89b20bf4b1366c1fe296da7a23ca Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 19 Sep 2021 18:46:26 +0200 Subject: [PATCH 1367/1841] Apply color brightness to addressable light effects (#2321) --- esphome/components/light/addressable_light.cpp | 6 +++--- esphome/components/light/addressable_light.h | 3 +++ esphome/components/light/addressable_light_effect.h | 7 ++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index 599d43d8b1..f3e6c0ef1d 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -27,7 +27,7 @@ std::unique_ptr AddressableLight::create_default_transition() return make_unique(*this); } -Color esp_color_from_light_color_values(LightColorValues val) { +Color color_from_light_color_values(LightColorValues val) { auto r = to_uint8_scale(val.get_color_brightness() * val.get_red()); auto g = to_uint8_scale(val.get_color_brightness() * val.get_green()); auto b = to_uint8_scale(val.get_color_brightness() * val.get_blue()); @@ -44,7 +44,7 @@ void AddressableLight::update_state(LightState *state) { return; // don't use LightState helper, gamma correction+brightness is handled by ESPColorView - this->all() = esp_color_from_light_color_values(val); + this->all() = color_from_light_color_values(val); this->schedule_show(); } @@ -54,7 +54,7 @@ void AddressableLightTransformer::start() { return; auto end_values = this->target_values_; - this->target_color_ = esp_color_from_light_color_values(end_values); + this->target_color_ = color_from_light_color_values(end_values); // our transition will handle brightness, disable brightness in correction. this->light_.correction_.set_local_brightness(255); diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index bba2158457..97f4a4687d 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -19,6 +19,9 @@ namespace light { using ESPColor ESPDEPRECATED("esphome::light::ESPColor is deprecated, use esphome::Color instead.", "v1.21") = Color; +/// Convert the color information from a `LightColorValues` object to a `Color` object (does not apply brightness). +Color color_from_light_color_values(LightColorValues val); + class AddressableLight : public LightOutput, public Component { public: virtual int32_t size() const = 0; diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index 1cb29dfa4e..358fe69c23 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -38,11 +38,8 @@ class AddressableLightEffect : public LightEffect { void stop() override { this->get_addressable_()->set_effect_active(false); } virtual void apply(AddressableLight &it, const Color ¤t_color) = 0; void apply() override { - LightColorValues color = this->state_->remote_values; - // not using any color correction etc. that will be handled by the addressable layer - Color current_color = - Color(static_cast(color.get_red() * 255), static_cast(color.get_green() * 255), - static_cast(color.get_blue() * 255), static_cast(color.get_white() * 255)); + // not using any color correction etc. that will be handled by the addressable layer through ESPColorCorrection + Color current_color = color_from_light_color_values(this->state_->remote_values); this->apply(*this->get_addressable_(), current_color); } From dbb195691b0ad784ac84d0273b48a69d31bad27c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Sep 2021 19:22:28 +0200 Subject: [PATCH 1368/1841] Bump pylint from 2.10.2 to 2.11.1 (#2334) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Otto winter --- esphome/__main__.py | 14 +-- esphome/components/binary_sensor/__init__.py | 16 ++-- esphome/components/deep_sleep/__init__.py | 3 +- .../components/esp32_ble_beacon/__init__.py | 4 +- .../components/esp32_ble_tracker/__init__.py | 13 +-- esphome/components/font/__init__.py | 5 +- esphome/components/lcd_gpio/display.py | 3 +- esphome/components/ledc/output.py | 6 +- esphome/components/light/effects.py | 6 +- esphome/components/logger/__init__.py | 8 +- esphome/components/mqtt/__init__.py | 7 +- esphome/components/neopixelbus/light.py | 8 +- esphome/components/nextion/base_component.py | 6 +- esphome/components/ntc/sensor.py | 4 +- esphome/components/packages/__init__.py | 3 +- esphome/components/partition/light.py | 3 +- esphome/components/pmsx003/sensor.py | 4 +- esphome/components/remote_base/__init__.py | 16 ++-- esphome/components/rotary_encoder/sensor.py | 3 +- esphome/components/sensor/__init__.py | 6 +- esphome/components/substitutions/__init__.py | 7 +- esphome/components/time/__init__.py | 36 +++----- esphome/components/wifi/wpa2_eap.py | 1 + esphome/config.py | 76 ++++++---------- esphome/config_validation.py | 89 +++++++------------ esphome/core/__init__.py | 18 ++-- esphome/core/config.py | 15 +--- esphome/cpp_generator.py | 12 ++- esphome/cpp_helpers.py | 4 +- esphome/dashboard/dashboard.py | 46 +++++----- esphome/espota2.py | 6 +- esphome/helpers.py | 8 +- esphome/mqtt.py | 9 +- esphome/pins.py | 15 ++-- esphome/platformio_api.py | 16 ++-- esphome/storage_json.py | 4 +- esphome/wizard.py | 84 ++++++----------- esphome/writer.py | 10 +-- esphome/yaml_util.py | 7 +- requirements_test.txt | 2 +- 40 files changed, 219 insertions(+), 384 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 121fa7cc9e..ba1019869d 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -72,7 +72,7 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api if default == "OTA": return CORE.address if show_mqtt and "mqtt" in CORE.config: - options.append(("MQTT ({})".format(CORE.config["mqtt"][CONF_BROKER]), "MQTT")) + options.append((f"MQTT ({CORE.config['mqtt'][CONF_BROKER]})", "MQTT")) if default == "OTA": return "MQTT" if default is not None: @@ -415,30 +415,30 @@ def command_update_all(args): click.echo(f"{half_line}{middle_text}{half_line}") for f in files: - print("Updating {}".format(color(Fore.CYAN, f))) + print(f"Updating {color(Fore.CYAN, f)}") print("-" * twidth) print() rc = run_external_process( "esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA" ) if rc == 0: - print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f)) + print_bar(f"[{color(Fore.BOLD_GREEN, 'SUCCESS')}] {f}") success[f] = True else: - print_bar("[{}] {}".format(color(Fore.BOLD_RED, "ERROR"), f)) + print_bar(f"[{color(Fore.BOLD_RED, 'ERROR')}] {f}") success[f] = False print() print() print() - print_bar("[{}]".format(color(Fore.BOLD_WHITE, "SUMMARY"))) + print_bar(f"[{color(Fore.BOLD_WHITE, 'SUMMARY')}]") failed = 0 for f in files: if success[f]: - print(" - {}: {}".format(f, color(Fore.GREEN, "SUCCESS"))) + print(f" - {f}: {color(Fore.GREEN, 'SUCCESS')}") else: - print(" - {}: {}".format(f, color(Fore.BOLD_RED, "FAILED"))) + print(f" - {f}: {color(Fore.BOLD_RED, 'FAILED')}") failed += 1 return failed diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 9cd2a045ff..60ac4303a7 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -231,17 +231,16 @@ def parse_multi_click_timing_str(value): parts = value.lower().split(" ") if len(parts) != 5: raise cv.Invalid( - "Multi click timing grammar consists of exactly 5 words, not {}" - "".format(len(parts)) + f"Multi click timing grammar consists of exactly 5 words, not {len(parts)}" ) try: state = cv.boolean(parts[0]) except cv.Invalid: # pylint: disable=raise-missing-from - raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0])) + raise cv.Invalid(f"First word must either be ON or OFF, not {parts[0]}") if parts[1] != "for": - raise cv.Invalid("Second word must be 'for', got {}".format(parts[1])) + raise cv.Invalid(f"Second word must be 'for', got {parts[1]}") if parts[2] == "at": if parts[3] == "least": @@ -250,8 +249,7 @@ def parse_multi_click_timing_str(value): key = CONF_MAX_LENGTH else: raise cv.Invalid( - "Third word after at must either be 'least' or 'most', got {}" - "".format(parts[3]) + f"Third word after at must either be 'least' or 'most', got {parts[3]}" ) try: length = cv.positive_time_period_milliseconds(parts[4]) @@ -296,13 +294,11 @@ def validate_multi_click_timing(value): new_state = v_.get(CONF_STATE, not state) if new_state == state: raise cv.Invalid( - "Timings must have alternating state. Indices {} and {} have " - "the same state {}".format(i, i + 1, state) + f"Timings must have alternating state. Indices {i} and {i + 1} have the same state {state}" ) if max_length is not None and max_length < min_length: raise cv.Invalid( - "Max length ({}) must be larger than min length ({})." - "".format(max_length, min_length) + f"Max length ({max_length}) must be larger than min length ({min_length})." ) state = new_state diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index b7bf27e79a..3f59ad52cf 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -16,8 +16,7 @@ def validate_pin_number(value): valid_pins = [0, 2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39] if value[CONF_NUMBER] not in valid_pins: raise cv.Invalid( - "Only pins {} support wakeup" - "".format(", ".join(str(x) for x in valid_pins)) + f"Only pins {', '.join(str(x) for x in valid_pins)} support wakeup" ) return value diff --git a/esphome/components/esp32_ble_beacon/__init__.py b/esphome/components/esp32_ble_beacon/__init__.py index 3e00692d3a..cd0cb4bed6 100644 --- a/esphome/components/esp32_ble_beacon/__init__.py +++ b/esphome/components/esp32_ble_beacon/__init__.py @@ -24,9 +24,7 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): uuid = config[CONF_UUID].hex - uuid_arr = [ - cg.RawExpression("0x{}".format(uuid[i : i + 2])) for i in range(0, len(uuid), 2) - ] + uuid_arr = [cg.RawExpression(f"0x{uuid[i:i + 2]}") for i in range(0, len(uuid), 2)] var = cg.new_Pvariable(config[CONF_ID], uuid_arr) await cg.register_component(var, config) cg.add(var.set_major(config[CONF_MAJOR])) diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index fec0f6dcfb..162a4dee89 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -51,8 +51,7 @@ def validate_scan_parameters(config): if window > interval: raise cv.Invalid( - "Scan window ({}) needs to be smaller than scan interval ({})" - "".format(window, interval) + f"Scan window ({window}) needs to be smaller than scan interval ({interval})" ) if interval.total_milliseconds * 3 > duration.total_milliseconds: @@ -97,9 +96,7 @@ def bt_uuid(value): ) return value raise cv.Invalid( - "Service UUID must be in 16 bit '{}', 32 bit '{}', or 128 bit '{}' format".format( - bt_uuid16_format, bt_uuid32_format, bt_uuid128_format - ) + f"Service UUID must be in 16 bit '{bt_uuid16_format}', 32 bit '{bt_uuid32_format}', or 128 bit '{bt_uuid128_format}' format" ) @@ -112,9 +109,7 @@ def as_hex_array(value): cpp_array = [ f"0x{part}" for part in [value[i : i + 2] for i in range(0, len(value), 2)] ] - return cg.RawExpression( - "(uint8_t*)(const uint8_t[16]){{{}}}".format(",".join(cpp_array)) - ) + return cg.RawExpression(f"(uint8_t*)(const uint8_t[16]){{{','.join(cpp_array)}}}") def as_reversed_hex_array(value): @@ -123,7 +118,7 @@ def as_reversed_hex_array(value): f"0x{part}" for part in [value[i : i + 2] for i in range(0, len(value), 2)] ] return cg.RawExpression( - "(uint8_t*)(const uint8_t[16]){{{}}}".format(",".join(reversed(cpp_array))) + f"(uint8_t*)(const uint8_t[16]){{{','.join(reversed(cpp_array))}}}" ) diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 16a7d8a612..e47a6f38af 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -61,8 +61,7 @@ def validate_pillow_installed(value): def validate_truetype_file(value): if value.endswith(".zip"): # for Google Fonts downloads raise cv.Invalid( - "Please unzip the font archive '{}' first and then use the .ttf files " - "inside.".format(value) + f"Please unzip the font archive '{value}' first and then use the .ttf files inside." ) if not value.endswith(".ttf"): raise cv.Invalid( @@ -131,7 +130,7 @@ async def to_code(config): ("a_char", glyph), ( "data", - cg.RawExpression(str(prog_arr) + " + " + str(glyph_args[glyph][0])), + cg.RawExpression(f"{str(prog_arr)} + {str(glyph_args[glyph][0])}"), ), ("offset_x", glyph_args[glyph][1]), ("offset_y", glyph_args[glyph][2]), diff --git a/esphome/components/lcd_gpio/display.py b/esphome/components/lcd_gpio/display.py index 6b0a2f69de..9fb635eafa 100644 --- a/esphome/components/lcd_gpio/display.py +++ b/esphome/components/lcd_gpio/display.py @@ -20,8 +20,7 @@ GPIOLCDDisplay = lcd_gpio_ns.class_("GPIOLCDDisplay", lcd_base.LCDDisplay) def validate_pin_length(value): if len(value) != 4 and len(value) != 8: raise cv.Invalid( - "LCD Displays can either operate in 4-pin or 8-pin mode," - "not {}-pin mode".format(len(value)) + f"LCD Displays can either operate in 4-pin or 8-pin mode,not {len(value)}-pin mode" ) return value diff --git a/esphome/components/ledc/output.py b/esphome/components/ledc/output.py index 83b1c6f096..0f5f186ff5 100644 --- a/esphome/components/ledc/output.py +++ b/esphome/components/ledc/output.py @@ -28,13 +28,11 @@ def validate_frequency(value): max_freq = calc_max_frequency(1) if value < min_freq: raise cv.Invalid( - "This frequency setting is not possible, please choose a higher " - "frequency (at least {}Hz)".format(int(min_freq)) + f"This frequency setting is not possible, please choose a higher frequency (at least {int(min_freq)}Hz)" ) if value > max_freq: raise cv.Invalid( - "This frequency setting is not possible, please choose a lower " - "frequency (at most {}Hz)".format(int(max_freq)) + f"This frequency setting is not possible, please choose a lower frequency (at most {int(max_freq)}Hz)" ) return value diff --git a/esphome/components/light/effects.py b/esphome/components/light/effects.py index be558a8140..4b2209c833 100644 --- a/esphome/components/light/effects.py +++ b/esphome/components/light/effects.py @@ -490,8 +490,7 @@ def validate_effects(allowed_effects): if key not in allowed_effects: errors.append( cv.Invalid( - "The effect '{}' is not allowed for this " - "light type".format(key), + f"The effect '{key}' is not allowed for this light type", [i], ) ) @@ -500,8 +499,7 @@ def validate_effects(allowed_effects): if name in names: errors.append( cv.Invalid( - "Found the effect name '{}' twice. All effects must have " - "unique names".format(name), + f"Found the effect name '{name}' twice. All effects must have unique names", [i], ) ) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 55178941ec..b821e8c5d8 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -86,8 +86,7 @@ def validate_local_no_higher_than_global(value): for tag, level in value.get(CONF_LOGS, {}).items(): if LOG_LEVEL_SEVERITY.index(level) > LOG_LEVEL_SEVERITY.index(global_level): raise EsphomeError( - "The local log level {} for {} must be less severe than the " - "global log level {}.".format(level, tag, global_level) + f"The local log level {level} for {tag} must be less severe than the global log level {global_level}." ) return value @@ -145,7 +144,7 @@ async def to_code(config): level = config[CONF_LEVEL] cg.add_define("USE_LOGGER") this_severity = LOG_LEVEL_SEVERITY.index(level) - cg.add_build_flag("-DESPHOME_LOG_LEVEL={}".format(LOG_LEVELS[level])) + cg.add_build_flag(f"-DESPHOME_LOG_LEVEL={LOG_LEVELS[level]}") verbose_severity = LOG_LEVEL_SEVERITY.index("VERBOSE") very_verbose_severity = LOG_LEVEL_SEVERITY.index("VERY_VERBOSE") @@ -220,8 +219,7 @@ def validate_printf(value): matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.X) if len(matches) != len(value[CONF_ARGS]): raise cv.Invalid( - "Found {} printf-patterns ({}), but {} args were given!" - "".format(len(matches), ", ".join(matches), len(value[CONF_ARGS])) + f"Found {len(matches)} printf-patterns ({', '.join(matches)}), but {len(value[CONF_ARGS])} args were given!" ) return value diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 19f78641df..73ee50ee65 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -266,8 +266,7 @@ async def to_code(config): if CONF_SSL_FINGERPRINTS in config: for fingerprint in config[CONF_SSL_FINGERPRINTS]: arr = [ - cg.RawExpression("0x{}".format(fingerprint[i : i + 2])) - for i in range(0, 40, 2) + cg.RawExpression(f"0x{fingerprint[i:i + 2]}") for i in range(0, 40, 2) ] cg.add(var.add_ssl_fingerprint(arr)) cg.add_build_flag("-DASYNC_TCP_SSL_ENABLED=1") @@ -353,9 +352,7 @@ def get_default_topic_for(data, component_type, name, suffix): sanitized_name = "".join( x for x in name.lower().replace(" ", "_") if x in allowlist ) - return "{}/{}/{}/{}".format( - data.topic_prefix, component_type, sanitized_name, suffix - ) + return f"{data.topic_prefix}/{component_type}/{sanitized_name}/{suffix}" async def register_mqtt_component(var, config): diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index 5eee5210f9..a86b4e4588 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -40,7 +40,7 @@ def validate_type(value): raise cv.Invalid("Must have B in type") rest = set(value) - set("RGBW") if rest: - raise cv.Invalid("Type has invalid color: {}".format(", ".join(rest))) + raise cv.Invalid(f"Type has invalid color: {', '.join(rest)}") if len(set(value)) != len(value): raise cv.Invalid("Type has duplicate color!") return value @@ -95,9 +95,7 @@ def validate_method_pin(value): for opt in (CONF_PIN, CONF_CLOCK_PIN, CONF_DATA_PIN): if opt in value and value[opt] not in pins_: raise cv.Invalid( - "Method {} only supports pin(s) {}".format( - method, ", ".join(f"GPIO{x}" for x in pins_) - ), + f"Method {method} only supports pin(s) {', '.join(f'GPIO{x}' for x in pins_)}", path=[CONF_METHOD], ) return value @@ -139,7 +137,7 @@ def format_method(config): if config[CONF_INVERT]: if method == "ESP8266_DMA": - variant = "Inverted" + variant + variant = f"Inverted{variant}" else: variant += "Inverted" diff --git a/esphome/components/nextion/base_component.py b/esphome/components/nextion/base_component.py index ce567bc3f1..75694ee4b2 100644 --- a/esphome/components/nextion/base_component.py +++ b/esphome/components/nextion/base_component.py @@ -30,16 +30,14 @@ CONF_FONT_ID = "font_id" def NextionName(value): - valid_chars = ascii_letters + digits + "." + valid_chars = f"{ascii_letters + digits}." if not isinstance(value, str) or len(value) > 29: raise cv.Invalid("Must be a string less than 29 characters") for char in value: if char not in valid_chars: raise cv.Invalid( - "Must only consist of upper/lowercase characters, numbers and the period '.'. The character '{}' cannot be used.".format( - char - ) + f"Must only consist of upper/lowercase characters, numbers and the period '.'. The character '{char}' cannot be used." ) return value diff --git a/esphome/components/ntc/sensor.py b/esphome/components/ntc/sensor.py index bf819ffd16..660208635c 100644 --- a/esphome/components/ntc/sensor.py +++ b/esphome/components/ntc/sensor.py @@ -105,9 +105,7 @@ def process_calibration(value): a, b, c = calc_steinhart_hart(value) else: raise cv.Invalid( - "Calibration parameter accepts either a list for steinhart-hart " - "calibration, or mapping for b-constant calibration, " - "not {}".format(type(value)) + f"Calibration parameter accepts either a list for steinhart-hart calibration, or mapping for b-constant calibration, not {type(value)}" ) return { diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index 330ffc2bf2..df0f0de13d 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -151,8 +151,7 @@ def do_packages_pass(config: dict): packages = CONFIG_SCHEMA(packages) if not isinstance(packages, dict): raise cv.Invalid( - "Packages must be a key to value mapping, got {} instead" - "".format(type(packages)) + f"Packages must be a key to value mapping, got {type(packages)} instead" ) for package_name, package_config in packages.items(): diff --git a/esphome/components/partition/light.py b/esphome/components/partition/light.py index 5ded6b906c..06bee2143e 100644 --- a/esphome/components/partition/light.py +++ b/esphome/components/partition/light.py @@ -20,8 +20,7 @@ PartitionLightOutput = partitions_ns.class_( def validate_from_to(value): if value[CONF_FROM] > value[CONF_TO]: raise cv.Invalid( - "From ({}) must not be larger than to ({})" - "".format(value[CONF_FROM], value[CONF_TO]) + f"From ({value[CONF_FROM]}) must not be larger than to ({value[CONF_TO]})" ) return value diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index 8b9e5c9af2..350117a235 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -63,9 +63,7 @@ SENSORS_TO_TYPE = { def validate_pmsx003_sensors(value): for key, types in SENSORS_TO_TYPE.items(): if key in value and value[CONF_TYPE] not in types: - raise cv.Invalid( - "{} does not have {} sensor!".format(value[CONF_TYPE], key) - ) + raise cv.Invalid(f"{value[CONF_TYPE]} does not have {key} sensor!") return value diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index d76dc6bc34..537ae2283c 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -491,8 +491,7 @@ def validate_raw_alternating(value): if i != 0: if this_negative == last_negative: raise cv.Invalid( - "Values must alternate between being positive and negative, " - "please see index {} and {}".format(i, i + 1), + f"Values must alternate between being positive and negative, please see index {i} and {i + 1}", [i], ) last_negative = this_negative @@ -619,13 +618,11 @@ def validate_rc_switch_code(value): for c in value: if c not in ("0", "1"): raise cv.Invalid( - "Invalid RCSwitch code character '{}'. Only '0' and '1' are allowed" - "".format(c) + f"Invalid RCSwitch code character '{c}'. Only '0' and '1' are allowed" ) if len(value) > 64: raise cv.Invalid( - "Maximum length for RCSwitch codes is 64, code '{}' has length {}" - "".format(value, len(value)) + f"Maximum length for RCSwitch codes is 64, code '{value}' has length {len(value)}" ) if not value: raise cv.Invalid("RCSwitch code must not be empty") @@ -638,14 +635,11 @@ def validate_rc_switch_raw_code(value): for c in value: if c not in ("0", "1", "x"): raise cv.Invalid( - "Invalid RCSwitch raw code character '{}'.Only '0', '1' and 'x' are allowed".format( - c - ) + f"Invalid RCSwitch raw code character '{c}'.Only '0', '1' and 'x' are allowed" ) if len(value) > 64: raise cv.Invalid( - "Maximum length for RCSwitch raw codes is 64, code '{}' has length {}" - "".format(value, len(value)) + f"Maximum length for RCSwitch raw codes is 64, code '{value}' has length {len(value)}" ) if not value: raise cv.Invalid("RCSwitch raw code must not be empty") diff --git a/esphome/components/rotary_encoder/sensor.py b/esphome/components/rotary_encoder/sensor.py index 311e63a7ba..e82b9d5f13 100644 --- a/esphome/components/rotary_encoder/sensor.py +++ b/esphome/components/rotary_encoder/sensor.py @@ -49,8 +49,7 @@ def validate_min_max_value(config): max_val = config[CONF_MAX_VALUE] if min_val >= max_val: raise cv.Invalid( - "Max value {} must be smaller than min value {}" - "".format(max_val, min_val) + f"Max value {max_val} must be smaller than min value {min_val}" ) return config diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index fd278be51e..ce4aafb3b0 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -109,8 +109,7 @@ def validate_send_first_at(value): send_every = value[CONF_SEND_EVERY] if send_first_at is not None and send_first_at > send_every: raise cv.Invalid( - "send_first_at must be smaller than or equal to send_every! {} <= {}" - "".format(send_first_at, send_every) + f"send_first_at must be smaller than or equal to send_every! {send_first_at} <= {send_every}" ) return value @@ -459,8 +458,7 @@ CONF_DEGREE = "degree" def validate_calibrate_polynomial(config): if config[CONF_DEGREE] >= len(config[CONF_DATAPOINTS]): raise cv.Invalid( - "Degree is too high! Maximum possible degree with given datapoints is " - "{}".format(len(config[CONF_DATAPOINTS]) - 1), + f"Degree is too high! Maximum possible degree with given datapoints is {len(config[CONF_DATAPOINTS]) - 1}", [CONF_DEGREE], ) return config diff --git a/esphome/components/substitutions/__init__.py b/esphome/components/substitutions/__init__.py index e7a5e24ee6..0cef417c15 100644 --- a/esphome/components/substitutions/__init__.py +++ b/esphome/components/substitutions/__init__.py @@ -25,8 +25,7 @@ def validate_substitution_key(value): for char in value: if char not in VALID_SUBSTITUTIONS_CHARACTERS: raise cv.Invalid( - "Substitution must only consist of upper/lowercase characters, the underscore " - "and numbers. The character '{}' cannot be used".format(char) + f"Substitution must only consist of upper/lowercase characters, the underscore and numbers. The character '{char}' cannot be used" ) return value @@ -42,6 +41,7 @@ async def to_code(config): pass +# pylint: disable=consider-using-f-string VARIABLE_PROG = re.compile( "\\$([{0}]+|\\{{[{0}]*\\}})".format(VALID_SUBSTITUTIONS_CHARACTERS) ) @@ -133,8 +133,7 @@ def do_substitution_pass(config, command_line_substitutions): with cv.prepend_path("substitutions"): if not isinstance(substitutions, dict): raise cv.Invalid( - "Substitutions must be a key to value mapping, got {}" - "".format(type(substitutions)) + f"Substitutions must be a key to value mapping, got {type(substitutions)}" ) replace_keys = [] diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index 4872c89f88..d548575d72 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -67,9 +67,7 @@ def _week_of_month(dt): def _tz_dst_str(dt): td = datetime.timedelta(hours=dt.hour, minutes=dt.minute, seconds=dt.second) - return "M{}.{}.{}/{}".format( - dt.month, _week_of_month(dt), dt.isoweekday() % 7, _tz_timedelta(td) - ) + return f"M{dt.month}.{_week_of_month(dt)}.{dt.isoweekday() % 7}/{_tz_timedelta(td)}" def _safe_tzname(tz, dt): @@ -88,7 +86,7 @@ def _non_dst_tz(tz, dt): _LOGGER.info( "Detected timezone '%s' with UTC offset %s", tzname, _tz_timedelta(utcoffset) ) - tzbase = "{}{}".format(tzname, _tz_timedelta(-1 * utcoffset)) + tzbase = f"{tzname}{_tz_timedelta(-1 * utcoffset)}" return tzbase @@ -129,14 +127,9 @@ def convert_tz(pytz_obj): dst_ends_utc = transition_times[idx2] dst_ends_local = dst_ends_utc + utcoffset_on - tzbase = "{}{}".format(tzname_off, _tz_timedelta(-1 * utcoffset_off)) + tzbase = f"{tzname_off}{_tz_timedelta(-1 * utcoffset_off)}" - tzext = "{}{},{},{}".format( - tzname_on, - _tz_timedelta(-1 * utcoffset_on), - _tz_dst_str(dst_begins_local), - _tz_dst_str(dst_ends_local), - ) + tzext = f"{tzname_on}{_tz_timedelta(-1 * utcoffset_on)},{_tz_dst_str(dst_begins_local)},{_tz_dst_str(dst_ends_local)}" _LOGGER.info( "Detected timezone '%s' with UTC offset %s and daylight saving time from " "%s to %s", @@ -176,9 +169,7 @@ def _parse_cron_part(part, min_value, max_value, special_mapping): data = part.split("/") if len(data) > 2: raise cv.Invalid( - "Can't have more than two '/' in one time expression, got {}".format( - part - ) + f"Can't have more than two '/' in one time expression, got {part}" ) offset, repeat = data offset_n = 0 @@ -194,18 +185,14 @@ def _parse_cron_part(part, min_value, max_value, special_mapping): except ValueError: # pylint: disable=raise-missing-from raise cv.Invalid( - "Repeat for '/' time expression must be an integer, got {}".format( - repeat - ) + f"Repeat for '/' time expression must be an integer, got {repeat}" ) return set(range(offset_n, max_value + 1, repeat_n)) if "-" in part: data = part.split("-") if len(data) > 2: raise cv.Invalid( - "Can't have more than two '-' in range time expression '{}'".format( - part - ) + f"Can't have more than two '-' in range time expression '{part}'" ) begin, end = data begin_n = _parse_cron_int( @@ -233,13 +220,11 @@ def cron_expression_validator(name, min_value, max_value, special_mapping=None): for v in value: if not isinstance(v, int): raise cv.Invalid( - "Expected integer for {} '{}', got {}".format(v, name, type(v)) + f"Expected integer for {v} '{name}', got {type(v)}" ) if v < min_value or v > max_value: raise cv.Invalid( - "{} {} is out of range (min={} max={}).".format( - name, v, min_value, max_value - ) + f"{name} {v} is out of range (min={min_value} max={max_value})." ) return list(sorted(value)) value = cv.string(value) @@ -295,8 +280,7 @@ def validate_cron_raw(value): value = value.split(" ") if len(value) != 6: raise cv.Invalid( - "Cron expression must consist of exactly 6 space-separated parts, " - "not {}".format(len(value)) + f"Cron expression must consist of exactly 6 space-separated parts, not {len(value)}" ) seconds, minutes, hours, days_of_month, months, days_of_week = value return { diff --git a/esphome/components/wifi/wpa2_eap.py b/esphome/components/wifi/wpa2_eap.py index 071737ccd7..3cb60e6175 100644 --- a/esphome/components/wifi/wpa2_eap.py +++ b/esphome/components/wifi/wpa2_eap.py @@ -57,6 +57,7 @@ def wrapped_load_pem_private_key(value, password): def read_relative_config_path(value): + # pylint: disable=unspecified-encoding return Path(CORE.relative_config_path(value)).read_text() diff --git a/esphome/config.py b/esphome/config.py index de261f7eba..e49293e6b2 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -42,7 +42,7 @@ def iter_components(config): yield domain, component, conf if component.is_platform_component: for p_config in conf: - p_name = "{}.{}".format(domain, p_config[CONF_PLATFORM]) + p_name = f"{domain}.{p_config[CONF_PLATFORM]}" platform = get_platform(domain, p_config[CONF_PLATFORM]) yield p_name, platform, p_config @@ -238,10 +238,7 @@ def do_id_pass(result): # type: (Config) -> None # No declared ID with this name import difflib - error = ( - "Couldn't find ID '{}'. Please check you have defined " - "an ID with that name in your configuration.".format(id.id) - ) + error = f"Couldn't find ID '{id.id}'. Please check you have defined an ID with that name in your configuration." # Find candidates matches = difflib.get_close_matches( id.id, [v[0].id for v in result.declare_ids if v[0].is_manual] @@ -257,9 +254,7 @@ def do_id_pass(result): # type: (Config) -> None continue if not match.type.inherits_from(id.type): result.add_str_error( - "ID '{}' of type {} doesn't inherit from {}. Please " - "double check your ID is pointing to the correct value" - "".format(id.id, match.type, id.type), + f"ID '{id.id}' of type {match.type} doesn't inherit from {id.type}. Please double check your ID is pointing to the correct value", path, ) @@ -497,7 +492,7 @@ def validate_config(config, command_line_substitutions): for dependency in comp.dependencies: if dependency not in config: result.add_str_error( - "Component {} requires component {}" "".format(domain, dependency), + f"Component {domain} requires component {dependency}", path, ) success = False @@ -508,8 +503,7 @@ def validate_config(config, command_line_substitutions): for conflict in comp.conflicts_with: if conflict in config: result.add_str_error( - "Component {} cannot be used together with component {}" - "".format(domain, conflict), + f"Component {domain} cannot be used together with component {conflict}", path, ) success = False @@ -518,7 +512,7 @@ def validate_config(config, command_line_substitutions): if CORE.esp_platform not in comp.esp_platforms: result.add_str_error( - "Component {} doesn't support {}.".format(domain, CORE.esp_platform), + f"Component {domain} doesn't support {CORE.esp_platform}.", path, ) continue @@ -529,8 +523,7 @@ def validate_config(config, command_line_substitutions): and not isinstance(conf, core.AutoLoad) ): result.add_str_error( - "Component {} cannot be loaded via YAML " - "(no CONFIG_SCHEMA).".format(domain), + f"Component {domain} cannot be loaded via YAML (no CONFIG_SCHEMA).", path, ) continue @@ -540,8 +533,7 @@ def validate_config(config, command_line_substitutions): result[domain] = conf = [conf] if not isinstance(comp.multi_conf, bool) and len(conf) > comp.multi_conf: result.add_str_error( - "Component {} supports a maximum of {} " - "entries ({} found).".format(domain, comp.multi_conf, len(conf)), + f"Component {domain} supports a maximum of {comp.multi_conf} entries ({len(conf)} found).", path, ) continue @@ -636,18 +628,14 @@ def _format_vol_invalid(ex, config): if isinstance(ex, ExtraKeysInvalid): if ex.candidates: - message += "[{}] is an invalid option for [{}]. Did you mean {}?".format( - ex.path[-1], paren, ", ".join(f"[{x}]" for x in ex.candidates) - ) + message += f"[{ex.path[-1]}] is an invalid option for [{paren}]. Did you mean {', '.join(f'[{x}]' for x in ex.candidates)}?" else: - message += "[{}] is an invalid option for [{}]. Please check the indentation.".format( - ex.path[-1], paren - ) + message += f"[{ex.path[-1]}] is an invalid option for [{paren}]. Please check the indentation." elif "extra keys not allowed" in str(ex): - message += "[{}] is an invalid option for [{}].".format(ex.path[-1], paren) + message += f"[{ex.path[-1]}] is an invalid option for [{paren}]." elif isinstance(ex, vol.RequiredFieldInvalid): if ex.msg == "required key not provided": - message += "'{}' is a required option for [{}].".format(ex.path[-1], paren) + message += f"'{ex.path[-1]}' is a required option for [{paren}]." else: # Required has set a custom error message message += ex.msg @@ -700,7 +688,7 @@ def line_info(config, path, highlight=True): obj = config.get_deepest_document_range_for_path(path) if obj: mark = obj.start_mark - source = "[source {}:{}]".format(mark.document, mark.line + 1) + source = f"[source {mark.document}:{mark.line + 1}]" return color(Fore.CYAN, source) return "None" @@ -724,9 +712,7 @@ def dump_dict(config, path, at_root=True): if at_root: error = config.get_error_for_path(path) if error is not None: - ret += ( - "\n" + color(Fore.BOLD_RED, _format_vol_invalid(error, config)) + "\n" - ) + ret += f"\n{color(Fore.BOLD_RED, _format_vol_invalid(error, config))}\n" if isinstance(conf, (list, tuple)): multiline = True @@ -738,11 +724,7 @@ def dump_dict(config, path, at_root=True): path_ = path + [i] error = config.get_error_for_path(path_) if error is not None: - ret += ( - "\n" - + color(Fore.BOLD_RED, _format_vol_invalid(error, config)) - + "\n" - ) + ret += f"\n{color(Fore.BOLD_RED, _format_vol_invalid(error, config))}\n" sep = "- " if config.is_in_error_path(path_): @@ -751,10 +733,10 @@ def dump_dict(config, path, at_root=True): msg = indent(msg) inf = line_info(config, path_, highlight=config.is_in_error_path(path_)) if inf is not None: - msg = inf + "\n" + msg + msg = f"{inf}\n{msg}" elif msg: msg = msg[2:] - ret += sep + msg + "\n" + ret += f"{sep + msg}\n" elif isinstance(conf, dict): multiline = True if not conf: @@ -765,11 +747,7 @@ def dump_dict(config, path, at_root=True): path_ = path + [k] error = config.get_error_for_path(path_) if error is not None: - ret += ( - "\n" - + color(Fore.BOLD_RED, _format_vol_invalid(error, config)) - + "\n" - ) + ret += f"\n{color(Fore.BOLD_RED, _format_vol_invalid(error, config))}\n" st = f"{k}: " if config.is_in_error_path(path_): @@ -778,30 +756,30 @@ def dump_dict(config, path, at_root=True): inf = line_info(config, path_, highlight=config.is_in_error_path(path_)) if m: - msg = "\n" + indent(msg) + msg = f"\n{indent(msg)}" if inf is not None: if m: - msg = " " + inf + msg + msg = f" {inf}{msg}" else: - msg = msg + " " + inf - ret += st + msg + "\n" + msg = f"{msg} {inf}" + ret += f"{st + msg}\n" elif isinstance(conf, str): if is_secret(conf): - conf = "!secret {}".format(is_secret(conf)) + conf = f"!secret {is_secret(conf)}" if not conf: conf += "''" if len(conf) > 80: - conf = "|-\n" + indent(conf) + conf = f"|-\n{indent(conf)}" error = config.get_error_for_path(path) col = Fore.BOLD_RED if error else Fore.KEEP ret += color(col, str(conf)) elif isinstance(conf, core.Lambda): if is_secret(conf): - conf = "!secret {}".format(is_secret(conf)) + conf = f"!secret {is_secret(conf)}" - conf = "!lambda |-\n" + indent(str(conf.value)) + conf = f"!lambda |-\n{indent(str(conf.value))}" error = config.get_error_for_path(path) col = Fore.BOLD_RED if error else Fore.KEEP ret += color(col, conf) @@ -860,7 +838,7 @@ def read_config(command_line_substitutions): errstr = color(Fore.BOLD_RED, f"{domain}:") errline = line_info(res, path) if errline: - errstr += " " + errline + errstr += f" {errline}" safe_print(errstr) safe_print(indent(dump_dict(res, path)[0])) return None diff --git a/esphome/config_validation.py b/esphome/config_validation.py index fb659c41ea..a864c65dc7 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -277,8 +277,7 @@ def string_strict(value): if isinstance(value, str): return value raise Invalid( - "Must be string, got {}. did you forget putting quotes " - "around the value?".format(type(value)) + f"Must be string, got {type(value)}. did you forget putting quotes around the value?" ) @@ -311,8 +310,7 @@ def boolean(value): if value in ("false", "no", "off", "disable"): return False raise Invalid( - "Expected boolean value, but cannot convert {} to a boolean. " - "Please use 'true' or 'false'".format(value) + f"Expected boolean value, but cannot convert {value} to a boolean. Please use 'true' or 'false'" ) @@ -358,8 +356,7 @@ def int_(value): if int(value) == value: return int(value) raise Invalid( - "This option only accepts integers with no fractional part. Please remove " - "the fractional part from {}".format(value) + f"This option only accepts integers with no fractional part. Please remove the fractional part from {value}" ) value = string_strict(value).lower() base = 10 @@ -424,20 +421,17 @@ def validate_id_name(value): raise Invalid( "Dashes are not supported in IDs, please use underscores instead." ) - valid_chars = ascii_letters + digits + "_" + valid_chars = f"{ascii_letters + digits}_" for char in value: if char not in valid_chars: raise Invalid( - "IDs must only consist of upper/lowercase characters, the underscore" - "character and numbers. The character '{}' cannot be used" - "".format(char) + f"IDs must only consist of upper/lowercase characters, the underscorecharacter and numbers. The character '{char}' cannot be used" ) if value in RESERVED_IDS: raise Invalid(f"ID '{value}' is reserved internally and cannot be used") if value in CORE.loaded_integrations: raise Invalid( - "ID '{}' conflicts with the name of an esphome integration, please use " - "another ID name.".format(value) + f"ID '{value}' conflicts with the name of an esphome integration, please use another ID name." ) return value @@ -525,7 +519,7 @@ def has_at_least_one_key(*keys): raise Invalid("expected dictionary") if not any(k in keys for k in obj): - raise Invalid("Must contain at least one of {}.".format(", ".join(keys))) + raise Invalid(f"Must contain at least one of {', '.join(keys)}.") return obj return validate @@ -540,9 +534,9 @@ def has_exactly_one_key(*keys): number = sum(k in keys for k in obj) if number > 1: - raise Invalid("Cannot specify more than one of {}.".format(", ".join(keys))) + raise Invalid(f"Cannot specify more than one of {', '.join(keys)}.") if number < 1: - raise Invalid("Must contain exactly one of {}.".format(", ".join(keys))) + raise Invalid(f"Must contain exactly one of {', '.join(keys)}.") return obj return validate @@ -557,7 +551,7 @@ def has_at_most_one_key(*keys): number = sum(k in keys for k in obj) if number > 1: - raise Invalid("Cannot specify more than one of {}.".format(", ".join(keys))) + raise Invalid(f"Cannot specify more than one of {', '.join(keys)}.") return obj return validate @@ -572,9 +566,7 @@ def has_none_or_all_keys(*keys): number = sum(k in keys for k in obj) if number != 0 and number != len(keys): - raise Invalid( - "Must specify either none or all of {}.".format(", ".join(keys)) - ) + raise Invalid(f"Must specify either none or all of {', '.join(keys)}.") return obj return validate @@ -632,8 +624,7 @@ def time_period_str_unit(value): if isinstance(value, int): raise Invalid( - "Don't know what '{0}' means as it has no time *unit*! Did you mean " - "'{0}s'?".format(value) + f"Don't know what '{value}' means as it has no time *unit*! Did you mean '{value}s'?" ) if isinstance(value, TimePeriod): value = str(value) @@ -659,7 +650,7 @@ def time_period_str_unit(value): match = re.match(r"^([-+]?[0-9]*\.?[0-9]*)\s*(\w*)$", value) if match is None: - raise Invalid("Expected time period with unit, " "got {}".format(value)) + raise Invalid(f"Expected time period with unit, got {value}") kwarg = unit_to_kwarg[one_of(*unit_to_kwarg)(match.group(2))] return TimePeriod(**{kwarg: float(match.group(1))}) @@ -796,7 +787,7 @@ METRIC_SUFFIXES = { def float_with_unit(quantity, regex_suffix, optional_unit=False): pattern = re.compile( - r"^([-+]?[0-9]*\.?[0-9]*)\s*(\w*?)" + regex_suffix + r"$", re.UNICODE + f"^([-+]?[0-9]*\\.?[0-9]*)\\s*(\\w*?){regex_suffix}$", re.UNICODE ) def validator(value): @@ -812,7 +803,7 @@ def float_with_unit(quantity, regex_suffix, optional_unit=False): mantissa = float(match.group(1)) if match.group(2) not in METRIC_SUFFIXES: - raise Invalid("Invalid {} suffix {}".format(quantity, match.group(2))) + raise Invalid(f"Invalid {quantity} suffix {match.group(2)}") multiplier = METRIC_SUFFIXES[match.group(2)] return mantissa * multiplier @@ -879,12 +870,11 @@ def validate_bytes(value): mantissa = int(match.group(1)) if match.group(2) not in METRIC_SUFFIXES: - raise Invalid("Invalid metric suffix {}".format(match.group(2))) + raise Invalid(f"Invalid metric suffix {match.group(2)}") multiplier = METRIC_SUFFIXES[match.group(2)] if multiplier < 1: raise Invalid( - "Only suffixes with positive exponents are supported. " - "Got {}".format(match.group(2)) + f"Only suffixes with positive exponents are supported. Got {match.group(2)}" ) return int(mantissa * multiplier) @@ -1184,10 +1174,8 @@ def one_of(*values, **kwargs): option = str(value) matches = difflib.get_close_matches(option, options_) if matches: - raise Invalid( - "Unknown value '{}', did you mean {}?" - "".format(value, ", ".join(f"'{x}'" for x in matches)) - ) + matches_str = ", ".join(f"'{x}'" for x in matches) + raise Invalid(f"Unknown value '{value}', did you mean {matches_str}?") raise Invalid(f"Unknown value '{value}', valid options are {options}.") return value @@ -1229,13 +1217,10 @@ def lambda_(value): entity_id_parts = re.split(LAMBDA_ENTITY_ID_PROG, value.value) if len(entity_id_parts) != 1: entity_ids = " ".join( - "'{}'".format(entity_id_parts[i]) for i in range(1, len(entity_id_parts), 2) + f"'{entity_id_parts[i]}'" for i in range(1, len(entity_id_parts), 2) ) raise Invalid( - "Lambda contains reference to entity-id-style ID {}. " - "The id() wrapper only works for ESPHome-internal types. For importing " - "states from Home Assistant use the 'homeassistant' sensor platforms." - "".format(entity_ids) + f"Lambda contains reference to entity-id-style ID {entity_ids}. The id() wrapper only works for ESPHome-internal types. For importing states from Home Assistant use the 'homeassistant' sensor platforms." ) return value @@ -1259,9 +1244,7 @@ def returning_lambda(value): def dimensions(value): if isinstance(value, list): if len(value) != 2: - raise Invalid( - "Dimensions must have a length of two, not {}".format(len(value)) - ) + raise Invalid(f"Dimensions must have a length of two, not {len(value)}") try: width, height = int(value[0]), int(value[1]) except ValueError: @@ -1301,19 +1284,16 @@ def directory(value): if data["content"]: return value raise Invalid( - "Could not find directory '{}'. Please make sure it exists (full path: {})." - "".format(path, os.path.abspath(path)) + f"Could not find directory '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})." ) if not os.path.exists(path): raise Invalid( - "Could not find directory '{}'. Please make sure it exists (full path: {})." - "".format(path, os.path.abspath(path)) + f"Could not find directory '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})." ) if not os.path.isdir(path): raise Invalid( - "Path '{}' is not a directory (full path: {})." - "".format(path, os.path.abspath(path)) + f"Path '{path}' is not a directory (full path: {os.path.abspath(path)})." ) return value @@ -1340,19 +1320,16 @@ def file_(value): if data["content"]: return value raise Invalid( - "Could not find file '{}'. Please make sure it exists (full path: {})." - "".format(path, os.path.abspath(path)) + f"Could not find file '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})." ) if not os.path.exists(path): raise Invalid( - "Could not find file '{}'. Please make sure it exists (full path: {})." - "".format(path, os.path.abspath(path)) + f"Could not find file '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})." ) if not os.path.isfile(path): raise Invalid( - "Path '{}' is not a file (full path: {})." - "".format(path, os.path.abspath(path)) + f"Path '{path}' is not a file (full path: {os.path.abspath(path)})." ) return value @@ -1405,7 +1382,7 @@ def typed_schema(schemas, **kwargs): value = value.copy() schema_option = value.pop(key, default_schema_option) if schema_option is None: - raise Invalid(key + " not specified!") + raise Invalid(f"{key} not specified!") key_v = key_validator(schema_option) value = schemas[key_v](value) value[key] = key_v @@ -1498,8 +1475,7 @@ def validate_registry_entry(name, registry): value = {value: {}} if not isinstance(value, dict): raise Invalid( - "{} must consist of key-value mapping! Got {}" - "".format(name.title(), value) + f"{name.title()} must consist of key-value mapping! Got {value}" ) key = next((x for x in value if x not in ignore_keys), None) if key is None: @@ -1509,9 +1485,8 @@ def validate_registry_entry(name, registry): key2 = next((x for x in value if x != key and x not in ignore_keys), None) if key2 is not None: raise Invalid( - "Cannot have two {0}s in one item. Key '{1}' overrides '{2}'! " - "Did you forget to indent the block inside the {0}?" - "".format(name, key, key2) + f"Cannot have two {name}s in one item. Key '{key}' overrides '{key2}'! " + f"Did you forget to indent the block inside the {key}?" ) if value[key] is None: diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index c45d0969e1..e89f64d14c 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -624,8 +624,7 @@ class EsphomeCore: expression = statement(expression) if not isinstance(expression, Statement): raise ValueError( - "Add '{}' must be expression or statement, not {}" - "".format(expression, type(expression)) + f"Add '{expression}' must be expression or statement, not {type(expression)}" ) self.main_statements.append(expression) @@ -639,8 +638,7 @@ class EsphomeCore: expression = statement(expression) if not isinstance(expression, Statement): raise ValueError( - "Add '{}' must be expression or statement, not {}" - "".format(expression, type(expression)) + f"Add '{expression}' must be expression or statement, not {type(expression)}" ) self.global_statements.append(expression) _LOGGER.debug("Adding global: %s", expression) @@ -649,8 +647,7 @@ class EsphomeCore: def add_library(self, library): if not isinstance(library, Library): raise ValueError( - "Library {} must be instance of Library, not {}" - "".format(library, type(library)) + f"Library {library} must be instance of Library, not {type(library)}" ) for other in self.libraries[:]: if other.name != library.name or other.name is None or library.name is None: @@ -660,9 +657,8 @@ class EsphomeCore: # Other is using a/the same repository, takes precendence break raise ValueError( - "Adding named Library with repository failed! Libraries {} and {} " + f"Adding named Library with repository failed! Libraries {library} and {other} " "requested with conflicting repositories!" - "".format(library, other) ) if library.repository is not None: @@ -681,9 +677,8 @@ class EsphomeCore: break raise ValueError( - "Version pinning failed! Libraries {} and {} " + f"Version pinning failed! Libraries {library} and {other} " "requested with conflicting versions!" - "".format(library, other) ) else: _LOGGER.debug("Adding library: %s", library) @@ -702,8 +697,7 @@ class EsphomeCore: pass else: raise ValueError( - "Define {} must be string or Define, not {}" - "".format(define, type(define)) + f"Define {define} must be string or Define, not {type(define)}" ) self.defines.add(define) _LOGGER.debug("Adding define: %s", define) diff --git a/esphome/core/config.py b/esphome/core/config.py index 45b8809f1e..222a775ee6 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -61,9 +61,7 @@ def validate_board(value: str): if value not in boardlist: raise cv.Invalid( - "Could not find board '{}'. Valid boards are {}".format( - value, ", ".join(sorted(boardlist)) - ) + f"Could not find board '{value}'. Valid boards are {', '.join(sorted(boardlist))}" ) return value @@ -102,9 +100,7 @@ def validate_arduino_version(value): and value_ not in PLATFORMIO_ESP8266_LUT ): raise cv.Invalid( - "Unfortunately the arduino framework version '{}' is unsupported " - "at this time. You can override this by manually using " - "espressif8266@".format(value) + f"Unfortunately the arduino framework version '{value}' is unsupported at this time. You can override this by manually using espressif8266@" ) if value_ in PLATFORMIO_ESP8266_LUT: return PLATFORMIO_ESP8266_LUT[value_] @@ -115,9 +111,7 @@ def validate_arduino_version(value): and value_ not in PLATFORMIO_ESP32_LUT ): raise cv.Invalid( - "Unfortunately the arduino framework version '{}' is unsupported " - "at this time. You can override this by manually using " - "espressif32@".format(value) + f"Unfortunately the arduino framework version '{value}' is unsupported at this time. You can override this by manually using espressif32@" ) if value_ in PLATFORMIO_ESP32_LUT: return PLATFORMIO_ESP32_LUT[value_] @@ -141,8 +135,7 @@ def valid_include(value): _, ext = os.path.splitext(value) if ext not in VALID_INCLUDE_EXTS: raise cv.Invalid( - "Include has invalid file extension {} - valid extensions are {}" - "".format(ext, ", ".join(VALID_INCLUDE_EXTS)) + f"Include has invalid file extension {ext} - valid extensions are {', '.join(VALID_INCLUDE_EXTS)}" ) return value diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index eda378e5eb..0442e2633b 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -182,7 +182,7 @@ class ArrayInitializer(Expression): cpp += f" {arg},\n" cpp += "}" else: - cpp = "{" + ", ".join(str(arg) for arg in self.args) + "}" + cpp = f"{{{', '.join(str(arg) for arg in self.args)}}}" return cpp @@ -348,13 +348,11 @@ def safe_exp(obj: SafeExpType) -> Expression: return float_ if isinstance(obj, ID): raise ValueError( - "Object {} is an ID. Did you forget to register the variable?" - "".format(obj) + f"Object {obj} is an ID. Did you forget to register the variable?" ) if inspect.isgenerator(obj): raise ValueError( - "Object {} is a coroutine. Did you forget to await the expression with " - "'await'?".format(obj) + f"Object {obj} is a coroutine. Did you forget to await the expression with 'await'?" ) raise ValueError("Object is not an expression", obj) @@ -703,7 +701,7 @@ class MockObj(Expression): return str(self.base) def __repr__(self): - return "MockObj<{}>".format(str(self.base)) + return f"MockObj<{str(self.base)}>" @property def _(self) -> "MockObj": @@ -761,7 +759,7 @@ class MockObjEnum(MockObj): self._is_class = kwargs.pop("is_class") base = kwargs.pop("base") if self._is_class: - base = base + "::" + self._enum + base = f"{base}::{self._enum}" kwargs["op"] = "::" kwargs["base"] = base MockObj.__init__(self, *args, **kwargs) diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 7912e4ae06..33fb485c2b 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -52,9 +52,7 @@ async def register_component(var, config): id_ = str(var.base) if id_ not in CORE.component_ids: raise ValueError( - "Component ID {} was not declared to inherit from Component, " - "or was registered twice. Please create a bug report with your " - "configuration.".format(id_) + f"Component ID {id_} was not declared to inherit from Component, or was registered twice. Please create a bug report with your configuration." ) CORE.component_ids.remove(id_) if CONF_SETUP_PRIORITY in config: diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 97f9d60693..38689675af 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -368,7 +368,7 @@ class WizardRequestHandler(BaseHandler): if k in ("name", "platform", "board", "ssid", "psk", "password") } kwargs["ota_password"] = secrets.token_hex(16) - destination = settings.rel_path(kwargs["name"] + ".yaml") + destination = settings.rel_path(f"{kwargs['name']}.yaml") wizard.wizard_write(path=destination, **kwargs) self.set_status(200) self.finish() @@ -512,7 +512,7 @@ class MDNSStatusThread(threading.Thread): while not STOP_EVENT.is_set(): entries = _list_dashboard_entries() stat.request_query( - {entry.filename: entry.name + ".local." for entry in entries} + {entry.filename: f"{entry.name}.local." for entry in entries} ) PING_REQUEST.wait() @@ -795,27 +795,27 @@ def make_app(debug=get_bool_env(ENV_DEV)): rel = settings.relative_url app = tornado.web.Application( [ - (rel + "", MainRequestHandler), - (rel + "login", LoginHandler), - (rel + "logout", LogoutHandler), - (rel + "logs", EsphomeLogsHandler), - (rel + "upload", EsphomeUploadHandler), - (rel + "compile", EsphomeCompileHandler), - (rel + "validate", EsphomeValidateHandler), - (rel + "clean-mqtt", EsphomeCleanMqttHandler), - (rel + "clean", EsphomeCleanHandler), - (rel + "vscode", EsphomeVscodeHandler), - (rel + "ace", EsphomeAceEditorHandler), - (rel + "update-all", EsphomeUpdateAllHandler), - (rel + "info", InfoRequestHandler), - (rel + "edit", EditRequestHandler), - (rel + "download.bin", DownloadBinaryRequestHandler), - (rel + "serial-ports", SerialPortRequestHandler), - (rel + "ping", PingRequestHandler), - (rel + "delete", DeleteRequestHandler), - (rel + "undo-delete", UndoDeleteRequestHandler), - (rel + "wizard.html", WizardRequestHandler), - (rel + r"static/(.*)", StaticFileHandler, {"path": get_static_path()}), + (f"{rel}", MainRequestHandler), + (f"{rel}login", LoginHandler), + (f"{rel}logout", LogoutHandler), + (f"{rel}logs", EsphomeLogsHandler), + (f"{rel}upload", EsphomeUploadHandler), + (f"{rel}compile", EsphomeCompileHandler), + (f"{rel}validate", EsphomeValidateHandler), + (f"{rel}clean-mqtt", EsphomeCleanMqttHandler), + (f"{rel}clean", EsphomeCleanHandler), + (f"{rel}vscode", EsphomeVscodeHandler), + (f"{rel}ace", EsphomeAceEditorHandler), + (f"{rel}update-all", EsphomeUpdateAllHandler), + (f"{rel}info", InfoRequestHandler), + (f"{rel}edit", EditRequestHandler), + (f"{rel}download.bin", DownloadBinaryRequestHandler), + (f"{rel}serial-ports", SerialPortRequestHandler), + (f"{rel}ping", PingRequestHandler), + (f"{rel}delete", DeleteRequestHandler), + (f"{rel}undo-delete", UndoDeleteRequestHandler), + (f"{rel}wizard.html", WizardRequestHandler), + (f"{rel}static/(.*)", StaticFileHandler, {"path": get_static_path()}), ], **app_settings, ) diff --git a/esphome/espota2.py b/esphome/espota2.py index 351f6feda9..f8a2fab94c 100644 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -52,9 +52,7 @@ class ProgressBar: return self.last_progress = new_progress block = int(round(bar_length * progress)) - text = "\rUploading: [{0}] {1}% {2}".format( - "=" * block + " " * (bar_length - block), new_progress, status - ) + text = f"\rUploading: [{'=' * block + ' ' * (bar_length - block)}] {new_progress}% {status}" sys.stderr.write(text) sys.stderr.flush() @@ -154,7 +152,7 @@ def check_error(data, expect): if not isinstance(expect, (list, tuple)): expect = [expect] if dat not in expect: - raise OTAError("Unexpected response from ESP: 0x{:02X}".format(data[0])) + raise OTAError(f"Unexpected response from ESP: 0x{data[0]:02X}") def send_check(sock, data, msg): diff --git a/esphome/helpers.py b/esphome/helpers.py index a1cb4367c5..3420b2cc12 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -54,7 +54,7 @@ def cpp_string_escape(string, encoding="utf-8"): result += f"\\{character:03o}" else: result += chr(character) - return '"' + result + '"' + return f'"{result}"' def run_system_command(*args): @@ -107,7 +107,7 @@ def _resolve_with_zeroconf(host): "host network mode?" ) from err try: - info = zc.resolve_host(host + ".") + info = zc.resolve_host(f"{host}.") except Exception as err: raise EsphomeError(f"Error resolving mDNS hostname: {err}") from err finally: @@ -136,9 +136,7 @@ def resolve_ip_address(host): return socket.gethostbyname(host) except OSError as err: errs.append(str(err)) - raise EsphomeError( - "Error resolving IP address: {}" "".format(", ".join(errs)) - ) from err + raise EsphomeError(f"Error resolving IP address: {', '.join(errs)}") from err def get_bool_env(var, default=False): diff --git a/esphome/mqtt.py b/esphome/mqtt.py index 9be87b5c5d..07602e8ced 100644 --- a/esphome/mqtt.py +++ b/esphome/mqtt.py @@ -104,9 +104,9 @@ def show_logs(config, topic=None, username=None, password=None, client_id=None): if CONF_LOG_TOPIC in conf: topic = config[CONF_MQTT][CONF_LOG_TOPIC][CONF_TOPIC] elif CONF_TOPIC_PREFIX in config[CONF_MQTT]: - topic = config[CONF_MQTT][CONF_TOPIC_PREFIX] + "/debug" + topic = f"{config[CONF_MQTT][CONF_TOPIC_PREFIX]}/debug" else: - topic = config[CONF_ESPHOME][CONF_NAME] + "/debug" + topic = f"{config[CONF_ESPHOME][CONF_NAME]}/debug" else: _LOGGER.error("MQTT isn't setup, can't start MQTT logs") return 1 @@ -158,9 +158,8 @@ def get_fingerprint(config): sha1 = hashlib.sha1(cert_der).hexdigest() - safe_print("SHA1 Fingerprint: " + color(Fore.CYAN, sha1)) + safe_print(f"SHA1 Fingerprint: {color(Fore.CYAN, sha1)}") safe_print( - "Copy the string above into mqtt.ssl_fingerprints section of {}" - "".format(CORE.config_path) + f"Copy the string above into mqtt.ssl_fingerprints section of {CORE.config_path}" ) return 0 diff --git a/esphome/pins.py b/esphome/pins.py index ff4ed9d9c1..c717424ff3 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -77,8 +77,7 @@ def validate_gpio_pin(value): 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]) + f"This pin cannot be used on ESP32-C3s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" ) return value if CORE.is_esp32: @@ -86,8 +85,7 @@ def validate_gpio_pin(value): raise cv.Invalid(f"ESP32: Invalid pin number: {value}") if value in _ESP_SDIO_PINS: raise cv.Invalid( - "This pin cannot be used on ESP32s and is already used by " - "the flash interface (function: {})".format(_ESP_SDIO_PINS[value]) + f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" ) if 9 <= value <= 10: _LOGGER.warning( @@ -105,8 +103,7 @@ def validate_gpio_pin(value): raise cv.Invalid(f"ESP8266: Invalid pin number: {value}") if value in _ESP_SDIO_PINS: raise cv.Invalid( - "This pin cannot be used on ESP8266s and is already used by " - "the flash interface (function: {})".format(_ESP_SDIO_PINS[value]) + f"This pin cannot be used on ESP8266s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" ) if 9 <= value <= 10: _LOGGER.warning( @@ -144,8 +141,7 @@ def output_pin(value): if CORE.is_esp32: if 34 <= value <= 39: raise cv.Invalid( - "ESP32: GPIO{} (34-39) can only be used as an " - "input pin.".format(value) + f"ESP32: GPIO{value} (34-39) can only be used as an input pin." ) return value if CORE.is_esp8266: @@ -278,8 +274,7 @@ def validate_has_interrupt(value): if CORE.is_esp8266: if value[CONF_NUMBER] >= 16: raise cv.Invalid( - "Pins GPIO16 and GPIO17 do not support interrupts and cannot be used " - "here, got {}".format(value[CONF_NUMBER]) + f"Pins GPIO16 and GPIO17 do not support interrupts and cannot be used here, got {value[CONF_NUMBER]}" ) return value diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 3d7e9a6d48..6506a44e88 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -39,7 +39,7 @@ def patch_structhash(): command.clean_build_dir = patched_clean_build_dir -IGNORE_LIB_WARNINGS = r"(?:" + "|".join(["Hash", "Update"]) + r")" +IGNORE_LIB_WARNINGS = f"(?:{'|'.join(['Hash', 'Update'])})" FILTER_PLATFORMIO_LINES = [ r"Verbose mode can be enabled via `-v, --verbose` option.*", r"CONFIGURATION: https://docs.platformio.org/.*", @@ -48,13 +48,9 @@ FILTER_PLATFORMIO_LINES = [ r"PACKAGES: .*", r"LDF: Library Dependency Finder -> http://bit.ly/configure-pio-ldf.*", r"LDF Modes: Finder ~ chain, Compatibility ~ soft.*", - r"Looking for " + IGNORE_LIB_WARNINGS + r" library in registry", - r"Warning! Library `.*'" - + IGNORE_LIB_WARNINGS - + r".*` has not been found in PlatformIO Registry.", - r"You can ignore this message, if `.*" - + IGNORE_LIB_WARNINGS - + r".*` is a built-in library.*", + f"Looking for {IGNORE_LIB_WARNINGS} library in registry", + f"Warning! Library `.*'{IGNORE_LIB_WARNINGS}.*` has not been found in PlatformIO Registry.", + f"You can ignore this message, if `.*{IGNORE_LIB_WARNINGS}.*` is a built-in library.*", r"Scanning dependencies...", r"Found \d+ compatible libraries", r"Memory Usage -> http://bit.ly/pio-memory-usage", @@ -296,6 +292,6 @@ class IDEData: # Windows if cc_path.endswith(".exe"): - return cc_path[:-7] + "addr2line.exe" + return f"{cc_path[:-7]}addr2line.exe" - return cc_path[:-3] + "addr2line" + return f"{cc_path[:-3]}addr2line" diff --git a/esphome/storage_json.py b/esphome/storage_json.py index c0fbc6edf7..517bb508ba 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -94,7 +94,7 @@ class StorageJSON: } def to_json(self): - return json.dumps(self.as_dict(), indent=2) + "\n" + return f"{json.dumps(self.as_dict(), indent=2)}\n" def save(self, path): write_file_if_changed(path, self.to_json()) @@ -214,7 +214,7 @@ class EsphomeStorageJSON: self.last_update_check_str = new.strftime("%Y-%m-%dT%H:%M:%S") def to_json(self): # type: () -> dict - return json.dumps(self.as_dict(), indent=2) + "\n" + return f"{json.dumps(self.as_dict(), indent=2)}\n" def save(self, path): # type: (str) -> None write_file_if_changed(path, self.to_json()) diff --git a/esphome/wizard.py b/esphome/wizard.py index 3f989dd93d..abb17e3119 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -74,19 +74,20 @@ def wizard_file(**kwargs): # Configure API if "password" in kwargs: - config += ' password: "{0}"\n'.format(kwargs["password"]) + config += f" password: \"{kwargs['password']}\"\n" # Configure OTA config += "\nota:\n" if "ota_password" in kwargs: - config += ' password: "{0}"'.format(kwargs["ota_password"]) + config += f" password: \"{kwargs['ota_password']}\"" elif "password" in kwargs: - config += ' password: "{0}"'.format(kwargs["password"]) + config += f" password: \"{kwargs['password']}\"" # Configuring wifi config += "\n\nwifi:\n" if "ssid" in kwargs: + # pylint: disable=consider-using-f-string config += """ ssid: "{ssid}" password: "{psk}" """.format( @@ -99,6 +100,7 @@ def wizard_file(**kwargs): networks: """ + # pylint: disable=consider-using-f-string config += """ # Enable fallback hotspot (captive portal) in case wifi connection fails ap: @@ -126,7 +128,7 @@ def wizard_write(path, **kwargs): platform = kwargs["platform"] write_file(path, wizard_file(**kwargs)) - storage = StorageJSON.from_wizard(name, name + ".local", platform, board) + storage = StorageJSON.from_wizard(name, f"{name}.local", platform, board) storage_path = ext_storage_path(os.path.dirname(path), os.path.basename(path)) storage.save(storage_path) @@ -168,14 +170,12 @@ def strip_accents(value): 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(Fore.CYAN, path)) + f"Please make your configuration file {color(Fore.CYAN, path)} have the extension .yaml or .yml" ) 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(Fore.CYAN, path)) + f"Uh oh, it seems like {color(Fore.CYAN, path)} already exists, please delete that file first or chose another configuration file." ) return 2 safe_print("Hi there!") @@ -191,17 +191,13 @@ def wizard(path): sleep(3.0) safe_print() safe_print_step(1, CORE_BIG) - safe_print( - "First up, please choose a " + color(Fore.GREEN, "name") + " for your node." - ) + safe_print(f"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." ) sleep(1) safe_print( - "For example, I like calling the node in my living room {}.".format( - color(Fore.BOLD_WHITE, "livingroom") - ) + f"For example, I like calling the node in my living room {color(Fore.BOLD_WHITE, 'livingroom')}." ) safe_print() sleep(1) @@ -222,13 +218,11 @@ def wizard(path): 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(Fore.CYAN, name)) - ) + safe_print(f'Shall I use "{color(Fore.CYAN, name)}" as the name instead?') sleep(0.5) name = default_input("(name [{}]): ", name) - safe_print('Great! Your node is now called "{}".'.format(color(Fore.CYAN, name))) + safe_print(f'Great! Your node is now called "{color(Fore.CYAN, name)}".') sleep(1) safe_print_step(2, ESP_BIG) safe_print( @@ -236,11 +230,7 @@ def wizard(path): "firmwares for it." ) safe_print( - "Are you using an " - + color(Fore.GREEN, "ESP32") - + " or " - + color(Fore.GREEN, "ESP8266") - + " platform? (Choose ESP8266 for Sonoff devices)" + f"Are you using an {color(Fore.GREEN, 'ESP32')} or {color(Fore.GREEN, 'ESP8266')} platform? (Choose ESP8266 for Sonoff devices)" ) while True: sleep(0.5) @@ -252,12 +242,9 @@ def wizard(path): break except vol.Invalid: safe_print( - "Unfortunately, I can't find an espressif microcontroller called " - '"{}". Please try again.'.format(platform) + f'Unfortunately, I can\'t find an espressif microcontroller called "{platform}". Please try again.' ) - safe_print( - "Thanks! You've chosen {} as your platform.".format(color(Fore.CYAN, platform)) - ) + safe_print(f"Thanks! You've chosen {color(Fore.CYAN, platform)} as your platform.") safe_print() sleep(1) @@ -270,24 +257,20 @@ def wizard(path): "http://docs.platformio.org/en/latest/platforms/espressif8266.html#boards" ) - safe_print( - "Next, I need to know what " + color(Fore.GREEN, "board") + " you're using." - ) + safe_print(f"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(Fore.GREEN, board_link)) - ) + safe_print(f"Please go to {color(Fore.GREEN, board_link)} and choose a board.") if platform == "ESP32": - safe_print("(Type " + color(Fore.GREEN, "esp01_1m") + " for Sonoff devices)") + safe_print(f"(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(Fore.BOLD_WHITE, "nodemcu-32s"))) + safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'nodemcu-32s')}\".") boards = list(ESP32_BOARD_PINS.keys()) else: - safe_print('For example "{}".'.format(color(Fore.BOLD_WHITE, "nodemcuv2"))) + safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'nodemcuv2')}\".") boards = list(ESP8266_BOARD_PINS.keys()) - safe_print("Options: {}".format(", ".join(sorted(boards)))) + safe_print(f"Options: {', '.join(sorted(boards))}") while True: board = input(color(Fore.BOLD_WHITE, "(board): ")) @@ -302,9 +285,7 @@ def wizard(path): sleep(0.25) safe_print() - safe_print( - "Way to go! You've chosen {} as your board.".format(color(Fore.CYAN, board)) - ) + safe_print(f"Way to go! You've chosen {color(Fore.CYAN, board)} as your board.") safe_print() sleep(1) @@ -313,12 +294,10 @@ def wizard(path): safe_print() sleep(1) safe_print( - "First, what's the " - + color(Fore.GREEN, "SSID") - + f" (the name) of the WiFi network {name} should connect to?" + f"First, what's the {color(Fore.GREEN, 'SSID')} (the name) of the WiFi network {name} should connect to?" ) sleep(1.5) - safe_print('For example "{}".'.format(color(Fore.BOLD_WHITE, "Abraham Linksys"))) + safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'Abraham Linksys')}\".") while True: ssid = input(color(Fore.BOLD_WHITE, "(ssid): ")) try: @@ -328,27 +307,23 @@ def wizard(path): safe_print( color( Fore.RED, - 'Unfortunately, "{}" doesn\'t seem to be a valid SSID. ' - "Please try again.".format(ssid), + f'Unfortunately, "{ssid}" doesn\'t seem to be a valid SSID. Please try again.', ) ) safe_print() sleep(1) safe_print( - 'Thank you very much! You\'ve just chosen "{}" as your SSID.' - "".format(color(Fore.CYAN, ssid)) + f'Thank you very much! You\'ve just chosen "{color(Fore.CYAN, ssid)}" as your SSID.' ) safe_print() sleep(0.75) safe_print( - "Now please state the " - + color(Fore.GREEN, "password") - + " of the WiFi network so that I can connect to it (Leave empty for no password)" + f"Now please state the {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(Fore.BOLD_WHITE, "PASSWORD42"))) + safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'PASSWORD42')}\"") sleep(0.5) psk = input(color(Fore.BOLD_WHITE, "(PSK): ")) safe_print( @@ -362,8 +337,7 @@ def wizard(path): "(over the air) and integrates into Home Assistant with a native API." ) safe_print( - "This can be insecure if you do not trust the WiFi network. Do you want to set " - "a " + color(Fore.GREEN, "password") + " for connecting to this ESP?" + f"This can be insecure if you do not trust the WiFi network. Do you want to set a {color(Fore.GREEN, 'password')} for connecting to this ESP?" ) safe_print() sleep(0.25) diff --git a/esphome/writer.py b/esphome/writer.py index 09ed284173..19953916c7 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -96,7 +96,7 @@ def get_include_text(): includes = "\n".join(includes) if not includes: continue - include_text += includes + "\n" + include_text += f"{includes}\n" return include_text @@ -134,7 +134,7 @@ def migrate_src_version_0_to_1(): content, count = replace_file_content( content, r'#include "esphomelib/application.h"', - CPP_INCLUDE_BEGIN + "\n" + CPP_INCLUDE_END, + f"{CPP_INCLUDE_BEGIN}\n{CPP_INCLUDE_END}", ) if count == 0: _LOGGER.error( @@ -322,7 +322,7 @@ def write_platformio_ini(content): ) else: content_format = INI_BASE_FORMAT - full_file = content_format[0] + INI_AUTO_GENERATE_BEGIN + "\n" + content + full_file = f"{content_format[0] + INI_AUTO_GENERATE_BEGIN}\n{content}" full_file += INI_AUTO_GENERATE_END + content_format[1] write_file_if_changed(path, full_file) @@ -444,9 +444,9 @@ def write_cpp(code_s): global_s = '#include "esphome.h"\n' global_s += CORE.cpp_global_section - full_file = code_format[0] + CPP_INCLUDE_BEGIN + "\n" + global_s + CPP_INCLUDE_END + full_file = f"{code_format[0] + CPP_INCLUDE_BEGIN}\n{global_s}{CPP_INCLUDE_END}" full_file += ( - code_format[1] + CPP_AUTO_GENERATE_BEGIN + "\n" + code_s + CPP_AUTO_GENERATE_END + f"{code_format[1] + CPP_AUTO_GENERATE_BEGIN}\n{code_s}{CPP_AUTO_GENERATE_END}" ) full_file += code_format[2] write_file_if_changed(path, full_file) diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index 10417e6de4..bdadbbd43a 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -183,9 +183,7 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors raise yaml.constructor.ConstructorError( "While constructing a mapping", node.start_mark, - "Expected a mapping for merging, but found {}".format( - type(item) - ), + f"Expected a mapping for merging, but found {type(item)}", value_node.start_mark, ) merge_pairs.extend(item.items()) @@ -193,8 +191,7 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors raise yaml.constructor.ConstructorError( "While constructing a mapping", node.start_mark, - "Expected a mapping or list of mappings for merging, " - "but found {}".format(type(value)), + f"Expected a mapping or list of mappings for merging, but found {type(value)}", value_node.start_mark, ) diff --git a/requirements_test.txt b/requirements_test.txt index bb735193f0..1d09bb6388 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.10.2 +pylint==2.11.1 flake8==3.9.2 black==21.9b0 pexpect==4.8.0 From 53bd197c44882a4ce1fd6d440ad2458306612ffd Mon Sep 17 00:00:00 2001 From: Stefan Rado Date: Sun, 19 Sep 2021 23:17:43 +0200 Subject: [PATCH 1369/1841] Add eco mode to tuya climate component (#1860) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/tuya/climate/__init__.py | 17 +++++++ .../components/tuya/climate/tuya_climate.cpp | 47 +++++++++++++++++-- .../components/tuya/climate/tuya_climate.h | 13 +++++ 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/esphome/components/tuya/climate/__init__.py b/esphome/components/tuya/climate/__init__.py index 471a8146e1..275a87edd3 100644 --- a/esphome/components/tuya/climate/__init__.py +++ b/esphome/components/tuya/climate/__init__.py @@ -23,6 +23,8 @@ CONF_CURRENT_TEMPERATURE_DATAPOINT = "current_temperature_datapoint" CONF_TEMPERATURE_MULTIPLIER = "temperature_multiplier" CONF_CURRENT_TEMPERATURE_MULTIPLIER = "current_temperature_multiplier" CONF_TARGET_TEMPERATURE_MULTIPLIER = "target_temperature_multiplier" +CONF_ECO_DATAPOINT = "eco_datapoint" +CONF_ECO_TEMPERATURE = "eco_temperature" TuyaClimate = tuya_ns.class_("TuyaClimate", climate.Climate, cg.Component) @@ -90,6 +92,14 @@ def validate_active_state_values(value): return value +def validate_eco_values(value): + if CONF_ECO_TEMPERATURE in value and CONF_ECO_DATAPOINT not in value: + raise cv.Invalid( + f"{CONF_ECO_DATAPOINT} required if using {CONF_ECO_TEMPERATURE}" + ) + return value + + CONFIG_SCHEMA = cv.All( climate.CLIMATE_SCHEMA.extend( { @@ -108,6 +118,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_TEMPERATURE_MULTIPLIER): cv.positive_float, cv.Optional(CONF_CURRENT_TEMPERATURE_MULTIPLIER): cv.positive_float, cv.Optional(CONF_TARGET_TEMPERATURE_MULTIPLIER): cv.positive_float, + cv.Optional(CONF_ECO_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_ECO_TEMPERATURE): cv.temperature, } ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TARGET_TEMPERATURE_DATAPOINT, CONF_SWITCH_DATAPOINT), @@ -115,6 +127,7 @@ CONFIG_SCHEMA = cv.All( validate_active_state_values, cv.has_at_most_one_key(CONF_ACTIVE_STATE_DATAPOINT, CONF_HEATING_STATE_PIN), cv.has_at_most_one_key(CONF_ACTIVE_STATE_DATAPOINT, CONF_COOLING_STATE_PIN), + validate_eco_values, ) @@ -179,3 +192,7 @@ async def to_code(config): config[CONF_TARGET_TEMPERATURE_MULTIPLIER] ) ) + if CONF_ECO_DATAPOINT in config: + cg.add(var.set_eco_id(config[CONF_ECO_DATAPOINT])) + if CONF_ECO_TEMPERATURE in config: + cg.add(var.set_eco_temperature(config[CONF_ECO_TEMPERATURE])) diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index ae4424e438..293bfb4f88 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -43,8 +43,9 @@ void TuyaClimate::setup() { } if (this->target_temperature_id_.has_value()) { this->parent_->register_listener(*this->target_temperature_id_, [this](const TuyaDatapoint &datapoint) { - this->target_temperature = datapoint.value_int * this->target_temperature_multiplier_; - ESP_LOGV(TAG, "MCU reported target temperature is: %.1f", this->target_temperature); + this->manual_temperature_ = datapoint.value_int * this->target_temperature_multiplier_; + ESP_LOGV(TAG, "MCU reported manual target temperature is: %.1f", this->manual_temperature_); + this->compute_target_temperature_(); this->compute_state_(); this->publish_state(); }); @@ -57,6 +58,15 @@ void TuyaClimate::setup() { this->publish_state(); }); } + if (this->eco_id_.has_value()) { + this->parent_->register_listener(*this->eco_id_, [this](const TuyaDatapoint &datapoint) { + this->eco_ = datapoint.value_bool; + ESP_LOGV(TAG, "MCU reported eco is: %s", ONOFF(this->eco_)); + this->compute_preset_(); + this->compute_target_temperature_(); + this->publish_state(); + }); + } } void TuyaClimate::loop() { @@ -100,16 +110,29 @@ void TuyaClimate::control(const climate::ClimateCall &call) { this->parent_->set_integer_datapoint_value(*this->target_temperature_id_, (int) (target_temperature / this->target_temperature_multiplier_)); } + + if (call.get_preset().has_value()) { + const climate::ClimatePreset preset = *call.get_preset(); + if (this->eco_id_.has_value()) { + const bool eco = preset == climate::CLIMATE_PRESET_ECO; + ESP_LOGV(TAG, "Setting eco: %s", ONOFF(eco)); + this->parent_->set_boolean_datapoint_value(*this->eco_id_, eco); + } + } } climate::ClimateTraits TuyaClimate::traits() { auto traits = climate::ClimateTraits(); + traits.set_supports_action(true); traits.set_supports_current_temperature(this->current_temperature_id_.has_value()); if (supports_heat_) traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); if (supports_cool_) traits.add_supported_mode(climate::CLIMATE_MODE_COOL); - traits.set_supports_action(true); + if (this->eco_id_.has_value()) { + traits.add_supported_preset(climate::CLIMATE_PRESET_NONE); + traits.add_supported_preset(climate::CLIMATE_PRESET_ECO); + } return traits; } @@ -125,6 +148,24 @@ void TuyaClimate::dump_config() { ESP_LOGCONFIG(TAG, " Current Temperature has datapoint ID %u", *this->current_temperature_id_); LOG_PIN(" Heating State Pin: ", this->heating_state_pin_); LOG_PIN(" Cooling State Pin: ", this->cooling_state_pin_); + if (this->eco_id_.has_value()) + ESP_LOGCONFIG(TAG, " Eco has datapoint ID %u", *this->eco_id_); +} + +void TuyaClimate::compute_preset_() { + if (this->eco_) { + this->preset = climate::CLIMATE_PRESET_ECO; + } else { + this->preset = climate::CLIMATE_PRESET_NONE; + } +} + +void TuyaClimate::compute_target_temperature_() { + if (this->eco_ && this->eco_temperature_.has_value()) { + this->target_temperature = *this->eco_temperature_; + } else { + this->target_temperature = this->manual_temperature_; + } } void TuyaClimate::compute_state_() { diff --git a/esphome/components/tuya/climate/tuya_climate.h b/esphome/components/tuya/climate/tuya_climate.h index f1a0c13a77..ec19d05308 100644 --- a/esphome/components/tuya/climate/tuya_climate.h +++ b/esphome/components/tuya/climate/tuya_climate.h @@ -32,15 +32,24 @@ class TuyaClimate : public climate::Climate, public Component { void set_target_temperature_multiplier(float temperature_multiplier) { this->target_temperature_multiplier_ = temperature_multiplier; } + void set_eco_id(uint8_t eco_id) { this->eco_id_ = eco_id; } + void set_eco_temperature(float eco_temperature) { this->eco_temperature_ = eco_temperature; } void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } protected: /// Override control to change settings of the climate device. void control(const climate::ClimateCall &call) override; + /// Return the traits of this controller. climate::ClimateTraits traits() override; + /// Re-compute the active preset of this climate controller. + void compute_preset_(); + + /// Re-compute the target temperature of this climate controller. + void compute_target_temperature_(); + /// Re-compute the state of this climate controller. void compute_state_(); @@ -61,9 +70,13 @@ class TuyaClimate : public climate::Climate, public Component { float current_temperature_multiplier_{1.0f}; float target_temperature_multiplier_{1.0f}; float hysteresis_{1.0f}; + optional eco_id_{}; + optional eco_temperature_{}; uint8_t active_state_; bool heating_state_{false}; bool cooling_state_{false}; + float manual_temperature_; + bool eco_; }; } // namespace tuya From c60c61820453a50557f21f79e2d6196be7ff7050 Mon Sep 17 00:00:00 2001 From: Luca Gugelmann <69755789+lgugelmann@users.noreply.github.com> Date: Sun, 19 Sep 2021 23:19:20 +0200 Subject: [PATCH 1370/1841] Fix SPIDevice::write_byte16 to actually take a 16 bit argument (#2345) --- esphome/components/spi/spi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index a4a2e11def..eb8f9ce7ce 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -246,7 +246,7 @@ class SPIDevice { return this->parent_->template write_byte(data); } - void write_byte16(uint8_t data) { + void write_byte16(uint16_t data) { return this->parent_->template write_byte16(data); } From a9908982565d1f386595dd29c2ef2d46fd3089ce Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 00:33:10 +0200 Subject: [PATCH 1371/1841] Add readv and writev for more efficient API packets (#2342) --- esphome/components/api/api_connection.cpp | 29 ++-- esphome/components/api/api_frame_helper.cpp | 127 ++++++++++-------- esphome/components/api/api_frame_helper.h | 5 +- .../components/socket/bsd_sockets_impl.cpp | 48 +++++++ esphome/components/socket/headers.h | 6 + .../components/socket/lwip_raw_tcp_impl.cpp | 87 +++++++++--- esphome/components/socket/socket.h | 2 + 7 files changed, 220 insertions(+), 84 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 31a530c04d..0fa4ca6397 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -702,15 +702,7 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin // string message = 3; buffer.encode_string(3, line, strlen(line)); // SubscribeLogsResponse - 29 - bool success = this->send_buffer(buffer, 29); - if (!success) { - buffer = this->create_buffer(); - // bool send_failed = 4; - buffer.encode_bool(4, true); - return this->send_buffer(buffer, 29); - } else { - return true; - } + return this->send_buffer(buffer, 29); } HelloResponse APIConnection::hello(const HelloRequest &msg) { @@ -783,8 +775,23 @@ void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistant bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) { if (this->remove_) return false; - if (!this->helper_->can_write_without_blocking()) - return false; + if (!this->helper_->can_write_without_blocking()) { + delay(0); + APIError err = helper_->loop(); + if (err != APIError::OK) { + on_fatal_error(); + ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); + return false; + } + if (!this->helper_->can_write_without_blocking()) { + // SubscribeLogsResponse + if (message_type != 29) { + ESP_LOGV(TAG, "Cannot send message because of TCP buffer space"); + } + delay(0); + return false; + } + } APIError err = this->helper_->write_packet(message_type, buffer.get_buffer()->data(), buffer.get_buffer()->size()); if (err == APIError::WOULD_BLOCK) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index e68831e594..15014b7937 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -125,13 +125,6 @@ APIError APINoiseFrameHelper::init() { HELPER_LOG("Setting nonblocking failed with errno %d", errno); return APIError::TCP_NONBLOCKING_FAILED; } - int enable = 1; - err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); - if (err != 0) { - state_ = State::FAILED; - HELPER_LOG("Setting nodelay failed with errno %d", errno); - return APIError::TCP_NODELAY_FAILED; - } // init prologue prologue_.insert(prologue_.end(), PROLOGUE_INIT, PROLOGUE_INIT + strlen(PROLOGUE_INIT)); @@ -494,12 +487,13 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload size_t total_len = 3 + mbuf.size; tmpbuf[1] = (uint8_t)(mbuf.size >> 8); tmpbuf[2] = (uint8_t) mbuf.size; + + struct iovec iov; + iov.iov_base = &tmpbuf[0]; + iov.iov_len = total_len; + // write raw to not have two packets sent if NAGLE disabled - aerr = write_raw_(&tmpbuf[0], total_len); - if (aerr != APIError::OK) { - return aerr; - } - return APIError::OK; + return write_raw_(&iov, 1); } APIError APINoiseFrameHelper::try_send_tx_buf_() { // try send from tx_buf @@ -526,16 +520,19 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() { * @param data The data to write * @param len The length of data */ -APIError APINoiseFrameHelper::write_raw_(const uint8_t *data, size_t len) { - if (len == 0) +APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { + if (iovcnt == 0) return APIError::OK; int err; APIError aerr; - // uncomment for even more debugging + size_t total_write_len = 0; + for (int i = 0; i < iovcnt; i++) { #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); + ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); #endif + total_write_len += iov[i].iov_len; + } if (!tx_buf_.empty()) { // try to empty tx_buf_ first @@ -546,41 +543,56 @@ APIError APINoiseFrameHelper::write_raw_(const uint8_t *data, size_t len) { if (!tx_buf_.empty()) { // tx buf not empty, can't write now because then stream would be inconsistent - tx_buf_.insert(tx_buf_.end(), data, data + len); + for (int i = 0; i < iovcnt; i++) { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base), + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + } return APIError::OK; } - ssize_t sent = socket_->write(data, len); + ssize_t sent = socket_->writev(iov, iovcnt); if (is_would_block(sent)) { // operation would block, add buffer to tx_buf - tx_buf_.insert(tx_buf_.end(), data, data + len); + for (int i = 0; i < iovcnt; i++) { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base), + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + } return APIError::OK; } else if (sent == -1) { // an error occured state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); return APIError::SOCKET_WRITE_FAILED; - } else if (sent != len) { + } else if (sent != total_write_len) { // partially sent, add end to tx_buf - tx_buf_.insert(tx_buf_.end(), data + sent, data + len); + size_t to_consume = sent; + for (int i = 0; i < iovcnt; i++) { + if (to_consume >= iov[i].iov_len) { + to_consume -= iov[i].iov_len; + } else { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base) + to_consume, + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + to_consume = 0; + } + } return APIError::OK; } // fully sent return APIError::OK; } APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) { - APIError aerr; - uint8_t header[3]; header[0] = 0x01; // indicator header[1] = (uint8_t)(len >> 8); header[2] = (uint8_t) len; - aerr = write_raw_(header, 3); - if (aerr != APIError::OK) - return aerr; - aerr = write_raw_(data, len); - return aerr; + struct iovec iov[2]; + iov[0].iov_base = header; + iov[0].iov_len = 3; + iov[1].iov_base = const_cast(data); + iov[1].iov_len = len; + + return write_raw_(iov, 2); } /** Initiate the data structures for the handshake. @@ -709,13 +721,6 @@ APIError APIPlaintextFrameHelper::init() { HELPER_LOG("Setting nonblocking failed with errno %d", errno); return APIError::TCP_NONBLOCKING_FAILED; } - int enable = 1; - err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); - if (err != 0) { - state_ = State::FAILED; - HELPER_LOG("Setting nodelay failed with errno %d", errno); - return APIError::TCP_NODELAY_FAILED; - } state_ = State::DATA; return APIError::OK; @@ -863,15 +868,13 @@ APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *pay ProtoVarInt(payload_len).encode(header); ProtoVarInt(type).encode(header); - aerr = write_raw_(&header[0], header.size()); - if (aerr != APIError::OK) { - return aerr; - } - aerr = write_raw_(payload, payload_len); - if (aerr != APIError::OK) { - return aerr; - } - return APIError::OK; + struct iovec iov[2]; + iov[0].iov_base = &header[0]; + iov[0].iov_len = header.size(); + iov[1].iov_base = const_cast(payload); + iov[1].iov_len = payload_len; + + return write_raw_(iov, 2); } APIError APIPlaintextFrameHelper::try_send_tx_buf_() { // try send from tx_buf @@ -896,16 +899,19 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() { * @param data The data to write * @param len The length of data */ -APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) { - if (len == 0) +APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { + if (iovcnt == 0) return APIError::OK; int err; APIError aerr; - // uncomment for even more debugging + size_t total_write_len = 0; + for (int i = 0; i < iovcnt; i++) { #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); + ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); #endif + total_write_len += iov[i].iov_len; + } if (!tx_buf_.empty()) { // try to empty tx_buf_ first @@ -916,23 +922,38 @@ APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) { if (!tx_buf_.empty()) { // tx buf not empty, can't write now because then stream would be inconsistent - tx_buf_.insert(tx_buf_.end(), data, data + len); + for (int i = 0; i < iovcnt; i++) { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base), + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + } return APIError::OK; } - ssize_t sent = socket_->write(data, len); + ssize_t sent = socket_->writev(iov, iovcnt); if (is_would_block(sent)) { // operation would block, add buffer to tx_buf - tx_buf_.insert(tx_buf_.end(), data, data + len); + for (int i = 0; i < iovcnt; i++) { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base), + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + } return APIError::OK; } else if (sent == -1) { // an error occured state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); return APIError::SOCKET_WRITE_FAILED; - } else if (sent != len) { + } else if (sent != total_write_len) { // partially sent, add end to tx_buf - tx_buf_.insert(tx_buf_.end(), data + sent, data + len); + size_t to_consume = sent; + for (int i = 0; i < iovcnt; i++) { + if (to_consume >= iov[i].iov_len) { + to_consume -= iov[i].iov_len; + } else { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base) + to_consume, + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + to_consume = 0; + } + } return APIError::OK; } // fully sent diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index a9a653cf4f..44df629b2f 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -58,6 +58,7 @@ const char *api_error_to_str(APIError err); class APIFrameHelper { public: + virtual ~APIFrameHelper() = default; virtual APIError init() = 0; virtual APIError loop() = 0; virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; @@ -96,7 +97,7 @@ class APINoiseFrameHelper : public APIFrameHelper { APIError try_read_frame_(ParsedFrame *frame); APIError try_send_tx_buf_(); APIError write_frame_(const uint8_t *data, size_t len); - APIError write_raw_(const uint8_t *data, size_t len); + APIError write_raw_(const struct iovec *iov, int iovcnt); APIError init_handshake_(); APIError check_handshake_finished_(); void send_explicit_handshake_reject_(const std::string &reason); @@ -154,7 +155,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper { APIError try_read_frame_(ParsedFrame *frame); APIError try_send_tx_buf_(); - APIError write_raw_(const uint8_t *data, size_t len); + APIError write_raw_(const struct iovec *iov, int iovcnt); std::unique_ptr socket_; diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index 6fb00ce22d..aa1bcd3b3c 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -6,6 +6,10 @@ #include +#ifdef ARDUINO_ARCH_ESP32 +#include +#endif + namespace esphome { namespace socket { @@ -76,7 +80,51 @@ class BSDSocketImpl : public Socket { } int listen(int backlog) override { return ::listen(fd_, backlog); } ssize_t read(void *buf, size_t len) override { return ::read(fd_, buf, len); } + ssize_t readv(const struct iovec *iov, int iovcnt) override { +#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR < 4 + // esp-idf v3 doesn't have readv, emulate it + ssize_t ret = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t err = this->read(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); + if (err == -1) { + if (ret != 0) + // if we already read some don't return an error + break; + return err; + } + ret += err; + if (err != iov[i].iov_len) + break; + } + return ret; +#else + return ::readv(fd_, iov, iovcnt); +#endif + } ssize_t write(const void *buf, size_t len) override { return ::write(fd_, buf, len); } + ssize_t send(void *buf, size_t len, int flags) { return ::send(fd_, buf, len, flags); } + ssize_t writev(const struct iovec *iov, int iovcnt) override { +#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR < 4 + // esp-idf v3 doesn't have writev, emulate it + ssize_t ret = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t err = + this->send(reinterpret_cast(iov[i].iov_base), iov[i].iov_len, i == iovcnt - 1 ? 0 : MSG_MORE); + if (err == -1) { + if (ret != 0) + // if we already wrote some don't return an error + break; + return err; + } + ret += err; + if (err != iov[i].iov_len) + break; + } + return ret; +#else + return ::writev(fd_, iov, iovcnt); +#endif + } int setblocking(bool blocking) override { int fl = ::fcntl(fd_, F_GETFL, 0); if (blocking) { diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h index da710b760e..fbe8f929a0 100644 --- a/esphome/components/socket/headers.h +++ b/esphome/components/socket/headers.h @@ -81,6 +81,11 @@ struct sockaddr_storage { }; typedef uint32_t socklen_t; +struct iovec { + void *iov_base; + size_t iov_len; +}; + #ifdef ARDUINO_ARCH_ESP8266 // arduino-esp8266 declares a global vars called INADDR_NONE/ANY which are invalid with the define #ifdef INADDR_ANY @@ -104,6 +109,7 @@ typedef uint32_t socklen_t; #include #include #include +#include #include #include #include diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 2147e36632..366f0972ef 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -371,7 +371,23 @@ class LWIPRawImpl : public Socket { return read; } - ssize_t write(const void *buf, size_t len) override { + ssize_t readv(const struct iovec *iov, int iovcnt) override { + ssize_t ret = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t err = read(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); + if (err == -1) { + if (ret != 0) + // if we already read some don't return an error + break; + return err; + } + ret += err; + if (err != iov[i].iov_len) + break; + } + return ret; + } + ssize_t internal_write(const void *buf, size_t len) { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -400,25 +416,60 @@ class LWIPRawImpl : public Socket { errno = ECONNRESET; return -1; } - if (tcp_nagle_disabled(pcb_)) { - LWIP_LOG("tcp_output(%p)", pcb_); - err = tcp_output(pcb_); - if (err == ERR_ABRT) { - LWIP_LOG(" -> err ERR_ABRT"); - // sometimes lwip returns ERR_ABRT for no apparent reason - // the connection works fine afterwards, and back with ESPAsyncTCP we - // indirectly also ignored this error - // FIXME: figure out where this is returned and what it means in this context - return to_send; - } - if (err != ERR_OK) { - LWIP_LOG(" -> err %d", err); - errno = ECONNRESET; - return -1; - } - } return to_send; } + int internal_output() { + LWIP_LOG("tcp_output(%p)", pcb_); + err_t err = tcp_output(pcb_); + if (err == ERR_ABRT) { + LWIP_LOG(" -> err ERR_ABRT"); + // sometimes lwip returns ERR_ABRT for no apparent reason + // the connection works fine afterwards, and back with ESPAsyncTCP we + // indirectly also ignored this error + // FIXME: figure out where this is returned and what it means in this context + return 0; + } + if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); + errno = ECONNRESET; + return -1; + } + return 0; + } + ssize_t write(const void *buf, size_t len) override { + ssize_t written = internal_write(buf, len); + if (written == -1) + return -1; + if (written == 0) + // no need to output if nothing written + return 0; + int err = internal_output(); + if (err == -1) + return -1; + return written; + } + ssize_t writev(const struct iovec *iov, int iovcnt) override { + ssize_t written = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t err = internal_write(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); + if (err == -1) { + if (written != 0) + // if we already read some don't return an error + break; + return err; + } + written += err; + if (err != iov[i].iov_len) + break; + } + if (written == 0) + // no need to output if nothing written + return 0; + int err = internal_output(); + if (err == -1) + return -1; + return written; + } int setblocking(bool blocking) override { if (pcb_ == nullptr) { errno = ECONNRESET; diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index 7a5ce79161..9920610bf5 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -31,7 +31,9 @@ class Socket { virtual int setsockopt(int level, int optname, const void *optval, socklen_t optlen) = 0; virtual int listen(int backlog) = 0; virtual ssize_t read(void *buf, size_t len) = 0; + virtual ssize_t readv(const struct iovec *iov, int iovcnt) = 0; virtual ssize_t write(const void *buf, size_t len) = 0; + virtual ssize_t writev(const struct iovec *iov, int iovcnt) = 0; virtual int setblocking(bool blocking) = 0; virtual int loop() { return 0; }; }; From c78fb90e2f3f89fd2a583296dce115eb46e82585 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Mon, 20 Sep 2021 04:30:41 +1200 Subject: [PATCH 1372/1841] Fix MQTT discovery for sensor state_class (#2331) --- esphome/components/sensor/sensor.cpp | 2 +- esphome/components/sensor/sensor.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 1dbc1c901a..db81edcc61 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -6,7 +6,7 @@ namespace sensor { static const char *const TAG = "sensor"; -const char *state_class_to_string(StateClass state_class) { +std::string state_class_to_string(StateClass state_class) { switch (state_class) { case STATE_CLASS_MEASUREMENT: return "measurement"; diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 34b8b26a54..209e83d205 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -1,8 +1,8 @@ #pragma once +#include "esphome/components/sensor/filter.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" -#include "esphome/components/sensor/filter.h" namespace esphome { namespace sensor { @@ -13,7 +13,7 @@ namespace sensor { if (!(obj)->get_device_class().empty()) { \ ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ } \ - ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->state_class)); \ + ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->get_state_class()).c_str()); \ ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \ ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \ if (!(obj)->get_icon().empty()) { \ @@ -36,7 +36,7 @@ enum StateClass : uint8_t { STATE_CLASS_TOTAL_INCREASING = 2, }; -const char *state_class_to_string(StateClass state_class); +std::string state_class_to_string(StateClass state_class); /** Base-class for all sensors. * From 4c61cf153c8139acf20053fe4c8904fa45953dae Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 19 Sep 2021 18:31:20 +0200 Subject: [PATCH 1373/1841] Light transition fixes (#2320) --- esphome/components/light/addressable_light.cpp | 11 ++++++----- esphome/components/light/light_state.cpp | 3 +++ esphome/components/light/transformers.h | 8 +++++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index a8fa2cd7ac..599d43d8b1 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -62,10 +62,13 @@ void AddressableLightTransformer::start() { } optional AddressableLightTransformer::apply() { - // Don't try to transition over running effects, instead immediately use the target values. write_state() and the - // effects pick up the change from current_values. + float smoothed_progress = LightTransitionTransformer::smoothed_progress(this->get_progress_()); + + // When running an output-buffer modifying effect, don't try to transition individual LEDs, but instead just fade the + // LightColorValues. write_state() then picks up the change in brightness, and the color change is picked up by the + // effects which respect it. if (this->light_.is_effect_active()) - return this->target_values_; + return LightColorValues::lerp(this->get_start_values(), this->get_target_values(), smoothed_progress); // Use a specialized transition for addressable lights: instead of using a unified transition for // all LEDs, we use the current state of each LED as the start. @@ -75,8 +78,6 @@ optional AddressableLightTransformer::apply() { // Instead, we "fake" the look of the LERP by using an exponential average over time and using // dynamically-calculated alpha values to match the look. - float smoothed_progress = LightTransitionTransformer::smoothed_progress(this->get_progress_()); - float denom = (1.0f - smoothed_progress); float alpha = denom == 0.0f ? 0.0f : (smoothed_progress - this->last_transition_progress_) / denom; diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 945d3910d5..d9574abf7a 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -121,6 +121,9 @@ void LightState::loop() { } if (this->transformer_->is_finished()) { + // if the transition has written directly to the output, current_values is outdated, so update it + this->current_values = this->transformer_->get_target_values(); + this->transformer_->stop(); this->transformer_ = nullptr; this->target_state_reached_callback_.call(); diff --git a/esphome/components/light/transformers.h b/esphome/components/light/transformers.h index d501d53f72..90646f4e61 100644 --- a/esphome/components/light/transformers.h +++ b/esphome/components/light/transformers.h @@ -12,12 +12,18 @@ namespace light { class LightTransitionTransformer : public LightTransformer { public: void start() override { - // When turning light on from off state, use colors from target state. + // When turning light on from off state, use target state and only increase brightness from zero. if (!this->start_values_.is_on() && this->target_values_.is_on()) { this->start_values_ = LightColorValues(this->target_values_); this->start_values_.set_brightness(0.0f); } + // When turning light off from on state, use source state and only decrease brightness to zero. + if (this->start_values_.is_on() && !this->target_values_.is_on()) { + this->target_values_ = LightColorValues(this->start_values_); + this->target_values_.set_brightness(0.0f); + } + // When changing color mode, go through off state, as color modes are orthogonal and there can't be two active. if (this->start_values_.get_color_mode() != this->target_values_.get_color_mode()) { this->changing_color_mode_ = true; From e3ffecefc098aba2499ce3f86c132486f855dc9b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 19 Sep 2021 18:31:31 +0200 Subject: [PATCH 1374/1841] Cease using deprecated Cover methods in automations (#2326) --- esphome/components/cover/automation.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index 0092f987f2..0b364e1e09 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -11,7 +11,7 @@ template class OpenAction : public Action { public: explicit OpenAction(Cover *cover) : cover_(cover) {} - void play(Ts... x) override { this->cover_->open(); } + void play(Ts... x) override { this->cover_->make_call().set_command_open().perform(); } protected: Cover *cover_; @@ -21,7 +21,7 @@ template class CloseAction : public Action { public: explicit CloseAction(Cover *cover) : cover_(cover) {} - void play(Ts... x) override { this->cover_->close(); } + void play(Ts... x) override { this->cover_->make_call().set_command_close().perform(); } protected: Cover *cover_; @@ -31,7 +31,7 @@ template class StopAction : public Action { public: explicit StopAction(Cover *cover) : cover_(cover) {} - void play(Ts... x) override { this->cover_->stop(); } + void play(Ts... x) override { this->cover_->make_call().set_command_stop().perform(); } protected: Cover *cover_; From d180aee57f8f93dc50cd38ed50dd85bba68246c0 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 19 Sep 2021 18:46:26 +0200 Subject: [PATCH 1375/1841] Apply color brightness to addressable light effects (#2321) --- esphome/components/light/addressable_light.cpp | 6 +++--- esphome/components/light/addressable_light.h | 3 +++ esphome/components/light/addressable_light_effect.h | 7 ++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index 599d43d8b1..f3e6c0ef1d 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -27,7 +27,7 @@ std::unique_ptr AddressableLight::create_default_transition() return make_unique(*this); } -Color esp_color_from_light_color_values(LightColorValues val) { +Color color_from_light_color_values(LightColorValues val) { auto r = to_uint8_scale(val.get_color_brightness() * val.get_red()); auto g = to_uint8_scale(val.get_color_brightness() * val.get_green()); auto b = to_uint8_scale(val.get_color_brightness() * val.get_blue()); @@ -44,7 +44,7 @@ void AddressableLight::update_state(LightState *state) { return; // don't use LightState helper, gamma correction+brightness is handled by ESPColorView - this->all() = esp_color_from_light_color_values(val); + this->all() = color_from_light_color_values(val); this->schedule_show(); } @@ -54,7 +54,7 @@ void AddressableLightTransformer::start() { return; auto end_values = this->target_values_; - this->target_color_ = esp_color_from_light_color_values(end_values); + this->target_color_ = color_from_light_color_values(end_values); // our transition will handle brightness, disable brightness in correction. this->light_.correction_.set_local_brightness(255); diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index bba2158457..97f4a4687d 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -19,6 +19,9 @@ namespace light { using ESPColor ESPDEPRECATED("esphome::light::ESPColor is deprecated, use esphome::Color instead.", "v1.21") = Color; +/// Convert the color information from a `LightColorValues` object to a `Color` object (does not apply brightness). +Color color_from_light_color_values(LightColorValues val); + class AddressableLight : public LightOutput, public Component { public: virtual int32_t size() const = 0; diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index 1cb29dfa4e..358fe69c23 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -38,11 +38,8 @@ class AddressableLightEffect : public LightEffect { void stop() override { this->get_addressable_()->set_effect_active(false); } virtual void apply(AddressableLight &it, const Color ¤t_color) = 0; void apply() override { - LightColorValues color = this->state_->remote_values; - // not using any color correction etc. that will be handled by the addressable layer - Color current_color = - Color(static_cast(color.get_red() * 255), static_cast(color.get_green() * 255), - static_cast(color.get_blue() * 255), static_cast(color.get_white() * 255)); + // not using any color correction etc. that will be handled by the addressable layer through ESPColorCorrection + Color current_color = color_from_light_color_values(this->state_->remote_values); this->apply(*this->get_addressable_(), current_color); } From 7c17e72db42ae6016bf315a7b09368395b38736d Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 00:33:10 +0200 Subject: [PATCH 1376/1841] Add readv and writev for more efficient API packets (#2342) --- esphome/components/api/api_connection.cpp | 29 ++-- esphome/components/api/api_frame_helper.cpp | 127 ++++++++++-------- esphome/components/api/api_frame_helper.h | 5 +- .../components/socket/bsd_sockets_impl.cpp | 48 +++++++ esphome/components/socket/headers.h | 6 + .../components/socket/lwip_raw_tcp_impl.cpp | 87 +++++++++--- esphome/components/socket/socket.h | 2 + 7 files changed, 220 insertions(+), 84 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 786fc28d68..8e6d1bc1c2 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -702,15 +702,7 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin // string message = 3; buffer.encode_string(3, line, strlen(line)); // SubscribeLogsResponse - 29 - bool success = this->send_buffer(buffer, 29); - if (!success) { - buffer = this->create_buffer(); - // bool send_failed = 4; - buffer.encode_bool(4, true); - return this->send_buffer(buffer, 29); - } else { - return true; - } + return this->send_buffer(buffer, 29); } HelloResponse APIConnection::hello(const HelloRequest &msg) { @@ -783,8 +775,23 @@ void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistant bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) { if (this->remove_) return false; - if (!this->helper_->can_write_without_blocking()) - return false; + if (!this->helper_->can_write_without_blocking()) { + delay(0); + APIError err = helper_->loop(); + if (err != APIError::OK) { + on_fatal_error(); + ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); + return false; + } + if (!this->helper_->can_write_without_blocking()) { + // SubscribeLogsResponse + if (message_type != 29) { + ESP_LOGV(TAG, "Cannot send message because of TCP buffer space"); + } + delay(0); + return false; + } + } APIError err = this->helper_->write_packet(message_type, buffer.get_buffer()->data(), buffer.get_buffer()->size()); if (err == APIError::WOULD_BLOCK) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index e68831e594..15014b7937 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -125,13 +125,6 @@ APIError APINoiseFrameHelper::init() { HELPER_LOG("Setting nonblocking failed with errno %d", errno); return APIError::TCP_NONBLOCKING_FAILED; } - int enable = 1; - err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); - if (err != 0) { - state_ = State::FAILED; - HELPER_LOG("Setting nodelay failed with errno %d", errno); - return APIError::TCP_NODELAY_FAILED; - } // init prologue prologue_.insert(prologue_.end(), PROLOGUE_INIT, PROLOGUE_INIT + strlen(PROLOGUE_INIT)); @@ -494,12 +487,13 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload size_t total_len = 3 + mbuf.size; tmpbuf[1] = (uint8_t)(mbuf.size >> 8); tmpbuf[2] = (uint8_t) mbuf.size; + + struct iovec iov; + iov.iov_base = &tmpbuf[0]; + iov.iov_len = total_len; + // write raw to not have two packets sent if NAGLE disabled - aerr = write_raw_(&tmpbuf[0], total_len); - if (aerr != APIError::OK) { - return aerr; - } - return APIError::OK; + return write_raw_(&iov, 1); } APIError APINoiseFrameHelper::try_send_tx_buf_() { // try send from tx_buf @@ -526,16 +520,19 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() { * @param data The data to write * @param len The length of data */ -APIError APINoiseFrameHelper::write_raw_(const uint8_t *data, size_t len) { - if (len == 0) +APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { + if (iovcnt == 0) return APIError::OK; int err; APIError aerr; - // uncomment for even more debugging + size_t total_write_len = 0; + for (int i = 0; i < iovcnt; i++) { #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); + ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); #endif + total_write_len += iov[i].iov_len; + } if (!tx_buf_.empty()) { // try to empty tx_buf_ first @@ -546,41 +543,56 @@ APIError APINoiseFrameHelper::write_raw_(const uint8_t *data, size_t len) { if (!tx_buf_.empty()) { // tx buf not empty, can't write now because then stream would be inconsistent - tx_buf_.insert(tx_buf_.end(), data, data + len); + for (int i = 0; i < iovcnt; i++) { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base), + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + } return APIError::OK; } - ssize_t sent = socket_->write(data, len); + ssize_t sent = socket_->writev(iov, iovcnt); if (is_would_block(sent)) { // operation would block, add buffer to tx_buf - tx_buf_.insert(tx_buf_.end(), data, data + len); + for (int i = 0; i < iovcnt; i++) { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base), + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + } return APIError::OK; } else if (sent == -1) { // an error occured state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); return APIError::SOCKET_WRITE_FAILED; - } else if (sent != len) { + } else if (sent != total_write_len) { // partially sent, add end to tx_buf - tx_buf_.insert(tx_buf_.end(), data + sent, data + len); + size_t to_consume = sent; + for (int i = 0; i < iovcnt; i++) { + if (to_consume >= iov[i].iov_len) { + to_consume -= iov[i].iov_len; + } else { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base) + to_consume, + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + to_consume = 0; + } + } return APIError::OK; } // fully sent return APIError::OK; } APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) { - APIError aerr; - uint8_t header[3]; header[0] = 0x01; // indicator header[1] = (uint8_t)(len >> 8); header[2] = (uint8_t) len; - aerr = write_raw_(header, 3); - if (aerr != APIError::OK) - return aerr; - aerr = write_raw_(data, len); - return aerr; + struct iovec iov[2]; + iov[0].iov_base = header; + iov[0].iov_len = 3; + iov[1].iov_base = const_cast(data); + iov[1].iov_len = len; + + return write_raw_(iov, 2); } /** Initiate the data structures for the handshake. @@ -709,13 +721,6 @@ APIError APIPlaintextFrameHelper::init() { HELPER_LOG("Setting nonblocking failed with errno %d", errno); return APIError::TCP_NONBLOCKING_FAILED; } - int enable = 1; - err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); - if (err != 0) { - state_ = State::FAILED; - HELPER_LOG("Setting nodelay failed with errno %d", errno); - return APIError::TCP_NODELAY_FAILED; - } state_ = State::DATA; return APIError::OK; @@ -863,15 +868,13 @@ APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *pay ProtoVarInt(payload_len).encode(header); ProtoVarInt(type).encode(header); - aerr = write_raw_(&header[0], header.size()); - if (aerr != APIError::OK) { - return aerr; - } - aerr = write_raw_(payload, payload_len); - if (aerr != APIError::OK) { - return aerr; - } - return APIError::OK; + struct iovec iov[2]; + iov[0].iov_base = &header[0]; + iov[0].iov_len = header.size(); + iov[1].iov_base = const_cast(payload); + iov[1].iov_len = payload_len; + + return write_raw_(iov, 2); } APIError APIPlaintextFrameHelper::try_send_tx_buf_() { // try send from tx_buf @@ -896,16 +899,19 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() { * @param data The data to write * @param len The length of data */ -APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) { - if (len == 0) +APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { + if (iovcnt == 0) return APIError::OK; int err; APIError aerr; - // uncomment for even more debugging + size_t total_write_len = 0; + for (int i = 0; i < iovcnt; i++) { #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); + ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); #endif + total_write_len += iov[i].iov_len; + } if (!tx_buf_.empty()) { // try to empty tx_buf_ first @@ -916,23 +922,38 @@ APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) { if (!tx_buf_.empty()) { // tx buf not empty, can't write now because then stream would be inconsistent - tx_buf_.insert(tx_buf_.end(), data, data + len); + for (int i = 0; i < iovcnt; i++) { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base), + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + } return APIError::OK; } - ssize_t sent = socket_->write(data, len); + ssize_t sent = socket_->writev(iov, iovcnt); if (is_would_block(sent)) { // operation would block, add buffer to tx_buf - tx_buf_.insert(tx_buf_.end(), data, data + len); + for (int i = 0; i < iovcnt; i++) { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base), + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + } return APIError::OK; } else if (sent == -1) { // an error occured state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); return APIError::SOCKET_WRITE_FAILED; - } else if (sent != len) { + } else if (sent != total_write_len) { // partially sent, add end to tx_buf - tx_buf_.insert(tx_buf_.end(), data + sent, data + len); + size_t to_consume = sent; + for (int i = 0; i < iovcnt; i++) { + if (to_consume >= iov[i].iov_len) { + to_consume -= iov[i].iov_len; + } else { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base) + to_consume, + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + to_consume = 0; + } + } return APIError::OK; } // fully sent diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index a9a653cf4f..44df629b2f 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -58,6 +58,7 @@ const char *api_error_to_str(APIError err); class APIFrameHelper { public: + virtual ~APIFrameHelper() = default; virtual APIError init() = 0; virtual APIError loop() = 0; virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; @@ -96,7 +97,7 @@ class APINoiseFrameHelper : public APIFrameHelper { APIError try_read_frame_(ParsedFrame *frame); APIError try_send_tx_buf_(); APIError write_frame_(const uint8_t *data, size_t len); - APIError write_raw_(const uint8_t *data, size_t len); + APIError write_raw_(const struct iovec *iov, int iovcnt); APIError init_handshake_(); APIError check_handshake_finished_(); void send_explicit_handshake_reject_(const std::string &reason); @@ -154,7 +155,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper { APIError try_read_frame_(ParsedFrame *frame); APIError try_send_tx_buf_(); - APIError write_raw_(const uint8_t *data, size_t len); + APIError write_raw_(const struct iovec *iov, int iovcnt); std::unique_ptr socket_; diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index a0cdb6ec42..aca15f118e 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -5,6 +5,10 @@ #include +#ifdef ARDUINO_ARCH_ESP32 +#include +#endif + namespace esphome { namespace socket { @@ -75,7 +79,51 @@ class BSDSocketImpl : public Socket { } int listen(int backlog) override { return ::listen(fd_, backlog); } ssize_t read(void *buf, size_t len) override { return ::read(fd_, buf, len); } + ssize_t readv(const struct iovec *iov, int iovcnt) override { +#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR < 4 + // esp-idf v3 doesn't have readv, emulate it + ssize_t ret = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t err = this->read(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); + if (err == -1) { + if (ret != 0) + // if we already read some don't return an error + break; + return err; + } + ret += err; + if (err != iov[i].iov_len) + break; + } + return ret; +#else + return ::readv(fd_, iov, iovcnt); +#endif + } ssize_t write(const void *buf, size_t len) override { return ::write(fd_, buf, len); } + ssize_t send(void *buf, size_t len, int flags) { return ::send(fd_, buf, len, flags); } + ssize_t writev(const struct iovec *iov, int iovcnt) override { +#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR < 4 + // esp-idf v3 doesn't have writev, emulate it + ssize_t ret = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t err = + this->send(reinterpret_cast(iov[i].iov_base), iov[i].iov_len, i == iovcnt - 1 ? 0 : MSG_MORE); + if (err == -1) { + if (ret != 0) + // if we already wrote some don't return an error + break; + return err; + } + ret += err; + if (err != iov[i].iov_len) + break; + } + return ret; +#else + return ::writev(fd_, iov, iovcnt); +#endif + } int setblocking(bool blocking) override { int fl = ::fcntl(fd_, F_GETFL, 0); if (blocking) { diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h index da710b760e..fbe8f929a0 100644 --- a/esphome/components/socket/headers.h +++ b/esphome/components/socket/headers.h @@ -81,6 +81,11 @@ struct sockaddr_storage { }; typedef uint32_t socklen_t; +struct iovec { + void *iov_base; + size_t iov_len; +}; + #ifdef ARDUINO_ARCH_ESP8266 // arduino-esp8266 declares a global vars called INADDR_NONE/ANY which are invalid with the define #ifdef INADDR_ANY @@ -104,6 +109,7 @@ typedef uint32_t socklen_t; #include #include #include +#include #include #include #include diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 39741ea7ec..3c225aeb82 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -371,7 +371,23 @@ class LWIPRawImpl : public Socket { return read; } - ssize_t write(const void *buf, size_t len) override { + ssize_t readv(const struct iovec *iov, int iovcnt) override { + ssize_t ret = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t err = read(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); + if (err == -1) { + if (ret != 0) + // if we already read some don't return an error + break; + return err; + } + ret += err; + if (err != iov[i].iov_len) + break; + } + return ret; + } + ssize_t internal_write(const void *buf, size_t len) { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -400,25 +416,60 @@ class LWIPRawImpl : public Socket { errno = ECONNRESET; return -1; } - if (tcp_nagle_disabled(pcb_)) { - LWIP_LOG("tcp_output(%p)", pcb_); - err = tcp_output(pcb_); - if (err == ERR_ABRT) { - LWIP_LOG(" -> err ERR_ABRT"); - // sometimes lwip returns ERR_ABRT for no apparent reason - // the connection works fine afterwards, and back with ESPAsyncTCP we - // indirectly also ignored this error - // FIXME: figure out where this is returned and what it means in this context - return to_send; - } - if (err != ERR_OK) { - LWIP_LOG(" -> err %d", err); - errno = ECONNRESET; - return -1; - } - } return to_send; } + int internal_output() { + LWIP_LOG("tcp_output(%p)", pcb_); + err_t err = tcp_output(pcb_); + if (err == ERR_ABRT) { + LWIP_LOG(" -> err ERR_ABRT"); + // sometimes lwip returns ERR_ABRT for no apparent reason + // the connection works fine afterwards, and back with ESPAsyncTCP we + // indirectly also ignored this error + // FIXME: figure out where this is returned and what it means in this context + return 0; + } + if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); + errno = ECONNRESET; + return -1; + } + return 0; + } + ssize_t write(const void *buf, size_t len) override { + ssize_t written = internal_write(buf, len); + if (written == -1) + return -1; + if (written == 0) + // no need to output if nothing written + return 0; + int err = internal_output(); + if (err == -1) + return -1; + return written; + } + ssize_t writev(const struct iovec *iov, int iovcnt) override { + ssize_t written = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t err = internal_write(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); + if (err == -1) { + if (written != 0) + // if we already read some don't return an error + break; + return err; + } + written += err; + if (err != iov[i].iov_len) + break; + } + if (written == 0) + // no need to output if nothing written + return 0; + int err = internal_output(); + if (err == -1) + return -1; + return written; + } int setblocking(bool blocking) override { if (pcb_ == nullptr) { errno = ECONNRESET; diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index 7a5ce79161..9920610bf5 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -31,7 +31,9 @@ class Socket { virtual int setsockopt(int level, int optname, const void *optval, socklen_t optlen) = 0; virtual int listen(int backlog) = 0; virtual ssize_t read(void *buf, size_t len) = 0; + virtual ssize_t readv(const struct iovec *iov, int iovcnt) = 0; virtual ssize_t write(const void *buf, size_t len) = 0; + virtual ssize_t writev(const struct iovec *iov, int iovcnt) = 0; virtual int setblocking(bool blocking) = 0; virtual int loop() { return 0; }; }; From 954b8a0cfff66d54e6a24d16aab5849bfbd6e627 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 20 Sep 2021 14:16:57 +1200 Subject: [PATCH 1377/1841] Bump version to 2021.9.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 5a5351f1b0..fb7250578c 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.0" +__version__ = "2021.9.1" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 8bda8e5393bcb339120214dc17e8e50fa21f6db7 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 18:58:49 +0200 Subject: [PATCH 1378/1841] Clean-up sensor integration (#2275) --- esphome/components/api/api_connection.cpp | 2 +- esphome/components/demo/demo_sensor.h | 2 +- esphome/components/mqtt/mqtt_sensor.cpp | 15 +-- esphome/components/sensor/filter.cpp | 28 ----- esphome/components/sensor/filter.h | 19 --- esphome/components/sensor/sensor.cpp | 106 ++++++++--------- esphome/components/sensor/sensor.h | 138 ++++++++-------------- 7 files changed, 101 insertions(+), 209 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 8e6d1bc1c2..d64cff96c2 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -422,7 +422,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { msg.accuracy_decimals = sensor->get_accuracy_decimals(); msg.force_update = sensor->get_force_update(); msg.device_class = sensor->get_device_class(); - msg.state_class = static_cast(sensor->state_class); + msg.state_class = static_cast(sensor->get_state_class()); msg.disabled_by_default = sensor->is_disabled_by_default(); return this->send_list_entities_sensor_response(msg); diff --git a/esphome/components/demo/demo_sensor.h b/esphome/components/demo/demo_sensor.h index 344aaf26f8..9a35674124 100644 --- a/esphome/components/demo/demo_sensor.h +++ b/esphome/components/demo/demo_sensor.h @@ -11,7 +11,7 @@ class DemoSensor : public sensor::Sensor, public PollingComponent { public: void update() override { float val = random_float(); - bool increasing = this->state_class == sensor::STATE_CLASS_TOTAL_INCREASING; + bool increasing = this->get_state_class() == sensor::STATE_CLASS_TOTAL_INCREASING; if (increasing) { float base = isnan(this->state) ? 0.0f : this->state; this->publish_state(base + val * 10); diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index d440e30fc4..e921056167 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -31,16 +31,9 @@ void MQTTSensorComponent::dump_config() { std::string MQTTSensorComponent::component_type() const { return "sensor"; } uint32_t MQTTSensorComponent::get_expire_after() const { - if (this->expire_after_.has_value()) { + if (this->expire_after_.has_value()) return *this->expire_after_; - } else { -#ifdef USE_DEEP_SLEEP - if (deep_sleep::global_has_deep_sleep) { - return 0; - } -#endif - return this->sensor_->calculate_expected_filter_update_interval() * 5; - } + return 0; } void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire_after_ = expire_after; } void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } @@ -61,8 +54,8 @@ void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo if (this->sensor_->get_force_update()) root["force_update"] = true; - if (this->sensor_->state_class != STATE_CLASS_NONE) - root["state_class"] = state_class_to_string(this->sensor_->state_class); + if (this->sensor_->get_state_class() != STATE_CLASS_NONE) + root["state_class"] = state_class_to_string(this->sensor_->get_state_class()); config.command_topic = false; } diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index bbe47b43ec..f048189959 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -8,7 +8,6 @@ namespace sensor { static const char *const TAG = "sensor.filter"; // Filter -uint32_t Filter::expected_interval(uint32_t input) { return input; } void Filter::input(float value) { ESP_LOGVV(TAG, "Filter(%p)::input(%f)", this, value); optional out = this->new_value(value); @@ -29,15 +28,6 @@ void Filter::initialize(Sensor *parent, Filter *next) { this->parent_ = parent; this->next_ = next; } -uint32_t Filter::calculate_remaining_interval(uint32_t input) { - uint32_t this_interval = this->expected_interval(input); - ESP_LOGVV(TAG, "Filter(%p)::calculate_remaining_interval(%u) -> %u", this, input, this_interval); - if (this->next_ == nullptr) { - return this_interval; - } else { - return this->next_->calculate_remaining_interval(this_interval); - } -} // MedianFilter MedianFilter::MedianFilter(size_t window_size, size_t send_every, size_t send_first_at) @@ -75,8 +65,6 @@ optional MedianFilter::new_value(float value) { return {}; } -uint32_t MedianFilter::expected_interval(uint32_t input) { return input * this->send_every_; } - // MinFilter MinFilter::MinFilter(size_t window_size, size_t send_every, size_t send_first_at) : send_every_(send_every), send_at_(send_every - send_first_at), window_size_(window_size) {} @@ -106,8 +94,6 @@ optional MinFilter::new_value(float value) { return {}; } -uint32_t MinFilter::expected_interval(uint32_t input) { return input * this->send_every_; } - // MaxFilter MaxFilter::MaxFilter(size_t window_size, size_t send_every, size_t send_first_at) : send_every_(send_every), send_at_(send_every - send_first_at), window_size_(window_size) {} @@ -137,8 +123,6 @@ optional MaxFilter::new_value(float value) { return {}; } -uint32_t MaxFilter::expected_interval(uint32_t input) { return input * this->send_every_; } - // SlidingWindowMovingAverageFilter SlidingWindowMovingAverageFilter::SlidingWindowMovingAverageFilter(size_t window_size, size_t send_every, size_t send_first_at) @@ -177,8 +161,6 @@ optional SlidingWindowMovingAverageFilter::new_value(float value) { return {}; } -uint32_t SlidingWindowMovingAverageFilter::expected_interval(uint32_t input) { return input * this->send_every_; } - // ExponentialMovingAverageFilter ExponentialMovingAverageFilter::ExponentialMovingAverageFilter(float alpha, size_t send_every) : send_every_(send_every), send_at_(send_every - 1), alpha_(alpha) {} @@ -203,7 +185,6 @@ optional ExponentialMovingAverageFilter::new_value(float value) { } void ExponentialMovingAverageFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; } void ExponentialMovingAverageFilter::set_alpha(float alpha) { this->alpha_ = alpha; } -uint32_t ExponentialMovingAverageFilter::expected_interval(uint32_t input) { return input * this->send_every_; } // LambdaFilter LambdaFilter::LambdaFilter(lambda_filter_t lambda_filter) : lambda_filter_(std::move(lambda_filter)) {} @@ -296,14 +277,6 @@ void OrFilter::initialize(Sensor *parent, Filter *next) { this->phi_.initialize(parent, nullptr); } -uint32_t OrFilter::expected_interval(uint32_t input) { - uint32_t min_interval = UINT32_MAX; - for (Filter *filter : this->filters_) { - min_interval = std::min(min_interval, filter->calculate_remaining_interval(input)); - } - - return min_interval; -} // DebounceFilter optional DebounceFilter::new_value(float value) { this->set_timeout("debounce", this->time_period_, [this, value]() { this->output(value); }); @@ -324,7 +297,6 @@ optional HeartbeatFilter::new_value(float value) { return {}; } -uint32_t HeartbeatFilter::expected_interval(uint32_t input) { return this->time_period_; } void HeartbeatFilter::setup() { this->set_interval("heartbeat", this->time_period_, [this]() { ESP_LOGVV(TAG, "HeartbeatFilter(%p)::interval(has_value=%s, last_input=%f)", this, YESNO(this->has_value_), diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 270a91ccff..29a6813ea9 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -33,11 +33,6 @@ class Filter { void input(float value); - /// Return the amount of time that this filter is expected to take based on the input time interval. - virtual uint32_t expected_interval(uint32_t input); - - uint32_t calculate_remaining_interval(uint32_t input); - void output(float value); protected: @@ -68,8 +63,6 @@ class MedianFilter : public Filter { void set_send_every(size_t send_every); void set_window_size(size_t window_size); - uint32_t expected_interval(uint32_t input) override; - protected: std::deque queue_; size_t send_every_; @@ -98,8 +91,6 @@ class MinFilter : public Filter { void set_send_every(size_t send_every); void set_window_size(size_t window_size); - uint32_t expected_interval(uint32_t input) override; - protected: std::deque queue_; size_t send_every_; @@ -128,8 +119,6 @@ class MaxFilter : public Filter { void set_send_every(size_t send_every); void set_window_size(size_t window_size); - uint32_t expected_interval(uint32_t input) override; - protected: std::deque queue_; size_t send_every_; @@ -159,8 +148,6 @@ class SlidingWindowMovingAverageFilter : public Filter { void set_send_every(size_t send_every); void set_window_size(size_t window_size); - uint32_t expected_interval(uint32_t input) override; - protected: float sum_{0.0}; std::deque queue_; @@ -183,8 +170,6 @@ class ExponentialMovingAverageFilter : public Filter { void set_send_every(size_t send_every); void set_alpha(float alpha); - uint32_t expected_interval(uint32_t input) override; - protected: bool first_value_{true}; float accumulator_{0.0f}; @@ -279,8 +264,6 @@ class HeartbeatFilter : public Filter, public Component { optional new_value(float value) override; - uint32_t expected_interval(uint32_t input) override; - float get_setup_priority() const override; protected: @@ -306,8 +289,6 @@ class OrFilter : public Filter { void initialize(Sensor *parent, Filter *next) override; - uint32_t expected_interval(uint32_t input) override; - optional new_value(float value) override; protected: diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index db81edcc61..0dc0275715 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -18,6 +18,51 @@ std::string state_class_to_string(StateClass state_class) { } } +Sensor::Sensor(const std::string &name) : Nameable(name), state(NAN), raw_state(NAN) {} +Sensor::Sensor() : Sensor("") {} + +std::string Sensor::get_unit_of_measurement() { + if (this->unit_of_measurement_.has_value()) + return *this->unit_of_measurement_; + return this->unit_of_measurement(); +} +void Sensor::set_unit_of_measurement(const std::string &unit_of_measurement) { + this->unit_of_measurement_ = unit_of_measurement; +} +std::string Sensor::unit_of_measurement() { return ""; } + +std::string Sensor::get_icon() { + if (this->icon_.has_value()) + return *this->icon_; + return this->icon(); +} +void Sensor::set_icon(const std::string &icon) { this->icon_ = icon; } +std::string Sensor::icon() { return ""; } + +int8_t Sensor::get_accuracy_decimals() { + if (this->accuracy_decimals_.has_value()) + return *this->accuracy_decimals_; + return this->accuracy_decimals(); +} +void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; } +int8_t Sensor::accuracy_decimals() { return 0; } + +std::string Sensor::get_device_class() { + if (this->device_class_.has_value()) + return *this->device_class_; + return this->device_class(); +} +void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } +std::string Sensor::device_class() { return ""; } + +void Sensor::set_state_class(StateClass state_class) { this->state_class_ = state_class; } +StateClass Sensor::get_state_class() { + if (this->state_class_.has_value()) + return *this->state_class_; + return this->state_class(); +} +StateClass Sensor::state_class() { return StateClass::STATE_CLASS_NONE; } + void Sensor::publish_state(float state) { this->raw_state = state; this->raw_callback_.call(state); @@ -30,54 +75,12 @@ void Sensor::publish_state(float state) { this->filter_list_->input(state); } } -std::string Sensor::unit_of_measurement() { return ""; } -std::string Sensor::icon() { return ""; } -uint32_t Sensor::update_interval() { return 0; } -int8_t Sensor::accuracy_decimals() { return 0; } -Sensor::Sensor(const std::string &name) : Nameable(name), state(NAN), raw_state(NAN) {} -Sensor::Sensor() : Sensor("") {} -void Sensor::set_unit_of_measurement(const std::string &unit_of_measurement) { - this->unit_of_measurement_ = unit_of_measurement; -} -void Sensor::set_icon(const std::string &icon) { this->icon_ = icon; } -void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; } void Sensor::add_on_state_callback(std::function &&callback) { this->callback_.add(std::move(callback)); } void Sensor::add_on_raw_state_callback(std::function &&callback) { this->raw_callback_.add(std::move(callback)); } -std::string Sensor::get_icon() { - if (this->icon_.has_value()) - return *this->icon_; - return this->icon(); -} -void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } -std::string Sensor::get_device_class() { - if (this->device_class_.has_value()) - return *this->device_class_; - return this->device_class(); -} -std::string Sensor::device_class() { return ""; } -void Sensor::set_state_class(StateClass state_class) { this->state_class = state_class; } -void Sensor::set_state_class(const std::string &state_class) { - if (str_equals_case_insensitive(state_class, "measurement")) { - this->state_class = STATE_CLASS_MEASUREMENT; - } else if (str_equals_case_insensitive(state_class, "total_increasing")) { - this->state_class = STATE_CLASS_TOTAL_INCREASING; - } else { - ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str()); - } -} -std::string Sensor::get_unit_of_measurement() { - if (this->unit_of_measurement_.has_value()) - return *this->unit_of_measurement_; - return this->unit_of_measurement(); -} -int8_t Sensor::get_accuracy_decimals() { - if (this->accuracy_decimals_.has_value()) - return *this->accuracy_decimals_; - return this->accuracy_decimals(); -} + void Sensor::add_filter(Filter *filter) { // inefficient, but only happens once on every sensor setup and nobody's going to have massive amounts of // filters @@ -119,24 +122,7 @@ void Sensor::internal_send_state_to_frontend(float state) { this->callback_.call(state); } bool Sensor::has_state() const { return this->has_state_; } -uint32_t Sensor::calculate_expected_filter_update_interval() { - uint32_t interval = this->update_interval(); - if (interval == 4294967295UL) - // update_interval: never - return 0; - - if (this->filter_list_ == nullptr) { - return interval; - } - - return this->filter_list_->calculate_remaining_interval(interval); -} uint32_t Sensor::hash_base() { return 2455723294UL; } -PollingSensorComponent::PollingSensorComponent(const std::string &name, uint32_t update_interval) - : PollingComponent(update_interval), Sensor(name) {} - -uint32_t PollingSensorComponent::update_interval() { return this->get_update_interval(); } - } // namespace sensor } // namespace esphome diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 209e83d205..85f56eb8b9 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -47,26 +47,42 @@ class Sensor : public Nameable { explicit Sensor(); explicit Sensor(const std::string &name); - /** Manually set the unit of measurement of this sensor. By default the sensor's default defined by - * unit_of_measurement() is used. - * - * @param unit_of_measurement The unit of measurement, "" to disable. - */ + /// Get the unit of measurement, using the manual override if set. + std::string get_unit_of_measurement(); + /// Manually set the unit of measurement. void set_unit_of_measurement(const std::string &unit_of_measurement); - /** Manually set the icon of this sensor. By default the sensor's default defined by icon() is used. - * - * @param icon The icon, for example "mdi:flash". "" to disable. - */ + /// Get the icon. Uses the manual override if specified or the default value instead. + std::string get_icon(); + /// Manually set the icon, for example "mdi:flash". void set_icon(const std::string &icon); - /** Manually set the accuracy in decimals for this sensor. By default, the sensor's default defined by - * accuracy_decimals() is used. - * - * @param accuracy_decimals The accuracy decimal that should be used. - */ + /// Get the accuracy in decimals, using the manual override if set. + int8_t get_accuracy_decimals(); + /// Manually set the accuracy in decimals. void set_accuracy_decimals(int8_t accuracy_decimals); + /// Get the device class, using the manual override if set. + std::string get_device_class(); + /// Manually set the device class. + void set_device_class(const std::string &device_class); + + /// Get the state class, using the manual override if set. + StateClass get_state_class(); + /// Manually set the state class. + void set_state_class(StateClass state_class); + + /** + * Get whether force update mode is enabled. + * + * If the sensor is in force_update mode, the frontend is required to save all + * state changes to the database when they are published, even if the state is the + * same as before. + */ + bool get_force_update() const { return force_update_; } + /// Set force update mode. + void set_force_update(bool force_update) { force_update_ = force_update; } + /// Add a filter to the filter chain. Will be appended to the back. void add_filter(Filter *filter); @@ -93,15 +109,6 @@ class Sensor : public Nameable { /// Getter-syntax for .raw_state float get_raw_state() const; - /// Get the accuracy in decimals. Uses the manual override if specified or the default value instead. - int8_t get_accuracy_decimals(); - - /// Get the unit of measurement. Uses the manual override if specified or the default value instead. - std::string get_unit_of_measurement(); - - /// Get the Home Assistant Icon. Uses the manual override if specified or the default value instead. - std::string get_icon(); - /** Publish a new state to the front-end. * * First, the new state will be assigned to the raw_value. Then it's passed through all filters @@ -127,35 +134,15 @@ class Sensor : public Nameable { */ float state; - /// Manually set the Home Assistant device class (see sensor::device_class) - void set_device_class(const std::string &device_class); - - /// Get the device class for this sensor, using the manual override if specified. - std::string get_device_class(); - - /** This member variable stores the current raw state of the sensor. Unlike .state, - * this will be updated immediately when publish_state is called. + /** This member variable stores the current raw state of the sensor, without any filters applied. + * + * Unlike .state,this will be updated immediately when publish_state is called. */ float raw_state; /// Return whether this sensor has gotten a full state (that passed through all filters) yet. bool has_state() const; - // The state class of this sensor state - StateClass state_class{STATE_CLASS_NONE}; - - /// Manually set the Home Assistant state class (see sensor::state_class) - void set_state_class(StateClass state_class); - void set_state_class(const std::string &state_class); - - /** Override this to set the Home Assistant device class for this sensor. - * - * Return "" to disable this feature. - * - * @return The device class of this sensor, for example "temperature". - */ - virtual std::string device_class(); - /** A unique ID for this sensor, empty for no unique id. See unique ID requirements: * https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements * @@ -163,65 +150,38 @@ class Sensor : public Nameable { */ virtual std::string unique_id(); - /// Return with which interval the sensor is polled. Return 0 for non-polling mode. - virtual uint32_t update_interval(); - - /// Calculate the expected update interval for values that pass through all filters. - uint32_t calculate_expected_filter_update_interval(); - void internal_send_state_to_frontend(float state); - bool get_force_update() const { return force_update_; } - /** Set this sensor's force_update mode. - * - * If the sensor is in force_update mode, the frontend is required to save all - * state changes to the database when they are published, even if the state is the - * same as before. - */ - void set_force_update(bool force_update) { force_update_ = force_update; } - protected: - /** Override this to set the Home Assistant unit of measurement for this sensor. - * - * Return "" to disable this feature. - * - * @return The icon of this sensor, for example "°C". - */ + /// Override this to set the default unit of measurement. virtual std::string unit_of_measurement(); // NOLINT - /** Override this to set the Home Assistant icon for this sensor. - * - * Return "" to disable this feature. - * - * @return The icon of this sensor, for example "mdi:battery". - */ + /// Override this to set the default icon. virtual std::string icon(); // NOLINT - /// Return the accuracy in decimals for this sensor. + /// Override this to set the default accuracy in decimals. virtual int8_t accuracy_decimals(); // NOLINT - optional device_class_{}; ///< Stores the override of the device class + /// Override this to set the default device class. + virtual std::string device_class(); // NOLINT + + /// Override this to set the default state class. + virtual StateClass state_class(); // NOLINT uint32_t hash_base() override; CallbackManager raw_callback_; ///< Storage for raw state callbacks. CallbackManager callback_; ///< Storage for filtered state callbacks. - /// Override the unit of measurement - optional unit_of_measurement_; - /// Override the icon advertised to Home Assistant, otherwise sensor's icon will be used. - optional icon_; - /// Override the accuracy in decimals, otherwise the sensor's values will be used. - optional accuracy_decimals_; - Filter *filter_list_{nullptr}; ///< Store all active filters. + bool has_state_{false}; - bool force_update_{false}; -}; + Filter *filter_list_{nullptr}; ///< Store all active filters. -class PollingSensorComponent : public PollingComponent, public Sensor { - public: - explicit PollingSensorComponent(const std::string &name, uint32_t update_interval); - - uint32_t update_interval() override; + optional unit_of_measurement_; ///< Unit of measurement override + optional icon_; ///< Icon override + optional accuracy_decimals_; ///< Accuracy in decimals override + optional device_class_; ///< Device class override + optional state_class_{STATE_CLASS_NONE}; ///< State class override + bool force_update_{false}; ///< Force update mode }; } // namespace sensor From 272ceadbb00d5980aa00c0ad5043878a330d275f Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 09:07:38 +0200 Subject: [PATCH 1379/1841] Redo docker build system with buildkit+multi-stage and cache pio packages (#2338) --- .devcontainer/devcontainer.json | 3 +- .github/workflows/ci-docker.yml | 5 + .github/workflows/ci.yml | 152 +++++++++--------- .github/workflows/docker-lint-build.yml | 100 ------------ .github/workflows/release.yml | 20 ++- docker/Dockerfile | 135 ++++++++++++++-- docker/Dockerfile.dev | 1 - docker/Dockerfile.hassio | 25 --- docker/Dockerfile.lint | 10 -- docker/build.py | 94 +++++------ docker/docker_entrypoint.sh | 18 +++ .../etc/cont-init.d/10-requirements.sh | 0 .../etc/cont-init.d/20-nginx.sh | 0 .../hassio-rootfs/etc/cont-init.d/30-dirs.sh | 9 ++ .../etc/nginx/includes/mime.types | 0 .../etc/nginx/includes/proxy_params.conf | 0 .../etc/nginx/includes/server_params.conf | 0 .../etc/nginx/includes/ssl_params.conf | 0 .../etc/nginx/nginx.conf | 0 .../etc/nginx/servers/direct-ssl.disabled | 0 .../etc/nginx/servers/direct.disabled | 0 .../etc/nginx/servers/ingress.conf | 0 .../etc/services.d/esphome/finish | 0 .../etc/services.d/esphome/run | 3 + .../etc/services.d/nginx/finish | 0 .../etc/services.d/nginx/run | 0 esphome/platformio_api.py | 4 +- script/ci-custom.py | 4 +- script/devcontainer-post-create | 5 +- 29 files changed, 295 insertions(+), 293 deletions(-) delete mode 100644 .github/workflows/docker-lint-build.yml delete mode 100644 docker/Dockerfile.dev delete mode 100644 docker/Dockerfile.hassio delete mode 100644 docker/Dockerfile.lint create mode 100755 docker/docker_entrypoint.sh rename docker/{rootfs => hassio-rootfs}/etc/cont-init.d/10-requirements.sh (100%) rename docker/{rootfs => hassio-rootfs}/etc/cont-init.d/20-nginx.sh (100%) create mode 100644 docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh rename docker/{rootfs => hassio-rootfs}/etc/nginx/includes/mime.types (100%) rename docker/{rootfs => hassio-rootfs}/etc/nginx/includes/proxy_params.conf (100%) rename docker/{rootfs => hassio-rootfs}/etc/nginx/includes/server_params.conf (100%) rename docker/{rootfs => hassio-rootfs}/etc/nginx/includes/ssl_params.conf (100%) rename docker/{rootfs => hassio-rootfs}/etc/nginx/nginx.conf (100%) rename docker/{rootfs => hassio-rootfs}/etc/nginx/servers/direct-ssl.disabled (100%) rename docker/{rootfs => hassio-rootfs}/etc/nginx/servers/direct.disabled (100%) rename docker/{rootfs => hassio-rootfs}/etc/nginx/servers/ingress.conf (100%) rename docker/{rootfs => hassio-rootfs}/etc/services.d/esphome/finish (100%) rename docker/{rootfs => hassio-rootfs}/etc/services.d/esphome/run (89%) rename docker/{rootfs => hassio-rootfs}/etc/services.d/nginx/finish (100%) rename docker/{rootfs => hassio-rootfs}/etc/services.d/nginx/run (100%) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3904962d7c..433e5d2792 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,7 +1,6 @@ { "name": "ESPHome Dev", - "context": "..", - "dockerFile": "../docker/Dockerfile.dev", + "image": "esphome/esphome-lint:dev", "postCreateCommand": [ "script/devcontainer-post-create" ], diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 45fd3e141b..33b15cb1dc 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -27,6 +27,11 @@ jobs: uses: actions/setup-python@v2 with: python-version: '3.9' + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set TAG run: | echo "TAG=check" >> $GITHUB_ENV diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b00e1b3835..875db0f9fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,61 +9,7 @@ on: pull_request: jobs: - ci-with-container: - name: ${{ matrix.name }} - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - id: clang-format - name: Run script/clang-format - - id: clang-tidy - name: Run script/clang-tidy for ESP8266 - options: --environment esp8266-tidy --grep ARDUINO_ARCH_ESP8266 - - id: clang-tidy - name: Run script/clang-tidy for ESP32 1/4 - options: --environment esp32-tidy --split-num 4 --split-at 1 - - id: clang-tidy - name: Run script/clang-tidy for ESP32 2/4 - options: --environment esp32-tidy --split-num 4 --split-at 2 - - id: clang-tidy - name: Run script/clang-tidy for ESP32 3/4 - options: --environment esp32-tidy --split-num 4 --split-at 3 - - id: clang-tidy - name: Run script/clang-tidy for ESP32 4/4 - options: --environment esp32-tidy --split-num 4 --split-at 4 - - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: ghcr.io/esphome/esphome-lint:1.2 - steps: - - uses: actions/checkout@v2 - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" - echo "::add-matcher::.github/workflows/matchers/gcc.json" - - # Also run git-diff-index so that the step is marked as failed on formatting errors, - # since clang-format doesn't do anything but change files if -i is passed. - - name: Run clang-format - run: | - script/clang-format -i - git diff-index --quiet HEAD -- - if: ${{ matrix.id == 'clang-format' }} - - - name: Run clang-tidy - run: script/clang-tidy --all-headers --fix ${{ matrix.options }} - if: ${{ matrix.id == 'clang-tidy' }} - - - name: Suggested changes - run: script/ci-suggest-changes - if: always() - ci: - # Don't use the esphome-lint docker image because it may contain outdated requirements. - # This way, all dependencies are cached via the cache action. name: ${{ matrix.name }} runs-on: ubuntu-latest strategy: @@ -77,48 +23,85 @@ jobs: - id: test file: tests/test1.yaml name: Test tests/test1.yaml + pio_cache_key: test1 - id: test file: tests/test2.yaml name: Test tests/test2.yaml + pio_cache_key: test2 - id: test file: tests/test3.yaml name: Test tests/test3.yaml + pio_cache_key: test1 - id: test file: tests/test4.yaml name: Test tests/test4.yaml + pio_cache_key: test4 - id: test file: tests/test5.yaml name: Test tests/test5.yaml + pio_cache_key: test5 - id: pytest name: Run pytest + - id: clang-format + name: Run script/clang-format + - id: clang-tidy + name: Run script/clang-tidy for ESP8266 + options: --environment esp8266-tidy --grep ARDUINO_ARCH_ESP8266 + pio_cache_key: tidyesp8266 + - id: clang-tidy + name: Run script/clang-tidy for ESP32 1/4 + options: --environment esp32-tidy --split-num 4 --split-at 1 + pio_cache_key: tidyesp32 + - id: clang-tidy + name: Run script/clang-tidy for ESP32 2/4 + options: --environment esp32-tidy --split-num 4 --split-at 2 + pio_cache_key: tidyesp32 + - id: clang-tidy + name: Run script/clang-tidy for ESP32 3/4 + options: --environment esp32-tidy --split-num 4 --split-at 3 + pio_cache_key: tidyesp32 + - id: clang-tidy + name: Run script/clang-tidy for ESP32 4/4 + options: --environment esp32-tidy --split-num 4 --split-at 4 + pio_cache_key: tidyesp32 steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 + id: python with: python-version: '3.7' - name: Cache pip modules - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} + key: pip-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }} restore-keys: | - esphome-pip-3.7- - - # Use per test platformio cache because tests have different platform versions - - name: Cache ~/.platformio - uses: actions/cache@v1 - with: - path: ~/.platformio - key: test-home-platformio-${{ matrix.file }}-${{ hashFiles('esphome/core/config.py') }} - restore-keys: | - test-home-platformio-${{ matrix.file }}- - if: ${{ matrix.id == 'test' }} + pip-${{ steps.python.outputs.python-version }}- - name: Set up python environment - run: script/setup + run: | + pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt + pip3 install -e . + + # Use per check platformio cache because checks use different parts + - name: Cache platformio + uses: actions/cache@v2 + with: + path: ~/.platformio + key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} + restore-keys: | + platformio-${{ matrix.pio_cache_key }}- + if: matrix.id == 'test' || matrix.id == 'clang-tidy' + + - name: Install clang tools + run: | + sudo apt-get install \ + clang-format-11 \ + clang-tidy-11 + if: matrix.id == 'clang-tidy' || matrix.id == 'clang-format' - name: Register problem matchers run: | @@ -127,20 +110,45 @@ jobs: echo "::add-matcher::.github/workflows/matchers/python.json" echo "::add-matcher::.github/workflows/matchers/pytest.json" echo "::add-matcher::.github/workflows/matchers/gcc.json" + echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" - name: Lint Custom run: | script/ci-custom.py script/build_codeowners.py --check - if: ${{ matrix.id == 'ci-custom' }} + if: matrix.id == 'ci-custom' + - name: Lint Python run: script/lint-python - if: ${{ matrix.id == 'lint-python' }} + if: matrix.id == 'lint-python' - run: esphome compile ${{ matrix.file }} - if: ${{ matrix.id == 'test' }} + if: matrix.id == 'test' + env: + # Also cache libdeps, store them in a ~/.platformio subfolder + PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps - name: Run pytest run: | pytest -vv --tb=native tests - if: ${{ matrix.id == 'pytest' }} + if: matrix.id == 'pytest' + + # Also run git-diff-index so that the step is marked as failed on formatting errors, + # since clang-format doesn't do anything but change files if -i is passed. + - name: Run clang-format + run: | + script/clang-format -i + git diff-index --quiet HEAD -- + if: matrix.id == 'clang-format' + + - name: Run clang-tidy + run: | + script/clang-tidy --all-headers --fix ${{ matrix.options }} + if: matrix.id == 'clang-tidy' + env: + # Also cache libdeps, store them in a ~/.platformio subfolder + PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps + + - name: Suggested changes + run: script/ci-suggest-changes + if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format') diff --git a/.github/workflows/docker-lint-build.yml b/.github/workflows/docker-lint-build.yml deleted file mode 100644 index 8350d4c719..0000000000 --- a/.github/workflows/docker-lint-build.yml +++ /dev/null @@ -1,100 +0,0 @@ -name: Build and publish lint docker image - -# Only run when docker paths change -on: - push: - branches: [dev] - paths: - - 'docker/Dockerfile.lint' - - 'requirements.txt' - - 'requirements_optional.txt' - - 'requirements_test.txt' - - 'platformio.ini' - - '.github/workflows/docker-lint-build.yml' - -jobs: - deploy-docker: - name: Build and publish docker containers - if: github.repository == 'esphome/esphome' - runs-on: ubuntu-latest - strategy: - matrix: - arch: [amd64, armv7, aarch64] - build_type: ["lint"] - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.9' - - name: Set TAG - run: | - echo "TAG=1.2" >> $GITHUB_ENV - - - name: Run build - run: | - docker/build.py \ - --tag "${TAG}" \ - --arch "${{ matrix.arch }}" \ - --build-type "${{ matrix.build_type }}" \ - build - - - name: Log in to docker hub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Log in to the GitHub container registry - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Run push - run: | - docker/build.py \ - --tag "${TAG}" \ - --arch "${{ matrix.arch }}" \ - --build-type "${{ matrix.build_type }}" \ - push - - deploy-docker-manifest: - if: github.repository == 'esphome/esphome' - runs-on: ubuntu-latest - needs: [deploy-docker] - strategy: - matrix: - build_type: ["lint"] - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.9' - - name: Set TAG - run: | - echo "TAG=1.2" >> $GITHUB_ENV - - name: Enable experimental manifest support - run: | - mkdir -p ~/.docker - echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json - - - name: Log in to docker hub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Log in to the GitHub container registry - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Run manifest - run: | - docker/build.py \ - --tag "${TAG}" \ - --build-type "${{ matrix.build_type }}" \ - manifest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3dc4952422..afd893d065 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -57,7 +57,7 @@ jobs: strategy: matrix: arch: [amd64, armv7, aarch64] - build_type: ["ha-addon", "docker"] + build_type: ["ha-addon", "docker", "lint"] steps: - uses: actions/checkout@v2 - name: Set up Python @@ -65,13 +65,10 @@ jobs: with: python-version: '3.9' - - name: Run build - run: | - docker/build.py \ - --tag "${{ needs.init.outputs.tag }}" \ - --arch "${{ matrix.arch }}" \ - --build-type "${{ matrix.build_type }}" \ - build + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 - name: Log in to docker hub uses: docker/login-action@v1 @@ -85,13 +82,14 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Run push + - name: Build and push run: | docker/build.py \ --tag "${{ needs.init.outputs.tag }}" \ --arch "${{ matrix.arch }}" \ --build-type "${{ matrix.build_type }}" \ - push + build \ + --push deploy-docker-manifest: if: github.repository == 'esphome/esphome' @@ -99,7 +97,7 @@ jobs: needs: [init, deploy-docker] strategy: matrix: - build_type: ["ha-addon", "docker"] + build_type: ["ha-addon", "docker", "lint"] steps: - uses: actions/checkout@v2 - name: Set up Python diff --git a/docker/Dockerfile b/docker/Dockerfile index 907c041119..d3ee219de8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,55 @@ -ARG BUILD_FROM=esphome/esphome-base:latest -FROM ${BUILD_FROM} +# Build these with the build.py script +# Example: +# python3 docker/build.py --tag dev --arch amd64 --build-type docker build + +# One of "docker", "hassio" +ARG BASEIMGTYPE=docker + +FROM ghcr.io/hassio-addons/debian-base/amd64:5.0.0 AS base-hassio-amd64 +FROM ghcr.io/hassio-addons/debian-base/aarch64:5.0.0 AS base-hassio-arm64 +FROM ghcr.io/hassio-addons/debian-base/armv7:5.0.0 AS base-hassio-armv7 +FROM debian:bullseye-20210816-slim AS base-docker-amd64 +FROM debian:bullseye-20210816-slim AS base-docker-arm64 +FROM debian:bullseye-20210816-slim AS base-docker-armv7 + +# Use TARGETARCH/TARGETVARIANT defined by docker +# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope +FROM base-${BASEIMGTYPE}-${TARGETARCH}${TARGETVARIANT} AS base + +RUN \ + apt-get update \ + # Use pinned versions so that we get updates with build caching + && apt-get install -y --no-install-recommends \ + python3=3.9.2-3 \ + python3-pip=20.3.4-4 \ + python3-setuptools=52.0.0-4 \ + python3-pil=8.1.2+dfsg-0.3 \ + python3-cryptography=3.3.2-1 \ + iputils-ping=3:20210202-1 \ + git=1:2.30.2-1 \ + curl=7.74.0-1.3+b1 \ + && rm -rf \ + /tmp/* \ + /var/{cache,log}/* \ + /var/lib/apt/lists/* + +ENV \ + # Fix click python3 lang warning https://click.palletsprojects.com/en/7.x/python3/ + LANG=C.UTF-8 LC_ALL=C.UTF-8 \ + # Store globally installed pio libs in /piolibs + PLATFORMIO_GLOBALLIB_DIR=/piolibs + +RUN \ + # Ubuntu python3-pip is missing wheel + pip3 install --no-cache-dir \ + wheel==0.36.2 \ + platformio==5.2.0 \ + # Change some platformio settings + && platformio settings set enable_telemetry No \ + && platformio settings set check_libraries_interval 1000000 \ + && platformio settings set check_platformio_interval 1000000 \ + && platformio settings set check_platforms_interval 1000000 \ + && mkdir -p /piolibs # First install requirements to leverage caching when requirements don't change COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / @@ -7,9 +57,14 @@ RUN \ pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ && /platformio_install_deps.py /platformio.ini -# Then copy esphome and install -COPY . . -RUN pip3 install --no-cache-dir -e . + + +# ======================= docker-type image ======================= +FROM base AS docker + +# Copy esphome and install +COPY . /esphome +RUN pip3 install --no-cache-dir -e /esphome # Settings for dashboard ENV USERNAME="" PASSWORD="" @@ -17,14 +72,74 @@ ENV USERNAME="" PASSWORD="" # Expose the dashboard to Docker EXPOSE 6052 -# Run healthcheck (heartbeat) -HEALTHCHECK --interval=30s --timeout=30s \ - CMD curl --fail http://localhost:6052 || exit 1 +COPY docker/docker_entrypoint.sh /entrypoint.sh # The directory the user should mount their configuration files to +VOLUME /config WORKDIR /config -# Set entrypoint to esphome so that the user doesn't have to type 'esphome' +# Set entrypoint to esphome (via a script) so that the user doesn't have to type 'esphome' # in every docker command twice -ENTRYPOINT ["esphome"] +ENTRYPOINT ["/entrypoint.sh"] # When no arguments given, start the dashboard in the workdir CMD ["dashboard", "/config"] + + + + +# ======================= hassio-type image ======================= +FROM base AS hassio + +RUN \ + apt-get update \ + # Use pinned versions so that we get updates with build caching + && apt-get install -y --no-install-recommends \ + nginx=1.18.0-6.1 \ + && rm -rf \ + /tmp/* \ + /var/{cache,log}/* \ + /var/lib/apt/lists/* + +ARG BUILD_VERSION=dev + +# Copy root filesystem +COPY docker/hassio-rootfs/ / + +# Copy esphome and install +COPY . /esphome +RUN pip3 install --no-cache-dir -e /esphome + +# Labels +LABEL \ + io.hass.name="ESPHome" \ + io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ + io.hass.type="addon" \ + io.hass.version="${BUILD_VERSION}" + # io.hass.arch is inherited from addon-debian-base + + + + +# ======================= lint-type image ======================= +FROM base AS lint + +ENV \ + PLATFORMIO_CORE_DIR=/esphome/.temp/platformio + +RUN \ + apt-get update \ + # Use pinned versions so that we get updates with build caching + && apt-get install -y --no-install-recommends \ + clang-format-11=1:11.0.1-2 \ + clang-tidy-11=1:11.0.1-2 \ + patch=2.7.6-7 \ + software-properties-common=0.96.20.2-2.1 \ + nano=5.4-2 \ + build-essential=12.9 \ + python3-dev=3.9.2-3 \ + && rm -rf \ + /tmp/* \ + /var/{cache,log}/* \ + /var/lib/apt/lists/* + +VOLUME ["/esphome"] +WORKDIR /esphome diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev deleted file mode 100644 index c646ce989b..0000000000 --- a/docker/Dockerfile.dev +++ /dev/null @@ -1 +0,0 @@ -FROM esphome/esphome-lint:1.2 diff --git a/docker/Dockerfile.hassio b/docker/Dockerfile.hassio deleted file mode 100644 index ad80074ada..0000000000 --- a/docker/Dockerfile.hassio +++ /dev/null @@ -1,25 +0,0 @@ -ARG BUILD_FROM=esphome/esphome-hassio-base:latest -FROM ${BUILD_FROM} - -# First install requirements to leverage caching when requirements don't change -COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / -RUN \ - pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ - && /platformio_install_deps.py /platformio.ini - -# Copy root filesystem -COPY docker/rootfs/ / - -# Then copy esphome and install -COPY . /opt/esphome/ -RUN pip3 install --no-cache-dir -e /opt/esphome - -# Build arguments -ARG BUILD_VERSION=dev - -# Labels -LABEL \ - io.hass.name="ESPHome" \ - io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ - io.hass.type="addon" \ - io.hass.version=${BUILD_VERSION} diff --git a/docker/Dockerfile.lint b/docker/Dockerfile.lint deleted file mode 100644 index 3a090c3b41..0000000000 --- a/docker/Dockerfile.lint +++ /dev/null @@ -1,10 +0,0 @@ -ARG BUILD_FROM=esphome/esphome-lint-base:latest -FROM ${BUILD_FROM} - -COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini / -RUN \ - pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \ - && /platformio_install_deps.py /platformio.ini - -VOLUME ["/esphome"] -WORKDIR /esphome diff --git a/docker/build.py b/docker/build.py index c926b3653b..1904457989 100755 --- a/docker/build.py +++ b/docker/build.py @@ -2,7 +2,7 @@ from dataclasses import dataclass import subprocess import argparse -import platform +from platform import machine import shlex import re import sys @@ -24,9 +24,6 @@ TYPE_LINT = 'lint' TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT] -BASE_VERSION = "4.2.0" - - parser = argparse.ArgumentParser() parser.add_argument("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag") parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for") @@ -34,27 +31,17 @@ parser.add_argument("--build-type", choices=TYPES, required=True, help="The type parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them") subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True) build_parser = subparsers.add_parser("build", help="Build the image") -push_parser = subparsers.add_parser("push", help="Tag the already built image and push it to docker hub") +build_parser.add_argument("--push", help="Also push the images") manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images") - -# only lists some possibilities, doesn't have to be perfect -# https://stackoverflow.com/a/45125525 -UNAME_TO_ARCH = { - "x86_64": ARCH_AMD64, - "aarch64": ARCH_AARCH64, - "aarch64_be": ARCH_AARCH64, - "arm": ARCH_ARMV7, -} - - @dataclass(frozen=True) class DockerParams: - build_from: str build_to: str manifest_to: str - dockerfile: str + baseimgtype: str + platform: str + target: str @classmethod def for_type_arch(cls, build_type, arch): @@ -63,18 +50,28 @@ class DockerParams: TYPE_HA_ADDON: "esphome/esphome-hassio", TYPE_LINT: "esphome/esphome-lint" }[build_type] - build_from = f"ghcr.io/{prefix}-base-{arch}:{BASE_VERSION}" build_to = f"{prefix}-{arch}" - dockerfile = { - TYPE_DOCKER: "docker/Dockerfile", - TYPE_HA_ADDON: "docker/Dockerfile.hassio", - TYPE_LINT: "docker/Dockerfile.lint", + baseimgtype = { + TYPE_DOCKER: "docker", + TYPE_HA_ADDON: "hassio", + TYPE_LINT: "docker", + }[build_type] + platform = { + ARCH_AMD64: "linux/amd64", + ARCH_ARMV7: "linux/arm/v7", + ARCH_AARCH64: "linux/arm64", + }[arch] + target = { + TYPE_DOCKER: "docker", + TYPE_HA_ADDON: "hassio", + TYPE_LINT: "lint", }[build_type] return cls( - build_from=build_from, build_to=build_to, manifest_to=prefix, - dockerfile=dockerfile + baseimgtype=baseimgtype, + platform=platform, + target=target, ) @@ -117,41 +114,26 @@ def main(): CHANNEL_RELEASE: "latest", }[channel] cache_img = f"ghcr.io/{params.build_to}:{cache_tag}" - run_command("docker", "pull", cache_img, ignore_error=True) - # 2. register QEMU binfmt (if not host arch) - is_native = UNAME_TO_ARCH.get(platform.machine()) == args.arch - if not is_native: - run_command( - "docker", "run", "--rm", "--privileged", "multiarch/qemu-user-static:5.2.0-2", - "--reset", "-p", "yes" - ) - - # 3. build - run_command( - "docker", "build", - "--build-arg", f"BUILD_FROM={params.build_from}", - "--build-arg", f"BUILD_VERSION={args.tag}", - "--tag", f"{params.build_to}:{args.tag}", - "--cache-from", cache_img, - "--file", params.dockerfile, - "." - ) - elif args.command == "push": - params = DockerParams.for_type_arch(args.build_type, args.arch) imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push] imgs += [f"ghcr.io/{params.build_to}:{tag}" for tag in tags_to_push] - src = imgs[0] - # 1. tag images - for img in imgs[1:]: - run_command( - "docker", "tag", src, img - ) - # 2. push images + + # 3. build + cmd = [ + "docker", "buildx", "build", + "--build-arg", f"BASEIMGTYPE={params.baseimgtype}", + "--build-arg", f"BUILD_VERSION={args.tag}", + "--cache-from", cache_img, + "--file", "docker/Dockerfile", + "--platform", params.platform, + "--target", params.target, + ] for img in imgs: - run_command( - "docker", "push", img - ) + cmd += ["--tag", img] + if args.push: + cmd.append("--push") + + run_command(*cmd, ".") elif args.command == "manifest": manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to diff --git a/docker/docker_entrypoint.sh b/docker/docker_entrypoint.sh new file mode 100755 index 0000000000..b3905d1fed --- /dev/null +++ b/docker/docker_entrypoint.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# If /cache is mounted, use that as PIO's coredir +# otherwise use path in /config (so that PIO packages aren't downloaded on each compile) + +if [[ -d /cache ]]; then + export PLATFORMIO_CORE_DIR=/cache/platformio +else + export PLATFORMIO_CORE_DIR=/config/.esphome/platformio +fi + +if [[ ! -d "${PLATFORMIO_CORE_DIR}" ]]; then + echo "Creating cache directory ${PLATFORMIO_CORE_DIR}" + echo "You can change this behavior by mounting a directory to the container's /cache directory." + mkdir -p "${PLATFORMIO_CORE_DIR}" +fi + +exec esphome "$@" diff --git a/docker/rootfs/etc/cont-init.d/10-requirements.sh b/docker/hassio-rootfs/etc/cont-init.d/10-requirements.sh similarity index 100% rename from docker/rootfs/etc/cont-init.d/10-requirements.sh rename to docker/hassio-rootfs/etc/cont-init.d/10-requirements.sh diff --git a/docker/rootfs/etc/cont-init.d/20-nginx.sh b/docker/hassio-rootfs/etc/cont-init.d/20-nginx.sh similarity index 100% rename from docker/rootfs/etc/cont-init.d/20-nginx.sh rename to docker/hassio-rootfs/etc/cont-init.d/20-nginx.sh diff --git a/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh b/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh new file mode 100644 index 0000000000..301fe4db63 --- /dev/null +++ b/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh @@ -0,0 +1,9 @@ +#!/usr/bin/with-contenv bashio +# ============================================================================== +# Community Hass.io Add-ons: ESPHome +# This files creates all directories used by esphome +# ============================================================================== + +PLATFORMIO_CORE_DIR=/data/cache/platformio + +mkdir -p "${PLATFORMIO_CORE_DIR}" diff --git a/docker/rootfs/etc/nginx/includes/mime.types b/docker/hassio-rootfs/etc/nginx/includes/mime.types similarity index 100% rename from docker/rootfs/etc/nginx/includes/mime.types rename to docker/hassio-rootfs/etc/nginx/includes/mime.types diff --git a/docker/rootfs/etc/nginx/includes/proxy_params.conf b/docker/hassio-rootfs/etc/nginx/includes/proxy_params.conf similarity index 100% rename from docker/rootfs/etc/nginx/includes/proxy_params.conf rename to docker/hassio-rootfs/etc/nginx/includes/proxy_params.conf diff --git a/docker/rootfs/etc/nginx/includes/server_params.conf b/docker/hassio-rootfs/etc/nginx/includes/server_params.conf similarity index 100% rename from docker/rootfs/etc/nginx/includes/server_params.conf rename to docker/hassio-rootfs/etc/nginx/includes/server_params.conf diff --git a/docker/rootfs/etc/nginx/includes/ssl_params.conf b/docker/hassio-rootfs/etc/nginx/includes/ssl_params.conf similarity index 100% rename from docker/rootfs/etc/nginx/includes/ssl_params.conf rename to docker/hassio-rootfs/etc/nginx/includes/ssl_params.conf diff --git a/docker/rootfs/etc/nginx/nginx.conf b/docker/hassio-rootfs/etc/nginx/nginx.conf similarity index 100% rename from docker/rootfs/etc/nginx/nginx.conf rename to docker/hassio-rootfs/etc/nginx/nginx.conf diff --git a/docker/rootfs/etc/nginx/servers/direct-ssl.disabled b/docker/hassio-rootfs/etc/nginx/servers/direct-ssl.disabled similarity index 100% rename from docker/rootfs/etc/nginx/servers/direct-ssl.disabled rename to docker/hassio-rootfs/etc/nginx/servers/direct-ssl.disabled diff --git a/docker/rootfs/etc/nginx/servers/direct.disabled b/docker/hassio-rootfs/etc/nginx/servers/direct.disabled similarity index 100% rename from docker/rootfs/etc/nginx/servers/direct.disabled rename to docker/hassio-rootfs/etc/nginx/servers/direct.disabled diff --git a/docker/rootfs/etc/nginx/servers/ingress.conf b/docker/hassio-rootfs/etc/nginx/servers/ingress.conf similarity index 100% rename from docker/rootfs/etc/nginx/servers/ingress.conf rename to docker/hassio-rootfs/etc/nginx/servers/ingress.conf diff --git a/docker/rootfs/etc/services.d/esphome/finish b/docker/hassio-rootfs/etc/services.d/esphome/finish similarity index 100% rename from docker/rootfs/etc/services.d/esphome/finish rename to docker/hassio-rootfs/etc/services.d/esphome/finish diff --git a/docker/rootfs/etc/services.d/esphome/run b/docker/hassio-rootfs/etc/services.d/esphome/run similarity index 89% rename from docker/rootfs/etc/services.d/esphome/run rename to docker/hassio-rootfs/etc/services.d/esphome/run index f806c50929..6218b200bd 100755 --- a/docker/rootfs/etc/services.d/esphome/run +++ b/docker/hassio-rootfs/etc/services.d/esphome/run @@ -22,5 +22,8 @@ if bashio::config.has_value 'relative_url'; then export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url') fi +export PLATFORMIO_CORE_DIR=/data/cache/platformio +export PLATFORMIO_GLOBALLIB_DIR=/piolibs + bashio::log.info "Starting ESPHome dashboard..." exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --hassio diff --git a/docker/rootfs/etc/services.d/nginx/finish b/docker/hassio-rootfs/etc/services.d/nginx/finish similarity index 100% rename from docker/rootfs/etc/services.d/nginx/finish rename to docker/hassio-rootfs/etc/services.d/nginx/finish diff --git a/docker/rootfs/etc/services.d/nginx/run b/docker/hassio-rootfs/etc/services.d/nginx/run similarity index 100% rename from docker/rootfs/etc/services.d/nginx/run rename to docker/hassio-rootfs/etc/services.d/nginx/run diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 6506a44e88..a99081a650 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -67,8 +67,8 @@ FILTER_PLATFORMIO_LINES = [ def run_platformio_cli(*args, **kwargs) -> Union[str, int]: os.environ["PLATFORMIO_FORCE_COLOR"] = "true" os.environ["PLATFORMIO_BUILD_DIR"] = os.path.abspath(CORE.relative_pioenvs_path()) - os.environ["PLATFORMIO_LIBDEPS_DIR"] = os.path.abspath( - CORE.relative_piolibdeps_path() + os.environ.setdefault( + "PLATFORMIO_LIBDEPS_DIR", os.path.abspath(CORE.relative_piolibdeps_path()) ) cmd = ["platformio"] + list(args) diff --git a/script/ci-custom.py b/script/ci-custom.py index cdc450a96b..3191842d8c 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -217,7 +217,9 @@ def lint_ext_check(fname): ) -@lint_file_check(exclude=["docker/rootfs/*", "docker/*.py", "script/*", "setup.py"]) +@lint_file_check( + exclude=["**.sh", "docker/hassio-rootfs/**", "docker/*.py", "script/*", "setup.py"] +) def lint_executable_bit(fname): ex = EXECUTABLE_BIT[fname] if ex != 100644: diff --git a/script/devcontainer-post-create b/script/devcontainer-post-create index 4b4dc5b6c9..120ab3307d 100755 --- a/script/devcontainer-post-create +++ b/script/devcontainer-post-create @@ -12,7 +12,6 @@ if [ ! -f $cpp_json ]; then pio init --ide vscode --silent sed -i "/\\/workspaces\/esphome\/include/d" $cpp_json else - echo "Cpp environment already configured. To reconfigure it you could run one the following commands:" - echo " pio init --ide vscode -e livingroom8266" - echo " pio init --ide vscode -e livingroom32" + echo "Cpp environment already configured. To reconfigure it you can run one the following commands:" + echo " pio init --ide vscode" fi From 5f21b925dad3a99819402bb294cef9182c956c45 Mon Sep 17 00:00:00 2001 From: synco Date: Mon, 20 Sep 2021 19:12:50 +1200 Subject: [PATCH 1380/1841] Calculating the AC only component of the samples (#1906) Co-authored-by: Synco Reynders Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .../components/ct_clamp/ct_clamp_sensor.cpp | 46 ++++--------------- esphome/components/ct_clamp/ct_clamp_sensor.h | 15 +++--- 2 files changed, 18 insertions(+), 43 deletions(-) diff --git a/esphome/components/ct_clamp/ct_clamp_sensor.cpp b/esphome/components/ct_clamp/ct_clamp_sensor.cpp index c27134d6ac..130cdfefba 100644 --- a/esphome/components/ct_clamp/ct_clamp_sensor.cpp +++ b/esphome/components/ct_clamp/ct_clamp_sensor.cpp @@ -8,18 +8,6 @@ namespace ct_clamp { static const char *const TAG = "ct_clamp"; -void CTClampSensor::setup() { - this->is_calibrating_offset_ = true; - this->high_freq_.start(); - this->set_timeout("calibrate_offset", this->sample_duration_, [this]() { - this->high_freq_.stop(); - this->is_calibrating_offset_ = false; - if (this->num_samples_ != 0) { - this->offset_ = this->sample_sum_ / this->num_samples_; - } - }); -} - void CTClampSensor::dump_config() { LOG_SENSOR("", "CT Clamp Sensor", this); ESP_LOGCONFIG(TAG, " Sample Duration: %.2fs", this->sample_duration_ / 1e3f); @@ -27,9 +15,6 @@ void CTClampSensor::dump_config() { } void CTClampSensor::update() { - if (this->is_calibrating_offset_) - return; - // Update only starts the sampling phase, in loop() the actual sampling is happening. // Request a high loop() execution interval during sampling phase. @@ -46,20 +31,23 @@ void CTClampSensor::update() { return; } - float raw = this->sample_sum_ / this->num_samples_; - float irms = std::sqrt(raw); - ESP_LOGD(TAG, "'%s' - Raw Value: %.2fA", this->name_.c_str(), irms); - this->publish_state(irms); + float dc = this->sample_sum_ / this->num_samples_; + float var = (this->sample_squared_sum_ / this->num_samples_) - dc * dc; + float ac = std::sqrt(var); + ESP_LOGD(TAG, "'%s' - Got %d samples", this->name_.c_str(), this->num_samples_); + ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA", this->name_.c_str(), ac); + this->publish_state(ac); }); // Set sampling values this->is_sampling_ = true; this->num_samples_ = 0; this->sample_sum_ = 0.0f; + this->sample_squared_sum_ = 0.0f; } void CTClampSensor::loop() { - if (!this->is_sampling_ && !this->is_calibrating_offset_) + if (!this->is_sampling_) return; // Perform a single sample @@ -67,22 +55,8 @@ void CTClampSensor::loop() { if (isnan(value)) return; - if (this->is_calibrating_offset_) { - this->sample_sum_ += value; - this->num_samples_++; - return; - } - - // Adjust DC offset via low pass filter (exponential moving average) - const float alpha = 0.001f; - this->offset_ = this->offset_ * (1 - alpha) + value * alpha; - - // Filtered value centered around the mid-point (0V) - float filtered = value - this->offset_; - - // IRMS is sqrt(∑v_i²) - float sq = filtered * filtered; - this->sample_sum_ += sq; + this->sample_sum_ += value; + this->sample_squared_sum_ += value * value; this->num_samples_++; } diff --git a/esphome/components/ct_clamp/ct_clamp_sensor.h b/esphome/components/ct_clamp/ct_clamp_sensor.h index c709f6718b..2f201c11a0 100644 --- a/esphome/components/ct_clamp/ct_clamp_sensor.h +++ b/esphome/components/ct_clamp/ct_clamp_sensor.h @@ -10,7 +10,6 @@ namespace ct_clamp { class CTClampSensor : public sensor::Sensor, public PollingComponent { public: - void setup() override; void update() override; void loop() override; void dump_config() override; @@ -35,17 +34,19 @@ class CTClampSensor : public sensor::Sensor, public PollingComponent { * * Diagram: https://learn.openenergymonitor.org/electricity-monitoring/ct-sensors/interface-with-arduino * - * This is automatically calculated with an exponential moving average/digital low pass filter. - * - * 0.5 is a good initial approximation to start with for most ESP8266 setups. + * The current clamp only measures AC, so any DC component is an unwanted artifact from the + * sampling circuit. The AC component is essentially the same as the calculating the Standard-Deviation, + * which can be done by cumulating 3 values per sample: + * 1) Number of samples + * 2) Sum of samples + * 3) Sum of sample squared + * https://en.wikipedia.org/wiki/Root_mean_square */ - float offset_ = 0.5f; float sample_sum_ = 0.0f; + float sample_squared_sum_ = 0.0f; uint32_t num_samples_ = 0; bool is_sampling_ = false; - /// Calibrate offset value once at boot - bool is_calibrating_offset_ = false; }; } // namespace ct_clamp From 82eca13d7b84a8972836b078531c1236bdd991bc Mon Sep 17 00:00:00 2001 From: besteru <69540218+besteru@users.noreply.github.com> Date: Mon, 20 Sep 2021 10:14:44 +0300 Subject: [PATCH 1381/1841] Fix error reporting for DHT bit read loop (#2344) --- esphome/components/dht/dht.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index a7f5747d68..734dde20a8 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -122,6 +122,8 @@ bool HOT ICACHE_RAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, break; } } + if (error_code != 0) + break; start_time = micros(); uint32_t end_time = start_time; @@ -136,6 +138,8 @@ bool HOT ICACHE_RAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, break; } } + if (error_code != 0) + break; if (i < 0) continue; From fff5ba03c25757ee45ad2b587a5948ebbd38f6a7 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 09:29:09 +0200 Subject: [PATCH 1382/1841] Also run docker CI when requirements change (#2347) --- .github/workflows/ci-docker.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 33b15cb1dc..12f5a7dfc2 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -7,11 +7,15 @@ on: paths: - 'docker/**' - '.github/workflows/**' + - 'requirements*.txt' + - 'platformio.ini' pull_request: paths: - 'docker/**' - '.github/workflows/**' + - 'requirements*.txt' + - 'platformio.ini' jobs: check-docker: From 945ed5d3bda2a1080f875eb6ad796e72c38adbb2 Mon Sep 17 00:00:00 2001 From: synco Date: Mon, 20 Sep 2021 19:29:47 +1200 Subject: [PATCH 1383/1841] Added graphing component (#2109) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Oxan van Leeuwen Co-authored-by: Synco Reynders Co-authored-by: Otto winter --- CODEOWNERS | 1 + esphome/components/display/display_buffer.cpp | 7 + esphome/components/display/display_buffer.h | 28 ++ esphome/components/graph/__init__.py | 216 +++++++++++ esphome/components/graph/graph.cpp | 361 ++++++++++++++++++ esphome/components/graph/graph.h | 178 +++++++++ esphome/const.py | 14 + esphome/core/defines.h | 1 + 8 files changed, 806 insertions(+) create mode 100644 esphome/components/graph/__init__.py create mode 100644 esphome/components/graph/graph.cpp create mode 100644 esphome/components/graph/graph.h diff --git a/CODEOWNERS b/CODEOWNERS index 8aa96d14af..ab3a0815ce 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -54,6 +54,7 @@ esphome/components/fingerprint_grow/* @OnFreund @loongyh esphome/components/globals/* @esphome/core esphome/components/gpio/* @esphome/core esphome/components/gps/* @coogle +esphome/components/graph/* @synco esphome/components/havells_solar/* @sourabhjaiswal esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/light/* @DotNetDann diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 9c4ee3189f..bbb7444cb5 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -233,6 +233,13 @@ void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color colo } } +#ifdef USE_GRAPH +void DisplayBuffer::graph(int x, int y, graph::Graph *graph, Color color_on) { graph->draw(this, x, y, color_on); } +void DisplayBuffer::legend(int x, int y, graph::Graph *graph, Color color_on) { + graph->draw_legend(this, x, y, color_on); +} +#endif // USE_GRAPH + void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1, int *width, int *height) { int x_offset, baseline; diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index b89eab0dba..3f89d3f8d2 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -9,6 +9,10 @@ #include "esphome/components/time/real_time_clock.h" #endif +#ifdef USE_GRAPH +#include "esphome/components/graph/graph.h" +#endif + namespace esphome { namespace display { @@ -273,6 +277,30 @@ class DisplayBuffer { */ void image(int x, int y, Image *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF); +#ifdef USE_GRAPH + /** Draw the `graph` with the top-left corner at [x,y] to the screen. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param graph The graph id to draw + * @param color_on The color to replace in binary images for the on bits. + */ + void graph(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON); + + /** Draw the `legend` for graph with the top-left corner at [x,y] to the screen. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param graph The graph id for which the legend applies to + * @param graph The graph id for which the legend applies to + * @param graph The graph id for which the legend applies to + * @param name_font The font used for the trace name + * @param value_font The font used for the trace value and units + * @param color_on The color of the border + */ + void legend(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON); +#endif // USE_GRAPH + /** Get the text bounds of the given string. * * @param x The x coordinate to place the string at, can be 0 if only interested in dimensions. diff --git a/esphome/components/graph/__init__.py b/esphome/components/graph/__init__.py new file mode 100644 index 0000000000..12acfee869 --- /dev/null +++ b/esphome/components/graph/__init__.py @@ -0,0 +1,216 @@ +from esphome.components.font import Font +from esphome.components import sensor, color +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import ( + CONF_COLOR, + CONF_DIRECTION, + CONF_DURATION, + CONF_ID, + CONF_LEGEND, + CONF_NAME, + CONF_NAME_FONT, + CONF_SHOW_LINES, + CONF_SHOW_UNITS, + CONF_SHOW_VALUES, + CONF_VALUE_FONT, + CONF_WIDTH, + CONF_SENSOR, + CONF_HEIGHT, + CONF_MIN_VALUE, + CONF_MAX_VALUE, + CONF_MIN_RANGE, + CONF_MAX_RANGE, + CONF_LINE_THICKNESS, + CONF_LINE_TYPE, + CONF_X_GRID, + CONF_Y_GRID, + CONF_BORDER, + CONF_TRACES, +) + +CODEOWNERS = ["@synco"] + +DEPENDENCIES = ["display", "sensor"] +MULTI_CONF = True + +graph_ns = cg.esphome_ns.namespace("graph") +Graph_ = graph_ns.class_("Graph", cg.Component) +GraphTrace = graph_ns.class_("GraphTrace") +GraphLegend = graph_ns.class_("GraphLegend") + +LineType = graph_ns.enum("LineType") +LINE_TYPE = { + "SOLID": LineType.LINE_TYPE_SOLID, + "DOTTED": LineType.LINE_TYPE_DOTTED, + "DASHED": LineType.LINE_TYPE_DASHED, +} + +DirectionType = graph_ns.enum("DirectionType") +DIRECTION_TYPE = { + "AUTO": DirectionType.DIRECTION_TYPE_AUTO, + "HORIZONTAL": DirectionType.DIRECTION_TYPE_HORIZONTAL, + "VERTICAL": DirectionType.DIRECTION_TYPE_VERTICAL, +} + +ValuePositionType = graph_ns.enum("ValuePositionType") +VALUE_POSITION_TYPE = { + "NONE": ValuePositionType.VALUE_POSITION_TYPE_NONE, + "AUTO": ValuePositionType.VALUE_POSITION_TYPE_AUTO, + "BESIDE": ValuePositionType.VALUE_POSITION_TYPE_BESIDE, + "BELOW": ValuePositionType.VALUE_POSITION_TYPE_BELOW, +} + + +GRAPH_TRACE_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(GraphTrace), + cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), + cv.Optional(CONF_NAME): cv.string, + cv.Optional(CONF_LINE_THICKNESS): cv.positive_int, + cv.Optional(CONF_LINE_TYPE): cv.enum(LINE_TYPE, upper=True), + cv.Optional(CONF_COLOR): cv.use_id(color.ColorStruct), + } +) + +GRAPH_LEGEND_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_ID): cv.declare_id(GraphLegend), + cv.Required(CONF_NAME_FONT): cv.use_id(Font), + cv.Optional(CONF_VALUE_FONT): cv.use_id(Font), + cv.Optional(CONF_WIDTH): cv.positive_not_null_int, + cv.Optional(CONF_HEIGHT): cv.positive_not_null_int, + cv.Optional(CONF_BORDER): cv.boolean, + cv.Optional(CONF_SHOW_LINES): cv.boolean, + cv.Optional(CONF_SHOW_VALUES): cv.enum(VALUE_POSITION_TYPE, upper=True), + cv.Optional(CONF_SHOW_UNITS): cv.boolean, + cv.Optional(CONF_DIRECTION): cv.enum(DIRECTION_TYPE, upper=True), + } +) + + +GRAPH_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(Graph_), + cv.Required(CONF_DURATION): cv.positive_time_period_seconds, + cv.Required(CONF_WIDTH): cv.positive_not_null_int, + cv.Required(CONF_HEIGHT): cv.positive_not_null_int, + cv.Optional(CONF_X_GRID): cv.positive_time_period_seconds, + cv.Optional(CONF_Y_GRID): cv.float_range(min=0, min_included=False), + cv.Optional(CONF_BORDER): cv.boolean, + # Single trace options in base + cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), + cv.Optional(CONF_LINE_THICKNESS): cv.positive_int, + cv.Optional(CONF_LINE_TYPE): cv.enum(LINE_TYPE, upper=True), + cv.Optional(CONF_COLOR): cv.use_id(color.ColorStruct), + # Axis specific options (Future feature may be to add second Y-axis) + cv.Optional(CONF_MIN_VALUE): cv.float_, + cv.Optional(CONF_MAX_VALUE): cv.float_, + cv.Optional(CONF_MIN_RANGE): cv.float_range(min=0, min_included=False), + cv.Optional(CONF_MAX_RANGE): cv.float_range(min=0, min_included=False), + cv.Optional(CONF_TRACES): cv.ensure_list(GRAPH_TRACE_SCHEMA), + cv.Optional(CONF_LEGEND): cv.ensure_list(GRAPH_LEGEND_SCHEMA), + } +) + + +def _relocate_fields_to_subfolder(config, subfolder, subschema): + fields = [k.schema for k in subschema.schema.keys()] + fields.remove(CONF_ID) + if subfolder in config: + # Ensure no ambigious fields in base of config + for f in fields: + if f in config: + raise cv.Invalid( + "You cannot use the '" + + str(f) + + "' field when already using 'traces:'. " + "Please move it into 'traces:' entry." + ) + else: + # Copy over all fields to subfolder: + trace = {} + for f in fields: + if f in config: + trace[f] = config.pop(f) + config[subfolder] = cv.ensure_list(subschema)(trace) + return config + + +def _relocate_trace(config): + return _relocate_fields_to_subfolder(config, CONF_TRACES, GRAPH_TRACE_SCHEMA) + + +CONFIG_SCHEMA = cv.All( + GRAPH_SCHEMA, + _relocate_trace, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_duration(config[CONF_DURATION])) + cg.add(var.set_width(config[CONF_WIDTH])) + cg.add(var.set_height(config[CONF_HEIGHT])) + await cg.register_component(var, config) + + # Graph options + if CONF_X_GRID in config: + cg.add(var.set_grid_x(config[CONF_X_GRID])) + if CONF_Y_GRID in config: + cg.add(var.set_grid_y(config[CONF_Y_GRID])) + if CONF_BORDER in config: + cg.add(var.set_border(config[CONF_BORDER])) + # Axis related options + if CONF_MIN_VALUE in config: + cg.add(var.set_min_value(config[CONF_MIN_VALUE])) + if CONF_MAX_VALUE in config: + cg.add(var.set_max_value(config[CONF_MAX_VALUE])) + if CONF_MIN_RANGE in config: + cg.add(var.set_min_range(config[CONF_MIN_RANGE])) + if CONF_MAX_RANGE in config: + cg.add(var.set_max_range(config[CONF_MAX_RANGE])) + # Trace options + for trace in config[CONF_TRACES]: + tr = cg.new_Pvariable(trace[CONF_ID], GraphTrace()) + sens = await cg.get_variable(trace[CONF_SENSOR]) + cg.add(tr.set_sensor(sens)) + if CONF_NAME in trace: + cg.add(tr.set_name(trace[CONF_NAME])) + else: + cg.add(tr.set_name(trace[CONF_SENSOR].id)) + if CONF_LINE_THICKNESS in trace: + cg.add(tr.set_line_thickness(trace[CONF_LINE_THICKNESS])) + if CONF_LINE_TYPE in trace: + cg.add(tr.set_line_type(trace[CONF_LINE_TYPE])) + if CONF_COLOR in trace: + c = await cg.get_variable(trace[CONF_COLOR]) + cg.add(tr.set_line_color(c)) + cg.add(var.add_trace(tr)) + # Add legend + if CONF_LEGEND in config: + lgd = config[CONF_LEGEND][0] + legend = cg.new_Pvariable(lgd[CONF_ID], GraphLegend()) + if CONF_NAME_FONT in lgd: + font = await cg.get_variable(lgd[CONF_NAME_FONT]) + cg.add(legend.set_name_font(font)) + if CONF_VALUE_FONT in lgd: + font = await cg.get_variable(lgd[CONF_VALUE_FONT]) + cg.add(legend.set_value_font(font)) + if CONF_WIDTH in lgd: + cg.add(legend.set_width(lgd[CONF_WIDTH])) + if CONF_HEIGHT in lgd: + cg.add(legend.set_height(lgd[CONF_HEIGHT])) + if CONF_BORDER in lgd: + cg.add(legend.set_border(lgd[CONF_BORDER])) + if CONF_SHOW_LINES in lgd: + cg.add(legend.set_lines(lgd[CONF_SHOW_LINES])) + if CONF_SHOW_VALUES in lgd: + cg.add(legend.set_values(lgd[CONF_SHOW_VALUES])) + if CONF_SHOW_UNITS in lgd: + cg.add(legend.set_units(lgd[CONF_SHOW_UNITS])) + if CONF_DIRECTION in lgd: + cg.add(legend.set_direction(lgd[CONF_DIRECTION])) + cg.add(var.add_legend(legend)) + + cg.add_define("USE_GRAPH") diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp new file mode 100644 index 0000000000..6ede553fdb --- /dev/null +++ b/esphome/components/graph/graph.cpp @@ -0,0 +1,361 @@ +#include "graph.h" +#include "esphome/components/display/display_buffer.h" +#include "esphome/core/color.h" +#include "esphome/core/log.h" +#include "esphome/core/esphal.h" +#include +#include +#include // std::cout, std::fixed +#include +namespace esphome { +namespace graph { + +using namespace display; + +static const char *const TAG = "graph"; +static const char *const TAGL = "graphlegend"; + +void HistoryData::init(int length) { + this->length_ = length; + this->samples_.resize(length, NAN); + this->last_sample_ = millis(); +} + +void HistoryData::take_sample(float data) { + uint32_t tm = millis(); + uint32_t dt = tm - last_sample_; + last_sample_ = tm; + + // Step data based on time + this->period_ += dt; + while (this->period_ >= this->update_time_) { + this->samples_[this->count_] = data; + this->period_ -= this->update_time_; + this->count_ = (this->count_ + 1) % this->length_; + ESP_LOGV(TAG, "Updating trace with value: %f", data); + } + if (!isnan(data)) { + // Recalc recent max/min + this->recent_min_ = data; + this->recent_max_ = data; + for (int i = 0; i < this->length_; i++) { + if (!isnan(this->samples_[i])) { + if (this->recent_max_ < this->samples_[i]) + this->recent_max_ = this->samples_[i]; + if (this->recent_min_ > this->samples_[i]) + this->recent_min_ = this->samples_[i]; + } + } + } +} + +void GraphTrace::init(Graph *g) { + ESP_LOGI(TAG, "Init trace for sensor %s", this->get_name().c_str()); + this->data_.init(g->get_width()); + sensor_->add_on_state_callback([this](float state) { this->data_.take_sample(state); }); + this->data_.set_update_time_ms(g->get_duration() * 1000 / g->get_width()); +} + +void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color) { + /// Plot border + if (this->border_) { + buff->horizontal_line(x_offset, y_offset, this->width_, color); + buff->horizontal_line(x_offset, y_offset + this->height_ - 1, this->width_, color); + buff->vertical_line(x_offset, y_offset, this->height_, color); + buff->vertical_line(x_offset + this->width_ - 1, y_offset, this->height_, color); + } + /// Determine best y-axis scale and range + float ymin = NAN; + float ymax = NAN; + for (auto *trace : traces_) { + float mx = trace->get_tracedata()->get_recent_max(); + float mn = trace->get_tracedata()->get_recent_min(); + if (isnan(ymax) || (ymax < mx)) + ymax = mx; + if (isnan(ymin) || (ymin > mn)) + ymin = mn; + } + // Adjust if manually overridden + if (!isnan(this->min_value_)) + ymin = this->min_value_; + if (!isnan(this->max_value_)) + ymax = this->max_value_; + + float yrange = ymax - ymin; + if (yrange > this->max_range_) { + // Look back in trace data to best-fit into local range + float mx = NAN; + float mn = NAN; + for (int16_t i = 0; i < this->width_; i++) { + for (auto *trace : traces_) { + float v = trace->get_tracedata()->get_value(i); + if (!isnan(v)) { + if ((v - mn) > this->max_range_) + break; + if ((mx - v) > this->max_range_) + break; + if (isnan(mx) || (v > mx)) + mx = v; + if (isnan(mn) || (v < mn)) + mn = v; + } + } + } + yrange = this->max_range_; + if (!isnan(mn)) { + ymin = mn; + ymax = ymin + this->max_range_; + } + ESP_LOGV(TAG, "Graphing at max_range. Using local min %f, max %f", mn, mx); + } + + float y_per_div = this->min_range_; + if (!isnan(this->gridspacing_y_)) { + y_per_div = this->gridspacing_y_; + } + // Restrict drawing too many gridlines + if (yrange > 10 * y_per_div) { + while (yrange > 10 * y_per_div) { + y_per_div *= 2; + } + ESP_LOGW(TAG, "Graphing reducing y-scale to prevent too many gridlines"); + } + + // Adjust limits to nice y_per_div boundaries + int yn = int(ymin / y_per_div); + int ym = int(ymax / y_per_div) + int(1 * (fmodf(ymax, y_per_div) != 0)); + ymin = yn * y_per_div; + ymax = ym * y_per_div; + yrange = ymax - ymin; + + /// Draw grid + if (!isnan(this->gridspacing_y_)) { + for (int y = yn; y <= ym; y++) { + int16_t py = (int16_t) roundf((this->height_ - 1) * (1.0 - (float) (y - yn) / (ym - yn))); + for (int x = 0; x < this->width_; x += 2) { + buff->draw_pixel_at(x_offset + x, y_offset + py, color); + } + } + } + if (!isnan(this->gridspacing_x_) && (this->gridspacing_x_ > 0)) { + int n = this->duration_ / this->gridspacing_x_; + // Restrict drawing too many gridlines + if (n > 20) { + while (n > 20) { + n /= 2; + } + ESP_LOGW(TAG, "Graphing reducing x-scale to prevent too many gridlines"); + } + for (int i = 0; i <= n; i++) { + for (int y = 0; y < this->height_; y += 2) { + buff->draw_pixel_at(x_offset + i * (this->width_ - 1) / n, y_offset + y, color); + } + } + } + + /// Draw traces + ESP_LOGV(TAG, "Updating graph. ymin %f, ymax %f", ymin, ymax); + for (auto *trace : traces_) { + Color c = trace->get_line_color(); + uint16_t thick = trace->get_line_thickness(); + for (int16_t i = 0; i < this->width_; i++) { + float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange; + if (!isnan(v) && (thick > 0)) { + int16_t x = this->width_ - 1 - i; + uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; + if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { + int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2; + for (int16_t t = 0; t < thick; t++) { + buff->draw_pixel_at(x_offset + x, y_offset + y + t, c); + } + } + } + } + } +} + +/// Determine the best coordinates of drawing text + lines +void GraphLegend::init(Graph *g) { + parent_ = g; + + // Determine maximum expected text and value width / height + int txtw = 0, txtos = 0, txtbl = 0, txth = 0; + int valw = 0, valos = 0, valbl = 0, valh = 0; + int lt = 0; + for (auto *trace : g->traces_) { + std::string txtstr = trace->get_name(); + int fw, fos, fbl, fh; + this->font_label->measure(txtstr.c_str(), &fw, &fos, &fbl, &fh); + if (fw > txtw) + txtw = fw; + if (fh > txth) + txth = fh; + if (trace->get_line_thickness() > lt) + lt = trace->get_line_thickness(); + ESP_LOGI(TAGL, " %s %d %d", txtstr.c_str(), fw, fh); + + if (this->values_ != VALUE_POSITION_TYPE_NONE) { + std::stringstream ss; + ss << std::fixed << std::setprecision(trace->sensor_->get_accuracy_decimals()) << trace->sensor_->get_state(); + std::string valstr = ss.str(); + if (this->units_) { + valstr += trace->sensor_->get_unit_of_measurement(); + } + this->font_value->measure(valstr.c_str(), &fw, &fos, &fbl, &fh); + if (fw > valw) + valw = fw; + if (fh > valh) + valh = fh; + ESP_LOGI(TAGL, " %s %d %d", valstr.c_str(), fw, fh); + } + } + // Add extra margin + txtw *= 1.2; + valw *= 1.2; + + uint8_t n = g->traces_.size(); + uint16_t w = this->width_; + uint16_t h = this->height_; + DirectionType dir = this->direction_; + ValuePositionType valpos = this->values_; + if (!this->font_value) { + valpos = VALUE_POSITION_TYPE_NONE; + } + // Line sample always goes below text for compactness + this->yl = txth + (txth / 4) + lt / 2; + + if (dir == DIRECTION_TYPE_AUTO) { + dir = DIRECTION_TYPE_HORIZONTAL; // as default + if (h > 0) { + dir = DIRECTION_TYPE_VERTICAL; + } + } + + if (valpos == VALUE_POSITION_TYPE_AUTO) { + // TODO: do something smarter?? - fit to w and h? + valpos = VALUE_POSITION_TYPE_BELOW; + } + + if (valpos == VALUE_POSITION_TYPE_BELOW) { + this->yv = txth + (txth / 4); + if (this->lines_) + this->yv += txth / 4 + lt; + } else if (valpos == VALUE_POSITION_TYPE_BESIDE) { + this->xv = (txtw + valw) / 2; + } + + // If width or height is specified we divide evenly within, else we do tight-fit + if (w == 0) { + this->x0 = txtw / 2; + this->xs = txtw; + if (valpos == VALUE_POSITION_TYPE_BELOW) { + this->xs = std::max(txtw, valw); + ; + this->x0 = this->xs / 2; + } else if (valpos == VALUE_POSITION_TYPE_BESIDE) { + this->xs = txtw + valw; + } + if (dir == DIRECTION_TYPE_VERTICAL) { + this->width_ = this->xs; + } else { + this->width_ = this->xs * n; + } + } else { + this->xs = w / n; + this->x0 = this->xs / 2; + } + + if (h == 0) { + this->ys = txth; + if (valpos == VALUE_POSITION_TYPE_BELOW) { + this->ys = txth + txth / 2 + valh; + if (this->lines_) { + this->ys += lt; + } + } else if (valpos == VALUE_POSITION_TYPE_BESIDE) { + if (this->lines_) { + this->ys = std::max(txth + txth / 4 + lt + txth / 4, valh + valh / 4); + } else { + this->ys = std::max(txth + txth / 4, valh + valh / 4); + } + this->height_ = this->ys * n; + } + if (dir == DIRECTION_TYPE_HORIZONTAL) { + this->height_ = this->ys; + } else { + this->height_ = this->ys * n; + } + } else { + this->ys = h / n; + } + + if (dir == DIRECTION_TYPE_HORIZONTAL) { + this->ys = 0; + } else { + this->xs = 0; + } +} + +void Graph::draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color) { + if (!legend_) + return; + + /// Plot border + if (this->border_) { + int w = legend_->width_; + int h = legend_->height_; + buff->horizontal_line(x_offset, y_offset, w, color); + buff->horizontal_line(x_offset, y_offset + h - 1, w, color); + buff->vertical_line(x_offset, y_offset, h, color); + buff->vertical_line(x_offset + w - 1, y_offset, h, color); + } + + int x = x_offset + legend_->x0; + int y = y_offset; + for (auto *trace : traces_) { + std::string txtstr = trace->get_name(); + ESP_LOGV(TAG, " %s", txtstr.c_str()); + + buff->printf(x, y, legend_->font_label, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", txtstr.c_str()); + + if (legend_->lines_) { + uint16_t thick = trace->get_line_thickness(); + for (int16_t i = 0; i < legend_->x0 * 4 / 3; i++) { + uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; + if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { + buff->vertical_line(x - legend_->x0 * 2 / 3 + i, y + legend_->yl - thick / 2, thick, trace->get_line_color()); + } + } + } + + if (legend_->values_ != VALUE_POSITION_TYPE_NONE) { + int xv = x + legend_->xv; + int yv = y + legend_->yv; + std::stringstream ss; + ss << std::fixed << std::setprecision(trace->sensor_->get_accuracy_decimals()) << trace->sensor_->get_state(); + std::string valstr = ss.str(); + if (legend_->units_) { + valstr += trace->sensor_->get_unit_of_measurement(); + } + buff->printf(xv, yv, legend_->font_value, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", valstr.c_str()); + ESP_LOGV(TAG, " value: %s", valstr.c_str()); + } + x += legend_->xs; + y += legend_->ys; + } +} + +void Graph::setup() { + for (auto *trace : traces_) { + trace->init(this); + } +} + +void Graph::dump_config() { + for (auto *trace : traces_) { + ESP_LOGCONFIG(TAG, "Graph for sensor %s", trace->get_name().c_str()); + } +} + +} // namespace graph +} // namespace esphome diff --git a/esphome/components/graph/graph.h b/esphome/components/graph/graph.h new file mode 100644 index 0000000000..8f6e74f67a --- /dev/null +++ b/esphome/components/graph/graph.h @@ -0,0 +1,178 @@ +#pragma once +#include +#include "esphome/core/color.h" +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { + +// forward declare DisplayBuffer +namespace display { +class DisplayBuffer; +class Font; +} // namespace display + +namespace graph { + +class Graph; + +const Color COLOR_ON(255, 255, 255, 255); + +/// Bit pattern defines the line-type +enum LineType { + LINE_TYPE_SOLID = 0b1111, + LINE_TYPE_DOTTED = 0b0101, + LINE_TYPE_DASHED = 0b1110, + // Following defines number of bits used to define line pattern + PATTERN_LENGTH = 4 +}; + +enum DirectionType { + DIRECTION_TYPE_AUTO, + DIRECTION_TYPE_HORIZONTAL, + DIRECTION_TYPE_VERTICAL, +}; + +enum ValuePositionType { + VALUE_POSITION_TYPE_NONE, + VALUE_POSITION_TYPE_AUTO, + VALUE_POSITION_TYPE_BESIDE, + VALUE_POSITION_TYPE_BELOW +}; + +class GraphLegend { + public: + void init(Graph *g); + void set_name_font(display::Font *font) { this->font_label = font; } + void set_value_font(display::Font *font) { this->font_value = font; } + void set_width(uint32_t width) { this->width_ = width; } + void set_height(uint32_t height) { this->height_ = height; } + void set_border(bool val) { this->border_ = val; } + void set_lines(bool val) { this->lines_ = val; } + void set_values(ValuePositionType val) { this->values_ = val; } + void set_units(bool val) { this->units_ = val; } + void set_direction(DirectionType val) { this->direction_ = val; } + + protected: + uint32_t width_{0}; + uint32_t height_{0}; + bool border_{true}; + bool lines_{true}; + ValuePositionType values_{VALUE_POSITION_TYPE_AUTO}; + bool units_{true}; + DirectionType direction_{DIRECTION_TYPE_AUTO}; + display::Font *font_label{nullptr}; + display::Font *font_value{nullptr}; + // Calculated values + Graph *parent_{nullptr}; + // (x0) (xs,ys) (xs,ys) + // ------> LABEL1 -------> LABEL2 -------> ... + // | \(xv,yv) \ . + // | \ \-> VALUE1+units + // (0,yl)| \-> VALUE1+units + // v (top_center) + // LINE_SAMPLE + int x0{0}; // X-offset to centre of label text + int xs{0}; // X spacing between labels + int ys{0}; // Y spacing between labels + int yl{0}; // Y spacing from label to line sample + int xv{0}; // X distance between label to value text + int yv{0}; // Y distance between label to value text + friend Graph; +}; + +class HistoryData { + public: + void init(int length); + ~HistoryData(); + void set_update_time_ms(uint32_t update_time_ms) { update_time_ = update_time_ms; } + void take_sample(float data); + int get_length() const { return length_; } + float get_value(int idx) const { return samples_[(count_ + length_ - 1 - idx) % length_]; } + float get_recent_max() const { return recent_max_; } + float get_recent_min() const { return recent_min_; } + + protected: + uint32_t last_sample_; + uint32_t period_{0}; /// in ms + uint32_t update_time_{0}; /// in ms + int length_; + int count_{0}; + float recent_min_{NAN}; + float recent_max_{NAN}; + std::vector samples_; +}; + +class GraphTrace { + public: + void init(Graph *g); + void set_name(std::string name) { name_ = name; } + void set_sensor(sensor::Sensor *sensor) { sensor_ = sensor; } + uint8_t get_line_thickness() { return this->line_thickness_; } + void set_line_thickness(uint8_t val) { this->line_thickness_ = val; } + enum LineType get_line_type() { return this->line_type_; } + void set_line_type(enum LineType val) { this->line_type_ = val; } + Color get_line_color() { return this->line_color_; } + void set_line_color(Color val) { this->line_color_ = val; } + const std::string get_name(void) { return name_; } + const HistoryData *get_tracedata() { return &data_; } + + protected: + sensor::Sensor *sensor_{nullptr}; + std::string name_{""}; + uint8_t line_thickness_{3}; + enum LineType line_type_ { LINE_TYPE_SOLID }; + Color line_color_{COLOR_ON}; + HistoryData data_; + + friend Graph; + friend GraphLegend; +}; + +class Graph : public Component { + public: + void draw(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color); + void draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color); + + void setup() override; + float get_setup_priority() const override { return setup_priority::PROCESSOR; } + void dump_config() override; + + void set_duration(uint32_t duration) { duration_ = duration; } + void set_width(uint32_t width) { width_ = width; } + void set_height(uint32_t height) { height_ = height; } + void set_min_value(float val) { this->min_value_ = val; } + void set_max_value(float val) { this->max_value_ = val; } + void set_min_range(float val) { this->min_range_ = val; } + void set_max_range(float val) { this->max_range_ = val; } + void set_grid_x(float val) { this->gridspacing_x_ = val; } + void set_grid_y(float val) { this->gridspacing_y_ = val; } + void set_border(bool val) { this->border_ = val; } + void add_trace(GraphTrace *trace) { traces_.push_back(trace); } + void add_legend(GraphLegend *legend) { + this->legend_ = legend; + legend->init(this); + } + uint32_t get_duration() { return duration_; } + uint32_t get_width() { return width_; } + uint32_t get_height() { return height_; } + + protected: + uint32_t duration_; /// in seconds + uint32_t width_; /// in pixels + uint32_t height_; /// in pixels + float min_value_{NAN}; + float max_value_{NAN}; + float min_range_{1.0}; + float max_range_{NAN}; + float gridspacing_x_{NAN}; + float gridspacing_y_{NAN}; + bool border_{true}; + std::vector traces_; + GraphLegend *legend_{nullptr}; + + friend GraphLegend; +}; + +} // namespace graph +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index a74068ab77..598b6351b4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -90,6 +90,7 @@ CONF_BIT_DEPTH = "bit_depth" CONF_BLUE = "blue" CONF_BOARD = "board" CONF_BOARD_FLASH_MODE = "board_flash_mode" +CONF_BORDER = "border" CONF_BRANCH = "branch" CONF_BRIGHTNESS = "brightness" CONF_BROKER = "broker" @@ -327,6 +328,7 @@ CONF_LAMBDA = "lambda" CONF_LAST_CONFIDENCE = "last_confidence" CONF_LAST_FINGER_ID = "last_finger_id" CONF_LATITUDE = "latitude" +CONF_LEGEND = "legend" CONF_LENGTH = "length" CONF_LEVEL = "level" CONF_LG = "lg" @@ -334,6 +336,8 @@ CONF_LIBRARIES = "libraries" CONF_LIGHT = "light" CONF_LIGHTNING_ENERGY = "lightning_energy" CONF_LIGHTNING_THRESHOLD = "lightning_threshold" +CONF_LINE_THICKNESS = "line_thickness" +CONF_LINE_TYPE = "line_type" CONF_LOADED_INTEGRATIONS = "loaded_integrations" CONF_LOCAL = "local" CONF_LOG_TOPIC = "log_topic" @@ -355,6 +359,7 @@ CONF_MAX_HEATING_RUN_TIME = "max_heating_run_time" CONF_MAX_LENGTH = "max_length" CONF_MAX_LEVEL = "max_level" CONF_MAX_POWER = "max_power" +CONF_MAX_RANGE = "max_range" CONF_MAX_REFRESH_RATE = "max_refresh_rate" CONF_MAX_SPEED = "max_speed" CONF_MAX_TEMPERATURE = "max_temperature" @@ -376,6 +381,7 @@ CONF_MIN_IDLE_TIME = "min_idle_time" CONF_MIN_LENGTH = "min_length" CONF_MIN_LEVEL = "min_level" CONF_MIN_POWER = "min_power" +CONF_MIN_RANGE = "min_range" CONF_MIN_TEMPERATURE = "min_temperature" CONF_MIN_VALUE = "min_value" CONF_MINUTE = "minute" @@ -393,6 +399,7 @@ CONF_MQTT_ID = "mqtt_id" CONF_MULTIPLEXER = "multiplexer" CONF_MULTIPLY = "multiply" CONF_NAME = "name" +CONF_NAME_FONT = "name_font" CONF_NBITS = "nbits" CONF_NEC = "nec" CONF_NETWORKS = "networks" @@ -584,6 +591,9 @@ CONF_SERVICES = "services" CONF_SET_POINT_MINIMUM_DIFFERENTIAL = "set_point_minimum_differential" CONF_SETUP_MODE = "setup_mode" CONF_SETUP_PRIORITY = "setup_priority" +CONF_SHOW_LINES = "show_lines" +CONF_SHOW_UNITS = "show_units" +CONF_SHOW_VALUES = "show_values" CONF_SHUNT_RESISTANCE = "shunt_resistance" CONF_SHUNT_VOLTAGE = "shunt_voltage" CONF_SHUTDOWN_MESSAGE = "shutdown_message" @@ -659,6 +669,7 @@ CONF_TOLERANCE = "tolerance" CONF_TOPIC = "topic" CONF_TOPIC_PREFIX = "topic_prefix" CONF_TOTAL = "total" +CONF_TRACES = "traces" CONF_TRANSITION_LENGTH = "transition_length" CONF_TRIGGER_ID = "trigger_id" CONF_TRIGGER_PIN = "trigger_pin" @@ -681,6 +692,7 @@ CONF_USE_ADDRESS = "use_address" CONF_USERNAME = "username" CONF_UUID = "uuid" CONF_VALUE = "value" +CONF_VALUE_FONT = "value_font" CONF_VARIABLES = "variables" CONF_VARIANT = "variant" CONF_VERSION = "version" @@ -704,6 +716,8 @@ CONF_WILL_MESSAGE = "will_message" CONF_WIND_DIRECTION_DEGREES = "wind_direction_degrees" CONF_WIND_SPEED = "wind_speed" CONF_WINDOW_SIZE = "window_size" +CONF_X_GRID = "x_grid" +CONF_Y_GRID = "y_grid" CONF_ZERO = "zero" ENV_NOGITIGNORE = "ESPHOME_NOGITIGNORE" diff --git a/esphome/core/defines.h b/esphome/core/defines.h index e3976d378e..89010ce246 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -19,6 +19,7 @@ #define USE_DEEP_SLEEP #define USE_ESP8266_PREFERENCES_FLASH #define USE_FAN +#define USE_GRAPH #define USE_HOMEASSISTANT_TIME #define USE_HTTP_REQUEST_ESP8266_HTTPS #define USE_I2C_MULTIPLEXER From 81685573e1fc760259d6d0eae467807acdfdd479 Mon Sep 17 00:00:00 2001 From: poptix Date: Mon, 20 Sep 2021 02:44:18 -0500 Subject: [PATCH 1384/1841] Properly calculate negative temperatures in sm300d2 (#2335) Co-authored-by: Matt Hallacy --- esphome/components/sm300d2/sm300d2.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/esphome/components/sm300d2/sm300d2.cpp b/esphome/components/sm300d2/sm300d2.cpp index d542582fcb..e41a4855db 100644 --- a/esphome/components/sm300d2/sm300d2.cpp +++ b/esphome/components/sm300d2/sm300d2.cpp @@ -29,8 +29,11 @@ void SM300D2Sensor::update() { } uint16_t calculated_checksum = this->sm300d2_checksum_(response); + // Occasionally the checksum has a +/- 0x80 offset. Negative temperatures are + // responsible for some of these. The rest are unknown/undocumented. if ((calculated_checksum != response[SM300D2_RESPONSE_LENGTH - 1]) && - (calculated_checksum - 0x80 != response[SM300D2_RESPONSE_LENGTH - 1])) { + (calculated_checksum - 0x80 != response[SM300D2_RESPONSE_LENGTH - 1]) && + (calculated_checksum + 0x80 != response[SM300D2_RESPONSE_LENGTH - 1])) { ESP_LOGW(TAG, "SM300D2 Checksum doesn't match: 0x%02X!=0x%02X", response[SM300D2_RESPONSE_LENGTH - 1], calculated_checksum); this->status_set_warning(); @@ -46,7 +49,10 @@ void SM300D2Sensor::update() { const uint16_t tvoc = (response[6] * 256) + response[7]; const uint16_t pm_2_5 = (response[8] * 256) + response[9]; const uint16_t pm_10_0 = (response[10] * 256) + response[11]; - const float temperature = response[12] + (response[13] * 0.1); + // A negative value is indicated by adding 0x80 (128) to the temperature value + const float temperature = ((response[12] + (response[13] * 0.1)) > 128) + ? (((response[12] + (response[13] * 0.1)) - 128) * -1) + : response[12] + (response[13] * 0.1); const float humidity = response[14] + (response[15] * 0.1); ESP_LOGD(TAG, "Received CO₂: %u ppm", co2); From 5e345783bdc68773c81012388bf5891c2f229ed4 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 09:55:18 +0200 Subject: [PATCH 1385/1841] Fix docker release deploy push flag (#2348) --- docker/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/build.py b/docker/build.py index 1904457989..cd8dca3b87 100755 --- a/docker/build.py +++ b/docker/build.py @@ -31,7 +31,7 @@ parser.add_argument("--build-type", choices=TYPES, required=True, help="The type parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them") subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True) build_parser = subparsers.add_parser("build", help="Build the image") -build_parser.add_argument("--push", help="Also push the images") +build_parser.add_argument("--push", help="Also push the images", action="store_true") manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images") From 3052c64dd707139b5b4ccca478915423d54d10e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE=20Srebrni=C4=8D?= Date: Mon, 20 Sep 2021 10:08:08 +0200 Subject: [PATCH 1386/1841] Add invert_colors option for st7735 (#2327) --- esphome/components/st7735/display.py | 3 +++ esphome/components/st7735/st7735.cpp | 7 ++++++- esphome/components/st7735/st7735.h | 4 +++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/esphome/components/st7735/display.py b/esphome/components/st7735/display.py index c1ede4e0ce..ae31f604a5 100644 --- a/esphome/components/st7735/display.py +++ b/esphome/components/st7735/display.py @@ -23,6 +23,7 @@ CONF_ROW_START = "row_start" CONF_COL_START = "col_start" CONF_EIGHT_BIT_COLOR = "eight_bit_color" CONF_USE_BGR = "use_bgr" +CONF_INVERT_COLORS = "invert_colors" SPIST7735 = st7735_ns.class_( "ST7735", cg.PollingComponent, display.DisplayBuffer, spi.SPIDevice @@ -58,6 +59,7 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_ROW_START): cv.int_, cv.Optional(CONF_EIGHT_BIT_COLOR, default=False): cv.boolean, cv.Optional(CONF_USE_BGR, default=False): cv.boolean, + cv.Optional(CONF_INVERT_COLORS, default=False): cv.boolean, } ) .extend(cv.COMPONENT_SCHEMA) @@ -90,6 +92,7 @@ async def to_code(config): config[CONF_ROW_START], config[CONF_EIGHT_BIT_COLOR], config[CONF_USE_BGR], + config[CONF_INVERT_COLORS], ) await setup_st7735(var, config) await spi.register_spi_device(var, config) diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index b44f76a9e6..1f14136637 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -220,12 +220,14 @@ static const uint8_t PROGMEM // clang-format on static const char *const TAG = "st7735"; -ST7735::ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr) +ST7735::ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr, + bool invert_colors) : model_(model), colstart_(colstart), rowstart_(rowstart), eightbitcolor_(eightbitcolor), usebgr_(usebgr), + invert_colors_(invert_colors), width_(width), height_(height) {} @@ -281,6 +283,9 @@ void ST7735::setup() { } sendcommand_(ST77XX_MADCTL, &data, 1); + if (this->invert_colors_) + sendcommand_(ST77XX_INVON, nullptr, 0); + this->init_internal_(this->get_buffer_length()); memset(this->buffer_, 0x00, this->get_buffer_length()); } diff --git a/esphome/components/st7735/st7735.h b/esphome/components/st7735/st7735.h index 0eb4ccadd8..c049fb9e83 100644 --- a/esphome/components/st7735/st7735.h +++ b/esphome/components/st7735/st7735.h @@ -37,7 +37,8 @@ class ST7735 : public PollingComponent, public spi::SPIDevice { public: - ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr); + ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr, + bool invert_colors); void dump_config() override; void setup() override; @@ -77,6 +78,7 @@ class ST7735 : public PollingComponent, uint8_t colstart_ = 0, rowstart_ = 0; bool eightbitcolor_ = false; bool usebgr_ = false; + bool invert_colors_ = false; int16_t width_ = 80, height_ = 80; // Watch heap size GPIOPin *reset_pin_{nullptr}; From 9ebe075f9b85ff5940748df3ce393bccc1e72c88 Mon Sep 17 00:00:00 2001 From: Christian Taedcke Date: Mon, 20 Sep 2021 10:12:32 +0200 Subject: [PATCH 1387/1841] Add deep sleep wakeup from touch (#1238) (#2281) --- esphome/components/deep_sleep/__init__.py | 5 ++++ .../deep_sleep/deep_sleep_component.cpp | 7 ++++++ .../deep_sleep/deep_sleep_component.h | 3 +++ .../components/esp32_touch/binary_sensor.py | 3 +++ .../components/esp32_touch/esp32_touch.cpp | 25 ++++++++++++++++--- esphome/components/esp32_touch/esp32_touch.h | 4 ++- 6 files changed, 43 insertions(+), 4 deletions(-) diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 3f59ad52cf..1e07b75173 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -44,6 +44,7 @@ EXT1_WAKEUP_MODES = { CONF_WAKEUP_PIN_MODE = "wakeup_pin_mode" CONF_ESP32_EXT1_WAKEUP = "esp32_ext1_wakeup" +CONF_TOUCH_WAKEUP = "touch_wakeup" CONFIG_SCHEMA = cv.Schema( { @@ -67,6 +68,7 @@ CONFIG_SCHEMA = cv.Schema( } ), ), + cv.Optional(CONF_TOUCH_WAKEUP): cv.All(cv.only_on_esp32, cv.boolean), } ).extend(cv.COMPONENT_SCHEMA) @@ -95,6 +97,9 @@ async def to_code(config): ) cg.add(var.set_ext1_wakeup(struct)) + if CONF_TOUCH_WAKEUP in config: + cg.add(var.set_touch_wakeup(config[CONF_TOUCH_WAKEUP])) + cg.add_define("USE_DEEP_SLEEP") diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 00f660f41a..9c354c8513 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -44,6 +44,7 @@ void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) { this->wakeup_pin_mode_ = wakeup_pin_mode; } void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; } +void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; } #endif void DeepSleepComponent::set_run_duration(uint32_t time_ms) { this->run_duration_ = time_ms; } void DeepSleepComponent::begin_sleep(bool manual) { @@ -80,6 +81,12 @@ void DeepSleepComponent::begin_sleep(bool manual) { if (this->ext1_wakeup_.has_value()) { esp_sleep_enable_ext1_wakeup(this->ext1_wakeup_->mask, this->ext1_wakeup_->wakeup_mode); } + + if (this->touch_wakeup_.has_value() && *(this->touch_wakeup_)) { + esp_sleep_enable_touchpad_wakeup(); + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + } + esp_deep_sleep_start(); #endif diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index 5050bd6b4d..575f7be72b 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -53,6 +53,8 @@ class DeepSleepComponent : public Component { void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode); void set_ext1_wakeup(Ext1Wakeup ext1_wakeup); + + void set_touch_wakeup(bool touch_wakeup); #endif /// Set a duration in ms for how long the code should run before entering deep sleep mode. void set_run_duration(uint32_t time_ms); @@ -74,6 +76,7 @@ class DeepSleepComponent : public Component { optional wakeup_pin_; WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE}; optional ext1_wakeup_; + optional touch_wakeup_; #endif optional run_duration_; bool next_enter_deep_sleep_{false}; diff --git a/esphome/components/esp32_touch/binary_sensor.py b/esphome/components/esp32_touch/binary_sensor.py index 300de23f08..198a43a738 100644 --- a/esphome/components/esp32_touch/binary_sensor.py +++ b/esphome/components/esp32_touch/binary_sensor.py @@ -15,6 +15,7 @@ ESP_PLATFORMS = [ESP_PLATFORM_ESP32] DEPENDENCIES = ["esp32_touch"] CONF_ESP32_TOUCH_ID = "esp32_touch_id" +CONF_WAKEUP_THRESHOLD = "wakeup_threshold" TOUCH_PADS = { 4: cg.global_ns.TOUCH_PAD_NUM0, @@ -47,6 +48,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( cv.GenerateID(CONF_ESP32_TOUCH_ID): cv.use_id(ESP32TouchComponent), cv.Required(CONF_PIN): validate_touch_pad, cv.Required(CONF_THRESHOLD): cv.uint16_t, + cv.Optional(CONF_WAKEUP_THRESHOLD, default=0): cv.uint16_t, } ) @@ -58,6 +60,7 @@ async def to_code(config): config[CONF_NAME], TOUCH_PADS[config[CONF_PIN]], config[CONF_THRESHOLD], + config[CONF_WAKEUP_THRESHOLD], ) await binary_sensor.register_binary_sensor(var, config) cg.add(hub.register_touch_pad(var)) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index a990e632af..9ae671a4a9 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -133,15 +133,34 @@ void ESP32TouchComponent::loop() { } void ESP32TouchComponent::on_shutdown() { + bool is_wakeup_source = false; + if (this->iir_filter_enabled_()) { touch_pad_filter_stop(); touch_pad_filter_delete(); } - touch_pad_deinit(); + + for (auto *child : this->children_) { + if (child->get_wakeup_threshold() != 0) { + if (!is_wakeup_source) { + is_wakeup_source = true; + // Touch sensor FSM mode must be 'TOUCH_FSM_MODE_TIMER' to use it to wake-up. + touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); + } + + // No filter available when using as wake-up source. + touch_pad_config(child->get_touch_pad(), child->get_wakeup_threshold()); + } + } + + if (!is_wakeup_source) { + touch_pad_deinit(); + } } -ESP32TouchBinarySensor::ESP32TouchBinarySensor(const std::string &name, touch_pad_t touch_pad, uint16_t threshold) - : BinarySensor(name), touch_pad_(touch_pad), threshold_(threshold) {} +ESP32TouchBinarySensor::ESP32TouchBinarySensor(const std::string &name, touch_pad_t touch_pad, uint16_t threshold, + uint16_t wakeup_threshold) + : BinarySensor(name), touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {} } // namespace esp32_touch } // namespace esphome diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 45d459a2ff..8b92a482c0 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -57,12 +57,13 @@ class ESP32TouchComponent : public Component { /// Simple helper class to expose a touch pad value as a binary sensor. class ESP32TouchBinarySensor : public binary_sensor::BinarySensor { public: - ESP32TouchBinarySensor(const std::string &name, touch_pad_t touch_pad, uint16_t threshold); + ESP32TouchBinarySensor(const std::string &name, touch_pad_t touch_pad, uint16_t threshold, uint16_t wakeup_threshold); touch_pad_t get_touch_pad() const { return touch_pad_; } uint16_t get_threshold() const { return threshold_; } void set_threshold(uint16_t threshold) { threshold_ = threshold; } uint16_t get_value() const { return value_; } + uint16_t get_wakeup_threshold() const { return wakeup_threshold_; } protected: friend ESP32TouchComponent; @@ -70,6 +71,7 @@ class ESP32TouchBinarySensor : public binary_sensor::BinarySensor { touch_pad_t touch_pad_; uint16_t threshold_; uint16_t value_; + const uint16_t wakeup_threshold_; }; } // namespace esp32_touch From 7452ef23b11575b2f8097f811f64015ec121eae2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 20 Sep 2021 10:16:59 +0200 Subject: [PATCH 1388/1841] Add ESPHOME_VERSION_CODE define (#2324) --- esphome/core/version.h | 3 +++ esphome/writer.py | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/esphome/core/version.h b/esphome/core/version.h index b64f581b25..7336ebf7f8 100644 --- a/esphome/core/version.h +++ b/esphome/core/version.h @@ -6,4 +6,7 @@ // // This file is only used by static analyzers and IDEs. +#include "esphome/core/macros.h" + #define ESPHOME_VERSION "dev" +#define ESPHOME_VERSION_CODE VERSION_CODE(2099, 12, 0) diff --git a/esphome/writer.py b/esphome/writer.py index 19953916c7..2e1e19de54 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -342,7 +342,9 @@ DEFINES_H_FORMAT = ESPHOME_H_FORMAT = """\ """ VERSION_H_FORMAT = """\ #pragma once +#include "esphome/core/macros.h" #define ESPHOME_VERSION "{}" +#define ESPHOME_VERSION_CODE VERSION_CODE({}, {}, {}) """ DEFINES_H_TARGET = "esphome/core/defines.h" VERSION_H_TARGET = "esphome/core/version.h" @@ -415,8 +417,7 @@ def copy_src_tree(): CORE.relative_src_path("esphome.h"), ESPHOME_H_FORMAT.format(include_s) ) write_file_if_changed( - CORE.relative_src_path("esphome", "core", "version.h"), - VERSION_H_FORMAT.format(__version__), + CORE.relative_src_path("esphome", "core", "version.h"), generate_version_h() ) @@ -426,6 +427,15 @@ def generate_defines_h(): return DEFINES_H_FORMAT.format("\n".join(define_content_l)) +def generate_version_h(): + match = re.match(r"^(\d+)\.(\d+).(\d+)-?\w*$", __version__) + if not match: + raise EsphomeError(f"Could not parse version {__version__}.") + return VERSION_H_FORMAT.format( + __version__, match.group(1), match.group(2), match.group(3) + ) + + def write_cpp(code_s): path = CORE.relative_src_path("main.cpp") if os.path.isfile(path): From 2d7f8b3bdfc664478958af77270c27e90f845d4f Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 10:31:48 +0200 Subject: [PATCH 1389/1841] Install python requirements after apt ones for better caching (#2349) * Install python requirements after apt ones for better caching * Fix buildkit caching works differently --- docker/Dockerfile | 21 ++++++++++++++++----- docker/build.py | 10 +++++----- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index d3ee219de8..9c3e864e43 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -51,17 +51,17 @@ RUN \ && platformio settings set check_platforms_interval 1000000 \ && mkdir -p /piolibs + + +# ======================= docker-type image ======================= +FROM base AS docker + # First install requirements to leverage caching when requirements don't change COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / RUN \ pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ && /platformio_install_deps.py /platformio.ini - - -# ======================= docker-type image ======================= -FROM base AS docker - # Copy esphome and install COPY . /esphome RUN pip3 install --no-cache-dir -e /esphome @@ -104,6 +104,12 @@ ARG BUILD_VERSION=dev # Copy root filesystem COPY docker/hassio-rootfs/ / +# First install requirements to leverage caching when requirements don't change +COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / +RUN \ + pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ + && /platformio_install_deps.py /platformio.ini + # Copy esphome and install COPY . /esphome RUN pip3 install --no-cache-dir -e /esphome @@ -141,5 +147,10 @@ RUN \ /var/{cache,log}/* \ /var/lib/apt/lists/* +COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / +RUN \ + pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ + && /platformio_install_deps.py /platformio.ini + VOLUME ["/esphome"] WORKDIR /esphome diff --git a/docker/build.py b/docker/build.py index cd8dca3b87..1157d8287a 100755 --- a/docker/build.py +++ b/docker/build.py @@ -109,9 +109,9 @@ def main(): # 1. pull cache image params = DockerParams.for_type_arch(args.build_type, args.arch) cache_tag = { - CHANNEL_DEV: "dev", - CHANNEL_BETA: "beta", - CHANNEL_RELEASE: "latest", + CHANNEL_DEV: "cache-dev", + CHANNEL_BETA: "cache-beta", + CHANNEL_RELEASE: "cache-latest", }[channel] cache_img = f"ghcr.io/{params.build_to}:{cache_tag}" @@ -123,7 +123,7 @@ def main(): "docker", "buildx", "build", "--build-arg", f"BASEIMGTYPE={params.baseimgtype}", "--build-arg", f"BUILD_VERSION={args.tag}", - "--cache-from", cache_img, + "--cache-from", f"type=registry,ref={cache_img}", "--file", "docker/Dockerfile", "--platform", params.platform, "--target", params.target, @@ -131,7 +131,7 @@ def main(): for img in imgs: cmd += ["--tag", img] if args.push: - cmd.append("--push") + cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"] run_command(*cmd, ".") elif args.command == "manifest": From 1e8e471dec19ceafba1997b1d9663f7912f244a2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 20 Sep 2021 11:16:31 +0200 Subject: [PATCH 1390/1841] Introduce call_dump_config() indirection (#2325) --- esphome/components/mqtt/mqtt_component.cpp | 6 ++++++ esphome/components/mqtt/mqtt_component.h | 2 ++ esphome/core/application.cpp | 2 +- esphome/core/component.cpp | 3 ++- esphome/core/component.h | 4 ++++ 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 6e376d0fef..3ed9aafb42 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -186,6 +186,12 @@ void MQTTComponent::call_loop() { this->schedule_resend_state(); } } +void MQTTComponent::call_dump_config() { + if (this->is_internal()) + return; + + this->dump_config(); +} void MQTTComponent::schedule_resend_state() { this->resend_state_ = true; } std::string MQTTComponent::unique_id() { return ""; } bool MQTTComponent::is_connected_() const { return global_mqtt_client->is_connected(); } diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 83a0c06644..668162da5a 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -62,6 +62,8 @@ class MQTTComponent : public Component { void call_loop() override; + void call_dump_config() override; + /// Send discovery info the Home Assistant, override this. virtual void send_discovery(JsonObject &root, SendDiscoveryConfig &config) = 0; diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 5ab1d973f4..d59ad23a5e 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -105,7 +105,7 @@ void Application::loop() { #endif } - this->components_[this->dump_config_at_]->dump_config(); + this->components_[this->dump_config_at_]->call_dump_config(); this->dump_config_at_++; } } diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 7682c083a5..e3b32978cd 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -64,8 +64,9 @@ bool Component::cancel_timeout(const std::string &name) { // NOLINT } void Component::call_loop() { this->loop(); } - void Component::call_setup() { this->setup(); } +void Component::call_dump_config() { this->dump_config(); } + uint32_t Component::get_component_state() const { return this->component_state_; } void Component::call() { uint32_t state = this->component_state_ & COMPONENT_STATE_MASK; diff --git a/esphome/core/component.h b/esphome/core/component.h index ea87ebcdfe..2654504fe8 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -148,8 +148,12 @@ class Component { const char *get_component_source() const; protected: + friend class Application; + virtual void call_loop(); virtual void call_setup(); + virtual void call_dump_config(); + /** Set an interval function with a unique name. Empty name means no cancelling possible. * * This will call f every interval ms. Can be cancelled via CancelInterval(). From ac0d921413c3884752193fe568fa82853f0f99e9 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 11:47:51 +0200 Subject: [PATCH 1391/1841] ESP-IDF support and generic target platforms (#2303) * Socket refactor and SSL * esp-idf temp * Fixes * Echo component and noise * Add noise API transport support * Updates * ESP-IDF * Complete * Fixes * Fixes * Versions update * New i2c APIs * Complete i2c refactor * SPI migration * Revert ESP Preferences migration, too complex for now * OTA support * Remove echo again * Remove ssl again * GPIOFlags updates * Rename esphal and ICACHE_RAM_ATTR * Make ESP32 arduino compilable again * Fix GPIO flags * Complete pin registry refactor and fixes * Fixes to make test1 compile * Remove sdkconfig file * Ignore sdkconfig file * Fixes in reviewing * Make test2 compile * Make test4 compile * Make test5 compile * Run clang-format * Fix lint errors * Use esp-idf APIs instead of btStart * Another round of fixes * Start implementing ESP8266 * Make test3 compile * Guard esp8266 code * Lint * Reformat * Fixes * Fixes v2 * more fixes * ESP-IDF tidy target * Convert ARDUINO_ARCH_ESPxx * Update WiFiSignalSensor * Update time ifdefs * OTA needs millis from hal * RestartSwitch needs delay from hal * ESP-IDF Uart * Fix OTA blank password * Allow setting sdkconfig * Fix idf partitions and allow setting sdkconfig from yaml * Re-add read/write compat APIs and fix esp8266 uart * Fix esp8266 store log strings in flash * Fix ESP32 arduino preferences not initialized * Update ifdefs * Change how sdkconfig change is detected * Add checks to ci-custom and fix them * Run clang-format * Add esp-idf clang-tidy target and fix errors * Fixes from clang-tidy idf round 2 * Fixes from compiling tests with esp-idf * Run clang-format * Switch test5.yaml to esp-idf * Implement ESP8266 Preferences * Lint * Re-do PIO package version selection a bit * Fix arduinoespressif32 package version * Fix unit tests * Lint * Lint fixes * Fix readv/writev not defined * Fix graphing component * Re-add all old options from core/config.py Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +- .gitignore | 2 + CODEOWNERS | 3 + esphome/__main__.py | 2 +- esphome/codegen.py | 3 + esphome/components/a4988/a4988.h | 2 +- esphome/components/ac_dimmer/ac_dimmer.cpp | 47 +- esphome/components/ac_dimmer/ac_dimmer.h | 20 +- esphome/components/ac_dimmer/output.py | 25 +- esphome/components/adc/adc_sensor.cpp | 29 +- esphome/components/adc/adc_sensor.h | 14 +- esphome/components/adc/sensor.py | 37 +- esphome/components/ade7953/ade7953.cpp | 33 +- esphome/components/ade7953/ade7953.h | 75 +- esphome/components/ade7953/sensor.py | 5 +- esphome/components/ads1115/ads1115.cpp | 3 +- esphome/components/aht10/aht10.cpp | 44 +- .../airthings_ble/airthings_listener.cpp | 2 +- .../airthings_ble/airthings_listener.h | 3 +- .../airthings_wave_plus.cpp | 4 +- .../airthings_wave_plus/airthings_wave_plus.h | 4 +- .../components/airthings_wave_plus/sensor.py | 6 +- esphome/components/am2320/am2320.cpp | 3 +- esphome/components/am43/am43.cpp | 3 +- esphome/components/am43/am43.h | 2 +- esphome/components/am43/cover/am43_cover.cpp | 2 +- esphome/components/am43/cover/am43_cover.h | 2 +- esphome/components/anova/anova.cpp | 2 +- esphome/components/anova/anova.h | 2 +- esphome/components/apds9960/apds9960.cpp | 1 + esphome/components/api/api_connection.cpp | 5 +- esphome/components/api/api_frame_helper.cpp | 1 + esphome/components/api/api_pb2.cpp | 1 + esphome/components/api/api_server.cpp | 4 +- esphome/components/api/proto.h | 1 + esphome/components/as3935/as3935.h | 2 +- esphome/components/as3935_i2c/as3935_i2c.cpp | 8 +- esphome/components/async_tcp/__init__.py | 6 + .../atc_mithermometer/atc_mithermometer.cpp | 2 +- .../atc_mithermometer/atc_mithermometer.h | 2 +- esphome/components/b_parasite/b_parasite.cpp | 4 +- esphome/components/b_parasite/b_parasite.h | 4 +- .../bang_bang/bang_bang_climate.cpp | 3 +- esphome/components/bh1750/bh1750.cpp | 3 +- esphome/components/binary_sensor/automation.h | 1 + esphome/components/ble_client/automation.h | 2 +- esphome/components/ble_client/ble_client.cpp | 2 +- esphome/components/ble_client/ble_client.h | 2 +- .../components/ble_client/sensor/automation.h | 2 +- .../ble_client/sensor/ble_sensor.cpp | 2 +- .../components/ble_client/sensor/ble_sensor.h | 2 +- .../ble_client/switch/ble_switch.cpp | 2 +- .../components/ble_client/switch/ble_switch.h | 2 +- .../ble_presence/ble_presence_device.cpp | 2 +- .../ble_presence/ble_presence_device.h | 2 +- .../components/ble_rssi/ble_rssi_sensor.cpp | 2 +- esphome/components/ble_rssi/ble_rssi_sensor.h | 2 +- .../components/ble_scanner/ble_scanner.cpp | 2 +- esphome/components/ble_scanner/ble_scanner.h | 2 +- esphome/components/bme280/bme280.cpp | 2 +- esphome/components/bme680/bme680.cpp | 1 + .../components/bme680_bsec/bme680_bsec.cpp | 2 +- esphome/components/bmp280/bmp280.cpp | 2 +- esphome/components/captive_portal/__init__.py | 19 +- .../captive_portal/captive_portal.cpp | 14 +- .../captive_portal/captive_portal.h | 4 + esphome/components/ccs811/ccs811.cpp | 5 +- esphome/components/climate/climate.cpp | 16 +- esphome/components/climate_ir/climate_ir.cpp | 2 +- esphome/components/cover/cover.cpp | 2 +- .../components/ct_clamp/ct_clamp_sensor.cpp | 2 +- esphome/components/ct_clamp/ct_clamp_sensor.h | 2 +- .../components/dallas/dallas_component.cpp | 2 +- esphome/components/dallas/esp_one_wire.cpp | 38 +- esphome/components/dallas/esp_one_wire.h | 3 +- esphome/components/debug/debug_component.cpp | 32 +- esphome/components/deep_sleep/__init__.py | 2 +- .../deep_sleep/deep_sleep_component.cpp | 30 +- .../deep_sleep/deep_sleep_component.h | 16 +- esphome/components/demo/demo_sensor.h | 2 +- esphome/components/dht/dht.cpp | 6 +- esphome/components/dht/dht.h | 6 +- esphome/components/display/display_buffer.cpp | 23 +- esphome/components/display/display_buffer.h | 1 + esphome/components/dsmr/__init__.py | 19 +- esphome/components/dsmr/dsmr.cpp | 7 +- esphome/components/dsmr/dsmr.h | 4 + .../duty_cycle/duty_cycle_sensor.cpp | 6 +- .../components/duty_cycle/duty_cycle_sensor.h | 8 +- esphome/components/duty_cycle/sensor.py | 4 +- esphome/components/e131/__init__.py | 17 +- esphome/components/e131/e131.cpp | 8 +- esphome/components/e131/e131.h | 4 + .../e131/e131_addressable_light_effect.cpp | 4 + .../e131/e131_addressable_light_effect.h | 4 + esphome/components/e131/e131_packet.cpp | 14 +- esphome/components/endstop/endstop_cover.cpp | 1 + esphome/components/esp32/__init__.py | 379 ++++++++ esphome/{ => components/esp32}/boards.py | 225 ----- esphome/components/esp32/const.py | 21 + esphome/components/esp32/core.cpp | 89 ++ esphome/components/esp32/gpio.py | 201 ++++ esphome/components/esp32/gpio_arduino.cpp | 107 +++ esphome/components/esp32/gpio_arduino.h | 36 + esphome/components/esp32/gpio_idf.cpp | 49 + esphome/components/esp32/gpio_idf.h | 96 ++ esphome/components/esp32/preferences.cpp | 99 ++ esphome/components/esp32/preferences.h | 12 + esphome/components/esp32_ble/__init__.py | 9 +- esphome/components/esp32_ble/ble.cpp | 36 +- esphome/components/esp32_ble/ble.h | 2 +- .../components/esp32_ble/ble_advertising.cpp | 5 +- .../components/esp32_ble/ble_advertising.h | 2 +- esphome/components/esp32_ble/ble_uuid.cpp | 6 +- esphome/components/esp32_ble/ble_uuid.h | 4 +- esphome/components/esp32_ble/queue.h | 8 +- .../components/esp32_ble_beacon/__init__.py | 9 +- .../esp32_ble_beacon/esp32_ble_beacon.cpp | 33 +- .../esp32_ble_beacon/esp32_ble_beacon.h | 2 +- .../components/esp32_ble_server/__init__.py | 9 +- .../components/esp32_ble_server/ble_2901.cpp | 2 +- .../components/esp32_ble_server/ble_2901.h | 2 +- .../components/esp32_ble_server/ble_2902.cpp | 4 +- .../components/esp32_ble_server/ble_2902.h | 2 +- .../esp32_ble_server/ble_characteristic.cpp | 2 +- .../esp32_ble_server/ble_characteristic.h | 4 +- .../esp32_ble_server/ble_descriptor.cpp | 5 +- .../esp32_ble_server/ble_descriptor.h | 2 +- .../esp32_ble_server/ble_server.cpp | 2 +- .../components/esp32_ble_server/ble_server.h | 2 +- .../esp32_ble_server/ble_service.cpp | 2 +- .../components/esp32_ble_server/ble_service.h | 2 +- .../components/esp32_ble_tracker/__init__.py | 8 +- .../components/esp32_ble_tracker/automation.h | 2 +- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 41 +- .../esp32_ble_tracker/esp32_ble_tracker.h | 2 +- esphome/components/esp32_ble_tracker/queue.h | 7 +- esphome/components/esp32_camera/__init__.py | 31 +- .../components/esp32_camera/esp32_camera.cpp | 7 +- .../components/esp32_camera/esp32_camera.h | 4 +- esphome/components/esp32_dac/esp32_dac.cpp | 19 +- esphome/components/esp32_dac/esp32_dac.h | 8 +- esphome/components/esp32_dac/output.py | 4 +- esphome/components/esp32_hall/esp32_hall.cpp | 10 +- esphome/components/esp32_hall/esp32_hall.h | 2 +- esphome/components/esp32_hall/sensor.py | 3 +- esphome/components/esp32_improv/__init__.py | 5 +- .../esp32_improv/esp32_improv_component.cpp | 2 +- .../esp32_improv/esp32_improv_component.h | 2 +- esphome/components/esp32_touch/__init__.py | 3 +- .../components/esp32_touch/binary_sensor.py | 8 +- .../components/esp32_touch/esp32_touch.cpp | 5 +- esphome/components/esp32_touch/esp32_touch.h | 9 +- esphome/components/esp8266/__init__.py | 213 +++++ esphome/components/esp8266/boards.py | 266 ++++++ esphome/components/esp8266/const.py | 8 + esphome/components/esp8266/core.cpp | 37 + esphome/components/esp8266/gpio.cpp | 96 ++ esphome/components/esp8266/gpio.h | 38 + esphome/components/esp8266/gpio.py | 170 ++++ esphome/components/esp8266/preferences.cpp | 263 +++++ esphome/components/esp8266/preferences.h | 14 + .../components/esp8266_pwm/esp8266_pwm.cpp | 4 +- esphome/components/esp8266_pwm/esp8266_pwm.h | 8 +- esphome/components/esp8266_pwm/output.py | 3 +- esphome/components/ethernet/__init__.py | 20 +- .../ethernet/ethernet_component.cpp | 24 +- .../components/ethernet/ethernet_component.h | 21 +- .../exposure_notifications.cpp | 2 +- .../exposure_notifications.h | 2 +- esphome/components/ezo/ezo.cpp | 5 +- esphome/components/fan/fan_state.cpp | 2 +- .../components/fastled_base/fastled_light.cpp | 4 + .../components/fastled_base/fastled_light.h | 4 + esphome/components/fastled_clockless/light.py | 3 +- esphome/components/fastled_spi/light.py | 17 +- .../fingerprint_grow/fingerprint_grow.cpp | 2 +- esphome/components/globals/__init__.py | 12 +- .../components/globals/globals_component.h | 40 +- .../gpio/binary_sensor/gpio_binary_sensor.h | 2 +- .../gpio/output/gpio_binary_output.h | 2 +- esphome/components/gpio/switch/gpio_switch.h | 2 +- esphome/components/gps/__init__.py | 5 +- esphome/components/gps/gps.cpp | 4 + esphome/components/gps/gps.h | 4 + esphome/components/gps/time/gps_time.cpp | 4 + esphome/components/gps/time/gps_time.h | 4 + esphome/components/graph/graph.cpp | 30 +- esphome/components/hdc1080/hdc1080.cpp | 17 +- esphome/components/hlw8012/hlw8012.h | 10 +- esphome/components/hlw8012/sensor.py | 8 +- .../hm3301/abstract_aqi_calculator.h | 5 + esphome/components/hm3301/aqi_calculator.h | 4 + .../hm3301/aqi_calculator_factory.h | 4 + esphome/components/hm3301/caqi_calculator.h | 4 + esphome/components/hm3301/hm3301.cpp | 4 + esphome/components/hm3301/hm3301.h | 4 + esphome/components/hm3301/sensor.py | 1 + esphome/components/http_request/__init__.py | 50 +- .../components/http_request/http_request.cpp | 10 +- .../components/http_request/http_request.h | 10 +- esphome/components/htu21d/htu21d.cpp | 17 +- esphome/components/hx711/hx711.h | 2 +- esphome/components/i2c/__init__.py | 71 +- esphome/components/i2c/i2c.cpp | 291 +----- esphome/components/i2c/i2c.h | 284 ++---- esphome/components/i2c/i2c_bus.h | 46 + esphome/components/i2c/i2c_bus_arduino.cpp | 97 ++ esphome/components/i2c/i2c_bus_arduino.h | 37 + esphome/components/i2c/i2c_bus_esp_idf.cpp | 147 +++ esphome/components/i2c/i2c_bus_esp_idf.h | 41 + .../components/ili9341/ili9341_display.cpp | 5 +- esphome/components/improv/improv.cpp | 2 + esphome/components/improv/improv.h | 5 + esphome/components/ina219/ina219.cpp | 10 +- esphome/components/ina226/ina226.cpp | 10 +- esphome/components/ina3221/ina3221.cpp | 5 +- .../inkbird_ibsth1_mini.cpp | 6 +- .../inkbird_ibsth1_mini/inkbird_ibsth1_mini.h | 2 +- esphome/components/inkplate6/display.py | 5 +- esphome/components/inkplate6/inkplate.cpp | 162 ++-- esphome/components/inkplate6/inkplate.h | 67 +- .../integration/integration_sensor.cpp | 3 +- .../integration/integration_sensor.h | 1 + esphome/components/json/__init__.py | 6 + esphome/components/json/json_util.cpp | 4 + esphome/components/json/json_util.h | 4 + esphome/components/lcd_base/lcd_display.cpp | 1 + .../components/lcd_gpio/gpio_lcd_display.cpp | 11 +- .../components/lcd_gpio/gpio_lcd_display.h | 2 +- .../lcd_pcf8574/pcf8574_display.cpp | 1 + esphome/components/ledc/ledc_output.cpp | 107 ++- esphome/components/ledc/ledc_output.h | 8 +- esphome/components/ledc/output.py | 3 +- esphome/components/light/light_state.cpp | 2 +- esphome/components/light/light_transformer.h | 3 + esphome/components/logger/__init__.py | 2 +- esphome/components/logger/logger.cpp | 74 +- esphome/components/logger/logger.h | 27 +- esphome/components/max31856/max31856.cpp | 4 +- esphome/components/max31856/max31856.h | 4 +- esphome/components/max31865/max31865.cpp | 6 +- esphome/components/max31865/max31865.h | 6 +- esphome/components/max7219/max7219.cpp | 3 +- .../components/max7219digit/max7219digit.cpp | 3 +- .../components/max7219digit/max7219digit.h | 4 +- esphome/components/max7219digit/max7219font.h | 4 +- esphome/components/mcp23008/mcp23008.h | 2 +- esphome/components/mcp23016/__init__.py | 61 +- esphome/components/mcp23016/mcp23016.cpp | 27 +- esphome/components/mcp23016/mcp23016.h | 23 +- esphome/components/mcp23017/mcp23017.h | 2 +- esphome/components/mcp23s08/mcp23s08.h | 2 +- esphome/components/mcp23s17/mcp23s17.h | 2 +- .../mcp23x08_base/mcp23x08_base.cpp | 22 +- .../components/mcp23x08_base/mcp23x08_base.h | 4 +- .../mcp23x17_base/mcp23x17_base.cpp | 22 +- .../components/mcp23x17_base/mcp23x17_base.h | 4 +- esphome/components/mcp23xxx_base/__init__.py | 79 +- .../mcp23xxx_base/mcp23xxx_base.cpp | 15 +- .../components/mcp23xxx_base/mcp23xxx_base.h | 26 +- esphome/components/mcp3008/mcp3008.h | 2 +- esphome/components/mcp4725/mcp4725.cpp | 7 +- esphome/components/mcp9808/mcp9808.cpp | 10 +- esphome/components/mdns/__init__.py | 25 + esphome/components/mdns/mdns_component.cpp | 74 ++ esphome/components/mdns/mdns_component.h | 37 + .../components/mdns/mdns_esp32_arduino.cpp | 27 + esphome/components/mdns/mdns_esp8266.cpp | 32 + esphome/components/mdns/mdns_esp_idf.cpp | 52 + esphome/components/midea/adapter.cpp | 4 + esphome/components/midea/adapter.h | 5 + esphome/components/midea/air_conditioner.cpp | 4 + esphome/components/midea/air_conditioner.h | 5 + esphome/components/midea/appliance_base.h | 14 +- esphome/components/midea/automations.h | 5 + esphome/components/midea/climate.py | 3 +- esphome/components/midea/midea_ir.h | 3 + esphome/components/mpr121/mpr121.cpp | 1 + esphome/components/mqtt/__init__.py | 1 + .../components/mqtt/custom_mqtt_device.cpp | 5 + esphome/components/mqtt/custom_mqtt_device.h | 5 + .../components/mqtt/mqtt_binary_sensor.cpp | 2 + esphome/components/mqtt/mqtt_binary_sensor.h | 3 +- esphome/components/mqtt/mqtt_client.cpp | 44 +- esphome/components/mqtt/mqtt_client.h | 12 +- esphome/components/mqtt/mqtt_climate.cpp | 4 +- esphome/components/mqtt/mqtt_climate.h | 2 + esphome/components/mqtt/mqtt_component.cpp | 5 + esphome/components/mqtt/mqtt_component.h | 6 + esphome/components/mqtt/mqtt_cover.cpp | 2 + esphome/components/mqtt/mqtt_cover.h | 2 + esphome/components/mqtt/mqtt_fan.cpp | 2 + esphome/components/mqtt/mqtt_fan.h | 2 + esphome/components/mqtt/mqtt_light.cpp | 2 + esphome/components/mqtt/mqtt_light.h | 2 + esphome/components/mqtt/mqtt_number.cpp | 2 + esphome/components/mqtt/mqtt_number.h | 2 + esphome/components/mqtt/mqtt_select.cpp | 2 + esphome/components/mqtt/mqtt_select.h | 2 + esphome/components/mqtt/mqtt_sensor.cpp | 2 + esphome/components/mqtt/mqtt_sensor.h | 2 + esphome/components/mqtt/mqtt_switch.cpp | 2 + esphome/components/mqtt/mqtt_switch.h | 2 + esphome/components/mqtt/mqtt_text_sensor.cpp | 2 + esphome/components/mqtt/mqtt_text_sensor.h | 2 + .../sensor/mqtt_subscribe_sensor.cpp | 5 + .../sensor/mqtt_subscribe_sensor.h | 6 + .../mqtt_subscribe_text_sensor.cpp | 4 + .../text_sensor/mqtt_subscribe_text_sensor.h | 6 + esphome/components/ms5611/ms5611.cpp | 1 + esphome/components/my9231/my9231.h | 3 +- esphome/components/neopixelbus/light.py | 7 +- .../neopixelbus/neopixelbus_light.h | 6 +- esphome/components/network/__init__.py | 12 +- esphome/components/network/ip_address.h | 45 + esphome/components/network/util.cpp | 54 ++ esphome/components/network/util.h | 16 + esphome/components/nextion/display.py | 4 +- esphome/components/nextion/nextion.h | 19 +- esphome/components/nextion/nextion_upload.cpp | 32 +- .../nextion/sensor/nextion_sensor.cpp | 2 +- esphome/components/ntc/ntc.cpp | 2 +- esphome/components/number/automation.cpp | 10 +- esphome/components/number/automation.h | 4 +- esphome/components/number/number.cpp | 2 +- esphome/components/ota/__init__.py | 18 +- esphome/components/ota/ota_component.cpp | 432 ++++++--- esphome/components/ota/ota_component.h | 27 +- esphome/components/pca9685/pca9685_output.h | 4 +- esphome/components/pcf8574/__init__.py | 61 +- esphome/components/pcf8574/pcf8574.cpp | 36 +- esphome/components/pcf8574/pcf8574.h | 23 +- esphome/components/pid/pid_autotuner.cpp | 2 +- esphome/components/pid/pid_climate.cpp | 2 +- esphome/components/pid/pid_controller.h | 6 +- esphome/components/pipsolar/pipsolar.cpp | 2 +- esphome/components/pipsolar/pipsolar.h | 2 +- esphome/components/pmsa003i/pmsa003i.cpp | 1 + esphome/components/pn532/pn532.cpp | 1 + esphome/components/pn532_i2c/pn532_i2c.cpp | 3 +- .../components/power_supply/power_supply.h | 2 +- .../prometheus/prometheus_handler.cpp | 8 +- .../prometheus/prometheus_handler.h | 4 + .../pulse_counter/pulse_counter_sensor.cpp | 14 +- .../pulse_counter/pulse_counter_sensor.h | 24 +- .../pulse_meter/pulse_meter_sensor.cpp | 6 +- .../pulse_meter/pulse_meter_sensor.h | 8 +- .../components/pulse_width/pulse_width.cpp | 4 +- esphome/components/pulse_width/pulse_width.h | 12 +- esphome/components/pulse_width/sensor.py | 4 +- .../pvvx_mithermometer/pvvx_mithermometer.cpp | 2 +- .../pvvx_mithermometer/pvvx_mithermometer.h | 2 +- esphome/components/qmc5883l/qmc5883l.cpp | 7 +- esphome/components/rc522/rc522.cpp | 10 +- esphome/components/rc522/rc522.h | 2 +- esphome/components/rc522_i2c/rc522_i2c.cpp | 5 +- esphome/components/remote_base/__init__.py | 7 +- .../components/remote_base/midea_protocol.h | 7 +- .../components/remote_base/remote_base.cpp | 2 +- esphome/components/remote_base/remote_base.h | 14 +- .../remote_base/samsung_protocol.cpp | 1 + .../remote_base/toshiba_ac_protocol.cpp | 1 + .../components/remote_receiver/__init__.py | 4 +- .../remote_receiver/remote_receiver.h | 16 +- .../remote_receiver/remote_receiver_esp32.cpp | 2 +- .../remote_receiver_esp8266.cpp | 10 +- .../remote_transmitter/remote_transmitter.h | 8 +- .../remote_transmitter_esp32.cpp | 2 +- .../remote_transmitter_esp8266.cpp | 2 +- .../resistance/resistance_sensor.cpp | 2 +- esphome/components/restart/restart_switch.cpp | 1 + .../rotary_encoder/rotary_encoder.cpp | 23 +- .../rotary_encoder/rotary_encoder.h | 14 +- esphome/components/rotary_encoder/sensor.py | 8 +- esphome/components/ruuvi_ble/ruuvi_ble.cpp | 2 +- esphome/components/ruuvi_ble/ruuvi_ble.h | 2 +- esphome/components/ruuvitag/ruuvitag.cpp | 2 +- esphome/components/ruuvitag/ruuvitag.h | 2 +- esphome/components/scd30/scd30.cpp | 17 +- esphome/components/sdp3x/sdp3x.cpp | 14 +- esphome/components/sensor/automation.h | 14 +- esphome/components/sensor/filter.cpp | 19 +- esphome/components/servo/servo.cpp | 1 + esphome/components/servo/servo.h | 2 +- esphome/components/sgp30/sgp30.cpp | 9 +- esphome/components/sgp40/sgp40.cpp | 15 +- esphome/components/sht3xd/sht3xd.cpp | 2 +- esphome/components/sht4x/sht4x.cpp | 4 +- esphome/components/shtcx/shtcx.cpp | 3 +- .../components/shutdown/shutdown_switch.cpp | 11 +- esphome/components/slow_pwm/slow_pwm_output.h | 2 +- esphome/components/sm16716/sm16716.h | 3 +- esphome/components/sm2135/sm2135.h | 3 +- esphome/components/sn74hc595/__init__.py | 33 +- esphome/components/sn74hc595/sn74hc595.cpp | 26 +- esphome/components/sn74hc595/sn74hc595.h | 16 +- esphome/components/sntp/sntp_component.cpp | 8 +- .../components/socket/bsd_sockets_impl.cpp | 13 +- esphome/components/socket/headers.h | 8 +- .../components/socket/lwip_raw_tcp_impl.cpp | 3 +- esphome/components/spi/spi.cpp | 73 +- esphome/components/spi/spi.h | 43 +- esphome/components/sps30/sps30.cpp | 2 +- .../components/ssd1306_base/ssd1306_base.h | 2 +- .../components/ssd1306_i2c/ssd1306_i2c.cpp | 4 +- .../components/ssd1322_base/ssd1322_base.h | 2 +- .../components/ssd1325_base/ssd1325_base.h | 2 +- .../components/ssd1327_base/ssd1327_base.h | 2 +- .../components/ssd1327_i2c/ssd1327_i2c.cpp | 4 +- .../components/ssd1331_base/ssd1331_base.h | 2 +- .../components/ssd1351_base/ssd1351_base.h | 2 +- esphome/components/st7735/st7735.cpp | 17 +- .../status/status_binary_sensor.cpp | 4 +- .../status_led/light/status_led_light.h | 2 +- esphome/components/status_led/status_led.h | 2 +- esphome/components/stepper/stepper.cpp | 1 + esphome/components/sts3x/sts3x.cpp | 2 +- esphome/components/sun/sun.h | 4 +- esphome/components/switch/switch.cpp | 2 +- esphome/components/sx1509/__init__.py | 68 +- .../sx1509/output/sx1509_float_output.cpp | 3 +- esphome/components/sx1509/sx1509.cpp | 51 +- esphome/components/sx1509/sx1509.h | 22 +- esphome/components/sx1509/sx1509_gpio_pin.cpp | 13 +- esphome/components/sx1509/sx1509_gpio_pin.h | 13 +- esphome/components/tca9548a/__init__.py | 37 +- esphome/components/tca9548a/tca9548a.cpp | 43 +- esphome/components/tca9548a/tca9548a.h | 24 +- esphome/components/tcs34725/tcs34725.cpp | 1 + .../template/number/template_number.cpp | 4 +- .../template/select/template_select.cpp | 2 +- .../template/sensor/template_sensor.cpp | 3 +- .../thermostat/thermostat_climate.cpp | 14 +- esphome/components/time/automation.cpp | 1 + esphome/components/time/real_time_clock.cpp | 2 +- .../time_based/time_based_cover.cpp | 1 + .../components/tlc59208f/tlc59208f_output.cpp | 2 +- .../components/tlc59208f/tlc59208f_output.h | 1 + esphome/components/tlc5947/tlc5947.h | 3 +- esphome/components/tm1637/tm1637.cpp | 29 +- esphome/components/tm1637/tm1637.h | 2 +- esphome/components/tm1651/__init__.py | 15 +- esphome/components/tm1651/tm1651.cpp | 4 + esphome/components/tm1651/tm1651.h | 14 +- esphome/components/tmp102/tmp102.cpp | 9 +- .../components/tof10120/tof10120_sensor.cpp | 8 +- esphome/components/toshiba/toshiba.cpp | 2 +- .../total_daily_energy/total_daily_energy.cpp | 4 +- .../total_daily_energy/total_daily_energy.h | 1 + esphome/components/tsl2591/tsl2591.cpp | 3 +- esphome/components/ttp229_bsf/ttp229_bsf.h | 2 +- esphome/components/ttp229_lsf/ttp229_lsf.cpp | 6 +- .../components/tuya/climate/tuya_climate.cpp | 2 +- esphome/components/tuya/tuya.cpp | 5 +- esphome/components/tx20/sensor.py | 4 +- esphome/components/tx20/tx20.cpp | 8 +- esphome/components/tx20/tx20.h | 8 +- esphome/components/uart/__init__.py | 45 +- esphome/components/uart/uart.cpp | 50 +- esphome/components/uart/uart.h | 147 +-- esphome/components/uart/uart_component.cpp | 24 + esphome/components/uart/uart_component.h | 67 ++ ...2.cpp => uart_component_esp32_arduino.cpp} | 103 +- .../uart/uart_component_esp32_arduino.h | 40 + ...esp8266.cpp => uart_component_esp8266.cpp} | 163 ++-- .../components/uart/uart_component_esp8266.h | 79 ++ .../uart/uart_component_esp_idf.cpp | 201 ++++ .../components/uart/uart_component_esp_idf.h | 39 + esphome/components/uln2003/uln2003.h | 2 +- .../ultrasonic/ultrasonic_sensor.cpp | 20 +- .../components/ultrasonic/ultrasonic_sensor.h | 7 +- esphome/components/uptime/uptime_sensor.cpp | 1 + esphome/components/vl53l0x/vl53l0x_sensor.cpp | 6 +- esphome/components/vl53l0x/vl53l0x_sensor.h | 2 +- esphome/components/web_server/web_server.cpp | 7 +- esphome/components/web_server/web_server.h | 4 + .../web_server_base/web_server_base.cpp | 12 +- .../web_server_base/web_server_base.h | 4 + esphome/components/wifi/__init__.py | 23 +- esphome/components/wifi/wifi_component.cpp | 81 +- esphome/components/wifi/wifi_component.h | 57 +- ...2.cpp => wifi_component_esp32_arduino.cpp} | 80 +- .../wifi/wifi_component_esp8266.cpp | 69 +- .../wifi/wifi_component_esp_idf.cpp | 901 ++++++++++++++++++ .../wifi_info/wifi_info_text_sensor.h | 18 +- .../wifi_signal/wifi_signal_sensor.h | 2 +- esphome/components/wled/__init__.py | 2 +- esphome/components/wled/wled_light_effect.cpp | 8 +- esphome/components/wled/wled_light_effect.h | 4 + esphome/components/xiaomi_ble/xiaomi_ble.cpp | 2 +- esphome/components/xiaomi_ble/xiaomi_ble.h | 2 +- .../components/xiaomi_cgd1/xiaomi_cgd1.cpp | 2 +- esphome/components/xiaomi_cgd1/xiaomi_cgd1.h | 2 +- .../components/xiaomi_cgdk2/xiaomi_cgdk2.cpp | 2 +- .../components/xiaomi_cgdk2/xiaomi_cgdk2.h | 2 +- .../components/xiaomi_cgg1/xiaomi_cgg1.cpp | 2 +- esphome/components/xiaomi_cgg1/xiaomi_cgg1.h | 2 +- .../components/xiaomi_cgpr1/xiaomi_cgpr1.cpp | 2 +- .../components/xiaomi_cgpr1/xiaomi_cgpr1.h | 2 +- .../xiaomi_gcls002/xiaomi_gcls002.cpp | 2 +- .../xiaomi_gcls002/xiaomi_gcls002.h | 2 +- .../xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp | 2 +- .../xiaomi_hhccjcy01/xiaomi_hhccjcy01.h | 2 +- .../xiaomi_hhccpot002/xiaomi_hhccpot002.cpp | 2 +- .../xiaomi_hhccpot002/xiaomi_hhccpot002.h | 2 +- .../xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp | 2 +- .../xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.h | 2 +- .../xiaomi_lywsd02/xiaomi_lywsd02.cpp | 2 +- .../xiaomi_lywsd02/xiaomi_lywsd02.h | 2 +- .../xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp | 2 +- .../xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h | 2 +- .../xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp | 2 +- .../xiaomi_lywsdcgq/xiaomi_lywsdcgq.h | 2 +- .../xiaomi_mhoc401/xiaomi_mhoc401.cpp | 2 +- .../xiaomi_mhoc401/xiaomi_mhoc401.h | 2 +- .../xiaomi_miscale/xiaomi_miscale.cpp | 2 +- .../xiaomi_miscale/xiaomi_miscale.h | 2 +- .../xiaomi_miscale2/xiaomi_miscale2.cpp | 2 +- .../xiaomi_miscale2/xiaomi_miscale2.h | 2 +- .../xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp | 2 +- .../xiaomi_mjyd02yla/xiaomi_mjyd02yla.h | 2 +- .../xiaomi_mue4094rt/xiaomi_mue4094rt.cpp | 2 +- .../xiaomi_mue4094rt/xiaomi_mue4094rt.h | 2 +- .../xiaomi_wx08zm/xiaomi_wx08zm.cpp | 2 +- .../components/xiaomi_wx08zm/xiaomi_wx08zm.h | 2 +- esphome/components/zyaura/sensor.py | 8 +- esphome/components/zyaura/zyaura.cpp | 14 +- esphome/components/zyaura/zyaura.h | 16 +- esphome/config.py | 724 ++++++++------ esphome/config_validation.py | 127 ++- esphome/const.py | 32 +- esphome/core/__init__.py | 82 +- esphome/core/application.cpp | 21 +- esphome/core/application.h | 1 - esphome/core/application_esp32.cpp | 21 - esphome/core/application_esp8266.cpp | 14 - esphome/core/component.cpp | 4 +- esphome/core/component.h | 2 +- esphome/core/config.py | 229 ++--- esphome/core/defines.h | 43 +- esphome/core/esphal.cpp | 303 ------ esphome/core/esphal.h | 128 --- esphome/core/gpio.h | 98 ++ esphome/core/hal.h | 47 + esphome/core/helpers.cpp | 52 +- esphome/core/helpers.h | 12 +- esphome/core/log.cpp | 2 +- esphome/core/log.h | 12 +- esphome/core/macros.h | 4 +- esphome/core/preferences.cpp | 303 ------ esphome/core/preferences.h | 127 +-- esphome/core/scheduler.cpp | 3 +- esphome/core/util.cpp | 105 -- esphome/core/util.h | 17 - esphome/cpp_generator.py | 4 + esphome/cpp_helpers.py | 15 +- esphome/cpp_types.py | 4 +- esphome/dashboard/dashboard.py | 10 +- esphome/final_validate.py | 24 - esphome/helpers.py | 12 +- esphome/loader.py | 47 +- esphome/pins.py | 443 +++------ esphome/platformio_api.py | 4 +- esphome/storage_json.py | 31 +- esphome/wizard.py | 16 +- esphome/writer.py | 114 +-- platformio.ini | 77 +- requirements.txt | 4 + script/ci-custom.py | 27 +- script/clang-tidy | 9 +- script/helpers.py | 25 +- .../binary_sensor/test_binary_sensor.py | 2 +- tests/dummy_main.cpp | 6 - tests/test1.yaml | 72 +- tests/test2.yaml | 6 +- tests/test3.yaml | 2 +- tests/test4.yaml | 19 +- tests/test5.yaml | 24 +- tests/unit_tests/test_core.py | 4 +- tests/unit_tests/test_cpp_helpers.py | 10 - tests/unit_tests/test_pins.py | 346 ------- tests/unit_tests/test_wizard.py | 2 +- 583 files changed, 9008 insertions(+), 5420 deletions(-) create mode 100644 esphome/components/esp32/__init__.py rename esphome/{ => components/esp32}/boards.py (77%) create mode 100644 esphome/components/esp32/const.py create mode 100644 esphome/components/esp32/core.cpp create mode 100644 esphome/components/esp32/gpio.py create mode 100644 esphome/components/esp32/gpio_arduino.cpp create mode 100644 esphome/components/esp32/gpio_arduino.h create mode 100644 esphome/components/esp32/gpio_idf.cpp create mode 100644 esphome/components/esp32/gpio_idf.h create mode 100644 esphome/components/esp32/preferences.cpp create mode 100644 esphome/components/esp32/preferences.h create mode 100644 esphome/components/esp8266/__init__.py create mode 100644 esphome/components/esp8266/boards.py create mode 100644 esphome/components/esp8266/const.py create mode 100644 esphome/components/esp8266/core.cpp create mode 100644 esphome/components/esp8266/gpio.cpp create mode 100644 esphome/components/esp8266/gpio.h create mode 100644 esphome/components/esp8266/gpio.py create mode 100644 esphome/components/esp8266/preferences.cpp create mode 100644 esphome/components/esp8266/preferences.h create mode 100644 esphome/components/i2c/i2c_bus.h create mode 100644 esphome/components/i2c/i2c_bus_arduino.cpp create mode 100644 esphome/components/i2c/i2c_bus_arduino.h create mode 100644 esphome/components/i2c/i2c_bus_esp_idf.cpp create mode 100644 esphome/components/i2c/i2c_bus_esp_idf.h create mode 100644 esphome/components/mdns/__init__.py create mode 100644 esphome/components/mdns/mdns_component.cpp create mode 100644 esphome/components/mdns/mdns_component.h create mode 100644 esphome/components/mdns/mdns_esp32_arduino.cpp create mode 100644 esphome/components/mdns/mdns_esp8266.cpp create mode 100644 esphome/components/mdns/mdns_esp_idf.cpp create mode 100644 esphome/components/network/ip_address.h create mode 100644 esphome/components/network/util.cpp create mode 100644 esphome/components/network/util.h create mode 100644 esphome/components/uart/uart_component.cpp create mode 100644 esphome/components/uart/uart_component.h rename esphome/components/uart/{uart_esp32.cpp => uart_component_esp32_arduino.cpp} (64%) create mode 100644 esphome/components/uart/uart_component_esp32_arduino.h rename esphome/components/uart/{uart_esp8266.cpp => uart_component_esp8266.cpp} (60%) create mode 100644 esphome/components/uart/uart_component_esp8266.h create mode 100644 esphome/components/uart/uart_component_esp_idf.cpp create mode 100644 esphome/components/uart/uart_component_esp_idf.h rename esphome/components/wifi/{wifi_component_esp32.cpp => wifi_component_esp32_arduino.cpp} (89%) create mode 100644 esphome/components/wifi/wifi_component_esp_idf.cpp delete mode 100644 esphome/core/application_esp32.cpp delete mode 100644 esphome/core/application_esp8266.cpp delete mode 100644 esphome/core/esphal.cpp delete mode 100644 esphome/core/esphal.h create mode 100644 esphome/core/gpio.h create mode 100644 esphome/core/hal.h delete mode 100644 esphome/core/preferences.cpp delete mode 100644 tests/unit_tests/test_pins.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 875db0f9fd..c2c434a9b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: name: Run script/clang-format - id: clang-tidy name: Run script/clang-tidy for ESP8266 - options: --environment esp8266-tidy --grep ARDUINO_ARCH_ESP8266 + options: --environment esp8266-tidy --grep USE_ESP8266 pio_cache_key: tidyesp8266 - id: clang-tidy name: Run script/clang-tidy for ESP32 1/4 @@ -64,6 +64,10 @@ jobs: name: Run script/clang-tidy for ESP32 4/4 options: --environment esp32-tidy --split-num 4 --split-at 4 pio_cache_key: tidyesp32 + - id: clang-tidy + name: Run script/clang-tidy for ESP32 esp-idf + options: --environment esp32-idf-tidy --grep USE_ESP_IDF + pio_cache_key: tidyesp32-idf steps: - uses: actions/checkout@v2 diff --git a/.gitignore b/.gitignore index cd7822bded..c52909f7c9 100644 --- a/.gitignore +++ b/.gitignore @@ -124,3 +124,5 @@ tests/.esphome/ /.temp-clang-tidy.cpp /.temp/ .pio/ + +sdkconfig.* diff --git a/CODEOWNERS b/CODEOWNERS index ab3a0815ce..385ec4d892 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -44,9 +44,11 @@ esphome/components/dfplayer/* @glmnet esphome/components/dht/* @OttoWinter esphome/components/ds1307/* @badbadc0ffee esphome/components/dsmr/* @glmnet @zuidwijk +esphome/components/esp32/* @esphome/core esphome/components/esp32_ble/* @jesserockz esphome/components/esp32_ble_server/* @jesserockz esphome/components/esp32_improv/* @jesserockz +esphome/components/esp8266/* @esphome/core esphome/components/exposure_notifications/* @OttoWinter esphome/components/ezo/* @ssieb esphome/components/fastled_base/* @OttoWinter @@ -81,6 +83,7 @@ esphome/components/mcp23x17_base/* @jesserockz esphome/components/mcp23xxx_base/* @jesserockz esphome/components/mcp2515/* @danielschramm @mvturnho esphome/components/mcp9808/* @k7hpn +esphome/components/mdns/* @esphome/core esphome/components/midea/* @dudanov esphome/components/mitsubishi/* @RubyBailey esphome/components/network/* @esphome/core diff --git a/esphome/__main__.py b/esphome/__main__.py index ba1019869d..6bc7e065ce 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -245,7 +245,7 @@ def upload_program(config, args, host): ota_conf = config[CONF_OTA] remote_port = ota_conf[CONF_PORT] - password = ota_conf[CONF_PASSWORD] + password = ota_conf.get(CONF_PASSWORD, "") return espota2.run_ota(host, remote_port, password, CORE.firmware_bin) diff --git a/esphome/codegen.py b/esphome/codegen.py index c05cc5efca..1b38fd9ed2 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -30,6 +30,7 @@ from esphome.cpp_generator import ( # noqa add_library, add_build_flag, add_define, + add_platformio_option, get_variable, get_variable_with_full_id, process_lambda, @@ -78,4 +79,6 @@ from esphome.cpp_types import ( # noqa JsonObjectConstRef, Controller, GPIOPin, + InternalGPIOPin, + gpio_Flags, ) diff --git a/esphome/components/a4988/a4988.h b/esphome/components/a4988/a4988.h index 5be0f3ce69..0fe7891110 100644 --- a/esphome/components/a4988/a4988.h +++ b/esphome/components/a4988/a4988.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/stepper/stepper.h" namespace esphome { diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index e86703bfe1..a7f1e6f3a9 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -1,10 +1,16 @@ +#ifdef USE_ARDUINO + #include "ac_dimmer.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include #endif +#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#include +#endif namespace esphome { namespace ac_dimmer { @@ -25,7 +31,7 @@ static const uint32_t GATE_ENABLE_TIME = 50; /// Function called from timer interrupt /// Input is current time in microseconds (micros()) /// Returns when next "event" is expected in µs, or 0 if no such event known. -uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) { +uint32_t IRAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) { // If no ZC signal received yet. if (this->crossed_zero_at == 0) return 0; @@ -37,13 +43,13 @@ uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) { if (this->enable_time_us != 0 && time_since_zc >= this->enable_time_us) { this->enable_time_us = 0; - this->gate_pin->digital_write(true); + this->gate_pin.digital_write(true); // Prevent too short pulses - this->disable_time_us = max(this->disable_time_us, time_since_zc + GATE_ENABLE_TIME); + this->disable_time_us = std::max(this->disable_time_us, time_since_zc + GATE_ENABLE_TIME); } if (this->disable_time_us != 0 && time_since_zc >= this->disable_time_us) { this->disable_time_us = 0; - this->gate_pin->digital_write(false); + this->gate_pin.digital_write(false); } if (time_since_zc < this->enable_time_us) @@ -63,7 +69,7 @@ uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) { } /// Run timer interrupt code and return in how many µs the next event is expected -uint32_t ICACHE_RAM_ATTR HOT timer_interrupt() { +uint32_t IRAM_ATTR HOT timer_interrupt() { // run at least with 1kHz uint32_t min_dt_us = 1000; uint32_t now = micros(); @@ -80,7 +86,7 @@ uint32_t ICACHE_RAM_ATTR HOT timer_interrupt() { } /// GPIO interrupt routine, called when ZC pin triggers -void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() { +void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() { uint32_t prev_crossed = this->crossed_zero_at; // 50Hz mains frequency should give a half cycle of 10ms a 60Hz will give 8.33ms @@ -97,7 +103,7 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() { if (this->value == 65535) { // fully on, enable output immediately - this->gate_pin->digital_write(true); + this->gate_pin.digital_write(true); } else if (this->init_cycle) { // send a full cycle this->init_cycle = false; @@ -105,29 +111,29 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() { this->disable_time_us = cycle_time_us; } else if (this->value == 0) { // fully off, disable output immediately - this->gate_pin->digital_write(false); + this->gate_pin.digital_write(false); } else { if (this->method == DIM_METHOD_TRAILING) { this->enable_time_us = 1; // cannot be 0 - this->disable_time_us = max((uint32_t) 10, this->value * this->cycle_time_us / 65535); + this->disable_time_us = std::max((uint32_t) 10, this->value * this->cycle_time_us / 65535); } else { // calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic // also take into account min_power auto min_us = this->cycle_time_us * this->min_power / 1000; - this->enable_time_us = max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535); + this->enable_time_us = std::max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535); if (this->method == DIM_METHOD_LEADING_PULSE) { // Minimum pulse time should be enough for the triac to trigger when it is close to the ZC zone // this is for brightness near 99% - this->disable_time_us = max(this->enable_time_us + GATE_ENABLE_TIME, (uint32_t) cycle_time_us / 10); + this->disable_time_us = std::max(this->enable_time_us + GATE_ENABLE_TIME, (uint32_t) cycle_time_us / 10); } else { - this->gate_pin->digital_write(false); + this->gate_pin.digital_write(false); this->disable_time_us = this->cycle_time_us; } } } } -void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) { +void IRAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) { // Attaching pin interrupts on the same pin will override the previous interrupt // However, the user expects that multiple dimmers sharing the same ZC pin will work. // We solve this in a bit of a hacky way: On each pin interrupt, we check all dimmers @@ -141,11 +147,11 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store } } -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 // ESP32 implementation, uses basically the same code but needs to wrap // timer_interrupt() function to auto-reschedule static hw_timer_t *dimmer_timer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); } +void IRAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); } #endif void AcDimmer::setup() { @@ -174,15 +180,16 @@ void AcDimmer::setup() { if (setup_zero_cross_pin) { this->zero_cross_pin_->setup(); this->store_.zero_cross_pin = this->zero_cross_pin_->to_isr(); - this->zero_cross_pin_->attach_interrupt(&AcDimmerDataStore::s_gpio_intr, &this->store_, FALLING); + this->zero_cross_pin_->attach_interrupt(&AcDimmerDataStore::s_gpio_intr, &this->store_, + gpio::INTERRUPT_FALLING_EDGE); } -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 // Uses ESP8266 waveform (soft PWM) class // PWM and AcDimmer can even run at the same time this way setTimer1Callback(&timer_interrupt); #endif -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 // 80 Divider -> 1 count=1µs dimmer_timer = timerBegin(0, 80, true); timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr, true); @@ -218,3 +225,5 @@ void AcDimmer::dump_config() { } // namespace ac_dimmer } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/ac_dimmer/ac_dimmer.h b/esphome/components/ac_dimmer/ac_dimmer.h index 00da061cfd..fd1bbc28db 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.h +++ b/esphome/components/ac_dimmer/ac_dimmer.h @@ -1,7 +1,9 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/output/float_output.h" namespace esphome { @@ -11,11 +13,11 @@ enum DimMethod { DIM_METHOD_LEADING_PULSE = 0, DIM_METHOD_LEADING, DIM_METHOD_TR struct AcDimmerDataStore { /// Zero-cross pin - ISRInternalGPIOPin *zero_cross_pin; + ISRInternalGPIOPin zero_cross_pin; /// Zero-cross pin number - used to share ZC pin across multiple dimmers uint8_t zero_cross_pin_number; /// Output pin to write to - ISRInternalGPIOPin *gate_pin; + ISRInternalGPIOPin gate_pin; /// Value of the dimmer - 0 to 65535. uint16_t value; /// Minimum power for activation @@ -37,7 +39,7 @@ struct AcDimmerDataStore { void gpio_intr(); static void s_gpio_intr(AcDimmerDataStore *store); -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 static void s_timer_intr(); #endif }; @@ -47,16 +49,16 @@ class AcDimmer : public output::FloatOutput, public Component { void setup() override; void dump_config() override; - void set_gate_pin(GPIOPin *gate_pin) { gate_pin_ = gate_pin; } - void set_zero_cross_pin(GPIOPin *zero_cross_pin) { zero_cross_pin_ = zero_cross_pin; } + void set_gate_pin(InternalGPIOPin *gate_pin) { gate_pin_ = gate_pin; } + void set_zero_cross_pin(InternalGPIOPin *zero_cross_pin) { zero_cross_pin_ = zero_cross_pin; } void set_init_with_half_cycle(bool init_with_half_cycle) { init_with_half_cycle_ = init_with_half_cycle; } void set_method(DimMethod method) { method_ = method; } protected: void write_state(float state) override; - GPIOPin *gate_pin_; - GPIOPin *zero_cross_pin_; + InternalGPIOPin *gate_pin_; + InternalGPIOPin *zero_cross_pin_; AcDimmerDataStore store_; bool init_with_half_cycle_; DimMethod method_; @@ -64,3 +66,5 @@ class AcDimmer : public output::FloatOutput, public Component { } // namespace ac_dimmer } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/ac_dimmer/output.py b/esphome/components/ac_dimmer/output.py index 2c37d325eb..c39fc382b6 100644 --- a/esphome/components/ac_dimmer/output.py +++ b/esphome/components/ac_dimmer/output.py @@ -19,17 +19,20 @@ DIM_METHODS = { CONF_GATE_PIN = "gate_pin" CONF_ZERO_CROSS_PIN = "zero_cross_pin" CONF_INIT_WITH_HALF_CYCLE = "init_with_half_cycle" -CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( - { - cv.Required(CONF_ID): cv.declare_id(AcDimmer), - cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema, - cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema, - cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean, - cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum( - DIM_METHODS, upper=True, space="_" - ), - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.All( + output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(AcDimmer), + cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean, + cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum( + DIM_METHODS, upper=True, space="_" + ), + } + ).extend(cv.COMPONENT_SCHEMA), + cv.only_with_arduino, +) async def to_code(config): diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 4106a0c49b..9a05c1d66b 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -2,6 +2,7 @@ #include "esphome/core/log.h" #ifdef USE_ADC_SENSOR_VCC +#include ADC_MODE(ADC_VCC) #endif @@ -10,7 +11,7 @@ namespace adc { static const char *const TAG = "adc"; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } inline adc1_channel_t gpio_to_adc1(uint8_t pin) { @@ -57,28 +58,28 @@ inline adc1_channel_t gpio_to_adc1(uint8_t pin) { void ADCSensor::setup() { ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); #ifndef USE_ADC_SENSOR_VCC - GPIOPin(this->pin_, INPUT).setup(); + pin_->setup(); #endif -#ifdef ARDUINO_ARCH_ESP32 - adc1_config_channel_atten(gpio_to_adc1(pin_), attenuation_); +#ifdef USE_ESP32 + adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), attenuation_); adc1_config_width(ADC_WIDTH_BIT_12); #if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 - adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_)); + adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_->get_pin())); #endif #endif } void ADCSensor::dump_config() { LOG_SENSOR("", "ADC Sensor", this); -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #ifdef USE_ADC_SENSOR_VCC ESP_LOGCONFIG(TAG, " Pin: VCC"); #else - ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); + LOG_PIN(" Pin: ", pin_); #endif #endif -#ifdef ARDUINO_ARCH_ESP32 - ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); +#ifdef USE_ESP32 + LOG_PIN(" Pin: ", pin_); switch (this->attenuation_) { case ADC_ATTEN_DB_0: ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); @@ -105,8 +106,8 @@ void ADCSensor::update() { this->publish_state(value_v); } float ADCSensor::sample() { -#ifdef ARDUINO_ARCH_ESP32 - int raw = adc1_get_raw(gpio_to_adc1(pin_)); +#ifdef USE_ESP32 + int raw = adc1_get_raw(gpio_to_adc1(pin_->get_pin())); float value_v = raw / 4095.0f; #if CONFIG_IDF_TARGET_ESP32 switch (this->attenuation_) { @@ -146,15 +147,15 @@ float ADCSensor::sample() { return value_v; #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #ifdef USE_ADC_SENSOR_VCC return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance) #else - return analogRead(this->pin_) / 1024.0f; // NOLINT + return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT #endif #endif } -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } #endif diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index 4591ed758d..b8c702be4e 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -1,12 +1,12 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/defines.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/voltage_sampler/voltage_sampler.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include "driver/adc.h" #endif @@ -15,7 +15,7 @@ namespace adc { class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { public: -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 /// Set the attenuation for this pin. Only available on the ESP32. void set_attenuation(adc_atten_t attenuation); #endif @@ -27,17 +27,17 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage void dump_config() override; /// `HARDWARE_LATE` setup priority. float get_setup_priority() const override; - void set_pin(uint8_t pin) { this->pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } float sample() override; -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 std::string unique_id() override; #endif protected: - uint8_t pin_; + InternalGPIOPin *pin_; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 adc_atten_t attenuation_{ADC_ATTEN_DB_0}; #endif }; diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 7c32e4a923..9a0407d0f4 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -5,11 +5,13 @@ from esphome.components import sensor, voltage_sampler from esphome.const import ( CONF_ATTENUATION, CONF_ID, + CONF_INPUT, CONF_PIN, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT, UNIT_VOLT, ) +from esphome.core import CORE AUTO_LOAD = ["voltage_sampler"] @@ -23,10 +25,34 @@ ATTENUATION_MODES = { def validate_adc_pin(value): - vcc = str(value).upper() - if vcc == "VCC": - return cv.only_on_esp8266(vcc) - return pins.analog_pin(value) + if str(value).upper() == "VCC": + return cv.only_on_esp8266("VCC") + + if CORE.is_esp32: + from esphome.components.esp32 import is_esp32c3 + + value = pins.internal_gpio_input_pin_number(value) + if is_esp32c3(): + if not (0 <= value <= 4): # ADC1 + raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.") + if not (32 <= value <= 39): # ADC1 + raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.") + elif CORE.is_esp8266: + from esphome.components.esp8266.gpio import CONF_ANALOG + + value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})( + value + ) + + if value != 17: # A0 + raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.") + return pins.gpio_pin_schema( + {CONF_ANALOG: True, CONF_INPUT: True}, internal=True + )(value) + else: + raise NotImplementedError + + return pins.internal_gpio_input_pin_schema(value) adc_ns = cg.esphome_ns.namespace("adc") @@ -62,7 +88,8 @@ async def to_code(config): if config[CONF_PIN] == "VCC": cg.add_define("USE_ADC_SENSOR_VCC") else: - cg.add(var.set_pin(config[CONF_PIN])) + pin = await cg.gpio_pin_expression(config[CONF_PIN]) + cg.add(var.set_pin(pin)) if CONF_ATTENUATION in config: cg.add(var.set_attenuation(config[CONF_ATTENUATION])) diff --git a/esphome/components/ade7953/ade7953.cpp b/esphome/components/ade7953/ade7953.cpp index 0e6d5624c1..2c61fc6a44 100644 --- a/esphome/components/ade7953/ade7953.cpp +++ b/esphome/components/ade7953/ade7953.cpp @@ -8,9 +8,7 @@ static const char *const TAG = "ade7953"; void ADE7953::dump_config() { ESP_LOGCONFIG(TAG, "ADE7953:"); - if (this->has_irq_) { - ESP_LOGCONFIG(TAG, " IRQ Pin: GPIO%u", this->irq_pin_number_); - } + LOG_PIN(" IRQ Pin: ", irq_pin_); LOG_I2C_DEVICE(this); LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "Voltage Sensor", this->voltage_sensor_); @@ -20,27 +18,28 @@ void ADE7953::dump_config() { LOG_SENSOR(" ", "Active Power B Sensor", this->active_power_b_sensor_); } -#define ADE_PUBLISH_(name, factor) \ - if ((name) && this->name##_sensor_) { \ - float value = *(name) / (factor); \ +#define ADE_PUBLISH_(name, val, factor) \ + if (err == i2c::ERROR_OK && this->name##_sensor_) { \ + float value = (val) / (factor); \ this->name##_sensor_->publish_state(value); \ } -#define ADE_PUBLISH(name, factor) ADE_PUBLISH_(name, factor) +#define ADE_PUBLISH(name, val, factor) ADE_PUBLISH_(name, val, factor) void ADE7953::update() { if (!this->is_setup_) return; - auto active_power_a = this->ade_read_(0x0312); - ADE_PUBLISH(active_power_a, 154.0f); - auto active_power_b = this->ade_read_(0x0313); - ADE_PUBLISH(active_power_b, 154.0f); - auto current_a = this->ade_read_(0x031A); - ADE_PUBLISH(current_a, 100000.0f); - auto current_b = this->ade_read_(0x031B); - ADE_PUBLISH(current_b, 100000.0f); - auto voltage = this->ade_read_(0x031C); - ADE_PUBLISH(voltage, 26000.0f); + uint32_t val; + i2c::ErrorCode err = ade_read_32_(0x0312, &val); + ADE_PUBLISH(active_power_a, (int32_t) val, 154.0f); + err = ade_read_32_(0x0313, &val); + ADE_PUBLISH(active_power_b, (int32_t) val, 154.0f); + err = ade_read_32_(0x031A, &val); + ADE_PUBLISH(current_a, (uint32_t) val, 100000.0f); + err = ade_read_32_(0x031B, &val); + ADE_PUBLISH(current_b, (uint32_t) val, 100000.0f); + err = ade_read_32_(0x031C, &val); + ADE_PUBLISH(voltage, (uint32_t) val, 26000.0f); // auto apparent_power_a = this->ade_read_(0x0310); // auto apparent_power_b = this->ade_read_(0x0311); diff --git a/esphome/components/ade7953/ade7953.h b/esphome/components/ade7953/ade7953.h index 5a582a991a..c6fb383ed8 100644 --- a/esphome/components/ade7953/ade7953.h +++ b/esphome/components/ade7953/ade7953.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" #include "esphome/components/sensor/sensor.h" @@ -10,10 +10,7 @@ namespace ade7953 { class ADE7953 : public i2c::I2CDevice, public PollingComponent { public: - void set_irq_pin(uint8_t irq_pin) { - has_irq_ = true; - irq_pin_number_ = irq_pin; - } + void set_irq_pin(InternalGPIOPin *irq_pin) { irq_pin_ = irq_pin; } void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } void set_current_a_sensor(sensor::Sensor *current_a_sensor) { current_a_sensor_ = current_a_sensor; } void set_current_b_sensor(sensor::Sensor *current_b_sensor) { current_b_sensor_ = current_b_sensor; } @@ -25,15 +22,13 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent { } void setup() override { - if (this->has_irq_) { - auto pin = GPIOPin(this->irq_pin_number_, INPUT); - this->irq_pin_ = &pin; + if (this->irq_pin_ != nullptr) { this->irq_pin_->setup(); } this->set_timeout(100, [this]() { - this->ade_write_(0x0010, 0x04); - this->ade_write_(0x00FE, 0xAD); - this->ade_write_(0x0120, 0x0030); + this->ade_write_8_(0x0010, 0x04); + this->ade_write_8_(0x00FE, 0xAD); + this->ade_write_16_(0x0120, 0x0030); this->is_setup_ = true; }); } @@ -43,31 +38,51 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent { void update() override; protected: - template bool ade_write_(uint16_t reg, T value) { + i2c::ErrorCode ade_write_8_(uint16_t reg, uint8_t value) { std::vector data; data.push_back(reg >> 8); data.push_back(reg >> 0); - for (int i = sizeof(T) - 1; i >= 0; i--) - data.push_back(value >> (i * 8)); - return this->write_bytes_raw(data); + data.push_back(value); + return write(data.data(), data.size()); } - template optional ade_read_(uint16_t reg) { - uint8_t hi = reg >> 8; - uint8_t lo = reg >> 0; - if (!this->write_bytes_raw({hi, lo})) - return {}; - auto ret = this->read_bytes_raw(); - if (!ret.has_value()) - return {}; - T result = 0; - for (int i = 0, j = sizeof(T) - 1; i < sizeof(T); i++, j--) - result |= T((*ret)[i]) << (j * 8); - return result; + i2c::ErrorCode ade_write_16_(uint16_t reg, uint16_t value) { + std::vector data; + data.push_back(reg >> 8); + data.push_back(reg >> 0); + data.push_back(value >> 8); + data.push_back(value >> 0); + return write(data.data(), data.size()); + } + i2c::ErrorCode ade_write_32_(uint16_t reg, uint32_t value) { + std::vector data; + data.push_back(reg >> 8); + data.push_back(reg >> 0); + data.push_back(value >> 24); + data.push_back(value >> 16); + data.push_back(value >> 8); + data.push_back(value >> 0); + return write(data.data(), data.size()); + } + i2c::ErrorCode ade_read_32_(uint16_t reg, uint32_t *value) { + uint8_t reg_data[2]; + reg_data[0] = reg >> 8; + reg_data[1] = reg >> 0; + i2c::ErrorCode err = write(reg_data, 2); + if (err != i2c::ERROR_OK) + return err; + uint8_t recv[4]; + err = read(recv, 4); + if (err != i2c::ERROR_OK) + return err; + *value = 0; + *value |= ((uint32_t) recv[0]) << 24; + *value |= ((uint32_t) recv[1]) << 24; + *value |= ((uint32_t) recv[2]) << 24; + *value |= ((uint32_t) recv[3]) << 24; + return i2c::ERROR_OK; } - bool has_irq_ = false; - uint8_t irq_pin_number_; - GPIOPin *irq_pin_{nullptr}; + InternalGPIOPin *irq_pin_ = nullptr; bool is_setup_{false}; sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *current_a_sensor_{nullptr}; diff --git a/esphome/components/ade7953/sensor.py b/esphome/components/ade7953/sensor.py index 80dafe2417..d02f466091 100644 --- a/esphome/components/ade7953/sensor.py +++ b/esphome/components/ade7953/sensor.py @@ -29,7 +29,7 @@ CONFIG_SCHEMA = ( cv.Schema( { cv.GenerateID(): cv.declare_id(ADE7953), - cv.Optional(CONF_IRQ_PIN): pins.input_pin, + cv.Optional(CONF_IRQ_PIN): pins.internal_gpio_input_pin_schema, cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=1, @@ -73,7 +73,8 @@ async def to_code(config): await i2c.register_i2c_device(var, config) if CONF_IRQ_PIN in config: - cg.add(var.set_irq_pin(config[CONF_IRQ_PIN])) + irq_pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN]) + cg.add(var.set_irq_pin(irq_pin)) for key in [ CONF_VOLTAGE, diff --git a/esphome/components/ads1115/ads1115.cpp b/esphome/components/ads1115/ads1115.cpp index 92f0ffbe3a..beb379db93 100644 --- a/esphome/components/ads1115/ads1115.cpp +++ b/esphome/components/ads1115/ads1115.cpp @@ -1,5 +1,6 @@ #include "ads1115.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace ads1115 { @@ -159,7 +160,7 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) { float ADS1115Sensor::sample() { return this->parent_->request_measurement(this); } void ADS1115Sensor::update() { float v = this->parent_->request_measurement(this); - if (!isnan(v)) { + if (!std::isnan(v)) { ESP_LOGD(TAG, "'%s': Got Voltage=%fV", this->get_name().c_str(), v); this->publish_state(v); } diff --git a/esphome/components/aht10/aht10.cpp b/esphome/components/aht10/aht10.cpp index da8c6e844e..713199212c 100644 --- a/esphome/components/aht10/aht10.cpp +++ b/esphome/components/aht10/aht10.cpp @@ -14,6 +14,7 @@ #include "aht10.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace aht10 { @@ -33,8 +34,19 @@ void AHT10Component::setup() { this->mark_failed(); return; } - uint8_t data; - if (!this->read_byte(0, &data, AHT10_DEFAULT_DELAY)) { + uint8_t data = 0; + if (this->write(&data, 1) != i2c::ERROR_OK) { + ESP_LOGD(TAG, "Communication with AHT10 failed!"); + this->mark_failed(); + return; + } + delay(AHT10_DEFAULT_DELAY); + if (this->read(&data, 1) != i2c::ERROR_OK) { + ESP_LOGD(TAG, "Communication with AHT10 failed!"); + this->mark_failed(); + return; + } + if (this->read(&data, 1) != i2c::ERROR_OK) { ESP_LOGD(TAG, "Communication with AHT10 failed!"); this->mark_failed(); return; @@ -55,15 +67,26 @@ void AHT10Component::update() { return; } uint8_t data[6]; - uint8_t delay = AHT10_DEFAULT_DELAY; + uint8_t delay_ms = AHT10_DEFAULT_DELAY; if (this->humidity_sensor_ != nullptr) - delay = AHT10_HUMIDITY_DELAY; + delay_ms = AHT10_HUMIDITY_DELAY; + bool success = false; for (int i = 0; i < AHT10_ATTEMPTS; ++i) { - ESP_LOGVV(TAG, "Attempt %u at %6ld", i, millis()); + ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis()); delay_microseconds_accurate(4); - if (!this->read_bytes(0, data, 6, delay)) { + + uint8_t reg = 0; + if (this->write(®, 1) != i2c::ERROR_OK) { ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); - } else if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy + continue; + } + delay(delay_ms); + if (this->read(data, 6) != i2c::ERROR_OK) { + ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); + continue; + } + + if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy ESP_LOGD(TAG, "AHT10 is busy, waiting..."); } else if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) { // Unrealistic humidity (0x0) @@ -80,11 +103,12 @@ void AHT10Component::update() { } } else { // data is valid, we can break the loop - ESP_LOGVV(TAG, "Answer at %6ld", millis()); + ESP_LOGVV(TAG, "Answer at %6u", millis()); + success = true; break; } } - if ((data[0] & 0x80) == 0x80) { + if (!success || (data[0] & 0x80) == 0x80) { ESP_LOGE(TAG, "Measurements reading timed-out!"); this->status_set_warning(); return; @@ -105,7 +129,7 @@ void AHT10Component::update() { this->temperature_sensor_->publish_state(temperature); } if (this->humidity_sensor_ != nullptr) { - if (isnan(humidity)) + if (std::isnan(humidity)) ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum"); this->humidity_sensor_->publish_state(humidity); } diff --git a/esphome/components/airthings_ble/airthings_listener.cpp b/esphome/components/airthings_ble/airthings_listener.cpp index a843bcb145..951961cb1b 100644 --- a/esphome/components/airthings_ble/airthings_listener.cpp +++ b/esphome/components/airthings_ble/airthings_listener.cpp @@ -1,7 +1,7 @@ #include "airthings_listener.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace airthings_ble { diff --git a/esphome/components/airthings_ble/airthings_listener.h b/esphome/components/airthings_ble/airthings_listener.h index cd240ac1ba..52f69ea970 100644 --- a/esphome/components/airthings_ble/airthings_listener.h +++ b/esphome/components/airthings_ble/airthings_listener.h @@ -1,10 +1,9 @@ #pragma once -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include "esphome/core/component.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#include namespace esphome { namespace airthings_ble { diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp index 6b4780726d..a8153ae87a 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -1,6 +1,6 @@ #include "airthings_wave_plus.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO namespace esphome { namespace airthings_wave_plus { @@ -141,4 +141,4 @@ void AirthingsWavePlus::setup() {} } // namespace airthings_wave_plus } // namespace esphome -#endif // ARDUINO_ARCH_ESP32 +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.h b/esphome/components/airthings_wave_plus/airthings_wave_plus.h index 88d7a7a01b..5677f05a62 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.h +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.h @@ -1,6 +1,6 @@ #pragma once -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO #include #include @@ -72,4 +72,4 @@ class AirthingsWavePlus : public PollingComponent, public ble_client::BLEClientN } // namespace airthings_wave_plus } // namespace esphome -#endif // ARDUINO_ARCH_ESP32 +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/airthings_wave_plus/sensor.py b/esphome/components/airthings_wave_plus/sensor.py index 4109fca700..8b902ea81c 100644 --- a/esphome/components/airthings_wave_plus/sensor.py +++ b/esphome/components/airthings_wave_plus/sensor.py @@ -34,7 +34,7 @@ AirthingsWavePlus = airthings_wave_plus_ns.class_( ) -CONFIG_SCHEMA = ( +CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(AirthingsWavePlus), @@ -83,7 +83,9 @@ CONFIG_SCHEMA = ( } ) .extend(cv.polling_component_schema("5mins")) - .extend(ble_client.BLE_CLIENT_SCHEMA) + .extend(ble_client.BLE_CLIENT_SCHEMA), + # Until BLEUUID reference removed + cv.only_with_arduino, ) diff --git a/esphome/components/am2320/am2320.cpp b/esphome/components/am2320/am2320.cpp index 7e8795dd30..b53eb69464 100644 --- a/esphome/components/am2320/am2320.cpp +++ b/esphome/components/am2320/am2320.cpp @@ -5,6 +5,7 @@ #include "am2320.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace am2320 { @@ -77,7 +78,7 @@ bool AM2320Component::read_bytes_(uint8_t a_register, uint8_t *data, uint8_t len if (conversion > 0) delay(conversion); - return this->parent_->raw_receive(this->address_, data, len); + return this->read(data, len) == i2c::ERROR_OK; } bool AM2320Component::read_data_(uint8_t *data) { diff --git a/esphome/components/am43/am43.cpp b/esphome/components/am43/am43.cpp index 94be194a1d..2130b334be 100644 --- a/esphome/components/am43/am43.cpp +++ b/esphome/components/am43/am43.cpp @@ -1,7 +1,8 @@ #include "am43.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace am43 { diff --git a/esphome/components/am43/am43.h b/esphome/components/am43/am43.h index 025d075b68..8dfe83e3a3 100644 --- a/esphome/components/am43/am43.h +++ b/esphome/components/am43/am43.h @@ -6,7 +6,7 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/am43/am43_base.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include diff --git a/esphome/components/am43/cover/am43_cover.cpp b/esphome/components/am43/cover/am43_cover.cpp index 1067ce9ebb..fd337ba17b 100644 --- a/esphome/components/am43/cover/am43_cover.cpp +++ b/esphome/components/am43/cover/am43_cover.cpp @@ -1,7 +1,7 @@ #include "am43_cover.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace am43 { diff --git a/esphome/components/am43/cover/am43_cover.h b/esphome/components/am43/cover/am43_cover.h index dba1391b63..f33f2d1734 100644 --- a/esphome/components/am43/cover/am43_cover.h +++ b/esphome/components/am43/cover/am43_cover.h @@ -6,7 +6,7 @@ #include "esphome/components/cover/cover.h" #include "esphome/components/am43/am43_base.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index d0ea818669..8e0724ad13 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -1,7 +1,7 @@ #include "anova.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace anova { diff --git a/esphome/components/anova/anova.h b/esphome/components/anova/anova.h index e033513013..554024e389 100644 --- a/esphome/components/anova/anova.h +++ b/esphome/components/anova/anova.h @@ -6,7 +6,7 @@ #include "esphome/components/climate/climate.h" #include "anova_base.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include diff --git a/esphome/components/apds9960/apds9960.cpp b/esphome/components/apds9960/apds9960.cpp index 15b0cb39f8..9ee873ac64 100644 --- a/esphome/components/apds9960/apds9960.cpp +++ b/esphome/components/apds9960/apds9960.cpp @@ -1,5 +1,6 @@ #include "apds9960.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace apds9960 { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 0fa4ca6397..450375f7cd 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1,7 +1,8 @@ #include "api_connection.h" #include "esphome/core/log.h" -#include "esphome/core/util.h" +#include "esphome/components/network/util.h" #include "esphome/core/version.h" +#include "esphome/core/hal.h" #include #ifdef USE_DEEP_SLEEP @@ -48,7 +49,7 @@ void APIConnection::loop() { if (this->remove_) return; - if (!network_is_connected()) { + if (!network::is_connected()) { // when network is disconnected force disconnect immediately // don't wait for timeout this->on_fatal_error(); diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 15014b7937..00f28457ae 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -3,6 +3,7 @@ #include "esphome/core/log.h" #include "esphome/core/helpers.h" #include "proto.h" +#include namespace esphome { namespace api { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index d6b85d257c..580e147245 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -2,6 +2,7 @@ // See scripts/api_protobuf/api_protobuf.py #include "api_pb2.h" #include "esphome/core/log.h" +#include namespace esphome { namespace api { diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index d84a35747c..8728597537 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -5,6 +5,8 @@ #include "esphome/core/log.h" #include "esphome/core/util.h" #include "esphome/core/version.h" +#include "esphome/core/hal.h" +#include "esphome/components/network/util.h" #include #ifdef USE_LOGGER @@ -130,7 +132,7 @@ void APIServer::loop() { } void APIServer::dump_config() { ESP_LOGCONFIG(TAG, "API Server:"); - ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->port_); + ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->port_); } bool APIServer::uses_password() const { return !this->password_.empty(); } bool APIServer::check_password(const std::string &password) const { diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index dd054bab7e..edbc916b01 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -246,6 +246,7 @@ class ProtoWriteBuffer { class ProtoMessage { public: + virtual ~ProtoMessage() = default; virtual void encode(ProtoWriteBuffer buffer) const = 0; void decode(const uint8_t *buffer, size_t length); #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/components/as3935/as3935.h b/esphome/components/as3935/as3935.h index 76c7697d38..2e65aab4d1 100644 --- a/esphome/components/as3935/as3935.h +++ b/esphome/components/as3935/as3935.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h" diff --git a/esphome/components/as3935_i2c/as3935_i2c.cpp b/esphome/components/as3935_i2c/as3935_i2c.cpp index 1a1cd8fe82..3a7fa7bf84 100644 --- a/esphome/components/as3935_i2c/as3935_i2c.cpp +++ b/esphome/components/as3935_i2c/as3935_i2c.cpp @@ -25,8 +25,12 @@ void I2CAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t I2CAS3935Component::read_register(uint8_t reg) { uint8_t value; - if (!this->read_byte(reg, &value, 2)) { - ESP_LOGW(TAG, "Read failed!"); + if (write(®, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Writing register failed!"); + return 0; + } + if (read(&value, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Reading register failed!"); return 0; } return value; diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index 22d4bb1fcb..8789448792 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -1,9 +1,15 @@ # Dummy integration to allow relying on AsyncTCP import esphome.codegen as cg +import esphome.config_validation as cv from esphome.core import CORE, coroutine_with_priority CODEOWNERS = ["@OttoWinter"] +CONFIG_SCHEMA = cv.All( + cv.Schema({}), + cv.only_with_arduino, +) + @coroutine_with_priority(200.0) async def to_code(config): diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.cpp b/esphome/components/atc_mithermometer/atc_mithermometer.cpp index 1dba259143..b04d634103 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.cpp +++ b/esphome/components/atc_mithermometer/atc_mithermometer.cpp @@ -1,7 +1,7 @@ #include "atc_mithermometer.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace atc_mithermometer { diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.h b/esphome/components/atc_mithermometer/atc_mithermometer.h index eff14811be..291c1d96cd 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.h +++ b/esphome/components/atc_mithermometer/atc_mithermometer.h @@ -4,7 +4,7 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace atc_mithermometer { diff --git a/esphome/components/b_parasite/b_parasite.cpp b/esphome/components/b_parasite/b_parasite.cpp index 3098d462d2..bc1463fd1c 100644 --- a/esphome/components/b_parasite/b_parasite.cpp +++ b/esphome/components/b_parasite/b_parasite.cpp @@ -1,7 +1,7 @@ #include "b_parasite.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace b_parasite { @@ -79,4 +79,4 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { } // namespace b_parasite } // namespace esphome -#endif // ARDUINO_ARCH_ESP32 +#endif // USE_ESP32 diff --git a/esphome/components/b_parasite/b_parasite.h b/esphome/components/b_parasite/b_parasite.h index 04f648ab63..bdd9a01b83 100644 --- a/esphome/components/b_parasite/b_parasite.h +++ b/esphome/components/b_parasite/b_parasite.h @@ -4,7 +4,7 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace b_parasite { @@ -37,4 +37,4 @@ class BParasite : public Component, public esp32_ble_tracker::ESPBTDeviceListene } // namespace b_parasite } // namespace esphome -#endif // ARDUINO_ARCH_ESP32 +#endif // USE_ESP32 diff --git a/esphome/components/bang_bang/bang_bang_climate.cpp b/esphome/components/bang_bang/bang_bang_climate.cpp index 4b41684707..5645f46f1c 100644 --- a/esphome/components/bang_bang/bang_bang_climate.cpp +++ b/esphome/components/bang_bang/bang_bang_climate.cpp @@ -69,7 +69,8 @@ void BangBangClimate::compute_state_() { this->switch_to_action_(climate::CLIMATE_ACTION_OFF); return; } - if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || isnan(this->target_temperature_high)) { + if (std::isnan(this->current_temperature) || std::isnan(this->target_temperature_low) || + std::isnan(this->target_temperature_high)) { // if any control parameters are nan, go to OFF action (not IDLE!) this->switch_to_action_(climate::CLIMATE_ACTION_OFF); return; diff --git a/esphome/components/bh1750/bh1750.cpp b/esphome/components/bh1750/bh1750.cpp index 43af116c9e..3645a45bf9 100644 --- a/esphome/components/bh1750/bh1750.cpp +++ b/esphome/components/bh1750/bh1750.cpp @@ -71,10 +71,11 @@ void BH1750Sensor::update() { float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; } void BH1750Sensor::read_data_() { uint16_t raw_value; - if (!this->parent_->raw_receive_16(this->address_, &raw_value, 1)) { + if (!this->read(reinterpret_cast(&raw_value), 2)) { this->status_set_warning(); return; } + raw_value = i2c::i2ctohs(raw_value); float lx = float(raw_value) / 1.2f; lx *= 69.0f / this->measurement_duration_; diff --git a/esphome/components/binary_sensor/automation.h b/esphome/components/binary_sensor/automation.h index 0c1e80afba..31bf1a5565 100644 --- a/esphome/components/binary_sensor/automation.h +++ b/esphome/components/binary_sensor/automation.h @@ -4,6 +4,7 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" +#include "esphome/core/hal.h" #include "esphome/components/binary_sensor/binary_sensor.h" namespace esphome { diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index 2db609de55..d015d4019c 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -3,7 +3,7 @@ #include "esphome/core/automation.h" #include "esphome/components/ble_client/ble_client.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_client { diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 0398113f83..f584cdf79e 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -4,7 +4,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "ble_client.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_client { diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index b9d16ddacd..a69460e8b6 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -4,7 +4,7 @@ #include "esphome/core/helpers.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include diff --git a/esphome/components/ble_client/sensor/automation.h b/esphome/components/ble_client/sensor/automation.h index a528493947..2255a5ac55 100644 --- a/esphome/components/ble_client/sensor/automation.h +++ b/esphome/components/ble_client/sensor/automation.h @@ -3,7 +3,7 @@ #include "esphome/core/automation.h" #include "esphome/components/ble_client/sensor/ble_sensor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_client { diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index 270822be9d..4459163389 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -4,7 +4,7 @@ #include "esphome/core/helpers.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_client { diff --git a/esphome/components/ble_client/sensor/ble_sensor.h b/esphome/components/ble_client/sensor/ble_sensor.h index 25e996b6ee..52c9e9d5ca 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.h +++ b/esphome/components/ble_client/sensor/ble_sensor.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/sensor/sensor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include namespace esphome { diff --git a/esphome/components/ble_client/switch/ble_switch.cpp b/esphome/components/ble_client/switch/ble_switch.cpp index 669984d705..00593da9d6 100644 --- a/esphome/components/ble_client/switch/ble_switch.cpp +++ b/esphome/components/ble_client/switch/ble_switch.cpp @@ -2,7 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_client { diff --git a/esphome/components/ble_client/switch/ble_switch.h b/esphome/components/ble_client/switch/ble_switch.h index f91af533f1..2e19c8aeef 100644 --- a/esphome/components/ble_client/switch/ble_switch.h +++ b/esphome/components/ble_client/switch/ble_switch.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/switch/switch.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include namespace esphome { diff --git a/esphome/components/ble_presence/ble_presence_device.cpp b/esphome/components/ble_presence/ble_presence_device.cpp index 1355c2bcc3..e482bb9a78 100644 --- a/esphome/components/ble_presence/ble_presence_device.cpp +++ b/esphome/components/ble_presence/ble_presence_device.cpp @@ -1,7 +1,7 @@ #include "ble_presence_device.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_presence { diff --git a/esphome/components/ble_presence/ble_presence_device.h b/esphome/components/ble_presence/ble_presence_device.h index dfc36d68cb..40cda89e62 100644 --- a/esphome/components/ble_presence/ble_presence_device.h +++ b/esphome/components/ble_presence/ble_presence_device.h @@ -4,7 +4,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/binary_sensor/binary_sensor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_presence { diff --git a/esphome/components/ble_rssi/ble_rssi_sensor.cpp b/esphome/components/ble_rssi/ble_rssi_sensor.cpp index 096259d8d1..4b37fcc6ef 100644 --- a/esphome/components/ble_rssi/ble_rssi_sensor.cpp +++ b/esphome/components/ble_rssi/ble_rssi_sensor.cpp @@ -1,7 +1,7 @@ #include "ble_rssi_sensor.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_rssi { diff --git a/esphome/components/ble_rssi/ble_rssi_sensor.h b/esphome/components/ble_rssi/ble_rssi_sensor.h index 2082a52469..c6acae2593 100644 --- a/esphome/components/ble_rssi/ble_rssi_sensor.h +++ b/esphome/components/ble_rssi/ble_rssi_sensor.h @@ -4,7 +4,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/sensor/sensor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_rssi { diff --git a/esphome/components/ble_scanner/ble_scanner.cpp b/esphome/components/ble_scanner/ble_scanner.cpp index 798824bb4e..f2cda227bb 100644 --- a/esphome/components/ble_scanner/ble_scanner.cpp +++ b/esphome/components/ble_scanner/ble_scanner.cpp @@ -1,7 +1,7 @@ #include "ble_scanner.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_scanner { diff --git a/esphome/components/ble_scanner/ble_scanner.h b/esphome/components/ble_scanner/ble_scanner.h index 194494144c..542d5047ec 100644 --- a/esphome/components/ble_scanner/ble_scanner.h +++ b/esphome/components/ble_scanner/ble_scanner.h @@ -7,7 +7,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/text_sensor/text_sensor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_scanner { diff --git a/esphome/components/bme280/bme280.cpp b/esphome/components/bme280/bme280.cpp index fe11b69945..18386430a2 100644 --- a/esphome/components/bme280/bme280.cpp +++ b/esphome/components/bme280/bme280.cpp @@ -186,7 +186,7 @@ void BME280Component::update() { this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() { int32_t t_fine = 0; float temperature = this->read_temperature_(&t_fine); - if (isnan(temperature)) { + if (std::isnan(temperature)) { ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values."); this->status_set_warning(); return; diff --git a/esphome/components/bme680/bme680.cpp b/esphome/components/bme680/bme680.cpp index ee7db3c65f..99e0b6f860 100644 --- a/esphome/components/bme680/bme680.cpp +++ b/esphome/components/bme680/bme680.cpp @@ -1,5 +1,6 @@ #include "bme680.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace bme680 { diff --git a/esphome/components/bme680_bsec/bme680_bsec.cpp b/esphome/components/bme680_bsec/bme680_bsec.cpp index e2cb7491a6..0a8ca7f3c3 100644 --- a/esphome/components/bme680_bsec/bme680_bsec.cpp +++ b/esphome/components/bme680_bsec/bme680_bsec.cpp @@ -381,7 +381,7 @@ void BME680BSECComponent::delay_ms(uint32_t period) { void BME680BSECComponent::load_state_() { uint32_t hash = fnv1_hash("bme680_bsec_state_" + to_string(this->address_)); - this->bsec_state_ = global_preferences.make_preference(hash, true); + this->bsec_state_ = global_preferences->make_preference(hash, true); uint8_t state[BSEC_MAX_STATE_BLOB_SIZE]; if (this->bsec_state_.load(&state)) { diff --git a/esphome/components/bmp280/bmp280.cpp b/esphome/components/bmp280/bmp280.cpp index d04a357ca2..b4348e8a74 100644 --- a/esphome/components/bmp280/bmp280.cpp +++ b/esphome/components/bmp280/bmp280.cpp @@ -139,7 +139,7 @@ void BMP280Component::update() { this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() { int32_t t_fine = 0; float temperature = this->read_temperature_(&t_fine); - if (isnan(temperature)) { + if (std::isnan(temperature)) { ESP_LOGW(TAG, "Invalid temperature, cannot read pressure values."); this->status_set_warning(); return; diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index 102bfd370e..a1cc5734c1 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -12,14 +12,17 @@ CODEOWNERS = ["@OttoWinter"] captive_portal_ns = cg.esphome_ns.namespace("captive_portal") CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component) -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(CaptivePortal), - cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( - web_server_base.WebServerBase - ), - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(CaptivePortal), + cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( + web_server_base.WebServerBase + ), + } + ).extend(cv.COMPONENT_SCHEMA), + cv.only_with_arduino, +) @coroutine_with_priority(64.0) diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index 3341769956..9e00adae3d 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "captive_portal.h" #include "esphome/core/log.h" #include "esphome/core/application.h" @@ -78,14 +80,14 @@ void CaptivePortal::start() { this->dns_server_ = make_unique(); this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); - IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); - this->dns_server_->start(53, "*", ip); + network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); + this->dns_server_->start(53, "*", (uint32_t) ip); this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { bool not_found = false; if (!this->active_) { not_found = true; - } else if (req->host() == wifi::global_wifi_component->wifi_soft_ap_ip().toString()) { + } else if (req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) { not_found = true; } @@ -94,8 +96,8 @@ void CaptivePortal::start() { return; } - auto url = "http://" + wifi::global_wifi_component->wifi_soft_ap_ip().toString(); - req->redirect(url); + auto url = "http://" + wifi::global_wifi_component->wifi_soft_ap_ip().str(); + req->redirect(url.c_str()); }); this->initialized_ = true; @@ -151,3 +153,5 @@ CaptivePortal *global_captive_portal = nullptr; // NOLINT(cppcoreguidelines-avo } // namespace captive_portal } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h index 399ffaabd3..b308de42b7 100644 --- a/esphome/components/captive_portal/captive_portal.h +++ b/esphome/components/captive_portal/captive_portal.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include #include #include "esphome/core/component.h" @@ -73,3 +75,5 @@ extern CaptivePortal *global_captive_portal; // NOLINT(cppcoreguidelines-avoid- } // namespace captive_portal } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/ccs811/ccs811.cpp b/esphome/components/ccs811/ccs811.cpp index 08df6f7774..6bdb4739cf 100644 --- a/esphome/components/ccs811/ccs811.cpp +++ b/esphome/components/ccs811/ccs811.cpp @@ -1,5 +1,6 @@ #include "ccs811.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace ccs811 { @@ -124,12 +125,12 @@ void CCS811Component::send_env_data_() { float humidity = NAN; if (this->humidity_ != nullptr) humidity = this->humidity_->state; - if (isnan(humidity) || humidity < 0 || humidity > 100) + if (std::isnan(humidity) || humidity < 0 || humidity > 100) humidity = 50; float temperature = NAN; if (this->temperature_ != nullptr) temperature = this->temperature_->state; - if (isnan(temperature) || temperature < -25 || temperature > 50) + if (std::isnan(temperature) || temperature < -25 || temperature > 50) temperature = 25; // temperature has a 25° offset to allow negative temperatures temperature += 25; diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 383103b014..a365b933bb 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -95,7 +95,7 @@ void ClimateCall::validate_() { ESP_LOGW(TAG, " Cannot set target temperature for climate device " "with two-point target temperature!"); this->target_temperature_.reset(); - } else if (isnan(target)) { + } else if (std::isnan(target)) { ESP_LOGW(TAG, " Target temperature must not be NAN!"); this->target_temperature_.reset(); } @@ -107,11 +107,11 @@ void ClimateCall::validate_() { this->target_temperature_high_.reset(); } } - if (this->target_temperature_low_.has_value() && isnan(*this->target_temperature_low_)) { + if (this->target_temperature_low_.has_value() && std::isnan(*this->target_temperature_low_)) { ESP_LOGW(TAG, " Target temperature low must not be NAN!"); this->target_temperature_low_.reset(); } - if (this->target_temperature_high_.has_value() && isnan(*this->target_temperature_high_)) { + if (this->target_temperature_high_.has_value() && std::isnan(*this->target_temperature_high_)) { ESP_LOGW(TAG, " Target temperature low must not be NAN!"); this->target_temperature_high_.reset(); } @@ -318,17 +318,23 @@ void Climate::add_on_state_callback(std::function &&callback) { static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL; optional Climate::restore_state_() { - this->rtc_ = - global_preferences.make_preference(this->get_object_id_hash() ^ RESTORE_STATE_VERSION); + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash() ^ + RESTORE_STATE_VERSION); ClimateDeviceRestoreState recovered{}; if (!this->rtc_.load(&recovered)) return {}; return recovered; } void Climate::save_state_() { +#if defined(USE_ESP_IDF) && !defined(CLANG_TIDY) +#pragma GCC diagnostic ignored "-Wclass-memaccess" +#endif ClimateDeviceRestoreState state{}; // initialize as zero to prevent random data on stack triggering erase memset(&state, 0, sizeof(ClimateDeviceRestoreState)); +#if USE_ESP_IDF && !defined(CLANG_TIDY) +#pragma GCC diagnostic pop +#endif state.mode = this->mode; auto traits = this->get_traits(); diff --git a/esphome/components/climate_ir/climate_ir.cpp b/esphome/components/climate_ir/climate_ir.cpp index 8d5ae6053d..b47d9b0141 100644 --- a/esphome/components/climate_ir/climate_ir.cpp +++ b/esphome/components/climate_ir/climate_ir.cpp @@ -52,7 +52,7 @@ void ClimateIR::setup() { this->swing_mode = climate::CLIMATE_SWING_OFF; } // Never send nan to HA - if (isnan(this->target_temperature)) + if (std::isnan(this->target_temperature)) this->target_temperature = 24; } diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 2b4452f5b7..e1ce211b64 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -180,7 +180,7 @@ void Cover::publish_state(bool save) { } } optional Cover::restore_state_() { - this->rtc_ = global_preferences.make_preference(this->get_object_id_hash()); + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); CoverRestoreState recovered{}; if (!this->rtc_.load(&recovered)) return {}; diff --git a/esphome/components/ct_clamp/ct_clamp_sensor.cpp b/esphome/components/ct_clamp/ct_clamp_sensor.cpp index 130cdfefba..0052b1426d 100644 --- a/esphome/components/ct_clamp/ct_clamp_sensor.cpp +++ b/esphome/components/ct_clamp/ct_clamp_sensor.cpp @@ -52,7 +52,7 @@ void CTClampSensor::loop() { // Perform a single sample float value = this->source_->sample(); - if (isnan(value)) + if (std::isnan(value)) return; this->sample_sum_ += value; diff --git a/esphome/components/ct_clamp/ct_clamp_sensor.h b/esphome/components/ct_clamp/ct_clamp_sensor.h index 2f201c11a0..10601ab852 100644 --- a/esphome/components/ct_clamp/ct_clamp_sensor.h +++ b/esphome/components/ct_clamp/ct_clamp_sensor.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/voltage_sampler/voltage_sampler.h" diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index 847d630e7c..0fc4108687 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -161,7 +161,7 @@ const std::string &DallasTemperatureSensor::get_address_name() { return this->address_name_; } -bool ICACHE_RAM_ATTR DallasTemperatureSensor::read_scratch_pad() { +bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() { ESPOneWire *wire = this->parent_->one_wire_; if (!wire->reset()) { return false; diff --git a/esphome/components/dallas/esp_one_wire.cpp b/esphome/components/dallas/esp_one_wire.cpp index 702d1eddc2..9278b83f7f 100644 --- a/esphome/components/dallas/esp_one_wire.cpp +++ b/esphome/components/dallas/esp_one_wire.cpp @@ -12,11 +12,11 @@ const int ONE_WIRE_ROM_SEARCH = 0xF0; ESPOneWire::ESPOneWire(GPIOPin *pin) : pin_(pin) {} -bool HOT ICACHE_RAM_ATTR ESPOneWire::reset() { +bool HOT IRAM_ATTR ESPOneWire::reset() { uint8_t retries = 125; // Wait for communication to clear - this->pin_->pin_mode(INPUT_PULLUP); + this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); do { if (--retries == 0) return false; @@ -24,12 +24,12 @@ bool HOT ICACHE_RAM_ATTR ESPOneWire::reset() { } while (!this->pin_->digital_read()); // Send 480µs LOW TX reset pulse - this->pin_->pin_mode(OUTPUT); + this->pin_->pin_mode(gpio::FLAG_OUTPUT); this->pin_->digital_write(false); delayMicroseconds(480); // Switch into RX mode, letting the pin float - this->pin_->pin_mode(INPUT_PULLUP); + this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); // after 15µs-60µs wait time, responder pulls low for 60µs-240µs // let's have 70µs just in case delayMicroseconds(70); @@ -39,9 +39,9 @@ bool HOT ICACHE_RAM_ATTR ESPOneWire::reset() { return r; } -void HOT ICACHE_RAM_ATTR ESPOneWire::write_bit(bool bit) { +void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) { // Initiate write/read by pulling low. - this->pin_->pin_mode(OUTPUT); + this->pin_->pin_mode(gpio::FLAG_OUTPUT); this->pin_->digital_write(false); // bus sampled within 15µs and 60µs after pulling LOW. @@ -60,14 +60,14 @@ void HOT ICACHE_RAM_ATTR ESPOneWire::write_bit(bool bit) { } } -bool HOT ICACHE_RAM_ATTR ESPOneWire::read_bit() { +bool HOT IRAM_ATTR ESPOneWire::read_bit() { // Initiate read slot by pulling LOW for at least 1µs - this->pin_->pin_mode(OUTPUT); + this->pin_->pin_mode(gpio::FLAG_OUTPUT); this->pin_->digital_write(false); delayMicroseconds(3); // release bus, we have to sample within 15µs of pulling low - this->pin_->pin_mode(INPUT_PULLUP); + this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); delayMicroseconds(10); bool r = this->pin_->digital_read(); @@ -76,43 +76,43 @@ bool HOT ICACHE_RAM_ATTR ESPOneWire::read_bit() { return r; } -void ICACHE_RAM_ATTR ESPOneWire::write8(uint8_t val) { +void IRAM_ATTR ESPOneWire::write8(uint8_t val) { for (uint8_t i = 0; i < 8; i++) { this->write_bit(bool((1u << i) & val)); } } -void ICACHE_RAM_ATTR ESPOneWire::write64(uint64_t val) { +void IRAM_ATTR ESPOneWire::write64(uint64_t val) { for (uint8_t i = 0; i < 64; i++) { this->write_bit(bool((1ULL << i) & val)); } } -uint8_t ICACHE_RAM_ATTR ESPOneWire::read8() { +uint8_t IRAM_ATTR ESPOneWire::read8() { uint8_t ret = 0; for (uint8_t i = 0; i < 8; i++) { ret |= (uint8_t(this->read_bit()) << i); } return ret; } -uint64_t ICACHE_RAM_ATTR ESPOneWire::read64() { +uint64_t IRAM_ATTR ESPOneWire::read64() { uint64_t ret = 0; for (uint8_t i = 0; i < 8; i++) { ret |= (uint64_t(this->read_bit()) << i); } return ret; } -void ICACHE_RAM_ATTR ESPOneWire::select(uint64_t address) { +void IRAM_ATTR ESPOneWire::select(uint64_t address) { this->write8(ONE_WIRE_ROM_SELECT); this->write64(address); } -void ICACHE_RAM_ATTR ESPOneWire::reset_search() { +void IRAM_ATTR ESPOneWire::reset_search() { this->last_discrepancy_ = 0; this->last_device_flag_ = false; this->last_family_discrepancy_ = 0; this->rom_number_ = 0; } -uint64_t HOT ICACHE_RAM_ATTR ESPOneWire::search() { +uint64_t HOT IRAM_ATTR ESPOneWire::search() { if (this->last_device_flag_) { return 0u; } @@ -196,7 +196,7 @@ uint64_t HOT ICACHE_RAM_ATTR ESPOneWire::search() { return this->rom_number_; } -std::vector ICACHE_RAM_ATTR ESPOneWire::search_vec() { +std::vector IRAM_ATTR ESPOneWire::search_vec() { std::vector res; this->reset_search(); @@ -206,12 +206,12 @@ std::vector ICACHE_RAM_ATTR ESPOneWire::search_vec() { return res; } -void ICACHE_RAM_ATTR ESPOneWire::skip() { +void IRAM_ATTR ESPOneWire::skip() { this->write8(0xCC); // skip ROM } GPIOPin *ESPOneWire::get_pin() { return this->pin_; } -uint8_t ICACHE_RAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast(&this->rom_number_); } +uint8_t IRAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast(&this->rom_number_); } } // namespace dallas } // namespace esphome diff --git a/esphome/components/dallas/esp_one_wire.h b/esphome/components/dallas/esp_one_wire.h index 68bcc0c193..728fa127d3 100644 --- a/esphome/components/dallas/esp_one_wire.h +++ b/esphome/components/dallas/esp_one_wire.h @@ -1,7 +1,8 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" +#include namespace esphome { namespace dallas { diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index 11590a0978..7fd8956148 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -4,8 +4,18 @@ #include "esphome/core/defines.h" #include "esphome/core/version.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include +#include +#endif + +#ifdef USE_ARDUINO +#include +#endif + +#ifdef USE_ESP_IDF +#include +#include #endif namespace esphome { @@ -21,9 +31,14 @@ void DebugComponent::dump_config() { #endif ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION); +#ifdef USE_ARDUINO this->free_heap_ = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) +#elif defined(USE_ESP_IDF) + this->free_heap_ = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); +#endif ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_); +#ifdef USE_ARDUINO const char *flash_mode; switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance) case FM_QIO: @@ -38,7 +53,7 @@ void DebugComponent::dump_config() { case FM_DOUT: flash_mode = "DOUT"; break; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 case FM_FAST_READ: flash_mode = "FAST_READ"; break; @@ -52,8 +67,9 @@ void DebugComponent::dump_config() { // NOLINTNEXTLINE(readability-static-accessed-through-instance) ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", ESP.getFlashChipSize() / 1024, ESP.getFlashChipSpeed() / 1000000, flash_mode); +#endif // USE_ARDUINO -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 esp_chip_info_t info; esp_chip_info(&info); const char *model; @@ -88,7 +104,9 @@ void DebugComponent::dump_config() { ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version()); - std::string mac = uint64_to_string(ESP.getEfuseMac()); // NOLINT(readability-static-accessed-through-instance) + uint64_t chip_mac = 0LL; + esp_efuse_mac_get_default((uint8_t *) (&chip_mac)); + std::string mac = uint64_to_string(chip_mac); ESP_LOGD(TAG, "EFuse MAC: %s", mac.c_str()); const char *reset_reason; @@ -187,7 +205,7 @@ void DebugComponent::dump_config() { ESP_LOGD(TAG, "Wakeup Reason: %s", wakeup_reason); #endif -#if defined(ARDUINO_ARCH_ESP8266) && !defined(CLANG_TIDY) +#if defined(USE_ESP8266) && !defined(CLANG_TIDY) ESP_LOGD(TAG, "Chip ID: 0x%08X", ESP.getChipId()); ESP_LOGD(TAG, "SDK Version: %s", ESP.getSdkVersion()); ESP_LOGD(TAG, "Core Version: %s", ESP.getCoreVersion().c_str()); @@ -199,7 +217,11 @@ void DebugComponent::dump_config() { #endif } void DebugComponent::loop() { +#ifdef USE_ARDUINO uint32_t new_free_heap = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) +#elif defined(USE_ESP_IDF) + uint32_t new_free_heap = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); +#endif if (new_free_heap < this->free_heap_ / 2) { this->free_heap_ = new_free_heap; ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_); diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 1e07b75173..f47888b8eb 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -62,7 +62,7 @@ CONFIG_SCHEMA = cv.Schema( cv.Schema( { cv.Required(CONF_PINS): cv.ensure_list( - pins.shorthand_input_pin, validate_pin_number + pins.internal_gpio_input_pin_schema, validate_pin_number ), cv.Required(CONF_MODE): cv.enum(EXT1_WAKEUP_MODES, upper=True), } diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 9c354c8513..e4b1edfb7b 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -2,6 +2,10 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" +#ifdef USE_ESP8266 +#include +#endif + namespace esphome { namespace deep_sleep { @@ -25,9 +29,9 @@ void DeepSleepComponent::dump_config() { if (this->run_duration_.has_value()) { ESP_LOGCONFIG(TAG, " Run Duration: %u ms", *this->run_duration_); } -#ifdef ARDUINO_ARCH_ESP32 - if (this->wakeup_pin_.has_value()) { - LOG_PIN(" Wakeup Pin: ", *this->wakeup_pin_); +#ifdef USE_ESP32 + if (wakeup_pin_ != nullptr) { + LOG_PIN(" Wakeup Pin: ", this->wakeup_pin_); } #endif } @@ -39,7 +43,7 @@ float DeepSleepComponent::get_loop_priority() const { return -100.0f; // run after everything else is ready } void DeepSleepComponent::set_sleep_duration(uint32_t time_ms) { this->sleep_duration_ = uint64_t(time_ms) * 1000; } -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) { this->wakeup_pin_mode_ = wakeup_pin_mode; } @@ -52,9 +56,9 @@ void DeepSleepComponent::begin_sleep(bool manual) { this->next_enter_deep_sleep_ = true; return; } -#ifdef ARDUINO_ARCH_ESP32 - if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_KEEP_AWAKE && this->wakeup_pin_.has_value() && - !this->sleep_duration_.has_value() && (*this->wakeup_pin_)->digital_read()) { +#ifdef USE_ESP32 + if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_KEEP_AWAKE && this->wakeup_pin_ != nullptr && + !this->sleep_duration_.has_value() && this->wakeup_pin_->digital_read()) { // Defer deep sleep until inactive if (!this->next_enter_deep_sleep_) { this->status_set_warning(); @@ -69,14 +73,14 @@ void DeepSleepComponent::begin_sleep(bool manual) { App.run_safe_shutdown_hooks(); -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 if (this->sleep_duration_.has_value()) esp_sleep_enable_timer_wakeup(*this->sleep_duration_); - if (this->wakeup_pin_.has_value()) { - bool level = !(*this->wakeup_pin_)->is_inverted(); - if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && (*this->wakeup_pin_)->digital_read()) + if (this->wakeup_pin_ != nullptr) { + bool level = this->wakeup_pin_->is_inverted(); + if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) level = !level; - esp_sleep_enable_ext0_wakeup(gpio_num_t((*this->wakeup_pin_)->get_pin()), level); + esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level); } if (this->ext1_wakeup_.has_value()) { esp_sleep_enable_ext1_wakeup(this->ext1_wakeup_->mask, this->ext1_wakeup_->wakeup_mode); @@ -90,7 +94,7 @@ void DeepSleepComponent::begin_sleep(bool manual) { esp_deep_sleep_start(); #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 ESP.deepSleep(*this->sleep_duration_); // NOLINT(readability-static-accessed-through-instance) #endif } diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index 575f7be72b..d7969ba999 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -3,12 +3,16 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/automation.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" + +#ifdef USE_ESP32 +#include +#endif namespace esphome { namespace deep_sleep { -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 /** The values of this enum define what should be done if deep sleep is set up with a wakeup pin on the ESP32 * and the scenario occurs that the wakeup pin is already in the wakeup state. @@ -44,11 +48,11 @@ class DeepSleepComponent : public Component { public: /// Set the duration in ms the component should sleep once it's in deep sleep mode. void set_sleep_duration(uint32_t time_ms); -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 /** Set the pin to wake up to on the ESP32 once it's in deep sleep mode. * Use the inverted property to set the wakeup level. */ - void set_wakeup_pin(GPIOPin *pin) { this->wakeup_pin_ = pin; } + void set_wakeup_pin(InternalGPIOPin *pin) { this->wakeup_pin_ = pin; } void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode); @@ -72,8 +76,8 @@ class DeepSleepComponent : public Component { protected: optional sleep_duration_; -#ifdef ARDUINO_ARCH_ESP32 - optional wakeup_pin_; +#ifdef USE_ESP32 + InternalGPIOPin *wakeup_pin_; WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE}; optional ext1_wakeup_; optional touch_wakeup_; diff --git a/esphome/components/demo/demo_sensor.h b/esphome/components/demo/demo_sensor.h index 9a35674124..b4afa03e11 100644 --- a/esphome/components/demo/demo_sensor.h +++ b/esphome/components/demo/demo_sensor.h @@ -13,7 +13,7 @@ class DemoSensor : public sensor::Sensor, public PollingComponent { float val = random_float(); bool increasing = this->get_state_class() == sensor::STATE_CLASS_TOTAL_INCREASING; if (increasing) { - float base = isnan(this->state) ? 0.0f : this->state; + float base = std::isnan(this->state) ? 0.0f : this->state; this->publish_state(base + val * 10); } else { if (val < 0.1) diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index 734dde20a8..2539bfe5ee 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -71,7 +71,7 @@ void DHT::set_dht_model(DHTModel model) { this->model_ = model; this->is_auto_detect_ = model == DHT_MODEL_AUTO_DETECT; } -bool HOT ICACHE_RAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool report_errors) { +bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool report_errors) { *humidity = NAN; *temperature = NAN; @@ -83,7 +83,7 @@ bool HOT ICACHE_RAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, InterruptLock lock; this->pin_->digital_write(false); - this->pin_->pin_mode(OUTPUT); + this->pin_->pin_mode(gpio::FLAG_OUTPUT); this->pin_->digital_write(false); if (this->model_ == DHT_MODEL_DHT11) { @@ -99,7 +99,7 @@ bool HOT ICACHE_RAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, } else { delayMicroseconds(800); } - this->pin_->pin_mode(INPUT_PULLUP); + this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); // Host pull up 20-40us then DHT response 80us // Start waiting for initial rising edge at the center when we diff --git a/esphome/components/dht/dht.h b/esphome/components/dht/dht.h index 9b848cb119..f3a29f9ce9 100644 --- a/esphome/components/dht/dht.h +++ b/esphome/components/dht/dht.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" namespace esphome { @@ -36,7 +36,7 @@ class DHT : public PollingComponent { */ void set_dht_model(DHTModel model); - void set_pin(GPIOPin *pin) { pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } void set_model(DHTModel model) { model_ = model; } void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } @@ -52,7 +52,7 @@ class DHT : public PollingComponent { protected: bool read_sensor_(float *temperature, float *humidity, bool report_errors); - GPIOPin *pin_; + InternalGPIOPin *pin_; DHTModel model_{DHT_MODEL_AUTO_DETECT}; bool is_auto_detect_{false}; sensor::Sensor *temperature_sensor_{nullptr}; diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index bbb7444cb5..2ee06e379f 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -4,6 +4,7 @@ #include "esphome/core/application.h" #include "esphome/core/color.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace display { @@ -372,7 +373,7 @@ bool Glyph::get_pixel(int x, int y) const { return false; const uint32_t width_8 = ((this->glyph_data_->width + 7u) / 8u) * 8u; const uint32_t pos = x_data + y_data * width_8; - return pgm_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u)); + return progmem_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u)); } const char *Glyph::get_char() const { return this->glyph_data_->a_char; } bool Glyph::compare_to(const char *str) const { @@ -464,22 +465,22 @@ bool Image::get_pixel(int x, int y) const { return false; const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; const uint32_t pos = x + y * width_8; - return pgm_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); + return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); } Color Image::get_color_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) return Color::BLACK; const uint32_t pos = (x + y * this->width_) * 3; - const uint32_t color32 = (pgm_read_byte(this->data_start_ + pos + 2) << 0) | - (pgm_read_byte(this->data_start_ + pos + 1) << 8) | - (pgm_read_byte(this->data_start_ + pos + 0) << 16); + const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) | + (progmem_read_byte(this->data_start_ + pos + 1) << 8) | + (progmem_read_byte(this->data_start_ + pos + 0) << 16); return Color(color32); } Color Image::get_grayscale_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) return Color::BLACK; const uint32_t pos = (x + y * this->width_); - const uint8_t gray = pgm_read_byte(this->data_start_ + pos); + const uint8_t gray = progmem_read_byte(this->data_start_ + pos); return Color(gray | gray << 8 | gray << 16 | gray << 24); } int Image::get_width() const { return this->width_; } @@ -496,7 +497,7 @@ bool Animation::get_pixel(int x, int y) const { if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) return false; const uint32_t pos = x + y * width_8 + frame_index; - return pgm_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); + return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); } Color Animation::get_color_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) @@ -505,9 +506,9 @@ Color Animation::get_color_pixel(int x, int y) const { if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) return Color::BLACK; const uint32_t pos = (x + y * this->width_ + frame_index) * 3; - const uint32_t color32 = (pgm_read_byte(this->data_start_ + pos + 2) << 0) | - (pgm_read_byte(this->data_start_ + pos + 1) << 8) | - (pgm_read_byte(this->data_start_ + pos + 0) << 16); + const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) | + (progmem_read_byte(this->data_start_ + pos + 1) << 8) | + (progmem_read_byte(this->data_start_ + pos + 0) << 16); return Color(color32); } Color Animation::get_grayscale_pixel(int x, int y) const { @@ -517,7 +518,7 @@ Color Animation::get_grayscale_pixel(int x, int y) const { if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) return Color::BLACK; const uint32_t pos = (x + y * this->width_ + frame_index); - const uint8_t gray = pgm_read_byte(this->data_start_ + pos); + const uint8_t gray = progmem_read_byte(this->data_start_ + pos); return Color(gray | gray << 8 | gray << 16 | gray << 24); } Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type) diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index 3f89d3f8d2..54488f18f7 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -4,6 +4,7 @@ #include "esphome/core/defines.h" #include "esphome/core/automation.h" #include "display_color_utils.h" +#include #ifdef USE_TIME #include "esphome/components/time/real_time_clock.h" diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py index 1f1a2f980e..dd6e6051aa 100644 --- a/esphome/components/dsmr/__init__.py +++ b/esphome/components/dsmr/__init__.py @@ -39,14 +39,17 @@ def _validate_key(value): return "".join(f"{part:02X}" for part in parts_int) -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(Dsmr), - cv.Optional(CONF_DECRYPTION_KEY): _validate_key, - cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean, - cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_, - } -).extend(uart.UART_DEVICE_SCHEMA) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(Dsmr), + cv.Optional(CONF_DECRYPTION_KEY): _validate_key, + cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean, + cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_, + } + ).extend(uart.UART_DEVICE_SCHEMA), + cv.only_with_arduino, +) async def to_code(config): diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index 7c1b406d42..54c4343cfe 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "dsmr.h" #include "esphome/core/log.h" @@ -128,8 +130,9 @@ void Dsmr::receive_encrypted_() { delay(4); // Wait for data } } - if (buffer_length > 0) + if (buffer_length > 0) { ESP_LOGW(TAG, "Timeout while waiting for encrypted data or invalid data received."); + } } bool Dsmr::parse_telegram() { @@ -186,3 +189,5 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) { } // namespace dsmr } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index dcb8f5f73d..dfee3b338a 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/text_sensor/text_sensor.h" @@ -103,3 +105,5 @@ class Dsmr : public Component, public uart::UARTDevice { }; } // namespace dsmr } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.cpp b/esphome/components/duty_cycle/duty_cycle_sensor.cpp index c989421948..3d7f731d5d 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.cpp +++ b/esphome/components/duty_cycle/duty_cycle_sensor.cpp @@ -15,7 +15,7 @@ void DutyCycleSensor::setup() { this->last_update_ = micros(); this->store_.last_interrupt = micros(); - this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, CHANGE); + this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); } void DutyCycleSensor::dump_config() { LOG_SENSOR("", "Duty Cycle Sensor", this); @@ -44,8 +44,8 @@ void DutyCycleSensor::update() { float DutyCycleSensor::get_setup_priority() const { return setup_priority::DATA; } -void ICACHE_RAM_ATTR DutyCycleSensorStore::gpio_intr(DutyCycleSensorStore *arg) { - const bool new_level = arg->pin->digital_read(); +void IRAM_ATTR DutyCycleSensorStore::gpio_intr(DutyCycleSensorStore *arg) { + const bool new_level = arg->pin.digital_read(); if (new_level == arg->last_level) return; arg->last_level = new_level; diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.h b/esphome/components/duty_cycle/duty_cycle_sensor.h index e168f20eff..22d3588fb7 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.h +++ b/esphome/components/duty_cycle/duty_cycle_sensor.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" namespace esphome { @@ -12,14 +12,14 @@ struct DutyCycleSensorStore { volatile uint32_t last_interrupt{0}; volatile uint32_t on_time{0}; volatile bool last_level{false}; - ISRInternalGPIOPin *pin; + ISRInternalGPIOPin pin; static void gpio_intr(DutyCycleSensorStore *arg); }; class DutyCycleSensor : public sensor::Sensor, public PollingComponent { public: - void set_pin(GPIOPin *pin) { pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } void setup() override; float get_setup_priority() const override; @@ -27,7 +27,7 @@ class DutyCycleSensor : public sensor::Sensor, public PollingComponent { void update() override; protected: - GPIOPin *pin_; + InternalGPIOPin *pin_; DutyCycleSensorStore store_{}; uint32_t last_update_; diff --git a/esphome/components/duty_cycle/sensor.py b/esphome/components/duty_cycle/sensor.py index 3537cb0973..6a367328e6 100644 --- a/esphome/components/duty_cycle/sensor.py +++ b/esphome/components/duty_cycle/sensor.py @@ -25,9 +25,7 @@ CONFIG_SCHEMA = ( .extend( { cv.GenerateID(): cv.declare_id(DutyCycleSensor), - cv.Required(CONF_PIN): cv.All( - pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt - ), + cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema), } ) .extend(cv.polling_component_schema("60s")) diff --git a/esphome/components/e131/__init__.py b/esphome/components/e131/__init__.py index 5eb823064d..bb662e0989 100644 --- a/esphome/components/e131/__init__.py +++ b/esphome/components/e131/__init__.py @@ -4,6 +4,8 @@ from esphome.components.light.types import AddressableLightEffect from esphome.components.light.effects import register_addressable_effect from esphome.const import CONF_ID, CONF_NAME, CONF_METHOD, CONF_CHANNELS +DEPENDENCIES = ["network"] + e131_ns = cg.esphome_ns.namespace("e131") E131AddressableLightEffect = e131_ns.class_( "E131AddressableLightEffect", AddressableLightEffect @@ -21,11 +23,16 @@ CHANNELS = { CONF_UNIVERSE = "universe" CONF_E131_ID = "e131_id" -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(E131Component), - cv.Optional(CONF_METHOD, default="MULTICAST"): cv.one_of(*METHODS, upper=True), - } +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(E131Component), + cv.Optional(CONF_METHOD, default="MULTICAST"): cv.one_of( + *METHODS, upper=True + ), + } + ), + cv.only_with_arduino, ) diff --git a/esphome/components/e131/e131.cpp b/esphome/components/e131/e131.cpp index 7694d039e5..35510fe204 100644 --- a/esphome/components/e131/e131.cpp +++ b/esphome/components/e131/e131.cpp @@ -1,12 +1,14 @@ +#ifdef USE_ARDUINO + #include "e131.h" #include "e131_addressable_light_effect.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include #include #endif @@ -104,3 +106,5 @@ bool E131Component::process_(int universe, const E131Packet &packet) { } // namespace e131 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/e131/e131.h b/esphome/components/e131/e131.h index 3f647edbf1..3819e522a5 100644 --- a/esphome/components/e131/e131.h +++ b/esphome/components/e131/e131.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include @@ -55,3 +57,5 @@ class E131Component : public esphome::Component { } // namespace e131 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/e131/e131_addressable_light_effect.cpp b/esphome/components/e131/e131_addressable_light_effect.cpp index f0f165b25f..371f3b9cbf 100644 --- a/esphome/components/e131/e131_addressable_light_effect.cpp +++ b/esphome/components/e131/e131_addressable_light_effect.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "e131.h" #include "e131_addressable_light_effect.h" #include "esphome/core/log.h" @@ -90,3 +92,5 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet } // namespace e131 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/e131/e131_addressable_light_effect.h b/esphome/components/e131/e131_addressable_light_effect.h index 1ab5d43164..e78f6bb0e0 100644 --- a/esphome/components/e131/e131_addressable_light_effect.h +++ b/esphome/components/e131/e131_addressable_light_effect.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include "esphome/components/light/addressable_light_effect.h" @@ -46,3 +48,5 @@ class E131AddressableLightEffect : public light::AddressableLightEffect { } // namespace e131 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/e131/e131_packet.cpp b/esphome/components/e131/e131_packet.cpp index 14fdc084a6..b20eb9f666 100644 --- a/esphome/components/e131/e131_packet.cpp +++ b/esphome/components/e131/e131_packet.cpp @@ -1,8 +1,14 @@ +#ifdef USE_ARDUINO + #include "e131.h" #include "esphome/core/log.h" #include "esphome/core/util.h" +#include "esphome/components/network/ip_address.h" +#include +#include #include +#include #include namespace esphome { @@ -63,8 +69,8 @@ bool E131Component::join_igmp_groups_() { if (!universe.second) continue; - ip4_addr_t multicast_addr = { - static_cast(IPAddress(239, 255, ((universe.first >> 8) & 0xff), ((universe.first >> 0) & 0xff)))}; + ip4_addr_t multicast_addr = {static_cast( + network::IPAddress(239, 255, ((universe.first >> 8) & 0xff), ((universe.first >> 0) & 0xff)))}; auto err = igmp_joingroup(IP4_ADDR_ANY4, &multicast_addr); @@ -98,7 +104,7 @@ void E131Component::leave_(int universe) { if (listen_method_ == E131_MULTICAST) { ip4_addr_t multicast_addr = { - static_cast(IPAddress(239, 255, ((universe >> 8) & 0xff), ((universe >> 0) & 0xff)))}; + static_cast(network::IPAddress(239, 255, ((universe >> 8) & 0xff), ((universe >> 0) & 0xff)))}; igmp_leavegroup(IP4_ADDR_ANY4, &multicast_addr); } @@ -134,3 +140,5 @@ bool E131Component::packet_(const std::vector &data, int &universe, E13 } // namespace e131 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/endstop/endstop_cover.cpp b/esphome/components/endstop/endstop_cover.cpp index cbc4b334d9..67c6a4ebd3 100644 --- a/esphome/components/endstop/endstop_cover.cpp +++ b/esphome/components/endstop/endstop_cover.cpp @@ -1,5 +1,6 @@ #include "endstop_cover.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace endstop { diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py new file mode 100644 index 0000000000..c86087cc25 --- /dev/null +++ b/esphome/components/esp32/__init__.py @@ -0,0 +1,379 @@ +from dataclasses import dataclass +from typing import Union +from pathlib import Path +import logging + +from esphome.helpers import write_file_if_changed +from esphome.const import ( + CONF_BOARD, + CONF_FRAMEWORK, + CONF_TYPE, + CONF_VARIANT, + CONF_VERSION, + KEY_CORE, + KEY_FRAMEWORK_VERSION, + KEY_TARGET_FRAMEWORK, + KEY_TARGET_PLATFORM, +) +from esphome.core import CORE, HexInt +import esphome.config_validation as cv +import esphome.codegen as cg + +from .const import ( + KEY_BOARD, + KEY_ESP32, + KEY_SDKCONFIG_OPTIONS, + KEY_VARIANT, + VARIANT_ESP32C3, + VARIANTS, +) + +# force import gpio to register pin schema +from .gpio import esp32_pin_to_code # noqa + + +_LOGGER = logging.getLogger(__name__) +CODEOWNERS = ["@esphome/core"] + + +def set_core_data(config): + CORE.data[KEY_ESP32] = {} + CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "esp32" + conf = config[CONF_FRAMEWORK] + if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: + CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "esp-idf" + CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] = {} + elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: + CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" + CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( + config[CONF_FRAMEWORK][CONF_VERSION_HINT] + ) + CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD] + CORE.data[KEY_ESP32][KEY_VARIANT] = config[CONF_VARIANT] + return config + + +def get_esp32_variant(): + return CORE.data[KEY_ESP32][KEY_VARIANT] + + +def is_esp32c3(): + return get_esp32_variant() == VARIANT_ESP32C3 + + +@dataclass +class RawSdkconfigValue: + """An sdkconfig value that won't be auto-formatted""" + + value: str + + +SdkconfigValueType = Union[bool, int, HexInt, str, RawSdkconfigValue] + + +def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType): + """Set an esp-idf sdkconfig value.""" + if not CORE.using_esp_idf: + raise ValueError("Not an esp-idf project") + CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS][name] = value + + +def _format_framework_arduino_version(ver: cv.Version) -> str: + # format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to + # a PIO platformio/framework-arduinoespressif32 value + # List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif32 + if ver <= cv.Version(1, 0, 3): + return f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + + +# NOTE: Keep this in mind when updating the recommended version: +# * New framework historically have had some regressions, especially for WiFi. +# The new version needs to be thoroughly validated before changing the +# recommended version as otherwise a bunch of devices could be bricked +# * For all constants below, update platformio.ini (in this repo) +# and platformio.ini/platformio-lint.ini in the esphome-docker-base repository + +# The default/recommended arduino framework version +# - https://github.com/espressif/arduino-esp32/releases +# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif32 +RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(1, 0, 6) +# The platformio/espressif32 version to use for arduino frameworks +# - https://github.com/platformio/platform-espressif32/releases +# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 +ARDUINO_PLATFORM_VERSION = cv.Version(3, 3, 2) + +# The default/recommended esp-idf framework version +# - https://github.com/espressif/esp-idf/releases +# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf +RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 3, 0) +# The platformio/espressif32 version to use for esp-idf frameworks +# - https://github.com/platformio/platform-espressif32/releases +# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 +ESP_IDF_PLATFORM_VERSION = cv.Version(3, 3, 2) + + +def _arduino_check_versions(value): + value = value.copy() + lookups = { + "dev": ("https://github.com/espressif/arduino-esp32.git", cv.Version(2, 0, 0)), + "latest": ("", cv.Version(1, 0, 3)), + "recommended": ( + _format_framework_arduino_version(RECOMMENDED_ARDUINO_FRAMEWORK_VERSION), + RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, + ), + } + ver_value = value[CONF_VERSION] + default_ver_hint = None + if ver_value.lower() in lookups: + default_ver_hint = str(lookups[ver_value.lower()][1]) + ver_value = lookups[ver_value.lower()][0] + else: + with cv.suppress_invalid(): + ver = cv.Version.parse(cv.version_number(value)) + if ver <= cv.Version(1, 0, 3): + ver_value = f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + else: + ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + default_ver_hint = str(ver) + value[CONF_VERSION] = ver_value + + if CONF_VERSION_HINT not in value and default_ver_hint is None: + raise cv.Invalid("Needs a version hint to understand the framework version") + + ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint) + value[CONF_VERSION_HINT] = ver_hint_s + plat_ver = value.get(CONF_PLATFORM_VERSION, ARDUINO_PLATFORM_VERSION) + value[CONF_PLATFORM_VERSION] = str(plat_ver) + + if cv.Version.parse(ver_hint_s) != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: + _LOGGER.warning( + "The selected arduino framework version is not the recommended one" + ) + _LOGGER.warning( + "If there are connectivity or build issues please remove the manual version" + ) + + return value + + +def _format_framework_espidf_version(ver: cv.Version) -> str: + # format the given arduino (https://github.com/espressif/esp-idf/releases) version to + # a PIO platformio/framework-espidf value + # List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf + return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + + +def _esp_idf_check_versions(value): + value = value.copy() + lookups = { + "dev": ("https://github.com/espressif/esp-idf.git", cv.Version(4, 3, 1)), + "latest": ("", cv.Version(4, 3, 0)), + "recommended": ( + _format_framework_espidf_version(RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION), + RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, + ), + } + ver_value = value[CONF_VERSION] + default_ver_hint = None + if ver_value.lower() in lookups: + default_ver_hint = str(lookups[ver_value.lower()][1]) + ver_value = lookups[ver_value.lower()][0] + else: + with cv.suppress_invalid(): + ver = cv.Version.parse(cv.version_number(value)) + ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + default_ver_hint = str(ver) + value[CONF_VERSION] = ver_value + + if CONF_VERSION_HINT not in value and default_ver_hint is None: + raise cv.Invalid("Needs a version hint to understand the framework version") + + ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint) + value[CONF_VERSION_HINT] = ver_hint_s + if cv.Version.parse(ver_hint_s) < cv.Version(4, 0, 0): + raise cv.Invalid("Only ESP-IDF 4.0+ is supported") + if cv.Version.parse(ver_hint_s) != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION: + _LOGGER.warning( + "The selected esp-idf framework version is not the recommended one" + ) + _LOGGER.warning( + "If there are connectivity or build issues please remove the manual version" + ) + + plat_ver = value.get(CONF_PLATFORM_VERSION, ESP_IDF_PLATFORM_VERSION) + value[CONF_PLATFORM_VERSION] = str(plat_ver) + + return value + + +CONF_VERSION_HINT = "version_hint" +CONF_PLATFORM_VERSION = "platform_version" +ARDUINO_FRAMEWORK_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, + cv.Optional(CONF_VERSION_HINT): cv.version_number, + cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, + } + ), + _arduino_check_versions, +) +CONF_SDKCONFIG_OPTIONS = "sdkconfig_options" +ESP_IDF_FRAMEWORK_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, + cv.Optional(CONF_VERSION_HINT): cv.version_number, + cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): { + cv.string_strict: cv.string_strict + }, + cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, + } + ), + _esp_idf_check_versions, +) + + +FRAMEWORK_ESP_IDF = "esp-idf" +FRAMEWORK_ARDUINO = "arduino" +FRAMEWORK_SCHEMA = cv.typed_schema( + { + FRAMEWORK_ESP_IDF: ESP_IDF_FRAMEWORK_SCHEMA, + FRAMEWORK_ARDUINO: ARDUINO_FRAMEWORK_SCHEMA, + }, + lower=True, + space="-", + default_type=FRAMEWORK_ARDUINO, +) + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_BOARD): cv.string_strict, + cv.Optional(CONF_VARIANT, default="ESP32"): cv.one_of( + *VARIANTS, upper=True + ), + cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA, + } + ), + set_core_data, +) + + +async def to_code(config): + cg.add_platformio_option("board", config[CONF_BOARD]) + cg.add_build_flag("-DUSE_ESP32") + cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) + cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}") + + conf = config[CONF_FRAMEWORK] + if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: + cg.add_platformio_option( + "platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}" + ) + cg.add_platformio_option("framework", "espidf") + cg.add_build_flag("-DUSE_ESP_IDF") + cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF") + cg.add_build_flag("-Wno-nonnull-compare") + cg.add_platformio_option( + "platform_packages", + [f"platformio/framework-espidf @ {conf[CONF_VERSION]}"], + ) + add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) + add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) + add_idf_sdkconfig_option( + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME", "partitions.csv" + ) + add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False) + add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True) + + cg.add_platformio_option("board_build.partitions", "partitions.csv") + + for name, value in conf[CONF_SDKCONFIG_OPTIONS].items(): + add_idf_sdkconfig_option(name, RawSdkconfigValue(value)) + + elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: + cg.add_platformio_option( + "platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}" + ) + cg.add_platformio_option("framework", "arduino") + cg.add_build_flag("-DUSE_ARDUINO") + cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") + cg.add_platformio_option( + "platform_packages", + [f"platformio/framework-arduinoespressif32 @ {conf[CONF_VERSION]}"], + ) + + cg.add_platformio_option("board_build.partitions", "partitions.csv") + + +ARDUINO_PARTITIONS_CSV = """\ +nvs, data, nvs, 0x009000, 0x005000, +otadata, data, ota, 0x00e000, 0x002000, +app0, app, ota_0, 0x010000, 0x1C0000, +app1, app, ota_1, 0x1D0000, 0x1C0000, +eeprom, data, 0x99, 0x390000, 0x001000, +spiffs, data, spiffs, 0x391000, 0x00F000 +""" + + +IDF_PARTITIONS_CSV = """\ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, , 0x4000, +otadata, data, ota, , 0x2000, +phy_init, data, phy, , 0x1000, +app0, app, ota_0, , 0x1C0000, +app1, app, ota_1, , 0x1C0000, +""" + + +def _format_sdkconfig_val(value: SdkconfigValueType) -> str: + if isinstance(value, bool): + return "y" if value else "n" + if isinstance(value, int): + return str(value) + if isinstance(value, str): + return f'"{value}"' + if isinstance(value, RawSdkconfigValue): + return value.value + raise ValueError + + +def _write_sdkconfig(): + # sdkconfig.{name} stores the real sdkconfig (modified by esp-idf with default) + # sdkconfig.{name}.esphomeinternal stores what esphome last wrote + # we use the internal one to detect if there were any changes, and if so write them to the + # real sdkconfig + sdk_path = Path(CORE.relative_build_path(f"sdkconfig.{CORE.name}")) + internal_path = Path( + CORE.relative_build_path(f"sdkconfig.{CORE.name}.esphomeinternal") + ) + + want_opts = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] + contents = ( + "\n".join( + f"{name}={_format_sdkconfig_val(value)}" + for name, value in sorted(want_opts.items()) + ) + + "\n" + ) + if write_file_if_changed(internal_path, contents): + # internal changed, update real one + write_file_if_changed(sdk_path, contents) + + +# Called by writer.py +def copy_files(): + if CORE.using_arduino: + write_file_if_changed( + CORE.relative_build_path("partitions.csv"), + ARDUINO_PARTITIONS_CSV, + ) + if CORE.using_esp_idf: + _write_sdkconfig() + write_file_if_changed( + CORE.relative_build_path("partitions.csv"), + IDF_PARTITIONS_CSV, + ) diff --git a/esphome/boards.py b/esphome/components/esp32/boards.py similarity index 77% rename from esphome/boards.py rename to esphome/components/esp32/boards.py index ba6fe889ea..ddf4bf2026 100644 --- a/esphome/boards.py +++ b/esphome/components/esp32/boards.py @@ -1,212 +1,3 @@ -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}, - "espmxdevkit": {}, - "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}, - "sonoff_basic": {}, - "sonoff_s20": {}, - "sonoff_sv": {}, - "sonoff_th": {}, - "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, - "espmxdevkit": FLASH_SIZE_1_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, - "sonoff_basic": FLASH_SIZE_1_MB, - "sonoff_s20": FLASH_SIZE_1_MB, - "sonoff_sv": FLASH_SIZE_1_MB, - "sonoff_th": FLASH_SIZE_1_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, @@ -1134,19 +925,3 @@ ESP32_BOARD_PINS = { }, "xinabox_cw02": {"LED": 27}, } - -ESP32_C3_BASE_PINS = { - "TX": 21, - "RX": 20, - "ADC1_0": 0, - "ADC1_1": 1, - "ADC1_2": 2, - "ADC1_3": 3, - "ADC1_4": 4, - "ADC2_0": 5, -} - -ESP32_C3_BOARD_PINS = { - "esp32-c3-devkitm-1": {"LED": 8}, - "esp32-c3-devkitc-02": "esp32-c3-devkitm-1", -} diff --git a/esphome/components/esp32/const.py b/esphome/components/esp32/const.py new file mode 100644 index 0000000000..b82f03bf68 --- /dev/null +++ b/esphome/components/esp32/const.py @@ -0,0 +1,21 @@ +import esphome.codegen as cg + +KEY_ESP32 = "esp32" +KEY_BOARD = "board" +KEY_VARIANT = "variant" +KEY_SDKCONFIG_OPTIONS = "sdkconfig_options" + +VARIANT_ESP32 = "ESP32" +VARIANT_ESP32S2 = "ESP32S2" +VARIANT_ESP32S3 = "ESP32S3" +VARIANT_ESP32C3 = "ESP32C3" +VARIANT_ESP32H2 = "ESP32H2" +VARIANTS = [ + VARIANT_ESP32, + VARIANT_ESP32S2, + VARIANT_ESP32S3, + VARIANT_ESP32C3, + VARIANT_ESP32H2, +] + +esp32_ns = cg.esphome_ns.namespace("esp32") diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp new file mode 100644 index 0000000000..96047df535 --- /dev/null +++ b/esphome/components/esp32/core.cpp @@ -0,0 +1,89 @@ +#ifdef USE_ESP32 + +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "preferences.h" +#include +#include +#include +#include + +#if ESP_IDF_VERSION_MAJOR >= 4 +#include +#endif + +void setup(); +void loop(); + +namespace esphome { + +void IRAM_ATTR HOT yield() { vPortYield(); } +uint32_t IRAM_ATTR HOT millis() { return (uint32_t)(esp_timer_get_time() / 1000ULL); } +void IRAM_ATTR HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); } +uint32_t IRAM_ATTR HOT micros() { return (uint32_t) esp_timer_get_time(); } +void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { + auto start = (uint64_t) esp_timer_get_time(); + while (((uint64_t) esp_timer_get_time()) - start < us) + ; +} +void arch_restart() { + esp_restart(); + // restart() doesn't always end execution + while (true) { // NOLINT(clang-diagnostic-unreachable-code) + yield(); + } +} +void IRAM_ATTR HOT arch_feed_wdt() { +#ifdef USE_ARDUINO +#if CONFIG_ARDUINO_RUNNING_CORE == 0 +#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 + // ESP32 uses "Task Watchdog" which is hooked to the FreeRTOS idle task. + // To cause the Watchdog to be triggered we need to put the current task + // to sleep to get the idle task scheduled. + delay(1); +#endif +#endif +#endif // USE_ARDUINO + +#ifdef USE_ESP_IDF +#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 + delay(1); +#endif +#endif // USE_ESP_IDF +} + +uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } +uint32_t arch_get_cpu_cycle_count() { +#if ESP_IDF_VERSION_MAJOR >= 4 + return cpu_hal_get_cycle_count(); +#else + uint32_t ccount; + __asm__ __volatile__("esync; rsr %0,ccount" : "=a"(ccount)); + return ccount; +#endif +} +uint32_t arch_get_cpu_freq_hz() { return rtc_clk_apb_freq_get(); } + +#ifdef USE_ESP_IDF +TaskHandle_t loop_task_handle = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +void loop_task(void *pv_params) { + setup(); + while (true) { + loop(); + } +} + +extern "C" void app_main() { + esp32::setup_preferences(); + xTaskCreate(loop_task, "loopTask", 8192, nullptr, 1, &loop_task_handle); +} +#endif // USE_ESP_IDF + +#ifdef USE_ARDUINO +extern "C" void init() { esp32::setup_preferences(); } +#endif // USE_ARDUINO + +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32/gpio.py b/esphome/components/esp32/gpio.py new file mode 100644 index 0000000000..93ab17db22 --- /dev/null +++ b/esphome/components/esp32/gpio.py @@ -0,0 +1,201 @@ +import logging + +from esphome.const import ( + CONF_ID, + CONF_INPUT, + CONF_INVERTED, + CONF_MODE, + CONF_NUMBER, + CONF_OPEN_DRAIN, + CONF_OUTPUT, + CONF_PULLDOWN, + CONF_PULLUP, +) +from esphome import pins +from esphome.core import CORE +import esphome.config_validation as cv +import esphome.codegen as cg + +from . import boards +from .const import KEY_BOARD, KEY_ESP32, esp32_ns + + +_LOGGER = logging.getLogger(__name__) + + +IDFInternalGPIOPin = esp32_ns.class_("IDFInternalGPIOPin", cg.InternalGPIOPin) +ArduinoInternalGPIOPin = esp32_ns.class_("ArduinoInternalGPIOPin", cg.InternalGPIOPin) + + +def _lookup_pin(value): + board = CORE.data[KEY_ESP32][KEY_BOARD] + board_pins = boards.ESP32_BOARD_PINS.get(board, {}) + + # Resolved aliased board pins (shorthand when two boards have the same pin configuration) + while isinstance(board_pins, str): + board_pins = boards.ESP32_BOARD_PINS[board_pins] + + if value in board_pins: + return board_pins[value] + if value in boards.ESP32_BASE_PINS: + return boards.ESP32_BASE_PINS[value] + raise cv.Invalid(f"Cannot resolve pin name '{value}' for board {board}.") + + +def _translate_pin(value): + if isinstance(value, dict) or value is None: + raise cv.Invalid( + "This variable only supports pin numbers, not full pin schemas " + "(with inverted and mode)." + ) + if isinstance(value, int): + return value + try: + return int(value) + except ValueError: + pass + if value.startswith("GPIO"): + return cv.int_(value[len("GPIO") :].strip()) + return _lookup_pin(value) + + +_ESP_SDIO_PINS = { + 6: "Flash Clock", + 7: "Flash Data 0", + 8: "Flash Data 1", + 11: "Flash Command", +} + + +def validate_gpio_pin(value): + value = _translate_pin(value) + if value < 0 or value > 39: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)") + if value in _ESP_SDIO_PINS: + raise cv.Invalid( + f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" + ) + if 9 <= value <= 10: + _LOGGER.warning( + "Pin %s (9-10) might already be used by the " + "flash interface in QUAD IO flash mode.", + value, + ) + if value in (20, 24, 28, 29, 30, 31): + # These pins are not exposed in GPIO mux (reason unknown) + # but they're missing from IO_MUX list in datasheet + raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.") + return value + + +def validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + is_output = mode[CONF_OUTPUT] + is_open_drain = mode[CONF_OPEN_DRAIN] + is_pullup = mode[CONF_PULLUP] + is_pulldown = mode[CONF_PULLDOWN] + + if is_input: + # All ESP32 pins support input mode + pass + if is_output and 34 <= num <= 39: + raise cv.Invalid( + f"GPIO{num} (34-39) does not support output pin mode.", + [CONF_MODE, CONF_OUTPUT], + ) + if is_open_drain and not is_output: + raise cv.Invalid( + "Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN] + ) + if is_pullup and 34 <= num <= 39: + raise cv.Invalid( + f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP] + ) + if is_pulldown and 34 <= num <= 39: + raise cv.Invalid( + f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN] + ) + + if CORE.using_arduino: + # (input, output, open_drain, pullup, pulldown) + supported_modes = { + # INPUT + (True, False, False, False, False), + # OUTPUT + (False, True, False, False, False), + # INPUT_PULLUP + (True, False, False, True, False), + # INPUT_PULLDOWN + (True, False, False, False, True), + # OUTPUT_OPEN_DRAIN + (False, True, True, False, False), + } + key = (is_input, is_output, is_open_drain, is_pullup, is_pulldown) + if key not in supported_modes: + raise cv.Invalid( + "This pin mode is not supported on ESP32 for arduino frameworks", + [CONF_MODE], + ) + + return value + + +# https://docs.espressif.com/projects/esp-idf/en/v3.3.5/api-reference/peripherals/gpio.html#_CPPv416gpio_drive_cap_t +gpio_drive_cap_t = cg.global_ns.enum("gpio_drive_cap_t") +DRIVE_STRENGTHS = { + 5.0: gpio_drive_cap_t.GPIO_DRIVE_CAP_0, + 10.0: gpio_drive_cap_t.GPIO_DRIVE_CAP_1, + 20.0: gpio_drive_cap_t.GPIO_DRIVE_CAP_2, + 40.0: gpio_drive_cap_t.GPIO_DRIVE_CAP_3, +} +gpio_num_t = cg.global_ns.enum("gpio_num_t") + + +def _choose_pin_declaration(value): + if CORE.using_esp_idf: + return cv.declare_id(IDFInternalGPIOPin)(value) + if CORE.using_arduino: + return cv.declare_id(ArduinoInternalGPIOPin)(value) + raise NotImplementedError + + +CONF_DRIVE_STRENGTH = "drive_strength" +ESP32_PIN_SCHEMA = cv.All( + { + cv.GenerateID(): _choose_pin_declaration, + cv.Required(CONF_NUMBER): validate_gpio_pin, + cv.Optional(CONF_MODE, default={}): cv.Schema( + { + cv.Optional(CONF_INPUT, default=False): cv.boolean, + cv.Optional(CONF_OUTPUT, default=False): cv.boolean, + cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean, + cv.Optional(CONF_PULLUP, default=False): cv.boolean, + cv.Optional(CONF_PULLDOWN, default=False): cv.boolean, + } + ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + cv.SplitDefault(CONF_DRIVE_STRENGTH, esp32_idf="20mA"): cv.All( + cv.only_with_esp_idf, + cv.float_with_unit("current", "mA", optional_unit=True), + cv.enum(DRIVE_STRENGTHS), + ), + }, + validate_supports, +) + + +@pins.PIN_SCHEMA_REGISTRY.register("esp32", ESP32_PIN_SCHEMA) +async def esp32_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + num = config[CONF_NUMBER] + if CORE.using_esp_idf: + cg.add(var.set_pin(getattr(gpio_num_t, f"GPIO_NUM_{num}"))) + else: + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + if CONF_DRIVE_STRENGTH in config: + cg.add(var.set_drive_strength(config[CONF_DRIVE_STRENGTH])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + return var diff --git a/esphome/components/esp32/gpio_arduino.cpp b/esphome/components/esp32/gpio_arduino.cpp new file mode 100644 index 0000000000..11de1e13e6 --- /dev/null +++ b/esphome/components/esp32/gpio_arduino.cpp @@ -0,0 +1,107 @@ +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +#include "gpio_arduino.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace esp32 { + +static const char *const TAG = "esp32"; + +struct ISRPinArg { + uint8_t pin; + bool inverted; +}; + +ISRInternalGPIOPin ArduinoInternalGPIOPin::to_isr() const { + auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory) + arg->pin = pin_; + arg->inverted = inverted_; + return ISRInternalGPIOPin((void *) arg); +} + +void ArduinoInternalGPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { + uint8_t arduino_mode = DISABLED; + switch (type) { + case gpio::INTERRUPT_RISING_EDGE: + arduino_mode = inverted_ ? FALLING : RISING; + break; + case gpio::INTERRUPT_FALLING_EDGE: + arduino_mode = inverted_ ? RISING : FALLING; + break; + case gpio::INTERRUPT_ANY_EDGE: + arduino_mode = CHANGE; + break; + case gpio::INTERRUPT_LOW_LEVEL: + arduino_mode = inverted_ ? ONHIGH : ONLOW; + break; + case gpio::INTERRUPT_HIGH_LEVEL: + arduino_mode = inverted_ ? ONLOW : ONHIGH; + break; + } + + attachInterruptArg(pin_, func, arg, arduino_mode); +} +void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) { + uint8_t mode; + if (flags == gpio::FLAG_INPUT) { + mode = INPUT; + } else if (flags == gpio::FLAG_OUTPUT) { + mode = OUTPUT; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + mode = INPUT_PULLUP; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { + mode = INPUT_PULLDOWN; + } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + mode = OUTPUT_OPEN_DRAIN; + } else { + return; + } + pinMode(pin_, mode); // NOLINT +} + +std::string ArduinoInternalGPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "GPIO%u", pin_); + return buffer; +} + +bool ArduinoInternalGPIOPin::digital_read() { + return bool(digitalRead(pin_)) != inverted_; // NOLINT +} +void ArduinoInternalGPIOPin::digital_write(bool value) { + digitalWrite(pin_, value != inverted_ ? 1 : 0); // NOLINT +} +void ArduinoInternalGPIOPin::detach_interrupt() const { + detachInterrupt(pin_); // NOLINT +} + +} // namespace esp32 + +using namespace esp32; + +bool IRAM_ATTR ISRInternalGPIOPin::digital_read() { + auto *arg = reinterpret_cast(arg_); + return bool(digitalRead(arg->pin)) != arg->inverted; // NOLINT +} +void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { + auto *arg = reinterpret_cast(arg_); + digitalWrite(arg->pin, value != arg->inverted ? 1 : 0); // NOLINT +} +void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { + auto *arg = reinterpret_cast(arg_); +#ifdef CONFIG_IDF_TARGET_ESP32C3 + GPIO.status_w1tc.val = 1UL << arg->pin; +#else + if (arg->pin < 32) { + GPIO.status_w1tc = 1UL << arg->pin; + } else { + GPIO.status1_w1tc.intr_st = 1UL << (arg->pin - 32); + } +#endif +} + +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/esp32/gpio_arduino.h b/esphome/components/esp32/gpio_arduino.h new file mode 100644 index 0000000000..a077723075 --- /dev/null +++ b/esphome/components/esp32/gpio_arduino.h @@ -0,0 +1,36 @@ +#pragma once + +#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#include "esphome/core/hal.h" + +namespace esphome { +namespace esp32 { + +class ArduinoInternalGPIOPin : public InternalGPIOPin { + public: + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_flags(gpio::Flags flags) { flags_ = flags; } + + void setup() override { pin_mode(flags_); } + void pin_mode(gpio::Flags flags) override; + bool digital_read() override; + void digital_write(bool value) override; + std::string dump_summary() const override; + void detach_interrupt() const override; + ISRInternalGPIOPin to_isr() const override; + uint8_t get_pin() const override { return pin_; } + bool is_inverted() const override { return inverted_; } + + protected: + void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; +}; + +} // namespace esp32 +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/esp32/gpio_idf.cpp b/esphome/components/esp32/gpio_idf.cpp new file mode 100644 index 0000000000..d662d5519a --- /dev/null +++ b/esphome/components/esp32/gpio_idf.cpp @@ -0,0 +1,49 @@ +#ifdef USE_ESP32_FRAMEWORK_ESP_IDF + +#include "gpio_idf.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace esp32 { + +static const char *const TAG = "esp32"; + +bool IDFInternalGPIOPin::isr_service_installed_ = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +struct ISRPinArg { + gpio_num_t pin; + bool inverted; +}; + +ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const { + auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory) + arg->pin = pin_; + arg->inverted = inverted_; + return ISRInternalGPIOPin((void *) arg); +} + +std::string IDFInternalGPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "GPIO%u", static_cast(pin_)); + return buffer; +} + +} // namespace esp32 + +using namespace esp32; + +bool IRAM_ATTR ISRInternalGPIOPin::digital_read() { + auto *arg = reinterpret_cast(arg_); + return bool(gpio_get_level(arg->pin)) != arg->inverted; +} +void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { + auto *arg = reinterpret_cast(arg_); + gpio_set_level(arg->pin, value != arg->inverted ? 1 : 0); +} +void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { + // not supported +} + +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ESP_IDF diff --git a/esphome/components/esp32/gpio_idf.h b/esphome/components/esp32/gpio_idf.h new file mode 100644 index 0000000000..6a383afcae --- /dev/null +++ b/esphome/components/esp32/gpio_idf.h @@ -0,0 +1,96 @@ +#pragma once + +#ifdef USE_ESP32_FRAMEWORK_ESP_IDF +#include "esphome/core/hal.h" +#include + +namespace esphome { +namespace esp32 { + +class IDFInternalGPIOPin : public InternalGPIOPin { + public: + void set_pin(gpio_num_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_drive_strength(gpio_drive_cap_t drive_strength) { drive_strength_ = drive_strength; } + void set_flags(gpio::Flags flags) { flags_ = flags; } + + void setup() override { + pin_mode(flags_); + gpio_set_drive_capability(pin_, drive_strength_); + } + void pin_mode(gpio::Flags flags) override { + gpio_config_t conf{}; + conf.pin_bit_mask = 1 << static_cast(pin_); + conf.mode = flags_to_mode_(flags); + conf.pull_up_en = flags & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE; + conf.pull_down_en = flags & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE; + conf.intr_type = GPIO_INTR_DISABLE; + gpio_config(&conf); + } + bool digital_read() override { return bool(gpio_get_level(pin_)) != inverted_; } + void digital_write(bool value) override { gpio_set_level(pin_, value != inverted_ ? 1 : 0); } + std::string dump_summary() const override; + void detach_interrupt() const override { gpio_intr_disable(pin_); } + ISRInternalGPIOPin to_isr() const override; + uint8_t get_pin() const override { return (uint8_t) pin_; } + bool is_inverted() const override { return inverted_; } + + protected: + static gpio_mode_t flags_to_mode_(gpio::Flags flags) { + flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)); + if (flags == gpio::FLAG_NONE) { + return GPIO_MODE_DISABLE; + } else if (flags == gpio::FLAG_INPUT) { + return GPIO_MODE_INPUT; + } else if (flags == gpio::FLAG_OUTPUT) { + return GPIO_MODE_OUTPUT; + } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + return GPIO_MODE_OUTPUT_OD; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + return GPIO_MODE_INPUT_OUTPUT_OD; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT)) { + return GPIO_MODE_INPUT_OUTPUT; + } else { + // unsupported + return GPIO_MODE_DISABLE; + } + } + void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const override { + gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE; + switch (type) { + case gpio::INTERRUPT_RISING_EDGE: + idf_type = inverted_ ? GPIO_INTR_NEGEDGE : GPIO_INTR_POSEDGE; + break; + case gpio::INTERRUPT_FALLING_EDGE: + idf_type = inverted_ ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE; + break; + case gpio::INTERRUPT_ANY_EDGE: + idf_type = GPIO_INTR_ANYEDGE; + break; + case gpio::INTERRUPT_LOW_LEVEL: + idf_type = inverted_ ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL; + break; + case gpio::INTERRUPT_HIGH_LEVEL: + idf_type = inverted_ ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL; + break; + } + gpio_set_intr_type(pin_, idf_type); + gpio_intr_enable(pin_); + if (!isr_service_installed_) { + gpio_install_isr_service(ESP_INTR_FLAG_LEVEL5); + isr_service_installed_ = true; + } + gpio_isr_handler_add(pin_, func, arg); + } + + gpio_num_t pin_; + bool inverted_; + gpio_drive_cap_t drive_strength_; + gpio::Flags flags_; + static bool isr_service_installed_; +}; + +} // namespace esp32 +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ESP_IDF diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp new file mode 100644 index 0000000000..639e6434d6 --- /dev/null +++ b/esphome/components/esp32/preferences.cpp @@ -0,0 +1,99 @@ +#ifdef USE_ESP32 + +#include "esphome/core/preferences.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace esp32 { + +static const char *const TAG = "esp32.preferences"; + +class ESP32PreferenceBackend : public ESPPreferenceBackend { + public: + std::string key; + uint32_t nvs_handle; + bool save(const uint8_t *data, size_t len) override { + esp_err_t err = nvs_set_blob(nvs_handle, key.c_str(), data, len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", key.c_str(), len, esp_err_to_name(err)); + return false; + } + err = nvs_commit(nvs_handle); + if (err != 0) { + ESP_LOGV(TAG, "nvs_commit('%s', len=%u) failed: %s", key.c_str(), len, esp_err_to_name(err)); + return false; + } + return true; + } + bool load(uint8_t *data, size_t len) override { + size_t actual_len; + esp_err_t err = nvs_get_blob(nvs_handle, key.c_str(), nullptr, &actual_len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key.c_str(), esp_err_to_name(err)); + return false; + } + if (actual_len != len) { + ESP_LOGVV(TAG, "NVS length does not match (%u!=%u)", actual_len, len); + return false; + } + err = nvs_get_blob(nvs_handle, key.c_str(), data, &len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key.c_str(), esp_err_to_name(err)); + return false; + } + return true; + } +}; + +class ESP32Preferences : public ESPPreferences { + public: + uint32_t nvs_handle; + uint32_t current_offset = 0; + + void open() { + esp_err_t err = nvs_open("esphome", NVS_READWRITE, &nvs_handle); + if (err == 0) + return; + + ESP_LOGW(TAG, "nvs_open failed: %s - erasing NVS...", esp_err_to_name(err)); + nvs_flash_deinit(); + nvs_flash_erase(); + nvs_flash_init(); + + err = nvs_open("esphome", NVS_READWRITE, &nvs_handle); + if (err != 0) { + nvs_handle = 0; + } + } + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { + return make_preference(length, type); + } + ESPPreferenceObject make_preference(size_t length, uint32_t type) override { + auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + pref->nvs_handle = nvs_handle; + current_offset += length; + + uint32_t keyval = current_offset ^ type; + char keybuf[16]; + snprintf(keybuf, sizeof(keybuf), "%d", keyval); + pref->key = keybuf; // copied to std::string + + return ESPPreferenceObject(pref); + } +}; + +void setup_preferences() { + auto *prefs = new ESP32Preferences(); // NOLINT(cppcoreguidelines-owning-memory) + prefs->open(); + global_preferences = prefs; +} + +} // namespace esp32 + +ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32/preferences.h b/esphome/components/esp32/preferences.h new file mode 100644 index 0000000000..e44213e4cf --- /dev/null +++ b/esphome/components/esp32/preferences.h @@ -0,0 +1,12 @@ +#pragma once +#ifdef USE_ESP32 + +namespace esphome { +namespace esp32 { + +void setup_preferences(); + +} // namespace esp32 +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index ccf1f6cafe..4b5c741ad9 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -1,8 +1,10 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_ID, ESP_PLATFORM_ESP32 +from esphome.const import CONF_ID +from esphome.core import CORE +from esphome.components.esp32 import add_idf_sdkconfig_option -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["esp32"] CODEOWNERS = ["@jesserockz"] CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] @@ -20,3 +22,6 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + + if CORE.using_esp_idf: + add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 2d710a6ef7..143be06e3b 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -1,17 +1,21 @@ -#include "ble.h" +#ifdef USE_ESP32 +#include "ble.h" #include "esphome/core/application.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 - #include #include #include #include +#include #include #include +#ifdef USE_ARDUINO +#include +#endif + namespace esphome { namespace esp32_ble { @@ -52,9 +56,29 @@ bool ESP32BLE::ble_setup_() { return false; } - if (!btStart()) { - ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); - return false; + if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { + // start bt controller + if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) { + esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + err = esp_bt_controller_init(&cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err)); + return false; + } + while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) + ; + } + if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) { + err = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_bt_controller_enable failed: %s", esp_err_to_name(err)); + return false; + } + } + if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { + ESP_LOGE(TAG, "esp bt controller enable failed"); + return false; + } } esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 149f0008a4..008eba3235 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -11,7 +11,7 @@ #include "esphome/components/esp32_ble_server/ble_server.h" #endif -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include diff --git a/esphome/components/esp32_ble/ble_advertising.cpp b/esphome/components/esp32_ble/ble_advertising.cpp index bcfb0d1a1b..31b1f4c383 100644 --- a/esphome/components/esp32_ble/ble_advertising.cpp +++ b/esphome/components/esp32_ble/ble_advertising.cpp @@ -1,8 +1,11 @@ #include "ble_advertising.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include "ble_uuid.h" +#include +#include +#include "esphome/core/log.h" namespace esphome { namespace esp32_ble { diff --git a/esphome/components/esp32_ble/ble_advertising.h b/esphome/components/esp32_ble/ble_advertising.h index d86089f333..01e2ba1295 100644 --- a/esphome/components/esp32_ble/ble_advertising.h +++ b/esphome/components/esp32_ble/ble_advertising.h @@ -2,7 +2,7 @@ #include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include diff --git a/esphome/components/esp32_ble/ble_uuid.cpp b/esphome/components/esp32_ble/ble_uuid.cpp index b33275110d..8556aa87df 100644 --- a/esphome/components/esp32_ble/ble_uuid.cpp +++ b/esphome/components/esp32_ble/ble_uuid.cpp @@ -1,6 +1,10 @@ #include "ble_uuid.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 + +#include +#include +#include "esphome/core/log.h" namespace esphome { namespace esp32_ble { diff --git a/esphome/components/esp32_ble/ble_uuid.h b/esphome/components/esp32_ble/ble_uuid.h index 89082e19fa..f953f9fede 100644 --- a/esphome/components/esp32_ble/ble_uuid.h +++ b/esphome/components/esp32_ble/ble_uuid.h @@ -1,9 +1,9 @@ #pragma once #include "esphome/core/helpers.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include diff --git a/esphome/components/esp32_ble/queue.h b/esphome/components/esp32_ble/queue.h index cd123d5469..8fb2803237 100644 --- a/esphome/components/esp32_ble/queue.h +++ b/esphome/components/esp32_ble/queue.h @@ -1,15 +1,19 @@ #pragma once + +#ifdef USE_ESP32 + #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include #include - -#ifdef ARDUINO_ARCH_ESP32 +#include #include #include #include +#include +#include /* * BLE events come in from a separate Task (thread) in the ESP32 stack. Rather diff --git a/esphome/components/esp32_ble_beacon/__init__.py b/esphome/components/esp32_ble_beacon/__init__.py index cd0cb4bed6..d6cbb15dd2 100644 --- a/esphome/components/esp32_ble_beacon/__init__.py +++ b/esphome/components/esp32_ble_beacon/__init__.py @@ -1,8 +1,10 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID, ESP_PLATFORM_ESP32 +from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID +from esphome.core import CORE +from esphome.components.esp32 import add_idf_sdkconfig_option -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["esp32"] CONFLICTS_WITH = ["esp32_ble_tracker"] esp32_ble_beacon_ns = cg.esphome_ns.namespace("esp32_ble_beacon") @@ -29,3 +31,6 @@ async def to_code(config): await cg.register_component(var, config) cg.add(var.set_major(config[CONF_MAJOR])) cg.add(var.set_minor(config[CONF_MINOR])) + + if CORE.using_esp_idf: + add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp index c9e00a1093..96afadd19a 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp @@ -1,14 +1,16 @@ #include "esp32_ble_beacon.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include -#include +#include #include #include #include #include +#include +#include "esphome/core/hal.h" namespace esphome { namespace esp32_ble_beacon { @@ -59,6 +61,7 @@ void ESP32BLEBeacon::ble_core_task(void *params) { delay(1000); // NOLINT } } + void ESP32BLEBeacon::ble_setup() { // Initialize non-volatile storage for the bluetooth controller esp_err_t err = nvs_flash_init(); @@ -67,9 +70,29 @@ void ESP32BLEBeacon::ble_setup() { return; } - if (!btStart()) { - ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); - return; + if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { + // start bt controller + if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) { + esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + err = esp_bt_controller_init(&cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err)); + return; + } + while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) + ; + } + if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) { + err = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_bt_controller_enable failed: %s", esp_err_to_name(err)); + return; + } + } + if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { + ESP_LOGE(TAG, "esp bt controller enable failed"); + return; + } } esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h index aba02830b3..d0ef73899c 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py index 0dcbcadc50..2fcc5c7743 100644 --- a/esphome/components/esp32_ble_server/__init__.py +++ b/esphome/components/esp32_ble_server/__init__.py @@ -1,12 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_MODEL, ESP_PLATFORM_ESP32 +from esphome.const import CONF_ID, CONF_MODEL from esphome.components import esp32_ble +from esphome.core import CORE +from esphome.components.esp32 import add_idf_sdkconfig_option AUTO_LOAD = ["esp32_ble"] CODEOWNERS = ["@jesserockz"] CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["esp32"] CONF_MANUFACTURER = "manufacturer" CONF_BLE_ID = "ble_id" @@ -37,3 +39,6 @@ async def to_code(config): cg.add_define("USE_ESP32_BLE_SERVER") cg.add(parent.set_server(var)) + + if CORE.using_esp_idf: + add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) diff --git a/esphome/components/esp32_ble_server/ble_2901.cpp b/esphome/components/esp32_ble_server/ble_2901.cpp index 962ba3ffbe..ee0808d2c4 100644 --- a/esphome/components/esp32_ble_server/ble_2901.cpp +++ b/esphome/components/esp32_ble_server/ble_2901.cpp @@ -1,7 +1,7 @@ #include "ble_2901.h" #include "esphome/components/esp32_ble/ble_uuid.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_ble_server { diff --git a/esphome/components/esp32_ble_server/ble_2901.h b/esphome/components/esp32_ble_server/ble_2901.h index 3bb23ae69d..60f53e55b2 100644 --- a/esphome/components/esp32_ble_server/ble_2901.h +++ b/esphome/components/esp32_ble_server/ble_2901.h @@ -2,7 +2,7 @@ #include "ble_descriptor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_ble_server { diff --git a/esphome/components/esp32_ble_server/ble_2902.cpp b/esphome/components/esp32_ble_server/ble_2902.cpp index 0a87b239f9..2f34573c37 100644 --- a/esphome/components/esp32_ble_server/ble_2902.cpp +++ b/esphome/components/esp32_ble_server/ble_2902.cpp @@ -1,7 +1,9 @@ #include "ble_2902.h" #include "esphome/components/esp32_ble/ble_uuid.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 + +#include namespace esphome { namespace esp32_ble_server { diff --git a/esphome/components/esp32_ble_server/ble_2902.h b/esphome/components/esp32_ble_server/ble_2902.h index 024eec755e..64605924ad 100644 --- a/esphome/components/esp32_ble_server/ble_2902.h +++ b/esphome/components/esp32_ble_server/ble_2902.h @@ -2,7 +2,7 @@ #include "ble_descriptor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_ble_server { diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index d8ff39cc94..fae8c13934 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -4,7 +4,7 @@ #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_ble_server { diff --git a/esphome/components/esp32_ble_server/ble_characteristic.h b/esphome/components/esp32_ble_server/ble_characteristic.h index bc5033f2ae..d2467dd176 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.h +++ b/esphome/components/esp32_ble_server/ble_characteristic.h @@ -5,13 +5,15 @@ #include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include #include #include #include +#include +#include namespace esphome { namespace esp32_ble_server { diff --git a/esphome/components/esp32_ble_server/ble_descriptor.cpp b/esphome/components/esp32_ble_server/ble_descriptor.cpp index 5262087ad1..bfb6224335 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.cpp +++ b/esphome/components/esp32_ble_server/ble_descriptor.cpp @@ -1,10 +1,11 @@ #include "ble_descriptor.h" #include "ble_characteristic.h" #include "ble_service.h" - #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#include + +#ifdef USE_ESP32 namespace esphome { namespace esp32_ble_server { diff --git a/esphome/components/esp32_ble_server/ble_descriptor.h b/esphome/components/esp32_ble_server/ble_descriptor.h index 1a72cb2b54..4b8fb345c3 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.h +++ b/esphome/components/esp32_ble_server/ble_descriptor.h @@ -2,7 +2,7 @@ #include "esphome/components/esp32_ble/ble_uuid.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index 5777e99e23..0b91c238c3 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -5,7 +5,7 @@ #include "esphome/core/application.h" #include "esphome/core/version.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include diff --git a/esphome/components/esp32_ble_server/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h index 9d955dda79..7df44b4c61 100644 --- a/esphome/components/esp32_ble_server/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -12,7 +12,7 @@ #include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include diff --git a/esphome/components/esp32_ble_server/ble_service.cpp b/esphome/components/esp32_ble_server/ble_service.cpp index 5cfc53a397..281164f0f5 100644 --- a/esphome/components/esp32_ble_server/ble_service.cpp +++ b/esphome/components/esp32_ble_server/ble_service.cpp @@ -2,7 +2,7 @@ #include "ble_server.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_ble_server { diff --git a/esphome/components/esp32_ble_server/ble_service.h b/esphome/components/esp32_ble_server/ble_service.h index 9fdb95bde5..16cc897238 100644 --- a/esphome/components/esp32_ble_server/ble_service.h +++ b/esphome/components/esp32_ble_server/ble_service.h @@ -3,7 +3,7 @@ #include "ble_characteristic.h" #include "esphome/components/esp32_ble/ble_uuid.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 162a4dee89..e3d52f345a 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -4,7 +4,6 @@ import esphome.config_validation as cv from esphome import automation from esphome.const import ( CONF_ID, - ESP_PLATFORM_ESP32, CONF_INTERVAL, CONF_DURATION, CONF_TRIGGER_ID, @@ -15,8 +14,10 @@ from esphome.const import ( CONF_ON_BLE_SERVICE_DATA_ADVERTISE, CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE, ) +from esphome.core import CORE +from esphome.components.esp32 import add_idf_sdkconfig_option -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["esp32"] AUTO_LOAD = ["xiaomi_ble", "ruuvi_ble"] CONF_ESP32_BLE_ID = "esp32_ble_id" @@ -216,6 +217,9 @@ async def to_code(config): cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) await automation.build_automation(trigger, [(adv_data_t_const_ref, "x")], conf) + if CORE.using_esp_idf: + add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) + async def register_ble_device(var, config): paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) diff --git a/esphome/components/esp32_ble_tracker/automation.h b/esphome/components/esp32_ble_tracker/automation.h index 9df2587ede..3505e9c26d 100644 --- a/esphome/components/esp32_ble_tracker/automation.h +++ b/esphome/components/esp32_ble_tracker/automation.h @@ -3,7 +3,7 @@ #include "esphome/core/automation.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_ble_tracker { diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 358fe50605..5568884b9a 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -1,18 +1,24 @@ +#ifdef USE_ESP32 + #include "esp32_ble_tracker.h" #include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/core/helpers.h" - -#ifdef ARDUINO_ARCH_ESP32 +#include "esphome/core/hal.h" #include #include #include #include +#include #include #include #include +#ifdef USE_ARDUINO +#include +#endif + // bt_trace.h #undef TAG @@ -126,14 +132,33 @@ bool ESP32BLETracker::ble_setup() { return false; } - esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); - - // Initialize the bluetooth controller with the default configuration - if (!btStart()) { - ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); - return false; + if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { + // start bt controller + if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) { + esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + err = esp_bt_controller_init(&cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err)); + return false; + } + while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) + ; + } + if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) { + err = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_bt_controller_enable failed: %s", esp_err_to_name(err)); + return false; + } + } + if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { + ESP_LOGE(TAG, "esp bt controller enable failed"); + return false; + } } + esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); + err = esp_bluedroid_init(); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", err); diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 0594c4a811..40955d39cf 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -4,7 +4,7 @@ #include "esphome/core/helpers.h" #include "queue.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include diff --git a/esphome/components/esp32_ble_tracker/queue.h b/esphome/components/esp32_ble_tracker/queue.h index f0f6ab9f17..3d38c17584 100644 --- a/esphome/components/esp32_ble_tracker/queue.h +++ b/esphome/components/esp32_ble_tracker/queue.h @@ -1,14 +1,17 @@ #pragma once + +#ifdef USE_ESP32 #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include #include - -#ifdef ARDUINO_ARCH_ESP32 +#include #include #include +#include +#include /* * BLE events come in from a separate Task (thread) in the ESP32 stack. Rather diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index e3c7383953..de61ab43cf 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -9,16 +9,16 @@ from esphome.const import ( CONF_PIN, CONF_SCL, CONF_SDA, - ESP_PLATFORM_ESP32, CONF_DATA_PINS, CONF_RESET_PIN, CONF_RESOLUTION, CONF_BRIGHTNESS, CONF_CONTRAST, ) +from esphome.core import CORE +from esphome.components.esp32 import add_idf_sdkconfig_option -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] -DEPENDENCIES = ["api"] +DEPENDENCIES = ["esp32", "api"] esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.Nameable) @@ -68,13 +68,15 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(): cv.declare_id(ESP32Camera), cv.Required(CONF_NAME): cv.string, cv.Optional(CONF_DISABLED_BY_DEFAULT, default=False): cv.boolean, - cv.Required(CONF_DATA_PINS): cv.All([pins.input_pin], cv.Length(min=8, max=8)), - cv.Required(CONF_VSYNC_PIN): pins.input_pin, - cv.Required(CONF_HREF_PIN): pins.input_pin, - cv.Required(CONF_PIXEL_CLOCK_PIN): pins.input_pin, + cv.Required(CONF_DATA_PINS): cv.All( + [pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8) + ), + cv.Required(CONF_VSYNC_PIN): pins.internal_gpio_input_pin_number, + cv.Required(CONF_HREF_PIN): pins.internal_gpio_input_pin_number, + cv.Required(CONF_PIXEL_CLOCK_PIN): pins.internal_gpio_input_pin_number, cv.Required(CONF_EXTERNAL_CLOCK): cv.Schema( { - cv.Required(CONF_PIN): pins.output_pin, + cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number, cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All( cv.frequency, cv.one_of(20e6, 10e6) ), @@ -82,12 +84,12 @@ CONFIG_SCHEMA = cv.Schema( ), cv.Required(CONF_I2C_PINS): cv.Schema( { - cv.Required(CONF_SDA): pins.output_pin, - cv.Required(CONF_SCL): pins.output_pin, + cv.Required(CONF_SDA): pins.internal_gpio_output_pin_number, + cv.Required(CONF_SCL): pins.internal_gpio_output_pin_number, } ), - cv.Optional(CONF_RESET_PIN): pins.output_pin, - cv.Optional(CONF_POWER_DOWN_PIN): pins.output_pin, + cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_POWER_DOWN_PIN): pins.internal_gpio_output_pin_number, cv.Optional(CONF_MAX_FRAMERATE, default="10 fps"): cv.All( cv.framerate, cv.Range(min=0, min_included=False, max=60) ), @@ -146,3 +148,8 @@ async def to_code(config): cg.add_define("USE_ESP32_CAMERA") cg.add_build_flag("-DBOARD_HAS_PSRAM") + + if CORE.using_esp_idf: + cg.add_library("espressif/esp32-camera", "1.0.0") + add_idf_sdkconfig_option("CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC", True) + add_idf_sdkconfig_option("CONFIG_ESP32_SPIRAM_SUPPORT", True) diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 072cf41460..05445f024b 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -1,7 +1,10 @@ +#ifdef USE_ESP32 + #include "esp32_camera.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" -#ifdef ARDUINO_ARCH_ESP32 +#include namespace esphome { namespace esp32_camera { @@ -42,7 +45,9 @@ void ESP32Camera::dump_config() { auto conf = this->config_; ESP_LOGCONFIG(TAG, "ESP32 Camera:"); ESP_LOGCONFIG(TAG, " Name: %s", this->name_.c_str()); +#ifdef USE_ARDUINO ESP_LOGCONFIG(TAG, " Board Has PSRAM: %s", YESNO(psramFound())); +#endif // USE_ARDUINO ESP_LOGCONFIG(TAG, " Data Pins: D0:%d D1:%d D2:%d D3:%d D4:%d D5:%d D6:%d D7:%d", conf.pin_d0, conf.pin_d1, conf.pin_d2, conf.pin_d3, conf.pin_d4, conf.pin_d5, conf.pin_d6, conf.pin_d7); ESP_LOGCONFIG(TAG, " VSYNC Pin: %d", conf.pin_vsync); diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index 03272d3b32..430391aa76 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -1,10 +1,12 @@ #pragma once -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include +#include +#include namespace esphome { namespace esp32_camera { diff --git a/esphome/components/esp32_dac/esp32_dac.cpp b/esphome/components/esp32_dac/esp32_dac.cpp index 20a047f0ba..7f37e2ce47 100644 --- a/esphome/components/esp32_dac/esp32_dac.cpp +++ b/esphome/components/esp32_dac/esp32_dac.cpp @@ -2,9 +2,14 @@ #include "esphome/core/log.h" #include "esphome/core/helpers.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 +#ifdef USE_ARDUINO #include +#endif +#ifdef USE_ESP_IDF +#include +#endif namespace esphome { namespace esp32_dac { @@ -15,6 +20,11 @@ void ESP32DAC::setup() { ESP_LOGCONFIG(TAG, "Setting up ESP32 DAC Output..."); this->pin_->setup(); this->turn_off(); + +#ifdef USE_ESP_IDF + auto channel = pin_->get_pin() == 25 ? DAC_CHANNEL_1 : DAC_CHANNEL_2; + dac_output_enable(channel); +#endif } void ESP32DAC::dump_config() { @@ -28,7 +38,14 @@ void ESP32DAC::write_state(float state) { state = 1.0f - state; state = state * 255; + +#ifdef USE_ESP_IDF + auto channel = pin_->get_pin() == 25 ? DAC_CHANNEL_1 : DAC_CHANNEL_2; + dac_output_voltage(channel, (uint8_t) state); +#endif +#ifdef USE_ARDUINO dacWrite(this->pin_->get_pin(), state); +#endif } } // namespace esp32_dac diff --git a/esphome/components/esp32_dac/esp32_dac.h b/esphome/components/esp32_dac/esp32_dac.h index 648efcfe4f..0fb1ddebf0 100644 --- a/esphome/components/esp32_dac/esp32_dac.h +++ b/esphome/components/esp32_dac/esp32_dac.h @@ -1,18 +1,18 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/automation.h" #include "esphome/components/output/float_output.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_dac { class ESP32DAC : public output::FloatOutput, public Component { public: - void set_pin(GPIOPin *pin) { pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } /// Initialize pin void setup() override; @@ -23,7 +23,7 @@ class ESP32DAC : public output::FloatOutput, public Component { protected: void write_state(float state) override; - GPIOPin *pin_; + InternalGPIOPin *pin_; }; } // namespace esp32_dac diff --git a/esphome/components/esp32_dac/output.py b/esphome/components/esp32_dac/output.py index 8534a1bae1..f119198618 100644 --- a/esphome/components/esp32_dac/output.py +++ b/esphome/components/esp32_dac/output.py @@ -2,9 +2,9 @@ from esphome import pins from esphome.components import output import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID, CONF_NUMBER, CONF_PIN, ESP_PLATFORM_ESP32 +from esphome.const import CONF_ID, CONF_NUMBER, CONF_PIN -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["esp32"] def valid_dac_pin(value): diff --git a/esphome/components/esp32_hall/esp32_hall.cpp b/esphome/components/esp32_hall/esp32_hall.cpp index 4bbf65e048..762497aedc 100644 --- a/esphome/components/esp32_hall/esp32_hall.cpp +++ b/esphome/components/esp32_hall/esp32_hall.cpp @@ -1,8 +1,8 @@ +#ifdef USE_ESP32 #include "esp32_hall.h" #include "esphome/core/log.h" -#include "esphome/core/esphal.h" - -#ifdef ARDUINO_ARCH_ESP32 +#include "esphome/core/hal.h" +#include namespace esphome { namespace esp32_hall { @@ -10,7 +10,9 @@ namespace esp32_hall { static const char *const TAG = "esp32_hall"; void ESP32HallSensor::update() { - float value = (hallRead() / 4095.0f) * 10000.0f; + adc1_config_width(ADC_WIDTH_BIT_12); + int value_int = hall_sensor_read(); + float value = (value_int / 4095.0f) * 10000.0f; ESP_LOGD(TAG, "'%s': Got reading %.0f µT", this->name_.c_str(), value); this->publish_state(value); } diff --git a/esphome/components/esp32_hall/esp32_hall.h b/esphome/components/esp32_hall/esp32_hall.h index 040280fff3..8db50c4667 100644 --- a/esphome/components/esp32_hall/esp32_hall.h +++ b/esphome/components/esp32_hall/esp32_hall.h @@ -3,7 +3,7 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_hall { diff --git a/esphome/components/esp32_hall/sensor.py b/esphome/components/esp32_hall/sensor.py index 4ba79de714..a752da2c97 100644 --- a/esphome/components/esp32_hall/sensor.py +++ b/esphome/components/esp32_hall/sensor.py @@ -3,13 +3,12 @@ import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( CONF_ID, - ESP_PLATFORM_ESP32, STATE_CLASS_MEASUREMENT, UNIT_MICROTESLA, ICON_MAGNET, ) -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["esp32"] esp32_hall_ns = cg.esphome_ns.namespace("esp32_hall") ESP32HallSensor = esp32_hall_ns.class_( diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index 4c337055cb..0b0214c63e 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -1,14 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor, output, esp32_ble_server -from esphome.const import CONF_ID, ESP_PLATFORM_ESP32 +from esphome.const import CONF_ID AUTO_LOAD = ["binary_sensor", "output", "improv", "esp32_ble_server"] CODEOWNERS = ["@jesserockz"] CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] -DEPENDENCIES = ["wifi"] -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["wifi", "esp32"] CONF_AUTHORIZED_DURATION = "authorized_duration" CONF_AUTHORIZER = "authorizer" diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index b584d2e8b9..fc58fbd264 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -5,7 +5,7 @@ #include "esphome/core/application.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_improv { diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index 262124f983..af39ae4748 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -10,7 +10,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_improv { diff --git a/esphome/components/esp32_touch/__init__.py b/esphome/components/esp32_touch/__init__.py index 1564476ecf..cdf6aa3abd 100644 --- a/esphome/components/esp32_touch/__init__.py +++ b/esphome/components/esp32_touch/__init__.py @@ -9,12 +9,11 @@ from esphome.const import ( CONF_SETUP_MODE, CONF_SLEEP_DURATION, CONF_VOLTAGE_ATTENUATION, - ESP_PLATFORM_ESP32, ) from esphome.core import TimePeriod AUTO_LOAD = ["binary_sensor"] -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["esp32"] esp32_touch_ns = cg.esphome_ns.namespace("esp32_touch") ESP32TouchComponent = esp32_touch_ns.class_("ESP32TouchComponent", cg.Component) diff --git a/esphome/components/esp32_touch/binary_sensor.py b/esphome/components/esp32_touch/binary_sensor.py index 198a43a738..93640334cd 100644 --- a/esphome/components/esp32_touch/binary_sensor.py +++ b/esphome/components/esp32_touch/binary_sensor.py @@ -5,14 +5,12 @@ from esphome.const import ( CONF_NAME, CONF_PIN, CONF_THRESHOLD, - ESP_PLATFORM_ESP32, CONF_ID, ) -from esphome.pins import validate_gpio_pin +from esphome.components.esp32 import gpio from . import esp32_touch_ns, ESP32TouchComponent -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] -DEPENDENCIES = ["esp32_touch"] +DEPENDENCIES = ["esp32_touch", "esp32"] CONF_ESP32_TOUCH_ID = "esp32_touch_id" CONF_WAKEUP_THRESHOLD = "wakeup_threshold" @@ -32,7 +30,7 @@ TOUCH_PADS = { def validate_touch_pad(value): - value = validate_gpio_pin(value) + value = gpio.validate_gpio_pin(value) if value not in TOUCH_PADS: raise cv.Invalid(f"Pin {value} does not support touch pads.") return value diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 9ae671a4a9..801106ab6c 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -1,7 +1,8 @@ +#ifdef USE_ESP32 + #include "esp32_touch.h" #include "esphome/core/log.h" - -#ifdef ARDUINO_ARCH_ESP32 +#include "esphome/core/hal.h" namespace esphome { namespace esp32_touch { diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 8b92a482c0..c584a6d9bc 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -1,9 +1,16 @@ #pragma once +#ifdef USE_ESP32 + #include "esphome/core/component.h" #include "esphome/components/binary_sensor/binary_sensor.h" +#include -#ifdef ARDUINO_ARCH_ESP32 +#if ESP_IDF_VERSION_MAJOR >= 4 +#include +#else +#include +#endif namespace esphome { namespace esp32_touch { diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py new file mode 100644 index 0000000000..592de06440 --- /dev/null +++ b/esphome/components/esp8266/__init__.py @@ -0,0 +1,213 @@ +import logging + +from esphome.const import ( + CONF_BOARD, + CONF_BOARD_FLASH_MODE, + CONF_FRAMEWORK, + CONF_VERSION, + KEY_CORE, + KEY_FRAMEWORK_VERSION, + KEY_TARGET_FRAMEWORK, + KEY_TARGET_PLATFORM, +) +from esphome.core import CORE +import esphome.config_validation as cv +import esphome.codegen as cg + +from .const import CONF_RESTORE_FROM_FLASH, KEY_BOARD, KEY_ESP8266 +from .boards import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS + +# force import gpio to register pin schema +from .gpio import esp8266_pin_to_code # noqa + + +CODEOWNERS = ["@esphome/core"] +_LOGGER = logging.getLogger(__name__) + + +def set_core_data(config): + CORE.data[KEY_ESP8266] = {} + CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "esp8266" + CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" + CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( + config[CONF_FRAMEWORK][CONF_VERSION_HINT] + ) + CORE.data[KEY_ESP8266][KEY_BOARD] = config[CONF_BOARD] + return config + + +def _format_framework_arduino_version(ver: cv.Version) -> str: + # format the given arduino (https://github.com/esp8266/Arduino/releases) version to + # a PIO platformio/framework-arduinoespressif8266 value + # List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif8266 + if ver <= cv.Version(2, 4, 1): + return f"~1.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + if ver <= cv.Version(2, 6, 2): + return f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + + +# NOTE: Keep this in mind when updating the recommended version: +# * New framework historically have had some regressions, especially for WiFi. +# The new version needs to be thoroughly validated before changing the +# recommended version as otherwise a bunch of devices could be bricked +# * For all constants below, update platformio.ini (in this repo) +# and platformio.ini/platformio-lint.ini in the esphome-docker-base repository + +# The default/recommended arduino framework version +# - https://github.com/esp8266/Arduino/releases +# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif8266 +RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 7, 4) +# The platformio/espressif8266 version to use for arduino 2 framework versions +# - https://github.com/platformio/platform-espressif8266/releases +# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif8266 +ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 2) +# for arduino 3 framework versions +ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 0, 2) + + +def _arduino_check_versions(value): + value = value.copy() + lookups = { + "dev": ("https://github.com/esp8266/Arduino.git", cv.Version(3, 0, 2)), + "latest": ("", cv.Version(3, 0, 2)), + "recommended": ( + _format_framework_arduino_version(RECOMMENDED_ARDUINO_FRAMEWORK_VERSION), + RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, + ), + } + ver_value = value[CONF_VERSION] + default_ver_hint = None + if ver_value.lower() in lookups: + default_ver_hint = str(lookups[ver_value.lower()][1]) + ver_value = lookups[ver_value.lower()][0] + else: + with cv.suppress_invalid(): + ver = cv.Version.parse(cv.version_number(value)) + if ver <= cv.Version(2, 4, 1): + ver_value = f"~1.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + elif ver <= cv.Version(2, 6, 2): + ver_value = f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + else: + ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + default_ver_hint = str(ver) + + value[CONF_VERSION] = ver_value + + if CONF_VERSION_HINT not in value and default_ver_hint is None: + raise cv.Invalid("Needs a version hint to understand the framework version") + + ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint) + value[CONF_VERSION_HINT] = ver_hint_s + plat_ver = value.get(CONF_PLATFORM_VERSION) + + if plat_ver is None: + ver_hint = cv.Version.parse(ver_hint_s) + if ver_hint >= cv.Version(3, 0, 0): + plat_ver = ARDUINO_3_PLATFORM_VERSION + elif ver_hint >= cv.Version(2, 5, 0): + plat_ver = ARDUINO_2_PLATFORM_VERSION + else: + plat_ver = cv.Version(1, 8, 0) + value[CONF_PLATFORM_VERSION] = str(plat_ver) + + if cv.Version.parse(ver_hint_s) != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: + _LOGGER.warning( + "The selected arduino framework version is not the recommended one" + ) + _LOGGER.warning( + "If there are connectivity or build issues please remove the manual version" + ) + + return value + + +CONF_VERSION_HINT = "version_hint" +CONF_PLATFORM_VERSION = "platform_version" +ARDUINO_FRAMEWORK_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, + cv.Optional(CONF_VERSION_HINT): cv.version_number, + cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, + } + ), + _arduino_check_versions, +) + + +BUILD_FLASH_MODES = ["qio", "qout", "dio", "dout"] +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_BOARD): cv.string_strict, + cv.Optional(CONF_FRAMEWORK, default={}): ARDUINO_FRAMEWORK_SCHEMA, + cv.Optional(CONF_RESTORE_FROM_FLASH, default=False): cv.boolean, + cv.Optional(CONF_BOARD_FLASH_MODE, default="dout"): cv.one_of( + *BUILD_FLASH_MODES, lower=True + ), + } + ), + set_core_data, +) + + +async def to_code(config): + cg.add_platformio_option("board", config[CONF_BOARD]) + cg.add_build_flag("-DUSE_ESP8266") + cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) + + conf = config[CONF_FRAMEWORK] + cg.add_platformio_option("framework", "arduino") + cg.add_build_flag("-DUSE_ARDUINO") + cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO") + cg.add_platformio_option( + "platform_packages", + [f"platformio/framework-arduinoespressif8266 @ {conf[CONF_VERSION]}"], + ) + cg.add_platformio_option( + "platform", f"platformio/espressif8266 @ {conf[CONF_PLATFORM_VERSION]}" + ) + + # Default for platformio is LWIP2_LOW_MEMORY with: + # - MSS=536 + # - LWIP_FEATURES enabled + # - this only adds some optional features like IP incoming packet reassembly and NAPT + # see also: + # https://github.com/esp8266/Arduino/blob/master/tools/sdk/lwip2/include/lwipopts.h + + # Instead we use LWIP2_HIGHER_BANDWIDTH_LOW_FLASH with: + # - MSS=1460 + # - LWIP_FEATURES disabled (because we don't need them) + # Other projects like Tasmota & ESPEasy also use this + cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH") + + if config[CONF_RESTORE_FROM_FLASH]: + cg.add_define("USE_ESP8266_PREFERENCES_FLASH") + + # Arduino 2 has a non-standards conformant new that returns a nullptr instead of failing when + # out of memory and exceptions are disabled. Since Arduino 2.6.0, this flag can be used to make + # new abort instead. Use it so that OOM fails early (on allocation) instead of on dereference of + # a NULL pointer (so the stacktrace makes more sense), and for consistency with Arduino 3, + # which always aborts if exceptions are disabled. + # For cases where nullptrs can be handled, use nothrow: `new (std::nothrow) T;` + cg.add_build_flag("-DNEW_OOM_ABORT") + + cg.add_platformio_option("board_build.flash_mode", config[CONF_BOARD_FLASH_MODE]) + + if config[CONF_BOARD] in ESP8266_FLASH_SIZES: + flash_size = ESP8266_FLASH_SIZES[config[CONF_BOARD]] + ld_scripts = ESP8266_LD_SCRIPTS[flash_size] + ver = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] + + if ver <= cv.Version(2, 3, 0): + # No ld script support + ld_script = None + if ver <= cv.Version(2, 4, 2): + # Old ld script path + ld_script = ld_scripts[0] + else: + ld_script = ld_scripts[1] + + if ld_script is not None: + cg.add_platformio_option("board_build.ldscript", ld_script) diff --git a/esphome/components/esp8266/boards.py b/esphome/components/esp8266/boards.py new file mode 100644 index 0000000000..c49aae4ffa --- /dev/null +++ b/esphome/components/esp8266/boards.py @@ -0,0 +1,266 @@ +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, + "espmxdevkit": FLASH_SIZE_1_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, + "sonoff_basic": FLASH_SIZE_1_MB, + "sonoff_s20": FLASH_SIZE_1_MB, + "sonoff_sv": FLASH_SIZE_1_MB, + "sonoff_th": FLASH_SIZE_1_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"), +} + +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}, + "espmxdevkit": {}, + "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}, + "sonoff_basic": {}, + "sonoff_s20": {}, + "sonoff_sv": {}, + "sonoff_th": {}, + "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, + "espmxdevkit": FLASH_SIZE_1_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, + "sonoff_basic": FLASH_SIZE_1_MB, + "sonoff_s20": FLASH_SIZE_1_MB, + "sonoff_sv": FLASH_SIZE_1_MB, + "sonoff_th": FLASH_SIZE_1_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"), +} diff --git a/esphome/components/esp8266/const.py b/esphome/components/esp8266/const.py new file mode 100644 index 0000000000..16a050360c --- /dev/null +++ b/esphome/components/esp8266/const.py @@ -0,0 +1,8 @@ +import esphome.codegen as cg + +KEY_ESP8266 = "esp8266" +KEY_BOARD = "board" +CONF_RESTORE_FROM_FLASH = "restore_from_flash" + +# esp8266 namespace is already defined by arduino, manually prefix esphome +esp8266_ns = cg.global_ns.namespace("esphome").namespace("esp8266") diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp new file mode 100644 index 0000000000..453f772e09 --- /dev/null +++ b/esphome/components/esp8266/core.cpp @@ -0,0 +1,37 @@ +#ifdef USE_ESP8266 + +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "preferences.h" +#include +#include + +namespace esphome { + +void IRAM_ATTR HOT yield() { ::yield(); } +uint32_t IRAM_ATTR HOT millis() { return ::millis(); } +void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); } +uint32_t IRAM_ATTR HOT micros() { return ::micros(); } +void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { ::delayMicroseconds(us); } +void arch_restart() { + ESP.restart(); // NOLINT(readability-static-accessed-through-instance) + // restart() doesn't always end execution + while (true) { // NOLINT(clang-diagnostic-unreachable-code) + yield(); + } +} +void IRAM_ATTR HOT arch_feed_wdt() { + ESP.wdtFeed(); // NOLINT(readability-static-accessed-through-instance) +} + +uint8_t progmem_read_byte(const uint8_t *addr) { + return pgm_read_byte(addr); // NOLINT +} +uint32_t arch_get_cpu_cycle_count() { + return ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) +} +uint32_t arch_get_cpu_freq_hz() { return F_CPU; } + +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp new file mode 100644 index 0000000000..4dbffa7f6c --- /dev/null +++ b/esphome/components/esp8266/gpio.cpp @@ -0,0 +1,96 @@ +#ifdef USE_ESP8266 + +#include "gpio.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace esp8266 { + +static const char *const TAG = "esp8266"; + +struct ISRPinArg { + uint8_t pin; + bool inverted; +}; + +ISRInternalGPIOPin ESP8266GPIOPin::to_isr() const { + auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory) + arg->pin = pin_; + arg->inverted = inverted_; + return ISRInternalGPIOPin((void *) arg); +} + +void ESP8266GPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { + uint8_t arduino_mode = 0; + switch (type) { + case gpio::INTERRUPT_RISING_EDGE: + arduino_mode = inverted_ ? FALLING : RISING; + break; + case gpio::INTERRUPT_FALLING_EDGE: + arduino_mode = inverted_ ? RISING : FALLING; + break; + case gpio::INTERRUPT_ANY_EDGE: + arduino_mode = CHANGE; + break; + case gpio::INTERRUPT_LOW_LEVEL: + arduino_mode = inverted_ ? ONHIGH : ONLOW; + break; + case gpio::INTERRUPT_HIGH_LEVEL: + arduino_mode = inverted_ ? ONLOW : ONHIGH; + break; + } + + attachInterruptArg(pin_, func, arg, arduino_mode); +} +void ESP8266GPIOPin::pin_mode(gpio::Flags flags) { + uint8_t mode; + if (flags == gpio::FLAG_INPUT) { + mode = INPUT; + } else if (flags == gpio::FLAG_OUTPUT) { + mode = OUTPUT; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + mode = INPUT_PULLUP; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { + mode = INPUT_PULLDOWN_16; + } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + mode = OUTPUT_OPEN_DRAIN; + } else { + return; + } + pinMode(pin_, mode); // NOLINT +} + +std::string ESP8266GPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "GPIO%u", pin_); + return buffer; +} + +bool ESP8266GPIOPin::digital_read() { + return bool(digitalRead(pin_)) != inverted_; // NOLINT +} +void ESP8266GPIOPin::digital_write(bool value) { + digitalWrite(pin_, value != inverted_ ? 1 : 0); // NOLINT +} +void ESP8266GPIOPin::detach_interrupt() const { detachInterrupt(pin_); } + +} // namespace esp8266 + +using namespace esp8266; + +bool IRAM_ATTR ISRInternalGPIOPin::digital_read() { + auto *arg = reinterpret_cast(arg_); + return bool(digitalRead(arg->pin)) != arg->inverted; // NOLINT +} +void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { + auto *arg = reinterpret_cast(arg_); + digitalWrite(arg->pin, value != arg->inverted ? 1 : 0); // NOLINT +} +void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { + auto *arg = reinterpret_cast(arg_); + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin); +} + +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/esp8266/gpio.h b/esphome/components/esp8266/gpio.h new file mode 100644 index 0000000000..465d1099ae --- /dev/null +++ b/esphome/components/esp8266/gpio.h @@ -0,0 +1,38 @@ +#pragma once + +#ifdef USE_ESP8266 + +#include "esphome/core/hal.h" +#include + +namespace esphome { +namespace esp8266 { + +class ESP8266GPIOPin : public InternalGPIOPin { + public: + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_flags(gpio::Flags flags) { flags_ = flags; } + + void setup() override { pin_mode(flags_); } + void pin_mode(gpio::Flags flags) override; + bool digital_read() override; + void digital_write(bool value) override; + std::string dump_summary() const override; + void detach_interrupt() const override; + ISRInternalGPIOPin to_isr() const override; + uint8_t get_pin() const override { return pin_; } + bool is_inverted() const override { return inverted_; } + + protected: + void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; +}; + +} // namespace esp8266 +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/esp8266/gpio.py b/esphome/components/esp8266/gpio.py new file mode 100644 index 0000000000..0ebfbd6f69 --- /dev/null +++ b/esphome/components/esp8266/gpio.py @@ -0,0 +1,170 @@ +import logging + +from esphome.const import ( + CONF_ID, + CONF_INPUT, + CONF_INVERTED, + CONF_MODE, + CONF_NUMBER, + CONF_OPEN_DRAIN, + CONF_OUTPUT, + CONF_PULLDOWN, + CONF_PULLUP, +) +from esphome import pins +from esphome.core import CORE +import esphome.config_validation as cv +import esphome.codegen as cg + +from . import boards +from .const import KEY_BOARD, KEY_ESP8266, esp8266_ns + + +_LOGGER = logging.getLogger(__name__) + + +ESP8266GPIOPin = esp8266_ns.class_("ESP8266GPIOPin", cg.InternalGPIOPin) + + +def _lookup_pin(value): + board = CORE.data[KEY_ESP8266][KEY_BOARD] + board_pins = boards.ESP8266_BOARD_PINS.get(board, {}) + + # Resolved aliased board pins (shorthand when two boards have the same pin configuration) + while isinstance(board_pins, str): + board_pins = boards.ESP8266_BOARD_PINS[board_pins] + + if value in board_pins: + return board_pins[value] + if value in boards.ESP8266_BASE_PINS: + return boards.ESP8266_BASE_PINS[value] + raise cv.Invalid(f"Cannot resolve pin name '{value}' for board {board}.") + + +def _translate_pin(value): + if isinstance(value, dict) or value is None: + raise cv.Invalid( + "This variable only supports pin numbers, not full pin schemas " + "(with inverted and mode)." + ) + if isinstance(value, int): + return value + try: + return int(value) + except ValueError: + pass + if value.startswith("GPIO"): + return cv.int_(value[len("GPIO") :].strip()) + return _lookup_pin(value) + + +_ESP_SDIO_PINS = { + 6: "Flash Clock", + 7: "Flash Data 0", + 8: "Flash Data 1", + 11: "Flash Command", +} + + +def validate_gpio_pin(value): + value = _translate_pin(value) + if value < 0 or value > 17: + raise cv.Invalid(f"ESP8266: Invalid pin number: {value}") + if value in _ESP_SDIO_PINS: + raise cv.Invalid( + f"This pin cannot be used on ESP8266s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" + ) + if 9 <= value <= 10: + _LOGGER.warning( + "ESP8266: Pin %s (9-10) might already be used by the " + "flash interface in QUAD IO flash mode.", + value, + ) + return value + + +def validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + is_output = mode[CONF_OUTPUT] + is_open_drain = mode[CONF_OPEN_DRAIN] + is_pullup = mode[CONF_PULLUP] + is_pulldown = mode[CONF_PULLDOWN] + is_analog = mode[CONF_ANALOG] + + if (not is_analog) and num == 17: + raise cv.Invalid( + "GPIO17 (TOUT) is an analog-only pin on the ESP8266.", + [CONF_MODE], + ) + if is_analog and num != 17: + raise cv.Invalid( + "Only GPIO17 is analog-capable on ESP8266.", + [CONF_MODE, CONF_ANALOG], + ) + if is_open_drain and not is_output: + raise cv.Invalid( + "Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN] + ) + if is_pullup and num == 0: + raise cv.Invalid( + "GPIO Pin 0 does not support pullup pin mode. " + "Please choose another pin.", + [CONF_MODE, CONF_PULLUP], + ) + if is_pulldown and num != 16: + raise cv.Invalid("Only GPIO16 supports pulldown.", [CONF_MODE, CONF_PULLDOWN]) + + # (input, output, open_drain, pullup, pulldown) + supported_modes = { + # INPUT + (True, False, False, False, False), + # OUTPUT + (False, True, False, False, False), + # INPUT_PULLUP + (True, False, False, True, False), + # INPUT_PULLDOWN_16 + (True, False, False, False, True), + # OUTPUT_OPEN_DRAIN + (False, True, True, False, False), + } + key = (is_input, is_output, is_open_drain, is_pullup, is_pulldown) + if key not in supported_modes: + raise cv.Invalid( + "This pin mode is not supported on ESP8266", + [CONF_MODE], + ) + + return value + + +CONF_ANALOG = "analog" +ESP8266_PIN_SCHEMA = cv.All( + { + cv.GenerateID(): cv.declare_id(ESP8266GPIOPin), + cv.Required(CONF_NUMBER): validate_gpio_pin, + cv.Optional(CONF_MODE, default={}): cv.Schema( + { + cv.Optional(CONF_ANALOG, default=False): cv.boolean, + cv.Optional(CONF_INPUT, default=False): cv.boolean, + cv.Optional(CONF_OUTPUT, default=False): cv.boolean, + cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean, + cv.Optional(CONF_PULLUP, default=False): cv.boolean, + cv.Optional(CONF_PULLDOWN, default=False): cv.boolean, + } + ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + }, + validate_supports, +) + + +@pins.PIN_SCHEMA_REGISTRY.register("esp8266", ESP8266_PIN_SCHEMA) +async def esp8266_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + return var diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp new file mode 100644 index 0000000000..7a8fdb8289 --- /dev/null +++ b/esphome/components/esp8266/preferences.cpp @@ -0,0 +1,263 @@ +#ifdef USE_ESP8266 + +#include +extern "C" { +#include "spi_flash.h" +} + +#include "preferences.h" +#include +#include "esphome/core/preferences.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/defines.h" + +namespace esphome { +namespace esp8266 { + +static const char *const TAG = "esp8266.preferences"; + +static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static uint32_t *s_flash_storage = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +static const uint32_t ESP_RTC_USER_MEM_START = 0x60001200; +#define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START) +static const uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128; +static const uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4; + +#ifdef USE_ESP8266_PREFERENCES_FLASH +static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 128; +#else +static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 64; +#endif + +static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) { + if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) { + return false; + } + *dest = ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr) + return true; +} + +static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) { + if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) { + return false; + } + if (index < 32 && s_prevent_write) { + return false; + } + + auto *ptr = &ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr) + *ptr = value; + return true; +} + +extern "C" uint32_t _SPIFFS_end; // NOLINT + +static const uint32_t get_esp8266_flash_sector() { + union { + uint32_t *ptr; + uint32_t uint; + } data{}; + data.ptr = &_SPIFFS_end; + return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE; +} +static const uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; } + +template uint32_t calculate_crc(It first, It last, uint32_t type) { + uint32_t crc = type; + while (first != last) { + crc ^= (*first++ * 2654435769UL) >> 1; + } + return crc; +} + +static bool safe_flash() { + if (!s_flash_dirty) + return true; + + ESP_LOGVV(TAG, "Saving preferences to flash..."); + SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK; + { + InterruptLock lock; + erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); + if (erase_res == SPI_FLASH_RESULT_OK) { + write_res = spi_flash_write(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); + } + } + if (erase_res != SPI_FLASH_RESULT_OK) { + ESP_LOGV(TAG, "Erase ESP8266 flash failed!"); + return false; + } + if (write_res != SPI_FLASH_RESULT_OK) { + ESP_LOGV(TAG, "Write ESP8266 flash failed!"); + return false; + } + + s_flash_dirty = false; + return true; +} + +static bool safe_to_flash(size_t offset, const uint32_t *data, size_t len) { + for (uint32_t i = 0; i < len; i++) { + uint32_t j = offset + i; + if (j >= ESP8266_FLASH_STORAGE_SIZE) + return false; + uint32_t v = data[i]; + uint32_t *ptr = &s_flash_storage[j]; + if (*ptr != v) + s_flash_dirty = true; + *ptr = v; + } + return safe_flash(); +} + +static bool load_from_flash(size_t offset, uint32_t *data, size_t len) { + for (size_t i = 0; i < len; i++) { + uint32_t j = offset + i; + if (j >= ESP8266_FLASH_STORAGE_SIZE) + return false; + data[i] = s_flash_storage[j]; + } + return true; +} + +static bool safe_to_rtc(size_t offset, const uint32_t *data, size_t len) { + for (uint32_t i = 0; i < len; i++) + if (!esp_rtc_user_mem_write(offset + i, data[i])) + return false; + return true; +} + +static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) { + for (uint32_t i = 0; i < len; i++) + if (!esp_rtc_user_mem_read(offset + i, &data[i])) + return false; + return true; +} + +class ESP8266PreferenceBackend : public ESPPreferenceBackend { + public: + size_t offset = 0; + uint32_t type = 0; + bool in_flash = false; + size_t length_words = 0; + + bool save(const uint8_t *data, size_t len) override { + if ((len + 3) / 4 != length_words) { + return false; + } + std::vector buffer; + buffer.resize(length_words + 1); + memcpy(buffer.data(), data, len); + buffer[buffer.size() - 1] = calculate_crc(buffer.begin(), buffer.end() - 1, type); + + if (in_flash) { + return safe_to_flash(offset, buffer.data(), buffer.size()); + } else { + return safe_to_rtc(offset, buffer.data(), buffer.size()); + } + } + bool load(uint8_t *data, size_t len) override { + if ((len + 3) / 4 != length_words) { + return false; + } + std::vector buffer; + buffer.resize(length_words + 1); + bool ret; + if (in_flash) { + ret = load_from_flash(offset, buffer.data(), buffer.size()); + } else { + ret = load_from_rtc(offset, buffer.data(), buffer.size()); + } + if (!ret) + return false; + + uint32_t crc = calculate_crc(buffer.begin(), buffer.end() - 1, type); + return buffer[buffer.size() - 1] == crc; + } +}; + +class ESP8266Preferences : public ESPPreferences { + public: + uint32_t current_offset = 0; + uint32_t current_flash_offset = 0; // in words + + void setup() { + s_flash_storage = new uint32_t[ESP8266_FLASH_STORAGE_SIZE]; // NOLINT + ESP_LOGVV(TAG, "Loading preferences from flash..."); + + { + InterruptLock lock; + spi_flash_read(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); + } + } + + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { + uint32_t length_words = (length + 3) / 4; + if (in_flash) { + uint32_t start = current_flash_offset; + uint32_t end = start + length_words + 1; + if (end > ESP8266_FLASH_STORAGE_SIZE) + return {}; + auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + pref->offset = start; + pref->type = type; + pref->length_words = length_words; + pref->in_flash = true; + current_flash_offset = end; + return {pref}; + } + + uint32_t start = current_offset; + uint32_t end = start + length_words + 1; + bool in_normal = start < 96; + // Normal: offset 0-95 maps to RTC offset 32 - 127, + // Eboot: offset 96-127 maps to RTC offset 0 - 31 words + if (in_normal && end > 96) { + // start is in normal but end is not -> switch to Eboot + current_offset = start = 96; + end = start + length_words + 1; + in_normal = false; + } + + if (end > 128) { + // Doesn't fit in data, return uninitialized preference obj. + return {}; + } + + uint32_t rtc_offset = in_normal ? start + 32 : start - 96; + + auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + pref->offset = rtc_offset; + pref->type = type; + pref->length_words = length_words; + pref->in_flash = false; + current_offset += length_words + 1; + return pref; + } + + ESPPreferenceObject make_preference(size_t length, uint32_t type) override { +#ifdef USE_ESP8266_PREFERENCES_FLASH + return make_preference(length, type, true); +#else + return make_preference(length, type, false); +#endif + } +}; + +void setup_preferences() { + auto *pref = new ESP8266Preferences(); // NOLINT(cppcoreguidelines-owning-memory) + pref->setup(); + global_preferences = pref; +} +void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; } + +} // namespace esp8266 + +ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/esp8266/preferences.h b/esphome/components/esp8266/preferences.h new file mode 100644 index 0000000000..edec915794 --- /dev/null +++ b/esphome/components/esp8266/preferences.h @@ -0,0 +1,14 @@ +#pragma once + +#ifdef USE_ESP8266 + +namespace esphome { +namespace esp8266 { + +void setup_preferences(); +void preferences_prevent_write(bool prevent); + +} // namespace esp8266 +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/esp8266_pwm/esp8266_pwm.cpp b/esphome/components/esp8266_pwm/esp8266_pwm.cpp index 186653acd1..e472edf2a7 100644 --- a/esphome/components/esp8266_pwm/esp8266_pwm.cpp +++ b/esphome/components/esp8266_pwm/esp8266_pwm.cpp @@ -1,11 +1,11 @@ -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include "esp8266_pwm.h" #include "esphome/core/macros.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" -#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) +#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) #error ESP8266 PWM requires at least arduino_version 2.4.0 #endif diff --git a/esphome/components/esp8266_pwm/esp8266_pwm.h b/esphome/components/esp8266_pwm/esp8266_pwm.h index 1bf875dc43..79530aacd4 100644 --- a/esphome/components/esp8266_pwm/esp8266_pwm.h +++ b/esphome/components/esp8266_pwm/esp8266_pwm.h @@ -1,9 +1,9 @@ #pragma once -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/automation.h" #include "esphome/components/output/float_output.h" @@ -12,7 +12,7 @@ namespace esp8266_pwm { class ESP8266PWM : public output::FloatOutput, public Component { public: - void set_pin(GPIOPin *pin) { pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } void set_frequency(float frequency) { this->frequency_ = frequency; } /// Dynamically update frequency void update_frequency(float frequency) override { @@ -29,7 +29,7 @@ class ESP8266PWM : public output::FloatOutput, public Component { protected: void write_state(float state) override; - GPIOPin *pin_; + InternalGPIOPin *pin_; float frequency_{1000.0}; /// Cache last output level for dynamic frequency updating float last_output_{0.0}; diff --git a/esphome/components/esp8266_pwm/output.py b/esphome/components/esp8266_pwm/output.py index 770d400013..3d52e5af16 100644 --- a/esphome/components/esp8266_pwm/output.py +++ b/esphome/components/esp8266_pwm/output.py @@ -7,10 +7,9 @@ from esphome.const import ( CONF_ID, CONF_NUMBER, CONF_PIN, - ESP_PLATFORM_ESP8266, ) -ESP_PLATFORMS = [ESP_PLATFORM_ESP8266] +DEPENDENCIES = ["esp8266"] def valid_pwm_pin(value): diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 95b7e40151..384fb3dbfb 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -1,7 +1,6 @@ from esphome import pins import esphome.config_validation as cv import esphome.codegen as cg -from esphome.components.network import add_mdns_library from esphome.const import ( CONF_DOMAIN, CONF_ID, @@ -9,17 +8,16 @@ from esphome.const import ( CONF_STATIC_IP, CONF_TYPE, CONF_USE_ADDRESS, - ESP_PLATFORM_ESP32, - CONF_ENABLE_MDNS, CONF_GATEWAY, CONF_SUBNET, CONF_DNS1, CONF_DNS2, ) from esphome.core import CORE, coroutine_with_priority +from esphome.components.network import IPAddress CONFLICTS_WITH = ["wifi"] -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["esp32"] AUTO_LOAD = ["network"] ethernet_ns = cg.esphome_ns.namespace("ethernet") @@ -55,7 +53,6 @@ MANUAL_IP_SCHEMA = cv.Schema( ) EthernetComponent = ethernet_ns.class_("EthernetComponent", cg.Component) -IPAddress = cg.global_ns.class_("IPAddress") ManualIP = ethernet_ns.struct("ManualIP") @@ -74,20 +71,24 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(EthernetComponent), cv.Required(CONF_TYPE): cv.enum(ETHERNET_TYPES, upper=True), - cv.Required(CONF_MDC_PIN): pins.output_pin, - cv.Required(CONF_MDIO_PIN): pins.input_output_pin, + cv.Required(CONF_MDC_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_MDIO_PIN): pins.internal_gpio_output_pin_number, cv.Optional(CONF_CLK_MODE, default="GPIO0_IN"): cv.enum( CLK_MODES, upper=True, space="_" ), cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31), cv.Optional(CONF_POWER_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_MANUAL_IP): MANUAL_IP_SCHEMA, - cv.Optional(CONF_ENABLE_MDNS, default=True): cv.boolean, cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name, cv.Optional(CONF_USE_ADDRESS): cv.string_strict, + cv.Optional("enable_mdns"): cv.invalid( + "This option has been removed. Please use the [disabled] option under the " + "new mdns component instead." + ), } ).extend(cv.COMPONENT_SCHEMA), _validate, + cv.only_with_arduino, ) @@ -122,6 +123,3 @@ async def to_code(config): cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP]))) cg.add_define("USE_ETHERNET") - - if config[CONF_ENABLE_MDNS]: - add_mdns_library() diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index cad76ae476..d03211deaf 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -3,7 +3,7 @@ #include "esphome/core/util.h" #include "esphome/core/application.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO #include #include @@ -75,10 +75,6 @@ void EthernetComponent::setup() { ESPHL_ERROR_CHECK(err, "ETH init error"); err = esp_eth_enable(); ESPHL_ERROR_CHECK(err, "ETH enable error"); - -#ifdef USE_MDNS - network_setup_mdns(); -#endif } void EthernetComponent::loop() { const uint32_t now = millis(); @@ -118,8 +114,6 @@ void EthernetComponent::loop() { } break; } - - network_tick_mdns(); } void EthernetComponent::dump_config() { ESP_LOGCONFIG(TAG, "Ethernet:"); @@ -131,10 +125,10 @@ void EthernetComponent::dump_config() { } float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } bool EthernetComponent::can_proceed() { return this->is_connected(); } -IPAddress EthernetComponent::get_ip_address() { +network::IPAddress EthernetComponent::get_ip_address() { tcpip_adapter_ip_info_t ip; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); - return IPAddress(ip.ip.addr); + return {ip.ip.addr}; } void EthernetComponent::on_wifi_event_(system_event_id_t event, system_event_info_t info) { @@ -229,10 +223,10 @@ bool EthernetComponent::is_connected() { return this->state_ == EthernetComponen void EthernetComponent::dump_connect_params_() { tcpip_adapter_ip_info_t ip; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); - ESP_LOGCONFIG(TAG, " IP Address: %s", IPAddress(ip.ip.addr).toString().c_str()); + ESP_LOGCONFIG(TAG, " IP Address: %s", network::IPAddress(ip.ip.addr).str().c_str()); ESP_LOGCONFIG(TAG, " Hostname: '%s'", App.get_name().c_str()); - ESP_LOGCONFIG(TAG, " Subnet: %s", IPAddress(ip.netmask.addr).toString().c_str()); - ESP_LOGCONFIG(TAG, " Gateway: %s", IPAddress(ip.gw.addr).toString().c_str()); + ESP_LOGCONFIG(TAG, " Subnet: %s", network::IPAddress(ip.netmask.addr).str().c_str()); + ESP_LOGCONFIG(TAG, " Gateway: %s", network::IPAddress(ip.gw.addr).str().c_str()); #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 4) const ip_addr_t *dns_ip1 = dns_getserver(0); @@ -243,8 +237,8 @@ void EthernetComponent::dump_connect_params_() { ip_addr_t tmp_ip2 = dns_getserver(1); const ip_addr_t *dns_ip2 = &tmp_ip2; #endif - ESP_LOGCONFIG(TAG, " DNS1: %s", IPAddress(dns_ip1->u_addr.ip4.addr).toString().c_str()); - ESP_LOGCONFIG(TAG, " DNS2: %s", IPAddress(dns_ip2->u_addr.ip4.addr).toString().c_str()); + ESP_LOGCONFIG(TAG, " DNS1: %s", network::IPAddress(dns_ip1->u_addr.ip4.addr).str().c_str()); + ESP_LOGCONFIG(TAG, " DNS2: %s", network::IPAddress(dns_ip2->u_addr.ip4.addr).str().c_str()); uint8_t mac[6]; esp_eth_get_mac(mac); ESP_LOGCONFIG(TAG, " MAC Address: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); @@ -270,4 +264,4 @@ void EthernetComponent::set_use_address(const std::string &use_address) { this-> } // namespace ethernet } // namespace esphome -#endif +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index dc8816ef86..3e0c798a0c 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -1,9 +1,10 @@ #pragma once -#include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#ifdef USE_ESP32_FRAMEWORK_ARDUINO -#ifdef ARDUINO_ARCH_ESP32 +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/network/ip_address.h" #include "esp_eth.h" #include @@ -19,11 +20,11 @@ enum EthernetType { }; struct ManualIP { - IPAddress static_ip; - IPAddress gateway; - IPAddress subnet; - IPAddress dns1; ///< The first DNS server. 0.0.0.0 for default. - IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default. + network::IPAddress static_ip; + network::IPAddress gateway; + network::IPAddress subnet; + network::IPAddress dns1; ///< The first DNS server. 0.0.0.0 for default. + network::IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default. }; enum class EthernetComponentState { @@ -50,7 +51,7 @@ class EthernetComponent : public Component { void set_clk_mode(eth_clock_mode_t clk_mode); void set_manual_ip(const ManualIP &manual_ip); - IPAddress get_ip_address(); + network::IPAddress get_ip_address(); std::string get_use_address() const; void set_use_address(const std::string &use_address); @@ -84,4 +85,4 @@ extern EthernetComponent *global_eth_component; } // namespace ethernet } // namespace esphome -#endif +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/exposure_notifications/exposure_notifications.cpp b/esphome/components/exposure_notifications/exposure_notifications.cpp index 9db181fbee..3083cf429c 100644 --- a/esphome/components/exposure_notifications/exposure_notifications.cpp +++ b/esphome/components/exposure_notifications/exposure_notifications.cpp @@ -2,7 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/helpers.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace exposure_notifications { diff --git a/esphome/components/exposure_notifications/exposure_notifications.h b/esphome/components/exposure_notifications/exposure_notifications.h index 6b9f61b2a0..f7383c28d9 100644 --- a/esphome/components/exposure_notifications/exposure_notifications.h +++ b/esphome/components/exposure_notifications/exposure_notifications.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace exposure_notifications { diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index 90b1e4ace9..81597f3466 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -1,5 +1,6 @@ #include "ezo.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace ezo { @@ -24,7 +25,7 @@ void EZOSensor::update() { return; } uint8_t c = 'R'; - this->write_bytes_raw(&c, 1); + this->write(&c, 1); this->state_ |= EZO_STATE_WAIT; this->start_time_ = millis(); this->wait_time_ = 900; @@ -35,7 +36,7 @@ void EZOSensor::loop() { if (!(this->state_ & EZO_STATE_WAIT)) { if (this->state_ & EZO_STATE_SEND_TEMP) { int len = sprintf((char *) buf, "T,%0.3f", this->tempcomp_); - this->write_bytes_raw(buf, len); + this->write(buf, len); this->state_ = EZO_STATE_WAIT | EZO_STATE_WAIT_TEMP; this->start_time_ = millis(); this->wait_time_ = 300; diff --git a/esphome/components/fan/fan_state.cpp b/esphome/components/fan/fan_state.cpp index a57115beb4..921b8d57e7 100644 --- a/esphome/components/fan/fan_state.cpp +++ b/esphome/components/fan/fan_state.cpp @@ -27,7 +27,7 @@ struct FanStateRTCState { }; void FanState::setup() { - this->rtc_ = global_preferences.make_preference(this->get_object_id_hash()); + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); FanStateRTCState recovered{}; if (!this->rtc_.load(&recovered)) return; diff --git a/esphome/components/fastled_base/fastled_light.cpp b/esphome/components/fastled_base/fastled_light.cpp index 15a5c8984c..486364d0c0 100644 --- a/esphome/components/fastled_base/fastled_light.cpp +++ b/esphome/components/fastled_base/fastled_light.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "fastled_light.h" #include "esphome/core/log.h" @@ -37,3 +39,5 @@ void FastLEDLightOutput::write_state(light::LightState *state) { } // namespace fastled_base } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/fastled_base/fastled_light.h b/esphome/components/fastled_base/fastled_light.h index d1c470599d..80840c3003 100644 --- a/esphome/components/fastled_base/fastled_light.h +++ b/esphome/components/fastled_base/fastled_light.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/components/light/addressable_light.h" @@ -237,3 +239,5 @@ class FastLEDLightOutput : public light::AddressableLight { } // namespace fastled_base } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/fastled_clockless/light.py b/esphome/components/fastled_clockless/light.py index d437d01dcf..acf9488ae3 100644 --- a/esphome/components/fastled_clockless/light.py +++ b/esphome/components/fastled_clockless/light.py @@ -45,10 +45,11 @@ CONFIG_SCHEMA = cv.All( fastled_base.BASE_SCHEMA.extend( { cv.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), - cv.Required(CONF_PIN): pins.output_pin, + cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number, } ), _validate, + cv.only_with_arduino, ) diff --git a/esphome/components/fastled_spi/light.py b/esphome/components/fastled_spi/light.py index d6ba0e8358..a729fc015a 100644 --- a/esphome/components/fastled_spi/light.py +++ b/esphome/components/fastled_spi/light.py @@ -24,13 +24,16 @@ CHIPSETS = [ "DOTSTAR", ] -CONFIG_SCHEMA = fastled_base.BASE_SCHEMA.extend( - { - cv.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), - cv.Required(CONF_DATA_PIN): pins.output_pin, - cv.Required(CONF_CLOCK_PIN): pins.output_pin, - cv.Optional(CONF_DATA_RATE): cv.frequency, - } +CONFIG_SCHEMA = cv.All( + fastled_base.BASE_SCHEMA.extend( + { + cv.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), + cv.Required(CONF_DATA_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_CLOCK_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_DATA_RATE): cv.frequency, + } + ), + cv.only_with_arduino, ) diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.cpp b/esphome/components/fingerprint_grow/fingerprint_grow.cpp index b0c0be59af..be17e29de3 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.cpp +++ b/esphome/components/fingerprint_grow/fingerprint_grow.cpp @@ -15,7 +15,7 @@ void FingerprintGrowComponent::update() { } if (this->sensing_pin_ != nullptr) { - if (this->sensing_pin_->digital_read() == HIGH) { + if (this->sensing_pin_->digital_read()) { ESP_LOGV(TAG, "No touch sensing"); this->waiting_removal_ = false; return; diff --git a/esphome/components/globals/__init__.py b/esphome/components/globals/__init__.py index 9039d0d62e..97a7ba3d54 100644 --- a/esphome/components/globals/__init__.py +++ b/esphome/components/globals/__init__.py @@ -14,6 +14,7 @@ from esphome.core import coroutine_with_priority CODEOWNERS = ["@esphome/core"] globals_ns = cg.esphome_ns.namespace("globals") GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component) +RestoringGlobalsComponent = globals_ns.class_("RestoringGlobalsComponent", cg.Component) GlobalVarSetAction = globals_ns.class_("GlobalVarSetAction", automation.Action) MULTI_CONF = True @@ -32,22 +33,25 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): type_ = cg.RawExpression(config[CONF_TYPE]) template_args = cg.TemplateArguments(type_) - res_type = GlobalsComponent.template(template_args) + restore = config[CONF_RESTORE_VALUE] + + type = RestoringGlobalsComponent if restore else GlobalsComponent + res_type = type.template(template_args) initial_value = None if CONF_INITIAL_VALUE in config: initial_value = cg.RawExpression(config[CONF_INITIAL_VALUE]) - rhs = GlobalsComponent.new(template_args, initial_value) + rhs = type.new(template_args, initial_value) glob = cg.Pvariable(config[CONF_ID], rhs, res_type) await cg.register_component(glob, config) - if config[CONF_RESTORE_VALUE]: + if restore: value = config[CONF_ID].id if isinstance(value, str): value = value.encode() hash_ = int(hashlib.md5(value).hexdigest()[:8], 16) - cg.add(glob.set_restore_value(hash_)) + cg.add(glob.set_name_hash(hash_)) @automation.register_action( diff --git a/esphome/components/globals/globals_component.h b/esphome/components/globals/globals_component.h index 397c55f6c4..b39c5f404b 100644 --- a/esphome/components/globals/globals_component.h +++ b/esphome/components/globals/globals_component.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" #include "esphome/core/helpers.h" +#include namespace esphome { namespace globals { @@ -16,37 +17,46 @@ template class GlobalsComponent : public Component { memcpy(this->value_, initial_value.data(), sizeof(T)); } + T &value() { return this->value_; } + void setup() override {} + + protected: + T value_{}; +}; + +template class RestoringGlobalsComponent : public Component { + public: + using value_type = T; + explicit RestoringGlobalsComponent() = default; + explicit RestoringGlobalsComponent(T initial_value) : value_(initial_value) {} + explicit RestoringGlobalsComponent( + std::array::type, std::extent::value> initial_value) { + memcpy(this->value_, initial_value.data(), sizeof(T)); + } + T &value() { return this->value_; } void setup() override { - if (this->restore_value_) { - this->rtc_ = global_preferences.make_preference(1944399030U ^ this->name_hash_); - this->rtc_.load(&this->value_); - } + this->rtc_ = global_preferences->make_preference(1944399030U ^ this->name_hash_); + this->rtc_.load(&this->value_); memcpy(&this->prev_value_, &this->value_, sizeof(T)); } float get_setup_priority() const override { return setup_priority::HARDWARE; } void loop() override { - if (this->restore_value_) { - int diff = memcmp(&this->value_, &this->prev_value_, sizeof(T)); - if (diff != 0) { - this->rtc_.save(&this->value_); - memcpy(&this->prev_value_, &this->value_, sizeof(T)); - } + int diff = memcmp(&this->value_, &this->prev_value_, sizeof(T)); + if (diff != 0) { + this->rtc_.save(&this->value_); + memcpy(&this->prev_value_, &this->value_, sizeof(T)); } } - void set_restore_value(uint32_t name_hash) { - this->restore_value_ = true; - this->name_hash_ = name_hash; - } + void set_name_hash(uint32_t name_hash) { this->name_hash_ = name_hash; } protected: T value_{}; T prev_value_{}; - bool restore_value_{false}; uint32_t name_hash_{}; ESPPreferenceObject rtc_; }; diff --git a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h index f655207243..33a173fe2e 100644 --- a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h +++ b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/binary_sensor/binary_sensor.h" namespace esphome { diff --git a/esphome/components/gpio/output/gpio_binary_output.h b/esphome/components/gpio/output/gpio_binary_output.h index 0a7dfb46e2..6b72c61c0f 100644 --- a/esphome/components/gpio/output/gpio_binary_output.h +++ b/esphome/components/gpio/output/gpio_binary_output.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/output/binary_output.h" namespace esphome { diff --git a/esphome/components/gpio/switch/gpio_switch.h b/esphome/components/gpio/switch/gpio_switch.h index b39e070c85..99f8060efa 100644 --- a/esphome/components/gpio/switch/gpio_switch.h +++ b/esphome/components/gpio/switch/gpio_switch.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/switch/switch.h" namespace esphome { diff --git a/esphome/components/gps/__init__.py b/esphome/components/gps/__init__.py index 9d323e6e4d..e485373175 100644 --- a/esphome/components/gps/__init__.py +++ b/esphome/components/gps/__init__.py @@ -28,7 +28,7 @@ GPSListener = gps_ns.class_("GPSListener") CONF_GPS_ID = "gps_id" MULTI_CONF = True -CONFIG_SCHEMA = ( +CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(GPS), @@ -64,7 +64,8 @@ CONFIG_SCHEMA = ( } ) .extend(cv.polling_component_schema("20s")) - .extend(uart.UART_DEVICE_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA), + cv.only_with_arduino, ) FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema("gps", require_rx=True) diff --git a/esphome/components/gps/gps.cpp b/esphome/components/gps/gps.cpp index 1e8ca94e9e..8c924d629c 100644 --- a/esphome/components/gps/gps.cpp +++ b/esphome/components/gps/gps.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "gps.h" #include "esphome/core/log.h" @@ -69,3 +71,5 @@ void GPS::loop() { } // namespace gps } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/gps/gps.h b/esphome/components/gps/gps.h index 50dd476ae3..40cda145ca 100644 --- a/esphome/components/gps/gps.h +++ b/esphome/components/gps/gps.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include "esphome/components/uart/uart.h" #include "esphome/components/sensor/sensor.h" @@ -63,3 +65,5 @@ class GPS : public PollingComponent, public uart::UARTDevice { } // namespace gps } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/gps/time/gps_time.cpp b/esphome/components/gps/time/gps_time.cpp index 5352c7e059..e46f24ba8e 100644 --- a/esphome/components/gps/time/gps_time.cpp +++ b/esphome/components/gps/time/gps_time.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "gps_time.h" #include "esphome/core/log.h" @@ -32,3 +34,5 @@ void GPSTime::from_tiny_gps_(TinyGPSPlus &tiny_gps) { } // namespace gps } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/gps/time/gps_time.h b/esphome/components/gps/time/gps_time.h index a1f69a7130..d0d1db83b5 100644 --- a/esphome/components/gps/time/gps_time.h +++ b/esphome/components/gps/time/gps_time.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include "esphome/components/time/real_time_clock.h" #include "esphome/components/gps/gps.h" @@ -22,3 +24,5 @@ class GPSTime : public time::RealTimeClock, public GPSListener { } // namespace gps } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index 6ede553fdb..ff736b7cb7 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -2,7 +2,7 @@ #include "esphome/components/display/display_buffer.h" #include "esphome/core/color.h" #include "esphome/core/log.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include #include #include // std::cout, std::fixed @@ -34,12 +34,12 @@ void HistoryData::take_sample(float data) { this->count_ = (this->count_ + 1) % this->length_; ESP_LOGV(TAG, "Updating trace with value: %f", data); } - if (!isnan(data)) { + if (!std::isnan(data)) { // Recalc recent max/min this->recent_min_ = data; this->recent_max_ = data; for (int i = 0; i < this->length_; i++) { - if (!isnan(this->samples_[i])) { + if (!std::isnan(this->samples_[i])) { if (this->recent_max_ < this->samples_[i]) this->recent_max_ = this->samples_[i]; if (this->recent_min_ > this->samples_[i]) @@ -70,15 +70,15 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo for (auto *trace : traces_) { float mx = trace->get_tracedata()->get_recent_max(); float mn = trace->get_tracedata()->get_recent_min(); - if (isnan(ymax) || (ymax < mx)) + if (std::isnan(ymax) || (ymax < mx)) ymax = mx; - if (isnan(ymin) || (ymin > mn)) + if (std::isnan(ymin) || (ymin > mn)) ymin = mn; } // Adjust if manually overridden - if (!isnan(this->min_value_)) + if (!std::isnan(this->min_value_)) ymin = this->min_value_; - if (!isnan(this->max_value_)) + if (!std::isnan(this->max_value_)) ymax = this->max_value_; float yrange = ymax - ymin; @@ -89,20 +89,20 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo for (int16_t i = 0; i < this->width_; i++) { for (auto *trace : traces_) { float v = trace->get_tracedata()->get_value(i); - if (!isnan(v)) { + if (!std::isnan(v)) { if ((v - mn) > this->max_range_) break; if ((mx - v) > this->max_range_) break; - if (isnan(mx) || (v > mx)) + if (std::isnan(mx) || (v > mx)) mx = v; - if (isnan(mn) || (v < mn)) + if (std::isnan(mn) || (v < mn)) mn = v; } } } yrange = this->max_range_; - if (!isnan(mn)) { + if (!std::isnan(mn)) { ymin = mn; ymax = ymin + this->max_range_; } @@ -110,7 +110,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo } float y_per_div = this->min_range_; - if (!isnan(this->gridspacing_y_)) { + if (!std::isnan(this->gridspacing_y_)) { y_per_div = this->gridspacing_y_; } // Restrict drawing too many gridlines @@ -129,7 +129,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo yrange = ymax - ymin; /// Draw grid - if (!isnan(this->gridspacing_y_)) { + if (!std::isnan(this->gridspacing_y_)) { for (int y = yn; y <= ym; y++) { int16_t py = (int16_t) roundf((this->height_ - 1) * (1.0 - (float) (y - yn) / (ym - yn))); for (int x = 0; x < this->width_; x += 2) { @@ -137,7 +137,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo } } } - if (!isnan(this->gridspacing_x_) && (this->gridspacing_x_ > 0)) { + if (!std::isnan(this->gridspacing_x_) && (this->gridspacing_x_ > 0)) { int n = this->duration_ / this->gridspacing_x_; // Restrict drawing too many gridlines if (n > 20) { @@ -160,7 +160,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo uint16_t thick = trace->get_line_thickness(); for (int16_t i = 0; i < this->width_; i++) { float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange; - if (!isnan(v) && (thick > 0)) { + if (!std::isnan(v) && (thick > 0)) { int16_t x = this->width_ - 1 - i; uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { diff --git a/esphome/components/hdc1080/hdc1080.cpp b/esphome/components/hdc1080/hdc1080.cpp index 507ac77a28..60e8943e67 100644 --- a/esphome/components/hdc1080/hdc1080.cpp +++ b/esphome/components/hdc1080/hdc1080.cpp @@ -1,5 +1,6 @@ #include "hdc1080.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace hdc1080 { @@ -36,18 +37,30 @@ void HDC1080Component::dump_config() { } void HDC1080Component::update() { uint16_t raw_temp; - if (!this->read_byte_16(HDC1080_CMD_TEMPERATURE, &raw_temp, 20)) { + if (this->write(&HDC1080_CMD_TEMPERATURE, 1) != i2c::ERROR_OK) { this->status_set_warning(); return; } + delay(20); + if (this->read(reinterpret_cast(&raw_temp), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + raw_temp = i2c::i2ctohs(raw_temp); float temp = raw_temp * 0.0025177f - 40.0f; // raw * 2^-16 * 165 - 40 this->temperature_->publish_state(temp); uint16_t raw_humidity; - if (!this->read_byte_16(HDC1080_CMD_HUMIDITY, &raw_humidity, 20)) { + if (this->write(&HDC1080_CMD_HUMIDITY, 1) != i2c::ERROR_OK) { this->status_set_warning(); return; } + delay(20); + if (this->read(reinterpret_cast(&raw_humidity), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + raw_humidity = i2c::i2ctohs(raw_humidity); float humidity = raw_humidity * 0.001525879f; // raw * 2^-16 * 100 this->humidity_->publish_state(humidity); diff --git a/esphome/components/hlw8012/hlw8012.h b/esphome/components/hlw8012/hlw8012.h index 52fb03c020..5060957cf1 100644 --- a/esphome/components/hlw8012/hlw8012.h +++ b/esphome/components/hlw8012/hlw8012.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/pulse_counter/pulse_counter_sensor.h" @@ -31,8 +31,8 @@ class HLW8012Component : public PollingComponent { void set_current_resistor(float current_resistor) { current_resistor_ = current_resistor; } void set_voltage_divider(float voltage_divider) { voltage_divider_ = voltage_divider; } void set_sel_pin(GPIOPin *sel_pin) { sel_pin_ = sel_pin; } - void set_cf_pin(GPIOPin *cf_pin) { cf_pin_ = cf_pin; } - void set_cf1_pin(GPIOPin *cf1_pin) { cf1_pin_ = cf1_pin; } + void set_cf_pin(InternalGPIOPin *cf_pin) { cf_pin_ = cf_pin; } + void set_cf1_pin(InternalGPIOPin *cf1_pin) { cf1_pin_ = cf1_pin; } void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } @@ -48,9 +48,9 @@ class HLW8012Component : public PollingComponent { HLW8012SensorModels sensor_model_{HLW8012_SENSOR_MODEL_HLW8012}; uint64_t cf_total_pulses_{0}; GPIOPin *sel_pin_; - GPIOPin *cf_pin_; + InternalGPIOPin *cf_pin_; pulse_counter::PulseCounterStorage cf_store_; - GPIOPin *cf1_pin_; + InternalGPIOPin *cf1_pin_; pulse_counter::PulseCounterStorage cf1_store_; sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *current_sensor_{nullptr}; diff --git a/esphome/components/hlw8012/sensor.py b/esphome/components/hlw8012/sensor.py index 11e9c8e4d4..033cccc3d4 100644 --- a/esphome/components/hlw8012/sensor.py +++ b/esphome/components/hlw8012/sensor.py @@ -50,12 +50,8 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(HLW8012Component), cv.Required(CONF_SEL_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_CF_PIN): cv.All( - pins.internal_gpio_input_pullup_pin_schema, pins.validate_has_interrupt - ), - cv.Required(CONF_CF1_PIN): cv.All( - pins.internal_gpio_input_pullup_pin_schema, pins.validate_has_interrupt - ), + cv.Required(CONF_CF_PIN): cv.All(pins.internal_gpio_input_pullup_pin_schema), + cv.Required(CONF_CF1_PIN): cv.All(pins.internal_gpio_input_pullup_pin_schema), cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=1, diff --git a/esphome/components/hm3301/abstract_aqi_calculator.h b/esphome/components/hm3301/abstract_aqi_calculator.h index f2573ff108..fb41b921d9 100644 --- a/esphome/components/hm3301/abstract_aqi_calculator.h +++ b/esphome/components/hm3301/abstract_aqi_calculator.h @@ -1,5 +1,8 @@ #pragma once +#ifdef USE_ARDUINO +#include + namespace esphome { namespace hm3301 { @@ -10,3 +13,5 @@ class AbstractAQICalculator { } // namespace hm3301 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/aqi_calculator.h b/esphome/components/hm3301/aqi_calculator.h index 627ee686fc..1410eac72b 100644 --- a/esphome/components/hm3301/aqi_calculator.h +++ b/esphome/components/hm3301/aqi_calculator.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "abstract_aqi_calculator.h" namespace esphome { @@ -46,3 +48,5 @@ class AQICalculator : public AbstractAQICalculator { } // namespace hm3301 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/aqi_calculator_factory.h b/esphome/components/hm3301/aqi_calculator_factory.h index 55608b6e51..3c6f9709b6 100644 --- a/esphome/components/hm3301/aqi_calculator_factory.h +++ b/esphome/components/hm3301/aqi_calculator_factory.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "caqi_calculator.h" #include "aqi_calculator.h" @@ -27,3 +29,5 @@ class AQICalculatorFactory { } // namespace hm3301 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/caqi_calculator.h b/esphome/components/hm3301/caqi_calculator.h index 403bac2713..51158454d0 100644 --- a/esphome/components/hm3301/caqi_calculator.h +++ b/esphome/components/hm3301/caqi_calculator.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/log.h" #include "abstract_aqi_calculator.h" @@ -52,3 +54,5 @@ class CAQICalculator : public AbstractAQICalculator { } // namespace hm3301 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/hm3301.cpp b/esphome/components/hm3301/hm3301.cpp index 96c1ec0ee9..759157f330 100644 --- a/esphome/components/hm3301/hm3301.cpp +++ b/esphome/components/hm3301/hm3301.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "esphome/core/log.h" #include "hm3301.h" @@ -102,3 +104,5 @@ uint16_t HM3301Component::get_sensor_value_(const uint8_t *data, uint8_t i) { } // namespace hm3301 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/hm3301.h b/esphome/components/hm3301/hm3301.h index b024f93719..61bbf7e4ab 100644 --- a/esphome/components/hm3301/hm3301.h +++ b/esphome/components/hm3301/hm3301.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" @@ -48,3 +50,5 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice { } // namespace hm3301 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/sensor.py b/esphome/components/hm3301/sensor.py index 2c3c2f7221..976a0488e1 100644 --- a/esphome/components/hm3301/sensor.py +++ b/esphome/components/hm3301/sensor.py @@ -84,6 +84,7 @@ CONFIG_SCHEMA = cv.All( .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x40)), _validate, + cv.only_with_arduino, ) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index eea2f0d407..a48b3c0acb 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -2,7 +2,6 @@ import urllib.parse as urlparse import esphome.codegen as cg import esphome.config_validation as cv -import esphome.final_validate as fv from esphome import automation from esphome.const import ( CONF_ID, @@ -12,7 +11,7 @@ from esphome.const import ( CONF_URL, CONF_ESP8266_DISABLE_SSL_SUPPORT, ) -from esphome.core import CORE, Lambda +from esphome.core import Lambda, CORE DEPENDENCIES = ["network"] AUTO_LOAD = ["json"] @@ -67,35 +66,24 @@ def validate_secure_url(config): return config -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(HttpRequestComponent), - cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string, - cv.Optional(CONF_TIMEOUT, default="5s"): cv.positive_time_period_milliseconds, - cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All( - cv.only_on_esp8266, cv.boolean - ), - } -).extend(cv.COMPONENT_SCHEMA) - - -def validate_framework(value): - if not CORE.is_esp8266: - # only for ESP8266 - return - - framework_version = fv.get_arduino_framework_version() - if framework_version is None or framework_version == "dev": - return - - if framework_version < "2.5.1": - raise cv.Invalid( - "This component is not supported on arduino framework version below 2.5.1, ", - "please check esphome->arduino_version", - ) - - -FINAL_VALIDATE_SCHEMA = cv.Schema(validate_framework) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HttpRequestComponent), + cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string, + cv.Optional( + CONF_TIMEOUT, default="5s" + ): cv.positive_time_period_milliseconds, + cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All( + cv.only_on_esp8266, cv.boolean + ), + } + ).extend(cv.COMPONENT_SCHEMA), + cv.require_framework_version( + esp8266_arduino=cv.Version(2, 5, 1), + esp32_arduino=cv.Version(0, 0, 0), + ), +) async def to_code(config): diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index 3f5bf6da87..f88ee19e5c 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "http_request.h" #include "esphome/core/macros.h" #include "esphome/core/log.h" @@ -28,10 +30,10 @@ void HttpRequestComponent::set_url(std::string url) { void HttpRequestComponent::send(const std::vector &response_triggers) { bool begin_status = false; const String url = this->url_.c_str(); -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 begin_status = this->client_.begin(url); #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) this->client_.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); #elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) @@ -79,7 +81,7 @@ void HttpRequestComponent::send(const std::vector ESP_LOGD(TAG, "HTTP Request completed; URL: %s; Code: %d", this->url_.c_str(), http_code); } -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 std::shared_ptr HttpRequestComponent::get_wifi_client_() { #ifdef USE_HTTP_REQUEST_ESP8266_HTTPS if (this->secure_) { @@ -111,3 +113,5 @@ const char *HttpRequestComponent::get_string() { } // namespace http_request } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index fa29f8aa3a..511096e7fa 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/components/json/json_util.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" @@ -9,10 +11,10 @@ #include #include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include #ifdef USE_HTTP_REQUEST_ESP8266_HTTPS #include @@ -54,7 +56,7 @@ class HttpRequestComponent : public Component { uint16_t timeout_{5000}; std::string body_; std::list
    headers_; -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 std::shared_ptr wifi_client_; #ifdef USE_HTTP_REQUEST_ESP8266_HTTPS std::shared_ptr wifi_client_secure_; @@ -137,3 +139,5 @@ class HttpRequestResponseTrigger : public Trigger { } // namespace http_request } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/htu21d/htu21d.cpp b/esphome/components/htu21d/htu21d.cpp index a954b2ad59..b53284ae3f 100644 --- a/esphome/components/htu21d/htu21d.cpp +++ b/esphome/components/htu21d/htu21d.cpp @@ -1,5 +1,6 @@ #include "htu21d.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace htu21d { @@ -35,18 +36,30 @@ void HTU21DComponent::dump_config() { } void HTU21DComponent::update() { uint16_t raw_temperature; - if (!this->read_byte_16(HTU21D_REGISTER_TEMPERATURE, &raw_temperature, 50)) { + if (this->write(&HTU21D_REGISTER_TEMPERATURE, 1) != i2c::ERROR_OK) { this->status_set_warning(); return; } + delay(50); // NOLINT + if (this->read(reinterpret_cast(&raw_temperature), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + raw_temperature = i2c::i2ctohs(raw_temperature); float temperature = (float(raw_temperature & 0xFFFC)) * 175.72f / 65536.0f - 46.85f; uint16_t raw_humidity; - if (!this->read_byte_16(HTU21D_REGISTER_HUMIDITY, &raw_humidity, 50)) { + if (this->write(&HTU21D_REGISTER_HUMIDITY, 1) != i2c::ERROR_OK) { this->status_set_warning(); return; } + delay(50); // NOLINT + if (this->read(reinterpret_cast(&raw_humidity), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + raw_humidity = i2c::i2ctohs(raw_humidity); float humidity = (float(raw_humidity & 0xFFFC)) * 125.0f / 65536.0f - 6.0f; ESP_LOGD(TAG, "Got Temperature=%.1f°C Humidity=%.1f%%", temperature, humidity); diff --git a/esphome/components/hx711/hx711.h b/esphome/components/hx711/hx711.h index 91c8317ee5..9fef649b03 100644 --- a/esphome/components/hx711/hx711.h +++ b/esphome/components/hx711/hx711.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" namespace esphome { diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index f43729066d..46f0abacc6 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -2,30 +2,56 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins from esphome.const import ( - CONF_CHANNEL, CONF_FREQUENCY, CONF_ID, + CONF_INPUT, + CONF_OUTPUT, CONF_SCAN, CONF_SCL, CONF_SDA, CONF_ADDRESS, CONF_I2C_ID, - CONF_MULTIPLEXER, ) -from esphome.core import coroutine_with_priority +from esphome.core import coroutine_with_priority, CORE CODEOWNERS = ["@esphome/core"] i2c_ns = cg.esphome_ns.namespace("i2c") -I2CComponent = i2c_ns.class_("I2CComponent", cg.Component) +I2CBus = i2c_ns.class_("I2CBus") +ArduinoI2CBus = i2c_ns.class_("ArduinoI2CBus", I2CBus, cg.Component) +IDFI2CBus = i2c_ns.class_("IDFI2CBus", I2CBus, cg.Component) I2CDevice = i2c_ns.class_("I2CDevice") -I2CMultiplexer = i2c_ns.class_("I2CMultiplexer", I2CDevice) + +CONF_SDA_PULLUP_ENABLED = "sda_pullup_enabled" +CONF_SCL_PULLUP_ENABLED = "scl_pullup_enabled" MULTI_CONF = True + + +def _bus_declare_type(value): + if CORE.using_arduino: + return cv.declare_id(ArduinoI2CBus)(value) + if CORE.using_esp_idf: + return cv.declare_id(IDFI2CBus)(value) + raise NotImplementedError + + +pin_with_input_and_output_support = cv.All( + pins.internal_gpio_pin_number({CONF_INPUT: True}), + pins.internal_gpio_pin_number({CONF_OUTPUT: True}), +) + + CONFIG_SCHEMA = cv.Schema( { - cv.GenerateID(): cv.declare_id(I2CComponent), - cv.Optional(CONF_SDA, default="SDA"): pins.input_pin, - cv.Optional(CONF_SCL, default="SCL"): pins.input_pin, + cv.GenerateID(): _bus_declare_type, + cv.Optional(CONF_SDA, default="SDA"): pin_with_input_and_output_support, + cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32_idf=True): cv.All( + cv.only_with_esp_idf, cv.boolean + ), + cv.Optional(CONF_SCL, default="SCL"): pin_with_input_and_output_support, + cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32_idf=True): cv.All( + cv.only_with_esp_idf, cv.boolean + ), cv.Optional(CONF_FREQUENCY, default="50kHz"): cv.All( cv.frequency, cv.Range(min=0, min_included=False) ), @@ -33,13 +59,6 @@ CONFIG_SCHEMA = cv.Schema( } ).extend(cv.COMPONENT_SCHEMA) -I2CMULTIPLEXER_SCHEMA = cv.Schema( - { - cv.Required(CONF_ID): cv.use_id(I2CMultiplexer), - cv.Required(CONF_CHANNEL): cv.uint8_t, - } -) - @coroutine_with_priority(1.0) async def to_code(config): @@ -48,10 +67,16 @@ async def to_code(config): await cg.register_component(var, config) cg.add(var.set_sda_pin(config[CONF_SDA])) + if CONF_SDA_PULLUP_ENABLED in config: + cg.add(var.set_sda_pullup_enabled(config[CONF_SDA_PULLUP_ENABLED])) cg.add(var.set_scl_pin(config[CONF_SCL])) + if CONF_SCL_PULLUP_ENABLED in config: + cg.add(var.set_scl_pullup_enabled(config[CONF_SCL_PULLUP_ENABLED])) + cg.add(var.set_frequency(int(config[CONF_FREQUENCY]))) cg.add(var.set_scan(config[CONF_SCAN])) - cg.add_library("Wire", None) + if CORE.using_arduino: + cg.add_library("Wire", None) def i2c_device_schema(default_address): @@ -62,8 +87,11 @@ def i2c_device_schema(default_address): :return: The i2c device schema, `extend` this in your config schema. """ schema = { - cv.GenerateID(CONF_I2C_ID): cv.use_id(I2CComponent), - cv.Optional(CONF_MULTIPLEXER): I2CMULTIPLEXER_SCHEMA, + cv.GenerateID(CONF_I2C_ID): cv.use_id(I2CBus), + cv.Optional("multiplexer"): cv.invalid( + "This option has been removed, please see " + "the tca9584a docs for the updated way to use multiplexers" + ), } if default_address is None: schema[cv.Required(CONF_ADDRESS)] = cv.i2c_address @@ -80,10 +108,5 @@ async def register_i2c_device(var, config): This is a coroutine, you need to await it with a 'yield' expression! """ parent = await cg.get_variable(config[CONF_I2C_ID]) - cg.add(var.set_i2c_parent(parent)) + cg.add(var.set_i2c_bus(parent)) cg.add(var.set_i2c_address(config[CONF_ADDRESS])) - if CONF_MULTIPLEXER in config: - multiplexer = await cg.get_variable(config[CONF_MULTIPLEXER][CONF_ID]) - cg.add( - var.set_i2c_multiplexer(multiplexer, config[CONF_MULTIPLEXER][CONF_CHANNEL]) - ) diff --git a/esphome/components/i2c/i2c.cpp b/esphome/components/i2c/i2c.cpp index a3a3f7f82a..035c483344 100644 --- a/esphome/components/i2c/i2c.cpp +++ b/esphome/components/i2c/i2c.cpp @@ -1,303 +1,40 @@ #include "i2c.h" #include "esphome/core/log.h" -#include "esphome/core/helpers.h" -#include "esphome/core/application.h" +#include namespace esphome { namespace i2c { static const char *const TAG = "i2c"; -I2CComponent::I2CComponent() { -#ifdef ARDUINO_ARCH_ESP32 - static uint8_t next_i2c_bus_num = 0; - if (next_i2c_bus_num == 0) - this->wire_ = &Wire; - else - this->wire_ = new TwoWire(next_i2c_bus_num); // NOLINT(cppcoreguidelines-owning-memory) - next_i2c_bus_num++; -#else - this->wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer) -#endif +bool I2CDevice::write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len) { + // we have to copy in order to be able to change byte order + std::unique_ptr temp{new uint16_t[len]}; + for (size_t i = 0; i < len; i++) + temp[i] = htoi2cs(data[i]); + return write_register(a_register, reinterpret_cast(data), len * 2) == ERROR_OK; } -void I2CComponent::setup() { - this->wire_->begin(this->sda_pin_, this->scl_pin_); - this->wire_->setClock(this->frequency_); -} -void I2CComponent::dump_config() { - ESP_LOGCONFIG(TAG, "I2C Bus:"); - ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_); - ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_); - ESP_LOGCONFIG(TAG, " Frequency: %u Hz", this->frequency_); - if (this->scan_) { - ESP_LOGI(TAG, "Scanning i2c bus for active devices..."); - uint8_t found = 0; - for (uint8_t address = 1; address < 120; address++) { - this->wire_->beginTransmission(address); - uint8_t error = this->wire_->endTransmission(); - - if (error == 0) { - ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address); - found++; - } else if (error == 4) { - ESP_LOGI(TAG, "Unknown error at address 0x%02X", address); - } - - delay(1); - } - if (found == 0) { - ESP_LOGI(TAG, "Found no i2c devices!"); - } - } -} -float I2CComponent::get_setup_priority() const { return setup_priority::BUS; } - -void I2CComponent::raw_begin_transmission(uint8_t address) { - ESP_LOGVV(TAG, "Beginning Transmission to 0x%02X:", address); - this->wire_->beginTransmission(address); -} -bool I2CComponent::raw_end_transmission(uint8_t address, bool send_stop) { - uint8_t status = this->wire_->endTransmission(send_stop); - ESP_LOGVV(TAG, " Transmission ended. Status code: 0x%02X", status); - - switch (status) { - case 0: - break; - case 1: - ESP_LOGW(TAG, "Too much data to fit in transmitter buffer for address 0x%02X", address); - break; - case 2: - ESP_LOGW(TAG, "Received NACK on transmit of address 0x%02X", address); - break; - case 3: - ESP_LOGW(TAG, "Received NACK on transmit of data for address 0x%02X", address); - break; - default: - ESP_LOGW(TAG, "Unknown transmit error %u for address 0x%02X", status, address); - break; - } - - return status == 0; -} -bool I2CComponent::raw_request_from(uint8_t address, uint8_t len) { - ESP_LOGVV(TAG, "Requesting %u bytes from 0x%02X:", len, address); - uint8_t ret = this->wire_->requestFrom(address, len); - if (ret != len) { - ESP_LOGW(TAG, "Requesting %u bytes from 0x%02X failed!", len, address); - return false; - } - return true; -} -void HOT I2CComponent::raw_write(uint8_t address, const uint8_t *data, uint8_t len) { - for (size_t i = 0; i < len; i++) { - ESP_LOGVV(TAG, " Writing 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); - this->wire_->write(data[i]); - App.feed_wdt(); - } -} -void HOT I2CComponent::raw_write_16(uint8_t address, const uint16_t *data, uint8_t len) { - for (size_t i = 0; i < len; i++) { - ESP_LOGVV(TAG, " Writing 0b" BYTE_TO_BINARY_PATTERN BYTE_TO_BINARY_PATTERN " (0x%04X)", - BYTE_TO_BINARY(data[i] >> 8), BYTE_TO_BINARY(data[i]), data[i]); - this->wire_->write(data[i] >> 8); - this->wire_->write(data[i]); - App.feed_wdt(); - } -} - -bool I2CComponent::raw_receive(uint8_t address, uint8_t *data, uint8_t len) { - if (!this->raw_request_from(address, len)) - return false; - for (uint8_t i = 0; i < len; i++) { - data[i] = this->wire_->read(); - ESP_LOGVV(TAG, " Received 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); - App.feed_wdt(); - } - return true; -} -bool I2CComponent::raw_receive_16(uint8_t address, uint16_t *data, uint8_t len) { - if (!this->raw_request_from(address, len * 2)) - return false; - auto *data_8 = reinterpret_cast(data); - for (uint8_t i = 0; i < len; i++) { - data_8[i * 2 + 1] = this->wire_->read(); - data_8[i * 2] = this->wire_->read(); - ESP_LOGVV(TAG, " Received 0b" BYTE_TO_BINARY_PATTERN BYTE_TO_BINARY_PATTERN " (0x%04X)", - BYTE_TO_BINARY(data_8[i * 2 + 1]), BYTE_TO_BINARY(data_8[i * 2]), data[i]); - } - return true; -} -bool I2CComponent::read_bytes(uint8_t address, uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion) { - if (!this->write_bytes(address, a_register, nullptr, 0)) - return false; - - if (conversion > 0) - delay(conversion); - return this->raw_receive(address, data, len); -} -bool I2CComponent::read_bytes_raw(uint8_t address, uint8_t *data, uint8_t len) { - return this->raw_receive(address, data, len); -} -bool I2CComponent::read_bytes_16(uint8_t address, uint8_t a_register, uint16_t *data, uint8_t len, - uint32_t conversion) { - if (!this->write_bytes(address, a_register, nullptr, 0)) - return false; - - if (conversion > 0) - delay(conversion); - return this->raw_receive_16(address, data, len); -} -bool I2CComponent::read_byte(uint8_t address, uint8_t a_register, uint8_t *data, uint32_t conversion) { - return this->read_bytes(address, a_register, data, 1, conversion); -} -bool I2CComponent::read_byte_16(uint8_t address, uint8_t a_register, uint16_t *data, uint32_t conversion) { - return this->read_bytes_16(address, a_register, data, 1, conversion); -} -bool I2CComponent::write_bytes(uint8_t address, uint8_t a_register, const uint8_t *data, uint8_t len) { - this->raw_begin_transmission(address); - this->raw_write(address, &a_register, 1); - this->raw_write(address, data, len); - return this->raw_end_transmission(address); -} -bool I2CComponent::write_bytes_raw(uint8_t address, const uint8_t *data, uint8_t len) { - this->raw_begin_transmission(address); - this->raw_write(address, data, len); - return this->raw_end_transmission(address); -} -bool I2CComponent::write_bytes_16(uint8_t address, uint8_t a_register, const uint16_t *data, uint8_t len) { - this->raw_begin_transmission(address); - this->raw_write(address, &a_register, 1); - this->raw_write_16(address, data, len); - return this->raw_end_transmission(address); -} -bool I2CComponent::write_byte(uint8_t address, uint8_t a_register, uint8_t data) { - return this->write_bytes(address, a_register, &data, 1); -} -bool I2CComponent::write_byte_16(uint8_t address, uint8_t a_register, uint16_t data) { - return this->write_bytes_16(address, a_register, &data, 1); -} - -void I2CDevice::set_i2c_address(uint8_t address) { this->address_ = address; } -#ifdef USE_I2C_MULTIPLEXER -void I2CDevice::set_i2c_multiplexer(I2CMultiplexer *multiplexer, uint8_t channel) { - ESP_LOGVV(TAG, " Setting Multiplexer %p for channel %d", multiplexer, channel); - this->multiplexer_ = multiplexer; - this->channel_ = channel; -} - -void I2CDevice::check_multiplexer_() { - if (this->multiplexer_ != nullptr) { - ESP_LOGVV(TAG, "Multiplexer setting channel to %d", this->channel_); - this->multiplexer_->set_channel(this->channel_); - } -} -#endif - -void I2CDevice::raw_begin_transmission() { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - this->parent_->raw_begin_transmission(this->address_); -} -bool I2CDevice::raw_end_transmission(bool send_stop) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->raw_end_transmission(this->address_, send_stop); -} -void I2CDevice::raw_write(const uint8_t *data, uint8_t len) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - this->parent_->raw_write(this->address_, data, len); -} -bool I2CDevice::read_bytes(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->read_bytes(this->address_, a_register, data, len, conversion); -} -bool I2CDevice::read_bytes_raw(uint8_t *data, uint8_t len) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->read_bytes_raw(this->address_, data, len); -} -bool I2CDevice::read_byte(uint8_t a_register, uint8_t *data, uint32_t conversion) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->read_byte(this->address_, a_register, data, conversion); -} -bool I2CDevice::write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->write_bytes(this->address_, a_register, data, len); -} -bool I2CDevice::write_bytes_raw(const uint8_t *data, uint8_t len) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->write_bytes_raw(this->address_, data, len); -} -bool I2CDevice::write_byte(uint8_t a_register, uint8_t data) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->write_byte(this->address_, a_register, data); -} -bool I2CDevice::read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len, uint32_t conversion) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->read_bytes_16(this->address_, a_register, data, len, conversion); -} -bool I2CDevice::read_byte_16(uint8_t a_register, uint16_t *data, uint32_t conversion) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->read_byte_16(this->address_, a_register, data, conversion); -} -bool I2CDevice::write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->write_bytes_16(this->address_, a_register, data, len); -} -bool I2CDevice::write_byte_16(uint8_t a_register, uint16_t data) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->write_byte_16(this->address_, a_register, data); -} -void I2CDevice::set_i2c_parent(I2CComponent *parent) { this->parent_ = parent; } - I2CRegister &I2CRegister::operator=(uint8_t value) { - this->parent_->write_byte(this->register_, value); + this->parent_->write_register(this->register_, &value, 1); return *this; } - I2CRegister &I2CRegister::operator&=(uint8_t value) { - this->parent_->write_byte(this->register_, this->get() & value); + value &= get(); + this->parent_->write_register(this->register_, &value, 1); return *this; } - I2CRegister &I2CRegister::operator|=(uint8_t value) { - this->parent_->write_byte(this->register_, this->get() | value); + value |= get(); + this->parent_->write_register(this->register_, &value, 1); return *this; } -uint8_t I2CRegister::get() { +uint8_t I2CRegister::get() const { uint8_t value = 0x00; - this->parent_->read_byte(this->register_, &value); + this->parent_->read_register(this->register_, &value, 1); return value; } -I2CRegister &I2CRegister::operator=(const std::vector &value) { - this->parent_->write_bytes(this->register_, value); - return *this; -} } // namespace i2c } // namespace esphome diff --git a/esphome/components/i2c/i2c.h b/esphome/components/i2c/i2c.h index 6f93d4c0e4..71ab650e97 100644 --- a/esphome/components/i2c/i2c.h +++ b/esphome/components/i2c/i2c.h @@ -1,198 +1,79 @@ #pragma once -#include -#include "esphome/core/defines.h" -#include "esphome/core/component.h" -#include "esphome/core/helpers.h" +#include "i2c_bus.h" +#include "esphome/core/optional.h" +#include +#include namespace esphome { namespace i2c { #define LOG_I2C_DEVICE(this) ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); -/** The I2CComponent is the base of ESPHome's i2c communication. - * - * It handles setting up the bus (with pins, clock frequency) and provides nice helper functions to - * make reading from the i2c bus easier (see read_bytes, write_bytes) and safe (with read timeouts). - * - * For the user, it has a few setters (see set_sda_pin, set_scl_pin, set_frequency) - * to setup some parameters for the bus. Additionally, the i2c component has a scan feature that will - * scan the entire 7-bit i2c address range for devices that respond to transmissions to make finding - * the address of an i2c device easier. - * - * On the ESP32, you can even have multiple I2C bus for communication, simply create multiple - * I2CComponents, each with different SDA and SCL pins and use `set_parent` on all I2CDevices that use - * the non-first I2C bus. - */ -class I2CComponent : public Component { - public: - I2CComponent(); - void set_sda_pin(uint8_t sda_pin) { sda_pin_ = sda_pin; } - void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; } - void set_frequency(uint32_t frequency) { frequency_ = frequency; } - void set_scan(bool scan) { scan_ = scan; } - - /** Read len amount of bytes from a register into data. Optionally with a conversion time after - * writing the register value to the bus. - * - * @param address The address to send the request to. - * @param a_register The register number to write to the bus before reading. - * @param data An array to store len amount of 8-bit bytes into. - * @param len The amount of bytes to request and write into data. - * @param conversion The time in ms between writing the register value and reading out the value. - * @return If the operation was successful. - */ - bool read_bytes(uint8_t address, uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0); - bool read_bytes_raw(uint8_t address, uint8_t *data, uint8_t len); - - /** Read len amount of 16-bit words (MSB first) from a register into data. - * - * @param address The address to send the request to. - * @param a_register The register number to write to the bus before reading. - * @param data An array to store len amount of 16-bit words into. - * @param len The amount of 16-bit words to request and write into data. - * @param conversion The time in ms between writing the register value and reading out the value. - * @return If the operation was successful. - */ - bool read_bytes_16(uint8_t address, uint8_t a_register, uint16_t *data, uint8_t len, uint32_t conversion = 0); - - /// Read a single byte from a register into the data variable. Return true if successful. - bool read_byte(uint8_t address, uint8_t a_register, uint8_t *data, uint32_t conversion = 0); - - /// Read a single 16-bit words (MSB first) from a register into the data variable. Return true if successful. - bool read_byte_16(uint8_t address, uint8_t a_register, uint16_t *data, uint32_t conversion = 0); - - /** Write len amount of 8-bit bytes to the specified register for address. - * - * @param address The address to use for the transmission. - * @param a_register The register to write the values to. - * @param data An array from which len bytes of data will be written to the bus. - * @param len The amount of bytes to write to the bus. - * @return If the operation was successful. - */ - bool write_bytes(uint8_t address, uint8_t a_register, const uint8_t *data, uint8_t len); - bool write_bytes_raw(uint8_t address, const uint8_t *data, uint8_t len); - - /** Write len amount of 16-bit words (MSB first) to the specified register for address. - * - * @param address The address to use for the transmission. - * @param a_register The register to write the values to. - * @param data An array from which len 16-bit words of data will be written to the bus. - * @param len The amount of bytes to write to the bus. - * @return If the operation was successful. - */ - bool write_bytes_16(uint8_t address, uint8_t a_register, const uint16_t *data, uint8_t len); - - /// Write a single byte of data into the specified register of address. Return true if successful. - bool write_byte(uint8_t address, uint8_t a_register, uint8_t data); - - /// Write a single 16-bit word of data into the specified register of address. Return true if successful. - bool write_byte_16(uint8_t address, uint8_t a_register, uint16_t data); - - // ========== INTERNAL METHODS ========== - // (In most use cases you won't need these) - /// Begin a write transmission to an address. - void raw_begin_transmission(uint8_t address); - - /// End a write transmission to an address, return true if successful. - bool raw_end_transmission(uint8_t address, bool send_stop = true); - - /** Request data from an address with a number of (8-bit) bytes. - * - * @param address The address to request the bytes from. - * @param len The number of bytes to receive, must not be 0. - * @return True if all requested bytes were read, false otherwise. - */ - bool raw_request_from(uint8_t address, uint8_t len); - - /// Write len amount of bytes from data to address. begin_transmission_ must be called before this. - void raw_write(uint8_t address, const uint8_t *data, uint8_t len); - - /// Write len amount of 16-bit words from data to address. begin_transmission_ must be called before this. - void raw_write_16(uint8_t address, const uint16_t *data, uint8_t len); - - /// Request len amount of bytes from address and write the result it into data. Returns true iff was successful. - bool raw_receive(uint8_t address, uint8_t *data, uint8_t len); - - /// Request len amount of 16-bit words from address and write the result into data. Returns true iff was successful. - bool raw_receive_16(uint8_t address, uint16_t *data, uint8_t len); - - /// Setup the i2c. bus - void setup() override; - void dump_config() override; - /// Set a very high setup priority to make sure it's loaded before all other hardware. - float get_setup_priority() const override; - - protected: - TwoWire *wire_; - uint8_t sda_pin_; - uint8_t scl_pin_; - uint32_t frequency_; - bool scan_; -}; - class I2CDevice; -class I2CMultiplexer; class I2CRegister { public: I2CRegister(I2CDevice *parent, uint8_t a_register) : parent_(parent), register_(a_register) {} I2CRegister &operator=(uint8_t value); - I2CRegister &operator=(const std::vector &value); I2CRegister &operator&=(uint8_t value); I2CRegister &operator|=(uint8_t value); - uint8_t get(); + explicit operator uint8_t() const { return get(); } + + uint8_t get() const; protected: I2CDevice *parent_; uint8_t register_; }; -/** All components doing communication on the I2C bus should subclass I2CDevice. - * - * This class stores 1. the address of the i2c device and has a helper function to allow - * users to manually set the address and 2. stores a reference to the "parent" I2CComponent. - * - * - * All this class basically does is to expose all helper functions from I2CComponent. - */ +// like ntohs/htons but without including networking headers. +// ("i2c" byte order is big-endian) +inline uint16_t i2ctohs(uint16_t i2cshort) { + union { + uint16_t x; + uint8_t y[2]; + } conv; + conv.x = i2cshort; + return ((uint16_t) conv.y[0] << 8) | ((uint16_t) conv.y[1] << 0); +} + +inline uint16_t htoi2cs(uint16_t hostshort) { return i2ctohs(hostshort); } + class I2CDevice { public: I2CDevice() = default; - I2CDevice(I2CComponent *parent, uint8_t address) : address_(address), parent_(parent) {} - /// Manually set the i2c address of this device. - void set_i2c_address(uint8_t address); -#ifdef USE_I2C_MULTIPLEXER - /// Manually set the i2c multiplexer of this device. - void set_i2c_multiplexer(I2CMultiplexer *multiplexer, uint8_t channel); -#endif - /// Manually set the parent i2c bus for this device. - void set_i2c_parent(I2CComponent *parent); + void set_i2c_address(uint8_t address) { address_ = address; } + void set_i2c_bus(I2CBus *bus) { bus_ = bus; } I2CRegister reg(uint8_t a_register) { return {this, a_register}; } - /// Begin a write transmission. - void raw_begin_transmission(); + ErrorCode read(uint8_t *data, size_t len) { return bus_->read(address_, data, len); } + ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len) { + ErrorCode err = this->write(&a_register, 1); + if (err != ERROR_OK) + return err; + return this->read(data, len); + } - /// End a write transmission, return true if successful. - bool raw_end_transmission(bool send_stop = true); + ErrorCode write(const uint8_t *data, uint8_t len) { return bus_->write(address_, data, len); } + ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len) { + WriteBuffer buffers[2]; + buffers[0].data = &a_register; + buffers[0].len = 1; + buffers[1].data = data; + buffers[1].len = len; + return bus_->writev(address_, buffers, 2); + } - /// Write len amount of bytes from data. begin_transmission_ must be called before this. - void raw_write(const uint8_t *data, uint8_t len); + // Compat APIs - /** Read len amount of bytes from a register into data. Optionally with a conversion time after - * writing the register value to the bus. - * - * @param a_register The register number to write to the bus before reading. - * @param data An array to store len amount of 8-bit bytes into. - * @param len The amount of bytes to request and write into data. - * @param conversion The time in ms between writing the register value and reading out the value. - * @return If the operation was successful. - */ - bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0); - bool read_bytes_raw(uint8_t *data, uint8_t len); + bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len) { + return read_register(a_register, data, len) == ERROR_OK; + } + bool read_bytes_raw(uint8_t *data, uint8_t len) { return read(data, len) == ERROR_OK; } template optional> read_bytes(uint8_t a_register) { std::array res; @@ -209,18 +90,15 @@ class I2CDevice { return res; } - /** Read len amount of 16-bit words (MSB first) from a register into data. - * - * @param a_register The register number to write to the bus before reading. - * @param data An array to store len amount of 16-bit words into. - * @param len The amount of 16-bit words to request and write into data. - * @param conversion The time in ms between writing the register value and reading out the value. - * @return If the operation was successful. - */ - bool read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len, uint32_t conversion = 0); + bool read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len) { + if (read_register(a_register, reinterpret_cast(data), len * 2) != ERROR_OK) + return false; + for (size_t i = 0; i < len; i++) + data[i] = i2ctohs(data[i]); + return true; + } - /// Read a single byte from a register into the data variable. Return true if successful. - bool read_byte(uint8_t a_register, uint8_t *data, uint32_t conversion = 0); + bool read_byte(uint8_t a_register, uint8_t *data) { return read_register(a_register, data, 1) == ERROR_OK; } optional read_byte(uint8_t a_register) { uint8_t data; @@ -229,66 +107,30 @@ class I2CDevice { return data; } - /// Read a single 16-bit words (MSB first) from a register into the data variable. Return true if successful. - bool read_byte_16(uint8_t a_register, uint16_t *data, uint32_t conversion = 0); + bool read_byte_16(uint8_t a_register, uint16_t *data) { return read_bytes_16(a_register, data, 1); } - /** Write len amount of 8-bit bytes to the specified register. - * - * @param a_register The register to write the values to. - * @param data An array from which len bytes of data will be written to the bus. - * @param len The amount of bytes to write to the bus. - * @return If the operation was successful. - */ - bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len); - bool write_bytes_raw(const uint8_t *data, uint8_t len); - - /** Write a vector of data to a register. - * - * @param a_register The register to write to. - * @param data The data to write. - * @return If the operation was successful. - */ - bool write_bytes(uint8_t a_register, const std::vector &data) { - return this->write_bytes(a_register, data.data(), data.size()); + bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) { + return write_register(a_register, data, len) == ERROR_OK; + } + + bool write_bytes(uint8_t a_register, const std::vector &data) { + return write_bytes(a_register, data.data(), data.size()); } - bool write_bytes_raw(const std::vector &data) { return this->write_bytes_raw(data.data(), data.size()); } template bool write_bytes(uint8_t a_register, const std::array &data) { - return this->write_bytes(a_register, data.data(), data.size()); - } - template bool write_bytes_raw(const std::array &data) { - return this->write_bytes_raw(data.data(), data.size()); + return write_bytes(a_register, data.data(), data.size()); } - /** Write len amount of 16-bit words (MSB first) to the specified register. - * - * @param a_register The register to write the values to. - * @param data An array from which len 16-bit words of data will be written to the bus. - * @param len The amount of bytes to write to the bus. - * @return If the operation was successful. - */ bool write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len); - /// Write a single byte of data into the specified register. Return true if successful. - bool write_byte(uint8_t a_register, uint8_t data); + bool write_byte(uint8_t a_register, uint8_t data) { return write_bytes(a_register, &data, 1); } - /// Write a single 16-bit word of data into the specified register. Return true if successful. - bool write_byte_16(uint8_t a_register, uint16_t data); + bool write_byte_16(uint8_t a_register, uint16_t data) { return write_bytes_16(a_register, &data, 1); } protected: - // Checks for multiplexer set and set channel - void check_multiplexer_(); uint8_t address_{0x00}; - I2CComponent *parent_{nullptr}; -#ifdef USE_I2C_MULTIPLEXER - I2CMultiplexer *multiplexer_{nullptr}; - uint8_t channel_; -#endif -}; -class I2CMultiplexer : public I2CDevice { - public: - I2CMultiplexer() = default; - virtual void set_channel(uint8_t channelno); + I2CBus *bus_{nullptr}; }; + } // namespace i2c } // namespace esphome diff --git a/esphome/components/i2c/i2c_bus.h b/esphome/components/i2c/i2c_bus.h new file mode 100644 index 0000000000..cb00260f43 --- /dev/null +++ b/esphome/components/i2c/i2c_bus.h @@ -0,0 +1,46 @@ +#pragma once +#include +#include + +namespace esphome { +namespace i2c { + +enum ErrorCode { + ERROR_OK = 0, + ERROR_INVALID_ARGUMENT = 1, + ERROR_NOT_ACKNOWLEDGED = 2, + ERROR_TIMEOUT = 3, + ERROR_NOT_INITIALIZED = 4, + ERROR_TOO_LARGE = 5, + ERROR_UNKNOWN = 6, +}; + +struct ReadBuffer { + uint8_t *data; + size_t len; +}; +struct WriteBuffer { + const uint8_t *data; + size_t len; +}; + +class I2CBus { + public: + virtual ErrorCode read(uint8_t address, uint8_t *buffer, size_t len) { + ReadBuffer buf; + buf.data = buffer; + buf.len = len; + return readv(address, &buf, 1); + } + virtual ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) = 0; + virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len) { + WriteBuffer buf; + buf.data = buffer; + buf.len = len; + return writev(address, &buf, 1); + } + virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) = 0; +}; + +} // namespace i2c +} // namespace esphome diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp new file mode 100644 index 0000000000..aba412c3f7 --- /dev/null +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -0,0 +1,97 @@ +#ifdef USE_ARDUINO + +#include "i2c_bus_arduino.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace i2c { + +static const char *const TAG = "i2c.arduino"; + +void ArduinoI2CBus::setup() { +#ifdef USE_ESP32 + static uint8_t next_bus_num = 0; + if (next_bus_num == 0) + wire_ = &Wire; + else + wire_ = new TwoWire(next_bus_num); // NOLINT(cppcoreguidelines-owning-memory) + next_bus_num++; +#else + wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer) +#endif + + wire_->begin(sda_pin_, scl_pin_); + wire_->setClock(frequency_); + initialized_ = true; +} +void ArduinoI2CBus::dump_config() { + ESP_LOGCONFIG(TAG, "I2C Bus:"); + ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_); + ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_); + ESP_LOGCONFIG(TAG, " Frequency: %u Hz", this->frequency_); + if (this->scan_) { + ESP_LOGI(TAG, "Scanning i2c bus for active devices..."); + uint8_t found = 0; + for (uint8_t address = 1; address < 120; address++) { + auto err = readv(address, nullptr, 0); + + if (err == ERROR_OK) { + ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address); + found++; + } else if (err == ERROR_UNKNOWN) { + ESP_LOGI(TAG, "Unknown error at address 0x%02X", address); + } + } + if (found == 0) { + ESP_LOGI(TAG, "Found no i2c devices!"); + } + } +} +ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { + if (!initialized_) + return ERROR_NOT_INITIALIZED; + size_t to_request = 0; + for (size_t i = 0; i < cnt; i++) + to_request += buffers[i].len; + size_t ret = wire_->requestFrom((int) address, (int) to_request, 1); + if (ret != to_request) { + return ERROR_TIMEOUT; + } + for (size_t i = 0; i < cnt; i++) { + const auto &buf = buffers[i]; + for (size_t j = 0; j < buf.len; j++) + buf.data[j] = wire_->read(); + } + return ERROR_OK; +} +ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { + if (!initialized_) + return ERROR_NOT_INITIALIZED; + + wire_->beginTransmission(address); + for (size_t i = 0; i < cnt; i++) { + const auto &buf = buffers[i]; + if (buf.len == 0) + continue; + size_t ret = wire_->write(buf.data, buf.len); + if (ret != buf.len) { + return ERROR_UNKNOWN; + } + } + uint8_t status = wire_->endTransmission(true); + if (status == 0) { + return ERROR_OK; + } else if (status == 1) { + // transmit buffer not large enough + return ERROR_UNKNOWN; + } else if (status == 2 || status == 3) { + return ERROR_NOT_ACKNOWLEDGED; + } + return ERROR_UNKNOWN; +} + +} // namespace i2c +} // namespace esphome + +#endif // USE_ESP_IDF diff --git a/esphome/components/i2c/i2c_bus_arduino.h b/esphome/components/i2c/i2c_bus_arduino.h new file mode 100644 index 0000000000..220027b3d4 --- /dev/null +++ b/esphome/components/i2c/i2c_bus_arduino.h @@ -0,0 +1,37 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "i2c_bus.h" +#include "esphome/core/component.h" +#include + +namespace esphome { +namespace i2c { + +class ArduinoI2CBus : public I2CBus, public Component { + public: + void setup() override; + void dump_config() override; + ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override; + ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) override; + float get_setup_priority() const override { return setup_priority::BUS; } + + void set_scan(bool scan) { scan_ = scan; } + void set_sda_pin(uint8_t sda_pin) { sda_pin_ = sda_pin; } + void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; } + void set_frequency(uint32_t frequency) { frequency_ = frequency; } + + protected: + TwoWire *wire_; + bool scan_; + uint8_t sda_pin_; + uint8_t scl_pin_; + uint32_t frequency_; + bool initialized_ = false; +}; + +} // namespace i2c +} // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp new file mode 100644 index 0000000000..4b93b41877 --- /dev/null +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -0,0 +1,147 @@ +#ifdef USE_ESP_IDF + +#include "i2c_bus_esp_idf.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace i2c { + +static const char *const TAG = "i2c.idf"; + +void IDFI2CBus::setup() { + static i2c_port_t next_port = 0; + port_ = next_port++; + + i2c_config_t conf{}; + memset(&conf, 0, sizeof(conf)); + conf.mode = I2C_MODE_MASTER; + conf.sda_io_num = sda_pin_; + conf.sda_pullup_en = sda_pullup_enabled_; + conf.scl_io_num = scl_pin_; + conf.scl_pullup_en = scl_pullup_enabled_; + conf.master.clk_speed = frequency_; + esp_err_t err = i2c_param_config(port_, &conf); + if (err != ESP_OK) { + ESP_LOGW(TAG, "i2c_param_config failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + err = i2c_driver_install(port_, I2C_MODE_MASTER, 0, 0, ESP_INTR_FLAG_IRAM); + if (err != ESP_OK) { + ESP_LOGW(TAG, "i2c_driver_install failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + initialized_ = true; +} +void IDFI2CBus::dump_config() { + ESP_LOGCONFIG(TAG, "I2C Bus:"); + ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_); + ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_); + ESP_LOGCONFIG(TAG, " Frequency: %u Hz", this->frequency_); + if (this->scan_) { + ESP_LOGI(TAG, "Scanning i2c bus for active devices..."); + uint8_t found = 0; + for (uint8_t address = 1; address < 120; address++) { + auto err = readv(address, nullptr, 0); + + if (err == ERROR_OK) { + ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address); + found++; + } else if (err == ERROR_UNKNOWN) { + ESP_LOGI(TAG, "Unknown error at address 0x%02X", address); + } + } + if (found == 0) { + ESP_LOGI(TAG, "Found no i2c devices!"); + } + } +} +ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { + if (!initialized_) + return ERROR_NOT_INITIALIZED; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + esp_err_t err = i2c_master_start(cmd); + if (err != ESP_OK) { + i2c_cmd_link_delete(cmd); + return ERROR_UNKNOWN; + } + err = i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_READ, true); + if (err != ESP_OK) { + i2c_cmd_link_delete(cmd); + return ERROR_UNKNOWN; + } + for (size_t i = 0; i < cnt; i++) { + const auto &buf = buffers[i]; + if (buf.len == 0) + continue; + err = i2c_master_read(cmd, buf.data, buf.len, i == cnt - 1 ? I2C_MASTER_LAST_NACK : I2C_MASTER_ACK); + if (err != ESP_OK) { + i2c_cmd_link_delete(cmd); + return ERROR_UNKNOWN; + } + } + err = i2c_master_stop(cmd); + if (err != ESP_OK) { + i2c_cmd_link_delete(cmd); + return ERROR_UNKNOWN; + } + err = i2c_master_cmd_begin(port_, cmd, 20 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); + if (err == ESP_FAIL) { + // transfer not acked + return ERROR_NOT_ACKNOWLEDGED; + } else if (err == ESP_ERR_TIMEOUT) { + return ERROR_TIMEOUT; + } else if (err != ESP_OK) { + return ERROR_UNKNOWN; + } + return ERROR_OK; +} +ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { + if (!initialized_) + return ERROR_NOT_INITIALIZED; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + esp_err_t err = i2c_master_start(cmd); + if (err != ESP_OK) { + i2c_cmd_link_delete(cmd); + return ERROR_UNKNOWN; + } + err = i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, true); + if (err != ESP_OK) { + i2c_cmd_link_delete(cmd); + return ERROR_UNKNOWN; + } + for (size_t i = 0; i < cnt; i++) { + const auto &buf = buffers[i]; + if (buf.len == 0) + continue; + err = i2c_master_write(cmd, buf.data, buf.len, true); + if (err != ESP_OK) { + i2c_cmd_link_delete(cmd); + return ERROR_UNKNOWN; + } + } + err = i2c_master_stop(cmd); + if (err != ESP_OK) { + i2c_cmd_link_delete(cmd); + return ERROR_UNKNOWN; + } + err = i2c_master_cmd_begin(port_, cmd, 20 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); + if (err == ESP_FAIL) { + // transfer not acked + return ERROR_NOT_ACKNOWLEDGED; + } else if (err == ESP_ERR_TIMEOUT) { + return ERROR_TIMEOUT; + } else if (err != ESP_OK) { + return ERROR_UNKNOWN; + } + return ERROR_OK; +} + +} // namespace i2c +} // namespace esphome + +#endif // USE_ESP_IDF diff --git a/esphome/components/i2c/i2c_bus_esp_idf.h b/esphome/components/i2c/i2c_bus_esp_idf.h new file mode 100644 index 0000000000..c7e67145a3 --- /dev/null +++ b/esphome/components/i2c/i2c_bus_esp_idf.h @@ -0,0 +1,41 @@ +#pragma once + +#ifdef USE_ESP_IDF + +#include "i2c_bus.h" +#include "esphome/core/component.h" +#include + +namespace esphome { +namespace i2c { + +class IDFI2CBus : public I2CBus, public Component { + public: + void setup() override; + void dump_config() override; + ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override; + ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) override; + float get_setup_priority() const override { return setup_priority::BUS; } + + void set_scan(bool scan) { scan_ = scan; } + void set_sda_pin(uint8_t sda_pin) { sda_pin_ = sda_pin; } + void set_sda_pullup_enabled(bool sda_pullup_enabled) { sda_pullup_enabled_ = sda_pullup_enabled; } + void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; } + void set_scl_pullup_enabled(bool scl_pullup_enabled) { scl_pullup_enabled_ = scl_pullup_enabled; } + void set_frequency(uint32_t frequency) { frequency_ = frequency; } + + protected: + i2c_port_t port_; + bool scan_; + uint8_t sda_pin_; + bool sda_pullup_enabled_; + uint8_t scl_pin_; + bool scl_pullup_enabled_; + uint32_t frequency_; + bool initialized_ = false; +}; + +} // namespace i2c +} // namespace esphome + +#endif // USE_ESP_IDF diff --git a/esphome/components/ili9341/ili9341_display.cpp b/esphome/components/ili9341/ili9341_display.cpp index c06d487e7d..b36d05c864 100644 --- a/esphome/components/ili9341/ili9341_display.cpp +++ b/esphome/components/ili9341/ili9341_display.cpp @@ -2,6 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" namespace esphome { namespace ili9341 { @@ -185,8 +186,8 @@ void ILI9341Display::end_data_() { this->disable(); } void ILI9341Display::init_lcd_(const uint8_t *init_cmd) { uint8_t cmd, x, num_args; const uint8_t *addr = init_cmd; - while ((cmd = pgm_read_byte(addr++)) > 0) { - x = pgm_read_byte(addr++); + while ((cmd = progmem_read_byte(addr++)) > 0) { + x = progmem_read_byte(addr++); num_args = x & 0x7F; send_command(cmd, addr, num_args); addr += num_args; diff --git a/esphome/components/improv/improv.cpp b/esphome/components/improv/improv.cpp index d1fee72866..4f6ed7702d 100644 --- a/esphome/components/improv/improv.cpp +++ b/esphome/components/improv/improv.cpp @@ -65,6 +65,7 @@ std::vector build_rpc_response(Command command, const std::vector build_rpc_response(Command command, const std::vector &datum) { std::vector out; uint32_t length = 0; @@ -85,5 +86,6 @@ std::vector build_rpc_response(Command command, const std::vector #include #include @@ -50,6 +53,8 @@ ImprovCommand parse_improv_data(const std::vector &data); ImprovCommand parse_improv_data(const uint8_t *data, size_t length); std::vector build_rpc_response(Command command, const std::vector &datum); +#ifdef USE_ARDUINO std::vector build_rpc_response(Command command, const std::vector &datum); +#endif // USE_ARDUINO } // namespace improv diff --git a/esphome/components/ina219/ina219.cpp b/esphome/components/ina219/ina219.cpp index 506b7e06ed..609f3d0f08 100644 --- a/esphome/components/ina219/ina219.cpp +++ b/esphome/components/ina219/ina219.cpp @@ -1,5 +1,6 @@ #include "ina219.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace ina219 { @@ -149,7 +150,7 @@ float INA219Component::get_setup_priority() const { return setup_priority::DATA; void INA219Component::update() { if (this->bus_voltage_sensor_ != nullptr) { uint16_t raw_bus_voltage; - if (!this->read_byte_16(INA219_REGISTER_BUS_VOLTAGE, &raw_bus_voltage, 1)) { + if (!this->read_byte_16(INA219_REGISTER_BUS_VOLTAGE, &raw_bus_voltage)) { this->status_set_warning(); return; } @@ -160,8 +161,9 @@ void INA219Component::update() { if (this->shunt_voltage_sensor_ != nullptr) { uint16_t raw_shunt_voltage; - if (!this->read_byte_16(INA219_REGISTER_SHUNT_VOLTAGE, &raw_shunt_voltage, 1)) { + if (!this->read_byte_16(INA219_REGISTER_SHUNT_VOLTAGE, &raw_shunt_voltage)) { this->status_set_warning(); + return; } float shunt_voltage_mv = int16_t(raw_shunt_voltage) * 0.01f; this->shunt_voltage_sensor_->publish_state(shunt_voltage_mv / 1000.0f); @@ -169,7 +171,7 @@ void INA219Component::update() { if (this->current_sensor_ != nullptr) { uint16_t raw_current; - if (!this->read_byte_16(INA219_REGISTER_CURRENT, &raw_current, 1)) { + if (!this->read_byte_16(INA219_REGISTER_CURRENT, &raw_current)) { this->status_set_warning(); return; } @@ -179,7 +181,7 @@ void INA219Component::update() { if (this->power_sensor_ != nullptr) { uint16_t raw_power; - if (!this->read_byte_16(INA219_REGISTER_POWER, &raw_power, 1)) { + if (!this->read_byte_16(INA219_REGISTER_POWER, &raw_power)) { this->status_set_warning(); return; } diff --git a/esphome/components/ina226/ina226.cpp b/esphome/components/ina226/ina226.cpp index 701a833041..2e30a5ac01 100644 --- a/esphome/components/ina226/ina226.cpp +++ b/esphome/components/ina226/ina226.cpp @@ -1,5 +1,6 @@ #include "ina226.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace ina226 { @@ -96,7 +97,7 @@ float INA226Component::get_setup_priority() const { return setup_priority::DATA; void INA226Component::update() { if (this->bus_voltage_sensor_ != nullptr) { uint16_t raw_bus_voltage; - if (!this->read_byte_16(INA226_REGISTER_BUS_VOLTAGE, &raw_bus_voltage, 1)) { + if (!this->read_byte_16(INA226_REGISTER_BUS_VOLTAGE, &raw_bus_voltage)) { this->status_set_warning(); return; } @@ -106,8 +107,9 @@ void INA226Component::update() { if (this->shunt_voltage_sensor_ != nullptr) { uint16_t raw_shunt_voltage; - if (!this->read_byte_16(INA226_REGISTER_SHUNT_VOLTAGE, &raw_shunt_voltage, 1)) { + if (!this->read_byte_16(INA226_REGISTER_SHUNT_VOLTAGE, &raw_shunt_voltage)) { this->status_set_warning(); + return; } float shunt_voltage_v = int16_t(raw_shunt_voltage) * 0.0000025f; this->shunt_voltage_sensor_->publish_state(shunt_voltage_v); @@ -115,7 +117,7 @@ void INA226Component::update() { if (this->current_sensor_ != nullptr) { uint16_t raw_current; - if (!this->read_byte_16(INA226_REGISTER_CURRENT, &raw_current, 1)) { + if (!this->read_byte_16(INA226_REGISTER_CURRENT, &raw_current)) { this->status_set_warning(); return; } @@ -125,7 +127,7 @@ void INA226Component::update() { if (this->power_sensor_ != nullptr) { uint16_t raw_power; - if (!this->read_byte_16(INA226_REGISTER_POWER, &raw_power, 1)) { + if (!this->read_byte_16(INA226_REGISTER_POWER, &raw_power)) { this->status_set_warning(); return; } diff --git a/esphome/components/ina3221/ina3221.cpp b/esphome/components/ina3221/ina3221.cpp index f2fcdb21eb..3f8e2d06df 100644 --- a/esphome/components/ina3221/ina3221.cpp +++ b/esphome/components/ina3221/ina3221.cpp @@ -1,5 +1,6 @@ #include "ina3221.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace ina3221 { @@ -87,7 +88,7 @@ void INA3221Component::update() { float bus_voltage_v = NAN, current_a = NAN; uint16_t raw; if (channel.should_measure_bus_voltage()) { - if (!this->read_byte_16(ina3221_bus_voltage_register(i), &raw, 1)) { + if (!this->read_byte_16(ina3221_bus_voltage_register(i), &raw)) { this->status_set_warning(); return; } @@ -96,7 +97,7 @@ void INA3221Component::update() { channel.bus_voltage_sensor_->publish_state(bus_voltage_v); } if (channel.should_measure_shunt_voltage()) { - if (!this->read_byte_16(ina3221_shunt_voltage_register(i), &raw, 1)) { + if (!this->read_byte_16(ina3221_shunt_voltage_register(i), &raw)) { this->status_set_warning(); return; } diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp index 81693c6d96..177bc76072 100644 --- a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp @@ -1,7 +1,7 @@ #include "inkbird_ibsth1_mini.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace inkbird_ibsth1_mini { @@ -88,10 +88,10 @@ bool InkbirdIBSTH1_MINI::parse_device(const esp32_ble_tracker::ESPBTDevice &devi auto humidity = ((mnf_data.data[1] << 8) + mnf_data.data[0]) / 100.0f; // Send temperature only if the value is set - if (!isnan(temperature) && this->temperature_ != nullptr) { + if (!std::isnan(temperature) && this->temperature_ != nullptr) { this->temperature_->publish_state(temperature); } - if (!isnan(external_temperature) && this->external_temperature_ != nullptr) { + if (!std::isnan(external_temperature) && this->external_temperature_ != nullptr) { this->external_temperature_->publish_state(external_temperature); } if (this->humidity_ != nullptr) { diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h index c3a9f7062d..1b6be7afe0 100644 --- a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h @@ -4,7 +4,7 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace inkbird_ibsth1_mini { diff --git a/esphome/components/inkplate6/display.py b/esphome/components/inkplate6/display.py index 8e00a69751..e4c71ea717 100644 --- a/esphome/components/inkplate6/display.py +++ b/esphome/components/inkplate6/display.py @@ -8,11 +8,9 @@ from esphome.const import ( CONF_LAMBDA, CONF_PAGES, CONF_WAKEUP_PIN, - ESP_PLATFORM_ESP32, ) -DEPENDENCIES = ["i2c"] -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["i2c", "esp32"] CONF_DISPLAY_DATA_0_PIN = "display_data_0_pin" CONF_DISPLAY_DATA_1_PIN = "display_data_1_pin" @@ -91,6 +89,7 @@ CONFIG_SCHEMA = cv.All( .extend(cv.polling_component_schema("5s")) .extend(i2c.i2c_device_schema(0x48)), cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), + cv.only_with_arduino, ) diff --git a/esphome/components/inkplate6/inkplate.cpp b/esphome/components/inkplate6/inkplate.cpp index b5cec6cf9e..8a05836db9 100644 --- a/esphome/components/inkplate6/inkplate.cpp +++ b/esphome/components/inkplate6/inkplate.cpp @@ -3,7 +3,9 @@ #include "esphome/core/application.h" #include "esphome/core/helpers.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +#include namespace esphome { namespace inkplate6 { @@ -185,7 +187,7 @@ void Inkplate6::eink_on_() { delay(2); - this->read_byte(0x00, &temperature_, 0); + this->read_register(0x00, nullptr, 0); this->le_pin_->digital_write(false); this->oe_pin_->digital_write(false); @@ -208,7 +210,7 @@ void Inkplate6::fill(Color color) { memset(this->partial_buffer_, fill, this->get_buffer_length_()); } - ESP_LOGV(TAG, "Fill finished (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Fill finished (%ums)", millis() - start_time); } void Inkplate6::display() { ESP_LOGV(TAG, "Display called"); @@ -218,12 +220,12 @@ void Inkplate6::display() { this->display3b_(); } else { if (this->partial_updating_ && this->partial_update_()) { - ESP_LOGV(TAG, "Display finished (partial) (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Display finished (partial) (%ums)", millis() - start_time); return; } this->display1b_(); } - ESP_LOGV(TAG, "Display finished (full) (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Display finished (full) (%ums)", millis() - start_time); } void Inkplate6::display1b_() { ESP_LOGV(TAG, "Display1b called"); @@ -246,32 +248,32 @@ void Inkplate6::display1b_() { clean_fast_(0, 11); uint32_t clock = (1 << this->cl_pin_->get_pin()); - ESP_LOGV(TAG, "Display1b start loops (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Display1b start loops (%ums)", millis() - start_time); for (int k = 0; k < 3; k++) { buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1]; vscan_start_(); for (int i = 0; i < this->get_height_internal(); i++) { buffer_value = *(buffer_ptr--); data = LUTB[(buffer_value >> 4) & 0x0F]; - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25); + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25); hscan_start_(send); data = LUTB[buffer_value & 0x0F]; - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25) | clock; + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) { buffer_value = *(buffer_ptr--); data = LUTB[(buffer_value >> 4) & 0x0F]; - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25) | clock; + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; data = LUTB[buffer_value & 0x0F]; - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25) | clock; + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; } @@ -281,31 +283,31 @@ void Inkplate6::display1b_() { } delayMicroseconds(230); } - ESP_LOGV(TAG, "Display1b first loop x %d (%lums)", 3, millis() - start_time); + ESP_LOGV(TAG, "Display1b first loop x %d (%ums)", 3, millis() - start_time); buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1]; vscan_start_(); for (int i = 0; i < this->get_height_internal(); i++) { buffer_value = *(buffer_ptr--); data = LUT2[(buffer_value >> 4) & 0x0F]; - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25); + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25); hscan_start_(send); data = LUT2[buffer_value & 0x0F]; - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25) | clock; + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) { buffer_value = *(buffer_ptr--); data = LUT2[(buffer_value >> 4) & 0x0F]; - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25) | clock; + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; data = LUT2[buffer_value & 0x0F]; - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25) | clock; + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; } @@ -314,13 +316,13 @@ void Inkplate6::display1b_() { vscan_end_(); } delayMicroseconds(230); - ESP_LOGV(TAG, "Display1b second loop (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Display1b second loop (%ums)", millis() - start_time); vscan_start_(); for (int i = 0; i < this->get_height_internal(); i++) { data = 0b00000000; - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25); + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25); hscan_start_(send); send |= clock; GPIO.out_w1ts = send; @@ -336,13 +338,13 @@ void Inkplate6::display1b_() { vscan_end_(); } delayMicroseconds(230); - ESP_LOGV(TAG, "Display1b third loop (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Display1b third loop (%ums)", millis() - start_time); vscan_start_(); eink_off_(); this->block_partial_ = false; this->partial_updates_ = 0; - ESP_LOGV(TAG, "Display1b finished (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Display1b finished (%ums)", millis() - start_time); } void Inkplate6::display3b_() { ESP_LOGV(TAG, "Display3b called"); @@ -380,11 +382,11 @@ void Inkplate6::display3b_() { pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) | (waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0); - send = ((pixel & B00000011) << 4) | (((pixel & B00001100) >> 2) << 18) | (((pixel & B00010000) >> 4) << 23) | - (((pixel & B11100000) >> 5) << 25); + send = ((pixel & 0b00000011) << 4) | (((pixel & 0b00001100) >> 2) << 18) | (((pixel & 0b00010000) >> 4) << 23) | + (((pixel & 0b11100000) >> 5) << 25); hscan_start_(send); - send = ((pixel2 & B00000011) << 4) | (((pixel2 & B00001100) >> 2) << 18) | (((pixel2 & B00010000) >> 4) << 23) | - (((pixel2 & B11100000) >> 5) << 25) | clock; + send = ((pixel2 & 0b00000011) << 4) | (((pixel2 & 0b00001100) >> 2) << 18) | + (((pixel2 & 0b00010000) >> 4) << 23) | (((pixel2 & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; @@ -398,13 +400,13 @@ void Inkplate6::display3b_() { pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) | (waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0); - send = ((pixel & B00000011) << 4) | (((pixel & B00001100) >> 2) << 18) | (((pixel & B00010000) >> 4) << 23) | - (((pixel & B11100000) >> 5) << 25) | clock; + send = ((pixel & 0b00000011) << 4) | (((pixel & 0b00001100) >> 2) << 18) | (((pixel & 0b00010000) >> 4) << 23) | + (((pixel & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; - send = ((pixel2 & B00000011) << 4) | (((pixel2 & B00001100) >> 2) << 18) | (((pixel2 & B00010000) >> 4) << 23) | - (((pixel2 & B11100000) >> 5) << 25) | clock; + send = ((pixel2 & 0b00000011) << 4) | (((pixel2 & 0b00001100) >> 2) << 18) | + (((pixel2 & 0b00010000) >> 4) << 23) | (((pixel2 & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; } @@ -418,7 +420,7 @@ void Inkplate6::display3b_() { clean_fast_(3, 1); vscan_start_(); eink_off_(); - ESP_LOGV(TAG, "Display3b finished (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Display3b finished (%ums)", millis() - start_time); } bool Inkplate6::partial_update_() { ESP_LOGV(TAG, "Partial update called"); @@ -445,7 +447,7 @@ bool Inkplate6::partial_update_() { this->partial_buffer_2_[n--] = LUTW[diffw & 0x0F] & LUTB[diffb & 0x0F]; } } - ESP_LOGV(TAG, "Partial update buffer built after (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Partial update buffer built after (%ums)", millis() - start_time); eink_on_(); uint32_t clock = (1 << this->cl_pin_->get_pin()); @@ -454,13 +456,13 @@ bool Inkplate6::partial_update_() { const uint8_t *data_ptr = &this->partial_buffer_2_[(this->get_buffer_length_() * 2) - 1]; for (int i = 0; i < this->get_height_internal(); i++) { data = *(data_ptr--); - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25); + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25); hscan_start_(send); for (int j = 0, jm = (this->get_width_internal() / 4) - 1; j < jm; j++) { data = *(data_ptr--); - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25) | clock; + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; } @@ -469,7 +471,7 @@ bool Inkplate6::partial_update_() { vscan_end_(); } delayMicroseconds(230); - ESP_LOGV(TAG, "Partial update loop k=%d (%lums)", k, millis() - start_time); + ESP_LOGV(TAG, "Partial update loop k=%d (%ums)", k, millis() - start_time); } clean_fast_(2, 2); clean_fast_(3, 1); @@ -477,7 +479,7 @@ bool Inkplate6::partial_update_() { eink_off_(); memcpy(this->buffer_, this->partial_buffer_, this->get_buffer_length_()); - ESP_LOGV(TAG, "Partial update finished (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Partial update finished (%ums)", millis() - start_time); return true; } void Inkplate6::vscan_start_() { @@ -538,7 +540,7 @@ void Inkplate6::clean() { clean_fast_(0, 8); // Black to Black clean_fast_(2, 1); // Black to White clean_fast_(1, 10); // White to White - ESP_LOGV(TAG, "Clean finished (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Clean finished (%ums)", millis() - start_time); } void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) { ESP_LOGV(TAG, "Clean fast called with: (%d, %d)", c, rep); @@ -547,16 +549,16 @@ void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) { eink_on_(); uint8_t data = 0; if (c == 0) // White - data = B10101010; + data = 0b10101010; else if (c == 1) // Black - data = B01010101; + data = 0b01010101; else if (c == 2) // Discharge - data = B00000000; + data = 0b00000000; else if (c == 3) // Skip - data = B11111111; + data = 0b11111111; - uint32_t send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25); + uint32_t send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25); uint32_t clock = (1 << this->cl_pin_->get_pin()); for (int k = 0; k < rep; k++) { @@ -576,46 +578,46 @@ void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) { vscan_end_(); } delayMicroseconds(230); - ESP_LOGV(TAG, "Clean fast rep loop %d finished (%lums)", k, millis() - start_time); + ESP_LOGV(TAG, "Clean fast rep loop %d finished (%ums)", k, millis() - start_time); } - ESP_LOGV(TAG, "Clean fast finished (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Clean fast finished (%ums)", millis() - start_time); } void Inkplate6::pins_z_state_() { - this->ckv_pin_->pin_mode(INPUT); - this->sph_pin_->pin_mode(INPUT); + this->ckv_pin_->pin_mode(gpio::FLAG_INPUT); + this->sph_pin_->pin_mode(gpio::FLAG_INPUT); - this->oe_pin_->pin_mode(INPUT); - this->gmod_pin_->pin_mode(INPUT); - this->spv_pin_->pin_mode(INPUT); + this->oe_pin_->pin_mode(gpio::FLAG_INPUT); + this->gmod_pin_->pin_mode(gpio::FLAG_INPUT); + this->spv_pin_->pin_mode(gpio::FLAG_INPUT); - this->display_data_0_pin_->pin_mode(INPUT); - this->display_data_1_pin_->pin_mode(INPUT); - this->display_data_2_pin_->pin_mode(INPUT); - this->display_data_3_pin_->pin_mode(INPUT); - this->display_data_4_pin_->pin_mode(INPUT); - this->display_data_5_pin_->pin_mode(INPUT); - this->display_data_6_pin_->pin_mode(INPUT); - this->display_data_7_pin_->pin_mode(INPUT); + this->display_data_0_pin_->pin_mode(gpio::FLAG_INPUT); + this->display_data_1_pin_->pin_mode(gpio::FLAG_INPUT); + this->display_data_2_pin_->pin_mode(gpio::FLAG_INPUT); + this->display_data_3_pin_->pin_mode(gpio::FLAG_INPUT); + this->display_data_4_pin_->pin_mode(gpio::FLAG_INPUT); + this->display_data_5_pin_->pin_mode(gpio::FLAG_INPUT); + this->display_data_6_pin_->pin_mode(gpio::FLAG_INPUT); + this->display_data_7_pin_->pin_mode(gpio::FLAG_INPUT); } void Inkplate6::pins_as_outputs_() { - this->ckv_pin_->pin_mode(OUTPUT); - this->sph_pin_->pin_mode(OUTPUT); + this->ckv_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->sph_pin_->pin_mode(gpio::FLAG_OUTPUT); - this->oe_pin_->pin_mode(OUTPUT); - this->gmod_pin_->pin_mode(OUTPUT); - this->spv_pin_->pin_mode(OUTPUT); + this->oe_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->gmod_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->spv_pin_->pin_mode(gpio::FLAG_OUTPUT); - this->display_data_0_pin_->pin_mode(OUTPUT); - this->display_data_1_pin_->pin_mode(OUTPUT); - this->display_data_2_pin_->pin_mode(OUTPUT); - this->display_data_3_pin_->pin_mode(OUTPUT); - this->display_data_4_pin_->pin_mode(OUTPUT); - this->display_data_5_pin_->pin_mode(OUTPUT); - this->display_data_6_pin_->pin_mode(OUTPUT); - this->display_data_7_pin_->pin_mode(OUTPUT); + this->display_data_0_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->display_data_1_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->display_data_2_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->display_data_3_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->display_data_4_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->display_data_5_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->display_data_6_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->display_data_7_pin_->pin_mode(gpio::FLAG_OUTPUT); } } // namespace inkplate6 } // namespace esphome -#endif +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/inkplate6/inkplate.h b/esphome/components/inkplate6/inkplate.h index f2821b22a9..56e95e95bb 100644 --- a/esphome/components/inkplate6/inkplate.h +++ b/esphome/components/inkplate6/inkplate.h @@ -1,26 +1,29 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" #include "esphome/components/display/display_buffer.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO namespace esphome { namespace inkplate6 { class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public i2c::I2CDevice { public: - const uint8_t LUT2[16] = {B10101010, B10101001, B10100110, B10100101, B10011010, B10011001, B10010110, B10010101, - B01101010, B01101001, B01100110, B01100101, B01011010, B01011001, B01010110, B01010101}; - const uint8_t LUTW[16] = {B11111111, B11111110, B11111011, B11111010, B11101111, B11101110, B11101011, B11101010, - B10111111, B10111110, B10111011, B10111010, B10101111, B10101110, B10101011, B10101010}; - const uint8_t LUTB[16] = {B11111111, B11111101, B11110111, B11110101, B11011111, B11011101, B11010111, B11010101, - B01111111, B01111101, B01110111, B01110101, B01011111, B01011101, B01010111, B01010101}; - const uint8_t pixelMaskLUT[8] = {B00000001, B00000010, B00000100, B00001000, - B00010000, B00100000, B01000000, B10000000}; - const uint8_t pixelMaskGLUT[2] = {B00001111, B11110000}; + const uint8_t LUT2[16] = {0b10101010, 0b10101001, 0b10100110, 0b10100101, 0b10011010, 0b10011001, + 0b10010110, 0b10010101, 0b01101010, 0b01101001, 0b01100110, 0b01100101, + 0b01011010, 0b01011001, 0b01010110, 0b01010101}; + const uint8_t LUTW[16] = {0b11111111, 0b11111110, 0b11111011, 0b11111010, 0b11101111, 0b11101110, + 0b11101011, 0b11101010, 0b10111111, 0b10111110, 0b10111011, 0b10111010, + 0b10101111, 0b10101110, 0b10101011, 0b10101010}; + const uint8_t LUTB[16] = {0b11111111, 0b11111101, 0b11110111, 0b11110101, 0b11011111, 0b11011101, + 0b11010111, 0b11010101, 0b01111111, 0b01111101, 0b01110111, 0b01110101, + 0b01011111, 0b01011101, 0b01010111, 0b01010101}; + const uint8_t pixelMaskLUT[8] = {0b00000001, 0b00000010, 0b00000100, 0b00001000, + 0b00010000, 0b00100000, 0b01000000, 0b10000000}; + const uint8_t pixelMaskGLUT[2] = {0b00001111, 0b11110000}; const uint8_t waveform3Bit[8][8] = {{0, 0, 0, 0, 1, 1, 1, 0}, {1, 2, 2, 2, 1, 1, 1, 0}, {0, 1, 2, 1, 1, 2, 1, 0}, {0, 2, 1, 2, 1, 2, 1, 0}, {0, 0, 0, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 2, 0}, {1, 1, 1, 2, 1, 2, 2, 0}, {0, 0, 0, 0, 0, 0, 2, 0}}; @@ -40,20 +43,20 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public void set_partial_updating(bool partial_updating) { this->partial_updating_ = partial_updating; } void set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; } - void set_display_data_0_pin(GPIOPin *data) { this->display_data_0_pin_ = data; } - void set_display_data_1_pin(GPIOPin *data) { this->display_data_1_pin_ = data; } - void set_display_data_2_pin(GPIOPin *data) { this->display_data_2_pin_ = data; } - void set_display_data_3_pin(GPIOPin *data) { this->display_data_3_pin_ = data; } - void set_display_data_4_pin(GPIOPin *data) { this->display_data_4_pin_ = data; } - void set_display_data_5_pin(GPIOPin *data) { this->display_data_5_pin_ = data; } - void set_display_data_6_pin(GPIOPin *data) { this->display_data_6_pin_ = data; } - void set_display_data_7_pin(GPIOPin *data) { this->display_data_7_pin_ = data; } + void set_display_data_0_pin(InternalGPIOPin *data) { this->display_data_0_pin_ = data; } + void set_display_data_1_pin(InternalGPIOPin *data) { this->display_data_1_pin_ = data; } + void set_display_data_2_pin(InternalGPIOPin *data) { this->display_data_2_pin_ = data; } + void set_display_data_3_pin(InternalGPIOPin *data) { this->display_data_3_pin_ = data; } + void set_display_data_4_pin(InternalGPIOPin *data) { this->display_data_4_pin_ = data; } + void set_display_data_5_pin(InternalGPIOPin *data) { this->display_data_5_pin_ = data; } + void set_display_data_6_pin(InternalGPIOPin *data) { this->display_data_6_pin_ = data; } + void set_display_data_7_pin(InternalGPIOPin *data) { this->display_data_7_pin_ = data; } void set_ckv_pin(GPIOPin *ckv) { this->ckv_pin_ = ckv; } - void set_cl_pin(GPIOPin *cl) { this->cl_pin_ = cl; } + void set_cl_pin(InternalGPIOPin *cl) { this->cl_pin_ = cl; } void set_gpio0_enable_pin(GPIOPin *gpio0_enable) { this->gpio0_enable_pin_ = gpio0_enable; } void set_gmod_pin(GPIOPin *gmod) { this->gmod_pin_ = gmod; } - void set_le_pin(GPIOPin *le) { this->le_pin_ = le; } + void set_le_pin(InternalGPIOPin *le) { this->le_pin_ = le; } void set_oe_pin(GPIOPin *oe) { this->oe_pin_ = oe; } void set_powerup_pin(GPIOPin *powerup) { this->powerup_pin_ = powerup; } void set_sph_pin(GPIOPin *sph) { this->sph_pin_ = sph; } @@ -130,20 +133,20 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public bool greyscale_; bool partial_updating_; - GPIOPin *display_data_0_pin_; - GPIOPin *display_data_1_pin_; - GPIOPin *display_data_2_pin_; - GPIOPin *display_data_3_pin_; - GPIOPin *display_data_4_pin_; - GPIOPin *display_data_5_pin_; - GPIOPin *display_data_6_pin_; - GPIOPin *display_data_7_pin_; + InternalGPIOPin *display_data_0_pin_; + InternalGPIOPin *display_data_1_pin_; + InternalGPIOPin *display_data_2_pin_; + InternalGPIOPin *display_data_3_pin_; + InternalGPIOPin *display_data_4_pin_; + InternalGPIOPin *display_data_5_pin_; + InternalGPIOPin *display_data_6_pin_; + InternalGPIOPin *display_data_7_pin_; GPIOPin *ckv_pin_; - GPIOPin *cl_pin_; + InternalGPIOPin *cl_pin_; GPIOPin *gpio0_enable_pin_; GPIOPin *gmod_pin_; - GPIOPin *le_pin_; + InternalGPIOPin *le_pin_; GPIOPin *oe_pin_; GPIOPin *powerup_pin_; GPIOPin *sph_pin_; @@ -155,4 +158,4 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public } // namespace inkplate6 } // namespace esphome -#endif +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/integration/integration_sensor.cpp b/esphome/components/integration/integration_sensor.cpp index 9a0f9fc58b..2a398e5240 100644 --- a/esphome/components/integration/integration_sensor.cpp +++ b/esphome/components/integration/integration_sensor.cpp @@ -1,6 +1,7 @@ #include "integration_sensor.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace integration { @@ -9,7 +10,7 @@ static const char *const TAG = "integration"; void IntegrationSensor::setup() { if (this->restore_) { - this->rtc_ = global_preferences.make_preference(this->get_object_id_hash()); + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); float preference_value = 0; this->rtc_.load(&preference_value); this->result_ = preference_value; diff --git a/esphome/components/integration/integration_sensor.h b/esphome/components/integration/integration_sensor.h index c575de094c..a3bdfbbb2b 100644 --- a/esphome/components/integration/integration_sensor.h +++ b/esphome/components/integration/integration_sensor.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/preferences.h" #include "esphome/core/automation.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" namespace esphome { diff --git a/esphome/components/json/__init__.py b/esphome/components/json/__init__.py index 9879754d9e..fda0a552f1 100644 --- a/esphome/components/json/__init__.py +++ b/esphome/components/json/__init__.py @@ -1,9 +1,15 @@ import esphome.codegen as cg +import esphome.config_validation as cv from esphome.core import coroutine_with_priority CODEOWNERS = ["@OttoWinter"] json_ns = cg.esphome_ns.namespace("json") +CONFIG_SCHEMA = cv.All( + cv.Schema({}), + cv.only_with_arduino, +) + @coroutine_with_priority(1.0) async def to_code(config): diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index aab5b1ce9b..12c5beb73f 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "json_util.h" #include "esphome/core/log.h" @@ -113,3 +115,5 @@ VectorJsonBuffer global_json_buffer; // NOLINT(cppcoreguidelines-avoid-non-cons } // namespace json } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/json/json_util.h b/esphome/components/json/json_util.h index 65b2496b85..577510e63a 100644 --- a/esphome/components/json/json_util.h +++ b/esphome/components/json/json_util.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include #include "esphome/core/helpers.h" @@ -62,3 +64,5 @@ extern VectorJsonBuffer global_json_buffer; // NOLINT(cppcoreguidelines-avoid-n } // namespace json } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/lcd_base/lcd_display.cpp b/esphome/components/lcd_base/lcd_display.cpp index 4c0d0ab3ea..ddd7d6a6b3 100644 --- a/esphome/components/lcd_base/lcd_display.cpp +++ b/esphome/components/lcd_base/lcd_display.cpp @@ -1,6 +1,7 @@ #include "lcd_display.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" namespace esphome { namespace lcd_base { diff --git a/esphome/components/lcd_gpio/gpio_lcd_display.cpp b/esphome/components/lcd_gpio/gpio_lcd_display.cpp index 5c1656ec3e..b0344d313c 100644 --- a/esphome/components/lcd_gpio/gpio_lcd_display.cpp +++ b/esphome/components/lcd_gpio/gpio_lcd_display.cpp @@ -30,8 +30,15 @@ void GPIOLCDDisplay::dump_config() { LOG_PIN(" RW Pin: ", this->rw_pin_); LOG_PIN(" Enable Pin: ", this->enable_pin_); - for (uint8_t i = 0; i < (this->is_four_bit_mode() ? 4 : 8); i++) { - ESP_LOGCONFIG(TAG, " Data Pin %u" LOG_PIN_PATTERN, i, LOG_PIN_ARGS(this->data_pins_[i])); + LOG_PIN(" Data Pin 0: ", data_pins_[0]); + LOG_PIN(" Data Pin 1: ", data_pins_[1]); + LOG_PIN(" Data Pin 2: ", data_pins_[2]); + LOG_PIN(" Data Pin 3: ", data_pins_[3]); + if (!is_four_bit_mode()) { + LOG_PIN(" Data Pin 4: ", data_pins_[4]); + LOG_PIN(" Data Pin 5: ", data_pins_[5]); + LOG_PIN(" Data Pin 6: ", data_pins_[6]); + LOG_PIN(" Data Pin 7: ", data_pins_[7]); } LOG_UPDATE_INTERVAL(this); } diff --git a/esphome/components/lcd_gpio/gpio_lcd_display.h b/esphome/components/lcd_gpio/gpio_lcd_display.h index 01f6f95d9a..aba254a90a 100644 --- a/esphome/components/lcd_gpio/gpio_lcd_display.h +++ b/esphome/components/lcd_gpio/gpio_lcd_display.h @@ -1,6 +1,6 @@ #pragma once -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/lcd_base/lcd_display.h" namespace esphome { diff --git a/esphome/components/lcd_pcf8574/pcf8574_display.cpp b/esphome/components/lcd_pcf8574/pcf8574_display.cpp index 4830b6f223..5b00b08aff 100644 --- a/esphome/components/lcd_pcf8574/pcf8574_display.cpp +++ b/esphome/components/lcd_pcf8574/pcf8574_display.cpp @@ -1,5 +1,6 @@ #include "pcf8574_display.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace lcd_pcf8574 { diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp index 45bee5b871..77610a476f 100644 --- a/esphome/components/ledc/ledc_output.cpp +++ b/esphome/components/ledc/ledc_output.cpp @@ -1,40 +1,20 @@ #include "ledc_output.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 +#ifdef USE_ARDUINO #include +#endif +#ifdef USE_ESP_IDF +#include +#endif namespace esphome { namespace ledc { static const char *const TAG = "ledc.output"; -void LEDCOutput::write_state(float state) { - if (this->pin_->is_inverted()) - state = 1.0f - state; - - this->duty_ = state; - const uint32_t max_duty = (uint32_t(1) << this->bit_depth_) - 1; - const float duty_rounded = roundf(state * max_duty); - auto duty = static_cast(duty_rounded); - ledcWrite(this->channel_, duty); -} - -void LEDCOutput::setup() { - this->update_frequency(this->frequency_); - this->turn_off(); - // Attach pin after setting default value - ledcAttachPin(this->pin_->get_pin(), this->channel_); -} - -void LEDCOutput::dump_config() { - ESP_LOGCONFIG(TAG, "LEDC Output:"); - LOG_PIN(" Pin ", this->pin_); - ESP_LOGCONFIG(TAG, " LEDC Channel: %u", this->channel_); - ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_); -} - float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); } float ledc_min_frequency_for_bit_depth(uint8_t bit_depth) { const float max_div_num = ((1 << 20) - 1) / 256.0f; @@ -50,6 +30,67 @@ optional ledc_bit_depth_for_frequency(float frequency) { return {}; } +void LEDCOutput::write_state(float state) { + if (this->pin_->is_inverted()) + state = 1.0f - state; + + this->duty_ = state; + const uint32_t max_duty = (uint32_t(1) << this->bit_depth_) - 1; + const float duty_rounded = roundf(state * max_duty); + auto duty = static_cast(duty_rounded); + +#ifdef USE_ARDUINO + ledcWrite(this->channel_, duty); +#endif +#ifdef USE_ESP_IDF + auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; + auto chan_num = static_cast(channel_ % 8); + ledc_set_duty(speed_mode, chan_num, duty); + ledc_update_duty(speed_mode, chan_num); +#endif +} + +void LEDCOutput::setup() { +#ifdef USE_ARDUINO + this->update_frequency(this->frequency_); + this->turn_off(); + // Attach pin after setting default value + ledcAttachPin(this->pin_->get_pin(), this->channel_); +#endif +#ifdef USE_ESP_IDF + auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; + auto timer_num = static_cast((channel_ % 8) / 2); + auto chan_num = static_cast(channel_ % 8); + + bit_depth_ = *ledc_bit_depth_for_frequency(frequency_); + + ledc_timer_config_t timer_conf{}; + timer_conf.speed_mode = speed_mode; + timer_conf.duty_resolution = static_cast(bit_depth_); + timer_conf.timer_num = timer_num; + timer_conf.freq_hz = (uint32_t) frequency_; + timer_conf.clk_cfg = LEDC_AUTO_CLK; + ledc_timer_config(&timer_conf); + + ledc_channel_config_t chan_conf{}; + chan_conf.gpio_num = pin_->get_pin(); + chan_conf.speed_mode = speed_mode; + chan_conf.channel = chan_num; + chan_conf.intr_type = LEDC_INTR_DISABLE; + chan_conf.timer_sel = timer_num; + chan_conf.duty = inverted_ == pin_->is_inverted() ? 0 : (1U << bit_depth_); + chan_conf.hpoint = 0; + ledc_channel_config(&chan_conf); +#endif +} + +void LEDCOutput::dump_config() { + ESP_LOGCONFIG(TAG, "LEDC Output:"); + LOG_PIN(" Pin ", this->pin_); + ESP_LOGCONFIG(TAG, " LEDC Channel: %u", this->channel_); + ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_); +} + void LEDCOutput::update_frequency(float frequency) { auto bit_depth_opt = ledc_bit_depth_for_frequency(frequency); if (!bit_depth_opt.has_value()) { @@ -58,7 +99,21 @@ void LEDCOutput::update_frequency(float frequency) { } this->bit_depth_ = bit_depth_opt.value_or(8); this->frequency_ = frequency; +#ifdef USE_ARDUINO ledcSetup(this->channel_, frequency, this->bit_depth_); +#endif // USE_ARDUINO +#ifdef USE_ESP_IDF + auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; + auto timer_num = static_cast((channel_ % 8) / 2); + + ledc_timer_config_t timer_conf{}; + timer_conf.speed_mode = speed_mode; + timer_conf.duty_resolution = static_cast(bit_depth_); + timer_conf.timer_num = timer_num; + timer_conf.freq_hz = (uint32_t) frequency_; + timer_conf.clk_cfg = LEDC_AUTO_CLK; + ledc_timer_config(&timer_conf); +#endif // re-apply duty this->write_state(this->duty_); } diff --git a/esphome/components/ledc/ledc_output.h b/esphome/components/ledc/ledc_output.h index b3b14fe855..f810ce1e35 100644 --- a/esphome/components/ledc/ledc_output.h +++ b/esphome/components/ledc/ledc_output.h @@ -1,11 +1,11 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/automation.h" #include "esphome/components/output/float_output.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ledc { @@ -14,7 +14,7 @@ extern uint8_t next_ledc_channel; class LEDCOutput : public output::FloatOutput, public Component { public: - explicit LEDCOutput(GPIOPin *pin) : pin_(pin) { this->channel_ = next_ledc_channel++; } + explicit LEDCOutput(InternalGPIOPin *pin) : pin_(pin) { this->channel_ = next_ledc_channel++; } void set_channel(uint8_t channel) { this->channel_ = channel; } void set_frequency(float frequency) { this->frequency_ = frequency; } @@ -31,7 +31,7 @@ class LEDCOutput : public output::FloatOutput, public Component { void write_state(float state) override; protected: - GPIOPin *pin_; + InternalGPIOPin *pin_; uint8_t channel_{}; uint8_t bit_depth_{}; float frequency_{}; diff --git a/esphome/components/ledc/output.py b/esphome/components/ledc/output.py index 0f5f186ff5..895dcc998b 100644 --- a/esphome/components/ledc/output.py +++ b/esphome/components/ledc/output.py @@ -7,10 +7,9 @@ from esphome.const import ( CONF_FREQUENCY, CONF_ID, CONF_PIN, - ESP_PLATFORM_ESP32, ) -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["esp32"] def calc_max_frequency(bit_depth): diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 398546a09f..f888006b17 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -53,7 +53,7 @@ void LightState::setup() { case LIGHT_RESTORE_DEFAULT_ON: case LIGHT_RESTORE_INVERTED_DEFAULT_OFF: case LIGHT_RESTORE_INVERTED_DEFAULT_ON: - this->rtc_ = global_preferences.make_preference(this->get_object_id_hash()); + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); // Attempt to load from preferences, else fall back to default values if (!this->rtc_.load(&recovered)) { recovered.state = false; diff --git a/esphome/components/light/light_transformer.h b/esphome/components/light/light_transformer.h index c5181abd4f..dd904d0eed 100644 --- a/esphome/components/light/light_transformer.h +++ b/esphome/components/light/light_transformer.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" #include "light_color_values.h" namespace esphome { @@ -9,6 +10,8 @@ namespace light { /// Base class for all light color transformers, such as transitions or flashes. class LightTransformer { public: + virtual ~LightTransformer() = default; + void setup(const LightColorValues &start_values, const LightColorValues &target_values, uint32_t length) { this->start_time_ = millis(); this->length_ = length; diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index b821e8c5d8..bc1bc6bb41 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -214,7 +214,7 @@ def validate_printf(value): (?:\.(?:\d+|\*))? # precision (?:h|l|ll|w|I|I32|I64)? # size [cCdiouxXeEfgGaAnpsSZ] # type - ) + ) """ # noqa matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.X) if len(matches) != len(value[CONF_ARGS]): diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 59dfa93429..045f7059a9 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -1,9 +1,15 @@ #include "logger.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP_IDF +#include "freertos/FreeRTOS.h" +#include +#endif + +#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) #include #endif -#include +#include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace logger { @@ -63,11 +69,11 @@ void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStr recursion_guard_ = true; this->reset_buffer_(); // copy format string - const char *format_pgm_p = (PGM_P) format; + auto *format_pgm_p = reinterpret_cast(format); size_t len = 0; char ch = '.'; while (!this->is_buffer_full_() && ch != '\0') { - this->tx_buffer_[this->tx_buffer_at_++] = ch = pgm_read_byte(format_pgm_p++); + this->tx_buffer_[this->tx_buffer_at_++] = ch = (char) progmem_read_byte(format_pgm_p++); } // Buffer full form copying format if (this->is_buffer_full_()) @@ -105,19 +111,26 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) { this->set_null_terminator_(); const char *msg = this->tx_buffer_ + offset; +#ifdef USE_ARDUINO if (this->baud_rate_ > 0) this->hw_serial_->println(msg); -#ifdef ARDUINO_ARCH_ESP32 +#endif // USE_ARDUINO +#ifdef USE_ESP_IDF + uart_write_bytes(uart_num_, msg, strlen(msg)); + uart_write_bytes(uart_num_, "\n", 1); +#endif + +#ifdef USE_ESP32 // Suppress network-logging if memory constrained, but still log to serial // ports. In some configurations (eg BLE enabled) there may be some transient // memory exhaustion, and trying to log when OOM can lead to a crash. Skipping // here usually allows the stack to recover instead. // See issue #1234 for analysis. - if (xPortGetFreeHeapSize() > 2048) - this->log_callback_.call(level, tag, msg); -#else - this->log_callback_.call(level, tag, msg); + if (xPortGetFreeHeapSize() < 2048) + return; #endif + + this->log_callback_.call(level, tag, msg); } Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart) @@ -128,9 +141,10 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart) void Logger::pre_setup() { if (this->baud_rate_ > 0) { +#ifdef USE_ARDUINO switch (this->uart_) { case UART_SELECTION_UART0: -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 case UART_SELECTION_UART0_SWAP: #endif this->hw_serial_ = &Serial; @@ -138,7 +152,7 @@ void Logger::pre_setup() { case UART_SELECTION_UART1: this->hw_serial_ = &Serial1; break; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 case UART_SELECTION_UART2: #if !CONFIG_IDF_TARGET_ESP32S2 && !CONFIG_IDF_TARGET_ESP32C3 // FIXME: Validate in config that UART2 can't be set for ESP32-S2 (only has @@ -148,23 +162,51 @@ void Logger::pre_setup() { break; #endif } +#endif // USE_ARDUINO +#ifdef USE_ESP_IDF + uart_num_ = UART_NUM_0; + switch (uart_) { + case UART_SELECTION_UART0: + uart_num_ = UART_NUM_0; + break; + case UART_SELECTION_UART1: + uart_num_ = UART_NUM_1; + break; + case UART_SELECTION_UART2: + uart_num_ = UART_NUM_2; + break; + } + uart_config_t uart_config = { + .baud_rate = (int) baud_rate_, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + }; + uart_param_config(uart_num_, &uart_config); + const int uart_buffer_size = tx_buffer_size_; + // Install UART driver using an event queue here + uart_driver_install(uart_num_, uart_buffer_size, uart_buffer_size, 10, nullptr, 0); +#endif +#ifdef USE_ARDUINO this->hw_serial_->begin(this->baud_rate_); -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 if (this->uart_ == UART_SELECTION_UART0_SWAP) { this->hw_serial_->swap(); } this->hw_serial_->setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); #endif +#endif // USE_ARDUINO } -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 else { uart_set_debug(UART_NO); } #endif global_logger = this; -#ifdef ARDUINO_ARCH_ESP32 +#if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO) esp_log_set_vprintf(esp_idf_log_vprintf_); if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) { esp_log_level_set("*", ESP_LOG_VERBOSE); @@ -183,10 +225,10 @@ void Logger::add_on_log_callback(std::function + +#ifdef USE_ARDUINO +#include +#endif +#ifdef USE_ESP_IDF +#include +#endif namespace esphome { @@ -17,11 +24,11 @@ namespace logger { enum UARTSelection { UART_SELECTION_UART0 = 0, UART_SELECTION_UART1, -#ifdef ARDUINO_ARCH_ESP32 - UART_SELECTION_UART2 +#ifdef USE_ESP32 + UART_SELECTION_UART2, #endif -#ifdef ARDUINO_ARCH_ESP8266 - UART_SELECTION_UART0_SWAP +#ifdef USE_ESP8266 + UART_SELECTION_UART0_SWAP, #endif }; @@ -32,7 +39,12 @@ class Logger : public Component { /// Manually set the baud rate for serial, set to 0 to disable. void set_baud_rate(uint32_t baud_rate); uint32_t get_baud_rate() const { return baud_rate_; } +#ifdef USE_ARDUINO HardwareSerial *get_hw_serial() const { return hw_serial_; } +#endif +#ifdef USE_ESP_IDF + uart_port_t get_uart_num() const { return uart_num_; } +#endif /// Get the UART used by the logger. UARTSelection get_uart() const; @@ -106,7 +118,12 @@ class Logger : public Component { int tx_buffer_at_{0}; int tx_buffer_size_{0}; UARTSelection uart_{UART_SELECTION_UART0}; +#ifdef USE_ARDUINO HardwareSerial *hw_serial_{nullptr}; +#endif +#ifdef USE_ESP_IDF + uart_port_t uart_num_; +#endif struct LogLevelOverride { std::string tag; int level; diff --git a/esphome/components/max31856/max31856.cpp b/esphome/components/max31856/max31856.cpp index 9b627e21a2..9300916fdc 100644 --- a/esphome/components/max31856/max31856.cpp +++ b/esphome/components/max31856/max31856.cpp @@ -163,7 +163,7 @@ void MAX31856Sensor::write_register_(uint8_t reg, uint8_t value) { ESP_LOGV(TAG, "write_register_ 0x%02X: 0x%02X", reg, value); } -const uint8_t MAX31856Sensor::read_register_(uint8_t reg) { +uint8_t MAX31856Sensor::read_register_(uint8_t reg) { ESP_LOGVV(TAG, "read_register_ 0x%02X", reg); this->enable(); ESP_LOGVV(TAG, "write_byte reg=0x%02X", reg); @@ -175,7 +175,7 @@ const uint8_t MAX31856Sensor::read_register_(uint8_t reg) { return value; } -const uint32_t MAX31856Sensor::read_register24_(uint8_t reg) { +uint32_t MAX31856Sensor::read_register24_(uint8_t reg) { ESP_LOGVV(TAG, "read_register_24_ 0x%02X", reg); this->enable(); ESP_LOGVV(TAG, "write_byte reg=0x%02X", reg); diff --git a/esphome/components/max31856/max31856.h b/esphome/components/max31856/max31856.h index 779eb52c8e..157aad433c 100644 --- a/esphome/components/max31856/max31856.h +++ b/esphome/components/max31856/max31856.h @@ -82,8 +82,8 @@ class MAX31856Sensor : public sensor::Sensor, protected: MAX31856ConfigFilter filter_; - const uint8_t read_register_(uint8_t reg); - const uint32_t read_register24_(uint8_t reg); + uint8_t read_register_(uint8_t reg); + uint32_t read_register24_(uint8_t reg); void write_register_(uint8_t reg, uint8_t value); void one_shot_temperature_(); diff --git a/esphome/components/max31865/max31865.cpp b/esphome/components/max31865/max31865.cpp index daadc26cdc..91946cde2c 100644 --- a/esphome/components/max31865/max31865.cpp +++ b/esphome/components/max31865/max31865.cpp @@ -156,7 +156,7 @@ void MAX31865Sensor::write_register_(uint8_t reg, uint8_t value) { ESP_LOGVV(TAG, "write_register_ 0x%02X: 0x%02X", reg, value); } -const uint8_t MAX31865Sensor::read_register_(uint8_t reg) { +uint8_t MAX31865Sensor::read_register_(uint8_t reg) { this->enable(); this->write_byte(reg); const uint8_t value(this->read_byte()); @@ -165,7 +165,7 @@ const uint8_t MAX31865Sensor::read_register_(uint8_t reg) { return value; } -const uint16_t MAX31865Sensor::read_register_16_(uint8_t reg) { +uint16_t MAX31865Sensor::read_register_16_(uint8_t reg) { this->enable(); this->write_byte(reg); const uint8_t msb(this->read_byte()); @@ -176,7 +176,7 @@ const uint16_t MAX31865Sensor::read_register_16_(uint8_t reg) { return value; } -float MAX31865Sensor::calc_temperature_(const float &rtd_ratio) { +float MAX31865Sensor::calc_temperature_(float rtd_ratio) { // Based loosely on Adafruit's library: https://github.com/adafruit/Adafruit_MAX31865 // Mainly based on formulas provided by Analog: // http://www.analog.com/media/en/technical-documentation/application-notes/AN709_0.pdf diff --git a/esphome/components/max31865/max31865.h b/esphome/components/max31865/max31865.h index 393dd4b434..b83753a678 100644 --- a/esphome/components/max31865/max31865.h +++ b/esphome/components/max31865/max31865.h @@ -49,9 +49,9 @@ class MAX31865Sensor : public sensor::Sensor, void read_data_(); void write_config_(uint8_t mask, uint8_t bits, uint8_t start_position = 0); void write_register_(uint8_t reg, uint8_t value); - const uint8_t read_register_(uint8_t reg); - const uint16_t read_register_16_(uint8_t reg); - float calc_temperature_(const float &rtd_ratio); + uint8_t read_register_(uint8_t reg); + uint16_t read_register_16_(uint8_t reg); + float calc_temperature_(float rtd_ratio); }; } // namespace max31865 diff --git a/esphome/components/max7219/max7219.cpp b/esphome/components/max7219/max7219.cpp index 97886e53fa..960ac58071 100644 --- a/esphome/components/max7219/max7219.cpp +++ b/esphome/components/max7219/max7219.cpp @@ -1,6 +1,7 @@ #include "max7219.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" namespace esphome { namespace max7219 { @@ -172,7 +173,7 @@ uint8_t MAX7219Component::print(uint8_t start_pos, const char *str) { for (; *str != '\0'; str++) { uint8_t data = MAX7219_UNKNOWN_CHAR; if (*str >= ' ' && *str <= '~') - data = pgm_read_byte(&MAX7219_ASCII_TO_RAW[*str - ' ']); + data = progmem_read_byte(&MAX7219_ASCII_TO_RAW[*str - ' ']); if (data == MAX7219_UNKNOWN_CHAR) { ESP_LOGW(TAG, "Encountered character '%c' with no MAX7219 representation while translating string!", *str); diff --git a/esphome/components/max7219digit/max7219digit.cpp b/esphome/components/max7219digit/max7219digit.cpp index 520694af9d..4fedd3d312 100644 --- a/esphome/components/max7219digit/max7219digit.cpp +++ b/esphome/components/max7219digit/max7219digit.cpp @@ -1,6 +1,7 @@ #include "max7219digit.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" #include "max7219font.h" namespace esphome { @@ -212,7 +213,7 @@ void MAX7219Component::scroll_left() { void MAX7219Component::send_char(uint8_t chip, uint8_t data) { // get this character from PROGMEM for (uint8_t i = 0; i < 8; i++) - this->max_displaybuffer_[chip * 8 + i] = pgm_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]); + this->max_displaybuffer_[chip * 8 + i] = progmem_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]); } // end of send_char // send one character (data) to position (chip) diff --git a/esphome/components/max7219digit/max7219digit.h b/esphome/components/max7219digit/max7219digit.h index 83f45e3a00..02fe8b6f42 100644 --- a/esphome/components/max7219digit/max7219digit.h +++ b/esphome/components/max7219digit/max7219digit.h @@ -54,8 +54,8 @@ class MAX7219Component : public PollingComponent, void set_scroll_mode(uint8_t mode) { this->scroll_mode_ = mode; }; void set_reverse(bool on_off) { this->reverse_ = on_off; }; - void send_char(byte chip, byte data); - void send64pixels(byte chip, const byte pixels[8]); + void send_char(uint8_t chip, uint8_t data); + void send64pixels(uint8_t chip, const uint8_t pixels[8]); void scroll_left(); void scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_t delay, uint16_t dwell); diff --git a/esphome/components/max7219digit/max7219font.h b/esphome/components/max7219digit/max7219font.h index 3d42d1cc2d..22d64d1ecd 100644 --- a/esphome/components/max7219digit/max7219font.h +++ b/esphome/components/max7219digit/max7219font.h @@ -1,11 +1,13 @@ #pragma once +#include "esphome/core/hal.h" + namespace esphome { namespace max7219digit { // bit patterns for the CP437 font -const byte MAX7219_DOT_MATRIX_FONT[256][8] PROGMEM = { +const uint8_t MAX7219_DOT_MATRIX_FONT[256][8] PROGMEM = { {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 0x00 {0x7E, 0x81, 0x95, 0xB1, 0xB1, 0x95, 0x81, 0x7E}, // 0x01 {0x7E, 0xFF, 0xEB, 0xCF, 0xCF, 0xEB, 0xFF, 0x7E}, // 0x02 diff --git a/esphome/components/mcp23008/mcp23008.h b/esphome/components/mcp23008/mcp23008.h index 42c8e497fa..406ce0b419 100644 --- a/esphome/components/mcp23008/mcp23008.h +++ b/esphome/components/mcp23008/mcp23008.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/mcp23x08_base/mcp23x08_base.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" namespace esphome { diff --git a/esphome/components/mcp23016/__init__.py b/esphome/components/mcp23016/__init__.py index 4d9657e794..c1209a9627 100644 --- a/esphome/components/mcp23016/__init__.py +++ b/esphome/components/mcp23016/__init__.py @@ -2,17 +2,19 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins from esphome.components import i2c -from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED +from esphome.const import ( + CONF_ID, + CONF_INPUT, + CONF_NUMBER, + CONF_MODE, + CONF_INVERTED, + CONF_OUTPUT, +) DEPENDENCIES = ["i2c"] MULTI_CONF = True mcp23016_ns = cg.esphome_ns.namespace("mcp23016") -MCP23016GPIOMode = mcp23016_ns.enum("MCP23016GPIOMode") -MCP23016_GPIO_MODES = { - "INPUT": MCP23016GPIOMode.MCP23016_INPUT, - "OUTPUT": MCP23016GPIOMode.MCP23016_OUTPUT, -} MCP23016 = mcp23016_ns.class_("MCP23016", cg.Component, i2c.I2CDevice) MCP23016GPIOPin = mcp23016_ns.class_("MCP23016GPIOPin", cg.GPIOPin) @@ -34,34 +36,41 @@ async def to_code(config): await i2c.register_i2c_device(var, config) +def validate_mode(value): + if not (value[CONF_INPUT] or value[CONF_OUTPUT]): + raise cv.Invalid("Mode must be either input or output") + if value[CONF_INPUT] and value[CONF_OUTPUT]: + raise cv.Invalid("Mode must be either input or output") + return value + + CONF_MCP23016 = "mcp23016" -MCP23016_OUTPUT_PIN_SCHEMA = cv.Schema( +MCP23016_PIN_SCHEMA = cv.All( { + cv.GenerateID(): cv.declare_id(MCP23016GPIOPin), cv.Required(CONF_MCP23016): cv.use_id(MCP23016), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum( - MCP23016_GPIO_MODES, upper=True - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - } -) -MCP23016_INPUT_PIN_SCHEMA = cv.Schema( - { - cv.Required(CONF_MCP23016): cv.use_id(MCP23016), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="INPUT"): cv.enum( - MCP23016_GPIO_MODES, upper=True + cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15), + cv.Optional(CONF_MODE, default={}): cv.All( + { + cv.Optional(CONF_INPUT, default=False): cv.boolean, + cv.Optional(CONF_OUTPUT, default=False): cv.boolean, + }, + validate_mode, ), cv.Optional(CONF_INVERTED, default=False): cv.boolean, } ) -@pins.PIN_SCHEMA_REGISTRY.register( - CONF_MCP23016, (MCP23016_OUTPUT_PIN_SCHEMA, MCP23016_INPUT_PIN_SCHEMA) -) +@pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23016, MCP23016_PIN_SCHEMA) async def mcp23016_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_MCP23016]) - return MCP23016GPIOPin.new( - parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED] - ) + + cg.add(var.set_parent(parent)) + + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + return var diff --git a/esphome/components/mcp23016/mcp23016.cpp b/esphome/components/mcp23016/mcp23016.cpp index f2b55fe2e2..a8df4e1745 100644 --- a/esphome/components/mcp23016/mcp23016.cpp +++ b/esphome/components/mcp23016/mcp23016.cpp @@ -1,5 +1,6 @@ #include "mcp23016.h" #include "esphome/core/log.h" +#include namespace esphome { namespace mcp23016 { @@ -29,17 +30,12 @@ void MCP23016::digital_write(uint8_t pin, bool value) { uint8_t reg_addr = pin < 8 ? MCP23016_OLAT0 : MCP23016_OLAT1; this->update_reg_(pin, value, reg_addr); } -void MCP23016::pin_mode(uint8_t pin, uint8_t mode) { +void MCP23016::pin_mode(uint8_t pin, gpio::Flags flags) { uint8_t iodir = pin < 8 ? MCP23016_IODIR0 : MCP23016_IODIR1; - switch (mode) { - case MCP23016_INPUT: - this->update_reg_(pin, true, iodir); - break; - case MCP23016_OUTPUT: - this->update_reg_(pin, false, iodir); - break; - default: - break; + if (flags == gpio::FLAG_INPUT) { + this->update_reg_(pin, true, iodir); + } else if (flags == gpio::FLAG_OUTPUT) { + this->update_reg_(pin, false, iodir); } } float MCP23016::get_setup_priority() const { return setup_priority::HARDWARE; } @@ -80,12 +76,15 @@ void MCP23016::update_reg_(uint8_t pin, bool pin_value, uint8_t reg_addr) { } } -MCP23016GPIOPin::MCP23016GPIOPin(MCP23016 *parent, uint8_t pin, uint8_t mode, bool inverted) - : GPIOPin(pin, mode, inverted), parent_(parent) {} -void MCP23016GPIOPin::setup() { this->pin_mode(this->mode_); } -void MCP23016GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); } +void MCP23016GPIOPin::setup() { pin_mode(flags_); } +void MCP23016GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool MCP23016GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void MCP23016GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } +std::string MCP23016GPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%u via MCP23016", pin_); + return buffer; +} } // namespace mcp23016 } // namespace esphome diff --git a/esphome/components/mcp23016/mcp23016.h b/esphome/components/mcp23016/mcp23016.h index 53502f80eb..a4890b4120 100644 --- a/esphome/components/mcp23016/mcp23016.h +++ b/esphome/components/mcp23016/mcp23016.h @@ -1,18 +1,12 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" namespace esphome { namespace mcp23016 { -/// Modes for MCP23016 pins -enum MCP23016GPIOMode : uint8_t { - MCP23016_INPUT = INPUT, // 0x00 - MCP23016_OUTPUT = OUTPUT // 0x01 -}; - enum MCP23016GPIORegisters { // 0 side MCP23016_GP0 = 0x00, @@ -38,7 +32,7 @@ class MCP23016 : public Component, public i2c::I2CDevice { bool digital_read(uint8_t pin); void digital_write(uint8_t pin, bool value); - void pin_mode(uint8_t pin, uint8_t mode); + void pin_mode(uint8_t pin, gpio::Flags flags); float get_setup_priority() const override; @@ -56,15 +50,22 @@ class MCP23016 : public Component, public i2c::I2CDevice { class MCP23016GPIOPin : public GPIOPin { public: - MCP23016GPIOPin(MCP23016 *parent, uint8_t pin, uint8_t mode, bool inverted = false); - void setup() override; - void pin_mode(uint8_t mode) override; + void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; + std::string dump_summary() const override; + + void set_parent(MCP23016 *parent) { parent_ = parent; } + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_flags(gpio::Flags flags) { flags_ = flags; } protected: MCP23016 *parent_; + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; }; } // namespace mcp23016 diff --git a/esphome/components/mcp23017/mcp23017.h b/esphome/components/mcp23017/mcp23017.h index fd9086a492..8959e06a41 100644 --- a/esphome/components/mcp23017/mcp23017.h +++ b/esphome/components/mcp23017/mcp23017.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/mcp23x17_base/mcp23x17_base.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" namespace esphome { diff --git a/esphome/components/mcp23s08/mcp23s08.h b/esphome/components/mcp23s08/mcp23s08.h index 4ca02c54fc..a2a6be880a 100644 --- a/esphome/components/mcp23s08/mcp23s08.h +++ b/esphome/components/mcp23s08/mcp23s08.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/mcp23x08_base/mcp23x08_base.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/spi/spi.h" namespace esphome { diff --git a/esphome/components/mcp23s17/mcp23s17.h b/esphome/components/mcp23s17/mcp23s17.h index 1ced144c23..cb5d6cfcd8 100644 --- a/esphome/components/mcp23s17/mcp23s17.h +++ b/esphome/components/mcp23s17/mcp23s17.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/mcp23x17_base/mcp23x17_base.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/spi/spi.h" namespace esphome { diff --git a/esphome/components/mcp23x08_base/mcp23x08_base.cpp b/esphome/components/mcp23x08_base/mcp23x08_base.cpp index 2b047fa288..2137b36921 100644 --- a/esphome/components/mcp23x08_base/mcp23x08_base.cpp +++ b/esphome/components/mcp23x08_base/mcp23x08_base.cpp @@ -19,22 +19,16 @@ void MCP23X08Base::digital_write(uint8_t pin, bool value) { this->update_reg(pin, value, reg_addr); } -void MCP23X08Base::pin_mode(uint8_t pin, uint8_t mode) { +void MCP23X08Base::pin_mode(uint8_t pin, gpio::Flags flags) { uint8_t iodir = mcp23x08_base::MCP23X08_IODIR; uint8_t gppu = mcp23x08_base::MCP23X08_GPPU; - switch (mode) { - case mcp23xxx_base::MCP23XXX_INPUT: - this->update_reg(pin, true, iodir); - break; - case mcp23xxx_base::MCP23XXX_INPUT_PULLUP: - this->update_reg(pin, true, iodir); - this->update_reg(pin, true, gppu); - break; - case mcp23xxx_base::MCP23XXX_OUTPUT: - this->update_reg(pin, false, iodir); - break; - default: - break; + if (flags == gpio::FLAG_INPUT) { + this->update_reg(pin, true, iodir); + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + this->update_reg(pin, true, iodir); + this->update_reg(pin, true, gppu); + } else if (flags == gpio::FLAG_OUTPUT) { + this->update_reg(pin, false, iodir); } } diff --git a/esphome/components/mcp23x08_base/mcp23x08_base.h b/esphome/components/mcp23x08_base/mcp23x08_base.h index 5e2c1a047f..910519119b 100644 --- a/esphome/components/mcp23x08_base/mcp23x08_base.h +++ b/esphome/components/mcp23x08_base/mcp23x08_base.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/mcp23xxx_base/mcp23xxx_base.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" namespace esphome { namespace mcp23x08_base { @@ -26,7 +26,7 @@ class MCP23X08Base : public mcp23xxx_base::MCP23XXXBase { public: bool digital_read(uint8_t pin) override; void digital_write(uint8_t pin, bool value) override; - void pin_mode(uint8_t pin, uint8_t mode) override; + void pin_mode(uint8_t pin, gpio::Flags flags) override; void pin_interrupt_mode(uint8_t pin, mcp23xxx_base::MCP23XXXInterruptMode interrupt_mode) override; protected: diff --git a/esphome/components/mcp23x17_base/mcp23x17_base.cpp b/esphome/components/mcp23x17_base/mcp23x17_base.cpp index 72dec2d457..e975670faa 100644 --- a/esphome/components/mcp23x17_base/mcp23x17_base.cpp +++ b/esphome/components/mcp23x17_base/mcp23x17_base.cpp @@ -19,22 +19,16 @@ void MCP23X17Base::digital_write(uint8_t pin, bool value) { this->update_reg(pin, value, reg_addr); } -void MCP23X17Base::pin_mode(uint8_t pin, uint8_t mode) { +void MCP23X17Base::pin_mode(uint8_t pin, gpio::Flags flags) { uint8_t iodir = pin < 8 ? mcp23x17_base::MCP23X17_IODIRA : mcp23x17_base::MCP23X17_IODIRB; uint8_t gppu = pin < 8 ? mcp23x17_base::MCP23X17_GPPUA : mcp23x17_base::MCP23X17_GPPUB; - switch (mode) { - case mcp23xxx_base::MCP23XXX_INPUT: - this->update_reg(pin, true, iodir); - break; - case mcp23xxx_base::MCP23XXX_INPUT_PULLUP: - this->update_reg(pin, true, iodir); - this->update_reg(pin, true, gppu); - break; - case mcp23xxx_base::MCP23XXX_OUTPUT: - this->update_reg(pin, false, iodir); - break; - default: - break; + if (flags == gpio::FLAG_INPUT) { + this->update_reg(pin, true, iodir); + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + this->update_reg(pin, true, iodir); + this->update_reg(pin, true, gppu); + } else if (flags == gpio::FLAG_OUTPUT) { + this->update_reg(pin, false, iodir); } } diff --git a/esphome/components/mcp23x17_base/mcp23x17_base.h b/esphome/components/mcp23x17_base/mcp23x17_base.h index 1bbcb97041..3d50ee8c03 100644 --- a/esphome/components/mcp23x17_base/mcp23x17_base.h +++ b/esphome/components/mcp23x17_base/mcp23x17_base.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/mcp23xxx_base/mcp23xxx_base.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" namespace esphome { namespace mcp23x17_base { @@ -38,7 +38,7 @@ class MCP23X17Base : public mcp23xxx_base::MCP23XXXBase { public: bool digital_read(uint8_t pin) override; void digital_write(uint8_t pin, bool value) override; - void pin_mode(uint8_t pin, uint8_t mode) override; + void pin_mode(uint8_t pin, gpio::Flags flags) override; void pin_interrupt_mode(uint8_t pin, mcp23xxx_base::MCP23XXXInterruptMode interrupt_mode) override; protected: diff --git a/esphome/components/mcp23xxx_base/__init__.py b/esphome/components/mcp23xxx_base/__init__.py index c22d377b3c..f2c2706416 100644 --- a/esphome/components/mcp23xxx_base/__init__.py +++ b/esphome/components/mcp23xxx_base/__init__.py @@ -3,11 +3,14 @@ import esphome.config_validation as cv from esphome import pins from esphome.const import ( CONF_ID, + CONF_INPUT, CONF_NUMBER, CONF_MODE, CONF_INVERTED, CONF_INTERRUPT, CONF_OPEN_DRAIN_INTERRUPT, + CONF_OUTPUT, + CONF_PULLUP, ) from esphome.core import coroutine @@ -47,26 +50,29 @@ async def register_mcp23xxx(config): return var +def validate_mode(value): + if not (value[CONF_INPUT] or value[CONF_OUTPUT]): + raise cv.Invalid("Mode must be either input or output") + if value[CONF_INPUT] and value[CONF_OUTPUT]: + raise cv.Invalid("Mode must be either input or output") + if value[CONF_PULLUP] and not value[CONF_INPUT]: + raise cv.Invalid("Pullup only available with input") + return value + + CONF_MCP23XXX = "mcp23xxx" -MCP23XXX_OUTPUT_PIN_SCHEMA = cv.Schema( +MCP23XXX_PIN_SCHEMA = cv.All( { + cv.GenerateID(): cv.declare_id(MCP23XXXGPIOPin), cv.Required(CONF_MCP23XXX): cv.use_id(MCP23XXXBase), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum( - MCP23XXX_GPIO_MODES, upper=True - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - cv.Optional(CONF_INTERRUPT, default="NO_INTERRUPT"): cv.enum( - MCP23XXX_INTERRUPT_MODES, upper=True - ), - } -) -MCP23XXX_INPUT_PIN_SCHEMA = cv.Schema( - { - cv.Required(CONF_MCP23XXX): cv.use_id(MCP23XXXBase), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="INPUT"): cv.enum( - MCP23XXX_GPIO_MODES, upper=True + cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15), + cv.Optional(CONF_MODE, default={}): cv.All( + { + cv.Optional(CONF_INPUT, default=False): cv.boolean, + cv.Optional(CONF_PULLUP, default=False): cv.boolean, + cv.Optional(CONF_OUTPUT, default=False): cv.boolean, + }, + validate_mode, ), cv.Optional(CONF_INVERTED, default=False): cv.boolean, cv.Optional(CONF_INTERRUPT, default="NO_INTERRUPT"): cv.enum( @@ -76,42 +82,31 @@ MCP23XXX_INPUT_PIN_SCHEMA = cv.Schema( ) -@pins.PIN_SCHEMA_REGISTRY.register( - CONF_MCP23XXX, (MCP23XXX_OUTPUT_PIN_SCHEMA, MCP23XXX_INPUT_PIN_SCHEMA) -) +@pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23XXX, MCP23XXX_PIN_SCHEMA) async def mcp23xxx_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_MCP23XXX]) - return MCP23XXXGPIOPin.new( - parent, - config[CONF_NUMBER], - config[CONF_MODE], - config[CONF_INVERTED], - config[CONF_INTERRUPT], - ) + + cg.add(var.set_parent(parent)) + + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + cg.add(var.set_interrupt_mode(config[CONF_INTERRUPT])) + return var # BEGIN Removed pin schemas below to show error in configuration # TODO remove in 2022.5.0 for id in ["mcp23008", "mcp23s08", "mcp23017", "mcp23s17"]: - PIN_SCHEMA = cv.Schema( - { - cv.Required(id): cv.invalid( - f"'{id}:' has been removed from the pin schema in 1.17.0, please use 'mcp23xxx:'" - ), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="INPUT"): cv.enum( - MCP23XXX_GPIO_MODES, upper=True - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - cv.Optional(CONF_INTERRUPT, default="NO_INTERRUPT"): cv.enum( - MCP23XXX_INTERRUPT_MODES, upper=True - ), - } + invalid_schema = cv.invalid( + f"'{id}:' has been removed from the pin schema in 1.17.0, please use 'mcp23xxx:'" ) # pylint: disable=cell-var-from-loop - @pins.PIN_SCHEMA_REGISTRY.register(id, (PIN_SCHEMA, PIN_SCHEMA)) + @pins.PIN_SCHEMA_REGISTRY.register(id, invalid_schema) def pin_to_code(config): pass diff --git a/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp b/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp index 37c55fceaf..14a703fb9f 100644 --- a/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp +++ b/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp @@ -6,16 +6,15 @@ namespace mcp23xxx_base { float MCP23XXXBase::get_setup_priority() const { return setup_priority::IO; } -MCP23XXXGPIOPin::MCP23XXXGPIOPin(MCP23XXXBase *parent, uint8_t pin, uint8_t mode, bool inverted, - MCP23XXXInterruptMode interrupt_mode) - : GPIOPin(pin, mode, inverted), parent_(parent), interrupt_mode_(interrupt_mode) {} -void MCP23XXXGPIOPin::setup() { this->pin_mode(this->mode_); } -void MCP23XXXGPIOPin::pin_mode(uint8_t mode) { - this->parent_->pin_mode(this->pin_, mode); - this->parent_->pin_interrupt_mode(this->pin_, this->interrupt_mode_); -} +void MCP23XXXGPIOPin::setup() { pin_mode(flags_); } +void MCP23XXXGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool MCP23XXXGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void MCP23XXXGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } +std::string MCP23XXXGPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%u via MCP23XXX", pin_); + return buffer; +} } // namespace mcp23xxx_base } // namespace esphome diff --git a/esphome/components/mcp23xxx_base/mcp23xxx_base.h b/esphome/components/mcp23xxx_base/mcp23xxx_base.h index bf01320264..a522ea28c5 100644 --- a/esphome/components/mcp23xxx_base/mcp23xxx_base.h +++ b/esphome/components/mcp23xxx_base/mcp23xxx_base.h @@ -1,25 +1,18 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" namespace esphome { namespace mcp23xxx_base { enum MCP23XXXInterruptMode : uint8_t { MCP23XXX_NO_INTERRUPT = 0, MCP23XXX_CHANGE, MCP23XXX_RISING, MCP23XXX_FALLING }; -/// Modes for MCP23XXX pins -enum MCP23XXXGPIOMode : uint8_t { - MCP23XXX_INPUT = INPUT, // 0x00 - MCP23XXX_INPUT_PULLUP = INPUT_PULLUP, // 0x02 - MCP23XXX_OUTPUT = OUTPUT // 0x01 -}; - class MCP23XXXBase : public Component { public: virtual bool digital_read(uint8_t pin); virtual void digital_write(uint8_t pin, bool value); - virtual void pin_mode(uint8_t pin, uint8_t mode); + virtual void pin_mode(uint8_t pin, gpio::Flags flags); virtual void pin_interrupt_mode(uint8_t pin, MCP23XXXInterruptMode interrupt_mode); void set_open_drain_ints(const bool value) { this->open_drain_ints_ = value; } @@ -38,16 +31,23 @@ class MCP23XXXBase : public Component { class MCP23XXXGPIOPin : public GPIOPin { public: - MCP23XXXGPIOPin(MCP23XXXBase *parent, uint8_t pin, uint8_t mode, bool inverted = false, - MCP23XXXInterruptMode interrupt_mode = MCP23XXX_NO_INTERRUPT); - void setup() override; - void pin_mode(uint8_t mode) override; + void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; + std::string dump_summary() const override; + + void set_parent(MCP23XXXBase *parent) { parent_ = parent; } + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_flags(gpio::Flags flags) { flags_ = flags; } + void set_interrupt_mode(MCP23XXXInterruptMode interrupt_mode) { interrupt_mode_ = interrupt_mode; } protected: MCP23XXXBase *parent_; + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; MCP23XXXInterruptMode interrupt_mode_; }; diff --git a/esphome/components/mcp3008/mcp3008.h b/esphome/components/mcp3008/mcp3008.h index 16f1c14fcb..6f3dc576ea 100644 --- a/esphome/components/mcp3008/mcp3008.h +++ b/esphome/components/mcp3008/mcp3008.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/spi/spi.h" #include "esphome/components/voltage_sampler/voltage_sampler.h" diff --git a/esphome/components/mcp4725/mcp4725.cpp b/esphome/components/mcp4725/mcp4725.cpp index a8b130208e..2cb19282b6 100644 --- a/esphome/components/mcp4725/mcp4725.cpp +++ b/esphome/components/mcp4725/mcp4725.cpp @@ -8,13 +8,10 @@ static const char *const TAG = "mcp4725"; void MCP4725::setup() { ESP_LOGCONFIG(TAG, "Setting up MCP4725 (0x%02X)...", this->address_); - - this->raw_begin_transmission(); - - if (!this->raw_end_transmission()) { + auto err = this->write(nullptr, 0); + if (err != i2c::ERROR_OK) { this->error_code_ = COMMUNICATION_FAILED; this->mark_failed(); - return; } } diff --git a/esphome/components/mcp9808/mcp9808.cpp b/esphome/components/mcp9808/mcp9808.cpp index 8f60df1d88..fca1331fc3 100644 --- a/esphome/components/mcp9808/mcp9808.cpp +++ b/esphome/components/mcp9808/mcp9808.cpp @@ -20,14 +20,14 @@ static const char *const TAG = "mcp9808"; void MCP9808Sensor::setup() { ESP_LOGCONFIG(TAG, "Setting up %s...", this->name_.c_str()); - uint16_t manu; - if (!this->read_byte_16(MCP9808_REG_MANUF_ID, &manu, 0) || manu != MCP9808_MANUF_ID) { + uint16_t manu = 0; + if (!this->read_byte_16(MCP9808_REG_MANUF_ID, &manu) || manu != MCP9808_MANUF_ID) { this->mark_failed(); ESP_LOGE(TAG, "%s manufacuturer id failed, device returned %X", this->name_.c_str(), manu); return; } - uint16_t dev_id; - if (!this->read_byte_16(MCP9808_REG_DEVICE_ID, &dev_id, 0) || dev_id != MCP9808_DEV_ID) { + uint16_t dev_id = 0; + if (!this->read_byte_16(MCP9808_REG_DEVICE_ID, &dev_id) || dev_id != MCP9808_DEV_ID) { this->mark_failed(); ESP_LOGE(TAG, "%s device id failed, device returned %X", this->name_.c_str(), dev_id); return; @@ -66,7 +66,7 @@ void MCP9808Sensor::update() { temp = (uint16_t)(msb) *16 + lsb / 16.0f; } - if (isnan(temp)) { + if (std::isnan(temp)) { this->status_set_warning(); return; } diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py new file mode 100644 index 0000000000..ec568ae2d5 --- /dev/null +++ b/esphome/components/mdns/__init__.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.core import CORE + +CODEOWNERS = ["@esphome/core"] +DEPENDENCIES = ["network"] + +CONF_DISABLED = "disabled" +CONFIG_SCHEMA = cv.Schema( + { + cv.Optional(CONF_DISABLED, default=False): cv.boolean, + } +) + + +async def to_code(config): + if config[CONF_DISABLED]: + return + + cg.add_define("USE_MDNS") + if CORE.using_arduino: + if CORE.is_esp32: + cg.add_library("ESPmDNS", None) + elif CORE.is_esp8266: + cg.add_library("ESP8266mDNS", None) diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp new file mode 100644 index 0000000000..c742fe3948 --- /dev/null +++ b/esphome/components/mdns/mdns_component.cpp @@ -0,0 +1,74 @@ +#include "mdns_component.h" +#include "esphome/core/defines.h" +#include "esphome/core/version.h" +#include "esphome/core/application.h" + +#ifdef USE_API +#include "esphome/components/api/api_server.h" +#endif + +namespace esphome { +namespace mdns { + +#ifndef WEBSERVER_PORT +#define WEBSERVER_PORT 80 // NOLINT +#endif + +std::vector MDNSComponent::compile_services_() { + std::vector res; + +#ifdef USE_API + if (api::global_api_server != nullptr) { + MDNSService service{}; + service.service_type = "esphomelib"; + service.proto = "_tcp"; + service.port = api::global_api_server->get_port(); + service.txt_records.push_back({"version", ESPHOME_VERSION}); + service.txt_records.push_back({"mac", get_mac_address()}); + const char *platform = nullptr; +#ifdef USE_ESP8266 + platform = "ESP8266"; +#endif +#ifdef USE_ESP32 + platform = "ESP32"; +#endif + if (platform != nullptr) { + service.txt_records.push_back({"platform", platform}); + } + + service.txt_records.push_back({"board", ESPHOME_BOARD}); + +#ifdef ESPHOME_PROJECT_NAME + service.txt_records.push_back({"project_name", ESPHOME_PROJECT_NAME}); + service.txt_records.push_back({"project_version", ESPHOME_PROJECT_VERSION}); +#endif // ESPHOME_PROJECT_NAME + res.push_back(service); + } +#endif // USE_API + +#ifdef USE_PROMETHEUS + { + MDNSService service{}; + service.service_type = "prometheus-http"; + service.proto = "_tcp"; + service.port = WEBSERVER_PORT; + res.push_back(service); + } +#endif + + if (res.empty()) { + // Publish "http" service if not using native API + // This is just to have *some* mDNS service so that .local resolution works + MDNSService service{}; + service.service_type = "http"; + service.proto = "_tcp"; + service.port = WEBSERVER_PORT; + service.txt_records.push_back({"version", ESPHOME_VERSION}); + res.push_back(service); + } + return res; +} +std::string MDNSComponent::compile_hostname_() { return App.get_name(); } + +} // namespace mdns +} // namespace esphome diff --git a/esphome/components/mdns/mdns_component.h b/esphome/components/mdns/mdns_component.h new file mode 100644 index 0000000000..985947d99c --- /dev/null +++ b/esphome/components/mdns/mdns_component.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include "esphome/core/component.h" + +namespace esphome { +namespace mdns { + +struct MDNSTXTRecord { + std::string key; + std::string value; +}; + +struct MDNSService { + std::string service_type; + std::string proto; + uint16_t port; + std::vector txt_records; +}; + +class MDNSComponent : public Component { + public: + void setup() override; + +#if defined(USE_ESP8266) && defined(USE_ARDUINO) + void loop() override; +#endif + float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + + protected: + std::vector compile_services_(); + std::string compile_hostname_(); +}; + +} // namespace mdns +} // namespace esphome diff --git a/esphome/components/mdns/mdns_esp32_arduino.cpp b/esphome/components/mdns/mdns_esp32_arduino.cpp new file mode 100644 index 0000000000..4d13b7321a --- /dev/null +++ b/esphome/components/mdns/mdns_esp32_arduino.cpp @@ -0,0 +1,27 @@ +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +#include "mdns_component.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace mdns { + +static const char *const TAG = "mdns"; + +void MDNSComponent::setup() { + MDNS.begin(compile_hostname_().c_str()); + + auto services = compile_services_(); + for (const auto &service : services) { + MDNS.addService(service.service_type.c_str(), service.proto.c_str(), service.port); + for (const auto &record : service.txt_records) { + MDNS.addServiceTxt(service.service_type.c_str(), service.proto.c_str(), record.key.c_str(), record.value.c_str()); + } + } +} + +} // namespace mdns +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp new file mode 100644 index 0000000000..48f31f1bbf --- /dev/null +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -0,0 +1,32 @@ +#if defined(USE_ESP8266) && defined(USE_ARDUINO) + +#include "mdns_component.h" +#include "esphome/core/log.h" +#include "esphome/components/network/ip_address.h" +#include "esphome/components/network/util.h" +#include + +namespace esphome { +namespace mdns { + +static const char *const TAG = "mdns"; + +void MDNSComponent::setup() { + network::IPAddress addr = network::get_ip_address(); + MDNS.begin(compile_hostname_().c_str(), (uint32_t) addr); + + auto services = compile_services_(); + for (const auto &service : services) { + MDNS.addService(service.service_type.c_str(), service.proto.c_str(), service.port); + for (const auto &record : service.txt_records) { + MDNS.addServiceTxt(service.service_type.c_str(), service.proto.c_str(), record.key.c_str(), record.value.c_str()); + } + } +} + +void MDNSComponent::loop() { MDNS.update(); } + +} // namespace mdns +} // namespace esphome + +#endif diff --git a/esphome/components/mdns/mdns_esp_idf.cpp b/esphome/components/mdns/mdns_esp_idf.cpp new file mode 100644 index 0000000000..17874f1ffe --- /dev/null +++ b/esphome/components/mdns/mdns_esp_idf.cpp @@ -0,0 +1,52 @@ +#ifdef USE_ESP_IDF + +#include "mdns_component.h" +#include "esphome/core/log.h" +#include +#include + +namespace esphome { +namespace mdns { + +static const char *const TAG = "mdns"; + +void MDNSComponent::setup() { + esp_err_t err = mdns_init(); + if (err != ESP_OK) { + ESP_LOGW(TAG, "MDNS init failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + + mdns_hostname_set(compile_hostname_().c_str()); + mdns_instance_name_set(compile_hostname_().c_str()); + + auto services = compile_services_(); + for (const auto &service : services) { + std::vector txt_records; + for (const auto &record : service.txt_records) { + mdns_txt_item_t it{}; + // dup strings to ensure the pointer is valid even after the record loop + it.key = strdup(record.key.c_str()); + it.value = strdup(record.value.c_str()); + txt_records.push_back(it); + } + err = mdns_service_add(nullptr, service.service_type.c_str(), service.proto.c_str(), service.port, + txt_records.data(), txt_records.size()); + + // free records + for (const auto &it : txt_records) { + delete it.key; // NOLINT(cppcoreguidelines-owning-memory) + delete it.value; // NOLINT(cppcoreguidelines-owning-memory) + } + + if (err != ESP_OK) { + ESP_LOGW(TAG, "Failed to register mDNS service %s: %s", service.service_type.c_str(), esp_err_to_name(err)); + } + } +} + +} // namespace mdns +} // namespace esphome + +#endif diff --git a/esphome/components/midea/adapter.cpp b/esphome/components/midea/adapter.cpp index bd5b289095..a3f19dbda8 100644 --- a/esphome/components/midea/adapter.cpp +++ b/esphome/components/midea/adapter.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "esphome/core/log.h" #include "adapter.h" @@ -171,3 +173,5 @@ void Converters::to_climate_traits(ClimateTraits &traits, const dudanov::midea:: } // namespace midea } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/midea/adapter.h b/esphome/components/midea/adapter.h index 8d8d57e8f9..2497cbbe5b 100644 --- a/esphome/components/midea/adapter.h +++ b/esphome/components/midea/adapter.h @@ -1,4 +1,7 @@ #pragma once + +#ifdef USE_ARDUINO + #include #include "esphome/components/climate/climate_traits.h" #include "appliance_base.h" @@ -40,3 +43,5 @@ class Converters { } // namespace midea } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/midea/air_conditioner.cpp b/esphome/components/midea/air_conditioner.cpp index a71f1dbdfb..103b852936 100644 --- a/esphome/components/midea/air_conditioner.cpp +++ b/esphome/components/midea/air_conditioner.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "esphome/core/log.h" #include "air_conditioner.h" #include "adapter.h" @@ -150,3 +152,5 @@ void AirConditioner::do_display_toggle() { } // namespace midea } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/midea/air_conditioner.h b/esphome/components/midea/air_conditioner.h index 895b6412f3..8dfb9dcb3d 100644 --- a/esphome/components/midea/air_conditioner.h +++ b/esphome/components/midea/air_conditioner.h @@ -1,4 +1,7 @@ #pragma once + +#ifdef USE_ARDUINO + #include #include "appliance_base.h" #include "esphome/components/sensor/sensor.h" @@ -39,3 +42,5 @@ class AirConditioner : public ApplianceBase } // namespace midea } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/midea/appliance_base.h b/esphome/components/midea/appliance_base.h index aa616ced36..43dd2ffb32 100644 --- a/esphome/components/midea/appliance_base.h +++ b/esphome/components/midea/appliance_base.h @@ -1,4 +1,7 @@ #pragma once + +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include "esphome/core/log.h" #include "esphome/components/uart/uart.h" @@ -19,7 +22,8 @@ using climate::ClimateMode; using climate::ClimateSwingMode; using climate::ClimateFanMode; -template class ApplianceBase : public Component, public uart::UARTDevice, public climate::Climate { +template +class ApplianceBase : public Component, public uart::UARTDevice, public climate::Climate, public Stream { static_assert(std::is_base_of::value, "T must derive from dudanov::midea::ApplianceBase class"); @@ -60,6 +64,12 @@ template class ApplianceBase : public Component, public uart::UARTDe } #endif + int available() override { return uart::UARTDevice::available(); } + int read() override { return uart::UARTDevice::read(); } + int peek() override { return uart::UARTDevice::peek(); } + void flush() override { uart::UARTDevice::flush(); } + size_t write(uint8_t data) override { return uart::UARTDevice::write(data); } + protected: T base_; std::set supported_modes_{}; @@ -74,3 +84,5 @@ template class ApplianceBase : public Component, public uart::UARTDe } // namespace midea } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/midea/automations.h b/esphome/components/midea/automations.h index 1f026c0c15..5b638286ac 100644 --- a/esphome/components/midea/automations.h +++ b/esphome/components/midea/automations.h @@ -1,4 +1,7 @@ #pragma once + +#ifdef USE_ARDUINO + #include "esphome/core/automation.h" #include "air_conditioner.h" @@ -54,3 +57,5 @@ template class PowerOffAction : public MideaActionBase { } // namespace midea } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/midea/climate.py b/esphome/components/midea/climate.py index 137fcdd607..0d0bdce471 100644 --- a/esphome/components/midea/climate.py +++ b/esphome/components/midea/climate.py @@ -151,7 +151,8 @@ CONFIG_SCHEMA = cv.All( } ) .extend(uart.UART_DEVICE_SCHEMA) - .extend(cv.COMPONENT_SCHEMA) + .extend(cv.COMPONENT_SCHEMA), + cv.only_with_arduino, ) # Actions diff --git a/esphome/components/midea/midea_ir.h b/esphome/components/midea/midea_ir.h index 2459d844a1..abd4324bcc 100644 --- a/esphome/components/midea/midea_ir.h +++ b/esphome/components/midea/midea_ir.h @@ -1,4 +1,6 @@ #pragma once + +#ifdef USE_ARDUINO #ifdef USE_REMOTE_TRANSMITTER #include "esphome/components/remote_base/midea_protocol.h" @@ -40,3 +42,4 @@ class IrSpecialData : public IrData { } // namespace esphome #endif +#endif // USE_ARDUINO diff --git a/esphome/components/mpr121/mpr121.cpp b/esphome/components/mpr121/mpr121.cpp index 274ed6dfec..7ba3da7b4d 100644 --- a/esphome/components/mpr121/mpr121.cpp +++ b/esphome/components/mpr121/mpr121.cpp @@ -1,5 +1,6 @@ #include "mpr121.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace mpr121 { diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 73ee50ee65..8f02f8d437 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -192,6 +192,7 @@ CONFIG_SCHEMA = cv.All( } ), validate_config, + cv.only_with_arduino, ) diff --git a/esphome/components/mqtt/custom_mqtt_device.cpp b/esphome/components/mqtt/custom_mqtt_device.cpp index 9dec9498ad..787cc1153f 100644 --- a/esphome/components/mqtt/custom_mqtt_device.cpp +++ b/esphome/components/mqtt/custom_mqtt_device.cpp @@ -1,4 +1,7 @@ #include "custom_mqtt_device.h" + +#ifdef USE_MQTT + #include "esphome/core/log.h" namespace esphome { @@ -28,3 +31,5 @@ bool CustomMQTTDevice::is_connected() { return global_mqtt_client != nullptr && } // namespace mqtt } // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt/custom_mqtt_device.h b/esphome/components/mqtt/custom_mqtt_device.h index 1c8b2e916e..9795d69304 100644 --- a/esphome/components/mqtt/custom_mqtt_device.h +++ b/esphome/components/mqtt/custom_mqtt_device.h @@ -1,5 +1,8 @@ #pragma once +#include "esphome/core/defines.h" +#ifdef USE_MQTT + #include "esphome/core/component.h" #include "mqtt_client.h" @@ -215,3 +218,5 @@ void CustomMQTTDevice::subscribe_json(const std::string &topic, void (T::*callba } // namespace mqtt } // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index 53a49c6844..d7322298bb 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -1,6 +1,7 @@ #include "mqtt_binary_sensor.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_BINARY_SENSOR namespace esphome { @@ -55,3 +56,4 @@ bool MQTTBinarySensorComponent::publish_state(bool state) { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_binary_sensor.h b/esphome/components/mqtt/mqtt_binary_sensor.h index 1ca82a947e..c459bea1f7 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.h +++ b/esphome/components/mqtt/mqtt_binary_sensor.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/defines.h" - +#ifdef USE_MQTT #ifdef USE_BINARY_SENSOR #include "mqtt_component.h" @@ -41,3 +41,4 @@ class MQTTBinarySensorComponent : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 237a8146e6..040b0001fe 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -1,9 +1,11 @@ #include "mqtt_client.h" +#ifdef USE_MQTT + #include "esphome/core/application.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/util.h" +#include "esphome/components/network/util.h" #include #ifdef USE_LOGGER #include "esphome/components/logger/logger.h" @@ -60,7 +62,7 @@ void MQTTClientComponent::setup() { void MQTTClientComponent::dump_config() { ESP_LOGCONFIG(TAG, "MQTT:"); ESP_LOGCONFIG(TAG, " Server Address: %s:%u (%s)", this->credentials_.address.c_str(), this->credentials_.port, - this->ip_.toString().c_str()); + this->ip_.str().c_str()); ESP_LOGCONFIG(TAG, " Username: " LOG_SECRET("'%s'"), this->credentials_.username.c_str()); ESP_LOGCONFIG(TAG, " Client ID: " LOG_SECRET("'%s'"), this->credentials_.client_id.c_str()); if (!this->discovery_info_.prefix.empty()) { @@ -87,11 +89,11 @@ void MQTTClientComponent::start_dnslookup_() { this->dns_resolve_error_ = false; this->dns_resolved_ = false; ip_addr_t addr; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 err_t err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr, MQTTClientComponent::dns_found_callback, this, LWIP_DNS_ADDRTYPE_IPV4); #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 err_t err = dns_gethostbyname(this->credentials_.address.c_str(), &addr, esphome::mqtt::MQTTClientComponent::dns_found_callback, this); #endif @@ -99,11 +101,11 @@ void MQTTClientComponent::start_dnslookup_() { case ERR_OK: { // Got IP immediately this->dns_resolved_ = true; -#ifdef ARDUINO_ARCH_ESP32 - this->ip_ = IPAddress(addr.u_addr.ip4.addr); +#ifdef USE_ESP32 + this->ip_ = addr.u_addr.ip4.addr; #endif -#ifdef ARDUINO_ARCH_ESP8266 - this->ip_ = IPAddress(addr.addr); +#ifdef USE_ESP8266 + this->ip_ = addr.addr; #endif this->start_connect_(); return; @@ -116,7 +118,7 @@ void MQTTClientComponent::start_dnslookup_() { default: case ERR_ARG: { // error -#if defined(ARDUINO_ARCH_ESP8266) +#if defined(USE_ESP8266) ESP_LOGW(TAG, "Error resolving MQTT broker IP address: %ld", err); #else ESP_LOGW(TAG, "Error resolving MQTT broker IP address: %d", err); @@ -143,10 +145,10 @@ void MQTTClientComponent::check_dnslookup_() { return; } - ESP_LOGD(TAG, "Resolved broker IP address to %s", this->ip_.toString().c_str()); + ESP_LOGD(TAG, "Resolved broker IP address to %s", this->ip_.str().c_str()); this->start_connect_(); } -#if defined(ARDUINO_ARCH_ESP8266) && LWIP_VERSION_MAJOR == 1 +#if defined(USE_ESP8266) && LWIP_VERSION_MAJOR == 1 void MQTTClientComponent::dns_found_callback(const char *name, ip_addr_t *ipaddr, void *callback_arg) { #else void MQTTClientComponent::dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg) { @@ -155,18 +157,18 @@ void MQTTClientComponent::dns_found_callback(const char *name, const ip_addr_t * if (ipaddr == nullptr) { a_this->dns_resolve_error_ = true; } else { -#ifdef ARDUINO_ARCH_ESP32 - a_this->ip_ = IPAddress(ipaddr->u_addr.ip4.addr); +#ifdef USE_ESP32 + a_this->ip_ = ipaddr->u_addr.ip4.addr; #endif -#ifdef ARDUINO_ARCH_ESP8266 - a_this->ip_ = IPAddress(ipaddr->addr); +#ifdef USE_ESP8266 + a_this->ip_ = ipaddr->addr; #endif a_this->dns_resolved_ = true; } } void MQTTClientComponent::start_connect_() { - if (!network_is_connected()) + if (!network::is_connected()) return; ESP_LOGI(TAG, "Connecting to MQTT..."); @@ -183,7 +185,7 @@ void MQTTClientComponent::start_connect_() { this->mqtt_client_.setCredentials(username, password); - this->mqtt_client_.setServer(this->ip_, this->credentials_.port); + this->mqtt_client_.setServer((uint32_t) this->ip_, this->credentials_.port); if (!this->last_will_.topic.empty()) { this->mqtt_client_.setWill(this->last_will_.topic.c_str(), this->last_will_.qos, this->last_will_.retain, this->last_will_.payload.c_str(), this->last_will_.payload.length()); @@ -251,7 +253,7 @@ void MQTTClientComponent::loop() { reason_s = LOG_STR("Unknown"); break; } - if (!network_is_connected()) { + if (!network::is_connected()) { reason_s = LOG_STR("WiFi disconnected"); } ESP_LOGW(TAG, "MQTT Disconnected: %s.", LOG_STR_ARG(reason_s)); @@ -477,7 +479,7 @@ static bool topic_match(const char *message, const char *subscription) { } void MQTTClientComponent::on_message(const std::string &topic, const std::string &payload) { -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 // on ESP8266, this is called in LWiP thread; some components do not like running // in an ISR. this->defer([this, topic, payload]() { @@ -485,7 +487,7 @@ void MQTTClientComponent::on_message(const std::string &topic, const std::string for (auto &subscription : this->subscriptions_) if (topic_match(topic.c_str(), subscription.topic.c_str())) subscription.callback(topic, payload); -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 }); #endif } @@ -587,3 +589,5 @@ float MQTTMessageTrigger::get_setup_priority() const { return setup_priority::AF } // namespace mqtt } // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 119e61db2b..fa689eaa04 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -1,10 +1,14 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/core/defines.h" + +#ifdef USE_MQTT + +#include "esphome/core/component.h" #include "esphome/core/automation.h" #include "esphome/core/log.h" #include "esphome/components/json/json_util.h" +#include "esphome/components/network/ip_address.h" #include #include "lwip/ip_addr.h" @@ -226,7 +230,7 @@ class MQTTClientComponent : public Component { void start_connect_(); void start_dnslookup_(); void check_dnslookup_(); -#if defined(ARDUINO_ARCH_ESP8266) && LWIP_VERSION_MAJOR == 1 +#if defined(USE_ESP8266) && LWIP_VERSION_MAJOR == 1 static void dns_found_callback(const char *name, ip_addr_t *ipaddr, void *callback_arg); #else static void dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg); @@ -265,7 +269,7 @@ class MQTTClientComponent : public Component { std::vector subscriptions_; AsyncMqttClient mqtt_client_; MQTTClientState state_{MQTT_CLIENT_DISCONNECTED}; - IPAddress ip_; + network::IPAddress ip_; bool dns_resolved_{false}; bool dns_resolve_error_{false}; std::vector children_; @@ -352,3 +356,5 @@ template class MQTTConnectedCondition : public Condition } // namespace mqtt } // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index be9dbb0a08..8519e296b0 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -1,6 +1,7 @@ #include "mqtt_climate.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_CLIMATE namespace esphome { @@ -245,7 +246,7 @@ bool MQTTClimateComponent::publish_state_() { if (!this->publish(this->get_mode_state_topic(), mode_s)) success = false; int8_t accuracy = traits.get_temperature_accuracy_decimals(); - if (traits.get_supports_current_temperature() && !isnan(this->device_->current_temperature)) { + if (traits.get_supports_current_temperature() && !std::isnan(this->device_->current_temperature)) { std::string payload = value_accuracy_to_string(this->device_->current_temperature, accuracy); if (!this->publish(this->get_current_temperature_state_topic(), payload)) success = false; @@ -359,3 +360,4 @@ bool MQTTClimateComponent::publish_state_() { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_climate.h b/esphome/components/mqtt/mqtt_climate.h index 8aea4feb26..8b8a8e866e 100644 --- a/esphome/components/mqtt/mqtt_climate.h +++ b/esphome/components/mqtt/mqtt_climate.h @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" +#ifdef USE_MQTT #ifdef USE_CLIMATE #include "esphome/components/climate/climate.h" @@ -48,3 +49,4 @@ class MQTTClimateComponent : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 3ed9aafb42..96cda57914 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -1,4 +1,7 @@ #include "mqtt_component.h" + +#ifdef USE_MQTT + #include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/core/helpers.h" @@ -198,3 +201,5 @@ bool MQTTComponent::is_connected_() const { return global_mqtt_client->is_connec } // namespace mqtt } // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 668162da5a..f07e752e02 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -1,5 +1,9 @@ #pragma once +#include "esphome/core/defines.h" + +#ifdef USE_MQTT + #include #include "esphome/core/component.h" @@ -181,3 +185,5 @@ class MQTTComponent : public Component { } // namespace mqtt } // namespace esphome + +#endif // USE_MQTt diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index b11ae1fb93..6a0d2d1bc5 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -1,6 +1,7 @@ #include "mqtt_cover.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_COVER namespace esphome { @@ -115,3 +116,4 @@ bool MQTTCoverComponent::publish_state() { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_cover.h b/esphome/components/mqtt/mqtt_cover.h index 5c2ce93987..b8c9f2617c 100644 --- a/esphome/components/mqtt/mqtt_cover.h +++ b/esphome/components/mqtt/mqtt_cover.h @@ -3,6 +3,7 @@ #include "esphome/core/defines.h" #include "mqtt_component.h" +#ifdef USE_MQTT #ifdef USE_COVER #include "esphome/components/cover/cover.h" @@ -40,3 +41,4 @@ class MQTTCoverComponent : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index b8eecf0ff3..c8db5ecece 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -1,6 +1,7 @@ #include "mqtt_fan.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_FAN #include "esphome/components/fan/fan_helpers.h" @@ -127,3 +128,4 @@ bool MQTTFanComponent::publish_state() { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_fan.h b/esphome/components/mqtt/mqtt_fan.h index 5495780a27..99d9c055cf 100644 --- a/esphome/components/mqtt/mqtt_fan.h +++ b/esphome/components/mqtt/mqtt_fan.h @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" +#ifdef USE_MQTT #ifdef USE_FAN #include "esphome/components/fan/fan_state.h" @@ -45,3 +46,4 @@ class MQTTFanComponent : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index f53be9c010..d028b1b037 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -1,6 +1,7 @@ #include "mqtt_light.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_LIGHT #include "esphome/components/light/light_json_schema.h" @@ -79,3 +80,4 @@ void MQTTJSONLightComponent::dump_config() { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_light.h b/esphome/components/mqtt/mqtt_light.h index 9417e71ddd..b0f3145900 100644 --- a/esphome/components/mqtt/mqtt_light.h +++ b/esphome/components/mqtt/mqtt_light.h @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" +#ifdef USE_MQTT #ifdef USE_LIGHT #include "mqtt_component.h" @@ -39,3 +40,4 @@ class MQTTJSONLightComponent : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index f209f4fe20..faa38056a9 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -1,6 +1,7 @@ #include "mqtt_number.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_NUMBER namespace esphome { @@ -63,3 +64,4 @@ bool MQTTNumberComponent::publish_state(float value) { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_number.h b/esphome/components/mqtt/mqtt_number.h index f44de91435..46dc221e9e 100644 --- a/esphome/components/mqtt/mqtt_number.h +++ b/esphome/components/mqtt/mqtt_number.h @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" +#ifdef USE_MQTT #ifdef USE_NUMBER #include "esphome/components/number/number.h" @@ -44,3 +45,4 @@ class MQTTNumberComponent : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index c0ac472d46..467a0cd84c 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -1,6 +1,7 @@ #include "mqtt_select.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_SELECT namespace esphome { @@ -56,3 +57,4 @@ bool MQTTSelectComponent::publish_state(const std::string &value) { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_select.h b/esphome/components/mqtt/mqtt_select.h index 013e905ead..0115c0a41e 100644 --- a/esphome/components/mqtt/mqtt_select.h +++ b/esphome/components/mqtt/mqtt_select.h @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" +#ifdef USE_MQTT #ifdef USE_SELECT #include "esphome/components/select/select.h" @@ -44,3 +45,4 @@ class MQTTSelectComponent : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index e921056167..72ec7c54ee 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -1,6 +1,7 @@ #include "mqtt_sensor.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_SENSOR #ifdef USE_DEEP_SLEEP @@ -77,3 +78,4 @@ std::string MQTTSensorComponent::unique_id() { return this->sensor_->unique_id() } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_sensor.h b/esphome/components/mqtt/mqtt_sensor.h index 8d8fa83531..2385529f4f 100644 --- a/esphome/components/mqtt/mqtt_sensor.h +++ b/esphome/components/mqtt/mqtt_sensor.h @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" +#ifdef USE_MQTT #ifdef USE_SENSOR #include "esphome/components/sensor/sensor.h" @@ -58,3 +59,4 @@ class MQTTSensorComponent : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index b73e1ab8dc..b13ddd5d9d 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -1,6 +1,7 @@ #include "mqtt_switch.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_SWITCH namespace esphome { @@ -58,3 +59,4 @@ bool MQTTSwitchComponent::publish_state(bool state) { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_switch.h b/esphome/components/mqtt/mqtt_switch.h index 33b829c856..9959d21872 100644 --- a/esphome/components/mqtt/mqtt_switch.h +++ b/esphome/components/mqtt/mqtt_switch.h @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" +#ifdef USE_MQTT #ifdef USE_SWITCH #include "esphome/components/switch/switch.h" @@ -39,3 +40,4 @@ class MQTTSwitchComponent : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index 8bc11d954c..fd96cd0902 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -1,6 +1,7 @@ #include "mqtt_text_sensor.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_TEXT_SENSOR namespace esphome { @@ -43,3 +44,4 @@ std::string MQTTTextSensor::unique_id() { return this->sensor_->unique_id(); } } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text_sensor.h b/esphome/components/mqtt/mqtt_text_sensor.h index a5ce0658c7..1d3f95b894 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.h +++ b/esphome/components/mqtt/mqtt_text_sensor.h @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" +#ifdef USE_MQTT #ifdef USE_TEXT_SENSOR #include "esphome/components/text_sensor/text_sensor.h" @@ -40,3 +41,4 @@ class MQTTTextSensor : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp b/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp index 06251bd7c8..e1accf3c70 100644 --- a/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp +++ b/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp @@ -1,4 +1,7 @@ #include "mqtt_subscribe_sensor.h" + +#ifdef USE_MQTT + #include "esphome/core/log.h" namespace esphome { @@ -31,3 +34,5 @@ void MQTTSubscribeSensor::dump_config() { } // namespace mqtt_subscribe } // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.h b/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.h index a303ccad89..0619326ac9 100644 --- a/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.h +++ b/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.h @@ -1,5 +1,9 @@ #pragma once +#include "esphome/core/defines.h" + +#ifdef USE_MQTT + #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/mqtt/mqtt_client.h" @@ -25,3 +29,5 @@ class MQTTSubscribeSensor : public sensor::Sensor, public Component { } // namespace mqtt_subscribe } // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.cpp b/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.cpp index 2b0908979c..8aa094a2d4 100644 --- a/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.cpp +++ b/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.cpp @@ -1,5 +1,7 @@ #include "mqtt_subscribe_text_sensor.h" +#ifdef USE_MQTT + #include "esphome/core/log.h" #include @@ -22,3 +24,5 @@ void MQTTSubscribeTextSensor::dump_config() { } // namespace mqtt_subscribe } // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.h b/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.h index 69409f6348..9f8e5c63cc 100644 --- a/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.h +++ b/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.h @@ -1,5 +1,9 @@ #pragma once +#include "esphome/core/defines.h" + +#ifdef USE_MQTT + #include "esphome/core/component.h" #include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/mqtt/mqtt_client.h" @@ -24,3 +28,5 @@ class MQTTSubscribeTextSensor : public text_sensor::TextSensor, public Component } // namespace mqtt_subscribe } // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/ms5611/ms5611.cpp b/esphome/components/ms5611/ms5611.cpp index 51dc569240..1d7516dbe8 100644 --- a/esphome/components/ms5611/ms5611.cpp +++ b/esphome/components/ms5611/ms5611.cpp @@ -1,5 +1,6 @@ #include "ms5611.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace ms5611 { diff --git a/esphome/components/my9231/my9231.h b/esphome/components/my9231/my9231.h index ee15f9743c..a777dcc960 100644 --- a/esphome/components/my9231/my9231.h +++ b/esphome/components/my9231/my9231.h @@ -1,8 +1,9 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/output/float_output.h" +#include namespace esphome { namespace my9231 { diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index a86b4e4588..0117f1b063 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -168,14 +168,15 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_VARIANT, default="800KBPS"): validate_variant, cv.Optional(CONF_METHOD, default=None): validate_method, cv.Optional(CONF_INVERT, default="no"): cv.boolean, - cv.Optional(CONF_PIN): pins.output_pin, - cv.Optional(CONF_CLOCK_PIN): pins.output_pin, - cv.Optional(CONF_DATA_PIN): pins.output_pin, + cv.Optional(CONF_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_CLOCK_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_DATA_PIN): pins.internal_gpio_output_pin_number, cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, } ).extend(cv.COMPONENT_SCHEMA), _validate, validate_method_pin, + cv.only_with_arduino, ) diff --git a/esphome/components/neopixelbus/neopixelbus_light.h b/esphome/components/neopixelbus/neopixelbus_light.h index 5359bac61b..34e10f2cfe 100644 --- a/esphome/components/neopixelbus/neopixelbus_light.h +++ b/esphome/components/neopixelbus/neopixelbus_light.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/macros.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" @@ -7,7 +9,7 @@ #include "esphome/components/light/light_output.h" #include "esphome/components/light/addressable_light.h" -#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) +#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) #error The NeoPixelBus library requires at least arduino_version 2.4.x #endif @@ -144,3 +146,5 @@ class NeoPixelRGBWLightOutput : public NeoPixelBusLightOutputBase +#include +#include +#include + +namespace esphome { +namespace network { + +struct IPAddress { + public: + IPAddress() : addr_({0, 0, 0, 0}) {} + IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) : addr_({first, second, third, fourth}) {} + IPAddress(uint32_t raw) { + addr_[0] = (uint8_t)(raw >> 0); + addr_[1] = (uint8_t)(raw >> 8); + addr_[2] = (uint8_t)(raw >> 16); + addr_[3] = (uint8_t)(raw >> 24); + } + operator uint32_t() const { + uint32_t res = 0; + res |= ((uint32_t) addr_[0]) << 0; + res |= ((uint32_t) addr_[1]) << 8; + res |= ((uint32_t) addr_[2]) << 16; + res |= ((uint32_t) addr_[3]) << 24; + return res; + } + std::string str() const { + char buffer[24]; + snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", addr_[0], addr_[1], addr_[2], addr_[3]); + return buffer; + } + bool operator==(const IPAddress &other) const { + return addr_[0] == other.addr_[0] && addr_[1] == other.addr_[1] && addr_[2] == other.addr_[2] && + addr_[3] == other.addr_[3]; + } + uint8_t operator[](int index) const { return addr_[index]; } + uint8_t &operator[](int index) { return addr_[index]; } + + protected: + std::array addr_; +}; + +} // namespace network +} // namespace esphome diff --git a/esphome/components/network/util.cpp b/esphome/components/network/util.cpp new file mode 100644 index 0000000000..f7ac6b543e --- /dev/null +++ b/esphome/components/network/util.cpp @@ -0,0 +1,54 @@ +#include "util.h" +#include "esphome/core/defines.h" + +#ifdef USE_WIFI +#include "esphome/components/wifi/wifi_component.h" +#endif + +#ifdef USE_ETHERNET +#include "esphome/components/ethernet/ethernet_component.h" +#endif + +namespace esphome { +namespace network { + +bool is_connected() { +#ifdef USE_ETHERNET + if (ethernet::global_eth_component != nullptr && ethernet::global_eth_component->is_connected()) + return true; +#endif + +#ifdef USE_WIFI + if (wifi::global_wifi_component != nullptr) + return wifi::global_wifi_component->is_connected(); +#endif + + return false; +} + +network::IPAddress get_ip_address() { +#ifdef USE_ETHERNET + if (ethernet::global_eth_component != nullptr) + return ethernet::global_eth_component->get_ip_address(); +#endif +#ifdef USE_WIFI + if (wifi::global_wifi_component != nullptr) + return wifi::global_wifi_component->get_ip_address(); +#endif + return {}; +} + +std::string get_use_address() { +#ifdef USE_ETHERNET + if (ethernet::global_eth_component != nullptr) + return ethernet::global_eth_component->get_use_address(); +#endif +#ifdef USE_WIFI + if (wifi::global_wifi_component != nullptr) + return wifi::global_wifi_component->get_use_address(); +#endif + return ""; +} + +} // namespace network +} // namespace esphome diff --git a/esphome/components/network/util.h b/esphome/components/network/util.h new file mode 100644 index 0000000000..f248d5cbf4 --- /dev/null +++ b/esphome/components/network/util.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include "ip_address.h" + +namespace esphome { +namespace network { + +/// Return whether the node is connected to the network (through wifi, eth, ...) +bool is_connected(); +/// Get the active network hostname +std::string get_use_address(); +IPAddress get_ip_address(); + +} // namespace network +} // namespace esphome diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index e693b2f1ec..f4b35fd56f 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -33,7 +33,7 @@ CONFIG_SCHEMA = ( display.BASIC_DISPLAY_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(Nextion), - cv.Optional(CONF_TFT_URL): cv.string, + cv.Optional(CONF_TFT_URL): cv.All(cv.string, cv.only_with_arduino), cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, cv.Optional(CONF_ON_SETUP): automation.validate_automation( { @@ -74,7 +74,7 @@ async def to_code(config): cg.add(var.set_writer(lambda_)) if CONF_TFT_URL in config: - cg.add_define("USE_TFT_UPLOAD") + cg.add_define("USE_NEXTION_TFT_UPLOAD") cg.add(var.set_tft_url(config[CONF_TFT_URL])) if CONF_TOUCH_SLEEP_TIMEOUT in config: diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 45c4a629c4..1bee41f6cf 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -7,11 +7,11 @@ #include "nextion_component.h" #include "esphome/components/display/display_color_utils.h" -#if defined(USE_ETHERNET) || defined(USE_WIFI) -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_NEXTION_TFT_UPLOAD +#ifdef USE_ESP32 #include #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include #include #endif @@ -652,7 +652,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe */ bool send_command_printf(const char *format, ...) __attribute__((format(printf, 2, 3))); -#ifdef USE_TFT_UPLOAD +#ifdef USE_NEXTION_TFT_UPLOAD /** * Set the tft file URL. https seems problamtic with arduino.. */ @@ -770,9 +770,8 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe const std::string &variable_name_to_send, const std::string &state_value, bool is_sleep_safe = false); -#ifdef USE_TFT_UPLOAD -#if defined(USE_ETHERNET) || defined(USE_WIFI) -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_NEXTION_TFT_UPLOAD +#ifdef USE_ESP8266 WiFiClient *wifi_client_{nullptr}; BearSSL::WiFiClientSecure *wifi_client_secure_{nullptr}; WiFiClient *get_wifi_client_(); @@ -801,9 +800,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe bool upload_from_buffer_(const uint8_t *file_buf, size_t buf_size); void upload_end_(); -#endif - -#endif +#endif // USE_NEXTION_TFT_UPLOAD bool get_is_connected_() { return this->is_connected_; } @@ -828,7 +825,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void remove_front_no_sensors_(); -#ifdef USE_TFT_UPLOAD +#ifdef USE_NEXTION_TFT_UPLOAD std::string tft_url_; uint8_t *transfer_buffer_{nullptr}; size_t transfer_buffer_size_; diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index f864a397cc..9a748277d8 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -1,16 +1,16 @@ +#ifdef USE_NEXTION_TFT_UPLOAD #include "nextion.h" #include "esphome/core/application.h" #include "esphome/core/macros.h" #include "esphome/core/util.h" #include "esphome/core/log.h" +#include "esphome/components/network/util.h" namespace esphome { namespace nextion { static const char *const TAG = "nextion_upload"; -#if defined(USE_TFT_UPLOAD) && (defined(USE_ETHERNET) || defined(USE_WIFI)) - // Followed guide // https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2 @@ -26,7 +26,7 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { if (range_end > this->tft_size_) range_end = this->tft_size_; -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); #elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) @@ -46,10 +46,10 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { int code = 0; bool begin_status = false; while (tries <= 5) { -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 begin_status = http->begin(this->tft_url_.c_str()); #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str()); #endif @@ -129,7 +129,7 @@ void Nextion::upload_tft() { return; } - if (!network_is_connected()) { + if (!network::is_connected()) { ESP_LOGD(TAG, "network is not connected"); return; } @@ -139,10 +139,10 @@ void Nextion::upload_tft() { HTTPClient http; http.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along bool begin_status = false; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 begin_status = http.begin(this->tft_url_.c_str()); #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); #elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) @@ -157,7 +157,7 @@ void Nextion::upload_tft() { if (!begin_status) { this->is_updating_ = false; ESP_LOGD(TAG, "connection failed"); -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 if (psramFound()) free(this->transfer_buffer_); // NOLINT else @@ -249,7 +249,7 @@ void Nextion::upload_tft() { } // Nextion wants 4096 bytes at a time. Make chunk_size a multiple of 4096 -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 uint32_t chunk_size = 8192; if (psramFound()) { chunk_size = this->content_length_; @@ -268,7 +268,7 @@ void Nextion::upload_tft() { #endif if (this->transfer_buffer_ == nullptr) { -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 if (psramFound()) { ESP_LOGD(TAG, "Allocating PSRAM buffer size %d, Free PSRAM size is %u", chunk_size, ESP.getFreePsram()); this->transfer_buffer_ = (uint8_t *) ps_malloc(chunk_size); @@ -289,7 +289,7 @@ void Nextion::upload_tft() { if (!this->transfer_buffer_) this->upload_end_(); -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 } #endif } @@ -325,7 +325,7 @@ void Nextion::upload_end_() { ESP.restart(); // NOLINT(readability-static-accessed-through-instance) } -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 WiFiClient *Nextion::get_wifi_client_() { if (this->tft_url_.compare(0, 6, "https:") == 0) { if (this->wifi_client_secure_ == nullptr) { @@ -342,9 +342,7 @@ WiFiClient *Nextion::get_wifi_client_() { return this->wifi_client_; } #endif - -#else -void Nextion::upload_tft() { ESP_LOGW(TAG, "tft_url, WIFI or Ethernet components are needed. Cannot upload."); } -#endif } // namespace nextion } // namespace esphome + +#endif // USE_NEXTION_TFT_UPLOAD diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp index 1e79164546..e983ebcc6f 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.cpp +++ b/esphome/components/nextion/sensor/nextion_sensor.cpp @@ -48,7 +48,7 @@ void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) { if (!this->nextion_->is_setup()) return; - if (isnan(state)) + if (std::isnan(state)) return; if (this->wave_chan_id_ == UINT8_MAX) { diff --git a/esphome/components/ntc/ntc.cpp b/esphome/components/ntc/ntc.cpp index 80a11384b9..333dbc5a75 100644 --- a/esphome/components/ntc/ntc.cpp +++ b/esphome/components/ntc/ntc.cpp @@ -14,7 +14,7 @@ void NTC::setup() { void NTC::dump_config() { LOG_SENSOR("", "NTC Sensor", this) } float NTC::get_setup_priority() const { return setup_priority::DATA; } void NTC::process_(float value) { - if (isnan(value)) { + if (std::isnan(value)) { this->publish_state(NAN); return; } diff --git a/esphome/components/number/automation.cpp b/esphome/components/number/automation.cpp index a0b169427f..c75d272660 100644 --- a/esphome/components/number/automation.cpp +++ b/esphome/components/number/automation.cpp @@ -7,7 +7,7 @@ namespace number { static const char *const TAG = "number.automation"; void ValueRangeTrigger::setup() { - this->rtc_ = global_preferences.make_preference(this->parent_->get_object_id_hash()); + this->rtc_ = global_preferences->make_preference(this->parent_->get_object_id_hash()); bool initial_state; if (this->rtc_.load(&initial_state)) { this->previous_in_range_ = initial_state; @@ -18,18 +18,18 @@ void ValueRangeTrigger::setup() { float ValueRangeTrigger::get_setup_priority() const { return setup_priority::HARDWARE; } void ValueRangeTrigger::on_state_(float state) { - if (isnan(state)) + if (std::isnan(state)) return; float local_min = this->min_.value(state); float local_max = this->max_.value(state); bool in_range; - if (isnan(local_min) && isnan(local_max)) { + if (std::isnan(local_min) && std::isnan(local_max)) { in_range = this->previous_in_range_; - } else if (isnan(local_min)) { + } else if (std::isnan(local_min)) { in_range = state <= local_max; - } else if (isnan(local_max)) { + } else if (std::isnan(local_max)) { in_range = state >= local_min; } else { in_range = local_min <= state && state <= local_max; diff --git a/esphome/components/number/automation.h b/esphome/components/number/automation.h index 9e812f8c49..98554a346a 100644 --- a/esphome/components/number/automation.h +++ b/esphome/components/number/automation.h @@ -57,9 +57,9 @@ template class NumberInRangeCondition : public Condition void set_max(float max) { this->max_ = max; } bool check(Ts... x) override { const float state = this->parent_->state; - if (isnan(this->min_)) { + if (std::isnan(this->min_)) { return state <= this->max_; - } else if (isnan(this->max_)) { + } else if (std::isnan(this->max_)) { return state >= this->min_; } else { return this->min_ <= state && state <= this->max_; diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index dbc1c88a5d..57a5c7c4bd 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -8,7 +8,7 @@ static const char *const TAG = "number"; void NumberCall::perform() { ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); - if (!this->value_.has_value() || isnan(*this->value_)) { + if (!this->value_.has_value() || std::isnan(*this->value_)) { ESP_LOGW(TAG, "No value set for NumberCall"); return; } diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 75641ad399..59ab22056b 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -15,6 +15,7 @@ from esphome.core import CORE, coroutine_with_priority CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] +AUTO_LOAD = ["socket"] CONF_ON_STATE_CHANGE = "on_state_change" CONF_ON_BEGIN = "on_begin" @@ -33,12 +34,21 @@ OTAProgressTrigger = ota_ns.class_("OTAProgressTrigger", automation.Trigger.temp OTAEndTrigger = ota_ns.class_("OTAEndTrigger", automation.Trigger.template()) OTAErrorTrigger = ota_ns.class_("OTAErrorTrigger", automation.Trigger.template()) + +def validate_password_support(value): + if CORE.using_arduino: + return value + if CORE.using_esp_idf: + raise cv.Invalid("Password support is not implemented yet for ESP-IDF") + raise NotImplementedError + + CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(OTAComponent), cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean, cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232): cv.port, - cv.Optional(CONF_PASSWORD, default=""): cv.string, + cv.Optional(CONF_PASSWORD): cv.All(cv.string, validate_password_support), cv.Optional( CONF_REBOOT_TIMEOUT, default="5min" ): cv.positive_time_period_milliseconds, @@ -76,7 +86,9 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_port(config[CONF_PORT])) - cg.add(var.set_auth_password(config[CONF_PASSWORD])) + if CONF_PASSWORD in config: + cg.add(var.set_auth_password(config[CONF_PASSWORD])) + cg.add_define("USE_OTA_PASSWORD") await cg.register_component(var, config) @@ -88,7 +100,7 @@ async def to_code(config): if CORE.is_esp8266: cg.add_library("Update", None) - elif CORE.is_esp32: + elif CORE.is_esp32 and CORE.using_arduino: cg.add_library("Hash", None) use_state_callback = False diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index e8ac1b9bcd..7ee3ed2866 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -2,14 +2,31 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" +#include "esphome/core/hal.h" #include "esphome/core/util.h" +#include "esphome/components/network/util.h" +#include #include + +#ifdef USE_ARDUINO +#ifdef USE_OTA_PASSWORD #include -#ifdef ARDUINO_ARCH_ESP32 +#endif // USE_OTA_PASSWORD + +#ifdef USE_ESP32 #include +#endif // USE_ESP32 +#endif // USE_ARDUINO + +#ifdef USE_ESP8266 +#include +#include "esphome/components/esp8266/preferences.h" +#endif // USE_ESP8266 + +#ifdef USE_ESP_IDF +#include #endif -#include namespace esphome { namespace ota { @@ -18,19 +35,177 @@ static const char *const TAG = "ota"; static const uint8_t OTA_VERSION_1_0 = 1; +class OTABackend { + public: + virtual ~OTABackend() = default; + virtual OTAResponseTypes begin(size_t image_size) = 0; + virtual void set_update_md5(const char *md5) = 0; + virtual OTAResponseTypes write(uint8_t *data, size_t len) = 0; + virtual OTAResponseTypes end() = 0; + virtual void abort() = 0; +}; + +#ifdef USE_ARDUINO +class ArduinoOTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override { + bool ret = Update.begin(image_size, U_FLASH); + if (ret) { +#ifdef USE_ESP8266 + esp8266::preferences_prevent_write(true); +#endif + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); +#ifdef USE_ESP8266 + if (error == UPDATE_ERROR_BOOTSTRAP) + return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; + if (error == UPDATE_ERROR_NEW_FLASH_CONFIG) + return OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG; + if (error == UPDATE_ERROR_FLASH_CONFIG) + return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; + if (error == UPDATE_ERROR_SPACE) + return OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE; +#endif +#ifdef USE_ESP32 + if (error == UPDATE_ERROR_SIZE) + return OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; +#endif + return OTA_RESPONSE_ERROR_UNKNOWN; + } + void set_update_md5(const char *md5) override { Update.setMD5(md5); } + OTAResponseTypes write(uint8_t *data, size_t len) override { + size_t written = Update.write(data, len); + if (written != len) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_OK; + } + OTAResponseTypes end() override { + if (!Update.end()) + return OTA_RESPONSE_ERROR_UPDATE_END; + return OTA_RESPONSE_OK; + } + void abort() override { +#ifdef USE_ESP32 + Update.abort(); +#endif + +#ifdef USE_ESP8266 + Update.end(); + esp8266::preferences_prevent_write(false); +#endif + } +}; +std::unique_ptr make_ota_backend() { return make_unique(); } +#endif // USE_ARDUINO + +#ifdef USE_ESP_IDF +class IDFOTABackend : public OTABackend { + public: + esp_ota_handle_t update_handle = 0; + + OTAResponseTypes begin(size_t image_size) override { + const esp_partition_t *update_partition = esp_ota_get_next_update_partition(nullptr); + if (update_partition == nullptr) { + return OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION; + } + esp_err_t err = esp_ota_begin(update_partition, image_size, &update_handle); + if (err != ESP_OK) { + esp_ota_abort(update_handle); + update_handle = 0; + if (err == ESP_ERR_INVALID_SIZE) { + return OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; + } else if (err == ESP_ERR_FLASH_OP_TIMEOUT || err == ESP_ERR_FLASH_OP_FAIL) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_ERROR_UNKNOWN; + } + return OTA_RESPONSE_OK; + } + void set_update_md5(const char *md5) override { + // pass + } + OTAResponseTypes write(uint8_t *data, size_t len) override { + esp_err_t err = esp_ota_write(update_handle, data, len); + if (err != ESP_OK) { + if (err == ESP_ERR_OTA_VALIDATE_FAILED) { + return OTA_RESPONSE_ERROR_MAGIC; + } else if (err == ESP_ERR_FLASH_OP_TIMEOUT || err == ESP_ERR_FLASH_OP_FAIL) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_ERROR_UNKNOWN; + } + return OTA_RESPONSE_OK; + } + OTAResponseTypes end() override { + esp_err_t err = esp_ota_end(update_handle); + update_handle = 0; + if (err != ESP_OK) { + if (err == ESP_ERR_OTA_VALIDATE_FAILED) { + return OTA_RESPONSE_ERROR_UPDATE_END; + } + return OTA_RESPONSE_ERROR_UNKNOWN; + } + return OTA_RESPONSE_OK; + } + void abort() override { esp_ota_abort(update_handle); } +}; +std::unique_ptr make_ota_backend() { return make_unique(); } +#endif // USE_ESP_IDF + void OTAComponent::setup() { - this->server_ = make_unique(this->port_); - this->server_->begin(); + server_ = socket::socket(AF_INET, SOCK_STREAM, 0); + if (server_ == nullptr) { + ESP_LOGW(TAG, "Could not create socket."); + this->mark_failed(); + return; + } + int enable = 1; + int err = server_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + if (err != 0) { + ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err); + // we can still continue + } + err = server_->setblocking(false); + if (err != 0) { + ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err); + this->mark_failed(); + return; + } + + struct sockaddr_in server; + memset(&server, 0, sizeof(server)); + server.sin_family = AF_INET; + server.sin_addr.s_addr = ESPHOME_INADDR_ANY; + server.sin_port = htons(this->port_); + + err = server_->bind((struct sockaddr *) &server, sizeof(server)); + if (err != 0) { + ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno); + this->mark_failed(); + return; + } + + err = server_->listen(4); + if (err != 0) { + ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno); + this->mark_failed(); + return; + } this->dump_config(); } void OTAComponent::dump_config() { ESP_LOGCONFIG(TAG, "Over-The-Air Updates:"); - ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->port_); + ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->port_); +#ifdef USE_OTA_PASSWORD if (!this->password_.empty()) { ESP_LOGCONFIG(TAG, " Using Password."); } +#endif if (this->has_safe_mode_ && this->safe_mode_rtc_value_ > 1) { ESP_LOGW(TAG, "Last Boot was an unhandled reset, will proceed to safe mode in %d restarts", this->safe_mode_num_attempts_ - this->safe_mode_rtc_value_); @@ -57,25 +232,31 @@ void OTAComponent::handle_() { char *sbuf = reinterpret_cast(buf); uint32_t ota_size; uint8_t ota_features; + std::unique_ptr backend; (void) ota_features; - if (!this->client_.connected()) { - this->client_ = this->server_->available(); + if (client_ == nullptr) { + struct sockaddr_storage source_addr; + socklen_t addr_len = sizeof(source_addr); + client_ = server_->accept((struct sockaddr *) &source_addr, &addr_len); + } + if (client_ == nullptr) + return; - if (!this->client_.connected()) - return; + int enable = 1; + int err = client_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); + if (err != 0) { + ESP_LOGW(TAG, "Socket could not enable tcp nodelay, errno: %d", errno); + return; } - // enable nodelay for outgoing data - this->client_.setNoDelay(true); - - ESP_LOGD(TAG, "Starting OTA Update from %s...", this->client_.remoteIP().toString().c_str()); + ESP_LOGD(TAG, "Starting OTA Update from %s...", this->client_->getpeername().c_str()); this->status_set_warning(); #ifdef USE_OTA_STATE_CALLBACK this->state_callback_.call(OTA_STARTED, 0.0f, 0); #endif - if (!this->wait_receive_(buf, 5)) { + if (!this->readall_(buf, 5)) { ESP_LOGW(TAG, "Reading magic bytes failed!"); goto error; } @@ -88,11 +269,12 @@ void OTAComponent::handle_() { } // Send OK and version - 2 bytes - this->client_.write(OTA_RESPONSE_OK); - this->client_.write(OTA_VERSION_1_0); + buf[0] = OTA_RESPONSE_OK; + buf[1] = OTA_VERSION_1_0; + this->writeall_(buf, 2); // Read features - 1 byte - if (!this->wait_receive_(buf, 1)) { + if (!this->readall_(buf, 1)) { ESP_LOGW(TAG, "Reading features failed!"); goto error; } @@ -100,10 +282,13 @@ void OTAComponent::handle_() { ESP_LOGV(TAG, "OTA features is 0x%02X", ota_features); // Acknowledge header - 1 byte - this->client_.write(OTA_RESPONSE_HEADER_OK); + buf[0] = OTA_RESPONSE_HEADER_OK; + this->writeall_(buf, 1); +#ifdef USE_OTA_PASSWORD if (!this->password_.empty()) { - this->client_.write(OTA_RESPONSE_REQUEST_AUTH); + buf[0] = OTA_RESPONSE_REQUEST_AUTH; + this->writeall_(buf, 1); MD5Builder md5_builder{}; md5_builder.begin(); sprintf(sbuf, "%08X", random_uint32()); @@ -113,7 +298,7 @@ void OTAComponent::handle_() { ESP_LOGV(TAG, "Auth: Nonce is %s", sbuf); // Send nonce, 32 bytes hex MD5 - if (this->client_.write(reinterpret_cast(sbuf), 32) != 32) { + if (!this->writeall_(reinterpret_cast(sbuf), 32)) { ESP_LOGW(TAG, "Auth: Writing nonce failed!"); goto error; } @@ -125,7 +310,7 @@ void OTAComponent::handle_() { md5_builder.add(sbuf); // Receive cnonce, 32 bytes hex MD5 - if (!this->wait_receive_(buf, 32)) { + if (!this->readall_(buf, 32)) { ESP_LOGW(TAG, "Auth: Reading cnonce failed!"); goto error; } @@ -140,7 +325,7 @@ void OTAComponent::handle_() { ESP_LOGV(TAG, "Auth: Result is %s", sbuf); // Receive result, 32 bytes hex MD5 - if (!this->wait_receive_(buf + 64, 32)) { + if (!this->writeall_(buf + 64, 32)) { ESP_LOGW(TAG, "Auth: Reading response failed!"); goto error; } @@ -157,12 +342,14 @@ void OTAComponent::handle_() { goto error; } } +#endif // USE_OTA_PASSWORD // Acknowledge auth OK - 1 byte - this->client_.write(OTA_RESPONSE_AUTH_OK); + buf[0] = OTA_RESPONSE_AUTH_OK; + this->writeall_(buf, 1); // Read size, 4 bytes MSB first - if (!this->wait_receive_(buf, 4)) { + if (!this->readall_(buf, 4)) { ESP_LOGW(TAG, "Reading size failed!"); goto error; } @@ -173,72 +360,46 @@ void OTAComponent::handle_() { } ESP_LOGV(TAG, "OTA size is %u bytes", ota_size); -#ifdef ARDUINO_ARCH_ESP8266 - global_preferences.prevent_write(true); -#endif - - if (!Update.begin(ota_size, U_FLASH)) { - uint8_t error = Update.getError(); - StreamString ss; - Update.printError(ss); -#ifdef ARDUINO_ARCH_ESP8266 - if (error == UPDATE_ERROR_BOOTSTRAP) { - error_code = OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; - goto error; - } - if (error == UPDATE_ERROR_NEW_FLASH_CONFIG) { - error_code = OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG; - goto error; - } - if (error == UPDATE_ERROR_FLASH_CONFIG) { - error_code = OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; - goto error; - } - if (error == UPDATE_ERROR_SPACE) { - error_code = OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE; - goto error; - } -#endif -#ifdef ARDUINO_ARCH_ESP32 - if (error == UPDATE_ERROR_SIZE) { - error_code = OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; - goto error; - } -#endif - ESP_LOGW(TAG, "Preparing OTA partition failed! '%s'", ss.c_str()); - error_code = OTA_RESPONSE_ERROR_UPDATE_PREPARE; + backend = make_ota_backend(); + error_code = backend->begin(ota_size); + if (error_code != OTA_RESPONSE_OK) goto error; - } update_started = true; // Acknowledge prepare OK - 1 byte - this->client_.write(OTA_RESPONSE_UPDATE_PREPARE_OK); + buf[0] = OTA_RESPONSE_UPDATE_PREPARE_OK; + this->writeall_(buf, 1); // Read binary MD5, 32 bytes - if (!this->wait_receive_(buf, 32)) { + if (!this->readall_(buf, 32)) { ESP_LOGW(TAG, "Reading binary MD5 checksum failed!"); goto error; } sbuf[32] = '\0'; ESP_LOGV(TAG, "Update: Binary MD5 is %s", sbuf); - Update.setMD5(sbuf); + backend->set_update_md5(sbuf); // Acknowledge MD5 OK - 1 byte - this->client_.write(OTA_RESPONSE_BIN_MD5_OK); + buf[0] = OTA_RESPONSE_BIN_MD5_OK; + this->writeall_(buf, 1); - while (!Update.isFinished()) { - size_t available = this->wait_receive_(buf, 0); - if (!available) { + while (total < ota_size) { + // TODO: timeout check + size_t requested = std::min(sizeof(buf), ota_size - total); + ssize_t read = this->client_->read(buf, requested); + if (read == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + continue; + ESP_LOGW(TAG, "Error receiving data for update, errno: %d", errno); goto error; } - uint32_t written = Update.write(buf, available); - if (written != available) { - ESP_LOGW(TAG, "Error writing binary data to flash: %u != %u!", written, available); // NOLINT - error_code = OTA_RESPONSE_ERROR_WRITING_FLASH; + error_code = backend->write(buf, read); + if (error_code != OTA_RESPONSE_OK) { + ESP_LOGW(TAG, "Error writing binary data to flash!"); goto error; } - total += written; + total += read; uint32_t now = millis(); if (now - last_progress > 1000) { @@ -254,24 +415,27 @@ void OTAComponent::handle_() { } // Acknowledge receive OK - 1 byte - this->client_.write(OTA_RESPONSE_RECEIVE_OK); + buf[0] = OTA_RESPONSE_RECEIVE_OK; + this->writeall_(buf, 1); - if (!Update.end()) { - error_code = OTA_RESPONSE_ERROR_UPDATE_END; + error_code = backend->end(); + if (error_code != OTA_RESPONSE_OK) { + ESP_LOGW(TAG, "Error ending OTA!"); goto error; } // Acknowledge Update end OK - 1 byte - this->client_.write(OTA_RESPONSE_UPDATE_END_OK); + buf[0] = OTA_RESPONSE_UPDATE_END_OK; + this->writeall_(buf, 1); // Read ACK - if (!this->wait_receive_(buf, 1, false) || buf[0] != OTA_RESPONSE_OK) { + if (!this->readall_(buf, 1) || buf[0] != OTA_RESPONSE_OK) { ESP_LOGW(TAG, "Reading back acknowledgement failed!"); // do not go to error, this is not fatal } - this->client_.flush(); - this->client_.stop(); + this->client_->close(); + this->client_ = nullptr; delay(10); ESP_LOGI(TAG, "OTA update finished!"); this->status_clear_warning(); @@ -282,88 +446,72 @@ void OTAComponent::handle_() { App.safe_reboot(); error: - if (update_started) { - StreamString ss; - Update.printError(ss); - ESP_LOGW(TAG, "Update end failed! Error: %s", ss.c_str()); - } - if (this->client_.connected()) { - this->client_.write(static_cast(error_code)); - this->client_.flush(); - } - this->client_.stop(); + buf[0] = static_cast(error_code); + this->writeall_(buf, 1); + this->client_->close(); + this->client_ = nullptr; -#ifdef ARDUINO_ARCH_ESP32 - if (update_started) { - Update.abort(); + if (backend != nullptr && update_started) { + backend->abort(); } -#endif - -#ifdef ARDUINO_ARCH_ESP8266 - if (update_started) { - Update.end(); - } -#endif this->status_momentary_error("onerror", 5000); #ifdef USE_OTA_STATE_CALLBACK this->state_callback_.call(OTA_ERROR, 0.0f, static_cast(error_code)); #endif - -#ifdef ARDUINO_ARCH_ESP8266 - global_preferences.prevent_write(false); -#endif } -size_t OTAComponent::wait_receive_(uint8_t *buf, size_t bytes, bool check_disconnected) { - size_t available = 0; +bool OTAComponent::readall_(uint8_t *buf, size_t len) { uint32_t start = millis(); - do { - App.feed_wdt(); - if (check_disconnected && !this->client_.connected()) { - ESP_LOGW(TAG, "Error client disconnected while receiving data!"); - return 0; - } - int availi = this->client_.available(); - if (availi < 0) { - ESP_LOGW(TAG, "Error reading data!"); - return 0; - } + uint32_t at = 0; + while (len - at > 0) { uint32_t now = millis(); - if (availi == 0 && now - start > 10000) { - ESP_LOGW(TAG, "Timeout waiting for data!"); - return 0; + if (now - start > 1000) { + ESP_LOGW(TAG, "Timed out reading %d bytes of data", len); + return false; } - available = size_t(availi); - yield(); - } while (bytes == 0 ? available == 0 : available < bytes); - if (bytes == 0) - bytes = std::min(available, size_t(1024)); - - bool success = false; - for (uint32_t i = 0; !success && i < 100; i++) { - int res = this->client_.read(buf, bytes); - - if (res != int(bytes)) { - // ESP32 implementation has an issue where calling read can fail with EAGAIN (race condition) - // so just re-try it until it works (with generous timeout of 1s) - // because we check with available() first this should not cause us any trouble in all other cases - delay(10); + ssize_t read = this->client_->read(buf + at, len - at); + if (read == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + delay(1); + continue; + } + ESP_LOGW(TAG, "Failed to read %d bytes of data, errno: %d", len, errno); + return false; } else { - success = true; + at += read; } + delay(1); } - if (!success) { - ESP_LOGW(TAG, "Reading %u bytes of binary data failed!", bytes); // NOLINT - return 0; - } - - return bytes; + return true; } +bool OTAComponent::writeall_(const uint8_t *buf, size_t len) { + uint32_t start = millis(); + uint32_t at = 0; + while (len - at > 0) { + uint32_t now = millis(); + if (now - start > 1000) { + ESP_LOGW(TAG, "Timed out writing %d bytes of data", len); + return false; + } -void OTAComponent::set_auth_password(const std::string &password) { this->password_ = password; } + ssize_t written = this->client_->write(buf + at, len - at); + if (written == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + delay(1); + continue; + } + ESP_LOGW(TAG, "Failed to write %d bytes of data, errno: %d", len, errno); + return false; + } else { + at += written; + } + delay(1); + } + return true; +} float OTAComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; } uint16_t OTAComponent::get_port() const { return this->port_; } @@ -373,7 +521,7 @@ bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_ this->safe_mode_start_time_ = millis(); this->safe_mode_enable_time_ = enable_time; this->safe_mode_num_attempts_ = num_attempts; - this->rtc_ = global_preferences.make_preference(233825507UL, false); + this->rtc_ = global_preferences->make_preference(233825507UL, false); this->safe_mode_rtc_value_ = this->read_rtc_(); ESP_LOGCONFIG(TAG, "There have been %u suspected unsuccessful boot attempts.", this->safe_mode_rtc_value_); diff --git a/esphome/components/ota/ota_component.h b/esphome/components/ota/ota_component.h index 04cea56ec2..f76295735e 100644 --- a/esphome/components/ota/ota_component.h +++ b/esphome/components/ota/ota_component.h @@ -1,12 +1,10 @@ #pragma once -#include - +#include "esphome/components/socket/socket.h" #include "esphome/core/component.h" #include "esphome/core/preferences.h" #include "esphome/core/helpers.h" -#include -#include +#include "esphome/core/defines.h" namespace esphome { namespace ota { @@ -32,6 +30,7 @@ enum OTAResponseTypes { OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 135, OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 136, OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137, + OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 138, OTA_RESPONSE_ERROR_UNKNOWN = 255, }; @@ -40,14 +39,9 @@ enum OTAState { OTA_COMPLETED = 0, OTA_STARTED, OTA_IN_PROGRESS, OTA_ERROR }; /// OTAComponent provides a simple way to integrate Over-the-Air updates into your app using ArduinoOTA. class OTAComponent : public Component { public: - /** Set a plaintext password that OTA will use for authentication. - * - * Warning: This password will be stored in plaintext in the ROM and can be read - * by intruders. - * - * @param password The plaintext password. - */ - void set_auth_password(const std::string &password); +#ifdef USE_OTA_PASSWORD + void set_auth_password(const std::string &password) { password_ = password; } +#endif // USE_OTA_PASSWORD /// Manually set the port OTA should listen on. void set_port(uint16_t port); @@ -76,14 +70,17 @@ class OTAComponent : public Component { uint32_t read_rtc_(); void handle_(); - size_t wait_receive_(uint8_t *buf, size_t bytes, bool check_disconnected = true); + bool readall_(uint8_t *buf, size_t len); + bool writeall_(const uint8_t *buf, size_t len); +#ifdef USE_OTA_PASSWORD std::string password_; +#endif // USE_OTA_PASSWORD uint16_t port_; - std::unique_ptr server_{nullptr}; - WiFiClient client_{}; + std::unique_ptr server_; + std::unique_ptr client_; bool has_safe_mode_{false}; ///< stores whether safe mode can be enabled. uint32_t safe_mode_start_time_; ///< stores when safe mode was enabled. diff --git a/esphome/components/pca9685/pca9685_output.h b/esphome/components/pca9685/pca9685_output.h index bd3f7f15f9..5dd52b5510 100644 --- a/esphome/components/pca9685/pca9685_output.h +++ b/esphome/components/pca9685/pca9685_output.h @@ -20,9 +20,10 @@ extern const uint8_t PCA9685_MODE_OUTNE_LOW; class PCA9685Output; -class PCA9685Channel : public output::FloatOutput, public Parented { +class PCA9685Channel : public output::FloatOutput { public: void set_channel(uint8_t channel) { channel_ = channel; } + void set_parent(PCA9685Output *parent) { parent_ = parent; } protected: friend class PCA9685Output; @@ -30,6 +31,7 @@ class PCA9685Channel : public output::FloatOutput, public Parentedwrite_gpio_(); } -void PCF8574Component::pin_mode(uint8_t pin, uint8_t mode) { - switch (mode) { - case PCF8574_INPUT: - // Clear mode mask bit - this->mode_mask_ &= ~(1 << pin); - // Write GPIO to enable input mode - this->write_gpio_(); - break; - case PCF8574_OUTPUT: - // Set mode mask bit - this->mode_mask_ |= 1 << pin; - break; - default: - break; +void PCF8574Component::pin_mode(uint8_t pin, gpio::Flags flags) { + if (flags == gpio::FLAG_INPUT) { + // Clear mode mask bit + this->mode_mask_ &= ~(1 << pin); + // Write GPIO to enable input mode + this->write_gpio_(); + } else if (flags == gpio::FLAG_OUTPUT) { + // Set mode mask bit + this->mode_mask_ |= 1 << pin; } } bool PCF8574Component::read_gpio_() { @@ -87,7 +82,7 @@ bool PCF8574Component::write_gpio_() { uint8_t data[2]; data[0] = value; data[1] = value >> 8; - if (!this->write_bytes_raw(data, this->pcf8575_ ? 2 : 1)) { + if (this->write(data, this->pcf8575_ ? 2 : 1) != i2c::ERROR_OK) { this->status_set_warning(); return false; } @@ -97,12 +92,15 @@ bool PCF8574Component::write_gpio_() { } float PCF8574Component::get_setup_priority() const { return setup_priority::IO; } -void PCF8574GPIOPin::setup() { this->pin_mode(this->mode_); } +void PCF8574GPIOPin::setup() { pin_mode(flags_); } +void PCF8574GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool PCF8574GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void PCF8574GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -void PCF8574GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); } -PCF8574GPIOPin::PCF8574GPIOPin(PCF8574Component *parent, uint8_t pin, uint8_t mode, bool inverted) - : GPIOPin(pin, mode, inverted), parent_(parent) {} +std::string PCF8574GPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%u via PCF8574", pin_); + return buffer; +} } // namespace pcf8574 } // namespace esphome diff --git a/esphome/components/pcf8574/pcf8574.h b/esphome/components/pcf8574/pcf8574.h index 925fa30899..c201e0615f 100644 --- a/esphome/components/pcf8574/pcf8574.h +++ b/esphome/components/pcf8574/pcf8574.h @@ -1,18 +1,12 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" namespace esphome { namespace pcf8574 { -/// Modes for PCF8574 pins -enum PCF8574GPIOMode : uint8_t { - PCF8574_INPUT = INPUT, - PCF8574_OUTPUT = OUTPUT, -}; - class PCF8574Component : public Component, public i2c::I2CDevice { public: PCF8574Component() = default; @@ -26,7 +20,7 @@ class PCF8574Component : public Component, public i2c::I2CDevice { /// Helper function to write the value of a pin. void digital_write(uint8_t pin, bool value); /// Helper function to set the pin mode of a pin. - void pin_mode(uint8_t pin, uint8_t mode); + void pin_mode(uint8_t pin, gpio::Flags flags); float get_setup_priority() const override; @@ -49,15 +43,22 @@ class PCF8574Component : public Component, public i2c::I2CDevice { /// Helper class to expose a PCF8574 pin as an internal input GPIO pin. class PCF8574GPIOPin : public GPIOPin { public: - PCF8574GPIOPin(PCF8574Component *parent, uint8_t pin, uint8_t mode, bool inverted = false); - void setup() override; - void pin_mode(uint8_t mode) override; + void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; + std::string dump_summary() const override; + + void set_parent(PCF8574Component *parent) { parent_ = parent; } + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_flags(gpio::Flags flags) { flags_ = flags; } protected: PCF8574Component *parent_; + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; }; } // namespace pcf8574 diff --git a/esphome/components/pid/pid_autotuner.cpp b/esphome/components/pid/pid_autotuner.cpp index 83e4b6be32..15c1c5f076 100644 --- a/esphome/components/pid/pid_autotuner.cpp +++ b/esphome/components/pid/pid_autotuner.cpp @@ -72,7 +72,7 @@ PIDAutotuner::PIDAutotuneResult PIDAutotuner::update(float setpoint, float proce return res; } - if (!isnan(this->setpoint_) && this->setpoint_ != setpoint) { + if (!std::isnan(this->setpoint_) && this->setpoint_ != setpoint) { ESP_LOGW(TAG, "Setpoint changed during autotune! The result will not be accurate!"); } this->setpoint_ = setpoint; diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index 4c7d92e26d..f5c7792782 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -100,7 +100,7 @@ void PIDClimate::write_output_(float value) { } void PIDClimate::update_pid_() { float value; - if (isnan(this->current_temperature) || isnan(this->target_temperature)) { + if (std::isnan(this->current_temperature) || std::isnan(this->target_temperature)) { // if any control parameters are nan, turn off all outputs value = 0.0; } else { diff --git a/esphome/components/pid/pid_controller.h b/esphome/components/pid/pid_controller.h index 4caad8dd8b..35e3eb9fc0 100644 --- a/esphome/components/pid/pid_controller.h +++ b/esphome/components/pid/pid_controller.h @@ -1,6 +1,6 @@ #pragma once -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" namespace esphome { namespace pid { @@ -23,9 +23,9 @@ struct PIDController { // i(t) := K_i * \int_{0}^{t} e(t) dt accumulated_integral_ += error * dt * ki; // constrain accumulated integral value - if (!isnan(min_integral) && accumulated_integral_ < min_integral) + if (!std::isnan(min_integral) && accumulated_integral_ < min_integral) accumulated_integral_ = min_integral; - if (!isnan(max_integral) && accumulated_integral_ > max_integral) + if (!std::isnan(max_integral) && accumulated_integral_ > max_integral) accumulated_integral_ = max_integral; integral_term = accumulated_integral_; diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index 8603adfbf4..7dbbd798ad 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -840,7 +840,7 @@ void Pipsolar::send_next_poll_() { this->used_polling_commands_[this->last_polling_command_].length); } -void Pipsolar::queue_command_(const char *command, byte length) { +void Pipsolar::queue_command_(const char *command, uint8_t length) { uint8_t next_position = command_queue_position_; for (uint8_t i = 0; i < COMMAND_QUEUE_LENGTH; i++) { uint8_t testposition = (next_position + i) % COMMAND_QUEUE_LENGTH; diff --git a/esphome/components/pipsolar/pipsolar.h b/esphome/components/pipsolar/pipsolar.h index 508036c7de..fe2a80d1d5 100644 --- a/esphome/components/pipsolar/pipsolar.h +++ b/esphome/components/pipsolar/pipsolar.h @@ -197,7 +197,7 @@ class Pipsolar : public uart::UARTDevice, public PollingComponent { uint16_t crc_xmodem_update_(uint16_t crc, uint8_t data); uint8_t send_next_command_(); void send_next_poll_(); - void queue_command_(const char *command, byte length); + void queue_command_(const char *command, uint8_t length); std::string command_queue_[COMMAND_QUEUE_LENGTH]; uint8_t command_queue_position_ = 0; uint8_t read_buffer_[PIPSOLAR_READ_BUFFER_LENGTH]; diff --git a/esphome/components/pmsa003i/pmsa003i.cpp b/esphome/components/pmsa003i/pmsa003i.cpp index 1396c9f3d4..ca3d28367a 100644 --- a/esphome/components/pmsa003i/pmsa003i.cpp +++ b/esphome/components/pmsa003i/pmsa003i.cpp @@ -1,5 +1,6 @@ #include "pmsa003i.h" #include "esphome/core/log.h" +#include namespace esphome { namespace pmsa003i { diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index 1c4160539a..ed2a2c1e35 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -2,6 +2,7 @@ #include #include "esphome/core/log.h" +#include "esphome/core/hal.h" // Based on: // - https://cdn-shop.adafruit.com/datasheets/PN532C106_Application+Note_v1.2.pdf diff --git a/esphome/components/pn532_i2c/pn532_i2c.cpp b/esphome/components/pn532_i2c/pn532_i2c.cpp index 25f24758bf..ef7480ec25 100644 --- a/esphome/components/pn532_i2c/pn532_i2c.cpp +++ b/esphome/components/pn532_i2c/pn532_i2c.cpp @@ -1,5 +1,6 @@ #include "pn532_i2c.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" // Based on: // - https://cdn-shop.adafruit.com/datasheets/PN532C106_Application+Note_v1.2.pdf @@ -11,7 +12,7 @@ namespace pn532_i2c { static const char *const TAG = "pn532_i2c"; -bool PN532I2C::write_data(const std::vector &data) { return this->write_bytes_raw(data.data(), data.size()); } +bool PN532I2C::write_data(const std::vector &data) { return this->write(data.data(), data.size()); } bool PN532I2C::read_data(std::vector &data, uint8_t len) { delay(1); diff --git a/esphome/components/power_supply/power_supply.h b/esphome/components/power_supply/power_supply.h index 12d2a9984f..66e4a7565a 100644 --- a/esphome/components/power_supply/power_supply.h +++ b/esphome/components/power_supply/power_supply.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" namespace esphome { namespace power_supply { diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index 06a0e39e2c..fa7b4fe132 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "prometheus_handler.h" #include "esphome/core/application.h" @@ -55,7 +57,7 @@ void PrometheusHandler::sensor_type_(AsyncResponseStream *stream) { void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor *obj) { if (obj->is_internal()) return; - if (!isnan(obj->state)) { + if (!std::isnan(obj->state)) { // We have a valid value, output this value stream->print(F("esphome_sensor_failed{id=\"")); stream->print(obj->get_object_id().c_str()); @@ -249,7 +251,7 @@ void PrometheusHandler::cover_type_(AsyncResponseStream *stream) { void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *obj) { if (obj->is_internal()) return; - if (!isnan(obj->position)) { + if (!std::isnan(obj->position)) { // We have a valid value, output this value stream->print(F("esphome_cover_failed{id=\"")); stream->print(obj->get_object_id().c_str()); @@ -310,3 +312,5 @@ void PrometheusHandler::switch_row_(AsyncResponseStream *stream, switch_::Switch } // namespace prometheus } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/prometheus/prometheus_handler.h b/esphome/components/prometheus/prometheus_handler.h index 6abd406556..5076883ba6 100644 --- a/esphome/components/prometheus/prometheus_handler.h +++ b/esphome/components/prometheus/prometheus_handler.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/components/web_server_base/web_server_base.h" #include "esphome/core/controller.h" #include "esphome/core/component.h" @@ -79,3 +81,5 @@ class PrometheusHandler : public AsyncWebHandler, public Component { } // namespace prometheus } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.cpp b/esphome/components/pulse_counter/pulse_counter_sensor.cpp index 602c0bdd67..f538a4c905 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.cpp +++ b/esphome/components/pulse_counter/pulse_counter_sensor.cpp @@ -8,15 +8,15 @@ static const char *const TAG = "pulse_counter"; const char *const EDGE_MODE_TO_STRING[] = {"DISABLE", "INCREMENT", "DECREMENT"}; -#ifdef ARDUINO_ARCH_ESP8266 -void ICACHE_RAM_ATTR PulseCounterStorage::gpio_intr(PulseCounterStorage *arg) { +#ifdef USE_ESP8266 +void IRAM_ATTR PulseCounterStorage::gpio_intr(PulseCounterStorage *arg) { const uint32_t now = micros(); const bool discard = now - arg->last_pulse < arg->filter_us; arg->last_pulse = now; if (discard) return; - PulseCounterCountMode mode = arg->isr_pin->digital_read() ? arg->rising_edge_mode : arg->falling_edge_mode; + PulseCounterCountMode mode = arg->isr_pin.digital_read() ? arg->rising_edge_mode : arg->falling_edge_mode; switch (mode) { case PULSE_COUNTER_DISABLE: break; @@ -28,11 +28,11 @@ void ICACHE_RAM_ATTR PulseCounterStorage::gpio_intr(PulseCounterStorage *arg) { break; } } -bool PulseCounterStorage::pulse_counter_setup(GPIOPin *pin) { +bool PulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { this->pin = pin; this->pin->setup(); this->isr_pin = this->pin->to_isr(); - this->pin->attach_interrupt(PulseCounterStorage::gpio_intr, this, CHANGE); + this->pin->attach_interrupt(PulseCounterStorage::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); return true; } pulse_counter_t PulseCounterStorage::read_raw_value() { @@ -43,8 +43,8 @@ pulse_counter_t PulseCounterStorage::read_raw_value() { } #endif -#ifdef ARDUINO_ARCH_ESP32 -bool PulseCounterStorage::pulse_counter_setup(GPIOPin *pin) { +#ifdef USE_ESP32 +bool PulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { static pcnt_unit_t next_pcnt_unit = PCNT_UNIT_0; this->pin = pin; this->pin->setup(); diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.h b/esphome/components/pulse_counter/pulse_counter_sensor.h index 3203ab81fd..94e37bc232 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.h +++ b/esphome/components/pulse_counter/pulse_counter_sensor.h @@ -1,10 +1,10 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #endif @@ -17,30 +17,30 @@ enum PulseCounterCountMode { PULSE_COUNTER_DECREMENT, }; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 using pulse_counter_t = int16_t; #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 using pulse_counter_t = int32_t; #endif struct PulseCounterStorage { - bool pulse_counter_setup(GPIOPin *pin); + bool pulse_counter_setup(InternalGPIOPin *pin); pulse_counter_t read_raw_value(); static void gpio_intr(PulseCounterStorage *arg); -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 volatile pulse_counter_t counter{0}; volatile uint32_t last_pulse{0}; #endif - GPIOPin *pin; -#ifdef ARDUINO_ARCH_ESP32 + InternalGPIOPin *pin; +#ifdef USE_ESP32 pcnt_unit_t pcnt_unit; #endif -#ifdef ARDUINO_ARCH_ESP8266 - ISRInternalGPIOPin *isr_pin; +#ifdef USE_ESP8266 + ISRInternalGPIOPin isr_pin; #endif PulseCounterCountMode rising_edge_mode{PULSE_COUNTER_INCREMENT}; PulseCounterCountMode falling_edge_mode{PULSE_COUNTER_DISABLE}; @@ -50,7 +50,7 @@ struct PulseCounterStorage { class PulseCounterSensor : public sensor::Sensor, public PollingComponent { public: - void set_pin(GPIOPin *pin) { pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } void set_rising_edge_mode(PulseCounterCountMode mode) { storage_.rising_edge_mode = mode; } void set_falling_edge_mode(PulseCounterCountMode mode) { storage_.falling_edge_mode = mode; } void set_filter_us(uint32_t filter) { storage_.filter_us = filter; } @@ -63,7 +63,7 @@ class PulseCounterSensor : public sensor::Sensor, public PollingComponent { void dump_config() override; protected: - GPIOPin *pin_; + InternalGPIOPin *pin_; PulseCounterStorage storage_; uint32_t current_total_ = 0; sensor::Sensor *total_sensor_; diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.cpp b/esphome/components/pulse_meter/pulse_meter_sensor.cpp index 1a35deba2f..fd1403b4fd 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.cpp +++ b/esphome/components/pulse_meter/pulse_meter_sensor.cpp @@ -9,7 +9,7 @@ static const char *const TAG = "pulse_meter"; void PulseMeterSensor::setup() { this->pin_->setup(); this->isr_pin_ = pin_->to_isr(); - this->pin_->attach_interrupt(PulseMeterSensor::gpio_intr, this, CHANGE); + this->pin_->attach_interrupt(PulseMeterSensor::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); this->last_detected_edge_us_ = 0; this->last_valid_edge_us_ = 0; @@ -56,14 +56,14 @@ void PulseMeterSensor::dump_config() { ESP_LOGCONFIG(TAG, " Assuming 0 pulses/min after not receiving a pulse for %us", this->timeout_us_ / 1000000); } -void ICACHE_RAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) { +void IRAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) { // This is an interrupt handler - we can't call any virtual method from this method // Get the current time before we do anything else so the measurements are consistent const uint32_t now = micros(); // We only look at rising edges - if (!sensor->isr_pin_->digital_read()) { + if (!sensor->isr_pin_.digital_read()) { return; } diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.h b/esphome/components/pulse_meter/pulse_meter_sensor.h index d02cff6123..1cebc1748e 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.h +++ b/esphome/components/pulse_meter/pulse_meter_sensor.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/core/helpers.h" @@ -10,7 +10,7 @@ namespace pulse_meter { class PulseMeterSensor : public sensor::Sensor, public Component { public: - void set_pin(GPIOPin *pin) { this->pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } void set_filter_us(uint32_t filter) { this->filter_us_ = filter; } void set_timeout_us(uint32_t timeout) { this->timeout_us_ = timeout; } void set_total_sensor(sensor::Sensor *sensor) { this->total_sensor_ = sensor; } @@ -25,8 +25,8 @@ class PulseMeterSensor : public sensor::Sensor, public Component { protected: static void gpio_intr(PulseMeterSensor *sensor); - GPIOPin *pin_ = nullptr; - ISRInternalGPIOPin *isr_pin_; + InternalGPIOPin *pin_ = nullptr; + ISRInternalGPIOPin isr_pin_; uint32_t filter_us_ = 0; uint32_t timeout_us_ = 1000000UL * 60UL * 5UL; sensor::Sensor *total_sensor_ = nullptr; diff --git a/esphome/components/pulse_width/pulse_width.cpp b/esphome/components/pulse_width/pulse_width.cpp index fb998ef4e1..8d66861049 100644 --- a/esphome/components/pulse_width/pulse_width.cpp +++ b/esphome/components/pulse_width/pulse_width.cpp @@ -6,8 +6,8 @@ namespace pulse_width { static const char *const TAG = "pulse_width"; -void ICACHE_RAM_ATTR PulseWidthSensorStore::gpio_intr(PulseWidthSensorStore *arg) { - const bool new_level = arg->pin_->digital_read(); +void IRAM_ATTR PulseWidthSensorStore::gpio_intr(PulseWidthSensorStore *arg) { + const bool new_level = arg->pin_.digital_read(); const uint32_t now = micros(); if (new_level) { arg->last_rise_ = now; diff --git a/esphome/components/pulse_width/pulse_width.h b/esphome/components/pulse_width/pulse_width.h index 9d32ce99b1..822688ec88 100644 --- a/esphome/components/pulse_width/pulse_width.h +++ b/esphome/components/pulse_width/pulse_width.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" namespace esphome { @@ -10,11 +10,11 @@ namespace pulse_width { /// Store data in a class that doesn't use multiple-inheritance (vtables in flash) class PulseWidthSensorStore { public: - void setup(GPIOPin *pin) { + void setup(InternalGPIOPin *pin) { pin->setup(); this->pin_ = pin->to_isr(); this->last_rise_ = micros(); - pin->attach_interrupt(&PulseWidthSensorStore::gpio_intr, this, CHANGE); + pin->attach_interrupt(&PulseWidthSensorStore::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); } static void gpio_intr(PulseWidthSensorStore *arg); uint32_t get_pulse_width_us() const { return this->last_width_; } @@ -22,14 +22,14 @@ class PulseWidthSensorStore { uint32_t get_last_rise() const { return last_rise_; } protected: - ISRInternalGPIOPin *pin_; + ISRInternalGPIOPin pin_; volatile uint32_t last_width_{0}; volatile uint32_t last_rise_{0}; }; class PulseWidthSensor : public sensor::Sensor, public PollingComponent { public: - void set_pin(GPIOPin *pin) { pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } void setup() override { this->store_.setup(this->pin_); } void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } @@ -37,7 +37,7 @@ class PulseWidthSensor : public sensor::Sensor, public PollingComponent { protected: PulseWidthSensorStore store_; - GPIOPin *pin_; + InternalGPIOPin *pin_; }; } // namespace pulse_width diff --git a/esphome/components/pulse_width/sensor.py b/esphome/components/pulse_width/sensor.py index 6c91104036..b090647627 100644 --- a/esphome/components/pulse_width/sensor.py +++ b/esphome/components/pulse_width/sensor.py @@ -26,9 +26,7 @@ CONFIG_SCHEMA = ( .extend( { cv.GenerateID(): cv.declare_id(PulseWidthSensor), - cv.Required(CONF_PIN): cv.All( - pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt - ), + cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema), } ) .extend(cv.polling_component_schema("60s")) diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp index 9edcd9166c..ff9723ab2f 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp @@ -1,7 +1,7 @@ #include "pvvx_mithermometer.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace pvvx_mithermometer { diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h index 4132954983..bb67769d4f 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h @@ -4,7 +4,7 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace pvvx_mithermometer { diff --git a/esphome/components/qmc5883l/qmc5883l.cpp b/esphome/components/qmc5883l/qmc5883l.cpp index 9e80cdbd88..f03b6af191 100644 --- a/esphome/components/qmc5883l/qmc5883l.cpp +++ b/esphome/components/qmc5883l/qmc5883l.cpp @@ -1,5 +1,7 @@ #include "qmc5883l.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" +#include namespace esphome { namespace qmc5883l { @@ -115,9 +117,10 @@ void QMC5883LComponent::update() { } bool QMC5883LComponent::read_byte_16_(uint8_t a_register, uint16_t *data) { - bool success = this->read_byte_16(a_register, data); + if (!this->read_byte_16(a_register, data)) + return false; *data = (*data & 0x00FF) << 8 | (*data & 0xFF00) >> 8; // Flip Byte order, LSB first; - return success; + return true; } } // namespace qmc5883l diff --git a/esphome/components/rc522/rc522.cpp b/esphome/components/rc522/rc522.cpp index 6261f63132..385641fea0 100644 --- a/esphome/components/rc522/rc522.cpp +++ b/esphome/components/rc522/rc522.cpp @@ -43,14 +43,14 @@ void RC522::setup() { // First set the resetPowerDownPin as digital input, to check the MFRC522 power down mode. if (reset_pin_ != nullptr) { - reset_pin_->pin_mode(INPUT); + reset_pin_->pin_mode(gpio::FLAG_INPUT); - if (reset_pin_->digital_read() == LOW) { // The MFRC522 chip is in power down mode. + if (!reset_pin_->digital_read()) { // The MFRC522 chip is in power down mode. ESP_LOGV(TAG, "Power down mode detected. Hard resetting..."); - reset_pin_->pin_mode(OUTPUT); // Now set the resetPowerDownPin as digital output. - reset_pin_->digital_write(LOW); // Make sure we have a clean LOW state. + reset_pin_->pin_mode(gpio::FLAG_OUTPUT); // Now set the resetPowerDownPin as digital output. + reset_pin_->digital_write(false); // Make sure we have a clean LOW state. delayMicroseconds(2); // 8.8.1 Reset timing requirements says about 100ns. Let us be generous: 2μsl - reset_pin_->digital_write(HIGH); // Exit power down mode. This triggers a hard reset. + reset_pin_->digital_write(true); // Exit power down mode. This triggers a hard reset. // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. // Let us be generous: 50ms. reset_timeout_ = millis(); diff --git a/esphome/components/rc522/rc522.h b/esphome/components/rc522/rc522.h index e4e3387ff6..d853d2f5ff 100644 --- a/esphome/components/rc522/rc522.h +++ b/esphome/components/rc522/rc522.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/automation.h" #include "esphome/components/binary_sensor/binary_sensor.h" diff --git a/esphome/components/rc522_i2c/rc522_i2c.cpp b/esphome/components/rc522_i2c/rc522_i2c.cpp index 896e27214a..6a3d8d2486 100644 --- a/esphome/components/rc522_i2c/rc522_i2c.cpp +++ b/esphome/components/rc522_i2c/rc522_i2c.cpp @@ -18,8 +18,9 @@ void RC522I2C::dump_config() { uint8_t RC522I2C::pcd_read_register(PcdRegister reg ///< The register to read from. One of the PCD_Register enums. ) { uint8_t value; - read_byte(reg >> 1, &value); - ESP_LOGVV(TAG, "read_register_(%x) -> %x", reg, value); + if (!read_byte(reg >> 1, &value)) + return 0; + ESP_LOGVV(TAG, "read_register_(%x) -> %u", reg, value); return value; } diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 537ae2283c..d2b848600d 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -1092,15 +1092,13 @@ MIDEA_SCHEMA = cv.Schema( [cv.Any(cv.hex_uint8_t, cv.uint8_t)], cv.Length(min=5, max=5), ), - cv.GenerateID(CONF_CODE_STORAGE_ID): cv.declare_id(cg.uint8), } ) @register_binary_sensor("midea", MideaBinarySensor, MIDEA_SCHEMA) def midea_binary_sensor(var, config): - arr_ = cg.progmem_array(config[CONF_CODE_STORAGE_ID], config[CONF_CODE]) - cg.add(var.set_code(arr_)) + cg.add(var.set_code(config[CONF_CODE])) @register_trigger("midea", MideaTrigger, MideaData) @@ -1119,5 +1117,4 @@ def midea_dumper(var, config): MIDEA_SCHEMA, ) async def midea_action(var, config, args): - arr_ = cg.progmem_array(config[CONF_CODE_STORAGE_ID], config[CONF_CODE]) - cg.add(var.set_code(arr_)) + cg.add(var.set_code(config[CONF_CODE])) diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h index 9b0d156617..12916bd44d 100644 --- a/esphome/components/remote_base/midea_protocol.h +++ b/esphome/components/remote_base/midea_protocol.h @@ -17,8 +17,6 @@ class MideaData { MideaData(const std::vector &data) { memcpy(this->data_, data.data(), std::min(data.size(), sizeof(this->data_))); } - // Make 40-bit copy from PROGMEM array - MideaData(const uint8_t *data) { memcpy_P(this->data_, data, OFFSET_CS); } // Default copy constructor MideaData(const MideaData &) = default; @@ -83,7 +81,7 @@ class MideaBinarySensor : public RemoteReceiverBinarySensorBase { auto data = MideaProtocol().decode(src); return data.has_value() && data.value() == this->data_; } - void set_code(const uint8_t *code) { this->data_ = code; } + void set_code(const std::vector &code) { this->data_ = code; } protected: MideaData data_; @@ -93,7 +91,8 @@ using MideaTrigger = RemoteReceiverTrigger; using MideaDumper = RemoteReceiverDumper; template class MideaAction : public RemoteTransmitterActionBase { - TEMPLATABLE_VALUE(const uint8_t *, code) + TEMPLATABLE_VALUE(std::vector, code) + void set_code(std::vector code) { code_ = code; } void encode(RemoteTransmitData *dst, Ts... x) override { MideaData data = this->code_.value(x...); data.finalize(); diff --git a/esphome/components/remote_base/remote_base.cpp b/esphome/components/remote_base/remote_base.cpp index d36c0d7ebe..a43f743ab6 100644 --- a/esphome/components/remote_base/remote_base.cpp +++ b/esphome/components/remote_base/remote_base.cpp @@ -6,7 +6,7 @@ namespace remote_base { static const char *const TAG = "remote_base"; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 RemoteRMTChannel::RemoteRMTChannel(uint8_t mem_block_num) : mem_block_num_(mem_block_num) { static rmt_channel_t next_rmt_channel = RMT_CHANNEL_0; this->channel_ = next_rmt_channel; diff --git a/esphome/components/remote_base/remote_base.h b/esphome/components/remote_base/remote_base.h index 16c732df83..68cf67d175 100644 --- a/esphome/components/remote_base/remote_base.h +++ b/esphome/components/remote_base/remote_base.h @@ -3,11 +3,11 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/automation.h" #include "esphome/components/binary_sensor/binary_sensor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #endif @@ -146,13 +146,13 @@ template class RemoteProtocol { class RemoteComponentBase { public: - explicit RemoteComponentBase(GPIOPin *pin) : pin_(pin){}; + explicit RemoteComponentBase(InternalGPIOPin *pin) : pin_(pin){}; protected: - GPIOPin *pin_; + InternalGPIOPin *pin_; }; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 class RemoteRMTChannel { public: explicit RemoteRMTChannel(uint8_t mem_block_num = 1); @@ -178,7 +178,7 @@ class RemoteRMTChannel { class RemoteTransmitterBase : public RemoteComponentBase { public: - RemoteTransmitterBase(GPIOPin *pin) : RemoteComponentBase(pin) {} + RemoteTransmitterBase(InternalGPIOPin *pin) : RemoteComponentBase(pin) {} class TransmitCall { public: explicit TransmitCall(RemoteTransmitterBase *parent) : parent_(parent) {} @@ -221,7 +221,7 @@ class RemoteReceiverDumperBase { class RemoteReceiverBase : public RemoteComponentBase { public: - RemoteReceiverBase(GPIOPin *pin) : RemoteComponentBase(pin) {} + RemoteReceiverBase(InternalGPIOPin *pin) : RemoteComponentBase(pin) {} void register_listener(RemoteReceiverListener *listener) { this->listeners_.push_back(listener); } void register_dumper(RemoteReceiverDumperBase *dumper) { if (dumper->is_secondary()) { diff --git a/esphome/components/remote_base/samsung_protocol.cpp b/esphome/components/remote_base/samsung_protocol.cpp index 0f2605d865..4571f332b3 100644 --- a/esphome/components/remote_base/samsung_protocol.cpp +++ b/esphome/components/remote_base/samsung_protocol.cpp @@ -1,5 +1,6 @@ #include "samsung_protocol.h" #include "esphome/core/log.h" +#include namespace esphome { namespace remote_base { diff --git a/esphome/components/remote_base/toshiba_ac_protocol.cpp b/esphome/components/remote_base/toshiba_ac_protocol.cpp index 27d042ace0..bd1d2a8f5b 100644 --- a/esphome/components/remote_base/toshiba_ac_protocol.cpp +++ b/esphome/components/remote_base/toshiba_ac_protocol.cpp @@ -1,5 +1,6 @@ #include "toshiba_ac_protocol.h" #include "esphome/core/log.h" +#include namespace esphome { namespace remote_base { diff --git a/esphome/components/remote_receiver/__init__.py b/esphome/components/remote_receiver/__init__.py index 7158368ed8..253204bd1a 100644 --- a/esphome/components/remote_receiver/__init__.py +++ b/esphome/components/remote_receiver/__init__.py @@ -25,9 +25,7 @@ CONFIG_SCHEMA = remote_base.validate_triggers( cv.Schema( { cv.GenerateID(): cv.declare_id(RemoteReceiverComponent), - cv.Required(CONF_PIN): cv.All( - pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt - ), + cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema), cv.Optional(CONF_DUMP, default=[]): remote_base.validate_dumpers, cv.Optional(CONF_TOLERANCE, default=25): cv.All( cv.percentage_int, cv.Range(min=0) diff --git a/esphome/components/remote_receiver/remote_receiver.h b/esphome/components/remote_receiver/remote_receiver.h index 25262e3366..50153c105d 100644 --- a/esphome/components/remote_receiver/remote_receiver.h +++ b/esphome/components/remote_receiver/remote_receiver.h @@ -6,7 +6,7 @@ namespace esphome { namespace remote_receiver { -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 struct RemoteReceiverComponentStore { static void gpio_intr(RemoteReceiverComponentStore *arg); @@ -21,23 +21,23 @@ struct RemoteReceiverComponentStore { bool overflow{false}; uint32_t buffer_size{1000}; uint8_t filter_us{10}; - ISRInternalGPIOPin *pin; + ISRInternalGPIOPin pin; }; #endif class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, public Component -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 , public remote_base::RemoteRMTChannel #endif { public: -#ifdef ARDUINO_ARCH_ESP32 - RemoteReceiverComponent(GPIOPin *pin, uint8_t mem_block_num = 1) +#ifdef USE_ESP32 + RemoteReceiverComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1) : RemoteReceiverBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {} #else - RemoteReceiverComponent(GPIOPin *pin) : RemoteReceiverBase(pin) {} + RemoteReceiverComponent(InternalGPIOPin *pin) : RemoteReceiverBase(pin) {} #endif void setup() override; void dump_config() override; @@ -49,13 +49,13 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, void set_idle_us(uint32_t idle_us) { this->idle_us_ = idle_us; } protected: -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 void decode_rmt_(rmt_item32_t *item, size_t len); RingbufHandle_t ringbuf_; esp_err_t error_code_{ESP_OK}; #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 RemoteReceiverComponentStore store_; HighFrequencyLoopRequester high_freq_; #endif diff --git a/esphome/components/remote_receiver/remote_receiver_esp32.cpp b/esphome/components/remote_receiver/remote_receiver_esp32.cpp index b2ddc69b1c..bec2af6718 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp32.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp32.cpp @@ -1,7 +1,7 @@ #include "remote_receiver.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include namespace esphome { diff --git a/esphome/components/remote_receiver/remote_receiver_esp8266.cpp b/esphome/components/remote_receiver/remote_receiver_esp8266.cpp index d509eea925..cf2c15402e 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp8266.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp8266.cpp @@ -1,20 +1,20 @@ #include "remote_receiver.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 namespace esphome { namespace remote_receiver { static const char *const TAG = "remote_receiver.esp8266"; -void ICACHE_RAM_ATTR HOT RemoteReceiverComponentStore::gpio_intr(RemoteReceiverComponentStore *arg) { +void IRAM_ATTR HOT RemoteReceiverComponentStore::gpio_intr(RemoteReceiverComponentStore *arg) { const uint32_t now = micros(); // If the lhs is 1 (rising edge) we should write to an uneven index and vice versa const uint32_t next = (arg->buffer_write_at + 1) % arg->buffer_size; - const bool level = arg->pin->digital_read(); + const bool level = arg->pin.digital_read(); if (level != next % 2) return; @@ -54,7 +54,7 @@ void RemoteReceiverComponent::setup() { } else { s.buffer_write_at = s.buffer_read_at = 0; } - this->pin_->attach_interrupt(RemoteReceiverComponentStore::gpio_intr, &this->store_, CHANGE); + this->pin_->attach_interrupt(RemoteReceiverComponentStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); } void RemoteReceiverComponent::dump_config() { ESP_LOGCONFIG(TAG, "Remote Receiver:"); diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index 853b5b6289..d05942de3b 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -8,13 +8,13 @@ namespace remote_transmitter { class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, public Component -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 , public remote_base::RemoteRMTChannel #endif { public: - explicit RemoteTransmitterComponent(GPIOPin *pin) : remote_base::RemoteTransmitterBase(pin) {} + explicit RemoteTransmitterComponent(InternalGPIOPin *pin) : remote_base::RemoteTransmitterBase(pin) {} void setup() override; @@ -26,7 +26,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, protected: void send_internal(uint32_t send_times, uint32_t send_wait) override; -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 void calculate_on_off_time_(uint32_t carrier_frequency, uint32_t *on_time_period, uint32_t *off_time_period); void mark_(uint32_t on_time, uint32_t off_time, uint32_t usec); @@ -34,7 +34,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, void space_(uint32_t usec); #endif -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 void configure_rmt(); uint32_t current_carrier_frequency_{UINT32_MAX}; diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index 90166d2741..ff53d0be84 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -2,7 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace remote_transmitter { diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp index f8735fe763..33c01985d7 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp @@ -2,7 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 namespace esphome { namespace remote_transmitter { diff --git a/esphome/components/resistance/resistance_sensor.cpp b/esphome/components/resistance/resistance_sensor.cpp index 1380354a5f..4d3dfa5928 100644 --- a/esphome/components/resistance/resistance_sensor.cpp +++ b/esphome/components/resistance/resistance_sensor.cpp @@ -13,7 +13,7 @@ void ResistanceSensor::dump_config() { ESP_LOGCONFIG(TAG, " Reference Voltage: %.1fV", this->reference_voltage_); } void ResistanceSensor::process_(float value) { - if (isnan(value)) { + if (std::isnan(value)) { this->publish_state(NAN); return; } diff --git a/esphome/components/restart/restart_switch.cpp b/esphome/components/restart/restart_switch.cpp index ea46c5f910..3076fde99e 100644 --- a/esphome/components/restart/restart_switch.cpp +++ b/esphome/components/restart/restart_switch.cpp @@ -1,4 +1,5 @@ #include "restart_switch.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" #include "esphome/core/application.h" diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index 8ef6f932c5..7c95fac98e 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -82,12 +82,12 @@ static const uint16_t DRAM_ATTR STATE_LOOKUP_TABLE[32] = { STATE_CW | STATE_S3 // 0x1F: stay here }; -void ICACHE_RAM_ATTR HOT RotaryEncoderSensorStore::gpio_intr(RotaryEncoderSensorStore *arg) { +void IRAM_ATTR HOT RotaryEncoderSensorStore::gpio_intr(RotaryEncoderSensorStore *arg) { // Forget upper bits and add pin states uint8_t input_state = arg->state & STATE_LUT_MASK; - if (arg->pin_a->digital_read()) + if (arg->pin_a.digital_read()) input_state |= STATE_PIN_A_HIGH; - if (arg->pin_b->digital_read()) + if (arg->pin_b.digital_read()) input_state |= STATE_PIN_B_HIGH; int8_t rotation_dir = 0; @@ -134,8 +134,8 @@ void RotaryEncoderSensor::setup() { this->pin_i_->setup(); } - this->pin_a_->attach_interrupt(RotaryEncoderSensorStore::gpio_intr, &this->store_, CHANGE); - this->pin_b_->attach_interrupt(RotaryEncoderSensorStore::gpio_intr, &this->store_, CHANGE); + this->pin_a_->attach_interrupt(RotaryEncoderSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); + this->pin_b_->attach_interrupt(RotaryEncoderSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); } void RotaryEncoderSensor::dump_config() { LOG_SENSOR("", "Rotary Encoder", this); @@ -157,13 +157,14 @@ void RotaryEncoderSensor::dump_config() { void RotaryEncoderSensor::loop() { std::array rotation_events; bool rotation_events_overflow; - ets_intr_lock(); - rotation_events = this->store_.rotation_events; - rotation_events_overflow = this->store_.rotation_events_overflow; + { + InterruptLock lock; + rotation_events = this->store_.rotation_events; + rotation_events_overflow = this->store_.rotation_events_overflow; - this->store_.rotation_events.fill(0); - this->store_.rotation_events_overflow = false; - ets_intr_unlock(); + this->store_.rotation_events.fill(0); + this->store_.rotation_events_overflow = false; + } if (rotation_events_overflow) { ESP_LOGW(TAG, "Captured more rotation events than expected"); diff --git a/esphome/components/rotary_encoder/rotary_encoder.h b/esphome/components/rotary_encoder/rotary_encoder.h index 000350d66c..4825e472a1 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.h +++ b/esphome/components/rotary_encoder/rotary_encoder.h @@ -3,7 +3,7 @@ #include #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/automation.h" #include "esphome/components/sensor/sensor.h" @@ -19,8 +19,8 @@ enum RotaryEncoderResolution { }; struct RotaryEncoderSensorStore { - ISRInternalGPIOPin *pin_a; - ISRInternalGPIOPin *pin_b; + ISRInternalGPIOPin pin_a; + ISRInternalGPIOPin pin_b; volatile int32_t counter{0}; RotaryEncoderResolution resolution{ROTARY_ENCODER_1_PULSE_PER_CYCLE}; @@ -37,8 +37,8 @@ struct RotaryEncoderSensorStore { class RotaryEncoderSensor : public sensor::Sensor, public Component { public: - void set_pin_a(GPIOPin *pin_a) { pin_a_ = pin_a; } - void set_pin_b(GPIOPin *pin_b) { pin_b_ = pin_b; } + void set_pin_a(InternalGPIOPin *pin_a) { pin_a_ = pin_a; } + void set_pin_b(InternalGPIOPin *pin_b) { pin_b_ = pin_b; } /** Set the resolution of the rotary encoder. * @@ -76,8 +76,8 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { } protected: - GPIOPin *pin_a_; - GPIOPin *pin_b_; + InternalGPIOPin *pin_a_; + InternalGPIOPin *pin_b_; GPIOPin *pin_i_{nullptr}; /// Index pin, if this is not nullptr, the counter will reset to 0 once this pin is HIGH. RotaryEncoderSensorStore store_{}; diff --git a/esphome/components/rotary_encoder/sensor.py b/esphome/components/rotary_encoder/sensor.py index e82b9d5f13..ef1110c6d8 100644 --- a/esphome/components/rotary_encoder/sensor.py +++ b/esphome/components/rotary_encoder/sensor.py @@ -64,12 +64,8 @@ CONFIG_SCHEMA = cv.All( .extend( { cv.GenerateID(): cv.declare_id(RotaryEncoderSensor), - cv.Required(CONF_PIN_A): cv.All( - pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt - ), - cv.Required(CONF_PIN_B): cv.All( - pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt - ), + cv.Required(CONF_PIN_A): cv.All(pins.internal_gpio_input_pin_schema), + cv.Required(CONF_PIN_B): cv.All(pins.internal_gpio_input_pin_schema), cv.Optional(CONF_PIN_RESET): pins.internal_gpio_output_pin_schema, cv.Optional(CONF_RESOLUTION, default=1): cv.enum(RESOLUTIONS, int=True), cv.Optional(CONF_MIN_VALUE): cv.int_, diff --git a/esphome/components/ruuvi_ble/ruuvi_ble.cpp b/esphome/components/ruuvi_ble/ruuvi_ble.cpp index e3e9f42305..bdd012cf5c 100644 --- a/esphome/components/ruuvi_ble/ruuvi_ble.cpp +++ b/esphome/components/ruuvi_ble/ruuvi_ble.cpp @@ -1,7 +1,7 @@ #include "ruuvi_ble.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ruuvi_ble { diff --git a/esphome/components/ruuvi_ble/ruuvi_ble.h b/esphome/components/ruuvi_ble/ruuvi_ble.h index 848004f3d7..add431ce42 100644 --- a/esphome/components/ruuvi_ble/ruuvi_ble.h +++ b/esphome/components/ruuvi_ble/ruuvi_ble.h @@ -3,7 +3,7 @@ #include "esphome/core/component.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ruuvi_ble { diff --git a/esphome/components/ruuvitag/ruuvitag.cpp b/esphome/components/ruuvitag/ruuvitag.cpp index f4e4a72270..9b462b4794 100644 --- a/esphome/components/ruuvitag/ruuvitag.cpp +++ b/esphome/components/ruuvitag/ruuvitag.cpp @@ -1,7 +1,7 @@ #include "ruuvitag.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ruuvitag { diff --git a/esphome/components/ruuvitag/ruuvitag.h b/esphome/components/ruuvitag/ruuvitag.h index 863c5775c2..63029ebb4d 100644 --- a/esphome/components/ruuvitag/ruuvitag.h +++ b/esphome/components/ruuvitag/ruuvitag.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/ruuvi_ble/ruuvi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ruuvitag { diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index 8dfd992abe..30775fdea4 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -1,5 +1,10 @@ #include "scd30.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" + +#ifdef USE_ESP8266 +#include +#endif namespace esphome { namespace scd30 { @@ -23,7 +28,7 @@ static const uint16_t SCD30_CMD_SOFT_RESET = 0xD304; void SCD30Component::setup() { ESP_LOGCONFIG(TAG, "Setting up scd30..."); -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 Wire.setClockStretchLimit(150000); #endif @@ -51,7 +56,7 @@ void SCD30Component::setup() { return; } } -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 // According ESP32 clock stretching is typically 30ms and up to 150ms "due to // internal calibration processes". The I2C peripheral only supports 13ms (at // least when running at 80MHz). @@ -73,7 +78,7 @@ void SCD30Component::setup() { return; } } -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 delay(30); #endif @@ -83,7 +88,7 @@ void SCD30Component::setup() { this->mark_failed(); return; } -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 delay(30); #endif @@ -193,7 +198,7 @@ bool SCD30Component::write_command_(uint16_t command, uint16_t data) { raw[2] = data >> 8; raw[3] = data & 0xFF; raw[4] = sht_crc_(raw[2], raw[3]); - return this->write_bytes_raw(raw, 5); + return this->write(raw, 5); } uint8_t SCD30Component::sht_crc_(uint8_t data1, uint8_t data2) { @@ -223,7 +228,7 @@ bool SCD30Component::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { + if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { return false; } diff --git a/esphome/components/sdp3x/sdp3x.cpp b/esphome/components/sdp3x/sdp3x.cpp index 5e6c5887ac..ba7a028f8e 100644 --- a/esphome/components/sdp3x/sdp3x.cpp +++ b/esphome/components/sdp3x/sdp3x.cpp @@ -17,29 +17,29 @@ void SDP3XComponent::update() { this->read_pressure_(); } void SDP3XComponent::setup() { ESP_LOGD(TAG, "Setting up SDP3X..."); - if (!this->write_bytes_raw(SDP3X_STOP_MEAS, 2)) { + if (this->write(SDP3X_STOP_MEAS, 2) != i2c::ERROR_OK) { ESP_LOGW(TAG, "Stop SDP3X failed!"); // This sometimes fails for no good reason } - if (!this->write_bytes_raw(SDP3X_SOFT_RESET, 2)) { + if (this->write(SDP3X_SOFT_RESET, 2) != i2c::ERROR_OK) { ESP_LOGW(TAG, "Soft Reset SDP3X failed!"); // This sometimes fails for no good reason } delay_microseconds_accurate(20000); - if (!this->write_bytes_raw(SDP3X_READ_ID1, 2)) { + if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Read ID1 SDP3X failed!"); this->mark_failed(); return; } - if (!this->write_bytes_raw(SDP3X_READ_ID2, 2)) { + if (this->write(SDP3X_READ_ID2, 2) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Read ID2 SDP3X failed!"); this->mark_failed(); return; } uint8_t data[18]; - if (!this->read_bytes_raw(data, 18)) { + if (this->read(data, 18) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Read ID SDP3X failed!"); this->mark_failed(); return; @@ -59,7 +59,7 @@ void SDP3XComponent::setup() { pressure_scale_factor_ = 240.0f * 100.0f; } - if (!this->write_bytes_raw(SDP3X_START_DP_AVG, 2)) { + if (this->write(SDP3X_START_DP_AVG, 2) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Start Measurements SDP3X failed!"); this->mark_failed(); return; @@ -77,7 +77,7 @@ void SDP3XComponent::dump_config() { void SDP3XComponent::read_pressure_() { uint8_t data[9]; - if (!this->read_bytes_raw(data, 9)) { + if (this->read(data, 9) != i2c::ERROR_OK) { ESP_LOGW(TAG, "Couldn't read SDP3X data!"); this->status_set_warning(); return; diff --git a/esphome/components/sensor/automation.h b/esphome/components/sensor/automation.h index c70fb93963..8cd0adbeb2 100644 --- a/esphome/components/sensor/automation.h +++ b/esphome/components/sensor/automation.h @@ -40,7 +40,7 @@ class ValueRangeTrigger : public Trigger, public Component { template void set_max(V max) { this->max_ = max; } void setup() override { - this->rtc_ = global_preferences.make_preference(this->parent_->get_object_id_hash()); + this->rtc_ = global_preferences->make_preference(this->parent_->get_object_id_hash()); bool initial_state; if (this->rtc_.load(&initial_state)) { this->previous_in_range_ = initial_state; @@ -52,18 +52,18 @@ class ValueRangeTrigger : public Trigger, public Component { protected: void on_state_(float state) { - if (isnan(state)) + if (std::isnan(state)) return; float local_min = this->min_.value(state); float local_max = this->max_.value(state); bool in_range; - if (isnan(local_min) && isnan(local_max)) { + if (std::isnan(local_min) && std::isnan(local_max)) { in_range = this->previous_in_range_; - } else if (isnan(local_min)) { + } else if (std::isnan(local_min)) { in_range = state <= local_max; - } else if (isnan(local_max)) { + } else if (std::isnan(local_max)) { in_range = state >= local_min; } else { in_range = local_min <= state && state <= local_max; @@ -92,9 +92,9 @@ template class SensorInRangeCondition : public Condition void set_max(float max) { this->max_ = max; } bool check(Ts... x) override { const float state = this->parent_->state; - if (isnan(this->min_)) { + if (std::isnan(this->min_)) { return state <= this->max_; - } else if (isnan(this->max_)) { + } else if (std::isnan(this->max_)) { return state >= this->min_; } else { return this->min_ <= state && state <= this->max_; diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index f048189959..63801e7996 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -1,6 +1,7 @@ #include "filter.h" #include "sensor.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace sensor { @@ -35,7 +36,7 @@ MedianFilter::MedianFilter(size_t window_size, size_t send_every, size_t send_fi void MedianFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; } void MedianFilter::set_window_size(size_t window_size) { this->window_size_ = window_size; } optional MedianFilter::new_value(float value) { - if (!isnan(value)) { + if (!std::isnan(value)) { while (this->queue_.size() >= this->window_size_) { this->queue_.pop_front(); } @@ -71,7 +72,7 @@ MinFilter::MinFilter(size_t window_size, size_t send_every, size_t send_first_at void MinFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; } void MinFilter::set_window_size(size_t window_size) { this->window_size_ = window_size; } optional MinFilter::new_value(float value) { - if (!isnan(value)) { + if (!std::isnan(value)) { while (this->queue_.size() >= this->window_size_) { this->queue_.pop_front(); } @@ -100,7 +101,7 @@ MaxFilter::MaxFilter(size_t window_size, size_t send_every, size_t send_first_at void MaxFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; } void MaxFilter::set_window_size(size_t window_size) { this->window_size_ = window_size; } optional MaxFilter::new_value(float value) { - if (!isnan(value)) { + if (!std::isnan(value)) { while (this->queue_.size() >= this->window_size_) { this->queue_.pop_front(); } @@ -130,7 +131,7 @@ SlidingWindowMovingAverageFilter::SlidingWindowMovingAverageFilter(size_t window void SlidingWindowMovingAverageFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; } void SlidingWindowMovingAverageFilter::set_window_size(size_t window_size) { this->window_size_ = window_size; } optional SlidingWindowMovingAverageFilter::new_value(float value) { - if (!isnan(value)) { + if (!std::isnan(value)) { if (this->queue_.size() == this->window_size_) { this->sum_ -= this->queue_[0]; this->queue_.pop_front(); @@ -165,7 +166,7 @@ optional SlidingWindowMovingAverageFilter::new_value(float value) { ExponentialMovingAverageFilter::ExponentialMovingAverageFilter(float alpha, size_t send_every) : send_every_(send_every), send_at_(send_every - 1), alpha_(alpha) {} optional ExponentialMovingAverageFilter::new_value(float value) { - if (!isnan(value)) { + if (!std::isnan(value)) { if (this->first_value_) this->accumulator_ = value; else @@ -211,8 +212,8 @@ optional MultiplyFilter::new_value(float value) { return value * this->mu FilterOutValueFilter::FilterOutValueFilter(float value_to_filter_out) : value_to_filter_out_(value_to_filter_out) {} optional FilterOutValueFilter::new_value(float value) { - if (isnan(this->value_to_filter_out_)) { - if (isnan(value)) + if (std::isnan(this->value_to_filter_out_)) { + if (std::isnan(value)) return {}; else return value; @@ -243,9 +244,9 @@ optional ThrottleFilter::new_value(float value) { // DeltaFilter DeltaFilter::DeltaFilter(float min_delta) : min_delta_(min_delta), last_value_(NAN) {} optional DeltaFilter::new_value(float value) { - if (isnan(value)) + if (std::isnan(value)) return {}; - if (isnan(this->last_value_)) { + if (std::isnan(this->last_value_)) { return this->last_value_ = value; } if (fabsf(value - this->last_value_) >= this->min_delta_) { diff --git a/esphome/components/servo/servo.cpp b/esphome/components/servo/servo.cpp index 0b018ddb2e..2e1ba587a2 100644 --- a/esphome/components/servo/servo.cpp +++ b/esphome/components/servo/servo.cpp @@ -1,5 +1,6 @@ #include "servo.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace servo { diff --git a/esphome/components/servo/servo.h b/esphome/components/servo/servo.h index d95a524a8b..e2e3823158 100644 --- a/esphome/components/servo/servo.h +++ b/esphome/components/servo/servo.h @@ -24,7 +24,7 @@ class Servo : public Component { void setup() override { float v; if (this->restore_) { - this->rtc_ = global_preferences.make_preference(global_servo_id); + this->rtc_ = global_preferences->make_preference(global_servo_id); global_servo_id++; if (this->rtc_.load(&v)) { this->output_->set_level(v); diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index 5a6f438429..1a64a12907 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -1,6 +1,7 @@ #include "sgp30.h" #include "esphome/core/log.h" #include "esphome/core/application.h" +#include namespace esphome { namespace sgp30 { @@ -84,7 +85,7 @@ void SGP30Component::setup() { // Hash with compilation time // This ensures the baseline storage is cleared after OTA uint32_t hash = fnv1_hash(App.get_compilation_time()); - this->pref_ = global_preferences.make_preference(hash, true); + this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->baselines_storage_)) { ESP_LOGI(TAG, "Loaded eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", this->baselines_storage_.eco2, @@ -172,7 +173,7 @@ void SGP30Component::send_env_data_() { float humidity = NAN; if (this->humidity_sensor_ != nullptr) humidity = this->humidity_sensor_->state; - if (isnan(humidity) || humidity < 0.0f || humidity > 100.0f) { + if (std::isnan(humidity) || humidity < 0.0f || humidity > 100.0f) { ESP_LOGW(TAG, "Compensation not possible yet: bad humidity data."); return; } else { @@ -182,7 +183,7 @@ void SGP30Component::send_env_data_() { if (this->temperature_sensor_ != nullptr) { temperature = float(this->temperature_sensor_->state); } - if (isnan(temperature) || temperature < -40.0f || temperature > 85.0f) { + if (std::isnan(temperature) || temperature < -40.0f || temperature > 85.0f) { ESP_LOGW(TAG, "Compensation not possible yet: bad temperature value data."); return; } else { @@ -334,7 +335,7 @@ bool SGP30Component::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { + if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { return false; } diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp index 1a1909eeb0..fddd0255b8 100644 --- a/esphome/components/sgp40/sgp40.cpp +++ b/esphome/components/sgp40/sgp40.cpp @@ -1,5 +1,6 @@ -#include "esphome/core/log.h" #include "sgp40.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" #include namespace esphome { @@ -55,7 +56,7 @@ void SGP40Component::setup() { // Hash with compilation time // This ensures the baseline storage is cleared after OTA uint32_t hash = fnv1_hash(App.get_compilation_time()); - this->pref_ = global_preferences.make_preference(hash, true); + this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->baselines_storage_)) { this->state0_ = this->baselines_storage_.state0; @@ -165,7 +166,7 @@ uint16_t SGP40Component::measure_raw_() { if (this->humidity_sensor_ != nullptr) { humidity = this->humidity_sensor_->state; } - if (isnan(humidity) || humidity < 0.0f || humidity > 100.0f) { + if (std::isnan(humidity) || humidity < 0.0f || humidity > 100.0f) { humidity = 50; } @@ -173,7 +174,7 @@ uint16_t SGP40Component::measure_raw_() { if (this->temperature_sensor_ != nullptr) { temperature = float(this->temperature_sensor_->state); } - if (isnan(temperature) || temperature < -40.0f || temperature > 85.0f) { + if (std::isnan(temperature) || temperature < -40.0f || temperature > 85.0f) { temperature = 25; } @@ -191,9 +192,9 @@ uint16_t SGP40Component::measure_raw_() { command[6] = tempticks & 0xFF; command[7] = generate_crc_(command + 5, 2); - if (!this->write_bytes_raw(command, 8)) { + if (this->write(command, 8) != i2c::ERROR_OK) { this->status_set_warning(); - ESP_LOGD(TAG, "write_bytes_raw error"); + ESP_LOGD(TAG, "write error"); return UINT16_MAX; } delay(250); // NOLINT @@ -302,7 +303,7 @@ bool SGP40Component::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { + if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { return false; } diff --git a/esphome/components/sht3xd/sht3xd.cpp b/esphome/components/sht3xd/sht3xd.cpp index be5b93c124..56a43d5161 100644 --- a/esphome/components/sht3xd/sht3xd.cpp +++ b/esphome/components/sht3xd/sht3xd.cpp @@ -103,7 +103,7 @@ bool SHT3XDComponent::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { + if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { return false; } diff --git a/esphome/components/sht4x/sht4x.cpp b/esphome/components/sht4x/sht4x.cpp index 6d7c917b57..248f32c4de 100644 --- a/esphome/components/sht4x/sht4x.cpp +++ b/esphome/components/sht4x/sht4x.cpp @@ -12,7 +12,7 @@ void SHT4XComponent::start_heater_() { uint8_t cmd[] = {MEASURECOMMANDS[this->heater_command_]}; ESP_LOGD(TAG, "Heater turning on"); - this->write_bytes_raw(cmd, 1); + this->write(cmd, 1); } void SHT4XComponent::setup() { @@ -53,7 +53,7 @@ void SHT4XComponent::update() { uint8_t cmd[] = {MEASURECOMMANDS[this->precision_]}; // Send command - this->write_bytes_raw(cmd, 1); + this->write(cmd, 1); this->set_timeout(10, [this]() { const uint8_t num_bytes = 6; diff --git a/esphome/components/shtcx/shtcx.cpp b/esphome/components/shtcx/shtcx.cpp index db3a3bf803..f2fb6bd5c3 100644 --- a/esphome/components/shtcx/shtcx.cpp +++ b/esphome/components/shtcx/shtcx.cpp @@ -1,5 +1,6 @@ #include "shtcx.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace shtcx { @@ -132,7 +133,7 @@ bool SHTCXComponent::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { + if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { return false; } diff --git a/esphome/components/shutdown/shutdown_switch.cpp b/esphome/components/shutdown/shutdown_switch.cpp index 87b755cab6..0e5853cc46 100644 --- a/esphome/components/shutdown/shutdown_switch.cpp +++ b/esphome/components/shutdown/shutdown_switch.cpp @@ -2,6 +2,13 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" +#ifdef USE_ESP32 +#include +#endif +#ifdef USE_ESP8266 +#include +#endif + namespace esphome { namespace shutdown { @@ -17,10 +24,10 @@ void ShutdownSwitch::write_state(bool state) { delay(100); // NOLINT App.run_safe_shutdown_hooks(); -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 ESP.deepSleep(0); // NOLINT(readability-static-accessed-through-instance) #endif -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 esp_deep_sleep_start(); #endif } diff --git a/esphome/components/slow_pwm/slow_pwm_output.h b/esphome/components/slow_pwm/slow_pwm_output.h index 4a2c1d0a14..f0524f36d8 100644 --- a/esphome/components/slow_pwm/slow_pwm_output.h +++ b/esphome/components/slow_pwm/slow_pwm_output.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/output/float_output.h" namespace esphome { diff --git a/esphome/components/sm16716/sm16716.h b/esphome/components/sm16716/sm16716.h index 85f78c8cf5..73414c0003 100644 --- a/esphome/components/sm16716/sm16716.h +++ b/esphome/components/sm16716/sm16716.h @@ -1,8 +1,9 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/output/float_output.h" +#include namespace esphome { namespace sm16716 { diff --git a/esphome/components/sm2135/sm2135.h b/esphome/components/sm2135/sm2135.h index e39730579f..0277e9ba1c 100644 --- a/esphome/components/sm2135/sm2135.h +++ b/esphome/components/sm2135/sm2135.h @@ -1,8 +1,9 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/output/float_output.h" +#include namespace esphome { namespace sm2135 { diff --git a/esphome/components/sn74hc595/__init__.py b/esphome/components/sn74hc595/__init__.py index 4437878970..0d1ff6ecba 100644 --- a/esphome/components/sn74hc595/__init__.py +++ b/esphome/components/sn74hc595/__init__.py @@ -3,10 +3,12 @@ import esphome.config_validation as cv from esphome import pins from esphome.const import ( CONF_ID, + CONF_MODE, CONF_NUMBER, CONF_INVERTED, CONF_DATA_PIN, CONF_CLOCK_PIN, + CONF_OUTPUT, ) DEPENDENCIES = [] @@ -48,19 +50,36 @@ async def to_code(config): cg.add(var.set_sr_count(config[CONF_SR_COUNT])) -SN74HC595_OUTPUT_PIN_SCHEMA = cv.Schema( +def _validate_output_mode(value): + if value is not True: + raise cv.Invalid("Only output mode is supported") + return value + + +SN74HC595_PIN_SCHEMA = cv.All( { + cv.GenerateID(): cv.declare_id(SN74HC595GPIOPin), cv.Required(CONF_SN74HC595): cv.use_id(SN74HC595Component), - cv.Required(CONF_NUMBER): cv.int_, + cv.Required(CONF_NUMBER): cv.int_range(min=0, max=7), + cv.Optional(CONF_MODE, default={}): cv.All( + { + cv.Optional(CONF_OUTPUT, default=True): cv.All( + cv.boolean, _validate_output_mode + ), + }, + ), cv.Optional(CONF_INVERTED, default=False): cv.boolean, } ) -SN74HC595_INPUT_PIN_SCHEMA = cv.Schema({}) -@pins.PIN_SCHEMA_REGISTRY.register( - CONF_SN74HC595, (SN74HC595_OUTPUT_PIN_SCHEMA, SN74HC595_INPUT_PIN_SCHEMA) -) +@pins.PIN_SCHEMA_REGISTRY.register(CONF_SN74HC595, SN74HC595_PIN_SCHEMA) async def sn74hc595_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_SN74HC595]) - return SN74HC595GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_INVERTED]) + cg.add(var.set_parent(parent)) + + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + return var diff --git a/esphome/components/sn74hc595/sn74hc595.cpp b/esphome/components/sn74hc595/sn74hc595.cpp index 596ebf755d..5ebf50e5cb 100644 --- a/esphome/components/sn74hc595/sn74hc595.cpp +++ b/esphome/components/sn74hc595/sn74hc595.cpp @@ -10,17 +10,17 @@ void SN74HC595Component::setup() { ESP_LOGCONFIG(TAG, "Setting up SN74HC595..."); if (this->have_oe_pin_) { // disable output - this->oe_pin_->pin_mode(OUTPUT); + this->oe_pin_->setup(); this->oe_pin_->digital_write(true); } // initialize output pins - this->clock_pin_->pin_mode(OUTPUT); - this->data_pin_->pin_mode(OUTPUT); - this->latch_pin_->pin_mode(OUTPUT); - this->clock_pin_->digital_write(LOW); - this->data_pin_->digital_write(LOW); - this->latch_pin_->digital_write(LOW); + this->clock_pin_->setup(); + this->data_pin_->setup(); + this->latch_pin_->setup(); + this->clock_pin_->digital_write(false); + this->data_pin_->digital_write(false); + this->latch_pin_->digital_write(false); // send state to shift register this->write_gpio_(); @@ -62,16 +62,14 @@ bool SN74HC595Component::write_gpio_() { float SN74HC595Component::get_setup_priority() const { return setup_priority::IO; } -void SN74HC595GPIOPin::setup() {} - -bool SN74HC595GPIOPin::digital_read() { return this->parent_->digital_read_(this->pin_) != this->inverted_; } - void SN74HC595GPIOPin::digital_write(bool value) { this->parent_->digital_write_(this->pin_, value != this->inverted_); } - -SN74HC595GPIOPin::SN74HC595GPIOPin(SN74HC595Component *parent, uint8_t pin, bool inverted) - : GPIOPin(pin, OUTPUT, inverted), parent_(parent) {} +std::string SN74HC595GPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%u via SN74HC595", pin_); + return buffer; +} } // namespace sn74hc595 } // namespace esphome diff --git a/esphome/components/sn74hc595/sn74hc595.h b/esphome/components/sn74hc595/sn74hc595.h index d6f9a68bc8..784019c3a6 100644 --- a/esphome/components/sn74hc595/sn74hc595.h +++ b/esphome/components/sn74hc595/sn74hc595.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" namespace esphome { namespace sn74hc595 { @@ -41,14 +41,20 @@ class SN74HC595Component : public Component { /// Helper class to expose a SC74HC595 pin as an internal output GPIO pin. class SN74HC595GPIOPin : public GPIOPin { public: - SN74HC595GPIOPin(SN74HC595Component *parent, uint8_t pin, bool inverted = false); - - void setup() override; - bool digital_read() override; + void setup() override {} + void pin_mode(gpio::Flags flags) override {} + bool digital_read() override { return false; } void digital_write(bool value) override; + std::string dump_summary() const override; + + void set_parent(SN74HC595Component *parent) { parent_ = parent; } + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } protected: SN74HC595Component *parent_; + uint8_t pin_; + bool inverted_; }; } // namespace sn74hc595 diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index 895c775b19..2b6cd10e80 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -1,10 +1,10 @@ #include "sntp_component.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include "lwip/apps/sntp.h" #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include "sntp.h" #endif @@ -20,13 +20,13 @@ static const char *const TAG = "sntp"; void SNTPComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up SNTP..."); -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 if (sntp_enabled()) { sntp_stop(); } sntp_setoperatingmode(SNTP_OPMODE_POLL); #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 sntp_stop(); #endif diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index aa1bcd3b3c..1db24973e7 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -6,8 +6,9 @@ #include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include +#include #endif namespace esphome { @@ -81,7 +82,7 @@ class BSDSocketImpl : public Socket { int listen(int backlog) override { return ::listen(fd_, backlog); } ssize_t read(void *buf, size_t len) override { return ::read(fd_, buf, len); } ssize_t readv(const struct iovec *iov, int iovcnt) override { -#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR < 4 +#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 4 // esp-idf v3 doesn't have readv, emulate it ssize_t ret = 0; for (int i = 0; i < iovcnt; i++) { @@ -97,6 +98,9 @@ class BSDSocketImpl : public Socket { break; } return ret; +#elif defined(USE_ESP32) + // ESP-IDF v4 only has symbol lwip_readv + return ::lwip_readv(fd_, iov, iovcnt); #else return ::readv(fd_, iov, iovcnt); #endif @@ -104,7 +108,7 @@ class BSDSocketImpl : public Socket { ssize_t write(const void *buf, size_t len) override { return ::write(fd_, buf, len); } ssize_t send(void *buf, size_t len, int flags) { return ::send(fd_, buf, len, flags); } ssize_t writev(const struct iovec *iov, int iovcnt) override { -#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR < 4 +#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 4 // esp-idf v3 doesn't have writev, emulate it ssize_t ret = 0; for (int i = 0; i < iovcnt; i++) { @@ -121,6 +125,9 @@ class BSDSocketImpl : public Socket { break; } return ret; +#elif defined(USE_ESP32) + // ESP-IDF v4 only has symbol lwip_writev + return ::lwip_writev(fd_, iov, iovcnt); #else return ::writev(fd_, iov, iovcnt); #endif diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h index fbe8f929a0..f9697fd421 100644 --- a/esphome/components/socket/headers.h +++ b/esphome/components/socket/headers.h @@ -86,7 +86,7 @@ struct iovec { size_t iov_len; }; -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 // arduino-esp8266 declares a global vars called INADDR_NONE/ANY which are invalid with the define #ifdef INADDR_ANY #undef INADDR_ANY @@ -97,7 +97,7 @@ struct iovec { #define ESPHOME_INADDR_ANY ((uint32_t) 0x00000000UL) #define ESPHOME_INADDR_NONE ((uint32_t) 0xFFFFFFFFUL) -#else // !ARDUINO_ARCH_ESP8266 +#else // !USE_ESP8266 #define ESPHOME_INADDR_ANY INADDR_ANY #define ESPHOME_INADDR_NONE INADDR_NONE #endif @@ -114,7 +114,7 @@ struct iovec { #include #include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ARDUINO // arduino-esp32 declares a global var called INADDR_NONE which is replaced // by the define #ifdef INADDR_NONE @@ -125,7 +125,7 @@ typedef uint32_t socklen_t; #define ESPHOME_INADDR_ANY ((uint32_t) 0x00000000UL) #define ESPHOME_INADDR_NONE ((uint32_t) 0xFFFFFFFFUL) -#else // !ARDUINO_ARCH_ESP32 +#else // !USE_ESP32 #define ESPHOME_INADDR_ANY INADDR_ANY #define ESPHOME_INADDR_NONE INADDR_NONE #endif diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 366f0972ef..7521d1339f 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -11,6 +11,7 @@ #include #include +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -492,7 +493,7 @@ class LWIPRawImpl : public Socket { // nothing to do here, we just don't push it to the queue return ERR_OK; } - auto sock = std::unique_ptr(new LWIPRawImpl(newpcb)); + auto sock = make_unique(newpcb); sock->init(); accepted_sockets_.push(std::move(sock)); return ERR_OK; diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index 3180447711..d883142c81 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -8,12 +8,13 @@ namespace spi { static const char *const TAG = "spi"; -void ICACHE_RAM_ATTR HOT SPIComponent::disable() { +void IRAM_ATTR HOT SPIComponent::disable() { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { this->hw_spi_->endTransaction(); } +#endif // USE_SPI_ARDUINO_BACKEND if (this->active_cs_) { - ESP_LOGVV(TAG, "Disabling SPI Chip on pin %u...", this->active_cs_->get_pin()); this->active_cs_->digital_write(true); this->active_cs_ = nullptr; } @@ -23,19 +24,37 @@ void SPIComponent::setup() { this->clk_->setup(); this->clk_->digital_write(true); +#ifdef USE_SPI_ARDUINO_BACKEND bool use_hw_spi = true; - if (this->clk_->is_inverted()) - use_hw_spi = false; const bool has_miso = this->miso_ != nullptr; const bool has_mosi = this->mosi_ != nullptr; - if (has_miso && this->miso_->is_inverted()) + int8_t clk_pin = -1, miso_pin = -1, mosi_pin = -1; + + if (!this->clk_->is_internal()) use_hw_spi = false; - if (has_mosi && this->mosi_->is_inverted()) + if (has_miso && !miso_->is_internal()) use_hw_spi = false; - int8_t clk_pin = this->clk_->get_pin(); - int8_t miso_pin = has_miso ? this->miso_->get_pin() : -1; - int8_t mosi_pin = has_mosi ? this->mosi_->get_pin() : -1; -#ifdef ARDUINO_ARCH_ESP8266 + if (has_mosi && !mosi_->is_internal()) + use_hw_spi = false; + if (use_hw_spi) { + auto *clk_internal = (InternalGPIOPin *) clk_; + auto *miso_internal = (InternalGPIOPin *) miso_; + auto *mosi_internal = (InternalGPIOPin *) mosi_; + + if (clk_internal->is_inverted()) + use_hw_spi = false; + if (has_miso && miso_internal->is_inverted()) + use_hw_spi = false; + if (has_mosi && mosi_internal->is_inverted()) + use_hw_spi = false; + + if (use_hw_spi) { + clk_pin = clk_internal->get_pin(); + miso_pin = has_miso ? miso_internal->get_pin() : -1; + mosi_pin = has_mosi ? mosi_internal->get_pin() : -1; + } + } +#ifdef USE_ESP8266 if (clk_pin == 6 && miso_pin == 7 && mosi_pin == 8) { // pass } else if (clk_pin == 14 && (!has_miso || miso_pin == 12) && (!has_mosi || mosi_pin == 13)) { @@ -50,8 +69,8 @@ void SPIComponent::setup() { this->hw_spi_->begin(); return; } -#endif -#ifdef ARDUINO_ARCH_ESP32 +#endif // USE_ESP8266 +#ifdef USE_ESP32 static uint8_t spi_bus_num = 0; if (spi_bus_num >= 2) { use_hw_spi = false; @@ -67,7 +86,8 @@ void SPIComponent::setup() { this->hw_spi_->begin(clk_pin, miso_pin, mosi_pin); return; } -#endif +#endif // USE_ESP32 +#endif // USE_SPI_ARDUINO_BACKEND if (this->miso_ != nullptr) { this->miso_->setup(); @@ -82,32 +102,28 @@ void SPIComponent::dump_config() { LOG_PIN(" CLK Pin: ", this->clk_); LOG_PIN(" MISO Pin: ", this->miso_); LOG_PIN(" MOSI Pin: ", this->mosi_); +#ifdef USE_SPI_ARDUINO_BACKEND ESP_LOGCONFIG(TAG, " Using HW SPI: %s", YESNO(this->hw_spi_ != nullptr)); +#endif // USE_SPI_ARDUINO_BACKEND } float SPIComponent::get_setup_priority() const { return setup_priority::BUS; } -void SPIComponent::debug_tx(uint8_t value) { - ESP_LOGVV(TAG, " TX 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(value), value); -} -void SPIComponent::debug_rx(uint8_t value) { - ESP_LOGVV(TAG, " RX 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(value), value); -} -void SPIComponent::debug_enable(uint8_t pin) { ESP_LOGVV(TAG, "Enabling SPI Chip on pin %u...", pin); } - void SPIComponent::cycle_clock_(bool value) { - uint32_t start = ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) - while (start - ESP.getCycleCount() < this->wait_cycle_) // NOLINT(readability-static-accessed-through-instance) + uint32_t start = arch_get_cpu_cycle_count(); + while (start - arch_get_cpu_cycle_count() < this->wait_cycle_) ; this->clk_->digital_write(value); start += this->wait_cycle_; - while (start - ESP.getCycleCount() < this->wait_cycle_) // NOLINT(readability-static-accessed-through-instance) + while (start - arch_get_cpu_cycle_count() < this->wait_cycle_) ; } // NOLINTNEXTLINE +#ifndef CLANG_TIDY #pragma GCC optimize("unroll-loops") // NOLINTNEXTLINE #pragma GCC optimize("O2") +#endif // CLANG_TIDY template uint8_t HOT SPIComponent::transfer_(uint8_t data) { @@ -153,15 +169,6 @@ uint8_t HOT SPIComponent::transfer_(uint8_t data) { } } -#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE - if (WRITE) { - SPIComponent::debug_tx(data); - } - if (READ) { - SPIComponent::debug_rx(out_data); - } -#endif - App.feed_wdt(); return out_data; diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index eb8f9ce7ce..601a5c5a7e 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -1,8 +1,16 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" +#include + +#ifdef USE_ARDUINO +#define USE_SPI_ARDUINO_BACKEND +#endif + +#ifdef USE_SPI_ARDUINO_BACKEND #include +#endif namespace esphome { namespace spi { @@ -72,18 +80,22 @@ class SPIComponent : public Component { void dump_config() override; template uint8_t read_byte() { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { return this->hw_spi_->transfer(0x00); } +#endif // USE_SPI_ARDUINO_BACKEND return this->transfer_(0x00); } template void read_array(uint8_t *data, size_t length) { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { this->hw_spi_->transfer(data, length); return; } +#endif // USE_SPI_ARDUINO_BACKEND for (size_t i = 0; i < length; i++) { data[i] = this->read_byte(); } @@ -91,19 +103,23 @@ class SPIComponent : public Component { template void write_byte(uint8_t data) { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { this->hw_spi_->write(data); return; } +#endif // USE_SPI_ARDUINO_BACKEND this->transfer_(data); } template void write_byte16(const uint16_t data) { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { this->hw_spi_->write16(data); return; } +#endif // USE_SPI_ARDUINO_BACKEND this->write_byte(data >> 8); this->write_byte(data); @@ -111,12 +127,14 @@ class SPIComponent : public Component { template void write_array16(const uint16_t *data, size_t length) { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { for (size_t i = 0; i < length; i++) { this->hw_spi_->write16(data[i]); } return; } +#endif // USE_SPI_ARDUINO_BACKEND for (size_t i = 0; i < length; i++) { this->write_byte16(data[i]); } @@ -124,11 +142,13 @@ class SPIComponent : public Component { template void write_array(const uint8_t *data, size_t length) { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { auto *data_c = const_cast(data); this->hw_spi_->writeBytes(data_c, length); return; } +#endif // USE_SPI_ARDUINO_BACKEND for (size_t i = 0; i < length; i++) { this->write_byte(data[i]); } @@ -136,6 +156,7 @@ class SPIComponent : public Component { template uint8_t transfer_byte(uint8_t data) { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->miso_ != nullptr) { if (this->hw_spi_ != nullptr) { return this->hw_spi_->transfer(data); @@ -143,12 +164,14 @@ class SPIComponent : public Component { return this->transfer_(data); } } +#endif // USE_SPI_ARDUINO_BACKEND this->write_byte(data); return 0; } template void transfer_array(uint8_t *data, size_t length) { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { if (this->miso_ != nullptr) { this->hw_spi_->transfer(data, length); @@ -157,6 +180,7 @@ class SPIComponent : public Component { } return; } +#endif // USE_SPI_ARDUINO_BACKEND if (this->miso_ != nullptr) { for (size_t i = 0; i < length; i++) { @@ -169,18 +193,19 @@ class SPIComponent : public Component { template void enable(GPIOPin *cs) { - if (cs != nullptr) { - SPIComponent::debug_enable(cs->get_pin()); - } - +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { uint8_t data_mode = (uint8_t(CLOCK_POLARITY) << 1) | uint8_t(CLOCK_PHASE); SPISettings settings(DATA_RATE, BIT_ORDER, data_mode); this->hw_spi_->beginTransaction(settings); } else { +#endif // USE_SPI_ARDUINO_BACKEND this->clk_->digital_write(CLOCK_POLARITY); - this->wait_cycle_ = uint32_t(F_CPU) / DATA_RATE / 2ULL; + uint32_t cpu_freq_hz = arch_get_cpu_freq_hz(); + this->wait_cycle_ = uint32_t(cpu_freq_hz) / DATA_RATE / 2ULL; +#ifdef USE_SPI_ARDUINO_BACKEND } +#endif // USE_SPI_ARDUINO_BACKEND if (cs != nullptr) { this->active_cs_ = cs; @@ -195,10 +220,6 @@ class SPIComponent : public Component { protected: inline void cycle_clock_(bool value); - static void debug_enable(uint8_t pin); - static void debug_tx(uint8_t value); - static void debug_rx(uint8_t value); - template uint8_t transfer_(uint8_t data); @@ -206,7 +227,9 @@ class SPIComponent : public Component { GPIOPin *miso_{nullptr}; GPIOPin *mosi_{nullptr}; GPIOPin *active_cs_{nullptr}; +#ifdef USE_SPI_ARDUINO_BACKEND SPIClass *hw_spi_{nullptr}; +#endif // USE_SPI_ARDUINO_BACKEND uint32_t wait_cycle_; }; diff --git a/esphome/components/sps30/sps30.cpp b/esphome/components/sps30/sps30.cpp index 3a31f4d607..472b7606ed 100644 --- a/esphome/components/sps30/sps30.cpp +++ b/esphome/components/sps30/sps30.cpp @@ -244,7 +244,7 @@ bool SPS30Component::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { + if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { return false; } diff --git a/esphome/components/ssd1306_base/ssd1306_base.h b/esphome/components/ssd1306_base/ssd1306_base.h index 0fe09709e7..2c54af7a67 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.h +++ b/esphome/components/ssd1306_base/ssd1306_base.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/display/display_buffer.h" namespace esphome { diff --git a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp index fce9796008..45fda4870e 100644 --- a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp +++ b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp @@ -10,8 +10,8 @@ void I2CSSD1306::setup() { ESP_LOGCONFIG(TAG, "Setting up I2C SSD1306..."); this->init_reset_(); - this->raw_begin_transmission(); - if (!this->raw_end_transmission()) { + auto err = this->write(nullptr, 0); + if (err != i2c::ERROR_OK) { this->error_code_ = COMMUNICATION_FAILED; this->mark_failed(); return; diff --git a/esphome/components/ssd1322_base/ssd1322_base.h b/esphome/components/ssd1322_base/ssd1322_base.h index 125e374246..6a790c0199 100644 --- a/esphome/components/ssd1322_base/ssd1322_base.h +++ b/esphome/components/ssd1322_base/ssd1322_base.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/display/display_buffer.h" namespace esphome { diff --git a/esphome/components/ssd1325_base/ssd1325_base.h b/esphome/components/ssd1325_base/ssd1325_base.h index a06ba69a59..cca9412c43 100644 --- a/esphome/components/ssd1325_base/ssd1325_base.h +++ b/esphome/components/ssd1325_base/ssd1325_base.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/display/display_buffer.h" namespace esphome { diff --git a/esphome/components/ssd1327_base/ssd1327_base.h b/esphome/components/ssd1327_base/ssd1327_base.h index 03f360b258..35b021c71b 100644 --- a/esphome/components/ssd1327_base/ssd1327_base.h +++ b/esphome/components/ssd1327_base/ssd1327_base.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/display/display_buffer.h" namespace esphome { diff --git a/esphome/components/ssd1327_i2c/ssd1327_i2c.cpp b/esphome/components/ssd1327_i2c/ssd1327_i2c.cpp index 2967d2f9c4..e9e047bfb6 100644 --- a/esphome/components/ssd1327_i2c/ssd1327_i2c.cpp +++ b/esphome/components/ssd1327_i2c/ssd1327_i2c.cpp @@ -10,8 +10,8 @@ void I2CSSD1327::setup() { ESP_LOGCONFIG(TAG, "Setting up I2C SSD1327..."); this->init_reset_(); - this->raw_begin_transmission(); - if (!this->raw_end_transmission()) { + auto err = this->write(nullptr, 0); + if (err != i2c::ERROR_OK) { this->error_code_ = COMMUNICATION_FAILED; this->mark_failed(); return; diff --git a/esphome/components/ssd1331_base/ssd1331_base.h b/esphome/components/ssd1331_base/ssd1331_base.h index 8d2bca5de0..b889a47fbe 100644 --- a/esphome/components/ssd1331_base/ssd1331_base.h +++ b/esphome/components/ssd1331_base/ssd1331_base.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/display/display_buffer.h" namespace esphome { diff --git a/esphome/components/ssd1351_base/ssd1351_base.h b/esphome/components/ssd1351_base/ssd1351_base.h index 2730f798b5..422e601f8b 100644 --- a/esphome/components/ssd1351_base/ssd1351_base.h +++ b/esphome/components/ssd1351_base/ssd1351_base.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/display/display_buffer.h" namespace esphome { diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index 1f14136637..8490aa1fe4 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -1,6 +1,7 @@ #include "st7735.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" namespace esphome { namespace st7735 { @@ -353,17 +354,17 @@ void ST7735::display_init_(const uint8_t *addr) { uint8_t num_commands, cmd, num_args; uint16_t ms; - num_commands = pgm_read_byte(addr++); // Number of commands to follow - while (num_commands--) { // For each command... - cmd = pgm_read_byte(addr++); // Read command - num_args = pgm_read_byte(addr++); // Number of args to follow - ms = num_args & ST_CMD_DELAY; // If hibit set, delay follows args - num_args &= ~ST_CMD_DELAY; // Mask out delay bit + num_commands = progmem_read_byte(addr++); // Number of commands to follow + while (num_commands--) { // For each command... + cmd = progmem_read_byte(addr++); // Read command + num_args = progmem_read_byte(addr++); // Number of args to follow + ms = num_args & ST_CMD_DELAY; // If hibit set, delay follows args + num_args &= ~ST_CMD_DELAY; // Mask out delay bit this->sendcommand_(cmd, addr, num_args); addr += num_args; if (ms) { - ms = pgm_read_byte(addr++); // Read post-command delay time (ms) + ms = progmem_read_byte(addr++); // Read post-command delay time (ms) if (ms == 255) ms = 500; // If 255, delay for 500 ms delay(ms); @@ -410,7 +411,7 @@ void HOT ST7735::senddata_(const uint8_t *data_bytes, uint8_t num_data_bytes) { this->cs_->digital_write(false); this->enable(); for (uint8_t i = 0; i < num_data_bytes; i++) { - this->write_byte(pgm_read_byte(data_bytes++)); // write byte - SPI library + this->write_byte(progmem_read_byte(data_bytes++)); // write byte - SPI library } this->cs_->digital_write(true); this->disable(); diff --git a/esphome/components/status/status_binary_sensor.cpp b/esphome/components/status/status_binary_sensor.cpp index 152e3aff9d..1795a9c41b 100644 --- a/esphome/components/status/status_binary_sensor.cpp +++ b/esphome/components/status/status_binary_sensor.cpp @@ -1,6 +1,6 @@ #include "status_binary_sensor.h" #include "esphome/core/log.h" -#include "esphome/core/util.h" +#include "esphome/components/network/util.h" #include "esphome/core/defines.h" #ifdef USE_MQTT @@ -16,7 +16,7 @@ namespace status { static const char *const TAG = "status"; void StatusBinarySensor::loop() { - bool status = network_is_connected(); + bool status = network::is_connected(); #ifdef USE_MQTT if (mqtt::global_mqtt_client != nullptr) { status = status && mqtt::global_mqtt_client->is_connected(); diff --git a/esphome/components/status_led/light/status_led_light.h b/esphome/components/status_led/light/status_led_light.h index 8a7f4b4da8..e90d381e3c 100644 --- a/esphome/components/status_led/light/status_led_light.h +++ b/esphome/components/status_led/light/status_led_light.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/light/light_output.h" namespace esphome { diff --git a/esphome/components/status_led/status_led.h b/esphome/components/status_led/status_led.h index 79f9f524a3..490557f3e7 100644 --- a/esphome/components/status_led/status_led.h +++ b/esphome/components/status_led/status_led.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" namespace esphome { namespace status_led { diff --git a/esphome/components/stepper/stepper.cpp b/esphome/components/stepper/stepper.cpp index d7f6cc6dda..7926024204 100644 --- a/esphome/components/stepper/stepper.cpp +++ b/esphome/components/stepper/stepper.cpp @@ -1,5 +1,6 @@ #include "stepper.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace stepper { diff --git a/esphome/components/sts3x/sts3x.cpp b/esphome/components/sts3x/sts3x.cpp index 77383c6daa..b1ecbc98f8 100644 --- a/esphome/components/sts3x/sts3x.cpp +++ b/esphome/components/sts3x/sts3x.cpp @@ -99,7 +99,7 @@ bool STS3XComponent::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { + if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { return false; } diff --git a/esphome/components/sun/sun.h b/esphome/components/sun/sun.h index 6a8364a5f0..0a2e6bcf97 100644 --- a/esphome/components/sun/sun.h +++ b/esphome/components/sun/sun.h @@ -80,7 +80,7 @@ class SunTrigger : public Trigger<>, public PollingComponent, public Parentedparent_->elevation(); - if (isnan(current)) + if (std::isnan(current)) return; bool crossed; @@ -90,7 +90,7 @@ class SunTrigger : public Trigger<>, public PollingComponent, public Parentedlast_elevation_ >= this->elevation_ && this->elevation_ > current; } - if (crossed && !isnan(this->last_elevation_)) { + if (crossed && !std::isnan(this->last_elevation_)) { this->trigger(); } this->last_elevation_ = current; diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index c96f9a40d0..0e12f5af0f 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -30,7 +30,7 @@ void Switch::toggle() { this->write_state(this->inverted_ == this->state); } optional Switch::get_initial_state() { - this->rtc_ = global_preferences.make_preference(this->get_object_id_hash()); + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); bool initial_state; if (!this->rtc_.load(&initial_state)) return {}; diff --git a/esphome/components/sx1509/__init__.py b/esphome/components/sx1509/__init__.py index 8e1239924a..f1b7d5f424 100644 --- a/esphome/components/sx1509/__init__.py +++ b/esphome/components/sx1509/__init__.py @@ -2,7 +2,15 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins from esphome.components import i2c -from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED +from esphome.const import ( + CONF_ID, + CONF_INPUT, + CONF_NUMBER, + CONF_MODE, + CONF_INVERTED, + CONF_OUTPUT, + CONF_PULLUP, +) CONF_KEYPAD = "keypad" CONF_KEY_ROWS = "key_rows" @@ -10,17 +18,12 @@ CONF_KEY_COLUMNS = "key_columns" CONF_SLEEP_TIME = "sleep_time" CONF_SCAN_TIME = "scan_time" CONF_DEBOUNCE_TIME = "debounce_time" +CONF_SX1509_ID = "sx1509_id" DEPENDENCIES = ["i2c"] MULTI_CONF = True sx1509_ns = cg.esphome_ns.namespace("sx1509") -SX1509GPIOMode = sx1509_ns.enum("SX1509GPIOMode") -SX1509_GPIO_MODES = { - "INPUT": SX1509GPIOMode.SX1509_INPUT, - "INPUT_PULLUP": SX1509GPIOMode.SX1509_INPUT_PULLUP, - "OUTPUT": SX1509GPIOMode.SX1509_OUTPUT, -} SX1509Component = sx1509_ns.class_("SX1509Component", cg.Component, i2c.I2CDevice) SX1509GPIOPin = sx1509_ns.class_("SX1509GPIOPin", cg.GPIOPin) @@ -64,34 +67,43 @@ async def to_code(config): cg.add(var.set_debounce_time(keypad[CONF_DEBOUNCE_TIME])) -CONF_SX1509 = "sx1509" -CONF_SX1509_ID = "sx1509_id" +def validate_mode(value): + if not (value[CONF_INPUT] or value[CONF_OUTPUT]): + raise cv.Invalid("Mode must be either input or output") + if value[CONF_INPUT] and value[CONF_OUTPUT]: + raise cv.Invalid("Mode must be either input or output") + if value[CONF_PULLUP] and not value[CONF_INPUT]: + raise cv.Invalid("Pullup only available with input") + return value -SX1509_OUTPUT_PIN_SCHEMA = cv.Schema( + +CONF_SX1509 = "sx1509" +SX1509_PIN_SCHEMA = cv.All( { - cv.Required(CONF_SX1509): cv.use_id(SX1509Component), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum( - SX1509_GPIO_MODES, upper=True + cv.GenerateID(): cv.declare_id(SX1509Component), + cv.Required(CONF_SX1509): cv.use_id(SX1509GPIOPin), + cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15), + cv.Optional(CONF_MODE, default={}): cv.All( + { + cv.Optional(CONF_INPUT, default=False): cv.boolean, + cv.Optional(CONF_PULLUP, default=False): cv.boolean, + cv.Optional(CONF_OUTPUT, default=False): cv.boolean, + }, + validate_mode, ), cv.Optional(CONF_INVERTED, default=False): cv.boolean, } ) -SX1509_INPUT_PIN_SCHEMA = cv.Schema( - { - cv.Required(CONF_SX1509): cv.use_id(SX1509Component), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="INPUT"): cv.enum(SX1509_GPIO_MODES, upper=True), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - } -) -@pins.PIN_SCHEMA_REGISTRY.register( - CONF_SX1509, (SX1509_OUTPUT_PIN_SCHEMA, SX1509_INPUT_PIN_SCHEMA) -) +@pins.PIN_SCHEMA_REGISTRY.register(CONF_SX1509, SX1509_PIN_SCHEMA) async def sx1509_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_SX1509]) - return SX1509GPIOPin.new( - parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED] - ) + cg.add(var.set_parent(parent)) + + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + return var diff --git a/esphome/components/sx1509/output/sx1509_float_output.cpp b/esphome/components/sx1509/output/sx1509_float_output.cpp index c68f8f9ded..e9c401eeed 100644 --- a/esphome/components/sx1509/output/sx1509_float_output.cpp +++ b/esphome/components/sx1509/output/sx1509_float_output.cpp @@ -16,7 +16,8 @@ void SX1509FloatOutputChannel::write_state(float state) { void SX1509FloatOutputChannel::setup() { ESP_LOGD(TAG, "setup pin %d", this->pin_); - this->parent_->pin_mode(this->pin_, SX1509_ANALOG_OUTPUT); + this->parent_->pin_mode(this->pin_, gpio::FLAG_OUTPUT); + this->parent_->setup_led_driver(this->pin_); this->turn_off(); } diff --git a/esphome/components/sx1509/sx1509.cpp b/esphome/components/sx1509/sx1509.cpp index 14d9ad7a61..9095dfeffa 100644 --- a/esphome/components/sx1509/sx1509.cpp +++ b/esphome/components/sx1509/sx1509.cpp @@ -18,13 +18,15 @@ void SX1509Component::setup() { this->write_byte(REG_RESET, 0x34); uint16_t data; - this->read_byte_16(REG_INTERRUPT_MASK_A, &data); - if (data == 0xFF00) { - clock_(INTERNAL_CLOCK_2MHZ); - } else { + if (!this->read_byte_16(REG_INTERRUPT_MASK_A, &data)) { this->mark_failed(); return; } + if (data != 0xFF00) { + this->mark_failed(); + return; + } + clock_(INTERNAL_CLOCK_2MHZ); delayMicroseconds(500); if (this->has_keypad_) this->setup_keypad_(); @@ -49,7 +51,8 @@ void SX1509Component::loop() { bool SX1509Component::digital_read(uint8_t pin) { if (this->ddr_mask_ & (1 << pin)) { uint16_t temp_reg_data; - this->read_byte_16(REG_DATA_B, &temp_reg_data); + if (!this->read_byte_16(REG_DATA_B, &temp_reg_data)) + return false; if (temp_reg_data & (1 << pin)) return true; } @@ -68,9 +71,9 @@ void SX1509Component::digital_write(uint8_t pin, bool bit_value) { this->write_byte_16(REG_DATA_B, temp_reg_data); } else { // Otherwise the pin is an input, pull-up/down - uint16_t temp_pullup; + uint16_t temp_pullup = 0; this->read_byte_16(REG_PULL_UP_B, &temp_pullup); - uint16_t temp_pull_down; + uint16_t temp_pull_down = 0; this->read_byte_16(REG_PULL_DOWN_B, &temp_pull_down); if (bit_value) { @@ -89,25 +92,21 @@ void SX1509Component::digital_write(uint8_t pin, bool bit_value) { } } -void SX1509Component::pin_mode(uint8_t pin, uint8_t mode) { +void SX1509Component::pin_mode(uint8_t pin, gpio::Flags flags) { this->read_byte_16(REG_DIR_B, &this->ddr_mask_); - if ((mode == SX1509_OUTPUT) || (mode == SX1509_ANALOG_OUTPUT)) + if (flags == gpio::FLAG_OUTPUT) this->ddr_mask_ &= ~(1 << pin); else this->ddr_mask_ |= (1 << pin); this->write_byte_16(REG_DIR_B, this->ddr_mask_); - if (mode == INPUT_PULLUP) - digital_write(pin, HIGH); - - if (mode == SX1509_ANALOG_OUTPUT) { - setup_led_driver_(pin); - } + if (flags & gpio::FLAG_PULLUP) + digital_write(pin, true); } -void SX1509Component::setup_led_driver_(uint8_t pin) { - uint16_t temp_word; - uint8_t temp_byte; +void SX1509Component::setup_led_driver(uint8_t pin) { + uint16_t temp_word = 0; + uint8_t temp_byte = 0; this->read_byte_16(REG_INPUT_DISABLE_B, &temp_word); temp_word |= (1 << pin); @@ -140,18 +139,18 @@ void SX1509Component::setup_led_driver_(uint8_t pin) { this->write_byte_16(REG_DATA_B, temp_word); } -void SX1509Component::clock_(byte osc_source, byte osc_pin_function, byte osc_freq_out, byte osc_divider) { +void SX1509Component::clock_(uint8_t osc_source, uint8_t osc_pin_function, uint8_t osc_freq_out, uint8_t osc_divider) { osc_source = (osc_source & 0b11) << 5; // 2-bit value, bits 6:5 osc_pin_function = (osc_pin_function & 1) << 4; // 1-bit value bit 4 osc_freq_out = (osc_freq_out & 0b1111); // 4-bit value, bits 3:0 uint8_t reg_clock = osc_source | osc_pin_function | osc_freq_out; this->write_byte(REG_CLOCK, reg_clock); - osc_divider = constrain(osc_divider, 1, 7); + osc_divider = clamp(osc_divider, 1, 7u); this->clk_x_ = 2000000; osc_divider = (osc_divider & 0b111) << 4; // 3-bit value, bits 6:4 - uint8_t reg_misc; + uint8_t reg_misc = 0; this->read_byte(REG_MISC, ®_misc); reg_misc &= ~(0b111 << 4); reg_misc |= osc_divider; @@ -159,7 +158,7 @@ void SX1509Component::clock_(byte osc_source, byte osc_pin_function, byte osc_fr } void SX1509Component::setup_keypad_() { - uint8_t temp_byte; + uint8_t temp_byte = 0; // setup row/col pins for INPUT OUTPUT this->read_byte_16(REG_DIR_B, &this->ddr_mask_); @@ -199,14 +198,14 @@ void SX1509Component::setup_keypad_() { } uint16_t SX1509Component::read_key_data() { - uint16_t key_data; + uint16_t key_data = 0; this->read_byte_16(REG_KEY_DATA_1, &key_data); return (0xFFFF ^ key_data); } void SX1509Component::set_debounce_config_(uint8_t config_value) { // First make sure clock is configured - uint8_t temp_byte; + uint8_t temp_byte = 0; this->read_byte(REG_MISC, &temp_byte); temp_byte |= (1 << 4); // Just default to no divider if not set this->write_byte(REG_MISC, temp_byte); @@ -227,13 +226,13 @@ void SX1509Component::set_debounce_time_(uint8_t time) { break; } } - config_value = constrain(config_value, 0, 7); + config_value = clamp(config_value, 0, 7); set_debounce_config_(config_value); } void SX1509Component::set_debounce_enable_(uint8_t pin) { - uint16_t debounce_enable; + uint16_t debounce_enable = 0; this->read_byte_16(REG_DEBOUNCE_ENABLE_B, &debounce_enable); debounce_enable |= (1 << pin); this->write_byte_16(REG_DEBOUNCE_ENABLE_B, debounce_enable); diff --git a/esphome/components/sx1509/sx1509.h b/esphome/components/sx1509/sx1509.h index 1390059675..5f0697b534 100644 --- a/esphome/components/sx1509/sx1509.h +++ b/esphome/components/sx1509/sx1509.h @@ -2,7 +2,7 @@ #include "esphome/components/i2c/i2c.h" #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "sx1509_gpio_pin.h" #include "sx1509_registers.h" @@ -15,16 +15,6 @@ const uint8_t EXTERNAL_CLOCK = 1; const uint8_t SOFTWARE_RESET = 0; const uint8_t HARDWARE_RESET = 1; -const uint8_t ANALOG_OUTPUT = 0x03; // To set a pin mode for PWM output - -// PinModes for SX1509 pins -enum SX1509GPIOMode : uint8_t { - SX1509_INPUT = INPUT, // 0x00 - SX1509_INPUT_PULLUP = INPUT_PULLUP, // 0x02 - SX1509_ANALOG_OUTPUT = ANALOG_OUTPUT, // 0x03 - SX1509_OUTPUT = OUTPUT, // 0x01 -}; - const uint8_t REG_I_ON[16] = {REG_I_ON_0, REG_I_ON_1, REG_I_ON_2, REG_I_ON_3, REG_I_ON_4, REG_I_ON_5, REG_I_ON_6, REG_I_ON_7, REG_I_ON_8, REG_I_ON_9, REG_I_ON_10, REG_I_ON_11, REG_I_ON_12, REG_I_ON_13, REG_I_ON_14, REG_I_ON_15}; @@ -47,9 +37,9 @@ class SX1509Component : public Component, public i2c::I2CDevice { bool digital_read(uint8_t pin); uint16_t read_key_data(); void set_pin_value(uint8_t pin, uint8_t i_on) { this->write_byte(REG_I_ON[pin], i_on); }; - void pin_mode(uint8_t pin, uint8_t mode); + void pin_mode(uint8_t pin, gpio::Flags flags); void digital_write(uint8_t pin, bool bit_value); - u_long get_clock() { return this->clk_x_; }; + uint32_t get_clock() { return this->clk_x_; }; void set_rows_cols(uint8_t rows, uint8_t cols) { this->rows_ = rows; this->cols_ = cols; @@ -60,10 +50,11 @@ class SX1509Component : public Component, public i2c::I2CDevice { void set_debounce_time(uint8_t debounce_time = 1) { this->debounce_time_ = debounce_time; }; void register_keypad_binary_sensor(SX1509Processor *binary_sensor) { this->keypad_binary_sensors_.push_back(binary_sensor); - }; + } + void setup_led_driver(uint8_t pin); protected: - u_long clk_x_ = 2000000; + uint32_t clk_x_ = 2000000; uint8_t frequency_ = 0; uint16_t ddr_mask_ = 0x00; uint16_t input_mask_ = 0x00; @@ -82,7 +73,6 @@ class SX1509Component : public Component, public i2c::I2CDevice { void set_debounce_pin_(uint8_t pin); void set_debounce_enable_(uint8_t pin); void set_debounce_keypad_(uint8_t time, uint8_t num_rows, uint8_t num_cols); - void setup_led_driver_(uint8_t pin); void clock_(uint8_t osc_source = 2, uint8_t osc_pin_function = 1, uint8_t osc_freq_out = 0, uint8_t osc_divider = 0); }; diff --git a/esphome/components/sx1509/sx1509_gpio_pin.cpp b/esphome/components/sx1509/sx1509_gpio_pin.cpp index ac55ac1ca5..2c6e0b0c32 100644 --- a/esphome/components/sx1509/sx1509_gpio_pin.cpp +++ b/esphome/components/sx1509/sx1509_gpio_pin.cpp @@ -7,14 +7,15 @@ namespace sx1509 { static const char *const TAG = "sx1509_gpio_pin"; -void SX1509GPIOPin::setup() { - ESP_LOGD(TAG, "setup pin %d", this->pin_); - this->parent_->pin_mode(this->pin_, this->mode_); -} - -void SX1509GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); } +void SX1509GPIOPin::setup() { pin_mode(flags_); } +void SX1509GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool SX1509GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void SX1509GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } +std::string SX1509GPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%u via MCP23016", pin_); + return buffer; +} } // namespace sx1509 } // namespace esphome diff --git a/esphome/components/sx1509/sx1509_gpio_pin.h b/esphome/components/sx1509/sx1509_gpio_pin.h index 39f841a2a4..4d8aa5ec83 100644 --- a/esphome/components/sx1509/sx1509_gpio_pin.h +++ b/esphome/components/sx1509/sx1509_gpio_pin.h @@ -9,15 +9,22 @@ class SX1509Component; class SX1509GPIOPin : public GPIOPin { public: - SX1509GPIOPin(SX1509Component *parent, uint8_t pin, uint8_t mode, bool inverted = false) - : GPIOPin(pin, mode, inverted), parent_(parent){}; void setup() override; - void pin_mode(uint8_t mode) override; + void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; + std::string dump_summary() const override; + + void set_parent(SX1509Component *parent) { parent_ = parent; } + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_flags(gpio::Flags flags) { flags_ = flags; } protected: SX1509Component *parent_; + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; }; } // namespace sx1509 diff --git a/esphome/components/tca9548a/__init__.py b/esphome/components/tca9548a/__init__.py index 62cbace56a..0f222b8fc7 100644 --- a/esphome/components/tca9548a/__init__.py +++ b/esphome/components/tca9548a/__init__.py @@ -1,30 +1,43 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c -from esphome.const import CONF_ID, CONF_SCAN +from esphome.const import CONF_CHANNEL, CONF_CHANNELS, CONF_ID, CONF_SCAN CODEOWNERS = ["@andreashergert1984"] DEPENDENCIES = ["i2c"] tca9548a_ns = cg.esphome_ns.namespace("tca9548a") -TCA9548AComponent = tca9548a_ns.class_( - "TCA9548AComponent", cg.PollingComponent, i2c.I2CMultiplexer -) +TCA9548AComponent = tca9548a_ns.class_("TCA9548AComponent", cg.Component, i2c.I2CDevice) +TCA9548AChannel = tca9548a_ns.class_("TCA9548AChannel", i2c.I2CBus) MULTI_CONF = True -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(TCA9548AComponent), - cv.Optional(CONF_SCAN, default=True): cv.boolean, - } -).extend(i2c.i2c_device_schema(0x70)) +CONF_BUS_ID = "bus_id" +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(TCA9548AComponent), + cv.Optional(CONF_SCAN): cv.invalid("This option has been removed"), + cv.Optional(CONF_CHANNELS, default=[]): cv.ensure_list( + { + cv.Required(CONF_BUS_ID): cv.declare_id(TCA9548AChannel), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7), + } + ), + } + ) + .extend(i2c.i2c_device_schema(0x70)) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - cg.add_define("USE_I2C_MULTIPLEXER") await cg.register_component(var, config) await i2c.register_i2c_device(var, config) - cg.add(var.set_scan(config[CONF_SCAN])) + + for conf in config[CONF_CHANNELS]: + chan = cg.new_Pvariable(conf[CONF_BUS_ID]) + cg.add(chan.set_parent(var)) + cg.add(chan.set_channel(conf[CONF_CHANNEL])) diff --git a/esphome/components/tca9548a/tca9548a.cpp b/esphome/components/tca9548a/tca9548a.cpp index 472b8b6673..5117ad8969 100644 --- a/esphome/components/tca9548a/tca9548a.cpp +++ b/esphome/components/tca9548a/tca9548a.cpp @@ -6,35 +6,46 @@ namespace tca9548a { static const char *const TAG = "tca9548a"; +i2c::ErrorCode TCA9548AChannel::readv(uint8_t address, i2c::ReadBuffer *buffers, size_t cnt) { + auto err = parent_->switch_to_channel(channel_); + if (err != i2c::ERROR_OK) + return err; + return parent_->bus_->readv(address, buffers, cnt); +} +i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt) { + auto err = parent_->switch_to_channel(channel_); + if (err != i2c::ERROR_OK) + return err; + return parent_->bus_->writev(address, buffers, cnt); +} + void TCA9548AComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up TCA9548A..."); uint8_t status = 0; - if (!this->read_byte(0x00, &status)) { + if (!this->read_register(0x00, &status, 1)) { ESP_LOGI(TAG, "TCA9548A failed"); + this->mark_failed(); return; } - // out of range to make sure on first set_channel a new one will be set - this->current_channelno_ = 8; - ESP_LOGCONFIG(TAG, "Channels currently open: %d", status); + ESP_LOGD(TAG, "Channels currently open: %d", status); } void TCA9548AComponent::dump_config() { ESP_LOGCONFIG(TAG, "TCA9548A:"); LOG_I2C_DEVICE(this); - if (this->scan_) { - for (uint8_t i = 0; i < 8; i++) { - ESP_LOGCONFIG(TAG, "Activating channel: %d", i); - this->set_channel(i); - this->parent_->dump_config(); - } - } } -void TCA9548AComponent::set_channel(uint8_t channelno) { - if (this->current_channelno_ != channelno) { - this->current_channelno_ = channelno; - uint8_t channelbyte = 1 << channelno; - this->write_byte(0x70, channelbyte); +i2c::ErrorCode TCA9548AComponent::switch_to_channel(uint8_t channel) { + if (this->is_failed()) + return i2c::ERROR_NOT_INITIALIZED; + if (current_channel_ == channel) + return i2c::ERROR_OK; + + uint8_t channel_val = 1 << channel; + auto err = this->write_register(0x70, &channel_val, 1); + if (err == i2c::ERROR_OK) { + current_channel_ = channel; } + return err; } } // namespace tca9548a diff --git a/esphome/components/tca9548a/tca9548a.h b/esphome/components/tca9548a/tca9548a.h index 50b1eb8b56..314346d317 100644 --- a/esphome/components/tca9548a/tca9548a.h +++ b/esphome/components/tca9548a/tca9548a.h @@ -6,17 +6,31 @@ namespace esphome { namespace tca9548a { -class TCA9548AComponent : public Component, public i2c::I2CMultiplexer { +class TCA9548AComponent; +class TCA9548AChannel : public i2c::I2CBus { + public: + void set_channel(uint8_t channel) { channel_ = channel; } + void set_parent(TCA9548AComponent *parent) { parent_ = parent; } + + i2c::ErrorCode readv(uint8_t address, i2c::ReadBuffer *buffers, size_t cnt) override; + i2c::ErrorCode writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt) override; + + protected: + uint8_t channel_; + TCA9548AComponent *parent_; +}; + +class TCA9548AComponent : public Component, public i2c::I2CDevice { public: - void set_scan(bool scan) { scan_ = scan; } void setup() override; void dump_config() override; void update(); - void set_channel(uint8_t channelno) override; + + i2c::ErrorCode switch_to_channel(uint8_t channel); protected: - bool scan_; - uint8_t current_channelno_; + friend class TCA9548AChannel; + uint8_t current_channel_ = 255; }; } // namespace tca9548a } // namespace esphome diff --git a/esphome/components/tcs34725/tcs34725.cpp b/esphome/components/tcs34725/tcs34725.cpp index 52548262c1..564d3dcda7 100644 --- a/esphome/components/tcs34725/tcs34725.cpp +++ b/esphome/components/tcs34725/tcs34725.cpp @@ -1,5 +1,6 @@ #include "tcs34725.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace tcs34725 { diff --git a/esphome/components/template/number/template_number.cpp b/esphome/components/template/number/template_number.cpp index eb9b17b976..a5b015c44d 100644 --- a/esphome/components/template/number/template_number.cpp +++ b/esphome/components/template/number/template_number.cpp @@ -14,9 +14,9 @@ void TemplateNumber::setup() { if (!this->restore_value_) { value = this->initial_value_; } else { - this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); + this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); if (!this->pref_.load(&value)) { - if (!isnan(this->initial_value_)) + if (!std::isnan(this->initial_value_)) value = this->initial_value_; else value = this->traits.get_min_value(); diff --git a/esphome/components/template/select/template_select.cpp b/esphome/components/template/select/template_select.cpp index 8695880856..219c341ec9 100644 --- a/esphome/components/template/select/template_select.cpp +++ b/esphome/components/template/select/template_select.cpp @@ -17,7 +17,7 @@ void TemplateSelect::setup() { ESP_LOGD(TAG, "State from initial: %s", value.c_str()); } else { size_t index; - this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); + this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); if (!this->pref_.load(&index)) { value = this->initial_option_; ESP_LOGD(TAG, "State from initial (could not load): %s", value.c_str()); diff --git a/esphome/components/template/sensor/template_sensor.cpp b/esphome/components/template/sensor/template_sensor.cpp index 63cbd70db0..b28eb3fed2 100644 --- a/esphome/components/template/sensor/template_sensor.cpp +++ b/esphome/components/template/sensor/template_sensor.cpp @@ -1,5 +1,6 @@ #include "template_sensor.h" #include "esphome/core/log.h" +#include namespace esphome { namespace template_ { @@ -12,7 +13,7 @@ void TemplateSensor::update() { if (val.has_value()) { this->publish_state(*val); } - } else if (!isnan(this->get_raw_state())) { + } else if (!std::isnan(this->get_raw_state())) { this->publish_state(this->get_raw_state()); } } diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index a75713cbb9..6193185321 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -88,17 +88,17 @@ climate::ClimateFanMode ThermostatClimate::locked_fan_mode() { return this->prev bool ThermostatClimate::hysteresis_valid() { if ((this->supports_cool_ || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) && - (isnan(this->cooling_deadband_) || isnan(this->cooling_overrun_))) + (std::isnan(this->cooling_deadband_) || std::isnan(this->cooling_overrun_))) return false; - if (this->supports_heat_ && (isnan(this->heating_deadband_) || isnan(this->heating_overrun_))) + if (this->supports_heat_ && (std::isnan(this->heating_deadband_) || std::isnan(this->heating_overrun_))) return false; return true; } void ThermostatClimate::validate_target_temperature() { - if (isnan(this->target_temperature)) { + if (std::isnan(this->target_temperature)) { this->target_temperature = ((this->get_traits().get_visual_max_temperature() - this->get_traits().get_visual_min_temperature()) / 2) + this->get_traits().get_visual_min_temperature(); @@ -121,7 +121,7 @@ void ThermostatClimate::validate_target_temperatures() { } void ThermostatClimate::validate_target_temperature_low() { - if (isnan(this->target_temperature_low)) { + if (std::isnan(this->target_temperature_low)) { this->target_temperature_low = this->get_traits().get_visual_min_temperature(); } else { // target_temperature_low must not be lower than the visual minimum @@ -139,7 +139,7 @@ void ThermostatClimate::validate_target_temperature_low() { } void ThermostatClimate::validate_target_temperature_high() { - if (isnan(this->target_temperature_high)) { + if (std::isnan(this->target_temperature_high)) { this->target_temperature_high = this->get_traits().get_visual_max_temperature(); } else { // target_temperature_high must not be lower than the visual maximum @@ -245,7 +245,7 @@ climate::ClimateTraits ThermostatClimate::traits() { climate::ClimateAction ThermostatClimate::compute_action_(const bool ignore_timers) { auto target_action = climate::CLIMATE_ACTION_IDLE; // if any hysteresis values or current_temperature is not valid, we go to OFF; - if (isnan(this->current_temperature) || !this->hysteresis_valid()) { + if (std::isnan(this->current_temperature) || !this->hysteresis_valid()) { return climate::CLIMATE_ACTION_OFF; } // do not change the action if an "ON" timer is running @@ -307,7 +307,7 @@ climate::ClimateAction ThermostatClimate::compute_action_(const bool ignore_time climate::ClimateAction ThermostatClimate::compute_supplemental_action_() { auto target_action = climate::CLIMATE_ACTION_IDLE; // if any hysteresis values or current_temperature is not valid, we go to OFF; - if (isnan(this->current_temperature) || !this->hysteresis_valid()) { + if (std::isnan(this->current_temperature) || !this->hysteresis_valid()) { return climate::CLIMATE_ACTION_OFF; } diff --git a/esphome/components/time/automation.cpp b/esphome/components/time/automation.cpp index f133ab021c..7e16d7141f 100644 --- a/esphome/components/time/automation.cpp +++ b/esphome/components/time/automation.cpp @@ -1,5 +1,6 @@ #include "automation.h" #include "esphome/core/log.h" +#include namespace esphome { namespace time { diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 27a2d84da6..064e6f899c 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -1,7 +1,7 @@ #include "real_time_clock.h" #include "esphome/core/log.h" #include "lwip/opt.h" -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include "sys/time.h" #endif #include diff --git a/esphome/components/time_based/time_based_cover.cpp b/esphome/components/time_based/time_based_cover.cpp index 60a33aa82a..3fa07167ca 100644 --- a/esphome/components/time_based/time_based_cover.cpp +++ b/esphome/components/time_based/time_based_cover.cpp @@ -1,5 +1,6 @@ #include "time_based_cover.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace time_based { diff --git a/esphome/components/tlc59208f/tlc59208f_output.cpp b/esphome/components/tlc59208f/tlc59208f_output.cpp index e416a5c62b..59fb9f98ed 100644 --- a/esphome/components/tlc59208f/tlc59208f_output.cpp +++ b/esphome/components/tlc59208f/tlc59208f_output.cpp @@ -75,7 +75,7 @@ void TLC59208FOutput::setup() { ESP_LOGV(TAG, " Resetting all devices on the bus..."); // Reset all devices on the bus - if (!this->parent_->write_byte(TLC59208F_SWRST_ADDR >> 1, TLC59208F_SWRST_SEQ[0], TLC59208F_SWRST_SEQ[1])) { + if (this->bus_->write(TLC59208F_SWRST_ADDR >> 1, TLC59208F_SWRST_SEQ, 2) != i2c::ERROR_OK) { ESP_LOGE(TAG, "RESET failed"); this->mark_failed(); return; diff --git a/esphome/components/tlc59208f/tlc59208f_output.h b/esphome/components/tlc59208f/tlc59208f_output.h index 79fccc97f3..68ca8061d7 100644 --- a/esphome/components/tlc59208f/tlc59208f_output.h +++ b/esphome/components/tlc59208f/tlc59208f_output.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/helpers.h" #include "esphome/components/output/float_output.h" #include "esphome/components/i2c/i2c.h" diff --git a/esphome/components/tlc5947/tlc5947.h b/esphome/components/tlc5947/tlc5947.h index b608b861e7..0eb7f10604 100644 --- a/esphome/components/tlc5947/tlc5947.h +++ b/esphome/components/tlc5947/tlc5947.h @@ -3,8 +3,9 @@ // https://www.ti.com/lit/ds/symlink/tlc5947.pdf #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/output/float_output.h" +#include namespace esphome { namespace tlc5947 { diff --git a/esphome/components/tm1637/tm1637.cpp b/esphome/components/tm1637/tm1637.cpp index 1f1c0fd301..488f3b6727 100644 --- a/esphome/components/tm1637/tm1637.cpp +++ b/esphome/components/tm1637/tm1637.cpp @@ -1,6 +1,7 @@ #include "tm1637.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" namespace esphome { namespace tm1637 { @@ -146,16 +147,16 @@ void TM1637Display::update() { float TM1637Display::get_setup_priority() const { return setup_priority::PROCESSOR; } void TM1637Display::bit_delay_() { delayMicroseconds(100); } void TM1637Display::start_() { - this->dio_pin_->pin_mode(OUTPUT); + this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT); this->bit_delay_(); } void TM1637Display::stop_() { - this->dio_pin_->pin_mode(OUTPUT); + this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT); bit_delay_(); - this->clk_pin_->pin_mode(INPUT); + this->clk_pin_->pin_mode(gpio::FLAG_INPUT); bit_delay_(); - this->dio_pin_->pin_mode(INPUT); + this->dio_pin_->pin_mode(gpio::FLAG_INPUT); bit_delay_(); } @@ -189,39 +190,39 @@ bool TM1637Display::send_byte_(uint8_t b) { // 8 Data Bits for (uint8_t i = 0; i < 8; i++) { // CLK low - this->clk_pin_->pin_mode(OUTPUT); + this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT); this->bit_delay_(); // Set data bit if (data & 0x01) - this->dio_pin_->pin_mode(INPUT); + this->dio_pin_->pin_mode(gpio::FLAG_INPUT); else - this->dio_pin_->pin_mode(OUTPUT); + this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT); this->bit_delay_(); // CLK high - this->clk_pin_->pin_mode(INPUT); + this->clk_pin_->pin_mode(gpio::FLAG_INPUT); this->bit_delay_(); data = data >> 1; } // Wait for acknowledge // CLK to zero - this->clk_pin_->pin_mode(OUTPUT); - this->dio_pin_->pin_mode(INPUT); + this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->dio_pin_->pin_mode(gpio::FLAG_INPUT); this->bit_delay_(); // CLK to high - this->clk_pin_->pin_mode(INPUT); + this->clk_pin_->pin_mode(gpio::FLAG_INPUT); this->bit_delay_(); uint8_t ack = this->dio_pin_->digital_read(); if (ack == 0) { - this->dio_pin_->pin_mode(OUTPUT); + this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT); } this->bit_delay_(); - this->clk_pin_->pin_mode(OUTPUT); + this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT); this->bit_delay_(); return ack; @@ -233,7 +234,7 @@ uint8_t TM1637Display::print(uint8_t start_pos, const char *str) { for (; *str != '\0'; str++) { uint8_t data = TM1637_UNKNOWN_CHAR; if (*str >= ' ' && *str <= '~') - data = pgm_read_byte(&TM1637_ASCII_TO_RAW[*str - ' ']); + data = progmem_read_byte(&TM1637_ASCII_TO_RAW[*str - ' ']); if (data == TM1637_UNKNOWN_CHAR) { ESP_LOGW(TAG, "Encountered character '%c' with no TM1637 representation while translating string!", *str); diff --git a/esphome/components/tm1637/tm1637.h b/esphome/components/tm1637/tm1637.h index 003344eae9..63b30ac13e 100644 --- a/esphome/components/tm1637/tm1637.h +++ b/esphome/components/tm1637/tm1637.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/defines.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #ifdef USE_TIME #include "esphome/components/time/real_time_clock.h" diff --git a/esphome/components/tm1651/__init__.py b/esphome/components/tm1651/__init__.py index d06c1bedde..9d2b17afdc 100644 --- a/esphome/components/tm1651/__init__.py +++ b/esphome/components/tm1651/__init__.py @@ -27,12 +27,15 @@ TM1651_BRIGHTNESS_OPTIONS = { 3: TM1651Display.TM1651_BRIGHTNESS_HIGH, } -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(TM1651Display), - cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_schema, - cv.Required(CONF_DIO_PIN): pins.internal_gpio_output_pin_schema, - } +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(TM1651Display), + cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_DIO_PIN): pins.internal_gpio_output_pin_schema, + } + ), + cv.only_with_arduino, ) validate_level_percent = cv.All(cv.int_range(min=0, max=100)) diff --git a/esphome/components/tm1651/tm1651.cpp b/esphome/components/tm1651/tm1651.cpp index 3689ef5894..c6bb1bc025 100644 --- a/esphome/components/tm1651/tm1651.cpp +++ b/esphome/components/tm1651/tm1651.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "tm1651.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" @@ -88,3 +90,5 @@ uint8_t TM1651Display::calculate_brightness_(uint8_t new_brightness) { } // namespace tm1651 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/tm1651/tm1651.h b/esphome/components/tm1651/tm1651.h index a18519bf41..72849bc8eb 100644 --- a/esphome/components/tm1651/tm1651.h +++ b/esphome/components/tm1651/tm1651.h @@ -1,9 +1,11 @@ #pragma once +#ifdef USE_ARDUINO + #include #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/automation.h" #include @@ -13,8 +15,8 @@ namespace tm1651 { class TM1651Display : public Component { public: - void set_clk_pin(GPIOPin *pin) { clk_pin_ = pin; } - void set_dio_pin(GPIOPin *pin) { dio_pin_ = pin; } + void set_clk_pin(InternalGPIOPin *pin) { clk_pin_ = pin; } + void set_dio_pin(InternalGPIOPin *pin) { dio_pin_ = pin; } void setup() override; void dump_config() override; @@ -28,8 +30,8 @@ class TM1651Display : public Component { protected: std::unique_ptr battery_display_; - GPIOPin *clk_pin_; - GPIOPin *dio_pin_; + InternalGPIOPin *clk_pin_; + InternalGPIOPin *dio_pin_; bool is_on_ = true; uint8_t brightness_; @@ -83,3 +85,5 @@ template class TurnOffAction : public Action, public Pare } // namespace tm1651 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/tmp102/tmp102.cpp b/esphome/components/tmp102/tmp102.cpp index 7b3dcad4aa..f6bb9a05c0 100644 --- a/esphome/components/tmp102/tmp102.cpp +++ b/esphome/components/tmp102/tmp102.cpp @@ -1,5 +1,6 @@ #include "tmp102.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace tmp102 { @@ -28,10 +29,16 @@ void TMP102Component::dump_config() { void TMP102Component::update() { uint16_t raw_temperature; - if (!this->read_byte_16(TMP102_REGISTER_TEMPERATURE, &raw_temperature, 50)) { + if (this->write(&TMP102_REGISTER_TEMPERATURE, 1) != i2c::ERROR_OK) { this->status_set_warning(); return; } + delay(50); // NOLINT + if (this->read(reinterpret_cast(&raw_temperature), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + raw_temperature = i2c::i2ctohs(raw_temperature); raw_temperature = raw_temperature >> 4; float temperature = raw_temperature * TMP102_CONVERSION_FACTOR; diff --git a/esphome/components/tof10120/tof10120_sensor.cpp b/esphome/components/tof10120/tof10120_sensor.cpp index cabfdad41d..4ba591f9c4 100644 --- a/esphome/components/tof10120/tof10120_sensor.cpp +++ b/esphome/components/tof10120/tof10120_sensor.cpp @@ -1,5 +1,6 @@ #include "tof10120_sensor.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" // Very basic support for TOF10120 distance sensor @@ -31,7 +32,12 @@ void TOF10120Sensor::update() { } uint8_t data[2]; - if (!this->read_bytes(TOF10120_DISTANCE_REGISTER, data, 2, TOF10120_DEFAULT_DELAY)) { + if (this->write(&TOF10120_DISTANCE_REGISTER, 1) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + delay(TOF10120_DEFAULT_DELAY); + if (this->read(data, 2) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Communication with TOF10120 failed on read"); this->status_set_warning(); return; diff --git a/esphome/components/toshiba/toshiba.cpp b/esphome/components/toshiba/toshiba.cpp index 81ed5ddce4..25528abbe1 100644 --- a/esphome/components/toshiba/toshiba.cpp +++ b/esphome/components/toshiba/toshiba.cpp @@ -127,7 +127,7 @@ void ToshibaClimate::setup() { this->fan_modes_ = this->toshiba_fan_modes_(); this->swing_modes_ = this->toshiba_swing_modes_(); // Never send nan to HA - if (isnan(this->target_temperature)) + if (std::isnan(this->target_temperature)) this->target_temperature = 24; } diff --git a/esphome/components/total_daily_energy/total_daily_energy.cpp b/esphome/components/total_daily_energy/total_daily_energy.cpp index 83333acab7..178dc7cbe0 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.cpp +++ b/esphome/components/total_daily_energy/total_daily_energy.cpp @@ -7,7 +7,7 @@ namespace total_daily_energy { static const char *const TAG = "total_daily_energy"; void TotalDailyEnergy::setup() { - this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); + this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); float recovered; if (this->pref_.load(&recovered)) { @@ -52,7 +52,7 @@ void TotalDailyEnergy::publish_state_and_save(float state) { } void TotalDailyEnergy::process_new_state_(float state) { - if (isnan(state)) + if (std::isnan(state)) return; const uint32_t now = millis(); const float old_state = this->last_power_state_; diff --git a/esphome/components/total_daily_energy/total_daily_energy.h b/esphome/components/total_daily_energy/total_daily_energy.h index fd71b8decc..9d2396d6e3 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.h +++ b/esphome/components/total_daily_energy/total_daily_energy.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/preferences.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/time/real_time_clock.h" diff --git a/esphome/components/tsl2591/tsl2591.cpp b/esphome/components/tsl2591/tsl2591.cpp index 1785fa46b4..7755437de2 100644 --- a/esphome/components/tsl2591/tsl2591.cpp +++ b/esphome/components/tsl2591/tsl2591.cpp @@ -1,5 +1,6 @@ #include "tsl2591.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace tsl2591 { @@ -362,7 +363,7 @@ float TSL2591Component::get_calculated_lux(uint16_t full_spectrum, uint16_t infr // For the curious "cpl" is counts per lux, a term used in AMS application notes. float cpl = (atime * again) / (this->device_factor_ * this->glass_attenuation_factor_); float lux = (((float) full_spectrum - (float) infrared)) * (1.0F - ((float) infrared / (float) full_spectrum)) / cpl; - return max(lux, 0.0F); + return std::max(lux, 0.0F); } } // namespace tsl2591 diff --git a/esphome/components/ttp229_bsf/ttp229_bsf.h b/esphome/components/ttp229_bsf/ttp229_bsf.h index d73e4fb185..59749a4fa7 100644 --- a/esphome/components/ttp229_bsf/ttp229_bsf.h +++ b/esphome/components/ttp229_bsf/ttp229_bsf.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/binary_sensor/binary_sensor.h" namespace esphome { diff --git a/esphome/components/ttp229_lsf/ttp229_lsf.cpp b/esphome/components/ttp229_lsf/ttp229_lsf.cpp index 6e3e68ea7a..21c7b02740 100644 --- a/esphome/components/ttp229_lsf/ttp229_lsf.cpp +++ b/esphome/components/ttp229_lsf/ttp229_lsf.cpp @@ -8,7 +8,8 @@ static const char *const TAG = "ttp229_lsf"; void TTP229LSFComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up ttp229..."); - if (!this->parent_->raw_request_from(this->address_, 2)) { + uint8_t data[2]; + if (this->read(data, 2) != i2c::ERROR_OK) { this->error_code_ = COMMUNICATION_FAILED; this->mark_failed(); return; @@ -28,10 +29,11 @@ void TTP229LSFComponent::dump_config() { } void TTP229LSFComponent::loop() { uint16_t touched = 0; - if (!this->parent_->raw_receive_16(this->address_, &touched, 1)) { + if (this->read(reinterpret_cast(&touched), 2) != i2c::ERROR_OK) { this->status_set_warning(); return; } + touched = i2c::i2ctohs(touched); this->status_clear_warning(); touched = reverse_bits_16(touched); for (auto *channel : this->channels_) { diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index 293bfb4f88..39d4203684 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -169,7 +169,7 @@ void TuyaClimate::compute_target_temperature_() { } void TuyaClimate::compute_state_() { - if (isnan(this->current_temperature) || isnan(this->target_temperature)) { + if (std::isnan(this->current_temperature) || std::isnan(this->target_temperature)) { // if any control parameters are nan, go to OFF action (not IDLE!) this->switch_to_action_(climate::CLIMATE_ACTION_OFF); return; diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index e42c74005e..bbbc9274c3 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -1,7 +1,8 @@ #include "tuya.h" #include "esphome/core/log.h" -#include "esphome/core/util.h" +#include "esphome/components/network/util.h" #include "esphome/core/helpers.h" +#include "esphome/core/util.h" namespace esphome { namespace tuya { @@ -389,7 +390,7 @@ void Tuya::send_empty_command_(TuyaCommandType command) { void Tuya::send_wifi_status_() { uint8_t status = 0x02; - if (network_is_connected()) { + if (network::is_connected()) { status = 0x03; // Protocol version 3 also supports specifying when connected to "the cloud" diff --git a/esphome/components/tx20/sensor.py b/esphome/components/tx20/sensor.py index ceb9b88d8d..84df82b5e6 100644 --- a/esphome/components/tx20/sensor.py +++ b/esphome/components/tx20/sensor.py @@ -33,9 +33,7 @@ CONFIG_SCHEMA = cv.Schema( accuracy_decimals=1, state_class=STATE_CLASS_NONE, ), - cv.Required(CONF_PIN): cv.All( - pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt - ), + cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/tx20/tx20.cpp b/esphome/components/tx20/tx20.cpp index f48e29521c..6e0b6343d1 100644 --- a/esphome/components/tx20/tx20.cpp +++ b/esphome/components/tx20/tx20.cpp @@ -20,7 +20,7 @@ void Tx20Component::setup() { this->store_.pin = this->pin_->to_isr(); this->store_.reset(); - this->pin_->attach_interrupt(Tx20ComponentStore::gpio_intr, &this->store_, CHANGE); + this->pin_->attach_interrupt(Tx20ComponentStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); } void Tx20Component::dump_config() { ESP_LOGCONFIG(TAG, "Tx20:"); @@ -140,8 +140,8 @@ void Tx20Component::decode_and_publish_() { } } -void ICACHE_RAM_ATTR Tx20ComponentStore::gpio_intr(Tx20ComponentStore *arg) { - arg->pin_state = arg->pin->digital_read(); +void IRAM_ATTR Tx20ComponentStore::gpio_intr(Tx20ComponentStore *arg) { + arg->pin_state = arg->pin.digital_read(); const uint32_t now = micros(); if (!arg->start_time) { // only detect a start if the bit is high @@ -183,7 +183,7 @@ void ICACHE_RAM_ATTR Tx20ComponentStore::gpio_intr(Tx20ComponentStore *arg) { arg->start_time = now; arg->buffer_index++; } -void ICACHE_RAM_ATTR Tx20ComponentStore::reset() { +void IRAM_ATTR Tx20ComponentStore::reset() { tx20_available = false; buffer_index = 0; spent_time = 0; diff --git a/esphome/components/tx20/tx20.h b/esphome/components/tx20/tx20.h index 56cb723fa1..1c617d0674 100644 --- a/esphome/components/tx20/tx20.h +++ b/esphome/components/tx20/tx20.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" namespace esphome { @@ -15,7 +15,7 @@ struct Tx20ComponentStore { volatile uint32_t spent_time; volatile bool tx20_available; volatile bool pin_state; - ISRInternalGPIOPin *pin; + ISRInternalGPIOPin pin; void reset(); static void gpio_intr(Tx20ComponentStore *arg); @@ -27,7 +27,7 @@ class Tx20Component : public Component { /// Get the textual representation of the wind direction ('N', 'SSE', ..). std::string get_wind_cardinal_direction() const; - void set_pin(GPIOPin *pin) { pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } void set_wind_speed_sensor(sensor::Sensor *wind_speed_sensor) { wind_speed_sensor_ = wind_speed_sensor; } void set_wind_direction_degrees_sensor(sensor::Sensor *wind_direction_degrees_sensor) { wind_direction_degrees_sensor_ = wind_direction_degrees_sensor; @@ -42,7 +42,7 @@ class Tx20Component : public Component { void decode_and_publish_(); std::string wind_cardinal_direction_; - GPIOPin *pin_; + InternalGPIOPin *pin_; sensor::Sensor *wind_speed_sensor_; sensor::Sensor *wind_direction_degrees_sensor_; Tx20ComponentStore store_; diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index d2fcac2cb6..35af3eedf7 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -7,6 +7,7 @@ from esphome import pins, automation from esphome.const import ( CONF_BAUD_RATE, CONF_ID, + CONF_NUMBER, CONF_RX_PIN, CONF_TX_PIN, CONF_UART_ID, @@ -18,7 +19,16 @@ from esphome.core import CORE CODEOWNERS = ["@esphome/core"] uart_ns = cg.esphome_ns.namespace("uart") -UARTComponent = uart_ns.class_("UARTComponent", cg.Component) +UARTComponent = uart_ns.class_("UARTComponent") + +IDFUARTComponent = uart_ns.class_("IDFUARTComponent", UARTComponent, cg.Component) +ESP32ArduinoUARTComponent = uart_ns.class_( + "ESP32ArduinoUARTComponent", UARTComponent, cg.Component +) +ESP8266UartComponent = uart_ns.class_( + "ESP8266UartComponent", UARTComponent, cg.Component +) + UARTDevice = uart_ns.class_("UARTDevice") UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action) MULTI_CONF = True @@ -37,12 +47,23 @@ def validate_raw_data(value): def validate_rx_pin(value): - value = pins.input_pin(value) - if CORE.is_esp8266 and value >= 16: + value = pins.internal_gpio_input_pin_schema(value) + if CORE.is_esp8266 and value[CONF_NUMBER] >= 16: raise cv.Invalid("Pins GPIO16 and GPIO17 cannot be used as RX pins on ESP8266.") return value +def _uart_declare_type(value): + if CORE.is_esp8266: + return cv.declare_id(ESP8266UartComponent)(value) + if CORE.is_esp32: + if CORE.using_arduino: + return cv.declare_id(ESP32ArduinoUARTComponent)(value) + if CORE.using_esp_idf: + return cv.declare_id(IDFUARTComponent)(value) + raise NotImplementedError + + UARTParityOptions = uart_ns.enum("UARTParityOptions") UART_PARITY_OPTIONS = { "NONE": UARTParityOptions.UART_CONFIG_PARITY_NONE, @@ -57,19 +78,19 @@ CONF_PARITY = "parity" CONFIG_SCHEMA = cv.All( cv.Schema( { - cv.GenerateID(): cv.declare_id(UARTComponent), + cv.GenerateID(): _uart_declare_type, cv.Required(CONF_BAUD_RATE): cv.int_range(min=1), - cv.Optional(CONF_TX_PIN): pins.output_pin, + cv.Optional(CONF_TX_PIN): pins.internal_gpio_output_pin_schema, cv.Optional(CONF_RX_PIN): validate_rx_pin, cv.Optional(CONF_RX_BUFFER_SIZE, default=256): cv.validate_bytes, - cv.SplitDefault(CONF_INVERT, esp32=False): cv.All( - cv.only_on_esp32, cv.boolean - ), cv.Optional(CONF_STOP_BITS, default=1): cv.one_of(1, 2, int=True), cv.Optional(CONF_DATA_BITS, default=8): cv.int_range(min=5, max=8), cv.Optional(CONF_PARITY, default="NONE"): cv.enum( UART_PARITY_OPTIONS, upper=True ), + cv.Optional(CONF_INVERT): cv.invalid( + "This option has been removed. Please instead use invert in the tx/rx pin schemas." + ), } ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN), @@ -84,12 +105,12 @@ async def to_code(config): cg.add(var.set_baud_rate(config[CONF_BAUD_RATE])) if CONF_TX_PIN in config: - cg.add(var.set_tx_pin(config[CONF_TX_PIN])) + tx_pin = await cg.gpio_pin_expression(config[CONF_TX_PIN]) + cg.add(var.set_tx_pin(tx_pin)) if CONF_RX_PIN in config: - cg.add(var.set_rx_pin(config[CONF_RX_PIN])) + rx_pin = await cg.gpio_pin_expression(config[CONF_RX_PIN]) + cg.add(var.set_rx_pin(rx_pin)) cg.add(var.set_rx_buffer_size(config[CONF_RX_BUFFER_SIZE])) - if CONF_INVERT in config: - cg.add(var.set_invert(config[CONF_INVERT])) cg.add(var.set_stop_bits(config[CONF_STOP_BITS])) cg.add(var.set_data_bits(config[CONF_DATA_BITS])) cg.add(var.set_parity(config[CONF_PARITY])) diff --git a/esphome/components/uart/uart.cpp b/esphome/components/uart/uart.cpp index 8cc8a47b14..22a22e2772 100644 --- a/esphome/components/uart/uart.cpp +++ b/esphome/components/uart/uart.cpp @@ -4,62 +4,28 @@ #include "esphome/core/application.h" #include "esphome/core/defines.h" -#ifdef USE_LOGGER -#include "esphome/components/logger/logger.h" -#endif - namespace esphome { namespace uart { static const char *const TAG = "uart"; -size_t UARTComponent::write(uint8_t data) { - this->write_byte(data); - return 1; -} -int UARTComponent::read() { - uint8_t data; - if (!this->read_byte(&data)) - return -1; - return data; -} -int UARTComponent::peek() { - uint8_t data; - if (!this->peek_byte(&data)) - return -1; - return data; -} - -void UARTComponent::check_logger_conflict_() { -#ifdef USE_LOGGER - if (this->hw_serial_ == nullptr || logger::global_logger->get_baud_rate() == 0) { - return; - } - - if (this->hw_serial_ == logger::global_logger->get_hw_serial()) { - ESP_LOGW(TAG, " You're using the same serial port for logging and the UART component. Please " - "disable logging over the serial port by setting logger->baud_rate to 0."); - } -#endif -} - void UARTDevice::check_uart_settings(uint32_t baud_rate, uint8_t stop_bits, UARTParityOptions parity, uint8_t data_bits) { - if (this->parent_->baud_rate_ != baud_rate) { + if (this->parent_->get_baud_rate() != baud_rate) { ESP_LOGE(TAG, " Invalid baud_rate: Integration requested baud_rate %u but you have %u!", baud_rate, - this->parent_->baud_rate_); + this->parent_->get_baud_rate()); } - if (this->parent_->stop_bits_ != stop_bits) { + if (this->parent_->get_stop_bits() != stop_bits) { ESP_LOGE(TAG, " Invalid stop bits: Integration requested stop_bits %u but you have %u!", stop_bits, - this->parent_->stop_bits_); + this->parent_->get_stop_bits()); } - if (this->parent_->data_bits_ != data_bits) { + if (this->parent_->get_data_bits() != data_bits) { ESP_LOGE(TAG, " Invalid number of data bits: Integration requested %u data bits but you have %u!", data_bits, - this->parent_->data_bits_); + this->parent_->get_data_bits()); } - if (this->parent_->parity_ != parity) { + if (this->parent_->get_parity() != parity) { ESP_LOGE(TAG, " Invalid parity: Integration requested parity %s but you have %s!", - LOG_STR_ARG(parity_to_str(parity)), LOG_STR_ARG(parity_to_str(this->parent_->parity_))); + LOG_STR_ARG(parity_to_str(parity)), LOG_STR_ARG(parity_to_str(this->parent_->get_parity()))); } } diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index a79b7b841e..c368f9ed6b 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -1,132 +1,15 @@ #pragma once #include -#include -#include "esphome/core/esphal.h" #include "esphome/core/component.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" +#include "uart_component.h" namespace esphome { namespace uart { -enum UARTParityOptions { - UART_CONFIG_PARITY_NONE, - UART_CONFIG_PARITY_EVEN, - UART_CONFIG_PARITY_ODD, -}; - -const LogString *parity_to_str(UARTParityOptions parity); - -#ifdef ARDUINO_ARCH_ESP8266 -class ESP8266SoftwareSerial { - public: - void setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_rate, uint8_t stop_bits, uint32_t data_bits, - UARTParityOptions parity, size_t rx_buffer_size); - - uint8_t read_byte(); - uint8_t peek_byte(); - - void flush(); - - void write_byte(uint8_t data); - - int available(); - - GPIOPin *gpio_tx_pin_{nullptr}; - GPIOPin *gpio_rx_pin_{nullptr}; - - protected: - static void gpio_intr(ESP8266SoftwareSerial *arg); - - void wait_(uint32_t *wait, const uint32_t &start); - bool read_bit_(uint32_t *wait, const uint32_t &start); - void write_bit_(bool bit, uint32_t *wait, const uint32_t &start); - - uint32_t bit_time_{0}; - uint8_t *rx_buffer_{nullptr}; - size_t rx_buffer_size_; - volatile size_t rx_in_pos_{0}; - size_t rx_out_pos_{0}; - uint8_t stop_bits_; - uint8_t data_bits_; - UARTParityOptions parity_; - ISRInternalGPIOPin *tx_pin_{nullptr}; - ISRInternalGPIOPin *rx_pin_{nullptr}; -}; -#endif - -class UARTComponent : public Component, public Stream { - public: - void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; } - uint32_t get_baud_rate() const { return baud_rate_; } - - uint32_t get_config(); - - void setup() override; - - void dump_config() override; - - void write_byte(uint8_t data); - - void write_array(const uint8_t *data, size_t len); - void write_array(const std::vector &data) { this->write_array(&data[0], data.size()); } - - void write_str(const char *str); - - bool peek_byte(uint8_t *data); - - bool read_byte(uint8_t *data); - - bool read_array(uint8_t *data, size_t len); - - int available() override; - - /// Block until all bytes have been written to the UART bus. - void flush() override; - - float get_setup_priority() const override { return setup_priority::BUS; } - - size_t write(uint8_t data) override; - int read() override; - int peek() override; - - void set_tx_pin(uint8_t tx_pin) { this->tx_pin_ = tx_pin; } - void set_rx_pin(uint8_t rx_pin) { this->rx_pin_ = rx_pin; } - void set_rx_buffer_size(size_t rx_buffer_size) { this->rx_buffer_size_ = rx_buffer_size; } -#ifdef ARDUINO_ARCH_ESP32 - void set_invert(bool invert) { this->invert_ = invert; } -#endif - void set_stop_bits(uint8_t stop_bits) { this->stop_bits_ = stop_bits; } - void set_data_bits(uint8_t data_bits) { this->data_bits_ = data_bits; } - void set_parity(UARTParityOptions parity) { this->parity_ = parity; } - - protected: - void check_logger_conflict_(); - bool check_read_timeout_(size_t len = 1); - friend class UARTDevice; - - HardwareSerial *hw_serial_{nullptr}; -#ifdef ARDUINO_ARCH_ESP8266 - ESP8266SoftwareSerial *sw_serial_{nullptr}; -#endif - optional tx_pin_; - optional rx_pin_; - size_t rx_buffer_size_; -#ifdef ARDUINO_ARCH_ESP32 - bool invert_; -#endif - uint32_t baud_rate_; - uint8_t stop_bits_; - uint8_t data_bits_; - UARTParityOptions parity_; - - private: -#ifdef ARDUINO_ARCH_ESP8266 - static bool serial0InUse; -#endif -}; - -class UARTDevice : public Stream { +class UARTDevice { public: UARTDevice() = default; UARTDevice(UARTComponent *parent) : parent_(parent) {} @@ -155,13 +38,27 @@ class UARTDevice : public Stream { return res; } - int available() override { return this->parent_->available(); } + int available() { return this->parent_->available(); } - void flush() override { return this->parent_->flush(); } + void flush() { return this->parent_->flush(); } - size_t write(uint8_t data) override { return this->parent_->write(data); } - int read() override { return this->parent_->read(); } - int peek() override { return this->parent_->peek(); } + // Compat APIs + int read() { + uint8_t data; + if (!read_byte(&data)) + return -1; + return data; + } + size_t write(uint8_t data) { + write_byte(data); + return 1; + } + int peek() { + uint8_t data; + if (!peek_byte(&data)) + return -1; + return data; + } /// Check that the configuration of the UART bus matches the provided values and otherwise print a warning void check_uart_settings(uint32_t baud_rate, uint8_t stop_bits = 1, diff --git a/esphome/components/uart/uart_component.cpp b/esphome/components/uart/uart_component.cpp new file mode 100644 index 0000000000..09b8c975ab --- /dev/null +++ b/esphome/components/uart/uart_component.cpp @@ -0,0 +1,24 @@ +#include "uart_component.h" + +namespace esphome { +namespace uart { + +static const char *const TAG = "uart"; + +bool UARTComponent::check_read_timeout_(size_t len) { + if (this->available() >= int(len)) + return true; + + uint32_t start_time = millis(); + while (this->available() < int(len)) { + if (millis() - start_time > 100) { + ESP_LOGE(TAG, "Reading from UART timed out at byte %u!", this->available()); + return false; + } + yield(); + } + return true; +} + +} // namespace uart +} // namespace esphome diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h new file mode 100644 index 0000000000..de85cd2ca3 --- /dev/null +++ b/esphome/components/uart/uart_component.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace uart { + +enum UARTParityOptions { + UART_CONFIG_PARITY_NONE, + UART_CONFIG_PARITY_EVEN, + UART_CONFIG_PARITY_ODD, +}; + +const LogString *parity_to_str(UARTParityOptions parity); + +class UARTComponent { + public: + void write_array(const std::vector &data) { this->write_array(&data[0], data.size()); } + void write_byte(uint8_t data) { this->write_array(&data, 1); }; + void write_str(const char *str) { + const auto *data = reinterpret_cast(str); + this->write_array(data, strlen(str)); + }; + + virtual void write_array(const uint8_t *data, size_t len) = 0; + + bool read_byte(uint8_t *data) { return this->read_array(data, 1); }; + virtual bool peek_byte(uint8_t *data) = 0; + virtual bool read_array(uint8_t *data, size_t len) = 0; + + /// Return available number of bytes. + virtual int available() = 0; + /// Block until all bytes have been written to the UART bus. + virtual void flush() = 0; + + void set_tx_pin(InternalGPIOPin *tx_pin) { this->tx_pin_ = tx_pin; } + void set_rx_pin(InternalGPIOPin *rx_pin) { this->rx_pin_ = rx_pin; } + void set_rx_buffer_size(size_t rx_buffer_size) { this->rx_buffer_size_ = rx_buffer_size; } + + void set_stop_bits(uint8_t stop_bits) { this->stop_bits_ = stop_bits; } + uint8_t get_stop_bits() const { return this->stop_bits_; } + void set_data_bits(uint8_t data_bits) { this->data_bits_ = data_bits; } + uint8_t get_data_bits() const { return this->data_bits_; } + void set_parity(UARTParityOptions parity) { this->parity_ = parity; } + UARTParityOptions get_parity() const { return this->parity_; } + void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; } + uint32_t get_baud_rate() const { return baud_rate_; } + + protected: + virtual void check_logger_conflict() = 0; + bool check_read_timeout_(size_t len = 1); + + InternalGPIOPin *tx_pin_; + InternalGPIOPin *rx_pin_; + size_t rx_buffer_size_; + uint32_t baud_rate_; + uint8_t stop_bits_; + uint8_t data_bits_; + UARTParityOptions parity_; +}; + +} // namespace uart +} // namespace esphome diff --git a/esphome/components/uart/uart_esp32.cpp b/esphome/components/uart/uart_component_esp32_arduino.cpp similarity index 64% rename from esphome/components/uart/uart_esp32.cpp rename to esphome/components/uart/uart_component_esp32_arduino.cpp index db2757780e..1b1ce382f2 100644 --- a/esphome/components/uart/uart_esp32.cpp +++ b/esphome/components/uart/uart_component_esp32_arduino.cpp @@ -1,13 +1,17 @@ -#ifdef ARDUINO_ARCH_ESP32 -#include "uart.h" -#include "esphome/core/log.h" -#include "esphome/core/helpers.h" +#ifdef USE_ESP32_FRAMEWORK_ARDUINO #include "esphome/core/application.h" #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "uart_component_esp32_arduino.h" + +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#endif namespace esphome { namespace uart { -static const char *const TAG = "uart_esp32"; +static const char *const TAG = "uart.arduino_esp32"; static const uint32_t UART_PARITY_EVEN = 0 << 0; static const uint32_t UART_PARITY_ODD = 1 << 0; @@ -20,7 +24,7 @@ static const uint32_t UART_NB_STOP_BIT_1 = 1 << 4; static const uint32_t UART_NB_STOP_BIT_2 = 3 << 4; static const uint32_t UART_TICK_APB_CLOCK = 1 << 27; -uint32_t UARTComponent::get_config() { +uint32_t ESP32ArduinoUARTComponent::get_config() { uint32_t config = 0; /* @@ -67,71 +71,63 @@ uint32_t UARTComponent::get_config() { return config; } -void UARTComponent::setup() { +void ESP32ArduinoUARTComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up UART..."); // Use Arduino HardwareSerial UARTs if all used pins match the ones // preconfigured by the platform. For example if RX disabled but TX pin // is 1 we still want to use Serial. + bool is_default_tx, is_default_rx; #ifdef CONFIG_IDF_TARGET_ESP32C3 - if (this->tx_pin_.value_or(21) == 21 && this->rx_pin_.value_or(20) == 20) { + is_default_tx = tx_pin_ == nullptr || tx_pin_->get_pin() == 21; + is_default_rx = rx_pin_ == nullptr || rx_pin_->get_pin() == 20; #else - if (this->tx_pin_.value_or(1) == 1 && this->rx_pin_.value_or(3) == 3) { + is_default_tx = tx_pin_ == nullptr || tx_pin_->get_pin() == 1; + is_default_rx = rx_pin_ == nullptr || rx_pin_->get_pin() == 3; #endif + if (is_default_tx && is_default_rx) { this->hw_serial_ = &Serial; } else { static uint8_t next_uart_num = 1; this->hw_serial_ = new HardwareSerial(next_uart_num++); // NOLINT(cppcoreguidelines-owning-memory) } - int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1; - int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1; - this->hw_serial_->begin(this->baud_rate_, get_config(), rx, tx, this->invert_); + int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1; + int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1; + bool invert = false; + if (tx_pin_ != nullptr && tx_pin_->is_inverted()) + invert = true; + if (rx_pin_ != nullptr && rx_pin_->is_inverted()) + invert = true; + this->hw_serial_->begin(this->baud_rate_, get_config(), rx, tx, invert); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); } -void UARTComponent::dump_config() { +void ESP32ArduinoUARTComponent::dump_config() { ESP_LOGCONFIG(TAG, "UART Bus:"); - if (this->tx_pin_.has_value()) { - ESP_LOGCONFIG(TAG, " TX Pin: GPIO%d", *this->tx_pin_); - } - if (this->rx_pin_.has_value()) { - ESP_LOGCONFIG(TAG, " RX Pin: GPIO%d", *this->rx_pin_); + LOG_PIN(" TX Pin: ", tx_pin_); + LOG_PIN(" RX Pin: ", rx_pin_); + if (this->rx_pin_ != nullptr) { ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_); } ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_); ESP_LOGCONFIG(TAG, " Data Bits: %u", this->data_bits_); ESP_LOGCONFIG(TAG, " Parity: %s", LOG_STR_ARG(parity_to_str(this->parity_))); ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_); - this->check_logger_conflict_(); + this->check_logger_conflict(); } -void UARTComponent::write_byte(uint8_t data) { - this->hw_serial_->write(data); - ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data), data); -} -void UARTComponent::write_array(const uint8_t *data, size_t len) { +void ESP32ArduinoUARTComponent::write_array(const uint8_t *data, size_t len) { this->hw_serial_->write(data, len); for (size_t i = 0; i < len; i++) { ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); } } -void UARTComponent::write_str(const char *str) { - this->hw_serial_->write(str); - ESP_LOGVV(TAG, " Wrote \"%s\"", str); -} -bool UARTComponent::read_byte(uint8_t *data) { - if (!this->check_read_timeout_()) - return false; - *data = this->hw_serial_->read(); - ESP_LOGVV(TAG, " Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(*data), *data); - return true; -} -bool UARTComponent::peek_byte(uint8_t *data) { +bool ESP32ArduinoUARTComponent::peek_byte(uint8_t *data) { if (!this->check_read_timeout_()) return false; *data = this->hw_serial_->peek(); return true; } -bool UARTComponent::read_array(uint8_t *data, size_t len) { +bool ESP32ArduinoUARTComponent::read_array(uint8_t *data, size_t len) { if (!this->check_read_timeout_(len)) return false; this->hw_serial_->readBytes(data, len); @@ -141,26 +137,25 @@ bool UARTComponent::read_array(uint8_t *data, size_t len) { return true; } -bool UARTComponent::check_read_timeout_(size_t len) { - if (this->available() >= len) - return true; - - uint32_t start_time = millis(); - while (this->available() < len) { - if (millis() - start_time > 1000) { - ESP_LOGE(TAG, "Reading from UART timed out at byte %u!", this->available()); - return false; - } - yield(); - } - return true; -} -int UARTComponent::available() { return this->hw_serial_->available(); } -void UARTComponent::flush() { +int ESP32ArduinoUARTComponent::available() { return this->hw_serial_->available(); } +void ESP32ArduinoUARTComponent::flush() { ESP_LOGVV(TAG, " Flushing..."); this->hw_serial_->flush(); } +void ESP32ArduinoUARTComponent::check_logger_conflict() { +#ifdef USE_LOGGER + if (this->hw_serial_ == nullptr || logger::global_logger->get_baud_rate() == 0) { + return; + } + + if (this->hw_serial_ == logger::global_logger->get_hw_serial()) { + ESP_LOGW(TAG, " You're using the same serial port for logging and the UART component. Please " + "disable logging over the serial port by setting logger->baud_rate to 0."); + } +#endif +} + } // namespace uart } // namespace esphome -#endif // ARDUINO_ARCH_ESP32 +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/uart/uart_component_esp32_arduino.h b/esphome/components/uart/uart_component_esp32_arduino.h new file mode 100644 index 0000000000..c6f445ff12 --- /dev/null +++ b/esphome/components/uart/uart_component_esp32_arduino.h @@ -0,0 +1,40 @@ +#pragma once + +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +#include +#include +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "uart_component.h" + +namespace esphome { +namespace uart { + +class ESP32ArduinoUARTComponent : public UARTComponent, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::BUS; } + + void write_array(const uint8_t *data, size_t len) override; + + bool peek_byte(uint8_t *data) override; + bool read_array(uint8_t *data, size_t len) override; + + int available() override; + void flush() override; + + uint32_t get_config(); + + protected: + void check_logger_conflict() override; + + HardwareSerial *hw_serial_{nullptr}; +}; + +} // namespace uart +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/uart/uart_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp similarity index 60% rename from esphome/components/uart/uart_esp8266.cpp rename to esphome/components/uart/uart_component_esp8266.cpp index a74f2601e4..2188a4a4bc 100644 --- a/esphome/components/uart/uart_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -1,9 +1,9 @@ -#ifdef ARDUINO_ARCH_ESP8266 -#include "uart.h" -#include "esphome/core/log.h" -#include "esphome/core/helpers.h" +#ifdef USE_ESP8266 +#include "uart_component_esp8266.h" #include "esphome/core/application.h" #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" #ifdef USE_LOGGER #include "esphome/components/logger/logger.h" @@ -12,10 +12,10 @@ namespace esphome { namespace uart { -static const char *const TAG = "uart_esp8266"; -bool UARTComponent::serial0InUse = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static const char *const TAG = "uart.arduino_esp8266"; +bool ESP8266UartComponent::serial0InUse = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -uint32_t UARTComponent::get_config() { +uint32_t ESP8266UartComponent::get_config() { uint32_t config = 0; if (this->parity_ == UART_CONFIG_PARITY_NONE) @@ -48,15 +48,15 @@ uint32_t UARTComponent::get_config() { return config; } -void UARTComponent::setup() { +void ESP8266UartComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up UART bus..."); // Use Arduino HardwareSerial UARTs if all used pins match the ones // preconfigured by the platform. For example if RX disabled but TX pin // is 1 we still want to use Serial. SerialConfig config = static_cast(get_config()); - if (!UARTComponent::serial0InUse && this->tx_pin_.value_or(1) == 1 && - this->rx_pin_.value_or(3) == 3 + if (!ESP8266UartComponent::serial0InUse && (tx_pin_ == nullptr || tx_pin_->get_pin() == 1) && + (rx_pin_ == nullptr || rx_pin_->get_pin() == 3) #ifdef USE_LOGGER // we will use UART0 if logger isn't using it in swapped mode && (logger::global_logger->get_hw_serial() == nullptr || @@ -66,9 +66,9 @@ void UARTComponent::setup() { this->hw_serial_ = &Serial; this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); - UARTComponent::serial0InUse = true; - } else if (!UARTComponent::serial0InUse && this->tx_pin_.value_or(15) == 15 && - this->rx_pin_.value_or(13) == 13 + ESP8266UartComponent::serial0InUse = true; + } else if (!ESP8266UartComponent::serial0InUse && (tx_pin_ == nullptr || tx_pin_->get_pin() == 15) && + (rx_pin_ == nullptr || rx_pin_->get_pin() == 13) #ifdef USE_LOGGER // we will use UART0 swapped if logger isn't using it in regular mode && (logger::global_logger->get_hw_serial() == nullptr || @@ -79,27 +79,23 @@ void UARTComponent::setup() { this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); this->hw_serial_->swap(); - UARTComponent::serial0InUse = true; - } else if (this->tx_pin_.value_or(2) == 2 && this->rx_pin_.value_or(8) == 8) { + ESP8266UartComponent::serial0InUse = true; + } else if ((tx_pin_ == nullptr || tx_pin_->get_pin() == 2) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 8)) { this->hw_serial_ = &Serial1; this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); } else { this->sw_serial_ = new ESP8266SoftwareSerial(); // NOLINT - int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1; - int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1; - this->sw_serial_->setup(tx, rx, this->baud_rate_, this->stop_bits_, this->data_bits_, this->parity_, + this->sw_serial_->setup(tx_pin_, rx_pin_, this->baud_rate_, this->stop_bits_, this->data_bits_, this->parity_, this->rx_buffer_size_); } } -void UARTComponent::dump_config() { +void ESP8266UartComponent::dump_config() { ESP_LOGCONFIG(TAG, "UART Bus:"); - if (this->tx_pin_.has_value()) { - ESP_LOGCONFIG(TAG, " TX Pin: GPIO%d", *this->tx_pin_); - } - if (this->rx_pin_.has_value()) { - ESP_LOGCONFIG(TAG, " RX Pin: GPIO%d", *this->rx_pin_); + LOG_PIN(" TX Pin: ", tx_pin_); + LOG_PIN(" RX Pin: ", rx_pin_); + if (this->rx_pin_ != nullptr) { ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_); // NOLINT } ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_); @@ -111,18 +107,23 @@ void UARTComponent::dump_config() { } else { ESP_LOGCONFIG(TAG, " Using software serial"); } - this->check_logger_conflict_(); + this->check_logger_conflict(); } -void UARTComponent::write_byte(uint8_t data) { - if (this->hw_serial_ != nullptr) { - this->hw_serial_->write(data); - } else { - this->sw_serial_->write_byte(data); +void ESP8266UartComponent::check_logger_conflict() { +#ifdef USE_LOGGER + if (this->hw_serial_ == nullptr || logger::global_logger->get_baud_rate() == 0) { + return; } - ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data), data); + + if (this->hw_serial_ == logger::global_logger->get_hw_serial()) { + ESP_LOGW(TAG, " You're using the same serial port for logging and the UART component. Please " + "disable logging over the serial port by setting logger->baud_rate to 0."); + } +#endif } -void UARTComponent::write_array(const uint8_t *data, size_t len) { + +void ESP8266UartComponent::write_array(const uint8_t *data, size_t len) { if (this->hw_serial_ != nullptr) { this->hw_serial_->write(data, len); } else { @@ -133,28 +134,7 @@ void UARTComponent::write_array(const uint8_t *data, size_t len) { ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); } } -void UARTComponent::write_str(const char *str) { - if (this->hw_serial_ != nullptr) { - this->hw_serial_->write(str); - } else { - const auto *data = reinterpret_cast(str); - for (size_t i = 0; data[i] != 0; i++) - this->sw_serial_->write_byte(data[i]); - } - ESP_LOGVV(TAG, " Wrote \"%s\"", str); -} -bool UARTComponent::read_byte(uint8_t *data) { - if (!this->check_read_timeout_()) - return false; - if (this->hw_serial_ != nullptr) { - *data = this->hw_serial_->read(); - } else { - *data = this->sw_serial_->read_byte(); - } - ESP_LOGVV(TAG, " Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(*data), *data); - return true; -} -bool UARTComponent::peek_byte(uint8_t *data) { +bool ESP8266UartComponent::peek_byte(uint8_t *data) { if (!this->check_read_timeout_()) return false; if (this->hw_serial_ != nullptr) { @@ -164,7 +144,7 @@ bool UARTComponent::peek_byte(uint8_t *data) { } return true; } -bool UARTComponent::read_array(uint8_t *data, size_t len) { +bool ESP8266UartComponent::read_array(uint8_t *data, size_t len) { if (!this->check_read_timeout_(len)) return false; if (this->hw_serial_ != nullptr) { @@ -179,28 +159,14 @@ bool UARTComponent::read_array(uint8_t *data, size_t len) { return true; } -bool UARTComponent::check_read_timeout_(size_t len) { - if (this->available() >= int(len)) - return true; - - uint32_t start_time = millis(); - while (this->available() < int(len)) { - if (millis() - start_time > 100) { - ESP_LOGE(TAG, "Reading from UART timed out at byte %u!", this->available()); - return false; - } - yield(); - } - return true; -} -int UARTComponent::available() { +int ESP8266UartComponent::available() { if (this->hw_serial_ != nullptr) { return this->hw_serial_->available(); } else { return this->sw_serial_->available(); } } -void UARTComponent::flush() { +void ESP8266UartComponent::flush() { ESP_LOGVV(TAG, " Flushing..."); if (this->hw_serial_ != nullptr) { this->hw_serial_->flush(); @@ -208,32 +174,31 @@ void UARTComponent::flush() { this->sw_serial_->flush(); } } -void ESP8266SoftwareSerial::setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_rate, uint8_t stop_bits, - uint32_t data_bits, UARTParityOptions parity, size_t rx_buffer_size) { +void ESP8266SoftwareSerial::setup(InternalGPIOPin *tx_pin, InternalGPIOPin *rx_pin, uint32_t baud_rate, + uint8_t stop_bits, uint32_t data_bits, UARTParityOptions parity, + size_t rx_buffer_size) { this->bit_time_ = F_CPU / baud_rate; this->rx_buffer_size_ = rx_buffer_size; this->stop_bits_ = stop_bits; this->data_bits_ = data_bits; this->parity_ = parity; - if (tx_pin != -1) { - auto pin = GPIOPin(tx_pin, OUTPUT); - this->gpio_tx_pin_ = &pin; - pin.setup(); - this->tx_pin_ = pin.to_isr(); - this->tx_pin_->digital_write(true); + if (tx_pin != nullptr) { + gpio_tx_pin_ = tx_pin; + gpio_tx_pin_->setup(); + tx_pin_ = gpio_tx_pin_->to_isr(); + tx_pin_.digital_write(true); } - if (rx_pin != -1) { - auto pin = GPIOPin(rx_pin, INPUT); - pin.setup(); - this->gpio_rx_pin_ = &pin; - this->rx_pin_ = pin.to_isr(); - this->rx_buffer_ = new uint8_t[this->rx_buffer_size_]; // NOLINT - pin.attach_interrupt(ESP8266SoftwareSerial::gpio_intr, this, FALLING); + if (rx_pin != nullptr) { + gpio_rx_pin_ = rx_pin; + gpio_rx_pin_->setup(); + rx_pin_ = gpio_rx_pin_->to_isr(); + rx_buffer_ = new uint8_t[this->rx_buffer_size_]; // NOLINT + gpio_rx_pin_->attach_interrupt(ESP8266SoftwareSerial::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE); } } -void ICACHE_RAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg) { +void IRAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg) { uint32_t wait = arg->bit_time_ + arg->bit_time_ / 3 - 500; - const uint32_t start = ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) + const uint32_t start = arch_get_cpu_cycle_count(); uint8_t rec = 0; // Manually unroll the loop for (int i = 0; i < arg->data_bits_; i++) @@ -254,10 +219,10 @@ void ICACHE_RAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg arg->rx_buffer_[arg->rx_in_pos_] = rec; arg->rx_in_pos_ = (arg->rx_in_pos_ + 1) % arg->rx_buffer_size_; // Clear RX pin so that the interrupt doesn't re-trigger right away again. - arg->rx_pin_->clear_interrupt(); + arg->rx_pin_.clear_interrupt(); } -void ICACHE_RAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { - if (this->tx_pin_ == nullptr) { +void IRAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { + if (this->gpio_tx_pin_ == nullptr) { ESP_LOGE(TAG, "UART doesn't have TX pins set!"); return; } @@ -273,7 +238,7 @@ void ICACHE_RAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { { InterruptLock lock; uint32_t wait = this->bit_time_; - const uint32_t start = ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) + const uint32_t start = arch_get_cpu_cycle_count(); // Start bit this->write_bit_(false, &wait, start); for (int i = 0; i < this->data_bits_; i++) { @@ -290,17 +255,17 @@ void ICACHE_RAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { this->wait_(&wait, start); } } -void ICACHE_RAM_ATTR ESP8266SoftwareSerial::wait_(uint32_t *wait, const uint32_t &start) { - while (ESP.getCycleCount() - start < *wait) // NOLINT(readability-static-accessed-through-instance) +void IRAM_ATTR ESP8266SoftwareSerial::wait_(uint32_t *wait, const uint32_t &start) { + while (arch_get_cpu_cycle_count() - start < *wait) ; *wait += this->bit_time_; } -bool ICACHE_RAM_ATTR ESP8266SoftwareSerial::read_bit_(uint32_t *wait, const uint32_t &start) { +bool IRAM_ATTR ESP8266SoftwareSerial::read_bit_(uint32_t *wait, const uint32_t &start) { this->wait_(wait, start); - return this->rx_pin_->digital_read(); + return this->rx_pin_.digital_read(); } -void ICACHE_RAM_ATTR ESP8266SoftwareSerial::write_bit_(bool bit, uint32_t *wait, const uint32_t &start) { - this->tx_pin_->digital_write(bit); +void IRAM_ATTR ESP8266SoftwareSerial::write_bit_(bool bit, uint32_t *wait, const uint32_t &start) { + this->tx_pin_.digital_write(bit); this->wait_(wait, start); } uint8_t ESP8266SoftwareSerial::read_byte() { @@ -327,4 +292,4 @@ int ESP8266SoftwareSerial::available() { } // namespace uart } // namespace esphome -#endif // ARDUINO_ARCH_ESP8266 +#endif // USE_ESP8266 diff --git a/esphome/components/uart/uart_component_esp8266.h b/esphome/components/uart/uart_component_esp8266.h new file mode 100644 index 0000000000..921d77e4f3 --- /dev/null +++ b/esphome/components/uart/uart_component_esp8266.h @@ -0,0 +1,79 @@ +#pragma once + +#ifdef USE_ESP8266 + +#include +#include +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "uart_component.h" + +namespace esphome { +namespace uart { + +class ESP8266SoftwareSerial { + public: + void setup(InternalGPIOPin *tx_pin, InternalGPIOPin *rx_pin, uint32_t baud_rate, uint8_t stop_bits, + uint32_t data_bits, UARTParityOptions parity, size_t rx_buffer_size); + + uint8_t read_byte(); + uint8_t peek_byte(); + + void flush(); + + void write_byte(uint8_t data); + + int available(); + + protected: + static void gpio_intr(ESP8266SoftwareSerial *arg); + + void wait_(uint32_t *wait, const uint32_t &start); + bool read_bit_(uint32_t *wait, const uint32_t &start); + void write_bit_(bool bit, uint32_t *wait, const uint32_t &start); + + uint32_t bit_time_{0}; + uint8_t *rx_buffer_{nullptr}; + size_t rx_buffer_size_; + volatile size_t rx_in_pos_{0}; + size_t rx_out_pos_{0}; + uint8_t stop_bits_; + uint8_t data_bits_; + UARTParityOptions parity_; + InternalGPIOPin *gpio_tx_pin_{nullptr}; + ISRInternalGPIOPin tx_pin_; + InternalGPIOPin *gpio_rx_pin_{nullptr}; + ISRInternalGPIOPin rx_pin_; +}; + +class ESP8266UartComponent : public UARTComponent, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::BUS; } + + void write_array(const uint8_t *data, size_t len) override; + + bool peek_byte(uint8_t *data) override; + bool read_array(uint8_t *data, size_t len) override; + + int available() override; + void flush() override; + + uint32_t get_config(); + + protected: + void check_logger_conflict() override; + + HardwareSerial *hw_serial_{nullptr}; + ESP8266SoftwareSerial *sw_serial_{nullptr}; + + private: + static bool serial0InUse; +}; + +} // namespace uart +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp new file mode 100644 index 0000000000..3d4a634a72 --- /dev/null +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -0,0 +1,201 @@ +#ifdef USE_ESP_IDF + +#include "uart_component_esp_idf.h" +#include "esphome/core/application.h" +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#endif + +namespace esphome { +namespace uart { +static const char *const TAG = "uart.idf"; + +uart_config_t IDFUARTComponent::get_config() { + uart_parity_t parity = UART_PARITY_DISABLE; + if (this->parity_ == UART_CONFIG_PARITY_EVEN) + parity = UART_PARITY_EVEN; + else if (this->parity_ == UART_CONFIG_PARITY_ODD) + parity = UART_PARITY_ODD; + + uart_word_length_t data_bits; + switch (this->data_bits_) { + case 5: + data_bits = UART_DATA_5_BITS; + break; + case 6: + data_bits = UART_DATA_6_BITS; + break; + case 7: + data_bits = UART_DATA_7_BITS; + break; + case 8: + data_bits = UART_DATA_8_BITS; + break; + default: + data_bits = UART_DATA_BITS_MAX; + break; + } + + uart_config_t uart_config; + uart_config.baud_rate = this->baud_rate_; + uart_config.data_bits = data_bits; + uart_config.parity = parity; + uart_config.stop_bits = this->stop_bits_ == 1 ? UART_STOP_BITS_1 : UART_STOP_BITS_2; + uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; + uart_config.source_clk = UART_SCLK_APB; + uart_config.rx_flow_ctrl_thresh = 122; + + return uart_config; +} + +void IDFUARTComponent::setup() { + static uint8_t next_uart_num = 0; +#ifdef USE_LOGGER + if (logger::global_logger->get_uart_num() == next_uart_num) + next_uart_num++; +#endif + if (next_uart_num >= UART_NUM_MAX) { + ESP_LOGW(TAG, "Maximum number of UART components created already."); + this->mark_failed(); + return; + } + this->uart_num_ = next_uart_num++; + ESP_LOGCONFIG(TAG, "Setting up UART %u...", this->uart_num_); + + this->lock_ = xSemaphoreCreateMutex(); + + xSemaphoreTake(this->lock_, portMAX_DELAY); + + uart_config_t uart_config = this->get_config(); + esp_err_t err = uart_param_config(this->uart_num_, &uart_config); + if (err != ESP_OK) { + ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + + err = uart_driver_install(this->uart_num_, this->rx_buffer_size_, 0, 0, nullptr, 0); + if (err != ESP_OK) { + ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + + int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1; + int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1; + + err = uart_set_pin(this->uart_num_, tx, rx, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); + if (err != ESP_OK) { + ESP_LOGW(TAG, "uart_set_pin failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + + uint32_t invert = 0; + if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) + invert |= UART_SIGNAL_TXD_INV; + if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) + invert |= UART_SIGNAL_RXD_INV; + + err = uart_set_line_inverse(this->uart_num_, invert); + if (err != ESP_OK) { + ESP_LOGW(TAG, "uart_set_line_inverse failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + + xSemaphoreGive(this->lock_); +} + +void IDFUARTComponent::dump_config() { + ESP_LOGCONFIG(TAG, "UART Bus:"); + ESP_LOGCONFIG(TAG, " Number: %u", this->uart_num_); + LOG_PIN(" TX Pin: ", tx_pin_); + LOG_PIN(" RX Pin: ", rx_pin_); + if (this->rx_pin_ != nullptr) { + ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_); + } + ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_); + ESP_LOGCONFIG(TAG, " Data Bits: %u", this->data_bits_); + ESP_LOGCONFIG(TAG, " Parity: %s", LOG_STR_ARG(parity_to_str(this->parity_))); + ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_); + this->check_logger_conflict(); +} + +void IDFUARTComponent::write_array(const uint8_t *data, size_t len) { + xSemaphoreTake(this->lock_, portMAX_DELAY); + uart_write_bytes(this->uart_num_, data, len); + xSemaphoreGive(this->lock_); + for (size_t i = 0; i < len; i++) { + ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); + } +} +bool IDFUARTComponent::peek_byte(uint8_t *data) { + if (!this->check_read_timeout_()) + return false; + xSemaphoreTake(this->lock_, portMAX_DELAY); + if (this->has_peek_) + *data = this->peek_byte_; + else { + int len = uart_read_bytes(this->uart_num_, data, 1, 20 / portTICK_RATE_MS); + if (len == 0) { + *data = 0; + } else { + this->has_peek_ = true; + this->peek_byte_ = *data; + } + } + xSemaphoreGive(this->lock_); + return true; +} +bool IDFUARTComponent::read_array(uint8_t *data, size_t len) { + size_t length_to_read = len; + if (!this->check_read_timeout_(len)) + return false; + xSemaphoreTake(this->lock_, portMAX_DELAY); + if (this->has_peek_) { + length_to_read--; + *data = this->peek_byte_; + data++; + this->has_peek_ = false; + } + if (length_to_read > 0) + uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_RATE_MS); + + xSemaphoreGive(this->lock_); + for (size_t i = 0; i < len; i++) { + ESP_LOGVV(TAG, " Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); + } + + return true; +} + +int IDFUARTComponent::available() { + size_t available; + + xSemaphoreTake(this->lock_, portMAX_DELAY); + uart_get_buffered_data_len(this->uart_num_, &available); + if (this->has_peek_) + available++; + xSemaphoreGive(this->lock_); + + return available; +} + +void IDFUARTComponent::flush() { + ESP_LOGVV(TAG, " Flushing..."); + xSemaphoreTake(this->lock_, portMAX_DELAY); + uart_wait_tx_done(this->uart_num_, portMAX_DELAY); + xSemaphoreGive(this->lock_); +} + +void IDFUARTComponent::check_logger_conflict() {} + +} // namespace uart +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/uart/uart_component_esp_idf.h b/esphome/components/uart/uart_component_esp_idf.h new file mode 100644 index 0000000000..68cceafda2 --- /dev/null +++ b/esphome/components/uart/uart_component_esp_idf.h @@ -0,0 +1,39 @@ +#pragma once + +#ifdef USE_ESP_IDF + +#include +#include "esphome/core/component.h" +#include "uart_component.h" + +namespace esphome { +namespace uart { + +class IDFUARTComponent : public UARTComponent, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::BUS; } + + void write_array(const uint8_t *data, size_t len) override; + + bool peek_byte(uint8_t *data) override; + bool read_array(uint8_t *data, size_t len) override; + + int available() override; + void flush() override; + + protected: + void check_logger_conflict() override; + uart_port_t uart_num_; + uart_config_t get_config(); + SemaphoreHandle_t lock_; + + bool has_peek_{false}; + uint8_t peek_byte_; +}; + +} // namespace uart +} // namespace esphome + +#endif // USE_ESP_IDF diff --git a/esphome/components/uln2003/uln2003.h b/esphome/components/uln2003/uln2003.h index 4bcf1e88e3..4f559ed9a0 100644 --- a/esphome/components/uln2003/uln2003.h +++ b/esphome/components/uln2003/uln2003.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/stepper/stepper.h" namespace esphome { diff --git a/esphome/components/ultrasonic/ultrasonic_sensor.cpp b/esphome/components/ultrasonic/ultrasonic_sensor.cpp index e53cd7cf7a..9f47f9f6b9 100644 --- a/esphome/components/ultrasonic/ultrasonic_sensor.cpp +++ b/esphome/components/ultrasonic/ultrasonic_sensor.cpp @@ -1,5 +1,6 @@ #include "ultrasonic_sensor.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace ultrasonic { @@ -11,22 +12,31 @@ void UltrasonicSensorComponent::setup() { this->trigger_pin_->setup(); this->trigger_pin_->digital_write(false); this->echo_pin_->setup(); + // isr is faster to access + echo_isr_ = echo_pin_->to_isr(); } void UltrasonicSensorComponent::update() { this->trigger_pin_->digital_write(true); delayMicroseconds(this->pulse_time_us_); this->trigger_pin_->digital_write(false); - uint32_t time = pulseIn( // NOLINT - this->echo_pin_->get_pin(), uint8_t(!this->echo_pin_->is_inverted()), this->timeout_us_); + const uint32_t start = micros(); + while (micros() - start < timeout_us_ && echo_isr_.digital_read()) + ; + while (micros() - start < timeout_us_ && !echo_isr_.digital_read()) + ; + const uint32_t pulse_start = micros(); + while (micros() - start < timeout_us_ && echo_isr_.digital_read()) + ; + const uint32_t pulse_end = micros(); - ESP_LOGV(TAG, "Echo took %uµs", time); + ESP_LOGV(TAG, "Echo took %uµs", pulse_end - pulse_start); - if (time == 0) { + if (pulse_end - start >= timeout_us_) { ESP_LOGD(TAG, "'%s' - Distance measurement timed out!", this->name_.c_str()); this->publish_state(NAN); } else { - float result = UltrasonicSensorComponent::us_to_m(time); + float result = UltrasonicSensorComponent::us_to_m(pulse_end - pulse_start); ESP_LOGD(TAG, "'%s' - Got distance: %.2f m", this->name_.c_str(), result); this->publish_state(result); } diff --git a/esphome/components/ultrasonic/ultrasonic_sensor.h b/esphome/components/ultrasonic/ultrasonic_sensor.h index 633c1b17fb..e0d71b99ef 100644 --- a/esphome/components/ultrasonic/ultrasonic_sensor.h +++ b/esphome/components/ultrasonic/ultrasonic_sensor.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/gpio.h" #include "esphome/components/sensor/sensor.h" namespace esphome { @@ -10,7 +10,7 @@ namespace ultrasonic { class UltrasonicSensorComponent : public sensor::Sensor, public PollingComponent { public: void set_trigger_pin(GPIOPin *trigger_pin) { trigger_pin_ = trigger_pin; } - void set_echo_pin(GPIOPin *echo_pin) { echo_pin_ = echo_pin; } + void set_echo_pin(InternalGPIOPin *echo_pin) { echo_pin_ = echo_pin; } /// Set the timeout for waiting for the echo in µs. void set_timeout_us(uint32_t timeout_us); @@ -34,7 +34,8 @@ class UltrasonicSensorComponent : public sensor::Sensor, public PollingComponent /// Helper function to convert the specified distance in meters to the echo duration in µs. GPIOPin *trigger_pin_; - GPIOPin *echo_pin_; + InternalGPIOPin *echo_pin_; + ISRInternalGPIOPin echo_isr_; uint32_t timeout_us_{}; /// 2 meters. uint32_t pulse_time_us_{}; }; diff --git a/esphome/components/uptime/uptime_sensor.cpp b/esphome/components/uptime/uptime_sensor.cpp index 755795ad53..40325d2a36 100644 --- a/esphome/components/uptime/uptime_sensor.cpp +++ b/esphome/components/uptime/uptime_sensor.cpp @@ -1,6 +1,7 @@ #include "uptime_sensor.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" namespace esphome { namespace uptime { diff --git a/esphome/components/vl53l0x/vl53l0x_sensor.cpp b/esphome/components/vl53l0x/vl53l0x_sensor.cpp index 65a4ec72bb..d68d69b79c 100644 --- a/esphome/components/vl53l0x/vl53l0x_sensor.cpp +++ b/esphome/components/vl53l0x/vl53l0x_sensor.cpp @@ -36,8 +36,6 @@ void VL53L0XSensor::setup() { if (!esphome::vl53l0x::VL53L0XSensor::enable_pin_setup_complete) { for (auto &vl53_sensor : vl53_sensors) { if (vl53_sensor->enable_pin_ != nullptr) { - // Disable the enable pin to force vl53 to HW Standby mode - ESP_LOGD(TAG, "i2c vl53l0x disable enable pins: GPIO%u", (vl53_sensor->enable_pin_)->get_pin()); // Set enable pin as OUTPUT and disable the enable pin to force vl53 to HW Standby mode vl53_sensor->enable_pin_->setup(); vl53_sensor->enable_pin_->digital_write(false); @@ -111,7 +109,7 @@ void VL53L0XSensor::setup() { reg(0xFF) = 0x00; reg(0x80) = 0x00; - uint8_t ref_spad_map[6]; + uint8_t ref_spad_map[6] = {}; this->read_bytes(0xB0, ref_spad_map, 6); reg(0xFF) = 0x01; @@ -294,7 +292,7 @@ void VL53L0XSensor::loop() { } if (this->waiting_for_interrupt_) { if (reg(0x13).get() & 0x07) { - uint16_t range_mm; + uint16_t range_mm = 0; this->read_byte_16(0x14 + 10, &range_mm); reg(0x0B) = 0x01; this->waiting_for_interrupt_ = false; diff --git a/esphome/components/vl53l0x/vl53l0x_sensor.h b/esphome/components/vl53l0x/vl53l0x_sensor.h index 83a6322dcd..a2e24e7550 100644 --- a/esphome/components/vl53l0x/vl53l0x_sensor.h +++ b/esphome/components/vl53l0x/vl53l0x_sensor.h @@ -3,7 +3,7 @@ #include #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index f82a8893eb..97777a8986 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1,8 +1,11 @@ +#ifdef USE_ARDUINO + #include "web_server.h" #include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/core/util.h" #include "esphome/components/json/json_util.h" +#include "esphome/components/network/util.h" #include "StreamString.h" @@ -154,7 +157,7 @@ void WebServer::setup() { } void WebServer::dump_config() { ESP_LOGCONFIG(TAG, "Web Server:"); - ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->base_->get_port()); + ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->base_->get_port()); if (this->using_auth()) { ESP_LOGCONFIG(TAG, " Basic authentication enabled"); } @@ -853,3 +856,5 @@ bool WebServer::isRequestHandlerTrivial() { return false; } } // namespace web_server } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 54d7356ac9..0eaa2e9a75 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include "esphome/core/controller.h" #include "esphome/components/web_server_base/web_server_base.h" @@ -192,3 +194,5 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { } // namespace web_server } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index 6351941aee..b0babcab46 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -1,12 +1,14 @@ +#ifdef USE_ARDUINO + #include "web_server_base.h" #include "esphome/core/log.h" #include "esphome/core/application.h" #include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include #endif @@ -27,12 +29,12 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin if (index == 0) { ESP_LOGI(TAG, "OTA Update Start: %s", filename.c_str()); this->ota_read_length_ = 0; -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 Update.runAsync(true); // NOLINTNEXTLINE(readability-static-accessed-through-instance) success = Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000); #endif -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 if (Update.isRunning()) Update.abort(); success = Update.begin(UPDATE_SIZE_UNKNOWN, U_FLASH); @@ -97,3 +99,5 @@ float WebServerBase::get_setup_priority() const { } // namespace web_server_base } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index 158006ae40..4ef67f959c 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include #include "esphome/core/component.h" @@ -74,3 +76,5 @@ class OTARequestHandler : public AsyncWebHandler { } // namespace web_server_base } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index c2943d0645..7d029bbea1 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv import esphome.final_validate as fv from esphome import automation from esphome.automation import Condition -from esphome.components.network import add_mdns_library from esphome.const import ( CONF_AP, CONF_BSSID, @@ -24,7 +23,6 @@ from esphome.const import ( CONF_STATIC_IP, CONF_SUBNET, CONF_USE_ADDRESS, - CONF_ENABLE_MDNS, CONF_PRIORITY, CONF_IDENTITY, CONF_CERTIFICATE_AUTHORITY, @@ -34,6 +32,7 @@ from esphome.const import ( CONF_EAP, ) from esphome.core import CORE, HexInt, coroutine_with_priority +from esphome.components.network import IPAddress from . import wpa2_eap @@ -41,7 +40,6 @@ AUTO_LOAD = ["network"] wifi_ns = cg.esphome_ns.namespace("wifi") EAPAuth = wifi_ns.struct("EAPAuth") -IPAddress = cg.global_ns.class_("IPAddress") ManualIP = wifi_ns.struct("ManualIP") WiFiComponent = wifi_ns.class_("WiFiComponent", cg.Component) WiFiAP = wifi_ns.struct("WiFiAP") @@ -155,18 +153,17 @@ def final_validate_power_esp32_ble(value): # WiFi should be in modem sleep (!=NONE) with BLE coexistence # https://docs.espressif.com/projects/esp-idf/en/v3.3.5/api-guides/wifi.html#station-sleep return - framework_version = fv.get_arduino_framework_version() - if framework_version not in (None, "dev") and framework_version < "1.0.5": - # Only frameworks 1.0.5+ impacted - return - full = fv.full_config.get() for conflicting in [ "esp32_ble", "esp32_ble_beacon", "esp32_ble_server", "esp32_ble_tracker", ]: - if conflicting in full: + try: + cv.require_framework_version(esp32_arduino=cv.Version(1, 0, 5))(None) + except cv.Invalid: + pass + else: raise cv.Invalid( f"power_save_mode NONE is incompatible with {conflicting}. " f"Please remove the power save mode. See also " @@ -236,7 +233,6 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_MANUAL_IP): STA_MANUAL_IP_SCHEMA, cv.Optional(CONF_EAP): EAP_AUTH_SCHEMA, cv.Optional(CONF_AP): WIFI_NETWORK_AP, - cv.Optional(CONF_ENABLE_MDNS, default=True): cv.boolean, cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name, cv.Optional( CONF_REBOOT_TIMEOUT, default="15min" @@ -249,6 +245,10 @@ CONFIG_SCHEMA = cv.All( cv.SplitDefault(CONF_OUTPUT_POWER, esp8266=20.0): cv.All( cv.decibel, cv.float_range(min=10.0, max=20.5) ), + cv.Optional("enable_mdns"): cv.invalid( + "This option has been removed. Please use the [disabled] option under the " + "new mdns component instead." + ), } ), _validate, @@ -345,9 +345,6 @@ async def to_code(config): cg.add_define("USE_WIFI") - if config[CONF_ENABLE_MDNS]: - add_mdns_library() - # Register at end for OTA safe mode await cg.register_component(var, config) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 0a1e347e8f..6e0ce8c044 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -1,9 +1,9 @@ #include "wifi_component.h" -#ifdef ARDUINO_ARCH_ESP32 +#if defined(USE_ESP32) || defined(USE_ESP_IDF) #include #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include #endif @@ -14,7 +14,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/util.h" #include "esphome/core/application.h" @@ -39,7 +39,7 @@ void WiFiComponent::setup() { this->wifi_pre_setup_(); uint32_t hash = fnv1_hash(App.get_compilation_time()); - this->pref_ = global_preferences.make_preference(hash, true); + this->pref_ = global_preferences->make_preference(hash, true); SavedWifiSettings save{}; if (this->pref_.load(&save)) { @@ -83,12 +83,10 @@ void WiFiComponent::setup() { esp32_improv::global_improv_component->start(); #endif this->wifi_apply_hostname_(); -#if defined(ARDUINO_ARCH_ESP32) && defined(USE_MDNS) - network_setup_mdns(); -#endif } void WiFiComponent::loop() { + this->wifi_loop_(); const uint32_t now = millis(); if (this->has_sta()) { @@ -158,8 +156,6 @@ void WiFiComponent::loop() { } } } - - network_tick_mdns(); } WiFiComponent::WiFiComponent() { global_wifi_component = this; } @@ -167,9 +163,9 @@ WiFiComponent::WiFiComponent() { global_wifi_component = this; } bool WiFiComponent::has_ap() const { return this->has_ap_; } bool WiFiComponent::has_sta() const { return !this->sta_.empty(); } void WiFiComponent::set_fast_connect(bool fast_connect) { this->fast_connect_ = fast_connect; } -IPAddress WiFiComponent::get_ip_address() { +network::IPAddress WiFiComponent::get_ip_address() { if (this->has_sta()) - return this->wifi_sta_ip_(); + return this->wifi_sta_ip(); if (this->has_ap()) return this->wifi_soft_ap_ip(); return {}; @@ -205,16 +201,13 @@ void WiFiComponent::setup_ap_config_() { ESP_LOGCONFIG(TAG, " AP Password: '%s'", this->ap_.get_password().c_str()); if (this->ap_.get_manual_ip().has_value()) { auto manual = *this->ap_.get_manual_ip(); - ESP_LOGCONFIG(TAG, " AP Static IP: '%s'", manual.static_ip.toString().c_str()); - ESP_LOGCONFIG(TAG, " AP Gateway: '%s'", manual.gateway.toString().c_str()); - ESP_LOGCONFIG(TAG, " AP Subnet: '%s'", manual.subnet.toString().c_str()); + ESP_LOGCONFIG(TAG, " AP Static IP: '%s'", manual.static_ip.str().c_str()); + ESP_LOGCONFIG(TAG, " AP Gateway: '%s'", manual.gateway.str().c_str()); + ESP_LOGCONFIG(TAG, " AP Subnet: '%s'", manual.subnet.str().c_str()); } this->ap_setup_ = this->wifi_start_ap_(this->ap_); - ESP_LOGCONFIG(TAG, " IP Address: %s", this->wifi_soft_ap_ip().toString().c_str()); -#if defined(ARDUINO_ARCH_ESP8266) && defined(USE_MDNS) - network_setup_mdns(this->wifi_soft_ap_ip(), 1); -#endif + ESP_LOGCONFIG(TAG, " IP Address: %s", this->wifi_soft_ap_ip().str().c_str()); if (!this->has_sta()) { this->state_ = WIFI_COMPONENT_STATE_AP; @@ -286,9 +279,8 @@ void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) { } if (ap.get_manual_ip().has_value()) { ManualIP m = *ap.get_manual_ip(); - ESP_LOGV(TAG, " Manual IP: Static IP=%s Gateway=%s Subnet=%s DNS1=%s DNS2=%s", m.static_ip.toString().c_str(), - m.gateway.toString().c_str(), m.subnet.toString().c_str(), m.dns1.toString().c_str(), - m.dns2.toString().c_str()); + ESP_LOGV(TAG, " Manual IP: Static IP=%s Gateway=%s Subnet=%s DNS1=%s DNS2=%s", m.static_ip.str().c_str(), + m.gateway.str().c_str(), m.subnet.str().c_str(), m.dns1.str().c_str(), m.dns2.str().c_str()); } else { ESP_LOGV(TAG, " Using DHCP IP"); } @@ -353,26 +345,23 @@ const LogString *get_signal_bars(int8_t rssi) { } void WiFiComponent::print_connect_params_() { - uint8_t bssid[6] = {}; - uint8_t *raw_bssid = WiFi.BSSID(); - if (raw_bssid != nullptr) - memcpy(bssid, raw_bssid, sizeof(bssid)); + bssid_t bssid = wifi_bssid(); - ESP_LOGCONFIG(TAG, " SSID: " LOG_SECRET("'%s'"), WiFi.SSID().c_str()); - ESP_LOGCONFIG(TAG, " IP Address: %s", WiFi.localIP().toString().c_str()); + ESP_LOGCONFIG(TAG, " SSID: " LOG_SECRET("'%s'"), wifi_ssid().c_str()); + ESP_LOGCONFIG(TAG, " IP Address: %s", wifi_sta_ip().str().c_str()); ESP_LOGCONFIG(TAG, " BSSID: " LOG_SECRET("%02X:%02X:%02X:%02X:%02X:%02X"), bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); ESP_LOGCONFIG(TAG, " Hostname: '%s'", App.get_name().c_str()); - int8_t rssi = WiFi.RSSI(); + int8_t rssi = wifi_rssi(); ESP_LOGCONFIG(TAG, " Signal strength: %d dB %s", rssi, LOG_STR_ARG(get_signal_bars(rssi))); if (this->selected_ap_.get_bssid().has_value()) { ESP_LOGV(TAG, " Priority: %.1f", this->get_sta_priority(*this->selected_ap_.get_bssid())); } - ESP_LOGCONFIG(TAG, " Channel: %d", WiFi.channel()); - ESP_LOGCONFIG(TAG, " Subnet: %s", WiFi.subnetMask().toString().c_str()); - ESP_LOGCONFIG(TAG, " Gateway: %s", WiFi.gatewayIP().toString().c_str()); - ESP_LOGCONFIG(TAG, " DNS1: %s", WiFi.dnsIP(0).toString().c_str()); - ESP_LOGCONFIG(TAG, " DNS2: %s", WiFi.dnsIP(1).toString().c_str()); + ESP_LOGCONFIG(TAG, " Channel: %d", wifi_channel_()); + ESP_LOGCONFIG(TAG, " Subnet: %s", wifi_subnet_mask_().str().c_str()); + ESP_LOGCONFIG(TAG, " Gateway: %s", wifi_gateway_ip_().str().c_str()); + ESP_LOGCONFIG(TAG, " DNS1: %s", wifi_dns_ip_(0).str().c_str()); + ESP_LOGCONFIG(TAG, " DNS2: %s", wifi_dns_ip_(1).str().c_str()); } void WiFiComponent::start_scanning() { @@ -500,10 +489,10 @@ void WiFiComponent::dump_config() { } void WiFiComponent::check_connecting_finished() { - wl_status_t status = this->wifi_sta_status_(); + auto status = this->wifi_sta_connect_status_(); - if (status == WL_CONNECTED) { - if (WiFi.SSID().equals("")) { + if (status == WiFiSTAConnectStatus::CONNECTED) { + if (wifi_ssid().empty()) { ESP_LOGW(TAG, "Incomplete connection."); this->retry_connect(); return; @@ -527,9 +516,6 @@ void WiFiComponent::check_connecting_finished() { } #endif -#if defined(ARDUINO_ARCH_ESP8266) && defined(USE_MDNS) - network_setup_mdns(this->wifi_sta_ip_(), 0); -#endif this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTED; this->num_retried_ = 0; return; @@ -548,26 +534,23 @@ void WiFiComponent::check_connecting_finished() { return; } - if (status == WL_IDLE_STATUS || status == WL_DISCONNECTED || status == WL_CONNECTION_LOST) { - // WL_DISCONNECTED is set while not connected yet. - // WL_IDLE_STATUS is set while we're waiting for the IP address. - // WL_CONNECTION_LOST happens on the ESP32 + if (status == WiFiSTAConnectStatus::CONNECTING) { return; } - if (status == WL_NO_SSID_AVAIL) { + if (status == WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND) { ESP_LOGW(TAG, "WiFi network can not be found anymore."); this->retry_connect(); return; } - if (status == WL_CONNECT_FAILED) { + if (status == WiFiSTAConnectStatus::ERROR_CONNECT_FAILED) { ESP_LOGW(TAG, "Connecting to WiFi network failed. Are the credentials wrong?"); this->retry_connect(); return; } - ESP_LOGW(TAG, "WiFi Unknown connection status %d", status); + ESP_LOGW(TAG, "WiFi Unknown connection status %d", (int) status); } void WiFiComponent::retry_connect() { @@ -608,8 +591,8 @@ bool WiFiComponent::can_proceed() { } void WiFiComponent::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; } bool WiFiComponent::is_connected() { - return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED && this->wifi_sta_status_() == WL_CONNECTED && - !this->error_from_callback_; + return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED && + this->wifi_sta_connect_status_() == WiFiSTAConnectStatus::CONNECTED && !this->error_from_callback_; } void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) { this->power_save_ = power_save; } @@ -641,7 +624,7 @@ void WiFiAP::set_password(const std::string &password) { this->password_ = passw void WiFiAP::set_eap(optional eap_auth) { this->eap_ = std::move(eap_auth); } #endif void WiFiAP::set_channel(optional channel) { this->channel_ = channel; } -void WiFiAP::set_manual_ip(optional manual_ip) { this->manual_ip_ = std::move(manual_ip); } +void WiFiAP::set_manual_ip(optional manual_ip) { this->manual_ip_ = manual_ip; } void WiFiAP::set_hidden(bool hidden) { this->hidden_ = hidden; } const std::string &WiFiAP::get_ssid() const { return this->ssid_; } const optional &WiFiAP::get_bssid() const { return this->bssid_; } diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 3a4213c93c..d04f15695d 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -5,20 +5,20 @@ #include "esphome/core/defines.h" #include "esphome/core/automation.h" #include "esphome/core/helpers.h" +#include "esphome/components/network/ip_address.h" #include -#include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO #include #include #include #endif -#ifdef ARDUINO_ARCH_ESP8266 -#include +#ifdef USE_ESP8266 #include +#include -#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) +#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) extern "C" { #include }; @@ -54,13 +54,21 @@ enum WiFiComponentState { WIFI_COMPONENT_STATE_AP, }; +enum class WiFiSTAConnectStatus : int { + IDLE, + CONNECTING, + CONNECTED, + ERROR_NETWORK_NOT_FOUND, + ERROR_CONNECT_FAILED, +}; + /// Struct for setting static IPs in WiFiComponent. struct ManualIP { - IPAddress static_ip; - IPAddress gateway; - IPAddress subnet; - IPAddress dns1; ///< The first DNS server. 0.0.0.0 for default. - IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default. + network::IPAddress static_ip; + network::IPAddress gateway; + network::IPAddress subnet; + network::IPAddress dns1; ///< The first DNS server. 0.0.0.0 for default. + network::IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default. }; #ifdef USE_WIFI_WPA2_EAP @@ -153,6 +161,10 @@ enum WiFiPowerSaveMode { WIFI_POWER_SAVE_HIGH, }; +#ifdef USE_ESP_IDF +struct IDFWiFiEvent; +#endif + /// This component is responsible for managing the ESP WiFi interface. class WiFiComponent : public Component { public: @@ -207,13 +219,13 @@ class WiFiComponent : public Component { bool has_sta() const; bool has_ap() const; - IPAddress get_ip_address(); + network::IPAddress get_ip_address(); std::string get_use_address() const; void set_use_address(const std::string &use_address); const std::vector &get_scan_result() const { return scan_result_; } - IPAddress wifi_soft_ap_ip(); + network::IPAddress wifi_soft_ap_ip(); bool has_sta_priority(const bssid_t &bssid) { for (auto &it : this->sta_priorities_) @@ -239,36 +251,46 @@ class WiFiComponent : public Component { }); } + network::IPAddress wifi_sta_ip(); + std::string wifi_ssid(); + bssid_t wifi_bssid(); + + int8_t wifi_rssi(); + protected: static std::string format_mac_addr(const uint8_t mac[6]); void setup_ap_config_(); void print_connect_params_(); + void wifi_loop_(); bool wifi_mode_(optional sta, optional ap); bool wifi_sta_pre_setup_(); bool wifi_apply_output_power_(float output_power); bool wifi_apply_power_save_(); bool wifi_sta_ip_config_(optional manual_ip); - IPAddress wifi_sta_ip_(); bool wifi_apply_hostname_(); bool wifi_sta_connect_(const WiFiAP &ap); void wifi_pre_setup_(); - wl_status_t wifi_sta_status_(); + WiFiSTAConnectStatus wifi_sta_connect_status_(); bool wifi_scan_start_(); bool wifi_ap_ip_config_(optional manual_ip); bool wifi_start_ap_(const WiFiAP &ap); bool wifi_disconnect_(); + int32_t wifi_channel_(); + network::IPAddress wifi_subnet_mask_(); + network::IPAddress wifi_gateway_ip_(); + network::IPAddress wifi_dns_ip_(int num); bool is_captive_portal_active_(); bool is_esp32_improv_active_(); -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 static void wifi_event_callback(System_Event_t *event); void wifi_scan_done_callback_(void *arg, STATUS status); static void s_wifi_scan_done_callback(void *arg, STATUS status); #endif -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO #if ESP_IDF_VERSION_MAJOR >= 4 void wifi_event_callback_(arduino_event_id_t event, arduino_event_info_t info); #else @@ -276,6 +298,9 @@ class WiFiComponent : public Component { #endif void wifi_scan_done_callback_(); #endif +#ifdef USE_ESP_IDF + void wifi_process_event_(IDFWiFiEvent *); +#endif std::string use_address_; std::vector sta_; diff --git a/esphome/components/wifi/wifi_component_esp32.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp similarity index 89% rename from esphome/components/wifi/wifi_component_esp32.cpp rename to esphome/components/wifi/wifi_component_esp32_arduino.cpp index 38c9e12bec..df2692ea81 100644 --- a/esphome/components/wifi/wifi_component_esp32.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -1,6 +1,6 @@ #include "wifi_component.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO #include @@ -15,7 +15,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/application.h" #include "esphome/core/util.h" @@ -24,6 +24,12 @@ namespace wifi { static const char *const TAG = "wifi_esp32"; +static bool s_sta_connected = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_got_ip = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connect_not_found = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connect_error = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + bool WiFiComponent::wifi_mode_(optional sta, optional ap) { uint8_t current_mode = WiFiClass::getMode(); bool current_sta = current_mode & 0b01; @@ -139,12 +145,12 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { return true; } -IPAddress WiFiComponent::wifi_sta_ip_() { +network::IPAddress WiFiComponent::wifi_sta_ip() { if (!this->has_sta()) - return IPAddress(); + return {}; tcpip_adapter_ip_info_t ip; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); - return IPAddress(ip.ip.addr); + return {ip.ip.addr}; } bool WiFiComponent::wifi_apply_hostname_() { @@ -280,6 +286,12 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { return false; } + s_sta_connecting = true; + s_sta_connected = false; + s_sta_got_ip = false; + s_sta_connect_error = false; + s_sta_connect_not_found = false; + return true; } const char *get_auth_mode_str(uint8_t mode) { @@ -429,6 +441,7 @@ void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_i buf[it.ssid_len] = '\0'; ESP_LOGV(TAG, "Event: Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); + s_sta_connected = true; break; } case SYSTEM_EVENT_STA_DISCONNECTED: { @@ -442,10 +455,14 @@ void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_i buf[it.ssid_len] = '\0'; if (it.reason == WIFI_REASON_NO_AP_FOUND) { ESP_LOGW(TAG, "Event: Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); + s_sta_connect_not_found = true; } else { ESP_LOGW(TAG, "Event: Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); + s_sta_connect_error = true; } + s_sta_connected = false; + s_sta_connecting = false; break; } case SYSTEM_EVENT_STA_AUTHMODE_CHANGE: { @@ -474,10 +491,12 @@ void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_i auto it = info.got_ip.ip_info; ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(it.ip).c_str(), format_ip4_addr(it.gw).c_str()); + s_sta_got_ip = true; break; } case SYSTEM_EVENT_STA_LOST_IP: { ESP_LOGV(TAG, "Event: Lost IP"); + s_sta_got_ip = false; break; } case SYSTEM_EVENT_AP_START: { @@ -559,7 +578,21 @@ void WiFiComponent::wifi_pre_setup_() { // Make sure WiFi is in clean state before anything starts this->wifi_mode_(false, false); } -wl_status_t WiFiComponent::wifi_sta_status_() { return WiFiClass::status(); } +WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { + if (s_sta_connected && s_sta_got_ip) { + return WiFiSTAConnectStatus::CONNECTED; + } + if (s_sta_connect_error) { + return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; + } + if (s_sta_connect_not_found) { + return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; + } + if (s_sta_connecting) { + return WiFiSTAConnectStatus::CONNECTING; + } + return WiFiSTAConnectStatus::IDLE; +} bool WiFiComponent::wifi_scan_start_() { // enable STA if (!this->wifi_mode_(true, {})) @@ -610,9 +643,9 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { info.gw.addr = static_cast(manual_ip->gateway); info.netmask.addr = static_cast(manual_ip->subnet); } else { - info.ip.addr = static_cast(IPAddress(192, 168, 4, 1)); - info.gw.addr = static_cast(IPAddress(192, 168, 4, 1)); - info.netmask.addr = static_cast(IPAddress(255, 255, 255, 0)); + info.ip.addr = static_cast(network::IPAddress(192, 168, 4, 1)); + info.gw.addr = static_cast(network::IPAddress(192, 168, 4, 1)); + info.netmask.addr = static_cast(network::IPAddress(255, 255, 255, 0)); } tcpip_adapter_dhcp_status_t dhcp_status; tcpip_adapter_dhcps_get_status(TCPIP_ADAPTER_IF_AP, &dhcp_status); @@ -630,13 +663,13 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { dhcps_lease_t lease; lease.enable = true; - IPAddress start_address = info.ip.addr; + network::IPAddress start_address = info.ip.addr; start_address[3] += 99; lease.start_ip.addr = static_cast(start_address); - ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.toString().c_str()); + ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str()); start_address[3] += 100; lease.end_ip.addr = static_cast(start_address); - ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.toString().c_str()); + ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); err = tcpip_adapter_dhcps_option(TCPIP_ADAPTER_OP_SET, TCPIP_ADAPTER_REQUESTED_IP_ADDRESS, &lease, sizeof(lease)); if (err != ESP_OK) { @@ -694,14 +727,31 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return true; } -IPAddress WiFiComponent::wifi_soft_ap_ip() { +network::IPAddress WiFiComponent::wifi_soft_ap_ip() { tcpip_adapter_ip_info_t ip; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip); - return IPAddress(ip.ip.addr); + return {ip.ip.addr}; } bool WiFiComponent::wifi_disconnect_() { return esp_wifi_disconnect(); } +bssid_t WiFiComponent::wifi_bssid() { + bssid_t bssid{}; + uint8_t *raw_bssid = WiFi.BSSID(); + if (raw_bssid != nullptr) { + for (size_t i = 0; i < bssid.size(); i++) + bssid[i] = raw_bssid[i]; + } + return bssid; +} +std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } +int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } +int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); } +network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } +network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } +network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; } +void WiFiComponent::wifi_loop_() {} + } // namespace wifi } // namespace esphome -#endif +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 5a0ba92b09..5dcfe7a108 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -1,7 +1,7 @@ #include "wifi_component.h" #include "esphome/core/macros.h" -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include @@ -30,7 +30,7 @@ extern "C" { #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/util.h" #include "esphome/core/application.h" @@ -39,6 +39,12 @@ namespace wifi { static const char *const TAG = "wifi_esp8266"; +static bool s_sta_connected = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_got_ip = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connect_not_found = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connect_error = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + bool WiFiComponent::wifi_mode_(optional sta, optional ap) { uint8_t current_mode = wifi_get_opmode(); bool current_sta = current_mode & 0b01; @@ -177,7 +183,7 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { return ret; } -IPAddress WiFiComponent::wifi_sta_ip_() { +network::IPAddress WiFiComponent::wifi_sta_ip() { if (!this->has_sta()) return {}; struct ip_info ip {}; @@ -319,6 +325,12 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { } } + s_sta_connecting = true; + s_sta_connected = false; + s_sta_got_ip = false; + s_sta_connect_error = false; + s_sta_connect_not_found = false; + return true; } @@ -453,6 +465,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { buf[it.ssid_len] = '\0'; ESP_LOGV(TAG, "Event: Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_addr(it.bssid).c_str(), it.channel); + s_sta_connected = true; break; } case EVENT_STAMODE_DISCONNECTED: { @@ -462,10 +475,14 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { buf[it.ssid_len] = '\0'; if (it.reason == REASON_NO_AP_FOUND) { ESP_LOGW(TAG, "Event: Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); + s_sta_connect_not_found = true; } else { ESP_LOGW(TAG, "Event: Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, format_mac_addr(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason))); + s_sta_connect_error = true; } + s_sta_connected = false; + s_sta_connecting = false; break; } case EVENT_STAMODE_AUTHMODE_CHANGE: { @@ -487,6 +504,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { auto it = event->event_info.got_ip; ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s netmask=%s", format_ip_addr(it.ip).c_str(), format_ip_addr(it.gw).c_str(), format_ip_addr(it.mask).c_str()); + s_sta_got_ip = true; break; } case EVENT_STAMODE_DHCP_TIMEOUT: { @@ -563,21 +581,22 @@ void WiFiComponent::wifi_pre_setup_() { this->wifi_mode_(false, false); } -wl_status_t WiFiComponent::wifi_sta_status_() { +WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { station_status_t status = wifi_station_get_connect_status(); switch (status) { case STATION_GOT_IP: - return WL_CONNECTED; + return WiFiSTAConnectStatus::CONNECTED; case STATION_NO_AP_FOUND: - return WL_NO_SSID_AVAIL; + return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; + ; case STATION_CONNECT_FAIL: case STATION_WRONG_PASSWORD: - return WL_CONNECT_FAILED; - case STATION_IDLE: - return WL_IDLE_STATUS; + return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; case STATION_CONNECTING: + return WiFiSTAConnectStatus::CONNECTING; + case STATION_IDLE: default: - return WL_DISCONNECTED; + return WiFiSTAConnectStatus::IDLE; } } bool WiFiComponent::wifi_scan_start_() { @@ -656,9 +675,9 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { info.gw.addr = static_cast(manual_ip->gateway); info.netmask.addr = static_cast(manual_ip->subnet); } else { - info.ip.addr = static_cast(IPAddress(192, 168, 4, 1)); - info.gw.addr = static_cast(IPAddress(192, 168, 4, 1)); - info.netmask.addr = static_cast(IPAddress(255, 255, 255, 0)); + info.ip.addr = static_cast(network::IPAddress(192, 168, 4, 1)); + info.gw.addr = static_cast(network::IPAddress(192, 168, 4, 1)); + info.netmask.addr = static_cast(network::IPAddress(255, 255, 255, 0)); } if (wifi_softap_dhcps_status() == DHCP_STARTED) { @@ -677,13 +696,13 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { #endif struct dhcps_lease lease {}; - IPAddress start_address = info.ip.addr; + network::IPAddress start_address = info.ip.addr; start_address[3] += 99; lease.start_ip.addr = static_cast(start_address); - ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.toString().c_str()); + ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str()); start_address[3] += 100; lease.end_ip.addr = static_cast(start_address); - ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.toString().c_str()); + ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); if (!wifi_softap_set_dhcps_lease(&lease)) { ESP_LOGV(TAG, "Setting SoftAP DHCP lease failed!"); return false; @@ -746,11 +765,27 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return true; } -IPAddress WiFiComponent::wifi_soft_ap_ip() { +network::IPAddress WiFiComponent::wifi_soft_ap_ip() { struct ip_info ip {}; wifi_get_ip_info(SOFTAP_IF, &ip); return {ip.ip.addr}; } +bssid_t WiFiComponent::wifi_bssid() { + bssid_t bssid{}; + uint8_t *raw_bssid = WiFi.BSSID(); + if (raw_bssid != nullptr) { + for (size_t i = 0; i < bssid.size(); i++) + bssid[i] = raw_bssid[i]; + } + return bssid; +} +std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } +int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } +int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); } +network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } +network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } +network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; } +void WiFiComponent::wifi_loop_() {} } // namespace wifi } // namespace esphome diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp new file mode 100644 index 0000000000..676b7f8fba --- /dev/null +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -0,0 +1,901 @@ +#include "wifi_component.h" + +#ifdef USE_ESP_IDF + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#ifdef USE_WIFI_WPA2_EAP +#include +#endif +#include "lwip/err.h" +#include "lwip/dns.h" + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" +#include "esphome/core/application.h" +#include "esphome/core/util.h" + +namespace esphome { +namespace wifi { + +static const char *const TAG = "wifi_esp32"; + +static EventGroupHandle_t s_wifi_event_group; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static xQueueHandle s_event_queue; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static esp_netif_t *s_sta_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static esp_netif_t *s_ap_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_started = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connected = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_got_ip = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_ap_started = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connect_not_found = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connect_error = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_wifi_started = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +struct IDFWiFiEvent { + esp_event_base_t event_base; + int32_t event_id; + union { + wifi_event_sta_scan_done_t sta_scan_done; + wifi_event_sta_connected_t sta_connected; + wifi_event_sta_disconnected_t sta_disconnected; + wifi_event_sta_authmode_change_t sta_authmode_change; + wifi_event_ap_staconnected_t ap_staconnected; + wifi_event_ap_stadisconnected_t ap_stadisconnected; + wifi_event_ap_probe_req_rx_t ap_probe_req_rx; + wifi_event_bss_rssi_low_t bss_rssi_low; + ip_event_got_ip_t ip_got_ip; + ip_event_ap_staipassigned_t ip_ap_staipassigned; + } data; +}; + +// general design: event handler translates events and pushes them to a queue, +// events get processed in the main loop +void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { + IDFWiFiEvent event; + memset(&event, 0, sizeof(IDFWiFiEvent)); + event.event_base = event_base; + event.event_id = event_id; + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { + // no data + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_STOP) { + // no data + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_AUTHMODE_CHANGE) { + memcpy(&event.data.sta_authmode_change, event_data, sizeof(wifi_event_sta_authmode_change_t)); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) { + memcpy(&event.data.sta_connected, event_data, sizeof(wifi_event_sta_connected_t)); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + memcpy(&event.data.sta_disconnected, event_data, sizeof(wifi_event_sta_disconnected_t)); + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + memcpy(&event.data.ip_got_ip, event_data, sizeof(ip_event_got_ip_t)); + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_LOST_IP) { + // no data + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) { + memcpy(&event.data.sta_scan_done, event_data, sizeof(wifi_event_sta_scan_done_t)); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_START) { + // no data + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STOP) { + // no data + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_PROBEREQRECVED) { + memcpy(&event.data.ap_probe_req_rx, event_data, sizeof(wifi_event_ap_probe_req_rx_t)); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED) { + memcpy(&event.data.ap_staconnected, event_data, sizeof(wifi_event_ap_staconnected_t)); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) { + memcpy(&event.data.ap_stadisconnected, event_data, sizeof(wifi_event_ap_stadisconnected_t)); + } else if (event_base == IP_EVENT && event_id == IP_EVENT_AP_STAIPASSIGNED) { + memcpy(&event.data.ip_ap_staipassigned, event_data, sizeof(ip_event_ap_staipassigned_t)); + } else { + // did not match any event, don't send anything + return; + } + + // copy to heap to keep queue object small + auto *to_send = new IDFWiFiEvent; // NOLINT(cppcoreguidelines-owning-memory) + memcpy(to_send, &event, sizeof(IDFWiFiEvent)); + // don't block, we may miss events but the core can handle that + if (xQueueSend(s_event_queue, &to_send, 0L) != pdPASS) { + delete to_send; // NOLINT(cppcoreguidelines-owning-memory) + } +} + +void WiFiComponent::wifi_pre_setup_() { + esp_err_t err = esp_netif_init(); + if (err != ERR_OK) { + ESP_LOGE(TAG, "esp_netif_init failed: %s", esp_err_to_name(err)); + return; + } + s_wifi_event_group = xEventGroupCreate(); + if (s_wifi_event_group == nullptr) { + ESP_LOGE(TAG, "xEventGroupCreate failed"); + return; + } + // NOLINTNEXTLINE(bugprone-sizeof-expression) + s_event_queue = xQueueCreate(64, sizeof(IDFWiFiEvent *)); + if (s_event_queue == nullptr) { + ESP_LOGE(TAG, "xQueueCreate failed"); + return; + } + err = esp_event_loop_create_default(); + if (err != ERR_OK) { + ESP_LOGE(TAG, "esp_event_loop_create_default failed: %s", esp_err_to_name(err)); + return; + } + esp_event_handler_instance_t instance_wifi_id, instance_ip_id; + err = esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, nullptr, &instance_wifi_id); + if (err != ERR_OK) { + ESP_LOGE(TAG, "esp_event_handler_instance_register failed: %s", esp_err_to_name(err)); + return; + } + err = esp_event_handler_instance_register(IP_EVENT, ESP_EVENT_ANY_ID, &event_handler, nullptr, &instance_ip_id); + if (err != ERR_OK) { + ESP_LOGE(TAG, "esp_event_handler_instance_register failed: %s", esp_err_to_name(err)); + return; + } + + s_sta_netif = esp_netif_create_default_wifi_sta(); + s_ap_netif = esp_netif_create_default_wifi_ap(); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + // cfg.nvs_enable = false; + err = esp_wifi_init(&cfg); + if (err != ERR_OK) { + ESP_LOGE(TAG, "esp_wifi_init failed: %s", esp_err_to_name(err)); + return; + } + err = esp_wifi_set_storage(WIFI_STORAGE_RAM); + if (err != ERR_OK) { + ESP_LOGE(TAG, "esp_wifi_set_storage failed: %s", esp_err_to_name(err)); + return; + } +} + +bool WiFiComponent::wifi_mode_(optional sta, optional ap) { + esp_err_t err; + wifi_mode_t current_mode = WIFI_MODE_NULL; + if (s_wifi_started) { + err = esp_wifi_get_mode(¤t_mode); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_wifi_get_mode failed: %s", esp_err_to_name(err)); + return false; + } + } + bool current_sta = current_mode == WIFI_MODE_STA || current_mode == WIFI_MODE_APSTA; + bool current_ap = current_mode == WIFI_MODE_AP || current_mode == WIFI_MODE_APSTA; + + bool set_sta = sta.has_value() ? *sta : current_sta; + bool set_ap = ap.has_value() ? *ap : current_ap; + + wifi_mode_t set_mode; + if (set_sta && set_ap) + set_mode = WIFI_MODE_APSTA; + else if (set_sta && !set_ap) + set_mode = WIFI_MODE_STA; + else if (!set_sta && set_ap) + set_mode = WIFI_MODE_AP; + else + set_mode = WIFI_MODE_NULL; + + if (current_mode == set_mode) + return true; + + if (set_sta && !current_sta) { + ESP_LOGV(TAG, "Enabling STA."); + } else if (!set_sta && current_sta) { + ESP_LOGV(TAG, "Disabling STA."); + } + if (set_ap && !current_ap) { + ESP_LOGV(TAG, "Enabling AP."); + } else if (!set_ap && current_ap) { + ESP_LOGV(TAG, "Disabling AP."); + } + + if (set_mode == WIFI_MODE_NULL && s_wifi_started) { + err = esp_wifi_stop(); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_stop failed: %s", esp_err_to_name(err)); + return false; + } + s_wifi_started = false; + return true; + } + + err = esp_wifi_set_mode(set_mode); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_wifi_set_mode failed: %s", esp_err_to_name(err)); + return false; + } + + if (set_mode != WIFI_MODE_NULL && !s_wifi_started) { + err = esp_wifi_start(); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_start failed: %s", esp_err_to_name(err)); + return false; + } + s_wifi_started = true; + } + + return true; +} + +bool WiFiComponent::wifi_sta_pre_setup_() { return this->wifi_mode_(true, {}); } + +bool WiFiComponent::wifi_apply_output_power_(float output_power) { + int8_t val = static_cast(output_power * 4); + return esp_wifi_set_max_tx_power(val) == ESP_OK; +} + +bool WiFiComponent::wifi_apply_power_save_() { + wifi_ps_type_t power_save; + switch (this->power_save_) { + case WIFI_POWER_SAVE_LIGHT: + power_save = WIFI_PS_MIN_MODEM; + break; + case WIFI_POWER_SAVE_HIGH: + power_save = WIFI_PS_MAX_MODEM; + break; + case WIFI_POWER_SAVE_NONE: + default: + power_save = WIFI_PS_NONE; + break; + } + return esp_wifi_set_ps(power_save) == ESP_OK; +} + +bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { + // enable STA + if (!this->wifi_mode_(true, {})) + return false; + + // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv417wifi_sta_config_t + wifi_config_t conf; + memset(&conf, 0, sizeof(conf)); + strncpy(reinterpret_cast(conf.sta.ssid), ap.get_ssid().c_str(), sizeof(conf.sta.ssid)); + strncpy(reinterpret_cast(conf.sta.password), ap.get_password().c_str(), sizeof(conf.sta.password)); + + // The weakest authmode to accept in the fast scan mode + if (ap.get_password().empty()) { + conf.sta.threshold.authmode = WIFI_AUTH_OPEN; + } else { + conf.sta.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK; + } + +#ifdef USE_WIFI_WPA2_EAP + if (ap.get_eap().has_value()) { + conf.sta.threshold.authmode = WIFI_AUTH_WPA2_ENTERPRISE; + } +#endif + + if (ap.get_bssid().has_value()) { + conf.sta.bssid_set = true; + memcpy(conf.sta.bssid, ap.get_bssid()->data(), 6); + } else { + conf.sta.bssid_set = false; + } + if (ap.get_channel().has_value()) { + conf.sta.channel = *ap.get_channel(); + conf.sta.scan_method = WIFI_FAST_SCAN; + } else { + conf.sta.scan_method = WIFI_ALL_CHANNEL_SCAN; + } + // Listen interval for ESP32 station to receive beacon when WIFI_PS_MAX_MODEM is set. + // Units: AP beacon intervals. Defaults to 3 if set to 0. + conf.sta.listen_interval = 0; + +#if ESP_IDF_VERSION_MAJOR >= 4 + // Protected Management Frame + // Device will prefer to connect in PMF mode if other device also advertizes PMF capability. + conf.sta.pmf_cfg.capable = true; + conf.sta.pmf_cfg.required = false; +#endif + + // note, we do our own filtering + // The minimum rssi to accept in the fast scan mode + conf.sta.threshold.rssi = -127; + + conf.sta.threshold.authmode = WIFI_AUTH_OPEN; + + wifi_config_t current_conf; + esp_err_t err; + err = esp_wifi_get_config(WIFI_IF_STA, ¤t_conf); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_wifi_get_config failed: %s", esp_err_to_name(err)); + // can continue + } + + if (memcmp(¤t_conf, &conf, sizeof(wifi_config_t)) != 0) { + err = esp_wifi_disconnect(); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_disconnect failed: %s", esp_err_to_name(err)); + return false; + } + } + + err = esp_wifi_set_config(WIFI_IF_STA, &conf); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_set_config failed: %s", esp_err_to_name(err)); + return false; + } + + if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) { + return false; + } + + // setup enterprise authentication if required +#ifdef USE_WIFI_WPA2_EAP + if (ap.get_eap().has_value()) { + // note: all certificates and keys have to be null terminated. Lengths are appended by +1 to include \0. + EAPAuth eap = ap.get_eap().value(); + err = esp_wifi_sta_wpa2_ent_set_identity((uint8_t *) eap.identity.c_str(), eap.identity.length()); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_identity failed! %d", err); + } + int ca_cert_len = strlen(eap.ca_cert); + int client_cert_len = strlen(eap.client_cert); + int client_key_len = strlen(eap.client_key); + if (ca_cert_len) { + err = esp_wifi_sta_wpa2_ent_set_ca_cert((uint8_t *) eap.ca_cert, ca_cert_len + 1); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_ca_cert failed! %d", err); + } + } + // workout what type of EAP this is + // validation is not required as the config tool has already validated it + if (client_cert_len && client_key_len) { + // if we have certs, this must be EAP-TLS + err = esp_wifi_sta_wpa2_ent_set_cert_key((uint8_t *) eap.client_cert, client_cert_len + 1, + (uint8_t *) eap.client_key, client_key_len + 1, + (uint8_t *) eap.password.c_str(), strlen(eap.password.c_str())); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_cert_key failed! %d", err); + } + } else { + // in the absence of certs, assume this is username/password based + err = esp_wifi_sta_wpa2_ent_set_username((uint8_t *) eap.username.c_str(), eap.username.length()); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_username failed! %d", err); + } + err = esp_wifi_sta_wpa2_ent_set_password((uint8_t *) eap.password.c_str(), eap.password.length()); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_password failed! %d", err); + } + } + esp_wpa2_config_t wpa2_config = WPA2_CONFIG_INIT_DEFAULT(); + err = esp_wifi_sta_wpa2_ent_enable(&wpa2_config); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_enable failed! %d", err); + } + } +#endif // USE_WIFI_WPA2_EAP + + err = esp_wifi_connect(); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_wifi_connect failed: %s", esp_err_to_name(err)); + return false; + } + + s_sta_connecting = true; + s_sta_connected = false; + s_sta_got_ip = false; + s_sta_connect_error = false; + s_sta_connect_not_found = false; + return true; +} + +bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { + // enable STA + if (!this->wifi_mode_(true, {})) + return false; + + tcpip_adapter_dhcp_status_t dhcp_status; + esp_err_t err = tcpip_adapter_dhcpc_get_status(TCPIP_ADAPTER_IF_STA, &dhcp_status); + if (err != ESP_OK) { + ESP_LOGV(TAG, "tcpip_adapter_dhcpc_get_status failed: %s", esp_err_to_name(err)); + return false; + } + + if (!manual_ip.has_value()) { + // Use DHCP client + if (dhcp_status != TCPIP_ADAPTER_DHCP_STARTED) { + err = tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); + if (err != ESP_OK) { + ESP_LOGV(TAG, "Starting DHCP client failed! %d", err); + } + return err == ESP_OK; + } + return true; + } + + tcpip_adapter_ip_info_t info; + memset(&info, 0, sizeof(info)); + info.ip.addr = static_cast(manual_ip->static_ip); + info.gw.addr = static_cast(manual_ip->gateway); + info.netmask.addr = static_cast(manual_ip->subnet); + + err = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); + if (err != ESP_OK) { + ESP_LOGV(TAG, "tcpip_adapter_dhcpc_stop failed: %s", esp_err_to_name(err)); + return false; + } + + err = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &info); + if (err != ESP_OK) { + ESP_LOGV(TAG, "tcpip_adapter_set_ip_info failed: %s", esp_err_to_name(err)); + return false; + } + + ip_addr_t dns; + dns.type = IPADDR_TYPE_V4; + if (uint32_t(manual_ip->dns1) != 0) { + dns.u_addr.ip4.addr = static_cast(manual_ip->dns1); + dns_setserver(0, &dns); + } + if (uint32_t(manual_ip->dns2) != 0) { + dns.u_addr.ip4.addr = static_cast(manual_ip->dns2); + dns_setserver(1, &dns); + } + + return true; +} + +network::IPAddress WiFiComponent::wifi_sta_ip() { + if (!this->has_sta()) + return {}; + tcpip_adapter_ip_info_t ip; + esp_err_t err = tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); + if (err != ESP_OK) { + ESP_LOGV(TAG, "tcpip_adapter_get_ip_info failed: %s", esp_err_to_name(err)); + return false; + } + return {ip.ip.addr}; +} + +bool WiFiComponent::wifi_apply_hostname_() { + // setting is done in SYSTEM_EVENT_STA_START callback + return true; +} +const char *get_auth_mode_str(uint8_t mode) { + switch (mode) { + case WIFI_AUTH_OPEN: + return "OPEN"; + case WIFI_AUTH_WEP: + return "WEP"; + case WIFI_AUTH_WPA_PSK: + return "WPA PSK"; + case WIFI_AUTH_WPA2_PSK: + return "WPA2 PSK"; + case WIFI_AUTH_WPA_WPA2_PSK: + return "WPA/WPA2 PSK"; + case WIFI_AUTH_WPA2_ENTERPRISE: + return "WPA2 Enterprise"; + case WIFI_AUTH_WPA3_PSK: + return "WPA3 PSK"; + case WIFI_AUTH_WPA2_WPA3_PSK: + return "WPA2/WPA3 PSK"; + case WIFI_AUTH_WAPI_PSK: + return "WAPI PSK"; + default: + return "UNKNOWN"; + } +} + +std::string format_ip4_addr(const esp_ip4_addr_t &ip) { + char buf[20]; + snprintf(buf, sizeof(buf), "%u.%u.%u.%u", uint8_t(ip.addr >> 0), uint8_t(ip.addr >> 8), uint8_t(ip.addr >> 16), + uint8_t(ip.addr >> 24)); + return buf; +} +const char *get_disconnect_reason_str(uint8_t reason) { + switch (reason) { + case WIFI_REASON_AUTH_EXPIRE: + return "Auth Expired"; + case WIFI_REASON_AUTH_LEAVE: + return "Auth Leave"; + case WIFI_REASON_ASSOC_EXPIRE: + return "Association Expired"; + case WIFI_REASON_ASSOC_TOOMANY: + return "Too Many Associations"; + case WIFI_REASON_NOT_AUTHED: + return "Not Authenticated"; + case WIFI_REASON_NOT_ASSOCED: + return "Not Associated"; + case WIFI_REASON_ASSOC_LEAVE: + return "Association Leave"; + case WIFI_REASON_ASSOC_NOT_AUTHED: + return "Association not Authenticated"; + case WIFI_REASON_DISASSOC_PWRCAP_BAD: + return "Disassociate Power Cap Bad"; + case WIFI_REASON_DISASSOC_SUPCHAN_BAD: + return "Disassociate Supported Channel Bad"; + case WIFI_REASON_IE_INVALID: + return "IE Invalid"; + case WIFI_REASON_MIC_FAILURE: + return "Mic Failure"; + case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: + return "4-Way Handshake Timeout"; + case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: + return "Group Key Update Timeout"; + case WIFI_REASON_IE_IN_4WAY_DIFFERS: + return "IE In 4-Way Handshake Differs"; + case WIFI_REASON_GROUP_CIPHER_INVALID: + return "Group Cipher Invalid"; + case WIFI_REASON_PAIRWISE_CIPHER_INVALID: + return "Pairwise Cipher Invalid"; + case WIFI_REASON_AKMP_INVALID: + return "AKMP Invalid"; + case WIFI_REASON_UNSUPP_RSN_IE_VERSION: + return "Unsupported RSN IE version"; + case WIFI_REASON_INVALID_RSN_IE_CAP: + return "Invalid RSN IE Cap"; + case WIFI_REASON_802_1X_AUTH_FAILED: + return "802.1x Authentication Failed"; + case WIFI_REASON_CIPHER_SUITE_REJECTED: + return "Cipher Suite Rejected"; + case WIFI_REASON_BEACON_TIMEOUT: + return "Beacon Timeout"; + case WIFI_REASON_NO_AP_FOUND: + return "AP Not Found"; + case WIFI_REASON_AUTH_FAIL: + return "Authentication Failed"; + case WIFI_REASON_ASSOC_FAIL: + return "Association Failed"; + case WIFI_REASON_HANDSHAKE_TIMEOUT: + return "Handshake Failed"; + case WIFI_REASON_CONNECTION_FAIL: + return "Connection Failed"; + case WIFI_REASON_UNSPECIFIED: + default: + return "Unspecified"; + } +} + +void WiFiComponent::wifi_loop_() { + while (true) { + IDFWiFiEvent *data; + if (xQueueReceive(s_event_queue, &data, 0L) != pdTRUE) { + // no event ready + break; + } + + // process event + wifi_process_event_(data); + + delete data; // NOLINT(cppcoreguidelines-owning-memory) + } +} +void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { + esp_err_t err; + if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_START) { + ESP_LOGV(TAG, "Event: WiFi STA start"); + // apply hostname + err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, App.get_name().c_str()); + if (err != ERR_OK) { + ESP_LOGW(TAG, "tcpip_adapter_set_hostname failed: %s", esp_err_to_name(err)); + } + + s_sta_started = true; + // re-apply power save mode + wifi_apply_power_save_(); + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_STOP) { + ESP_LOGV(TAG, "Event: WiFi STA stop"); + s_sta_started = false; + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_AUTHMODE_CHANGE) { + const auto &it = data->data.sta_authmode_change; + ESP_LOGV(TAG, "Event: Authmode Change old=%s new=%s", get_auth_mode_str(it.old_mode), + get_auth_mode_str(it.new_mode)); + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_CONNECTED) { + const auto &it = data->data.sta_connected; + char buf[33]; + assert(it.ssid_len <= 32); + memcpy(buf, it.ssid, it.ssid_len); + buf[it.ssid_len] = '\0'; + ESP_LOGV(TAG, "Event: Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, + format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); + s_sta_connected = true; + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) { + const auto &it = data->data.sta_disconnected; + char buf[33]; + assert(it.ssid_len <= 32); + memcpy(buf, it.ssid, it.ssid_len); + buf[it.ssid_len] = '\0'; + if (it.reason == WIFI_REASON_NO_AP_FOUND) { + ESP_LOGW(TAG, "Event: Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); + s_sta_connect_not_found = true; + + } else { + ESP_LOGW(TAG, "Event: Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, + format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); + s_sta_connect_error = true; + } + s_sta_connected = false; + s_sta_connecting = false; + error_from_callback_ = true; + + } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_GOT_IP) { + const auto &it = data->data.ip_got_ip; + ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(it.ip_info.ip).c_str(), + format_ip4_addr(it.ip_info.gw).c_str()); + s_sta_got_ip = true; + + } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_LOST_IP) { + ESP_LOGV(TAG, "Event: Lost IP"); + s_sta_got_ip = false; + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_SCAN_DONE) { + const auto &it = data->data.sta_scan_done; + ESP_LOGV(TAG, "Event: WiFi Scan Done status=%u number=%u scan_id=%u", it.status, it.number, it.scan_id); + + scan_result_.clear(); + this->scan_done_ = true; + if (it.status != 0) { + // scan error + return; + } + + uint16_t number = it.number; + std::vector records(number); + err = esp_wifi_scan_get_ap_records(&number, records.data()); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_wifi_scan_get_ap_records failed: %s", esp_err_to_name(err)); + return; + } + records.resize(number); + + scan_result_.reserve(number); + for (int i = 0; i < number; i++) { + auto &record = records[i]; + bssid_t bssid; + std::copy(record.bssid, record.bssid + 6, bssid.begin()); + std::string ssid(reinterpret_cast(record.ssid)); + WiFiScanResult result(bssid, ssid, record.primary, record.rssi, record.authmode != WIFI_AUTH_OPEN, ssid.empty()); + scan_result_.push_back(result); + } + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_START) { + ESP_LOGV(TAG, "Event: WiFi AP start"); + s_ap_started = true; + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STOP) { + ESP_LOGV(TAG, "Event: WiFi AP stop"); + s_ap_started = false; + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_PROBEREQRECVED) { + const auto &it = data->data.ap_probe_req_rx; + ESP_LOGVV(TAG, "Event: AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi); + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STACONNECTED) { + const auto &it = data->data.ap_staconnected; + ESP_LOGV(TAG, "Event: AP client connected MAC=%s", format_mac_addr(it.mac).c_str()); + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STADISCONNECTED) { + const auto &it = data->data.ap_stadisconnected; + ESP_LOGV(TAG, "Event: AP client disconnected MAC=%s", format_mac_addr(it.mac).c_str()); + + } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_AP_STAIPASSIGNED) { + const auto &it = data->data.ip_ap_staipassigned; + ESP_LOGV(TAG, "Event: AP client assigned IP %s", format_ip4_addr(it.ip).c_str()); + } +} + +WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { + if (s_sta_connected && s_sta_got_ip) { + return WiFiSTAConnectStatus::CONNECTED; + } + if (s_sta_connect_error) { + return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; + } + if (s_sta_connect_not_found) { + return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; + } + if (s_sta_connecting) { + return WiFiSTAConnectStatus::CONNECTING; + } + return WiFiSTAConnectStatus::IDLE; +} +bool WiFiComponent::wifi_scan_start_() { + // enable STA + if (!this->wifi_mode_(true, {})) + return false; + + wifi_scan_config_t config{}; + config.ssid = nullptr; + config.bssid = nullptr; + config.channel = 0; + config.show_hidden = true; + config.scan_type = WIFI_SCAN_TYPE_ACTIVE; + config.scan_time.active.min = 100; + config.scan_time.active.max = 300; + + esp_err_t err = esp_wifi_scan_start(&config, false); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_scan_start failed: %s", esp_err_to_name(err)); + return false; + } + + scan_done_ = false; + return true; +} +bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { + esp_err_t err; + + // enable AP + if (!this->wifi_mode_({}, true)) + return false; + + tcpip_adapter_ip_info_t info; + memset(&info, 0, sizeof(info)); + if (manual_ip.has_value()) { + info.ip.addr = static_cast(manual_ip->static_ip); + info.gw.addr = static_cast(manual_ip->gateway); + info.netmask.addr = static_cast(manual_ip->subnet); + } else { + info.ip.addr = static_cast(network::IPAddress(192, 168, 4, 1)); + info.gw.addr = static_cast(network::IPAddress(192, 168, 4, 1)); + info.netmask.addr = static_cast(network::IPAddress(255, 255, 255, 0)); + } + tcpip_adapter_dhcp_status_t dhcp_status; + tcpip_adapter_dhcps_get_status(TCPIP_ADAPTER_IF_AP, &dhcp_status); + err = tcpip_adapter_dhcps_stop(TCPIP_ADAPTER_IF_AP); + if (err != ESP_OK) { + ESP_LOGV(TAG, "tcpip_adapter_dhcps_stop failed! %d", err); + return false; + } + + err = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_AP, &info); + if (err != ESP_OK) { + ESP_LOGV(TAG, "tcpip_adapter_set_ip_info failed! %d", err); + return false; + } + + dhcps_lease_t lease; + lease.enable = true; + network::IPAddress start_address = info.ip.addr; + start_address[3] += 99; + lease.start_ip.addr = static_cast(start_address); + ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str()); + start_address[3] += 100; + lease.end_ip.addr = static_cast(start_address); + ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); + err = tcpip_adapter_dhcps_option(TCPIP_ADAPTER_OP_SET, TCPIP_ADAPTER_REQUESTED_IP_ADDRESS, &lease, sizeof(lease)); + + if (err != ESP_OK) { + ESP_LOGV(TAG, "tcpip_adapter_dhcps_option failed! %d", err); + return false; + } + + err = tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP); + + if (err != ESP_OK) { + ESP_LOGV(TAG, "tcpip_adapter_dhcps_start failed! %d", err); + return false; + } + + return true; +} +bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { + // enable AP + if (!this->wifi_mode_({}, true)) + return false; + + wifi_config_t conf; + memset(&conf, 0, sizeof(conf)); + strncpy(reinterpret_cast(conf.ap.ssid), ap.get_ssid().c_str(), sizeof(conf.ap.ssid)); + conf.ap.channel = ap.get_channel().value_or(1); + conf.ap.ssid_hidden = ap.get_ssid().size(); + conf.ap.max_connection = 5; + conf.ap.beacon_interval = 100; + + if (ap.get_password().empty()) { + conf.ap.authmode = WIFI_AUTH_OPEN; + *conf.ap.password = 0; + } else { + conf.ap.authmode = WIFI_AUTH_WPA2_PSK; + strncpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.password)); + } + +#if ESP_IDF_VERSION_MAJOR >= 4 + // pairwise cipher of SoftAP, group cipher will be derived using this. + conf.ap.pairwise_cipher = WIFI_CIPHER_TYPE_CCMP; +#endif + + esp_err_t err = esp_wifi_set_config(WIFI_IF_AP, &conf); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_set_config failed! %d", err); + return false; + } + + if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) { + ESP_LOGV(TAG, "wifi_ap_ip_config_ failed!"); + return false; + } + + return true; +} +network::IPAddress WiFiComponent::wifi_soft_ap_ip() { + tcpip_adapter_ip_info_t ip; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip); + return {ip.ip.addr}; +} +bool WiFiComponent::wifi_disconnect_() { return esp_wifi_disconnect(); } + +bssid_t WiFiComponent::wifi_bssid() { + wifi_ap_record_t info; + esp_err_t err = esp_wifi_sta_get_ap_info(&info); + bssid_t res{}; + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err)); + return res; + } + std::copy(info.bssid, info.bssid + 6, res.begin()); + return res; +} +std::string WiFiComponent::wifi_ssid() { + wifi_ap_record_t info{}; + esp_err_t err = esp_wifi_sta_get_ap_info(&info); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err)); + return ""; + } + auto *ssid_s = reinterpret_cast(info.ssid); + size_t len = strnlen(ssid_s, sizeof(info.ssid)); + return {ssid_s, len}; +} +int8_t WiFiComponent::wifi_rssi() { + wifi_ap_record_t info; + esp_err_t err = esp_wifi_sta_get_ap_info(&info); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err)); + return 0; + } + return info.rssi; +} +int32_t WiFiComponent::wifi_channel_() { + uint8_t primary; + wifi_second_chan_t second; + esp_err_t err = esp_wifi_get_channel(&primary, &second); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_wifi_get_channel failed: %s", esp_err_to_name(err)); + return 0; + } + return primary; +} +network::IPAddress WiFiComponent::wifi_subnet_mask_() { + esp_netif_ip_info_t ip; + esp_err_t err = esp_netif_get_ip_info(s_sta_netif, &ip); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); + return {}; + } + return {ip.netmask.addr}; +} +network::IPAddress WiFiComponent::wifi_gateway_ip_() { + esp_netif_ip_info_t ip; + esp_err_t err = esp_netif_get_ip_info(s_sta_netif, &ip); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); + return {}; + } + return {ip.gw.addr}; +} +network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { + const ip_addr_t *dns_ip = dns_getserver(num); + return {dns_ip->u_addr.ip4.addr}; +} + +} // namespace wifi +} // namespace esphome + +#endif diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index 6d2be08fa0..de1c7f71fc 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -10,10 +10,10 @@ namespace wifi_info { class IPAddressWiFiInfo : public Component, public text_sensor::TextSensor { public: void loop() override { - IPAddress ip = WiFi.localIP(); + auto ip = wifi::global_wifi_component->wifi_sta_ip(); if (ip != this->last_ip_) { this->last_ip_ = ip; - this->publish_state(ip.toString().c_str()); + this->publish_state(ip.str().c_str()); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } @@ -21,15 +21,15 @@ class IPAddressWiFiInfo : public Component, public text_sensor::TextSensor { void dump_config() override; protected: - IPAddress last_ip_; + network::IPAddress last_ip_; }; class SSIDWiFiInfo : public Component, public text_sensor::TextSensor { public: void loop() override { - String ssid = WiFi.SSID(); - if (this->last_ssid_ != ssid.c_str()) { - this->last_ssid_ = std::string(ssid.c_str()); + std::string ssid = wifi::global_wifi_component->wifi_ssid(); + if (this->last_ssid_ != ssid) { + this->last_ssid_ = ssid; this->publish_state(this->last_ssid_); } } @@ -44,9 +44,9 @@ class SSIDWiFiInfo : public Component, public text_sensor::TextSensor { class BSSIDWiFiInfo : public Component, public text_sensor::TextSensor { public: void loop() override { - uint8_t *bssid = WiFi.BSSID(); - if (memcmp(bssid, this->last_bssid_.data(), 6) != 0) { - std::copy(bssid, bssid + 6, this->last_bssid_.data()); + wifi::bssid_t bssid = wifi::global_wifi_component->wifi_bssid(); + if (memcmp(bssid.data(), last_bssid_.data(), 6) != 0) { + std::copy(bssid.begin(), bssid.end(), last_bssid_.begin()); char buf[30]; sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); this->publish_state(buf); diff --git a/esphome/components/wifi_signal/wifi_signal_sensor.h b/esphome/components/wifi_signal/wifi_signal_sensor.h index 8fe108a530..f797aaa590 100644 --- a/esphome/components/wifi_signal/wifi_signal_sensor.h +++ b/esphome/components/wifi_signal/wifi_signal_sensor.h @@ -10,7 +10,7 @@ namespace wifi_signal { class WiFiSignalSensor : public sensor::Sensor, public PollingComponent { public: - void update() override { this->publish_state(WiFi.RSSI()); } + void update() override { this->publish_state(wifi::global_wifi_component->wifi_rssi()); } void dump_config() override; std::string unique_id() override { return get_mac_address() + "-wifisignal"; } diff --git a/esphome/components/wled/__init__.py b/esphome/components/wled/__init__.py index c9e23bb7eb..2795529203 100644 --- a/esphome/components/wled/__init__.py +++ b/esphome/components/wled/__init__.py @@ -7,7 +7,7 @@ from esphome.const import CONF_NAME, CONF_PORT wled_ns = cg.esphome_ns.namespace("wled") WLEDLightEffect = wled_ns.class_("WLEDLightEffect", AddressableLightEffect) -CONFIG_SCHEMA = cv.Schema({}) +CONFIG_SCHEMA = cv.All(cv.Schema({}), cv.only_with_arduino) @register_addressable_effect( diff --git a/esphome/components/wled/wled_light_effect.cpp b/esphome/components/wled/wled_light_effect.cpp index 500b9bbad2..8c68bca6e3 100644 --- a/esphome/components/wled/wled_light_effect.cpp +++ b/esphome/components/wled/wled_light_effect.cpp @@ -1,12 +1,14 @@ +#ifdef USE_ARDUINO + #include "wled_light_effect.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include #include #endif @@ -246,3 +248,5 @@ bool WLEDLightEffect::parse_dnrgb_frame_(light::AddressableLight &it, const uint } // namespace wled } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/wled/wled_light_effect.h b/esphome/components/wled/wled_light_effect.h index 2a7654ec27..f0021ca978 100644 --- a/esphome/components/wled/wled_light_effect.h +++ b/esphome/components/wled/wled_light_effect.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include "esphome/components/light/addressable_light_effect.h" @@ -39,3 +41,5 @@ class WLEDLightEffect : public light::AddressableLightEffect { } // namespace wled } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 3e7e591f9a..884969f793 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -2,7 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/helpers.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include "mbedtls/ccm.h" diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.h b/esphome/components/xiaomi_ble/xiaomi_ble.h index 7681cbd89c..54ab9a144f 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.h +++ b/esphome/components/xiaomi_ble/xiaomi_ble.h @@ -3,7 +3,7 @@ #include "esphome/core/component.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_ble { diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp index cfbe986fff..97bbd6e6d6 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp @@ -1,7 +1,7 @@ #include "xiaomi_cgd1.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_cgd1 { diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h index b9e05f857c..d05cffc4d1 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_cgd1 { diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp index 8f42eaecaf..a97ca93206 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp @@ -1,7 +1,7 @@ #include "xiaomi_cgdk2.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_cgdk2 { diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h index 70f2ae9e2e..8fd9946537 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_cgdk2 { diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp index d0a91d23f0..e1f83e4ddd 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp @@ -1,7 +1,7 @@ #include "xiaomi_cgg1.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_cgg1 { diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h index e1d812e929..966c05ac79 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_cgg1 { diff --git a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp index 75ed947c25..db63beea89 100644 --- a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp +++ b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp @@ -1,7 +1,7 @@ #include "xiaomi_cgpr1.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_cgpr1 { diff --git a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h index 0b7369c798..eff4b1c6fb 100644 --- a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h +++ b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h @@ -6,7 +6,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_cgpr1 { diff --git a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp index f626daad88..990346e01e 100644 --- a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp +++ b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp @@ -1,7 +1,7 @@ #include "xiaomi_gcls002.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_gcls002 { diff --git a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.h b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.h index d800e2837d..08e1bd7e54 100644 --- a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.h +++ b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_gcls002 { diff --git a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp index 3f2cbf963a..30990b121d 100644 --- a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp +++ b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp @@ -1,7 +1,7 @@ #include "xiaomi_hhccjcy01.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_hhccjcy01 { diff --git a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h index bd9d742b2d..aa99cc004a 100644 --- a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h +++ b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_hhccjcy01 { diff --git a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp index 92b7171004..3ae29088bb 100644 --- a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp +++ b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp @@ -1,7 +1,7 @@ #include "xiaomi_hhccpot002.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_hhccpot002 { diff --git a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.h b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.h index 1add8e27b1..ce746b9ee0 100644 --- a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.h +++ b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_hhccpot002 { diff --git a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp index 646796525e..1efebc2849 100644 --- a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp +++ b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp @@ -1,7 +1,7 @@ #include "xiaomi_jqjcy01ym.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_jqjcy01ym { diff --git a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.h b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.h index d750e1e97f..ca1ad0f27e 100644 --- a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.h +++ b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_jqjcy01ym { diff --git a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp index 1ecf0606a2..a6f27c58b9 100644 --- a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp +++ b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp @@ -1,7 +1,7 @@ #include "xiaomi_lywsd02.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_lywsd02 { diff --git a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.h b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.h index ec00464cb5..641a02bd5a 100644 --- a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.h +++ b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_lywsd02 { diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp index 3eb0b68318..547cc7c114 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp @@ -1,7 +1,7 @@ #include "xiaomi_lywsd03mmc.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_lywsd03mmc { diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h index c2828e3cd1..95710a1508 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_lywsd03mmc { diff --git a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp index f16fce296b..749ca83afb 100644 --- a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp +++ b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp @@ -1,7 +1,7 @@ #include "xiaomi_lywsdcgq.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_lywsdcgq { diff --git a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.h b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.h index 553b5965fd..cbc76f9dd3 100644 --- a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.h +++ b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_lywsdcgq { diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp index fdbb799ea6..0cad5c67b2 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp @@ -1,7 +1,7 @@ #include "xiaomi_mhoc401.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_mhoc401 { diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h index e80916f855..4ab882b2af 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_mhoc401 { diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp index 36b4c8cc00..62378de72c 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp @@ -1,7 +1,7 @@ #include "xiaomi_miscale.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_miscale { diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.h b/esphome/components/xiaomi_miscale/xiaomi_miscale.h index d9da4f9421..409fdeaf93 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.h +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.h @@ -4,7 +4,7 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_miscale { diff --git a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp index 34c5a975c7..9ae95c5f97 100644 --- a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp +++ b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp @@ -1,7 +1,7 @@ #include "xiaomi_miscale2.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_miscale2 { diff --git a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.h b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.h index ead522e1f2..9f7ebf6a1f 100644 --- a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.h +++ b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.h @@ -4,7 +4,7 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_miscale2 { diff --git a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp index df1cede068..16c0b42279 100644 --- a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp +++ b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp @@ -1,7 +1,7 @@ #include "xiaomi_mjyd02yla.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_mjyd02yla { diff --git a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h index 973b19a372..34b1fe4af0 100644 --- a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h +++ b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h @@ -6,7 +6,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_mjyd02yla { diff --git a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp index 53429c1806..1a8e72bd2c 100644 --- a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp +++ b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp @@ -1,7 +1,7 @@ #include "xiaomi_mue4094rt.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_mue4094rt { diff --git a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.h b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.h index 31f913ec94..904c575ae6 100644 --- a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.h +++ b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_mue4094rt { diff --git a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp index 7529bb7431..b57bf5cd05 100644 --- a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp +++ b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp @@ -1,7 +1,7 @@ #include "xiaomi_wx08zm.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_wx08zm { diff --git a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.h b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.h index f3eba0e159..297c7ab47d 100644 --- a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.h +++ b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.h @@ -6,7 +6,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_wx08zm { diff --git a/esphome/components/zyaura/sensor.py b/esphome/components/zyaura/sensor.py index 74f1f9ec61..28a708b866 100644 --- a/esphome/components/zyaura/sensor.py +++ b/esphome/components/zyaura/sensor.py @@ -26,12 +26,8 @@ ZyAuraSensor = zyaura_ns.class_("ZyAuraSensor", cg.PollingComponent) CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(ZyAuraSensor), - cv.Required(CONF_CLOCK_PIN): cv.All( - pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt - ), - cv.Required(CONF_DATA_PIN): cv.All( - pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt - ), + cv.Required(CONF_CLOCK_PIN): cv.All(pins.internal_gpio_input_pin_schema), + cv.Required(CONF_DATA_PIN): cv.All(pins.internal_gpio_input_pin_schema), cv.Optional(CONF_CO2): sensor.sensor_schema( unit_of_measurement=UNIT_PARTS_PER_MILLION, icon=ICON_MOLECULE_CO2, diff --git a/esphome/components/zyaura/zyaura.cpp b/esphome/components/zyaura/zyaura.cpp index 989791f098..11643a5c23 100644 --- a/esphome/components/zyaura/zyaura.cpp +++ b/esphome/components/zyaura/zyaura.cpp @@ -6,7 +6,7 @@ namespace zyaura { static const char *const TAG = "zyaura"; -bool ICACHE_RAM_ATTR ZaDataProcessor::decode(uint32_t ms, bool data) { +bool IRAM_ATTR ZaDataProcessor::decode(uint32_t ms, bool data) { // check if a new message has started, based on time since previous bit if ((ms - this->prev_ms_) > ZA_MAX_MS) { this->num_bits_ = 0; @@ -37,24 +37,24 @@ bool ICACHE_RAM_ATTR ZaDataProcessor::decode(uint32_t ms, bool data) { return false; } -void ZaSensorStore::setup(GPIOPin *pin_clock, GPIOPin *pin_data) { +void ZaSensorStore::setup(InternalGPIOPin *pin_clock, InternalGPIOPin *pin_data) { pin_clock->setup(); pin_data->setup(); this->pin_clock_ = pin_clock->to_isr(); this->pin_data_ = pin_data->to_isr(); - pin_clock->attach_interrupt(ZaSensorStore::interrupt, this, FALLING); + pin_clock->attach_interrupt(ZaSensorStore::interrupt, this, gpio::INTERRUPT_FALLING_EDGE); } -void ICACHE_RAM_ATTR ZaSensorStore::interrupt(ZaSensorStore *arg) { +void IRAM_ATTR ZaSensorStore::interrupt(ZaSensorStore *arg) { uint32_t now = millis(); - bool data_bit = arg->pin_data_->digital_read(); + bool data_bit = arg->pin_data_.digital_read(); if (arg->processor_.decode(now, data_bit)) { arg->set_data_(arg->processor_.message); } } -void ICACHE_RAM_ATTR ZaSensorStore::set_data_(ZaMessage *message) { +void IRAM_ATTR ZaSensorStore::set_data_(ZaMessage *message) { switch (message->type) { case HUMIDITY: this->humidity = (message->value > 10000) ? NAN : (message->value / 100.0f); @@ -82,7 +82,7 @@ bool ZyAuraSensor::publish_state_(sensor::Sensor *sensor, float *value) { sensor->publish_state(*value); // Sensor reported wrong value - if (isnan(*value)) { + if (std::isnan(*value)) { ESP_LOGW(TAG, "Sensor reported invalid data. Is the update interval too small?"); this->status_set_warning(); return false; diff --git a/esphome/components/zyaura/zyaura.h b/esphome/components/zyaura/zyaura.h index eed0c55c35..2b9e3fbb35 100644 --- a/esphome/components/zyaura/zyaura.h +++ b/esphome/components/zyaura/zyaura.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" namespace esphome { @@ -46,12 +46,12 @@ class ZaSensorStore { float temperature = NAN; float humidity = NAN; - void setup(GPIOPin *pin_clock, GPIOPin *pin_data); + void setup(InternalGPIOPin *pin_clock, InternalGPIOPin *pin_data); static void interrupt(ZaSensorStore *arg); protected: - ISRInternalGPIOPin *pin_clock_; - ISRInternalGPIOPin *pin_data_; + ISRInternalGPIOPin pin_clock_; + ISRInternalGPIOPin pin_data_; ZaDataProcessor processor_; void set_data_(ZaMessage *message); @@ -60,8 +60,8 @@ class ZaSensorStore { /// Component for reading temperature/co2/humidity measurements from ZyAura sensors. class ZyAuraSensor : public PollingComponent { public: - void set_pin_clock(GPIOPin *pin) { pin_clock_ = pin; } - void set_pin_data(GPIOPin *pin) { pin_data_ = pin; } + void set_pin_clock(InternalGPIOPin *pin) { pin_clock_ = pin; } + void set_pin_data(InternalGPIOPin *pin) { pin_data_ = pin; } void set_co2_sensor(sensor::Sensor *co2_sensor) { co2_sensor_ = co2_sensor; } void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } @@ -73,8 +73,8 @@ class ZyAuraSensor : public PollingComponent { protected: ZaSensorStore store_; - GPIOPin *pin_clock_; - GPIOPin *pin_data_; + InternalGPIOPin *pin_clock_; + InternalGPIOPin *pin_data_; sensor::Sensor *co2_sensor_{nullptr}; sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; diff --git a/esphome/config.py b/esphome/config.py index e49293e6b2..af6c5b0b64 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -1,4 +1,6 @@ -import collections +import abc +import functools +import heapq import logging import re @@ -15,6 +17,7 @@ from esphome.const import ( CONF_PACKAGES, CONF_SUBSTITUTIONS, CONF_EXTERNAL_COMPONENTS, + TARGET_PLATFORMS, ) from esphome.core import CORE, EsphomeError from esphome.helpers import indent @@ -56,6 +59,27 @@ def _path_begins_with(path, other): # type: (ConfigPath, ConfigPath) -> bool return path[: len(other)] == other +@functools.total_ordering +class _ValidationStepTask: + def __init__(self, priority: float, id_number: int, step: "ConfigValidationStep"): + self.priority = priority + self.id_number = id_number + self.step = step + + @property + def _cmp_tuple(self) -> Tuple[float, int]: + return (-self.priority, self.id_number) + + def __eq__(self, other): + return self._cmp_tuple == other._cmp_tuple + + def __ne__(self, other): + return not (self == other) + + def __lt__(self, other): + return self._cmp_tuple < other._cmp_tuple + + class Config(OrderedDict, fv.FinalValidateConfig): def __init__(self): super().__init__() @@ -68,6 +92,10 @@ class Config(OrderedDict, fv.FinalValidateConfig): # A list of components ids with the config path self.declare_ids = [] # type: List[Tuple[core.ID, ConfigPath]] self._data = {} + # Store pending validation tasks (in heap order) + self._validation_tasks: List[_ValidationStepTask] = [] + # ID to ensure stable order for keys with equal priority + self._validation_tasks_id = 0 def add_error(self, error): # type: (vol.Invalid) -> None @@ -83,6 +111,18 @@ class Config(OrderedDict, fv.FinalValidateConfig): error.path = error.path[last_root + 1 :] self.errors.append(error) + def add_validation_step(self, step: "ConfigValidationStep"): + id_num = self._validation_tasks_id + self._validation_tasks_id += 1 + heapq.heappush( + self._validation_tasks, _ValidationStepTask(step.priority, id_num, step) + ) + + def run_validation_steps(self): + while self._validation_tasks: + task = heapq.heappop(self._validation_tasks) + task.step.run(self) + @contextmanager def catch_error(self, path=None): path = path or [] @@ -206,92 +246,6 @@ def iter_ids(config, path=None): yield from iter_ids(value, path + [key]) -def do_id_pass(result): # type: (Config) -> None - from esphome.cpp_generator import MockObjClass - from esphome.cpp_types import Component - - searching_ids = [] # type: List[Tuple[core.ID, ConfigPath]] - for id, path in iter_ids(result): - if id.is_declaration: - if id.id is not None: - # Look for duplicate definitions - match = next((v for v in result.declare_ids if v[0].id == id.id), None) - if match is not None: - opath = "->".join(str(v) for v in match[1]) - result.add_str_error(f"ID {id.id} redefined! Check {opath}", path) - continue - result.declare_ids.append((id, path)) - else: - searching_ids.append((id, path)) - # Resolve default ids after manual IDs - for id, _ in result.declare_ids: - id.resolve([v[0].id for v in result.declare_ids]) - if isinstance(id.type, MockObjClass) and id.type.inherits_from(Component): - CORE.component_ids.add(id.id) - - # Check searched IDs - for id, path in searching_ids: - if id.id is not None: - # manually declared - match = next((v[0] for v in result.declare_ids if v[0].id == id.id), None) - if match is None or not match.is_manual: - # No declared ID with this name - import difflib - - error = f"Couldn't find ID '{id.id}'. Please check you have defined an ID with that name in your configuration." - # Find candidates - matches = difflib.get_close_matches( - id.id, [v[0].id for v in result.declare_ids if v[0].is_manual] - ) - if matches: - matches_s = ", ".join(f'"{x}"' for x in matches) - error += f" These IDs look similar: {matches_s}." - result.add_str_error(error, path) - continue - if not isinstance(match.type, MockObjClass) or not isinstance( - id.type, MockObjClass - ): - continue - if not match.type.inherits_from(id.type): - result.add_str_error( - f"ID '{id.id}' of type {match.type} doesn't inherit from {id.type}. Please double check your ID is pointing to the correct value", - path, - ) - - if id.id is None and id.type is not None: - matches = [] - for v in result.declare_ids: - if v[0] is None or not isinstance(v[0].type, MockObjClass): - continue - inherits = v[0].type.inherits_from(id.type) - if inherits: - matches.append(v[0]) - - if len(matches) == 0: - result.add_str_error( - f"Couldn't find any component that can be used for '{id.type}'. Are you missing a hub declaration?", - path, - ) - elif len(matches) == 1: - id.id = matches[0].id - elif len(matches) > 1: - if str(id.type) == "time::RealTimeClock": - id.id = matches[0].id - else: - manual_declared_count = sum(1 for m in matches if m.is_manual) - if manual_declared_count > 0: - ids = ", ".join([f"'{m.id}'" for m in matches if m.is_manual]) - result.add_str_error( - f"Too many candidates found for '{path[-1]}' type '{id.type}' {'Some are' if manual_declared_count > 1 else 'One is'} {ids}", - path, - ) - else: - result.add_str_error( - f"Too many candidates found for '{path[-1]}' type '{id.type}' You must assign an explicit ID to the parent component you want to use.", - path, - ) - - def recursive_check_replaceme(value): if isinstance(value, list): return cv.Schema([recursive_check_replaceme])(value) @@ -310,6 +264,389 @@ def recursive_check_replaceme(value): return value +class ConfigValidationStep(abc.ABC): + """A step to for the validation phase.""" + + # Priority of this step, higher means run earlier + priority: float = 0.0 + + @abc.abstractmethod + def run(self, result: Config) -> None: + ... + + +class LoadValidationStep(ConfigValidationStep): + """Load step, this step is called once for each domain config fragment. + + Responsibilties: + - Load component code + - Ensure all AUTO_LOADs are added + - Set output paths of result + """ + + def __init__(self, domain: str, conf: ConfigType): + self.domain = domain + self.conf = conf + + def run(self, result: Config) -> None: + if self.domain.startswith("."): + # Ignore top-level keys starting with a dot + return + result.add_output_path([self.domain], self.domain) + result[self.domain] = self.conf + component = get_component(self.domain) + path = [self.domain] + if component is None: + result.add_str_error(f"Component not found: {self.domain}", path) + return + CORE.loaded_integrations.add(self.domain) + + # Process AUTO_LOAD + for load in component.auto_load: + if load not in result: + result.add_validation_step(AutoLoadValidationStep(load)) + + if not component.is_platform_component: + result.add_validation_step( + MetadataValidationStep([self.domain], self.domain, self.conf, component) + ) + return + + # This is a platform component, proceed to reading platform entries + # Remove this is as an output path + result.remove_output_path([self.domain], self.domain) + + # Ensure conf is a list + if not self.conf: + result[self.domain] = self.conf = [] + elif not isinstance(self.conf, list): + result[self.domain] = self.conf = [self.conf] + + for i, p_config in enumerate(self.conf): + path = [self.domain, i] + # Construct temporary unknown output path + p_domain = f"{self.domain}.unknown" + result.add_output_path(path, p_domain) + result[self.domain][i] = p_config + if not isinstance(p_config, dict): + result.add_str_error("Platform schemas must be key-value pairs.", path) + continue + p_name = p_config.get("platform") + if p_name is None: + result.add_str_error("No platform specified! See 'platform' key.", path) + continue + # Remove temp output path and construct new one + result.remove_output_path(path, p_domain) + p_domain = f"{self.domain}.{p_name}" + result.add_output_path(path, p_domain) + # Try Load platform + platform = get_platform(self.domain, p_name) + if platform is None: + result.add_str_error(f"Platform not found: '{p_domain}'", path) + continue + CORE.loaded_integrations.add(p_name) + + # Process AUTO_LOAD + for load in platform.auto_load: + if load not in result: + result.add_validation_step(AutoLoadValidationStep(load)) + + result.add_validation_step( + MetadataValidationStep(path, p_domain, p_config, platform) + ) + + +class AutoLoadValidationStep(ConfigValidationStep): + """Auto load step. This step is used to automatically load components if + a component requested that with AUTO_LOAD. + """ + + # Only load after all regular loads have taken place + priority = -1.0 + + def __init__(self, domain: str): + self.domain = domain + + def run(self, result: Config) -> None: + if self.domain in result: + # already loaded + return + result.add_validation_step(LoadValidationStep(self.domain, core.AutoLoad())) + + +class MetadataValidationStep(ConfigValidationStep): + """Validate component metadata + + Responsibilties: + - Config transformation (nullable, multi conf) + - Check dependencies + - Check conflicts + - Check supported target platforms + """ + + # All components need to be loaded first to ensure dependency check works + priority = -2.0 + + def __init__( + self, + path: ConfigPath, + domain: str, + conf: ConfigType, + component: ComponentManifest, + ) -> None: + self.path = path + self.domain = domain + self.conf = conf + self.comp = component + + def run(self, result: Config) -> None: + if self.conf is None: + result[self.domain] = self.conf = {} + + success = True + for dependency in self.comp.dependencies: + if dependency not in result: + result.add_str_error( + f"Component {self.domain} requires component {dependency}", + self.path, + ) + success = False + if not success: + return + + success = True + for conflict in self.comp.conflicts_with: + if conflict in result: + result.add_str_error( + f"Component {self.domain} cannot be used together with component {conflict}", + self.path, + ) + success = False + if not success: + return + + if ( + not self.comp.is_platform_component + and self.comp.config_schema is None + and not isinstance(self.conf, core.AutoLoad) + ): + result.add_str_error( + f"Component {self.domain} cannot be loaded via YAML " + "(no CONFIG_SCHEMA).", + self.path, + ) + return + + if self.comp.multi_conf: + if not isinstance(self.conf, list): + result[self.domain] = self.conf = [self.conf] + if ( + not isinstance(self.comp.multi_conf, bool) + and len(self.conf) > self.comp.multi_conf + ): + result.add_str_error( + f"Component {self.domain} supports a maximum of {self.comp.multi_conf} " + f"entries ({len(self.conf)} found).", + self.path, + ) + return + for i, part_conf in enumerate(self.conf): + result.add_validation_step( + SchemaValidationStep( + self.domain, self.path + [i], part_conf, self.comp + ) + ) + return + + result.add_validation_step( + SchemaValidationStep(self.domain, self.path, self.conf, self.comp) + ) + + +class SchemaValidationStep(ConfigValidationStep): + """Schema validation step. + + During this step all CONFIG_SCHEMAs are checked against the configs. + """ + + def __init__( + self, domain: str, path: ConfigPath, conf: ConfigType, comp: ComponentManifest + ): + self.path = path + self.conf = conf + self.comp = comp + + def run(self, result: Config) -> None: + if self.comp.config_schema is None: + return + with result.catch_error(self.path): + if self.comp.is_platform: + # Remove 'platform' key for validation + input_conf = OrderedDict(self.conf) + platform_val = input_conf.pop("platform") + schema = cv.Schema(self.comp.config_schema) + validated = schema(input_conf) + # Ensure result is OrderedDict so we can call move_to_end + if not isinstance(validated, OrderedDict): + validated = OrderedDict(validated) + validated["platform"] = platform_val + validated.move_to_end("platform", last=False) + result.set_by_path(self.path, validated) + else: + schema = cv.Schema(self.comp.config_schema) + validated = schema(self.conf) + result.set_by_path(self.path, validated) + + result.add_validation_step(FinalValidateValidationStep(self.path, self.comp)) + + +class IDPassValidationStep(ConfigValidationStep): + """ID Pass step. + + During this step all ID references are checked. + + If an automatic ID reference is used, a fitting declared ID is automatically searched. + Also checks duplicate ID names, and that referenced IDs are declared. + """ + + # Has to happen after all schemas validated + priority = -10.0 + + def __init__(self) -> None: + pass + + def run(self, result: Config) -> None: + from esphome.cpp_generator import MockObjClass + from esphome.cpp_types import Component + + if result.errors: + # If result already has errors, skip this step + # Otherwise the user will get a bunch of missing ID warnings + # because the component that did not validate doesn't have any IDs set + return + + searching_ids = [] # type: List[Tuple[core.ID, ConfigPath]] + for id, path in iter_ids(result): + if id.is_declaration: + if id.id is not None: + # Look for duplicate definitions + match = next( + (v for v in result.declare_ids if v[0].id == id.id), None + ) + if match is not None: + opath = "->".join(str(v) for v in match[1]) + result.add_str_error( + f"ID {id.id} redefined! Check {opath}", path + ) + continue + result.declare_ids.append((id, path)) + else: + searching_ids.append((id, path)) + + # Resolve default ids after manual IDs + for id, _ in result.declare_ids: + id.resolve([v[0].id for v in result.declare_ids]) + if isinstance(id.type, MockObjClass) and id.type.inherits_from(Component): + CORE.component_ids.add(id.id) + + # Check searched IDs + for id, path in searching_ids: + if id.id is not None: + # manually declared + match = next( + (v[0] for v in result.declare_ids if v[0].id == id.id), None + ) + if match is None or not match.is_manual: + # No declared ID with this name + import difflib + + error = ( + f"Couldn't find ID '{id.id}'. Please check you have defined " + "an ID with that name in your configuration." + ) + # Find candidates + matches = difflib.get_close_matches( + id.id, [v[0].id for v in result.declare_ids if v[0].is_manual] + ) + if matches: + matches_s = ", ".join(f'"{x}"' for x in matches) + error += f" These IDs look similar: {matches_s}." + result.add_str_error(error, path) + continue + if not isinstance(match.type, MockObjClass) or not isinstance( + id.type, MockObjClass + ): + continue + if not match.type.inherits_from(id.type): + result.add_str_error( + f"ID '{id.id}' of type {match.type} doesn't inherit from {id.type}. " + "Please double check your ID is pointing to the correct value", + path, + ) + + if id.id is None and id.type is not None: + matches = [] + for v in result.declare_ids: + if v[0] is None or not isinstance(v[0].type, MockObjClass): + continue + inherits = v[0].type.inherits_from(id.type) + if inherits: + matches.append(v[0]) + + if len(matches) == 0: + result.add_str_error( + f"Couldn't find any component that can be used for '{id.type}'. Are you missing a hub declaration?", + path, + ) + elif len(matches) == 1: + id.id = matches[0].id + elif len(matches) > 1: + if str(id.type) == "time::RealTimeClock": + id.id = matches[0].id + else: + manual_declared_count = sum(1 for m in matches if m.is_manual) + if manual_declared_count > 0: + ids = ", ".join( + [f"'{m.id}'" for m in matches if m.is_manual] + ) + result.add_str_error( + f"Too many candidates found for '{path[-1]}' type '{id.type}' {'Some are' if manual_declared_count > 1 else 'One is'} {ids}", + path, + ) + else: + result.add_str_error( + f"Too many candidates found for '{path[-1]}' type '{id.type}' You must assign an explicit ID to the parent component you want to use.", + path, + ) + + +class FinalValidateValidationStep(ConfigValidationStep): + """Run final_validate_schema for all components.""" + + # Has to happen after ID pass validated + priority = -20.0 + + def __init__(self, path: ConfigPath, comp: ComponentManifest) -> None: + self.path = path + self.comp = comp + + def run(self, result: Config) -> None: + if result.errors: + # If result already has errors, skip this step + return + + if self.comp.final_validate_schema is None: + return + + token = fv.full_config.set(result) + + conf = result.get_nested_item(self.path) + with result.catch_error(self.path): + self.comp.final_validate_schema(conf) + + fv.full_config.reset(token) + + def validate_config(config, command_line_substitutions): result = Config() @@ -384,217 +721,31 @@ def validate_config(config, command_line_substitutions): result[CONF_ESPHOME] = config[CONF_ESPHOME] result.add_output_path([CONF_ESPHOME], CONF_ESPHOME) try: - core_config.preload_core_config(config) + core_config.preload_core_config(config, result) except vol.Invalid as err: result.add_error(err) return result # Remove temporary esphome config path again, it will be reloaded later result.remove_output_path([CONF_ESPHOME], CONF_ESPHOME) - # 3. Load components. - # Load components (also AUTO_LOAD) and set output paths of result - # Queue of items to load, FIFO - load_queue = collections.deque() + # First run platform validation steps + for key in TARGET_PLATFORMS: + if key in config: + result.add_validation_step(LoadValidationStep(key, config[key])) + result.run_validation_steps() + + if result.errors: + return result + for domain, conf in config.items(): - load_queue.append((domain, conf)) + result.add_validation_step(LoadValidationStep(domain, conf)) + result.add_validation_step(IDPassValidationStep()) - # List of items to enter next stage - check_queue = ( - [] - ) # type: List[Tuple[ConfigPath, str, ConfigType, ComponentManifest]] - - # This step handles: - # - Adding output path - # - Auto Load - # - Loading configs into result - - while load_queue: - domain, conf = load_queue.popleft() - if domain.startswith("."): - # Ignore top-level keys starting with a dot - continue - result.add_output_path([domain], domain) - result[domain] = conf - component = get_component(domain) - path = [domain] - if component is None: - result.add_str_error(f"Component not found: {domain}", path) - continue - CORE.loaded_integrations.add(domain) - - # Process AUTO_LOAD - for load in component.auto_load: - if load not in config: - load_conf = core.AutoLoad() - config[load] = load_conf - load_queue.append((load, load_conf)) - - if not component.is_platform_component: - check_queue.append(([domain], domain, conf, component)) - continue - - # This is a platform component, proceed to reading platform entries - # Remove this is as an output path - result.remove_output_path([domain], domain) - - # Ensure conf is a list - if not conf: - result[domain] = conf = [] - elif not isinstance(conf, list): - result[domain] = conf = [conf] - - for i, p_config in enumerate(conf): - path = [domain, i] - # Construct temporary unknown output path - p_domain = f"{domain}.unknown" - result.add_output_path(path, p_domain) - result[domain][i] = p_config - if not isinstance(p_config, dict): - result.add_str_error("Platform schemas must be key-value pairs.", path) - continue - p_name = p_config.get("platform") - if p_name is None: - result.add_str_error("No platform specified! See 'platform' key.", path) - continue - # Remove temp output path and construct new one - result.remove_output_path(path, p_domain) - p_domain = f"{domain}.{p_name}" - result.add_output_path(path, p_domain) - # Try Load platform - platform = get_platform(domain, p_name) - if platform is None: - result.add_str_error(f"Platform not found: '{p_domain}'", path) - continue - CORE.loaded_integrations.add(p_name) - - # Process AUTO_LOAD - for load in platform.auto_load: - if load not in config: - load_conf = core.AutoLoad() - config[load] = load_conf - load_queue.append((load, load_conf)) - - check_queue.append((path, p_domain, p_config, platform)) - - # 4. Validate component metadata, including - # - Transformation (nullable, multi conf) - # - Dependencies - # - Conflicts - # - Supported ESP Platform - - # List of items to proceed to next stage - validate_queue = [] # type: List[Tuple[ConfigPath, ConfigType, ComponentManifest]] - for path, domain, conf, comp in check_queue: - if conf is None: - result[domain] = conf = {} - - success = True - for dependency in comp.dependencies: - if dependency not in config: - result.add_str_error( - f"Component {domain} requires component {dependency}", - path, - ) - success = False - if not success: - continue - - success = True - for conflict in comp.conflicts_with: - if conflict in config: - result.add_str_error( - f"Component {domain} cannot be used together with component {conflict}", - path, - ) - success = False - if not success: - continue - - if CORE.esp_platform not in comp.esp_platforms: - result.add_str_error( - f"Component {domain} doesn't support {CORE.esp_platform}.", - path, - ) - continue - - if ( - not comp.is_platform_component - and comp.config_schema is None - and not isinstance(conf, core.AutoLoad) - ): - result.add_str_error( - f"Component {domain} cannot be loaded via YAML (no CONFIG_SCHEMA).", - path, - ) - continue - - if comp.multi_conf: - if not isinstance(conf, list): - result[domain] = conf = [conf] - if not isinstance(comp.multi_conf, bool) and len(conf) > comp.multi_conf: - result.add_str_error( - f"Component {domain} supports a maximum of {comp.multi_conf} entries ({len(conf)} found).", - path, - ) - continue - for i, part_conf in enumerate(conf): - validate_queue.append((path + [i], part_conf, comp)) - continue - - validate_queue.append((path, conf, comp)) - - # 5. Validate configuration schema - for path, conf, comp in validate_queue: - if comp.config_schema is None: - continue - with result.catch_error(path): - if comp.is_platform: - # Remove 'platform' key for validation - input_conf = OrderedDict(conf) - platform_val = input_conf.pop("platform") - validated = comp.config_schema(input_conf) - # Ensure result is OrderedDict so we can call move_to_end - if not isinstance(validated, OrderedDict): - validated = OrderedDict(validated) - validated["platform"] = platform_val - validated.move_to_end("platform", last=False) - result.set_by_path(path, validated) - else: - validated = comp.config_schema(conf) - result.set_by_path(path, validated) - - # 6. If no validation errors, check IDs - if not result.errors: - # Only parse IDs if no validation error. Otherwise - # user gets confusing messages - do_id_pass(result) - - # 7. Final validation - if not result.errors: - # Inter - components validation - token = fv.full_config.set(result) - - for path, _, comp in validate_queue: - if comp.final_validate_schema is None: - continue - conf = result.get_nested_item(path) - with result.catch_error(path): - comp.final_validate_schema(conf) - - fv.full_config.reset(token) + result.run_validation_steps() return result -def _nested_getitem(data, path): - for item_index in path: - try: - data = data[item_index] - except (KeyError, IndexError, TypeError): - return None - return data - - def humanize_error(config, validation_error): validation_error = str(validation_error) m = re.match( @@ -661,7 +812,6 @@ def _load_config(command_line_substitutions): config = yaml_util.load_yaml(CORE.config_path) except EsphomeError as e: raise InvalidYAMLError(e) from e - CORE.raw_config = config try: result = validate_config(config, command_line_substitutions) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index a864c65dc7..0d8f4f3b64 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1,5 +1,6 @@ """Helpers for config validation using voluptuous.""" +from dataclasses import dataclass import logging import os import re @@ -33,6 +34,9 @@ from esphome.const import ( CONF_UPDATE_INTERVAL, CONF_TYPE_ID, CONF_TYPE, + KEY_CORE, + KEY_FRAMEWORK_VERSION, + KEY_TARGET_FRAMEWORK, ) from esphome.core import ( CORE, @@ -492,20 +496,37 @@ def templatable(other_validators): def only_on(platforms): - """Validate that this option can only be specified on the given ESP platforms.""" + """Validate that this option can only be specified on the given target platforms.""" if not isinstance(platforms, list): platforms = [platforms] def validator_(obj): - if CORE.esp_platform not in platforms: + if CORE.target_platform not in platforms: raise Invalid(f"This feature is only available on {platforms}") return obj return validator_ -only_on_esp32 = only_on("ESP32") -only_on_esp8266 = only_on("ESP8266") +def only_with_framework(frameworks): + """Validate that this option can only be specified on the given frameworks.""" + if not isinstance(frameworks, list): + frameworks = [frameworks] + + def validator_(obj): + if CORE.target_framework not in frameworks: + raise Invalid( + f"This feature is only available with frameworks {frameworks}" + ) + return obj + + return validator_ + + +only_on_esp32 = only_on("esp32") +only_on_esp8266 = only_on("esp8266") +only_with_arduino = only_with_framework("arduino") +only_with_esp_idf = only_with_framework("esp-idf") # Adapted from: @@ -1025,7 +1046,7 @@ def requires_component(comp): # pylint: disable=unsupported-membership-test def validator(value): # pylint: disable=unsupported-membership-test - if comp not in CORE.raw_config: + if comp not in CORE.loaded_integrations: raise Invalid(f"This option requires component {comp}") return value @@ -1401,18 +1422,32 @@ class GenerateID(Optional): class SplitDefault(Optional): """Mark this key to have a split default for ESP8266/ESP32.""" - def __init__(self, key, esp8266=vol.UNDEFINED, esp32=vol.UNDEFINED): + def __init__( + self, + key, + esp8266=vol.UNDEFINED, + esp32=vol.UNDEFINED, + esp32_arduino=vol.UNDEFINED, + esp32_idf=vol.UNDEFINED, + ): super().__init__(key) self._esp8266_default = vol.default_factory(esp8266) - self._esp32_default = vol.default_factory(esp32) + self._esp32_arduino_default = vol.default_factory( + esp32_arduino if esp32 is vol.UNDEFINED else esp32 + ) + self._esp32_idf_default = vol.default_factory( + esp32_idf if esp32 is vol.UNDEFINED else esp32 + ) @property def default(self): if CORE.is_esp8266: return self._esp8266_default - if CORE.is_esp32: - return self._esp32_default - raise ValueError + if CORE.is_esp32 and CORE.using_arduino: + return self._esp32_arduino_default + if CORE.is_esp32 and CORE.using_esp_idf: + return self._esp32_idf_default + raise NotImplementedError @default.setter def default(self, value): @@ -1431,7 +1466,7 @@ class OnlyWith(Optional): @property def default(self): # pylint: disable=unsupported-membership-test - if self._component in CORE.raw_config: + if self._component in CORE.loaded_integrations: return self._default return vol.UNDEFINED @@ -1613,3 +1648,73 @@ def source_refresh(value: str): if value.lower() == "never": return source_refresh("1000y") return positive_time_period_seconds(value) + + +@dataclass(frozen=True, order=True) +class Version: + major: int + minor: int + patch: int + + def __str__(self): + return f"{self.major}.{self.minor}.{self.patch}" + + @classmethod + def parse(cls, value: str) -> "Version": + match = re.match(r"(\d+).(\d+).(\d+)", value) + if match is None: + raise ValueError(f"Not a valid version number {value}") + major = int(match[1]) + minor = int(match[2]) + patch = int(match[3]) + return Version(major=major, minor=minor, patch=patch) + + +def version_number(value): + value = string_strict(value) + try: + return str(Version.parse(value)) + except ValueError as e: + raise Invalid("Not a version number") from e + + +def require_framework_version( + *, + esp_idf=None, + esp32_arduino=None, + esp8266_arduino=None, +): + def validator(value): + core_data = CORE.data[KEY_CORE] + framework = core_data[KEY_TARGET_FRAMEWORK] + if framework == "esp-idf": + if esp_idf is None: + raise Invalid("This feature is incompatible with esp-idf") + required = esp_idf + elif CORE.is_esp32 and framework == "arduino": + if esp32_arduino is None: + raise Invalid( + "This feature is incompatible with ESP32 using arduino framework" + ) + required = esp32_arduino + elif CORE.is_esp8266 and framework == "arduino": + if esp8266_arduino is None: + raise Invalid("This feature is incompatible with ESP8266") + required = esp8266_arduino + else: + raise NotImplementedError + if core_data[KEY_FRAMEWORK_VERSION] < required: + raise Invalid( + f"This feature requires at least framework version {required}" + ) + return value + + return validator + + +@contextmanager +def suppress_invalid(): + try: + yield + except vol.Invalid: + pass diff --git a/esphome/const.py b/esphome/const.py index 598b6351b4..6a3296bcea 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,25 +2,11 @@ __version__ = "2021.10.0-dev" -ESP_PLATFORM_ESP32 = "ESP32" -ESP_PLATFORM_ESP8266 = "ESP8266" -ESP_PLATFORMS = [ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266] - ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" -# Lookup table from ESP32 arduino framework version to latest platformio -# package with that version -# See also https://github.com/platformio/platform-espressif32/releases -ARDUINO_VERSION_ESP32 = { - "dev": "https://github.com/platformio/platform-espressif32.git", - "1.0.6": "platformio/espressif32@3.2.0", - "1.0.5": "platformio/espressif32@3.1.1", - "1.0.4": "platformio/espressif32@3.0.0", - "1.0.3": "platformio/espressif32@1.10.0", - "1.0.2": "platformio/espressif32@1.9.0", - "1.0.1": "platformio/espressif32@1.7.0", - "1.0.0": "platformio/espressif32@1.5.0", -} +TARGET_PLATFORMS = ["esp32", "esp8266"] +TARGET_FRAMEWORKS = ["arduino", "esp-idf"] + # See also https://github.com/platformio/platform-espressif8266/releases ARDUINO_VERSION_ESP8266 = { "dev": "https://github.com/platformio/platform-espressif8266.git", @@ -204,13 +190,11 @@ CONF_ECO2 = "eco2" CONF_EFFECT = "effect" CONF_EFFECTS = "effects" CONF_ELSE = "else" -CONF_ENABLE_MDNS = "enable_mdns" CONF_ENABLE_PIN = "enable_pin" CONF_ENABLE_TIME = "enable_time" CONF_ENERGY = "energy" CONF_ENTITY_ID = "entity_id" CONF_ESP8266_DISABLE_SSL_SUPPORT = "esp8266_disable_ssl_support" -CONF_ESP8266_RESTORE_FROM_FLASH = "esp8266_restore_from_flash" CONF_ESPHOME = "esphome" CONF_ETHERNET = "ethernet" CONF_EVENT = "event" @@ -253,6 +237,7 @@ CONF_FORCE_UPDATE = "force_update" CONF_FORMALDEHYDE = "formaldehyde" CONF_FORMAT = "format" CONF_FORWARD_ACTIVE_ENERGY = "forward_active_energy" +CONF_FRAMEWORK = "framework" CONF_FREQUENCY = "frequency" CONF_FROM = "from" CONF_FULL_SPECTRUM = "full_spectrum" @@ -307,6 +292,7 @@ CONF_INFRARED = "infrared" CONF_INITIAL_MODE = "initial_mode" CONF_INITIAL_OPTION = "initial_option" CONF_INITIAL_VALUE = "initial_value" +CONF_INPUT = "input" CONF_INTEGRATION_TIME = "integration_time" CONF_INTENSITY = "intensity" CONF_INTERLOCK = "interlock" @@ -447,6 +433,7 @@ CONF_ON_VALUE = "on_value" CONF_ON_VALUE_RANGE = "on_value_range" CONF_ONE = "one" CONF_OPEN_ACTION = "open_action" +CONF_OPEN_DRAIN = "open_drain" CONF_OPEN_DRAIN_INTERRUPT = "open_drain_interrupt" CONF_OPEN_DURATION = "open_duration" CONF_OPEN_ENDSTOP = "open_endstop" @@ -520,6 +507,8 @@ CONF_PRIORITY = "priority" CONF_PROJECT = "project" CONF_PROTOCOL = "protocol" CONF_PULL_MODE = "pull_mode" +CONF_PULLDOWN = "pulldown" +CONF_PULLUP = "pullup" CONF_PULSE_LENGTH = "pulse_length" CONF_QOS = "qos" CONF_RADON = "radon" @@ -890,3 +879,8 @@ STATE_CLASS_MEASUREMENT = "measurement" # The state represents a total that only increases, a decrease is considered a reset. STATE_CLASS_TOTAL_INCREASING = "total_increasing" + +KEY_CORE = "core" +KEY_TARGET_PLATFORM = "target_platform" +KEY_TARGET_FRAMEWORK = "target_framework" +KEY_FRAMEWORK_VERSION = "framework_version" diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index e89f64d14c..9ec7fe358d 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -2,16 +2,17 @@ import logging import math import os import re -from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Union from esphome.const import ( - CONF_ARDUINO_VERSION, CONF_COMMENT, CONF_ESPHOME, CONF_USE_ADDRESS, CONF_ETHERNET, CONF_WIFI, - SOURCE_FILE_EXTENSIONS, + KEY_CORE, + KEY_TARGET_FRAMEWORK, + KEY_TARGET_PLATFORM, ) from esphome.coroutine import FakeAwaitable as _FakeAwaitable from esphome.coroutine import FakeEventLoop as _FakeEventLoop @@ -20,7 +21,6 @@ from esphome.coroutine import FakeEventLoop as _FakeEventLoop from esphome.coroutine import coroutine, coroutine_with_priority # noqa from esphome.helpers import ensure_unique_string, is_hassio from esphome.util import OrderedDict -from esphome import boards if TYPE_CHECKING: from ..cpp_generator import MockObj, MockObjClass, Statement @@ -441,19 +441,6 @@ class Library: return NotImplemented -def find_source_files(file): - files = set() - directory = os.path.abspath(os.path.dirname(file)) - for f in os.listdir(directory): - if not os.path.isfile(os.path.join(directory, f)): - continue - _, ext = os.path.splitext(f) - if ext.lower() not in SOURCE_FILE_EXTENSIONS: - continue - files.add(f) - return files - - # pylint: disable=too-many-instance-attributes,too-many-public-methods class EsphomeCore: def __init__(self): @@ -464,16 +451,13 @@ class EsphomeCore: self.ace = False # The name of the node self.name: Optional[str] = None + # Additional data components can store temporary data in + # The first key to this dict should always be the integration name + self.data = {} # The relative path to the configuration YAML self.config_path: Optional[str] = None # The relative path to where all build files are stored self.build_path: Optional[str] = None - # The platform (ESP8266, ESP32) of this device - self.esp_platform: Optional[str] = None - # The board that's used (for example nodemcuv2) - self.board: Optional[str] = None - # The full raw configuration - self.raw_config: Optional["ConfigType"] = None # The validated configuration, this is None until the config has been validated self.config: Optional["ConfigType"] = None # The pending tasks in the task queue (mostly for C++ generation) @@ -494,6 +478,8 @@ class EsphomeCore: self.build_flags: Set[str] = set() # A set of defines to set for the compile process in esphome/core/defines.h self.defines: Set["Define"] = set() + # A map of all platformio options to apply + self.platformio_options: Dict[str, Union[str, List[str]]] = {} # A set of strings of names of loaded integrations, used to find namespace ID conflicts self.loaded_integrations = set() # A set of component IDs to track what Component subclasses are declared @@ -504,11 +490,9 @@ class EsphomeCore: def reset(self): self.dashboard = False self.name = None + self.data = {} self.config_path = None self.build_path = None - self.esp_platform = None - self.board = None - self.raw_config = None self.config = None self.event_loop = _FakeEventLoop() self.task_counter = 0 @@ -518,6 +502,7 @@ class EsphomeCore: self.libraries = [] self.build_flags = set() self.defines = set() + self.platformio_options = {} self.loaded_integrations = set() self.component_ids = set() @@ -544,13 +529,6 @@ class EsphomeCore: return None - @property - def arduino_version(self) -> str: - if self.config is None: - raise ValueError("Config has not been loaded yet") - - return self.config[CONF_ESPHOME][CONF_ARDUINO_VERSION] - @property def config_dir(self): return os.path.dirname(self.config_path) @@ -586,27 +564,29 @@ class EsphomeCore: def firmware_bin(self): return self.relative_pioenvs_path(self.name, "firmware.bin") + @property + def target_platform(self): + return self.data[KEY_CORE][KEY_TARGET_PLATFORM] + @property def is_esp8266(self): - if self.esp_platform is None: - raise ValueError("No platform specified") - return self.esp_platform == "ESP8266" + return self.target_platform == "esp8266" @property def is_esp32(self): - """Check if the ESP32 platform is used. - - This checks if the ESP32 platform is in use, which - support ESP32 as well as other chips such as ESP32-C3 - """ - if self.esp_platform is None: - raise ValueError("No platform specified") - return self.esp_platform == "ESP32" + return self.target_platform == "esp32" @property - def is_esp32_c3(self): - """Check if the ESP32-C3 SoC is being used.""" - return self.is_esp32 and self.board in boards.ESP32_C3_BOARD_PINS + def target_framework(self): + return self.data[KEY_CORE][KEY_TARGET_FRAMEWORK] + + @property + def using_arduino(self): + return self.target_framework == "arduino" + + @property + def using_esp_idf(self): + return self.target_framework == "esp-idf" def add_job(self, func, *args, **kwargs): self.event_loop.add_job(func, *args, **kwargs) @@ -703,6 +683,14 @@ class EsphomeCore: _LOGGER.debug("Adding define: %s", define) return define + def add_platformio_option(self, key: str, value: Union[str, List[str]]) -> None: + new_val = value + old_val = self.platformio_options.get(key) + if isinstance(old_val, list): + assert isinstance(value, list) + new_val = old_val + value + self.platformio_options[key] = new_val + def _get_variable_generator(self, id): while True: try: diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index d59ad23a5e..a4d61f819c 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -1,7 +1,7 @@ #include "esphome/core/application.h" #include "esphome/core/log.h" #include "esphome/core/version.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #ifdef USE_STATUS_LED #include "esphome/components/status_led/status_led.h" @@ -60,9 +60,6 @@ void Application::setup() { ESP_LOGI(TAG, "setup() finished successfully!"); this->schedule_dump_config(); this->calculate_looping_components_(); - - // Dummy function to link some symbols into the binary. - force_link_symbols(); } void Application::loop() { uint32_t new_app_state = 0; @@ -110,11 +107,11 @@ void Application::loop() { } } -void ICACHE_RAM_ATTR HOT Application::feed_wdt() { +void IRAM_ATTR HOT Application::feed_wdt() { static uint32_t last_feed = 0; uint32_t now = millis(); if (now - last_feed > 3) { - this->feed_wdt_arch_(); + arch_feed_wdt(); last_feed = now; #ifdef USE_STATUS_LED if (status_led::global_status_led != nullptr) { @@ -127,11 +124,7 @@ void Application::reboot() { ESP_LOGI(TAG, "Forcing a reboot..."); for (auto *comp : this->components_) comp->on_shutdown(); - ESP.restart(); // NOLINT(readability-static-accessed-through-instance) - // restart() doesn't always end execution - while (true) { - yield(); - } + arch_restart(); } void Application::safe_reboot() { ESP_LOGI(TAG, "Rebooting safely..."); @@ -139,11 +132,7 @@ void Application::safe_reboot() { comp->on_safe_shutdown(); for (auto *comp : this->components_) comp->on_shutdown(); - ESP.restart(); // NOLINT(readability-static-accessed-through-instance) - // restart() doesn't always end execution - while (true) { - yield(); - } + arch_restart(); } void Application::calculate_looping_components_() { diff --git a/esphome/core/application.h b/esphome/core/application.h index e5f686a320..5c1483d301 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -51,7 +51,6 @@ class Application { this->name_ = name; } this->compilation_time_ = compilation_time; - global_preferences.begin(); } #ifdef USE_BINARY_SENSOR diff --git a/esphome/core/application_esp32.cpp b/esphome/core/application_esp32.cpp deleted file mode 100644 index 9f084428bb..0000000000 --- a/esphome/core/application_esp32.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "esphome/core/application.h" - -#ifdef ARDUINO_ARCH_ESP32 - -namespace esphome { - -static const char *const TAG = "app_esp32"; - -void ICACHE_RAM_ATTR HOT Application::feed_wdt_arch_() { -#if CONFIG_ARDUINO_RUNNING_CORE == 0 -#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 - // ESP32 uses "Task Watchdog" which is hooked to the FreeRTOS idle task. - // To cause the Watchdog to be triggered we need to put the current task - // to sleep to get the idle task scheduled. - delay(1); -#endif -#endif -} - -} // namespace esphome -#endif diff --git a/esphome/core/application_esp8266.cpp b/esphome/core/application_esp8266.cpp deleted file mode 100644 index ee32417634..0000000000 --- a/esphome/core/application_esp8266.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "esphome/core/application.h" - -#ifdef ARDUINO_ARCH_ESP8266 - -namespace esphome { - -static const char *const TAG = "app_esp8266"; - -void ICACHE_RAM_ATTR HOT Application::feed_wdt_arch_() { - ESP.wdtFeed(); // NOLINT(readability-static-accessed-through-instance) -} - -} // namespace esphome -#endif diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index e3b32978cd..c85b445b08 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -1,7 +1,7 @@ #include "esphome/core/component.h" #include "esphome/core/application.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include @@ -144,7 +144,7 @@ void Component::status_momentary_error(const std::string &name, uint32_t length) } void Component::dump_config() {} float Component::get_actual_setup_priority() const { - if (isnan(this->setup_priority_override_)) + if (std::isnan(this->setup_priority_override_)) return this->get_setup_priority(); return this->setup_priority_override_; } diff --git a/esphome/core/component.h b/esphome/core/component.h index 2654504fe8..85256c0f0f 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -2,7 +2,7 @@ #include #include -#include "Arduino.h" +#include #include "esphome/core/optional.h" diff --git a/esphome/core/config.py b/esphome/core/config.py index 222a775ee6..71add56b13 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -4,7 +4,7 @@ import re import esphome.codegen as cg import esphome.config_validation as cv -from esphome import automation, boards +from esphome import automation from esphome.const import ( CONF_ARDUINO_VERSION, CONF_BOARD, @@ -12,6 +12,7 @@ from esphome.const import ( CONF_BUILD_PATH, CONF_COMMENT, CONF_ESPHOME, + CONF_FRAMEWORK, CONF_INCLUDES, CONF_LIBRARIES, CONF_NAME, @@ -23,11 +24,10 @@ from esphome.const import ( CONF_PRIORITY, CONF_PROJECT, CONF_TRIGGER_ID, - CONF_ESP8266_RESTORE_FROM_FLASH, - ARDUINO_VERSION_ESP8266, - ARDUINO_VERSION_ESP32, + CONF_TYPE, CONF_VERSION, - ESP_PLATFORMS, + KEY_CORE, + TARGET_PLATFORMS, ) from esphome.core import CORE, coroutine_with_priority from esphome.helpers import copy_file_if_changed, walk_files @@ -50,79 +50,6 @@ VERSION_REGEX = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:[ab]\d+)?$") CONF_NAME_ADD_MAC_SUFFIX = "name_add_mac_suffix" -def validate_board(value: str): - if CORE.is_esp8266: - boardlist = boards.ESP8266_BOARD_PINS.keys() - elif CORE.is_esp32: - boardlist = list(boards.ESP32_BOARD_PINS.keys()) - boardlist += list(boards.ESP32_C3_BOARD_PINS.keys()) - else: - raise NotImplementedError - - if value not in boardlist: - raise cv.Invalid( - f"Could not find board '{value}'. Valid boards are {', '.join(sorted(boardlist))}" - ) - return value - - -validate_platform = cv.one_of(*ESP_PLATFORMS, upper=True) - -PLATFORMIO_ESP8266_LUT = { - **ARDUINO_VERSION_ESP8266, - # Keep this in mind when updating the recommended version: - # * New framework historically have had some regressions, especially for WiFi, BLE and the - # bootloader system. The new version needs to be thoroughly validated before changing the - # recommended version as otherwise a bunch of devices could be bricked - # * The docker images need to be updated to ship the new recommended version, in order not - # to DDoS platformio servers. - # Update this file: https://github.com/esphome/esphome-docker-base/blob/main/platformio.ini - "RECOMMENDED": ARDUINO_VERSION_ESP8266["2.7.4"], - "LATEST": "espressif8266", - "DEV": ARDUINO_VERSION_ESP8266["dev"], -} - -PLATFORMIO_ESP32_LUT = { - **ARDUINO_VERSION_ESP32, - # See PLATFORMIO_ESP8266_LUT for considerations when changing the recommended version - "RECOMMENDED": ARDUINO_VERSION_ESP32["1.0.6"], - "LATEST": "espressif32", - "DEV": ARDUINO_VERSION_ESP32["dev"], -} - - -def validate_arduino_version(value): - value = cv.string_strict(value) - value_ = value.upper() - if CORE.is_esp8266: - if ( - VERSION_REGEX.match(value) is not None - and value_ not in PLATFORMIO_ESP8266_LUT - ): - raise cv.Invalid( - f"Unfortunately the arduino framework version '{value}' is unsupported at this time. You can override this by manually using espressif8266@" - ) - if value_ in PLATFORMIO_ESP8266_LUT: - return PLATFORMIO_ESP8266_LUT[value_] - return value - if CORE.is_esp32: - if ( - VERSION_REGEX.match(value) is not None - and value_ not in PLATFORMIO_ESP32_LUT - ): - raise cv.Invalid( - f"Unfortunately the arduino framework version '{value}' is unsupported at this time. You can override this by manually using espressif32@" - ) - if value_ in PLATFORMIO_ESP32_LUT: - return PLATFORMIO_ESP32_LUT[value_] - return value - raise NotImplementedError - - -def default_build_path(): - return CORE.name - - VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} @@ -149,27 +76,17 @@ def valid_project_name(value: str): return value +CONF_ESP8266_RESTORE_FROM_FLASH = "esp8266_restore_from_flash" CONFIG_SCHEMA = cv.Schema( { cv.Required(CONF_NAME): cv.hostname, - cv.Required(CONF_PLATFORM): cv.one_of("ESP8266", "ESP32", upper=True), - cv.Required(CONF_BOARD): validate_board, cv.Optional(CONF_COMMENT): cv.string, - cv.Optional( - CONF_ARDUINO_VERSION, default="recommended" - ): validate_arduino_version, - cv.Optional(CONF_BUILD_PATH, default=default_build_path): cv.string, + cv.Required(CONF_BUILD_PATH): cv.string, cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema( { cv.string_strict: cv.Any([cv.string], cv.string), } ), - cv.SplitDefault(CONF_ESP8266_RESTORE_FROM_FLASH, esp8266=False): cv.All( - cv.only_on_esp8266, cv.boolean - ), - cv.SplitDefault(CONF_BOARD_FLASH_MODE, esp8266="dout"): cv.one_of( - *BUILD_FLASH_MODES, lower=True - ), cv.Optional(CONF_ON_BOOT): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StartupTrigger), @@ -201,38 +118,78 @@ CONFIG_SCHEMA = cv.Schema( PRELOAD_CONFIG_SCHEMA = cv.Schema( { cv.Required(CONF_NAME): cv.valid_name, - cv.Required(CONF_PLATFORM): validate_platform, + cv.Optional(CONF_BUILD_PATH): cv.string, + # Compat options, these were moved to target-platform specific sections + # but we'll keep these around for a long time because every config would + # be impacted + cv.Optional(CONF_PLATFORM): cv.one_of(*TARGET_PLATFORMS, lower=True), + cv.Optional(CONF_BOARD): cv.string_strict, + cv.Optional(CONF_ESP8266_RESTORE_FROM_FLASH): cv.valid, + cv.Optional(CONF_BOARD_FLASH_MODE): cv.valid, + cv.Optional(CONF_ARDUINO_VERSION): cv.valid, }, extra=cv.ALLOW_EXTRA, ) -PRELOAD_CONFIG_SCHEMA2 = PRELOAD_CONFIG_SCHEMA.extend( - { - cv.Required(CONF_BOARD): validate_board, - cv.Optional(CONF_BUILD_PATH, default=default_build_path): cv.string, - } -) +def preload_core_config(config, result): + with cv.prepend_path(CONF_ESPHOME): + conf = PRELOAD_CONFIG_SCHEMA(config[CONF_ESPHOME]) -def preload_core_config(config): - core_key = "esphome" - if "esphomeyaml" in config: - _LOGGER.warning( - "The esphomeyaml section has been renamed to esphome in 1.11.0. " - "Please replace 'esphomeyaml:' in your configuration with 'esphome:'." + CORE.name = conf[CONF_NAME] + CORE.data[KEY_CORE] = {} + + if CONF_BUILD_PATH not in conf: + conf[CONF_BUILD_PATH] = CORE.name + CORE.build_path = CORE.relative_config_path(conf[CONF_BUILD_PATH]) + + has_oldstyle = CONF_PLATFORM in conf + newstyle_found = [key for key in TARGET_PLATFORMS if key in config] + oldstyle_opts = [ + CONF_ESP8266_RESTORE_FROM_FLASH, + CONF_BOARD_FLASH_MODE, + CONF_ARDUINO_VERSION, + CONF_BOARD, + ] + + if not has_oldstyle and not newstyle_found: + raise cv.Invalid("Platform missing for core options!", [CONF_ESPHOME]) + if has_oldstyle and newstyle_found: + raise cv.Invalid( + f"Please remove the `platform` key from the [esphome] block. You're already using the new style with the [{conf[CONF_PLATFORM]}] block", + [CONF_ESPHOME, CONF_PLATFORM], ) - config[CONF_ESPHOME] = config.pop("esphomeyaml") - core_key = "esphomeyaml" - if CONF_ESPHOME not in config: - raise cv.RequiredFieldInvalid("required key not provided", CONF_ESPHOME) - with cv.prepend_path(core_key): - out = PRELOAD_CONFIG_SCHEMA(config[CONF_ESPHOME]) - CORE.name = out[CONF_NAME] - CORE.esp_platform = out[CONF_PLATFORM] - with cv.prepend_path(core_key): - out2 = PRELOAD_CONFIG_SCHEMA2(config[CONF_ESPHOME]) - CORE.board = out2[CONF_BOARD] - CORE.build_path = CORE.relative_config_path(out2[CONF_BUILD_PATH]) + if len(newstyle_found) > 1: + raise cv.Invalid( + f"Found multiple target platform blocks: {', '.join(newstyle_found)}. Only one is allowed.", + [newstyle_found[0]], + ) + if newstyle_found: + # Convert to newstyle + for key in oldstyle_opts: + if key in conf: + raise cv.Invalid( + f"Please move {key} to the [{newstyle_found[0]}] block.", + [CONF_ESPHOME, key], + ) + + if has_oldstyle: + plat = conf.pop(CONF_PLATFORM) + plat_conf = {} + if CONF_ESP8266_RESTORE_FROM_FLASH in conf: + plat_conf["restore_from_flash"] = conf.pop(CONF_ESP8266_RESTORE_FROM_FLASH) + if CONF_BOARD_FLASH_MODE in conf: + plat_conf[CONF_BOARD_FLASH_MODE] = conf.pop(CONF_BOARD_FLASH_MODE) + if CONF_ARDUINO_VERSION in conf: + plat_conf[CONF_FRAMEWORK] = { + CONF_TYPE: "arduino", + CONF_VERSION: conf.pop(CONF_ARDUINO_VERSION), + } + if CONF_BOARD in conf: + plat_conf[CONF_BOARD] = conf.pop(CONF_BOARD) + # Insert generated target platform config to main config + config[plat] = plat_conf + config[CONF_ESPHOME] = conf def include_file(path, basename): @@ -263,25 +220,10 @@ async def add_includes(includes): @coroutine_with_priority(-1000.0) -async def _esp8266_add_lwip_type(): - # If any component has already set this, do not change it - if any( - flag.startswith("-DPIO_FRAMEWORK_ARDUINO_LWIP2_") for flag in CORE.build_flags - ): - return - - # Default for platformio is LWIP2_LOW_MEMORY with: - # - MSS=536 - # - LWIP_FEATURES enabled - # - this only adds some optional features like IP incoming packet reassembly and NAPT - # see also: - # https://github.com/esp8266/Arduino/blob/master/tools/sdk/lwip2/include/lwipopts.h - - # Instead we use LWIP2_HIGHER_BANDWIDTH_LOW_FLASH with: - # - MSS=1460 - # - LWIP_FEATURES disabled (because we don't need them) - # Other projects like Tasmota & ESPEasy also use this - cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH") +async def _add_platformio_options(pio_options): + # Add includes at the very end, so that they override everything + for key, val in pio_options.items(): + cg.add_platformio_option(key, val) @coroutine_with_priority(30.0) @@ -315,10 +257,6 @@ async def to_code(config): CORE.add_job(_add_automations, config) - # Set LWIP build constants for ESP8266 - if CORE.is_esp8266: - CORE.add_job(_esp8266_add_lwip_type) - cg.add_build_flag("-fno-exceptions") # Libraries @@ -337,25 +275,16 @@ async def to_code(config): else: cg.add_library(lib, None) - if CORE.is_esp8266: - # Arduino 2 has a non-standards conformant new that returns a nullptr instead of failing when - # out of memory and exceptions are disabled. Since Arduino 2.6.0, this flag can be used to make - # new abort instead. Use it so that OOM fails early (on allocation) instead of on dereference of - # a NULL pointer (so the stacktrace makes more sense), and for consistency with Arduino 3, - # which always aborts if exceptions are disabled. - # For cases where nullptrs can be handled, use nothrow: `new (std::nothrow) T;` - cg.add_build_flag("-DNEW_OOM_ABORT") - cg.add_build_flag("-Wno-unused-variable") cg.add_build_flag("-Wno-unused-but-set-variable") cg.add_build_flag("-Wno-sign-compare") - if config.get(CONF_ESP8266_RESTORE_FROM_FLASH, False): - cg.add_define("USE_ESP8266_PREFERENCES_FLASH") if config[CONF_INCLUDES]: CORE.add_job(add_includes, config[CONF_INCLUDES]) - cg.add_define("ESPHOME_BOARD", CORE.board) if CONF_PROJECT in config: cg.add_define("ESPHOME_PROJECT_NAME", config[CONF_PROJECT][CONF_NAME]) cg.add_define("ESPHOME_PROJECT_VERSION", config[CONF_PROJECT][CONF_VERSION]) + + if config[CONF_PLATFORMIO_OPTIONS]: + CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS]) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 89010ce246..ac40921a4a 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -13,7 +13,9 @@ // Feature flags #define USE_API #define USE_BINARY_SENSOR +#ifdef USE_ARDUINO #define USE_CAPTIVE_PORTAL +#endif #define USE_CLIMATE #define USE_COVER #define USE_DEEP_SLEEP @@ -21,13 +23,17 @@ #define USE_FAN #define USE_GRAPH #define USE_HOMEASSISTANT_TIME -#define USE_HTTP_REQUEST_ESP8266_HTTPS -#define USE_I2C_MULTIPLEXER + +#ifdef USE_ARDUINO #define USE_JSON +#define USE_NEXTION_TFT_UPLOAD +#define USE_MQTT +#define USE_CAPTIVE_PORTAL +#endif // USE_ARDUINO + #define USE_LIGHT #define USE_LOGGER #define USE_MDNS -#define USE_MQTT #define USE_NUMBER #define USE_OTA_STATE_CALLBACK #define USE_POWER_SUPPLY @@ -37,22 +43,29 @@ #define USE_STATUS_LED #define USE_SWITCH #define USE_TEXT_SENSOR -#define USE_TFT_UPLOAD + #define USE_TIME #define USE_WIFI +#ifdef USE_ARDUINO #define USE_WIFI_WPA2_EAP - -#ifdef ARDUINO_ARCH_ESP32 -#define USE_ESP32_BLE_SERVER -#define USE_ESP32_CAMERA -#define USE_ETHERNET -#define USE_IMPROV -#define USE_SOCKET_IMPL_BSD_SOCKETS #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP32 +#define USE_ESP32_BLE_SERVER +#define USE_ESP32_CAMERA + +#ifdef USE_ARDUINO +#define USE_ETHERNET +#endif // USE_ARDUINO + +#define USE_IMPROV +#define USE_SOCKET_IMPL_BSD_SOCKETS +#endif // USE_ESP32 + +#ifdef USE_ESP8266 #define USE_ADC_SENSOR_VCC #define USE_SOCKET_IMPL_LWIP_TCP +#define USE_HTTP_REQUEST_ESP8266_HTTPS #endif #define USE_API_PLAINTEXT @@ -60,3 +73,9 @@ // Disabled feature flags //#define USE_BSEC // Requires a library with proprietary license. +#define USE_TIME +#define USE_DEEP_SLEEP +#define ESPHOME_BOARD "dummy_board" +#define USE_MDNS +#define USE_API_NOISE +#define USE_API_PLAINTEXT diff --git a/esphome/core/esphal.cpp b/esphome/core/esphal.cpp deleted file mode 100644 index 849fdf95ad..0000000000 --- a/esphome/core/esphal.cpp +++ /dev/null @@ -1,303 +0,0 @@ -#include "esphome/core/esphal.h" -#include "esphome/core/macros.h" -#include "esphome/core/helpers.h" -#include "esphome/core/defines.h" -#include "esphome/core/log.h" - -#ifdef ARDUINO_ARCH_ESP8266 -extern "C" { -typedef struct { // NOLINT - void *interruptInfo; // NOLINT - void *functionInfo; // NOLINT -} ArgStructure; - -void ICACHE_RAM_ATTR __attachInterruptArg(uint8_t pin, void (*)(void *), void *fp, // NOLINT - int mode); -void ICACHE_RAM_ATTR __detachInterrupt(uint8_t pin); // NOLINT -}; -#endif - -namespace esphome { - -static const char *const TAG = "esphal"; - -GPIOPin::GPIOPin(uint8_t pin, uint8_t mode, bool inverted) - : pin_(pin), - mode_(mode), - inverted_(inverted), -#ifdef ARDUINO_ARCH_ESP8266 - gpio_read_(pin < 16 ? &GPI : &GP16I), - gpio_mask_(pin < 16 ? (1UL << pin) : 1) -#elif ARDUINO_ARCH_ESP32 -#ifdef CONFIG_IDF_TARGET_ESP32C3 - gpio_set_(&GPIO.out_w1ts.val), - gpio_clear_(&GPIO.out_w1tc.val), - gpio_read_(&GPIO.in.val), -#else - gpio_set_(pin < 32 ? &GPIO.out_w1ts : &GPIO.out1_w1ts.val), - gpio_clear_(pin < 32 ? &GPIO.out_w1tc : &GPIO.out1_w1tc.val), - gpio_read_(pin < 32 ? &GPIO.in : &GPIO.in1.val), -#endif - gpio_mask_(pin < 32 ? (1UL << pin) : (1UL << (pin - 32))) -#endif -{ -} - -const LogString *GPIOPin::get_pin_mode_name() const { - const char *mode_s; - switch (this->mode_) { - case INPUT: - return LOG_STR("INPUT"); - case OUTPUT: - return LOG_STR("OUTPUT"); - case INPUT_PULLUP: - return LOG_STR("INPUT_PULLUP"); - case OUTPUT_OPEN_DRAIN: - return LOG_STR("OUTPUT_OPEN_DRAIN"); - case SPECIAL: - return LOG_STR("SPECIAL"); - case FUNCTION_1: - return LOG_STR("FUNCTION_1"); - case FUNCTION_2: - return LOG_STR("FUNCTION_2"); - case FUNCTION_3: - return LOG_STR("FUNCTION_3"); - case FUNCTION_4: - return LOG_STR("FUNCTION_4"); -#ifdef ARDUINO_ARCH_ESP32 - case PULLUP: - return LOG_STR("PULLUP"); - case PULLDOWN: - return LOG_STR("PULLDOWN"); - case INPUT_PULLDOWN: - return LOG_STR("INPUT_PULLDOWN"); - case OPEN_DRAIN: - return LOG_STR("OPEN_DRAIN"); - case FUNCTION_5: - return LOG_STR("FUNCTION_5"); - case FUNCTION_6: - return LOG_STR("FUNCTION_6"); - case ANALOG: - return LOG_STR("ANALOG"); -#endif -#ifdef ARDUINO_ARCH_ESP8266 - case FUNCTION_0: - return LOG_STR("FUNCTION_0"); - case WAKEUP_PULLUP: - return LOG_STR("WAKEUP_PULLUP"); - case WAKEUP_PULLDOWN: - return LOG_STR("WAKEUP_PULLDOWN"); - case INPUT_PULLDOWN_16: - return LOG_STR("INPUT_PULLDOWN_16"); -#endif - default: - return LOG_STR("UNKNOWN"); - } -} - -unsigned char GPIOPin::get_pin() const { return this->pin_; } -unsigned char GPIOPin::get_mode() const { return this->mode_; } - -bool GPIOPin::is_inverted() const { return this->inverted_; } -void GPIOPin::setup() { this->pin_mode(this->mode_); } -bool ICACHE_RAM_ATTR HOT GPIOPin::digital_read() { - return bool((*this->gpio_read_) & this->gpio_mask_) != this->inverted_; -} -bool ICACHE_RAM_ATTR HOT ISRInternalGPIOPin::digital_read() { - return bool((*this->gpio_read_) & this->gpio_mask_) != this->inverted_; -} -void ICACHE_RAM_ATTR HOT GPIOPin::digital_write(bool value) { -#ifdef ARDUINO_ARCH_ESP8266 - if (this->pin_ != 16) { - if (value != this->inverted_) { - GPOS = this->gpio_mask_; - } else { - GPOC = this->gpio_mask_; - } - } else { - if (value != this->inverted_) { - GP16O |= 1; - } else { - GP16O &= ~1; - } - } -#endif -#ifdef ARDUINO_ARCH_ESP32 - if (value != this->inverted_) { - (*this->gpio_set_) = this->gpio_mask_; - } else { - (*this->gpio_clear_) = this->gpio_mask_; - } -#endif -} -void ICACHE_RAM_ATTR HOT ISRInternalGPIOPin::digital_write(bool value) { -#ifdef ARDUINO_ARCH_ESP8266 - if (this->pin_ != 16) { - if (value != this->inverted_) { - GPOS = this->gpio_mask_; - } else { - GPOC = this->gpio_mask_; - } - } else { - if (value != this->inverted_) { - GP16O |= 1; - } else { - GP16O &= ~1; - } - } -#endif -#ifdef ARDUINO_ARCH_ESP32 - if (value != this->inverted_) { - (*this->gpio_set_) = this->gpio_mask_; - } else { - (*this->gpio_clear_) = this->gpio_mask_; - } -#endif -} -ISRInternalGPIOPin::ISRInternalGPIOPin(uint8_t pin, -#ifdef ARDUINO_ARCH_ESP32 - volatile uint32_t *gpio_clear, volatile uint32_t *gpio_set, -#endif - volatile uint32_t *gpio_read, uint32_t gpio_mask, bool inverted) - : pin_(pin), - inverted_(inverted), - gpio_read_(gpio_read), - gpio_mask_(gpio_mask) -#ifdef ARDUINO_ARCH_ESP32 - , - gpio_clear_(gpio_clear), - gpio_set_(gpio_set) -#endif -{ -} -void ICACHE_RAM_ATTR ISRInternalGPIOPin::clear_interrupt() { -#ifdef ARDUINO_ARCH_ESP8266 - GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, this->gpio_mask_); -#endif -#ifdef ARDUINO_ARCH_ESP32 -#ifdef CONFIG_IDF_TARGET_ESP32C3 - GPIO.status_w1tc.val = this->gpio_mask_; -#else - if (this->pin_ < 32) { - GPIO.status_w1tc = this->gpio_mask_; - } else { - GPIO.status1_w1tc.intr_st = this->gpio_mask_; - } -#endif -#endif -} - -void ICACHE_RAM_ATTR HOT GPIOPin::pin_mode(uint8_t mode) { -#ifdef ARDUINO_ARCH_ESP8266 - if (this->pin_ == 16 && mode == INPUT_PULLUP) { - // pullups are not available on GPIO16, manually override with - // input mode. - pinMode(16, INPUT); - return; - } -#endif - pinMode(this->pin_, mode); -} - -#ifdef ARDUINO_ARCH_ESP8266 -struct ESPHomeInterruptFuncInfo { - void (*func)(void *); - void *arg; -}; - -void ICACHE_RAM_ATTR interrupt_handler(void *arg) { - ArgStructure *as = static_cast(arg); - auto *info = static_cast(as->functionInfo); - info->func(info->arg); -} -#endif - -void GPIOPin::detach_interrupt() const { this->detach_interrupt_(); } -void GPIOPin::detach_interrupt_() const { -#ifdef ARDUINO_ARCH_ESP8266 - __detachInterrupt(get_pin()); -#endif -#ifdef ARDUINO_ARCH_ESP32 - detachInterrupt(get_pin()); -#endif -} -void GPIOPin::attach_interrupt_(void (*func)(void *), void *arg, int mode) const { - if (this->inverted_) { - if (mode == RISING) { - mode = FALLING; - } else if (mode == FALLING) { - mode = RISING; - } - } -#ifdef ARDUINO_ARCH_ESP8266 - ArgStructure *as = new ArgStructure; // NOLINT - as->interruptInfo = nullptr; - - as->functionInfo = new ESPHomeInterruptFuncInfo{ - // NOLINT - .func = func, - .arg = arg, - }; - - __attachInterruptArg(this->pin_, interrupt_handler, as, mode); -#endif -#ifdef ARDUINO_ARCH_ESP32 - // work around issue https://github.com/espressif/arduino-esp32/pull/1776 in arduino core - // yet again proves how horrible code is there :( - how could that have been accepted... - auto *attach = reinterpret_cast(attachInterruptArg); - attach(this->pin_, func, arg, mode); -#endif -} - -ISRInternalGPIOPin *GPIOPin::to_isr() const { - return new ISRInternalGPIOPin(this->pin_, // NOLINT -#ifdef ARDUINO_ARCH_ESP32 - this->gpio_clear_, this->gpio_set_, -#endif - this->gpio_read_, this->gpio_mask_, this->inverted_); -} - -void force_link_symbols() { -#ifdef ARDUINO_ARCH_ESP8266 - // Tasmota uses magic bytes in the binary to check if an OTA firmware is compatible - // with their settings - ESPHome uses a different settings system (that can also survive - // erases). So set magic bytes indicating all tasmota versions are supported. - // This only adds 12 bytes of binary size, which is an acceptable price to pay for easier support - // for Tasmota. - // https://github.com/arendst/Tasmota/blob/b05301b1497942167a015a6113b7f424e42942cd/tasmota/settings.ino#L346-L380 - // https://github.com/arendst/Tasmota/blob/b05301b1497942167a015a6113b7f424e42942cd/tasmota/i18n.h#L652-L654 - const static uint32_t TASMOTA_MAGIC_BYTES[] PROGMEM = {0x5AA55AA5, 0xFFFFFFFF, 0xA55AA55A}; - // Force link symbol by using a volatile integer (GCC attribute used does not work because of LTO) - volatile int x = 0; - x = TASMOTA_MAGIC_BYTES[x]; -#endif -} - -} // namespace esphome - -#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) -// Fix 2.3.0 std missing memchr -extern "C" { -void *memchr(const void *s, int c, size_t n) { - if (n == 0) - return nullptr; - const uint8_t *p = reinterpret_cast(s); - do { - if (*p++ == c) - return const_cast(reinterpret_cast(p - 1)); - } while (--n != 0); - return nullptr; -} -}; -#endif - -#ifdef ARDUINO_ARCH_ESP8266 -extern "C" { -extern void resetPins() { // NOLINT - // Added in framework 2.7.0 - // usually this sets up all pins to be in INPUT mode - // however, not strictly needed as we set up the pins properly - // ourselves and this causes pins to toggle during reboot. -} -} -#endif diff --git a/esphome/core/esphal.h b/esphome/core/esphal.h deleted file mode 100644 index 1b92f816b1..0000000000 --- a/esphome/core/esphal.h +++ /dev/null @@ -1,128 +0,0 @@ -#pragma once - -#include "Arduino.h" - -// Fix some arduino defs -#ifdef round -#undef round -#endif -#ifdef bool -#undef bool -#endif -#ifdef true -#undef true -#endif -#ifdef false -#undef false -#endif -#ifdef min -#undef min -#endif -#ifdef max -#undef max -#endif -#ifdef abs -#undef abs -#endif - -#include "esphome/core/log.h" - -namespace esphome { - -#define LOG_PIN(prefix, pin) \ - if ((pin) != nullptr) { \ - ESP_LOGCONFIG(TAG, prefix LOG_PIN_PATTERN, LOG_PIN_ARGS(pin)); \ - } -#define LOG_PIN_PATTERN "GPIO%u (Mode: %s%s)" -#define LOG_PIN_ARGS(pin) \ - (pin)->get_pin(), LOG_STR_ARG((pin)->get_pin_mode_name()), ((pin)->is_inverted() ? ", INVERTED" : "") - -/// Copy of GPIOPin that is safe to use from ISRs (with no virtual functions) -class ISRInternalGPIOPin { - public: - ISRInternalGPIOPin(uint8_t pin, -#ifdef ARDUINO_ARCH_ESP32 - volatile uint32_t *gpio_clear, volatile uint32_t *gpio_set, -#endif - volatile uint32_t *gpio_read, uint32_t gpio_mask, bool inverted); - bool digital_read(); - void digital_write(bool value); - void clear_interrupt(); - - protected: - const uint8_t pin_; - const bool inverted_; - volatile uint32_t *const gpio_read_; - const uint32_t gpio_mask_; -#ifdef ARDUINO_ARCH_ESP32 - volatile uint32_t *const gpio_clear_; - volatile uint32_t *const gpio_set_; -#endif -}; - -/** A high-level abstraction class that can expose a pin together with useful options like pinMode. - * - * Set the parameters for this at construction time and use setup() to apply them. The inverted parameter will - * automatically invert the input/output for you. - * - * Use read_value() and write_value() to use digitalRead() and digitalWrite(), respectively. - */ -class GPIOPin { - public: - /** Construct the GPIOPin instance. - * - * @param pin The GPIO pin number of this instance. - * @param mode The Arduino pinMode that this pin should be put into at setup(). - * @param inverted Whether all digitalRead/digitalWrite calls should be inverted. - */ - GPIOPin(uint8_t pin, uint8_t mode, bool inverted = false); - - /// Setup the pin mode. - virtual void setup(); - /// Read the binary value from this pin using digitalRead (and inverts automatically). - virtual bool digital_read(); - /// Write the binary value to this pin using digitalWrite (and inverts automatically). - virtual void digital_write(bool value); - /// Set the pin mode - virtual void pin_mode(uint8_t mode); - - /// Get the GPIO pin number. - uint8_t get_pin() const; - const LogString *get_pin_mode_name() const; - /// Get the pinMode of this pin. - uint8_t get_mode() const; - /// Return whether this pin shall be treated as inverted. (for example active-low) - bool is_inverted() const; - - template void attach_interrupt(void (*func)(T *), T *arg, int mode) const; - void detach_interrupt() const; - - ISRInternalGPIOPin *to_isr() const; - - protected: - void attach_interrupt_(void (*func)(void *), void *arg, int mode) const; - void detach_interrupt_() const; - - const uint8_t pin_; - const uint8_t mode_; - const bool inverted_; -#ifdef ARDUINO_ARCH_ESP32 - volatile uint32_t *const gpio_set_; - volatile uint32_t *const gpio_clear_; -#endif - volatile uint32_t *const gpio_read_; - const uint32_t gpio_mask_; -}; - -template void GPIOPin::attach_interrupt(void (*func)(T *), T *arg, int mode) const { - this->attach_interrupt_(reinterpret_cast(func), arg, mode); -} -/** This function can be used by the HAL to force-link specific symbols - * into the generated binary without modifying the linker script. - * - * It is called by the application very early on startup and should not be used for anything - * other than forcing symbols to be linked. - */ -void force_link_symbols(); - -} // namespace esphome diff --git a/esphome/core/gpio.h b/esphome/core/gpio.h new file mode 100644 index 0000000000..25d56b9020 --- /dev/null +++ b/esphome/core/gpio.h @@ -0,0 +1,98 @@ +#pragma once +#include +#include + +namespace esphome { + +#define LOG_PIN(prefix, pin) \ + if ((pin) != nullptr) { \ + ESP_LOGCONFIG(TAG, prefix "%s", pin->dump_summary().c_str()); \ + } + +// put GPIO flags in a namepsace to not pollute esphome namespace +namespace gpio { + +enum Flags : uint8_t { + // Can't name these just INPUT because of Arduino defines :( + FLAG_NONE = 0x00, + FLAG_INPUT = 0x01, + FLAG_OUTPUT = 0x02, + FLAG_OPEN_DRAIN = 0x04, + FLAG_PULLUP = 0x08, + FLAG_PULLDOWN = 0x10, +}; + +class FlagsHelper { + public: + constexpr FlagsHelper(Flags val) : val_(val) {} + constexpr operator Flags() const { return val_; } + + protected: + Flags val_; +}; +constexpr FlagsHelper operator&(Flags lhs, Flags rhs) { + return static_cast(static_cast(lhs) & static_cast(rhs)); +} +constexpr FlagsHelper operator|(Flags lhs, Flags rhs) { + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +enum InterruptType : uint8_t { + INTERRUPT_RISING_EDGE = 1, + INTERRUPT_FALLING_EDGE = 2, + INTERRUPT_ANY_EDGE = 3, + INTERRUPT_LOW_LEVEL = 4, + INTERRUPT_HIGH_LEVEL = 5, +}; + +} // namespace gpio + +class GPIOPin { + public: + virtual void setup() = 0; + + virtual void pin_mode(gpio::Flags flags) = 0; + + virtual bool digital_read() = 0; + + virtual void digital_write(bool value) = 0; + + virtual std::string dump_summary() const = 0; + + virtual bool is_internal() { return false; } +}; + +/// Copy of GPIOPin that is safe to use from ISRs (with no virtual functions) +class ISRInternalGPIOPin { + public: + ISRInternalGPIOPin() = default; + ISRInternalGPIOPin(void *arg) : arg_(arg) {} + bool digital_read(); + void digital_write(bool value); + void clear_interrupt(); + + protected: + void *arg_ = nullptr; +}; + +class InternalGPIOPin : public GPIOPin { + public: + template void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const { + this->attach_interrupt_(reinterpret_cast(func), arg, type); + } + + virtual void detach_interrupt() const = 0; + + virtual ISRInternalGPIOPin to_isr() const = 0; + + virtual uint8_t get_pin() const = 0; + + bool is_internal() override { return true; } + + virtual bool is_inverted() const = 0; + + protected: + virtual void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const = 0; +}; + +} // namespace esphome diff --git a/esphome/core/hal.h b/esphome/core/hal.h new file mode 100644 index 0000000000..2843bb9b15 --- /dev/null +++ b/esphome/core/hal.h @@ -0,0 +1,47 @@ +#pragma once +#include +#include +#include "gpio.h" + +#if defined(USE_ESP32_FRAMEWORK_ESP_IDF) +#include +#ifndef PROGMEM +#define PROGMEM +#endif + +#elif defined(USE_ESP32_FRAMEWORK_ARDUINO) + +#include + +#ifndef PROGMEM +#define PROGMEM +#endif + +#elif defined(USE_ESP8266) + +#include +#ifndef PROGMEM +#define PROGMEM ICACHE_RODATA_ATTR +#endif + +#else + +#define IRAM_ATTR +#define PROGMEM + +#endif + +namespace esphome { + +void yield(); +uint32_t millis(); +uint32_t micros(); +void delay(uint32_t ms); +void delayMicroseconds(uint32_t us); +void __attribute__((noreturn)) arch_restart(); +void arch_feed_wdt(); +uint32_t arch_get_cpu_cycle_count(); +uint32_t arch_get_cpu_freq_hz(); +uint8_t progmem_read_byte(const uint8_t *addr); + +} // namespace esphome diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 53c7d1de43..a90eb74be2 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -1,15 +1,22 @@ #include "esphome/core/helpers.h" #include #include +#include +#include -#ifdef ARDUINO_ARCH_ESP8266 +#if defined(USE_ESP8266) #include -#else +#include +#elif defined(USE_ESP32_FRAMEWORK_ARDUINO) #include +#elif defined(USE_ESP_IDF) +#include "esp_system.h" +#include +#include #endif #include "esphome/core/log.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" namespace esphome { @@ -18,10 +25,10 @@ static const char *const TAG = "helpers"; std::string get_mac_address() { char tmp[20]; uint8_t mac[6]; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 esp_efuse_mac_get_default(mac); #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 WiFi.macAddress(mac); #endif sprintf(tmp, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); @@ -31,10 +38,10 @@ std::string get_mac_address() { std::string get_mac_address_pretty() { char tmp[20]; uint8_t mac[6]; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 esp_efuse_mac_get_default(mac); #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 WiFi.macAddress(mac); #endif sprintf(tmp, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); @@ -44,9 +51,9 @@ std::string get_mac_address_pretty() { std::string generate_hostname(const std::string &base) { return base + std::string("-") + get_mac_address(); } uint32_t random_uint32() { -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 return esp_random(); -#else +#elif defined(USE_ESP8266) return os_random(); #endif } @@ -56,11 +63,13 @@ double random_double() { return random_uint32() / double(UINT32_MAX); } float random_float() { return float(random_double()); } void fill_random(uint8_t *data, size_t len) { -#ifdef ARDUINO_ARCH_ESP32 +#if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO) esp_fill_random(data, len); -#else +#elif defined(USE_ESP8266) int err = os_get_random(data, len); assert(err == 0); +#else +#error "No random source for this system config" #endif } @@ -123,10 +132,13 @@ std::string truncate_string(const std::string &s, size_t length) { } std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { - auto multiplier = float(powf(10.0f, accuracy_decimals)); - float value_rounded = roundf(value * multiplier) / multiplier; + if (accuracy_decimals < 0) { + auto multiplier = powf(10.0f, accuracy_decimals); + value = roundf(value * multiplier) / multiplier; + accuracy_decimals = 0; + } char tmp[32]; // should be enough, but we should maybe improve this at some point. - dtostrf(value_rounded, 0, uint8_t(std::max(0, int(accuracy_decimals))), tmp); + snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value); return std::string(tmp); } std::string uint64_to_string(uint64_t num) { @@ -334,13 +346,13 @@ std::string hexencode(const uint8_t *data, uint32_t len) { return res; } -#ifdef ARDUINO_ARCH_ESP8266 -ICACHE_RAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } -ICACHE_RAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } +#ifdef USE_ESP8266 +IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } +IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } #endif -#ifdef ARDUINO_ARCH_ESP32 -ICACHE_RAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } -ICACHE_RAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } +#ifdef USE_ESP32_FRAMEWORK_ARDUINO +IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } +IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } #endif } // namespace esphome diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 6c0ee8399e..40d94005e7 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -6,7 +6,7 @@ #include #include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO #include "esp32-hal-psram.h" #endif @@ -151,7 +151,7 @@ uint32_t encode_uint32(uint8_t msb, uint8_t byte2, uint8_t byte3, uint8_t lsb); * This behaves like std::lock_guard. As long as the value is visible in the current stack, all interrupts * (including flash reads) will be disabled. * - * Please note all functions called when the interrupt lock must be marked ICACHE_RAM_ATTR (loading code into + * Please note all functions called when the interrupt lock must be marked IRAM_ATTR (loading code into * instruction cache is done via interrupts; disabling interrupts prevents data not already in cache from being * pulled from flash). * @@ -173,7 +173,7 @@ class InterruptLock { ~InterruptLock(); protected: -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 uint32_t xt_state_; #endif }; @@ -277,8 +277,8 @@ template class TemplatableValue { LAMBDA, } type_; - T value_; - std::function f_; + T value_{}; + std::function f_{}; }; template class TemplatableStringValue : public TemplatableValue { @@ -329,7 +329,7 @@ uint32_t fnv1_hash(const std::string &str); template T *new_buffer(size_t length) { T *buffer; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO if (psramFound()) { buffer = (T *) ps_malloc(length); } else { diff --git a/esphome/core/log.cpp b/esphome/core/log.cpp index 9b49a4c6ba..424154d253 100644 --- a/esphome/core/log.cpp +++ b/esphome/core/log.cpp @@ -46,7 +46,7 @@ void HOT esp_log_vprintf_(int level, const char *tag, int line, const __FlashStr } #endif -#ifdef ARDUINO_ARCH_ESP32 +#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) int HOT esp_idf_log_vprintf_(const char *format, va_list args) { // NOLINT #ifdef USE_LOGGER auto *log = logger::global_logger; diff --git a/esphome/core/log.h b/esphome/core/log.h index 3c2f1e6999..590ad26032 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -8,10 +8,14 @@ #include "WString.h" #endif -// Both the ESP-IDF and Arduino also define ESP_LOG* macros. Include them here, so that they won't -// be reincluded later on and redefine our macros. -#ifdef ARDUINO_ARCH_ESP32 +#include "esphome/core/macros.h" + +// Include ESP-IDF/Arduino based logging methods here so they don't undefine ours later +#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) +#include #include +#endif +#ifdef USE_ESP32_FRAMEWORK_ARDUINO #include #endif @@ -58,7 +62,7 @@ void esp_log_vprintf_(int level, const char *tag, int line, const char *format, #ifdef USE_STORE_LOG_STR_IN_FLASH void esp_log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format, va_list args); #endif -#ifdef ARDUINO_ARCH_ESP32 +#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT #endif diff --git a/esphome/core/macros.h b/esphome/core/macros.h index 59b52bf7a1..b0027a276c 100644 --- a/esphome/core/macros.h +++ b/esphome/core/macros.h @@ -2,7 +2,7 @@ #define VERSION_CODE(major, minor, patch) ((major) << 16 | (minor) << 8 | (patch)) -#if defined(ARDUINO_ARCH_ESP8266) +#if defined(USE_ESP8266) #include #if defined(ARDUINO_ESP8266_MAJOR) && defined(ARDUINO_ESP8266_MINOR) && defined(ARDUINO_ESP8266_REVISION) // v3.0.1+ @@ -43,7 +43,7 @@ #warning "Could not determine Arduino framework version, update esphome/core/macros.h!" #endif -#elif defined(ARDUINO_ARCH_ESP32) +#elif defined(USE_ESP32_FRAMEWORK_ARDUINO) #if defined(IDF_VER) // identifies v2, needed since v1 doesn't have the esp_arduino_version.h header #include diff --git a/esphome/core/preferences.cpp b/esphome/core/preferences.cpp deleted file mode 100644 index f051ce0e8a..0000000000 --- a/esphome/core/preferences.cpp +++ /dev/null @@ -1,303 +0,0 @@ -#include "esphome/core/preferences.h" -#include "esphome/core/log.h" -#include "esphome/core/helpers.h" -#include "esphome/core/application.h" - -#ifdef ARDUINO_ARCH_ESP8266 -extern "C" { -#include "spi_flash.h" -} -#endif -#ifdef ARDUINO_ARCH_ESP32 -#include "nvs.h" -#include "nvs_flash.h" -#endif - -namespace esphome { - -static const char *const TAG = "preferences"; - -ESPPreferenceObject::ESPPreferenceObject(size_t offset, size_t length, uint32_t type) - : offset_(offset), length_words_(length), type_(type), data_(length + 1) {} -bool ESPPreferenceObject::load_() { - if (!this->is_initialized()) { - ESP_LOGV(TAG, "Load Pref Not initialized!"); - return false; - } - if (!this->load_internal_()) - return false; - - bool valid = this->data_[this->length_words_] == this->calculate_crc_(); - - ESP_LOGVV(TAG, "LOAD %u: valid=%s, 0=0x%08X 1=0x%08X (Type=%u, CRC=0x%08X)", this->offset_, // NOLINT - YESNO(valid), this->data_[0], this->data_[1], this->type_, this->calculate_crc_()); - return valid; -} -bool ESPPreferenceObject::save_() { - if (!this->is_initialized()) { - ESP_LOGV(TAG, "Save Pref Not initialized!"); - return false; - } - - this->data_[this->length_words_] = this->calculate_crc_(); - if (!this->save_internal_()) - return false; - ESP_LOGVV(TAG, "SAVE %u: 0=0x%08X 1=0x%08X (Type=%u, CRC=0x%08X)", this->offset_, // NOLINT - this->data_[0], this->data_[1], this->type_, this->calculate_crc_()); - return true; -} - -#ifdef ARDUINO_ARCH_ESP8266 - -static const uint32_t ESP_RTC_USER_MEM_START = 0x60001200; -#define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START) -static const uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128; -static const uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4; - -#ifdef USE_ESP8266_PREFERENCES_FLASH -static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 128; -#else -static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 64; -#endif - -static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) { - if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) { - return false; - } - *dest = ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr) - return true; -} - -static bool esp8266_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - -static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) { - if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) { - return false; - } - if (index < 32 && global_preferences.is_prevent_write()) { - return false; - } - - auto *ptr = &ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr) - *ptr = value; - return true; -} - -extern "C" uint32_t _SPIFFS_end; // NOLINT - -static const uint32_t get_esp8266_flash_sector() { - union { - uint32_t *ptr; - uint32_t uint; - } data{}; - data.ptr = &_SPIFFS_end; - return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE; -} -static const uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; } - -void ESPPreferences::save_esp8266_flash_() { - if (!esp8266_flash_dirty) - return; - - ESP_LOGVV(TAG, "Saving preferences to flash..."); - SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK; - { - InterruptLock lock; - erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); - if (erase_res == SPI_FLASH_RESULT_OK) { - write_res = spi_flash_write(get_esp8266_flash_address(), this->flash_storage_, ESP8266_FLASH_STORAGE_SIZE * 4); - } - } - if (erase_res != SPI_FLASH_RESULT_OK) { - ESP_LOGV(TAG, "Erase ESP8266 flash failed!"); - return; - } - if (write_res != SPI_FLASH_RESULT_OK) { - ESP_LOGV(TAG, "Write ESP8266 flash failed!"); - return; - } - - esp8266_flash_dirty = false; -} - -bool ESPPreferenceObject::save_internal_() { - if (this->in_flash_) { - for (uint32_t i = 0; i <= this->length_words_; i++) { - uint32_t j = this->offset_ + i; - if (j >= ESP8266_FLASH_STORAGE_SIZE) - return false; - uint32_t v = this->data_[i]; - uint32_t *ptr = &global_preferences.flash_storage_[j]; - if (*ptr != v) - esp8266_flash_dirty = true; - *ptr = v; - } - global_preferences.save_esp8266_flash_(); - return true; - } - - for (uint32_t i = 0; i <= this->length_words_; i++) { - if (!esp_rtc_user_mem_write(this->offset_ + i, this->data_[i])) - return false; - } - - return true; -} -bool ESPPreferenceObject::load_internal_() { - if (this->in_flash_) { - for (uint32_t i = 0; i <= this->length_words_; i++) { - uint32_t j = this->offset_ + i; - if (j >= ESP8266_FLASH_STORAGE_SIZE) - return false; - this->data_[i] = global_preferences.flash_storage_[j]; - } - - return true; - } - - for (uint32_t i = 0; i <= this->length_words_; i++) { - if (!esp_rtc_user_mem_read(this->offset_ + i, &this->data_[i])) - return false; - } - return true; -} -ESPPreferences::ESPPreferences() - // offset starts from start of user RTC mem (64 words before that are reserved for system), - // an additional 32 words at the start of user RTC are for eboot (OTA, see eboot_command.h), - // which will be reset each time OTA occurs - : current_offset_(0) {} - -void ESPPreferences::begin() { - this->flash_storage_ = new uint32_t[ESP8266_FLASH_STORAGE_SIZE]; // NOLINT - ESP_LOGVV(TAG, "Loading preferences from flash..."); - - { - InterruptLock lock; - spi_flash_read(get_esp8266_flash_address(), this->flash_storage_, ESP8266_FLASH_STORAGE_SIZE * 4); - } -} - -ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type, bool in_flash) { - if (in_flash) { - uint32_t start = this->current_flash_offset_; - uint32_t end = start + length + 1; - if (end > ESP8266_FLASH_STORAGE_SIZE) - return {}; - auto pref = ESPPreferenceObject(start, length, type); - pref.in_flash_ = true; - this->current_flash_offset_ = end; - return pref; - } - - uint32_t start = this->current_offset_; - uint32_t end = start + length + 1; - bool in_normal = start < 96; - // Normal: offset 0-95 maps to RTC offset 32 - 127, - // Eboot: offset 96-127 maps to RTC offset 0 - 31 words - if (in_normal && end > 96) { - // start is in normal but end is not -> switch to Eboot - this->current_offset_ = start = 96; - end = start + length + 1; - in_normal = false; - } - - if (end > 128) { - // Doesn't fit in data, return uninitialized preference obj. - return {}; - } - - uint32_t rtc_offset; - if (in_normal) { - rtc_offset = start + 32; - } else { - rtc_offset = start - 96; - } - - auto pref = ESPPreferenceObject(rtc_offset, length, type); - this->current_offset_ += length + 1; - return pref; -} -void ESPPreferences::prevent_write(bool prevent) { this->prevent_write_ = prevent; } -bool ESPPreferences::is_prevent_write() { return this->prevent_write_; } -#endif - -#ifdef ARDUINO_ARCH_ESP32 -bool ESPPreferenceObject::save_internal_() { - if (global_preferences.nvs_handle_ == 0) - return false; - - char key[32]; - sprintf(key, "%u", this->offset_); - uint32_t len = (this->length_words_ + 1) * 4; - esp_err_t err = nvs_set_blob(global_preferences.nvs_handle_, key, this->data_.data(), len); - if (err) { - ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", key, len, esp_err_to_name(err)); - return false; - } - err = nvs_commit(global_preferences.nvs_handle_); - if (err) { - ESP_LOGV(TAG, "nvs_commit('%s', len=%u) failed: %s", key, len, esp_err_to_name(err)); - return false; - } - return true; -} -bool ESPPreferenceObject::load_internal_() { - if (global_preferences.nvs_handle_ == 0) - return false; - - char key[32]; - sprintf(key, "%u", this->offset_); - size_t len = (this->length_words_ + 1) * 4; - - size_t actual_len; - esp_err_t err = nvs_get_blob(global_preferences.nvs_handle_, key, nullptr, &actual_len); - if (err) { - ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key, esp_err_to_name(err)); - return false; - } - if (actual_len != len) { - ESP_LOGVV(TAG, "NVS length does not match. Assuming key changed (%u!=%u)", actual_len, len); - return false; - } - err = nvs_get_blob(global_preferences.nvs_handle_, key, this->data_.data(), &len); - if (err) { - ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key, esp_err_to_name(err)); - return false; - } - return true; -} -ESPPreferences::ESPPreferences() : current_offset_(0) {} -void ESPPreferences::begin() { - auto ns = truncate_string(App.get_name(), 15); - esp_err_t err = nvs_open(ns.c_str(), NVS_READWRITE, &this->nvs_handle_); - if (err) { - ESP_LOGW(TAG, "nvs_open failed: %s - erasing NVS...", esp_err_to_name(err)); - nvs_flash_deinit(); - nvs_flash_erase(); - nvs_flash_init(); - - err = nvs_open(ns.c_str(), NVS_READWRITE, &this->nvs_handle_); - if (err) { - this->nvs_handle_ = 0; - } - } -} - -ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type, bool in_flash) { - auto pref = ESPPreferenceObject(this->current_offset_, length, type); - this->current_offset_++; - return pref; -} -#endif -uint32_t ESPPreferenceObject::calculate_crc_() const { - uint32_t crc = this->type_; - for (size_t i = 0; i < this->length_words_; i++) { - crc ^= (this->data_[i] * 2654435769UL) >> 1; - } - return crc; -} -bool ESPPreferenceObject::is_initialized() const { return !this->data_.empty(); } - -ESPPreferences global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - -} // namespace esphome diff --git a/esphome/core/preferences.h b/esphome/core/preferences.h index 7ed9a2c8c5..ff7911ed3a 100644 --- a/esphome/core/preferences.h +++ b/esphome/core/preferences.h @@ -1,112 +1,61 @@ #pragma once -#include -#include -#include #include - -#include "esphome/core/defines.h" +#include +#include namespace esphome { +class ESPPreferenceBackend { + public: + virtual bool save(const uint8_t *data, size_t len) = 0; + virtual bool load(uint8_t *data, size_t len) = 0; +}; + class ESPPreferenceObject { public: ESPPreferenceObject() = default; - ESPPreferenceObject(size_t offset, size_t length, uint32_t type); + ESPPreferenceObject(ESPPreferenceBackend *backend) : backend_(backend) {} - template bool save(T *src); + template bool save(const T *src) { + if (backend_ == nullptr) + return false; + return backend_->save(reinterpret_cast(src), sizeof(T)); + } - template bool load(T *dest); - - bool is_initialized() const; + template bool load(T *dest) { + if (backend_ == nullptr) + return false; + return backend_->load(reinterpret_cast(dest), sizeof(T)); + } protected: - friend class ESPPreferences; - - bool save_(); - bool load_(); - bool save_internal_(); - bool load_internal_(); - - uint32_t calculate_crc_() const; - - size_t offset_ = 0; - size_t length_words_ = 0; - uint32_t type_ = 0; - std::vector data_; -#ifdef ARDUINO_ARCH_ESP8266 - bool in_flash_ = false; -#endif + ESPPreferenceBackend *backend_; }; -#ifdef ARDUINO_ARCH_ESP8266 -#ifdef USE_ESP8266_PREFERENCES_FLASH -static const bool DEFAULT_IN_FLASH = true; -#else -static const bool DEFAULT_IN_FLASH = false; -#endif -#endif - -#ifdef ARDUINO_ARCH_ESP32 -static const bool DEFAULT_IN_FLASH = true; -#endif - class ESPPreferences { public: - ESPPreferences(); - void begin(); - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash = DEFAULT_IN_FLASH); - template ESPPreferenceObject make_preference(uint32_t type, bool in_flash = DEFAULT_IN_FLASH); - -#ifdef ARDUINO_ARCH_ESP8266 - /** On the ESP8266, we can't override the first 128 bytes during OTA uploads - * as the eboot parameters are stored there. Writing there during an OTA upload - * would invalidate applying the new firmware. During normal operation, we use - * this part of the RTC user memory, but stop writing to it during OTA uploads. - * - * @param prevent Whether to prevent writing to the first 32 words of RTC user memory. - */ - void prevent_write(bool prevent); - bool is_prevent_write(); + virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) = 0; + virtual ESPPreferenceObject make_preference(size_t length, uint32_t type) = 0; +#ifndef USE_ESP8266 + template::value, bool>::type = true> +#else + // esp8266 toolchain doesn't have is_trivially_copyable + template #endif - - protected: - friend ESPPreferenceObject; - - uint32_t current_offset_; -#ifdef ARDUINO_ARCH_ESP32 - uint32_t nvs_handle_; -#endif -#ifdef ARDUINO_ARCH_ESP8266 - void save_esp8266_flash_(); - bool prevent_write_{false}; - uint32_t *flash_storage_; - uint32_t current_flash_offset_; + ESPPreferenceObject make_preference(uint32_t type, bool in_flash) { + return this->make_preference(sizeof(T), type, in_flash); + } +#ifndef USE_ESP8266 + template::value, bool>::type = true> +#else + template #endif + ESPPreferenceObject make_preference(uint32_t type) { + return this->make_preference(sizeof(T), type); + } }; -extern ESPPreferences global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - -template ESPPreferenceObject ESPPreferences::make_preference(uint32_t type, bool in_flash) { - return this->make_preference((sizeof(T) + 3) / 4, type, in_flash); -} - -template bool ESPPreferenceObject::save(T *src) { - if (!this->is_initialized()) - return false; - // ensure all bytes are 0 (in case sizeof(T) is not multiple of 4) - std::fill_n(data_.begin(), length_words_, 0); - memcpy(data_.data(), src, sizeof(T)); - return this->save_(); -} - -template bool ESPPreferenceObject::load(T *dest) { - std::fill_n(data_.begin(), length_words_, 0); - if (!this->load_()) - return false; - - memcpy(dest, data_.data(), sizeof(T)); - return true; -} +extern ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esphome diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 5718e3b396..a6d3e0307e 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -1,6 +1,7 @@ #include "scheduler.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" #include namespace esphome { @@ -81,7 +82,7 @@ optional HOT Scheduler::next_schedule_in() { return 0; return next_time - now; } -void ICACHE_RAM_ATTR HOT Scheduler::call() { +void IRAM_ATTR HOT Scheduler::call() { const uint32_t now = this->millis_(); this->process_to_add(); diff --git a/esphome/core/util.cpp b/esphome/core/util.cpp index e0132e2e4a..996cf8e310 100644 --- a/esphome/core/util.cpp +++ b/esphome/core/util.cpp @@ -4,47 +4,16 @@ #include "esphome/core/version.h" #include "esphome/core/log.h" -#ifdef USE_WIFI -#include "esphome/components/wifi/wifi_component.h" -#endif - #ifdef USE_API #include "esphome/components/api/api_server.h" #endif -#ifdef USE_ETHERNET -#include "esphome/components/ethernet/ethernet_component.h" -#endif - #ifdef USE_MQTT #include "esphome/components/mqtt/mqtt_client.h" #endif -#ifdef USE_MDNS -#ifdef ARDUINO_ARCH_ESP32 -#include -#endif -#ifdef ARDUINO_ARCH_ESP8266 -#include -#endif -#endif - namespace esphome { -bool network_is_connected() { -#ifdef USE_ETHERNET - if (ethernet::global_eth_component != nullptr && ethernet::global_eth_component->is_connected()) - return true; -#endif - -#ifdef USE_WIFI - if (wifi::global_wifi_component != nullptr) - return wifi::global_wifi_component->is_connected(); -#endif - - return false; -} - bool api_is_connected() { #ifdef USE_API if (api::global_api_server != nullptr) { @@ -65,78 +34,4 @@ bool mqtt_is_connected() { bool remote_is_connected() { return api_is_connected() || mqtt_is_connected(); } -#if defined(ARDUINO_ARCH_ESP8266) && defined(USE_MDNS) -static bool mdns_setup; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -#endif - -#ifndef WEBSERVER_PORT -static const uint8_t WEBSERVER_PORT = 80; -#endif - -#ifdef USE_MDNS -#ifdef ARDUINO_ARCH_ESP8266 -void network_setup_mdns(const IPAddress &address, int interface) { - // Latest arduino framework breaks mDNS for AP interface - // see https://github.com/esp8266/Arduino/issues/6114 - if (interface == 1) - return; - MDNS.begin(App.get_name().c_str(), address); - mdns_setup = true; -#endif -#ifdef ARDUINO_ARCH_ESP32 - void network_setup_mdns() { - MDNS.begin(App.get_name().c_str()); -#endif -#ifdef USE_API - if (api::global_api_server != nullptr) { - MDNS.addService("esphomelib", "tcp", api::global_api_server->get_port()); - // DNS-SD (!=mDNS !) requires at least one TXT record for service discovery - let's add version - MDNS.addServiceTxt("esphomelib", "tcp", "version", ESPHOME_VERSION); - MDNS.addServiceTxt("esphomelib", "tcp", "address", network_get_address().c_str()); - MDNS.addServiceTxt("esphomelib", "tcp", "mac", get_mac_address().c_str()); -#ifdef ARDUINO_ARCH_ESP8266 - MDNS.addServiceTxt("esphomelib", "tcp", "platform", "ESP8266"); -#endif -#ifdef ARDUINO_ARCH_ESP32 - MDNS.addServiceTxt("esphomelib", "tcp", "platform", "ESP32"); -#endif - MDNS.addServiceTxt("esphomelib", "tcp", "board", ESPHOME_BOARD); -#ifdef ESPHOME_PROJECT_NAME - MDNS.addServiceTxt("esphomelib", "tcp", "project_name", ESPHOME_PROJECT_NAME); - MDNS.addServiceTxt("esphomelib", "tcp", "project_version", ESPHOME_PROJECT_VERSION); -#endif - } else { -#endif - // Publish "http" service if not using native API nor the webserver component - // This is just to have *some* mDNS service so that .local resolution works - MDNS.addService("http", "tcp", WEBSERVER_PORT); - MDNS.addServiceTxt("http", "tcp", "version", ESPHOME_VERSION); -#ifdef USE_API - } -#endif -#ifdef USE_PROMETHEUS - MDNS.addService("prometheus-http", "tcp", WEBSERVER_PORT); -#endif - } -#endif - - void network_tick_mdns() { -#if defined(ARDUINO_ARCH_ESP8266) && defined(USE_MDNS) - if (mdns_setup) - MDNS.update(); -#endif - } - - std::string network_get_address() { -#ifdef USE_ETHERNET - if (ethernet::global_eth_component != nullptr) - return ethernet::global_eth_component->get_use_address(); -#endif -#ifdef USE_WIFI - if (wifi::global_wifi_component != nullptr) - return wifi::global_wifi_component->get_use_address(); -#endif - return ""; - } - } // namespace esphome diff --git a/esphome/core/util.h b/esphome/core/util.h index 764c6aaf03..1ca0173eab 100644 --- a/esphome/core/util.h +++ b/esphome/core/util.h @@ -1,15 +1,8 @@ #pragma once #include -#include "IPAddress.h" - namespace esphome { -/// Return whether the node is connected to the network (through wifi, eth, ...) -bool network_is_connected(); -/// Get the active network hostname -std::string network_get_address(); - /// Return whether the node has at least one client connected to the native API bool api_is_connected(); @@ -19,14 +12,4 @@ bool mqtt_is_connected(); /// Return whether the node has any form of "remote" connection via the API or to an MQTT broker bool remote_is_connected(); -/// Manually set up the network stack (outside of the App.setup() loop, for example in OTA safe mode) -#ifdef ARDUINO_ARCH_ESP8266 -void network_setup_mdns(const IPAddress &address, int interface); -#endif -#ifdef ARDUINO_ARCH_ESP32 -void network_setup_mdns(); -#endif - -void network_tick_mdns(); - } // namespace esphome diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 0442e2633b..691f45f91f 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -566,6 +566,10 @@ def add_define(name: str, value: SafeExpType = None): CORE.add_define(Define(name, safe_exp(value))) +def add_platformio_option(key: str, value: Union[str, List[str]]): + CORE.add_platformio_option(key, value) + + async def get_variable(id_: ID) -> "MockObj": """ Wait for the given ID to be defined in the code generation and diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 33fb485c2b..a2eafaa0e8 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -1,9 +1,6 @@ import logging from esphome.const import ( - CONF_INVERTED, - CONF_MODE, - CONF_NUMBER, CONF_SETUP_PRIORITY, CONF_UPDATE_INTERVAL, CONF_TYPE_ID, @@ -12,8 +9,8 @@ from esphome.const import ( # pylint: disable=unused-import from esphome.core import coroutine, ID, CORE from esphome.types import ConfigType -from esphome.cpp_generator import RawExpression, add, get_variable -from esphome.cpp_types import App, GPIOPin +from esphome.cpp_generator import add, get_variable +from esphome.cpp_types import App from esphome.util import Registry, RegistryEntry @@ -26,17 +23,13 @@ async def gpio_pin_expression(conf): This is a coroutine, you must await it with a 'await' expression! """ if conf is None: - return + return None from esphome import pins for key, (func, _) in pins.PIN_SCHEMA_REGISTRY.items(): if key in conf: return await coroutine(func)(conf) - - number = conf[CONF_NUMBER] - mode = conf[CONF_MODE] - inverted = conf.get(CONF_INVERTED) - return GPIOPin.new(number, RawExpression(mode), inverted) + return await coroutine(pins.PIN_SCHEMA_REGISTRY[CORE.target_platform][0])(conf) async def register_component(var, config): diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index ee606ec7b3..7a8eb8e04c 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -30,5 +30,7 @@ JsonObject = arduino_json_ns.class_("JsonObject") JsonObjectRef = JsonObject.operator("ref") JsonObjectConstRef = JsonObjectRef.operator("const") Controller = esphome_ns.class_("Controller") - GPIOPin = esphome_ns.class_("GPIOPin") +InternalGPIOPin = esphome_ns.class_("InternalGPIOPin", GPIOPin) +gpio_ns = esphome_ns.namespace("gpio") +gpio_Flags = gpio_ns.enum("Flags", is_class=True) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 38689675af..bfe6808729 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -441,16 +441,10 @@ class DashboardEntry: return self.storage.comment @property - def esp_platform(self): + def target_platform(self): if self.storage is None: return None - return self.storage.esp_platform - - @property - def board(self): - if self.storage is None: - return None - return self.storage.board + return self.storage.target_platform @property def update_available(self): diff --git a/esphome/final_validate.py b/esphome/final_validate.py index 199c68210e..96dd2fd651 100644 --- a/esphome/final_validate.py +++ b/esphome/final_validate.py @@ -4,13 +4,6 @@ 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): @@ -63,20 +56,3 @@ def id_declaration_match_schema(schema): 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 3420b2cc12..1193d61eaa 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -209,15 +209,21 @@ def write_file(path: Union[Path, str], text: str): raise EsphomeError(f"Could not write file at {path}") from err -def write_file_if_changed(path: Union[Path, str], text: str): +def write_file_if_changed(path: Union[Path, str], text: str) -> bool: + """Write text to the given path, but not if the contents match already. + + Returns true if the file was changed. + """ if not isinstance(path, Path): path = Path(path) src_content = None if path.is_file(): src_content = read_file(path) - if src_content != text: - write_file(path, text) + if src_content == text: + return False + write_file(path, text) + return True def copy_file_if_changed(src: os.PathLike, dst: os.PathLike) -> None: diff --git a/esphome/loader.py b/esphome/loader.py index f74fc6367d..05d2e5a213 100644 --- a/esphome/loader.py +++ b/esphome/loader.py @@ -1,6 +1,5 @@ import logging -import typing -from typing import Callable, List, Optional, Dict, Any, ContextManager +from typing import Callable, List, Optional, Any, ContextManager from types import ModuleType import importlib import importlib.util @@ -8,8 +7,9 @@ import importlib.resources import importlib.abc import sys from pathlib import Path +from dataclasses import dataclass -from esphome.const import ESP_PLATFORMS, SOURCE_FILE_EXTENSIONS +from esphome.const import SOURCE_FILE_EXTENSIONS import esphome.core.config from esphome.core import CORE from esphome.types import ConfigType @@ -17,20 +17,13 @@ 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) +@dataclass(frozen=True, order=True) +class FileResource: + package: str + resource: str def path(self) -> ContextManager[Path]: - return importlib.resources.path(self._package, self._resource) + return importlib.resources.path(self.package, self.resource) class ComponentManifest: @@ -39,6 +32,13 @@ class ComponentManifest: @property def package(self) -> str: + """Return the package name the module is contained in. + + Examples: + - esphome/components/gpio/__init__.py -> esphome.components.gpio + - esphome/components/gpio/switch/__init__.py -> esphome.components.gpio.switch + - esphome/components/a4988/stepper.py -> esphome.components.a4988 + """ return self.module.__package__ @property @@ -61,10 +61,6 @@ class ComponentManifest: 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", []) @@ -91,17 +87,20 @@ class ComponentManifest: return getattr(self.module, "FINAL_VALIDATE_SCHEMA", None) @property - def source_files(self) -> Dict[Path, SourceFile]: - ret = {} + def resources(self) -> List[FileResource]: + """Return a list of all file resources defined in the package of this component. + + This will return all cpp source files that are located in the same folder as the + loaded .py file (does not look through subdirectories) + """ + 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) + ret.append(FileResource(self.package, resource)) return ret diff --git a/esphome/pins.py b/esphome/pins.py index c717424ff3..ae762c1a1a 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -1,330 +1,143 @@ -import logging +import operator +from functools import reduce -import esphome.config_validation as cv -from esphome.const import CONF_INVERTED, CONF_MODE, CONF_NUMBER -from esphome.core import CORE +from esphome.const import ( + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, + CONF_OPEN_DRAIN, + CONF_OUTPUT, + CONF_PULLDOWN, + CONF_PULLUP, +) from esphome.util import SimpleRegistry -from esphome import boards - -_LOGGER = logging.getLogger(__name__) - - -def _lookup_pin(value): - if CORE.is_esp8266: - board_pins_dict = boards.ESP8266_BOARD_PINS - base_pins = boards.ESP8266_BASE_PINS - elif CORE.is_esp32: - 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 - - board_pins = board_pins_dict.get(CORE.board, {}) - - # Resolved aliased board pins (shorthand when two boards have the same pin configuration) - while isinstance(board_pins, str): - board_pins = board_pins_dict[board_pins] - - if value in board_pins: - return board_pins[value] - if value in base_pins: - return base_pins[value] - raise cv.Invalid(f"Cannot resolve pin name '{value}' for board {CORE.board}.") - - -def _translate_pin(value): - if isinstance(value, dict) or value is None: - raise cv.Invalid( - "This variable only supports pin numbers, not full pin schemas " - "(with inverted and mode)." - ) - if isinstance(value, int): - return value - try: - return int(value) - except ValueError: - pass - if value.startswith("GPIO"): - return cv.Coerce(int)(value[len("GPIO") :].strip()) - return _lookup_pin(value) - - -_ESP_SDIO_PINS = { - 6: "Flash Clock", - 7: "Flash Data 0", - 8: "Flash Data 1", - 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( - f"This pin cannot be used on ESP32-C3s and is already used by the flash interface (function: {_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}") - if value in _ESP_SDIO_PINS: - raise cv.Invalid( - f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" - ) - if 9 <= value <= 10: - _LOGGER.warning( - "ESP32: Pin %s (9-10) might already be used by the " - "flash interface in QUAD IO flash mode.", - value, - ) - if value in (20, 24, 28, 29, 30, 31): - # These pins are not exposed in GPIO mux (reason unknown) - # but they're missing from IO_MUX list in datasheet - raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.") - return value - if CORE.is_esp8266: - if value < 0 or value > 17: - raise cv.Invalid(f"ESP8266: Invalid pin number: {value}") - if value in _ESP_SDIO_PINS: - raise cv.Invalid( - f"This pin cannot be used on ESP8266s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" - ) - if 9 <= value <= 10: - _LOGGER.warning( - "ESP8266: Pin %s (9-10) might already be used by the " - "flash interface in QUAD IO flash mode.", - value, - ) - return value - raise NotImplementedError - - -def input_pin(value): - value = validate_gpio_pin(value) - if CORE.is_esp8266 and value == 17: - raise cv.Invalid("GPIO17 (TOUT) is an analog-only pin on the ESP8266.") - return value - - -def input_pullup_pin(value): - value = input_pin(value) - if CORE.is_esp32: - return output_pin(value) - if CORE.is_esp8266: - if value == 0: - raise cv.Invalid( - "GPIO Pin 0 does not support pullup pin mode. " - "Please choose another pin." - ) - return value - raise NotImplementedError - - -def output_pin(value): - value = validate_gpio_pin(value) - if CORE.is_esp32: - if 34 <= value <= 39: - raise cv.Invalid( - f"ESP32: GPIO{value} (34-39) can only be used as an input pin." - ) - return value - if CORE.is_esp8266: - if value == 17: - raise cv.Invalid("GPIO17 (TOUT) is an analog-only pin on the ESP8266.") - return value - raise NotImplementedError - - -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.") - if CORE.is_esp8266: - if value == 17: # A0 - return value - raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.") - raise NotImplementedError - - -input_output_pin = cv.All(input_pin, output_pin) - -PIN_MODES_ESP8266 = [ - "INPUT", - "OUTPUT", - "INPUT_PULLUP", - "OUTPUT_OPEN_DRAIN", - "SPECIAL", - "FUNCTION_1", - "FUNCTION_2", - "FUNCTION_3", - "FUNCTION_4", - "FUNCTION_0", - "WAKEUP_PULLUP", - "WAKEUP_PULLDOWN", - "INPUT_PULLDOWN_16", -] -PIN_MODES_ESP32 = [ - "INPUT", - "OUTPUT", - "INPUT_PULLUP", - "OUTPUT_OPEN_DRAIN", - "SPECIAL", - "FUNCTION_1", - "FUNCTION_2", - "FUNCTION_3", - "FUNCTION_4", - "PULLUP", - "PULLDOWN", - "INPUT_PULLDOWN", - "OPEN_DRAIN", - "FUNCTION_5", - "FUNCTION_6", - "ANALOG", -] - - -def pin_mode(value): - if CORE.is_esp32: - return cv.one_of(*PIN_MODES_ESP32, upper=True)(value) - if CORE.is_esp8266: - return cv.one_of(*PIN_MODES_ESP8266, upper=True)(value) - raise NotImplementedError - - -GPIO_FULL_OUTPUT_PIN_SCHEMA = cv.Schema( - { - cv.Required(CONF_NUMBER): output_pin, - cv.Optional(CONF_MODE, default="OUTPUT"): pin_mode, - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - } -) - -GPIO_FULL_INPUT_PIN_SCHEMA = cv.Schema( - { - cv.Required(CONF_NUMBER): input_pin, - cv.Optional(CONF_MODE, default="INPUT"): pin_mode, - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - } -) - -GPIO_FULL_INPUT_PULLUP_PIN_SCHEMA = cv.Schema( - { - cv.Required(CONF_NUMBER): input_pin, - cv.Optional(CONF_MODE, default="INPUT_PULLUP"): pin_mode, - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - } -) - -GPIO_FULL_ANALOG_PIN_SCHEMA = cv.Schema( - { - cv.Required(CONF_NUMBER): analog_pin, - cv.Optional(CONF_MODE, default="INPUT"): pin_mode, - } -) - - -def shorthand_output_pin(value): - value = output_pin(value) - return GPIO_FULL_OUTPUT_PIN_SCHEMA({CONF_NUMBER: value}) - - -def shorthand_input_pin(value): - value = input_pin(value) - return GPIO_FULL_INPUT_PIN_SCHEMA({CONF_NUMBER: value}) - - -def shorthand_input_pullup_pin(value): - value = input_pullup_pin(value) - return GPIO_FULL_INPUT_PIN_SCHEMA( - { - CONF_NUMBER: value, - CONF_MODE: "INPUT_PULLUP", - } - ) - - -def shorthand_analog_pin(value): - value = analog_pin(value) - return GPIO_FULL_ANALOG_PIN_SCHEMA({CONF_NUMBER: value}) - - -def validate_has_interrupt(value): - if CORE.is_esp8266: - if value[CONF_NUMBER] >= 16: - raise cv.Invalid( - f"Pins GPIO16 and GPIO17 do not support interrupts and cannot be used here, got {value[CONF_NUMBER]}" - ) - return value +from esphome.core import CORE PIN_SCHEMA_REGISTRY = SimpleRegistry() -def internal_gpio_output_pin_schema(value): - if isinstance(value, dict): - return GPIO_FULL_OUTPUT_PIN_SCHEMA(value) - return shorthand_output_pin(value) +def _set_mode(value, default_mode): + import esphome.config_validation as cv + + if CONF_MODE not in value: + return {**value, CONF_MODE: default_mode} + mode = value[CONF_MODE] + if not isinstance(mode, str): + return value + # mode is a string, try parsing it like arduino pin modes + PIN_MODES = { + "INPUT": { + CONF_INPUT: True, + }, + "OUTPUT": { + CONF_OUTPUT: True, + }, + "INPUT_PULLUP": { + CONF_INPUT: True, + CONF_PULLUP: True, + }, + "OUTPUT_OPEN_DRAIN": { + CONF_OUTPUT: True, + CONF_OPEN_DRAIN: True, + }, + "INPUT_PULLDOWN_16": { + CONF_INPUT: True, + CONF_PULLDOWN: True, + }, + "INPUT_PULLDOWN": { + CONF_INPUT: True, + CONF_PULLDOWN: True, + }, + } + if mode.upper() not in PIN_MODES: + raise cv.Invalid(f"Unknown pin mode {mode}", [CONF_MODE]) + return {**value, CONF_MODE: PIN_MODES[mode.upper()]} -def gpio_output_pin_schema(value): - if isinstance(value, dict): - for key, entry in PIN_SCHEMA_REGISTRY.items(): - if key in value: - return entry[1][0](value) - return internal_gpio_output_pin_schema(value) +def _schema_creator(default_mode, internal: bool = False): + def validator(value): + if not isinstance(value, dict): + return validator({CONF_NUMBER: value}) + value = _set_mode(value, default_mode) + if not internal: + for key, entry in PIN_SCHEMA_REGISTRY.items(): + if key != CORE.target_platform and key in value: + return entry[1](value) + return PIN_SCHEMA_REGISTRY[CORE.target_platform][1](value) + + return validator -def internal_gpio_input_pin_schema(value): - if isinstance(value, dict): - return GPIO_FULL_INPUT_PIN_SCHEMA(value) - return shorthand_input_pin(value) +def _internal_number_creator(mode): + def validator(value): + value_d = {CONF_NUMBER: value} + value_d = _set_mode(value_d, mode) + return PIN_SCHEMA_REGISTRY[CORE.target_platform][1](value_d)[CONF_NUMBER] + + return validator -def internal_gpio_analog_pin_schema(value): - if isinstance(value, dict): - return GPIO_FULL_ANALOG_PIN_SCHEMA(value) - return shorthand_analog_pin(value) +def gpio_flags_expr(mode): + """Convert the given mode dict to a gpio Flags expression""" + import esphome.codegen as cg + + FLAGS_MAPPING = { + CONF_INPUT: cg.gpio_Flags.FLAG_INPUT, + CONF_OUTPUT: cg.gpio_Flags.FLAG_OUTPUT, + CONF_OPEN_DRAIN: cg.gpio_Flags.FLAG_OPEN_DRAIN, + CONF_PULLUP: cg.gpio_Flags.FLAG_PULLUP, + CONF_PULLDOWN: cg.gpio_Flags.FLAG_PULLDOWN, + } + active_flags = [v for k, v in FLAGS_MAPPING.items() if mode.get(k)] + if active_flags: + return cg.gpio_Flags.FLAG_NONE + + return reduce(operator.or_, active_flags) -def gpio_input_pin_schema(value): - if isinstance(value, dict): - for key, entry in PIN_SCHEMA_REGISTRY.items(): - if key in value: - return entry[1][1](value) - return internal_gpio_input_pin_schema(value) - - -def internal_gpio_input_pullup_pin_schema(value): - if isinstance(value, dict): - return GPIO_FULL_INPUT_PULLUP_PIN_SCHEMA(value) - return shorthand_input_pullup_pin(value) - - -def gpio_input_pullup_pin_schema(value): - if isinstance(value, dict): - for key, entry in PIN_SCHEMA_REGISTRY.items(): - if key in value: - return entry[1][1](value) - return internal_gpio_input_pullup_pin_schema(value) +gpio_pin_schema = _schema_creator +internal_gpio_pin_number = _internal_number_creator +gpio_output_pin_schema = _schema_creator( + { + CONF_OUTPUT: True, + } +) +gpio_input_pin_schema = _schema_creator( + { + CONF_INPUT: True, + } +) +gpio_input_pullup_pin_schema = _schema_creator( + { + CONF_INPUT: True, + CONF_PULLUP: True, + } +) +internal_gpio_output_pin_schema = _schema_creator( + { + CONF_OUTPUT: True, + }, + internal=True, +) +internal_gpio_output_pin_number = _internal_number_creator({CONF_OUTPUT: True}) +internal_gpio_input_pin_schema = _schema_creator( + { + CONF_INPUT: True, + }, + internal=True, +) +internal_gpio_input_pin_number = _internal_number_creator({CONF_INPUT: True}) +internal_gpio_input_pullup_pin_schema = _schema_creator( + { + CONF_INPUT: True, + CONF_PULLUP: True, + }, + internal=True, +) +internal_gpio_input_pullup_pin_number = _internal_number_creator( + { + CONF_INPUT: True, + CONF_PULLUP: True, + } +) diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index a99081a650..100def3310 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -196,7 +196,7 @@ def _parse_register(config, regex, line): STACKTRACE_ESP8266_EXCEPTION_TYPE_RE = re.compile(r"[eE]xception \((\d+)\):") 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_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})") @@ -204,7 +204,7 @@ STACKTRACE_BAD_ALLOC_RE = re.compile( r"^last failed alloc call: (4[0-9a-fA-F]{7})\((\d+)\)$" ) STACKTRACE_ESP32_BACKTRACE_RE = re.compile( - r"Backtrace:(?:\s+0x[0-9a-fA-F]{8}:0x[0-9a-fA-F]{8})+" + r"Backtrace:(?:\s*0x[0-9a-fA-F]{8}:0x[0-9a-fA-F]{8})+" ) STACKTRACE_ESP32_BACKTRACE_PC_RE = re.compile(r"4[0-9a-f]{7}") STACKTRACE_ESP8266_BACKTRACE_PC_RE = re.compile(r"4[0-9a-f]{7}") diff --git a/esphome/storage_json.py b/esphome/storage_json.py index 517bb508ba..e70d3d6c93 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -40,10 +40,8 @@ class StorageJSON: comment, esphome_version, src_version, - arduino_version, address, - esp_platform, - board, + target_platform, build_path, firmware_bin_path, loaded_integrations, @@ -60,15 +58,10 @@ class StorageJSON: # The version of the file in src/main.cpp - Used to migrate the file assert src_version is None or isinstance(src_version, int) self.src_version = src_version # type: int - # The version of the Arduino framework, the build files need to be cleared each time - # this changes - self.arduino_version = arduino_version # type: str # Address of the ESP, for example livingroom.local or a static IP self.address = address # type: str # The type of ESP in use, either ESP32 or ESP8266 - self.esp_platform = esp_platform # type: str - # The ESP board used, for example nodemcuv2 - self.board = board # type: str + self.target_platform = target_platform # type: str # The absolute path to the platformio project self.build_path = build_path # type: str # The absolute path to the firmware binary @@ -84,10 +77,8 @@ class StorageJSON: "comment": self.comment, "esphome_version": self.esphome_version, "src_version": self.src_version, - "arduino_version": self.arduino_version, "address": self.address, - "esp_platform": self.esp_platform, - "board": self.board, + "esp_platform": self.target_platform, "build_path": self.build_path, "firmware_bin_path": self.firmware_bin_path, "loaded_integrations": self.loaded_integrations, @@ -109,28 +100,24 @@ class StorageJSON: comment=esph.comment, esphome_version=const.__version__, src_version=1, - arduino_version=esph.arduino_version, address=esph.address, - esp_platform=esph.esp_platform, - board=esph.board, + target_platform=esph.target_platform, build_path=esph.build_path, firmware_bin_path=esph.firmware_bin, loaded_integrations=list(esph.loaded_integrations), ) @staticmethod - def from_wizard(name, address, esp_platform, board): - # type: (str, str, str, str) -> StorageJSON + def from_wizard(name, address, esp_platform): + # type: (str, str, str) -> StorageJSON return StorageJSON( storage_version=1, name=name, comment=None, esphome_version=const.__version__, src_version=1, - arduino_version=None, address=address, - esp_platform=esp_platform, - board=board, + target_platform=esp_platform, build_path=None, firmware_bin_path=None, loaded_integrations=[], @@ -147,10 +134,8 @@ class StorageJSON: "esphome_version", storage.get("esphomeyaml_version") ) src_version = storage.get("src_version") - arduino_version = storage.get("arduino_version") address = storage.get("address") esp_platform = storage.get("esp_platform") - board = storage.get("board") build_path = storage.get("build_path") firmware_bin_path = storage.get("firmware_bin_path") loaded_integrations = storage.get("loaded_integrations", []) @@ -160,10 +145,8 @@ class StorageJSON: comment, esphome_version, src_version, - arduino_version, address, esp_platform, - board, build_path, firmware_bin_path, loaded_integrations, diff --git a/esphome/wizard.py b/esphome/wizard.py index abb17e3119..5c35fac73a 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -10,7 +10,6 @@ from esphome.helpers import get_bool_env, write_file from esphome.log import color, Fore # pylint: disable=anomalous-backslash-in-string -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 @@ -116,6 +115,8 @@ captive_portal: def wizard_write(path, **kwargs): + from esphome.components.esp8266 import boards as esp8266_boards + name = kwargs["name"] board = kwargs["board"] @@ -124,11 +125,13 @@ def wizard_write(path, **kwargs): kwargs[key] = sanitize_double_quotes(kwargs[key]) if "platform" not in kwargs: - kwargs["platform"] = "ESP8266" if board in ESP8266_BOARD_PINS else "ESP32" + kwargs["platform"] = ( + "ESP8266" if board in esp8266_boards.ESP8266_BOARD_PINS else "ESP32" + ) platform = kwargs["platform"] write_file(path, wizard_file(**kwargs)) - storage = StorageJSON.from_wizard(name, f"{name}.local", platform, board) + storage = StorageJSON.from_wizard(name, f"{name}.local", platform) storage_path = ext_storage_path(os.path.dirname(path), os.path.basename(path)) storage.save(storage_path) @@ -168,6 +171,9 @@ def strip_accents(value): def wizard(path): + from esphome.components.esp32 import boards as esp32_boards + from esphome.components.esp8266 import boards as esp8266_boards + if not path.endswith(".yaml") and not path.endswith(".yml"): safe_print( f"Please make your configuration file {color(Fore.CYAN, path)} have the extension .yaml or .yml" @@ -266,10 +272,10 @@ def wizard(path): # Don't sleep because user needs to copy link if platform == "ESP32": safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'nodemcu-32s')}\".") - boards = list(ESP32_BOARD_PINS.keys()) + boards = list(esp32_boards.ESP32_BOARD_PINS.keys()) else: safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'nodemcuv2')}\".") - boards = list(ESP8266_BOARD_PINS.keys()) + boards = list(esp8266_boards.ESP8266_BOARD_PINS.keys()) safe_print(f"Options: {', '.join(sorted(boards))}") while True: diff --git a/esphome/writer.py b/esphome/writer.py index 2e1e19de54..29532d4f64 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -2,17 +2,13 @@ import logging import os import re from pathlib import Path -from typing import Dict +from typing import Dict, List, Union from esphome.config import iter_components from esphome.const import ( - CONF_BOARD_FLASH_MODE, - CONF_ESPHOME, - CONF_PLATFORMIO_OPTIONS, HEADER_FILE_EXTENSIONS, SOURCE_FILE_EXTENSIONS, __version__, - ARDUINO_VERSION_ESP8266, ENV_NOGITIGNORE, ) from esphome.core import CORE, EsphomeError @@ -25,7 +21,6 @@ from esphome.helpers import ( get_bool_env, ) from esphome.storage_json import StorageJSON, storage_path -from esphome.boards import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS from esphome import loader _LOGGER = logging.getLogger(__name__) @@ -72,7 +67,9 @@ upload_flags = """, ) -UPLOAD_SPEED_OVERRIDE = {"esp210": 57600} +UPLOAD_SPEED_OVERRIDE = { + "esp210": 57600, +} def get_flags(key): @@ -166,10 +163,6 @@ def storage_should_clean(old, new): # type: (StorageJSON, StorageJSON) -> bool if old.src_version != new.src_version: return True - if old.arduino_version != new.arduino_version: - return True - if old.board != new.board: - return True if old.build_path != new.build_path: return True return False @@ -192,10 +185,10 @@ def update_storage_json(): new.save(path) -def format_ini(data): +def format_ini(data: Dict[str, Union[str, List[str]]]) -> str: content = "" for key, value in sorted(data.items()): - if isinstance(value, (list, set, tuple)): + if isinstance(value, list): content += f"{key} =\n" for x in value: content += f" {x}\n" @@ -204,82 +197,15 @@ def format_ini(data): return content -def gather_lib_deps(): - return [x.as_lib_dep for x in CORE.libraries] - - -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(build_flags)) - - -ESP32_LARGE_PARTITIONS_CSV = """\ -nvs, data, nvs, 0x009000, 0x005000, -otadata, data, ota, 0x00e000, 0x002000, -app0, app, ota_0, 0x010000, 0x1C0000, -app1, app, ota_1, 0x1D0000, 0x1C0000, -eeprom, data, 0x99, 0x390000, 0x001000, -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(overrides.pop("build_flags", [])) - - data = { - "platform": CORE.arduino_version, - "board": CORE.board, - "framework": "arduino", - "lib_deps": lib_deps + ["${common.lib_deps}"], - "build_flags": build_flags + ["${common.build_flags}"], - "upload_speed": UPLOAD_SPEED_OVERRIDE.get(CORE.board, 115200), - } - - if CORE.is_esp32: - data["board_build.partitions"] = "partitions.csv" - partitions_csv = CORE.relative_build_path("partitions.csv") - write_file_if_changed(partitions_csv, ESP32_LARGE_PARTITIONS_CSV) - - # pylint: disable=unsubscriptable-object - if CONF_BOARD_FLASH_MODE in CORE.config[CONF_ESPHOME]: - flash_mode = CORE.config[CONF_ESPHOME][CONF_BOARD_FLASH_MODE] - data["board_build.flash_mode"] = flash_mode - - # Build flags - if CORE.is_esp8266 and CORE.board in ESP8266_FLASH_SIZES: - flash_size = ESP8266_FLASH_SIZES[CORE.board] - ld_scripts = ESP8266_LD_SCRIPTS[flash_size] - - versions_with_old_ldscripts = [ - ARDUINO_VERSION_ESP8266["2.4.0"], - ARDUINO_VERSION_ESP8266["2.4.1"], - ARDUINO_VERSION_ESP8266["2.4.2"], - ] - if CORE.arduino_version == ARDUINO_VERSION_ESP8266["2.3.0"]: - # No ld script support - ld_script = None - if CORE.arduino_version in versions_with_old_ldscripts: - # Old ld script path - ld_script = ld_scripts[0] - else: - ld_script = ld_scripts[1] - - if ld_script is not None: - data["board_build.ldscript"] = ld_script - - # Ignore libraries that are not explicitly used, but may - # be added by LDF - # data['lib_ldf_mode'] = 'chain' - data.update(overrides) + CORE.add_platformio_option( + "lib_deps", [x.as_lib_dep for x in CORE.libraries] + ["${common.lib_deps}"] + ) + # Sort to avoid changing build flags order + CORE.add_platformio_option("build_flags", sorted(CORE.build_flags)) content = f"[env:{CORE.name}]\n" - content += format_ini(data) + content += format_ini(CORE.platformio_options) return content @@ -361,12 +287,15 @@ or use the custom_components folder. def copy_src_tree(): - source_files: Dict[Path, loader.SourceFile] = {} + source_files: List[loader.FileResource] = [] for _, component, _ in iter_components(CORE.config): - source_files.update(component.source_files) + source_files += component.resources + source_files_map = { + Path(x.package.replace(".", "/") + "/" + x.resource): x for x in source_files + } # Convert to list and sort - source_files_l = list(source_files.items()) + source_files_l = list(source_files_map.items()) source_files_l.sort() # Build #include list for esphome.h @@ -377,7 +306,7 @@ def copy_src_tree(): include_l.append("") include_s = "\n".join(include_l) - source_files_copy = source_files.copy() + source_files_copy = source_files_map.copy() ignore_targets = [Path(x) for x in (DEFINES_H_TARGET, VERSION_H_TARGET)] for t in ignore_targets: source_files_copy.pop(t) @@ -420,6 +349,11 @@ def copy_src_tree(): CORE.relative_src_path("esphome", "core", "version.h"), generate_version_h() ) + if CORE.is_esp32: + from esphome.components.esp32 import copy_files + + copy_files() + def generate_defines_h(): define_content_l = [x.as_macro for x in CORE.defines] diff --git a/platformio.ini b/platformio.ini index 89e9ce9374..0e2649e63a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -5,7 +5,7 @@ [platformio] default_envs = esp8266, esp32 -src_dir = . +src_dir = esphome include_dir = [runtime] @@ -47,7 +47,11 @@ src_filter = [common:esp8266] extends = common -platform = platformio/espressif8266@3.1.0 +; when changing this also copy it to esphome-docker-base images +platform = platformio/espressif8266 @ 3.2.0 +platform_packages = + platformio/framework-arduinoespressif8266 @ ~3.30002.0 + framework = arduino board = nodemcuv2 lib_deps = @@ -55,29 +59,88 @@ lib_deps = ESP8266WiFi ; wifi (Arduino built-in) Update ; ota (Arduino built-in) ottowinter/ESPAsyncTCP-esphome@1.2.3 ; async_tcp +build_flags = + ${common.build_flags} + -DUSE_ESP8266 [common:esp32] extends = common -platform = platformio/espressif32@3.2.0 +; when changing this also copy it to esphome-docker-base images +platform = platformio/espressif32 @ 3.3.2 +platform_packages = + platformio/framework-arduinoespressif32 @ ~3.10006.0 + framework = arduino board = nodemcu-32s lib_deps = ${common.lib_deps} Hash ; ota (Arduino built-in) esphome/AsyncTCP-esphome@1.2.2 ; async_tcp +build_flags = + ${common.build_flags} + -DUSE_ESP32 +src_filter = ${common.src_filter} + +[common:espidf] +; when changing this also copy it to esphome-docker-base images +platform = platformio/espressif32 @ 3.3.2 +platform_packages = + platformio/framework-espidf @ ~3.40300.0 + +framework = espidf +board = nodemcu-32s +lib_deps = + esphome/noise-c@0.1.1 ; used by api + espressif/esp32-camera@1.0.0 ; used by esp32_camera + NeoPixelBus@2.6.7 +build_flags = + ${common.build_flags} + ${common:esp32.build_flags} + -DUSE_ESP_IDF + -DUSE_ESP32_FRAMEWORK_ESP_IDF +src_filter = ${common:esp32.src_filter} [env:esp8266] extends = common:esp8266 -build_flags = ${common:esp8266.build_flags} ${runtime.build_flags} +build_flags = + ${common:esp8266.build_flags} + ${runtime.build_flags} + -DUSE_ARDUINO + -DUSE_ESP8266_FRAMEWORK_ARDUINO [env:esp8266-tidy] extends = common:esp8266 -build_flags = ${common:esp8266.build_flags} ${clangtidy.build_flags} +build_flags = + ${common:esp8266.build_flags} + ${clangtidy.build_flags} + -DUSE_ARDUINO + -DUSE_ESP8266_FRAMEWORK_ARDUINO [env:esp32] extends = common:esp32 -build_flags = ${common:esp32.build_flags} ${runtime.build_flags} +build_flags = + ${common:esp32.build_flags} + ${runtime.build_flags} + -DUSE_ARDUINO + -DUSE_ESP32_FRAMEWORK_ARDUINO [env:esp32-tidy] extends = common:esp32 -build_flags = ${common:esp32.build_flags} ${clangtidy.build_flags} +build_flags = + ${common:esp32.build_flags} + ${clangtidy.build_flags} + -DUSE_ARDUINO + -DUSE_ESP32_FRAMEWORK_ARDUINO + +[env:esp32-idf] +extends = common:espidf +build_flags = + ${common:espidf.build_flags} + ${runtime.build_flags} + + +[env:esp32-idf-tidy] +extends = common:espidf +build_flags = + ${common:espidf.build_flags} + ${clangtidy.build_flags} diff --git a/requirements.txt b/requirements.txt index 64ffd366e1..c6d967fc33 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,7 @@ esptool==3.1 click==8.0.1 esphome-dashboard==20210908.0 aioesphomeapi==9.1.0 + +# esp-idf requires this, but doesn't bundle it by default +# https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 +kconfiglib==13.7.1 diff --git a/script/ci-custom.py b/script/ci-custom.py index 3191842d8c..8e9ca487a6 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -408,7 +408,6 @@ ARDUINO_FORBIDDEN_RE = r"[^\w\d](" + r"|".join(ARDUINO_FORBIDDEN) + r")\(.*" exclude=[ "esphome/components/mqtt/custom_mqtt_device.h", "esphome/components/sun/sun.cpp", - "esphome/core/esphal.*", ], ) def lint_no_arduino_framework_functions(fname, match): @@ -422,6 +421,28 @@ def lint_no_arduino_framework_functions(fname, match): ) +IDF_CONVERSION_FORBIDDEN = { + "ARDUINO_ARCH_ESP32": "USE_ESP32", + "ARDUINO_ARCH_ESP8266": "USE_ESP8266", + "pgm_read_byte": "progmem_read_byte", + "ICACHE_RAM_ATTR": "IRAM_ATTR", + "esphome/core/esphal.h": "esphome/core/hal.h", +} +IDF_CONVERSION_FORBIDDEN_RE = r"(" + r"|".join(IDF_CONVERSION_FORBIDDEN) + r").*" + + +@lint_re_check( + IDF_CONVERSION_FORBIDDEN_RE, + include=cpp_include, +) +def lint_no_removed_in_idf_conversions(fname, match): + replacement = IDF_CONVERSION_FORBIDDEN[match.group(1)] + return ( + f"The macro {highlight(match.group(1))} can no longer be used in ESPHome directly. " + f"Plese use {highlight(replacement)} instead." + ) + + @lint_re_check( r"[^\w\d]byte\s+[\w\d]+\s*=", include=cpp_include, @@ -498,6 +519,8 @@ def lint_relative_py_import(fname): ], exclude=[ "esphome/components/socket/headers.h", + "esphome/components/esp32/core.cpp", + "esphome/components/esp8266/core.cpp", ], ) def lint_namespace(fname, content): @@ -575,7 +598,7 @@ def lint_inclusive_language(fname, match): "esphome/components/text_sensor/text_sensor.h", "esphome/components/climate/climate.h", "esphome/core/component.h", - "esphome/core/esphal.h", + "esphome/core/gpio.h", "esphome/core/log.h", "tests/custom.h", ], diff --git a/script/clang-tidy b/script/clang-tidy index 463959fa5d..8a1baf8a22 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -45,7 +45,11 @@ def clang_options(idedata): # pretend we're an Xtensa compiler, which gates some features in the headers '-D__XTENSA__', # allow to condition code on the presence of clang-tidy - '-DCLANG_TIDY' + '-DCLANG_TIDY', + # (esp-idf) Disable this header because they use asm with registers clang-tidy doesn't know + '-D__XTENSA_API_H__', + # (esp-idf) Fix __once_callable in some libstdc++ headers + '-D_GLIBCXX_HAVE_TLS', ] # copy compiler flags, except those clang doesn't understand. @@ -57,7 +61,8 @@ def clang_options(idedata): # add include directories, using -isystem for dependencies to suppress their errors for directory in idedata['includes']['toolchain']: - cmd.extend(['-isystem', directory]) + if 'xtensa-esp32s2-elf' not in directory: + cmd.extend(['-isystem', directory]) for directory in sorted(set(idedata['includes']['build'])): dependency = "framework-arduino" in directory or "/libdeps/" in directory cmd.extend(['-isystem' if dependency else '-I', directory]) diff --git a/script/helpers.py b/script/helpers.py index d32e0eafbe..2c40d47e04 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -112,15 +112,36 @@ def git_ls_files(patterns=None): return {s[3].strip(): int(s[0]) for s in lines} +IDF_TIDY_SDKCONFIG = """\ +CONFIG_BT_ENABLED=y +""" + + def load_idedata(environment): platformio_ini = Path(root_path) / "platformio.ini" temp_idedata = Path(temp_folder) / f"idedata-{environment}.json" + changed = False if not platformio_ini.is_file() or not temp_idedata.is_file(): changed = True elif platformio_ini.stat().st_mtime >= temp_idedata.stat().st_mtime: changed = True - else: - changed = False + + if environment == "esp32-idf-tidy": + # sdkconfig needs to be written before idedata is run + # but the file is also modified by the build process, so + # store a temp file to keep track of the + + sdk_internal = Path(temp_folder) / f"{environment}-internal-sdkconfig" + sdkconfig = Path(root_path) / f"sdkconfig.{environment}" + if ( + changed + or not sdk_internal.is_file() + or sdk_internal.read_text() != IDF_TIDY_SDKCONFIG + ): + changed = True + sdkconfig.write_text(IDF_TIDY_SDKCONFIG) + sdk_internal.parent.mkdir(exist_ok=True) + sdk_internal.write_text(IDF_TIDY_SDKCONFIG) if not changed: return json.loads(temp_idedata.read_text()) diff --git a/tests/component_tests/binary_sensor/test_binary_sensor.py b/tests/component_tests/binary_sensor/test_binary_sensor.py index 8da93a476e..514bc6ee5f 100644 --- a/tests/component_tests/binary_sensor/test_binary_sensor.py +++ b/tests/component_tests/binary_sensor/test_binary_sensor.py @@ -30,7 +30,7 @@ def test_binary_sensor_sets_mandatory_fields(generate_main): # Then assert 'bs_1->set_name("test bs1");' in main_cpp - assert "bs_1->set_pin(new GPIOPin" in main_cpp + assert "bs_1->set_pin(" in main_cpp def test_binary_sensor_config_value_internal_set(generate_main): diff --git a/tests/dummy_main.cpp b/tests/dummy_main.cpp index f9ba868096..d956387665 100644 --- a/tests/dummy_main.cpp +++ b/tests/dummy_main.cpp @@ -27,12 +27,6 @@ void setup() { auto *ota = new ota::OTAComponent(); // NOLINT ota->set_port(8266); - auto *gpio = new gpio::GPIOSwitch(); // NOLINT - gpio->set_name("GPIO Switch"); - gpio->set_pin(new GPIOPin(8, OUTPUT, false)); // NOLINT - App.register_component(gpio); - App.register_switch(gpio); - App.setup(); } diff --git a/tests/test1.yaml b/tests/test1.yaml index cd4179f394..ab089dbc15 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -71,7 +71,6 @@ 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 @@ -82,6 +81,9 @@ wifi: reboot_timeout: 120s power_save_mode: light +mdns: + disabled: false + http_request: useragent: esphome/device timeout: 10s @@ -170,6 +172,7 @@ i2c: scan: True frequency: 100kHz setup_priority: -100 + id: i2c_bus spi: clk_pin: GPIO21 @@ -177,15 +180,18 @@ spi: miso_pin: GPIO23 uart: - - tx_pin: GPIO22 - rx_pin: GPIO23 + - tx_pin: + number: GPIO22 + inverted: yes + rx_pin: + number: GPIO23 + inverted: yes baud_rate: 115200 id: uart0 parity: NONE data_bits: 8 stop_bits: 1 rx_buffer_size: 512 - invert: false - id: adalight_uart tx_pin: GPIO25 @@ -246,6 +252,7 @@ deep_sleep: ads1115: address: 0x48 + i2c_id: i2c_bus dallas: pin: GPIO23 @@ -436,6 +443,7 @@ sensor: availability: state_topic: livingroom/custom_state_topic measurement_duration: 31 + i2c_id: i2c_bus - platform: bme280 temperature: name: 'Outside Temperature' @@ -449,6 +457,7 @@ sensor: address: 0x77 iir_filter: 16x update_interval: 15s + i2c_id: i2c_bus - platform: bme680 temperature: name: 'Outside Temperature' @@ -464,6 +473,7 @@ sensor: temperature: 320 duration: 150ms update_interval: 15s + i2c_id: i2c_bus - platform: bmp085 temperature: name: 'Outside Temperature' @@ -473,6 +483,7 @@ sensor: - lambda: >- return x / powf(1.0 - (x / 44330.0), 5.255); update_interval: 15s + i2c_id: i2c_bus - platform: bmp280 temperature: name: 'Outside Temperature' @@ -482,6 +493,7 @@ sensor: address: 0x77 update_interval: 15s iir_filter: 16x + i2c_id: i2c_bus - platform: dallas address: 0x1C0000031EDD2A28 name: 'Living Room Temperature' @@ -503,6 +515,7 @@ sensor: humidity: name: 'Living Room Humidity 4' update_interval: 15s + i2c_id: i2c_bus - platform: duty_cycle pin: GPIO25 name: Duty Cycle Sensor @@ -515,6 +528,7 @@ sensor: humidity: name: 'Living Room Pressure 5' update_interval: 15s + i2c_id: i2c_bus - platform: hlw8012 sel_pin: 5 cf_pin: 14 @@ -560,6 +574,7 @@ sensor: range: 130uT oversampling: 8x update_interval: 15s + i2c_id: i2c_bus - platform: qmc5883l address: 0x0D field_strength_x: @@ -573,6 +588,7 @@ sensor: range: 800uT oversampling: 256x update_interval: 15s + i2c_id: i2c_bus - platform: hx711 name: 'HX711 Value' dout_pin: GPIO23 @@ -593,6 +609,7 @@ sensor: max_voltage: 32.0V max_current: 3.2A update_interval: 15s + i2c_id: i2c_bus - platform: ina226 address: 0x40 shunt_resistance: 0.1 ohm @@ -606,6 +623,7 @@ sensor: name: 'INA226 Shunt Voltage' max_current: 3.2A update_interval: 15s + i2c_id: i2c_bus - platform: ina3221 address: 0x40 channel_1: @@ -619,12 +637,14 @@ sensor: shunt_voltage: name: 'INA3221 Channel 1 Shunt Voltage' update_interval: 15s + i2c_id: i2c_bus - platform: htu21d temperature: name: 'Living Room Temperature 6' humidity: name: 'Living Room Humidity 6' update_interval: 15s + i2c_id: i2c_bus - platform: max6675 name: 'Living Room Temperature' cs_pin: GPIO23 @@ -670,6 +690,7 @@ sensor: name: 'MPU6050 Gyro z' temperature: name: 'MPU6050 Temperature' + i2c_id: i2c_bus - platform: ms5611 temperature: name: 'Outside Temperature' @@ -677,6 +698,7 @@ sensor: name: 'Outside Pressure' address: 0x77 update_interval: 15s + i2c_id: i2c_bus - platform: pmsa003i pm_1_0: name: "PMSA003i PM1.0" @@ -698,6 +720,7 @@ sensor: name: "PMSA003i PMC <10µm" address: 0x12 standard_units: True + i2c_id: i2c_bus - platform: pulse_counter name: 'Pulse Counter' pin: GPIO12 @@ -768,10 +791,12 @@ sensor: humidity: name: 'Living Room Humidity 8' address: 0x44 + i2c_id: i2c_bus update_interval: 15s - platform: sts3x name: 'Living Room Temperature 9' address: 0x4A + i2c_id: i2c_bus - platform: scd30 co2: name: 'Living Room CO2 9' @@ -785,6 +810,7 @@ sensor: altitude_compensation: 10m ambient_pressure_compensation: 961mBar temperature_offset: 4.2C + i2c_id: i2c_bus - platform: sgp30 eco2: name: 'Workshop eCO2' @@ -794,6 +820,7 @@ sensor: accuracy_decimals: 1 address: 0x58 update_interval: 5s + i2c_id: i2c_bus - platform: sps30 pm_1_0: name: 'Workshop PM <1µm Weight concentration' @@ -824,6 +851,7 @@ sensor: id: 'workshop_PMC_10_0' address: 0x69 update_interval: 10s + i2c_id: i2c_bus - platform: sht4x temperature: name: 'SHT4X Temperature' @@ -831,6 +859,7 @@ sensor: name: 'SHT4X Humidity' address: 0x44 update_interval: 15s + i2c_id: i2c_bus - platform: shtcx temperature: name: 'Living Room Temperature 10' @@ -838,6 +867,7 @@ sensor: name: 'Living Room Humidity 10' address: 0x70 update_interval: 15s + i2c_id: i2c_bus - platform: template name: 'Template Sensor' state_class: measurement @@ -863,6 +893,7 @@ sensor: is_cs_package: true integration_time: 402ms gain: 16x + i2c_id: i2c_bus - platform: tsl2591 id: this_little_light_of_mine address: 0x29 @@ -882,6 +913,7 @@ sensor: calculated_lux: name: "tsl2591 calculated_lux" id: tsl2591_cl + i2c_id: i2c_bus - platform: ultrasonic trigger_pin: GPIO25 echo_pin: @@ -921,6 +953,7 @@ sensor: name: CCS811 TVOC update_interval: 30s baseline: 0x4242 + i2c_id: i2c_bus - platform: tx20 wind_speed: name: 'Windspeed' @@ -946,6 +979,7 @@ sensor: - platform: tmp117 name: 'TMP117 Temperature' update_interval: 5s + i2c_id: i2c_bus - platform: hm3301 pm_1_0: name: 'PM1.0' @@ -956,6 +990,7 @@ sensor: aqi: name: 'AQI' calculation_type: 'CAQI' + i2c_id: i2c_bus - platform: teleinfo tag_name: "HCHC" name: "hchc" @@ -965,15 +1000,18 @@ sensor: - platform: mcp9808 name: 'MCP9808 Temperature' update_interval: 15s + i2c_id: i2c_bus - platform: ezo id: ph_ezo address: 99 unit_of_measurement: 'pH' + i2c_id: i2c_bus - platform: sdp3x name: "HVAC Filter Pressure drop" id: filter_pressure update_interval: 5s accuracy_decimals: 3 + i2c_id: i2c_bus - platform: cs5460a id: cs5460a1 current: @@ -1216,14 +1254,18 @@ binary_sensor: pca9685: frequency: 500 address: 0x0 + i2c_id: i2c_bus tlc59208f: - address: 0x20 id: tlc59208f_1 + i2c_id: i2c_bus - address: 0x22 id: tlc59208f_2 + i2c_id: i2c_bus - address: 0x24 id: tlc59208f_3 + i2c_id: i2c_bus my9231: data_pin: GPIO12 @@ -1363,6 +1405,7 @@ output: id: dac_output - platform: mcp4725 id: mcp4725_dac_output + i2c_id: i2c_bus e131: @@ -1992,6 +2035,7 @@ display: address: 0x3F lambda: |- it.print("Hello World!"); + i2c_id: i2c_bus - platform: max7219 cs_pin: GPIO23 num_chips: 1 @@ -2039,6 +2083,7 @@ display: then: lambda: |- ESP_LOGD("display", "1 -> 2"); + i2c_id: i2c_bus - platform: ssd1306_spi model: 'SSD1306 128x64' cs_pin: GPIO23 @@ -2073,6 +2118,7 @@ display: - id: page13272 lambda: |- // Nothing + i2c_id: i2c_bus - platform: ssd1327_spi model: 'SSD1327 128x128' cs_pin: GPIO23 @@ -2151,6 +2197,7 @@ pn532_spi: payload: !lambda 'return x;' pn532_i2c: + i2c_id: i2c_bus rdm6300: uart_id: uart0 @@ -2167,11 +2214,13 @@ rc522_i2c: on_tag: - lambda: |- ESP_LOGD("main", "Found tag %s", x.c_str()); + i2c_id: i2c_bus - update_interval: 1s on_tag: - lambda: |- ESP_LOGD("main", "Found tag %s", x.c_str()); + i2c_id: i2c_bus gps: uart_id: uart0 @@ -2198,6 +2247,7 @@ time: on_time: seconds: 0 then: ds1307.read_time + i2c_id: i2c_bus cover: - platform: template @@ -2225,31 +2275,35 @@ debug: tca9548a: - address: 0x70 id: multiplex0 - scan: True + channels: + - bus_id: multiplex0_chan0 + channel: 0 + i2c_id: i2c_bus - address: 0x71 id: multiplex1 - scan: True - multiplexer: - id: multiplex0 - channel: 0 + i2c_id: multiplex0_chan0 pcf8574: - id: 'pcf8574_hub' address: 0x21 pcf8575: False + i2c_id: i2c_bus mcp23017: - id: 'mcp23017_hub' open_drain_interrupt: 'true' + i2c_id: i2c_bus mcp23008: - id: 'mcp23008_hub' address: 0x22 open_drain_interrupt: 'true' + i2c_id: i2c_bus mcp23016: - id: 'mcp23016_hub' address: 0x23 + i2c_id: i2c_bus stepper: - platform: a4988 diff --git a/tests/test2.yaml b/tests/test2.yaml index 54932953d5..e6df4d513e 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -14,13 +14,15 @@ 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 subnet: 255.255.255.0 domain: .local +mdns: + disabled: true + api: i2c: @@ -360,7 +362,7 @@ esp32_ble_tracker: ble_client: - mac_address: 01:02:03:04:05:06 id: airthings01 - + airthings_ble: #esp32_ble_beacon: diff --git a/tests/test3.yaml b/tests/test3.yaml index 5602481c36..de46cec99c 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -259,7 +259,7 @@ ota: logger: hardware_uart: UART1 level: DEBUG - esp8266_store_log_strings_in_flash: false + esp8266_store_log_strings_in_flash: true web_server: diff --git a/tests/test4.yaml b/tests/test4.yaml index e52e8cc33c..a205f0802e 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -379,7 +379,7 @@ display: 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 @@ -407,6 +407,23 @@ display: full_update_every: 30 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: inkplate6 + id: inkplate_display + greyscale: false + partial_updating: false + update_interval: 60s + + ckv_pin: GPIO1 + sph_pin: GPIO1 + gmod_pin: GPIO1 + gpio0_enable_pin: GPIO1 + oe_pin: GPIO1 + spv_pin: GPIO1 + powerup_pin: GPIO1 + wakeup_pin: GPIO1 + vcom_pin: GPIO1 + + text_sensor: - platform: pipsolar diff --git a/tests/test5.yaml b/tests/test5.yaml index 84d4a5a44e..f165617551 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -1,12 +1,15 @@ esphome: name: test5 - platform: ESP32 - board: nodemcu-32s build_path: build/test5 project: name: esphome.test5_project version: "1.0.0" +esp32: + board: nodemcu-32s + framework: + type: esp-idf + wifi: networks: - ssid: 'MySSID' @@ -151,20 +154,3 @@ sensor: uart_id: uart2 co2: name: CO2 Sensor - -display: - - platform: inkplate6 - id: inkplate_display - greyscale: false - partial_updating: false - update_interval: 60s - - ckv_pin: GPIO1 - sph_pin: GPIO1 - gmod_pin: GPIO1 - gpio0_enable_pin: GPIO1 - oe_pin: GPIO1 - spv_pin: GPIO1 - powerup_pin: GPIO1 - wakeup_pin: GPIO1 - vcom_pin: GPIO1 diff --git a/tests/unit_tests/test_core.py b/tests/unit_tests/test_core.py index 37b4d6db57..9a15bf0b9c 100644 --- a/tests/unit_tests/test_core.py +++ b/tests/unit_tests/test_core.py @@ -531,13 +531,13 @@ class TestEsphomeCore: assert target.address == "4.3.2.1" def test_is_esp32(self, target): - target.esp_platform = "ESP32" + target.data[const.KEY_CORE] = {const.KEY_TARGET_PLATFORM: "esp32"} assert target.is_esp32 is True assert target.is_esp8266 is False def test_is_esp8266(self, target): - target.esp_platform = "ESP8266" + target.data[const.KEY_CORE] = {const.KEY_TARGET_PLATFORM: "esp8266"} assert target.is_esp32 is False assert target.is_esp8266 is True diff --git a/tests/unit_tests/test_cpp_helpers.py b/tests/unit_tests/test_cpp_helpers.py index ae7a61e01f..ad234250ce 100644 --- a/tests/unit_tests/test_cpp_helpers.py +++ b/tests/unit_tests/test_cpp_helpers.py @@ -3,7 +3,6 @@ from mock import Mock from esphome import cpp_helpers as ch from esphome import const -from esphome.cpp_generator import MockObj @pytest.mark.asyncio @@ -13,15 +12,6 @@ async def test_gpio_pin_expression__conf_is_none(monkeypatch): assert actual is None -@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} - ) - - assert isinstance(actual, MockObj) - - @pytest.mark.asyncio async def test_register_component(monkeypatch): var = Mock(base="foo.bar") diff --git a/tests/unit_tests/test_pins.py b/tests/unit_tests/test_pins.py deleted file mode 100644 index d2ffd5f7cd..0000000000 --- a/tests/unit_tests/test_pins.py +++ /dev/null @@ -1,346 +0,0 @@ -""" -Please Note: - -These tests cover the process of identifying information about pins, they do not -check if the definition of MCUs and pins is correct. - -""" -import logging - -import pytest - -from esphome.config_validation import Invalid -from esphome.core import EsphomeCore -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 = boards.FLASH_SIZE_2_MB - -MOCK_ESP32_BOARD_ID = "_mock_esp32" -MOCK_ESP32_PINS = {"Y0": 12, "Y1": 8, "Y2": 3, "LED": 9, "A0": 8} -MOCK_ESP32_BOARD_ALIAS_ID = "_mock_esp32_alias" - -UNKNOWN_PLATFORM = "STM32" - - -@pytest.fixture -def mock_mcu(monkeypatch): - """ - Add a mock MCU into the lists as a stable fixture - """ - 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 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 -def core(monkeypatch, mock_mcu): - core = EsphomeCore() - monkeypatch.setattr(pins, "CORE", core) - return core - - -@pytest.fixture -def core_esp8266(core): - core.esp_platform = "ESP8266" - core.board = MOCK_ESP8266_BOARD_ID - return core - - -@pytest.fixture -def core_esp32(core): - core.esp_platform = "ESP32" - core.board = MOCK_ESP32_BOARD_ID - return core - - -class Test_lookup_pin: - @pytest.mark.parametrize( - "value, expected", - ( - ("X1", 5), - ("MOSI", 13), - ), - ) - def test_valid_esp8266_pin(self, core_esp8266, value, expected): - actual = pins._lookup_pin(value) - - assert actual == expected - - def test_valid_esp8266_pin_alias(self, core_esp8266): - core_esp8266.board = MOCK_ESP8266_BOARD_ALIAS_ID - - actual = pins._lookup_pin("X2") - - assert actual == 4 - - @pytest.mark.parametrize( - "value, expected", - ( - ("Y1", 8), - ("A0", 8), - ("MOSI", 23), - ), - ) - def test_valid_esp32_pin(self, core_esp32, value, expected): - actual = pins._lookup_pin(value) - - assert actual == expected - - def test_valid_32_pin_alias(self, core_esp32): - core_esp32.board = MOCK_ESP32_BOARD_ALIAS_ID - - actual = pins._lookup_pin("Y2") - - assert actual == 3 - - def test_invalid_pin(self, core_esp8266): - with pytest.raises( - Invalid, match="Cannot resolve pin name 'X42' for board _mock_esp8266." - ): - pins._lookup_pin("X42") - - def test_unsupported_platform(self, core): - core.esp_platform = UNKNOWN_PLATFORM - - with pytest.raises(NotImplementedError): - pins._lookup_pin("TX") - - -class Test_translate_pin: - @pytest.mark.parametrize( - "value, expected", - ( - (2, 2), - ("3", 3), - ("GPIO4", 4), - ("TX", 1), - ("Y0", 12), - ), - ) - def test_valid_values(self, core_esp32, value, expected): - actual = pins._translate_pin(value) - - assert actual == expected - - @pytest.mark.parametrize("value", ({}, None)) - def test_invalid_values(self, core_esp32, value): - with pytest.raises(Invalid, match="This variable only supports"): - pins._translate_pin(value) - - -class Test_validate_gpio_pin: - def test_esp32_valid(self, core_esp32): - actual = pins.validate_gpio_pin("GPIO22") - - assert actual == 22 - - @pytest.mark.parametrize( - "value, match", - ( - (-1, "ESP32: Invalid pin number: -1"), - (40, "ESP32: Invalid pin number: 40"), - (6, "This pin cannot be used on ESP32s and"), - (7, "This pin cannot be used on ESP32s and"), - (8, "This pin cannot be used on ESP32s and"), - (11, "This pin cannot be used on ESP32s and"), - (20, "The pin GPIO20 is not usable on ESP32s"), - (24, "The pin GPIO24 is not usable on ESP32s"), - (28, "The pin GPIO28 is not usable on ESP32s"), - (29, "The pin GPIO29 is not usable on ESP32s"), - (30, "The pin GPIO30 is not usable on ESP32s"), - (31, "The pin GPIO31 is not usable on ESP32s"), - ), - ) - def test_esp32_invalid_pin(self, core_esp32, value, match): - with pytest.raises(Invalid, match=match): - pins.validate_gpio_pin(value) - - @pytest.mark.parametrize("value", (9, 10)) - def test_esp32_warning(self, core_esp32, caplog, value): - caplog.at_level(logging.WARNING) - pins.validate_gpio_pin(value) - - assert len(caplog.messages) == 1 - assert caplog.messages[0].endswith("flash interface in QUAD IO flash mode.") - - def test_esp8266_valid(self, core_esp8266): - actual = pins.validate_gpio_pin("GPIO12") - - assert actual == 12 - - @pytest.mark.parametrize( - "value, match", - ( - (-1, "ESP8266: Invalid pin number: -1"), - (18, "ESP8266: Invalid pin number: 18"), - (6, "This pin cannot be used on ESP8266s and"), - (7, "This pin cannot be used on ESP8266s and"), - (8, "This pin cannot be used on ESP8266s and"), - (11, "This pin cannot be used on ESP8266s and"), - ), - ) - def test_esp8266_invalid_pin(self, core_esp8266, value, match): - with pytest.raises(Invalid, match=match): - pins.validate_gpio_pin(value) - - @pytest.mark.parametrize("value", (9, 10)) - def test_esp8266_warning(self, core_esp8266, caplog, value): - caplog.at_level(logging.WARNING) - pins.validate_gpio_pin(value) - - assert len(caplog.messages) == 1 - assert caplog.messages[0].endswith("flash interface in QUAD IO flash mode.") - - def test_unknown_device(self, core): - core.esp_platform = UNKNOWN_PLATFORM - - with pytest.raises(NotImplementedError): - pins.validate_gpio_pin("0") - - -class Test_input_pin: - @pytest.mark.parametrize("value, expected", (("X0", 16),)) - def test_valid_esp8266_values(self, core_esp8266, value, expected): - actual = pins.input_pin(value) - - assert actual == expected - - @pytest.mark.parametrize( - "value, expected", - ( - ("Y0", 12), - (17, 17), - ), - ) - def test_valid_esp32_values(self, core_esp32, value, expected): - actual = pins.input_pin(value) - - assert actual == expected - - @pytest.mark.parametrize("value", (17,)) - def test_invalid_esp8266_values(self, core_esp8266, value): - with pytest.raises(Invalid): - pins.input_pin(value) - - def test_unknown_platform(self, core): - core.esp_platform = UNKNOWN_PLATFORM - - with pytest.raises(NotImplementedError): - pins.input_pin(2) - - -class Test_input_pullup_pin: - @pytest.mark.parametrize("value, expected", (("X0", 16),)) - def test_valid_esp8266_values(self, core_esp8266, value, expected): - actual = pins.input_pullup_pin(value) - - assert actual == expected - - @pytest.mark.parametrize( - "value, expected", - ( - ("Y0", 12), - (17, 17), - ), - ) - def test_valid_esp32_values(self, core_esp32, value, expected): - actual = pins.input_pullup_pin(value) - - assert actual == expected - - @pytest.mark.parametrize("value", (0,)) - def test_invalid_esp8266_values(self, core_esp8266, value): - with pytest.raises(Invalid): - pins.input_pullup_pin(value) - - def test_unknown_platform(self, core): - core.esp_platform = UNKNOWN_PLATFORM - - with pytest.raises(NotImplementedError): - pins.input_pullup_pin(2) - - -class Test_output_pin: - @pytest.mark.parametrize("value, expected", (("X0", 16),)) - def test_valid_esp8266_values(self, core_esp8266, value, expected): - actual = pins.output_pin(value) - - assert actual == expected - - @pytest.mark.parametrize( - "value, expected", - ( - ("Y0", 12), - (17, 17), - ), - ) - def test_valid_esp32_values(self, core_esp32, value, expected): - actual = pins.output_pin(value) - - assert actual == expected - - @pytest.mark.parametrize("value", (17,)) - def test_invalid_esp8266_values(self, core_esp8266, value): - with pytest.raises(Invalid): - pins.output_pin(value) - - @pytest.mark.parametrize("value", range(34, 40)) - def test_invalid_esp32_values(self, core_esp32, value): - with pytest.raises(Invalid): - pins.output_pin(value) - - def test_unknown_platform(self, core): - core.esp_platform = UNKNOWN_PLATFORM - - with pytest.raises(NotImplementedError): - pins.output_pin(2) - - -class Test_analog_pin: - @pytest.mark.parametrize("value, expected", ((17, 17),)) - def test_valid_esp8266_values(self, core_esp8266, value, expected): - actual = pins.analog_pin(value) - - assert actual == expected - - @pytest.mark.parametrize( - "value, expected", - ( - (32, 32), - (39, 39), - ), - ) - def test_valid_esp32_values(self, core_esp32, value, expected): - actual = pins.analog_pin(value) - - assert actual == expected - - @pytest.mark.parametrize("value", ("X0",)) - def test_invalid_esp8266_values(self, core_esp8266, value): - with pytest.raises(Invalid): - pins.analog_pin(value) - - @pytest.mark.parametrize("value", ("Y0",)) - def test_invalid_esp32_values(self, core_esp32, value): - with pytest.raises(Invalid): - pins.analog_pin(value) - - def test_unknown_platform(self, core): - core.esp_platform = UNKNOWN_PLATFORM - - with pytest.raises(NotImplementedError): - pins.analog_pin(2) diff --git a/tests/unit_tests/test_wizard.py b/tests/unit_tests/test_wizard.py index 56bd5119b5..18e040b0a6 100644 --- a/tests/unit_tests/test_wizard.py +++ b/tests/unit_tests/test_wizard.py @@ -2,7 +2,7 @@ import esphome.wizard as wz import pytest -from esphome.boards import ESP8266_BOARD_PINS +from esphome.components.esp8266.boards import ESP8266_BOARD_PINS from mock import MagicMock From e65a7d887fa4b7e0188c677ea14d26a39019ff64 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 12:02:37 +0200 Subject: [PATCH 1392/1841] Bump aioesphomeapi to 9.1.1 (#2350) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c6d967fc33..e97c79985f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.0 esptool==3.1 click==8.0.1 esphome-dashboard==20210908.0 -aioesphomeapi==9.1.0 +aioesphomeapi==9.1.1 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 250bf3f0541f9f70f11634ed6a70ef2d0d25ded8 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 13:14:05 +0200 Subject: [PATCH 1393/1841] CI cache only restore from direct matches (#2351) --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2c434a9b8..45e2f2735c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,8 +96,6 @@ jobs: with: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} - restore-keys: | - platformio-${{ matrix.pio_cache_key }}- if: matrix.id == 'test' || matrix.id == 'clang-tidy' - name: Install clang tools From bac58bba4d7edddd8a75bfd7caf054d64499cbd3 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 20 Sep 2021 22:13:46 +0200 Subject: [PATCH 1394/1841] fixes compilation error in rtttl (#2357) Compilation error for millis() and delay() after #2303 --- esphome/components/rtttl/rtttl.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/rtttl/rtttl.cpp b/esphome/components/rtttl/rtttl.cpp index 84cc2ce48c..d571c2f287 100644 --- a/esphome/components/rtttl/rtttl.cpp +++ b/esphome/components/rtttl/rtttl.cpp @@ -1,4 +1,5 @@ #include "rtttl.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" namespace esphome { From 7c884329eb570214aff952a597e8922312b7df4b Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 21 Sep 2021 06:34:56 +0200 Subject: [PATCH 1395/1841] Fix MDNS not registered (#2359) --- esphome/components/mdns/__init__.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index ec568ae2d5..c0c0865643 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -1,3 +1,4 @@ +from esphome.const import CONF_ID import esphome.codegen as cg import esphome.config_validation as cv from esphome.core import CORE @@ -5,11 +6,26 @@ from esphome.core import CORE CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] +mdns_ns = cg.esphome_ns.namespace("mdns") +MDNSComponent = mdns_ns.class_("MDNSComponent", cg.Component) + + +def _remove_id_if_disabled(value): + value = value.copy() + if value[CONF_DISABLED]: + value.pop(CONF_ID) + return value + + CONF_DISABLED = "disabled" -CONFIG_SCHEMA = cv.Schema( - { - cv.Optional(CONF_DISABLED, default=False): cv.boolean, - } +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MDNSComponent), + cv.Optional(CONF_DISABLED, default=False): cv.boolean, + } + ), + _remove_id_if_disabled, ) @@ -23,3 +39,6 @@ async def to_code(config): cg.add_library("ESPmDNS", None) elif CORE.is_esp8266: cg.add_library("ESP8266mDNS", None) + + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) From 24f445dade6279bbdcb0fb8123e0285d7494a17e Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 21 Sep 2021 06:37:13 +0200 Subject: [PATCH 1396/1841] Fix src_filter in platformio.ini after src_dir change (#2353) --- platformio.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index 0e2649e63a..412c18085b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -41,9 +41,9 @@ lib_deps = build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = - + - + - +<.temp/all-include.cpp> + +<./> + +<../tests/dummy_main.cpp> + +<../.temp/all-include.cpp> [common:esp8266] extends = common From 71fc61117b8f90ab15418447068ce3302d4e67d2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 21 Sep 2021 06:52:01 +0200 Subject: [PATCH 1397/1841] Fix duplicate defines and restore alphabetical order (#2352) --- esphome/core/defines.h | 48 ++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index ac40921a4a..62468dfbfe 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -12,10 +12,9 @@ // Feature flags #define USE_API +#define USE_API_NOISE +#define USE_API_PLAINTEXT #define USE_BINARY_SENSOR -#ifdef USE_ARDUINO -#define USE_CAPTIVE_PORTAL -#endif #define USE_CLIMATE #define USE_COVER #define USE_DEEP_SLEEP @@ -23,14 +22,6 @@ #define USE_FAN #define USE_GRAPH #define USE_HOMEASSISTANT_TIME - -#ifdef USE_ARDUINO -#define USE_JSON -#define USE_NEXTION_TFT_UPLOAD -#define USE_MQTT -#define USE_CAPTIVE_PORTAL -#endif // USE_ARDUINO - #define USE_LIGHT #define USE_LOGGER #define USE_MDNS @@ -43,39 +34,36 @@ #define USE_STATUS_LED #define USE_SWITCH #define USE_TEXT_SENSOR - #define USE_TIME #define USE_WIFI + +// Arduino-specific feature flags #ifdef USE_ARDUINO +#define USE_CAPTIVE_PORTAL +#define USE_JSON +#define USE_NEXTION_TFT_UPLOAD +#define USE_MQTT #define USE_WIFI_WPA2_EAP #endif +// ESP32-specific feature flags #ifdef USE_ESP32 #define USE_ESP32_BLE_SERVER #define USE_ESP32_CAMERA +#define USE_IMPROV +#define USE_SOCKET_IMPL_BSD_SOCKETS #ifdef USE_ARDUINO #define USE_ETHERNET -#endif // USE_ARDUINO - -#define USE_IMPROV -#define USE_SOCKET_IMPL_BSD_SOCKETS -#endif // USE_ESP32 - -#ifdef USE_ESP8266 -#define USE_ADC_SENSOR_VCC -#define USE_SOCKET_IMPL_LWIP_TCP -#define USE_HTTP_REQUEST_ESP8266_HTTPS +#endif #endif -#define USE_API_PLAINTEXT -#define USE_API_NOISE +// ESP8266-specific feature flags +#ifdef USE_ESP8266 +#define USE_ADC_SENSOR_VCC +#define USE_HTTP_REQUEST_ESP8266_HTTPS +#define USE_SOCKET_IMPL_LWIP_TCP +#endif // Disabled feature flags //#define USE_BSEC // Requires a library with proprietary license. -#define USE_TIME -#define USE_DEEP_SLEEP -#define ESPHOME_BOARD "dummy_board" -#define USE_MDNS -#define USE_API_NOISE -#define USE_API_PLAINTEXT From 491f8cc61193ae7900f13c9d4ea497f870d62c98 Mon Sep 17 00:00:00 2001 From: Alex <33379584+alexyao2015@users.noreply.github.com> Date: Tue, 21 Sep 2021 06:47:51 -0500 Subject: [PATCH 1398/1841] Configurable Flash Write Interval (#2119) Co-authored-by: Alex <33379584+alexyao2015@users.noreply.github.com> Co-authored-by: Otto winter --- CODEOWNERS | 1 + esphome/components/esp32/__init__.py | 1 + esphome/components/esp32/preferences.cpp | 72 +++++++++++++++++++--- esphome/components/esp8266/__init__.py | 1 + esphome/components/esp8266/preferences.cpp | 64 +++++++++---------- esphome/components/ota/ota_component.cpp | 5 +- esphome/components/preferences/__init__.py | 24 ++++++++ esphome/components/preferences/syncer.h | 23 +++++++ esphome/components/wifi/wifi_component.cpp | 2 + esphome/core/preferences.h | 8 +++ 10 files changed, 160 insertions(+), 41 deletions(-) create mode 100644 esphome/components/preferences/__init__.py create mode 100644 esphome/components/preferences/syncer.h diff --git a/CODEOWNERS b/CODEOWNERS index 385ec4d892..adf96b7382 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -104,6 +104,7 @@ esphome/components/pn532/* @OttoWinter @jesserockz esphome/components/pn532_i2c/* @OttoWinter @jesserockz esphome/components/pn532_spi/* @OttoWinter @jesserockz esphome/components/power_supply/* @esphome/core +esphome/components/preferences/* @esphome/core esphome/components/pulse_meter/* @stevebaxter esphome/components/pvvx_mithermometer/* @pasiz esphome/components/rc522/* @glmnet diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index c86087cc25..719b7b5f31 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -34,6 +34,7 @@ from .gpio import esp32_pin_to_code # noqa _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@esphome/core"] +AUTO_LOAD = ["preferences"] def set_core_data(config): diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index 639e6434d6..96b7e7809e 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -4,30 +4,53 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include +#include +#include +#include namespace esphome { namespace esp32 { static const char *const TAG = "esp32.preferences"; +struct NVSData { + std::string key; + std::vector data; +}; + +static std::vector s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + class ESP32PreferenceBackend : public ESPPreferenceBackend { public: std::string key; uint32_t nvs_handle; bool save(const uint8_t *data, size_t len) override { - esp_err_t err = nvs_set_blob(nvs_handle, key.c_str(), data, len); - if (err != 0) { - ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", key.c_str(), len, esp_err_to_name(err)); - return false; - } - err = nvs_commit(nvs_handle); - if (err != 0) { - ESP_LOGV(TAG, "nvs_commit('%s', len=%u) failed: %s", key.c_str(), len, esp_err_to_name(err)); - return false; + // try find in pending saves and update that + for (auto &obj : s_pending_save) { + if (obj.key == key) { + obj.data.assign(data, data + len); + return true; + } } + NVSData save{}; + save.key = key; + save.data.assign(data, data + len); + s_pending_save.emplace_back(save); return true; } bool load(uint8_t *data, size_t len) override { + // try find in pending saves and load from that + for (auto &obj : s_pending_save) { + if (obj.key == key) { + if (obj.data.size() != len) { + // size mismatch + return false; + } + memcpy(data, obj.data.data(), len); + return true; + } + } + size_t actual_len; esp_err_t err = nvs_get_blob(nvs_handle, key.c_str(), nullptr, &actual_len); if (err != 0) { @@ -82,6 +105,37 @@ class ESP32Preferences : public ESPPreferences { return ESPPreferenceObject(pref); } + + bool sync() override { + if (s_pending_save.empty()) + return true; + + ESP_LOGD(TAG, "Saving preferences to flash..."); + // goal try write all pending saves even if one fails + bool any_failed = false; + + // go through vector from back to front (makes erase easier/more efficient) + for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) { + const auto &save = s_pending_save[i]; + esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.data(), save.data.size()); + if (err != 0) { + ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", save.key.c_str(), save.data.size(), + esp_err_to_name(err)); + any_failed = true; + continue; + } + s_pending_save.erase(s_pending_save.begin() + i); + } + + // note: commit on esp-idf currently is a no-op, nvs_set_blob always writes + esp_err_t err = nvs_commit(nvs_handle); + if (err != 0) { + ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err)); + return false; + } + + return !any_failed; + } }; void setup_preferences() { diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 592de06440..6eb4f67115 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -23,6 +23,7 @@ from .gpio import esp8266_pin_to_code # noqa CODEOWNERS = ["@esphome/core"] _LOGGER = logging.getLogger(__name__) +AUTO_LOAD = ["preferences"] def set_core_data(config): diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index 7a8fdb8289..7c0c264054 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -73,33 +73,7 @@ template uint32_t calculate_crc(It first, It last, uint32_t type) { return crc; } -static bool safe_flash() { - if (!s_flash_dirty) - return true; - - ESP_LOGVV(TAG, "Saving preferences to flash..."); - SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK; - { - InterruptLock lock; - erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); - if (erase_res == SPI_FLASH_RESULT_OK) { - write_res = spi_flash_write(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); - } - } - if (erase_res != SPI_FLASH_RESULT_OK) { - ESP_LOGV(TAG, "Erase ESP8266 flash failed!"); - return false; - } - if (write_res != SPI_FLASH_RESULT_OK) { - ESP_LOGV(TAG, "Write ESP8266 flash failed!"); - return false; - } - - s_flash_dirty = false; - return true; -} - -static bool safe_to_flash(size_t offset, const uint32_t *data, size_t len) { +static bool save_to_flash(size_t offset, const uint32_t *data, size_t len) { for (uint32_t i = 0; i < len; i++) { uint32_t j = offset + i; if (j >= ESP8266_FLASH_STORAGE_SIZE) @@ -110,7 +84,7 @@ static bool safe_to_flash(size_t offset, const uint32_t *data, size_t len) { s_flash_dirty = true; *ptr = v; } - return safe_flash(); + return true; } static bool load_from_flash(size_t offset, uint32_t *data, size_t len) { @@ -123,7 +97,7 @@ static bool load_from_flash(size_t offset, uint32_t *data, size_t len) { return true; } -static bool safe_to_rtc(size_t offset, const uint32_t *data, size_t len) { +static bool save_to_rtc(size_t offset, const uint32_t *data, size_t len) { for (uint32_t i = 0; i < len; i++) if (!esp_rtc_user_mem_write(offset + i, data[i])) return false; @@ -154,9 +128,9 @@ class ESP8266PreferenceBackend : public ESPPreferenceBackend { buffer[buffer.size() - 1] = calculate_crc(buffer.begin(), buffer.end() - 1, type); if (in_flash) { - return safe_to_flash(offset, buffer.data(), buffer.size()); + return save_to_flash(offset, buffer.data(), buffer.size()); } else { - return safe_to_rtc(offset, buffer.data(), buffer.size()); + return save_to_rtc(offset, buffer.data(), buffer.size()); } } bool load(uint8_t *data, size_t len) override { @@ -245,6 +219,34 @@ class ESP8266Preferences : public ESPPreferences { return make_preference(length, type, false); #endif } + + bool sync() override { + if (!s_flash_dirty) + return true; + if (s_prevent_write) + return false; + + ESP_LOGD(TAG, "Saving preferences to flash..."); + SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK; + { + InterruptLock lock; + erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); + if (erase_res == SPI_FLASH_RESULT_OK) { + write_res = spi_flash_write(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); + } + } + if (erase_res != SPI_FLASH_RESULT_OK) { + ESP_LOGV(TAG, "Erase ESP8266 flash failed!"); + return false; + } + if (write_res != SPI_FLASH_RESULT_OK) { + ESP_LOGV(TAG, "Write ESP8266 flash failed!"); + return false; + } + + s_flash_dirty = false; + return true; + } }; void setup_preferences() { diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 7ee3ed2866..f217bd32d1 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -548,7 +548,10 @@ bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_ return false; } } -void OTAComponent::write_rtc_(uint32_t val) { this->rtc_.save(&val); } +void OTAComponent::write_rtc_(uint32_t val) { + this->rtc_.save(&val); + global_preferences->sync(); +} uint32_t OTAComponent::read_rtc_() { uint32_t val; if (!this->rtc_.load(&val)) diff --git a/esphome/components/preferences/__init__.py b/esphome/components/preferences/__init__.py new file mode 100644 index 0000000000..4844ad6c02 --- /dev/null +++ b/esphome/components/preferences/__init__.py @@ -0,0 +1,24 @@ +from esphome.const import CONF_ID +import esphome.codegen as cg +import esphome.config_validation as cv + +CODEOWNERS = ["@esphome/core"] + +preferences_ns = cg.esphome_ns.namespace("preferences") +IntervalSyncer = preferences_ns.class_("IntervalSyncer", cg.Component) + +CONF_FLASH_WRITE_INTERVAL = "flash_write_interval" +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(IntervalSyncer), + cv.Optional( + CONF_FLASH_WRITE_INTERVAL, default="60s" + ): cv.positive_time_period_milliseconds, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_write_interval(config[CONF_FLASH_WRITE_INTERVAL])) + await cg.register_component(var, config) diff --git a/esphome/components/preferences/syncer.h b/esphome/components/preferences/syncer.h new file mode 100644 index 0000000000..af1fe9ba4a --- /dev/null +++ b/esphome/components/preferences/syncer.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/preferences.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace preferences { + +class IntervalSyncer : public Component { + public: + void set_write_interval(uint32_t write_interval) { write_interval_ = write_interval; } + void setup() override { + set_interval(write_interval_, []() { global_preferences->sync(); }); + } + void on_shutdown() override { global_preferences->sync(); } + float get_setup_priority() const override { return setup_priority::BUS; } + + protected: + uint32_t write_interval_; +}; + +} // namespace preferences +} // namespace esphome diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 6e0ce8c044..47cd0ef9ad 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -232,6 +232,8 @@ void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &pa strncpy(save.ssid, ssid.c_str(), sizeof(save.ssid)); strncpy(save.password, password.c_str(), sizeof(save.password)); this->pref_.save(&save); + // ensure it's written immediately + global_preferences->sync(); WiFiAP sta{}; sta.set_ssid(ssid); diff --git a/esphome/core/preferences.h b/esphome/core/preferences.h index ff7911ed3a..b3f4b77f78 100644 --- a/esphome/core/preferences.h +++ b/esphome/core/preferences.h @@ -37,6 +37,14 @@ class ESPPreferences { public: virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) = 0; virtual ESPPreferenceObject make_preference(size_t length, uint32_t type) = 0; + + /** + * Commit pending writes to flash. + * + * @return true if write is successful. + */ + virtual bool sync() = 0; + #ifndef USE_ESP8266 template::value, bool>::type = true> #else From 92a24d52be5ee60772c10554967a0d011e7ca723 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Tue, 21 Sep 2021 17:11:58 +0200 Subject: [PATCH 1399/1841] Fix OTA password mismatch error. (#2363) Co-authored-by: Maurice Makaay --- esphome/components/ota/ota_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index f217bd32d1..5e07bb64e7 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -325,7 +325,7 @@ void OTAComponent::handle_() { ESP_LOGV(TAG, "Auth: Result is %s", sbuf); // Receive result, 32 bytes hex MD5 - if (!this->writeall_(buf + 64, 32)) { + if (!this->readall_(buf + 64, 32)) { ESP_LOGW(TAG, "Auth: Reading response failed!"); goto error; } From 637b55bfbfe8d55b372271507f11040189d140a0 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 21 Sep 2021 17:12:17 +0200 Subject: [PATCH 1400/1841] Allow compilation against IDF from repository (#2355) * Fix src_filter in platformio.ini after src_dir change * Add -Wno-nonnull-compare to platformio.ini as well * Create default sdkconfig for static analysis * Add more compiler flags to clang ignore list * Clean-up platformio.ini * Remove unnecessary blank line * Fix accidentally dropped library * Don't gitignore sdkconfig.defaults Co-authored-by: Otto winter Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .gitignore | 1 + platformio.ini | 99 +++++++++++++++++++++++++--------------------- script/clang-tidy | 4 +- script/helpers.py | 31 ++++++--------- sdkconfig.defaults | 17 ++++++++ 5 files changed, 85 insertions(+), 67 deletions(-) create mode 100644 sdkconfig.defaults diff --git a/.gitignore b/.gitignore index c52909f7c9..57b8478bd7 100644 --- a/.gitignore +++ b/.gitignore @@ -126,3 +126,4 @@ tests/.esphome/ .pio/ sdkconfig.* +!sdkconfig.defaults diff --git a/platformio.ini b/platformio.ini index 412c18085b..1901f175af 100644 --- a/platformio.ini +++ b/platformio.ini @@ -4,7 +4,7 @@ ; It's *not* used during runtime. [platformio] -default_envs = esp8266, esp32 +default_envs = esp8266, esp32, esp32-idf src_dir = esphome include_dir = @@ -26,18 +26,8 @@ build_flags = [common] lib_deps = - ottowinter/AsyncMqttClient-esphome@0.8.4 ; mqtt - ottowinter/ArduinoJson-esphomelib@5.13.3 ; json - esphome/ESPAsyncWebServer-esphome@1.3.0 ; web_server_base - fastled/FastLED@3.3.2 ; fastled_base - makuna/NeoPixelBus@2.6.7 ; neopixelbus - mikalhart/TinyGPSPlus@1.0.2 ; gps - freekode/TM1651@1.0.1 ; tm1651 - seeed-studio/Grove - Laser PM2.5 Sensor HM3301@1.0.3 ; hm3301 - glmnet/Dsmr@0.5 ; dsmr - rweather/Crypto@0.2.0 ; dsmr - esphome/noise-c@0.1.1 ; api - dudanov/MideaUART@1.1.0 ; midea + esphome/noise-c@0.1.1 ; api + makuna/NeoPixelBus@2.6.7 ; neopixelbus build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = @@ -45,8 +35,32 @@ src_filter = +<../tests/dummy_main.cpp> +<../.temp/all-include.cpp> -[common:esp8266] +[common:arduino] extends = common +lib_deps = + ${common.lib_deps} + ottowinter/AsyncMqttClient-esphome@0.8.4 ; mqtt + ottowinter/ArduinoJson-esphomelib@5.13.3 ; json + esphome/ESPAsyncWebServer-esphome@1.3.0 ; web_server_base + fastled/FastLED@3.3.2 ; fastled_base + mikalhart/TinyGPSPlus@1.0.2 ; gps + freekode/TM1651@1.0.1 ; tm1651 + seeed-studio/Grove - Laser PM2.5 Sensor HM3301@1.0.3 ; hm3301 + glmnet/Dsmr@0.5 ; dsmr + rweather/Crypto@0.2.0 ; dsmr + dudanov/MideaUART@1.1.0 ; midea +build_flags = + ${common.build_flags} + -DUSE_ARDUINO + +[common:idf] +extends = common +build_flags = + ${common.build_flags} + -DUSE_ESP_IDF + +[common:esp8266] +extends = common:arduino ; when changing this also copy it to esphome-docker-base images platform = platformio/espressif8266 @ 3.2.0 platform_packages = @@ -55,16 +69,17 @@ platform_packages = framework = arduino board = nodemcuv2 lib_deps = - ${common.lib_deps} + ${common:arduino.lib_deps} ESP8266WiFi ; wifi (Arduino built-in) Update ; ota (Arduino built-in) ottowinter/ESPAsyncTCP-esphome@1.2.3 ; async_tcp build_flags = - ${common.build_flags} + ${common:arduino.build_flags} -DUSE_ESP8266 + -DUSE_ESP8266_FRAMEWORK_ARDUINO -[common:esp32] -extends = common +[common:esp32-arduino] +extends = common:arduino ; when changing this also copy it to esphome-docker-base images platform = platformio/espressif32 @ 3.3.2 platform_packages = @@ -73,15 +88,16 @@ platform_packages = framework = arduino board = nodemcu-32s lib_deps = - ${common.lib_deps} + ${common:arduino.lib_deps} Hash ; ota (Arduino built-in) esphome/AsyncTCP-esphome@1.2.2 ; async_tcp build_flags = - ${common.build_flags} + ${common:arduino.build_flags} -DUSE_ESP32 -src_filter = ${common.src_filter} + -DUSE_ESP32_FRAMEWORK_ARDUINO -[common:espidf] +[common:esp32-idf] +extends = common:idf ; when changing this also copy it to esphome-docker-base images platform = platformio/espressif32 @ 3.3.2 platform_packages = @@ -90,57 +106,48 @@ platform_packages = framework = espidf board = nodemcu-32s lib_deps = - esphome/noise-c@0.1.1 ; used by api - espressif/esp32-camera@1.0.0 ; used by esp32_camera - NeoPixelBus@2.6.7 + ${common:idf.lib_deps} + espressif/esp32-camera@1.0.0 ; esp32_camera build_flags = - ${common.build_flags} - ${common:esp32.build_flags} - -DUSE_ESP_IDF + ${common:idf.build_flags} + -Wno-nonnull-compare + -DUSE_ESP32 -DUSE_ESP32_FRAMEWORK_ESP_IDF -src_filter = ${common:esp32.src_filter} [env:esp8266] extends = common:esp8266 build_flags = ${common:esp8266.build_flags} ${runtime.build_flags} - -DUSE_ARDUINO - -DUSE_ESP8266_FRAMEWORK_ARDUINO [env:esp8266-tidy] extends = common:esp8266 build_flags = ${common:esp8266.build_flags} ${clangtidy.build_flags} - -DUSE_ARDUINO - -DUSE_ESP8266_FRAMEWORK_ARDUINO [env:esp32] -extends = common:esp32 +extends = common:esp32-arduino build_flags = - ${common:esp32.build_flags} + ${common:esp32-arduino.build_flags} ${runtime.build_flags} - -DUSE_ARDUINO - -DUSE_ESP32_FRAMEWORK_ARDUINO [env:esp32-tidy] -extends = common:esp32 +extends = common:esp32-arduino build_flags = - ${common:esp32.build_flags} + ${common:esp32-arduino.build_flags} ${clangtidy.build_flags} - -DUSE_ARDUINO - -DUSE_ESP32_FRAMEWORK_ARDUINO [env:esp32-idf] -extends = common:espidf +extends = common:esp32-idf +board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32-idf build_flags = - ${common:espidf.build_flags} + ${common:esp32-idf.build_flags} ${runtime.build_flags} - [env:esp32-idf-tidy] -extends = common:espidf +extends = common:esp32-idf +board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32-idf-tidy build_flags = - ${common:espidf.build_flags} + ${common:esp32-idf.build_flags} ${clangtidy.build_flags} diff --git a/script/clang-tidy b/script/clang-tidy index 8a1baf8a22..2612a18c1c 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -54,7 +54,9 @@ def clang_options(idedata): # copy compiler flags, except those clang doesn't understand. cmd.extend(flag for flag in idedata['cxx_flags'].split(' ') - if flag not in ('-free', '-fipa-pta', '-fstrict-volatile-bitfields', '-mlongcalls', '-mtext-section-literals')) + if flag not in ('-free', '-fipa-pta', '-fstrict-volatile-bitfields', + '-mlongcalls', '-mtext-section-literals', + '-mfix-esp32-psram-cache-issue', '-mfix-esp32-psram-cache-strategy=memw')) # defines cmd.extend(f'-D{define}' for define in idedata['defines']) diff --git a/script/helpers.py b/script/helpers.py index 2c40d47e04..430d8a8e7f 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -112,11 +112,6 @@ def git_ls_files(patterns=None): return {s[3].strip(): int(s[0]) for s in lines} -IDF_TIDY_SDKCONFIG = """\ -CONFIG_BT_ENABLED=y -""" - - def load_idedata(environment): platformio_ini = Path(root_path) / "platformio.ini" temp_idedata = Path(temp_folder) / f"idedata-{environment}.json" @@ -126,30 +121,26 @@ def load_idedata(environment): elif platformio_ini.stat().st_mtime >= temp_idedata.stat().st_mtime: changed = True - if environment == "esp32-idf-tidy": - # sdkconfig needs to be written before idedata is run - # but the file is also modified by the build process, so - # store a temp file to keep track of the + if "idf" in environment: + # remove full sdkconfig when the defaults have changed so that it is regenerated + default_sdkconfig = Path(root_path) / "sdkconfig.defaults" + temp_sdkconfig = Path(temp_folder) / f"sdkconfig-{environment}" - sdk_internal = Path(temp_folder) / f"{environment}-internal-sdkconfig" - sdkconfig = Path(root_path) / f"sdkconfig.{environment}" - if ( - changed - or not sdk_internal.is_file() - or sdk_internal.read_text() != IDF_TIDY_SDKCONFIG - ): + if not temp_sdkconfig.is_file(): + changed = True + elif default_sdkconfig.stat().st_mtime >= temp_sdkconfig.stat().st_mtime: + temp_sdkconfig.unlink() changed = True - sdkconfig.write_text(IDF_TIDY_SDKCONFIG) - sdk_internal.parent.mkdir(exist_ok=True) - sdk_internal.write_text(IDF_TIDY_SDKCONFIG) if not changed: return json.loads(temp_idedata.read_text()) + # ensure temp directory exists before running pio, as it writes sdkconfig to it + Path(temp_folder).mkdir(exist_ok=True) + stdout = subprocess.check_output(["pio", "run", "-t", "idedata", "-e", environment]) match = re.search(r'{\s*".*}', stdout.decode("utf-8")) data = json.loads(match.group()) - temp_idedata.parent.mkdir(exist_ok=True) temp_idedata.write_text(json.dumps(data, indent=2) + "\n") return data diff --git a/sdkconfig.defaults b/sdkconfig.defaults new file mode 100644 index 0000000000..6b2d6f8f2e --- /dev/null +++ b/sdkconfig.defaults @@ -0,0 +1,17 @@ +# ESP-IDF sdkconfig defaults used for development purposes only, not used during runtime. Used when PlatformIO is ran +# directly from the source directory, e.g. by IDEs or for static analysis (clang-tidy). This should enable all flags +# that are set by any component. + +# esp32 +CONFIG_COMPILER_OPTIMIZATION_DEFAULT=n +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_PARTITION_TABLE_CUSTOM=y +#CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_SINGLE_APP=n + +# esp32_ble +CONFIG_BT_ENABLED=y + +# esp32_camera +CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC=y +CONFIG_ESP32_SPIRAM_SUPPORT=y From bbac1534a3b2b1c961c13e8da23dd4ffdbe2ed22 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 21 Sep 2021 21:59:11 +0200 Subject: [PATCH 1401/1841] Fix ESP8266 preferences not set up (#2362) --- esphome/components/esp8266/__init__.py | 7 +++++-- esphome/components/esp8266/core.cpp | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 6eb4f67115..93a461ba1f 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -10,11 +10,11 @@ from esphome.const import ( KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, ) -from esphome.core import CORE +from esphome.core import CORE, coroutine_with_priority import esphome.config_validation as cv import esphome.codegen as cg -from .const import CONF_RESTORE_FROM_FLASH, KEY_BOARD, KEY_ESP8266 +from .const import CONF_RESTORE_FROM_FLASH, KEY_BOARD, KEY_ESP8266, esp8266_ns from .boards import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS # force import gpio to register pin schema @@ -153,7 +153,10 @@ CONFIG_SCHEMA = cv.All( ) +@coroutine_with_priority(1000) async def to_code(config): + cg.add(esp8266_ns.setup_preferences()) + cg.add_platformio_option("board", config[CONF_BOARD]) cg.add_build_flag("-DUSE_ESP8266") cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp index 453f772e09..b78600e7a3 100644 --- a/esphome/components/esp8266/core.cpp +++ b/esphome/components/esp8266/core.cpp @@ -32,6 +32,28 @@ uint32_t arch_get_cpu_cycle_count() { } uint32_t arch_get_cpu_freq_hz() { return F_CPU; } +void force_link_symbols() { + // Tasmota uses magic bytes in the binary to check if an OTA firmware is compatible + // with their settings - ESPHome uses a different settings system (that can also survive + // erases). So set magic bytes indicating all tasmota versions are supported. + // This only adds 12 bytes of binary size, which is an acceptable price to pay for easier support + // for Tasmota. + // https://github.com/arendst/Tasmota/blob/b05301b1497942167a015a6113b7f424e42942cd/tasmota/settings.ino#L346-L380 + // https://github.com/arendst/Tasmota/blob/b05301b1497942167a015a6113b7f424e42942cd/tasmota/i18n.h#L652-L654 + const static uint32_t TASMOTA_MAGIC_BYTES[] PROGMEM = {0x5AA55AA5, 0xFFFFFFFF, 0xA55AA55A}; + // Force link symbol by using a volatile integer (GCC attribute used does not work because of LTO) + volatile int x = 0; + x = TASMOTA_MAGIC_BYTES[x]; +} + +extern "C" void resetPins() { // NOLINT + // Added in framework 2.7.0 + // usually this sets up all pins to be in INPUT mode + // however, not strictly needed as we set up the pins properly + // ourselves and this causes pins to toggle during reboot. + force_link_symbols(); +} + } // namespace esphome #endif // USE_ESP8266 From c8a8acd46ef0d04cee2af02dde0e87f263860189 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Wed, 22 Sep 2021 13:55:49 +1200 Subject: [PATCH 1402/1841] Fix ESP8266 preference loading (#2367) --- esphome/components/esp8266/preferences.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index 7c0c264054..041736943b 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -149,7 +149,12 @@ class ESP8266PreferenceBackend : public ESPPreferenceBackend { return false; uint32_t crc = calculate_crc(buffer.begin(), buffer.end() - 1, type); - return buffer[buffer.size() - 1] == crc; + if (buffer[buffer.size() - 1] != crc) { + return false; + } + + memcpy(data, buffer.data(), len); + return true; } }; From c51352d04d5440f507cc3b79193bbb98cbfa17c1 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Wed, 22 Sep 2021 13:59:21 +1200 Subject: [PATCH 1403/1841] Allow non-addressable lights in light partitions (#2256) --- .../light/addressable_light_wrapper.h | 56 +++++++++++++++++ esphome/components/partition/light.py | 60 ++++++++++++++----- esphome/const.py | 2 + tests/test1.yaml | 1 + 4 files changed, 104 insertions(+), 15 deletions(-) create mode 100644 esphome/components/light/addressable_light_wrapper.h diff --git a/esphome/components/light/addressable_light_wrapper.h b/esphome/components/light/addressable_light_wrapper.h new file mode 100644 index 0000000000..813dd43313 --- /dev/null +++ b/esphome/components/light/addressable_light_wrapper.h @@ -0,0 +1,56 @@ +#pragma once + +#include "esphome/core/component.h" +#include "addressable_light.h" + +namespace esphome { +namespace light { + +class AddressableLightWrapper : public light::AddressableLight { + public: + explicit AddressableLightWrapper(light::LightState *light_state) : light_state_(light_state) { + this->wrapper_state_ = new uint8_t[5]; + } + + int32_t size() const override { return 1; } + + void clear_effect_data() override { this->wrapper_state_[4] = 0; } + + light::LightTraits get_traits() override { return this->light_state_->get_traits(); } + + void write_state(light::LightState *state) override { + float gamma = this->light_state_->get_gamma_correct(); + float r = gamma_uncorrect(this->wrapper_state_[0] / 255.0f, gamma); + float g = gamma_uncorrect(this->wrapper_state_[1] / 255.0f, gamma); + float b = gamma_uncorrect(this->wrapper_state_[2] / 255.0f, gamma); + float w = gamma_uncorrect(this->wrapper_state_[3] / 255.0f, gamma); + float brightness = fmaxf(r, fmaxf(g, b)); + + auto call = this->light_state_->make_call(); + call.set_state(true); + call.set_brightness_if_supported(1.0f); + call.set_color_brightness_if_supported(brightness); + call.set_red_if_supported(r); + call.set_green_if_supported(g); + call.set_blue_if_supported(b); + call.set_white_if_supported(w); + call.set_transition_length_if_supported(0); + call.set_publish(false); + call.set_save(false); + call.perform(); + + this->mark_shown_(); + } + + protected: + light::ESPColorView get_view_internal(int32_t index) const override { + return {&this->wrapper_state_[0], &this->wrapper_state_[1], &this->wrapper_state_[2], + &this->wrapper_state_[3], &this->wrapper_state_[4], &this->correction_}; + } + + light::LightState *light_state_; + uint8_t *wrapper_state_; +}; + +} // namespace light +} // namespace esphome diff --git a/esphome/components/partition/light.py b/esphome/components/partition/light.py index 06bee2143e..ada83a123e 100644 --- a/esphome/components/partition/light.py +++ b/esphome/components/partition/light.py @@ -2,9 +2,12 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import light from esphome.const import ( + CONF_ADDRESSABLE_LIGHT_ID, CONF_FROM, CONF_ID, + CONF_LIGHT_ID, CONF_SEGMENTS, + CONF_SINGLE_LIGHT_ID, CONF_TO, CONF_OUTPUT_ID, CONF_REVERSED, @@ -12,30 +15,47 @@ from esphome.const import ( partitions_ns = cg.esphome_ns.namespace("partition") AddressableSegment = partitions_ns.class_("AddressableSegment") +AddressableLightWrapper = cg.esphome_ns.namespace("light").class_( + "AddressableLightWrapper" +) PartitionLightOutput = partitions_ns.class_( "PartitionLightOutput", light.AddressableLight ) def validate_from_to(value): - if value[CONF_FROM] > value[CONF_TO]: + if CONF_ID in value and value[CONF_FROM] > value[CONF_TO]: raise cv.Invalid( f"From ({value[CONF_FROM]}) must not be larger than to ({value[CONF_TO]})" ) return value +ADDRESSABLE_SEGMENT_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(light.AddressableLightState), + cv.Required(CONF_FROM): cv.positive_int, + cv.Required(CONF_TO): cv.positive_int, + cv.Optional(CONF_REVERSED, default=False): cv.boolean, + } +) + +NONADDRESSABLE_SEGMENT_SCHEMA = cv.COMPONENT_SCHEMA.extend( + { + cv.Required(CONF_SINGLE_LIGHT_ID): cv.use_id(light.LightState), + cv.GenerateID(CONF_ADDRESSABLE_LIGHT_ID): cv.declare_id( + AddressableLightWrapper + ), + cv.GenerateID(CONF_LIGHT_ID): cv.declare_id(light.types.LightState), + } +) + CONFIG_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend( { cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(PartitionLightOutput), cv.Required(CONF_SEGMENTS): cv.All( cv.ensure_list( - { - cv.Required(CONF_ID): cv.use_id(light.AddressableLightState), - cv.Required(CONF_FROM): cv.positive_int, - cv.Required(CONF_TO): cv.positive_int, - cv.Optional(CONF_REVERSED, default=False): cv.boolean, - }, + cv.Any(ADDRESSABLE_SEGMENT_SCHEMA, NONADDRESSABLE_SEGMENT_SCHEMA), validate_from_to, ), cv.Length(min=1), @@ -47,15 +67,25 @@ CONFIG_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend( async def to_code(config): segments = [] for conf in config[CONF_SEGMENTS]: - var = await cg.get_variable(conf[CONF_ID]) - segments.append( - AddressableSegment( - var, - conf[CONF_FROM], - conf[CONF_TO] - conf[CONF_FROM] + 1, - conf[CONF_REVERSED], + if CONF_SINGLE_LIGHT_ID in conf: + wrapper = cg.new_Pvariable( + conf[CONF_ADDRESSABLE_LIGHT_ID], + await cg.get_variable(conf[CONF_SINGLE_LIGHT_ID]), + ) + light_state = cg.new_Pvariable(conf[CONF_LIGHT_ID], "", wrapper) + await cg.register_component(light_state, conf) + cg.add(cg.App.register_light(light_state)) + segments.append(AddressableSegment(light_state, 0, 1, False)) + + else: + segments.append( + AddressableSegment( + await cg.get_variable(conf[CONF_ID]), + conf[CONF_FROM], + conf[CONF_TO] - conf[CONF_FROM] + 1, + conf[CONF_REVERSED], + ) ) - ) var = cg.new_Pvariable(config[CONF_OUTPUT_ID], segments) await cg.register_component(var, config) diff --git a/esphome/const.py b/esphome/const.py index 6a3296bcea..f032cf0fc3 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -320,6 +320,7 @@ CONF_LEVEL = "level" CONF_LG = "lg" CONF_LIBRARIES = "libraries" CONF_LIGHT = "light" +CONF_LIGHT_ID = "light_id" CONF_LIGHTNING_ENERGY = "lightning_energy" CONF_LIGHTNING_THRESHOLD = "lightning_threshold" CONF_LINE_THICKNESS = "line_thickness" @@ -586,6 +587,7 @@ CONF_SHOW_VALUES = "show_values" CONF_SHUNT_RESISTANCE = "shunt_resistance" CONF_SHUNT_VOLTAGE = "shunt_voltage" CONF_SHUTDOWN_MESSAGE = "shutdown_message" +CONF_SINGLE_LIGHT_ID = "single_light_id" CONF_SIZE = "size" CONF_SLEEP_DURATION = "sleep_duration" CONF_SLEEP_PIN = "sleep_pin" diff --git a/tests/test1.yaml b/tests/test1.yaml index ab089dbc15..c1ddf26488 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1615,6 +1615,7 @@ light: - id: addr2 from: 20 to: 25 + - single_light_id: ${roomname}_lights remote_transmitter: - pin: 32 From 40e0100c1efb10be745523c13b66a17f3f02e18f Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Wed, 22 Sep 2021 14:57:16 +1000 Subject: [PATCH 1404/1841] add = to default font glpyh list (#2361) --- esphome/components/font/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index e47a6f38af..7225bf5bb9 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -72,7 +72,7 @@ def validate_truetype_file(value): DEFAULT_GLYPHS = ( - ' !"%()+,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' + ' !"%()+=,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' ) CONF_RAW_DATA_ID = "raw_data_id" CONF_RAW_GLYPH_ID = "raw_glyph_id" From 11daabc9c25a7f4dda4bf3c5a111ab31e15774cf Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 22 Sep 2021 10:32:39 +0200 Subject: [PATCH 1405/1841] Fix docker pio settings not applied (#2370) --- docker/docker_entrypoint.sh | 16 +++++++++++----- docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh | 4 ++-- docker/hassio-rootfs/etc/services.d/esphome/run | 8 +++++++- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/docker/docker_entrypoint.sh b/docker/docker_entrypoint.sh index b3905d1fed..75d5e0b7b5 100755 --- a/docker/docker_entrypoint.sh +++ b/docker/docker_entrypoint.sh @@ -4,15 +4,21 @@ # otherwise use path in /config (so that PIO packages aren't downloaded on each compile) if [[ -d /cache ]]; then - export PLATFORMIO_CORE_DIR=/cache/platformio + pio_cache_base=/cache/platformio else - export PLATFORMIO_CORE_DIR=/config/.esphome/platformio + pio_cache_base=/config/.esphome/platformio fi -if [[ ! -d "${PLATFORMIO_CORE_DIR}" ]]; then - echo "Creating cache directory ${PLATFORMIO_CORE_DIR}" +if [[ ! -d "${pio_cache_base}" ]]; then + echo "Creating cache directory ${pio_cache_base}" echo "You can change this behavior by mounting a directory to the container's /cache directory." - mkdir -p "${PLATFORMIO_CORE_DIR}" + mkdir -p "${pio_cache_base}" fi +# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json` +# setting `core_dir` would therefore prevent pio from accessing +export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms" +export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages" +export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache" + exec esphome "$@" diff --git a/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh b/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh index 301fe4db63..1073a2fa45 100644 --- a/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh +++ b/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh @@ -4,6 +4,6 @@ # This files creates all directories used by esphome # ============================================================================== -PLATFORMIO_CORE_DIR=/data/cache/platformio +pio_cache_base=/data/cache/platformio -mkdir -p "${PLATFORMIO_CORE_DIR}" +mkdir -p "${pio_cache_base}" diff --git a/docker/hassio-rootfs/etc/services.d/esphome/run b/docker/hassio-rootfs/etc/services.d/esphome/run index 6218b200bd..a0f20d63d6 100755 --- a/docker/hassio-rootfs/etc/services.d/esphome/run +++ b/docker/hassio-rootfs/etc/services.d/esphome/run @@ -22,7 +22,13 @@ if bashio::config.has_value 'relative_url'; then export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url') fi -export PLATFORMIO_CORE_DIR=/data/cache/platformio +pio_cache_base=/data/cache/platformio +# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json` +# setting `core_dir` would therefore prevent pio from accessing +export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms" +export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages" +export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache" + export PLATFORMIO_GLOBALLIB_DIR=/piolibs bashio::log.info "Starting ESPHome dashboard..." From 888e315553dafef858996fa937db51a932fe7e84 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 22 Sep 2021 10:37:46 +0200 Subject: [PATCH 1406/1841] Fix OTA crash during reading of new bin file. (#2366) Co-authored-by: Maurice Makaay --- esphome/components/ota/ota_component.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 5e07bb64e7..0c13efa135 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -388,8 +388,10 @@ void OTAComponent::handle_() { size_t requested = std::min(sizeof(buf), ota_size - total); ssize_t read = this->client_->read(buf, requested); if (read == -1) { - if (errno == EAGAIN || errno == EWOULDBLOCK) + if (errno == EAGAIN || errno == EWOULDBLOCK) { + delay(1); continue; + } ESP_LOGW(TAG, "Error receiving data for update, errno: %d", errno); goto error; } From 13b3412b459f6e2e124e22d1806386db0c063a9a Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Wed, 22 Sep 2021 21:12:42 +1200 Subject: [PATCH 1407/1841] Fix Dallas parent not being set (#2369) --- esphome/components/dallas/sensor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/dallas/sensor.py b/esphome/components/dallas/sensor.py index 05c2c8b90c..14ad0efa7b 100644 --- a/esphome/components/dallas/sensor.py +++ b/esphome/components/dallas/sensor.py @@ -46,5 +46,7 @@ async def to_code(config): if CONF_RESOLUTION in config: cg.add(var.set_resolution(config[CONF_RESOLUTION])) + cg.add(var.set_parent(hub)) + cg.add(hub.register_sensor(var)) await sensor.register_sensor(var, config) From 0929a0f8aa55954bf9269ee2f2dc591605a7da96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Maggioni?= Date: Wed, 22 Sep 2021 11:15:51 +0200 Subject: [PATCH 1408/1841] Discard senseair commands echoes & fix calibration result check (#2358) --- esphome/components/senseair/senseair.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/esphome/components/senseair/senseair.cpp b/esphome/components/senseair/senseair.cpp index 8fbb6f69db..610892dd9e 100644 --- a/esphome/components/senseair/senseair.cpp +++ b/esphome/components/senseair/senseair.cpp @@ -78,9 +78,12 @@ uint16_t SenseAirComponent::senseair_checksum_(uint8_t *ptr, uint8_t length) { } void SenseAirComponent::background_calibration() { + // Responses are just echoes but must be read to clear the buffer ESP_LOGD(TAG, "SenseAir Starting background calibration"); - this->senseair_write_command_(SENSEAIR_COMMAND_CLEAR_ACK_REGISTER, nullptr, 0); - this->senseair_write_command_(SENSEAIR_COMMAND_BACKGROUND_CAL, nullptr, 0); + uint8_t command_length = sizeof(SENSEAIR_COMMAND_CLEAR_ACK_REGISTER) / sizeof(SENSEAIR_COMMAND_CLEAR_ACK_REGISTER[0]); + uint8_t response[command_length]; + this->senseair_write_command_(SENSEAIR_COMMAND_CLEAR_ACK_REGISTER, response, command_length); + this->senseair_write_command_(SENSEAIR_COMMAND_BACKGROUND_CAL, response, command_length); } void SenseAirComponent::background_calibration_result() { @@ -98,18 +101,25 @@ void SenseAirComponent::background_calibration_result() { return; } - ESP_LOGD(TAG, "SenseAir Result=%s (%02x%02x%02x)", response[2] == 2 ? "OK" : "NOT_OK", response[2], response[3], - response[4]); + // Check if 5th bit (register CI6) is set + ESP_LOGD(TAG, "SenseAir Result=%s (%02x%02x%02x %02x%02x %02x%02x)", (response[4] & 0b100000) != 0 ? "OK" : "NOT_OK", + response[0], response[1], response[2], response[3], response[4], response[5], response[6]); } void SenseAirComponent::abc_enable() { + // Response is just an echo but must be read to clear the buffer ESP_LOGD(TAG, "SenseAir Enabling automatic baseline calibration"); - this->senseair_write_command_(SENSEAIR_COMMAND_ABC_ENABLE, nullptr, 0); + uint8_t command_length = sizeof(SENSEAIR_COMMAND_ABC_ENABLE) / sizeof(SENSEAIR_COMMAND_ABC_ENABLE[0]); + uint8_t response[command_length]; + this->senseair_write_command_(SENSEAIR_COMMAND_ABC_ENABLE, response, command_length); } void SenseAirComponent::abc_disable() { + // Response is just an echo but must be read to clear the buffer ESP_LOGD(TAG, "SenseAir Disabling automatic baseline calibration"); - this->senseair_write_command_(SENSEAIR_COMMAND_ABC_DISABLE, nullptr, 0); + uint8_t command_length = sizeof(SENSEAIR_COMMAND_ABC_DISABLE) / sizeof(SENSEAIR_COMMAND_ABC_DISABLE[0]); + uint8_t response[command_length]; + this->senseair_write_command_(SENSEAIR_COMMAND_ABC_DISABLE, response, command_length); } void SenseAirComponent::abc_get_period() { From ed593544d896df1a830cd7320432ddcde9ddcbec Mon Sep 17 00:00:00 2001 From: Silvio <4004968+s1lvi0@users.noreply.github.com> Date: Wed, 22 Sep 2021 12:03:42 +0200 Subject: [PATCH 1409/1841] Add support for Daly Smart BMS (#2156) * Add support for Daly Smart BMS * Fix clang-format and python lint * Fix const declaration * Add code owner * Fix malloc with std::vector * Fix with suggestions * Revert "Fix with suggestions" This reverts commit bc618f20cf83e3df903fdbbca8d2529d946264b0. * Fix last commit * Fix Python Lint * Fix typo * Use std::vector instead pointer and fix loop * Fix typo * Add test configuration to test3.yaml * Fix test3.yaml * Fix uart in test3.yaml --- CODEOWNERS | 1 + esphome/components/daly_bms/__init__.py | 27 +++ esphome/components/daly_bms/binary_sensor.py | 49 +++++ esphome/components/daly_bms/daly_bms.cpp | 181 +++++++++++++++++ esphome/components/daly_bms/daly_bms.h | 83 ++++++++ esphome/components/daly_bms/sensor.py | 192 +++++++++++++++++++ esphome/components/daly_bms/text_sensor.py | 39 ++++ tests/test3.yaml | 44 +++++ 8 files changed, 616 insertions(+) create mode 100644 esphome/components/daly_bms/__init__.py create mode 100644 esphome/components/daly_bms/binary_sensor.py create mode 100644 esphome/components/daly_bms/daly_bms.cpp create mode 100644 esphome/components/daly_bms/daly_bms.h create mode 100644 esphome/components/daly_bms/sensor.py create mode 100644 esphome/components/daly_bms/text_sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index adf96b7382..2577e0d706 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -39,6 +39,7 @@ esphome/components/coolix/* @glmnet esphome/components/cover/* @esphome/core esphome/components/cs5460a/* @balrog-kun esphome/components/ct_clamp/* @jesserockz +esphome/components/daly_bms/* @s1lvi0 esphome/components/debug/* @OttoWinter esphome/components/dfplayer/* @glmnet esphome/components/dht/* @OttoWinter diff --git a/esphome/components/daly_bms/__init__.py b/esphome/components/daly_bms/__init__.py new file mode 100644 index 0000000000..45b8f98f0c --- /dev/null +++ b/esphome/components/daly_bms/__init__.py @@ -0,0 +1,27 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart +from esphome.const import CONF_ID + +CODEOWNERS = ["@s1lvi0"] +DEPENDENCIES = ["uart"] +AUTO_LOAD = ["sensor", "text_sensor", "binary_sensor"] + +CONF_BMS_DALY_ID = "bms_daly_id" + +daly_bms = cg.esphome_ns.namespace("daly_bms") +DalyBmsComponent = daly_bms.class_( + "DalyBmsComponent", cg.PollingComponent, uart.UARTDevice +) + +CONFIG_SCHEMA = ( + cv.Schema({cv.GenerateID(): cv.declare_id(DalyBmsComponent)}) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.polling_component_schema("30s")) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) diff --git a/esphome/components/daly_bms/binary_sensor.py b/esphome/components/daly_bms/binary_sensor.py new file mode 100644 index 0000000000..23330cd945 --- /dev/null +++ b/esphome/components/daly_bms/binary_sensor.py @@ -0,0 +1,49 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import CONF_ID +from . import DalyBmsComponent, CONF_BMS_DALY_ID + +CONF_CHARGING_MOS_ENABLED = "charging_mos_enabled" +CONF_DISCHARGING_MOS_ENABLED = "discharging_mos_enabled" + +TYPES = [ + CONF_CHARGING_MOS_ENABLED, + CONF_DISCHARGING_MOS_ENABLED, +] + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent), + cv.Optional( + CONF_CHARGING_MOS_ENABLED + ): binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor), + } + ), + cv.Optional( + CONF_DISCHARGING_MOS_ENABLED + ): binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor), + } + ), + } + ).extend(cv.COMPONENT_SCHEMA) +) + + +async def setup_conf(config, key, hub): + if key in config: + conf = config[key] + sens = cg.new_Pvariable(conf[CONF_ID]) + await binary_sensor.register_binary_sensor(sens, conf) + cg.add(getattr(hub, f"set_{key}_binary_sensor")(sens)) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_BMS_DALY_ID]) + for key in TYPES: + await setup_conf(config, key, hub) diff --git a/esphome/components/daly_bms/daly_bms.cpp b/esphome/components/daly_bms/daly_bms.cpp new file mode 100644 index 0000000000..19e8f12e1c --- /dev/null +++ b/esphome/components/daly_bms/daly_bms.cpp @@ -0,0 +1,181 @@ +#include "daly_bms.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace daly_bms { + +static const char *const TAG = "daly_bms"; + +static const uint8_t DALY_FRAME_SIZE = 13; +static const uint8_t DALY_TEMPERATURE_OFFSET = 40; +static const uint16_t DALY_CURRENT_OFFSET = 30000; + +static const uint8_t DALY_REQUEST_BATTERY_LEVEL = 0x90; +static const uint8_t DALY_REQUEST_MIN_MAX_VOLTAGE = 0x91; +static const uint8_t DALY_REQUEST_MIN_MAX_TEMPERATURE = 0x92; +static const uint8_t DALY_REQUEST_MOS = 0x93; +static const uint8_t DALY_REQUEST_STATUS = 0x94; +static const uint8_t DALY_REQUEST_TEMPERATURE = 0x96; + +void DalyBmsComponent::setup() {} + +void DalyBmsComponent::dump_config() { + ESP_LOGCONFIG(TAG, "Daly BMS:"); + this->check_uart_settings(9600); +} + +void DalyBmsComponent::update() { + this->request_data(DALY_REQUEST_BATTERY_LEVEL); + this->request_data(DALY_REQUEST_MIN_MAX_VOLTAGE); + this->request_data(DALY_REQUEST_MIN_MAX_TEMPERATURE); + this->request_data(DALY_REQUEST_MOS); + this->request_data(DALY_REQUEST_STATUS); + this->request_data(DALY_REQUEST_TEMPERATURE); + + std::vector get_battery_level_data; + int available_data = this->available(); + if (available_data >= DALY_FRAME_SIZE) { + get_battery_level_data.resize(available_data); + this->read_array(get_battery_level_data.data(), available_data); + this->decode_data(get_battery_level_data); + } +} + +float DalyBmsComponent::get_setup_priority() const { return setup_priority::DATA; } + +void DalyBmsComponent::request_data(uint8_t data_id) { + uint8_t request_message[DALY_FRAME_SIZE]; + + request_message[0] = 0xA5; // Start Flag + request_message[1] = 0x80; // Communication Module Address + request_message[2] = data_id; // Data ID + request_message[3] = 0x08; // Data Length (Fixed) + request_message[4] = 0x00; // Empty Data + request_message[5] = 0x00; // | + request_message[6] = 0x00; // | + request_message[7] = 0x00; // | + request_message[8] = 0x00; // | + request_message[9] = 0x00; // | + request_message[10] = 0x00; // | + request_message[11] = 0x00; // Empty Data + request_message[12] = (uint8_t)(request_message[0] + request_message[1] + request_message[2] + + request_message[3]); // Checksum (Lower byte of the other bytes sum) + + this->write_array(request_message, sizeof(request_message)); + this->flush(); +} + +void DalyBmsComponent::decode_data(std::vector data) { + auto it = data.begin(); + + while ((it = std::find(it, data.end(), 0xA5)) != data.end()) { + if (data.end() - it >= DALY_FRAME_SIZE && it[1] == 0x01) { + uint8_t checksum; + int sum = 0; + for (int i = 0; i < 12; i++) { + sum += it[i]; + } + checksum = sum; + + if (checksum == it[12]) { + switch (it[2]) { + case DALY_REQUEST_BATTERY_LEVEL: + if (this->voltage_sensor_) { + this->voltage_sensor_->publish_state((float) encode_uint16(it[4], it[5]) / 10); + } + if (this->current_sensor_) { + this->current_sensor_->publish_state(((float) (encode_uint16(it[8], it[9]) - DALY_CURRENT_OFFSET) / 10)); + } + if (this->battery_level_sensor_) { + this->battery_level_sensor_->publish_state((float) encode_uint16(it[10], it[11]) / 10); + } + break; + + case DALY_REQUEST_MIN_MAX_VOLTAGE: + if (this->max_cell_voltage_) { + this->max_cell_voltage_->publish_state((float) encode_uint16(it[4], it[5]) / 1000); + } + if (this->max_cell_voltage_number_) { + this->max_cell_voltage_number_->publish_state(it[6]); + } + if (this->min_cell_voltage_) { + this->min_cell_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); + } + if (this->min_cell_voltage_number_) { + this->min_cell_voltage_number_->publish_state(it[9]); + } + break; + + case DALY_REQUEST_MIN_MAX_TEMPERATURE: + if (this->max_temperature_) { + this->max_temperature_->publish_state(it[4] - DALY_TEMPERATURE_OFFSET); + } + if (this->max_temperature_probe_number_) { + this->max_temperature_probe_number_->publish_state(it[5]); + } + if (this->min_temperature_) { + this->min_temperature_->publish_state(it[6] - DALY_TEMPERATURE_OFFSET); + } + if (this->min_temperature_probe_number_) { + this->min_temperature_probe_number_->publish_state(it[7]); + } + break; + + case DALY_REQUEST_MOS: + if (this->status_text_sensor_ != nullptr) { + switch (it[4]) { + case 0: + this->status_text_sensor_->publish_state("Stationary"); + break; + case 1: + this->status_text_sensor_->publish_state("Charging"); + break; + case 2: + this->status_text_sensor_->publish_state("Discharging"); + break; + default: + break; + } + } + if (this->charging_mos_enabled_) { + this->charging_mos_enabled_->publish_state(it[5]); + } + if (this->discharging_mos_enabled_) { + this->discharging_mos_enabled_->publish_state(it[6]); + } + if (this->remaining_capacity_) { + this->remaining_capacity_->publish_state((float) encode_uint32(it[8], it[9], it[10], it[11]) / 1000); + } + break; + + case DALY_REQUEST_STATUS: + if (this->cells_number_) { + this->cells_number_->publish_state(it[4]); + } + break; + + case DALY_REQUEST_TEMPERATURE: + if (it[4] == 1) { + if (this->temperature_1_sensor_) { + this->temperature_1_sensor_->publish_state(it[5] - DALY_TEMPERATURE_OFFSET); + } + if (this->temperature_2_sensor_) { + this->temperature_2_sensor_->publish_state(it[6] - DALY_TEMPERATURE_OFFSET); + } + } + break; + + default: + break; + } + } + std::advance(it, DALY_FRAME_SIZE); + } else { + std::advance(it, 1); + } + } +} + +} // namespace daly_bms +} // namespace esphome diff --git a/esphome/components/daly_bms/daly_bms.h b/esphome/components/daly_bms/daly_bms.h new file mode 100644 index 0000000000..e4f48776dd --- /dev/null +++ b/esphome/components/daly_bms/daly_bms.h @@ -0,0 +1,83 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace daly_bms { + +class DalyBmsComponent : public PollingComponent, public uart::UARTDevice { + public: + DalyBmsComponent() = default; + + // SENSORS + void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } + void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } + void set_battery_level_sensor(sensor::Sensor *battery_level_sensor) { battery_level_sensor_ = battery_level_sensor; } + void set_max_cell_voltage_sensor(sensor::Sensor *max_cell_voltage) { max_cell_voltage_ = max_cell_voltage; } + void set_max_cell_voltage_number_sensor(sensor::Sensor *max_cell_voltage_number) { + max_cell_voltage_number_ = max_cell_voltage_number; + } + void set_min_cell_voltage_sensor(sensor::Sensor *min_cell_voltage) { min_cell_voltage_ = min_cell_voltage; } + void set_min_cell_voltage_number_sensor(sensor::Sensor *min_cell_voltage_number) { + min_cell_voltage_number_ = min_cell_voltage_number; + } + void set_max_temperature_sensor(sensor::Sensor *max_temperature) { max_temperature_ = max_temperature; } + void set_max_temperature_probe_number_sensor(sensor::Sensor *max_temperature_probe_number) { + max_temperature_probe_number_ = max_temperature_probe_number; + } + void set_min_temperature_sensor(sensor::Sensor *min_temperature) { min_temperature_ = min_temperature; } + void set_min_temperature_probe_number_sensor(sensor::Sensor *min_temperature_probe_number) { + min_temperature_probe_number_ = min_temperature_probe_number; + } + void set_remaining_capacity_sensor(sensor::Sensor *remaining_capacity) { remaining_capacity_ = remaining_capacity; } + void set_cells_number_sensor(sensor::Sensor *cells_number) { cells_number_ = cells_number; } + void set_temperature_1_sensor(sensor::Sensor *temperature_1_sensor) { temperature_1_sensor_ = temperature_1_sensor; } + void set_temperature_2_sensor(sensor::Sensor *temperature_2_sensor) { temperature_2_sensor_ = temperature_2_sensor; } + // TEXT_SENSORS + void set_status_text_sensor(text_sensor::TextSensor *status_text_sensor) { status_text_sensor_ = status_text_sensor; } + // BINARY_SENSORS + void set_charging_mos_enabled_binary_sensor(binary_sensor::BinarySensor *charging_mos_enabled) { + charging_mos_enabled_ = charging_mos_enabled; + } + void set_discharging_mos_enabled_binary_sensor(binary_sensor::BinarySensor *discharging_mos_enabled) { + discharging_mos_enabled_ = discharging_mos_enabled; + } + + void setup() override; + void dump_config() override; + void update() override; + + float get_setup_priority() const override; + + protected: + void request_data(uint8_t data_id); + void decode_data(std::vector data); + + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *battery_level_sensor_{nullptr}; + sensor::Sensor *max_cell_voltage_{nullptr}; + sensor::Sensor *max_cell_voltage_number_{nullptr}; + sensor::Sensor *min_cell_voltage_{nullptr}; + sensor::Sensor *min_cell_voltage_number_{nullptr}; + sensor::Sensor *max_temperature_{nullptr}; + sensor::Sensor *max_temperature_probe_number_{nullptr}; + sensor::Sensor *min_temperature_{nullptr}; + sensor::Sensor *min_temperature_probe_number_{nullptr}; + sensor::Sensor *remaining_capacity_{nullptr}; + sensor::Sensor *cells_number_{nullptr}; + sensor::Sensor *temperature_1_sensor_{nullptr}; + sensor::Sensor *temperature_2_sensor_{nullptr}; + + text_sensor::TextSensor *status_text_sensor_{nullptr}; + + binary_sensor::BinarySensor *charging_mos_enabled_{nullptr}; + binary_sensor::BinarySensor *discharging_mos_enabled_{nullptr}; +}; + +} // namespace daly_bms +} // namespace esphome diff --git a/esphome/components/daly_bms/sensor.py b/esphome/components/daly_bms/sensor.py new file mode 100644 index 0000000000..1d0ee89914 --- /dev/null +++ b/esphome/components/daly_bms/sensor.py @@ -0,0 +1,192 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_VOLTAGE, + CONF_CURRENT, + CONF_BATTERY_LEVEL, + CONF_MAX_TEMPERATURE, + CONF_MIN_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_NONE, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_PERCENT, + UNIT_CELSIUS, + UNIT_EMPTY, + ICON_FLASH, + ICON_PERCENT, + ICON_COUNTER, + ICON_THERMOMETER, + ICON_GAUGE, +) +from . import DalyBmsComponent, CONF_BMS_DALY_ID + +CONF_MAX_CELL_VOLTAGE = "max_cell_voltage" +CONF_MAX_CELL_VOLTAGE_NUMBER = "max_cell_voltage_number" +CONF_MIN_CELL_VOLTAGE = "min_cell_voltage" +CONF_MIN_CELL_VOLTAGE_NUMBER = "min_cell_voltage_number" +CONF_MAX_TEMPERATURE_PROBE_NUMBER = "max_temperature_probe_number" +CONF_MIN_TEMPERATURE_PROBE_NUMBER = "min_temperature_probe_number" +CONF_CELLS_NUMBER = "cells_number" + +CONF_REMAINING_CAPACITY = "remaining_capacity" +CONF_TEMPERATURE_1 = "temperature_1" +CONF_TEMPERATURE_2 = "temperature_2" + +ICON_CURRENT_DC = "mdi:current-dc" +ICON_BATTERY_OUTLINE = "mdi:battery-outline" +ICON_THERMOMETER_CHEVRON_UP = "mdi:thermometer-chevron-up" +ICON_THERMOMETER_CHEVRON_DOWN = "mdi:thermometer-chevron-down" +ICON_CAR_BATTERY = "mdi:car-battery" + +UNIT_AMPERE_HOUR = "Ah" + +TYPES = [ + CONF_VOLTAGE, + CONF_CURRENT, + CONF_BATTERY_LEVEL, + CONF_MAX_CELL_VOLTAGE, + CONF_MAX_CELL_VOLTAGE_NUMBER, + CONF_MIN_CELL_VOLTAGE, + CONF_MIN_CELL_VOLTAGE_NUMBER, + CONF_MAX_TEMPERATURE, + CONF_MAX_TEMPERATURE_PROBE_NUMBER, + CONF_MIN_TEMPERATURE, + CONF_MIN_TEMPERATURE_PROBE_NUMBER, + CONF_CELLS_NUMBER, + CONF_REMAINING_CAPACITY, + CONF_TEMPERATURE_1, + CONF_TEMPERATURE_2, +] + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, + ICON_FLASH, + 1, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + UNIT_AMPERE, + ICON_CURRENT_DC, + 1, + DEVICE_CLASS_CURRENT, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + UNIT_PERCENT, + ICON_PERCENT, + 1, + DEVICE_CLASS_BATTERY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_MAX_CELL_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, + ICON_FLASH, + 2, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_MAX_CELL_VOLTAGE_NUMBER): sensor.sensor_schema( + UNIT_EMPTY, + ICON_COUNTER, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_MIN_CELL_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, + ICON_FLASH, + 2, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_MIN_CELL_VOLTAGE_NUMBER): sensor.sensor_schema( + UNIT_EMPTY, + ICON_COUNTER, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_MAX_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, + ICON_THERMOMETER_CHEVRON_UP, + 0, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_MAX_TEMPERATURE_PROBE_NUMBER): sensor.sensor_schema( + UNIT_EMPTY, + ICON_COUNTER, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_MIN_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, + ICON_THERMOMETER_CHEVRON_DOWN, + 0, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_MIN_TEMPERATURE_PROBE_NUMBER): sensor.sensor_schema( + UNIT_EMPTY, + ICON_COUNTER, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_REMAINING_CAPACITY): sensor.sensor_schema( + UNIT_AMPERE_HOUR, + ICON_GAUGE, + 2, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELLS_NUMBER): sensor.sensor_schema( + UNIT_EMPTY, + ICON_COUNTER, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_TEMPERATURE_1): sensor.sensor_schema( + UNIT_CELSIUS, + ICON_THERMOMETER, + 0, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE_2): sensor.sensor_schema( + UNIT_CELSIUS, + ICON_THERMOMETER, + 0, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + ), + } + ).extend(cv.COMPONENT_SCHEMA) +) + + +async def setup_conf(config, key, hub): + if key in config: + conf = config[key] + sens = await sensor.new_sensor(conf) + cg.add(getattr(hub, f"set_{key}_sensor")(sens)) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_BMS_DALY_ID]) + for key in TYPES: + await setup_conf(config, key, hub) diff --git a/esphome/components/daly_bms/text_sensor.py b/esphome/components/daly_bms/text_sensor.py new file mode 100644 index 0000000000..de49a0b4b9 --- /dev/null +++ b/esphome/components/daly_bms/text_sensor.py @@ -0,0 +1,39 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import CONF_ICON, CONF_ID, CONF_STATUS +from . import DalyBmsComponent, CONF_BMS_DALY_ID + +ICON_CAR_BATTERY = "mdi:car-battery" + +TYPES = [ + CONF_STATUS, +] + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent), + cv.Optional(CONF_STATUS): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + cv.Optional(CONF_ICON, default=ICON_CAR_BATTERY): cv.icon, + } + ), + } + ).extend(cv.COMPONENT_SCHEMA) +) + + +async def setup_conf(config, key, hub): + if key in config: + conf = config[key] + sens = cg.new_Pvariable(conf[CONF_ID]) + await text_sensor.register_text_sensor(sens, conf) + cg.add(getattr(hub, f"set_{key}_text_sensor")(sens)) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_BMS_DALY_ID]) + for key in TYPES: + await setup_conf(config, key, hub) diff --git a/tests/test3.yaml b/tests/test3.yaml index de46cec99c..386775749d 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -273,6 +273,37 @@ adalight: sensor: + - platform: daly_bms + voltage: + name: "Battery Voltage" + current: + name: "Battery Current" + battery_level: + name: "Battery Level" + max_cell_voltage: + name: "Max Cell Voltage" + max_cell_voltage_number: + name: "Max Cell Voltage Number" + min_cell_voltage: + name: "Min Cell Voltage" + min_cell_voltage_number: + name: "Min Cell Voltage Number" + max_temperature: + name: "Max Temperature" + max_temperature_probe_number: + name: "Max Temperature Probe Number" + min_temperature: + name: "Min Temperature" + min_temperature_probe_number: + name: "Min Temperature Probe Number" + remaining_capacity: + name: "Remaining Capacity" + cells_number: + name: "Cells Number" + temperature_1: + name: "Temperature 1" + temperature_2: + name: "Temperature 2" - platform: apds9960 type: proximity name: APDS9960 Proximity @@ -621,6 +652,11 @@ mpr121: address: 0x5A binary_sensor: + - platform: daly_bms + charging_mos_enabled: + name: "Charging MOS" + discharging_mos_enabled: + name: "Discharging MOS" - platform: apds9960 direction: up name: APDS9960 Up @@ -701,6 +737,9 @@ status_led: pin: GPIO2 text_sensor: + - platform: daly_bms + status: + name: "BMS Status" - platform: version name: 'ESPHome Version' icon: mdi:icon @@ -1219,3 +1258,8 @@ fingerprint_grow: dsmr: decryption_key: 00112233445566778899aabbccddeeff uart_id: uart6 + +daly_bms: + update_interval: 20s + uart_id: uart1 + From f1364d4af4a72826a79f7427ec17449f5f9061a5 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Wed, 22 Sep 2021 12:12:55 +0200 Subject: [PATCH 1410/1841] Combine code of xiaomi_miscale and xiaomi_miscale2 (#2266) * Combine xiaomi_miscale and xiaomi_miscale2 * check if message contains impedance * auto detect scale version * remove xiaomi_miscale2 * fix lint errors * Apply suggestions from code review Co-authored-by: Oxan van Leeuwen * Apply suggestions from code review on old code * Fix clang-tidy warnings Co-authored-by: Oxan van Leeuwen --- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 4 +- .../esp32_ble_tracker/esp32_ble_tracker.h | 4 +- esphome/components/xiaomi_miscale/sensor.py | 12 ++ .../xiaomi_miscale/xiaomi_miscale.cpp | 85 +++++++++++-- .../xiaomi_miscale/xiaomi_miscale.h | 6 + esphome/components/xiaomi_miscale2/sensor.py | 60 +--------- .../xiaomi_miscale2/xiaomi_miscale2.cpp | 112 ------------------ .../xiaomi_miscale2/xiaomi_miscale2.h | 40 ------- 8 files changed, 98 insertions(+), 225 deletions(-) delete mode 100644 esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp delete mode 100644 esphome/components/xiaomi_miscale2/xiaomi_miscale2.h diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 5568884b9a..3ca250d52d 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -380,8 +380,8 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const { } return false; } -esp_bt_uuid_t ESPBTUUID::get_uuid() { return this->uuid_; } -std::string ESPBTUUID::to_string() { +esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; } +std::string ESPBTUUID::to_string() const { char sbuf[64]; switch (this->uuid_.len) { case ESP_UUID_LEN_16: diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 40955d39cf..fc5498f91e 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -34,9 +34,9 @@ class ESPBTUUID { bool operator==(const ESPBTUUID &uuid) const; bool operator!=(const ESPBTUUID &uuid) const { return !(*this == uuid); } - esp_bt_uuid_t get_uuid(); + esp_bt_uuid_t get_uuid() const; - std::string to_string(); + std::string to_string() const; protected: esp_bt_uuid_t uuid_; diff --git a/esphome/components/xiaomi_miscale/sensor.py b/esphome/components/xiaomi_miscale/sensor.py index 3a112dfa34..517870cc01 100644 --- a/esphome/components/xiaomi_miscale/sensor.py +++ b/esphome/components/xiaomi_miscale/sensor.py @@ -8,6 +8,9 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_KILOGRAM, ICON_SCALE_BATHROOM, + UNIT_OHM, + CONF_IMPEDANCE, + ICON_OMEGA, ) DEPENDENCIES = ["esp32_ble_tracker"] @@ -28,6 +31,12 @@ CONFIG_SCHEMA = ( accuracy_decimals=2, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_IMPEDANCE): sensor.sensor_schema( + unit_of_measurement=UNIT_OHM, + icon=ICON_OMEGA, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), } ) .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) @@ -45,3 +54,6 @@ async def to_code(config): if CONF_WEIGHT in config: sens = await sensor.new_sensor(config[CONF_WEIGHT]) cg.add(var.set_weight(sens)) + if CONF_IMPEDANCE in config: + sens = await sensor.new_sensor(config[CONF_IMPEDANCE]) + cg.add(var.set_impedance(sens)) diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp index 62378de72c..4587045136 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp @@ -11,6 +11,7 @@ static const char *const TAG = "xiaomi_miscale"; void XiaomiMiscale::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi Miscale"); LOG_SENSOR(" ", "Weight", this->weight_); + LOG_SENSOR(" ", "Impedance", this->impedance_); } bool XiaomiMiscale::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { @@ -26,14 +27,22 @@ bool XiaomiMiscale::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!res.has_value()) { continue; } + if (!(parse_message(service_data.data, *res))) { continue; } + if (!(report_results(res, device.address_str()))) { continue; } + if (res->weight.has_value() && this->weight_ != nullptr) this->weight_->publish_state(*res->weight); + + if (res->version == 1 && this->impedance_ != nullptr) { + ESP_LOGW(TAG, "Impedance is only supported on version 2. Your scale was identified as verison 1."); + } else if (res->impedance.has_value() && this->impedance_ != nullptr) + this->impedance_->publish_state(*res->impedance); success = true; } @@ -42,8 +51,14 @@ bool XiaomiMiscale::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { optional XiaomiMiscale::parse_header(const esp32_ble_tracker::ServiceData &service_data) { ParseResult result; - if (!service_data.uuid.contains(0x1D, 0x18)) { - ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes."); + if (service_data.uuid == esp32_ble_tracker::ESPBTUUID::from_uint16(0x181D) && service_data.data.size() == 10) { + result.version = 1; + } else if (service_data.uuid == esp32_ble_tracker::ESPBTUUID::from_uint16(0x181B) && service_data.data.size() == 13) { + result.version = 2; + } else { + ESP_LOGVV(TAG, + "parse_header(): Couldn't identify scale version or data size was not correct. UUID: %s, data_size: %d", + service_data.uuid.to_string().c_str(), service_data.data.size()); return {}; } @@ -51,7 +66,15 @@ optional XiaomiMiscale::parse_header(const esp32_ble_tracker::Servi } bool XiaomiMiscale::parse_message(const std::vector &message, ParseResult &result) { - // example 1d18 a2 6036 e307 07 11 0f1f11 + if (result.version == 1) { + return parse_message_V1(message, result); + } else { + return parse_message_V2(message, result); + } +} + +bool XiaomiMiscale::parse_message_V1(const std::vector &message, ParseResult &result) { + // message size is checked in parse_header // 1-2 Weight (MISCALE 181D) // 3-4 Years (MISCALE 181D) // 5 month (MISCALE 181D) @@ -61,21 +84,56 @@ bool XiaomiMiscale::parse_message(const std::vector &message, ParseResu // 9 second (MISCALE 181D) const uint8_t *data = message.data(); - const int data_length = 10; - - if (message.size() != data_length) { - ESP_LOGVV(TAG, "parse_message(): payload has wrong size (%d)!", message.size()); - return false; - } // weight, 2 bytes, 16-bit unsigned integer, 1 kg const int16_t weight = uint16_t(data[1]) | (uint16_t(data[2]) << 8); if (data[0] == 0x22 || data[0] == 0xa2) result.weight = weight * 0.01f / 2.0f; // unit 'kg' else if (data[0] == 0x12 || data[0] == 0xb2) - result.weight = weight * 0.01f * 0.6; // unit 'jin' + result.weight = weight * 0.01f * 0.6f; // unit 'jin' else if (data[0] == 0x03 || data[0] == 0xb3) - result.weight = weight * 0.01f * 0.453592; // unit 'lbs' + result.weight = weight * 0.01f * 0.453592f; // unit 'lbs' + + return true; +} + +bool XiaomiMiscale::parse_message_V2(const std::vector &message, ParseResult &result) { + // message size is checked in parse_header + // 2-3 Years (MISCALE 2 181B) + // 4 month (MISCALE 2 181B) + // 5 day (MISCALE 2 181B) + // 6 hour (MISCALE 2 181B) + // 7 minute (MISCALE 2 181B) + // 8 second (MISCALE 2 181B) + // 9-10 impedance (MISCALE 2 181B) + // 11-12 weight (MISCALE 2 181B) + + const uint8_t *data = message.data(); + + bool has_impedance = ((data[1] & (1 << 1)) != 0); + bool is_stabilized = ((data[1] & (1 << 5)) != 0); + bool load_removed = ((data[1] & (1 << 7)) != 0); + + if (!is_stabilized || load_removed) { + return false; + } + + // weight, 2 bytes, 16-bit unsigned integer, 1 kg + const int16_t weight = uint16_t(data[11]) | (uint16_t(data[12]) << 8); + if (data[0] == 0x02) + result.weight = weight * 0.01f / 2.0f; // unit 'kg' + else if (data[0] == 0x03) + result.weight = weight * 0.01f * 0.453592f; // unit 'lbs' + + if (has_impedance) { + // impedance, 2 bytes, 16-bit + const int16_t impedance = uint16_t(data[9]) | (uint16_t(data[10]) << 8); + result.impedance = impedance; + + if (impedance == 0 || impedance >= 3000) { + return false; + } + } return true; } @@ -86,11 +144,14 @@ bool XiaomiMiscale::report_results(const optional &result, const st return false; } - ESP_LOGD(TAG, "Got Xiaomi Miscale (%s):", address.c_str()); + ESP_LOGD(TAG, "Got Xiaomi Miscale v%d (%s):", result->version, address.c_str()); if (result->weight.has_value()) { ESP_LOGD(TAG, " Weight: %.2fkg", *result->weight); } + if (result->impedance.has_value()) { + ESP_LOGD(TAG, " Impedance: %.0fohm", *result->impedance); + } return true; } diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.h b/esphome/components/xiaomi_miscale/xiaomi_miscale.h index 409fdeaf93..3c958afc03 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.h +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.h @@ -10,7 +10,9 @@ namespace esphome { namespace xiaomi_miscale { struct ParseResult { + int version; optional weight; + optional impedance; }; class XiaomiMiscale : public Component, public esp32_ble_tracker::ESPBTDeviceListener { @@ -21,13 +23,17 @@ class XiaomiMiscale : public Component, public esp32_ble_tracker::ESPBTDeviceLis void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } void set_weight(sensor::Sensor *weight) { weight_ = weight; } + void set_impedance(sensor::Sensor *impedance) { impedance_ = impedance; } protected: uint64_t address_; sensor::Sensor *weight_{nullptr}; + sensor::Sensor *impedance_{nullptr}; optional parse_header(const esp32_ble_tracker::ServiceData &service_data); bool parse_message(const std::vector &message, ParseResult &result); + bool parse_message_V1(const std::vector &message, ParseResult &result); + bool parse_message_V2(const std::vector &message, ParseResult &result); bool report_results(const optional &result, const std::string &address); }; diff --git a/esphome/components/xiaomi_miscale2/sensor.py b/esphome/components/xiaomi_miscale2/sensor.py index 7cc5984c62..de04e8171e 100644 --- a/esphome/components/xiaomi_miscale2/sensor.py +++ b/esphome/components/xiaomi_miscale2/sensor.py @@ -1,59 +1,5 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import sensor, esp32_ble_tracker -from esphome.const import ( - CONF_MAC_ADDRESS, - CONF_ID, - CONF_WEIGHT, - STATE_CLASS_MEASUREMENT, - UNIT_KILOGRAM, - ICON_SCALE_BATHROOM, - UNIT_OHM, - CONF_IMPEDANCE, - ICON_OMEGA, + +CONFIG_SCHEMA = cv.invalid( + "This platform has been combined into xiaomi_miscale. Use xiaomi_miscale instead." ) - -DEPENDENCIES = ["esp32_ble_tracker"] - -xiaomi_miscale2_ns = cg.esphome_ns.namespace("xiaomi_miscale2") -XiaomiMiscale2 = xiaomi_miscale2_ns.class_( - "XiaomiMiscale2", esp32_ble_tracker.ESPBTDeviceListener, cg.Component -) - -CONFIG_SCHEMA = ( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(XiaomiMiscale2), - cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_WEIGHT): sensor.sensor_schema( - unit_of_measurement=UNIT_KILOGRAM, - icon=ICON_SCALE_BATHROOM, - accuracy_decimals=2, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_IMPEDANCE): sensor.sensor_schema( - unit_of_measurement=UNIT_OHM, - icon=ICON_OMEGA, - accuracy_decimals=0, - state_class=STATE_CLASS_MEASUREMENT, - ), - } - ) - .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) - .extend(cv.COMPONENT_SCHEMA) -) - - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await esp32_ble_tracker.register_ble_device(var, config) - - cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) - - if CONF_WEIGHT in config: - sens = await sensor.new_sensor(config[CONF_WEIGHT]) - cg.add(var.set_weight(sens)) - if CONF_IMPEDANCE in config: - sens = await sensor.new_sensor(config[CONF_IMPEDANCE]) - cg.add(var.set_impedance(sens)) diff --git a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp deleted file mode 100644 index 9ae95c5f97..0000000000 --- a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include "xiaomi_miscale2.h" -#include "esphome/core/log.h" - -#ifdef USE_ESP32 - -namespace esphome { -namespace xiaomi_miscale2 { - -static const char *const TAG = "xiaomi_miscale2"; - -void XiaomiMiscale2::dump_config() { - ESP_LOGCONFIG(TAG, "Xiaomi Miscale2"); - LOG_SENSOR(" ", "Weight", this->weight_); - LOG_SENSOR(" ", "Impedance", this->impedance_); -} - -bool XiaomiMiscale2::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { - if (device.address_uint64() != this->address_) { - ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); - return false; - } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); - - bool success = false; - for (auto &service_data : device.get_service_datas()) { - auto res = parse_header(service_data); - if (!res.has_value()) { - continue; - } - if (!(parse_message(service_data.data, *res))) { - continue; - } - if (!(report_results(res, device.address_str()))) { - continue; - } - if (res->weight.has_value() && this->weight_ != nullptr) - this->weight_->publish_state(*res->weight); - if (res->impedance.has_value() && this->impedance_ != nullptr) - this->impedance_->publish_state(*res->impedance); - success = true; - } - - return success; -} - -optional XiaomiMiscale2::parse_header(const esp32_ble_tracker::ServiceData &service_data) { - ParseResult result; - if (!service_data.uuid.contains(0x1B, 0x18)) { - ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes."); - return {}; - } - - return result; -} - -bool XiaomiMiscale2::parse_message(const std::vector &message, ParseResult &result) { - // 2-3 Years (MISCALE 2 181B) - // 4 month (MISCALE 2 181B) - // 5 day (MISCALE 2 181B) - // 6 hour (MISCALE 2 181B) - // 7 minute (MISCALE 2 181B) - // 8 second (MISCALE 2 181B) - // 9-10 impedance (MISCALE 2 181B) - // 11-12 weight (MISCALE 2 181B) - - const uint8_t *data = message.data(); - const int data_length = 13; - - if (message.size() != data_length) { - ESP_LOGVV(TAG, "parse_message(): payload has wrong size (%d)!", message.size()); - return false; - } - - bool is_stabilized = ((data[1] & (1 << 5)) != 0); - bool load_removed = ((data[1] & (1 << 7)) != 0); - - // weight, 2 bytes, 16-bit unsigned integer, 1 kg - const int16_t weight = uint16_t(data[11]) | (uint16_t(data[12]) << 8); - if (data[0] == 0x02) - result.weight = weight * 0.01f / 2.0f; // unit 'kg' - else if (data[0] == 0x03) - result.weight = weight * 0.01f * 0.453592; // unit 'lbs' - - // impedance, 2 bytes, 16-bit - const int16_t impedance = uint16_t(data[9]) | (uint16_t(data[10]) << 8); - result.impedance = impedance; - - return is_stabilized && !load_removed && impedance != 0 && impedance < 3000; -} - -bool XiaomiMiscale2::report_results(const optional &result, const std::string &address) { - if (!result.has_value()) { - ESP_LOGVV(TAG, "report_results(): no results available."); - return false; - } - - ESP_LOGD(TAG, "Got Xiaomi Miscale2 (%s):", address.c_str()); - - if (result->weight.has_value()) { - ESP_LOGD(TAG, " Weight: %.2fkg", *result->weight); - } - if (result->impedance.has_value()) { - ESP_LOGD(TAG, " Impedance: %.0fohm", *result->impedance); - } - - return true; -} - -} // namespace xiaomi_miscale2 -} // namespace esphome - -#endif diff --git a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.h b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.h deleted file mode 100644 index 9f7ebf6a1f..0000000000 --- a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" - -#ifdef USE_ESP32 - -namespace esphome { -namespace xiaomi_miscale2 { - -struct ParseResult { - optional weight; - optional impedance; -}; - -class XiaomiMiscale2 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { - public: - void set_address(uint64_t address) { address_ = address; }; - - bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; - void dump_config() override; - float get_setup_priority() const override { return setup_priority::DATA; } - void set_weight(sensor::Sensor *weight) { weight_ = weight; } - void set_impedance(sensor::Sensor *impedance) { impedance_ = impedance; } - - protected: - uint64_t address_; - sensor::Sensor *weight_{nullptr}; - sensor::Sensor *impedance_{nullptr}; - - optional parse_header(const esp32_ble_tracker::ServiceData &service_data); - bool parse_message(const std::vector &message, ParseResult &result); - bool report_results(const optional &result, const std::string &address); -}; - -} // namespace xiaomi_miscale2 -} // namespace esphome - -#endif From 9fe7b0887416dd274f4acffa5b8fdfef720e01c9 Mon Sep 17 00:00:00 2001 From: Tommy van der Vorst Date: Wed, 22 Sep 2021 12:39:41 +0200 Subject: [PATCH 1411/1841] Add support for Waveshare 7.5 inch (C) bichromatic display (black-and-white only for now) (#1844) * Add support for Waveshare 7.5 inch (B) bichromatic display (black-and-white only for now) * Use drawing commands specific to bichromatic displays * Fix inaccurate comment * Fix merge error * Formatting Co-authored-by: Oxan van Leeuwen --- .../components/waveshare_epaper/display.py | 4 + .../waveshare_epaper/waveshare_epaper.cpp | 96 +++++++++++++++++++ .../waveshare_epaper/waveshare_epaper.h | 23 +++++ 3 files changed, 123 insertions(+) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index e825456c36..64f5597a65 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -40,6 +40,9 @@ WaveshareEPaper5P8In = waveshare_epaper_ns.class_( WaveshareEPaper7P5In = waveshare_epaper_ns.class_( "WaveshareEPaper7P5In", WaveshareEPaper ) +WaveshareEPaper7P5InBC = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5InBC", WaveshareEPaper +) WaveshareEPaper7P5InV2 = waveshare_epaper_ns.class_( "WaveshareEPaper7P5InV2", WaveshareEPaper ) @@ -66,6 +69,7 @@ MODELS = { "4.20in-bv2": ("b", WaveshareEPaper4P2InBV2), "5.83in": ("b", WaveshareEPaper5P8In), "7.50in": ("b", WaveshareEPaper7P5In), + "7.50in-bc": ("b", WaveshareEPaper7P5InBC), "7.50inv2": ("b", WaveshareEPaper7P5InV2), "2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE), } diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index c32e7d27a0..92fa289cfa 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -1081,6 +1081,102 @@ void WaveshareEPaper7P5InV2::dump_config() { LOG_UPDATE_INTERVAL(this); } +/* 7.50in-bc */ +void WaveshareEPaper7P5InBC::initialize() { + /* The command sequence is similar to the 7P5In display but differs in subtle ways + to allow for faster updates. */ + // COMMAND POWER SETTING + this->command(0x01); + this->data(0x37); + this->data(0x00); + + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0xCF); + this->data(0x08); + + // COMMAND PLL CONTROL + this->command(0x30); + this->data(0x3A); + + // COMMAND VCM_DC_SETTING: all temperature range + this->command(0x82); + this->data(0x28); + + // COMMAND BOOSTER SOFT START + this->command(0x06); + this->data(0xC7); + this->data(0xCC); + this->data(0x15); + + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x77); + + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + + // COMMAND FLASH CONTROL + this->command(0x65); + this->data(0x00); + + // COMMAND RESOLUTION SETTING + this->command(0x61); + this->data(0x02); // 640 >> 8 + this->data(0x80); + this->data(0x01); // 384 >> 8 + this->data(0x80); + + // COMMAND FLASH MODE + this->command(0xE5); + this->data(0x03); +} + +void HOT WaveshareEPaper7P5InBC::display() { + // COMMAND DATA START TRANSMISSION 1 + this->command(0x10); + this->start_data_(); + + for (size_t i = 0; i < this->get_buffer_length_(); i++) { + // A line of eight source pixels (each a bit in this byte) + uint8_t eight_pixels = this->buffer_[i]; + + for (uint8_t j = 0; j < 8; j += 2) { + /* For bichromatic displays, each byte represents two pixels. Each nibble encodes a pixel: 0=white, 3=black, + 4=color. Therefore, e.g. 0x44 = two adjacent color pixels, 0x33 is two adjacent black pixels, etc. If you want + to draw using the color pixels, change '0x30' with '0x40' and '0x03' with '0x04' below. */ + uint8_t left_nibble = (eight_pixels & 0x80) ? 0x30 : 0x00; + eight_pixels <<= 1; + uint8_t right_nibble = (eight_pixels & 0x80) ? 0x03 : 0x00; + eight_pixels <<= 1; + this->write_byte(left_nibble | right_nibble); + } + App.feed_wdt(); + } + this->end_data_(); + + // Unlike the 7P5In display, we send the "power on" command here rather than during initialization + // COMMAND POWER ON + this->command(0x04); + + // COMMAND DISPLAY REFRESH + this->command(0x12); +} + +int WaveshareEPaper7P5InBC::get_width_internal() { return 640; } + +int WaveshareEPaper7P5InBC::get_height_internal() { return 384; } + +void WaveshareEPaper7P5InBC::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.5in-bc"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + static const uint8_t LUT_SIZE_TTGO_DKE_PART = 153; static const uint8_t PART_UPDATE_LUT_TTGO_DKE[LUT_SIZE_TTGO_DKE_PART] = { diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index f7603c5af0..b50596643d 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -278,6 +278,29 @@ class WaveshareEPaper7P5In : public WaveshareEPaper { int get_height_internal() override; }; +class WaveshareEPaper7P5InBC : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + class WaveshareEPaper7P5InV2 : public WaveshareEPaper { public: void initialize() override; From 8e36e1b92e95a478888a21507def2eaee4bbfc9c Mon Sep 17 00:00:00 2001 From: Stanislav Meduna Date: Wed, 22 Sep 2021 12:43:17 +0200 Subject: [PATCH 1412/1841] ili9341: use larger SPI transfers (#1628) The original version uses write_byte to tranfer every byte of the display buffer which is quite extensive as every byte needs to be waited for in the SPI driver. This patch prepares transfers in 64-byte chunks. The result is a visible faster redraw of the display. Co-authored-by: Otto winter --- .../components/ili9341/ili9341_display.cpp | 55 +++++++++++++++---- esphome/components/ili9341/ili9341_display.h | 4 ++ 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/esphome/components/ili9341/ili9341_display.cpp b/esphome/components/ili9341/ili9341_display.cpp index b36d05c864..88f8bac272 100644 --- a/esphome/components/ili9341/ili9341_display.cpp +++ b/esphome/components/ili9341/ili9341_display.cpp @@ -93,12 +93,14 @@ void ILI9341Display::display_() { this->start_data_(); uint32_t start_pos = ((this->y_low_ * this->width_) + x_low_); for (uint16_t row = 0; row < h; row++) { - for (uint16_t col = 0; col < w; col++) { - uint32_t pos = start_pos + (row * width_) + col; + uint32_t pos = start_pos + (row * width_); + uint32_t rem = w; - uint16_t color = convert_to_16bit_color_(buffer_[pos]); - this->write_byte(color >> 8); - this->write_byte(color); + while (rem > 0) { + uint32_t sz = buffer_to_transfer_(pos, rem); + this->write_array(transfer_buffer_, 2 * sz); + pos += sz; + rem -= sz; } } this->end_data_(); @@ -140,16 +142,32 @@ void ILI9341Display::fill(Color color) { } void ILI9341Display::fill_internal_(Color color) { + if (color.raw_32 == COLOR_BLACK.raw_32) { + memset(transfer_buffer_, 0, sizeof(transfer_buffer_)); + } else { + uint8_t *dst = transfer_buffer_; + auto color565 = display::ColorUtil::color_to_565(color); + + while (dst < transfer_buffer_ + sizeof(transfer_buffer_)) { + *dst++ = (uint8_t)(color565 >> 8); + *dst++ = (uint8_t) color565; + } + } + + uint32_t rem = this->get_width_internal() * this->get_height_internal(); + this->set_addr_window_(0, 0, this->get_width_internal(), this->get_height_internal()); this->start_data_(); - auto color565 = display::ColorUtil::color_to_565(color); - for (uint32_t i = 0; i < (this->get_width_internal()) * (this->get_height_internal()); i++) { - this->write_byte(color565 >> 8); - this->write_byte(color565); - buffer_[i] = 0; + while (rem > 0) { + size_t sz = rem <= sizeof(transfer_buffer_) ? rem : sizeof(transfer_buffer_); + this->write_array(transfer_buffer_, sz); + rem -= sz; } + this->end_data_(); + + memset(buffer_, 0, (this->get_width_internal()) * (this->get_height_internal())); } void HOT ILI9341Display::draw_absolute_pixel_internal(int x, int y, Color color) { @@ -220,6 +238,23 @@ void ILI9341Display::invert_display_(bool invert) { this->command(invert ? ILI93 int ILI9341Display::get_width_internal() { return this->width_; } int ILI9341Display::get_height_internal() { return this->height_; } +uint32_t ILI9341Display::buffer_to_transfer_(uint32_t pos, uint32_t sz) { + uint8_t *src = buffer_ + pos; + uint8_t *dst = transfer_buffer_; + + if (sz > sizeof(transfer_buffer_) / 2) { + sz = sizeof(transfer_buffer_) / 2; + } + + for (uint32_t i = 0; i < sz; ++i) { + uint16_t color = convert_to_16bit_color_(*src++); + *dst++ = (uint8_t)(color >> 8); + *dst++ = (uint8_t) color; + } + + return sz; +} + // M5Stack display void ILI9341M5Stack::initialize() { this->init_lcd_(INITCMD_M5STACK); diff --git a/esphome/components/ili9341/ili9341_display.h b/esphome/components/ili9341/ili9341_display.h index 2b6ecc6871..d8c90c9d33 100644 --- a/esphome/components/ili9341/ili9341_display.h +++ b/esphome/components/ili9341/ili9341_display.h @@ -71,6 +71,10 @@ class ILI9341Display : public PollingComponent, void start_data_(); void end_data_(); + uint8_t transfer_buffer_[64]; + + uint32_t buffer_to_transfer_(uint32_t pos, uint32_t sz); + GPIOPin *reset_pin_{nullptr}; GPIOPin *led_pin_{nullptr}; GPIOPin *dc_pin_; From 654e31124ea4069545ea2768175552155f2b0d6e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 22 Sep 2021 22:59:03 +1200 Subject: [PATCH 1413/1841] Correctly invert the float output state (#2368) --- esphome/components/output/float_output.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/esphome/components/output/float_output.cpp b/esphome/components/output/float_output.cpp index 5820a2d7cd..f120f86f1f 100644 --- a/esphome/components/output/float_output.cpp +++ b/esphome/components/output/float_output.cpp @@ -31,14 +31,13 @@ void FloatOutput::set_level(float state) { this->power_.unrequest(); } #endif + + if (!(state == 0.0f && this->zero_means_zero_)) // regardless of min_power_, 0.0 means off + state = (state * (this->max_power_ - this->min_power_)) + this->min_power_; + if (this->is_inverted()) state = 1.0f - state; - if (state == 0.0f && this->zero_means_zero_) { // regardless of min_power_, 0.0 means off - this->write_state(state); - return; - } - float adjusted_value = (state * (this->max_power_ - this->min_power_)) + this->min_power_; - this->write_state(adjusted_value); + this->write_state(state); } void FloatOutput::write_state(bool state) { this->set_level(state != this->inverted_ ? 1.0f : 0.0f); } From b20760c93c3f8874e0d73d515e0e88d04a7c87aa Mon Sep 17 00:00:00 2001 From: Stephen Tierney Date: Wed, 22 Sep 2021 21:24:19 +1000 Subject: [PATCH 1414/1841] Add support for LTR390 (#1505) * Add support for ltr390 * Fix linting errors * Fix more linting errors * Linting fixes continued * Linting forever * Another one * Fix regression and linting * Fix narrowing conversion * Add test and bugfix * Add codeowners * Update CODEOWNERS * Update sensor defs * Reformatted with black * Fixed device class import * Update CODEOWNERS * Update CODEOWNERS * Adding all config options As requested https://github.com/esphome/esphome/pull/1505#discussion_r597326897 * Moving test to different config file test1.yml runs out of memory * Update according to comments * Add safety clause to reading modes * Fix clang-tidy complaint * Revert change to i2c component * Fix for changes in dev * Revert "Revert change to i2c component" This reverts commit 2810df59e9c05311df6d32149ed79a393676503b. Co-authored-by: Otto winter Co-authored-by: Oxan van Leeuwen --- CODEOWNERS | 1 + esphome/components/i2c/i2c.h | 6 +- esphome/components/ltr390/__init__.py | 0 esphome/components/ltr390/ltr390.cpp | 166 ++++++++++++++++++++++++++ esphome/components/ltr390/ltr390.h | 93 +++++++++++++++ esphome/components/ltr390/sensor.py | 98 +++++++++++++++ tests/test2.yaml | 14 +++ 7 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 esphome/components/ltr390/__init__.py create mode 100644 esphome/components/ltr390/ltr390.cpp create mode 100644 esphome/components/ltr390/ltr390.h create mode 100644 esphome/components/ltr390/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 2577e0d706..92ee989309 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -74,6 +74,7 @@ esphome/components/json/* @OttoWinter esphome/components/ledc/* @OttoWinter esphome/components/light/* @esphome/core esphome/components/logger/* @esphome/core +esphome/components/ltr390/* @sjtrny esphome/components/max7219digit/* @rspaargaren esphome/components/mcp23008/* @jesserockz esphome/components/mcp23017/* @jesserockz diff --git a/esphome/components/i2c/i2c.h b/esphome/components/i2c/i2c.h index 71ab650e97..7ee4cdd811 100644 --- a/esphome/components/i2c/i2c.h +++ b/esphome/components/i2c/i2c.h @@ -13,8 +13,6 @@ namespace i2c { class I2CDevice; class I2CRegister { public: - I2CRegister(I2CDevice *parent, uint8_t a_register) : parent_(parent), register_(a_register) {} - I2CRegister &operator=(uint8_t value); I2CRegister &operator&=(uint8_t value); I2CRegister &operator|=(uint8_t value); @@ -24,6 +22,10 @@ class I2CRegister { uint8_t get() const; protected: + friend class I2CDevice; + + I2CRegister(I2CDevice *parent, uint8_t a_register) : parent_(parent), register_(a_register) {} + I2CDevice *parent_; uint8_t register_; }; diff --git a/esphome/components/ltr390/__init__.py b/esphome/components/ltr390/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/ltr390/ltr390.cpp b/esphome/components/ltr390/ltr390.cpp new file mode 100644 index 0000000000..36f3835724 --- /dev/null +++ b/esphome/components/ltr390/ltr390.cpp @@ -0,0 +1,166 @@ +#include "ltr390.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace ltr390 { + +static const char *const TAG = "ltr390"; + +static const float GAINVALUES[5] = {1.0, 3.0, 6.0, 9.0, 18.0}; +static const float RESOLUTIONVALUE[6] = {4.0, 2.0, 1.0, 0.5, 0.25, 0.125}; +static const uint32_t MODEADDRESSES[2] = {0x0D, 0x10}; + +uint32_t little_endian_bytes_to_int(const uint8_t *buffer, uint8_t num_bytes) { + uint32_t value = 0; + + for (int i = 0; i < num_bytes; i++) { + value <<= 8; + value |= buffer[num_bytes - i - 1]; + } + + return value; +} + +optional LTR390Component::read_sensor_data_(LTR390MODE mode) { + const uint8_t num_bytes = 3; + uint8_t buffer[num_bytes]; + + // Wait until data available + const uint32_t now = millis(); + while (true) { + std::bitset<8> status = this->reg(LTR390_MAIN_STATUS).get(); + bool available = status[3]; + if (available) + break; + + if (millis() - now > 100) { + ESP_LOGW(TAG, "Sensor didn't return any data, aborting"); + return {}; + } + ESP_LOGD(TAG, "Waiting for data"); + delay(2); + } + + if (!this->read_bytes(MODEADDRESSES[mode], buffer, num_bytes)) { + ESP_LOGW(TAG, "Reading data from sensor failed!"); + return {}; + } + + return little_endian_bytes_to_int(buffer, num_bytes); +} + +void LTR390Component::read_als_() { + auto val = this->read_sensor_data_(LTR390_MODE_ALS); + if (!val.has_value()) + return; + uint32_t als = *val; + + if (this->light_sensor_ != nullptr) { + float lux = (0.6 * als) / (GAINVALUES[this->gain_] * RESOLUTIONVALUE[this->res_]) * this->wfac_; + this->light_sensor_->publish_state(lux); + } + + if (this->als_sensor_ != nullptr) { + this->als_sensor_->publish_state(als); + } +} + +void LTR390Component::read_uvs_() { + auto val = this->read_sensor_data_(LTR390_MODE_UVS); + if (!val.has_value()) + return; + uint32_t uv = *val; + + if (this->uvi_sensor_ != nullptr) { + this->uvi_sensor_->publish_state(uv / LTR390_SENSITIVITY * this->wfac_); + } + + if (this->uv_sensor_ != nullptr) { + this->uv_sensor_->publish_state(uv); + } +} + +void LTR390Component::read_mode_(int mode_index) { + // Set mode + LTR390MODE mode = std::get<0>(this->mode_funcs_[mode_index]); + + std::bitset<8> ctrl = this->reg(LTR390_MAIN_CTRL).get(); + ctrl[LTR390_CTRL_MODE] = mode; + this->reg(LTR390_MAIN_CTRL) = ctrl.to_ulong(); + + // After the sensor integration time do the following + this->set_timeout(((uint32_t) RESOLUTIONVALUE[this->res_]) * 100, [this, mode_index]() { + // Read from the sensor + std::get<1>(this->mode_funcs_[mode_index])(); + + // If there are more modes to read then begin the next + // otherwise stop + if (mode_index + 1 < this->mode_funcs_.size()) { + this->read_mode_(mode_index + 1); + } else { + this->reading_ = false; + } + }); +} + +void LTR390Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up ltr390..."); + + // reset + std::bitset<8> ctrl = this->reg(LTR390_MAIN_CTRL).get(); + ctrl[LTR390_CTRL_RST] = true; + this->reg(LTR390_MAIN_CTRL) = ctrl.to_ulong(); + delay(10); + + // Enable + ctrl = this->reg(LTR390_MAIN_CTRL).get(); + ctrl[LTR390_CTRL_EN] = true; + this->reg(LTR390_MAIN_CTRL) = ctrl.to_ulong(); + + // check enabled + ctrl = this->reg(LTR390_MAIN_CTRL).get(); + bool enabled = ctrl[LTR390_CTRL_EN]; + + if (!enabled) { + ESP_LOGW(TAG, "Sensor didn't respond with enabled state"); + this->mark_failed(); + return; + } + + // Set gain + this->reg(LTR390_GAIN) = gain_; + + // Set resolution + uint8_t res = this->reg(LTR390_MEAS_RATE).get(); + // resolution is in bits 5-7 + res &= ~0b01110000; + res |= res << 4; + this->reg(LTR390_MEAS_RATE) = res; + + // Set sensor read state + this->reading_ = false; + + // If we need the light sensor then add to the list + if (this->light_sensor_ != nullptr || this->als_sensor_ != nullptr) { + this->mode_funcs_.emplace_back(LTR390_MODE_ALS, std::bind(<R390Component::read_als_, this)); + } + + // If we need the UV sensor then add to the list + if (this->uvi_sensor_ != nullptr || this->uv_sensor_ != nullptr) { + this->mode_funcs_.emplace_back(LTR390_MODE_UVS, std::bind(<R390Component::read_uvs_, this)); + } +} + +void LTR390Component::dump_config() { LOG_I2C_DEVICE(this); } + +void LTR390Component::update() { + if (!this->reading_ && !mode_funcs_.empty()) { + this->reading_ = true; + this->read_mode_(0); + } +} + +} // namespace ltr390 +} // namespace esphome diff --git a/esphome/components/ltr390/ltr390.h b/esphome/components/ltr390/ltr390.h new file mode 100644 index 0000000000..d607a3e55f --- /dev/null +++ b/esphome/components/ltr390/ltr390.h @@ -0,0 +1,93 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/optional.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" +#include + +namespace esphome { +namespace ltr390 { + +enum LTR390CTRL { + LTR390_CTRL_EN = 1, + LTR390_CTRL_MODE = 3, + LTR390_CTRL_RST = 4, +}; + +// enums from https://github.com/adafruit/Adafruit_LTR390/ + +static const uint8_t LTR390_MAIN_CTRL = 0x00; +static const uint8_t LTR390_MEAS_RATE = 0x04; +static const uint8_t LTR390_GAIN = 0x05; +static const uint8_t LTR390_PART_ID = 0x06; +static const uint8_t LTR390_MAIN_STATUS = 0x07; +static const float LTR390_SENSITIVITY = 2300.0; + +// Sensing modes +enum LTR390MODE { + LTR390_MODE_ALS, + LTR390_MODE_UVS, +}; + +// Sensor gain levels +enum LTR390GAIN { + LTR390_GAIN_1 = 0, + LTR390_GAIN_3, // Default + LTR390_GAIN_6, + LTR390_GAIN_9, + LTR390_GAIN_18, +}; + +// Sensor resolution +enum LTR390RESOLUTION { + LTR390_RESOLUTION_20BIT, + LTR390_RESOLUTION_19BIT, + LTR390_RESOLUTION_18BIT, // Default + LTR390_RESOLUTION_17BIT, + LTR390_RESOLUTION_16BIT, + LTR390_RESOLUTION_13BIT, +}; + +class LTR390Component : public PollingComponent, public i2c::I2CDevice { + public: + float get_setup_priority() const override { return setup_priority::DATA; } + void setup() override; + void dump_config() override; + void update() override; + + void set_gain_value(LTR390GAIN gain) { this->gain_ = gain; } + void set_res_value(LTR390RESOLUTION res) { this->res_ = res; } + void set_wfac_value(float wfac) { this->wfac_ = wfac; } + + void set_light_sensor(sensor::Sensor *light_sensor) { this->light_sensor_ = light_sensor; } + void set_als_sensor(sensor::Sensor *als_sensor) { this->als_sensor_ = als_sensor; } + void set_uvi_sensor(sensor::Sensor *uvi_sensor) { this->uvi_sensor_ = uvi_sensor; } + void set_uv_sensor(sensor::Sensor *uv_sensor) { this->uv_sensor_ = uv_sensor; } + + protected: + optional read_sensor_data_(LTR390MODE mode); + + void read_als_(); + void read_uvs_(); + + void read_mode_(int mode_index); + + bool reading_; + + // a list of modes and corresponding read functions + std::vector>> mode_funcs_; + + LTR390GAIN gain_; + LTR390RESOLUTION res_; + float wfac_; + + sensor::Sensor *light_sensor_{nullptr}; + sensor::Sensor *als_sensor_{nullptr}; + + sensor::Sensor *uvi_sensor_{nullptr}; + sensor::Sensor *uv_sensor_{nullptr}; +}; + +} // namespace ltr390 +} // namespace esphome diff --git a/esphome/components/ltr390/sensor.py b/esphome/components/ltr390/sensor.py new file mode 100644 index 0000000000..0e70f7bb1b --- /dev/null +++ b/esphome/components/ltr390/sensor.py @@ -0,0 +1,98 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_GAIN, + CONF_LIGHT, + CONF_RESOLUTION, + UNIT_LUX, + ICON_BRIGHTNESS_5, + DEVICE_CLASS_ILLUMINANCE, +) + +CODEOWNERS = ["@sjtrny"] +DEPENDENCIES = ["i2c"] + +ltr390_ns = cg.esphome_ns.namespace("ltr390") + +LTR390Component = ltr390_ns.class_( + "LTR390Component", cg.PollingComponent, i2c.I2CDevice +) + +CONF_AMBIENT_LIGHT = "ambient_light" +CONF_UV_INDEX = "uv_index" +CONF_UV = "uv" +CONF_WINDOW_CORRECTION_FACTOR = "window_correction_factor" + +UNIT_COUNTS = "#" +UNIT_UVI = "UVI" + +LTR390GAIN = ltr390_ns.enum("LTR390GAIN") +GAIN_OPTIONS = { + "X1": LTR390GAIN.LTR390_GAIN_1, + "X3": LTR390GAIN.LTR390_GAIN_3, + "X6": LTR390GAIN.LTR390_GAIN_6, + "X9": LTR390GAIN.LTR390_GAIN_9, + "X18": LTR390GAIN.LTR390_GAIN_18, +} + +LTR390RESOLUTION = ltr390_ns.enum("LTR390RESOLUTION") +RES_OPTIONS = { + 20: LTR390RESOLUTION.LTR390_RESOLUTION_20BIT, + 19: LTR390RESOLUTION.LTR390_RESOLUTION_19BIT, + 18: LTR390RESOLUTION.LTR390_RESOLUTION_18BIT, + 17: LTR390RESOLUTION.LTR390_RESOLUTION_17BIT, + 16: LTR390RESOLUTION.LTR390_RESOLUTION_16BIT, + 13: LTR390RESOLUTION.LTR390_RESOLUTION_13BIT, +} + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(LTR390Component), + cv.Optional(CONF_LIGHT): sensor.sensor_schema( + UNIT_LUX, ICON_BRIGHTNESS_5, 1, DEVICE_CLASS_ILLUMINANCE + ), + cv.Optional(CONF_AMBIENT_LIGHT): sensor.sensor_schema( + UNIT_COUNTS, ICON_BRIGHTNESS_5, 1, DEVICE_CLASS_ILLUMINANCE + ), + cv.Optional(CONF_UV_INDEX): sensor.sensor_schema( + UNIT_UVI, ICON_BRIGHTNESS_5, 5, DEVICE_CLASS_ILLUMINANCE + ), + cv.Optional(CONF_UV): sensor.sensor_schema( + UNIT_COUNTS, ICON_BRIGHTNESS_5, 1, DEVICE_CLASS_ILLUMINANCE + ), + cv.Optional(CONF_GAIN, default="X3"): cv.enum(GAIN_OPTIONS), + cv.Optional(CONF_RESOLUTION, default=18): cv.enum(RES_OPTIONS), + cv.Optional(CONF_WINDOW_CORRECTION_FACTOR, default=1.0): cv.float_range( + min=1.0 + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x53)), + cv.has_at_least_one_key(CONF_LIGHT, CONF_AMBIENT_LIGHT, CONF_UV_INDEX, CONF_UV), +) + +TYPES = { + CONF_LIGHT: "set_light_sensor", + CONF_AMBIENT_LIGHT: "set_als_sensor", + CONF_UV_INDEX: "set_uvi_sensor", + CONF_UV: "set_uv_sensor", +} + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + cg.add(var.set_gain_value(config[CONF_GAIN])) + cg.add(var.set_res_value(config[CONF_RESOLUTION])) + cg.add(var.set_wfac_value(config[CONF_WINDOW_CORRECTION_FACTOR])) + + for key, funcName in TYPES.items(): + if key in config: + sens = await sensor.new_sensor(config[key]) + cg.add(getattr(var, funcName)(sens)) diff --git a/tests/test2.yaml b/tests/test2.yaml index e6df4d513e..d0634e0f7b 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -238,6 +238,20 @@ sensor: name: 'Inkbird IBS-TH1 Humidity' battery_level: name: 'Inkbird IBS-TH1 Battery Level' + - platform: ltr390 + uv: + name: "LTR390 UV" + uv_index: + name: "LTR390 UVI" + light: + name: "LTR390 Light" + ambient_light: + name: "LTR390 ALS" + gain: "X3" + resolution: 18 + window_correction_factor: 1.0 + address: 0x53 + update_interval: 60s - platform: sgp40 name: 'Workshop VOC' update_interval: 5s From e32722db70fb6dbc2d44aaa80fa7607d59ae3bc9 Mon Sep 17 00:00:00 2001 From: Trevor North Date: Wed, 22 Sep 2021 12:29:05 +0100 Subject: [PATCH 1415/1841] Allow sloppy datapoint message length (#1982) This allows datapoint update messages to be handled even if the overall message is longer than required (likely that it contains trailing empty bytes). The specific type handling will read only the expected data lengths so we only need to hard bail if we have too little data not too much. --- esphome/components/tuya/tuya.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index bbbc9274c3..d73ba50462 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -241,8 +241,10 @@ void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) { size_t data_size = (buffer[2] << 8) + buffer[3]; const uint8_t *data = buffer + 4; size_t data_len = len - 4; - if (data_size != data_len) { - ESP_LOGW(TAG, "Datapoint %u is not expected size (%zu != %zu)", datapoint.id, data_size, data_len); + if (data_size > data_len) { + ESP_LOGW(TAG, "Datapoint %u has extra bytes that will be ignored (%zu > %zu)", datapoint.id, data_size, data_len); + } else if (data_size < data_len) { + ESP_LOGW(TAG, "Datapoint %u is truncated and cannot be parsed (%zu < %zu)", datapoint.id, data_size, data_len); return; } datapoint.len = data_len; From fd836e982e31d8ff082a9f5c6847f217d4163b41 Mon Sep 17 00:00:00 2001 From: wifwucite <74489218+wifwucite@users.noreply.github.com> Date: Wed, 22 Sep 2021 13:42:58 +0200 Subject: [PATCH 1416/1841] Mqtt topics to support numeric fan speed (#1859) * numeric speed added * when dumping config for MQTT components log a note when skipped due to is_internal * added new topics to paython code validation/generation * reformatted with black * formatting corrected * use dump_config_ mechanism to skip internal components * use dump_config_ mechanism to skip internal components * style issues resolved * do_dump_config removed * formatting fixed * formatting fixed * Drop parent dump_config() calls Co-authored-by: Oxan van Leeuwen --- esphome/components/fan/__init__.py | 20 +++++++++++++ esphome/components/mqtt/mqtt_fan.cpp | 44 ++++++++++++++++++++++++++++ esphome/components/mqtt/mqtt_fan.h | 5 ++++ esphome/const.py | 2 ++ tests/test1.yaml | 2 ++ 5 files changed, 73 insertions(+) diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 46ff0c2d53..f8772948fc 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -12,6 +12,8 @@ from esphome.const import ( CONF_OSCILLATION_COMMAND_TOPIC, CONF_OSCILLATION_STATE_TOPIC, CONF_SPEED, + CONF_SPEED_LEVEL_COMMAND_TOPIC, + CONF_SPEED_LEVEL_STATE_TOPIC, CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC, CONF_NAME, @@ -57,6 +59,12 @@ FAN_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All( cv.requires_component("mqtt"), cv.subscribe_topic ), + cv.Optional(CONF_SPEED_LEVEL_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_SPEED_LEVEL_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), cv.Optional(CONF_SPEED_STATE_TOPIC): cv.All( cv.requires_component("mqtt"), cv.publish_topic ), @@ -104,6 +112,18 @@ async def setup_fan_core_(var, config): config[CONF_OSCILLATION_COMMAND_TOPIC] ) ) + if CONF_SPEED_LEVEL_STATE_TOPIC in config: + cg.add( + mqtt_.set_custom_speed_level_state_topic( + config[CONF_SPEED_LEVEL_STATE_TOPIC] + ) + ) + if CONF_SPEED_LEVEL_COMMAND_TOPIC in config: + cg.add( + mqtt_.set_custom_speed_level_command_topic( + config[CONF_SPEED_LEVEL_COMMAND_TOPIC] + ) + ) if CONF_SPEED_STATE_TOPIC in config: cg.add(mqtt_.set_custom_speed_state_topic(config[CONF_SPEED_STATE_TOPIC])) if CONF_SPEED_COMMAND_TOPIC in config: diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index c8db5ecece..ed1ab605aa 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -16,6 +16,7 @@ MQTTFanComponent::MQTTFanComponent(FanState *state) : MQTTComponent(), state_(st FanState *MQTTFanComponent::get_state() const { return this->state_; } std::string MQTTFanComponent::component_type() const { return "fan"; } + void MQTTFanComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { auto val = parse_on_off(payload.c_str()); @@ -64,6 +65,26 @@ void MQTTFanComponent::setup() { }); } + if (this->state_->get_traits().supports_speed()) { + this->subscribe(this->get_speed_level_command_topic(), + [this](const std::string &topic, const std::string &payload) { + optional speed_level_opt = parse_int(payload); + if (speed_level_opt.has_value()) { + const int speed_level = speed_level_opt.value(); + if (speed_level >= 0 && speed_level <= this->state_->get_traits().supported_speed_count()) { + ESP_LOGD(TAG, "New speed level %d", speed_level); + this->state_->make_call().set_speed(speed_level).perform(); + } else { + ESP_LOGW(TAG, "Invalid speed level %d", speed_level); + this->status_momentary_warning("speed", 5000); + } + } else { + ESP_LOGW(TAG, "Invalid speed level %s (int expected)", payload.c_str()); + this->status_momentary_warning("speed", 5000); + } + }); + } + if (this->state_->get_traits().supports_speed()) { this->subscribe(this->get_speed_command_topic(), [this](const std::string &topic, const std::string &payload) { this->state_->make_call() @@ -75,6 +96,22 @@ void MQTTFanComponent::setup() { auto f = std::bind(&MQTTFanComponent::publish_state, this); this->state_->add_on_state_callback([this, f]() { this->defer("send", f); }); } + +void MQTTFanComponent::dump_config() { + ESP_LOGCONFIG(TAG, "MQTT Fan '%s': ", this->state_->get_name().c_str()); + LOG_MQTT_COMPONENT(true, true); + if (this->state_->get_traits().supports_oscillation()) { + ESP_LOGCONFIG(TAG, " Oscillation State Topic: '%s'", this->get_oscillation_state_topic().c_str()); + ESP_LOGCONFIG(TAG, " Oscillation Command Topic: '%s'", this->get_oscillation_command_topic().c_str()); + } + if (this->state_->get_traits().supports_speed()) { + ESP_LOGCONFIG(TAG, " Speed Level State Topic: '%s'", this->get_speed_level_state_topic().c_str()); + ESP_LOGCONFIG(TAG, " Speed Level Command Topic: '%s'", this->get_speed_level_command_topic().c_str()); + ESP_LOGCONFIG(TAG, " Speed State Topic: '%s'", this->get_speed_state_topic().c_str()); + ESP_LOGCONFIG(TAG, " Speed Command Topic: '%s'", this->get_speed_command_topic().c_str()); + } +} + bool MQTTFanComponent::send_initial_state() { return this->publish_state(); } std::string MQTTFanComponent::friendly_name() const { return this->state_->get_name(); } void MQTTFanComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { @@ -83,6 +120,8 @@ void MQTTFanComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfi root["oscillation_state_topic"] = this->get_oscillation_state_topic(); } if (this->state_->get_traits().supports_speed()) { + root["speed_level_command_topic"] = this->get_speed_level_command_topic(); + root["speed_level_state_topic"] = this->get_speed_level_state_topic(); root["speed_command_topic"] = this->get_speed_command_topic(); root["speed_state_topic"] = this->get_speed_state_topic(); } @@ -99,6 +138,11 @@ bool MQTTFanComponent::publish_state() { failed = failed || !success; } auto traits = this->state_->get_traits(); + if (traits.supports_speed()) { + std::string payload = to_string(this->state_->speed); + bool success = this->publish(this->get_speed_level_state_topic(), payload); + failed = failed || !success; + } if (traits.supports_speed()) { const char *payload; // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) diff --git a/esphome/components/mqtt/mqtt_fan.h b/esphome/components/mqtt/mqtt_fan.h index 99d9c055cf..00263e13eb 100644 --- a/esphome/components/mqtt/mqtt_fan.h +++ b/esphome/components/mqtt/mqtt_fan.h @@ -17,6 +17,8 @@ class MQTTFanComponent : public mqtt::MQTTComponent { MQTT_COMPONENT_CUSTOM_TOPIC(oscillation, command) MQTT_COMPONENT_CUSTOM_TOPIC(oscillation, state) + MQTT_COMPONENT_CUSTOM_TOPIC(speed_level, command) + MQTT_COMPONENT_CUSTOM_TOPIC(speed_level, state) MQTT_COMPONENT_CUSTOM_TOPIC(speed, command) MQTT_COMPONENT_CUSTOM_TOPIC(speed, state) @@ -26,6 +28,9 @@ class MQTTFanComponent : public mqtt::MQTTComponent { // (In most use cases you won't need these) /// Setup the fan subscriptions and discovery. void setup() override; + + void dump_config() override; + /// Send the full current state to MQTT. bool send_initial_state() override; bool publish_state(); diff --git a/esphome/const.py b/esphome/const.py index f032cf0fc3..2cf261b4b5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -597,6 +597,8 @@ CONF_SOURCE = "source" CONF_SPEED = "speed" CONF_SPEED_COMMAND_TOPIC = "speed_command_topic" CONF_SPEED_COUNT = "speed_count" +CONF_SPEED_LEVEL_COMMAND_TOPIC = "speed_level_command_topic" +CONF_SPEED_LEVEL_STATE_TOPIC = "speed_level_state_topic" CONF_SPEED_STATE_TOPIC = "speed_state_topic" CONF_SPI_ID = "spi_id" CONF_SPIKE_REJECTION = "spike_rejection" diff --git a/tests/test1.yaml b/tests/test1.yaml index c1ddf26488..0fb14fd34b 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1978,6 +1978,8 @@ fan: direction_output: gpio_26 oscillation_state_topic: oscillation/state/topic oscillation_command_topic: oscillation/command/topic + speed_level_state_topic: speed_level/state/topic + speed_level_command_topic: speed_level/command/topic speed_state_topic: speed/state/topic speed_command_topic: speed/command/topic on_speed_set: From 8bebf138ee7010a09c14ac547e1e74299d4f599d Mon Sep 17 00:00:00 2001 From: Gustavo Ambrozio Date: Wed, 22 Sep 2021 01:44:09 -1000 Subject: [PATCH 1417/1841] Wifi scan results (#1605) * adding a scan results wifi text sensor * Code comment * Adding scan results to test * Removing redundant call * linting * Better method to update wifi info Co-authored-by: Otto Winter * Getting loop back At least for now. * Trying out suggestion again * Applying cr suggestions Co-authored-by: Otto Winter --- esphome/components/wifi_info/text_sensor.py | 10 +++++++ .../wifi_info/wifi_info_text_sensor.cpp | 1 + .../wifi_info/wifi_info_text_sensor.h | 29 +++++++++++++++++++ esphome/const.py | 1 + tests/test1.yaml | 2 ++ 5 files changed, 43 insertions(+) diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index 50ec3eb272..1922502204 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -5,6 +5,7 @@ from esphome.const import ( CONF_BSSID, CONF_ID, CONF_IP_ADDRESS, + CONF_SCAN_RESULTS, CONF_SSID, CONF_MAC_ADDRESS, ) @@ -15,6 +16,9 @@ wifi_info_ns = cg.esphome_ns.namespace("wifi_info") IPAddressWiFiInfo = wifi_info_ns.class_( "IPAddressWiFiInfo", text_sensor.TextSensor, cg.Component ) +ScanResultsWiFiInfo = wifi_info_ns.class_( + "ScanResultsWiFiInfo", text_sensor.TextSensor, cg.PollingComponent +) SSIDWiFiInfo = wifi_info_ns.class_("SSIDWiFiInfo", text_sensor.TextSensor, cg.Component) BSSIDWiFiInfo = wifi_info_ns.class_( "BSSIDWiFiInfo", text_sensor.TextSensor, cg.Component @@ -30,6 +34,11 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(): cv.declare_id(IPAddressWiFiInfo), } ), + cv.Optional(CONF_SCAN_RESULTS): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ScanResultsWiFiInfo), + } + ).extend(cv.polling_component_schema("60s")), cv.Optional(CONF_SSID): text_sensor.TEXT_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(SSIDWiFiInfo), @@ -62,3 +71,4 @@ async def to_code(config): await setup_conf(config, CONF_SSID) await setup_conf(config, CONF_BSSID) await setup_conf(config, CONF_MAC_ADDRESS) + await setup_conf(config, CONF_SCAN_RESULTS) diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.cpp b/esphome/components/wifi_info/wifi_info_text_sensor.cpp index 92e5d93a5a..0b73de68de 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.cpp +++ b/esphome/components/wifi_info/wifi_info_text_sensor.cpp @@ -7,6 +7,7 @@ namespace wifi_info { static const char *const TAG = "wifi_info"; void IPAddressWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "WifiInfo IPAddress", this); } +void ScanResultsWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "WifiInfo Scan Results", this); } void SSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "WifiInfo SSID", this); } void BSSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "WifiInfo BSSID", this); } void MacAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "WifiInfo Mac Address", this); } diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index de1c7f71fc..b2f37de363 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -24,6 +24,35 @@ class IPAddressWiFiInfo : public Component, public text_sensor::TextSensor { network::IPAddress last_ip_; }; +class ScanResultsWiFiInfo : public PollingComponent, public text_sensor::TextSensor { + public: + void update() override { + std::string scan_results; + for (auto &scan : wifi::global_wifi_component->get_scan_result()) { + if (scan.get_is_hidden()) + continue; + + scan_results += scan.get_ssid(); + scan_results += ": "; + scan_results += esphome::to_string(scan.get_rssi()); + scan_results += "dB\n"; + } + + if (this->last_scan_results_ != scan_results) { + this->last_scan_results_ = scan_results; + // There's a limit of 255 characters per state. + // Longer states just don't get sent so we truncate it. + this->publish_state(scan_results.substr(0, 255)); + } + } + float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + std::string unique_id() override { return get_mac_address() + "-wifiinfo-scanresults"; } + void dump_config() override; + + protected: + std::string last_scan_results_; +}; + class SSIDWiFiInfo : public Component, public text_sensor::TextSensor { public: void loop() override { diff --git a/esphome/const.py b/esphome/const.py index 2cf261b4b5..a52085fbb7 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -558,6 +558,7 @@ CONF_SAFE_MODE = "safe_mode" CONF_SAMSUNG = "samsung" CONF_SATELLITES = "satellites" CONF_SCAN = "scan" +CONF_SCAN_RESULTS = "scan_results" CONF_SCL = "scl" CONF_SCL_PIN = "scl_pin" CONF_SDA = "sda" diff --git a/tests/test1.yaml b/tests/test1.yaml index 0fb14fd34b..5bec56c80f 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2359,6 +2359,8 @@ text_sensor: name: Template Text Sensor id: ${textname}_text - platform: wifi_info + scan_results: + name: 'Scan Results' ip_address: name: 'IP Address' ssid: From 66761ff3400681828940e6b28c4466679bf045e3 Mon Sep 17 00:00:00 2001 From: ZJY <934526987@qq.com> Date: Wed, 22 Sep 2021 19:47:41 +0800 Subject: [PATCH 1418/1841] Add SSD1305 support to SSD1306 integration along with few new options (#1902) * Add serveral options for SSD1306 integration * Add SSD1305 support (SSD1305 is similar to SSD1306, it seems SSD1305 has brightness and color register but does not have charge pump) * Add some description when manipulating registers * Add flip, offset and invert option to get more compatibility with various display modules * Fix typo `setup_ssd1036' -> `setup_ssd1306' * Add SSD1306 brightness validation tip * Add more description, limit offset range * Changes according to linter * Fix test * Raise error instead of using warning * Fix wrong logic * Remove logger Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> * Remove logging import Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/ssd1306_base/__init__.py | 44 +++++++- .../components/ssd1306_base/ssd1306_base.cpp | 106 ++++++++++++++---- .../components/ssd1306_base/ssd1306_base.h | 16 +++ esphome/components/ssd1306_i2c/display.py | 4 +- .../components/ssd1306_i2c/ssd1306_i2c.cpp | 5 + esphome/components/ssd1306_spi/display.py | 4 +- .../components/ssd1306_spi/ssd1306_spi.cpp | 5 + tests/test1.yaml | 2 +- 8 files changed, 159 insertions(+), 27 deletions(-) diff --git a/esphome/components/ssd1306_base/__init__.py b/esphome/components/ssd1306_base/__init__.py index 9652d01efa..bc2e558f1b 100644 --- a/esphome/components/ssd1306_base/__init__.py +++ b/esphome/components/ssd1306_base/__init__.py @@ -8,12 +8,19 @@ from esphome.const import ( CONF_MODEL, CONF_RESET_PIN, CONF_BRIGHTNESS, + CONF_CONTRAST, + CONF_INVERT, ) ssd1306_base_ns = cg.esphome_ns.namespace("ssd1306_base") SSD1306 = ssd1306_base_ns.class_("SSD1306", cg.PollingComponent, display.DisplayBuffer) SSD1306Model = ssd1306_base_ns.enum("SSD1306Model") +CONF_FLIP_X = "flip_x" +CONF_FLIP_Y = "flip_y" +CONF_OFFSET_X = "offset_x" +CONF_OFFSET_Y = "offset_y" + MODELS = { "SSD1306_128X32": SSD1306Model.SSD1306_MODEL_128_32, "SSD1306_128X64": SSD1306Model.SSD1306_MODEL_128_64, @@ -23,21 +30,44 @@ MODELS = { "SH1106_128X64": SSD1306Model.SH1106_MODEL_128_64, "SH1106_96X16": SSD1306Model.SH1106_MODEL_96_16, "SH1106_64X48": SSD1306Model.SH1106_MODEL_64_48, + "SSD1305_128X32": SSD1306Model.SSD1305_MODEL_128_32, + "SSD1305_128X64": SSD1306Model.SSD1305_MODEL_128_64, } SSD1306_MODEL = cv.enum(MODELS, upper=True, space="_") + +def _validate(value): + model = value[CONF_MODEL] + if model not in ("SSD1305_128X32", "SSD1305_128X64"): + # Contrast is default value (1.0) while brightness is not + # Indicates user is using old `brightness` option + if value[CONF_BRIGHTNESS] != 1.0 and value[CONF_CONTRAST] == 1.0: + raise cv.Invalid( + "SSD1306/SH1106 no longer accepts brightness option, " + 'please use "contrast" instead.' + ) + + return value + + SSD1306_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend( { cv.Required(CONF_MODEL): SSD1306_MODEL, cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, + cv.Optional(CONF_CONTRAST, default=1.0): cv.percentage, cv.Optional(CONF_EXTERNAL_VCC): cv.boolean, + cv.Optional(CONF_FLIP_X, default=True): cv.boolean, + cv.Optional(CONF_FLIP_Y, default=True): cv.boolean, + cv.Optional(CONF_OFFSET_X, default=0): cv.int_range(min=0, max=15), + cv.Optional(CONF_OFFSET_Y, default=0): cv.int_range(min=0, max=15), + cv.Optional(CONF_INVERT, default=False): cv.boolean, } ).extend(cv.polling_component_schema("1s")) -async def setup_ssd1036(var, config): +async def setup_ssd1306(var, config): await cg.register_component(var, config) await display.register_display(var, config) @@ -47,8 +77,20 @@ async def setup_ssd1036(var, config): cg.add(var.set_reset_pin(reset)) if CONF_BRIGHTNESS in config: cg.add(var.init_brightness(config[CONF_BRIGHTNESS])) + if CONF_CONTRAST in config: + cg.add(var.init_contrast(config[CONF_CONTRAST])) if CONF_EXTERNAL_VCC in config: cg.add(var.set_external_vcc(config[CONF_EXTERNAL_VCC])) + if CONF_FLIP_X in config: + cg.add(var.init_flip_x(config[CONF_FLIP_X])) + if CONF_FLIP_Y in config: + cg.add(var.init_flip_y(config[CONF_FLIP_X])) + if CONF_OFFSET_X in config: + cg.add(var.init_offset_x(config[CONF_OFFSET_X])) + if CONF_OFFSET_Y in config: + cg.add(var.init_offset_y(config[CONF_OFFSET_Y])) + if CONF_INVERT in config: + cg.add(var.init_invert(config[CONF_INVERT])) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index d321933e8f..b1a2538ebd 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -8,12 +8,13 @@ namespace ssd1306_base { static const char *const TAG = "ssd1306"; static const uint8_t SSD1306_MAX_CONTRAST = 255; +static const uint8_t SSD1305_MAX_BRIGHTNESS = 255; static const uint8_t SSD1306_COMMAND_DISPLAY_OFF = 0xAE; static const uint8_t SSD1306_COMMAND_DISPLAY_ON = 0xAF; static const uint8_t SSD1306_COMMAND_SET_DISPLAY_CLOCK_DIV = 0xD5; static const uint8_t SSD1306_COMMAND_SET_MULTIPLEX = 0xA8; -static const uint8_t SSD1306_COMMAND_SET_DISPLAY_OFFSET = 0xD3; +static const uint8_t SSD1306_COMMAND_SET_DISPLAY_OFFSET_Y = 0xD3; static const uint8_t SSD1306_COMMAND_SET_START_LINE = 0x40; static const uint8_t SSD1306_COMMAND_CHARGE_PUMP = 0x8D; static const uint8_t SSD1306_COMMAND_MEMORY_MODE = 0x20; @@ -28,33 +29,60 @@ static const uint8_t SSD1306_COMMAND_DISPLAY_ALL_ON_RESUME = 0xA4; static const uint8_t SSD1306_COMMAND_DEACTIVATE_SCROLL = 0x2E; static const uint8_t SSD1306_COMMAND_COLUMN_ADDRESS = 0x21; static const uint8_t SSD1306_COMMAND_PAGE_ADDRESS = 0x22; +static const uint8_t SSD1306_COMMAND_NORMAL_DISPLAY = 0xA6; +static const uint8_t SSD1306_COMMAND_INVERSE_DISPLAY = 0xA7; -static const uint8_t SSD1306_NORMAL_DISPLAY = 0xA6; +static const uint8_t SSD1305_COMMAND_SET_BRIGHTNESS = 0x82; +static const uint8_t SSD1305_COMMAND_SET_AREA_COLOR = 0xD8; void SSD1306::setup() { this->init_internal_(this->get_buffer_length_()); + // Turn off display during initialization (0xAE) this->command(SSD1306_COMMAND_DISPLAY_OFF); - this->command(SSD1306_COMMAND_SET_DISPLAY_CLOCK_DIV); - this->command(0x80); // suggested ratio + // Set oscillator frequency to 4'b1000 with no clock division (0xD5) + this->command(SSD1306_COMMAND_SET_DISPLAY_CLOCK_DIV); + // Oscillator frequency <= 4'b1000, no clock division + this->command(0x80); + + // Enable low power display mode for SSD1305 (0xD8) + if (this->is_ssd1305_()) { + this->command(SSD1305_COMMAND_SET_AREA_COLOR); + this->command(0x05); + } + + // Set mux ratio to [Y pixels - 1] (0xA8) this->command(SSD1306_COMMAND_SET_MULTIPLEX); this->command(this->get_height_internal() - 1); - this->command(SSD1306_COMMAND_SET_DISPLAY_OFFSET); - this->command(0x00); // no offset - this->command(SSD1306_COMMAND_SET_START_LINE | 0x00); // start at line 0 - this->command(SSD1306_COMMAND_CHARGE_PUMP); - if (this->external_vcc_) - this->command(0x10); - else - this->command(0x14); + // Set Y offset (0xD3) + this->command(SSD1306_COMMAND_SET_DISPLAY_OFFSET_Y); + this->command(0x00 + this->offset_y_); + // Set start line at line 0 (0x40) + this->command(SSD1306_COMMAND_SET_START_LINE | 0x00); + // SSD1305 does not have charge pump + if (!this->is_ssd1305_()) { + // Enable charge pump (0x8D) + this->command(SSD1306_COMMAND_CHARGE_PUMP); + if (this->external_vcc_) + this->command(0x10); + else + this->command(0x14); + } + + // Set addressing mode to horizontal (0x20) this->command(SSD1306_COMMAND_MEMORY_MODE); this->command(0x00); - this->command(SSD1306_COMMAND_SEGRE_MAP | 0x01); - this->command(SSD1306_COMMAND_COM_SCAN_DEC); + // X flip mode (0xA0, 0xA1) + this->command(SSD1306_COMMAND_SEGRE_MAP | this->flip_x_); + + // Y flip mode (0xC0, 0xC8) + this->command(SSD1306_COMMAND_COM_SCAN_INC | (this->flip_y_ << 3)); + + // Set pin configuration (0xDA) this->command(SSD1306_COMMAND_SET_COM_PINS); switch (this->model_) { case SSD1306_MODEL_128_32: @@ -67,25 +95,37 @@ void SSD1306::setup() { case SH1106_MODEL_128_64: case SSD1306_MODEL_64_48: case SH1106_MODEL_64_48: + case SSD1305_MODEL_128_32: + case SSD1305_MODEL_128_64: this->command(0x12); break; } + // Pre-charge period (0xD9) this->command(SSD1306_COMMAND_SET_PRE_CHARGE); if (this->external_vcc_) this->command(0x22); else this->command(0xF1); + // Set V_COM (0xDB) this->command(SSD1306_COMMAND_SET_VCOM_DETECT); this->command(0x00); + // Display output follow RAM (0xA4) this->command(SSD1306_COMMAND_DISPLAY_ALL_ON_RESUME); - this->command(SSD1306_NORMAL_DISPLAY); + // Inverse display mode (0xA6, 0xA7) + this->command(SSD1306_COMMAND_NORMAL_DISPLAY | this->invert_); + + // Disable scrolling mode (0x2E) this->command(SSD1306_COMMAND_DEACTIVATE_SCROLL); - set_brightness(this->brightness_); + // Contrast and brighrness + // SSD1306 does not have brightness setting + set_contrast(this->contrast_); + if (this->is_ssd1305_()) + set_brightness(this->brightness_); this->fill(Color::BLACK); // clear display - ensures we do not see garbage at power-on this->display(); // ...write buffer, which actually clears the display's memory @@ -101,12 +141,12 @@ void SSD1306::display() { this->command(SSD1306_COMMAND_COLUMN_ADDRESS); switch (this->model_) { case SSD1306_MODEL_64_48: - this->command(0x20); - this->command(0x20 + this->get_width_internal() - 1); + this->command(0x20 + this->offset_x_); + this->command(0x20 + this->offset_x_ + this->get_width_internal() - 1); break; default: - this->command(0); // Page start address, 0 - this->command(this->get_width_internal() - 1); + this->command(0 + this->offset_x_); // Page start address, 0 + this->command(this->get_width_internal() + this->offset_x_ - 1); break; } @@ -122,16 +162,28 @@ bool SSD1306::is_sh1106_() const { return this->model_ == SH1106_MODEL_96_16 || this->model_ == SH1106_MODEL_128_32 || this->model_ == SH1106_MODEL_128_64; } +bool SSD1306::is_ssd1305_() const { + return this->model_ == SSD1305_MODEL_128_64 || this->model_ == SSD1305_MODEL_128_64; +} void SSD1306::update() { this->do_update_(); this->display(); } +void SSD1306::set_contrast(float contrast) { + // validation + this->contrast_ = clamp(contrast, 0.0F, 1.0F); + // now write the new contrast level to the display (0x81) + this->command(SSD1306_COMMAND_SET_CONTRAST); + this->command(int(SSD1306_MAX_CONTRAST * (this->contrast_))); +} void SSD1306::set_brightness(float brightness) { // validation + if (!this->is_ssd1305_()) + return; this->brightness_ = clamp(brightness, 0.0F, 1.0F); - // now write the new brightness level to the display - this->command(SSD1306_COMMAND_SET_CONTRAST); - this->command(int(SSD1306_MAX_CONTRAST * (this->brightness_))); + // now write the new brightness level to the display (0x82) + this->command(SSD1305_COMMAND_SET_BRIGHTNESS); + this->command(int(SSD1305_MAX_BRIGHTNESS * (this->brightness_))); } bool SSD1306::is_on() { return this->is_on_; } void SSD1306::turn_on() { @@ -146,9 +198,11 @@ int SSD1306::get_height_internal() { switch (this->model_) { case SSD1306_MODEL_128_32: case SH1106_MODEL_128_32: + case SSD1305_MODEL_128_32: return 32; case SSD1306_MODEL_128_64: case SH1106_MODEL_128_64: + case SSD1305_MODEL_128_64: return 64; case SSD1306_MODEL_96_16: case SH1106_MODEL_96_16: @@ -166,6 +220,8 @@ int SSD1306::get_width_internal() { case SH1106_MODEL_128_32: case SSD1306_MODEL_128_64: case SH1106_MODEL_128_64: + case SSD1305_MODEL_128_32: + case SSD1305_MODEL_128_64: return 128; case SSD1306_MODEL_96_16: case SH1106_MODEL_96_16: @@ -227,6 +283,10 @@ const char *SSD1306::model_str_() { return "SH1106 96x16"; case SH1106_MODEL_64_48: return "SH1106 64x48"; + case SSD1305_MODEL_128_32: + return "SSD1305 128x32"; + case SSD1305_MODEL_128_64: + return "SSD1305 128x32"; default: return "Unknown"; } diff --git a/esphome/components/ssd1306_base/ssd1306_base.h b/esphome/components/ssd1306_base/ssd1306_base.h index 2c54af7a67..54cb10d153 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.h +++ b/esphome/components/ssd1306_base/ssd1306_base.h @@ -16,6 +16,8 @@ enum SSD1306Model { SH1106_MODEL_128_64, SH1106_MODEL_96_16, SH1106_MODEL_64_48, + SSD1305_MODEL_128_32, + SSD1305_MODEL_128_64, }; class SSD1306 : public PollingComponent, public display::DisplayBuffer { @@ -29,8 +31,15 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer { void set_model(SSD1306Model model) { this->model_ = model; } void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } void set_external_vcc(bool external_vcc) { this->external_vcc_ = external_vcc; } + void init_contrast(float contrast) { this->contrast_ = contrast; } + void set_contrast(float contrast); void init_brightness(float brightness) { this->brightness_ = brightness; } void set_brightness(float brightness); + void init_flip_x(boolean flip_x) { this->flip_x_ = flip_x; } + void init_flip_y(boolean flip_y) { this->flip_y_ = flip_y; } + void init_offset_x(uint8_t offset_x) { this->offset_x_ = offset_x; } + void init_offset_y(uint8_t offset_y) { this->offset_y_ = offset_y; } + void init_invert(boolean invert) { this->invert_ = invert; } bool is_on(); void turn_on(); void turn_off(); @@ -43,6 +52,7 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer { void init_reset_(); bool is_sh1106_() const; + bool is_ssd1305_() const; void draw_absolute_pixel_internal(int x, int y, Color color) override; @@ -55,7 +65,13 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer { GPIOPin *reset_pin_{nullptr}; bool external_vcc_{false}; bool is_on_{false}; + float contrast_{1.0}; float brightness_{1.0}; + bool flip_x_{true}; + bool flip_y_{true}; + uint8_t offset_x_{0}; + uint8_t offset_y_{0}; + bool invert_{false}; }; } // namespace ssd1306_base diff --git a/esphome/components/ssd1306_i2c/display.py b/esphome/components/ssd1306_i2c/display.py index 4b51a90431..c51ab5f93e 100644 --- a/esphome/components/ssd1306_i2c/display.py +++ b/esphome/components/ssd1306_i2c/display.py @@ -1,6 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import ssd1306_base, i2c +from esphome.components.ssd1306_base import _validate from esphome.const import CONF_ID, CONF_LAMBDA, CONF_PAGES AUTO_LOAD = ["ssd1306_base"] @@ -18,10 +19,11 @@ CONFIG_SCHEMA = cv.All( .extend(cv.COMPONENT_SCHEMA) .extend(i2c.i2c_device_schema(0x3C)), cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), + _validate, ) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - await ssd1306_base.setup_ssd1036(var, config) + await ssd1306_base.setup_ssd1306(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp index 45fda4870e..fddea25fc8 100644 --- a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp +++ b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp @@ -25,6 +25,11 @@ void I2CSSD1306::dump_config() { ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); LOG_PIN(" Reset Pin: ", this->reset_pin_); ESP_LOGCONFIG(TAG, " External VCC: %s", YESNO(this->external_vcc_)); + ESP_LOGCONFIG(TAG, " Flip X: %s", YESNO(this->flip_x_)); + ESP_LOGCONFIG(TAG, " Flip Y: %s", YESNO(this->flip_y_)); + ESP_LOGCONFIG(TAG, " Offset X: %d", this->offset_x_); + ESP_LOGCONFIG(TAG, " Offset Y: %d", this->offset_y_); + ESP_LOGCONFIG(TAG, " Inverted Color: %s", YESNO(this->invert_)); LOG_UPDATE_INTERVAL(this); if (this->error_code_ == COMMUNICATION_FAILED) { diff --git a/esphome/components/ssd1306_spi/display.py b/esphome/components/ssd1306_spi/display.py index f7dd1553ba..0af1168bde 100644 --- a/esphome/components/ssd1306_spi/display.py +++ b/esphome/components/ssd1306_spi/display.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins from esphome.components import spi, ssd1306_base +from esphome.components.ssd1306_base import _validate from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES AUTO_LOAD = ["ssd1306_base"] @@ -20,12 +21,13 @@ CONFIG_SCHEMA = cv.All( .extend(cv.COMPONENT_SCHEMA) .extend(spi.spi_device_schema()), cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), + _validate, ) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - await ssd1306_base.setup_ssd1036(var, config) + await ssd1306_base.setup_ssd1306(var, config) await spi.register_spi_device(var, config) dc = await cg.gpio_pin_expression(config[CONF_DC_PIN]) diff --git a/esphome/components/ssd1306_spi/ssd1306_spi.cpp b/esphome/components/ssd1306_spi/ssd1306_spi.cpp index 5ef25b8139..33d474a8ee 100644 --- a/esphome/components/ssd1306_spi/ssd1306_spi.cpp +++ b/esphome/components/ssd1306_spi/ssd1306_spi.cpp @@ -22,6 +22,11 @@ void SPISSD1306::dump_config() { LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); ESP_LOGCONFIG(TAG, " External VCC: %s", YESNO(this->external_vcc_)); + ESP_LOGCONFIG(TAG, " Flip X: %s", YESNO(this->flip_x_)); + ESP_LOGCONFIG(TAG, " Flip Y: %s", YESNO(this->flip_y_)); + ESP_LOGCONFIG(TAG, " Offset X: %d", this->offset_x_); + ESP_LOGCONFIG(TAG, " Offset Y: %d", this->offset_y_); + ESP_LOGCONFIG(TAG, " Inverted Color: %s", YESNO(this->invert_)); LOG_UPDATE_INTERVAL(this); } void SPISSD1306::command(uint8_t value) { diff --git a/tests/test1.yaml b/tests/test1.yaml index 5bec56c80f..6109e9f5c2 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2072,7 +2072,7 @@ display: reset_pin: GPIO23 address: 0x3C id: display1 - brightness: 60% + contrast: 60% pages: - id: page1 lambda: |- From ed3ad615d8500fd4844db457926e6d6fbb000569 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 22 Sep 2021 14:07:39 +0200 Subject: [PATCH 1419/1841] Fix compilation due to incompatibility between #1237 and IDF changes (#2372) --- esphome/components/ssd1306_base/ssd1306_base.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/ssd1306_base/ssd1306_base.h b/esphome/components/ssd1306_base/ssd1306_base.h index 54cb10d153..09417a2c10 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.h +++ b/esphome/components/ssd1306_base/ssd1306_base.h @@ -35,11 +35,11 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer { void set_contrast(float contrast); void init_brightness(float brightness) { this->brightness_ = brightness; } void set_brightness(float brightness); - void init_flip_x(boolean flip_x) { this->flip_x_ = flip_x; } - void init_flip_y(boolean flip_y) { this->flip_y_ = flip_y; } + void init_flip_x(bool flip_x) { this->flip_x_ = flip_x; } + void init_flip_y(bool flip_y) { this->flip_y_ = flip_y; } void init_offset_x(uint8_t offset_x) { this->offset_x_ = offset_x; } void init_offset_y(uint8_t offset_y) { this->offset_y_ = offset_y; } - void init_invert(boolean invert) { this->invert_ = invert; } + void init_invert(bool invert) { this->invert_ = invert; } bool is_on(); void turn_on(); void turn_off(); From 0406e271007c539de4d7decd7692dea8a1f046ed Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 22 Sep 2021 19:07:57 +0200 Subject: [PATCH 1420/1841] Don't generate IDs with the name of loaded integrations (#2373) --- esphome/core/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 9ec7fe358d..8021fc2f53 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -312,7 +312,7 @@ class ID: if self.id is None: base = str(self.type).replace("::", "_").lower() name = "".join(c for c in base if c.isalnum() or c == "_") - used = set(registered_ids) | set(RESERVED_IDS) + used = set(registered_ids) | set(RESERVED_IDS) | CORE.loaded_integrations self.id = ensure_unique_string(name, used) return self.id From 262d69308dc45f9ec133ad9aae532dad81efaa8f Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Wed, 22 Sep 2021 19:08:42 +0200 Subject: [PATCH 1421/1841] fix i2c scanning eror for Arduino (#2364) --- esphome/components/i2c/i2c_bus_arduino.cpp | 5 ++--- esphome/components/i2c/i2c_bus_esp_idf.cpp | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index aba412c3f7..40d8049617 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -33,9 +33,8 @@ void ArduinoI2CBus::dump_config() { if (this->scan_) { ESP_LOGI(TAG, "Scanning i2c bus for active devices..."); uint8_t found = 0; - for (uint8_t address = 1; address < 120; address++) { - auto err = readv(address, nullptr, 0); - + for (uint8_t address = 8; address < 120; address++) { + auto err = writev(address, nullptr, 0); if (err == ERROR_OK) { ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address); found++; diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 4b93b41877..8bf97b63ec 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -43,8 +43,8 @@ void IDFI2CBus::dump_config() { if (this->scan_) { ESP_LOGI(TAG, "Scanning i2c bus for active devices..."); uint8_t found = 0; - for (uint8_t address = 1; address < 120; address++) { - auto err = readv(address, nullptr, 0); + for (uint8_t address = 8; address < 120; address++) { + auto err = writev(address, nullptr, 0); if (err == ERROR_OK) { ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address); From f463cd98f8543a36ec9925083561796b088d030d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Sep 2021 19:50:11 +0200 Subject: [PATCH 1422/1841] Bump tzlocal from 2.1 to 3.0 (#2294) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Otto winter --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e97c79985f..95ca95430d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ PyYAML==5.4.1 paho-mqtt==1.5.1 colorama==0.4.4 tornado==6.1 -tzlocal==2.1 +tzlocal==3.0 pytz==2021.1 pyserial==3.5 platformio==5.2.0 From edb557f79e1cf3fc33a93d8da59748ea2c097fb0 Mon Sep 17 00:00:00 2001 From: Philipp Riederer Date: Wed, 22 Sep 2021 19:50:19 +0200 Subject: [PATCH 1423/1841] ledc: do not try to write_state to an uninitialized output (#1732) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Philipp Tölke Co-authored-by: Otto winter --- esphome/components/ledc/ledc_output.cpp | 11 +++++++++++ esphome/components/ledc/ledc_output.h | 1 + 2 files changed, 12 insertions(+) diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp index 77610a476f..21a747e34d 100644 --- a/esphome/components/ledc/ledc_output.cpp +++ b/esphome/components/ledc/ledc_output.cpp @@ -31,6 +31,11 @@ optional ledc_bit_depth_for_frequency(float frequency) { } void LEDCOutput::write_state(float state) { + if (!initialized_) { + ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!"); + return; + } + if (this->pin_->is_inverted()) state = 1.0f - state; @@ -81,6 +86,7 @@ void LEDCOutput::setup() { chan_conf.duty = inverted_ == pin_->is_inverted() ? 0 : (1U << bit_depth_); chan_conf.hpoint = 0; ledc_channel_config(&chan_conf); + initialized_ = true; #endif } @@ -101,8 +107,13 @@ void LEDCOutput::update_frequency(float frequency) { this->frequency_ = frequency; #ifdef USE_ARDUINO ledcSetup(this->channel_, frequency, this->bit_depth_); + initialized_ = true; #endif // USE_ARDUINO #ifdef USE_ESP_IDF + if (!initialized_) { + ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!"); + return; + } auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; auto timer_num = static_cast((channel_ % 8) / 2); diff --git a/esphome/components/ledc/ledc_output.h b/esphome/components/ledc/ledc_output.h index f810ce1e35..e02cefd170 100644 --- a/esphome/components/ledc/ledc_output.h +++ b/esphome/components/ledc/ledc_output.h @@ -36,6 +36,7 @@ class LEDCOutput : public output::FloatOutput, public Component { uint8_t bit_depth_{}; float frequency_{}; float duty_{0.0f}; + bool initialized_ = false; }; template class SetFrequencyAction : public Action { From b398d826c17adcb8ef9d6c330fca7d496cab8ba8 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 22 Sep 2021 20:07:43 +0200 Subject: [PATCH 1424/1841] Fix two i2c error code return errors (#2375) --- esphome/components/pn532_i2c/pn532_i2c.cpp | 4 +++- esphome/components/scd30/scd30.cpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/pn532_i2c/pn532_i2c.cpp b/esphome/components/pn532_i2c/pn532_i2c.cpp index ef7480ec25..e7c99e94b0 100644 --- a/esphome/components/pn532_i2c/pn532_i2c.cpp +++ b/esphome/components/pn532_i2c/pn532_i2c.cpp @@ -12,7 +12,9 @@ namespace pn532_i2c { static const char *const TAG = "pn532_i2c"; -bool PN532I2C::write_data(const std::vector &data) { return this->write(data.data(), data.size()); } +bool PN532I2C::write_data(const std::vector &data) { + return this->write(data.data(), data.size()) == i2c::ERROR_OK; +} bool PN532I2C::read_data(std::vector &data, uint8_t len) { delay(1); diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index 30775fdea4..d1246d9766 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -198,7 +198,7 @@ bool SCD30Component::write_command_(uint16_t command, uint16_t data) { raw[2] = data >> 8; raw[3] = data & 0xFF; raw[4] = sht_crc_(raw[2], raw[3]); - return this->write(raw, 5); + return this->write(raw, 5) == i2c::ERROR_OK; } uint8_t SCD30Component::sht_crc_(uint8_t data1, uint8_t data2) { From 5ddba719c56bc59a88f41c4d2ba896402774d5c1 Mon Sep 17 00:00:00 2001 From: Stijn Tintel Date: Wed, 22 Sep 2021 21:13:24 +0300 Subject: [PATCH 1425/1841] Fix ir_climate on ESP32-C3 (#2314) Co-authored-by: Otto winter --- esphome/components/remote_base/remote_base.cpp | 4 ++-- .../remote_transmitter/remote_transmitter_esp32.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/remote_base/remote_base.cpp b/esphome/components/remote_base/remote_base.cpp index a43f743ab6..a853c9849e 100644 --- a/esphome/components/remote_base/remote_base.cpp +++ b/esphome/components/remote_base/remote_base.cpp @@ -14,8 +14,8 @@ RemoteRMTChannel::RemoteRMTChannel(uint8_t mem_block_num) : mem_block_num_(mem_b } void RemoteRMTChannel::config_rmt(rmt_config_t &rmt) { - if (rmt_channel_t(int(this->channel_) + this->mem_block_num_) > RMT_CHANNEL_7) { - this->mem_block_num_ = int(RMT_CHANNEL_7) - int(this->channel_) + 1; + if (rmt_channel_t(int(this->channel_) + this->mem_block_num_) >= RMT_CHANNEL_MAX) { + this->mem_block_num_ = int(RMT_CHANNEL_MAX) - int(this->channel_); ESP_LOGW(TAG, "Not enough RMT memory blocks available, reduced to %i blocks.", this->mem_block_num_); } rmt.channel = this->channel_; diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index ff53d0be84..a1f7663a24 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -92,7 +92,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen val = this->from_microseconds(static_cast(val)); do { - int32_t item = std::min(val, 32767); + int32_t item = std::min(val, int32_t(32767)); val -= item; if (rmt_i % 2 == 0) { From ea6a7a22ff451d164285b92574d4dd846f37d111 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 23 Sep 2021 20:45:41 +1200 Subject: [PATCH 1426/1841] Fix ESP8266 ADC (#2376) --- esphome/components/adc/adc_sensor.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 9a05c1d66b..c8f8b0e0f6 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -1,9 +1,13 @@ #include "adc_sensor.h" #include "esphome/core/log.h" +#ifdef USE_ESP8266 #ifdef USE_ADC_SENSOR_VCC #include ADC_MODE(ADC_VCC) +#else +#include +#endif #endif namespace esphome { From 17dcba8f8ac932460ca87c68cfcd052958ed0305 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 23 Sep 2021 21:19:17 +1200 Subject: [PATCH 1427/1841] Fix: Pin flags code generation returning FLAG_NONE (#2377) Co-authored-by: Otto winter --- esphome/cpp_generator.py | 17 +++++++++++++++++ esphome/pins.py | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 691f45f91f..8460c1d462 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -310,6 +310,19 @@ class FloatLiteral(Literal): return f"{self.f}f" +class BinOpExpression(Expression): + __slots__ = ("op", "lhs", "rhs") + + def __init__(self, op: str, lhs: SafeExpType, rhs: SafeExpType): + # Remove every None on end + self.op = op + self.lhs = safe_exp(lhs) + self.rhs = safe_exp(rhs) + + def __str__(self): + return f"{self.lhs} {self.op} {self.rhs}" + + def safe_exp(obj: SafeExpType) -> Expression: """Try to convert obj to an expression by automatically converting native python types to expressions/literals. @@ -756,6 +769,10 @@ class MockObj(Expression): next_op = "->" return MockObj(f"{self.base}[{item}]", next_op) + def __or__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression("|", self, other) + return MockObj(op) + class MockObjEnum(MockObj): def __init__(self, *args, **kwargs): diff --git a/esphome/pins.py b/esphome/pins.py index ae762c1a1a..2b3adce86d 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -90,7 +90,7 @@ def gpio_flags_expr(mode): CONF_PULLDOWN: cg.gpio_Flags.FLAG_PULLDOWN, } active_flags = [v for k, v in FLAGS_MAPPING.items() if mode.get(k)] - if active_flags: + if not active_flags: return cg.gpio_Flags.FLAG_NONE return reduce(operator.or_, active_flags) From a27a8841913e1447d08b9c64c86c686780463842 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 23 Sep 2021 11:53:10 +0200 Subject: [PATCH 1428/1841] Add missing MockObj operators (#2378) --- esphome/cpp_generator.py | 183 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 176 insertions(+), 7 deletions(-) diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 8460c1d462..cf357f2814 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -313,14 +313,26 @@ class FloatLiteral(Literal): class BinOpExpression(Expression): __slots__ = ("op", "lhs", "rhs") - def __init__(self, op: str, lhs: SafeExpType, rhs: SafeExpType): - # Remove every None on end - self.op = op + def __init__(self, lhs: SafeExpType, op: str, rhs: SafeExpType): self.lhs = safe_exp(lhs) + self.op = op self.rhs = safe_exp(rhs) def __str__(self): - return f"{self.lhs} {self.op} {self.rhs}" + # Surround with parentheses to ensure generated code has same + # order as python one + return f"({self.lhs} {self.op} {self.rhs})" + + +class UnaryOpExpression(Expression): + __slots__ = ("op", "exp") + + def __init__(self, op: str, exp: SafeExpType): + self.op = op + self.exp = safe_exp(exp) + + def __str__(self): + return f"({self.op}{self.exp})" def safe_exp(obj: SafeExpType) -> Expression: @@ -729,6 +741,7 @@ class MockObj(Expression): return MockObj(f"new {self.base}", "->") def template(self, *args: SafeExpType) -> "MockObj": + """Apply template parameters to this object.""" if len(args) != 1 or not isinstance(args[0], TemplateArguments): args = TemplateArguments(*args) else: @@ -749,6 +762,10 @@ class MockObj(Expression): return MockObjEnum(enum=name, is_class=is_class, base=self.base, op=self.op) def operator(self, name: str) -> "MockObj": + """Various other operations. + + Named operator because it's a C++ keyword and can't occur in valid code. + """ if name == "ref": return MockObj(f"{self.base} &", "") if name == "ptr": @@ -769,8 +786,160 @@ class MockObj(Expression): next_op = "->" return MockObj(f"{self.base}[{item}]", next_op) + def __lt__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "<", other) + return MockObj(op) + + def __le__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "<=", other) + return MockObj(op) + + def __eq__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "==", other) + return MockObj(op) + + def __ne__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "!=", other) + return MockObj(op) + + def __gt__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, ">", other) + return MockObj(op) + + def __ge__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, ">=", other) + return MockObj(op) + + def __add__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "+", other) + return MockObj(op) + + def __sub__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "-", other) + return MockObj(op) + + def __mul__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "*", other) + return MockObj(op) + + def __truediv__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "/", other) + return MockObj(op) + + def __mod__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "%", other) + return MockObj(op) + + def __lshift__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "<<", other) + return MockObj(op) + + def __rshift__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, ">>", other) + return MockObj(op) + + def __and__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "&", other) + return MockObj(op) + + def __xor__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "^", other) + return MockObj(op) + def __or__(self, other: SafeExpType) -> "MockObj": - op = BinOpExpression("|", self, other) + op = BinOpExpression(self, "|", other) + return MockObj(op) + + def __radd__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, "+", self) + return MockObj(op) + + def __rsub__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, "-", self) + return MockObj(op) + + def __rmul__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, "*", self) + return MockObj(op) + + def __rtruediv__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, "/", self) + return MockObj(op) + + def __rmod__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, "%", self) + return MockObj(op) + + def __rlshift__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, "<<", self) + return MockObj(op) + + def __rrshift__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, ">>", self) + return MockObj(op) + + def __rand__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, "&", self) + return MockObj(op) + + def __rxor__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, "^", self) + return MockObj(op) + + def __ror__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(other, "|", self) + return MockObj(op) + + def __iadd__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "+=", other) + return MockObj(op) + + def __isub__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "-=", other) + return MockObj(op) + + def __imul__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "*=", other) + return MockObj(op) + + def __itruediv__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "/=", other) + return MockObj(op) + + def __imod__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "%=", other) + return MockObj(op) + + def __ilshift__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "<<=", other) + return MockObj(op) + + def __irshift__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, ">>=", other) + return MockObj(op) + + def __iand__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "&=", other) + return MockObj(op) + + def __ixor__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "^=", other) + return MockObj(op) + + def __ior__(self, other: SafeExpType) -> "MockObj": + op = BinOpExpression(self, "|=", other) + return MockObj(op) + + def __neg__(self) -> "MockObj": + op = UnaryOpExpression("-", self) + return MockObj(op) + + def __pos__(self) -> "MockObj": + op = UnaryOpExpression("+", self) + return MockObj(op) + + def __invert__(self) -> "MockObj": + op = UnaryOpExpression("~", self) return MockObj(op) @@ -807,10 +976,10 @@ class MockObjClass(MockObj): self._parents += paren._parents def inherits_from(self, other: "MockObjClass") -> bool: - if self == other: + if str(self) == str(other): return True for parent in self._parents: - if parent == other: + if str(parent) == str(other): return True return False From 210a9a41621c3e391531d31d8c8822a4e9d79ec7 Mon Sep 17 00:00:00 2001 From: Christian Taedcke Date: Thu, 23 Sep 2021 18:24:29 +0200 Subject: [PATCH 1429/1841] Fix esp-idf pinmask bit-shift overflow (#2380) --- esphome/components/esp32/gpio_idf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32/gpio_idf.h b/esphome/components/esp32/gpio_idf.h index 6a383afcae..a83c6bbc97 100644 --- a/esphome/components/esp32/gpio_idf.h +++ b/esphome/components/esp32/gpio_idf.h @@ -20,7 +20,7 @@ class IDFInternalGPIOPin : public InternalGPIOPin { } void pin_mode(gpio::Flags flags) override { gpio_config_t conf{}; - conf.pin_bit_mask = 1 << static_cast(pin_); + conf.pin_bit_mask = 1ULL << static_cast(pin_); conf.mode = flags_to_mode_(flags); conf.pull_up_en = flags & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE; conf.pull_down_en = flags & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE; From 963b28181fdbcc2c3e77dec5adc7b4c653a6bc7d Mon Sep 17 00:00:00 2001 From: Christian Taedcke Date: Thu, 23 Sep 2021 20:11:40 +0200 Subject: [PATCH 1430/1841] Always execute i2c bus recovery on setup (#2379) --- esphome/components/i2c/i2c_bus_arduino.cpp | 28 ++++++++++++++++ esphome/components/i2c/i2c_bus_arduino.h | 3 ++ esphome/components/i2c/i2c_bus_esp_idf.cpp | 38 ++++++++++++++++++++++ esphome/components/i2c/i2c_bus_esp_idf.h | 3 ++ 4 files changed, 72 insertions(+) diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index 40d8049617..b983fb7636 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -2,6 +2,7 @@ #include "i2c_bus_arduino.h" #include "esphome/core/log.h" +#include #include namespace esphome { @@ -10,6 +11,7 @@ namespace i2c { static const char *const TAG = "i2c.arduino"; void ArduinoI2CBus::setup() { + recover(); #ifdef USE_ESP32 static uint8_t next_bus_num = 0; if (next_bus_num == 0) @@ -90,6 +92,32 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn return ERROR_UNKNOWN; } +void ArduinoI2CBus::recover() { + // Perform I2C bus recovery, see + // https://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf + // or see the linux kernel implementation, e.g. + // https://elixir.bootlin.com/linux/v5.14.6/source/drivers/i2c/i2c-core-base.c#L200 + + // try to get about 100kHz toggle frequency + const auto half_period_usec = 1000000 / 100000 / 2; + const auto recover_scl_periods = 9; + + // configure scl as output + pinMode(scl_pin_, OUTPUT); // NOLINT + + // set scl high + digitalWrite(scl_pin_, 1); // NOLINT + + // in total generate 9 falling-rising edges + for (auto i = 0; i < recover_scl_periods; i++) { + delayMicroseconds(half_period_usec); + digitalWrite(scl_pin_, 0); // NOLINT + delayMicroseconds(half_period_usec); + digitalWrite(scl_pin_, 1); // NOLINT + } + + delayMicroseconds(half_period_usec); +} } // namespace i2c } // namespace esphome diff --git a/esphome/components/i2c/i2c_bus_arduino.h b/esphome/components/i2c/i2c_bus_arduino.h index 220027b3d4..49be0c358c 100644 --- a/esphome/components/i2c/i2c_bus_arduino.h +++ b/esphome/components/i2c/i2c_bus_arduino.h @@ -22,6 +22,9 @@ class ArduinoI2CBus : public I2CBus, public Component { void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; } void set_frequency(uint32_t frequency) { frequency_ = frequency; } + private: + void recover(); + protected: TwoWire *wire_; bool scan_; diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 8bf97b63ec..5ce5d40c00 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -1,6 +1,7 @@ #ifdef USE_ESP_IDF #include "i2c_bus_esp_idf.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" #include @@ -13,6 +14,8 @@ void IDFI2CBus::setup() { static i2c_port_t next_port = 0; port_ = next_port++; + recover(); + i2c_config_t conf{}; memset(&conf, 0, sizeof(conf)); conf.mode = I2C_MODE_MASTER; @@ -141,6 +144,41 @@ ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { return ERROR_OK; } +void IDFI2CBus::recover() { + // Perform I2C bus recovery, see + // https://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf + // or see the linux kernel implementation, e.g. + // https://elixir.bootlin.com/linux/v5.14.6/source/drivers/i2c/i2c-core-base.c#L200 + + // try to get about 100kHz toggle frequency + const auto half_period_usec = 1000000 / 100000 / 2; + const auto recover_scl_periods = 9; + const gpio_num_t scl_pin = static_cast(scl_pin_); + + // configure scl as output + gpio_config_t conf{}; + conf.pin_bit_mask = 1ULL << static_cast(scl_pin_); + conf.mode = GPIO_MODE_OUTPUT; + conf.pull_up_en = GPIO_PULLUP_DISABLE; + conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + conf.intr_type = GPIO_INTR_DISABLE; + + gpio_config(&conf); + + // set scl high + gpio_set_level(scl_pin, 1); + + // in total generate 9 falling-rising edges + for (auto i = 0; i < recover_scl_periods; i++) { + delayMicroseconds(half_period_usec); + gpio_set_level(scl_pin, 0); + delayMicroseconds(half_period_usec); + gpio_set_level(scl_pin, 1); + } + + delayMicroseconds(half_period_usec); +} + } // namespace i2c } // namespace esphome diff --git a/esphome/components/i2c/i2c_bus_esp_idf.h b/esphome/components/i2c/i2c_bus_esp_idf.h index c7e67145a3..9985e618f8 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.h +++ b/esphome/components/i2c/i2c_bus_esp_idf.h @@ -24,6 +24,9 @@ class IDFI2CBus : public I2CBus, public Component { void set_scl_pullup_enabled(bool scl_pullup_enabled) { scl_pullup_enabled_ = scl_pullup_enabled; } void set_frequency(uint32_t frequency) { frequency_ = frequency; } + private: + void recover(); + protected: i2c_port_t port_; bool scan_; From aea2491fa4f7e649241265ae0f2d21b697b48930 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Sep 2021 23:36:19 +0200 Subject: [PATCH 1431/1841] Bump voluptuous from 0.12.1 to 0.12.2 (#2381) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 95ca95430d..537a802e06 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -voluptuous==0.12.1 +voluptuous==0.12.2 PyYAML==5.4.1 paho-mqtt==1.5.1 colorama==0.4.4 From 52dd79691b82e32fd9e61ade165c551109de7af8 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Fri, 24 Sep 2021 14:15:22 +0200 Subject: [PATCH 1432/1841] Read unencrypted DSMR telegrams in chunks (#2382) Co-authored-by: Maurice Makaay --- esphome/components/dsmr/dsmr.cpp | 31 +++++++++++++++++++------------ esphome/components/dsmr/dsmr.h | 1 + 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index 54c4343cfe..b798fe5d44 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -20,19 +20,22 @@ void Dsmr::loop() { } void Dsmr::receive_telegram_() { - while (available()) { + int count = MAX_BYTES_PER_LOOP; + while (available() && count-- > 0) { const char c = read(); - if (c == '/') { // header: forward slash + // Find a new telegram header, i.e. forward slash. + if (c == '/') { ESP_LOGV(TAG, "Header found"); header_found_ = true; footer_found_ = false; telegram_len_ = 0; } - if (!header_found_) continue; - if (telegram_len_ >= MAX_TELEGRAM_LENGTH) { // Buffer overflow + + // Check for buffer overflow. + if (telegram_len_ >= MAX_TELEGRAM_LENGTH) { header_found_ = false; footer_found_ = false; ESP_LOGE(TAG, "Error: Message larger than buffer"); @@ -45,18 +48,22 @@ void Dsmr::receive_telegram_() { while (c == '(' && (telegram_[telegram_len_ - 1] == '\n' || telegram_[telegram_len_ - 1] == '\r')) telegram_len_--; + // Store the byte in the buffer. telegram_[telegram_len_] = c; telegram_len_++; - if (c == '!') { // footer: exclamation mark + + // Check for a footer, i.e. exlamation mark, followed by a hex checksum. + if (c == '!') { ESP_LOGV(TAG, "Footer found"); footer_found_ = true; - } else { - if (footer_found_ && c == 10) { // last \n after footer - header_found_ = false; - // Parse message - if (parse_telegram()) - return; - } + continue; + } + // Check for the end of the hex checksum, i.e. a newline. + if (footer_found_ && c == '\n') { + header_found_ = false; + // Parse the telegram and publish sensor values. + if (parse_telegram()) + return; } } } diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index dfee3b338a..4f9a66b3d0 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -17,6 +17,7 @@ namespace esphome { namespace dsmr { static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500; +static constexpr uint32_t MAX_BYTES_PER_LOOP = 50; static constexpr uint32_t POLL_TIMEOUT = 1000; using namespace ::dsmr::fields; From aec02afcdc36d0c626db727d27dd46888c2578de Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 24 Sep 2021 18:02:28 +0200 Subject: [PATCH 1433/1841] Fix clang-tidy header filter (#2385) * Fix clang-tidy header filter * Allow private members * Fix clang-tidy detections * Run clang-format * Fix remaining detections * Fix graph * Run clang-format --- .clang-tidy | 12 +- .../airthings_wave_plus.cpp | 4 +- esphome/components/am43/am43.cpp | 6 +- esphome/components/am43/cover/am43_cover.cpp | 6 +- esphome/components/anova/anova.cpp | 4 +- esphome/components/anova/anova.h | 2 +- esphome/components/api/api_frame_helper.h | 9 +- esphome/components/api/api_noise_context.h | 2 +- esphome/components/api/api_server.h | 2 +- .../atc_mithermometer/atc_mithermometer.cpp | 12 +- .../atc_mithermometer/atc_mithermometer.h | 6 +- esphome/components/ble_client/automation.h | 10 +- esphome/components/ble_client/ble_client.cpp | 28 ++--- esphome/components/ble_client/ble_client.h | 13 ++- .../components/ble_client/sensor/automation.h | 5 +- .../ble_client/sensor/ble_sensor.cpp | 12 +- .../components/ble_client/sensor/ble_sensor.h | 4 +- .../ble_client/switch/ble_switch.cpp | 4 +- .../ble_presence/ble_presence_device.h | 4 +- esphome/components/ble_scanner/ble_scanner.h | 2 +- esphome/components/daly_bms/daly_bms.cpp | 18 +-- esphome/components/daly_bms/daly_bms.h | 4 +- esphome/components/esp32/gpio_arduino.cpp | 2 +- esphome/components/esp32/gpio_arduino.h | 2 +- esphome/components/esp32/gpio_idf.cpp | 2 +- esphome/components/esp32/gpio_idf.h | 13 ++- esphome/components/esp32_ble/ble.h | 2 + esphome/components/esp32_ble/queue.h | 26 +++-- .../esp32_ble_beacon/esp32_ble_beacon.h | 4 + .../esp32_ble_server/ble_characteristic.h | 4 +- .../components/esp32_ble_server/ble_server.h | 1 + .../esp32_ble_tracker/esp32_ble_tracker.cpp | 38 +++---- .../esp32_ble_tracker/esp32_ble_tracker.h | 25 +++-- esphome/components/esp32_ble_tracker/queue.h | 26 ++--- .../components/esp32_camera/esp32_camera.h | 1 + .../esp32_improv/esp32_improv_component.cpp | 4 +- .../esp32_improv/esp32_improv_component.h | 3 +- esphome/components/esp8266/gpio.cpp | 2 +- esphome/components/esp8266/gpio.h | 2 +- .../ethernet/ethernet_component.cpp | 28 ++--- .../components/ethernet/ethernet_component.h | 7 +- .../components/fastled_base/fastled_light.h | 104 +++++++++--------- esphome/components/graph/graph.cpp | 73 ++++++------ esphome/components/graph/graph.h | 29 ++--- esphome/components/i2c/i2c_bus_arduino.cpp | 4 +- esphome/components/i2c/i2c_bus_arduino.h | 2 +- esphome/components/i2c/i2c_bus_esp_idf.cpp | 4 +- esphome/components/i2c/i2c_bus_esp_idf.h | 2 +- .../inkbird_ibsth1_mini.cpp | 4 +- .../inkbird_ibsth1_mini/inkbird_ibsth1_mini.h | 2 +- .../components/inkbird_ibsth1_mini/sensor.py | 6 +- esphome/components/ledc/ledc_output.h | 1 + esphome/components/light/addressable_light.h | 2 +- .../light/addressable_light_wrapper.h | 2 +- esphome/components/light/esp_range_view.h | 2 + esphome/components/midea/appliance_base.h | 17 +-- .../pvvx_mithermometer/pvvx_mithermometer.cpp | 12 +- .../pvvx_mithermometer/pvvx_mithermometer.h | 6 +- .../components/remote_base/midea_protocol.h | 2 +- esphome/components/remote_base/remote_base.h | 4 +- .../remote_receiver/remote_receiver_esp32.cpp | 28 ++--- .../remote_transmitter/remote_transmitter.h | 2 +- .../remote_transmitter_esp32.cpp | 8 +- esphome/components/socket/headers.h | 27 +++-- esphome/components/t6615/t6615.h | 2 +- .../uart/uart_component_esp8266.cpp | 10 +- .../components/uart/uart_component_esp8266.h | 2 +- .../uart/uart_component_esp_idf.cpp | 4 +- .../components/uart/uart_component_esp_idf.h | 2 +- .../wifi_info/wifi_info_text_sensor.h | 2 +- .../xiaomi_miscale/xiaomi_miscale.cpp | 20 ++-- .../xiaomi_miscale/xiaomi_miscale.h | 10 +- esphome/core/gpio.h | 6 +- esphome/core/hal.h | 2 +- esphome/core/helpers.h | 4 +- script/clang-tidy | 3 +- 76 files changed, 404 insertions(+), 367 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 98a1568e5a..79276f81c3 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -135,10 +135,14 @@ CheckOptions: value: 'UPPER_CASE' - key: readability-identifier-naming.ParameterCase value: 'lower_case' - - key: readability-identifier-naming.PrivateMemberPrefix - value: 'NO_PRIVATE_MEMBERS_ALWAYS_USE_PROTECTED' - - key: readability-identifier-naming.PrivateMethodPrefix - value: 'NO_PRIVATE_METHODS_ALWAYS_USE_PROTECTED' + - key: readability-identifier-naming.PrivateMemberCase + value: 'lower_case' + - key: readability-identifier-naming.PrivateMemberSuffix + value: '_' + - key: readability-identifier-naming.PrivateMethodCase + value: 'lower_case' + - key: readability-identifier-naming.PrivateMethodSuffix + value: '_' - key: readability-identifier-naming.ClassMemberCase value: 'lower_case' - key: readability-identifier-naming.ClassMemberCase diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp index a8153ae87a..0eaffbd889 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -31,7 +31,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt break; } this->handle_ = chr->handle; - this->node_state = esp32_ble_tracker::ClientState::Established; + this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; request_read_values_(); break; @@ -99,7 +99,7 @@ bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && c void AirthingsWavePlus::loop() {} void AirthingsWavePlus::update() { - if (this->node_state != esp32_ble_tracker::ClientState::Established) { + if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { if (!parent()->enabled) { ESP_LOGW(TAG, "Reconnecting to device"); parent()->set_enabled(true); diff --git a/esphome/components/am43/am43.cpp b/esphome/components/am43/am43.cpp index 2130b334be..a62e3bb6df 100644 --- a/esphome/components/am43/am43.cpp +++ b/esphome/components/am43/am43.cpp @@ -31,7 +31,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i } case ESP_GATTC_DISCONNECT_EVT: { this->logged_in_ = false; - this->node_state = espbt::ClientState::Idle; + this->node_state = espbt::ClientState::IDLE; if (this->battery_ != nullptr) this->battery_->publish_state(NAN); if (this->illuminance_ != nullptr) @@ -54,7 +54,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i break; } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - this->node_state = espbt::ClientState::Established; + this->node_state = espbt::ClientState::ESTABLISHED; this->update(); break; } @@ -93,7 +93,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i } void Am43::update() { - if (this->node_state != espbt::ClientState::Established) { + if (this->node_state != espbt::ClientState::ESTABLISHED) { ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str()); return; } diff --git a/esphome/components/am43/cover/am43_cover.cpp b/esphome/components/am43/cover/am43_cover.cpp index fd337ba17b..274c527760 100644 --- a/esphome/components/am43/cover/am43_cover.cpp +++ b/esphome/components/am43/cover/am43_cover.cpp @@ -24,7 +24,7 @@ void Am43Component::setup() { } void Am43Component::loop() { - if (this->node_state == espbt::ClientState::Established && !this->logged_in_) { + if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) { auto packet = this->encoder_->get_send_pin_request(this->pin_); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, @@ -46,7 +46,7 @@ CoverTraits Am43Component::get_traits() { } void Am43Component::control(const CoverCall &call) { - if (this->node_state != espbt::ClientState::Established) { + if (this->node_state != espbt::ClientState::ESTABLISHED) { ESP_LOGW(TAG, "[%s] Cannot send cover control, not connected", this->get_name().c_str()); return; } @@ -98,7 +98,7 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - this->node_state = espbt::ClientState::Established; + this->node_state = espbt::ClientState::ESTABLISHED; break; } case ESP_GATTC_NOTIFY_EVT: { diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index 8e0724ad13..5d9afddc74 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -72,7 +72,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ break; } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - this->node_state = espbt::ClientState::Established; + this->node_state = espbt::ClientState::ESTABLISHED; this->current_request_ = 0; this->update(); break; @@ -129,7 +129,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ void Anova::set_unit_of_measurement(const char *unit) { this->fahrenheit_ = !strncmp(unit, "f", 1); } void Anova::update() { - if (this->node_state != espbt::ClientState::Established) + if (this->node_state != espbt::ClientState::ESTABLISHED) return; if (this->current_request_ < 2) { diff --git a/esphome/components/anova/anova.h b/esphome/components/anova/anova.h index 554024e389..2e6910f326 100644 --- a/esphome/components/anova/anova.h +++ b/esphome/components/anova/anova.h @@ -27,7 +27,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode esp_ble_gattc_cb_param_t *param) override; void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } - climate::ClimateTraits traits() { + climate::ClimateTraits traits() override { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); traits.set_supports_heat_mode(true); diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 44df629b2f..7fdb26fd40 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -1,7 +1,8 @@ #pragma once #include -#include #include +#include +#include #include "esphome/core/defines.h" @@ -75,8 +76,8 @@ class APIFrameHelper { class APINoiseFrameHelper : public APIFrameHelper { public: APINoiseFrameHelper(std::unique_ptr socket, std::shared_ptr ctx) - : socket_(std::move(socket)), ctx_(ctx) {} - ~APINoiseFrameHelper(); + : socket_(std::move(socket)), ctx_(std::move(std::move(ctx))) {} + ~APINoiseFrameHelper() override; APIError init() override; APIError loop() override; APIError read_packet(ReadPacketBuffer *buffer) override; @@ -136,7 +137,7 @@ class APINoiseFrameHelper : public APIFrameHelper { class APIPlaintextFrameHelper : public APIFrameHelper { public: APIPlaintextFrameHelper(std::unique_ptr socket) : socket_(std::move(socket)) {} - ~APIPlaintextFrameHelper() = default; + ~APIPlaintextFrameHelper() override = default; APIError init() override; APIError loop() override; APIError read_packet(ReadPacketBuffer *buffer) override; diff --git a/esphome/components/api/api_noise_context.h b/esphome/components/api/api_noise_context.h index fba6b65a26..324e69d945 100644 --- a/esphome/components/api/api_noise_context.h +++ b/esphome/components/api/api_noise_context.h @@ -11,7 +11,7 @@ using psk_t = std::array; class APINoiseContext { public: - void set_psk(psk_t psk) { psk_ = std::move(psk); } + void set_psk(psk_t psk) { psk_ = psk; } const psk_t &get_psk() const { return psk_; } protected: diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index d659f24358..056d9f54f2 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -32,7 +32,7 @@ class APIServer : public Component, public Controller { void set_reboot_timeout(uint32_t reboot_timeout); #ifdef USE_API_NOISE - void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(std::move(psk)); } + void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); } std::shared_ptr get_noise_ctx() { return noise_ctx_; } #endif // USE_API_NOISE diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.cpp b/esphome/components/atc_mithermometer/atc_mithermometer.cpp index b04d634103..42c30598ad 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.cpp +++ b/esphome/components/atc_mithermometer/atc_mithermometer.cpp @@ -25,14 +25,14 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device bool success = false; for (auto &service_data : device.get_service_datas()) { - auto res = parse_header(service_data); + auto res = parse_header_(service_data); if (!res.has_value()) { continue; } - if (!(parse_message(service_data.data, *res))) { + if (!(parse_message_(service_data.data, *res))) { continue; } - if (!(report_results(res, device.address_str()))) { + if (!(report_results_(res, device.address_str()))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) @@ -49,7 +49,7 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device return success; } -optional ATCMiThermometer::parse_header(const esp32_ble_tracker::ServiceData &service_data) { +optional ATCMiThermometer::parse_header_(const esp32_ble_tracker::ServiceData &service_data) { ParseResult result; if (!service_data.uuid.contains(0x1A, 0x18)) { ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes."); @@ -68,7 +68,7 @@ optional ATCMiThermometer::parse_header(const esp32_ble_tracker::Se return result; } -bool ATCMiThermometer::parse_message(const std::vector &message, ParseResult &result) { +bool ATCMiThermometer::parse_message_(const std::vector &message, ParseResult &result) { // Byte 0-5 mac in correct order // Byte 6-7 Temperature in uint16 // Byte 8 Humidity in percent @@ -101,7 +101,7 @@ bool ATCMiThermometer::parse_message(const std::vector &message, ParseR return true; } -bool ATCMiThermometer::report_results(const optional &result, const std::string &address) { +bool ATCMiThermometer::report_results_(const optional &result, const std::string &address) { if (!result.has_value()) { ESP_LOGVV(TAG, "report_results(): no results available."); return false; diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.h b/esphome/components/atc_mithermometer/atc_mithermometer.h index 291c1d96cd..ca079bf8c1 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.h +++ b/esphome/components/atc_mithermometer/atc_mithermometer.h @@ -36,9 +36,9 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice sensor::Sensor *battery_level_{nullptr}; sensor::Sensor *battery_voltage_{nullptr}; - optional parse_header(const esp32_ble_tracker::ServiceData &service_data); - bool parse_message(const std::vector &message, ParseResult &result); - bool report_results(const optional &result, const std::string &address); + optional parse_header_(const esp32_ble_tracker::ServiceData &service_data); + bool parse_message_(const std::vector &message, ParseResult &result); + bool report_results_(const optional &result, const std::string &address); }; } // namespace atc_mithermometer diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index d015d4019c..6c374046ba 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -11,11 +11,12 @@ class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode { public: explicit BLEClientConnectTrigger(BLEClient *parent) { parent->register_ble_node(this); } void loop() override {} - void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override { if (event == ESP_GATTC_OPEN_EVT && param->open.status == ESP_GATT_OK) this->trigger(); if (event == ESP_GATTC_SEARCH_CMPL_EVT) - this->node_state = espbt::ClientState::Established; + this->node_state = espbt::ClientState::ESTABLISHED; } }; @@ -23,11 +24,12 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode { public: explicit BLEClientDisconnectTrigger(BLEClient *parent) { parent->register_ble_node(this); } void loop() override {} - void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override { if (event == ESP_GATTC_DISCONNECT_EVT && memcmp(param->disconnect.remote_bda, this->parent_->remote_bda, 6) == 0) this->trigger(); if (event == ESP_GATTC_SEARCH_CMPL_EVT) - this->node_state = espbt::ClientState::Established; + this->node_state = espbt::ClientState::ESTABLISHED; } }; diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index f584cdf79e..8ff516d735 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -17,12 +17,12 @@ void BLEClient::setup() { ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret); this->mark_failed(); } - this->set_states(espbt::ClientState::Idle); + this->set_states_(espbt::ClientState::IDLE); this->enabled = true; } void BLEClient::loop() { - if (this->state() == espbt::ClientState::Discovered) { + if (this->state() == espbt::ClientState::DISCOVERED) { this->connect(); } for (auto *node : this->nodes_) @@ -39,11 +39,11 @@ bool BLEClient::parse_device(const espbt::ESPBTDevice &device) { return false; if (device.address_uint64() != this->address) return false; - if (this->state() != espbt::ClientState::Idle) + if (this->state() != espbt::ClientState::IDLE) return false; ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str()); - this->set_states(espbt::ClientState::Discovered); + this->set_states_(espbt::ClientState::DISCOVERED); auto addr = device.address_uint64(); this->remote_bda[0] = (addr >> 40) & 0xFF; @@ -69,7 +69,7 @@ std::string BLEClient::address_str() const { void BLEClient::set_enabled(bool enabled) { if (enabled == this->enabled) return; - if (!enabled && this->state() != espbt::ClientState::Idle) { + if (!enabled && this->state() != espbt::ClientState::IDLE) { ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str()); auto ret = esp_ble_gattc_close(this->gattc_if, this->conn_id); if (ret) { @@ -84,9 +84,9 @@ void BLEClient::connect() { auto ret = esp_ble_gattc_open(this->gattc_if, this->remote_bda, BLE_ADDR_TYPE_PUBLIC, true); if (ret) { ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret); - this->set_states(espbt::ClientState::Idle); + this->set_states_(espbt::ClientState::IDLE); } else { - this->set_states(espbt::ClientState::Connecting); + this->set_states_(espbt::ClientState::CONNECTING); } } @@ -97,7 +97,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if) return; - bool all_established = this->all_nodes_established(); + bool all_established = this->all_nodes_established_(); switch (event) { case ESP_GATTC_REG_EVT: { @@ -113,7 +113,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str()); if (param->open.status != ESP_GATT_OK) { ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status); - this->set_states(espbt::ClientState::Idle); + this->set_states_(espbt::ClientState::IDLE); break; } this->conn_id = param->open.conn_id; @@ -126,7 +126,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es case ESP_GATTC_CFG_MTU_EVT: { if (param->cfg_mtu.status != ESP_GATT_OK) { ESP_LOGW(TAG, "cfg_mtu to %s failed, status %d", this->address_str().c_str(), param->cfg_mtu.status); - this->set_states(espbt::ClientState::Idle); + this->set_states_(espbt::ClientState::IDLE); break; } ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu); @@ -141,7 +141,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es for (auto &svc : this->services_) delete svc; // NOLINT(cppcoreguidelines-owning-memory) this->services_.clear(); - this->set_states(espbt::ClientState::Idle); + this->set_states_(espbt::ClientState::IDLE); break; } case ESP_GATTC_SEARCH_RES_EVT: { @@ -160,8 +160,8 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es ESP_LOGI(TAG, " start_handle: 0x%x end_handle: 0x%x", svc->start_handle, svc->end_handle); svc->parse_characteristics(); } - this->set_states(espbt::ClientState::Connected); - this->set_state(espbt::ClientState::Established); + this->set_states_(espbt::ClientState::CONNECTED); + this->set_state(espbt::ClientState::ESTABLISHED); break; } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { @@ -192,7 +192,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es node->gattc_event_handler(event, esp_gattc_if, param); // Delete characteristics after clients have used them to save RAM. - if (!all_established && this->all_nodes_established()) { + if (!all_established && this->all_nodes_established_()) { for (auto &svc : this->services_) delete svc; // NOLINT(cppcoreguidelines-owning-memory) this->services_.clear(); diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index a69460e8b6..4a17ccb79b 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -82,10 +82,11 @@ class BLEClient : public espbt::ESPBTClient, public Component { void dump_config() override; void loop() override; - void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; bool parse_device(const espbt::ESPBTDevice &device) override; void on_scan_end() override {} - void connect(); + void connect() override; void set_address(uint64_t address) { this->address = address; } @@ -116,16 +117,16 @@ class BLEClient : public espbt::ESPBTClient, public Component { std::string address_str() const; protected: - void set_states(espbt::ClientState st) { + void set_states_(espbt::ClientState st) { this->set_state(st); for (auto &node : nodes_) node->node_state = st; } - bool all_nodes_established() { - if (this->state() != espbt::ClientState::Established) + bool all_nodes_established_() { + if (this->state() != espbt::ClientState::ESTABLISHED) return false; for (auto &node : nodes_) - if (node->node_state != espbt::ClientState::Established) + if (node->node_state != espbt::ClientState::ESTABLISHED) return false; return true; } diff --git a/esphome/components/ble_client/sensor/automation.h b/esphome/components/ble_client/sensor/automation.h index 2255a5ac55..2baaafe2ec 100644 --- a/esphome/components/ble_client/sensor/automation.h +++ b/esphome/components/ble_client/sensor/automation.h @@ -11,10 +11,11 @@ namespace ble_client { class BLESensorNotifyTrigger : public Trigger, public BLESensor { public: explicit BLESensorNotifyTrigger(BLESensor *sensor) { sensor_ = sensor; } - void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override { switch (event) { case ESP_GATTC_SEARCH_CMPL_EVT: { - this->sensor_->node_state = espbt::ClientState::Established; + this->sensor_->node_state = espbt::ClientState::ESTABLISHED; break; } case ESP_GATTC_NOTIFY_EVT: { diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index 4459163389..7a2e3ddc8b 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -71,7 +71,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status); } } else { - this->node_state = espbt::ClientState::Established; + this->node_state = espbt::ClientState::ESTABLISHED; } break; } @@ -84,7 +84,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga } if (param->read.handle == this->handle) { this->status_clear_warning(); - this->publish_state(this->parse_data(param->read.value, param->read.value_len)); + this->publish_state(this->parse_data_(param->read.value, param->read.value_len)); } break; } @@ -93,11 +93,11 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga break; ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), param->notify.handle, param->notify.value[0]); - this->publish_state(this->parse_data(param->notify.value, param->notify.value_len)); + this->publish_state(this->parse_data_(param->notify.value, param->notify.value_len)); break; } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - this->node_state = espbt::ClientState::Established; + this->node_state = espbt::ClientState::ESTABLISHED; break; } default: @@ -105,7 +105,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga } } -float BLESensor::parse_data(uint8_t *value, uint16_t value_len) { +float BLESensor::parse_data_(uint8_t *value, uint16_t value_len) { if (this->data_to_value_func_.has_value()) { std::vector data(value, value + value_len); return (*this->data_to_value_func_)(data); @@ -115,7 +115,7 @@ float BLESensor::parse_data(uint8_t *value, uint16_t value_len) { } void BLESensor::update() { - if (this->node_state != espbt::ClientState::Established) { + if (this->node_state != espbt::ClientState::ESTABLISHED) { ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str()); return; } diff --git a/esphome/components/ble_client/sensor/ble_sensor.h b/esphome/components/ble_client/sensor/ble_sensor.h index 52c9e9d5ca..d9f310b575 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.h +++ b/esphome/components/ble_client/sensor/ble_sensor.h @@ -32,13 +32,13 @@ class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClie void set_descr_uuid16(uint16_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } - void set_data_to_value(data_to_value_t &&lambda_) { this->data_to_value_func_ = lambda_; } + void set_data_to_value(data_to_value_t &&lambda) { this->data_to_value_func_ = lambda; } void set_enable_notify(bool notify) { this->notify_ = notify; } uint16_t handle; protected: uint32_t hash_base() override; - float parse_data(uint8_t *value, uint16_t value_len); + float parse_data_(uint8_t *value, uint16_t value_len); optional data_to_value_func_{}; bool notify_; espbt::ESPBTUUID service_uuid_; diff --git a/esphome/components/ble_client/switch/ble_switch.cpp b/esphome/components/ble_client/switch/ble_switch.cpp index 00593da9d6..6de5252404 100644 --- a/esphome/components/ble_client/switch/ble_switch.cpp +++ b/esphome/components/ble_client/switch/ble_switch.cpp @@ -21,10 +21,10 @@ void BLEClientSwitch::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i this->publish_state(this->parent_->enabled); break; case ESP_GATTC_OPEN_EVT: - this->node_state = espbt::ClientState::Established; + this->node_state = espbt::ClientState::ESTABLISHED; break; case ESP_GATTC_DISCONNECT_EVT: - this->node_state = espbt::ClientState::Idle; + this->node_state = espbt::ClientState::IDLE; this->publish_state(this->parent_->enabled); break; default: diff --git a/esphome/components/ble_presence/ble_presence_device.h b/esphome/components/ble_presence/ble_presence_device.h index 40cda89e62..dcccf844d2 100644 --- a/esphome/components/ble_presence/ble_presence_device.h +++ b/esphome/components/ble_presence/ble_presence_device.h @@ -93,8 +93,8 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, float get_setup_priority() const override { return setup_priority::DATA; } protected: - enum MATCH_TYPE { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID }; - MATCH_TYPE match_by_; + enum MatchType { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID }; + MatchType match_by_; bool found_{false}; diff --git a/esphome/components/ble_scanner/ble_scanner.h b/esphome/components/ble_scanner/ble_scanner.h index 542d5047ec..b330eff696 100644 --- a/esphome/components/ble_scanner/ble_scanner.h +++ b/esphome/components/ble_scanner/ble_scanner.h @@ -15,7 +15,7 @@ namespace ble_scanner { class BLEScanner : public text_sensor::TextSensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component { public: bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { - this->publish_state("{\"timestamp\":" + to_string(::time(NULL)) + + this->publish_state("{\"timestamp\":" + to_string(::time(nullptr)) + "," "\"address\":\"" + device.address_str() + diff --git a/esphome/components/daly_bms/daly_bms.cpp b/esphome/components/daly_bms/daly_bms.cpp index 19e8f12e1c..44c05f0686 100644 --- a/esphome/components/daly_bms/daly_bms.cpp +++ b/esphome/components/daly_bms/daly_bms.cpp @@ -26,25 +26,25 @@ void DalyBmsComponent::dump_config() { } void DalyBmsComponent::update() { - this->request_data(DALY_REQUEST_BATTERY_LEVEL); - this->request_data(DALY_REQUEST_MIN_MAX_VOLTAGE); - this->request_data(DALY_REQUEST_MIN_MAX_TEMPERATURE); - this->request_data(DALY_REQUEST_MOS); - this->request_data(DALY_REQUEST_STATUS); - this->request_data(DALY_REQUEST_TEMPERATURE); + this->request_data_(DALY_REQUEST_BATTERY_LEVEL); + this->request_data_(DALY_REQUEST_MIN_MAX_VOLTAGE); + this->request_data_(DALY_REQUEST_MIN_MAX_TEMPERATURE); + this->request_data_(DALY_REQUEST_MOS); + this->request_data_(DALY_REQUEST_STATUS); + this->request_data_(DALY_REQUEST_TEMPERATURE); std::vector get_battery_level_data; int available_data = this->available(); if (available_data >= DALY_FRAME_SIZE) { get_battery_level_data.resize(available_data); this->read_array(get_battery_level_data.data(), available_data); - this->decode_data(get_battery_level_data); + this->decode_data_(get_battery_level_data); } } float DalyBmsComponent::get_setup_priority() const { return setup_priority::DATA; } -void DalyBmsComponent::request_data(uint8_t data_id) { +void DalyBmsComponent::request_data_(uint8_t data_id) { uint8_t request_message[DALY_FRAME_SIZE]; request_message[0] = 0xA5; // Start Flag @@ -66,7 +66,7 @@ void DalyBmsComponent::request_data(uint8_t data_id) { this->flush(); } -void DalyBmsComponent::decode_data(std::vector data) { +void DalyBmsComponent::decode_data_(std::vector data) { auto it = data.begin(); while ((it = std::find(it, data.end(), 0xA5)) != data.end()) { diff --git a/esphome/components/daly_bms/daly_bms.h b/esphome/components/daly_bms/daly_bms.h index e4f48776dd..b5d4c8ae39 100644 --- a/esphome/components/daly_bms/daly_bms.h +++ b/esphome/components/daly_bms/daly_bms.h @@ -54,8 +54,8 @@ class DalyBmsComponent : public PollingComponent, public uart::UARTDevice { float get_setup_priority() const override; protected: - void request_data(uint8_t data_id); - void decode_data(std::vector data); + void request_data_(uint8_t data_id); + void decode_data_(std::vector data); sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *current_sensor_{nullptr}; diff --git a/esphome/components/esp32/gpio_arduino.cpp b/esphome/components/esp32/gpio_arduino.cpp index 11de1e13e6..c4bb21a0aa 100644 --- a/esphome/components/esp32/gpio_arduino.cpp +++ b/esphome/components/esp32/gpio_arduino.cpp @@ -21,7 +21,7 @@ ISRInternalGPIOPin ArduinoInternalGPIOPin::to_isr() const { return ISRInternalGPIOPin((void *) arg); } -void ArduinoInternalGPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { +void ArduinoInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { uint8_t arduino_mode = DISABLED; switch (type) { case gpio::INTERRUPT_RISING_EDGE: diff --git a/esphome/components/esp32/gpio_arduino.h b/esphome/components/esp32/gpio_arduino.h index a077723075..e88d39b1a8 100644 --- a/esphome/components/esp32/gpio_arduino.h +++ b/esphome/components/esp32/gpio_arduino.h @@ -23,7 +23,7 @@ class ArduinoInternalGPIOPin : public InternalGPIOPin { bool is_inverted() const override { return inverted_; } protected: - void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; uint8_t pin_; bool inverted_; diff --git a/esphome/components/esp32/gpio_idf.cpp b/esphome/components/esp32/gpio_idf.cpp index d662d5519a..478b28a89a 100644 --- a/esphome/components/esp32/gpio_idf.cpp +++ b/esphome/components/esp32/gpio_idf.cpp @@ -8,7 +8,7 @@ namespace esp32 { static const char *const TAG = "esp32"; -bool IDFInternalGPIOPin::isr_service_installed_ = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +bool IDFInternalGPIOPin::isr_service_installed = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) struct ISRPinArg { gpio_num_t pin; diff --git a/esphome/components/esp32/gpio_idf.h b/esphome/components/esp32/gpio_idf.h index a83c6bbc97..448151cd0f 100644 --- a/esphome/components/esp32/gpio_idf.h +++ b/esphome/components/esp32/gpio_idf.h @@ -21,7 +21,7 @@ class IDFInternalGPIOPin : public InternalGPIOPin { void pin_mode(gpio::Flags flags) override { gpio_config_t conf{}; conf.pin_bit_mask = 1ULL << static_cast(pin_); - conf.mode = flags_to_mode_(flags); + conf.mode = flags_to_mode(flags); conf.pull_up_en = flags & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE; conf.pull_down_en = flags & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE; conf.intr_type = GPIO_INTR_DISABLE; @@ -36,7 +36,7 @@ class IDFInternalGPIOPin : public InternalGPIOPin { bool is_inverted() const override { return inverted_; } protected: - static gpio_mode_t flags_to_mode_(gpio::Flags flags) { + static gpio_mode_t flags_to_mode(gpio::Flags flags) { flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)); if (flags == gpio::FLAG_NONE) { return GPIO_MODE_DISABLE; @@ -55,7 +55,7 @@ class IDFInternalGPIOPin : public InternalGPIOPin { return GPIO_MODE_DISABLE; } } - void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const override { + void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override { gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE; switch (type) { case gpio::INTERRUPT_RISING_EDGE: @@ -76,9 +76,9 @@ class IDFInternalGPIOPin : public InternalGPIOPin { } gpio_set_intr_type(pin_, idf_type); gpio_intr_enable(pin_); - if (!isr_service_installed_) { + if (!isr_service_installed) { gpio_install_isr_service(ESP_INTR_FLAG_LEVEL5); - isr_service_installed_ = true; + isr_service_installed = true; } gpio_isr_handler_add(pin_, func, arg); } @@ -87,7 +87,8 @@ class IDFInternalGPIOPin : public InternalGPIOPin { bool inverted_; gpio_drive_cap_t drive_strength_; gpio::Flags flags_; - static bool isr_service_installed_; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) + static bool isr_service_installed; }; } // namespace esp32 diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 008eba3235..0477dee070 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -19,6 +19,7 @@ namespace esphome { namespace esp32_ble { +// NOLINTNEXTLINE(modernize-use-using) typedef struct { void *peer_device; bool connected; @@ -65,6 +66,7 @@ class ESP32BLE : public Component { BLEAdvertising *advertising_; }; +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern ESP32BLE *global_ble; } // namespace esp32_ble diff --git a/esphome/components/esp32_ble/queue.h b/esphome/components/esp32_ble/queue.h index 8fb2803237..8d05eca058 100644 --- a/esphome/components/esp32_ble/queue.h +++ b/esphome/components/esp32_ble/queue.h @@ -28,33 +28,33 @@ namespace esp32_ble { template class Queue { public: - Queue() { m = xSemaphoreCreateMutex(); } + Queue() { m_ = xSemaphoreCreateMutex(); } void push(T *element) { if (element == nullptr) return; - if (xSemaphoreTake(m, 5L / portTICK_PERIOD_MS)) { - q.push(element); - xSemaphoreGive(m); + if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) { + q_.push(element); + xSemaphoreGive(m_); } } T *pop() { T *element = nullptr; - if (xSemaphoreTake(m, 5L / portTICK_PERIOD_MS)) { - if (!q.empty()) { - element = q.front(); - q.pop(); + if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) { + if (!q_.empty()) { + element = q_.front(); + q_.pop(); } - xSemaphoreGive(m); + xSemaphoreGive(m_); } return element; } protected: - std::queue q; - SemaphoreHandle_t m; + std::queue q_; + SemaphoreHandle_t m_; }; // Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop(). @@ -105,11 +105,13 @@ class BLEEvent { }; union { + // NOLINTNEXTLINE(readability-identifier-naming) struct gap_event { esp_gap_ble_cb_event_t gap_event; esp_ble_gap_cb_param_t gap_param; } gap; + // NOLINTNEXTLINE(readability-identifier-naming) struct gattc_event { esp_gattc_cb_event_t gattc_event; esp_gatt_if_t gattc_if; @@ -117,6 +119,7 @@ class BLEEvent { uint8_t data[64]; } gattc; + // NOLINTNEXTLINE(readability-identifier-naming) struct gatts_event { esp_gatts_cb_event_t gatts_event; esp_gatt_if_t gatts_if; @@ -124,6 +127,7 @@ class BLEEvent { uint8_t data[64]; } gatts; } event_; + // NOLINTNEXTLINE(readability-identifier-naming) enum ble_event_t : uint8_t { GAP, GATTC, diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h index d0ef73899c..80ad2041f2 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h @@ -9,6 +9,7 @@ namespace esphome { namespace esp32_ble_beacon { +// NOLINTNEXTLINE(modernize-use-using) typedef struct { uint8_t flags[3]; uint8_t length; @@ -17,6 +18,7 @@ typedef struct { uint16_t beacon_type; } __attribute__((packed)) esp_ble_ibeacon_head_t; +// NOLINTNEXTLINE(modernize-use-using) typedef struct { uint8_t proximity_uuid[16]; uint16_t major; @@ -24,6 +26,7 @@ typedef struct { uint8_t measured_power; } __attribute__((packed)) esp_ble_ibeacon_vendor_t; +// NOLINTNEXTLINE(modernize-use-using) typedef struct { esp_ble_ibeacon_head_t ibeacon_head; esp_ble_ibeacon_vendor_t ibeacon_vendor; @@ -50,6 +53,7 @@ class ESP32BLEBeacon : public Component { uint16_t minor_{}; }; +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern ESP32BLEBeacon *global_esp32_ble_beacon; } // namespace esp32_ble_beacon diff --git a/esphome/components/esp32_ble_server/ble_characteristic.h b/esphome/components/esp32_ble_server/ble_characteristic.h index d2467dd176..d7af3a934a 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.h +++ b/esphome/components/esp32_ble_server/ble_characteristic.h @@ -24,7 +24,7 @@ class BLEService; class BLECharacteristic { public: - BLECharacteristic(const ESPBTUUID uuid, uint32_t properties); + BLECharacteristic(ESPBTUUID uuid, uint32_t properties); void set_value(const uint8_t *data, size_t length); void set_value(std::vector value); @@ -49,7 +49,7 @@ class BLECharacteristic { void do_create(BLEService *service); void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); - void on_write(const std::function &)> &&func) { this->on_write_ = std::move(func); } + void on_write(const std::function &)> &&func) { this->on_write_ = func; } void add_descriptor(BLEDescriptor *descriptor); diff --git a/esphome/components/esp32_ble_server/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h index 7df44b4c61..9f7e8b8fc0 100644 --- a/esphome/components/esp32_ble_server/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -87,6 +87,7 @@ class BLEServer : public Component { } state_{INIT}; }; +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern BLEServer *global_ble_server; } // namespace esp32_ble_server diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 3ca250d52d..9e987a994a 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -50,29 +50,29 @@ void ESP32BLETracker::setup() { return; } - global_esp32_ble_tracker->start_scan(true); + global_esp32_ble_tracker->start_scan_(true); } void ESP32BLETracker::loop() { BLEEvent *ble_event = this->ble_events_.pop(); while (ble_event != nullptr) { if (ble_event->type_) - this->real_gattc_event_handler(ble_event->event_.gattc.gattc_event, ble_event->event_.gattc.gattc_if, - &ble_event->event_.gattc.gattc_param); + this->real_gattc_event_handler_(ble_event->event_.gattc.gattc_event, ble_event->event_.gattc.gattc_if, + &ble_event->event_.gattc.gattc_param); else - this->real_gap_event_handler(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param); + this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param); delete ble_event; // NOLINT(cppcoreguidelines-owning-memory) ble_event = this->ble_events_.pop(); } bool connecting = false; for (auto *client : this->clients_) { - if (client->state() == ClientState::Connecting || client->state() == ClientState::Discovered) + if (client->state() == ClientState::CONNECTING || client->state() == ClientState::DISCOVERED) connecting = true; } if (!connecting && xSemaphoreTake(this->scan_end_lock_, 0L)) { xSemaphoreGive(this->scan_end_lock_); - global_esp32_ble_tracker->start_scan(false); + global_esp32_ble_tracker->start_scan_(false); } if (xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) { @@ -94,7 +94,7 @@ void ESP32BLETracker::loop() { for (auto *client : this->clients_) if (client->parse_device(device)) { found = true; - if (client->state() == ClientState::Discovered) { + if (client->state() == ClientState::DISCOVERED) { esp_ble_gap_stop_scanning(); if (xSemaphoreTake(this->scan_end_lock_, 10L / portTICK_PERIOD_MS)) { xSemaphoreGive(this->scan_end_lock_); @@ -196,7 +196,7 @@ bool ESP32BLETracker::ble_setup() { return true; } -void ESP32BLETracker::start_scan(bool first) { +void ESP32BLETracker::start_scan_(bool first) { if (!xSemaphoreTake(this->scan_end_lock_, 0L)) { ESP_LOGW(TAG, "Cannot start scan!"); return; @@ -233,38 +233,38 @@ void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_ga global_esp32_ble_tracker->ble_events_.push(gap_event); } // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) -void ESP32BLETracker::real_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { +void ESP32BLETracker::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch (event) { case ESP_GAP_BLE_SCAN_RESULT_EVT: - global_esp32_ble_tracker->gap_scan_result(param->scan_rst); + global_esp32_ble_tracker->gap_scan_result_(param->scan_rst); break; case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: - global_esp32_ble_tracker->gap_scan_set_param_complete(param->scan_param_cmpl); + global_esp32_ble_tracker->gap_scan_set_param_complete_(param->scan_param_cmpl); break; case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: - global_esp32_ble_tracker->gap_scan_start_complete(param->scan_start_cmpl); + global_esp32_ble_tracker->gap_scan_start_complete_(param->scan_start_cmpl); break; case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: - global_esp32_ble_tracker->gap_scan_stop_complete(param->scan_stop_cmpl); + global_esp32_ble_tracker->gap_scan_stop_complete_(param->scan_stop_cmpl); break; default: break; } } -void ESP32BLETracker::gap_scan_set_param_complete(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) { +void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) { this->scan_set_param_failed_ = param.status; } -void ESP32BLETracker::gap_scan_start_complete(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m) { +void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m) { this->scan_start_failed_ = param.status; } -void ESP32BLETracker::gap_scan_stop_complete(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m) { +void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m) { xSemaphoreGive(this->scan_end_lock_); } -void ESP32BLETracker::gap_scan_result(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { +void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { if (xSemaphoreTake(this->scan_result_lock_, 0L)) { if (this->scan_result_index_ < 16) { @@ -283,8 +283,8 @@ void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i global_esp32_ble_tracker->ble_events_.push(gattc_event); } // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) -void ESP32BLETracker::real_gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t *param) { +void ESP32BLETracker::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) { for (auto *client : global_esp32_ble_tracker->clients_) { client->gattc_event_handler(event, gattc_if, param); } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index fc5498f91e..71885a564f 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -135,15 +135,15 @@ class ESPBTDeviceListener { enum class ClientState { // Connection is idle, no device detected. - Idle, + IDLE, // Device advertisement found. - Discovered, + DISCOVERED, // Connection in progress. - Connecting, + CONNECTING, // Initial connection established. - Connected, + CONNECTED, // The client and sub-clients have completed setup. - Established, + ESTABLISHED, }; class ESPBTClient : public ESPBTDeviceListener { @@ -185,23 +185,23 @@ class ESP32BLETracker : public Component { /// The FreeRTOS task managing the bluetooth interface. static bool ble_setup(); /// Start a single scan by setting up the parameters and doing some esp-idf calls. - void start_scan(bool first); + void start_scan_(bool first); /// Callback that will handle all GAP events and redistribute them to other callbacks. static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); - void real_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); + void real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); /// Called when a `ESP_GAP_BLE_SCAN_RESULT_EVT` event is received. - void gap_scan_result(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m); + void gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m); /// Called when a `ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT` event is received. - void gap_scan_set_param_complete(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m); + void gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m); /// Called when a `ESP_GAP_BLE_SCAN_START_COMPLETE_EVT` event is received. - void gap_scan_start_complete(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m); + void gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m); /// Called when a `ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT` event is received. - void gap_scan_stop_complete(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m); + void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m); int app_id_; /// Callback that will handle all GATTC events and redistribute them to other callbacks. static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); - void real_gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); + void real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); /// Vector of addresses that have already been printed in print_bt_device_info std::vector already_discovered_; @@ -225,6 +225,7 @@ class ESP32BLETracker : public Component { Queue ble_events_; }; +// NOLINTNEXTLINE extern ESP32BLETracker *global_esp32_ble_tracker; } // namespace esp32_ble_tracker diff --git a/esphome/components/esp32_ble_tracker/queue.h b/esphome/components/esp32_ble_tracker/queue.h index 3d38c17584..f09b2ca8d7 100644 --- a/esphome/components/esp32_ble_tracker/queue.h +++ b/esphome/components/esp32_ble_tracker/queue.h @@ -26,33 +26,33 @@ namespace esp32_ble_tracker { template class Queue { public: - Queue() { m = xSemaphoreCreateMutex(); } + Queue() { m_ = xSemaphoreCreateMutex(); } void push(T *element) { if (element == nullptr) return; - if (xSemaphoreTake(m, 5L / portTICK_PERIOD_MS)) { - q.push(element); - xSemaphoreGive(m); + if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) { + q_.push(element); + xSemaphoreGive(m_); } } T *pop() { T *element = nullptr; - if (xSemaphoreTake(m, 5L / portTICK_PERIOD_MS)) { - if (!q.empty()) { - element = q.front(); - q.pop(); + if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) { + if (!q_.empty()) { + element = q_.front(); + q_.pop(); } - xSemaphoreGive(m); + xSemaphoreGive(m_); } return element; } protected: - std::queue q; - SemaphoreHandle_t m; + std::queue q_; + SemaphoreHandle_t m_; }; // Received GAP and GATTC events are only queued, and get processed in the main loop(). @@ -87,12 +87,12 @@ class BLEEvent { }; union { - struct gap_event { + struct gap_event { // NOLINT(readability-identifier-naming) esp_gap_ble_cb_event_t gap_event; esp_ble_gap_cb_param_t gap_param; } gap; - struct gattc_event { + struct gattc_event { // NOLINT(readability-identifier-naming) esp_gattc_cb_event_t gattc_event; esp_gatt_if_t gattc_if; esp_ble_gattc_cb_param_t gattc_param; diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index 430391aa76..6246dc2f12 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -106,6 +106,7 @@ class ESP32Camera : public Component, public Nameable { uint32_t last_update_{0}; }; +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern ESP32Camera *global_esp32_camera; } // namespace esp32_camera diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index fc58fbd264..faa9ab7df6 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -126,7 +126,7 @@ void ESP32ImprovComponent::loop() { std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome"; std::vector data = improv::build_rpc_response(improv::WIFI_SETTINGS, {url}); - this->send_response(data); + this->send_response_(data); this->set_timeout("end-service", 1000, [this] { this->service_->stop(); this->set_state_(improv::STATE_STOPPED); @@ -181,7 +181,7 @@ void ESP32ImprovComponent::set_error_(improv::Error error) { } } -void ESP32ImprovComponent::send_response(std::vector &response) { +void ESP32ImprovComponent::send_response_(std::vector &response) { this->rpc_response_->set_value(response); if (this->state_ != improv::STATE_STOPPED) this->rpc_response_->notify(); diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index af39ae4748..53cda5f399 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -63,12 +63,13 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent { void set_state_(improv::State state); void set_error_(improv::Error error); - void send_response(std::vector &response); + void send_response_(std::vector &response); void process_incoming_data_(); void on_wifi_connect_timeout_(); bool check_identify_(); }; +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern ESP32ImprovComponent *global_improv_component; } // namespace esp32_improv diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index 4dbffa7f6c..cb703c18e1 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -20,7 +20,7 @@ ISRInternalGPIOPin ESP8266GPIOPin::to_isr() const { return ISRInternalGPIOPin((void *) arg); } -void ESP8266GPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { +void ESP8266GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { uint8_t arduino_mode = 0; switch (type) { case gpio::INTERRUPT_RISING_EDGE: diff --git a/esphome/components/esp8266/gpio.h b/esphome/components/esp8266/gpio.h index 465d1099ae..0474d0baa6 100644 --- a/esphome/components/esp8266/gpio.h +++ b/esphome/components/esp8266/gpio.h @@ -25,7 +25,7 @@ class ESP8266GPIOPin : public InternalGPIOPin { bool is_inverted() const override { return inverted_; } protected: - void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; uint8_t pin_; bool inverted_; diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index d03211deaf..d55db0a7d8 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -45,11 +45,11 @@ void EthernetComponent::setup() { switch (this->type_) { case ETHERNET_TYPE_LAN8720: { - memcpy(&this->eth_config, &phy_lan8720_default_ethernet_config, sizeof(eth_config_t)); + memcpy(&this->eth_config_, &phy_lan8720_default_ethernet_config, sizeof(eth_config_t)); break; } case ETHERNET_TYPE_TLK110: { - memcpy(&this->eth_config, &phy_tlk110_default_ethernet_config, sizeof(eth_config_t)); + memcpy(&this->eth_config_, &phy_tlk110_default_ethernet_config, sizeof(eth_config_t)); break; } default: { @@ -58,20 +58,20 @@ void EthernetComponent::setup() { } } - this->eth_config.phy_addr = static_cast(this->phy_addr_); - this->eth_config.clock_mode = this->clk_mode_; - this->eth_config.gpio_config = EthernetComponent::eth_phy_config_gpio_; - this->eth_config.tcpip_input = tcpip_adapter_eth_input; + this->eth_config_.phy_addr = static_cast(this->phy_addr_); + this->eth_config_.clock_mode = this->clk_mode_; + this->eth_config_.gpio_config = EthernetComponent::eth_phy_config_gpio; + this->eth_config_.tcpip_input = tcpip_adapter_eth_input; if (this->power_pin_ != nullptr) { - this->orig_power_enable_fun_ = this->eth_config.phy_power_enable; - this->eth_config.phy_power_enable = EthernetComponent::eth_phy_power_enable_; + this->orig_power_enable_fun_ = this->eth_config_.phy_power_enable; + this->eth_config_.phy_power_enable = EthernetComponent::eth_phy_power_enable; } tcpipInit(); esp_err_t err; - err = esp_eth_init(&this->eth_config); + err = esp_eth_init(&this->eth_config_); ESPHL_ERROR_CHECK(err, "ETH init error"); err = esp_eth_enable(); ESPHL_ERROR_CHECK(err, "ETH enable error"); @@ -209,11 +209,11 @@ void EthernetComponent::start_connect_() { this->connect_begin_ = millis(); this->status_set_warning(); } -void EthernetComponent::eth_phy_config_gpio_() { +void EthernetComponent::eth_phy_config_gpio() { phy_rmii_configure_data_interface_pins(); phy_rmii_smi_configure_pins(global_eth_component->mdc_pin_, global_eth_component->mdio_pin_); } -void EthernetComponent::eth_phy_power_enable_(bool enable) { +void EthernetComponent::eth_phy_power_enable(bool enable) { global_eth_component->power_pin_->digital_write(enable); // power up takes some time, datasheet says max 300µs delay(1); @@ -242,9 +242,9 @@ void EthernetComponent::dump_connect_params_() { uint8_t mac[6]; esp_eth_get_mac(mac); ESP_LOGCONFIG(TAG, " MAC Address: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - ESP_LOGCONFIG(TAG, " Is Full Duplex: %s", YESNO(this->eth_config.phy_get_duplex_mode())); - ESP_LOGCONFIG(TAG, " Link Up: %s", YESNO(this->eth_config.phy_check_link())); - ESP_LOGCONFIG(TAG, " Link Speed: %u", this->eth_config.phy_get_speed_mode() ? 100 : 10); + ESP_LOGCONFIG(TAG, " Is Full Duplex: %s", YESNO(this->eth_config_.phy_get_duplex_mode())); + ESP_LOGCONFIG(TAG, " Link Up: %s", YESNO(this->eth_config_.phy_check_link())); + ESP_LOGCONFIG(TAG, " Link Speed: %u", this->eth_config_.phy_get_speed_mode() ? 100 : 10); } void EthernetComponent::set_phy_addr(uint8_t phy_addr) { this->phy_addr_ = phy_addr; } void EthernetComponent::set_power_pin(GPIOPin *power_pin) { this->power_pin_ = power_pin; } diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 3e0c798a0c..abe1c62030 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -60,8 +60,8 @@ class EthernetComponent : public Component { void start_connect_(); void dump_connect_params_(); - static void eth_phy_config_gpio_(); - static void eth_phy_power_enable_(bool enable); + static void eth_phy_config_gpio(); + static void eth_phy_power_enable(bool enable); std::string use_address_; uint8_t phy_addr_{0}; @@ -76,10 +76,11 @@ class EthernetComponent : public Component { bool connected_{false}; EthernetComponentState state_{EthernetComponentState::STOPPED}; uint32_t connect_begin_; - eth_config_t eth_config; + eth_config_t eth_config_; eth_phy_power_enable_func orig_power_enable_fun_; }; +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern EthernetComponent *global_eth_component; } // namespace ethernet diff --git a/esphome/components/fastled_base/fastled_light.h b/esphome/components/fastled_base/fastled_light.h index 80840c3003..26f0f33d2a 100644 --- a/esphome/components/fastled_base/fastled_light.h +++ b/esphome/components/fastled_base/fastled_light.h @@ -44,33 +44,33 @@ class FastLEDLightOutput : public light::AddressableLight { CLEDController &add_leds(int num_leds) { switch (CHIPSET) { case LPD8806: { - static LPD8806Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static LPD8806Controller controller; + return add_leds(&controller, num_leds); } case WS2801: { - static WS2801Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static WS2801Controller controller; + return add_leds(&controller, num_leds); } case WS2803: { - static WS2803Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static WS2803Controller controller; + return add_leds(&controller, num_leds); } case SM16716: { - static SM16716Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static SM16716Controller controller; + return add_leds(&controller, num_leds); } case P9813: { - static P9813Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static P9813Controller controller; + return add_leds(&controller, num_leds); } case DOTSTAR: case APA102: { - static APA102Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static APA102Controller controller; + return add_leds(&controller, num_leds); } case SK9822: { - static SK9822Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static SK9822Controller controller; + return add_leds(&controller, num_leds); } } } @@ -78,33 +78,33 @@ class FastLEDLightOutput : public light::AddressableLight { template CLEDController &add_leds(int num_leds) { switch (CHIPSET) { case LPD8806: { - static LPD8806Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static LPD8806Controller controller; + return add_leds(&controller, num_leds); } case WS2801: { - static WS2801Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static WS2801Controller controller; + return add_leds(&controller, num_leds); } case WS2803: { - static WS2803Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static WS2803Controller controller; + return add_leds(&controller, num_leds); } case SM16716: { - static SM16716Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static SM16716Controller controller; + return add_leds(&controller, num_leds); } case P9813: { - static P9813Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static P9813Controller controller; + return add_leds(&controller, num_leds); } case DOTSTAR: case APA102: { - static APA102Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static APA102Controller controller; + return add_leds(&controller, num_leds); } case SK9822: { - static SK9822Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static SK9822Controller controller; + return add_leds(&controller, num_leds); } } } @@ -113,33 +113,33 @@ class FastLEDLightOutput : public light::AddressableLight { CLEDController &add_leds(int num_leds) { switch (CHIPSET) { case LPD8806: { - static LPD8806Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static LPD8806Controller controller; + return add_leds(&controller, num_leds); } case WS2801: { - static WS2801Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static WS2801Controller controller; + return add_leds(&controller, num_leds); } case WS2803: { - static WS2803Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static WS2803Controller controller; + return add_leds(&controller, num_leds); } case SM16716: { - static SM16716Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static SM16716Controller controller; + return add_leds(&controller, num_leds); } case P9813: { - static P9813Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static P9813Controller controller; + return add_leds(&controller, num_leds); } case DOTSTAR: case APA102: { - static APA102Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static APA102Controller controller; + return add_leds(&controller, num_leds); } case SK9822: { - static SK9822Controller CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static SK9822Controller controller; + return add_leds(&controller, num_leds); } } } @@ -147,30 +147,30 @@ class FastLEDLightOutput : public light::AddressableLight { #ifdef FASTLED_HAS_CLOCKLESS template class CHIPSET, uint8_t DATA_PIN, EOrder RGB_ORDER> CLEDController &add_leds(int num_leds) { - static CHIPSET CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static CHIPSET controller; + return add_leds(&controller, num_leds); } template class CHIPSET, uint8_t DATA_PIN> CLEDController &add_leds(int num_leds) { - static CHIPSET CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static CHIPSET controller; + return add_leds(&controller, num_leds); } template class CHIPSET, uint8_t DATA_PIN> CLEDController &add_leds(int num_leds) { - static CHIPSET CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static CHIPSET controller; + return add_leds(&controller, num_leds); } #endif template class CHIPSET, EOrder RGB_ORDER> CLEDController &add_leds(int num_leds) { - static CHIPSET CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static CHIPSET controller; + return add_leds(&controller, num_leds); } template class CHIPSET> CLEDController &add_leds(int num_leds) { - static CHIPSET CONTROLLER; - return add_leds(&CONTROLLER, num_leds); + static CHIPSET controller; + return add_leds(&controller, num_leds); } #ifdef FASTLED_HAS_BLOCKLESS diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index ff736b7cb7..a9daad4ab9 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -185,7 +185,7 @@ void GraphLegend::init(Graph *g) { for (auto *trace : g->traces_) { std::string txtstr = trace->get_name(); int fw, fos, fbl, fh; - this->font_label->measure(txtstr.c_str(), &fw, &fos, &fbl, &fh); + this->font_label_->measure(txtstr.c_str(), &fw, &fos, &fbl, &fh); if (fw > txtw) txtw = fw; if (fh > txth) @@ -201,7 +201,7 @@ void GraphLegend::init(Graph *g) { if (this->units_) { valstr += trace->sensor_->get_unit_of_measurement(); } - this->font_value->measure(valstr.c_str(), &fw, &fos, &fbl, &fh); + this->font_value_->measure(valstr.c_str(), &fw, &fos, &fbl, &fh); if (fw > valw) valw = fw; if (fh > valh) @@ -218,11 +218,11 @@ void GraphLegend::init(Graph *g) { uint16_t h = this->height_; DirectionType dir = this->direction_; ValuePositionType valpos = this->values_; - if (!this->font_value) { + if (!this->font_value_) { valpos = VALUE_POSITION_TYPE_NONE; } // Line sample always goes below text for compactness - this->yl = txth + (txth / 4) + lt / 2; + this->yl_ = txth + (txth / 4) + lt / 2; if (dir == DIRECTION_TYPE_AUTO) { dir = DIRECTION_TYPE_HORIZONTAL; // as default @@ -237,62 +237,62 @@ void GraphLegend::init(Graph *g) { } if (valpos == VALUE_POSITION_TYPE_BELOW) { - this->yv = txth + (txth / 4); + this->yv_ = txth + (txth / 4); if (this->lines_) - this->yv += txth / 4 + lt; + this->yv_ += txth / 4 + lt; } else if (valpos == VALUE_POSITION_TYPE_BESIDE) { - this->xv = (txtw + valw) / 2; + this->xv_ = (txtw + valw) / 2; } // If width or height is specified we divide evenly within, else we do tight-fit if (w == 0) { - this->x0 = txtw / 2; - this->xs = txtw; + this->x0_ = txtw / 2; + this->xs_ = txtw; if (valpos == VALUE_POSITION_TYPE_BELOW) { - this->xs = std::max(txtw, valw); + this->xs_ = std::max(txtw, valw); ; - this->x0 = this->xs / 2; + this->x0_ = this->xs_ / 2; } else if (valpos == VALUE_POSITION_TYPE_BESIDE) { - this->xs = txtw + valw; + this->xs_ = txtw + valw; } if (dir == DIRECTION_TYPE_VERTICAL) { - this->width_ = this->xs; + this->width_ = this->xs_; } else { - this->width_ = this->xs * n; + this->width_ = this->xs_ * n; } } else { - this->xs = w / n; - this->x0 = this->xs / 2; + this->xs_ = w / n; + this->x0_ = this->xs_ / 2; } if (h == 0) { - this->ys = txth; + this->ys_ = txth; if (valpos == VALUE_POSITION_TYPE_BELOW) { - this->ys = txth + txth / 2 + valh; + this->ys_ = txth + txth / 2 + valh; if (this->lines_) { - this->ys += lt; + this->ys_ += lt; } } else if (valpos == VALUE_POSITION_TYPE_BESIDE) { if (this->lines_) { - this->ys = std::max(txth + txth / 4 + lt + txth / 4, valh + valh / 4); + this->ys_ = std::max(txth + txth / 4 + lt + txth / 4, valh + valh / 4); } else { - this->ys = std::max(txth + txth / 4, valh + valh / 4); + this->ys_ = std::max(txth + txth / 4, valh + valh / 4); } - this->height_ = this->ys * n; + this->height_ = this->ys_ * n; } if (dir == DIRECTION_TYPE_HORIZONTAL) { - this->height_ = this->ys; + this->height_ = this->ys_; } else { - this->height_ = this->ys * n; + this->height_ = this->ys_ * n; } } else { - this->ys = h / n; + this->ys_ = h / n; } if (dir == DIRECTION_TYPE_HORIZONTAL) { - this->ys = 0; + this->ys_ = 0; } else { - this->xs = 0; + this->xs_ = 0; } } @@ -310,38 +310,39 @@ void Graph::draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_ buff->vertical_line(x_offset + w - 1, y_offset, h, color); } - int x = x_offset + legend_->x0; + int x = x_offset + legend_->x0_; int y = y_offset; for (auto *trace : traces_) { std::string txtstr = trace->get_name(); ESP_LOGV(TAG, " %s", txtstr.c_str()); - buff->printf(x, y, legend_->font_label, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", txtstr.c_str()); + buff->printf(x, y, legend_->font_label_, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", txtstr.c_str()); if (legend_->lines_) { uint16_t thick = trace->get_line_thickness(); - for (int16_t i = 0; i < legend_->x0 * 4 / 3; i++) { + for (int16_t i = 0; i < legend_->x0_ * 4 / 3; i++) { uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { - buff->vertical_line(x - legend_->x0 * 2 / 3 + i, y + legend_->yl - thick / 2, thick, trace->get_line_color()); + buff->vertical_line(x - legend_->x0_ * 2 / 3 + i, y + legend_->yl_ - thick / 2, thick, + trace->get_line_color()); } } } if (legend_->values_ != VALUE_POSITION_TYPE_NONE) { - int xv = x + legend_->xv; - int yv = y + legend_->yv; + int xv = x + legend_->xv_; + int yv = y + legend_->yv_; std::stringstream ss; ss << std::fixed << std::setprecision(trace->sensor_->get_accuracy_decimals()) << trace->sensor_->get_state(); std::string valstr = ss.str(); if (legend_->units_) { valstr += trace->sensor_->get_unit_of_measurement(); } - buff->printf(xv, yv, legend_->font_value, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", valstr.c_str()); + buff->printf(xv, yv, legend_->font_value_, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", valstr.c_str()); ESP_LOGV(TAG, " value: %s", valstr.c_str()); } - x += legend_->xs; - y += legend_->ys; + x += legend_->xs_; + y += legend_->ys_; } } diff --git a/esphome/components/graph/graph.h b/esphome/components/graph/graph.h index 8f6e74f67a..f935917c57 100644 --- a/esphome/components/graph/graph.h +++ b/esphome/components/graph/graph.h @@ -1,8 +1,9 @@ #pragma once -#include +#include "esphome/components/sensor/sensor.h" #include "esphome/core/color.h" #include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" +#include +#include namespace esphome { @@ -43,8 +44,8 @@ enum ValuePositionType { class GraphLegend { public: void init(Graph *g); - void set_name_font(display::Font *font) { this->font_label = font; } - void set_value_font(display::Font *font) { this->font_value = font; } + void set_name_font(display::Font *font) { this->font_label_ = font; } + void set_value_font(display::Font *font) { this->font_value_ = font; } void set_width(uint32_t width) { this->width_ = width; } void set_height(uint32_t height) { this->height_ = height; } void set_border(bool val) { this->border_ = val; } @@ -61,8 +62,8 @@ class GraphLegend { ValuePositionType values_{VALUE_POSITION_TYPE_AUTO}; bool units_{true}; DirectionType direction_{DIRECTION_TYPE_AUTO}; - display::Font *font_label{nullptr}; - display::Font *font_value{nullptr}; + display::Font *font_label_{nullptr}; + display::Font *font_value_{nullptr}; // Calculated values Graph *parent_{nullptr}; // (x0) (xs,ys) (xs,ys) @@ -72,12 +73,12 @@ class GraphLegend { // (0,yl)| \-> VALUE1+units // v (top_center) // LINE_SAMPLE - int x0{0}; // X-offset to centre of label text - int xs{0}; // X spacing between labels - int ys{0}; // Y spacing between labels - int yl{0}; // Y spacing from label to line sample - int xv{0}; // X distance between label to value text - int yv{0}; // Y distance between label to value text + int x0_{0}; // X-offset to centre of label text + int xs_{0}; // X spacing between labels + int ys_{0}; // Y spacing between labels + int yl_{0}; // Y spacing from label to line sample + int xv_{0}; // X distance between label to value text + int yv_{0}; // Y distance between label to value text friend Graph; }; @@ -106,7 +107,7 @@ class HistoryData { class GraphTrace { public: void init(Graph *g); - void set_name(std::string name) { name_ = name; } + void set_name(std::string name) { name_ = std::move(name); } void set_sensor(sensor::Sensor *sensor) { sensor_ = sensor; } uint8_t get_line_thickness() { return this->line_thickness_; } void set_line_thickness(uint8_t val) { this->line_thickness_ = val; } @@ -114,7 +115,7 @@ class GraphTrace { void set_line_type(enum LineType val) { this->line_type_ = val; } Color get_line_color() { return this->line_color_; } void set_line_color(Color val) { this->line_color_ = val; } - const std::string get_name(void) { return name_; } + const std::string get_name() { return name_; } const HistoryData *get_tracedata() { return &data_; } protected: diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index b983fb7636..87dbcb66d8 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -11,7 +11,7 @@ namespace i2c { static const char *const TAG = "i2c.arduino"; void ArduinoI2CBus::setup() { - recover(); + recover_(); #ifdef USE_ESP32 static uint8_t next_bus_num = 0; if (next_bus_num == 0) @@ -92,7 +92,7 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn return ERROR_UNKNOWN; } -void ArduinoI2CBus::recover() { +void ArduinoI2CBus::recover_() { // Perform I2C bus recovery, see // https://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf // or see the linux kernel implementation, e.g. diff --git a/esphome/components/i2c/i2c_bus_arduino.h b/esphome/components/i2c/i2c_bus_arduino.h index 49be0c358c..42589dcfb7 100644 --- a/esphome/components/i2c/i2c_bus_arduino.h +++ b/esphome/components/i2c/i2c_bus_arduino.h @@ -23,7 +23,7 @@ class ArduinoI2CBus : public I2CBus, public Component { void set_frequency(uint32_t frequency) { frequency_ = frequency; } private: - void recover(); + void recover_(); protected: TwoWire *wire_; diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 5ce5d40c00..28e71ab2a0 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -14,7 +14,7 @@ void IDFI2CBus::setup() { static i2c_port_t next_port = 0; port_ = next_port++; - recover(); + recover_(); i2c_config_t conf{}; memset(&conf, 0, sizeof(conf)); @@ -144,7 +144,7 @@ ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { return ERROR_OK; } -void IDFI2CBus::recover() { +void IDFI2CBus::recover_() { // Perform I2C bus recovery, see // https://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf // or see the linux kernel implementation, e.g. diff --git a/esphome/components/i2c/i2c_bus_esp_idf.h b/esphome/components/i2c/i2c_bus_esp_idf.h index 9985e618f8..ba5fbf25c5 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.h +++ b/esphome/components/i2c/i2c_bus_esp_idf.h @@ -25,7 +25,7 @@ class IDFI2CBus : public I2CBus, public Component { void set_frequency(uint32_t frequency) { frequency_ = frequency; } private: - void recover(); + void recover_(); protected: i2c_port_t port_; diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp index 177bc76072..c01fc274f4 100644 --- a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp @@ -8,7 +8,7 @@ namespace inkbird_ibsth1_mini { static const char *const TAG = "inkbird_ibsth1_mini"; -void InkbirdIBSTH1_MINI::dump_config() { +void InkbirdIbstH1Mini::dump_config() { ESP_LOGCONFIG(TAG, "Inkbird IBS TH1 MINI"); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "External Temperature", this->external_temperature_); @@ -16,7 +16,7 @@ void InkbirdIBSTH1_MINI::dump_config() { LOG_SENSOR(" ", "Battery Level", this->battery_level_); } -bool InkbirdIBSTH1_MINI::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { +bool InkbirdIbstH1Mini::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { // The below is based on my research and reverse engineering of a single device // It is entirely possible that some of that may be inaccurate or incomplete diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h index 1b6be7afe0..bdca2d0cac 100644 --- a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h @@ -9,7 +9,7 @@ namespace esphome { namespace inkbird_ibsth1_mini { -class InkbirdIBSTH1_MINI : public Component, public esp32_ble_tracker::ESPBTDeviceListener { +class InkbirdIbstH1Mini : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; } diff --git a/esphome/components/inkbird_ibsth1_mini/sensor.py b/esphome/components/inkbird_ibsth1_mini/sensor.py index a71921f8ed..0ab9f8b3e0 100644 --- a/esphome/components/inkbird_ibsth1_mini/sensor.py +++ b/esphome/components/inkbird_ibsth1_mini/sensor.py @@ -21,14 +21,14 @@ DEPENDENCIES = ["esp32_ble_tracker"] CONF_EXTERNAL_TEMPERATURE = "external_temperature" inkbird_ibsth1_mini_ns = cg.esphome_ns.namespace("inkbird_ibsth1_mini") -InkbirdUBSTH1_MINI = inkbird_ibsth1_mini_ns.class_( - "InkbirdIBSTH1_MINI", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +InkbirdIbstH1Mini = inkbird_ibsth1_mini_ns.class_( + "InkbirdIbstH1Mini", esp32_ble_tracker.ESPBTDeviceListener, cg.Component ) CONFIG_SCHEMA = ( cv.Schema( { - cv.GenerateID(): cv.declare_id(InkbirdUBSTH1_MINI), + cv.GenerateID(): cv.declare_id(InkbirdIbstH1Mini), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, diff --git a/esphome/components/ledc/ledc_output.h b/esphome/components/ledc/ledc_output.h index e02cefd170..a78bf440a9 100644 --- a/esphome/components/ledc/ledc_output.h +++ b/esphome/components/ledc/ledc_output.h @@ -10,6 +10,7 @@ namespace esphome { namespace ledc { +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern uint8_t next_ledc_channel; class LEDCOutput : public output::FloatOutput, public Component { diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index 97f4a4687d..fea7508515 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -80,7 +80,7 @@ class AddressableLight : public LightOutput, public Component { void mark_shown_() { #ifdef USE_POWER_SUPPLY - for (auto c : *this) { + for (const auto &c : *this) { if (c.get().is_on()) { this->power_.request(); return; diff --git a/esphome/components/light/addressable_light_wrapper.h b/esphome/components/light/addressable_light_wrapper.h index 813dd43313..cd5bcabd47 100644 --- a/esphome/components/light/addressable_light_wrapper.h +++ b/esphome/components/light/addressable_light_wrapper.h @@ -9,7 +9,7 @@ namespace light { class AddressableLightWrapper : public light::AddressableLight { public: explicit AddressableLightWrapper(light::LightState *light_state) : light_state_(light_state) { - this->wrapper_state_ = new uint8_t[5]; + this->wrapper_state_ = new uint8_t[5]; // NOLINT(cppcoreguidelines-owning-memory) } int32_t size() const override { return 1; } diff --git a/esphome/components/light/esp_range_view.h b/esphome/components/light/esp_range_view.h index f4a7980543..07d18af79f 100644 --- a/esphome/components/light/esp_range_view.h +++ b/esphome/components/light/esp_range_view.h @@ -18,6 +18,7 @@ class ESPRangeView : public ESPColorSettable { public: ESPRangeView(AddressableLight *parent, int32_t begin, int32_t end) : parent_(parent), begin_(begin), end_(end < begin ? begin : end) {} + ESPRangeView(const ESPRangeView &) = default; int32_t size() const { return this->end_ - this->begin_; } ESPColorView operator[](int32_t index) const; @@ -62,6 +63,7 @@ class ESPRangeView : public ESPColorSettable { class ESPRangeIterator { public: ESPRangeIterator(const ESPRangeView &range, int32_t i) : range_(range), i_(i) {} + ESPRangeIterator(const ESPRangeIterator &) = default; ESPRangeIterator operator++() { this->i_++; return *this; diff --git a/esphome/components/midea/appliance_base.h b/esphome/components/midea/appliance_base.h index 43dd2ffb32..88a722e389 100644 --- a/esphome/components/midea/appliance_base.h +++ b/esphome/components/midea/appliance_base.h @@ -31,9 +31,10 @@ class ApplianceBase : public Component, public uart::UARTDevice, public climate: ApplianceBase() { this->base_.setStream(this); this->base_.addOnStateCallback(std::bind(&ApplianceBase::on_status_change, this)); - dudanov::midea::ApplianceBase::setLogger([](int level, const char *tag, int line, String format, va_list args) { - esp_log_vprintf_(level, tag, line, format.c_str(), args); - }); + dudanov::midea::ApplianceBase::setLogger( + [](int level, const char *tag, int line, const String &format, va_list args) { + esp_log_vprintf_(level, tag, line, format.c_str(), args); + }); } bool can_proceed() override { return this->base_.getAutoconfStatus() != dudanov::midea::AutoconfStatus::AUTOCONF_PROGRESS; @@ -46,11 +47,11 @@ class ApplianceBase : public Component, public uart::UARTDevice, public climate: void set_request_attempts(uint32_t attempts) { this->base_.setNumAttempts(attempts); } void set_beeper_feedback(bool state) { this->base_.setBeeper(state); } void set_autoconf(bool value) { this->base_.setAutoconf(value); } - void set_supported_modes(std::set modes) { this->supported_modes_ = std::move(modes); } - void set_supported_swing_modes(std::set modes) { this->supported_swing_modes_ = std::move(modes); } - void set_supported_presets(std::set presets) { this->supported_presets_ = std::move(presets); } - void set_custom_presets(std::set presets) { this->supported_custom_presets_ = std::move(presets); } - void set_custom_fan_modes(std::set modes) { this->supported_custom_fan_modes_ = std::move(modes); } + void set_supported_modes(const std::set &modes) { this->supported_modes_ = modes; } + void set_supported_swing_modes(const std::set &modes) { this->supported_swing_modes_ = modes; } + void set_supported_presets(const std::set &presets) { this->supported_presets_ = presets; } + void set_custom_presets(const std::set &presets) { this->supported_custom_presets_ = presets; } + void set_custom_fan_modes(const std::set &modes) { this->supported_custom_fan_modes_ = modes; } virtual void on_status_change() = 0; #ifdef USE_REMOTE_TRANSMITTER void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) { diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp index ff9723ab2f..a41ad1bfcb 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp @@ -25,14 +25,14 @@ bool PVVXMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &devic bool success = false; for (auto &service_data : device.get_service_datas()) { - auto res = parse_header(service_data); + auto res = parse_header_(service_data); if (!res.has_value()) { continue; } - if (!(parse_message(service_data.data, *res))) { + if (!(parse_message_(service_data.data, *res))) { continue; } - if (!(report_results(res, device.address_str()))) { + if (!(report_results_(res, device.address_str()))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) @@ -49,7 +49,7 @@ bool PVVXMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &devic return success; } -optional PVVXMiThermometer::parse_header(const esp32_ble_tracker::ServiceData &service_data) { +optional PVVXMiThermometer::parse_header_(const esp32_ble_tracker::ServiceData &service_data) { ParseResult result; if (!service_data.uuid.contains(0x1A, 0x18)) { ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes."); @@ -68,7 +68,7 @@ optional PVVXMiThermometer::parse_header(const esp32_ble_tracker::S return result; } -bool PVVXMiThermometer::parse_message(const std::vector &message, ParseResult &result) { +bool PVVXMiThermometer::parse_message_(const std::vector &message, ParseResult &result) { /* All data little endian uint8_t size; // = 19 @@ -109,7 +109,7 @@ bool PVVXMiThermometer::parse_message(const std::vector &message, Parse return true; } -bool PVVXMiThermometer::report_results(const optional &result, const std::string &address) { +bool PVVXMiThermometer::report_results_(const optional &result, const std::string &address) { if (!result.has_value()) { ESP_LOGVV(TAG, "report_results(): no results available."); return false; diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h index bb67769d4f..ad8baed35f 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h @@ -36,9 +36,9 @@ class PVVXMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevic sensor::Sensor *battery_level_{nullptr}; sensor::Sensor *battery_voltage_{nullptr}; - optional parse_header(const esp32_ble_tracker::ServiceData &service_data); - bool parse_message(const std::vector &message, ParseResult &result); - bool report_results(const optional &result, const std::string &address); + optional parse_header_(const esp32_ble_tracker::ServiceData &service_data); + bool parse_message_(const std::vector &message, ParseResult &result); + bool report_results_(const optional &result, const std::string &address); }; } // namespace pvvx_mithermometer diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h index 12916bd44d..35ea23acfb 100644 --- a/esphome/components/remote_base/midea_protocol.h +++ b/esphome/components/remote_base/midea_protocol.h @@ -92,7 +92,7 @@ using MideaDumper = RemoteReceiverDumper; template class MideaAction : public RemoteTransmitterActionBase { TEMPLATABLE_VALUE(std::vector, code) - void set_code(std::vector code) { code_ = code; } + void set_code(const std::vector &code) { code_ = code; } void encode(RemoteTransmitData *dst, Ts... x) override { MideaData data = this->code_.value(x...); data.finalize(); diff --git a/esphome/components/remote_base/remote_base.h b/esphome/components/remote_base/remote_base.h index 68cf67d175..dd6f7c3482 100644 --- a/esphome/components/remote_base/remote_base.h +++ b/esphome/components/remote_base/remote_base.h @@ -161,11 +161,11 @@ class RemoteRMTChannel { void set_clock_divider(uint8_t clock_divider) { this->clock_divider_ = clock_divider; } protected: - uint32_t from_microseconds(uint32_t us) { + uint32_t from_microseconds_(uint32_t us) { const uint32_t ticks_per_ten_us = 80000000u / this->clock_divider_ / 100000u; return us * ticks_per_ten_us / 10; } - uint32_t to_microseconds(uint32_t ticks) { + uint32_t to_microseconds_(uint32_t ticks) { const uint32_t ticks_per_ten_us = 80000000u / this->clock_divider_ / 100000u; return (ticks * 10) / ticks_per_ten_us; } diff --git a/esphome/components/remote_receiver/remote_receiver_esp32.cpp b/esphome/components/remote_receiver/remote_receiver_esp32.cpp index bec2af6718..dde9b843c9 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp32.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp32.cpp @@ -20,9 +20,9 @@ void RemoteReceiverComponent::setup() { rmt.rx_config.filter_en = false; } else { rmt.rx_config.filter_en = true; - rmt.rx_config.filter_ticks_thresh = this->from_microseconds(this->filter_us_); + rmt.rx_config.filter_ticks_thresh = this->from_microseconds_(this->filter_us_); } - rmt.rx_config.idle_threshold = this->from_microseconds(this->idle_us_); + rmt.rx_config.idle_threshold = this->from_microseconds_(this->idle_us_); esp_err_t error = rmt_config(&rmt); if (error != ESP_OK) { @@ -90,14 +90,14 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { ESP_LOGVV(TAG, "START:"); for (size_t i = 0; i < len; i++) { if (item[i].level0) { - ESP_LOGVV(TAG, "%u A: ON %uus (%u ticks)", i, this->to_microseconds(item[i].duration0), item[i].duration0); + ESP_LOGVV(TAG, "%u A: ON %uus (%u ticks)", i, this->to_microseconds_(item[i].duration0), item[i].duration0); } else { - ESP_LOGVV(TAG, "%u A: OFF %uus (%u ticks)", i, this->to_microseconds(item[i].duration0), item[i].duration0); + ESP_LOGVV(TAG, "%u A: OFF %uus (%u ticks)", i, this->to_microseconds_(item[i].duration0), item[i].duration0); } if (item[i].level1) { - ESP_LOGVV(TAG, "%u B: ON %uus (%u ticks)", i, this->to_microseconds(item[i].duration1), item[i].duration1); + ESP_LOGVV(TAG, "%u B: ON %uus (%u ticks)", i, this->to_microseconds_(item[i].duration1), item[i].duration1); } else { - ESP_LOGVV(TAG, "%u B: OFF %uus (%u ticks)", i, this->to_microseconds(item[i].duration1), item[i].duration1); + ESP_LOGVV(TAG, "%u B: OFF %uus (%u ticks)", i, this->to_microseconds_(item[i].duration1), item[i].duration1); } } ESP_LOGVV(TAG, "\n"); @@ -111,16 +111,16 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { } else { if (prev_length > 0) { if (prev_level) { - this->temp_.push_back(this->to_microseconds(prev_length) * multiplier); + this->temp_.push_back(this->to_microseconds_(prev_length) * multiplier); } else { - this->temp_.push_back(-int32_t(this->to_microseconds(prev_length)) * multiplier); + this->temp_.push_back(-int32_t(this->to_microseconds_(prev_length)) * multiplier); } } prev_level = bool(item[i].level0); prev_length = item[i].duration0; } - if (this->to_microseconds(prev_length) > this->idle_us_) { + if (this->to_microseconds_(prev_length) > this->idle_us_) { break; } @@ -131,24 +131,24 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { } else { if (prev_length > 0) { if (prev_level) { - this->temp_.push_back(this->to_microseconds(prev_length) * multiplier); + this->temp_.push_back(this->to_microseconds_(prev_length) * multiplier); } else { - this->temp_.push_back(-int32_t(this->to_microseconds(prev_length)) * multiplier); + this->temp_.push_back(-int32_t(this->to_microseconds_(prev_length)) * multiplier); } } prev_level = bool(item[i].level1); prev_length = item[i].duration1; } - if (this->to_microseconds(prev_length) > this->idle_us_) { + if (this->to_microseconds_(prev_length) > this->idle_us_) { break; } } if (prev_length > 0) { if (prev_level) { - this->temp_.push_back(this->to_microseconds(prev_length) * multiplier); + this->temp_.push_back(this->to_microseconds_(prev_length) * multiplier); } else { - this->temp_.push_back(-int32_t(this->to_microseconds(prev_length)) * multiplier); + this->temp_.push_back(-int32_t(this->to_microseconds_(prev_length)) * multiplier); } } } diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index d05942de3b..733ac5e50d 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -35,7 +35,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, #endif #ifdef USE_ESP32 - void configure_rmt(); + void configure_rmt_(); uint32_t current_carrier_frequency_{UINT32_MAX}; bool initialized_{false}; diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index a1f7663a24..500d7193f3 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -9,7 +9,7 @@ namespace remote_transmitter { static const char *const TAG = "remote_transmitter"; -void RemoteTransmitterComponent::setup() { this->configure_rmt(); } +void RemoteTransmitterComponent::setup() { this->configure_rmt_(); } void RemoteTransmitterComponent::dump_config() { ESP_LOGCONFIG(TAG, "Remote Transmitter..."); @@ -27,7 +27,7 @@ void RemoteTransmitterComponent::dump_config() { } } -void RemoteTransmitterComponent::configure_rmt() { +void RemoteTransmitterComponent::configure_rmt_() { rmt_config_t c{}; this->config_rmt(c); @@ -77,7 +77,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen if (this->current_carrier_frequency_ != this->temp_.get_carrier_frequency()) { this->current_carrier_frequency_ = this->temp_.get_carrier_frequency(); - this->configure_rmt(); + this->configure_rmt_(); } this->rmt_temp_.clear(); @@ -89,7 +89,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen bool level = val >= 0; if (!level) val = -val; - val = this->from_microseconds(static_cast(val)); + val = this->from_microseconds_(static_cast(val)); do { int32_t item = std::min(val, int32_t(32767)); diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h index f9697fd421..a383c0071d 100644 --- a/esphome/components/socket/headers.h +++ b/esphome/components/socket/headers.h @@ -7,10 +7,10 @@ #ifdef USE_SOCKET_IMPL_LWIP_TCP #define LWIP_INTERNAL -#include #include "lwip/inet.h" -#include -#include +#include +#include +#include /* Address families. */ #define AF_UNSPEC 0 @@ -45,9 +45,10 @@ #define SOL_SOCKET 0xfff /* options for socket level */ -typedef uint8_t sa_family_t; -typedef uint16_t in_port_t; +using sa_family_t = uint8_t; +using in_port_t = uint16_t; +// NOLINTNEXTLINE(readability-identifier-naming) struct sockaddr_in { uint8_t sin_len; sa_family_t sin_family; @@ -57,6 +58,7 @@ struct sockaddr_in { char sin_zero[SIN_ZERO_LEN]; }; +// NOLINTNEXTLINE(readability-identifier-naming) struct sockaddr_in6 { uint8_t sin6_len; /* length of this structure */ sa_family_t sin6_family; /* AF_INET6 */ @@ -66,12 +68,14 @@ struct sockaddr_in6 { uint32_t sin6_scope_id; /* Set of interfaces for scope */ }; +// NOLINTNEXTLINE(readability-identifier-naming) struct sockaddr { uint8_t sa_len; sa_family_t sa_family; char sa_data[14]; }; +// NOLINTNEXTLINE(readability-identifier-naming) struct sockaddr_storage { uint8_t s2_len; sa_family_t ss_family; @@ -79,8 +83,9 @@ struct sockaddr_storage { uint32_t s2_data2[3]; uint32_t s2_data3[3]; }; -typedef uint32_t socklen_t; +using socklen_t = uint32_t; +// NOLINTNEXTLINE(readability-identifier-naming) struct iovec { void *iov_base; size_t iov_len; @@ -106,13 +111,13 @@ struct iovec { #ifdef USE_SOCKET_IMPL_BSD_SOCKETS -#include -#include +#include +#include #include +#include +#include #include #include -#include -#include #ifdef USE_ARDUINO // arduino-esp32 declares a global var called INADDR_NONE which is replaced @@ -121,7 +126,7 @@ struct iovec { #undef INADDR_NONE #endif // not defined for ESP32 -typedef uint32_t socklen_t; +using socklen_t = uint32_t; #define ESPHOME_INADDR_ANY ((uint32_t) 0x00000000UL) #define ESPHOME_INADDR_NONE ((uint32_t) 0xFFFFFFFFUL) diff --git a/esphome/components/t6615/t6615.h b/esphome/components/t6615/t6615.h index a075685023..fb53032e8d 100644 --- a/esphome/components/t6615/t6615.h +++ b/esphome/components/t6615/t6615.h @@ -35,7 +35,7 @@ class T6615Component : public PollingComponent, public uart::UARTDevice { void send_ppm_command_(); T6615Command command_ = T6615Command::NONE; - unsigned long command_time_ = 0; + uint32_t command_time_ = 0; sensor::Sensor *co2_sensor_{nullptr}; }; diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index 2188a4a4bc..973306cde2 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -13,7 +13,7 @@ namespace esphome { namespace uart { static const char *const TAG = "uart.arduino_esp8266"; -bool ESP8266UartComponent::serial0InUse = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +bool ESP8266UartComponent::serial0_in_use = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) uint32_t ESP8266UartComponent::get_config() { uint32_t config = 0; @@ -55,7 +55,7 @@ void ESP8266UartComponent::setup() { // is 1 we still want to use Serial. SerialConfig config = static_cast(get_config()); - if (!ESP8266UartComponent::serial0InUse && (tx_pin_ == nullptr || tx_pin_->get_pin() == 1) && + if (!ESP8266UartComponent::serial0_in_use && (tx_pin_ == nullptr || tx_pin_->get_pin() == 1) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 3) #ifdef USE_LOGGER // we will use UART0 if logger isn't using it in swapped mode @@ -66,8 +66,8 @@ void ESP8266UartComponent::setup() { this->hw_serial_ = &Serial; this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); - ESP8266UartComponent::serial0InUse = true; - } else if (!ESP8266UartComponent::serial0InUse && (tx_pin_ == nullptr || tx_pin_->get_pin() == 15) && + ESP8266UartComponent::serial0_in_use = true; + } else if (!ESP8266UartComponent::serial0_in_use && (tx_pin_ == nullptr || tx_pin_->get_pin() == 15) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 13) #ifdef USE_LOGGER // we will use UART0 swapped if logger isn't using it in regular mode @@ -79,7 +79,7 @@ void ESP8266UartComponent::setup() { this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); this->hw_serial_->swap(); - ESP8266UartComponent::serial0InUse = true; + ESP8266UartComponent::serial0_in_use = true; } else if ((tx_pin_ == nullptr || tx_pin_->get_pin() == 2) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 8)) { this->hw_serial_ = &Serial1; this->hw_serial_->begin(this->baud_rate_, config); diff --git a/esphome/components/uart/uart_component_esp8266.h b/esphome/components/uart/uart_component_esp8266.h index 921d77e4f3..eed14f3265 100644 --- a/esphome/components/uart/uart_component_esp8266.h +++ b/esphome/components/uart/uart_component_esp8266.h @@ -70,7 +70,7 @@ class ESP8266UartComponent : public UARTComponent, public Component { ESP8266SoftwareSerial *sw_serial_{nullptr}; private: - static bool serial0InUse; + static bool serial0_in_use; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) }; } // namespace uart diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 3d4a634a72..1cccd5821e 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -14,7 +14,7 @@ namespace esphome { namespace uart { static const char *const TAG = "uart.idf"; -uart_config_t IDFUARTComponent::get_config() { +uart_config_t IDFUARTComponent::get_config_() { uart_parity_t parity = UART_PARITY_DISABLE; if (this->parity_ == UART_CONFIG_PARITY_EVEN) parity = UART_PARITY_EVEN; @@ -70,7 +70,7 @@ void IDFUARTComponent::setup() { xSemaphoreTake(this->lock_, portMAX_DELAY); - uart_config_t uart_config = this->get_config(); + uart_config_t uart_config = this->get_config_(); esp_err_t err = uart_param_config(this->uart_num_, &uart_config); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err)); diff --git a/esphome/components/uart/uart_component_esp_idf.h b/esphome/components/uart/uart_component_esp_idf.h index 68cceafda2..27fb80d2cc 100644 --- a/esphome/components/uart/uart_component_esp_idf.h +++ b/esphome/components/uart/uart_component_esp_idf.h @@ -26,7 +26,7 @@ class IDFUARTComponent : public UARTComponent, public Component { protected: void check_logger_conflict() override; uart_port_t uart_num_; - uart_config_t get_config(); + uart_config_t get_config_(); SemaphoreHandle_t lock_; bool has_peek_{false}; diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index b2f37de363..5b54451ed0 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -13,7 +13,7 @@ class IPAddressWiFiInfo : public Component, public text_sensor::TextSensor { auto ip = wifi::global_wifi_component->wifi_sta_ip(); if (ip != this->last_ip_) { this->last_ip_ = ip; - this->publish_state(ip.str().c_str()); + this->publish_state(ip.str()); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp index 4587045136..de77e6146b 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp @@ -23,16 +23,16 @@ bool XiaomiMiscale::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { bool success = false; for (auto &service_data : device.get_service_datas()) { - auto res = parse_header(service_data); + auto res = parse_header_(service_data); if (!res.has_value()) { continue; } - if (!(parse_message(service_data.data, *res))) { + if (!(parse_message_(service_data.data, *res))) { continue; } - if (!(report_results(res, device.address_str()))) { + if (!(report_results_(res, device.address_str()))) { continue; } @@ -49,7 +49,7 @@ bool XiaomiMiscale::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return success; } -optional XiaomiMiscale::parse_header(const esp32_ble_tracker::ServiceData &service_data) { +optional XiaomiMiscale::parse_header_(const esp32_ble_tracker::ServiceData &service_data) { ParseResult result; if (service_data.uuid == esp32_ble_tracker::ESPBTUUID::from_uint16(0x181D) && service_data.data.size() == 10) { result.version = 1; @@ -65,15 +65,15 @@ optional XiaomiMiscale::parse_header(const esp32_ble_tracker::Servi return result; } -bool XiaomiMiscale::parse_message(const std::vector &message, ParseResult &result) { +bool XiaomiMiscale::parse_message_(const std::vector &message, ParseResult &result) { if (result.version == 1) { - return parse_message_V1(message, result); + return parse_message_v1_(message, result); } else { - return parse_message_V2(message, result); + return parse_message_v2_(message, result); } } -bool XiaomiMiscale::parse_message_V1(const std::vector &message, ParseResult &result) { +bool XiaomiMiscale::parse_message_v1_(const std::vector &message, ParseResult &result) { // message size is checked in parse_header // 1-2 Weight (MISCALE 181D) // 3-4 Years (MISCALE 181D) @@ -97,7 +97,7 @@ bool XiaomiMiscale::parse_message_V1(const std::vector &message, ParseR return true; } -bool XiaomiMiscale::parse_message_V2(const std::vector &message, ParseResult &result) { +bool XiaomiMiscale::parse_message_v2_(const std::vector &message, ParseResult &result) { // message size is checked in parse_header // 2-3 Years (MISCALE 2 181B) // 4 month (MISCALE 2 181B) @@ -138,7 +138,7 @@ bool XiaomiMiscale::parse_message_V2(const std::vector &message, ParseR return true; } -bool XiaomiMiscale::report_results(const optional &result, const std::string &address) { +bool XiaomiMiscale::report_results_(const optional &result, const std::string &address) { if (!result.has_value()) { ESP_LOGVV(TAG, "report_results(): no results available."); return false; diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.h b/esphome/components/xiaomi_miscale/xiaomi_miscale.h index 3c958afc03..3e51405ddc 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.h +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.h @@ -30,11 +30,11 @@ class XiaomiMiscale : public Component, public esp32_ble_tracker::ESPBTDeviceLis sensor::Sensor *weight_{nullptr}; sensor::Sensor *impedance_{nullptr}; - optional parse_header(const esp32_ble_tracker::ServiceData &service_data); - bool parse_message(const std::vector &message, ParseResult &result); - bool parse_message_V1(const std::vector &message, ParseResult &result); - bool parse_message_V2(const std::vector &message, ParseResult &result); - bool report_results(const optional &result, const std::string &address); + optional parse_header_(const esp32_ble_tracker::ServiceData &service_data); + bool parse_message_(const std::vector &message, ParseResult &result); + bool parse_message_v1_(const std::vector &message, ParseResult &result); + bool parse_message_v2_(const std::vector &message, ParseResult &result); + bool report_results_(const optional &result, const std::string &address); }; } // namespace xiaomi_miscale diff --git a/esphome/core/gpio.h b/esphome/core/gpio.h index 25d56b9020..1d3fb89805 100644 --- a/esphome/core/gpio.h +++ b/esphome/core/gpio.h @@ -6,7 +6,7 @@ namespace esphome { #define LOG_PIN(prefix, pin) \ if ((pin) != nullptr) { \ - ESP_LOGCONFIG(TAG, prefix "%s", pin->dump_summary().c_str()); \ + ESP_LOGCONFIG(TAG, prefix "%s", (pin)->dump_summary().c_str()); \ } // put GPIO flags in a namepsace to not pollute esphome namespace @@ -78,7 +78,7 @@ class ISRInternalGPIOPin { class InternalGPIOPin : public GPIOPin { public: template void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const { - this->attach_interrupt_(reinterpret_cast(func), arg, type); + this->attach_interrupt(reinterpret_cast(func), arg, type); } virtual void detach_interrupt() const = 0; @@ -92,7 +92,7 @@ class InternalGPIOPin : public GPIOPin { virtual bool is_inverted() const = 0; protected: - virtual void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const = 0; + virtual void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const = 0; }; } // namespace esphome diff --git a/esphome/core/hal.h b/esphome/core/hal.h index 2843bb9b15..a86dbf2534 100644 --- a/esphome/core/hal.h +++ b/esphome/core/hal.h @@ -37,7 +37,7 @@ void yield(); uint32_t millis(); uint32_t micros(); void delay(uint32_t ms); -void delayMicroseconds(uint32_t us); +void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming) void __attribute__((noreturn)) arch_restart(); void arch_feed_wdt(); uint32_t arch_get_cpu_cycle_count(); diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 40d94005e7..86cd3b086e 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -333,10 +333,10 @@ template T *new_buffer(size_t length) { if (psramFound()) { buffer = (T *) ps_malloc(length); } else { - buffer = new T[length]; + buffer = new T[length]; // NOLINT(cppcoreguidelines-owning-memory) } #else - buffer = new T[length]; // NOLINT + buffer = new T[length]; // NOLINT(cppcoreguidelines-owning-memory) #endif return buffer; diff --git a/script/clang-tidy b/script/clang-tidy index 2612a18c1c..6e79059372 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -17,7 +17,7 @@ import pexpect sys.path.append(os.path.dirname(__file__)) from helpers import shlex_quote, get_output, filter_grep, \ - build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata + build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata, basepath def clang_options(idedata): @@ -89,6 +89,7 @@ def run_tidy(args, options, tmpdir, queue, lock, failed_files): invocation.append('-quiet') invocation.append(os.path.abspath(path)) + invocation.append(f"--header-filter={os.path.abspath(basepath)}/.*") invocation.append('--') invocation.extend(options) invocation_s = ' '.join(shlex_quote(x) for x in invocation) From 8503e08ee66f57b4e727550615f26af3e15ce9fe Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 25 Sep 2021 09:14:07 +0200 Subject: [PATCH 1434/1841] Fix InterruptLock on ESP-IDF (#2388) --- esphome/components/dallas/__init__.py | 18 ++++++++----- esphome/components/dht/dht.cpp | 39 +++++++++++++-------------- esphome/core/helpers.cpp | 2 +- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/esphome/components/dallas/__init__.py b/esphome/components/dallas/__init__.py index 762bfdc3c3..2dbc69b8e2 100644 --- a/esphome/components/dallas/__init__.py +++ b/esphome/components/dallas/__init__.py @@ -11,13 +11,17 @@ dallas_ns = cg.esphome_ns.namespace("dallas") DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent) ESPOneWire = dallas_ns.class_("ESPOneWire") -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(DallasComponent), - cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire), - cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, - } -).extend(cv.polling_component_schema("60s")) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(DallasComponent), + cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire), + cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, + } + ).extend(cv.polling_component_schema("60s")), + # pin_mode call logs in esp-idf, but InterruptLock is active -> crash + cv.only_with_arduino, +) async def to_code(config): diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index 2539bfe5ee..2a4ccf1529 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -79,28 +79,27 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r int8_t i = 0; uint8_t data[5] = {0, 0, 0, 0, 0}; + this->pin_->digital_write(false); + this->pin_->pin_mode(gpio::FLAG_OUTPUT); + this->pin_->digital_write(false); + + if (this->model_ == DHT_MODEL_DHT11) { + delayMicroseconds(18000); + } else if (this->model_ == DHT_MODEL_SI7021) { + delayMicroseconds(500); + this->pin_->digital_write(true); + delayMicroseconds(40); + } else if (this->model_ == DHT_MODEL_DHT22_TYPE2) { + delayMicroseconds(2000); + } else if (this->model_ == DHT_MODEL_AM2302) { + delayMicroseconds(1000); + } else { + delayMicroseconds(800); + } + this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + { InterruptLock lock; - - this->pin_->digital_write(false); - this->pin_->pin_mode(gpio::FLAG_OUTPUT); - this->pin_->digital_write(false); - - if (this->model_ == DHT_MODEL_DHT11) { - delayMicroseconds(18000); - } else if (this->model_ == DHT_MODEL_SI7021) { - delayMicroseconds(500); - this->pin_->digital_write(true); - delayMicroseconds(40); - } else if (this->model_ == DHT_MODEL_DHT22_TYPE2) { - delayMicroseconds(2000); - } else if (this->model_ == DHT_MODEL_AM2302) { - delayMicroseconds(1000); - } else { - delayMicroseconds(800); - } - this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); - // Host pull up 20-40us then DHT response 80us // Start waiting for initial rising edge at the center when we // expect the DHT response (30us+40us) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index a90eb74be2..2b77c5827a 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -350,7 +350,7 @@ std::string hexencode(const uint8_t *data, uint32_t len) { IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } #endif -#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#ifdef USE_ESP32 IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } #endif From 278863d0272c3005b6e5b2f6c0ff3d1c297cb4e4 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 25 Sep 2021 09:16:32 +0200 Subject: [PATCH 1435/1841] Fix some issues with wifi driver after IDF refactor (#2387) --- .../wifi/wifi_component_esp32_arduino.cpp | 156 ++++++++++-------- .../wifi/wifi_component_esp8266.cpp | 14 +- .../wifi/wifi_component_esp_idf.cpp | 13 +- 3 files changed, 102 insertions(+), 81 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index df2692ea81..a0f8a3d114 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -24,11 +24,7 @@ namespace wifi { static const char *const TAG = "wifi_esp32"; -static bool s_sta_connected = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static bool s_sta_got_ip = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static bool s_sta_connect_not_found = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static bool s_sta_connect_error = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) bool WiFiComponent::wifi_mode_(optional sta, optional ap) { uint8_t current_mode = WiFiClass::getMode(); @@ -122,8 +118,8 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { info.netmask.addr = static_cast(manual_ip->subnet); esp_err_t dhcp_stop_ret = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); - if (dhcp_stop_ret != ESP_OK) { - ESP_LOGV(TAG, "Stopping DHCP client failed! %d", dhcp_stop_ret); + if (dhcp_stop_ret != ESP_OK && dhcp_stop_ret != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED) { + ESP_LOGV(TAG, "Stopping DHCP client failed! %s", esp_err_to_name(dhcp_stop_ret)); } esp_err_t wifi_set_info_ret = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &info); @@ -280,18 +276,14 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { this->wifi_apply_hostname_(); + s_sta_connecting = true; + err = esp_wifi_connect(); if (err != ESP_OK) { ESP_LOGW(TAG, "esp_wifi_connect failed! %d", err); return false; } - s_sta_connecting = true; - s_sta_connected = false; - s_sta_got_ip = false; - s_sta_connect_error = false; - s_sta_connect_not_found = false; - return true; } const char *get_auth_mode_str(uint8_t mode) { @@ -402,35 +394,78 @@ const char *get_disconnect_reason_str(uint8_t reason) { return "Unspecified"; } } + #if ESP_IDF_VERSION_MAJOR >= 4 -void WiFiComponent::wifi_event_callback_(arduino_event_id_t event, arduino_event_info_t info) { -#else -void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_info_t info) { -#endif + +#define ESPHOME_EVENT_ID_WIFI_READY ARDUINO_EVENT_WIFI_READY +#define ESPHOME_EVENT_ID_WIFI_SCAN_DONE ARDUINO_EVENT_WIFI_SCAN_DONE +#define ESPHOME_EVENT_ID_WIFI_STA_START ARDUINO_EVENT_WIFI_STA_START +#define ESPHOME_EVENT_ID_WIFI_STA_STOP ARDUINO_EVENT_WIFI_STA_STOP +#define ESPHOME_EVENT_ID_WIFI_STA_CONNECTED ARDUINO_EVENT_WIFI_STA_CONNECTED +#define ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED ARDUINO_EVENT_WIFI_STA_DISCONNECTED +#define ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE +#define ESPHOME_EVENT_ID_WIFI_STA_GOT_IP ARDUINO_EVENT_WIFI_STA_GOT_IP +#define ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6 ARDUINO_EVENT_WIFI_STA_GOT_IP6 +#define ESPHOME_EVENT_ID_WIFI_STA_LOST_IP ARDUINO_EVENT_WIFI_STA_LOST_IP +#define ESPHOME_EVENT_ID_WIFI_AP_START ARDUINO_EVENT_WIFI_AP_START +#define ESPHOME_EVENT_ID_WIFI_AP_STOP ARDUINO_EVENT_WIFI_AP_STOP +#define ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED ARDUINO_EVENT_WIFI_AP_STACONNECTED +#define ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED ARDUINO_EVENT_WIFI_AP_STADISCONNECTED +#define ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED +#define ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED +#define ESPHOME_EVENT_ID_WIFI_AP_GOT_IP6 ARDUINO_EVENT_WIFI_AP_GOT_IP6 +using esphome_wifi_event_id_t = arduino_event_id_t; +using esphome_wifi_event_info_t = arduino_event_info_t; + +#else // ESP_IDF_VERSION_MAJOR >= 4 + +#define ESPHOME_EVENT_ID_WIFI_READY SYSTEM_EVENT_WIFI_READY +#define ESPHOME_EVENT_ID_WIFI_SCAN_DONE SYSTEM_EVENT_SCAN_DONE +#define ESPHOME_EVENT_ID_WIFI_STA_START SYSTEM_EVENT_STA_START +#define ESPHOME_EVENT_ID_WIFI_STA_STOP SYSTEM_EVENT_STA_STOP +#define ESPHOME_EVENT_ID_WIFI_STA_CONNECTED SYSTEM_EVENT_STA_CONNECTED +#define ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED SYSTEM_EVENT_STA_DISCONNECTED +#define ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE SYSTEM_EVENT_STA_AUTHMODE_CHANGE +#define ESPHOME_EVENT_ID_WIFI_STA_GOT_IP SYSTEM_EVENT_STA_GOT_IP +#define ESPHOME_EVENT_ID_WIFI_STA_LOST_IP SYSTEM_EVENT_STA_LOST_IP +#define ESPHOME_EVENT_ID_WIFI_AP_START SYSTEM_EVENT_AP_START +#define ESPHOME_EVENT_ID_WIFI_AP_STOP SYSTEM_EVENT_AP_STOP +#define ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED SYSTEM_EVENT_AP_STACONNECTED +#define ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED SYSTEM_EVENT_AP_STADISCONNECTED +#define ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED SYSTEM_EVENT_AP_STAIPASSIGNED +#define ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED SYSTEM_EVENT_AP_PROBEREQRECVED +using esphome_wifi_event_id_t = system_event_id_t; +using esphome_wifi_event_info_t = system_event_info_t; + +#endif // !(ESP_IDF_VERSION_MAJOR >= 4) + +void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_wifi_event_info_t info) { switch (event) { - case SYSTEM_EVENT_WIFI_READY: { + case ESPHOME_EVENT_ID_WIFI_READY: { ESP_LOGV(TAG, "Event: WiFi ready"); break; } - case SYSTEM_EVENT_SCAN_DONE: { + case ESPHOME_EVENT_ID_WIFI_SCAN_DONE: { #if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_scan_done; #else auto it = info.scan_done; #endif ESP_LOGV(TAG, "Event: WiFi Scan Done status=%u number=%u scan_id=%u", it.status, it.number, it.scan_id); + + this->wifi_scan_done_callback_(); break; } - case SYSTEM_EVENT_STA_START: { + case ESPHOME_EVENT_ID_WIFI_STA_START: { ESP_LOGV(TAG, "Event: WiFi STA start"); tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, App.get_name().c_str()); break; } - case SYSTEM_EVENT_STA_STOP: { + case ESPHOME_EVENT_ID_WIFI_STA_STOP: { ESP_LOGV(TAG, "Event: WiFi STA stop"); break; } - case SYSTEM_EVENT_STA_CONNECTED: { + case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: { #if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_sta_connected; #else @@ -441,10 +476,10 @@ void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_i buf[it.ssid_len] = '\0'; ESP_LOGV(TAG, "Event: Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); - s_sta_connected = true; + break; } - case SYSTEM_EVENT_STA_DISCONNECTED: { + case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: { #if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_sta_disconnected; #else @@ -455,17 +490,26 @@ void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_i buf[it.ssid_len] = '\0'; if (it.reason == WIFI_REASON_NO_AP_FOUND) { ESP_LOGW(TAG, "Event: Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); - s_sta_connect_not_found = true; } else { ESP_LOGW(TAG, "Event: Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); - s_sta_connect_error = true; } - s_sta_connected = false; + + uint8_t reason = it.reason; + if (reason == WIFI_REASON_AUTH_EXPIRE || reason == WIFI_REASON_BEACON_TIMEOUT || + reason == WIFI_REASON_NO_AP_FOUND || reason == WIFI_REASON_ASSOC_FAIL || + reason == WIFI_REASON_HANDSHAKE_TIMEOUT) { + err_t err = esp_wifi_disconnect(); + if (err != ESP_OK) { + ESP_LOGV(TAG, "Disconnect failed: %s", esp_err_to_name(err)); + } + this->error_from_callback_ = true; + } + s_sta_connecting = false; break; } - case SYSTEM_EVENT_STA_AUTHMODE_CHANGE: { + case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: { #if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_sta_authmode_change; #else @@ -487,27 +531,26 @@ void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_i } break; } - case SYSTEM_EVENT_STA_GOT_IP: { + case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP: { auto it = info.got_ip.ip_info; ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(it.ip).c_str(), format_ip4_addr(it.gw).c_str()); - s_sta_got_ip = true; + s_sta_connecting = false; break; } - case SYSTEM_EVENT_STA_LOST_IP: { + case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: { ESP_LOGV(TAG, "Event: Lost IP"); - s_sta_got_ip = false; break; } - case SYSTEM_EVENT_AP_START: { + case ESPHOME_EVENT_ID_WIFI_AP_START: { ESP_LOGV(TAG, "Event: WiFi AP start"); break; } - case SYSTEM_EVENT_AP_STOP: { + case ESPHOME_EVENT_ID_WIFI_AP_STOP: { ESP_LOGV(TAG, "Event: WiFi AP stop"); break; } - case SYSTEM_EVENT_AP_STACONNECTED: { + case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: { #if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_sta_connected; auto &mac = it.bssid; @@ -518,7 +561,7 @@ void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_i ESP_LOGV(TAG, "Event: AP client connected MAC=%s", format_mac_addr(mac).c_str()); break; } - case SYSTEM_EVENT_AP_STADISCONNECTED: { + case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: { #if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_sta_disconnected; auto &mac = it.bssid; @@ -529,11 +572,11 @@ void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_i ESP_LOGV(TAG, "Event: AP client disconnected MAC=%s", format_mac_addr(mac).c_str()); break; } - case SYSTEM_EVENT_AP_STAIPASSIGNED: { + case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: { ESP_LOGV(TAG, "Event: AP client assigned IP"); break; } - case SYSTEM_EVENT_AP_PROBEREQRECVED: { + case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: { #if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_ap_probereqrecved; #else @@ -545,31 +588,6 @@ void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_i default: break; } - -#if ESP_IDF_VERSION_MAJOR >= 4 - if (event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) { - uint8_t reason = info.wifi_sta_disconnected.reason; -#else - if (event == SYSTEM_EVENT_STA_DISCONNECTED) { - uint8_t reason = info.disconnected.reason; -#endif - if (reason == WIFI_REASON_AUTH_EXPIRE || reason == WIFI_REASON_BEACON_TIMEOUT || - reason == WIFI_REASON_NO_AP_FOUND || reason == WIFI_REASON_ASSOC_FAIL || - reason == WIFI_REASON_HANDSHAKE_TIMEOUT) { - err_t err = esp_wifi_disconnect(); - if (err != ESP_OK) { - ESP_LOGV(TAG, "Disconnect failed: %s", esp_err_to_name(err)); - } - this->error_from_callback_ = true; - } - } -#if ESP_IDF_VERSION_MAJOR >= 4 - if (event == ARDUINO_EVENT_WIFI_SCAN_DONE) { -#else - if (event == SYSTEM_EVENT_SCAN_DONE) { -#endif - this->wifi_scan_done_callback_(); - } } void WiFiComponent::wifi_pre_setup_() { auto f = std::bind(&WiFiComponent::wifi_event_callback_, this, std::placeholders::_1, std::placeholders::_2); @@ -579,16 +597,14 @@ void WiFiComponent::wifi_pre_setup_() { this->wifi_mode_(false, false); } WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { - if (s_sta_connected && s_sta_got_ip) { + auto status = WiFiClass::status(); + if (status == WL_CONNECTED) { return WiFiSTAConnectStatus::CONNECTED; - } - if (s_sta_connect_error) { + } else if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST || status == WL_DISCONNECTED) { return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; - } - if (s_sta_connect_not_found) { + } else if (status == WL_NO_SSID_AVAIL) { return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; - } - if (s_sta_connecting) { + } else if (s_sta_connecting) { return WiFiSTAConnectStatus::CONNECTING; } return WiFiSTAConnectStatus::IDLE; diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 5dcfe7a108..2021773209 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -309,6 +309,14 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { this->wifi_apply_hostname_(); + // Reset flags, do this _before_ wifi_station_connect as the callback method + // may be called from wifi_station_connect + s_sta_connecting = true; + s_sta_connected = false; + s_sta_got_ip = false; + s_sta_connect_error = false; + s_sta_connect_not_found = false; + ETS_UART_INTR_DISABLE(); ret = wifi_station_connect(); ETS_UART_INTR_ENABLE(); @@ -325,12 +333,6 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { } } - s_sta_connecting = true; - s_sta_connected = false; - s_sta_got_ip = false; - s_sta_connect_error = false; - s_sta_connect_not_found = false; - return true; } diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 676b7f8fba..9ec3f80014 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -377,17 +377,20 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { } #endif // USE_WIFI_WPA2_EAP + // Reset flags, do this _before_ wifi_station_connect as the callback method + // may be called from wifi_station_connect + s_sta_connecting = true; + s_sta_connected = false; + s_sta_got_ip = false; + s_sta_connect_error = false; + s_sta_connect_not_found = false; + err = esp_wifi_connect(); if (err != ESP_OK) { ESP_LOGW(TAG, "esp_wifi_connect failed: %s", esp_err_to_name(err)); return false; } - s_sta_connecting = true; - s_sta_connected = false; - s_sta_got_ip = false; - s_sta_connect_error = false; - s_sta_connect_not_found = false; return true; } From d344b1ca0efcb93a74bb64adf5c5cb4e907c1f54 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 25 Sep 2021 10:04:57 +0200 Subject: [PATCH 1436/1841] Fix arduino esp32 wifi v2 (#2389) --- esphome/components/wifi/wifi_component_esp32_arduino.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index a0f8a3d114..e1332e3181 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -600,7 +600,7 @@ WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { auto status = WiFiClass::status(); if (status == WL_CONNECTED) { return WiFiSTAConnectStatus::CONNECTED; - } else if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST || status == WL_DISCONNECTED) { + } else if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST) { return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; } else if (status == WL_NO_SSID_AVAIL) { return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; From 5342edf04af2476ba88eceadbe1a2e08682ab2f3 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 25 Sep 2021 10:05:32 +0200 Subject: [PATCH 1437/1841] Misc fixes for esp-idf (#2386) --- esphome/components/logger/logger.cpp | 13 ++++++------- esphome/core/config.py | 5 +++++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 045f7059a9..4352b7e208 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -176,13 +176,12 @@ void Logger::pre_setup() { uart_num_ = UART_NUM_2; break; } - uart_config_t uart_config = { - .baud_rate = (int) baud_rate_, - .data_bits = UART_DATA_8_BITS, - .parity = UART_PARITY_DISABLE, - .stop_bits = UART_STOP_BITS_1, - .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, - }; + uart_config_t uart_config{}; + uart_config.baud_rate = (int) baud_rate_; + uart_config.data_bits = UART_DATA_8_BITS; + uart_config.parity = UART_PARITY_DISABLE; + uart_config.stop_bits = UART_STOP_BITS_1; + uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; uart_param_config(uart_num_, &uart_config); const int uart_buffer_size = tx_buffer_size_; // Install UART driver using an event queue here diff --git a/esphome/core/config.py b/esphome/core/config.py index 71add56b13..bbdfcf124c 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -247,6 +247,11 @@ async def _add_automations(config): @coroutine_with_priority(100.0) async def to_code(config): cg.add_global(cg.global_ns.namespace("esphome").using) + # These can be used by user lambdas, put them to default scope + cg.add_global(cg.RawExpression("using std::isnan")) + cg.add_global(cg.RawExpression("using std::min")) + cg.add_global(cg.RawExpression("using std::max")) + cg.add( cg.App.pre_setup( config[CONF_NAME], From 95a6715b2b3ebc30d31430b24beb888583a1f913 Mon Sep 17 00:00:00 2001 From: rbaron Date: Sat, 25 Sep 2021 13:16:27 +0200 Subject: [PATCH 1438/1841] Adds light sensor support for b-parasites (#2391) --- esphome/components/b_parasite/b_parasite.cpp | 20 ++++++++++++++++++++ esphome/components/b_parasite/b_parasite.h | 2 ++ esphome/components/b_parasite/sensor.py | 10 ++++++++++ tests/test2.yaml | 2 ++ 4 files changed, 34 insertions(+) diff --git a/esphome/components/b_parasite/b_parasite.cpp b/esphome/components/b_parasite/b_parasite.cpp index bc1463fd1c..ee12226977 100644 --- a/esphome/components/b_parasite/b_parasite.cpp +++ b/esphome/components/b_parasite/b_parasite.cpp @@ -14,6 +14,7 @@ void BParasite::dump_config() { LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Soil Moisture", this->soil_moisture_); + LOG_SENSOR(" ", "Illuminance", this->illuminance_); } bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { @@ -36,6 +37,15 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { const auto &data = service_data.data; + const uint8_t protocol_version = data[0] >> 4; + if (protocol_version != 1) { + ESP_LOGE(TAG, "Unsupported protocol version: %u", protocol_version); + return false; + } + + // Some b-parasite versions have an (optional) illuminance sensor. + bool has_illuminance = data[0] & 0x1; + // Counter for deduplicating messages. uint8_t counter = data[1] & 0x0f; if (last_processed_counter_ == counter) { @@ -59,6 +69,9 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { uint16_t soil_moisture = data[8] << 8 | data[9]; float moisture_percent = (100.0f * soil_moisture) / (1 << 16); + // Ambient light in lux. + float illuminance = has_illuminance ? data[16] << 8 | data[17] : 0.0f; + if (battery_voltage_ != nullptr) { battery_voltage_->publish_state(battery_voltage); } @@ -71,6 +84,13 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (soil_moisture_ != nullptr) { soil_moisture_->publish_state(moisture_percent); } + if (illuminance_ != nullptr) { + if (has_illuminance) { + illuminance_->publish_state(illuminance); + } else { + ESP_LOGE(TAG, "No lux information is present in the BLE packet"); + } + } last_processed_counter_ = counter; return true; diff --git a/esphome/components/b_parasite/b_parasite.h b/esphome/components/b_parasite/b_parasite.h index bdd9a01b83..70ee4ab23c 100644 --- a/esphome/components/b_parasite/b_parasite.h +++ b/esphome/components/b_parasite/b_parasite.h @@ -22,6 +22,7 @@ class BParasite : public Component, public esp32_ble_tracker::ESPBTDeviceListene void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } void set_soil_moisture(sensor::Sensor *soil_moisture) { soil_moisture_ = soil_moisture; } + void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; } protected: // The received advertisement packet contains an unsigned 4 bits wrap-around counter @@ -32,6 +33,7 @@ class BParasite : public Component, public esp32_ble_tracker::ESPBTDeviceListene sensor::Sensor *temperature_{nullptr}; sensor::Sensor *humidity_{nullptr}; sensor::Sensor *soil_moisture_{nullptr}; + sensor::Sensor *illuminance_{nullptr}; }; } // namespace b_parasite diff --git a/esphome/components/b_parasite/sensor.py b/esphome/components/b_parasite/sensor.py index 46ed64337f..d51c48c602 100644 --- a/esphome/components/b_parasite/sensor.py +++ b/esphome/components/b_parasite/sensor.py @@ -5,14 +5,17 @@ from esphome.const import ( CONF_BATTERY_VOLTAGE, CONF_HUMIDITY, CONF_ID, + CONF_ILLUMINANCE, CONF_MOISTURE, CONF_MAC_ADDRESS, CONF_TEMPERATURE, DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, + UNIT_LUX, UNIT_PERCENT, UNIT_VOLT, ) @@ -55,6 +58,12 @@ CONFIG_SCHEMA = ( device_class=DEVICE_CLASS_HUMIDITY, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( + unit_of_measurement=UNIT_LUX, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), } ) .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) @@ -74,6 +83,7 @@ async def to_code(config): (CONF_HUMIDITY, var.set_humidity), (CONF_BATTERY_VOLTAGE, var.set_battery_voltage), (CONF_MOISTURE, var.set_soil_moisture), + (CONF_ILLUMINANCE, var.set_illuminance), ]: if config_key in config: sens = await sensor.new_sensor(config[config_key]) diff --git a/tests/test2.yaml b/tests/test2.yaml index d0634e0f7b..4541fba616 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -91,6 +91,8 @@ sensor: name: 'b-parasite Soil Moisture' battery_voltage: name: 'b-parasite Battery Voltage' + illuminance: + name: 'b-parasite Illuminance' - platform: senseair id: senseair0 co2: From bdcffc7ba91c32e33b77df1e22f8868a9421eb29 Mon Sep 17 00:00:00 2001 From: irtimaled Date: Sun, 26 Sep 2021 01:27:43 -0700 Subject: [PATCH 1439/1841] fix: Setting Tuya string DP value (#2394) --- esphome/components/tuya/tuya.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index d73ba50462..4f65fa7118 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -479,7 +479,7 @@ void Tuya::set_string_datapoint_value(uint8_t datapoint_id, const std::string &v for (char const &c : value) { data.push_back(c); } - this->send_datapoint_command_(datapoint->id, datapoint->type, data); + this->send_datapoint_command_(datapoint_id, TuyaDatapointType::STRING, data); } void Tuya::set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) { From 7246f42a8ec4ba2c15ff930c69dacc4f174f6269 Mon Sep 17 00:00:00 2001 From: irtimaled Date: Sun, 26 Sep 2021 01:34:06 -0700 Subject: [PATCH 1440/1841] Tuya rgb support (#2278) Co-authored-by: Oxan van Leeuwen Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/rgbct/light.py | 2 +- esphome/components/rgbw/light.py | 10 ++- esphome/components/rgbww/light.py | 2 +- esphome/components/tuya/light/__init__.py | 12 ++- esphome/components/tuya/light/tuya_light.cpp | 91 ++++++++++++++------ esphome/components/tuya/light/tuya_light.h | 5 ++ esphome/const.py | 1 + esphome/core/helpers.cpp | 33 +++++++ esphome/core/helpers.h | 3 +- 9 files changed, 129 insertions(+), 30 deletions(-) diff --git a/esphome/components/rgbct/light.py b/esphome/components/rgbct/light.py index e525c207c7..0565057316 100644 --- a/esphome/components/rgbct/light.py +++ b/esphome/components/rgbct/light.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome.components import light, output from esphome.const import ( CONF_BLUE, + CONF_COLOR_INTERLOCK, CONF_COLOR_TEMPERATURE, CONF_GREEN, CONF_RED, @@ -16,7 +17,6 @@ CODEOWNERS = ["@jesserockz"] rgbct_ns = cg.esphome_ns.namespace("rgbct") RGBCTLightOutput = rgbct_ns.class_("RGBCTLightOutput", light.LightOutput) -CONF_COLOR_INTERLOCK = "color_interlock" CONF_WHITE_BRIGHTNESS = "white_brightness" CONFIG_SCHEMA = cv.All( diff --git a/esphome/components/rgbw/light.py b/esphome/components/rgbw/light.py index de26edf7d5..f747580f61 100644 --- a/esphome/components/rgbw/light.py +++ b/esphome/components/rgbw/light.py @@ -1,11 +1,17 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import light, output -from esphome.const import CONF_BLUE, CONF_GREEN, CONF_RED, CONF_OUTPUT_ID, CONF_WHITE +from esphome.const import ( + CONF_BLUE, + CONF_COLOR_INTERLOCK, + CONF_GREEN, + CONF_RED, + CONF_OUTPUT_ID, + CONF_WHITE, +) rgbw_ns = cg.esphome_ns.namespace("rgbw") RGBWLightOutput = rgbw_ns.class_("RGBWLightOutput", light.LightOutput) -CONF_COLOR_INTERLOCK = "color_interlock" CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend( { diff --git a/esphome/components/rgbww/light.py b/esphome/components/rgbww/light.py index c0ce85e267..35f77b154b 100644 --- a/esphome/components/rgbww/light.py +++ b/esphome/components/rgbww/light.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome.components import light, output from esphome.const import ( CONF_BLUE, + CONF_COLOR_INTERLOCK, CONF_CONSTANT_BRIGHTNESS, CONF_GREEN, CONF_RED, @@ -16,7 +17,6 @@ from esphome.const import ( rgbww_ns = cg.esphome_ns.namespace("rgbww") RGBWWLightOutput = rgbww_ns.class_("RGBWWLightOutput", light.LightOutput) -CONF_COLOR_INTERLOCK = "color_interlock" CONFIG_SCHEMA = cv.All( light.RGB_LIGHT_SCHEMA.extend( diff --git a/esphome/components/tuya/light/__init__.py b/esphome/components/tuya/light/__init__.py index f43cc570ca..6678fc47d8 100644 --- a/esphome/components/tuya/light/__init__.py +++ b/esphome/components/tuya/light/__init__.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_SWITCH_DATAPOINT, CONF_COLD_WHITE_COLOR_TEMPERATURE, CONF_WARM_WHITE_COLOR_TEMPERATURE, + CONF_COLOR_INTERLOCK, ) from .. import tuya_ns, CONF_TUYA_ID, Tuya @@ -20,6 +21,7 @@ CONF_MIN_VALUE_DATAPOINT = "min_value_datapoint" CONF_COLOR_TEMPERATURE_DATAPOINT = "color_temperature_datapoint" CONF_COLOR_TEMPERATURE_INVERT = "color_temperature_invert" CONF_COLOR_TEMPERATURE_MAX_VALUE = "color_temperature_max_value" +CONF_RGB_DATAPOINT = "rgb_datapoint" TuyaLight = tuya_ns.class_("TuyaLight", light.LightOutput, cg.Component) @@ -31,6 +33,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DIMMER_DATAPOINT): cv.uint8_t, cv.Optional(CONF_MIN_VALUE_DATAPOINT): cv.uint8_t, cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_RGB_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean, cv.Inclusive( CONF_COLOR_TEMPERATURE_DATAPOINT, "color_temperature" ): cv.uint8_t, @@ -52,7 +56,9 @@ CONFIG_SCHEMA = cv.All( ): cv.positive_time_period_milliseconds, } ).extend(cv.COMPONENT_SCHEMA), - cv.has_at_least_one_key(CONF_DIMMER_DATAPOINT, CONF_SWITCH_DATAPOINT), + cv.has_at_least_one_key( + CONF_DIMMER_DATAPOINT, CONF_SWITCH_DATAPOINT, CONF_RGB_DATAPOINT + ), ) @@ -67,6 +73,8 @@ async def to_code(config): cg.add(var.set_min_value_datapoint_id(config[CONF_MIN_VALUE_DATAPOINT])) if CONF_SWITCH_DATAPOINT in config: cg.add(var.set_switch_id(config[CONF_SWITCH_DATAPOINT])) + if CONF_RGB_DATAPOINT in config: + cg.add(var.set_rgb_id(config[CONF_RGB_DATAPOINT])) if CONF_COLOR_TEMPERATURE_DATAPOINT in config: cg.add(var.set_color_temperature_id(config[CONF_COLOR_TEMPERATURE_DATAPOINT])) cg.add(var.set_color_temperature_invert(config[CONF_COLOR_TEMPERATURE_INVERT])) @@ -87,5 +95,7 @@ async def to_code(config): config[CONF_COLOR_TEMPERATURE_MAX_VALUE] ) ) + + cg.add(var.set_color_interlock(config[CONF_COLOR_INTERLOCK])) paren = await cg.get_variable(config[CONF_TUYA_ID]) cg.add(var.set_tuya_parent(paren)) diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index 6f3adfcdfd..97f6de9bae 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -1,5 +1,6 @@ #include "esphome/core/log.h" #include "tuya_light.h" +#include "esphome/core/helpers.h" namespace esphome { namespace tuya { @@ -34,6 +35,18 @@ void TuyaLight::setup() { call.perform(); }); } + if (rgb_id_.has_value()) { + this->parent_->register_listener(*this->rgb_id_, [this](const TuyaDatapoint &datapoint) { + auto red = parse_hex(datapoint.value_string, 0, 2); + auto green = parse_hex(datapoint.value_string, 2, 2); + auto blue = parse_hex(datapoint.value_string, 4, 2); + if (red.has_value() && green.has_value() && blue.has_value()) { + auto call = this->state_->make_call(); + call.set_rgb(float(*red) / 255, float(*green) / 255, float(*blue) / 255); + call.perform(); + } + }); + } if (min_value_datapoint_id_.has_value()) { parent_->set_integer_datapoint_value(*this->min_value_datapoint_id_, this->min_value_); } @@ -45,14 +58,31 @@ void TuyaLight::dump_config() { ESP_LOGCONFIG(TAG, " Dimmer has datapoint ID %u", *this->dimmer_id_); if (this->switch_id_.has_value()) ESP_LOGCONFIG(TAG, " Switch has datapoint ID %u", *this->switch_id_); + if (this->rgb_id_.has_value()) + ESP_LOGCONFIG(TAG, " RGB has datapoint ID %u", *this->rgb_id_); } light::LightTraits TuyaLight::get_traits() { auto traits = light::LightTraits(); if (this->color_temperature_id_.has_value() && this->dimmer_id_.has_value()) { - traits.set_supported_color_modes({light::ColorMode::COLOR_TEMPERATURE}); + if (this->rgb_id_.has_value()) { + if (this->color_interlock_) + traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::COLOR_TEMPERATURE}); + else + traits.set_supported_color_modes( + {light::ColorMode::RGB_COLOR_TEMPERATURE, light::ColorMode::COLOR_TEMPERATURE}); + } else + traits.set_supported_color_modes({light::ColorMode::COLOR_TEMPERATURE}); traits.set_min_mireds(this->cold_white_temperature_); traits.set_max_mireds(this->warm_white_temperature_); + } else if (this->rgb_id_.has_value()) { + if (this->dimmer_id_.has_value()) { + if (this->color_interlock_) + traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::WHITE}); + else + traits.set_supported_color_modes({light::ColorMode::RGB_WHITE}); + } else + traits.set_supported_color_modes({light::ColorMode::RGB}); } else if (this->dimmer_id_.has_value()) { traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS}); } else { @@ -64,38 +94,51 @@ light::LightTraits TuyaLight::get_traits() { void TuyaLight::setup_state(light::LightState *state) { state_ = state; } void TuyaLight::write_state(light::LightState *state) { - float brightness; - state->current_values_as_brightness(&brightness); + float red = 0.0f, green = 0.0f, blue = 0.0f; + float color_temperature = 0.0f, brightness = 0.0f; - if (brightness == 0.0f) { - // turning off, first try via switch (if exists), then dimmer - if (switch_id_.has_value()) { - parent_->set_boolean_datapoint_value(*this->switch_id_, false); - } else if (dimmer_id_.has_value()) { - parent_->set_integer_datapoint_value(*this->dimmer_id_, 0); + if (this->rgb_id_.has_value()) { + if (this->color_temperature_id_.has_value()) { + state->current_values_as_rgbct(&red, &green, &blue, &color_temperature, &brightness); + } else if (this->dimmer_id_.has_value()) { + state->current_values_as_rgbw(&red, &green, &blue, &brightness); + } else { + state->current_values_as_rgb(&red, &green, &blue); } - return; + } else if (this->color_temperature_id_.has_value()) { + state->current_values_as_ct(&color_temperature, &brightness); + } else { + state->current_values_as_brightness(&brightness); } - if (this->color_temperature_id_.has_value()) { - uint32_t color_temp_int = - static_cast(this->color_temperature_max_value_ * - (state->current_values.get_color_temperature() - this->cold_white_temperature_) / - (this->warm_white_temperature_ - this->cold_white_temperature_)); - if (this->color_temperature_invert_) { - color_temp_int = this->color_temperature_max_value_ - color_temp_int; + if (brightness > 0.0f || !color_interlock_) { + if (this->color_temperature_id_.has_value()) { + uint32_t color_temp_int = static_cast(color_temperature * this->color_temperature_max_value_); + if (this->color_temperature_invert_) { + color_temp_int = this->color_temperature_max_value_ - color_temp_int; + } + parent_->set_integer_datapoint_value(*this->color_temperature_id_, color_temp_int); + } + + if (this->dimmer_id_.has_value()) { + auto brightness_int = static_cast(brightness * this->max_value_); + brightness_int = std::max(brightness_int, this->min_value_); + + parent_->set_integer_datapoint_value(*this->dimmer_id_, brightness_int); } - parent_->set_integer_datapoint_value(*this->color_temperature_id_, color_temp_int); } - auto brightness_int = static_cast(brightness * this->max_value_); - brightness_int = std::max(brightness_int, this->min_value_); - - if (this->dimmer_id_.has_value()) { - parent_->set_integer_datapoint_value(*this->dimmer_id_, brightness_int); + if (brightness == 0.0f || !color_interlock_) { + if (this->rgb_id_.has_value()) { + char buffer[7]; + sprintf(buffer, "%02X%02X%02X", int(red * 255), int(green * 255), int(blue * 255)); + std::string value = buffer; + this->parent_->set_string_datapoint_value(*this->rgb_id_, value); + } } + if (this->switch_id_.has_value()) { - parent_->set_boolean_datapoint_value(*this->switch_id_, true); + parent_->set_boolean_datapoint_value(*this->switch_id_, state->current_values.is_on()); } } diff --git a/esphome/components/tuya/light/tuya_light.h b/esphome/components/tuya/light/tuya_light.h index 20753fa90b..de9ec5e45f 100644 --- a/esphome/components/tuya/light/tuya_light.h +++ b/esphome/components/tuya/light/tuya_light.h @@ -16,6 +16,7 @@ class TuyaLight : public Component, public light::LightOutput { this->min_value_datapoint_id_ = min_value_datapoint_id; } void set_switch_id(uint8_t switch_id) { this->switch_id_ = switch_id; } + void set_rgb_id(uint8_t rgb_id) { this->rgb_id_ = rgb_id; } void set_color_temperature_id(uint8_t color_temperature_id) { this->color_temperature_id_ = color_temperature_id; } void set_color_temperature_invert(bool color_temperature_invert) { this->color_temperature_invert_ = color_temperature_invert; @@ -32,6 +33,8 @@ class TuyaLight : public Component, public light::LightOutput { void set_warm_white_temperature(float warm_white_temperature) { this->warm_white_temperature_ = warm_white_temperature; } + void set_color_interlock(bool color_interlock) { color_interlock_ = color_interlock; } + light::LightTraits get_traits() override; void setup_state(light::LightState *state) override; void write_state(light::LightState *state) override; @@ -44,6 +47,7 @@ class TuyaLight : public Component, public light::LightOutput { optional dimmer_id_{}; optional min_value_datapoint_id_{}; optional switch_id_{}; + optional rgb_id_{}; optional color_temperature_id_{}; uint32_t min_value_ = 0; uint32_t max_value_ = 255; @@ -51,6 +55,7 @@ class TuyaLight : public Component, public light::LightOutput { float cold_white_temperature_; float warm_white_temperature_; bool color_temperature_invert_{false}; + bool color_interlock_{false}; light::LightState *state_{nullptr}; }; diff --git a/esphome/const.py b/esphome/const.py index a52085fbb7..054f032da4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -111,6 +111,7 @@ CONF_COLD_WHITE_COLOR_TEMPERATURE = "cold_white_color_temperature" CONF_COLOR = "color" CONF_COLOR_BRIGHTNESS = "color_brightness" CONF_COLOR_CORRECT = "color_correct" +CONF_COLOR_INTERLOCK = "color_interlock" CONF_COLOR_MODE = "color_mode" CONF_COLOR_TEMPERATURE = "color_temperature" CONF_COLORS = "colors" diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 2b77c5827a..a190566bea 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -270,6 +270,39 @@ optional parse_int(const std::string &str) { return {}; return value; } + +optional parse_hex(const char chr) { + int out = chr; + if (out >= '0' && out <= '9') + return (out - '0'); + if (out >= 'A' && out <= 'F') + return (10 + (out - 'A')); + if (out >= 'a' && out <= 'f') + return (10 + (out - 'a')); + return {}; +} + +optional parse_hex(const std::string &str, size_t start, size_t length) { + if (str.length() < start) { + return {}; + } + size_t end = start + length; + if (str.length() < end) { + return {}; + } + int out = 0; + for (size_t i = start; i < end; i++) { + char chr = str[i]; + auto digit = parse_hex(chr); + if (!digit.has_value()) { + ESP_LOGW(TAG, "Can't convert '%s' to number, invalid character %c!", str.substr(start, length).c_str(), chr); + return {}; + } + out = (out << 4) | *digit; + } + return out; +} + uint32_t fnv1_hash(const std::string &str) { uint32_t hash = 2166136261UL; for (char c : str) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 86cd3b086e..8118585db5 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -43,7 +43,8 @@ std::string to_string(double val); std::string to_string(long double val); optional parse_float(const std::string &str); optional parse_int(const std::string &str); - +optional parse_hex(const std::string &str, size_t start, size_t length); +optional parse_hex(char chr); /// Sanitize the hostname by removing characters that are not in the allowlist and truncating it to 63 chars. std::string sanitize_hostname(const std::string &hostname); From 4d28afc153ba51b133e48e81d98694d3c47152ee Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Mon, 27 Sep 2021 05:32:46 +1000 Subject: [PATCH 1441/1841] add fan.cycle_speed action (#2329) --- esphome/components/fan/__init__.py | 7 ++++++ esphome/components/fan/automation.h | 36 +++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index f8772948fc..15895976d1 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -41,6 +41,7 @@ FAN_DIRECTION_ENUM = { TurnOnAction = fan_ns.class_("TurnOnAction", automation.Action) TurnOffAction = fan_ns.class_("TurnOffAction", automation.Action) ToggleAction = fan_ns.class_("ToggleAction", automation.Action) +CycleSpeedAction = fan_ns.class_("CycleSpeedAction", automation.Action) FanTurnOnTrigger = fan_ns.class_("FanTurnOnTrigger", automation.Trigger.template()) FanTurnOffTrigger = fan_ns.class_("FanTurnOffTrigger", automation.Trigger.template()) @@ -204,6 +205,12 @@ async def fan_turn_on_to_code(config, action_id, template_arg, args): return var +@automation.register_action("fan.cycle_speed", CycleSpeedAction, FAN_ACTION_SCHEMA) +async def fan_cycle_speed_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) + + @automation.register_condition( "fan.is_on", FanIsOnCondition, diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index 7ff7c720df..608f772b75 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -50,6 +50,42 @@ template class ToggleAction : public Action { FanState *state_; }; +template class CycleSpeedAction : public Action { + public: + explicit CycleSpeedAction(FanState *state) : state_(state) {} + + void play(Ts... x) override { + // check to see if fan supports speeds and is on + if (this->state_->get_traits().supported_speed_count()) { + if (this->state_->state) { + int speed = this->state_->speed + 1; + int supported_speed_count = this->state_->get_traits().supported_speed_count(); + if (speed > supported_speed_count) { + // was running at max speed, so turn off + speed = 1; + auto call = this->state_->turn_off(); + call.set_speed(speed); + call.perform(); + } else { + auto call = this->state_->turn_on(); + call.set_speed(speed); + call.perform(); + } + } else { + // fan was off, so set speed to 1 + auto call = this->state_->turn_on(); + call.set_speed(1); + call.perform(); + } + } else { + // fan doesn't support speed counts, so toggle + this->state_->toggle().perform(); + } + } + + FanState *state_; +}; + template class FanIsOnCondition : public Condition { public: explicit FanIsOnCondition(FanState *state) : state_(state) {} From 7672ba2c8d6d918a4b44220d2d02198332e6762a Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Sun, 26 Sep 2021 22:27:24 +0200 Subject: [PATCH 1442/1841] Modbus controller (#1779) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 7 + esphome/components/modbus/__init__.py | 14 +- esphome/components/modbus/modbus.cpp | 126 +++- esphome/components/modbus/modbus.h | 20 +- .../components/modbus_controller/__init__.py | 114 ++++ .../binary_sensor/__init__.py | 81 +++ .../binary_sensor/modbus_binarysensor.cpp | 40 ++ .../binary_sensor/modbus_binarysensor.h | 43 ++ esphome/components/modbus_controller/const.py | 13 + .../modbus_controller/modbus_controller.cpp | 559 ++++++++++++++++++ .../modbus_controller/modbus_controller.h | 454 ++++++++++++++ .../modbus_controller/number/__init__.py | 157 +++++ .../number/modbus_number.cpp | 83 +++ .../modbus_controller/number/modbus_number.h | 48 ++ .../modbus_controller/output/__init__.py | 74 +++ .../output/modbus_output.cpp | 61 ++ .../modbus_controller/output/modbus_output.h | 45 ++ .../modbus_controller/sensor/__init__.py | 109 ++++ .../sensor/modbus_sensor.cpp | 36 ++ .../modbus_controller/sensor/modbus_sensor.h | 35 ++ .../modbus_controller/switch/__init__.py | 81 +++ .../switch/modbus_switch.cpp | 70 +++ .../modbus_controller/switch/modbus_switch.h | 44 ++ .../modbus_controller/text_sensor/__init__.py | 101 ++++ .../text_sensor/modbus_textsensor.cpp | 56 ++ .../text_sensor/modbus_textsensor.h | 52 ++ tests/test5.yaml | 16 + 27 files changed, 2505 insertions(+), 34 deletions(-) create mode 100644 esphome/components/modbus_controller/__init__.py create mode 100644 esphome/components/modbus_controller/binary_sensor/__init__.py create mode 100644 esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp create mode 100644 esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h create mode 100644 esphome/components/modbus_controller/const.py create mode 100644 esphome/components/modbus_controller/modbus_controller.cpp create mode 100644 esphome/components/modbus_controller/modbus_controller.h create mode 100644 esphome/components/modbus_controller/number/__init__.py create mode 100644 esphome/components/modbus_controller/number/modbus_number.cpp create mode 100644 esphome/components/modbus_controller/number/modbus_number.h create mode 100644 esphome/components/modbus_controller/output/__init__.py create mode 100644 esphome/components/modbus_controller/output/modbus_output.cpp create mode 100644 esphome/components/modbus_controller/output/modbus_output.h create mode 100644 esphome/components/modbus_controller/sensor/__init__.py create mode 100644 esphome/components/modbus_controller/sensor/modbus_sensor.cpp create mode 100644 esphome/components/modbus_controller/sensor/modbus_sensor.h create mode 100644 esphome/components/modbus_controller/switch/__init__.py create mode 100644 esphome/components/modbus_controller/switch/modbus_switch.cpp create mode 100644 esphome/components/modbus_controller/switch/modbus_switch.h create mode 100644 esphome/components/modbus_controller/text_sensor/__init__.py create mode 100644 esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp create mode 100644 esphome/components/modbus_controller/text_sensor/modbus_textsensor.h diff --git a/CODEOWNERS b/CODEOWNERS index 92ee989309..17825a9205 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -88,6 +88,13 @@ esphome/components/mcp9808/* @k7hpn esphome/components/mdns/* @esphome/core esphome/components/midea/* @dudanov esphome/components/mitsubishi/* @RubyBailey +esphome/components/modbus_controller/* @martgras +esphome/components/modbus_controller/binary_sensor/* @martgras +esphome/components/modbus_controller/number/* @martgras +esphome/components/modbus_controller/output/* @martgras +esphome/components/modbus_controller/sensor/* @martgras +esphome/components/modbus_controller/switch/* @martgras +esphome/components/modbus_controller/text_sensor/* @martgras esphome/components/network/* @esphome/core esphome/components/nextion/* @senexcrenshaw esphome/components/nextion/binary_sensor/* @senexcrenshaw diff --git a/esphome/components/modbus/__init__.py b/esphome/components/modbus/__init__.py index 6b454cbaf0..254322d097 100644 --- a/esphome/components/modbus/__init__.py +++ b/esphome/components/modbus/__init__.py @@ -2,7 +2,11 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.cpp_helpers import gpio_pin_expression from esphome.components import uart -from esphome.const import CONF_FLOW_CONTROL_PIN, CONF_ID, CONF_ADDRESS +from esphome.const import ( + CONF_FLOW_CONTROL_PIN, + CONF_ID, + CONF_ADDRESS, +) from esphome import pins DEPENDENCIES = ["uart"] @@ -13,11 +17,16 @@ ModbusDevice = modbus_ns.class_("ModbusDevice") MULTI_CONF = True CONF_MODBUS_ID = "modbus_id" +CONF_SEND_WAIT_TIME = "send_wait_time" + CONFIG_SCHEMA = ( cv.Schema( { cv.GenerateID(): cv.declare_id(Modbus), cv.Optional(CONF_FLOW_CONTROL_PIN): pins.gpio_output_pin_schema, + cv.Optional( + CONF_SEND_WAIT_TIME, default="250ms" + ): cv.positive_time_period_milliseconds, } ) .extend(cv.COMPONENT_SCHEMA) @@ -36,6 +45,9 @@ async def to_code(config): pin = await gpio_pin_expression(config[CONF_FLOW_CONTROL_PIN]) cg.add(var.set_flow_control_pin(pin)) + if CONF_SEND_WAIT_TIME in config: + cg.add(var.set_send_wait_time(config[CONF_SEND_WAIT_TIME])) + def modbus_device_schema(default_address): schema = { diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 2d714e72a2..1f6d868baf 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -1,5 +1,6 @@ #include "modbus.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" namespace esphome { namespace modbus { @@ -13,10 +14,15 @@ void Modbus::setup() { } void Modbus::loop() { const uint32_t now = millis(); + if (now - this->last_modbus_byte_ > 50) { this->rx_buffer_.clear(); this->last_modbus_byte_ = now; } + // stop blocking new send commands after send_wait_time_ ms regardless if a response has been received since then + if (now - this->last_send_ > send_wait_time_) { + waiting_for_response = 0; + } while (this->available()) { uint8_t byte; @@ -49,48 +55,66 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { size_t at = this->rx_buffer_.size(); this->rx_buffer_.push_back(byte); const uint8_t *raw = &this->rx_buffer_[0]; - + ESP_LOGV(TAG, "Modbus received Byte %d (0X%x)", byte, byte); // Byte 0: modbus address (match all) if (at == 0) return true; uint8_t address = raw[0]; - - // Byte 1: Function (msb indicates error) - if (at == 1) - return (byte & 0x80) != 0x80; - + uint8_t function_code = raw[1]; // Byte 2: Size (with modbus rtu function code 4/3) // See also https://en.wikipedia.org/wiki/Modbus if (at == 2) return true; uint8_t data_len = raw[2]; - // Byte 3..3+data_len-1: Data - if (at < 3 + data_len) + uint8_t data_offset = 3; + // the response for write command mirrors the requests and data startes at offset 2 instead of 3 for read commands + if (function_code == 0x5 || function_code == 0x06 || function_code == 0x10) { + data_offset = 2; + data_len = 4; + } + + // Error ( msb indicates error ) + // response format: Byte[0] = device address, Byte[1] function code | 0x80 , Byte[2] excpetion code, Byte[3-4] crc + if ((function_code & 0x80) == 0x80) { + data_offset = 2; + data_len = 1; + } + + // Byte data_offset..data_offset+data_len-1: Data + if (at < data_offset + data_len) return true; // Byte 3+data_len: CRC_LO (over all bytes) - if (at == 3 + data_len) + if (at == data_offset + data_len) return true; - // Byte 3+len+1: CRC_HI (over all bytes) - uint16_t computed_crc = crc16(raw, 3 + data_len); - uint16_t remote_crc = uint16_t(raw[3 + data_len]) | (uint16_t(raw[3 + data_len + 1]) << 8); + + // Byte data_offset+len+1: CRC_HI (over all bytes) + uint16_t computed_crc = crc16(raw, data_offset + data_len); + uint16_t remote_crc = uint16_t(raw[data_offset + data_len]) | (uint16_t(raw[data_offset + data_len + 1]) << 8); if (computed_crc != remote_crc) { ESP_LOGW(TAG, "Modbus CRC Check failed! %02X!=%02X", computed_crc, remote_crc); return false; } - std::vector data(this->rx_buffer_.begin() + 3, this->rx_buffer_.begin() + 3 + data_len); + waiting_for_response = 0; + std::vector data(this->rx_buffer_.begin() + data_offset, this->rx_buffer_.begin() + data_offset + data_len); bool found = false; for (auto *device : this->devices_) { if (device->address_ == address) { - device->on_modbus_data(data); + // Is it an error response? + if ((function_code & 0x80) == 0x80) { + ESP_LOGW(TAG, "Modbus error function code: 0x%X exception: %d", function_code, raw[2]); + device->on_modbus_error(function_code & 0x7F, raw[2]); + } else { + device->on_modbus_data(data); + } found = true; } } if (!found) { - ESP_LOGW(TAG, "Got Modbus frame from unknown address 0x%02X!", address); + ESP_LOGW(TAG, "Got Modbus frame from unknown address 0x%02X! ", address); } // return false to reset buffer @@ -100,31 +124,79 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { void Modbus::dump_config() { ESP_LOGCONFIG(TAG, "Modbus:"); LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_); + ESP_LOGCONFIG(TAG, " Send Wait Time: %d ms", this->send_wait_time_); } float Modbus::get_setup_priority() const { // After UART bus return setup_priority::BUS - 1.0f; } -void Modbus::send(uint8_t address, uint8_t function, uint16_t start_address, uint16_t register_count) { - uint8_t frame[8]; - frame[0] = address; - frame[1] = function; - frame[2] = start_address >> 8; - frame[3] = start_address >> 0; - frame[4] = register_count >> 8; - frame[5] = register_count >> 0; - auto crc = crc16(frame, 6); - frame[6] = crc >> 0; - frame[7] = crc >> 8; + +void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address, uint16_t number_of_entities, + uint8_t payload_len, const uint8_t *payload) { + static const size_t MAX_VALUES = 128; + + if (number_of_entities > MAX_VALUES) { + ESP_LOGE(TAG, "send too many values %d max=%zu", number_of_entities, MAX_VALUES); + return; + } + + std::vector data; + data.push_back(address); + data.push_back(function_code); + data.push_back(start_address >> 8); + data.push_back(start_address >> 0); + if (function_code != 0x5 && function_code != 0x6) { + data.push_back(number_of_entities >> 8); + data.push_back(number_of_entities >> 0); + } + + if (payload != nullptr) { + if (function_code == 0xF || function_code == 0x10) { // Write multiple + data.push_back(payload_len); // Byte count is required for write + } else { + payload_len = 2; // Write single register or coil + } + for (int i = 0; i < payload_len; i++) { + data.push_back(payload[i]); + } + } + + auto crc = crc16(data.data(), data.size()); + data.push_back(crc >> 0); + data.push_back(crc >> 8); if (this->flow_control_pin_ != nullptr) this->flow_control_pin_->digital_write(true); - this->write_array(frame, 8); + this->write_array(data); this->flush(); if (this->flow_control_pin_ != nullptr) this->flow_control_pin_->digital_write(false); + waiting_for_response = address; + last_send_ = millis(); + ESP_LOGV(TAG, "Modbus write: %s", hexencode(data).c_str()); +} + +// Helper function for lambdas +// Send raw command. Except CRC everything must be contained in payload +void Modbus::send_raw(const std::vector &payload) { + if (payload.empty()) { + return; + } + + if (this->flow_control_pin_ != nullptr) + this->flow_control_pin_->digital_write(true); + + auto crc = crc16(payload.data(), payload.size()); + this->write_array(payload); + this->write_byte(crc & 0xFF); + this->write_byte((crc >> 8) & 0xFF); + this->flush(); + if (this->flow_control_pin_ != nullptr) + this->flow_control_pin_->digital_write(false); + waiting_for_response = payload[0]; + last_send_ = millis(); } } // namespace modbus diff --git a/esphome/components/modbus/modbus.h b/esphome/components/modbus/modbus.h index 876c46b688..400e29e08b 100644 --- a/esphome/components/modbus/modbus.h +++ b/esphome/components/modbus/modbus.h @@ -22,17 +22,21 @@ class Modbus : public uart::UARTDevice, public Component { float get_setup_priority() const override; - void send(uint8_t address, uint8_t function, uint16_t start_address, uint16_t register_count); - + void send(uint8_t address, uint8_t function_code, uint16_t start_address, uint16_t number_of_entities, + uint8_t payload_len = 0, const uint8_t *payload = nullptr); + void send_raw(const std::vector &payload); void set_flow_control_pin(GPIOPin *flow_control_pin) { this->flow_control_pin_ = flow_control_pin; } + uint8_t waiting_for_response{0}; + void set_send_wait_time(uint16_t time_in_ms) { send_wait_time_ = time_in_ms; } protected: GPIOPin *flow_control_pin_{nullptr}; bool parse_modbus_byte_(uint8_t byte); - + uint16_t send_wait_time_{250}; std::vector rx_buffer_; uint32_t last_modbus_byte_{0}; + uint32_t last_send_{0}; std::vector devices_; }; @@ -43,10 +47,14 @@ class ModbusDevice { void set_parent(Modbus *parent) { parent_ = parent; } void set_address(uint8_t address) { address_ = address; } virtual void on_modbus_data(const std::vector &data) = 0; - - void send(uint8_t function, uint16_t start_address, uint16_t register_count) { - this->parent_->send(this->address_, function, start_address, register_count); + virtual void on_modbus_error(uint8_t function_code, uint8_t exception_code) {} + void send(uint8_t function, uint16_t start_address, uint16_t number_of_entities, uint8_t payload_len = 0, + const uint8_t *payload = nullptr) { + this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload); } + void send_raw(const std::vector &payload) { this->parent_->send_raw(payload); } + // If more than one device is connected block sending a new command before a response is received + bool waiting_for_response() { return parent_->waiting_for_response != 0; } protected: friend Modbus; diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py new file mode 100644 index 0000000000..7a69029dab --- /dev/null +++ b/esphome/components/modbus_controller/__init__.py @@ -0,0 +1,114 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import modbus +from esphome.const import CONF_ID, CONF_ADDRESS +from esphome.cpp_helpers import logging +from .const import ( + CONF_COMMAND_THROTTLE, +) + +CODEOWNERS = ["@martgras"] + +AUTO_LOAD = ["modbus"] + +MULTI_CONF = True + +# pylint: disable=invalid-name +modbus_controller_ns = cg.esphome_ns.namespace("modbus_controller") +ModbusController = modbus_controller_ns.class_( + "ModbusController", cg.PollingComponent, modbus.ModbusDevice +) + +SensorItem = modbus_controller_ns.struct("SensorItem") + +ModbusFunctionCode_ns = modbus_controller_ns.namespace("ModbusFunctionCode") +ModbusFunctionCode = ModbusFunctionCode_ns.enum("ModbusFunctionCode") +MODBUS_FUNCTION_CODE = { + "read_coils": ModbusFunctionCode.READ_COILS, + "read_discrete_inputs": ModbusFunctionCode.READ_DISCRETE_INPUTS, + "read_holding_registers": ModbusFunctionCode.READ_HOLDING_REGISTERS, + "read_input_registers": ModbusFunctionCode.READ_INPUT_REGISTERS, + "write_single_coil": ModbusFunctionCode.WRITE_SINGLE_COIL, + "write_single_register": ModbusFunctionCode.WRITE_SINGLE_REGISTER, + "write_multiple_coils": ModbusFunctionCode.WRITE_MULTIPLE_COILS, + "write_multiple_registers": ModbusFunctionCode.WRITE_MULTIPLE_REGISTERS, +} + +ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType") +ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType") +MODBUS_REGISTER_TYPE = { + "coil": ModbusRegisterType.COIL, + "discrete_input": ModbusRegisterType.DISCRETE, + "holding": ModbusRegisterType.HOLDING, + "read": ModbusRegisterType.READ, +} + +SensorValueType_ns = modbus_controller_ns.namespace("SensorValueType") +SensorValueType = SensorValueType_ns.enum("SensorValueType") +SENSOR_VALUE_TYPE = { + "RAW": SensorValueType.RAW, + "U_WORD": SensorValueType.U_WORD, + "S_WORD": SensorValueType.S_WORD, + "U_DWORD": SensorValueType.U_DWORD, + "U_DWORD_R": SensorValueType.U_DWORD_R, + "S_DWORD": SensorValueType.S_DWORD, + "S_DWORD_R": SensorValueType.S_DWORD_R, + "U_QWORD": SensorValueType.U_QWORD, + "U_QWORDU_R": SensorValueType.U_QWORD_R, + "S_QWORD": SensorValueType.S_QWORD, + "U_QWORD_R": SensorValueType.S_QWORD_R, + "FP32": SensorValueType.FP32, + "FP32_R": SensorValueType.FP32_R, +} + + +MULTI_CONF = True + +_LOGGER = logging.getLogger(__name__) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ModbusController), + cv.Optional( + CONF_COMMAND_THROTTLE, default="0ms" + ): cv.positive_time_period_milliseconds, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(modbus.modbus_device_schema(0x01)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID], config[CONF_COMMAND_THROTTLE]) + cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE])) + await register_modbus_device(var, config) + + +async def register_modbus_device(var, config): + cg.add(var.set_address(config[CONF_ADDRESS])) + await cg.register_component(var, config) + return await modbus.register_modbus_device(var, config) + + +def function_code_to_register(function_code): + FUNCTION_CODE_TYPE_MAP = { + "read_coils": ModbusRegisterType.COIL, + "read_discrete_inputs": ModbusRegisterType.DISCRETE, + "read_holding_registers": ModbusRegisterType.HOLDING, + "read_input_registers": ModbusRegisterType.READ, + "write_single_coil": ModbusRegisterType.COIL, + "write_single_register": ModbusRegisterType.HOLDING, + "write_multiple_coils": ModbusRegisterType.COIL, + "write_multiple_registers": ModbusRegisterType.HOLDING, + } + return FUNCTION_CODE_TYPE_MAP[function_code] + + +def find_by_value(dict, find_value): + for (key, value) in MODBUS_REGISTER_TYPE.items(): + print(find_value, value) + if find_value == value: + return key + return "not found" diff --git a/esphome/components/modbus_controller/binary_sensor/__init__.py b/esphome/components/modbus_controller/binary_sensor/__init__.py new file mode 100644 index 0000000000..d46ff71f2d --- /dev/null +++ b/esphome/components/modbus_controller/binary_sensor/__init__.py @@ -0,0 +1,81 @@ +from esphome.components import binary_sensor +import esphome.config_validation as cv +import esphome.codegen as cg + +from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OFFSET +from .. import ( + SensorItem, + modbus_controller_ns, + ModbusController, + MODBUS_REGISTER_TYPE, +) +from ..const import ( + CONF_BITMASK, + CONF_BYTE_OFFSET, + CONF_FORCE_NEW_RANGE, + CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_TYPE, + CONF_SKIP_UPDATES, +) + +DEPENDENCIES = ["modbus_controller"] +CODEOWNERS = ["@martgras"] + + +ModbusBinarySensor = modbus_controller_ns.class_( + "ModbusBinarySensor", cg.Component, binary_sensor.BinarySensor, SensorItem +) + +CONFIG_SCHEMA = cv.All( + binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ModbusBinarySensor), + cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), + cv.Required(CONF_ADDRESS): cv.positive_int, + cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), + cv.Optional(CONF_OFFSET, default=0): cv.positive_int, + cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, + cv.Optional(CONF_BITMASK, default=0x1): cv.hex_uint32_t, + cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, + cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + } + ).extend(cv.COMPONENT_SCHEMA), +) + + +async def to_code(config): + byte_offset = 0 + if CONF_OFFSET in config: + byte_offset = config[CONF_OFFSET] + # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET + if CONF_BYTE_OFFSET in config: + byte_offset = config[CONF_BYTE_OFFSET] + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_REGISTER_TYPE], + config[CONF_ADDRESS], + byte_offset, + config[CONF_BITMASK], + config[CONF_SKIP_UPDATES], + config[CONF_FORCE_NEW_RANGE], + ) + await cg.register_component(var, config) + await binary_sensor.register_binary_sensor(var, config) + + paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) + cg.add(paren.add_sensor_item(var)) + if CONF_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_LAMBDA], + [ + (ModbusBinarySensor.operator("ptr"), "item"), + (cg.float_, "x"), + ( + cg.std_vector.template(cg.uint8).operator("const").operator("ref"), + "data", + ), + ], + return_type=cg.optional.template(bool), + ) + cg.add(var.set_template(template_)) diff --git a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp new file mode 100644 index 0000000000..81066b3f5c --- /dev/null +++ b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp @@ -0,0 +1,40 @@ +#include "modbus_binarysensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace modbus_controller { + +static const char *const TAG = "modbus_controller.binary_sensor"; + +void ModbusBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Modbus Controller Binary Sensor", this); } + +void ModbusBinarySensor::parse_and_publish(const std::vector &data) { + bool value; + + switch (this->register_type) { + case ModbusRegisterType::DISCRETE_INPUT: + value = coil_from_vector(this->offset, data); + break; + case ModbusRegisterType::COIL: + // offset for coil is the actual number of the coil not the byte offset + value = coil_from_vector(this->offset, data); + break; + default: + value = get_data(data, this->offset) & this->bitmask; + break; + } + // Is there a lambda registered + // call it with the pre converted value and the raw data array + if (this->transform_func_.has_value()) { + // the lambda can parse the response itself + auto val = (*this->transform_func_)(this, value, data); + if (val.has_value()) { + ESP_LOGV(TAG, "Value overwritten by lambda"); + value = val.value(); + } + } + this->publish_state(value); +} + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h new file mode 100644 index 0000000000..c516d6b916 --- /dev/null +++ b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h @@ -0,0 +1,43 @@ +#pragma once + +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/modbus_controller/modbus_controller.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace modbus_controller { + +class ModbusBinarySensor : public Component, public binary_sensor::BinarySensor, public SensorItem { + public: + ModbusBinarySensor(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask, + uint8_t skip_updates, bool force_new_range) + : Component(), binary_sensor::BinarySensor() { + this->register_type = register_type; + this->start_address = start_address; + this->offset = offset; + this->bitmask = bitmask; + this->sensor_value_type = SensorValueType::BIT; + this->skip_updates = skip_updates; + this->force_new_range = force_new_range; + + if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) + this->register_count = offset + 1; + else + this->register_count = 1; + } + + void parse_and_publish(const std::vector &data) override; + void set_state(bool state) { this->state = state; } + + void dump_config() override; + + using transform_func_t = + optional(ModbusBinarySensor *, bool, const std::vector &)>>; + void set_template(transform_func_t &&f) { this->transform_func_ = f; } + + protected: + transform_func_t transform_func_{nullopt}; +}; + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/const.py b/esphome/components/modbus_controller/const.py new file mode 100644 index 0000000000..3cd114e673 --- /dev/null +++ b/esphome/components/modbus_controller/const.py @@ -0,0 +1,13 @@ +CONF_BITMASK = "bitmask" +CONF_BYTE_OFFSET = "byte_offset" +CONF_COMMAND_THROTTLE = "command_throttle" +CONF_FORCE_NEW_RANGE = "force_new_range" +CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id" +CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode" +CONF_RAW_ENCODE = "raw_encode" +CONF_REGISTER_COUNT = "register_count" +CONF_REGISTER_TYPE = "register_type" +CONF_RESPONSE_SIZE = "response_size" +CONF_SKIP_UPDATES = "skip_updates" +CONF_VALUE_TYPE = "value_type" +CONF_WRITE_LAMBDA = "write_lambda" diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp new file mode 100644 index 0000000000..70b5bf8eae --- /dev/null +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -0,0 +1,559 @@ +#include "modbus_controller.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace modbus_controller { + +static const char *const TAG = "modbus_controller"; + +void ModbusController::setup() { + // Modbus::setup(); + this->create_register_ranges_(); +} + +/* + To work with the existing modbus class and avoid polling for responses a command queue is used. + send_next_command will submit the command at the top of the queue and set the corresponding callback + to handle the response from the device. + Once the response has been processed it is removed from the queue and the next command is sent +*/ +bool ModbusController::send_next_command_() { + uint32_t last_send = millis() - this->last_command_timestamp_; + + if ((last_send > this->command_throttle_) && !waiting_for_response() && !command_queue_.empty()) { + auto &command = command_queue_.front(); + + ESP_LOGV(TAG, "Sending next modbus command to device %d register 0x%02X count %d", this->address_, + command->register_address, command->register_count); + command->send(); + this->last_command_timestamp_ = millis(); + if (!command->on_data_func) { // No handler remove from queue directly after sending + command_queue_.pop_front(); + } + } + return (!command_queue_.empty()); +} + +// Queue incoming response +void ModbusController::on_modbus_data(const std::vector &data) { + auto ¤t_command = this->command_queue_.front(); + if (current_command != nullptr) { + // Move the commandItem to the response queue + current_command->payload = data; + this->incoming_queue_.push(std::move(current_command)); + ESP_LOGV(TAG, "Modbus response queued"); + command_queue_.pop_front(); + } +} + +// Dispatch the response to the registered handler +void ModbusController::process_modbus_data_(const ModbusCommandItem *response) { + ESP_LOGV(TAG, "Process modbus response for address 0x%X size: %zu", response->register_address, + response->payload.size()); + response->on_data_func(response->register_type, response->register_address, response->payload); +} + +void ModbusController::on_modbus_error(uint8_t function_code, uint8_t exception_code) { + ESP_LOGE(TAG, "Modbus error function code: 0x%X exception: %d ", function_code, exception_code); + // Remove pending command waiting for a response + auto ¤t_command = this->command_queue_.front(); + if (current_command != nullptr) { + ESP_LOGE(TAG, + "Modbus error - last command: function code=0x%X register adddress = 0x%X " + "registers count=%d " + "payload size=%zu", + function_code, current_command->register_address, current_command->register_count, + current_command->payload.size()); + command_queue_.pop_front(); + } +} + +void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data) { + ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address); + + auto vec_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) { + return (r.start_address == start_address && r.register_type == register_type); + }); + + if (vec_it == register_ranges_.end()) { + ESP_LOGE(TAG, "Handle incoming data : No matching range for sensor found - start_address : 0x%X", start_address); + return; + } + auto map_it = sensormap_.find(vec_it->first_sensorkey); + if (map_it == sensormap_.end()) { + ESP_LOGE(TAG, "Handle incoming data : No sensor found in at start_address : 0x%X (0x%llX)", start_address, + vec_it->first_sensorkey); + return; + } + // loop through all sensors with the same start address + while (map_it != sensormap_.end() && map_it->second->start_address == start_address) { + if (map_it->second->register_type == register_type) { + map_it->second->parse_and_publish(data); + } + map_it++; + } +} + +void ModbusController::queue_command(const ModbusCommandItem &command) { + // check if this commmand is already qeued. + // not very effective but the queue is never really large + for (auto &item : command_queue_) { + if (item->register_address == command.register_address && item->register_count == command.register_count && + item->register_type == command.register_type) { + ESP_LOGW(TAG, "Duplicate modbus command found"); + // update the payload of the queued command + // replaces a previous command + item->payload = command.payload; + return; + } + } + command_queue_.push_back(make_unique(command)); +} + +void ModbusController::update_range_(RegisterRange &r) { + ESP_LOGV(TAG, "Range : %X Size: %x (%d) skip: %d", r.start_address, r.register_count, (int) r.register_type, + r.skip_updates_counter); + if (r.skip_updates_counter == 0) { + ModbusCommandItem command_item = + ModbusCommandItem::create_read_command(this, r.register_type, r.start_address, r.register_count); + queue_command(command_item); + r.skip_updates_counter = r.skip_updates; // reset counter to config value + } else { + r.skip_updates_counter--; + } +} +// +// Queue the modbus requests to be send. +// Once we get a response to the command it is removed from the queue and the next command is send +// +void ModbusController::update() { + if (!command_queue_.empty()) { + ESP_LOGV(TAG, "%zu modbus commands already in queue", command_queue_.size()); + } else { + ESP_LOGV(TAG, "Updating modbus component"); + } + + for (auto &r : this->register_ranges_) { + ESP_LOGVV(TAG, "Updating range 0x%X", r.start_address); + update_range_(r); + } +} + +// walk through the sensors and determine the registerranges to read +size_t ModbusController::create_register_ranges_() { + register_ranges_.clear(); + uint8_t n = 0; + if (sensormap_.empty()) { + return 0; + } + + auto ix = sensormap_.begin(); + auto prev = ix; + int total_register_count = 0; + uint16_t current_start_address = ix->second->start_address; + uint8_t buffer_offset = ix->second->offset; + uint8_t skip_updates = ix->second->skip_updates; + auto first_sensorkey = ix->second->getkey(); + total_register_count = 0; + while (ix != sensormap_.end()) { + ESP_LOGV(TAG, "Register: 0x%X %d %d 0x%llx (%d) buffer_offset = %d (0x%X) skip=%u", ix->second->start_address, + ix->second->register_count, ix->second->offset, ix->second->getkey(), total_register_count, buffer_offset, + buffer_offset, ix->second->skip_updates); + // if this is a sequential address based on number of registers and address of previous sensor + // convert to an offset to the previous sensor (address 0x101 becomes address 0x100 offset 2 bytes) + if (!ix->second->force_new_range && total_register_count >= 0 && + prev->second->register_type == ix->second->register_type && + prev->second->start_address + total_register_count == ix->second->start_address && + prev->second->start_address < ix->second->start_address) { + ix->second->start_address = prev->second->start_address; + ix->second->offset += prev->second->offset + prev->second->get_register_size(); + + // replace entry in sensormap_ + auto const value = ix->second; + sensormap_.erase(ix); + sensormap_.insert({value->getkey(), value}); + // move iterator back to new element + ix = sensormap_.find(value->getkey()); // next(prev, 1); + } + if (current_start_address != ix->second->start_address || + // ( prev->second->start_address + prev->second->offset != ix->second->start_address) || + ix->second->register_type != prev->second->register_type) { + // Difference doesn't match so we have a gap + if (n > 0) { + RegisterRange r; + r.start_address = current_start_address; + r.register_count = total_register_count; + if (prev->second->register_type == ModbusRegisterType::COIL || + prev->second->register_type == ModbusRegisterType::DISCRETE_INPUT) { + r.register_count = prev->second->offset + 1; + } + r.register_type = prev->second->register_type; + r.first_sensorkey = first_sensorkey; + r.skip_updates = skip_updates; + r.skip_updates_counter = 0; + ESP_LOGV(TAG, "Add range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates); + register_ranges_.push_back(r); + } + skip_updates = ix->second->skip_updates; + current_start_address = ix->second->start_address; + first_sensorkey = ix->second->getkey(); + total_register_count = ix->second->register_count; + buffer_offset = ix->second->offset; + n = 1; + } else { + n++; + if (ix->second->offset != prev->second->offset || n == 1) { + total_register_count += ix->second->register_count; + buffer_offset += ix->second->get_register_size(); + } + // use the lowest non zero value for the whole range + // Because zero is the default value for skip_updates it is excluded from getting the min value. + if (ix->second->skip_updates != 0) { + if (skip_updates != 0) { + skip_updates = std::min(skip_updates, ix->second->skip_updates); + } else { + skip_updates = ix->second->skip_updates; + } + } + } + prev = ix++; + } + // Add the last range + if (n > 0) { + RegisterRange r; + r.start_address = current_start_address; + // r.register_count = prev->second->offset>>1 + prev->second->get_register_size(); + r.register_count = total_register_count; + if (prev->second->register_type == ModbusRegisterType::COIL || + prev->second->register_type == ModbusRegisterType::DISCRETE_INPUT) { + r.register_count = prev->second->offset + 1; + } + r.register_type = prev->second->register_type; + r.first_sensorkey = first_sensorkey; + r.skip_updates = skip_updates; + r.skip_updates_counter = 0; + ESP_LOGV(TAG, "Add last range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates); + register_ranges_.push_back(r); + } + return register_ranges_.size(); +} + +void ModbusController::dump_config() { + ESP_LOGCONFIG(TAG, "ModbusController:"); + ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + ESP_LOGCONFIG(TAG, "sensormap"); + for (auto &it : sensormap_) { + ESP_LOGCONFIG("TAG", " Sensor 0x%llX start=0x%X count=%d size=%d", it.second->getkey(), it.second->start_address, + it.second->register_count, it.second->get_register_size()); + } +#endif +} + +void ModbusController::loop() { + // Incoming data to process? + if (!incoming_queue_.empty()) { + auto &message = incoming_queue_.front(); + if (message != nullptr) + process_modbus_data_(message.get()); + incoming_queue_.pop(); + + } else { + // all messages processed send pending commmands + send_next_command_(); + } +} + +void ModbusController::on_write_register_response(ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data) { + ESP_LOGV(TAG, "Command ACK 0x%X %d ", get_data(data, 0), get_data(data, 1)); +} + +void ModbusController::dump_sensormap_() { + ESP_LOGV("modbuscontroller.h", "sensormap"); + for (auto &it : sensormap_) { + ESP_LOGV("modbuscontroller.h", " Sensor 0x%llX start=0x%X count=%d size=%d", it.second->getkey(), + it.second->start_address, it.second->register_count, it.second->get_register_size()); + } +} + +ModbusCommandItem ModbusCommandItem::create_read_command( + ModbusController *modbusdevice, ModbusRegisterType register_type, uint16_t start_address, uint16_t register_count, + std::function &data)> + &&handler) { + ModbusCommandItem cmd; + cmd.modbusdevice = modbusdevice; + cmd.register_type = register_type; + cmd.function_code = modbus_register_read_function(register_type); + cmd.register_address = start_address; + cmd.register_count = register_count; + cmd.on_data_func = std::move(handler); + return cmd; +} + +ModbusCommandItem ModbusCommandItem::create_read_command(ModbusController *modbusdevice, + ModbusRegisterType register_type, uint16_t start_address, + uint16_t register_count) { + ModbusCommandItem cmd; + cmd.modbusdevice = modbusdevice; + cmd.register_type = register_type; + cmd.function_code = modbus_register_read_function(register_type); + cmd.register_address = start_address; + cmd.register_count = register_count; + cmd.on_data_func = [modbusdevice](ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data) { + modbusdevice->on_register_data(register_type, start_address, data); + }; + return cmd; +} + +ModbusCommandItem ModbusCommandItem::create_write_multiple_command(ModbusController *modbusdevice, + uint16_t start_address, uint16_t register_count, + const std::vector &values) { + ModbusCommandItem cmd; + cmd.modbusdevice = modbusdevice; + cmd.register_type = ModbusRegisterType::HOLDING; + cmd.function_code = ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS; + cmd.register_address = start_address; + cmd.register_count = register_count; + cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data) { + modbusdevice->on_write_register_response(cmd.register_type, start_address, data); + }; + for (auto v : values) { + cmd.payload.push_back((v / 256) & 0xFF); + cmd.payload.push_back(v & 0xFF); + } + return cmd; +} + +ModbusCommandItem ModbusCommandItem::create_write_single_coil(ModbusController *modbusdevice, uint16_t address, + bool value) { + ModbusCommandItem cmd; + cmd.modbusdevice = modbusdevice; + cmd.register_type = ModbusRegisterType::COIL; + cmd.function_code = ModbusFunctionCode::WRITE_SINGLE_COIL; + cmd.register_address = address; + cmd.register_count = 1; + cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data) { + modbusdevice->on_write_register_response(cmd.register_type, start_address, data); + }; + cmd.payload.push_back(value ? 0xFF : 0); + cmd.payload.push_back(0); + return cmd; +} + +ModbusCommandItem ModbusCommandItem::create_write_multiple_coils(ModbusController *modbusdevice, uint16_t start_address, + const std::vector &values) { + ModbusCommandItem cmd; + cmd.modbusdevice = modbusdevice; + cmd.register_type = ModbusRegisterType::COIL; + cmd.function_code = ModbusFunctionCode::WRITE_MULTIPLE_COILS; + cmd.register_address = start_address; + cmd.register_count = values.size(); + cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data) { + modbusdevice->on_write_register_response(cmd.register_type, start_address, data); + }; + + uint8_t bitmask = 0; + int bitcounter = 0; + for (auto coil : values) { + if (coil) { + bitmask |= (1 << bitcounter); + } + bitcounter++; + if (bitcounter % 8 == 0) { + cmd.payload.push_back(bitmask); + bitmask = 0; + } + } + // add remaining bits + if (bitcounter % 8) { + cmd.payload.push_back(bitmask); + } + return cmd; +} + +ModbusCommandItem ModbusCommandItem::create_write_single_command(ModbusController *modbusdevice, uint16_t start_address, + int16_t value) { + ModbusCommandItem cmd; + cmd.modbusdevice = modbusdevice; + cmd.register_type = ModbusRegisterType::HOLDING; + cmd.function_code = ModbusFunctionCode::WRITE_SINGLE_REGISTER; + cmd.register_address = start_address; + cmd.register_count = 1; // not used here anyways + cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data) { + modbusdevice->on_write_register_response(cmd.register_type, start_address, data); + }; + cmd.payload.push_back((value / 256) & 0xFF); + cmd.payload.push_back((value % 256) & 0xFF); + return cmd; +} + +ModbusCommandItem ModbusCommandItem::create_custom_command( + ModbusController *modbusdevice, const std::vector &values, + std::function &data)> + &&handler) { + ModbusCommandItem cmd; + cmd.modbusdevice = modbusdevice; + cmd.function_code = ModbusFunctionCode::CUSTOM; + if (handler == nullptr) { + cmd.on_data_func = [](ModbusRegisterType, uint16_t, const std::vector &data) { + ESP_LOGI(TAG, "Custom Command sent"); + }; + } else { + cmd.on_data_func = handler; + } + cmd.payload = values; + + return cmd; +} + +bool ModbusCommandItem::send() { + if (this->function_code != ModbusFunctionCode::CUSTOM) { + modbusdevice->send(uint8_t(this->function_code), this->register_address, this->register_count, this->payload.size(), + this->payload.empty() ? nullptr : &this->payload[0]); + } else { + modbusdevice->send_raw(this->payload); + } + ESP_LOGV(TAG, "Command sent %d 0x%X %d", uint8_t(this->function_code), this->register_address, this->register_count); + return true; +} + +std::vector float_to_payload(float value, SensorValueType value_type) { + union { + float float_value; + uint32_t raw; + } raw_to_float; + + std::vector data; + int32_t val; + + switch (value_type) { + case SensorValueType::U_WORD: + case SensorValueType::S_WORD: + // cast truncates the float do some rounding here + data.push_back(lroundf(value) & 0xFFFF); + break; + case SensorValueType::U_DWORD: + case SensorValueType::S_DWORD: + val = lroundf(value); + data.push_back((val & 0xFFFF0000) >> 16); + data.push_back(val & 0xFFFF); + break; + case SensorValueType::U_DWORD_R: + case SensorValueType::S_DWORD_R: + val = lroundf(value); + data.push_back(val & 0xFFFF); + data.push_back((val & 0xFFFF0000) >> 16); + break; + case SensorValueType::FP32: + raw_to_float.float_value = value; + data.push_back((raw_to_float.raw & 0xFFFF0000) >> 16); + data.push_back(raw_to_float.raw & 0xFFFF); + break; + case SensorValueType::FP32_R: + raw_to_float.float_value = value; + data.push_back(raw_to_float.raw & 0xFFFF); + data.push_back((raw_to_float.raw & 0xFFFF0000) >> 16); + break; + default: + ESP_LOGE(TAG, "Invalid data type for modbus float to payload conversation"); + break; + } + return data; +} + +float payload_to_float(const std::vector &data, SensorValueType sensor_value_type, uint8_t offset, + uint32_t bitmask) { + union { + float float_value; + uint32_t raw; + } raw_to_float; + + int64_t value = 0; // int64_t because it can hold signed and unsigned 32 bits + float result = NAN; + + switch (sensor_value_type) { + case SensorValueType::U_WORD: + value = mask_and_shift_by_rightbit(get_data(data, offset), bitmask); // default is 0xFFFF ; + result = static_cast(value); + break; + case SensorValueType::U_DWORD: + value = get_data(data, offset); + value = mask_and_shift_by_rightbit((uint32_t) value, bitmask); + result = static_cast(value); + break; + case SensorValueType::U_DWORD_R: + value = get_data(data, offset); + value = static_cast(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16; + value = mask_and_shift_by_rightbit((uint32_t) value, bitmask); + result = static_cast(value); + break; + case SensorValueType::S_WORD: + value = mask_and_shift_by_rightbit(get_data(data, offset), + bitmask); // default is 0xFFFF ; + result = static_cast(value); + break; + case SensorValueType::S_DWORD: + value = mask_and_shift_by_rightbit(get_data(data, offset), bitmask); + result = static_cast(value); + break; + case SensorValueType::S_DWORD_R: { + value = get_data(data, offset); + // Currently the high word is at the low position + // the sign bit is therefore at low before the switch + uint32_t sign_bit = (value & 0x8000) << 16; + value = mask_and_shift_by_rightbit( + static_cast(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask); + result = static_cast(value); + } break; + case SensorValueType::U_QWORD: + // Ignore bitmask for U_QWORD + value = get_data(data, offset); + result = static_cast(value); + break; + + case SensorValueType::S_QWORD: + // Ignore bitmask for S_QWORD + value = get_data(data, offset); + result = static_cast(value); + break; + case SensorValueType::U_QWORD_R: + // Ignore bitmask for U_QWORD + value = get_data(data, offset); + value = static_cast(value & 0xFFFF) << 48 | (value & 0xFFFF000000000000) >> 48 | + static_cast(value & 0xFFFF0000) << 32 | (value & 0x0000FFFF00000000) >> 32 | + static_cast(value & 0xFFFF00000000) << 16 | (value & 0x00000000FFFF0000) >> 16; + result = static_cast(value); + break; + + case SensorValueType::S_QWORD_R: + // Ignore bitmask for S_QWORD + value = get_data(data, offset); + result = static_cast(value); + break; + case SensorValueType::FP32: + raw_to_float.raw = get_data(data, offset); + ESP_LOGD(TAG, "FP32 = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value); + result = raw_to_float.float_value; + break; + case SensorValueType::FP32_R: { + auto tmp = get_data(data, offset); + raw_to_float.raw = static_cast(tmp & 0xFFFF) << 16 | (tmp & 0xFFFF0000) >> 16; + ESP_LOGD(TAG, "FP32_R = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value); + result = raw_to_float.float_value; + } break; + default: + break; + } + return result; +} + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h new file mode 100644 index 0000000000..4b5f4337db --- /dev/null +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -0,0 +1,454 @@ +#pragma once + +#include "esphome/core/component.h" + +#include "esphome/core/automation.h" +#include "esphome/components/modbus/modbus.h" + +#include +#include +#include +#include + +namespace esphome { +namespace modbus_controller { + +class ModbusController; + +enum class ModbusFunctionCode { + CUSTOM = 0x00, + READ_COILS = 0x01, + READ_DISCRETE_INPUTS = 0x02, + READ_HOLDING_REGISTERS = 0x03, + READ_INPUT_REGISTERS = 0x04, + WRITE_SINGLE_COIL = 0x05, + WRITE_SINGLE_REGISTER = 0x06, + READ_EXCEPTION_STATUS = 0x07, // not implemented + DIAGNOSTICS = 0x08, // not implemented + GET_COMM_EVENT_COUNTER = 0x0B, // not implemented + GET_COMM_EVENT_LOG = 0x0C, // not implemented + WRITE_MULTIPLE_COILS = 0x0F, + WRITE_MULTIPLE_REGISTERS = 0x10, + REPORT_SERVER_ID = 0x11, // not implemented + READ_FILE_RECORD = 0x14, // not implemented + WRITE_FILE_RECORD = 0x15, // not implemented + MASK_WRITE_REGISTER = 0x16, // not implemented + READ_WRITE_MULTIPLE_REGISTERS = 0x17, // not implemented + READ_FIFO_QUEUE = 0x18, // not implemented +}; + +enum class ModbusRegisterType : int { + CUSTOM = 0x0, + COIL = 0x01, + DISCRETE_INPUT = 0x02, + HOLDING = 0x03, + READ = 0x04, +}; + +enum class SensorValueType : uint8_t { + RAW = 0x00, // variable length + U_WORD = 0x1, // 1 Register unsigned + U_DWORD = 0x2, // 2 Registers unsigned + S_WORD = 0x3, // 1 Register signed + S_DWORD = 0x4, // 2 Registers signed + BIT = 0x5, + U_DWORD_R = 0x6, // 2 Registers unsigned + S_DWORD_R = 0x7, // 2 Registers unsigned + U_QWORD = 0x8, + S_QWORD = 0x9, + U_QWORD_R = 0xA, + S_QWORD_R = 0xB, + FP32 = 0xC, + FP32_R = 0xD +}; + +struct RegisterRange { + uint16_t start_address; + ModbusRegisterType register_type; + uint8_t register_count; + uint8_t skip_updates; // the config value + uint64_t first_sensorkey; + uint8_t skip_updates_counter; // the running value +} __attribute__((packed)); + +inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) { + switch (reg_type) { + case ModbusRegisterType::COIL: + return ModbusFunctionCode::READ_COILS; + break; + case ModbusRegisterType::DISCRETE_INPUT: + return ModbusFunctionCode::READ_DISCRETE_INPUTS; + break; + case ModbusRegisterType::HOLDING: + return ModbusFunctionCode::READ_HOLDING_REGISTERS; + break; + case ModbusRegisterType::READ: + return ModbusFunctionCode::READ_INPUT_REGISTERS; + break; + default: + return ModbusFunctionCode::CUSTOM; + break; + } +} +inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_type) { + switch (reg_type) { + case ModbusRegisterType::COIL: + return ModbusFunctionCode::WRITE_SINGLE_COIL; + break; + case ModbusRegisterType::DISCRETE_INPUT: + return ModbusFunctionCode::CUSTOM; + break; + case ModbusRegisterType::HOLDING: + return ModbusFunctionCode::READ_WRITE_MULTIPLE_REGISTERS; + break; + case ModbusRegisterType::READ: + return ModbusFunctionCode::CUSTOM; + break; + default: + return ModbusFunctionCode::CUSTOM; + break; + } +} + +/** All sensors are stored in a map + * to enable binary sensors for values encoded as bits in the same register the key of each sensor + * the key is a 64 bit integer that combines the register properties + * sensormap_ is sorted by this key. The key ensures the correct order when creating consequtive ranges + * Format: function_code (8 bit) | start address (16 bit)| offset (8bit)| bitmask (32 bit) + */ +inline uint64_t calc_key(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset = 0, + uint32_t bitmask = 0) { + return uint64_t((uint16_t(register_type) << 24) + (uint32_t(start_address) << 8) + (offset & 0xFF)) << 32 | bitmask; +} +inline uint16_t register_from_key(uint64_t key) { return (key >> 40) & 0xFFFF; } + +inline uint8_t c_to_hex(char c) { return (c >= 'A') ? (c >= 'a') ? (c - 'a' + 10) : (c - 'A' + 10) : (c - '0'); } + +/** Get a byte from a hex string + * hex_byte_from_str("1122",1) returns uint_8 value 0x22 == 34 + * hex_byte_from_str("1122",0) returns 0x11 + * @param value string containing hex encoding + * @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in + * the hex string is byte_pos * 2 + * @return byte value + */ +inline uint8_t byte_from_hex_str(const std::string &value, uint8_t pos) { + if (value.length() < pos * 2 + 1) + return 0; + return (c_to_hex(value[pos * 2]) << 4) | c_to_hex(value[pos * 2 + 1]); +} + +/** Get a word from a hex string + * @param value string containing hex encoding + * @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in + * the hex string is byte_pos * 2 + * @return word value + */ +inline uint16_t word_from_hex_str(const std::string &value, uint8_t pos) { + return byte_from_hex_str(value, pos) << 8 | byte_from_hex_str(value, pos + 1); +} + +/** Get a dword from a hex string + * @param value string containing hex encoding + * @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in + * the hex string is byte_pos * 2 + * @return dword value + */ +inline uint32_t dword_from_hex_str(const std::string &value, uint8_t pos) { + return word_from_hex_str(value, pos) << 16 | word_from_hex_str(value, pos + 2); +} + +/** Get a qword from a hex string + * @param value string containing hex encoding + * @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in + * the hex string is byte_pos * 2 + * @return qword value + */ +inline uint64_t qword_from_hex_str(const std::string &value, uint8_t pos) { + return static_cast(dword_from_hex_str(value, pos)) << 32 | dword_from_hex_str(value, pos + 4); +} + +// Extract data from modbus response buffer +/** Extract data from modbus response buffer + * @param T one of supported integer data types int_8,int_16,int_32,int_64 + * @param data modbus response buffer (uint8_t) + * @param buffer_offset offset in bytes. + * @return value of type T extracted from buffer + */ +template T get_data(const std::vector &data, size_t buffer_offset) { + if (sizeof(T) == sizeof(uint8_t)) { + return T(data[buffer_offset]); + } + if (sizeof(T) == sizeof(uint16_t)) { + return T((uint16_t(data[buffer_offset + 0]) << 8) | (uint16_t(data[buffer_offset + 1]) << 0)); + } + + if (sizeof(T) == sizeof(uint32_t)) { + return get_data(data, buffer_offset) << 16 | get_data(data, (buffer_offset + 2)); + } + + if (sizeof(T) == sizeof(uint64_t)) { + return static_cast(get_data(data, buffer_offset)) << 32 | + (static_cast(get_data(data, buffer_offset + 4))); + } +} + +/** Extract coil data from modbus response buffer + * Responses for coil are packed into bytes . + * coil 3 is bit 3 of the first response byte + * coil 9 is bit 2 of the second response byte + * @param coil number of the cil + * @param data modbus response buffer (uint8_t) + * @return content of coil register + */ +inline bool coil_from_vector(int coil, const std::vector &data) { + auto data_byte = coil / 8; + return (data[data_byte] & (1 << (coil % 8))) > 0; +} + +/** Extract bits from value and shift right according to the bitmask + * if the bitmask is 0x00F0 we want the values frrom bit 5 - 8. + * the result is then shifted right by the postion if the first right set bit in the mask + * Usefull for modbus data where more than one value is packed in a 16 bit register + * Example: on Epever the "Length of night" register 0x9065 encodes values of the whole night length of time as + * D15 - D8 = hour, D7 - D0 = minute + * To get the hours use mask 0xFF00 and 0x00FF for the minute + * @param data an integral value between 16 aand 32 bits, + * @param bitmask the bitmask to apply + */ +template N mask_and_shift_by_rightbit(N data, uint32_t mask) { + auto result = (mask & data); + if (result == 0) { + return result; + } + for (int pos = 0; pos < sizeof(N) << 3; pos++) { + if ((mask & (1 << pos)) != 0) + return result >> pos; + } + return 0; +} + +/** convert float value to vector suitable for sending + * @param value float value to cconvert + * @param value_type defines if 16/32 or FP32 is used + * @return vector containing the modbus register words in correct order + */ +std::vector float_to_payload(float value, SensorValueType value_type); + +/** convert vector response payload to float + * @param value float value to cconvert + * @param sensor_value_type defines if 16/32/64 bits or FP32 is used + * @param offset offset to the data in data + * @param bitmask bitmask used for masking and shifting + * @return float version of the input + */ +float payload_to_float(const std::vector &data, SensorValueType sensor_value_type, uint8_t offset, + uint32_t bitmask); + +class ModbusController; + +struct SensorItem { + ModbusRegisterType register_type; + SensorValueType sensor_value_type; + uint16_t start_address; + uint32_t bitmask; + uint8_t offset; + uint8_t register_count; + uint8_t skip_updates; + bool force_new_range{false}; + + virtual void parse_and_publish(const std::vector &data) = 0; + + uint64_t getkey() const { return calc_key(register_type, start_address, offset, bitmask); } + + size_t virtual get_register_size() const { + size_t size = 0; + switch (sensor_value_type) { + case SensorValueType::BIT: + size = 1; + break; + case SensorValueType::U_WORD: + case SensorValueType::S_WORD: + size = 2; + break; + case SensorValueType::U_DWORD: + case SensorValueType::S_DWORD: + case SensorValueType::U_DWORD_R: + case SensorValueType::S_DWORD_R: + case SensorValueType::FP32: + case SensorValueType::FP32_R: + size = 4; + break; + case SensorValueType::U_QWORD: + case SensorValueType::U_QWORD_R: + case SensorValueType::S_QWORD: + case SensorValueType::S_QWORD_R: + size = 8; + break; + case SensorValueType::RAW: + size = this->register_count * 2; + } + return size; + } +}; + +struct ModbusCommandItem { + static const size_t MAX_PAYLOAD_BYTES = 240; + ModbusController *modbusdevice; + uint16_t register_address; + uint16_t register_count; + ModbusFunctionCode function_code; + ModbusRegisterType register_type; + std::function &data)> + on_data_func; + std::vector payload = {}; + bool send(); + + /// factory methods + /** Create modbus read command + * Function code 02-04 + * @param modbusdevice pointer to the device to execute the command + * @param function_code modbus function code for the read command + * @param start_address modbus address of the first register to read + * @param register_count number of registers to read + * @param handler function called when the response is received + * @return ModbusCommandItem with the prepared command + */ + static ModbusCommandItem create_read_command( + ModbusController *modbusdevice, ModbusRegisterType register_type, uint16_t start_address, uint16_t register_count, + std::function &data)> + &&handler); + /** Create modbus read command + * Function code 02-04 + * @param modbusdevice pointer to the device to execute the command + * @param function_code modbus function code for the read command + * @param start_address modbus address of the first register to read + * @param register_count number of registers to read + * @return ModbusCommandItem with the prepared command + */ + static ModbusCommandItem create_read_command(ModbusController *modbusdevice, ModbusRegisterType register_type, + uint16_t start_address, uint16_t register_count); + /** Create modbus read command + * Function code 02-04 + * @param modbusdevice pointer to the device to execute the command + * @param function_code modbus function code for the read command + * @param start_address modbus address of the first register to read + * @param register_count number of registers to read + * @param handler function called when the response is received + * @return ModbusCommandItem with the prepared command + */ + static ModbusCommandItem create_write_multiple_command(ModbusController *modbusdevice, uint16_t start_address, + uint16_t register_count, const std::vector &values); + /** Create modbus write multiple registers command + * Function 16 (10hex) Write Multiple Registers + * @param modbusdevice pointer to the device to execute the command + * @param start_address modbus address of the first register to read + * @param register_count number of registers to read + * @param values uint16_t array to be written to the registers + * @return ModbusCommandItem with the prepared command + */ + static ModbusCommandItem create_write_single_command(ModbusController *modbusdevice, uint16_t start_address, + int16_t value); + /** Create modbus write single registers command + * Function 05 (05hex) Write Single Coil + * @param modbusdevice pointer to the device to execute the command + * @param start_address modbus address of the first register to read + * @param value uint16_t data to be written to the registers + * @return ModbusCommandItem with the prepared command + */ + static ModbusCommandItem create_write_single_coil(ModbusController *modbusdevice, uint16_t address, bool value); + + /** Create modbus write multiple registers command + * Function 15 (0Fhex) Write Multiple Coils + * @param modbusdevice pointer to the device to execute the command + * @param start_address modbus address of the first register to read + * @param value bool vector of values to be written to the registers + * @return ModbusCommandItem with the prepared command + */ + static ModbusCommandItem create_write_multiple_coils(ModbusController *modbusdevice, uint16_t start_address, + const std::vector &values); + /** Create custom modbus command + * @param modbusdevice pointer to the device to execute the command + * @param values byte vector of data to be sent to the device. The compplete payload must be provided with the + * exception of the crc codess + * @param handler function called when the response is received. Default is just logging a response + * @return ModbusCommandItem with the prepared command + */ + static ModbusCommandItem create_custom_command( + ModbusController *modbusdevice, const std::vector &values, + std::function &data)> + &&handler = nullptr); +}; + +/** Modbus controller class. + * Each instance handles the modbus commuinication for all sensors with the same modbus address + * + * all sensor items (sensors, switches, binarysensor ...) are parsed in modbus address ranges. + * when esphome calls ModbusController::Update the commands for each range are created and sent + * Responses for the commands are dispatched to the modbus sensor items. + */ + +class ModbusController : public PollingComponent, public modbus::ModbusDevice { + public: + ModbusController(uint16_t throttle = 0) : modbus::ModbusDevice(), command_throttle_(throttle){}; + void dump_config() override; + void loop() override; + void setup() override; + void update() override; + + /// queues a modbus command in the send queue + void queue_command(const ModbusCommandItem &command); + /// Registers a sensor with the controller. Called by esphomes code generator + void add_sensor_item(SensorItem *item) { sensormap_[item->getkey()] = item; } + /// called when a modbus response was prased without errors + void on_modbus_data(const std::vector &data) override; + /// called when a modbus error response was received + void on_modbus_error(uint8_t function_code, uint8_t exception_code) override; + /// default delegate called by process_modbus_data when a response has retrieved from the incoming queue + void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector &data); + /// default delegate called by process_modbus_data when a response for a write response has retrieved from the + /// incoming queue + void on_write_register_response(ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data); + /// called by esphome generated code to set the command_throttle period + void set_command_throttle(uint16_t command_throttle) { this->command_throttle_ = command_throttle; } + + protected: + /// parse sensormap_ and create range of sequential addresses + size_t create_register_ranges_(); + /// submit the read command for the address range to the send queue + void update_range_(RegisterRange &r); + /// parse incoming modbus data + void process_modbus_data_(const ModbusCommandItem *response); + /// send the next modbus command from the send queue + bool send_next_command_(); + /// get the number of queued modbus commands (should be mostly empty) + size_t get_command_queue_length_() { return command_queue_.size(); } + /// dump the parsed sensormap for diagnostics + void dump_sensormap_(); + /// Collection of all sensors for this component + /// see calc_key how the key is contructed + std::map sensormap_; + /// Continous range of modbus registers + std::vector register_ranges_; + /// Hold the pending requests to be sent + std::list> command_queue_; + /// modbus response data waiting to get processed + std::queue> incoming_queue_; + /// when was the last send operation + uint32_t last_command_timestamp_; + /// min time in ms between sending modbus commands + uint16_t command_throttle_; +}; + +/** convert vector response payload to float + * @param value float value to cconvert + * @param item SensorItem object + * @return float version of the input + */ +inline float payload_to_float(const std::vector &data, const SensorItem &item) { + return payload_to_float(data, item.sensor_value_type, item.offset, item.bitmask); +} + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py new file mode 100644 index 0000000000..c7919bb972 --- /dev/null +++ b/esphome/components/modbus_controller/number/__init__.py @@ -0,0 +1,157 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import number +from esphome.const import ( + CONF_ADDRESS, + CONF_ID, + CONF_LAMBDA, + CONF_MAX_VALUE, + CONF_MIN_VALUE, + CONF_MULTIPLY, + CONF_OFFSET, + CONF_STEP, +) + +from .. import ( + modbus_controller_ns, + ModbusController, + SENSOR_VALUE_TYPE, + SensorItem, +) + + +from ..const import ( + CONF_BITMASK, + CONF_BYTE_OFFSET, + CONF_FORCE_NEW_RANGE, + CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_COUNT, + CONF_SKIP_UPDATES, + CONF_VALUE_TYPE, + CONF_WRITE_LAMBDA, +) + +DEPENDENCIES = ["modbus_controller"] +CODEOWNERS = ["@martgras"] + + +ModbusNumber = modbus_controller_ns.class_( + "ModbusNumber", cg.Component, number.Number, SensorItem +) + +TYPE_REGISTER_MAP = { + "RAW": 1, + "U_WORD": 1, + "S_WORD": 1, + "U_DWORD": 2, + "U_DWORD_R": 2, + "S_DWORD": 2, + "S_DWORD_R": 2, + "U_QWORD": 4, + "U_QWORDU_R": 4, + "S_QWORD": 4, + "U_QWORD_R": 4, + "FP32": 2, + "FP32_R": 2, +} + + +def validate_min_max(config): + if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]: + raise cv.Invalid("max_value must be greater than min_value") + if config[CONF_MIN_VALUE] < -16777215: + raise cv.Invalid("max_value must be greater than -16777215") + if config[CONF_MAX_VALUE] > 16777215: + raise cv.Invalid("max_value must not be greater than 16777215") + return config + + +CONFIG_SCHEMA = cv.All( + number.NUMBER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ModbusNumber), + cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), + cv.Required(CONF_ADDRESS): cv.positive_int, + cv.Optional(CONF_OFFSET, default=0): cv.positive_int, + cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, + cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t, + cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), + cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int, + cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, + cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, + cv.GenerateID(): cv.declare_id(ModbusNumber), + # 24 bits are the maximum value for fp32 before precison is lost + # 0x00FFFFFF = 16777215 + cv.Optional(CONF_MAX_VALUE, default=16777215.0): cv.float_, + cv.Optional(CONF_MIN_VALUE, default=-16777215.0): cv.float_, + cv.Optional(CONF_STEP, default=1): cv.positive_float, + cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, + } + ).extend(cv.polling_component_schema("60s")), + validate_min_max, +) + + +async def to_code(config): + byte_offset = 0 + if CONF_OFFSET in config: + byte_offset = config[CONF_OFFSET] + # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET + if CONF_BYTE_OFFSET in config: + byte_offset = config[CONF_BYTE_OFFSET] + value_type = config[CONF_VALUE_TYPE] + reg_count = config[CONF_REGISTER_COUNT] + if reg_count == 0: + reg_count = TYPE_REGISTER_MAP[value_type] + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_ADDRESS], + byte_offset, + config[CONF_BITMASK], + config[CONF_VALUE_TYPE], + reg_count, + config[CONF_SKIP_UPDATES], + config[CONF_FORCE_NEW_RANGE], + ) + + await cg.register_component(var, config) + await number.register_number( + var, + config, + min_value=config[CONF_MIN_VALUE], + max_value=config[CONF_MAX_VALUE], + step=config[CONF_STEP], + ) + + cg.add(var.set_write_multiply(config[CONF_MULTIPLY])) + parent = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) + + cg.add(var.set_parent(parent)) + cg.add(parent.add_sensor_item(var)) + if CONF_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_LAMBDA], + [ + (ModbusNumber.operator("ptr"), "item"), + (cg.float_, "x"), + ( + cg.std_vector.template(cg.uint8).operator("const").operator("ref"), + "data", + ), + ], + return_type=cg.optional.template(float), + ) + cg.add(var.set_template(template_)) + if CONF_WRITE_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_WRITE_LAMBDA], + [ + (ModbusNumber.operator("ptr"), "item"), + (cg.float_, "x"), + (cg.std_vector.template(cg.uint16).operator("ref"), "payload"), + ], + return_type=cg.optional.template(float), + ) + cg.add(var.set_write_template(template_)) diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp new file mode 100644 index 0000000000..95c6ac6f6a --- /dev/null +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -0,0 +1,83 @@ +#include +#include "modbus_number.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace modbus_controller { + +static const char *const TAG = "modbus.number"; + +void ModbusNumber::parse_and_publish(const std::vector &data) { + union { + float float_value; + uint32_t raw; + } raw_to_float; + + float result = payload_to_float(data, *this); + + // Is there a lambda registered + // call it with the pre converted value and the raw data array + if (this->transform_func_.has_value()) { + // the lambda can parse the response itself + auto val = (*this->transform_func_)(this, result, data); + if (val.has_value()) { + ESP_LOGV(TAG, "Value overwritten by lambda"); + result = val.value(); + } + } + ESP_LOGD(TAG, "Number new state : %.02f", result); + // this->sensor_->raw_state = result; + this->publish_state(result); +} + +void ModbusNumber::control(float value) { + union { + float float_value; + uint32_t raw; + } raw_to_float; + + std::vector data; + auto original_value = value; + // Is there are lambda configured? + if (this->write_transform_func_.has_value()) { + // data is passed by reference + // the lambda can fill the empty vector directly + // in that case the return value is ignored + auto val = (*this->write_transform_func_)(this, value, data); + if (val.has_value()) { + ESP_LOGV(TAG, "Value overwritten by lambda"); + value = val.value(); + } else { + ESP_LOGV(TAG, "Communication handled by lambda - exiting control"); + return; + } + } else { + value = multiply_by_ * value; + } + + // lambda didn't set payload + if (data.empty()) { + data = float_to_payload(value, this->sensor_value_type); + } + + ESP_LOGD(TAG, + "Updating register: connected Sensor=%s start address=0x%X register count=%d new value=%.02f (val=%.02f)", + this->get_name().c_str(), this->start_address, this->register_count, value, value); + + // Create and send the write command + auto write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, + this->register_count, data); + + // publish new value + write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address, + const std::vector &data) { + // gets called when the write command is ack'd from the device + parent_->on_write_register_response(write_cmd.register_type, start_address, data); + this->publish_state(value); + }; + parent_->queue_command(write_cmd); +} +void ModbusNumber::dump_config() { LOG_NUMBER(TAG, "Modbus Number", this); } + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/number/modbus_number.h b/esphome/components/modbus_controller/number/modbus_number.h new file mode 100644 index 0000000000..0fd4e314bc --- /dev/null +++ b/esphome/components/modbus_controller/number/modbus_number.h @@ -0,0 +1,48 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "esphome/components/modbus_controller/modbus_controller.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace modbus_controller { + +using value_to_data_t = std::function(float); + +class ModbusNumber : public number::Number, public Component, public SensorItem { + public: + ModbusNumber(uint16_t start_address, uint8_t offset, uint32_t bitmask, SensorValueType value_type, int register_count, + uint8_t skip_updates, bool force_new_range) + : number::Number(), Component(), SensorItem() { + this->register_type = ModbusRegisterType::HOLDING; + this->start_address = start_address; + this->offset = offset; + this->bitmask = bitmask; + this->sensor_value_type = value_type; + this->register_count = register_count; + this->skip_updates = skip_updates; + this->force_new_range = force_new_range; + }; + + void dump_config() override; + void parse_and_publish(const std::vector &data) override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + void set_update_interval(int) {} + void set_parent(ModbusController *parent) { this->parent_ = parent; } + void set_write_multiply(float factor) { multiply_by_ = factor; } + + using transform_func_t = std::function(ModbusNumber *, float, const std::vector &)>; + using write_transform_func_t = std::function(ModbusNumber *, float, std::vector &)>; + void set_template(transform_func_t &&f) { this->transform_func_ = f; } + void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } + + protected: + void control(float value) override; + optional transform_func_; + optional write_transform_func_; + ModbusController *parent_; + float multiply_by_{1.0}; +}; + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/output/__init__.py b/esphome/components/modbus_controller/output/__init__.py new file mode 100644 index 0000000000..9c41fc011c --- /dev/null +++ b/esphome/components/modbus_controller/output/__init__.py @@ -0,0 +1,74 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import output + +from esphome.const import ( + CONF_ADDRESS, + CONF_ID, + CONF_MULTIPLY, + CONF_OFFSET, +) + +from .. import ( + SensorItem, + modbus_controller_ns, + ModbusController, +) + +from ..const import ( + CONF_BYTE_OFFSET, + CONF_MODBUS_CONTROLLER_ID, + CONF_VALUE_TYPE, + CONF_WRITE_LAMBDA, +) +from ..sensor import SENSOR_VALUE_TYPE + +DEPENDENCIES = ["modbus_controller"] +CODEOWNERS = ["@martgras"] + + +ModbusOutput = modbus_controller_ns.class_( + "ModbusOutput", cg.Component, output.FloatOutput, SensorItem +) + +CONFIG_SCHEMA = cv.All( + output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), + cv.GenerateID(): cv.declare_id(ModbusOutput), + cv.Required(CONF_ADDRESS): cv.positive_int, + cv.Optional(CONF_OFFSET, default=0): cv.positive_int, + cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, + cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), + cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, + } + ), +) + + +async def to_code(config): + byte_offset = 0 + if CONF_OFFSET in config: + byte_offset = config[CONF_OFFSET] + # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET + if CONF_BYTE_OFFSET in config: + byte_offset = config[CONF_BYTE_OFFSET] + var = cg.new_Pvariable( + config[CONF_ID], config[CONF_ADDRESS], byte_offset, config[CONF_VALUE_TYPE] + ) + await output.register_output(var, config) + cg.add(var.set_write_multiply(config[CONF_MULTIPLY])) + parent = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) + cg.add(var.set_parent(parent)) + if CONF_WRITE_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_WRITE_LAMBDA], + [ + (ModbusOutput.operator("ptr"), "item"), + (cg.float_, "x"), + (cg.std_vector.template(cg.uint16).operator("ref"), "payload"), + ], + return_type=cg.optional.template(float), + ) + cg.add(var.set_write_template(template_)) diff --git a/esphome/components/modbus_controller/output/modbus_output.cpp b/esphome/components/modbus_controller/output/modbus_output.cpp new file mode 100644 index 0000000000..f7d7c42342 --- /dev/null +++ b/esphome/components/modbus_controller/output/modbus_output.cpp @@ -0,0 +1,61 @@ +#include +#include "modbus_output.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace modbus_controller { + +static const char *const TAG = "modbus_controller.output"; + +void ModbusOutput::setup() {} + +/** Write a value to the device + * + */ +void ModbusOutput::write_state(float value) { + union { + float float_value; + uint32_t raw; + } raw_to_float; + + std::vector data; + auto original_value = value; + // Is there are lambda configured? + if (this->write_transform_func_.has_value()) { + // data is passed by reference + // the lambda can fill the empty vector directly + // in that case the return value is ignored + auto val = (*this->write_transform_func_)(this, value, data); + if (val.has_value()) { + ESP_LOGV(TAG, "Value overwritten by lambda"); + value = val.value(); + } else { + ESP_LOGV(TAG, "Communication handled by lambda - exiting control"); + return; + } + } else { + value = multiply_by_ * value; + } + // lambda didn't set payload + if (data.empty()) { + data = float_to_payload(value, this->sensor_value_type); + } + + ESP_LOGD(TAG, "Updating register: start address=0x%X register count=%d new value=%.02f (val=%.02f)", + this->start_address, this->register_count, value, original_value); + + // Create and send the write command + auto write_cmd = + ModbusCommandItem::create_write_multiple_command(parent_, this->start_address, this->register_count, data); + parent_->queue_command(write_cmd); +} + +void ModbusOutput::dump_config() { + ESP_LOGCONFIG(TAG, "Modbus Float Output:"); + LOG_FLOAT_OUTPUT(this); + ESP_LOGCONFIG(TAG, "Modbus device start address=0x%X register count=%d value type=%hhu", this->start_address, + this->register_count, this->sensor_value_type); +} + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/output/modbus_output.h b/esphome/components/modbus_controller/output/modbus_output.h new file mode 100644 index 0000000000..f46aef4683 --- /dev/null +++ b/esphome/components/modbus_controller/output/modbus_output.h @@ -0,0 +1,45 @@ +#pragma once + +#include "esphome/components/output/float_output.h" +#include "esphome/components/modbus_controller/modbus_controller.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace modbus_controller { + +using value_to_data_t = std::function(float); + +class ModbusOutput : public output::FloatOutput, public Component, public SensorItem { + public: + ModbusOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type) + : output::FloatOutput(), Component() { + this->register_type = ModbusRegisterType::HOLDING; + this->start_address = start_address; + this->offset = offset; + this->bitmask = bitmask; + this->sensor_value_type = value_type; + this->skip_updates = 0; + this->start_address += offset; + this->offset = 0; + } + void setup() override; + void dump_config() override; + + void set_parent(ModbusController *parent) { this->parent_ = parent; } + void set_write_multiply(float factor) { multiply_by_ = factor; } + // Do nothing + void parse_and_publish(const std::vector &data) override{}; + + using write_transform_func_t = std::function(ModbusOutput *, float, std::vector &)>; + void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } + + protected: + void write_state(float value) override; + optional write_transform_func_{nullopt}; + + ModbusController *parent_; + float multiply_by_{1.0}; +}; + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/sensor/__init__.py b/esphome/components/modbus_controller/sensor/__init__.py new file mode 100644 index 0000000000..687f3d82fb --- /dev/null +++ b/esphome/components/modbus_controller/sensor/__init__.py @@ -0,0 +1,109 @@ +from esphome.components import sensor +import esphome.config_validation as cv +import esphome.codegen as cg + +from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET +from .. import ( + SensorItem, + modbus_controller_ns, + ModbusController, + MODBUS_REGISTER_TYPE, + SENSOR_VALUE_TYPE, +) +from ..const import ( + CONF_BITMASK, + CONF_BYTE_OFFSET, + CONF_FORCE_NEW_RANGE, + CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_COUNT, + CONF_REGISTER_TYPE, + CONF_SKIP_UPDATES, + CONF_VALUE_TYPE, +) + +DEPENDENCIES = ["modbus_controller"] +CODEOWNERS = ["@martgras"] + + +ModbusSensor = modbus_controller_ns.class_( + "ModbusSensor", cg.Component, sensor.Sensor, SensorItem +) + +TYPE_REGISTER_MAP = { + "RAW": 1, + "U_WORD": 1, + "S_WORD": 1, + "U_DWORD": 2, + "U_DWORD_R": 2, + "S_DWORD": 2, + "S_DWORD_R": 2, + "U_QWORD": 4, + "U_QWORDU_R": 4, + "S_QWORD": 4, + "U_QWORD_R": 4, + "FP32": 2, + "FP32_R": 2, +} + + +CONFIG_SCHEMA = cv.All( + sensor.SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ModbusSensor), + cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), + cv.Required(CONF_ADDRESS): cv.positive_int, + cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), + cv.Optional(CONF_OFFSET, default=0): cv.positive_int, + cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, + cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t, + cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), + cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int, + cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, + cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + } + ).extend(cv.COMPONENT_SCHEMA), +) + + +async def to_code(config): + byte_offset = 0 + if CONF_OFFSET in config: + byte_offset = config[CONF_OFFSET] + # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET + if CONF_BYTE_OFFSET in config: + byte_offset = config[CONF_BYTE_OFFSET] + value_type = config[CONF_VALUE_TYPE] + reg_count = config[CONF_REGISTER_COUNT] + if reg_count == 0: + reg_count = TYPE_REGISTER_MAP[value_type] + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_REGISTER_TYPE], + config[CONF_ADDRESS], + byte_offset, + config[CONF_BITMASK], + config[CONF_VALUE_TYPE], + reg_count, + config[CONF_SKIP_UPDATES], + config[CONF_FORCE_NEW_RANGE], + ) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + + paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) + cg.add(paren.add_sensor_item(var)) + if CONF_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_LAMBDA], + [ + (ModbusSensor.operator("ptr"), "item"), + (cg.float_, "x"), + ( + cg.std_vector.template(cg.uint8).operator("const").operator("ref"), + "data", + ), + ], + return_type=cg.optional.template(float), + ) + cg.add(var.set_template(template_)) diff --git a/esphome/components/modbus_controller/sensor/modbus_sensor.cpp b/esphome/components/modbus_controller/sensor/modbus_sensor.cpp new file mode 100644 index 0000000000..dbd0525347 --- /dev/null +++ b/esphome/components/modbus_controller/sensor/modbus_sensor.cpp @@ -0,0 +1,36 @@ + +#include "modbus_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace modbus_controller { + +static const char *const TAG = "modbus_controller.sensor"; + +void ModbusSensor::dump_config() { LOG_SENSOR(TAG, "Modbus Controller Sensor", this); } + +void ModbusSensor::parse_and_publish(const std::vector &data) { + union { + float float_value; + uint32_t raw; + } raw_to_float; + + float result = payload_to_float(data, *this); + + // Is there a lambda registered + // call it with the pre converted value and the raw data array + if (this->transform_func_.has_value()) { + // the lambda can parse the response itself + auto val = (*this->transform_func_)(this, result, data); + if (val.has_value()) { + ESP_LOGV(TAG, "Value overwritten by lambda"); + result = val.value(); + } + } + ESP_LOGD(TAG, "Sensor new state: %.02f", result); + // this->sensor_->raw_state = result; + this->publish_state(result); +} + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/sensor/modbus_sensor.h b/esphome/components/modbus_controller/sensor/modbus_sensor.h new file mode 100644 index 0000000000..4f48c2a4dd --- /dev/null +++ b/esphome/components/modbus_controller/sensor/modbus_sensor.h @@ -0,0 +1,35 @@ +#pragma once + +#include "esphome/components/modbus_controller/modbus_controller.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace modbus_controller { + +class ModbusSensor : public Component, public sensor::Sensor, public SensorItem { + public: + ModbusSensor(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask, + SensorValueType value_type, int register_count, uint8_t skip_updates, bool force_new_range) + : Component(), sensor::Sensor() { + this->register_type = register_type; + this->start_address = start_address; + this->offset = offset; + this->bitmask = bitmask; + this->sensor_value_type = value_type; + this->register_count = register_count; + this->skip_updates = skip_updates; + this->force_new_range = force_new_range; + } + + void parse_and_publish(const std::vector &data) override; + void dump_config() override; + using transform_func_t = std::function(ModbusSensor *, float, const std::vector &)>; + void set_template(transform_func_t &&f) { this->transform_func_ = f; } + + protected: + optional transform_func_{nullopt}; +}; + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/switch/__init__.py b/esphome/components/modbus_controller/switch/__init__.py new file mode 100644 index 0000000000..e03b0d37be --- /dev/null +++ b/esphome/components/modbus_controller/switch/__init__.py @@ -0,0 +1,81 @@ +from esphome.components import switch +import esphome.config_validation as cv +import esphome.codegen as cg + + +from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET +from .. import ( + MODBUS_REGISTER_TYPE, + SensorItem, + modbus_controller_ns, + ModbusController, +) +from ..const import ( + CONF_BITMASK, + CONF_BYTE_OFFSET, + CONF_FORCE_NEW_RANGE, + CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_TYPE, +) + +DEPENDENCIES = ["modbus_controller"] +CODEOWNERS = ["@martgras"] + + +ModbusSwitch = modbus_controller_ns.class_( + "ModbusSwitch", cg.Component, switch.Switch, SensorItem +) + + +CONFIG_SCHEMA = cv.All( + switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ModbusSwitch), + cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), + cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), + cv.Required(CONF_ADDRESS): cv.positive_int, + cv.Optional(CONF_OFFSET, default=0): cv.positive_int, + cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, + cv.Optional(CONF_BITMASK, default=0x1): cv.hex_uint32_t, + cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + } + ).extend(cv.COMPONENT_SCHEMA), +) + + +async def to_code(config): + byte_offset = 0 + if CONF_OFFSET in config: + byte_offset = config[CONF_OFFSET] + # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET + if CONF_BYTE_OFFSET in config: + byte_offset = config[CONF_BYTE_OFFSET] + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_REGISTER_TYPE], + config[CONF_ADDRESS], + byte_offset, + config[CONF_BITMASK], + config[CONF_FORCE_NEW_RANGE], + ) + await cg.register_component(var, config) + await switch.register_switch(var, config) + + paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) + cg.add(paren.add_sensor_item(var)) + cg.add(var.set_parent(paren)) + if CONF_LAMBDA in config: + publish_template_ = await cg.process_lambda( + config[CONF_LAMBDA], + [ + (ModbusSwitch.operator("ptr"), "item"), + (bool, "x"), + ( + cg.std_vector.template(cg.uint8).operator("const").operator("ref"), + "data", + ), + ], + return_type=cg.optional.template(bool), + ) + cg.add(var.set_template(publish_template_)) diff --git a/esphome/components/modbus_controller/switch/modbus_switch.cpp b/esphome/components/modbus_controller/switch/modbus_switch.cpp new file mode 100644 index 0000000000..ce9557e6c4 --- /dev/null +++ b/esphome/components/modbus_controller/switch/modbus_switch.cpp @@ -0,0 +1,70 @@ + +#include "modbus_switch.h" +#include "esphome/core/log.h" +namespace esphome { +namespace modbus_controller { + +static const char *const TAG = "modbus_controller.switch"; + +void ModbusSwitch::setup() { + // value isn't required + // without it we crash on save + this->get_initial_state(); +} +void ModbusSwitch::dump_config() { LOG_SWITCH(TAG, "Modbus Controller Switch", this); } + +void ModbusSwitch::parse_and_publish(const std::vector &data) { + bool value = false; + switch (this->register_type) { + case ModbusRegisterType::DISCRETE_INPUT: + case ModbusRegisterType::COIL: + // offset for coil is the actual number of the coil not the byte offset + value = coil_from_vector(this->offset, data); + break; + default: + value = get_data(data, this->offset) & this->bitmask; + break; + } + + // Is there a lambda registered + // call it with the pre converted value and the raw data array + if (this->publish_transform_func_) { + // the lambda can parse the response itself + auto val = (*this->publish_transform_func_)(this, value, data); + if (val.has_value()) { + ESP_LOGV(TAG, "Value overwritten by lambda"); + value = val.value(); + } + } + + ESP_LOGV(TAG, "Publish '%s': new value = %s type = %d address = %X offset = %x", this->get_name().c_str(), + ONOFF(value), (int) this->register_type, this->start_address, this->offset); + this->publish_state(value); +} + +void ModbusSwitch::write_state(bool state) { + // This will be called every time the user requests a state change. + ModbusCommandItem cmd; + ESP_LOGV(TAG, "write_state '%s': new value = %s type = %d address = %X offset = %x", this->get_name().c_str(), + ONOFF(state), (int) this->register_type, this->start_address, this->offset); + switch (this->register_type) { + case ModbusRegisterType::COIL: + // offset for coil and discrete inputs is the coil/register number not bytes + cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state); + break; + case ModbusRegisterType::DISCRETE_INPUT: + cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, state); + break; + + default: + // since offset is in bytes and a register is 16 bits we get the start by adding offset/2 + cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, + state ? 0xFFFF & this->bitmask : 0); + break; + } + this->parent_->queue_command(cmd); + publish_state(state); +} +// ModbusSwitch end +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/switch/modbus_switch.h b/esphome/components/modbus_controller/switch/modbus_switch.h new file mode 100644 index 0000000000..a38668fabb --- /dev/null +++ b/esphome/components/modbus_controller/switch/modbus_switch.h @@ -0,0 +1,44 @@ +#pragma once + +#include "esphome/components/modbus_controller/modbus_controller.h" +#include "esphome/components/switch/switch.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace modbus_controller { + +class ModbusSwitch : public Component, public switch_::Switch, public SensorItem { + public: + ModbusSwitch(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask, + bool force_new_range) + : Component(), switch_::Switch() { + this->register_type = register_type; + this->start_address = start_address; + this->offset = offset; + this->bitmask = bitmask; + this->sensor_value_type = SensorValueType::BIT; + this->skip_updates = 0; + this->register_count = 1; + if (register_type == ModbusRegisterType::HOLDING || register_type == ModbusRegisterType::COIL) { + this->start_address += offset; + this->offset = 0; + } + this->force_new_range = force_new_range; + }; + void setup() override; + void write_state(bool state) override; + void dump_config() override; + void set_state(bool state) { this->state = state; } + void parse_and_publish(const std::vector &data) override; + void set_parent(ModbusController *parent) { this->parent_ = parent; } + + using transform_func_t = std::function(ModbusSwitch *, bool, const std::vector &)>; + void set_template(transform_func_t &&f) { this->publish_transform_func_ = f; } + + protected: + ModbusController *parent_; + optional publish_transform_func_{nullopt}; +}; + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/text_sensor/__init__.py b/esphome/components/modbus_controller/text_sensor/__init__.py new file mode 100644 index 0000000000..2c02c86795 --- /dev/null +++ b/esphome/components/modbus_controller/text_sensor/__init__.py @@ -0,0 +1,101 @@ +from esphome.components import text_sensor +import esphome.config_validation as cv +import esphome.codegen as cg + + +from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET +from .. import ( + SensorItem, + modbus_controller_ns, + ModbusController, + MODBUS_REGISTER_TYPE, +) +from ..const import ( + CONF_BYTE_OFFSET, + CONF_FORCE_NEW_RANGE, + CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_COUNT, + CONF_RESPONSE_SIZE, + CONF_SKIP_UPDATES, + CONF_RAW_ENCODE, + CONF_REGISTER_TYPE, +) + +DEPENDENCIES = ["modbus_controller"] +CODEOWNERS = ["@martgras"] + + +ModbusTextSensor = modbus_controller_ns.class_( + "ModbusTextSensor", cg.Component, text_sensor.TextSensor, SensorItem +) + +RawEncoding_ns = modbus_controller_ns.namespace("RawEncoding") +RawEncoding = RawEncoding_ns.enum("RawEncoding") +RAW_ENCODING = { + "NONE": RawEncoding.NONE, + "HEXBYTES": RawEncoding.HEXBYTES, + "COMMA": RawEncoding.COMMA, +} + +CONFIG_SCHEMA = cv.All( + text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ModbusTextSensor), + cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), + cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), + cv.Required(CONF_ADDRESS): cv.positive_int, + cv.Optional(CONF_OFFSET, default=0): cv.positive_int, + cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, + cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int, + cv.Optional(CONF_RESPONSE_SIZE, default=2): cv.positive_int, + cv.Optional(CONF_RAW_ENCODE, default="NONE"): cv.enum(RAW_ENCODING), + cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, + cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + } + ).extend(cv.COMPONENT_SCHEMA), +) + + +async def to_code(config): + byte_offset = 0 + if CONF_OFFSET in config: + byte_offset = config[CONF_OFFSET] + # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET + if CONF_BYTE_OFFSET in config: + byte_offset = config[CONF_BYTE_OFFSET] + response_size = config[CONF_RESPONSE_SIZE] + reg_count = config[CONF_REGISTER_COUNT] + if reg_count == 0: + reg_count = response_size / 2 + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_REGISTER_TYPE], + config[CONF_ADDRESS], + byte_offset, + reg_count, + config[CONF_RESPONSE_SIZE], + config[CONF_RAW_ENCODE], + config[CONF_SKIP_UPDATES], + config[CONF_FORCE_NEW_RANGE], + ) + + await cg.register_component(var, config) + await text_sensor.register_text_sensor(var, config) + + paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) + cg.add(paren.add_sensor_item(var)) + if CONF_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_LAMBDA], + [ + (ModbusTextSensor.operator("ptr"), "item"), + (cg.std_string.operator("const").operator("ref"), "x"), + ( + cg.std_vector.template(cg.uint8).operator("const").operator("ref"), + "data", + ), + ], + return_type=cg.optional.template(cg.std_string), + ) + cg.add(var.set_template(template_)) diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp new file mode 100644 index 0000000000..a06d44e90b --- /dev/null +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp @@ -0,0 +1,56 @@ + +#include "modbus_textsensor.h" +#include "esphome/core/log.h" +#include +#include + +namespace esphome { +namespace modbus_controller { + +static const char *const TAG = "modbus_controller.text_sensor"; + +void ModbusTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Modbus Controller Text Sensor", this); } + +void ModbusTextSensor::parse_and_publish(const std::vector &data) { + std::ostringstream output; + uint8_t max_items = this->response_bytes_; + char buffer[4]; + bool add_comma = false; + for (auto b : data) { + switch (this->encode_) { + case RawEncoding::HEXBYTES: + sprintf(buffer, "%02x", b); + output << buffer; + break; + case RawEncoding::COMMA: + sprintf(buffer, add_comma ? ",%d" : "%d", b); + output << buffer; + add_comma = true; + break; + // Anything else no encoding + case RawEncoding::NONE: + default: + output << (char) b; + break; + } + if (--max_items == 0) { + break; + } + } + + auto result = output.str(); + // Is there a lambda registered + // call it with the pre converted value and the raw data array + if (this->transform_func_.has_value()) { + // the lambda can parse the response itself + auto val = (*this->transform_func_)(this, result, data); + if (val.has_value()) { + ESP_LOGV(TAG, "Value overwritten by lambda"); + result = val.value(); + } + } + this->publish_state(result); +} + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h new file mode 100644 index 0000000000..28d0f0b241 --- /dev/null +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h @@ -0,0 +1,52 @@ +#pragma once + +#include "esphome/components/modbus_controller/modbus_controller.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace modbus_controller { + +enum class RawEncoding { NONE = 0, HEXBYTES = 1, COMMA = 2 }; + +class ModbusTextSensor : public Component, public text_sensor::TextSensor, public SensorItem { + public: + ModbusTextSensor(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint8_t register_count, + uint16_t response_bytes, RawEncoding encode, uint8_t skip_updates, bool force_new_range) + : Component() { + this->register_type = register_type; + this->start_address = start_address; + this->offset = offset; + this->response_bytes_ = response_bytes; + this->register_count = register_count; + this->encode_ = encode; + this->skip_updates = skip_updates; + this->bitmask = 0xFFFFFFFF; + this->sensor_value_type = SensorValueType::RAW; + this->force_new_range = force_new_range; + } + size_t get_register_size() const override { + if (sensor_value_type == SensorValueType::RAW) { + return this->response_bytes_; + } else { + return SensorItem::get_register_size(); + } + } + + void dump_config() override; + + void parse_and_publish(const std::vector &data) override; + using transform_func_t = + std::function(ModbusTextSensor *, std::string, const std::vector &)>; + void set_template(transform_func_t &&f) { this->transform_func_ = f; } + + protected: + optional transform_func_{nullopt}; + + protected: + RawEncoding encode_; + uint16_t response_bytes_; +}; + +} // namespace modbus_controller +} // namespace esphome diff --git a/tests/test5.yaml b/tests/test5.yaml index f165617551..b22b19550e 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -36,6 +36,14 @@ i2c: modbus: uart_id: uart1 + flow_control_pin: 5 + id: mod_bus1 + +modbus_controller: + - id: modbus_controller_test + address: 0x2 + modbus_id: mod_bus1 + binary_sensor: - platform: gpio @@ -150,6 +158,14 @@ sensor: name: "SelecEM2M Maximum Demand Apparent Power" disabled_by_default: true + - id: battery_voltage + name: "Battery voltage2" + platform: modbus_controller + modbus_controller_id: modbus_controller_test + address: 0x331A + register_type: read + value_type: U_WORD + - platform: t6615 uart_id: uart2 co2: From 0d0954d74b3609615a583730479bdaa62113613c Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Mon, 27 Sep 2021 00:32:33 +0400 Subject: [PATCH 1443/1841] Midea fix (#2395) --- esphome/components/midea/climate.py | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/midea/climate.py b/esphome/components/midea/climate.py index 0d0bdce471..08e82025b6 100644 --- a/esphome/components/midea/climate.py +++ b/esphome/components/midea/climate.py @@ -282,4 +282,4 @@ async def to_code(config): if CONF_HUMIDITY_SETPOINT in config: sens = await sensor.new_sensor(config[CONF_HUMIDITY_SETPOINT]) cg.add(var.set_humidity_setpoint_sensor(sens)) - cg.add_library("dudanov/MideaUART", "1.1.5") + cg.add_library("dudanov/MideaUART", "1.1.8") diff --git a/platformio.ini b/platformio.ini index 1901f175af..6e605768fc 100644 --- a/platformio.ini +++ b/platformio.ini @@ -48,7 +48,7 @@ lib_deps = seeed-studio/Grove - Laser PM2.5 Sensor HM3301@1.0.3 ; hm3301 glmnet/Dsmr@0.5 ; dsmr rweather/Crypto@0.2.0 ; dsmr - dudanov/MideaUART@1.1.0 ; midea + dudanov/MideaUART@1.1.8 ; midea build_flags = ${common.build_flags} -DUSE_ARDUINO From 4c390d9f9f323c7af7093d77392d55d5fd46f354 Mon Sep 17 00:00:00 2001 From: JonasEr <8407728+JonasEr@users.noreply.github.com> Date: Sun, 26 Sep 2021 23:38:08 +0300 Subject: [PATCH 1444/1841] Extend nfc ndef records with Text (#2191) Co-authored-by: Oxan van Leeuwen --- esphome/components/nfc/ndef_message.cpp | 50 +++++++------- esphome/components/nfc/ndef_message.h | 4 +- esphome/components/nfc/ndef_record.cpp | 52 +++++--------- esphome/components/nfc/ndef_record.h | 76 ++++----------------- esphome/components/nfc/ndef_record_text.cpp | 40 +++++++++++ esphome/components/nfc/ndef_record_text.h | 41 +++++++++++ esphome/components/nfc/ndef_record_uri.cpp | 48 +++++++++++++ esphome/components/nfc/ndef_record_uri.h | 76 +++++++++++++++++++++ 8 files changed, 264 insertions(+), 123 deletions(-) create mode 100644 esphome/components/nfc/ndef_record_text.cpp create mode 100644 esphome/components/nfc/ndef_record_text.h create mode 100644 esphome/components/nfc/ndef_record_uri.cpp create mode 100644 esphome/components/nfc/ndef_record_uri.h diff --git a/esphome/components/nfc/ndef_message.cpp b/esphome/components/nfc/ndef_message.cpp index b1554f41ae..d8c940254e 100644 --- a/esphome/components/nfc/ndef_message.cpp +++ b/esphome/components/nfc/ndef_message.cpp @@ -10,16 +10,13 @@ NdefMessage::NdefMessage(std::vector &data) { uint8_t index = 0; while (index <= data.size()) { uint8_t tnf_byte = data[index++]; - bool me = tnf_byte & 0x40; - bool sr = tnf_byte & 0x10; - bool il = tnf_byte & 0x08; - uint8_t tnf = tnf_byte & 0x07; + bool me = tnf_byte & 0x40; // Message End bit (is set if this is the last record of the message) + bool sr = tnf_byte & 0x10; // Short record bit (is set if payload size is less or equal to 255 bytes) + bool il = tnf_byte & 0x08; // ID length bit (is set if ID Length field exists) + uint8_t tnf = tnf_byte & 0x07; // Type Name Format ESP_LOGVV(TAG, "me=%s, sr=%s, il=%s, tnf=%d", YESNO(me), YESNO(sr), YESNO(il), tnf); - auto record = make_unique(); - record->set_tnf(tnf); - uint8_t type_length = data[index++]; uint32_t payload_length = 0; if (sr) { @@ -38,28 +35,34 @@ NdefMessage::NdefMessage(std::vector &data) { ESP_LOGVV(TAG, "Lengths: type=%d, payload=%d, id=%d", type_length, payload_length, id_length); std::string type_str(data.begin() + index, data.begin() + index + type_length); - record->set_type(type_str); + index += type_length; + std::string id_str = ""; if (il) { - std::string id_str(data.begin() + index, data.begin() + index + id_length); - record->set_id(id_str); + id_str = std::string(data.begin() + index, data.begin() + index + id_length); index += id_length; } - uint8_t payload_identifier = 0x00; - if (type_str == "U") { - payload_identifier = data[index++]; - payload_length -= 1; + std::vector payload_data(data.begin() + index, data.begin() + index + payload_length); + + std::unique_ptr record; + + // Based on tnf and type, create a more specific NdefRecord object + // constructed from the payload data + if (tnf == TNF_WELL_KNOWN && type_str == "U") { + record = make_unique(payload_data); + } else if (tnf == TNF_WELL_KNOWN && type_str == "T") { + record = make_unique(payload_data); + } else { + // Could not recognize the record, so store as generic one. + record = make_unique(payload_data); + record->set_tnf(tnf); + record->set_type(type_str); } - std::string payload_str(data.begin() + index, data.begin() + index + payload_length); + record->set_id(id_str); - if (payload_identifier > 0x00 && payload_identifier <= PAYLOAD_IDENTIFIERS_COUNT) { - payload_str.insert(0, PAYLOAD_IDENTIFIERS[payload_identifier]); - } - - record->set_payload(payload_str); index += payload_length; ESP_LOGV(TAG, "Adding record type %s = %s", record->get_type().c_str(), record->get_payload().c_str()); @@ -82,13 +85,10 @@ bool NdefMessage::add_record(std::unique_ptr record) { bool NdefMessage::add_text_record(const std::string &text) { return this->add_text_record(text, "en"); }; bool NdefMessage::add_text_record(const std::string &text, const std::string &encoding) { - std::string payload = to_string(text.length()) + encoding + text; - return this->add_record(make_unique(TNF_WELL_KNOWN, "T", payload)); + return this->add_record(make_unique(encoding, text)); } -bool NdefMessage::add_uri_record(const std::string &uri) { - return this->add_record(make_unique(TNF_WELL_KNOWN, "U", uri)); -} +bool NdefMessage::add_uri_record(const std::string &uri) { return this->add_record(make_unique(uri)); } std::vector NdefMessage::encode() { std::vector data; diff --git a/esphome/components/nfc/ndef_message.h b/esphome/components/nfc/ndef_message.h index 20140e8c1a..5e44a06011 100644 --- a/esphome/components/nfc/ndef_message.h +++ b/esphome/components/nfc/ndef_message.h @@ -5,6 +5,8 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "ndef_record.h" +#include "ndef_record_text.h" +#include "ndef_record_uri.h" namespace esphome { namespace nfc { @@ -18,7 +20,7 @@ class NdefMessage { NdefMessage(const NdefMessage &msg) { records_.reserve(msg.records_.size()); for (const auto &r : msg.records_) { - records_.emplace_back(make_unique(*r)); + records_.emplace_back(r->clone()); } } diff --git a/esphome/components/nfc/ndef_record.cpp b/esphome/components/nfc/ndef_record.cpp index a75f5978ec..8a3a7d375d 100644 --- a/esphome/components/nfc/ndef_record.cpp +++ b/esphome/components/nfc/ndef_record.cpp @@ -5,40 +5,22 @@ namespace nfc { static const char *const TAG = "nfc.ndef_record"; -uint32_t NdefRecord::get_encoded_size() { - uint32_t size = 2; - if (this->payload_.length() > 255) { - size += 4; - } else { - size += 1; - } - if (this->id_.length()) { - size += 1; - } - size += (this->type_.length() + this->payload_.length() + this->id_.length()); - return size; +NdefRecord::NdefRecord(std::vector payload_data) { + this->payload_ = std::string(payload_data.begin(), payload_data.end()); } std::vector NdefRecord::encode(bool first, bool last) { std::vector data; - data.push_back(this->get_tnf_byte(first, last)); + // Get encoded payload, this is overriden by more specific record classes + std::vector payload_data = get_encoded_payload(); + + size_t payload_length = payload_data.size(); + + data.push_back(this->create_flag_byte(first, last, payload_length)); data.push_back(this->type_.length()); - uint8_t payload_prefix = 0x00; - uint8_t payload_prefix_length = 0x00; - for (uint8_t i = 1; i < PAYLOAD_IDENTIFIERS_COUNT; i++) { - std::string prefix = PAYLOAD_IDENTIFIERS[i]; - if (this->payload_.substr(0, prefix.length()).find(prefix) != std::string::npos) { - payload_prefix = i; - payload_prefix_length = prefix.length(); - break; - } - } - - uint32_t payload_length = this->payload_.length() - payload_prefix_length + 1; - if (payload_length <= 255) { data.push_back(payload_length); } else { @@ -58,25 +40,23 @@ std::vector NdefRecord::encode(bool first, bool last) { data.insert(data.end(), this->id_.begin(), this->id_.end()); } - data.push_back(payload_prefix); - - data.insert(data.end(), this->payload_.begin() + payload_prefix_length, this->payload_.end()); + data.insert(data.end(), payload_data.begin(), payload_data.end()); return data; } -uint8_t NdefRecord::get_tnf_byte(bool first, bool last) { - uint8_t value = this->tnf_; +uint8_t NdefRecord::create_flag_byte(bool first, bool last, size_t payload_size) { + uint8_t value = this->tnf_ & 0b00000111; if (first) { - value = value | 0x80; + value = value | 0x80; // Set MB bit } if (last) { - value = value | 0x40; + value = value | 0x40; // Set ME bit } - if (this->payload_.length() <= 255) { - value = value | 0x10; + if (payload_size <= 255) { + value = value | 0x10; // Set SR bit } if (this->id_.length()) { - value = value | 0x08; + value = value | 0x08; // Set IL bit } return value; }; diff --git a/esphome/components/nfc/ndef_record.h b/esphome/components/nfc/ndef_record.h index 680fe20986..4fab1c03e4 100644 --- a/esphome/components/nfc/ndef_record.h +++ b/esphome/components/nfc/ndef_record.h @@ -15,86 +15,40 @@ static const uint8_t TNF_UNKNOWN = 0x05; static const uint8_t TNF_UNCHANGED = 0x06; static const uint8_t TNF_RESERVED = 0x07; -static const uint8_t PAYLOAD_IDENTIFIERS_COUNT = 0x23; -static const char *const PAYLOAD_IDENTIFIERS[] = {"", - "http://www.", - "https://www.", - "http://", - "https://", - "tel:", - "mailto:", - "ftp://anonymous:anonymous@", - "ftp://ftp.", - "ftps://", - "sftp://", - "smb://", - "nfs://", - "ftp://", - "dav://", - "news:", - "telnet://", - "imap:", - "rtsp://", - "urn:", - "pop:", - "sip:", - "sips:", - "tftp:", - "btspp://", - "btl2cap://", - "btgoep://", - "tcpobex://", - "irdaobex://", - "file://", - "urn:epc:id:", - "urn:epc:tag:", - "urn:epc:pat:", - "urn:epc:raw:", - "urn:epc:", - "urn:nfc:"}; - class NdefRecord { public: NdefRecord(){}; - NdefRecord(uint8_t tnf, const std::string &type, const std::string &payload) { - this->tnf_ = tnf; - this->type_ = type; - this->set_payload(payload); - }; - NdefRecord(uint8_t tnf, const std::string &type, const std::string &payload, const std::string &id) { - this->tnf_ = tnf; - this->type_ = type; - this->set_payload(payload); - this->id_ = id; - }; - NdefRecord(const NdefRecord &rhs) { - this->tnf_ = rhs.tnf_; - this->type_ = rhs.type_; - this->payload_ = rhs.payload_; - this->payload_identifier_ = rhs.payload_identifier_; - this->id_ = rhs.id_; - }; + NdefRecord(std::vector payload_data); void set_tnf(uint8_t tnf) { this->tnf_ = tnf; }; void set_type(const std::string &type) { this->type_ = type; }; - void set_payload_identifier(uint8_t payload_identifier) { this->payload_identifier_ = payload_identifier; }; void set_payload(const std::string &payload) { this->payload_ = payload; }; void set_id(const std::string &id) { this->id_ = id; }; + NdefRecord(const NdefRecord &) = default; + virtual ~NdefRecord() {} + virtual std::unique_ptr clone() const { // To allow copying polymorphic classes + return make_unique(*this); + }; uint32_t get_encoded_size(); std::vector encode(bool first, bool last); - uint8_t get_tnf_byte(bool first, bool last); + + uint8_t create_flag_byte(bool first, bool last, size_t payload_size); const std::string &get_type() const { return this->type_; }; const std::string &get_id() const { return this->id_; }; - const std::string &get_payload() const { return this->payload_; }; + virtual const std::string &get_payload() const { return this->payload_; }; + + virtual std::vector get_encoded_payload() { + std::vector empty_payload; + return empty_payload; + }; protected: uint8_t tnf_; std::string type_; - uint8_t payload_identifier_; - std::string payload_; std::string id_; + std::string payload_; }; } // namespace nfc diff --git a/esphome/components/nfc/ndef_record_text.cpp b/esphome/components/nfc/ndef_record_text.cpp new file mode 100644 index 0000000000..80b0108b46 --- /dev/null +++ b/esphome/components/nfc/ndef_record_text.cpp @@ -0,0 +1,40 @@ +#include "ndef_record_text.h" +#include "ndef_record.h" + +namespace esphome { +namespace nfc { + +static const char *const TAG = "nfc.ndef_record_text"; + +NdefRecordText::NdefRecordText(const std::vector &payload) { + if (payload.empty()) { + ESP_LOGE(TAG, "Record payload too short"); + return; + } + + uint8_t language_code_length = payload[0] & 0b00111111; // Todo, make use of encoding bit? + + this->language_code_ = std::string(payload.begin() + 1, payload.begin() + 1 + language_code_length); + + this->text_ = std::string(payload.begin() + 1 + language_code_length, payload.end()); + + this->tnf_ = TNF_WELL_KNOWN; + + this->type_ = "T"; +} + +std::vector NdefRecordText::get_encoded_payload() { + std::vector data; + + uint8_t flag_byte = this->language_code_.length() & 0b00111111; // UTF8 assumed + + data.push_back(flag_byte); + + data.insert(data.end(), this->language_code_.begin(), this->language_code_.end()); + + data.insert(data.end(), this->text_.begin(), this->text_.end()); + return data; +} + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/ndef_record_text.h b/esphome/components/nfc/ndef_record_text.h new file mode 100644 index 0000000000..94375cc860 --- /dev/null +++ b/esphome/components/nfc/ndef_record_text.h @@ -0,0 +1,41 @@ +#pragma once + +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "ndef_record.h" + +namespace esphome { +namespace nfc { + +class NdefRecordText : public NdefRecord { + public: + NdefRecordText(){}; + NdefRecordText(const std::vector &payload); + NdefRecordText(const std::string &language_code, const std::string &text) { + this->tnf_ = TNF_WELL_KNOWN; + this->type_ = "T"; + this->language_code_ = language_code; + this->text_ = text; + }; + NdefRecordText(const std::string &language_code, const std::string &text, const std::string &id) { + this->tnf_ = TNF_WELL_KNOWN; + this->type_ = "T"; + this->language_code_ = language_code; + this->text_ = text; + this->id_ = id; + }; + NdefRecordText(const NdefRecordText &) = default; + + std::unique_ptr clone() const override { return make_unique(*this); }; + + std::vector get_encoded_payload() override; + + const std::string &get_payload() const override { return this->text_; }; + + protected: + std::string text_; + std::string language_code_; +}; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/ndef_record_uri.cpp b/esphome/components/nfc/ndef_record_uri.cpp new file mode 100644 index 0000000000..8fd043a1de --- /dev/null +++ b/esphome/components/nfc/ndef_record_uri.cpp @@ -0,0 +1,48 @@ +#include "ndef_record_uri.h" + +namespace esphome { +namespace nfc { + +static const char *const TAG = "nfc.ndef_record_uri"; + +NdefRecordUri::NdefRecordUri(const std::vector &payload) { + if (payload.empty()) { + ESP_LOGE(TAG, "Record payload too short"); + return; + } + + uint8_t payload_identifier = payload[0]; // First byte of payload is prefix code + + std::string uri(payload.begin() + 1, payload.end()); + + if (payload_identifier > 0x00 && payload_identifier <= PAYLOAD_IDENTIFIERS_COUNT) { + uri.insert(0, PAYLOAD_IDENTIFIERS[payload_identifier]); + } + + this->tnf_ = TNF_WELL_KNOWN; + this->type_ = "U"; + this->set_URI(uri); +} + +std::vector NdefRecordUri::get_encoded_payload() { + std::vector data; + + uint8_t payload_prefix = 0x00; + uint8_t payload_prefix_length = 0x00; + for (uint8_t i = 1; i < PAYLOAD_IDENTIFIERS_COUNT; i++) { + std::string prefix = PAYLOAD_IDENTIFIERS[i]; + if (this->URI_.substr(0, prefix.length()).find(prefix) != std::string::npos) { + payload_prefix = i; + payload_prefix_length = prefix.length(); + break; + } + } + + data.push_back(payload_prefix); + + data.insert(data.end(), this->URI_.begin() + payload_prefix_length, this->URI_.end()); + return data; +} + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/ndef_record_uri.h b/esphome/components/nfc/ndef_record_uri.h new file mode 100644 index 0000000000..75f9e19ee0 --- /dev/null +++ b/esphome/components/nfc/ndef_record_uri.h @@ -0,0 +1,76 @@ +#pragma once + +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "ndef_record.h" + +namespace esphome { +namespace nfc { + +static const uint8_t PAYLOAD_IDENTIFIERS_COUNT = 0x23; +static const char *const PAYLOAD_IDENTIFIERS[] = {"", + "http://www.", + "https://www.", + "http://", + "https://", + "tel:", + "mailto:", + "ftp://anonymous:anonymous@", + "ftp://ftp.", + "ftps://", + "sftp://", + "smb://", + "nfs://", + "ftp://", + "dav://", + "news:", + "telnet://", + "imap:", + "rtsp://", + "urn:", + "pop:", + "sip:", + "sips:", + "tftp:", + "btspp://", + "btl2cap://", + "btgoep://", + "tcpobex://", + "irdaobex://", + "file://", + "urn:epc:id:", + "urn:epc:tag:", + "urn:epc:pat:", + "urn:epc:raw:", + "urn:epc:", + "urn:nfc:"}; + +class NdefRecordUri : public NdefRecord { + public: + NdefRecordUri(){}; + NdefRecordUri(const std::vector &payload); + NdefRecordUri(const std::string &URI) { + this->tnf_ = TNF_WELL_KNOWN; + this->type_ = "U"; + this->URI_ = URI; + }; + NdefRecordUri(const std::string &URI, const std::string &id) { + this->tnf_ = TNF_WELL_KNOWN; + this->type_ = "U"; + this->URI_ = URI; + this->id_ = id; + }; + NdefRecordUri(const NdefRecordUri &) = default; + std::unique_ptr clone() const override { return make_unique(*this); }; + + void set_URI(const std::string &URI) { this->URI_ = URI; }; + + std::vector get_encoded_payload() override; + const std::string &get_payload() const override { return this->URI_; }; + + protected: + std::string URI_; +}; + +} // namespace nfc +} // namespace esphome From 756c6721e9372667df7466570e170d59b69b1b11 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 27 Sep 2021 11:11:27 +1300 Subject: [PATCH 1445/1841] Fix NDEF URI casing (#2397) --- esphome/components/nfc/ndef_record_uri.cpp | 6 +++--- esphome/components/nfc/ndef_record_uri.h | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/esphome/components/nfc/ndef_record_uri.cpp b/esphome/components/nfc/ndef_record_uri.cpp index 8fd043a1de..9064f04f29 100644 --- a/esphome/components/nfc/ndef_record_uri.cpp +++ b/esphome/components/nfc/ndef_record_uri.cpp @@ -21,7 +21,7 @@ NdefRecordUri::NdefRecordUri(const std::vector &payload) { this->tnf_ = TNF_WELL_KNOWN; this->type_ = "U"; - this->set_URI(uri); + this->set_uri(uri); } std::vector NdefRecordUri::get_encoded_payload() { @@ -31,7 +31,7 @@ std::vector NdefRecordUri::get_encoded_payload() { uint8_t payload_prefix_length = 0x00; for (uint8_t i = 1; i < PAYLOAD_IDENTIFIERS_COUNT; i++) { std::string prefix = PAYLOAD_IDENTIFIERS[i]; - if (this->URI_.substr(0, prefix.length()).find(prefix) != std::string::npos) { + if (this->uri_.substr(0, prefix.length()).find(prefix) != std::string::npos) { payload_prefix = i; payload_prefix_length = prefix.length(); break; @@ -40,7 +40,7 @@ std::vector NdefRecordUri::get_encoded_payload() { data.push_back(payload_prefix); - data.insert(data.end(), this->URI_.begin() + payload_prefix_length, this->URI_.end()); + data.insert(data.end(), this->uri_.begin() + payload_prefix_length, this->uri_.end()); return data; } diff --git a/esphome/components/nfc/ndef_record_uri.h b/esphome/components/nfc/ndef_record_uri.h index 75f9e19ee0..4c21724c5c 100644 --- a/esphome/components/nfc/ndef_record_uri.h +++ b/esphome/components/nfc/ndef_record_uri.h @@ -49,27 +49,27 @@ class NdefRecordUri : public NdefRecord { public: NdefRecordUri(){}; NdefRecordUri(const std::vector &payload); - NdefRecordUri(const std::string &URI) { + NdefRecordUri(const std::string &uri) { this->tnf_ = TNF_WELL_KNOWN; this->type_ = "U"; - this->URI_ = URI; + this->uri_ = uri; }; - NdefRecordUri(const std::string &URI, const std::string &id) { + NdefRecordUri(const std::string &uri, const std::string &id) { this->tnf_ = TNF_WELL_KNOWN; this->type_ = "U"; - this->URI_ = URI; + this->uri_ = uri; this->id_ = id; }; NdefRecordUri(const NdefRecordUri &) = default; std::unique_ptr clone() const override { return make_unique(*this); }; - void set_URI(const std::string &URI) { this->URI_ = URI; }; + void set_uri(const std::string &uri) { this->uri_ = uri; }; std::vector get_encoded_payload() override; - const std::string &get_payload() const override { return this->URI_; }; + const std::string &get_payload() const override { return this->uri_; }; protected: - std::string URI_; + std::string uri_; }; } // namespace nfc From 97e76d64d60d2d46a4bd4a4b50d11c496d596138 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 27 Sep 2021 11:40:28 +0200 Subject: [PATCH 1446/1841] Re-enable TCP nodelay for ESP32 (#2390) --- esphome/components/api/api_frame_helper.cpp | 15 +++++++++++ .../components/socket/lwip_raw_tcp_impl.cpp | 27 ++++++++++--------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 00f28457ae..4971272f41 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -127,6 +127,14 @@ APIError APINoiseFrameHelper::init() { return APIError::TCP_NONBLOCKING_FAILED; } + int enable = 1; + err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("Setting nodelay failed with errno %d", errno); + return APIError::TCP_NODELAY_FAILED; + } + // init prologue prologue_.insert(prologue_.end(), PROLOGUE_INIT, PROLOGUE_INIT + strlen(PROLOGUE_INIT)); @@ -722,6 +730,13 @@ APIError APIPlaintextFrameHelper::init() { HELPER_LOG("Setting nonblocking failed with errno %d", errno); return APIError::TCP_NONBLOCKING_FAILED; } + int enable = 1; + err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("Setting nodelay failed with errno %d", errno); + return APIError::TCP_NODELAY_FAILED; + } state_ = State::DATA; return APIError::OK; diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 7521d1339f..54dfddac3f 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -257,7 +257,7 @@ class LWIPRawImpl : public Socket { errno = EINVAL; return -1; } - *reinterpret_cast(optval) = tcp_nagle_disabled(pcb_); + *reinterpret_cast(optval) = nodelay_; *optlen = 4; return 0; } @@ -286,11 +286,7 @@ class LWIPRawImpl : public Socket { return -1; } int val = *reinterpret_cast(optval); - if (val != 0) { - tcp_nagle_disable(pcb_); - } else { - tcp_nagle_enable(pcb_); - } + nodelay_ = val; return 0; } @@ -444,9 +440,11 @@ class LWIPRawImpl : public Socket { if (written == 0) // no need to output if nothing written return 0; - int err = internal_output(); - if (err == -1) - return -1; + if (nodelay_) { + int err = internal_output(); + if (err == -1) + return -1; + } return written; } ssize_t writev(const struct iovec *iov, int iovcnt) override { @@ -466,9 +464,11 @@ class LWIPRawImpl : public Socket { if (written == 0) // no need to output if nothing written return 0; - int err = internal_output(); - if (err == -1) - return -1; + if (nodelay_) { + int err = internal_output(); + if (err == -1) + return -1; + } return written; } int setblocking(bool blocking) override { @@ -550,6 +550,9 @@ class LWIPRawImpl : public Socket { bool rx_closed_ = false; pbuf *rx_buf_ = nullptr; size_t rx_buf_offset_ = 0; + // don't use lwip nodelay flag, it sometimes causes reconnect + // instead use it for determining whether to call lwip_output + bool nodelay_ = false; }; std::unique_ptr socket(int domain, int type, int protocol) { From 45940b051439cb0053889597643d3a5a62ab73ad Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 27 Sep 2021 19:10:53 +0200 Subject: [PATCH 1447/1841] Dashboard node import and render in browser (#2374) --- CODEOWNERS | 1 + .../components/dashboard_import/__init__.py | 45 ++++++++ .../dashboard_import/dashboard_import.cpp | 12 ++ .../dashboard_import/dashboard_import.h | 12 ++ esphome/components/mdns/mdns_component.cpp | 8 ++ esphome/core/defines.h | 2 + esphome/dashboard/dashboard.py | 104 ++++++++++++++---- esphome/zeroconf.py | 81 +++++++++++++- 8 files changed, 237 insertions(+), 28 deletions(-) create mode 100644 esphome/components/dashboard_import/__init__.py create mode 100644 esphome/components/dashboard_import/dashboard_import.cpp create mode 100644 esphome/components/dashboard_import/dashboard_import.h diff --git a/CODEOWNERS b/CODEOWNERS index 17825a9205..0b974b95e6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -40,6 +40,7 @@ esphome/components/cover/* @esphome/core esphome/components/cs5460a/* @balrog-kun esphome/components/ct_clamp/* @jesserockz esphome/components/daly_bms/* @s1lvi0 +esphome/components/dashboard_import/* @esphome/core esphome/components/debug/* @OttoWinter esphome/components/dfplayer/* @glmnet esphome/components/dht/* @OttoWinter diff --git a/esphome/components/dashboard_import/__init__.py b/esphome/components/dashboard_import/__init__.py new file mode 100644 index 0000000000..2b884d3b9a --- /dev/null +++ b/esphome/components/dashboard_import/__init__.py @@ -0,0 +1,45 @@ +from pathlib import Path + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components.packages import validate_source_shorthand +from esphome.yaml_util import dump + + +dashboard_import_ns = cg.esphome_ns.namespace("dashboard_import") + +# payload is in `esphomelib` mdns record, which only exists if api +# is enabled +DEPENDENCIES = ["api"] +CODEOWNERS = ["@esphome/core"] + + +def validate_import_url(value): + value = cv.string_strict(value) + value = cv.Length(max=255)(value) + # ignore result, only check if it's a valid shorthand + validate_source_shorthand(value) + return value + + +CONF_PACKAGE_IMPORT_URL = "package_import_url" +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_PACKAGE_IMPORT_URL): validate_import_url, + } +) + + +async def to_code(config): + cg.add_define("USE_DASHBOARD_IMPORT") + cg.add(dashboard_import_ns.set_package_import_url(config[CONF_PACKAGE_IMPORT_URL])) + + +def import_config(path: str, name: str, project_name: str, import_url: str) -> None: + p = Path(path) + + if p.exists(): + raise FileExistsError + + config = {"substitutions": {"name": name}, "packages": {project_name: import_url}} + p.write_text(dump(config), encoding="utf8") diff --git a/esphome/components/dashboard_import/dashboard_import.cpp b/esphome/components/dashboard_import/dashboard_import.cpp new file mode 100644 index 0000000000..6875fd61a5 --- /dev/null +++ b/esphome/components/dashboard_import/dashboard_import.cpp @@ -0,0 +1,12 @@ +#include "dashboard_import.h" + +namespace esphome { +namespace dashboard_import { + +static std::string g_package_import_url; // NOLINT + +std::string get_package_import_url() { return g_package_import_url; } +void set_package_import_url(std::string url) { g_package_import_url = std::move(url); } + +} // namespace dashboard_import +} // namespace esphome diff --git a/esphome/components/dashboard_import/dashboard_import.h b/esphome/components/dashboard_import/dashboard_import.h new file mode 100644 index 0000000000..0ca2994aab --- /dev/null +++ b/esphome/components/dashboard_import/dashboard_import.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace esphome { +namespace dashboard_import { + +std::string get_package_import_url(); +void set_package_import_url(std::string url); + +} // namespace dashboard_import +} // namespace esphome diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index c742fe3948..372d980eb0 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -6,6 +6,9 @@ #ifdef USE_API #include "esphome/components/api/api_server.h" #endif +#ifdef USE_DASHBOARD_IMPORT +#include "esphome/components/dashboard_import/dashboard_import.h" +#endif namespace esphome { namespace mdns { @@ -42,6 +45,11 @@ std::vector MDNSComponent::compile_services_() { service.txt_records.push_back({"project_name", ESPHOME_PROJECT_NAME}); service.txt_records.push_back({"project_version", ESPHOME_PROJECT_VERSION}); #endif // ESPHOME_PROJECT_NAME + +#ifdef USE_DASHBOARD_IMPORT + service.txt_records.push_back({"package_import_url", dashboard_import::get_package_import_url()}); +#endif + res.push_back(service); } #endif // USE_API diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 62468dfbfe..1c3b17d071 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -67,3 +67,5 @@ // Disabled feature flags //#define USE_BSEC // Requires a library with proprietary license. + +#define USE_DASHBOARD_IMPORT diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index bfe6808729..492b86384d 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -41,7 +41,7 @@ from .util import password_hash # pylint: disable=unused-import, wrong-import-order from typing import Optional # noqa -from esphome.zeroconf import DashboardStatus, EsphomeZeroconf +from esphome.zeroconf import DashboardImportDiscovery, DashboardStatus, EsphomeZeroconf _LOGGER = logging.getLogger(__name__) @@ -154,9 +154,6 @@ def is_authenticated(request_handler): def bind_config(func): def decorator(self, *args, **kwargs): configuration = self.get_argument("configuration") - if not is_allowed(configuration): - self.set_status(500) - return None kwargs = kwargs.copy() kwargs["configuration"] = configuration return func(self, *args, **kwargs) @@ -363,8 +360,8 @@ class WizardRequestHandler(BaseHandler): from esphome import wizard kwargs = { - k: "".join(x.decode() for x in v) - for k, v in self.request.arguments.items() + k: v + for k, v in json.loads(self.request.body.decode()).items() if k in ("name", "platform", "board", "ssid", "psk", "password") } kwargs["ota_password"] = secrets.token_hex(16) @@ -374,6 +371,29 @@ class WizardRequestHandler(BaseHandler): self.finish() +class ImportRequestHandler(BaseHandler): + @authenticated + def post(self): + from esphome.components.dashboard_import import import_config + + args = json.loads(self.request.body.decode()) + try: + name = args["name"] + import_config( + settings.rel_path(f"{name}.yaml"), + name, + args["project_name"], + args["package_import_url"], + ) + except FileExistsError: + self.set_status(500) + self.write("File already exists") + return + + self.set_status(200) + self.finish() + + class DownloadBinaryRequestHandler(BaseHandler): @authenticated @bind_config @@ -469,15 +489,51 @@ class DashboardEntry: return self.storage.loaded_integrations +class ListDevicesHandler(BaseHandler): + @authenticated + def get(self): + entries = _list_dashboard_entries() + self.set_header("content-type", "application/json") + configured = {entry.name for entry in entries} + self.write( + json.dumps( + { + "configured": [ + { + "name": entry.name, + "configuration": entry.filename, + "loaded_integrations": entry.loaded_integrations, + "deployed_version": entry.update_old, + "current_version": entry.update_new, + "path": entry.path, + "comment": entry.comment, + "address": entry.address, + "target_platform": entry.target_platform, + } + for entry in entries + ], + "importable": [ + { + "name": res.device_name, + "package_import_url": res.package_import_url, + "project_name": res.project_name, + "project_version": res.project_version, + } + for res in IMPORT_RESULT.values() + if res.device_name not in configured + ], + } + ) + ) + + class MainRequestHandler(BaseHandler): @authenticated def get(self): begin = bool(self.get_argument("begin", False)) - entries = _list_dashboard_entries() self.render( get_template_path("index"), - entries=entries, begin=begin, **template_args(), login_enabled=settings.using_auth, @@ -495,6 +551,8 @@ def _ping_func(filename, address): class MDNSStatusThread(threading.Thread): def run(self): + global IMPORT_RESULT + zc = EsphomeZeroconf() def on_update(dat): @@ -502,17 +560,22 @@ class MDNSStatusThread(threading.Thread): PING_RESULT[key] = b stat = DashboardStatus(zc, on_update) + imports = DashboardImportDiscovery(zc) + stat.start() while not STOP_EVENT.is_set(): entries = _list_dashboard_entries() stat.request_query( {entry.filename: f"{entry.name}.local." for entry in entries} ) + IMPORT_RESULT = imports.import_state PING_REQUEST.wait() PING_REQUEST.clear() + stat.stop() stat.join() + imports.cancel() zc.close() @@ -567,10 +630,6 @@ class PingRequestHandler(BaseHandler): self.write(json.dumps(PING_RESULT)) -def is_allowed(configuration): - return os.path.sep not in configuration - - class InfoRequestHandler(BaseHandler): @authenticated @bind_config @@ -613,20 +672,18 @@ class DeleteRequestHandler(BaseHandler): def post(self, configuration=None): config_file = settings.rel_path(configuration) storage_path = ext_storage_path(settings.config_dir, configuration) - storage_json = StorageJSON.load(storage_path) - if storage_json is None: - self.set_status(500) - return - name = storage_json.name trash_path = trash_storage_path(settings.config_dir) mkdir_p(trash_path) shutil.move(config_file, os.path.join(trash_path, configuration)) - # Delete build folder (if exists) - build_folder = os.path.join(settings.config_dir, name) - if build_folder is not None: - shutil.rmtree(build_folder, os.path.join(trash_path, name)) + storage_json = StorageJSON.load(storage_path) + if storage_json is not None: + # Delete build folder (if exists) + name = storage_json.name + build_folder = os.path.join(settings.config_dir, name) + if build_folder is not None: + shutil.rmtree(build_folder, os.path.join(trash_path, name)) class UndoDeleteRequestHandler(BaseHandler): @@ -639,6 +696,7 @@ class UndoDeleteRequestHandler(BaseHandler): PING_RESULT = {} # type: dict +IMPORT_RESULT = {} STOP_EVENT = threading.Event() PING_REQUEST = threading.Event() @@ -808,8 +866,10 @@ def make_app(debug=get_bool_env(ENV_DEV)): (f"{rel}ping", PingRequestHandler), (f"{rel}delete", DeleteRequestHandler), (f"{rel}undo-delete", UndoDeleteRequestHandler), - (f"{rel}wizard.html", WizardRequestHandler), + (f"{rel}wizard", WizardRequestHandler), (f"{rel}static/(.*)", StaticFileHandler, {"path": get_static_path()}), + (f"{rel}devices", ListDevicesHandler), + (f"{rel}import", ImportRequestHandler), ], **app_settings, ) diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index e6853531f2..a19fc143ec 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -2,6 +2,8 @@ import socket import threading import time from typing import Dict, Optional +import logging +from dataclasses import dataclass from zeroconf import ( DNSAddress, @@ -10,11 +12,14 @@ from zeroconf import ( DNSQuestion, RecordUpdateListener, Zeroconf, + ServiceBrowser, ) +from zeroconf._services import ServiceStateChange _CLASS_IN = 1 _FLAGS_QR_QUERY = 0x0000 # query _TYPE_A = 1 +_LOGGER = logging.getLogger(__name__) class HostResolver(RecordUpdateListener): @@ -57,7 +62,7 @@ class HostResolver(RecordUpdateListener): return True -class DashboardStatus(RecordUpdateListener, threading.Thread): +class DashboardStatus(threading.Thread): PING_AFTER = 15 * 1000 # Send new mDNS request after 15 seconds OFFLINE_AFTER = PING_AFTER * 2 # Offline if no mDNS response after 30 seconds @@ -70,9 +75,6 @@ class DashboardStatus(RecordUpdateListener, threading.Thread): self.query_event = threading.Event() self.on_update = on_update - def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None: - pass - def request_query(self, hosts: Dict[str, str]) -> None: self.query_hosts = set(hosts.values()) self.key_to_host = hosts @@ -93,7 +95,6 @@ class DashboardStatus(RecordUpdateListener, threading.Thread): ) def run(self) -> None: - self.zc.add_listener(self, None) while not self.stop_event.is_set(): self.on_update( {key: self.host_status(host) for key, host in self.key_to_host.items()} @@ -110,7 +111,75 @@ class DashboardStatus(RecordUpdateListener, threading.Thread): self.zc.send(out) self.query_event.wait() self.query_event.clear() - self.zc.remove_listener(self) + + +ESPHOME_SERVICE_TYPE = "_esphomelib._tcp.local." +TXT_RECORD_PACKAGE_IMPORT_URL = b"package_import_url" +TXT_RECORD_PROJECT_NAME = b"project_name" +TXT_RECORD_PROJECT_VERSION = b"project_version" + + +@dataclass +class DiscoveredImport: + device_name: str + package_import_url: str + project_name: str + project_version: str + + +class DashboardImportDiscovery: + def __init__(self, zc: Zeroconf) -> None: + self.zc = zc + self.service_browser = ServiceBrowser( + self.zc, ESPHOME_SERVICE_TYPE, [self._on_update] + ) + self.import_state = {} + + def _on_update( + self, + zeroconf: Zeroconf, + service_type: str, + name: str, + state_change: ServiceStateChange, + ) -> None: + _LOGGER.debug( + "service_update: type=%s name=%s state_change=%s", + service_type, + name, + state_change, + ) + if service_type != ESPHOME_SERVICE_TYPE: + return + if state_change == ServiceStateChange.Removed: + self.import_state.pop(name, None) + + info = zeroconf.get_service_info(service_type, name) + _LOGGER.debug("-> resolved info: %s", info) + if info is None: + return + node_name = name[: -len(ESPHOME_SERVICE_TYPE) - 1] + required_keys = [ + TXT_RECORD_PACKAGE_IMPORT_URL, + TXT_RECORD_PROJECT_NAME, + TXT_RECORD_PROJECT_VERSION, + ] + if any(key not in info.properties for key in required_keys): + # Not a dashboard import device + return + + import_url = info.properties[TXT_RECORD_PACKAGE_IMPORT_URL].decode() + project_name = info.properties[TXT_RECORD_PROJECT_NAME].decode() + project_version = info.properties[TXT_RECORD_PROJECT_VERSION].decode() + + self.import_state[name] = DiscoveredImport( + device_name=node_name, + package_import_url=import_url, + project_name=project_name, + project_version=project_version, + ) + + def cancel(self) -> None: + self.service_browser.cancel() class EsphomeZeroconf(Zeroconf): From b2d516c70a5b2357cf0c7c7cf394269c631e6653 Mon Sep 17 00:00:00 2001 From: Christian Taedcke Date: Mon, 27 Sep 2021 21:53:05 +0200 Subject: [PATCH 1448/1841] ccs811: Skip reading data if it is not available (#2404) On bootup the ccs811 reports that no data is available. No error flag is set in that case. The current implementation ignores this, reads and publishes the invalid data, which is 0xFDFD for both tvoc and co2 in my case. This commit fixes this and does not read and publish invalid data. --- esphome/components/ccs811/ccs811.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/ccs811/ccs811.cpp b/esphome/components/ccs811/ccs811.cpp index 6bdb4739cf..11a66f5100 100644 --- a/esphome/components/ccs811/ccs811.cpp +++ b/esphome/components/ccs811/ccs811.cpp @@ -86,8 +86,11 @@ void CCS811Component::setup() { } } void CCS811Component::update() { - if (!this->status_has_data_()) + if (!this->status_has_data_()) { + ESP_LOGD(TAG, "Status indicates no data ready!"); this->status_set_warning(); + return; + } // page 12 - alg result data auto alg_data = this->read_bytes<4>(0x02); From 8c86a18dc6b3b8f62eed60f2310f638d2de313a5 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Mon, 27 Sep 2021 21:53:47 +0200 Subject: [PATCH 1449/1841] Add missing include for defines.h. (#2403) Co-authored-by: Maurice Makaay --- esphome/components/bme680_bsec/bme680_bsec.h | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/bme680_bsec/bme680_bsec.h b/esphome/components/bme680_bsec/bme680_bsec.h index 365aec725e..53bc5c3280 100644 --- a/esphome/components/bme680_bsec/bme680_bsec.h +++ b/esphome/components/bme680_bsec/bme680_bsec.h @@ -5,6 +5,7 @@ #include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/i2c/i2c.h" #include "esphome/core/preferences.h" +#include "esphome/core/defines.h" #include #ifdef USE_BSEC From 1ba560dc9e61ab3bbdc4d1ae35bcd98a0a6d1cdf Mon Sep 17 00:00:00 2001 From: irtimaled Date: Mon, 27 Sep 2021 12:54:51 -0700 Subject: [PATCH 1450/1841] fix: stop tuya light state getting reset (#2401) * fix: stop tuya light state getting reset * fix typo --- esphome/components/tuya/light/tuya_light.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index 97f6de9bae..f75cc964aa 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -111,6 +111,11 @@ void TuyaLight::write_state(light::LightState *state) { state->current_values_as_brightness(&brightness); } + if (!state->current_values.is_on() && this->switch_id_.has_value()) { + parent_->set_boolean_datapoint_value(*this->switch_id_, false); + return; + } + if (brightness > 0.0f || !color_interlock_) { if (this->color_temperature_id_.has_value()) { uint32_t color_temp_int = static_cast(color_temperature * this->color_temperature_max_value_); @@ -138,7 +143,7 @@ void TuyaLight::write_state(light::LightState *state) { } if (this->switch_id_.has_value()) { - parent_->set_boolean_datapoint_value(*this->switch_id_, state->current_values.is_on()); + parent_->set_boolean_datapoint_value(*this->switch_id_, true); } } From e30f17f64f9d634984ed421f18dde71bae6d3884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20P=C3=A9rez=20Ferro?= Date: Mon, 27 Sep 2021 22:22:45 +0200 Subject: [PATCH 1451/1841] Add Current based cover (#1439) * Adding first version of current_base cover. No Interlock yet. * simplifying code * Implementing malfunction protection * Adding test * Fixing too long lines * Fixing test sensor names * Adding missing id's in ade7953 tests * Adding code owners as requested * Fixing issue setting position when stop reached * Fixing issue setting position when stop reached * Black formatting * Fixing format issues * Fix for concurrent changes Co-authored-by: Oxan van Leeuwen --- CODEOWNERS | 1 + esphome/components/current_based/__init__.py | 1 + esphome/components/current_based/cover.py | 124 +++++++++ .../current_based/current_based_cover.cpp | 251 ++++++++++++++++++ .../current_based/current_based_cover.h | 95 +++++++ tests/test3.yaml | 28 ++ 6 files changed, 500 insertions(+) create mode 100644 esphome/components/current_based/__init__.py create mode 100644 esphome/components/current_based/cover.py create mode 100644 esphome/components/current_based/current_based_cover.cpp create mode 100644 esphome/components/current_based/current_based_cover.h diff --git a/CODEOWNERS b/CODEOWNERS index 0b974b95e6..3c3a1e04ee 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -39,6 +39,7 @@ esphome/components/coolix/* @glmnet esphome/components/cover/* @esphome/core esphome/components/cs5460a/* @balrog-kun esphome/components/ct_clamp/* @jesserockz +esphome/components/current_based/* @djwmarcx esphome/components/daly_bms/* @s1lvi0 esphome/components/dashboard_import/* @esphome/core esphome/components/debug/* @OttoWinter diff --git a/esphome/components/current_based/__init__.py b/esphome/components/current_based/__init__.py new file mode 100644 index 0000000000..e7f41154b7 --- /dev/null +++ b/esphome/components/current_based/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@djwmarcx"] diff --git a/esphome/components/current_based/cover.py b/esphome/components/current_based/cover.py new file mode 100644 index 0000000000..eb77a90aff --- /dev/null +++ b/esphome/components/current_based/cover.py @@ -0,0 +1,124 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.components import sensor, cover +from esphome.const import ( + CONF_CLOSE_ACTION, + CONF_CLOSE_DURATION, + CONF_ID, + CONF_OPEN_ACTION, + CONF_OPEN_DURATION, + CONF_STOP_ACTION, + CONF_MAX_DURATION, +) + + +CONF_OPEN_SENSOR = "open_sensor" +CONF_OPEN_MOVING_CURRENT_THRESHOLD = "open_moving_current_threshold" +CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD = "open_obstacle_current_threshold" + +CONF_CLOSE_SENSOR = "close_sensor" +CONF_CLOSE_MOVING_CURRENT_THRESHOLD = "close_moving_current_threshold" +CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD = "close_obstacle_current_threshold" + +CONF_OBSTACLE_ROLLBACK = "obstacle_rollback" +CONF_MALFUNCTION_DETECTION = "malfunction_detection" +CONF_MALFUNCTION_ACTION = "malfunction_action" +CONF_START_SENSING_DELAY = "start_sensing_delay" + +current_based_ns = cg.esphome_ns.namespace("current_based") +CurrentBasedCover = current_based_ns.class_( + "CurrentBasedCover", cover.Cover, cg.Component +) + +CONFIG_SCHEMA = cover.COVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CurrentBasedCover), + cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True), + cv.Required(CONF_OPEN_SENSOR): cv.use_id(sensor.Sensor), + cv.Required(CONF_OPEN_MOVING_CURRENT_THRESHOLD): cv.float_range( + min=0, min_included=False + ), + cv.Optional(CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD): cv.float_range( + min=0, min_included=False + ), + cv.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True), + cv.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds, + cv.Required(CONF_CLOSE_SENSOR): cv.use_id(sensor.Sensor), + cv.Required(CONF_CLOSE_MOVING_CURRENT_THRESHOLD): cv.float_range( + min=0, min_included=False + ), + cv.Optional(CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD): cv.float_range( + min=0, min_included=False + ), + cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True), + cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds, + cv.Optional(CONF_OBSTACLE_ROLLBACK, default="10%"): cv.percentage, + cv.Optional(CONF_MAX_DURATION): cv.positive_time_period_milliseconds, + cv.Optional(CONF_MALFUNCTION_DETECTION, default=True): cv.boolean, + cv.Optional(CONF_MALFUNCTION_ACTION): automation.validate_automation( + single=True + ), + cv.Optional( + CONF_START_SENSING_DELAY, default="500ms" + ): cv.positive_time_period_milliseconds, + } +).extend(cv.COMPONENT_SCHEMA) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield cover.register_cover(var, config) + + yield automation.build_automation( + var.get_stop_trigger(), [], config[CONF_STOP_ACTION] + ) + + # OPEN + bin = yield cg.get_variable(config[CONF_OPEN_SENSOR]) + cg.add(var.set_open_sensor(bin)) + cg.add( + var.set_open_moving_current_threshold( + config[CONF_OPEN_MOVING_CURRENT_THRESHOLD] + ) + ) + if CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD in config: + cg.add( + var.set_open_obstacle_current_threshold( + config[CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD] + ) + ) + cg.add(var.set_open_duration(config[CONF_OPEN_DURATION])) + yield automation.build_automation( + var.get_open_trigger(), [], config[CONF_OPEN_ACTION] + ) + + # CLOSE + bin = yield cg.get_variable(config[CONF_CLOSE_SENSOR]) + cg.add(var.set_close_sensor(bin)) + cg.add( + var.set_close_moving_current_threshold( + config[CONF_CLOSE_MOVING_CURRENT_THRESHOLD] + ) + ) + if CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD in config: + cg.add( + var.set_close_obstacle_current_threshold( + config[CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD] + ) + ) + cg.add(var.set_close_duration(config[CONF_CLOSE_DURATION])) + yield automation.build_automation( + var.get_close_trigger(), [], config[CONF_CLOSE_ACTION] + ) + + cg.add(var.set_obstacle_rollback(config[CONF_OBSTACLE_ROLLBACK])) + if CONF_MAX_DURATION in config: + cg.add(var.set_max_duration(config[CONF_MAX_DURATION])) + cg.add(var.set_malfunction_detection(config[CONF_MALFUNCTION_DETECTION])) + if CONF_MALFUNCTION_ACTION in config: + yield automation.build_automation( + var.get_malfunction_trigger(), [], config[CONF_MALFUNCTION_ACTION] + ) + cg.add(var.set_start_sensing_delay(config[CONF_START_SENSING_DELAY])) diff --git a/esphome/components/current_based/current_based_cover.cpp b/esphome/components/current_based/current_based_cover.cpp new file mode 100644 index 0000000000..9f0a59377d --- /dev/null +++ b/esphome/components/current_based/current_based_cover.cpp @@ -0,0 +1,251 @@ +#include "current_based_cover.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace current_based { + +static const char *const TAG = "current_based.cover"; + +using namespace esphome::cover; + +CoverTraits CurrentBasedCover::get_traits() { + auto traits = CoverTraits(); + traits.set_supports_position(true); + traits.set_is_assumed_state(false); + return traits; +} +void CurrentBasedCover::control(const CoverCall &call) { + if (call.get_stop()) { + this->direction_idle_(); + } + if (call.get_position().has_value()) { + auto pos = *call.get_position(); + if (pos == this->position) { + // already at target + } else { + auto op = pos < this->position ? COVER_OPERATION_CLOSING : COVER_OPERATION_OPENING; + this->target_position_ = pos; + this->start_direction_(op); + } + } +} +void CurrentBasedCover::setup() { + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->apply(this); + } else { + this->position = 0.5f; + } +} + +void CurrentBasedCover::loop() { + if (this->current_operation == COVER_OPERATION_IDLE) + return; + + const uint32_t now = millis(); + + if (this->current_operation == COVER_OPERATION_OPENING) { + if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction + this->direction_idle_(); + this->malfunction_trigger_->trigger(); + ESP_LOGI(TAG, "'%s' - Malfunction detected during opening. Current flow detected in close circuit", + this->name_.c_str()); + } else if (this->is_opening_blocked_()) { // Blocked + ESP_LOGD(TAG, "'%s' - Obstacle detected during opening.", this->name_.c_str()); + this->direction_idle_(); + if (this->obstacle_rollback_ != 0) { + this->set_timeout("rollback", 300, [this]() { + ESP_LOGD(TAG, "'%s' - Rollback.", this->name_.c_str()); + this->target_position_ = clamp(this->position - this->obstacle_rollback_, 0.0F, 1.0F); + this->start_direction_(COVER_OPERATION_CLOSING); + }); + } + } else if (this->is_initial_delay_finished_() && !this->is_opening_()) { // End reached + auto dur = (now - this->start_dir_time_) / 1e3f; + ESP_LOGD(TAG, "'%s' - Open position reached. Took %.1fs.", this->name_.c_str(), dur); + this->direction_idle_(COVER_OPEN); + } + } else if (this->current_operation == COVER_OPERATION_CLOSING) { + if (this->malfunction_detection_ && this->is_opening_()) { // Malfunction + this->direction_idle_(); + this->malfunction_trigger_->trigger(); + ESP_LOGI(TAG, "'%s' - Malfunction detected during closing. Current flow detected in open circuit", + this->name_.c_str()); + } else if (this->is_closing_blocked_()) { // Blocked + ESP_LOGD(TAG, "'%s' - Obstacle detected during closing.", this->name_.c_str()); + this->direction_idle_(); + if (this->obstacle_rollback_ != 0) { + this->set_timeout("rollback", 300, [this]() { + ESP_LOGD(TAG, "'%s' - Rollback.", this->name_.c_str()); + this->target_position_ = clamp(this->position + this->obstacle_rollback_, 0.0F, 1.0F); + this->start_direction_(COVER_OPERATION_OPENING); + }); + } + } else if (this->is_initial_delay_finished_() && !this->is_closing_()) { // End reached + auto dur = (now - this->start_dir_time_) / 1e3f; + ESP_LOGD(TAG, "'%s' - Close position reached. Took %.1fs.", this->name_.c_str(), dur); + this->direction_idle_(COVER_CLOSED); + } + } else if (now - this->start_dir_time_ > this->max_duration_) { + ESP_LOGD(TAG, "'%s' - Max duration reached. Stopping cover.", this->name_.c_str()); + this->direction_idle_(); + } + + // Recompute position every loop cycle + this->recompute_position_(); + + if (this->current_operation != COVER_OPERATION_IDLE && this->is_at_target_()) { + this->direction_idle_(); + } + + // Send current position every second + if (this->current_operation != COVER_OPERATION_IDLE && now - this->last_publish_time_ > 1000) { + this->publish_state(false); + this->last_publish_time_ = now; + } +} + +void CurrentBasedCover::direction_idle_(float new_position) { + this->start_direction_(COVER_OPERATION_IDLE); + if (new_position != FLT_MAX) { + this->position = new_position; + } + this->publish_state(); +} + +void CurrentBasedCover::dump_config() { + LOG_COVER("", "Endstop Cover", this); + LOG_SENSOR(" ", "Open Sensor", this->open_sensor_); + ESP_LOGCONFIG(TAG, " Open moving current threshold: %.11fA", this->open_moving_current_threshold_); + if (this->open_obstacle_current_threshold_ != FLT_MAX) { + ESP_LOGCONFIG(TAG, " Open obstacle current threshold: %.11fA", this->open_obstacle_current_threshold_); + } + ESP_LOGCONFIG(TAG, " Open Duration: %.1fs", this->open_duration_ / 1e3f); + LOG_SENSOR(" ", "Close Sensor", this->close_sensor_); + ESP_LOGCONFIG(TAG, " Close moving current threshold: %.11fA", this->close_moving_current_threshold_); + if (this->close_obstacle_current_threshold_ != FLT_MAX) { + ESP_LOGCONFIG(TAG, " Close obstacle current threshold: %.11fA", this->close_obstacle_current_threshold_); + } + ESP_LOGCONFIG(TAG, " Close Duration: %.1fs", this->close_duration_ / 1e3f); + ESP_LOGCONFIG(TAG, "Obstacle Rollback: %.1f%%", this->obstacle_rollback_ * 100); + if (this->max_duration_ != UINT32_MAX) { + ESP_LOGCONFIG(TAG, "Maximun duration: %.1fs", this->max_duration_ / 1e3f); + } + ESP_LOGCONFIG(TAG, "Start sensing delay: %.1fs", this->start_sensing_delay_ / 1e3f); + ESP_LOGCONFIG(TAG, "Malfunction detection: %s", YESNO(this->malfunction_detection_)); +} + +float CurrentBasedCover::get_setup_priority() const { return setup_priority::DATA; } +void CurrentBasedCover::stop_prev_trigger_() { + if (this->prev_command_trigger_ != nullptr) { + this->prev_command_trigger_->stop_action(); + this->prev_command_trigger_ = nullptr; + } +} + +bool CurrentBasedCover::is_opening_() const { + return this->open_sensor_->get_state() > this->open_moving_current_threshold_; +} + +bool CurrentBasedCover::is_opening_blocked_() const { + if (this->open_obstacle_current_threshold_ == FLT_MAX) { + return false; + } + return this->open_sensor_->get_state() > this->open_obstacle_current_threshold_; +} + +bool CurrentBasedCover::is_closing_() const { + return this->close_sensor_->get_state() > this->close_moving_current_threshold_; +} + +bool CurrentBasedCover::is_closing_blocked_() const { + if (this->close_obstacle_current_threshold_ == FLT_MAX) { + return false; + } + return this->open_sensor_->get_state() > this->open_obstacle_current_threshold_; +} +bool CurrentBasedCover::is_initial_delay_finished_() const { + return millis() - this->start_dir_time_ > this->start_sensing_delay_; +} + +bool CurrentBasedCover::is_at_target_() const { + switch (this->current_operation) { + case COVER_OPERATION_OPENING: + if (this->target_position_ == COVER_OPEN) { + if (!this->is_initial_delay_finished_()) // During initial delay, state is assumed + return false; + return !this->is_opening_(); + } + return this->position >= this->target_position_; + case COVER_OPERATION_CLOSING: + if (this->target_position_ == COVER_CLOSED) { + if (!this->is_initial_delay_finished_()) // During initial delay, state is assumed + return false; + return !this->is_closing_(); + } + return this->position <= this->target_position_; + case COVER_OPERATION_IDLE: + default: + return true; + } +} +void CurrentBasedCover::start_direction_(CoverOperation dir) { + if (dir == this->current_operation) + return; + + this->recompute_position_(); + Trigger<> *trig; + switch (dir) { + case COVER_OPERATION_IDLE: + trig = this->stop_trigger_; + break; + case COVER_OPERATION_OPENING: + trig = this->open_trigger_; + break; + case COVER_OPERATION_CLOSING: + trig = this->close_trigger_; + break; + default: + return; + } + + this->current_operation = dir; + + this->stop_prev_trigger_(); + trig->trigger(); + this->prev_command_trigger_ = trig; + + const auto now = millis(); + this->start_dir_time_ = now; + this->last_recompute_time_ = now; +} +void CurrentBasedCover::recompute_position_() { + if (this->current_operation == COVER_OPERATION_IDLE) + return; + + float dir; + float action_dur; + switch (this->current_operation) { + case COVER_OPERATION_OPENING: + dir = 1.0F; + action_dur = this->open_duration_; + break; + case COVER_OPERATION_CLOSING: + dir = -1.0F; + action_dur = this->close_duration_; + break; + default: + return; + } + + const auto now = millis(); + this->position += dir * (now - this->last_recompute_time_) / action_dur; + this->position = clamp(this->position, 0.0F, 1.0F); + + this->last_recompute_time_ = now; +} + +} // namespace current_based +} // namespace esphome diff --git a/esphome/components/current_based/current_based_cover.h b/esphome/components/current_based/current_based_cover.h new file mode 100644 index 0000000000..220b770c05 --- /dev/null +++ b/esphome/components/current_based/current_based_cover.h @@ -0,0 +1,95 @@ +#pragma once + +#include "esphome/components/cover/cover.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include + +namespace esphome { +namespace current_based { + +class CurrentBasedCover : public cover::Cover, public Component { + public: + void setup() override; + void loop() override; + void dump_config() override; + float get_setup_priority() const override; + + Trigger<> *get_stop_trigger() const { return this->stop_trigger_; } + + Trigger<> *get_open_trigger() const { return this->open_trigger_; } + void set_open_sensor(sensor::Sensor *open_sensor) { this->open_sensor_ = open_sensor; } + void set_open_moving_current_threshold(float open_moving_current_threshold) { + this->open_moving_current_threshold_ = open_moving_current_threshold; + } + void set_open_obstacle_current_threshold(float open_obstacle_current_threshold) { + this->open_obstacle_current_threshold_ = open_obstacle_current_threshold; + } + void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; } + + Trigger<> *get_close_trigger() const { return this->close_trigger_; } + void set_close_sensor(sensor::Sensor *close_sensor) { this->close_sensor_ = close_sensor; } + void set_close_moving_current_threshold(float close_moving_current_threshold) { + this->close_moving_current_threshold_ = close_moving_current_threshold; + } + void set_close_obstacle_current_threshold(float close_obstacle_current_threshold) { + this->close_obstacle_current_threshold_ = close_obstacle_current_threshold; + } + void set_close_duration(uint32_t close_duration) { this->close_duration_ = close_duration; } + + void set_max_duration(uint32_t max_duration) { this->max_duration_ = max_duration; } + void set_obstacle_rollback(float obstacle_rollback) { this->obstacle_rollback_ = obstacle_rollback; } + + void set_malfunction_detection(bool malfunction_detection) { this->malfunction_detection_ = malfunction_detection; } + void set_start_sensing_delay(uint32_t start_sensing_delay) { this->start_sensing_delay_ = start_sensing_delay; } + + Trigger<> *get_malfunction_trigger() const { return this->malfunction_trigger_; } + + cover::CoverTraits get_traits() override; + + protected: + void control(const cover::CoverCall &call) override; + void stop_prev_trigger_(); + + bool is_at_target_() const; + bool is_opening_() const; + bool is_opening_blocked_() const; + bool is_closing_() const; + bool is_closing_blocked_() const; + bool is_initial_delay_finished_() const; + + void direction_idle_(float new_position = FLT_MAX); + void start_direction_(cover::CoverOperation dir); + + void recompute_position_(); + + Trigger<> *stop_trigger_{new Trigger<>()}; + + sensor::Sensor *open_sensor_{nullptr}; + Trigger<> *open_trigger_{new Trigger<>()}; + float open_moving_current_threshold_; + float open_obstacle_current_threshold_{FLT_MAX}; + uint32_t open_duration_; + + sensor::Sensor *close_sensor_{nullptr}; + Trigger<> *close_trigger_{new Trigger<>()}; + float close_moving_current_threshold_; + float close_obstacle_current_threshold_{FLT_MAX}; + uint32_t close_duration_; + + uint32_t max_duration_{UINT32_MAX}; + bool malfunction_detection_{true}; + Trigger<> *malfunction_trigger_{new Trigger<>()}; + uint32_t start_sensing_delay_; + float obstacle_rollback_; + + Trigger<> *prev_command_trigger_{nullptr}; + uint32_t last_recompute_time_{0}; + uint32_t start_dir_time_{0}; + uint32_t last_publish_time_{0}; + float target_position_{0}; +}; + +} // namespace current_based +} // namespace esphome diff --git a/tests/test3.yaml b/tests/test3.yaml index 386775749d..49f48f4cfa 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -426,14 +426,19 @@ sensor: irq_pin: GPIO16 voltage: name: ADE7953 Voltage + id: ade7953_voltage current_a: name: ADE7953 Current A + id: ade7953_current_a current_b: name: ADE7953 Current B + id: ade7953_current_b active_power_a: name: ADE7953 Active Power A + id: ade7953_active_power_a active_power_b: name: ADE7953 Active Power B + id: ade7953_active_power_b - platform: pzem004t uart_id: uart3 voltage: @@ -1021,6 +1026,29 @@ cover: close_action: - switch.turn_on: gpio_switch2 close_duration: 4.5min + - platform: current_based + name: "Current Based Cover" + open_sensor: ade7953_current_a + open_moving_current_threshold: 0.5 + open_obstacle_current_threshold: 0.8 + open_duration: 12s + open_action: + - switch.turn_on: gpio_switch1 + close_sensor: ade7953_current_b + close_moving_current_threshold: 0.5 + close_obstacle_current_threshold: 0.8 + close_duration: 10s + close_action: + - switch.turn_on: gpio_switch2 + stop_action: + - switch.turn_off: gpio_switch1 + - switch.turn_off: gpio_switch2 + obstacle_rollback: 30% + start_sensing_delay: 0.8s + malfunction_detection: true + malfunction_action: + then: + - logger.log: "Malfunction Detected" - platform: template name: Template Cover with Tilt tilt_lambda: 'return 0.5;' From 2eb5f89d82bb9ac61aebb096751005f9c5769c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Mon, 27 Sep 2021 22:31:15 +0200 Subject: [PATCH 1452/1841] Add cover toggle support (#1809) * Add cover toggle support Step through open/stop/close/stop sequence with every toggle * Move the cover toggle logic to perform() * Add clang-tidy CI suggestion * Implement cover toggle action as cover trait * Handle toggle correctly if cover fully closed on POR * Fix CI finding * Add deprecated warning * Don't add already deprecated interface Co-authored-by: Oxan van Leeuwen * Don't add already deprecated interface Co-authored-by: Oxan van Leeuwen * Don't add already deprecated interface Co-authored-by: Oxan van Leeuwen Co-authored-by: Mueller, Daniel Co-authored-by: Oxan van Leeuwen --- esphome/components/cover/__init__.py | 7 +++++++ esphome/components/cover/automation.h | 10 ++++++++++ esphome/components/cover/cover.cpp | 20 +++++++++++++++++++ esphome/components/cover/cover.h | 6 +++++- esphome/components/cover/cover_traits.h | 3 +++ .../time_based/time_based_cover.cpp | 17 ++++++++++++++++ .../components/time_based/time_based_cover.h | 1 + tests/test3.yaml | 7 +++++++ 8 files changed, 70 insertions(+), 1 deletion(-) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 6fd6ac81b0..46b8906adb 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -59,6 +59,7 @@ validate_cover_operation = cv.enum(COVER_OPERATIONS, upper=True) OpenAction = cover_ns.class_("OpenAction", automation.Action) CloseAction = cover_ns.class_("CloseAction", automation.Action) StopAction = cover_ns.class_("StopAction", automation.Action) +ToggleAction = cover_ns.class_("ToggleAction", automation.Action) ControlAction = cover_ns.class_("ControlAction", automation.Action) CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action) CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition) @@ -119,6 +120,12 @@ async def cover_stop_to_code(config, action_id, template_arg, args): return cg.new_Pvariable(action_id, template_arg, paren) +@automation.register_action("cover.toggle", ToggleAction, COVER_ACTION_SCHEMA) +def cover_toggle_to_code(config, action_id, template_arg, args): + paren = yield cg.get_variable(config[CONF_ID]) + yield cg.new_Pvariable(action_id, template_arg, paren) + + COVER_CONTROL_ACTION_SCHEMA = cv.Schema( { cv.Required(CONF_ID): cv.use_id(Cover), diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index 0b364e1e09..79bca6826e 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -37,6 +37,16 @@ template class StopAction : public Action { Cover *cover_; }; +template class ToggleAction : public Action { + public: + explicit ToggleAction(Cover *cover) : cover_(cover) {} + + void play(Ts... x) override { this->cover_->make_call().set_command_toggle().perform(); } + + protected: + Cover *cover_; +}; + template class ControlAction : public Action { public: explicit ControlAction(Cover *cover) : cover_(cover) {} diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index e1ce211b64..863adb1d81 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -43,6 +43,8 @@ CoverCall &CoverCall::set_command(const char *command) { this->set_command_close(); } else if (strcasecmp(command, "STOP") == 0) { this->set_command_stop(); + } else if (strcasecmp(command, "TOGGLE") == 0) { + this->set_command_toggle(); } else { ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command); } @@ -60,6 +62,10 @@ CoverCall &CoverCall::set_command_stop() { this->stop_ = true; return *this; } +CoverCall &CoverCall::set_command_toggle() { + this->toggle_ = true; + return *this; +} CoverCall &CoverCall::set_position(float position) { this->position_ = position; return *this; @@ -85,10 +91,14 @@ void CoverCall::perform() { if (this->tilt_.has_value()) { ESP_LOGD(TAG, " Tilt: %.0f%%", *this->tilt_ * 100.0f); } + if (this->toggle_.has_value()) { + ESP_LOGD(TAG, " Command: TOGGLE"); + } this->parent_->control(*this); } const optional &CoverCall::get_position() const { return this->position_; } const optional &CoverCall::get_tilt() const { return this->tilt_; } +const optional &CoverCall::get_toggle() const { return this->toggle_; } void CoverCall::validate_() { auto traits = this->parent_->get_traits(); if (this->position_.has_value()) { @@ -111,6 +121,12 @@ void CoverCall::validate_() { this->tilt_ = clamp(tilt, 0.0f, 1.0f); } } + if (this->toggle_.has_value()) { + if (!traits.get_supports_toggle()) { + ESP_LOGW(TAG, "'%s' - This cover device does not support toggle!", this->parent_->get_name().c_str()); + this->toggle_.reset(); + } + } if (this->stop_) { if (this->position_.has_value()) { ESP_LOGW(TAG, "Cannot set position when stopping a cover!"); @@ -120,6 +136,10 @@ void CoverCall::validate_() { ESP_LOGW(TAG, "Cannot set tilt when stopping a cover!"); this->tilt_.reset(); } + if (this->toggle_.has_value()) { + ESP_LOGW(TAG, "Cannot set toggle when stopping a cover!"); + this->toggle_.reset(); + } } } CoverCall &CoverCall::set_stop(bool stop) { diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index 72ec15a459..8f98a88a42 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -29,7 +29,7 @@ class CoverCall { public: CoverCall(Cover *parent); - /// Set the command as a string, "STOP", "OPEN", "CLOSE". + /// Set the command as a string, "STOP", "OPEN", "CLOSE", "TOGGLE". CoverCall &set_command(const char *command); /// Set the command to open the cover. CoverCall &set_command_open(); @@ -37,6 +37,8 @@ class CoverCall { CoverCall &set_command_close(); /// Set the command to stop the cover. CoverCall &set_command_stop(); + /// Set the command to toggle the cover. + CoverCall &set_command_toggle(); /// Set the call to a certain target position. CoverCall &set_position(float position); /// Set the call to a certain target tilt. @@ -50,6 +52,7 @@ class CoverCall { const optional &get_position() const; bool get_stop() const; const optional &get_tilt() const; + const optional &get_toggle() const; protected: void validate_(); @@ -58,6 +61,7 @@ class CoverCall { bool stop_{false}; optional position_{}; optional tilt_{}; + optional toggle_{}; }; /// Struct used to store the restored state of a cover diff --git a/esphome/components/cover/cover_traits.h b/esphome/components/cover/cover_traits.h index 2df4a0738e..fb30883f77 100644 --- a/esphome/components/cover/cover_traits.h +++ b/esphome/components/cover/cover_traits.h @@ -13,11 +13,14 @@ class CoverTraits { void set_supports_position(bool supports_position) { this->supports_position_ = supports_position; } bool get_supports_tilt() const { return this->supports_tilt_; } void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; } + bool get_supports_toggle() const { return this->supports_toggle_; } + void set_supports_toggle(bool supports_toggle) { this->supports_toggle_ = supports_toggle; } protected: bool is_assumed_state_{false}; bool supports_position_{false}; bool supports_tilt_{false}; + bool supports_toggle_{false}; }; } // namespace cover diff --git a/esphome/components/time_based/time_based_cover.cpp b/esphome/components/time_based/time_based_cover.cpp index 3fa07167ca..522252e907 100644 --- a/esphome/components/time_based/time_based_cover.cpp +++ b/esphome/components/time_based/time_based_cover.cpp @@ -52,6 +52,7 @@ float TimeBasedCover::get_setup_priority() const { return setup_priority::DATA; CoverTraits TimeBasedCover::get_traits() { auto traits = CoverTraits(); traits.set_supports_position(true); + traits.set_supports_toggle(true); traits.set_is_assumed_state(this->assumed_state_); return traits; } @@ -60,6 +61,20 @@ void TimeBasedCover::control(const CoverCall &call) { this->start_direction_(COVER_OPERATION_IDLE); this->publish_state(); } + if (call.get_toggle().has_value()) { + if (this->current_operation != COVER_OPERATION_IDLE) { + this->start_direction_(COVER_OPERATION_IDLE); + this->publish_state(); + } else { + if (this->position == COVER_CLOSED || this->last_operation_ == COVER_OPERATION_CLOSING) { + this->target_position_ = COVER_OPEN; + this->start_direction_(COVER_OPERATION_OPENING); + } else { + this->target_position_ = COVER_CLOSED; + this->start_direction_(COVER_OPERATION_CLOSING); + } + } + } if (call.get_position().has_value()) { auto pos = *call.get_position(); if (pos == this->position) { @@ -105,9 +120,11 @@ void TimeBasedCover::start_direction_(CoverOperation dir) { trig = this->stop_trigger_; break; case COVER_OPERATION_OPENING: + this->last_operation_ = dir; trig = this->open_trigger_; break; case COVER_OPERATION_CLOSING: + this->last_operation_ = dir; trig = this->close_trigger_; break; default: diff --git a/esphome/components/time_based/time_based_cover.h b/esphome/components/time_based/time_based_cover.h index 6c48c26ed1..517ab77cb3 100644 --- a/esphome/components/time_based/time_based_cover.h +++ b/esphome/components/time_based/time_based_cover.h @@ -45,6 +45,7 @@ class TimeBasedCover : public cover::Cover, public Component { float target_position_{0}; bool has_built_in_endstop_{false}; bool assumed_state_{false}; + cover::CoverOperation last_operation_{cover::COVER_OPERATION_OPENING}; }; } // namespace time_based diff --git a/tests/test3.yaml b/tests/test3.yaml index 49f48f4cfa..b261d6cc8e 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -729,6 +729,12 @@ binary_sensor: id: r0_sensor name: 'R0 Sensor' component_name: page0.r0 + - platform: template + id: 'cover_toggle' + on_press: + then: + - cover.toggle: time_based_cover + globals: - id: my_global_string type: std::string @@ -1018,6 +1024,7 @@ cover: max_duration: 10min - platform: time_based name: Time Based Cover + id: time_based_cover stop_action: - switch.turn_on: gpio_switch1 open_action: From 5624fafb3a55662172a629bb09ea8a3ac105e8e5 Mon Sep 17 00:00:00 2001 From: 0hax <43876620+0hax@users.noreply.github.com> Date: Mon, 27 Sep 2021 22:32:08 +0200 Subject: [PATCH 1453/1841] Fix handling of timestamps in Teleinfo component. (#2392) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * teleinfo: avoid a buffer overflow. When reading tag or values, data is written to the buffer even if the size if bigger than the buffer. Add a new 'max_len' argument to get_field() to avoid this error. Signed-off-by: 0hax <0hax@protonmail.com> * teleinfo: read extra timestamp field for some tags. Some tags has an extra timestamp field that need to be read before the actual data. The code is inspired by Jpsy work: https://github.com/Jpsy/esphome/commit/29339c14f96ed7cf7a68911ca7d9bd5eb94955d6 Signed-off-by: 0hax <0hax@protonmail.com> * teleinfo: increase MAX_BUF_SIZE to suffice for 3-phase Linky in Standard mode. * teleinfo: handle DATE tag correctly. The DATE tag is special due its format and need to be handled separately. Fix from DrCoolzic. Signed-off-by: 0hax <0hax@protonmail.com> Co-authored-by: Jörg Wagner --- esphome/components/teleinfo/teleinfo.cpp | 37 +++++++++++++++++++++--- esphome/components/teleinfo/teleinfo.h | 4 ++- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/esphome/components/teleinfo/teleinfo.cpp b/esphome/components/teleinfo/teleinfo.cpp index 8240615cc5..badd66ae83 100644 --- a/esphome/components/teleinfo/teleinfo.cpp +++ b/esphome/components/teleinfo/teleinfo.cpp @@ -7,7 +7,7 @@ namespace teleinfo { static const char *const TAG = "teleinfo"; /* Helpers */ -static int get_field(char *dest, char *buf_start, char *buf_end, int sep) { +static int get_field(char *dest, char *buf_start, char *buf_end, int sep, int max_len) { char *field_end; int len; @@ -15,6 +15,8 @@ static int get_field(char *dest, char *buf_start, char *buf_end, int sep) { if (!field_end) return 0; len = field_end - buf_start; + if (len >= max_len) + return len; strncpy(dest, buf_start, len); dest[len] = '\0'; @@ -106,9 +108,22 @@ void TeleInfo::loop() { * 0xa | Tag | 0x9 | Data | 0x9 | CRC | 0xd * ^^^^^^^^^^^^^^^^^^^^^^^^^ * Checksum is computed on the above in standard mode. + * + * Note that some Tags may have a timestamp in Standard mode. In this case + * the group would looks like this: + * 0xa | Tag | 0x9 | Timestamp | 0x9 | Data | 0x9 | CRC | 0xd + * + * The DATE tag is a special case. The group looks like this + * 0xa | Tag | 0x9 | Timestamp | 0x9 | 0x9 | CRC | 0xd + * */ while ((buf_finger = static_cast(memchr(buf_finger, (int) 0xa, buf_index_ - 1))) && ((buf_finger - buf_) < buf_index_)) { + /* + * Make sure timesamp is nullified between each tag as some tags don't + * have a timestamp + */ + timestamp_[0] = '\0'; /* Point to the first char of the group after 0xa */ buf_finger += 1; @@ -123,7 +138,7 @@ void TeleInfo::loop() { continue; /* Get tag */ - field_len = get_field(tag_, buf_finger, grp_end, separator_); + field_len = get_field(tag_, buf_finger, grp_end, separator_, MAX_TAG_SIZE); if (!field_len || field_len >= MAX_TAG_SIZE) { ESP_LOGE(TAG, "Invalid tag."); break; @@ -132,8 +147,22 @@ void TeleInfo::loop() { /* Advance buf_finger to after the tag and the separator. */ buf_finger += field_len + 1; - /* Get value (after next separator) */ - field_len = get_field(val_, buf_finger, grp_end, separator_); + /* + * If there is two separators and the tag is not equal to "DATE", + * it means there is a timestamp to read first. + */ + if (std::count(buf_finger, grp_end, separator_) == 2 && strcmp(tag_, "DATE") != 0) { + field_len = get_field(timestamp_, buf_finger, grp_end, separator_, MAX_TIMESTAMP_SIZE); + if (!field_len || field_len >= MAX_TIMESTAMP_SIZE) { + ESP_LOGE(TAG, "Invalid Timestamp"); + break; + } + + /* Advance buf_finger to after the first data and the separator. */ + buf_finger += field_len + 1; + } + + field_len = get_field(val_, buf_finger, grp_end, separator_, MAX_VAL_SIZE); if (!field_len || field_len >= MAX_VAL_SIZE) { ESP_LOGE(TAG, "Invalid Value"); break; diff --git a/esphome/components/teleinfo/teleinfo.h b/esphome/components/teleinfo/teleinfo.h index f10024691e..2be34cfb78 100644 --- a/esphome/components/teleinfo/teleinfo.h +++ b/esphome/components/teleinfo/teleinfo.h @@ -11,7 +11,8 @@ namespace teleinfo { */ static const uint8_t MAX_TAG_SIZE = 64; static const uint16_t MAX_VAL_SIZE = 256; -static const uint16_t MAX_BUF_SIZE = 1024; +static const uint16_t MAX_BUF_SIZE = 2048; +static const uint16_t MAX_TIMESTAMP_SIZE = 14; class TeleInfoListener { public: @@ -36,6 +37,7 @@ class TeleInfo : public PollingComponent, public uart::UARTDevice { uint32_t buf_index_{0}; char tag_[MAX_TAG_SIZE]; char val_[MAX_VAL_SIZE]; + char timestamp_[MAX_TIMESTAMP_SIZE]; enum State { OFF, ON, From 6417d8132d392c9a43ec94aac661c569ad6fe146 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 27 Sep 2021 14:08:21 -0700 Subject: [PATCH 1454/1841] bump dashboard to 20210927.0 (#2405) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 537a802e06..7ee22f0415 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.0 esptool==3.1 click==8.0.1 -esphome-dashboard==20210908.0 +esphome-dashboard==20210927.0 aioesphomeapi==9.1.1 # esp-idf requires this, but doesn't bundle it by default From 5596751c2c0a99d1c4df1d281d943007653424dd Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 27 Sep 2021 23:24:55 +0200 Subject: [PATCH 1455/1841] Add str_sprintf function that returns std::string (#2408) --- esphome/core/helpers.cpp | 15 +++++++++++++++ esphome/core/helpers.h | 3 +++ 2 files changed, 18 insertions(+) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index a190566bea..0092d202c4 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -351,6 +351,21 @@ bool str_startswith(const std::string &full, const std::string &start) { return bool str_endswith(const std::string &full, const std::string &ending) { return full.rfind(ending) == (full.size() - ending.size()); } +std::string str_sprintf(const char *fmt, ...) { + std::string str; + va_list args; + + va_start(args, fmt); + size_t length = vsnprintf(nullptr, 0, fmt, args); + va_end(args); + + str.resize(length); + va_start(args, fmt); + vsnprintf(&str[0], length + 1, fmt, args); + va_end(args); + + return str; +} uint16_t encode_uint16(uint8_t msb, uint8_t lsb) { return (uint16_t(msb) << 8) | uint16_t(lsb); } std::array decode_uint16(uint16_t value) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 8118585db5..f5a7a197ca 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -59,6 +59,9 @@ bool str_equals_case_insensitive(const std::string &a, const std::string &b); bool str_startswith(const std::string &full, const std::string &start); bool str_endswith(const std::string &full, const std::string &ending); +/// sprintf-like function returning std::string instead of writing to char array. +std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...); + class HighFrequencyLoopRequester { public: void start(); From be965a60eba6bb769e2a5afdbc8eed132f077a59 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 28 Sep 2021 02:53:38 +0200 Subject: [PATCH 1456/1841] Merge pull request from GHSA-48mj-p7x2-5jfm * Move web_server auth to web_server_base * Fix * Fix * Add middleware system --- esphome/components/web_server/__init__.py | 8 +-- esphome/components/web_server/web_server.cpp | 7 -- esphome/components/web_server/web_server.h | 8 --- .../web_server_base/web_server_base.cpp | 11 +++ .../web_server_base/web_server_base.h | 72 +++++++++++++++++-- 5 files changed, 81 insertions(+), 25 deletions(-) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 7f17767657..240ba7c8a0 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -34,8 +34,8 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_JS_INCLUDE): cv.file_, cv.Optional(CONF_AUTH): cv.Schema( { - cv.Required(CONF_USERNAME): cv.string_strict, - cv.Required(CONF_PASSWORD): cv.string_strict, + cv.Required(CONF_USERNAME): cv.All(cv.string_strict, cv.Length(min=1)), + cv.Required(CONF_PASSWORD): cv.All(cv.string_strict, cv.Length(min=1)), } ), cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( @@ -57,8 +57,8 @@ async def to_code(config): cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) if CONF_AUTH in config: - cg.add(var.set_username(config[CONF_AUTH][CONF_USERNAME])) - cg.add(var.set_password(config[CONF_AUTH][CONF_PASSWORD])) + cg.add(paren.set_auth_username(config[CONF_AUTH][CONF_USERNAME])) + cg.add(paren.set_auth_password(config[CONF_AUTH][CONF_PASSWORD])) if CONF_CSS_INCLUDE in config: cg.add_define("WEBSERVER_CSS_INCLUDE") path = CORE.relative_config_path(config[CONF_CSS_INCLUDE]) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 97777a8986..28e741cf24 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -158,9 +158,6 @@ void WebServer::setup() { void WebServer::dump_config() { ESP_LOGCONFIG(TAG, "Web Server:"); ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->base_->get_port()); - if (this->using_auth()) { - ESP_LOGCONFIG(TAG, " Basic authentication enabled"); - } } float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; } @@ -764,10 +761,6 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return false; } void WebServer::handleRequest(AsyncWebServerRequest *request) { - if (this->using_auth() && !request->authenticate(this->username_, this->password_)) { - return request->requestAuthentication(); - } - if (request->url() == "/") { this->handle_index_request(request); return; diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 0eaa2e9a75..021d5a0646 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -32,10 +32,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { public: WebServer(web_server_base::WebServerBase *base) : base_(base) {} - void set_username(const char *username) { username_ = username; } - - void set_password(const char *password) { password_ = password; } - /** Set the URL to the CSS that's sent to each client. Defaults to * https://esphome.io/_static/webserver-v1.min.css * @@ -85,8 +81,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_js_request(AsyncWebServerRequest *request); #endif - bool using_auth() { return username_ != nullptr && password_ != nullptr; } - #ifdef USE_SENSOR void on_sensor_update(sensor::Sensor *obj, float state) override; /// Handle a sensor request under '/sensor/'. @@ -184,8 +178,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { protected: web_server_base::WebServerBase *base_; AsyncEventSource events_{"/events"}; - const char *username_{nullptr}; - const char *password_{nullptr}; const char *css_url_{nullptr}; const char *css_include_{nullptr}; const char *js_url_{nullptr}; diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index b0babcab46..3c269b28b8 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -17,6 +17,17 @@ namespace web_server_base { static const char *const TAG = "web_server_base"; +void WebServerBase::add_handler(AsyncWebHandler *handler) { + // remove all handlers + + if (!credentials_.username.empty()) { + handler = new internal::AuthMiddlewareHandler(handler, &credentials_); + } + this->handlers_.push_back(handler); + if (this->server_ != nullptr) + this->server_->addHandler(handler); +} + void report_ota_error() { StreamString ss; Update.printError(ss); diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index 4ef67f959c..7afc72b9d2 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -10,6 +10,68 @@ namespace esphome { namespace web_server_base { +namespace internal { + +class MiddlewareHandler : public AsyncWebHandler { + public: + MiddlewareHandler(AsyncWebHandler *next) : next_(next) {} + + bool canHandle(AsyncWebServerRequest *request) override { return next_->canHandle(request); } + void handleRequest(AsyncWebServerRequest *request) override { next_->handleRequest(request); } + void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, + bool final) override { + next_->handleUpload(request, filename, index, data, len, final); + } + void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override { + next_->handleBody(request, data, len, index, total); + } + bool isRequestHandlerTrivial() override { return next_->isRequestHandlerTrivial(); } + + protected: + AsyncWebHandler *next_; +}; + +struct Credentials { + std::string username; + std::string password; +}; + +class AuthMiddlewareHandler : public MiddlewareHandler { + public: + AuthMiddlewareHandler(AsyncWebHandler *next, Credentials *credentials) + : MiddlewareHandler(next), credentials_(credentials) {} + + bool check_auth(AsyncWebServerRequest *request) { + bool success = request->authenticate(credentials_->username.c_str(), credentials_->password.c_str()); + if (!success) { + request->requestAuthentication(); + } + return success; + } + + void handleRequest(AsyncWebServerRequest *request) override { + if (!check_auth(request)) + return; + MiddlewareHandler::handleRequest(request); + } + void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, + bool final) override { + if (!check_auth(request)) + return; + MiddlewareHandler::handleUpload(request, filename, index, data, len, final); + } + void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override { + if (!check_auth(request)) + return; + MiddlewareHandler::handleBody(request, data, len, index, total); + } + + protected: + Credentials *credentials_; +}; + +} // namespace internal + class WebServerBase : public Component { public: void init() { @@ -34,13 +96,10 @@ class WebServerBase : public Component { std::shared_ptr get_server() const { return server_; } float get_setup_priority() const override; - void add_handler(AsyncWebHandler *handler) { - // remove all handlers + void set_auth_username(std::string auth_username) { credentials_.username = auth_username; } + void set_auth_password(std::string auth_password) { credentials_.password = auth_password; } - this->handlers_.push_back(handler); - if (this->server_ != nullptr) - this->server_->addHandler(handler); - } + void add_handler(AsyncWebHandler *handler); void add_ota_handler(); @@ -54,6 +113,7 @@ class WebServerBase : public Component { uint16_t port_{80}; std::shared_ptr server_{nullptr}; std::vector handlers_; + internal::Credentials credentials_; }; class OTARequestHandler : public AsyncWebHandler { From 2234f6aacf8cc653307fed80f3750317a82c4f83 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 28 Sep 2021 15:33:30 +1300 Subject: [PATCH 1457/1841] Fix lint issues in web_server_base (#2409) --- esphome/components/web_server_base/web_server_base.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index 7afc72b9d2..bc37337ca5 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -3,6 +3,7 @@ #ifdef USE_ARDUINO #include +#include #include "esphome/core/component.h" #include @@ -96,8 +97,8 @@ class WebServerBase : public Component { std::shared_ptr get_server() const { return server_; } float get_setup_priority() const override; - void set_auth_username(std::string auth_username) { credentials_.username = auth_username; } - void set_auth_password(std::string auth_password) { credentials_.password = auth_password; } + void set_auth_username(std::string auth_username) { credentials_.username = std::move(auth_username); } + void set_auth_password(std::string auth_password) { credentials_.password = std::move(auth_password); } void add_handler(AsyncWebHandler *handler); From cb5efc1c422f0ced500eebacd44b9fe52ebaff97 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 12:02:37 +0200 Subject: [PATCH 1458/1841] Bump aioesphomeapi to 9.1.1 (#2350) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 18752e16a3..bd90f31cbb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,4 @@ platformio==5.2.0 esptool==3.1 click==7.1.2 esphome-dashboard==20210908.0 -aioesphomeapi==9.0.0 +aioesphomeapi==9.1.1 From 185340764548d751e6005b46af9e097c119eb95d Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Mon, 27 Sep 2021 00:32:33 +0400 Subject: [PATCH 1459/1841] Midea fix (#2395) --- esphome/components/midea/climate.py | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/midea/climate.py b/esphome/components/midea/climate.py index 137fcdd607..7717613fc4 100644 --- a/esphome/components/midea/climate.py +++ b/esphome/components/midea/climate.py @@ -281,4 +281,4 @@ async def to_code(config): if CONF_HUMIDITY_SETPOINT in config: sens = await sensor.new_sensor(config[CONF_HUMIDITY_SETPOINT]) cg.add(var.set_humidity_setpoint_sensor(sens)) - cg.add_library("dudanov/MideaUART", "1.1.5") + cg.add_library("dudanov/MideaUART", "1.1.8") diff --git a/platformio.ini b/platformio.ini index f4dea3fcb9..73d5595dcd 100644 --- a/platformio.ini +++ b/platformio.ini @@ -37,7 +37,7 @@ lib_deps = glmnet/Dsmr@0.3 ; used by dsmr rweather/Crypto@0.2.0 ; used by dsmr esphome/noise-c@0.1.1 ; used by api - dudanov/MideaUART@1.1.0 ; used by midea + dudanov/MideaUART@1.1.8 ; used by midea build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE From 4579f78bf97dc8f09b2eccd7e58b2804aad33ef5 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 28 Sep 2021 02:53:38 +0200 Subject: [PATCH 1460/1841] Merge pull request from GHSA-48mj-p7x2-5jfm --- esphome/components/web_server/__init__.py | 8 +-- esphome/components/web_server/web_server.cpp | 13 +--- esphome/components/web_server/web_server.h | 8 --- .../web_server_base/web_server_base.cpp | 11 +++ .../web_server_base/web_server_base.h | 72 +++++++++++++++++-- 5 files changed, 84 insertions(+), 28 deletions(-) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 7f17767657..240ba7c8a0 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -34,8 +34,8 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_JS_INCLUDE): cv.file_, cv.Optional(CONF_AUTH): cv.Schema( { - cv.Required(CONF_USERNAME): cv.string_strict, - cv.Required(CONF_PASSWORD): cv.string_strict, + cv.Required(CONF_USERNAME): cv.All(cv.string_strict, cv.Length(min=1)), + cv.Required(CONF_PASSWORD): cv.All(cv.string_strict, cv.Length(min=1)), } ), cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( @@ -57,8 +57,8 @@ async def to_code(config): cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) if CONF_AUTH in config: - cg.add(var.set_username(config[CONF_AUTH][CONF_USERNAME])) - cg.add(var.set_password(config[CONF_AUTH][CONF_PASSWORD])) + cg.add(paren.set_auth_username(config[CONF_AUTH][CONF_USERNAME])) + cg.add(paren.set_auth_password(config[CONF_AUTH][CONF_PASSWORD])) if CONF_CSS_INCLUDE in config: cg.add_define("WEBSERVER_CSS_INCLUDE") path = CORE.relative_config_path(config[CONF_CSS_INCLUDE]) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index dc97bcd5c2..e19a54931a 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1,8 +1,8 @@ #include "web_server.h" -#include "esphome/core/log.h" -#include "esphome/core/application.h" -#include "esphome/core/util.h" #include "esphome/components/json/json_util.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" +#include "esphome/core/util.h" #include "StreamString.h" @@ -151,9 +151,6 @@ void WebServer::setup() { void WebServer::dump_config() { ESP_LOGCONFIG(TAG, "Web Server:"); ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->base_->get_port()); - if (this->using_auth()) { - ESP_LOGCONFIG(TAG, " Basic authentication enabled"); - } } float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; } @@ -728,10 +725,6 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return false; } void WebServer::handleRequest(AsyncWebServerRequest *request) { - if (this->using_auth() && !request->authenticate(this->username_, this->password_)) { - return request->requestAuthentication(); - } - if (request->url() == "/") { this->handle_index_request(request); return; diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 54d7356ac9..4e9224ee26 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -30,10 +30,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { public: WebServer(web_server_base::WebServerBase *base) : base_(base) {} - void set_username(const char *username) { username_ = username; } - - void set_password(const char *password) { password_ = password; } - /** Set the URL to the CSS that's sent to each client. Defaults to * https://esphome.io/_static/webserver-v1.min.css * @@ -83,8 +79,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_js_request(AsyncWebServerRequest *request); #endif - bool using_auth() { return username_ != nullptr && password_ != nullptr; } - #ifdef USE_SENSOR void on_sensor_update(sensor::Sensor *obj, float state) override; /// Handle a sensor request under '/sensor/'. @@ -182,8 +176,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { protected: web_server_base::WebServerBase *base_; AsyncEventSource events_{"/events"}; - const char *username_{nullptr}; - const char *password_{nullptr}; const char *css_url_{nullptr}; const char *css_include_{nullptr}; const char *js_url_{nullptr}; diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index 85711704b9..832456dc83 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -15,6 +15,17 @@ namespace web_server_base { static const char *const TAG = "web_server_base"; +void WebServerBase::add_handler(AsyncWebHandler *handler) { + // remove all handlers + + if (!credentials_.username.empty()) { + handler = new internal::AuthMiddlewareHandler(handler, &credentials_); + } + this->handlers_.push_back(handler); + if (this->server_ != nullptr) + this->server_->addHandler(handler); +} + void report_ota_error() { StreamString ss; Update.printError(ss); diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index b6024ceafa..1bfec13fc5 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -7,6 +7,68 @@ namespace esphome { namespace web_server_base { +namespace internal { + +class MiddlewareHandler : public AsyncWebHandler { + public: + MiddlewareHandler(AsyncWebHandler *next) : next_(next) {} + + bool canHandle(AsyncWebServerRequest *request) override { return next_->canHandle(request); } + void handleRequest(AsyncWebServerRequest *request) override { next_->handleRequest(request); } + void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, + bool final) override { + next_->handleUpload(request, filename, index, data, len, final); + } + void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override { + next_->handleBody(request, data, len, index, total); + } + bool isRequestHandlerTrivial() override { return next_->isRequestHandlerTrivial(); } + + protected: + AsyncWebHandler *next_; +}; + +struct Credentials { + std::string username; + std::string password; +}; + +class AuthMiddlewareHandler : public MiddlewareHandler { + public: + AuthMiddlewareHandler(AsyncWebHandler *next, Credentials *credentials) + : MiddlewareHandler(next), credentials_(credentials) {} + + bool check_auth(AsyncWebServerRequest *request) { + bool success = request->authenticate(credentials_->username.c_str(), credentials_->password.c_str()); + if (!success) { + request->requestAuthentication(); + } + return success; + } + + void handleRequest(AsyncWebServerRequest *request) override { + if (!check_auth(request)) + return; + MiddlewareHandler::handleRequest(request); + } + void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, + bool final) override { + if (!check_auth(request)) + return; + MiddlewareHandler::handleUpload(request, filename, index, data, len, final); + } + void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override { + if (!check_auth(request)) + return; + MiddlewareHandler::handleBody(request, data, len, index, total); + } + + protected: + Credentials *credentials_; +}; + +} // namespace internal + class WebServerBase : public Component { public: void init() { @@ -32,13 +94,10 @@ class WebServerBase : public Component { AsyncWebServer *get_server() const { return server_; } float get_setup_priority() const override; - void add_handler(AsyncWebHandler *handler) { - // remove all handlers + void set_auth_username(std::string auth_username) { credentials_.username = auth_username; } + void set_auth_password(std::string auth_password) { credentials_.password = auth_password; } - this->handlers_.push_back(handler); - if (this->server_ != nullptr) - this->server_->addHandler(handler); - } + void add_handler(AsyncWebHandler *handler); void add_ota_handler(); @@ -52,6 +111,7 @@ class WebServerBase : public Component { uint16_t port_{80}; AsyncWebServer *server_{nullptr}; std::vector handlers_; + internal::Credentials credentials_; }; class OTARequestHandler : public AsyncWebHandler { From 8ef2ad17b5e298dd96d0b4b8de38f3be1975ddbe Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 28 Sep 2021 15:33:30 +1300 Subject: [PATCH 1461/1841] Fix lint issues in web_server_base (#2409) --- esphome/components/web_server_base/web_server_base.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index 1bfec13fc5..9c43358e48 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -1,5 +1,7 @@ #pragma once +#include +#include #include "esphome/core/component.h" #include @@ -94,8 +96,8 @@ class WebServerBase : public Component { AsyncWebServer *get_server() const { return server_; } float get_setup_priority() const override; - void set_auth_username(std::string auth_username) { credentials_.username = auth_username; } - void set_auth_password(std::string auth_password) { credentials_.password = auth_password; } + void set_auth_username(std::string auth_username) { credentials_.username = std::move(auth_username); } + void set_auth_password(std::string auth_password) { credentials_.password = std::move(auth_password); } void add_handler(AsyncWebHandler *handler); From a2485a18cb0f62c2bb2de6ff1298f8e107173f14 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 28 Sep 2021 15:41:58 +1300 Subject: [PATCH 1462/1841] Bump version to 2021.9.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index fb7250578c..f69f6e91da 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.1" +__version__ = "2021.9.2" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 2b9054d3b2bedd6b3ed8e5cf87e0236b2acbf8eb Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Wed, 29 Sep 2021 03:26:46 +1300 Subject: [PATCH 1463/1841] Initialised ESPPreferenceObject::backend_ to nullptr (#2411) --- esphome/core/preferences.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/preferences.h b/esphome/core/preferences.h index b3f4b77f78..ad45cd9684 100644 --- a/esphome/core/preferences.h +++ b/esphome/core/preferences.h @@ -30,7 +30,7 @@ class ESPPreferenceObject { } protected: - ESPPreferenceBackend *backend_; + ESPPreferenceBackend *backend_{nullptr}; }; class ESPPreferences { From af04f565cf203719f6340bcd263e50b856ae2a6f Mon Sep 17 00:00:00 2001 From: Stephen Tierney Date: Wed, 29 Sep 2021 06:10:25 +1000 Subject: [PATCH 1464/1841] Add support for SCD4X (#2217) * Initial commit * clang-format fixes * Update CODEOWNERS * clang-format fixes * Fix merge error * Fix missing return Co-authored-by: Otto winter --- CODEOWNERS | 1 + esphome/components/scd4x/__init__.py | 0 esphome/components/scd4x/scd4x.cpp | 259 +++++++++++++++++++++++++++ esphome/components/scd4x/scd4x.h | 53 ++++++ esphome/components/scd4x/sensor.py | 98 ++++++++++ tests/test1.yaml | 13 ++ 6 files changed, 424 insertions(+) create mode 100644 esphome/components/scd4x/__init__.py create mode 100644 esphome/components/scd4x/scd4x.cpp create mode 100644 esphome/components/scd4x/scd4x.h create mode 100644 esphome/components/scd4x/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 3c3a1e04ee..2972b30b33 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -125,6 +125,7 @@ esphome/components/restart/* @esphome/core esphome/components/rf_bridge/* @jesserockz esphome/components/rgbct/* @jesserockz esphome/components/rtttl/* @glmnet +esphome/components/scd4x/* @sjtrny esphome/components/script/* @esphome/core esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdp3x/* @Azimath diff --git a/esphome/components/scd4x/__init__.py b/esphome/components/scd4x/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/scd4x/scd4x.cpp b/esphome/components/scd4x/scd4x.cpp new file mode 100644 index 0000000000..c91fd5e882 --- /dev/null +++ b/esphome/components/scd4x/scd4x.cpp @@ -0,0 +1,259 @@ +#include "scd4x.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace scd4x { + +static const char *const TAG = "scd4x"; + +static const uint16_t SCD4X_CMD_GET_SERIAL_NUMBER = 0x3682; +static const uint16_t SCD4X_CMD_TEMPERATURE_OFFSET = 0x241d; +static const uint16_t SCD4X_CMD_ALTITUDE_COMPENSATION = 0x2427; +static const uint16_t SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION = 0xe000; +static const uint16_t SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION = 0x2416; +static const uint16_t SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS = 0x21b1; +static const uint16_t SCD4X_CMD_GET_DATA_READY_STATUS = 0xe4b8; +static const uint16_t SCD4X_CMD_READ_MEASUREMENT = 0xec05; +static const uint16_t SCD4X_CMD_PERFORM_FORCED_CALIBRATION = 0x362f; +static const uint16_t SCD4X_CMD_STOP_MEASUREMENTS = 0x3f86; + +static const float SCD4X_TEMPERATURE_OFFSET_MULTIPLIER = (1 << 16) / 175.0f; + +void SCD4XComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up scd4x..."); + + // the sensor needs 1000 ms to enter the idle state + this->set_timeout(1000, [this]() { + // Check if measurement is ready before reading the value + if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_STATUS)) { + ESP_LOGE(TAG, "Failed to write data ready status command"); + this->mark_failed(); + return; + } + + uint16_t raw_read_status[1]; + if (!this->read_data_(raw_read_status, 1)) { + ESP_LOGE(TAG, "Failed to read data ready status"); + this->mark_failed(); + return; + } + + // In order to query the device periodic measurement must be ceased + if (raw_read_status[0]) { + ESP_LOGD(TAG, "Sensor has data available, stopping periodic measurement"); + if (!this->write_command_(SCD4X_CMD_STOP_MEASUREMENTS)) { + ESP_LOGE(TAG, "Failed to stop measurements"); + this->mark_failed(); + return; + } + } + + if (!this->write_command_(SCD4X_CMD_GET_SERIAL_NUMBER)) { + ESP_LOGE(TAG, "Failed to write get serial command"); + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + + uint16_t raw_serial_number[3]; + if (!this->read_data_(raw_serial_number, 3)) { + ESP_LOGE(TAG, "Failed to read serial number"); + this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED; + this->mark_failed(); + return; + } + ESP_LOGD(TAG, "Serial number %02d.%02d.%02d", (uint16_t(raw_serial_number[0]) >> 8), + uint16_t(raw_serial_number[0] & 0xFF), (uint16_t(raw_serial_number[1]) >> 8)); + + if (!this->write_command_(SCD4X_CMD_TEMPERATURE_OFFSET, + (uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) { + ESP_LOGE(TAG, "Error setting temperature offset."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } + + // If pressure compensation available use it + // else use altitude + if (ambient_pressure_compensation_) { + if (!this->write_command_(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, ambient_pressure_compensation_)) { + ESP_LOGE(TAG, "Error setting ambient pressure compensation."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } + } else { + if (!this->write_command_(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { + ESP_LOGE(TAG, "Error setting altitude compensation."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } + } + + if (!this->write_command_(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) { + ESP_LOGE(TAG, "Error setting automatic self calibration."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } + + // Finally start sensor measurements + if (!this->write_command_(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) { + ESP_LOGE(TAG, "Error starting continuous measurements."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } + + initialized_ = true; + ESP_LOGD(TAG, "Sensor initialized"); + }); +} + +void SCD4XComponent::dump_config() { + ESP_LOGCONFIG(TAG, "scd4x:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + switch (this->error_code_) { + case COMMUNICATION_FAILED: + ESP_LOGW(TAG, "Communication failed! Is the sensor connected?"); + break; + case MEASUREMENT_INIT_FAILED: + ESP_LOGW(TAG, "Measurement Initialization failed!"); + break; + case SERIAL_NUMBER_IDENTIFICATION_FAILED: + ESP_LOGW(TAG, "Unable to read sensor firmware version"); + break; + default: + ESP_LOGW(TAG, "Unknown setup error!"); + break; + } + } + ESP_LOGCONFIG(TAG, " Automatic self calibration: %s", ONOFF(this->enable_asc_)); + if (this->ambient_pressure_compensation_) { + ESP_LOGCONFIG(TAG, " Altitude compensation disabled"); + ESP_LOGCONFIG(TAG, " Ambient pressure compensation: %dmBar", this->ambient_pressure_); + } else { + ESP_LOGCONFIG(TAG, " Ambient pressure compensation disabled"); + ESP_LOGCONFIG(TAG, " Altitude compensation: %dm", this->altitude_compensation_); + } + ESP_LOGCONFIG(TAG, " Temperature offset: %.2f °C", this->temperature_offset_); + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "CO2", this->co2_sensor_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); +} + +void SCD4XComponent::update() { + if (!initialized_) { + return; + } + + // Check if data is ready + if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_STATUS)) { + this->status_set_warning(); + return; + } + + uint16_t raw_read_status[1]; + if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) { + this->status_set_warning(); + ESP_LOGW(TAG, "Data not ready yet!"); + return; + } + + if (!this->write_command_(SCD4X_CMD_READ_MEASUREMENT)) { + ESP_LOGW(TAG, "Error reading measurement!"); + this->status_set_warning(); + return; + } + + // Read off sensor data + uint16_t raw_data[3]; + if (!this->read_data_(raw_data, 3)) { + this->status_set_warning(); + return; + } + + if (this->co2_sensor_ != nullptr) + this->co2_sensor_->publish_state(raw_data[0]); + + if (this->temperature_sensor_ != nullptr) { + const float temperature = -45.0f + (175.0f * (raw_data[1])) / (1 << 16); + this->temperature_sensor_->publish_state(temperature); + } + + if (this->humidity_sensor_ != nullptr) { + const float humidity = (100.0f * raw_data[2]) / (1 << 16); + this->humidity_sensor_->publish_state(humidity); + } + + this->status_clear_warning(); +} + +uint8_t SCD4XComponent::sht_crc_(uint8_t data1, uint8_t data2) { + uint8_t bit; + uint8_t crc = 0xFF; + + crc ^= data1; + for (bit = 8; bit > 0; --bit) { + if (crc & 0x80) + crc = (crc << 1) ^ 0x131; + else + crc = (crc << 1); + } + + crc ^= data2; + for (bit = 8; bit > 0; --bit) { + if (crc & 0x80) + crc = (crc << 1) ^ 0x131; + else + crc = (crc << 1); + } + + return crc; +} + +bool SCD4XComponent::read_data_(uint16_t *data, uint8_t len) { + const uint8_t num_bytes = len * 3; + std::vector buf(num_bytes); + + if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { + return false; + } + + for (uint8_t i = 0; i < len; i++) { + const uint8_t j = 3 * i; + uint8_t crc = sht_crc_(buf[j], buf[j + 1]); + if (crc != buf[j + 2]) { + ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); + return false; + } + data[i] = (buf[j] << 8) | buf[j + 1]; + } + return true; +} + +bool SCD4XComponent::write_command_(uint16_t command) { + const uint8_t num_bytes = 2; + uint8_t buffer[num_bytes]; + + buffer[0] = (command >> 8); + buffer[1] = command & 0xff; + + return this->write(buffer, num_bytes) == i2c::ERROR_OK; +} + +bool SCD4XComponent::write_command_(uint16_t command, uint16_t data) { + uint8_t raw[5]; + raw[0] = command >> 8; + raw[1] = command & 0xFF; + raw[2] = data >> 8; + raw[3] = data & 0xFF; + raw[4] = sht_crc_(raw[2], raw[3]); + return this->write(raw, 5) == i2c::ERROR_OK; +} + +} // namespace scd4x +} // namespace esphome diff --git a/esphome/components/scd4x/scd4x.h b/esphome/components/scd4x/scd4x.h new file mode 100644 index 0000000000..3c428b8623 --- /dev/null +++ b/esphome/components/scd4x/scd4x.h @@ -0,0 +1,53 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace scd4x { + +enum ERRORCODE { COMMUNICATION_FAILED, SERIAL_NUMBER_IDENTIFICATION_FAILED, MEASUREMENT_INIT_FAILED, UNKNOWN }; + +class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { + public: + float get_setup_priority() const override { return setup_priority::DATA; } + void setup() override; + void dump_config() override; + void update() override; + + void set_automatic_self_calibration(bool asc) { enable_asc_ = asc; } + void set_altitude_compensation(uint16_t altitude) { altitude_compensation_ = altitude; } + void set_ambient_pressure_compensation(float pressure) { + ambient_pressure_compensation_ = true; + ambient_pressure_ = (uint16_t)(pressure * 1000); + } + void set_temperature_offset(float offset) { temperature_offset_ = offset; }; + + void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; } + void set_temperature_sensor(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }; + void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } + + protected: + uint8_t sht_crc_(uint8_t data1, uint8_t data2); + bool read_data_(uint16_t *data, uint8_t len); + bool write_command_(uint16_t command); + bool write_command_(uint16_t command, uint16_t data); + + ERRORCODE error_code_; + + bool initialized_{false}; + + float temperature_offset_; + uint16_t altitude_compensation_; + bool ambient_pressure_compensation_; + uint16_t ambient_pressure_; + bool enable_asc_; + + sensor::Sensor *co2_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; +}; + +} // namespace scd4x +} // namespace esphome diff --git a/esphome/components/scd4x/sensor.py b/esphome/components/scd4x/sensor.py new file mode 100644 index 0000000000..0b1a960f6f --- /dev/null +++ b/esphome/components/scd4x/sensor.py @@ -0,0 +1,98 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor + +from esphome.const import ( + CONF_ID, + CONF_CO2, + CONF_HUMIDITY, + CONF_TEMPERATURE, + DEVICE_CLASS_CARBON_DIOXIDE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_PARTS_PER_MILLION, + ICON_MOLECULE_CO2, + ICON_THERMOMETER, + ICON_WATER_PERCENT, + UNIT_CELSIUS, + UNIT_PERCENT, +) + +CODEOWNERS = ["@sjtrny"] +DEPENDENCIES = ["i2c"] + +scd4x_ns = cg.esphome_ns.namespace("scd4x") +SCD4XComponent = scd4x_ns.class_("SCD4XComponent", cg.PollingComponent, i2c.I2CDevice) + +CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" +CONF_ALTITUDE_COMPENSATION = "altitude_compensation" +CONF_AMBIENT_PRESSURE_COMPENSATION = "ambient_pressure_compensation" +CONF_TEMPERATURE_OFFSET = "temperature_offset" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(SCD4XComponent), + cv.Optional(CONF_CO2): sensor.sensor_schema( + unit_of_measurement=UNIT_PARTS_PER_MILLION, + icon=ICON_MOLECULE_CO2, + accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_WATER_PERCENT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_AUTOMATIC_SELF_CALIBRATION, default=True): cv.boolean, + cv.Optional(CONF_ALTITUDE_COMPENSATION, default="0m"): cv.All( + cv.float_with_unit("altitude", "(m|m a.s.l.|MAMSL|MASL)"), + cv.int_range(min=0, max=0xFFFF, max_included=False), + ), + cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION): cv.pressure, + cv.Optional(CONF_TEMPERATURE_OFFSET, default="4°C"): cv.temperature, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x62)) +) + +SENSOR_MAP = { + CONF_CO2: "set_co2_sensor", + CONF_TEMPERATURE: "set_temperature_sensor", + CONF_HUMIDITY: "set_humidity_sensor", +} + +SETTING_MAP = { + CONF_AUTOMATIC_SELF_CALIBRATION: "set_automatic_self_calibration", + CONF_ALTITUDE_COMPENSATION: "set_altitude_compensation", + CONF_AMBIENT_PRESSURE_COMPENSATION: "set_ambient_pressure_compensation", + CONF_TEMPERATURE_OFFSET: "set_temperature_offset", +} + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + for key, funcName in SETTING_MAP.items(): + if key in config: + cg.add(getattr(var, funcName)(config[key])) + + for key, funcName in SENSOR_MAP.items(): + + if key in config: + sens = await sensor.new_sensor(config[key]) + cg.add(getattr(var, funcName)(sens)) diff --git a/tests/test1.yaml b/tests/test1.yaml index 6109e9f5c2..77f7da2b08 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -811,6 +811,19 @@ sensor: ambient_pressure_compensation: 961mBar temperature_offset: 4.2C i2c_id: i2c_bus + - platform: scd4x + co2: + name: "SCD4X CO2" + temperature: + name: "SCD4X Temperature" + humidity: + name: "SCD4X Humidity" + update_interval: 15s + automatic_self_calibration: true + altitude_compensation: 10m + ambient_pressure_compensation: 961mBar + temperature_offset: 4.2C + i2c_id: i2c_bus - platform: sgp30 eco2: name: 'Workshop eCO2' From c39ac9edfebead943cc51fb6ac4fe9b039b5a5c4 Mon Sep 17 00:00:00 2001 From: irtimaled Date: Tue, 28 Sep 2021 13:19:17 -0700 Subject: [PATCH 1465/1841] Support HSV-based color support on tuya light (#2400) * fix: stop tuya light state getting reset * fix typo * Support for HSV color in Tuya * Clamp formatting --- esphome/components/tuya/light/__init__.py | 11 +++- esphome/components/tuya/light/tuya_light.cpp | 33 ++++++++-- esphome/components/tuya/light/tuya_light.h | 2 + esphome/core/helpers.cpp | 63 ++++++++++++++++++++ esphome/core/helpers.h | 5 ++ 5 files changed, 107 insertions(+), 7 deletions(-) diff --git a/esphome/components/tuya/light/__init__.py b/esphome/components/tuya/light/__init__.py index 6678fc47d8..b983e3f84e 100644 --- a/esphome/components/tuya/light/__init__.py +++ b/esphome/components/tuya/light/__init__.py @@ -22,6 +22,7 @@ CONF_COLOR_TEMPERATURE_DATAPOINT = "color_temperature_datapoint" CONF_COLOR_TEMPERATURE_INVERT = "color_temperature_invert" CONF_COLOR_TEMPERATURE_MAX_VALUE = "color_temperature_max_value" CONF_RGB_DATAPOINT = "rgb_datapoint" +CONF_HSV_DATAPOINT = "hsv_datapoint" TuyaLight = tuya_ns.class_("TuyaLight", light.LightOutput, cg.Component) @@ -33,7 +34,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DIMMER_DATAPOINT): cv.uint8_t, cv.Optional(CONF_MIN_VALUE_DATAPOINT): cv.uint8_t, cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t, - cv.Optional(CONF_RGB_DATAPOINT): cv.uint8_t, + cv.Exclusive(CONF_RGB_DATAPOINT, "color"): cv.uint8_t, + cv.Exclusive(CONF_HSV_DATAPOINT, "color"): cv.uint8_t, cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean, cv.Inclusive( CONF_COLOR_TEMPERATURE_DATAPOINT, "color_temperature" @@ -57,7 +59,10 @@ CONFIG_SCHEMA = cv.All( } ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key( - CONF_DIMMER_DATAPOINT, CONF_SWITCH_DATAPOINT, CONF_RGB_DATAPOINT + CONF_DIMMER_DATAPOINT, + CONF_SWITCH_DATAPOINT, + CONF_RGB_DATAPOINT, + CONF_HSV_DATAPOINT, ), ) @@ -75,6 +80,8 @@ async def to_code(config): cg.add(var.set_switch_id(config[CONF_SWITCH_DATAPOINT])) if CONF_RGB_DATAPOINT in config: cg.add(var.set_rgb_id(config[CONF_RGB_DATAPOINT])) + elif CONF_HSV_DATAPOINT in config: + cg.add(var.set_hsv_id(config[CONF_HSV_DATAPOINT])) if CONF_COLOR_TEMPERATURE_DATAPOINT in config: cg.add(var.set_color_temperature_id(config[CONF_COLOR_TEMPERATURE_DATAPOINT])) cg.add(var.set_color_temperature_invert(config[CONF_COLOR_TEMPERATURE_INVERT])) diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index f75cc964aa..133ee1e557 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -46,6 +46,19 @@ void TuyaLight::setup() { call.perform(); } }); + } else if (hsv_id_.has_value()) { + this->parent_->register_listener(*this->hsv_id_, [this](const TuyaDatapoint &datapoint) { + auto hue = parse_hex(datapoint.value_string, 0, 4); + auto saturation = parse_hex(datapoint.value_string, 4, 4); + auto value = parse_hex(datapoint.value_string, 8, 4); + if (hue.has_value() && saturation.has_value() && value.has_value()) { + float red, green, blue; + hsv_to_rgb(*hue, float(*saturation) / 1000, float(*value) / 1000, red, green, blue); + auto call = this->state_->make_call(); + call.set_rgb(red, green, blue); + call.perform(); + } + }); } if (min_value_datapoint_id_.has_value()) { parent_->set_integer_datapoint_value(*this->min_value_datapoint_id_, this->min_value_); @@ -60,12 +73,14 @@ void TuyaLight::dump_config() { ESP_LOGCONFIG(TAG, " Switch has datapoint ID %u", *this->switch_id_); if (this->rgb_id_.has_value()) ESP_LOGCONFIG(TAG, " RGB has datapoint ID %u", *this->rgb_id_); + else if (this->hsv_id_.has_value()) + ESP_LOGCONFIG(TAG, " HSV has datapoint ID %u", *this->hsv_id_); } light::LightTraits TuyaLight::get_traits() { auto traits = light::LightTraits(); if (this->color_temperature_id_.has_value() && this->dimmer_id_.has_value()) { - if (this->rgb_id_.has_value()) { + if (this->rgb_id_.has_value() || this->hsv_id_.has_value()) { if (this->color_interlock_) traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::COLOR_TEMPERATURE}); else @@ -75,7 +90,7 @@ light::LightTraits TuyaLight::get_traits() { traits.set_supported_color_modes({light::ColorMode::COLOR_TEMPERATURE}); traits.set_min_mireds(this->cold_white_temperature_); traits.set_max_mireds(this->warm_white_temperature_); - } else if (this->rgb_id_.has_value()) { + } else if (this->rgb_id_.has_value() || this->hsv_id_.has_value()) { if (this->dimmer_id_.has_value()) { if (this->color_interlock_) traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::WHITE}); @@ -97,7 +112,7 @@ void TuyaLight::write_state(light::LightState *state) { float red = 0.0f, green = 0.0f, blue = 0.0f; float color_temperature = 0.0f, brightness = 0.0f; - if (this->rgb_id_.has_value()) { + if (this->rgb_id_.has_value() || this->hsv_id_.has_value()) { if (this->color_temperature_id_.has_value()) { state->current_values_as_rgbct(&red, &green, &blue, &color_temperature, &brightness); } else if (this->dimmer_id_.has_value()) { @@ -137,8 +152,16 @@ void TuyaLight::write_state(light::LightState *state) { if (this->rgb_id_.has_value()) { char buffer[7]; sprintf(buffer, "%02X%02X%02X", int(red * 255), int(green * 255), int(blue * 255)); - std::string value = buffer; - this->parent_->set_string_datapoint_value(*this->rgb_id_, value); + std::string rgb_value = buffer; + this->parent_->set_string_datapoint_value(*this->rgb_id_, rgb_value); + } else if (this->hsv_id_.has_value()) { + int hue; + float saturation, value; + rgb_to_hsv(red, green, blue, hue, saturation, value); + char buffer[13]; + sprintf(buffer, "%04X%04X%04X", hue, int(saturation * 1000), int(value * 1000)); + std::string hsv_value = buffer; + this->parent_->set_string_datapoint_value(*this->hsv_id_, hsv_value); } } diff --git a/esphome/components/tuya/light/tuya_light.h b/esphome/components/tuya/light/tuya_light.h index de9ec5e45f..3d9f25271c 100644 --- a/esphome/components/tuya/light/tuya_light.h +++ b/esphome/components/tuya/light/tuya_light.h @@ -17,6 +17,7 @@ class TuyaLight : public Component, public light::LightOutput { } void set_switch_id(uint8_t switch_id) { this->switch_id_ = switch_id; } void set_rgb_id(uint8_t rgb_id) { this->rgb_id_ = rgb_id; } + void set_hsv_id(uint8_t hsv_id) { this->hsv_id_ = hsv_id; } void set_color_temperature_id(uint8_t color_temperature_id) { this->color_temperature_id_ = color_temperature_id; } void set_color_temperature_invert(bool color_temperature_invert) { this->color_temperature_invert_ = color_temperature_invert; @@ -48,6 +49,7 @@ class TuyaLight : public Component, public light::LightOutput { optional min_value_datapoint_id_{}; optional switch_id_{}; optional rgb_id_{}; + optional hsv_id_{}; optional color_temperature_id_{}; uint32_t min_value_ = 0; uint32_t max_value_ = 255; diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 0092d202c4..731ee6c8f5 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -394,6 +394,69 @@ std::string hexencode(const uint8_t *data, uint32_t len) { return res; } +void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value) { + float max_color_value = std::max(std::max(red, green), blue); + float min_color_value = std::min(std::min(red, green), blue); + float delta = max_color_value - min_color_value; + + if (delta == 0) + hue = 0; + else if (max_color_value == red) + hue = int(fmod(((60 * ((green - blue) / delta)) + 360), 360)); + else if (max_color_value == green) + hue = int(fmod(((60 * ((blue - red) / delta)) + 120), 360)); + else if (max_color_value == blue) + hue = int(fmod(((60 * ((red - green) / delta)) + 240), 360)); + + if (max_color_value == 0) + saturation = 0; + else + saturation = delta / max_color_value; + + value = max_color_value; +} + +void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue) { + float chroma = value * saturation; + float hue_prime = fmod(hue / 60.0, 6); + float intermediate = chroma * (1 - fabs(fmod(hue_prime, 2) - 1)); + float delta = value - chroma; + + if (0 <= hue_prime && hue_prime < 1) { + red = chroma; + green = intermediate; + blue = 0; + } else if (1 <= hue_prime && hue_prime < 2) { + red = intermediate; + green = chroma; + blue = 0; + } else if (2 <= hue_prime && hue_prime < 3) { + red = 0; + green = chroma; + blue = intermediate; + } else if (3 <= hue_prime && hue_prime < 4) { + red = 0; + green = intermediate; + blue = chroma; + } else if (4 <= hue_prime && hue_prime < 5) { + red = intermediate; + green = 0; + blue = chroma; + } else if (5 <= hue_prime && hue_prime < 6) { + red = chroma; + green = 0; + blue = intermediate; + } else { + red = 0; + green = 0; + blue = 0; + } + + red += delta; + green += delta; + blue += delta; +} + #ifdef USE_ESP8266 IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index f5a7a197ca..24b55eade0 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -149,6 +149,11 @@ std::array decode_uint16(uint16_t value); /// Encode a 32-bit unsigned integer given four bytes in MSB -> LSB order uint32_t encode_uint32(uint8_t msb, uint8_t byte2, uint8_t byte3, uint8_t lsb); +/// Convert RGB floats (0-1) to hue (0-360) & saturation/value percentage (0-1) +void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value); +/// Convert hue (0-360) & saturation/value percentage (0-1) to RGB floats (0-1) +void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue); + /*** * An interrupt helper class. * From c26ea7e4e093cbedc77ca5cd3fc39e062d37a3de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Tue, 28 Sep 2021 23:02:13 +0200 Subject: [PATCH 1466/1841] Tuya: add cover component (#2279) --- esphome/components/tuya/cover/__init__.py | 54 ++++++++++++++++++ esphome/components/tuya/cover/tuya_cover.cpp | 58 ++++++++++++++++++++ esphome/components/tuya/cover/tuya_cover.h | 33 +++++++++++ tests/test4.yaml | 5 ++ 4 files changed, 150 insertions(+) create mode 100644 esphome/components/tuya/cover/__init__.py create mode 100644 esphome/components/tuya/cover/tuya_cover.cpp create mode 100644 esphome/components/tuya/cover/tuya_cover.h diff --git a/esphome/components/tuya/cover/__init__.py b/esphome/components/tuya/cover/__init__.py new file mode 100644 index 0000000000..7816f39bf8 --- /dev/null +++ b/esphome/components/tuya/cover/__init__.py @@ -0,0 +1,54 @@ +from esphome.components import cover +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import ( + CONF_OUTPUT_ID, + CONF_MIN_VALUE, + CONF_MAX_VALUE, +) +from .. import tuya_ns, CONF_TUYA_ID, Tuya + +DEPENDENCIES = ["tuya"] + +CONF_POSITION_DATAPOINT = "position_datapoint" +CONF_INVERT_POSITION = "invert_position" + +TuyaCover = tuya_ns.class_("TuyaCover", cover.Cover, cg.Component) + + +def validate_range(config): + if config[CONF_MIN_VALUE] > config[CONF_MAX_VALUE]: + raise cv.Invalid( + "min_value({}) cannot be greater than max_value({})".format( + config[CONF_MIN_VALUE], config[CONF_MAX_VALUE] + ) + ) + return config + + +CONFIG_SCHEMA = cv.All( + cover.COVER_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaCover), + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Required(CONF_POSITION_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_MIN_VALUE, default=0): cv.int_, + cv.Optional(CONF_MAX_VALUE, default=100): cv.int_, + cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, + }, + ).extend(cv.COMPONENT_SCHEMA), + validate_range, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + await cg.register_component(var, config) + await cover.register_cover(var, config) + + cg.add(var.set_position_id(config[CONF_POSITION_DATAPOINT])) + cg.add(var.set_min_value(config[CONF_MIN_VALUE])) + cg.add(var.set_max_value(config[CONF_MAX_VALUE])) + cg.add(var.set_invert_position(config[CONF_INVERT_POSITION])) + paren = await cg.get_variable(config[CONF_TUYA_ID]) + cg.add(var.set_tuya_parent(paren)) diff --git a/esphome/components/tuya/cover/tuya_cover.cpp b/esphome/components/tuya/cover/tuya_cover.cpp new file mode 100644 index 0000000000..7da1312938 --- /dev/null +++ b/esphome/components/tuya/cover/tuya_cover.cpp @@ -0,0 +1,58 @@ +#include "esphome/core/log.h" +#include "tuya_cover.h" + +namespace esphome { +namespace tuya { + +static const char *const TAG = "tuya.cover"; + +void TuyaCover::setup() { + this->value_range_ = this->max_value_ - this->min_value_; + if (this->position_id_.has_value()) { + this->parent_->register_listener(*this->position_id_, [this](const TuyaDatapoint &datapoint) { + auto pos = float(datapoint.value_uint - this->min_value_) / this->value_range_; + if (this->invert_position_) + pos = 1.0f - pos; + this->position = pos; + this->publish_state(); + }); + } +} + +void TuyaCover::control(const cover::CoverCall &call) { + if (call.get_stop()) { + auto pos = this->position; + if (this->invert_position_) + pos = 1.0f - pos; + auto position_int = static_cast(pos * this->value_range_); + position_int = position_int + this->min_value_; + + parent_->set_integer_datapoint_value(*this->position_id_, position_int); + } + if (call.get_position().has_value()) { + auto pos = *call.get_position(); + if (this->invert_position_) + pos = 1.0f - pos; + auto position_int = static_cast(pos * this->value_range_); + position_int = position_int + this->min_value_; + + parent_->set_integer_datapoint_value(*this->position_id_, position_int); + } + + this->publish_state(); +} + +void TuyaCover::dump_config() { + ESP_LOGCONFIG(TAG, "Tuya Cover:"); + if (this->position_id_.has_value()) + ESP_LOGCONFIG(TAG, " Position has datapoint ID %u", *this->position_id_); +} + +cover::CoverTraits TuyaCover::get_traits() { + auto traits = cover::CoverTraits(); + traits.set_supports_position(true); + return traits; +} + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/cover/tuya_cover.h b/esphome/components/tuya/cover/tuya_cover.h new file mode 100644 index 0000000000..b62e58dc1b --- /dev/null +++ b/esphome/components/tuya/cover/tuya_cover.h @@ -0,0 +1,33 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/tuya/tuya.h" +#include "esphome/components/cover/cover.h" + +namespace esphome { +namespace tuya { + +class TuyaCover : public cover::Cover, public Component { + public: + void setup() override; + void dump_config() override; + void set_position_id(uint8_t dimmer_id) { this->position_id_ = dimmer_id; } + void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } + void set_min_value(uint32_t min_value) { min_value_ = min_value; } + void set_max_value(uint32_t max_value) { max_value_ = max_value; } + void set_invert_position(bool invert_position) { invert_position_ = invert_position; } + + protected: + void control(const cover::CoverCall &call) override; + cover::CoverTraits get_traits() override; + + Tuya *parent_; + optional position_id_{}; + uint32_t min_value_ = 0; + uint32_t max_value_ = 100; + uint32_t value_range_; + bool invert_position_ = false; +}; + +} // namespace tuya +} // namespace esphome diff --git a/tests/test4.yaml b/tests/test4.yaml index a205f0802e..4f2025ad74 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -358,6 +358,11 @@ light: warm_white_color_temperature: 500 mireds gamma_correct: 1 +cover: + - platform: tuya + id: tuya_cover + position_datapoint: 2 + display: - platform: addressable_light id: led_matrix_32x8_display From 855c98d81500448a311263d51250f0babf8aaedb Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 28 Sep 2021 23:15:52 +0200 Subject: [PATCH 1467/1841] Fix tuya cover lint checks (#2414) --- esphome/components/tuya/cover/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esphome/components/tuya/cover/__init__.py b/esphome/components/tuya/cover/__init__.py index 7816f39bf8..5a654841f7 100644 --- a/esphome/components/tuya/cover/__init__.py +++ b/esphome/components/tuya/cover/__init__.py @@ -19,9 +19,7 @@ TuyaCover = tuya_ns.class_("TuyaCover", cover.Cover, cg.Component) def validate_range(config): if config[CONF_MIN_VALUE] > config[CONF_MAX_VALUE]: raise cv.Invalid( - "min_value({}) cannot be greater than max_value({})".format( - config[CONF_MIN_VALUE], config[CONF_MAX_VALUE] - ) + f"min_value ({config[CONF_MIN_VALUE]}) cannot be greater than max_value ({config[CONF_MAX_VALUE]})" ) return config From 7af1c044939ea54f7fc1e74b15bb2fac9d75a11a Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 28 Sep 2021 23:16:00 +0200 Subject: [PATCH 1468/1841] Bump debian base to 5.1.0 / 20210902 (#2413) --- docker/Dockerfile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 9c3e864e43..e66c3e1d95 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,12 +5,12 @@ # One of "docker", "hassio" ARG BASEIMGTYPE=docker -FROM ghcr.io/hassio-addons/debian-base/amd64:5.0.0 AS base-hassio-amd64 -FROM ghcr.io/hassio-addons/debian-base/aarch64:5.0.0 AS base-hassio-arm64 -FROM ghcr.io/hassio-addons/debian-base/armv7:5.0.0 AS base-hassio-armv7 -FROM debian:bullseye-20210816-slim AS base-docker-amd64 -FROM debian:bullseye-20210816-slim AS base-docker-arm64 -FROM debian:bullseye-20210816-slim AS base-docker-armv7 +FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.0 AS base-hassio-amd64 +FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.0 AS base-hassio-arm64 +FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.0 AS base-hassio-armv7 +FROM debian:bullseye-20210902-slim AS base-docker-amd64 +FROM debian:bullseye-20210902-slim AS base-docker-arm64 +FROM debian:bullseye-20210902-slim AS base-docker-armv7 # Use TARGETARCH/TARGETVARIANT defined by docker # https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope From 505d1d78fb29c55b024d4d0b7eea5a02e3af7bce Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 29 Sep 2021 12:19:19 +1300 Subject: [PATCH 1469/1841] Remove default initializations from tuya cover (#2415) --- esphome/components/tuya/cover/tuya_cover.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/tuya/cover/tuya_cover.h b/esphome/components/tuya/cover/tuya_cover.h index b62e58dc1b..c3b0c3e069 100644 --- a/esphome/components/tuya/cover/tuya_cover.h +++ b/esphome/components/tuya/cover/tuya_cover.h @@ -23,10 +23,10 @@ class TuyaCover : public cover::Cover, public Component { Tuya *parent_; optional position_id_{}; - uint32_t min_value_ = 0; - uint32_t max_value_ = 100; + uint32_t min_value_; + uint32_t max_value_; uint32_t value_range_; - bool invert_position_ = false; + bool invert_position_; }; } // namespace tuya From 4f5e4f3b86d0d2069b51757025b0c585bf33fc27 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 29 Sep 2021 23:21:52 +0200 Subject: [PATCH 1470/1841] Move #ifdef to after header include (#2417) defines.h needs to be included first. Fixes esphome/issues#2490. --- esphome/components/nextion/nextion_upload.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index 9a748277d8..cebdbec31a 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -1,6 +1,7 @@ +#include "nextion.h" + #ifdef USE_NEXTION_TFT_UPLOAD -#include "nextion.h" #include "esphome/core/application.h" #include "esphome/core/macros.h" #include "esphome/core/util.h" From 3dfc8d42915f5224a9ac13b6f4be5e4d5828cbac Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Thu, 30 Sep 2021 07:25:06 +1000 Subject: [PATCH 1471/1841] String manipulation filters for text sensors (#2393) * initial text sensor filter POC * fixed verbose logging * add append, prepend, substitute filters * add to lower, get to upper working without dummy * clang lint * more linting... * std::move append and prepend filters * fix verbose filter::input logging * value.c_str() in input print * lambda filter verbose log fix * correct log tag, neaten to upper and to lower * add on_raw_value automation/trigger --- esphome/components/text_sensor/__init__.py | 97 +++++++++++++++ esphome/components/text_sensor/automation.h | 7 ++ esphome/components/text_sensor/filter.cpp | 74 ++++++++++++ esphome/components/text_sensor/filter.h | 112 ++++++++++++++++++ .../components/text_sensor/text_sensor.cpp | 59 ++++++++- esphome/components/text_sensor/text_sensor.h | 29 ++++- 6 files changed, 373 insertions(+), 5 deletions(-) create mode 100644 esphome/components/text_sensor/filter.cpp create mode 100644 esphome/components/text_sensor/filter.h diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index d06f12de0e..cdb4b85e9a 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -4,16 +4,22 @@ from esphome import automation from esphome.components import mqtt from esphome.const import ( CONF_DISABLED_BY_DEFAULT, + CONF_FILTERS, CONF_ICON, CONF_ID, CONF_INTERNAL, CONF_ON_VALUE, + CONF_ON_RAW_VALUE, CONF_TRIGGER_ID, CONF_MQTT_ID, CONF_NAME, CONF_STATE, + CONF_FROM, + CONF_TO, ) from esphome.core import CORE, coroutine_with_priority +from esphome.util import Registry + IS_PLATFORM_COMPONENT = True @@ -25,6 +31,9 @@ TextSensorPtr = TextSensor.operator("ptr") TextSensorStateTrigger = text_sensor_ns.class_( "TextSensorStateTrigger", automation.Trigger.template(cg.std_string) ) +TextSensorStateRawTrigger = text_sensor_ns.class_( + "TextSensorStateRawTrigger", automation.Trigger.template(cg.std_string) +) TextSensorPublishAction = text_sensor_ns.class_( "TextSensorPublishAction", automation.Action ) @@ -32,21 +41,101 @@ TextSensorStateCondition = text_sensor_ns.class_( "TextSensorStateCondition", automation.Condition ) +FILTER_REGISTRY = Registry() +validate_filters = cv.validate_registry("filter", FILTER_REGISTRY) + +# Filters +Filter = text_sensor_ns.class_("Filter") +LambdaFilter = text_sensor_ns.class_("LambdaFilter", Filter) +ToUpperFilter = text_sensor_ns.class_("ToUpperFilter", Filter) +ToLowerFilter = text_sensor_ns.class_("ToLowerFilter", Filter) +AppendFilter = text_sensor_ns.class_("AppendFilter", Filter) +PrependFilter = text_sensor_ns.class_("PrependFilter", Filter) +SubstituteFilter = text_sensor_ns.class_("SubstituteFilter", Filter) + + +@FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda) +async def lambda_filter_to_code(config, filter_id): + lambda_ = await cg.process_lambda( + config, [(cg.std_string, "x")], return_type=cg.optional.template(cg.std_string) + ) + return cg.new_Pvariable(filter_id, lambda_) + + +@FILTER_REGISTRY.register("to_upper", ToUpperFilter, {}) +async def to_upper_filter_to_code(config, filter_id): + return cg.new_Pvariable(filter_id) + + +@FILTER_REGISTRY.register("to_lower", ToLowerFilter, {}) +async def to_lower_filter_to_code(config, filter_id): + return cg.new_Pvariable(filter_id) + + +@FILTER_REGISTRY.register("append", AppendFilter, cv.string) +async def append_filter_to_code(config, filter_id): + return cg.new_Pvariable(filter_id, config) + + +@FILTER_REGISTRY.register("prepend", PrependFilter, cv.string) +async def prepend_filter_to_code(config, filter_id): + return cg.new_Pvariable(filter_id, config) + + +def validate_substitute(value): + if isinstance(value, dict): + return cv.Schema( + { + cv.Required(CONF_FROM): cv.string, + cv.Required(CONF_TO): cv.string, + } + )(value) + value = cv.string(value) + if "->" not in value: + raise cv.Invalid("Substitute mapping must contain '->'") + a, b = value.split("->", 1) + a, b = a.strip(), b.strip() + return validate_substitute({CONF_FROM: cv.string(a), CONF_TO: cv.string(b)}) + + +@FILTER_REGISTRY.register( + "substitute", + SubstituteFilter, + cv.All(cv.ensure_list(validate_substitute), cv.Length(min=2)), +) +async def substitute_filter_to_code(config, filter_id): + from_strings = [conf[CONF_FROM] for conf in config] + to_strings = [conf[CONF_TO] for conf in config] + return cg.new_Pvariable(filter_id, from_strings, to_strings) + + icon = cv.icon TEXT_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor), cv.Optional(CONF_ICON): icon, + cv.Optional(CONF_FILTERS): validate_filters, cv.Optional(CONF_ON_VALUE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TextSensorStateTrigger), } ), + cv.Optional(CONF_ON_RAW_VALUE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + TextSensorStateRawTrigger + ), + } + ), } ) +async def build_filters(config): + return await cg.build_registry_list(FILTER_REGISTRY, config) + + async def setup_text_sensor_core_(var, config): cg.add(var.set_name(config[CONF_NAME])) cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) @@ -55,10 +144,18 @@ async def setup_text_sensor_core_(var, config): if CONF_ICON in config: cg.add(var.set_icon(config[CONF_ICON])) + if config.get(CONF_FILTERS): # must exist and not be empty + filters = await build_filters(config[CONF_FILTERS]) + cg.add(var.set_filters(filters)) + for conf in config.get(CONF_ON_VALUE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [(cg.std_string, "x")], conf) + for conf in config.get(CONF_ON_RAW_VALUE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.std_string, "x")], conf) + if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) diff --git a/esphome/components/text_sensor/automation.h b/esphome/components/text_sensor/automation.h index d82fd27c1f..dd02aeaf3b 100644 --- a/esphome/components/text_sensor/automation.h +++ b/esphome/components/text_sensor/automation.h @@ -16,6 +16,13 @@ class TextSensorStateTrigger : public Trigger { } }; +class TextSensorStateRawTrigger : public Trigger { + public: + explicit TextSensorStateRawTrigger(TextSensor *parent) { + parent->add_on_raw_state_callback([this](std::string value) { this->trigger(std::move(value)); }); + } +}; + template class TextSensorStateCondition : public Condition { public: explicit TextSensorStateCondition(TextSensor *parent) : parent_(parent) {} diff --git a/esphome/components/text_sensor/filter.cpp b/esphome/components/text_sensor/filter.cpp new file mode 100644 index 0000000000..14df6238ff --- /dev/null +++ b/esphome/components/text_sensor/filter.cpp @@ -0,0 +1,74 @@ +#include "filter.h" +#include "text_sensor.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace text_sensor { + +static const char *const TAG = "text_sensor.filter"; + +// Filter +void Filter::input(const std::string &value) { + ESP_LOGVV(TAG, "Filter(%p)::input(%s)", this, value.c_str()); + optional out = this->new_value(value); + if (out.has_value()) + this->output(*out); +} +void Filter::output(const std::string &value) { + if (this->next_ == nullptr) { + ESP_LOGVV(TAG, "Filter(%p)::output(%s) -> SENSOR", this, value.c_str()); + this->parent_->internal_send_state_to_frontend(value); + } else { + ESP_LOGVV(TAG, "Filter(%p)::output(%s) -> %p", this, value.c_str(), this->next_); + this->next_->input(value); + } +} +void Filter::initialize(TextSensor *parent, Filter *next) { + ESP_LOGVV(TAG, "Filter(%p)::initialize(parent=%p next=%p)", this, parent, next); + this->parent_ = parent; + this->next_ = next; +} + +// LambdaFilter +LambdaFilter::LambdaFilter(lambda_filter_t lambda_filter) : lambda_filter_(std::move(lambda_filter)) {} +const lambda_filter_t &LambdaFilter::get_lambda_filter() const { return this->lambda_filter_; } +void LambdaFilter::set_lambda_filter(const lambda_filter_t &lambda_filter) { this->lambda_filter_ = lambda_filter; } + +optional LambdaFilter::new_value(std::string value) { + auto it = this->lambda_filter_(value); + ESP_LOGVV(TAG, "LambdaFilter(%p)::new_value(%s) -> %s", this, value.c_str(), it.value_or("").c_str()); + return it; +} + +// ToUpperFilter +optional ToUpperFilter::new_value(std::string value) { + for (char &c : value) + c = ::toupper(c); + return value; +} + +// ToLowerFilter +optional ToLowerFilter::new_value(std::string value) { + for (char &c : value) + c = ::toupper(c); + return value; +} + +// Append +optional AppendFilter::new_value(std::string value) { return value + this->suffix_; } + +// Prepend +optional PrependFilter::new_value(std::string value) { return this->prefix_ + value; } + +// Substitute +optional SubstituteFilter::new_value(std::string value) { + std::size_t pos; + for (int i = 0; i < this->from_strings_.size(); i++) + while ((pos = value.find(this->from_strings_[i])) != std::string::npos) + value.replace(pos, this->from_strings_[i].size(), this->to_strings_[i]); + return value; +} + +} // namespace text_sensor +} // namespace esphome diff --git a/esphome/components/text_sensor/filter.h b/esphome/components/text_sensor/filter.h new file mode 100644 index 0000000000..6a1d9ab04e --- /dev/null +++ b/esphome/components/text_sensor/filter.h @@ -0,0 +1,112 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include +#include + +namespace esphome { +namespace text_sensor { + +class TextSensor; + +/** Apply a filter to text sensor values such as to_upper. + * + * This class is purposefully kept quite simple, since more complicated + * filters should really be done with the filter sensor in Home Assistant. + */ +class Filter { + public: + /** This will be called every time the filter receives a new value. + * + * It can return an empty optional to indicate that the filter chain + * should stop, otherwise the value in the filter will be passed down + * the chain. + * + * @param value The new value. + * @return An optional string, the new value that should be pushed out. + */ + virtual optional new_value(std::string value); + + /// Initialize this filter, please note this can be called more than once. + virtual void initialize(TextSensor *parent, Filter *next); + + void input(const std::string &value); + + void output(const std::string &value); + + protected: + friend TextSensor; + + Filter *next_{nullptr}; + TextSensor *parent_{nullptr}; +}; + +using lambda_filter_t = std::function(std::string)>; + +/** This class allows for creation of simple template filters. + * + * The constructor accepts a lambda of the form std::string -> optional. + * It will be called with each new value in the filter chain and returns the modified + * value that shall be passed down the filter chain. Returning an empty Optional + * means that the value shall be discarded. + */ +class LambdaFilter : public Filter { + public: + explicit LambdaFilter(lambda_filter_t lambda_filter); + + optional new_value(std::string value) override; + + const lambda_filter_t &get_lambda_filter() const; + void set_lambda_filter(const lambda_filter_t &lambda_filter); + + protected: + lambda_filter_t lambda_filter_; +}; + +/// A simple filter that converts all text to uppercase +class ToUpperFilter : public Filter { + public: + optional new_value(std::string value) override; +}; + +/// A simple filter that converts all text to lowercase +class ToLowerFilter : public Filter { + public: + optional new_value(std::string value) override; +}; + +/// A simple filter that adds a string to the end of another string +class AppendFilter : public Filter { + public: + AppendFilter(std::string suffix) : suffix_(std::move(suffix)) {} + optional new_value(std::string value) override; + + protected: + std::string suffix_; +}; + +/// A simple filter that adds a string to the start of another string +class PrependFilter : public Filter { + public: + PrependFilter(std::string prefix) : prefix_(std::move(prefix)) {} + optional new_value(std::string value) override; + + protected: + std::string prefix_; +}; + +/// A simple filter that replaces a substring with another substring +class SubstituteFilter : public Filter { + public: + SubstituteFilter(std::vector from_strings, std::vector to_strings) + : from_strings_(std::move(from_strings)), to_strings_(std::move(to_strings)) {} + optional new_value(std::string value) override; + + protected: + std::vector from_strings_; + std::vector to_strings_; +}; + +} // namespace text_sensor +} // namespace esphome diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 8738860d55..774f3a8cb6 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -10,21 +10,72 @@ TextSensor::TextSensor() : TextSensor("") {} TextSensor::TextSensor(const std::string &name) : Nameable(name) {} void TextSensor::publish_state(const std::string &state) { - this->state = state; + this->raw_state = state; + this->raw_callback_.call(state); + + ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), state.c_str()); + + if (this->filter_list_ == nullptr) { + this->internal_send_state_to_frontend(state); + } else { + this->filter_list_->input(state); + } +} + +void TextSensor::add_filter(Filter *filter) { + // inefficient, but only happens once on every sensor setup and nobody's going to have massive amounts of + // filters + ESP_LOGVV(TAG, "TextSensor(%p)::add_filter(%p)", this, filter); + if (this->filter_list_ == nullptr) { + this->filter_list_ = filter; + } else { + Filter *last_filter = this->filter_list_; + while (last_filter->next_ != nullptr) + last_filter = last_filter->next_; + last_filter->initialize(this, filter); + } + filter->initialize(this, nullptr); +} +void TextSensor::add_filters(const std::vector &filters) { + for (Filter *filter : filters) { + this->add_filter(filter); + } +} +void TextSensor::set_filters(const std::vector &filters) { + this->clear_filters(); + this->add_filters(filters); +} +void TextSensor::clear_filters() { + if (this->filter_list_ != nullptr) { + ESP_LOGVV(TAG, "TextSensor(%p)::clear_filters()", this); + } + this->filter_list_ = nullptr; +} + +void TextSensor::add_on_state_callback(std::function callback) { + this->callback_.add(std::move(callback)); +} +void TextSensor::add_on_raw_state_callback(std::function callback) { + this->raw_callback_.add(std::move(callback)); +} + +std::string TextSensor::get_state() const { return this->state; } +std::string TextSensor::get_raw_state() const { return this->raw_state; } +void TextSensor::internal_send_state_to_frontend(const std::string &state) { + this->state = this->raw_state; this->has_state_ = true; ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str()); this->callback_.call(state); } + void TextSensor::set_icon(const std::string &icon) { this->icon_ = icon; } -void TextSensor::add_on_state_callback(std::function callback) { - this->callback_.add(std::move(callback)); -} std::string TextSensor::get_icon() { if (this->icon_.has_value()) return *this->icon_; return this->icon(); } std::string TextSensor::icon() { return ""; } + std::string TextSensor::unique_id() { return ""; } bool TextSensor::has_state() { return this->has_state_; } uint32_t TextSensor::hash_base() { return 334300109UL; } diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index 5293f0d216..7804deedb6 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include "esphome/components/text_sensor/filter.h" namespace esphome { namespace text_sensor { @@ -22,13 +23,33 @@ class TextSensor : public Nameable { explicit TextSensor(); explicit TextSensor(const std::string &name); + /// Getter-syntax for .state. + std::string get_state() const; + /// Getter-syntax for .raw_state + std::string get_raw_state() const; + void publish_state(const std::string &state); void set_icon(const std::string &icon); + /// Add a filter to the filter chain. Will be appended to the back. + void add_filter(Filter *filter); + + /// Add a list of vectors to the back of the filter chain. + void add_filters(const std::vector &filters); + + /// Clear the filters and replace them by filters. + void set_filters(const std::vector &filters); + + /// Clear the entire filter chain. + void clear_filters(); + void add_on_state_callback(std::function callback); + /// Add a callback that will be called every time the sensor sends a raw value. + void add_on_raw_state_callback(std::function callback); std::string state; + std::string raw_state; // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) @@ -40,10 +61,16 @@ class TextSensor : public Nameable { bool has_state(); + void internal_send_state_to_frontend(const std::string &state); + protected: uint32_t hash_base() override; - CallbackManager callback_; + CallbackManager raw_callback_; ///< Storage for raw state callbacks. + CallbackManager callback_; ///< Storage for filtered state callbacks. + + Filter *filter_list_{nullptr}; ///< Store all active filters. + optional icon_; bool has_state_{false}; }; From 946db3fd506509368a7d18aa070eef133ffaeb3f Mon Sep 17 00:00:00 2001 From: Andy Allsopp Date: Thu, 30 Sep 2021 12:03:30 +0100 Subject: [PATCH 1472/1841] Add viewport meta tag to web server layout (#2419) * Update web_server.cpp Added viewport meta tag to web_server.cpp in order to better control layout on mobile browsers. Adds 70 characters. Vastly improves accessibility on mobile devices. * Update web_server.cpp split line to meet clang format requirement. * Update web_server.cpp Reworked line break for clangtidy --- esphome/components/web_server/web_server.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 28e741cf24..d72262b4d3 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -164,7 +164,9 @@ float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f void WebServer::handle_index_request(AsyncWebServerRequest *request) { AsyncResponseStream *stream = request->beginResponseStream("text/html"); std::string title = App.get_name() + " Web Server"; - stream->print(F("")); + stream->print(F("<!DOCTYPE html><html lang=\"en\"><head><meta charset=UTF-8>" + "<meta name=\"viewport\" content=\"width=device-width, " + "initial-scale=1.0\"><title>")); stream->print(title.c_str()); stream->print(F("")); #ifdef WEBSERVER_CSS_INCLUDE From 0e4f1ac40d58a17af878e7c4bf6ae0bb14ceb543 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 30 Sep 2021 16:24:02 +0200 Subject: [PATCH 1473/1841] Fix default environment for clang-tidy (#2420) * Drop unnecessary platformio call from script/lint-cpp * Default environment for clang-tidy to esp32-tidy --- script/clang-tidy | 2 +- script/lint-cpp | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/script/clang-tidy b/script/clang-tidy index 6e79059372..87ba1c84b5 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -122,7 +122,7 @@ def main(): parser.add_argument('-j', '--jobs', type=int, default=multiprocessing.cpu_count(), help='number of tidy instances to be run in parallel.') - parser.add_argument('-e', '--environment', default='esp8266-tidy', + parser.add_argument('-e', '--environment', default='esp32-tidy', help='the PlatformIO environment to run against (esp8266-tidy or esp32-tidy)') parser.add_argument('files', nargs='*', default=[], help='files to be processed (regex on path)') diff --git a/script/lint-cpp b/script/lint-cpp index 170d61d539..ac03ca0f23 100755 --- a/script/lint-cpp +++ b/script/lint-cpp @@ -3,9 +3,6 @@ set -e cd "$(dirname "$0")/.." -if [[ ! -e ".gcc-flags.json" ]]; then - pio init --ide atom -fi set -x From 5b0fbbaada91766de1c4ee950378a82bc8116149 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 30 Sep 2021 16:25:08 +0200 Subject: [PATCH 1474/1841] Replace std::move() with const references where possible (#2421) * Replace std::move() with const references where possible * Fix formatting --- esphome/components/http_request/http_request.h | 2 +- esphome/components/pipsolar/output/pipsolar_output.h | 2 +- esphome/components/pipsolar/switch/pipsolar_switch.h | 4 ++-- esphome/components/rf_bridge/rf_bridge.h | 3 +-- esphome/components/select/automation.h | 2 +- esphome/components/sim800l/sim800l.h | 2 +- esphome/components/template/select/template_select.h | 2 +- esphome/components/text_sensor/automation.h | 4 ++-- 8 files changed, 10 insertions(+), 11 deletions(-) diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index 511096e7fa..9cc027b58d 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -40,7 +40,7 @@ class HttpRequestComponent : public Component { void set_method(const char *method) { this->method_ = method; } void set_useragent(const char *useragent) { this->useragent_ = useragent; } void set_timeout(uint16_t timeout) { this->timeout_ = timeout; } - void set_body(std::string body) { this->body_ = std::move(body); } + void set_body(const std::string &body) { this->body_ = body; } void set_headers(std::list
    headers) { this->headers_ = std::move(headers); } void send(const std::vector &response_triggers); void close(); diff --git a/esphome/components/pipsolar/output/pipsolar_output.h b/esphome/components/pipsolar/output/pipsolar_output.h index 932efe01c2..fe783cf034 100644 --- a/esphome/components/pipsolar/output/pipsolar_output.h +++ b/esphome/components/pipsolar/output/pipsolar_output.h @@ -13,7 +13,7 @@ class PipsolarOutput : public output::FloatOutput { public: PipsolarOutput() {} void set_parent(Pipsolar *parent) { this->parent_ = parent; } - void set_set_command(std::string command) { this->set_command_ = std::move(command); }; + void set_set_command(const std::string &command) { this->set_command_ = command; }; void set_possible_values(std::vector possible_values) { this->possible_values_ = std::move(possible_values); } void set_value(float value) { this->write_state(value); }; diff --git a/esphome/components/pipsolar/switch/pipsolar_switch.h b/esphome/components/pipsolar/switch/pipsolar_switch.h index 3fe4c7dfa1..11ff6c853a 100644 --- a/esphome/components/pipsolar/switch/pipsolar_switch.h +++ b/esphome/components/pipsolar/switch/pipsolar_switch.h @@ -10,8 +10,8 @@ class Pipsolar; class PipsolarSwitch : public switch_::Switch, public Component { public: void set_parent(Pipsolar *parent) { this->parent_ = parent; }; - void set_on_command(std::string command) { this->on_command_ = std::move(command); }; - void set_off_command(std::string command) { this->off_command_ = std::move(command); }; + void set_on_command(const std::string &command) { this->on_command_ = command; }; + void set_off_command(const std::string &command) { this->off_command_ = command; }; void dump_config() override; protected: diff --git a/esphome/components/rf_bridge/rf_bridge.h b/esphome/components/rf_bridge/rf_bridge.h index 2fa4eb05c5..9156d995bc 100644 --- a/esphome/components/rf_bridge/rf_bridge.h +++ b/esphome/components/rf_bridge/rf_bridge.h @@ -85,8 +85,7 @@ class RFBridgeReceivedCodeTrigger : public Trigger { class RFBridgeReceivedAdvancedCodeTrigger : public Trigger { public: explicit RFBridgeReceivedAdvancedCodeTrigger(RFBridgeComponent *parent) { - parent->add_on_advanced_code_received_callback( - [this](RFBridgeAdvancedData data) { this->trigger(std::move(data)); }); + parent->add_on_advanced_code_received_callback([this](const RFBridgeAdvancedData &data) { this->trigger(data); }); } }; diff --git a/esphome/components/select/automation.h b/esphome/components/select/automation.h index 59525f879e..1e0bfed63d 100644 --- a/esphome/components/select/automation.h +++ b/esphome/components/select/automation.h @@ -10,7 +10,7 @@ namespace select { class SelectStateTrigger : public Trigger { public: explicit SelectStateTrigger(Select *parent) { - parent->add_on_state_callback([this](std::string value) { this->trigger(std::move(value)); }); + parent->add_on_state_callback([this](const std::string &value) { this->trigger(value); }); } }; diff --git a/esphome/components/sim800l/sim800l.h b/esphome/components/sim800l/sim800l.h index fa9c392bfc..21e9ac4a50 100644 --- a/esphome/components/sim800l/sim800l.h +++ b/esphome/components/sim800l/sim800l.h @@ -74,7 +74,7 @@ class Sim800LReceivedMessageTrigger : public Trigger { public: explicit Sim800LReceivedMessageTrigger(Sim800LComponent *parent) { parent->add_on_sms_received_callback( - [this](std::string message, std::string sender) { this->trigger(std::move(message), std::move(sender)); }); + [this](const std::string &message, const std::string &sender) { this->trigger(message, sender); }); } }; diff --git a/esphome/components/template/select/template_select.h b/esphome/components/template/select/template_select.h index e24eb6e880..2f00765c3d 100644 --- a/esphome/components/template/select/template_select.h +++ b/esphome/components/template/select/template_select.h @@ -19,7 +19,7 @@ class TemplateSelect : public select::Select, public PollingComponent { Trigger *get_set_trigger() const { return this->set_trigger_; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } - void set_initial_option(std::string initial_option) { this->initial_option_ = std::move(initial_option); } + void set_initial_option(const std::string &initial_option) { this->initial_option_ = initial_option; } void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } protected: diff --git a/esphome/components/text_sensor/automation.h b/esphome/components/text_sensor/automation.h index dd02aeaf3b..d7286845e0 100644 --- a/esphome/components/text_sensor/automation.h +++ b/esphome/components/text_sensor/automation.h @@ -12,14 +12,14 @@ namespace text_sensor { class TextSensorStateTrigger : public Trigger { public: explicit TextSensorStateTrigger(TextSensor *parent) { - parent->add_on_state_callback([this](std::string value) { this->trigger(std::move(value)); }); + parent->add_on_state_callback([this](const std::string &value) { this->trigger(value); }); } }; class TextSensorStateRawTrigger : public Trigger { public: explicit TextSensorStateRawTrigger(TextSensor *parent) { - parent->add_on_raw_state_callback([this](std::string value) { this->trigger(std::move(value)); }); + parent->add_on_raw_state_callback([this](const std::string &value) { this->trigger(value); }); } }; From 1031ea431348893eb30e1866b327c86bcfabcaf2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 30 Sep 2021 18:07:28 +0200 Subject: [PATCH 1475/1841] Fix line endings normalization (#2407) * Strip CRLF line endings from modbus controller files * Normalize all line endings to LF --- .gitattributes | 3 +- .../modbus_controller/number/modbus_number.h | 96 +++++++++---------- .../modbus_controller/output/modbus_output.h | 90 ++++++++--------- 3 files changed, 95 insertions(+), 94 deletions(-) diff --git a/.gitattributes b/.gitattributes index 94f480de94..dad0966222 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ -* text=auto eol=lf \ No newline at end of file +# Normalize line endings to LF in the repository +* text eol=lf diff --git a/esphome/components/modbus_controller/number/modbus_number.h b/esphome/components/modbus_controller/number/modbus_number.h index 0fd4e314bc..271bbfac50 100644 --- a/esphome/components/modbus_controller/number/modbus_number.h +++ b/esphome/components/modbus_controller/number/modbus_number.h @@ -1,48 +1,48 @@ -#pragma once - -#include "esphome/components/number/number.h" -#include "esphome/components/modbus_controller/modbus_controller.h" -#include "esphome/core/component.h" - -namespace esphome { -namespace modbus_controller { - -using value_to_data_t = std::function(float); - -class ModbusNumber : public number::Number, public Component, public SensorItem { - public: - ModbusNumber(uint16_t start_address, uint8_t offset, uint32_t bitmask, SensorValueType value_type, int register_count, - uint8_t skip_updates, bool force_new_range) - : number::Number(), Component(), SensorItem() { - this->register_type = ModbusRegisterType::HOLDING; - this->start_address = start_address; - this->offset = offset; - this->bitmask = bitmask; - this->sensor_value_type = value_type; - this->register_count = register_count; - this->skip_updates = skip_updates; - this->force_new_range = force_new_range; - }; - - void dump_config() override; - void parse_and_publish(const std::vector &data) override; - float get_setup_priority() const override { return setup_priority::HARDWARE; } - void set_update_interval(int) {} - void set_parent(ModbusController *parent) { this->parent_ = parent; } - void set_write_multiply(float factor) { multiply_by_ = factor; } - - using transform_func_t = std::function(ModbusNumber *, float, const std::vector &)>; - using write_transform_func_t = std::function(ModbusNumber *, float, std::vector &)>; - void set_template(transform_func_t &&f) { this->transform_func_ = f; } - void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } - - protected: - void control(float value) override; - optional transform_func_; - optional write_transform_func_; - ModbusController *parent_; - float multiply_by_{1.0}; -}; - -} // namespace modbus_controller -} // namespace esphome +#pragma once + +#include "esphome/components/number/number.h" +#include "esphome/components/modbus_controller/modbus_controller.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace modbus_controller { + +using value_to_data_t = std::function(float); + +class ModbusNumber : public number::Number, public Component, public SensorItem { + public: + ModbusNumber(uint16_t start_address, uint8_t offset, uint32_t bitmask, SensorValueType value_type, int register_count, + uint8_t skip_updates, bool force_new_range) + : number::Number(), Component(), SensorItem() { + this->register_type = ModbusRegisterType::HOLDING; + this->start_address = start_address; + this->offset = offset; + this->bitmask = bitmask; + this->sensor_value_type = value_type; + this->register_count = register_count; + this->skip_updates = skip_updates; + this->force_new_range = force_new_range; + }; + + void dump_config() override; + void parse_and_publish(const std::vector &data) override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + void set_update_interval(int) {} + void set_parent(ModbusController *parent) { this->parent_ = parent; } + void set_write_multiply(float factor) { multiply_by_ = factor; } + + using transform_func_t = std::function(ModbusNumber *, float, const std::vector &)>; + using write_transform_func_t = std::function(ModbusNumber *, float, std::vector &)>; + void set_template(transform_func_t &&f) { this->transform_func_ = f; } + void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } + + protected: + void control(float value) override; + optional transform_func_; + optional write_transform_func_; + ModbusController *parent_; + float multiply_by_{1.0}; +}; + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/output/modbus_output.h b/esphome/components/modbus_controller/output/modbus_output.h index f46aef4683..053186a321 100644 --- a/esphome/components/modbus_controller/output/modbus_output.h +++ b/esphome/components/modbus_controller/output/modbus_output.h @@ -1,45 +1,45 @@ -#pragma once - -#include "esphome/components/output/float_output.h" -#include "esphome/components/modbus_controller/modbus_controller.h" -#include "esphome/core/component.h" - -namespace esphome { -namespace modbus_controller { - -using value_to_data_t = std::function(float); - -class ModbusOutput : public output::FloatOutput, public Component, public SensorItem { - public: - ModbusOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type) - : output::FloatOutput(), Component() { - this->register_type = ModbusRegisterType::HOLDING; - this->start_address = start_address; - this->offset = offset; - this->bitmask = bitmask; - this->sensor_value_type = value_type; - this->skip_updates = 0; - this->start_address += offset; - this->offset = 0; - } - void setup() override; - void dump_config() override; - - void set_parent(ModbusController *parent) { this->parent_ = parent; } - void set_write_multiply(float factor) { multiply_by_ = factor; } - // Do nothing - void parse_and_publish(const std::vector &data) override{}; - - using write_transform_func_t = std::function(ModbusOutput *, float, std::vector &)>; - void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } - - protected: - void write_state(float value) override; - optional write_transform_func_{nullopt}; - - ModbusController *parent_; - float multiply_by_{1.0}; -}; - -} // namespace modbus_controller -} // namespace esphome +#pragma once + +#include "esphome/components/output/float_output.h" +#include "esphome/components/modbus_controller/modbus_controller.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace modbus_controller { + +using value_to_data_t = std::function(float); + +class ModbusOutput : public output::FloatOutput, public Component, public SensorItem { + public: + ModbusOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type) + : output::FloatOutput(), Component() { + this->register_type = ModbusRegisterType::HOLDING; + this->start_address = start_address; + this->offset = offset; + this->bitmask = bitmask; + this->sensor_value_type = value_type; + this->skip_updates = 0; + this->start_address += offset; + this->offset = 0; + } + void setup() override; + void dump_config() override; + + void set_parent(ModbusController *parent) { this->parent_ = parent; } + void set_write_multiply(float factor) { multiply_by_ = factor; } + // Do nothing + void parse_and_publish(const std::vector &data) override{}; + + using write_transform_func_t = std::function(ModbusOutput *, float, std::vector &)>; + void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } + + protected: + void write_state(float value) override; + optional write_transform_func_{nullopt}; + + ModbusController *parent_; + float multiply_by_{1.0}; +}; + +} // namespace modbus_controller +} // namespace esphome From c89018a4319ada1a5fe96e6f368e194721cb956c Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Thu, 30 Sep 2021 18:08:15 +0200 Subject: [PATCH 1476/1841] Option to ignore CRC for EFuse MAC address (#2399) * Accept changes as proposed by black. * Added test and implemented optional correctly. * Disable PHY RF full calibration (because it calls the breaking MAC retrieval function). * Disable CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE instead of enable, dummy! * Rename CONF_IGNORE_EFUSE_MAC_CRC to CONF_ESP32_IGNORE_EFUSE_MAC_CRC. * Removed unused import. * Fix ordering of constants. * Moved all MAC address logic to core helpers. * Use pretty MAC address for the log. * Use standard MAC formatter function for debug component. * Fix clang-formatting. * Fix clang-formatting. * Brought wording of comments in line with other function-describing comments. * Processed code review by @OttoWinter * Add USE_ESP32_IGNORE_EFUSE_MAC_CRC to defines.h Co-authored-by: Maurice Makaay --- esphome/components/debug/debug_component.cpp | 5 +-- esphome/components/esp32/__init__.py | 13 +++++++ .../wifi/wifi_component_esp_idf.cpp | 6 ++++ esphome/const.py | 2 ++ esphome/core/defines.h | 1 + esphome/core/helpers.cpp | 34 ++++++++++++++----- esphome/core/helpers.h | 12 ++++++- tests/test5.yaml | 2 ++ 8 files changed, 61 insertions(+), 14 deletions(-) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index 7fd8956148..b856733121 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -104,10 +104,7 @@ void DebugComponent::dump_config() { ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version()); - uint64_t chip_mac = 0LL; - esp_efuse_mac_get_default((uint8_t *) (&chip_mac)); - std::string mac = uint64_to_string(chip_mac); - ESP_LOGD(TAG, "EFuse MAC: %s", mac.c_str()); + ESP_LOGD(TAG, "EFuse MAC: %s", get_mac_address_pretty().c_str()); const char *reset_reason; switch (rtc_get_reset_reason(0)) { diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 719b7b5f31..1316c7ccbe 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -10,6 +10,8 @@ from esphome.const import ( CONF_TYPE, CONF_VARIANT, CONF_VERSION, + CONF_ADVANCED, + CONF_IGNORE_EFUSE_MAC_CRC, KEY_CORE, KEY_FRAMEWORK_VERSION, KEY_TARGET_FRAMEWORK, @@ -230,6 +232,11 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All( cv.string_strict: cv.string_strict }, cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, + cv.Optional(CONF_ADVANCED, default={}): cv.Schema( + { + cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean, + } + ), } ), _esp_idf_check_versions, @@ -295,6 +302,12 @@ async def to_code(config): for name, value in conf[CONF_SDKCONFIG_OPTIONS].items(): add_idf_sdkconfig_option(name, RawSdkconfigValue(value)) + if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_MAC_CRC]: + cg.add_define("USE_ESP32_IGNORE_EFUSE_MAC_CRC") + add_idf_sdkconfig_option( + "CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE", False + ) + elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: cg.add_platformio_option( "platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}" diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 9ec3f80014..7f71b7078c 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -110,6 +110,12 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi } void WiFiComponent::wifi_pre_setup_() { +#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC + uint8_t mac[6]; + get_mac_address_raw(mac); + set_mac_address(mac); + ESP_LOGV(TAG, "Use EFuse MAC without checking CRC: %s", get_mac_address_pretty().c_str()); +#endif esp_err_t err = esp_netif_init(); if (err != ERR_OK) { ESP_LOGE(TAG, "esp_netif_init failed: %s", esp_err_to_name(err)); diff --git a/esphome/const.py b/esphome/const.py index 054f032da4..265576bfbe 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -42,6 +42,7 @@ CONF_ACTION_ID = "action_id" CONF_ACTIVE_POWER = "active_power" CONF_ADDRESS = "address" CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id" +CONF_ADVANCED = "advanced" CONF_ALPHA = "alpha" CONF_ALTITUDE = "altitude" CONF_AND = "and" @@ -281,6 +282,7 @@ CONF_IDLE_ACTION = "idle_action" CONF_IDLE_LEVEL = "idle_level" CONF_IDLE_TIME = "idle_time" CONF_IF = "if" +CONF_IGNORE_EFUSE_MAC_CRC = "ignore_efuse_mac_crc" CONF_IIR_FILTER = "iir_filter" CONF_ILLUMINANCE = "illuminance" CONF_IMPEDANCE = "impedance" diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 1c3b17d071..7c2261920a 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -50,6 +50,7 @@ #ifdef USE_ESP32 #define USE_ESP32_BLE_SERVER #define USE_ESP32_CAMERA +#define USE_ESP32_IGNORE_EFUSE_MAC_CRC #define USE_IMPROV #define USE_SOCKET_IMPL_BSD_SOCKETS diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 731ee6c8f5..780df3ca6d 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -1,4 +1,5 @@ #include "esphome/core/helpers.h" +#include "esphome/core/defines.h" #include #include #include @@ -14,6 +15,10 @@ #include #include #endif +#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC +#include "esp_efuse.h" +#include "esp_efuse_table.h" +#endif #include "esphome/core/log.h" #include "esphome/core/hal.h" @@ -22,15 +27,27 @@ namespace esphome { static const char *const TAG = "helpers"; -std::string get_mac_address() { - char tmp[20]; - uint8_t mac[6]; +void get_mac_address_raw(uint8_t *mac) { #ifdef USE_ESP32 +#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC + // On some devices, the MAC address that is burnt into EFuse does not + // match the CRC that goes along with it. For those devices, this + // work-around reads and uses the MAC address as-is from EFuse, + // without doing the CRC check. + esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, 48); +#else esp_efuse_mac_get_default(mac); #endif +#endif #ifdef USE_ESP8266 WiFi.macAddress(mac); #endif +} + +std::string get_mac_address() { + char tmp[20]; + uint8_t mac[6]; + get_mac_address_raw(mac); sprintf(tmp, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); return std::string(tmp); } @@ -38,16 +55,15 @@ std::string get_mac_address() { std::string get_mac_address_pretty() { char tmp[20]; uint8_t mac[6]; -#ifdef USE_ESP32 - esp_efuse_mac_get_default(mac); -#endif -#ifdef USE_ESP8266 - WiFi.macAddress(mac); -#endif + get_mac_address_raw(mac); sprintf(tmp, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); return std::string(tmp); } +#ifdef USE_ESP32 +void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); } +#endif + std::string generate_hostname(const std::string &base) { return base + std::string("-") + get_mac_address(); } uint32_t random_uint32() { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 24b55eade0..61cc9a9e4a 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -26,11 +26,21 @@ namespace esphome { /// The characters that are allowed in a hostname. extern const char *const HOSTNAME_CHARACTER_ALLOWLIST; -/// Gets the MAC address as a string, this can be used as way to identify this ESP. +/// Read the raw MAC address into the provided byte array (6 bytes). +void get_mac_address_raw(uint8_t *mac); + +/// Get the MAC address as a string, using lower case hex notation. +/// This can be used as way to identify this ESP. std::string get_mac_address(); +/// Get the MAC address as a string, using colon-separated upper case hex notation. std::string get_mac_address_pretty(); +#ifdef USE_ESP32 +/// Set the MAC address to use from the provided byte array (6 bytes). +void set_mac_address(uint8_t *mac); +#endif + std::string to_string(const std::string &val); std::string to_string(int val); std::string to_string(long val); // NOLINT diff --git a/tests/test5.yaml b/tests/test5.yaml index b22b19550e..aca8434fbf 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -9,6 +9,8 @@ esp32: board: nodemcu-32s framework: type: esp-idf + advanced: + ignore_efuse_mac_crc: true wifi: networks: From 5a2984d03a17cb1843d505b88205b8042f0a05ab Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Fri, 1 Oct 2021 10:11:07 +0200 Subject: [PATCH 1477/1841] Fix attach_interrupt(...) for esp-idf framework (#2416) Co-authored-by: Maurice Makaay --- esphome/components/esp32/gpio_idf.cpp | 71 +++++++++++++++++++++++++++ esphome/components/esp32/gpio_idf.h | 68 +++---------------------- 2 files changed, 77 insertions(+), 62 deletions(-) diff --git a/esphome/components/esp32/gpio_idf.cpp b/esphome/components/esp32/gpio_idf.cpp index 478b28a89a..d1853e1f8b 100644 --- a/esphome/components/esp32/gpio_idf.cpp +++ b/esphome/components/esp32/gpio_idf.cpp @@ -22,6 +22,77 @@ ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const { return ISRInternalGPIOPin((void *) arg); } +void IDFInternalGPIOPin::setup() { + pin_mode(flags_); + gpio_set_drive_capability(pin_, drive_strength_); +} + +void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) { + gpio_config_t conf{}; + conf.pin_bit_mask = 1ULL << static_cast(pin_); + conf.mode = flags_to_mode(flags); + conf.pull_up_en = flags & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE; + conf.pull_down_en = flags & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE; + conf.intr_type = GPIO_INTR_DISABLE; + gpio_config(&conf); +} + +bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; } + +void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); } + +gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) { + flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)); + if (flags == gpio::FLAG_NONE) { + return GPIO_MODE_DISABLE; + } else if (flags == gpio::FLAG_INPUT) { + return GPIO_MODE_INPUT; + } else if (flags == gpio::FLAG_OUTPUT) { + return GPIO_MODE_OUTPUT; + } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + return GPIO_MODE_OUTPUT_OD; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + return GPIO_MODE_INPUT_OUTPUT_OD; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT)) { + return GPIO_MODE_INPUT_OUTPUT; + } else { + // unsupported + return GPIO_MODE_DISABLE; + } +} + +void IDFInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { + gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE; + switch (type) { + case gpio::INTERRUPT_RISING_EDGE: + idf_type = inverted_ ? GPIO_INTR_NEGEDGE : GPIO_INTR_POSEDGE; + break; + case gpio::INTERRUPT_FALLING_EDGE: + idf_type = inverted_ ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE; + break; + case gpio::INTERRUPT_ANY_EDGE: + idf_type = GPIO_INTR_ANYEDGE; + break; + case gpio::INTERRUPT_LOW_LEVEL: + idf_type = inverted_ ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL; + break; + case gpio::INTERRUPT_HIGH_LEVEL: + idf_type = inverted_ ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL; + break; + } + gpio_set_intr_type(pin_, idf_type); + gpio_intr_enable(pin_); + if (!isr_service_installed) { + auto res = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3); + if (res != ESP_OK) { + ESP_LOGE(TAG, "attach_interrupt(): call to gpio_install_isr_service() failed, error code: %d", res); + return; + } + isr_service_installed = true; + } + gpio_isr_handler_add(pin_, func, arg); +} + std::string IDFInternalGPIOPin::dump_summary() const { char buffer[32]; snprintf(buffer, sizeof(buffer), "GPIO%u", static_cast(pin_)); diff --git a/esphome/components/esp32/gpio_idf.h b/esphome/components/esp32/gpio_idf.h index 448151cd0f..a99571cc46 100644 --- a/esphome/components/esp32/gpio_idf.h +++ b/esphome/components/esp32/gpio_idf.h @@ -13,22 +13,10 @@ class IDFInternalGPIOPin : public InternalGPIOPin { void set_inverted(bool inverted) { inverted_ = inverted; } void set_drive_strength(gpio_drive_cap_t drive_strength) { drive_strength_ = drive_strength; } void set_flags(gpio::Flags flags) { flags_ = flags; } - - void setup() override { - pin_mode(flags_); - gpio_set_drive_capability(pin_, drive_strength_); - } - void pin_mode(gpio::Flags flags) override { - gpio_config_t conf{}; - conf.pin_bit_mask = 1ULL << static_cast(pin_); - conf.mode = flags_to_mode(flags); - conf.pull_up_en = flags & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE; - conf.pull_down_en = flags & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE; - conf.intr_type = GPIO_INTR_DISABLE; - gpio_config(&conf); - } - bool digital_read() override { return bool(gpio_get_level(pin_)) != inverted_; } - void digital_write(bool value) override { gpio_set_level(pin_, value != inverted_ ? 1 : 0); } + void setup() override; + void pin_mode(gpio::Flags flags) override; + bool digital_read() override; + void digital_write(bool value) override; std::string dump_summary() const override; void detach_interrupt() const override { gpio_intr_disable(pin_); } ISRInternalGPIOPin to_isr() const override; @@ -36,52 +24,8 @@ class IDFInternalGPIOPin : public InternalGPIOPin { bool is_inverted() const override { return inverted_; } protected: - static gpio_mode_t flags_to_mode(gpio::Flags flags) { - flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)); - if (flags == gpio::FLAG_NONE) { - return GPIO_MODE_DISABLE; - } else if (flags == gpio::FLAG_INPUT) { - return GPIO_MODE_INPUT; - } else if (flags == gpio::FLAG_OUTPUT) { - return GPIO_MODE_OUTPUT; - } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { - return GPIO_MODE_OUTPUT_OD; - } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { - return GPIO_MODE_INPUT_OUTPUT_OD; - } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT)) { - return GPIO_MODE_INPUT_OUTPUT; - } else { - // unsupported - return GPIO_MODE_DISABLE; - } - } - void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override { - gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE; - switch (type) { - case gpio::INTERRUPT_RISING_EDGE: - idf_type = inverted_ ? GPIO_INTR_NEGEDGE : GPIO_INTR_POSEDGE; - break; - case gpio::INTERRUPT_FALLING_EDGE: - idf_type = inverted_ ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE; - break; - case gpio::INTERRUPT_ANY_EDGE: - idf_type = GPIO_INTR_ANYEDGE; - break; - case gpio::INTERRUPT_LOW_LEVEL: - idf_type = inverted_ ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL; - break; - case gpio::INTERRUPT_HIGH_LEVEL: - idf_type = inverted_ ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL; - break; - } - gpio_set_intr_type(pin_, idf_type); - gpio_intr_enable(pin_); - if (!isr_service_installed) { - gpio_install_isr_service(ESP_INTR_FLAG_LEVEL5); - isr_service_installed = true; - } - gpio_isr_handler_add(pin_, func, arg); - } + static gpio_mode_t flags_to_mode(gpio::Flags flags); + void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; gpio_num_t pin_; bool inverted_; From d0dfc94a61cd6d3758e4284156d5ce707a7803b2 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Fri, 1 Oct 2021 12:53:37 +0200 Subject: [PATCH 1478/1841] Fix I2C recovery on Arduino (#2412) Co-authored-by: Maurice Makaay --- esphome/components/i2c/i2c_bus_arduino.cpp | 130 +++++++++++++++++---- esphome/components/i2c/i2c_bus_arduino.h | 7 ++ 2 files changed, 117 insertions(+), 20 deletions(-) diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index 87dbcb66d8..539091ed9c 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -32,6 +32,17 @@ void ArduinoI2CBus::dump_config() { ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_); ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_); ESP_LOGCONFIG(TAG, " Frequency: %u Hz", this->frequency_); + switch (this->recovery_result_) { + case RECOVERY_COMPLETED: + ESP_LOGCONFIG(TAG, " Recovery: bus successfully recovered"); + break; + case RECOVERY_FAILED_SCL_LOW: + ESP_LOGCONFIG(TAG, " Recovery: failed, SCL is held low on the bus"); + break; + case RECOVERY_FAILED_SDA_LOW: + ESP_LOGCONFIG(TAG, " Recovery: failed, SDA is held low on the bus"); + break; + } if (this->scan_) { ESP_LOGI(TAG, "Scanning i2c bus for active devices..."); uint8_t found = 0; @@ -92,31 +103,110 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn return ERROR_UNKNOWN; } +/// Perform I2C bus recovery, see: +/// https://www.nxp.com/docs/en/user-guide/UM10204.pdf +/// https://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf void ArduinoI2CBus::recover_() { - // Perform I2C bus recovery, see - // https://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf - // or see the linux kernel implementation, e.g. - // https://elixir.bootlin.com/linux/v5.14.6/source/drivers/i2c/i2c-core-base.c#L200 + ESP_LOGI(TAG, "Performing I2C bus recovery"); - // try to get about 100kHz toggle frequency - const auto half_period_usec = 1000000 / 100000 / 2; - const auto recover_scl_periods = 9; - - // configure scl as output - pinMode(scl_pin_, OUTPUT); // NOLINT - - // set scl high - digitalWrite(scl_pin_, 1); // NOLINT - - // in total generate 9 falling-rising edges - for (auto i = 0; i < recover_scl_periods; i++) { - delayMicroseconds(half_period_usec); - digitalWrite(scl_pin_, 0); // NOLINT - delayMicroseconds(half_period_usec); - digitalWrite(scl_pin_, 1); // NOLINT + // Activate the pull up resistor on the SCL pin. This should make the + // signal on the line HIGH. If SCL is pulled low on the I2C bus however, + // then some device is interfering with the SCL line. In that case, + // the I2C bus cannot be recovered. + pinMode(scl_pin_, INPUT_PULLUP); // NOLINT + if (digitalRead(scl_pin_) == LOW) { // NOLINT + ESP_LOGE(TAG, "Recovery failed: SCL is held LOW on the I2C bus"); + recovery_result_ = RECOVERY_FAILED_SCL_LOW; + return; } + // From the specification: + // "If the data line (SDA) is stuck LOW, send nine clock pulses. The + // device that held the bus LOW should release it sometime within + // those nine clocks." + // We don't really have to detect if SDA is stuck low. We'll simply send + // nine clock pulses here, just in case SDA is stuck. + + // Use a 100kHz toggle frequency (i.e. the maximum frequency for I2C + // running in standard-mode). The resulting frequency will be lower, + // because of the additional function calls that are done, but that + // is no problem. + const auto half_period_usec = 1000000 / 100000 / 2; + + // Make sure that switching to mode OUTPUT will make SCL low, just in + // case other code has setup the pin to output a HIGH signal. + digitalWrite(scl_pin_, LOW); // NOLINT + + // Activate the pull up resistor for SDA, so after the clock pulse cycle + // we can verify if SDA is pulled high. Also make sure that switching to + // mode OUTPUT will make SDA low. + pinMode(sda_pin_, INPUT_PULLUP); // NOLINT + digitalWrite(sda_pin_, LOW); // NOLINT + + ESP_LOGI(TAG, "Sending 9 clock pulses to drain any stuck device output"); delayMicroseconds(half_period_usec); + for (auto i = 0; i < 9; i++) { + // Release pull up resistor and switch to output to make the signal LOW. + pinMode(scl_pin_, INPUT); // NOLINT + pinMode(scl_pin_, OUTPUT); // NOLINT + delayMicroseconds(half_period_usec); + + // Release output and activate pull up resistor to make the signal HIGH. + pinMode(scl_pin_, INPUT); // NOLINT + pinMode(scl_pin_, INPUT_PULLUP); // NOLINT + delayMicroseconds(half_period_usec); + + // When SCL is kept LOW at this point, we might be looking at a device + // that applies clock stretching. Wait for the release of the SCL line, + // but not forever. There is no specification for the maximum allowed + // time. We'll stick to 500ms here. + auto wait = 20; + while (wait-- && digitalRead(scl_pin_) == LOW) { // NOLINT + delay(25); + } + if (digitalRead(scl_pin_) == LOW) { // NOLINT + ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle"); + recovery_result_ = RECOVERY_FAILED_SCL_LOW; + return; + } + } + + // By now, any stuck device ought to have sent all remaining bits of its + // transation, meaning that it should have freed up the SDA line, resulting + // in SDA being pulled up. + if (digitalRead(sda_pin_) == LOW) { // NOLINT + ESP_LOGE(TAG, "Recovery failed: SDA is held LOW after clock pulse cycle"); + recovery_result_ = RECOVERY_FAILED_SDA_LOW; + return; + } + + // From the specification: + // "I2C-bus compatible devices must reset their bus logic on receipt of + // a START or repeated START condition such that they all anticipate + // the sending of a target address, even if these START conditions are + // not positioned according to the proper format." + // While the 9 clock pulses from above might have drained all bits of a + // single byte within a transaction, a device might have more bytes to + // transmit. So here we'll generate a START condition to snap the device + // out of this state. + // SCL and SDA are already high at this point, so we can generate a START + // condition by making the SDA signal LOW. + ESP_LOGI(TAG, "Generate START condition to reset bus logic of I2C devices"); + pinMode(sda_pin_, INPUT); // NOLINT + pinMode(sda_pin_, OUTPUT); // NOLINT + delayMicroseconds(half_period_usec); + + // From the specification: + // "A START condition immediately followed by a STOP condition (void + // message) is an illegal format. Many devices however are designed to + // operate properly under this condition." + // Finally, we'll bring the I2C bus into a starting state by generating + // a STOP condition. + ESP_LOGI(TAG, "Generate STOP condition to finalize recovery"); + pinMode(sda_pin_, INPUT); // NOLINT + pinMode(sda_pin_, INPUT_PULLUP); // NOLINT + + recovery_result_ = RECOVERY_COMPLETED; } } // namespace i2c } // namespace esphome diff --git a/esphome/components/i2c/i2c_bus_arduino.h b/esphome/components/i2c/i2c_bus_arduino.h index 42589dcfb7..82f043ef7d 100644 --- a/esphome/components/i2c/i2c_bus_arduino.h +++ b/esphome/components/i2c/i2c_bus_arduino.h @@ -9,6 +9,12 @@ namespace esphome { namespace i2c { +enum RecoveryCode { + RECOVERY_FAILED_SCL_LOW, + RECOVERY_FAILED_SDA_LOW, + RECOVERY_COMPLETED, +}; + class ArduinoI2CBus : public I2CBus, public Component { public: void setup() override; @@ -24,6 +30,7 @@ class ArduinoI2CBus : public I2CBus, public Component { private: void recover_(); + RecoveryCode recovery_result_; protected: TwoWire *wire_; From 15ab8918af0fbc0f7f6eb2cf09456f2b8ce9c640 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Oct 2021 15:20:12 +0200 Subject: [PATCH 1479/1841] Bump aioesphomeapi from 9.1.1 to 9.1.2 (#2426) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7ee22f0415..5ff9f7e282 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.0 esptool==3.1 click==8.0.1 esphome-dashboard==20210927.0 -aioesphomeapi==9.1.1 +aioesphomeapi==9.1.2 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 932e0469f7a63c3a20326f1e911620744a9c7c11 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sat, 2 Oct 2021 16:02:01 +0200 Subject: [PATCH 1480/1841] Fix ESP32 esp-idf OTA updates (#2424) * WIP on separating out the OTA backends. * Split off the individual OTA backends. * Cleanup the three backends, split into .h and .cpp. * After successfull flashing, activate the new boot partition. * Fix linting issues. * Minor cleanup Co-authored-by: Maurice Makaay Co-authored-by: Otto winter --- esphome/components/ota/ota_backend.h | 18 +++ .../ota/ota_backend_arduino_esp32.cpp | 46 ++++++ .../ota/ota_backend_arduino_esp32.h | 22 +++ .../ota/ota_backend_arduino_esp8266.cpp | 58 ++++++++ .../ota/ota_backend_arduino_esp8266.h | 25 ++++ .../components/ota/ota_backend_esp_idf.cpp | 72 +++++++++ esphome/components/ota/ota_backend_esp_idf.h | 27 ++++ esphome/components/ota/ota_component.cpp | 139 ++---------------- 8 files changed, 279 insertions(+), 128 deletions(-) create mode 100644 esphome/components/ota/ota_backend.h create mode 100644 esphome/components/ota/ota_backend_arduino_esp32.cpp create mode 100644 esphome/components/ota/ota_backend_arduino_esp32.h create mode 100644 esphome/components/ota/ota_backend_arduino_esp8266.cpp create mode 100644 esphome/components/ota/ota_backend_arduino_esp8266.h create mode 100644 esphome/components/ota/ota_backend_esp_idf.cpp create mode 100644 esphome/components/ota/ota_backend_esp_idf.h diff --git a/esphome/components/ota/ota_backend.h b/esphome/components/ota/ota_backend.h new file mode 100644 index 0000000000..c253e009c6 --- /dev/null +++ b/esphome/components/ota/ota_backend.h @@ -0,0 +1,18 @@ +#pragma once +#include "ota_component.h" + +namespace esphome { +namespace ota { + +class OTABackend { + public: + virtual ~OTABackend() = default; + virtual OTAResponseTypes begin(size_t image_size) = 0; + virtual void set_update_md5(const char *md5) = 0; + virtual OTAResponseTypes write(uint8_t *data, size_t len) = 0; + virtual OTAResponseTypes end() = 0; + virtual void abort() = 0; +}; + +} // namespace ota +} // namespace esphome diff --git a/esphome/components/ota/ota_backend_arduino_esp32.cpp b/esphome/components/ota/ota_backend_arduino_esp32.cpp new file mode 100644 index 0000000000..4759737dbd --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_esp32.cpp @@ -0,0 +1,46 @@ +#include "esphome/core/defines.h" +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +#include "ota_backend_arduino_esp32.h" +#include "ota_component.h" +#include "ota_backend.h" + +#include + +namespace esphome { +namespace ota { + +OTAResponseTypes ArduinoESP32OTABackend::begin(size_t image_size) { + bool ret = Update.begin(image_size, U_FLASH); + if (ret) { + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); + if (error == UPDATE_ERROR_SIZE) + return OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; + return OTA_RESPONSE_ERROR_UNKNOWN; +} + +void ArduinoESP32OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } + +OTAResponseTypes ArduinoESP32OTABackend::write(uint8_t *data, size_t len) { + size_t written = Update.write(data, len); + if (written != len) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_OK; +} + +OTAResponseTypes ArduinoESP32OTABackend::end() { + if (!Update.end()) + return OTA_RESPONSE_ERROR_UPDATE_END; + return OTA_RESPONSE_OK; +} + +void ArduinoESP32OTABackend::abort() { Update.abort(); } + +} // namespace ota +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/ota/ota_backend_arduino_esp32.h b/esphome/components/ota/ota_backend_arduino_esp32.h new file mode 100644 index 0000000000..8343bdf94f --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_esp32.h @@ -0,0 +1,22 @@ +#pragma once +#include "esphome/core/defines.h" +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +#include "ota_component.h" +#include "ota_backend.h" + +namespace esphome { +namespace ota { + +class ArduinoESP32OTABackend : public OTABackend { + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; +}; + +} // namespace ota +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.cpp b/esphome/components/ota/ota_backend_arduino_esp8266.cpp new file mode 100644 index 0000000000..8e8a4f36ba --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_esp8266.cpp @@ -0,0 +1,58 @@ +#include "esphome/core/defines.h" +#ifdef USE_ARDUINO +#ifdef USE_ESP8266 + +#include "ota_backend_arduino_esp8266.h" +#include "ota_component.h" +#include "ota_backend.h" +#include "esphome/components/esp8266/preferences.h" + +#include + +namespace esphome { +namespace ota { + +OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) { + bool ret = Update.begin(image_size, U_FLASH); + if (ret) { + esp8266::preferences_prevent_write(true); + } + + uint8_t error = Update.getError(); + if (error == UPDATE_ERROR_BOOTSTRAP) + return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; + if (error == UPDATE_ERROR_NEW_FLASH_CONFIG) + return OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG; + if (error == UPDATE_ERROR_FLASH_CONFIG) + return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; + if (error == UPDATE_ERROR_SPACE) + return OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE; + return OTA_RESPONSE_ERROR_UNKNOWN; +} + +void ArduinoESP8266OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } + +OTAResponseTypes ArduinoESP8266OTABackend::write(uint8_t *data, size_t len) { + size_t written = Update.write(data, len); + if (written != len) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_OK; +} + +OTAResponseTypes ArduinoESP8266OTABackend::end() { + if (!Update.end()) + return OTA_RESPONSE_ERROR_UPDATE_END; + return OTA_RESPONSE_OK; +} + +void ArduinoESP8266OTABackend::abort() { + Update.end(); + esp8266::preferences_prevent_write(false); +} + +} // namespace ota +} // namespace esphome + +#endif +#endif diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.h b/esphome/components/ota/ota_backend_arduino_esp8266.h new file mode 100644 index 0000000000..d1195af911 --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_esp8266.h @@ -0,0 +1,25 @@ +#pragma once +#include "esphome/core/defines.h" +#ifdef USE_ARDUINO +#ifdef USE_ESP8266 + +#include "ota_component.h" +#include "ota_backend.h" + +namespace esphome { +namespace ota { + +class ArduinoESP8266OTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; +}; + +} // namespace ota +} // namespace esphome + +#endif +#endif diff --git a/esphome/components/ota/ota_backend_esp_idf.cpp b/esphome/components/ota/ota_backend_esp_idf.cpp new file mode 100644 index 0000000000..4eb17d82f1 --- /dev/null +++ b/esphome/components/ota/ota_backend_esp_idf.cpp @@ -0,0 +1,72 @@ +#include "esphome/core/defines.h" +#ifdef USE_ESP_IDF + +#include "ota_backend_esp_idf.h" +#include "ota_component.h" +#include + +namespace esphome { +namespace ota { + +OTAResponseTypes IDFOTABackend::begin(size_t image_size) { + this->partition_ = esp_ota_get_next_update_partition(nullptr); + if (this->partition_ == nullptr) { + return OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION; + } + esp_err_t err = esp_ota_begin(this->partition_, image_size, &this->update_handle_); + if (err != ESP_OK) { + esp_ota_abort(this->update_handle_); + this->update_handle_ = 0; + if (err == ESP_ERR_INVALID_SIZE) { + return OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; + } else if (err == ESP_ERR_FLASH_OP_TIMEOUT || err == ESP_ERR_FLASH_OP_FAIL) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_ERROR_UNKNOWN; + } + return OTA_RESPONSE_OK; +} + +void IDFOTABackend::set_update_md5(const char *md5) { + // pass +} + +OTAResponseTypes IDFOTABackend::write(uint8_t *data, size_t len) { + esp_err_t err = esp_ota_write(this->update_handle_, data, len); + if (err != ESP_OK) { + if (err == ESP_ERR_OTA_VALIDATE_FAILED) { + return OTA_RESPONSE_ERROR_MAGIC; + } else if (err == ESP_ERR_FLASH_OP_TIMEOUT || err == ESP_ERR_FLASH_OP_FAIL) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_ERROR_UNKNOWN; + } + return OTA_RESPONSE_OK; +} + +OTAResponseTypes IDFOTABackend::end() { + esp_err_t err = esp_ota_end(this->update_handle_); + this->update_handle_ = 0; + if (err == ESP_OK) { + err = esp_ota_set_boot_partition(this->partition_); + if (err == ESP_OK) { + return OTA_RESPONSE_OK; + } + } + if (err == ESP_ERR_OTA_VALIDATE_FAILED) { + return OTA_RESPONSE_ERROR_UPDATE_END; + } + if (err == ESP_ERR_FLASH_OP_TIMEOUT || err == ESP_ERR_FLASH_OP_FAIL) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_ERROR_UNKNOWN; +} + +void IDFOTABackend::abort() { + esp_ota_abort(this->update_handle_); + this->update_handle_ = 0; +} + +} // namespace ota +} // namespace esphome +#endif diff --git a/esphome/components/ota/ota_backend_esp_idf.h b/esphome/components/ota/ota_backend_esp_idf.h new file mode 100644 index 0000000000..d6e2e2742a --- /dev/null +++ b/esphome/components/ota/ota_backend_esp_idf.h @@ -0,0 +1,27 @@ +#pragma once +#include "esphome/core/defines.h" +#ifdef USE_ESP_IDF + +#include "ota_component.h" +#include "ota_backend.h" +#include + +namespace esphome { +namespace ota { + +class IDFOTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; + + private: + esp_ota_handle_t update_handle_{0}; + const esp_partition_t *partition_; +}; + +} // namespace ota +} // namespace esphome +#endif diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 0c13efa135..e1188a4d31 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -1,4 +1,8 @@ #include "ota_component.h" +#include "ota_backend.h" +#include "ota_backend_arduino_esp32.h" +#include "ota_backend_arduino_esp8266.h" +#include "ota_backend_esp_idf.h" #include "esphome/core/log.h" #include "esphome/core/application.h" @@ -9,23 +13,8 @@ #include #include -#ifdef USE_ARDUINO #ifdef USE_OTA_PASSWORD #include -#endif // USE_OTA_PASSWORD - -#ifdef USE_ESP32 -#include -#endif // USE_ESP32 -#endif // USE_ARDUINO - -#ifdef USE_ESP8266 -#include -#include "esphome/components/esp8266/preferences.h" -#endif // USE_ESP8266 - -#ifdef USE_ESP_IDF -#include #endif namespace esphome { @@ -35,125 +24,19 @@ static const char *const TAG = "ota"; static const uint8_t OTA_VERSION_1_0 = 1; -class OTABackend { - public: - virtual ~OTABackend() = default; - virtual OTAResponseTypes begin(size_t image_size) = 0; - virtual void set_update_md5(const char *md5) = 0; - virtual OTAResponseTypes write(uint8_t *data, size_t len) = 0; - virtual OTAResponseTypes end() = 0; - virtual void abort() = 0; -}; - +std::unique_ptr make_ota_backend() { #ifdef USE_ARDUINO -class ArduinoOTABackend : public OTABackend { - public: - OTAResponseTypes begin(size_t image_size) override { - bool ret = Update.begin(image_size, U_FLASH); - if (ret) { #ifdef USE_ESP8266 - esp8266::preferences_prevent_write(true); -#endif - return OTA_RESPONSE_OK; - } - - uint8_t error = Update.getError(); -#ifdef USE_ESP8266 - if (error == UPDATE_ERROR_BOOTSTRAP) - return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; - if (error == UPDATE_ERROR_NEW_FLASH_CONFIG) - return OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG; - if (error == UPDATE_ERROR_FLASH_CONFIG) - return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; - if (error == UPDATE_ERROR_SPACE) - return OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE; -#endif + return make_unique(); +#endif // USE_ESP8266 #ifdef USE_ESP32 - if (error == UPDATE_ERROR_SIZE) - return OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; -#endif - return OTA_RESPONSE_ERROR_UNKNOWN; - } - void set_update_md5(const char *md5) override { Update.setMD5(md5); } - OTAResponseTypes write(uint8_t *data, size_t len) override { - size_t written = Update.write(data, len); - if (written != len) { - return OTA_RESPONSE_ERROR_WRITING_FLASH; - } - return OTA_RESPONSE_OK; - } - OTAResponseTypes end() override { - if (!Update.end()) - return OTA_RESPONSE_ERROR_UPDATE_END; - return OTA_RESPONSE_OK; - } - void abort() override { -#ifdef USE_ESP32 - Update.abort(); -#endif - -#ifdef USE_ESP8266 - Update.end(); - esp8266::preferences_prevent_write(false); -#endif - } -}; -std::unique_ptr make_ota_backend() { return make_unique(); } + return make_unique(); +#endif // USE_ESP32 #endif // USE_ARDUINO - #ifdef USE_ESP_IDF -class IDFOTABackend : public OTABackend { - public: - esp_ota_handle_t update_handle = 0; - - OTAResponseTypes begin(size_t image_size) override { - const esp_partition_t *update_partition = esp_ota_get_next_update_partition(nullptr); - if (update_partition == nullptr) { - return OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION; - } - esp_err_t err = esp_ota_begin(update_partition, image_size, &update_handle); - if (err != ESP_OK) { - esp_ota_abort(update_handle); - update_handle = 0; - if (err == ESP_ERR_INVALID_SIZE) { - return OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; - } else if (err == ESP_ERR_FLASH_OP_TIMEOUT || err == ESP_ERR_FLASH_OP_FAIL) { - return OTA_RESPONSE_ERROR_WRITING_FLASH; - } - return OTA_RESPONSE_ERROR_UNKNOWN; - } - return OTA_RESPONSE_OK; - } - void set_update_md5(const char *md5) override { - // pass - } - OTAResponseTypes write(uint8_t *data, size_t len) override { - esp_err_t err = esp_ota_write(update_handle, data, len); - if (err != ESP_OK) { - if (err == ESP_ERR_OTA_VALIDATE_FAILED) { - return OTA_RESPONSE_ERROR_MAGIC; - } else if (err == ESP_ERR_FLASH_OP_TIMEOUT || err == ESP_ERR_FLASH_OP_FAIL) { - return OTA_RESPONSE_ERROR_WRITING_FLASH; - } - return OTA_RESPONSE_ERROR_UNKNOWN; - } - return OTA_RESPONSE_OK; - } - OTAResponseTypes end() override { - esp_err_t err = esp_ota_end(update_handle); - update_handle = 0; - if (err != ESP_OK) { - if (err == ESP_ERR_OTA_VALIDATE_FAILED) { - return OTA_RESPONSE_ERROR_UPDATE_END; - } - return OTA_RESPONSE_ERROR_UNKNOWN; - } - return OTA_RESPONSE_OK; - } - void abort() override { esp_ota_abort(update_handle); } -}; -std::unique_ptr make_ota_backend() { return make_unique(); } + return make_unique(); #endif // USE_ESP_IDF +} void OTAComponent::setup() { server_ = socket::socket(AF_INET, SOCK_STREAM, 0); From a7687c3e17245ca383ce5bcf2951a0ab54afdba1 Mon Sep 17 00:00:00 2001 From: cvwillegen Date: Sun, 3 Oct 2021 13:27:59 +0200 Subject: [PATCH 1481/1841] Add local MAC address to WiFi info (#2428) --- esphome/components/wifi/wifi_component.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 47cd0ef9ad..703afa99bc 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -349,6 +349,7 @@ const LogString *get_signal_bars(int8_t rssi) { void WiFiComponent::print_connect_params_() { bssid_t bssid = wifi_bssid(); + ESP_LOGCONFIG(TAG, " Local MAC: %s", get_mac_address_pretty().c_str()); ESP_LOGCONFIG(TAG, " SSID: " LOG_SECRET("'%s'"), wifi_ssid().c_str()); ESP_LOGCONFIG(TAG, " IP Address: %s", wifi_sta_ip().str().c_str()); ESP_LOGCONFIG(TAG, " BSSID: " LOG_SECRET("%02X:%02X:%02X:%02X:%02X:%02X"), bssid[0], bssid[1], bssid[2], bssid[3], From eaa5200a35ea1b5acec49a5b61356f81c60a1d2f Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sun, 3 Oct 2021 07:10:43 -0500 Subject: [PATCH 1482/1841] Thermostat publish state fix (#2427) --- .../thermostat/thermostat_climate.cpp | 34 ++++++++++++------- .../thermostat/thermostat_climate.h | 8 ++--- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 6193185321..ce15c53bbe 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -18,8 +18,8 @@ void ThermostatClimate::setup() { // add a callback so that whenever the sensor state changes we can take action this->sensor_->add_on_state_callback([this](float state) { this->current_temperature = state; - // required action may have changed, recompute, refresh - this->switch_to_action_(this->compute_action_()); + // required action may have changed, recompute, refresh, we'll publish_state() later + this->switch_to_action_(this->compute_action_(), false); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); // current temperature and possibly action changed, so publish the new state this->publish_state(); @@ -34,8 +34,8 @@ void ThermostatClimate::setup() { this->mode = this->default_mode_; this->change_away_(false); } - // refresh the climate action based on the restored settings - this->switch_to_action_(this->compute_action_()); + // refresh the climate action based on the restored settings, we'll publish_state() later + this->switch_to_action_(this->compute_action_(), false); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); this->setup_complete_ = true; this->publish_state(); @@ -47,11 +47,11 @@ float ThermostatClimate::heat_deadband() { return this->heating_deadband_; } float ThermostatClimate::heat_overrun() { return this->heating_overrun_; } void ThermostatClimate::refresh() { - this->switch_to_mode_(this->mode); - this->switch_to_action_(this->compute_action_()); + this->switch_to_mode_(this->mode, false); + this->switch_to_action_(this->compute_action_(), false); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); - this->switch_to_fan_mode_(this->fan_mode.value()); - this->switch_to_swing_mode_(this->swing_mode); + this->switch_to_fan_mode_(this->fan_mode.value(), false); + this->switch_to_swing_mode_(this->swing_mode, false); this->check_temperature_change_trigger_(); this->publish_state(); } @@ -346,7 +346,7 @@ climate::ClimateAction ThermostatClimate::compute_supplemental_action_() { return target_action; } -void ThermostatClimate::switch_to_action_(climate::ClimateAction action) { +void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool publish_state) { // setup_complete_ helps us ensure an action is called immediately after boot if ((action == this->action) && this->setup_complete_) // already in target mode @@ -358,6 +358,8 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action) { // switching from OFF to IDLE or vice-versa -- this is only a visual difference. // OFF means user manually disabled, IDLE means the temperature is in target range. this->action = action; + if (publish_state) + this->publish_state(); return; } @@ -452,6 +454,8 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action) { ESP_LOGVV(TAG, "Calling FAN_ONLY action with HEATING/COOLING action"); trig_fan->trigger(); } + if (publish_state) + this->publish_state(); } } @@ -509,13 +513,15 @@ void ThermostatClimate::trigger_supplemental_action_() { } } -void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { +void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bool publish_state) { // setup_complete_ helps us ensure an action is called immediately after boot if ((fan_mode == this->prev_fan_mode_) && this->setup_complete_) // already in target mode return; this->fan_mode = fan_mode; + if (publish_state) + this->publish_state(); if (this->fan_mode_ready_()) { Trigger<> *trig = this->fan_mode_auto_trigger_; @@ -574,7 +580,7 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { } } -void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) { +void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode, bool publish_state) { // setup_complete_ helps us ensure an action is called immediately after boot if ((mode == this->prev_mode_) && this->setup_complete_) // already in target mode @@ -615,9 +621,11 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) { this->mode = mode; this->prev_mode_ = mode; this->prev_mode_trigger_ = trig; + if (publish_state) + this->publish_state(); } -void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mode) { +void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mode, bool publish_state) { // setup_complete_ helps us ensure an action is called immediately after boot if ((swing_mode == this->prev_swing_mode_) && this->setup_complete_) // already in target mode @@ -652,6 +660,8 @@ void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mo this->swing_mode = swing_mode; this->prev_swing_mode_ = swing_mode; this->prev_swing_mode_trigger_ = trig; + if (publish_state) + this->publish_state(); } bool ThermostatClimate::idle_action_ready_() { diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 60777e7c81..8d3e926752 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -160,18 +160,18 @@ class ThermostatClimate : public climate::Climate, public Component { climate::ClimateAction compute_supplemental_action_(); /// Switch the climate device to the given climate action. - void switch_to_action_(climate::ClimateAction action); + void switch_to_action_(climate::ClimateAction action, bool publish_state = true); void switch_to_supplemental_action_(climate::ClimateAction action); void trigger_supplemental_action_(); /// Switch the climate device to the given climate fan mode. - void switch_to_fan_mode_(climate::ClimateFanMode fan_mode); + void switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bool publish_state = true); /// Switch the climate device to the given climate mode. - void switch_to_mode_(climate::ClimateMode mode); + void switch_to_mode_(climate::ClimateMode mode, bool publish_state = true); /// Switch the climate device to the given climate swing mode. - void switch_to_swing_mode_(climate::ClimateSwingMode swing_mode); + void switch_to_swing_mode_(climate::ClimateSwingMode swing_mode, bool publish_state = true); /// Check if the temperature change trigger should be called. void check_temperature_change_trigger_(); From 912793eddf70f2ec59e086f22b076c82e183073f Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 3 Oct 2021 14:10:53 +0200 Subject: [PATCH 1483/1841] Convert time to use tzdata (#2425) --- esphome/components/time/__init__.py | 160 ++++++++-------------------- requirements.txt | 4 +- 2 files changed, 49 insertions(+), 115 deletions(-) diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index d548575d72..5c2155d764 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -1,10 +1,8 @@ -import bisect -import datetime import logging -import math -import string +from importlib import resources +from typing import Optional +from datetime import timezone -import pytz import tzlocal import esphome.codegen as cg @@ -44,111 +42,47 @@ ESPTime = time_ns.struct("ESPTime") TimeHasTimeCondition = time_ns.class_("TimeHasTimeCondition", Condition) -def _tz_timedelta(td): - offset_hour = int(td.total_seconds() / (60 * 60)) - offset_minute = int(abs(td.total_seconds() / 60)) % 60 - offset_second = int(abs(td.total_seconds())) % 60 - if offset_hour == 0 and offset_minute == 0 and offset_second == 0: - return "0" - if offset_minute == 0 and offset_second == 0: - return f"{offset_hour}" - if offset_second == 0: - return f"{offset_hour}:{offset_minute}" - return f"{offset_hour}:{offset_minute}:{offset_second}" - - -# https://stackoverflow.com/a/16804556/8924614 -def _week_of_month(dt): - first_day = dt.replace(day=1) - dom = dt.day - adjusted_dom = dom + first_day.weekday() - return int(math.ceil(adjusted_dom / 7.0)) - - -def _tz_dst_str(dt): - td = datetime.timedelta(hours=dt.hour, minutes=dt.minute, seconds=dt.second) - return f"M{dt.month}.{_week_of_month(dt)}.{dt.isoweekday() % 7}/{_tz_timedelta(td)}" - - -def _safe_tzname(tz, dt): - tzname = tz.tzname(dt) - # pytz does not always return valid tznames - # For example: 'Europe/Saratov' returns '+04' - # Work around it by using a generic name for the timezone - if not all(c in string.ascii_letters for c in tzname): - return "TZ" - return tzname - - -def _non_dst_tz(tz, dt): - tzname = _safe_tzname(tz, dt) - utcoffset = tz.utcoffset(dt) - _LOGGER.info( - "Detected timezone '%s' with UTC offset %s", tzname, _tz_timedelta(utcoffset) - ) - tzbase = f"{tzname}{_tz_timedelta(-1 * utcoffset)}" - return tzbase - - -def convert_tz(pytz_obj): - tz = pytz_obj - - now = datetime.datetime.now() - first_january = datetime.datetime(year=now.year, month=1, day=1) - - if not isinstance(tz, pytz.tzinfo.DstTzInfo): - return _non_dst_tz(tz, first_january) - - # pylint: disable=protected-access - transition_times = tz._utc_transition_times - transition_info = tz._transition_info - idx = max(0, bisect.bisect_right(transition_times, now)) - if idx >= len(transition_times): - return _non_dst_tz(tz, now) - - idx1, idx2 = idx, idx + 1 - dstoffset1 = transition_info[idx1][1] - if dstoffset1 == datetime.timedelta(seconds=0): - # Normalize to 1 being DST on - idx1, idx2 = idx + 1, idx + 2 - - if idx2 >= len(transition_times): - return _non_dst_tz(tz, now) - - if transition_times[idx2].year > now.year + 1: - # Next transition is scheduled after this year - # Probably a scheduler timezone change. - return _non_dst_tz(tz, now) - - utcoffset_on, _, tzname_on = transition_info[idx1] - utcoffset_off, _, tzname_off = transition_info[idx2] - dst_begins_utc = transition_times[idx1] - dst_begins_local = dst_begins_utc + utcoffset_off - dst_ends_utc = transition_times[idx2] - dst_ends_local = dst_ends_utc + utcoffset_on - - tzbase = f"{tzname_off}{_tz_timedelta(-1 * utcoffset_off)}" - - tzext = f"{tzname_on}{_tz_timedelta(-1 * utcoffset_on)},{_tz_dst_str(dst_begins_local)},{_tz_dst_str(dst_ends_local)}" - _LOGGER.info( - "Detected timezone '%s' with UTC offset %s and daylight saving time from " - "%s to %s", - tzname_off, - _tz_timedelta(utcoffset_off), - dst_begins_local.strftime("%d %B %X"), - dst_ends_local.strftime("%d %B %X"), - ) - return tzbase + tzext - - -def detect_tz(): +def _load_tzdata(iana_key: str) -> Optional[bytes]: + # From https://tzdata.readthedocs.io/en/latest/#examples try: - tz = tzlocal.get_localzone() - except pytz.exceptions.UnknownTimeZoneError: - _LOGGER.warning("Could not auto-detect timezone. Using UTC...") - return "UTC" + package_loc, resource = iana_key.rsplit("/", 1) + except ValueError: + return None + package = "tzdata.zoneinfo." + package_loc.replace("/", ".") - return convert_tz(tz) + try: + return resources.read_binary(package, resource) + except (FileNotFoundError, ModuleNotFoundError): + return None + + +def _extract_tz_string(tzfile: bytes) -> str: + try: + return tzfile.split(b"\n")[-2].decode() + except (IndexError, UnicodeDecodeError): + _LOGGER.error("Could not determine TZ string. Please report this issue.") + _LOGGER.error("tzfile contents: %s", tzfile, exc_info=True) + raise + + +def detect_tz() -> str: + localzone = tzlocal.get_localzone() + if localzone is timezone.utc: + return "UTC0" + if not hasattr(localzone, "key"): + raise cv.Invalid( + "Could not automatically determine timezone, please set timezone manually." + ) + iana_key = localzone.key + _LOGGER.info("Detected timezone '%s'", iana_key) + tzfile = _load_tzdata(iana_key) + if tzfile is None: + raise cv.Invalid( + "Could not automatically determine timezone, please set timezone manually." + ) + ret = _extract_tz_string(tzfile) + _LOGGER.debug(" -> TZ string %s", ret) + return ret def _parse_cron_int(value, special_mapping, message): @@ -327,15 +261,15 @@ def validate_cron_keys(value): return cv.has_at_least_one_key(*CRON_KEYS)(value) -def validate_tz(value): +def validate_tz(value: str) -> str: value = cv.string_strict(value) - try: - pytz_obj = pytz.timezone(value) - except pytz.UnknownTimeZoneError: # pylint: disable=broad-except + tzfile = _load_tzdata(value) + if tzfile is None: + # Not a IANA key, probably a TZ string return value - return convert_tz(pytz_obj) + return _extract_tz_string(tzfile) TIME_SCHEMA = cv.Schema( diff --git a/requirements.txt b/requirements.txt index 5ff9f7e282..ba2c43e5b7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,8 +3,8 @@ PyYAML==5.4.1 paho-mqtt==1.5.1 colorama==0.4.4 tornado==6.1 -tzlocal==3.0 -pytz==2021.1 +tzlocal==3.0 # from time +tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.0 esptool==3.1 From cee08debffa2f9f33a76e8948d8a6029c1854403 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sun, 3 Oct 2021 16:15:01 +0200 Subject: [PATCH 1484/1841] Hotfix for ESP8266 OTA issue: ERROR Error binary size (#2432) Co-authored-by: Maurice Makaay --- esphome/components/ota/ota_backend_arduino_esp8266.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.cpp b/esphome/components/ota/ota_backend_arduino_esp8266.cpp index 8e8a4f36ba..23dc0d4e21 100644 --- a/esphome/components/ota/ota_backend_arduino_esp8266.cpp +++ b/esphome/components/ota/ota_backend_arduino_esp8266.cpp @@ -16,6 +16,7 @@ OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) { bool ret = Update.begin(image_size, U_FLASH); if (ret) { esp8266::preferences_prevent_write(true); + return OTA_RESPONSE_OK; } uint8_t error = Update.getError(); From 1627dff166768ec79928fc3e65a3b18f1d7267d4 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sun, 3 Oct 2021 21:53:40 +0200 Subject: [PATCH 1485/1841] Disable dependency finder on ESP32 (#2435) --- esphome/components/airthings_wave_plus/sensor.py | 4 ++++ esphome/components/captive_portal/__init__.py | 6 +++++- esphome/components/esp32/__init__.py | 2 ++ esphome/components/ethernet/__init__.py | 3 +++ esphome/components/http_request/__init__.py | 5 +++++ esphome/components/mcp3008/__init__.py | 4 ++++ esphome/components/mdns/__init__.py | 9 +++++---- esphome/components/ota/__init__.py | 1 + esphome/components/spi/__init__.py | 5 ++++- esphome/components/web_server_base/__init__.py | 2 ++ 10 files changed, 35 insertions(+), 6 deletions(-) diff --git a/esphome/components/airthings_wave_plus/sensor.py b/esphome/components/airthings_wave_plus/sensor.py index 8b902ea81c..2100341536 100644 --- a/esphome/components/airthings_wave_plus/sensor.py +++ b/esphome/components/airthings_wave_plus/sensor.py @@ -1,6 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, ble_client +from esphome.core import CORE from esphome.const import ( DEVICE_CLASS_CARBON_DIOXIDE, @@ -116,3 +117,6 @@ async def to_code(config): if CONF_TVOC in config: sens = await sensor.new_sensor(config[CONF_TVOC]) cg.add(var.set_tvoc(sens)) + + if CORE.is_esp32: + cg.add_library("ESP32 BLE Arduino", None) diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index a1cc5734c1..384a3f23a0 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -3,7 +3,7 @@ import esphome.config_validation as cv from esphome.components import web_server_base from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID from esphome.const import CONF_ID -from esphome.core import coroutine_with_priority +from esphome.core import coroutine_with_priority, CORE AUTO_LOAD = ["web_server_base"] DEPENDENCIES = ["wifi"] @@ -32,3 +32,7 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID], paren) await cg.register_component(var, config) cg.add_define("USE_CAPTIVE_PORTAL") + + if CORE.is_esp32: + cg.add_library("DNSServer", None) + cg.add_library("WiFi", None) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 1316c7ccbe..704f9bb3e8 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -276,6 +276,8 @@ async def to_code(config): cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}") + cg.add_platformio_option("lib_ldf_mode", "off") + conf = config[CONF_FRAMEWORK] if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: cg.add_platformio_option( diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 384fb3dbfb..bbf64a3cd1 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -123,3 +123,6 @@ async def to_code(config): cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP]))) cg.add_define("USE_ETHERNET") + + if CORE.is_esp32: + cg.add_library("WiFi", None) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index a48b3c0acb..6e249c4247 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -92,6 +92,11 @@ async def to_code(config): cg.add(var.set_useragent(config[CONF_USERAGENT])) if CORE.is_esp8266 and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]: cg.add_define("USE_HTTP_REQUEST_ESP8266_HTTPS") + + if CORE.is_esp32: + cg.add_library("WiFiClientSecure", None) + cg.add_library("HTTPClient", None) + await cg.register_component(var, config) diff --git a/esphome/components/mcp3008/__init__.py b/esphome/components/mcp3008/__init__.py index 24a48664c1..431963acfd 100644 --- a/esphome/components/mcp3008/__init__.py +++ b/esphome/components/mcp3008/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import spi from esphome.const import CONF_ID +from esphome.core import CORE DEPENDENCIES = ["spi"] AUTO_LOAD = ["sensor"] @@ -23,3 +24,6 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await spi.register_spi_device(var, config) + + if CORE.is_esp32: + cg.add_library("SPI", None) diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index c0c0865643..b95469d9da 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -30,15 +30,16 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - if config[CONF_DISABLED]: - return - - cg.add_define("USE_MDNS") if CORE.using_arduino: if CORE.is_esp32: cg.add_library("ESPmDNS", None) elif CORE.is_esp8266: cg.add_library("ESP8266mDNS", None) + if config[CONF_DISABLED]: + return + + cg.add_define("USE_MDNS") + var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 59ab22056b..7856d35580 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -101,6 +101,7 @@ async def to_code(config): if CORE.is_esp8266: cg.add_library("Update", None) elif CORE.is_esp32 and CORE.using_arduino: + cg.add_library("Update", None) cg.add_library("Hash", None) use_state_callback = False diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index 803a45814c..3a96cce99b 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -10,7 +10,7 @@ from esphome.const import ( CONF_SPI_ID, CONF_CS_PIN, ) -from esphome.core import coroutine_with_priority +from esphome.core import coroutine_with_priority, CORE CODEOWNERS = ["@esphome/core"] spi_ns = cg.esphome_ns.namespace("spi") @@ -46,6 +46,9 @@ async def to_code(config): mosi = await cg.gpio_pin_expression(config[CONF_MOSI_PIN]) cg.add(var.set_mosi(mosi)) + if CORE.is_esp32: + cg.add_library("SPI", None) + def spi_device_schema(cs_pin_required=True): """Create a schema for an SPI device. diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 8f64c473f3..95d59a863e 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -24,6 +24,8 @@ async def to_code(config): await cg.register_component(var, config) if CORE.is_esp32: + cg.add_library("WiFi", None) cg.add_library("FS", None) + cg.add_library("Update", None) # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json cg.add_library("esphome/ESPAsyncWebServer-esphome", "1.3.0") From 49f46a7cddec08d169ab23a16d6f97c0f8a8f564 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sun, 3 Oct 2021 21:55:19 +0200 Subject: [PATCH 1486/1841] Use size_t to fix comparision using RISC-V toolchain (#2436) --- esphome/components/ota/ota_component.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index e1188a4d31..e897d952ef 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -109,11 +109,11 @@ void OTAComponent::loop() { void OTAComponent::handle_() { OTAResponseTypes error_code = OTA_RESPONSE_ERROR_UNKNOWN; bool update_started = false; - uint32_t total = 0; + size_t total = 0; uint32_t last_progress = 0; uint8_t buf[1024]; char *sbuf = reinterpret_cast(buf); - uint32_t ota_size; + size_t ota_size; uint8_t ota_features; std::unique_ptr backend; (void) ota_features; From 46b4c970d160816e91e933a3f6af402b1812e86a Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sun, 3 Oct 2021 22:21:45 +0200 Subject: [PATCH 1487/1841] Fix socket abstraction for ESP-IDF v4 (#2434) --- esphome/components/socket/bsd_sockets_impl.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index aca15f118e..dcf4ea1f4e 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -96,6 +96,9 @@ class BSDSocketImpl : public Socket { break; } return ret; +#elif defined(ARDUINO_ARCH_ESP32) + // ESP-IDF v4 only has symbol lwip_readv + return ::lwip_readv(fd_, iov, iovcnt); #else return ::readv(fd_, iov, iovcnt); #endif @@ -120,6 +123,9 @@ class BSDSocketImpl : public Socket { break; } return ret; +#elif defined(ARDUINO_ARCH_ESP32) + // ESP-IDF v4 only has symbol lwip_writev + return ::lwip_writev(fd_, iov, iovcnt); #else return ::writev(fd_, iov, iovcnt); #endif From 5c06cd8eb32ccbde3d3c9605ba70d302a7ae18b1 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Mon, 4 Oct 2021 12:33:25 +0200 Subject: [PATCH 1488/1841] Fix I2C recovery ESP32 esp-idf (#2438) Co-authored-by: Maurice Makaay --- esphome/components/i2c/i2c_bus_arduino.cpp | 49 ++++---- esphome/components/i2c/i2c_bus_esp_idf.cpp | 125 +++++++++++++++++---- esphome/components/i2c/i2c_bus_esp_idf.h | 7 ++ 3 files changed, 137 insertions(+), 44 deletions(-) diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index 539091ed9c..4b519e4873 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -12,6 +12,7 @@ static const char *const TAG = "i2c.arduino"; void ArduinoI2CBus::setup() { recover_(); + #ifdef USE_ESP32 static uint8_t next_bus_num = 0; if (next_bus_num == 0) @@ -109,11 +110,19 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn void ArduinoI2CBus::recover_() { ESP_LOGI(TAG, "Performing I2C bus recovery"); - // Activate the pull up resistor on the SCL pin. This should make the - // signal on the line HIGH. If SCL is pulled low on the I2C bus however, - // then some device is interfering with the SCL line. In that case, - // the I2C bus cannot be recovered. - pinMode(scl_pin_, INPUT_PULLUP); // NOLINT + // For the upcoming operations, target for a 100kHz toggle frequency. + // This is the maximum frequency for I2C running in standard-mode. + // The actual frequency will be lower, because of the additional + // function calls that are done, but that is no problem. + const auto half_period_usec = 1000000 / 100000 / 2; + + // Activate input and pull up resistor for the SCL pin. + pinMode(scl_pin_, INPUT_PULLUP); // NOLINT + + // This should make the signal on the line HIGH. If SCL is pulled low + // on the I2C bus however, then some device is interfering with the SCL + // line. In that case, the I2C bus cannot be recovered. + delayMicroseconds(half_period_usec); if (digitalRead(scl_pin_) == LOW) { // NOLINT ESP_LOGE(TAG, "Recovery failed: SCL is held LOW on the I2C bus"); recovery_result_ = RECOVERY_FAILED_SCL_LOW; @@ -125,25 +134,13 @@ void ArduinoI2CBus::recover_() { // device that held the bus LOW should release it sometime within // those nine clocks." // We don't really have to detect if SDA is stuck low. We'll simply send - // nine clock pulses here, just in case SDA is stuck. + // nine clock pulses here, just in case SDA is stuck. Actual checks on + // the SDA line status will be done after the clock pulses. - // Use a 100kHz toggle frequency (i.e. the maximum frequency for I2C - // running in standard-mode). The resulting frequency will be lower, - // because of the additional function calls that are done, but that - // is no problem. - const auto half_period_usec = 1000000 / 100000 / 2; - - // Make sure that switching to mode OUTPUT will make SCL low, just in - // case other code has setup the pin to output a HIGH signal. + // Make sure that switching to output mode will make SCL low, just in + // case other code has setup the pin for a HIGH signal. digitalWrite(scl_pin_, LOW); // NOLINT - // Activate the pull up resistor for SDA, so after the clock pulse cycle - // we can verify if SDA is pulled high. Also make sure that switching to - // mode OUTPUT will make SDA low. - pinMode(sda_pin_, INPUT_PULLUP); // NOLINT - digitalWrite(sda_pin_, LOW); // NOLINT - - ESP_LOGI(TAG, "Sending 9 clock pulses to drain any stuck device output"); delayMicroseconds(half_period_usec); for (auto i = 0; i < 9; i++) { // Release pull up resistor and switch to output to make the signal LOW. @@ -171,6 +168,11 @@ void ArduinoI2CBus::recover_() { } } + // Activate input and pull resistor for the SDA pin, so we can verify + // that SDA is pulled HIGH in the following step. + pinMode(sda_pin_, INPUT_PULLUP); // NOLINT + digitalWrite(sda_pin_, LOW); // NOLINT + // By now, any stuck device ought to have sent all remaining bits of its // transation, meaning that it should have freed up the SDA line, resulting // in SDA being pulled up. @@ -191,10 +193,9 @@ void ArduinoI2CBus::recover_() { // out of this state. // SCL and SDA are already high at this point, so we can generate a START // condition by making the SDA signal LOW. - ESP_LOGI(TAG, "Generate START condition to reset bus logic of I2C devices"); + delayMicroseconds(half_period_usec); pinMode(sda_pin_, INPUT); // NOLINT pinMode(sda_pin_, OUTPUT); // NOLINT - delayMicroseconds(half_period_usec); // From the specification: // "A START condition immediately followed by a STOP condition (void @@ -202,7 +203,7 @@ void ArduinoI2CBus::recover_() { // operate properly under this condition." // Finally, we'll bring the I2C bus into a starting state by generating // a STOP condition. - ESP_LOGI(TAG, "Generate STOP condition to finalize recovery"); + delayMicroseconds(half_period_usec); pinMode(sda_pin_, INPUT); // NOLINT pinMode(sda_pin_, INPUT_PULLUP); // NOLINT diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 28e71ab2a0..91fd1499e9 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -43,6 +43,17 @@ void IDFI2CBus::dump_config() { ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_); ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_); ESP_LOGCONFIG(TAG, " Frequency: %u Hz", this->frequency_); + switch (this->recovery_result_) { + case RECOVERY_COMPLETED: + ESP_LOGCONFIG(TAG, " Recovery: bus successfully recovered"); + break; + case RECOVERY_FAILED_SCL_LOW: + ESP_LOGCONFIG(TAG, " Recovery: failed, SCL is held low on the bus"); + break; + case RECOVERY_FAILED_SDA_LOW: + ESP_LOGCONFIG(TAG, " Recovery: failed, SDA is held low on the bus"); + break; + } if (this->scan_) { ESP_LOGI(TAG, "Scanning i2c bus for active devices..."); uint8_t found = 0; @@ -144,39 +155,113 @@ ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { return ERROR_OK; } +/// Perform I2C bus recovery, see: +/// https://www.nxp.com/docs/en/user-guide/UM10204.pdf +/// https://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf void IDFI2CBus::recover_() { - // Perform I2C bus recovery, see - // https://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf - // or see the linux kernel implementation, e.g. - // https://elixir.bootlin.com/linux/v5.14.6/source/drivers/i2c/i2c-core-base.c#L200 + ESP_LOGI(TAG, "Performing I2C bus recovery"); - // try to get about 100kHz toggle frequency - const auto half_period_usec = 1000000 / 100000 / 2; - const auto recover_scl_periods = 9; const gpio_num_t scl_pin = static_cast(scl_pin_); + const gpio_num_t sda_pin = static_cast(sda_pin_); - // configure scl as output - gpio_config_t conf{}; - conf.pin_bit_mask = 1ULL << static_cast(scl_pin_); - conf.mode = GPIO_MODE_OUTPUT; - conf.pull_up_en = GPIO_PULLUP_DISABLE; - conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - conf.intr_type = GPIO_INTR_DISABLE; + // For the upcoming operations, target for a 60kHz toggle frequency. + // 1000kHz is the maximum frequency for I2C running in standard-mode, + // but lower frequencies are not a problem. + // Note: the timing that is used here is chosen manually, to get + // results that are close to the timing that can be archieved by the + // implementation for the Arduino framework. + const auto half_period_usec = 7; - gpio_config(&conf); - - // set scl high + // Configure SCL pin for open drain input/output, with a pull up resistor. gpio_set_level(scl_pin, 1); + gpio_config_t scl_config{}; + scl_config.pin_bit_mask = 1ULL << scl_pin_; + scl_config.mode = GPIO_MODE_INPUT_OUTPUT_OD; + scl_config.pull_up_en = GPIO_PULLUP_ENABLE; + scl_config.pull_down_en = GPIO_PULLDOWN_DISABLE; + scl_config.intr_type = GPIO_INTR_DISABLE; + gpio_config(&scl_config); - // in total generate 9 falling-rising edges - for (auto i = 0; i < recover_scl_periods; i++) { - delayMicroseconds(half_period_usec); + // Configure SDA pin for open drain input/output, with a pull up resistor. + gpio_set_level(sda_pin, 1); + gpio_config_t sda_conf{}; + sda_conf.pin_bit_mask = 1ULL << sda_pin_; + sda_conf.mode = GPIO_MODE_INPUT_OUTPUT_OD; + sda_conf.pull_up_en = GPIO_PULLUP_ENABLE; + sda_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + sda_conf.intr_type = GPIO_INTR_DISABLE; + gpio_config(&sda_conf); + + // If SCL is pulled low on the I2C bus, then some device is interfering + // with the SCL line. In that case, the I2C bus cannot be recovered. + delayMicroseconds(half_period_usec); + if (gpio_get_level(scl_pin) == 0) { + ESP_LOGE(TAG, "Recovery failed: SCL is held LOW on the I2C bus"); + recovery_result_ = RECOVERY_FAILED_SCL_LOW; + return; + } + + // From the specification: + // "If the data line (SDA) is stuck LOW, send nine clock pulses. The + // device that held the bus LOW should release it sometime within + // those nine clocks." + // We don't really have to detect if SDA is stuck low. We'll simply send + // nine clock pulses here, just in case SDA is stuck. Actual checks on + // the SDA line status will be done after the clock pulses. + for (auto i = 0; i < 9; i++) { gpio_set_level(scl_pin, 0); delayMicroseconds(half_period_usec); gpio_set_level(scl_pin, 1); + delayMicroseconds(half_period_usec); + + // When SCL is kept LOW at this point, we might be looking at a device + // that applies clock stretching. Wait for the release of the SCL line, + // but not forever. There is no specification for the maximum allowed + // time. We'll stick to 500ms here. + auto wait = 20; + while (wait-- && gpio_get_level(scl_pin) == 0) { + delay(25); + } + if (gpio_get_level(scl_pin) == 0) { + ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle"); + recovery_result_ = RECOVERY_FAILED_SCL_LOW; + return; + } } + // By now, any stuck device ought to have sent all remaining bits of its + // transation, meaning that it should have freed up the SDA line, resulting + // in SDA being pulled up. + if (gpio_get_level(sda_pin) == 0) { + ESP_LOGE(TAG, "Recovery failed: SDA is held LOW after clock pulse cycle"); + recovery_result_ = RECOVERY_FAILED_SDA_LOW; + return; + } + + // From the specification: + // "I2C-bus compatible devices must reset their bus logic on receipt of + // a START or repeated START condition such that they all anticipate + // the sending of a target address, even if these START conditions are + // not positioned according to the proper format." + // While the 9 clock pulses from above might have drained all bits of a + // single byte within a transaction, a device might have more bytes to + // transmit. So here we'll generate a START condition to snap the device + // out of this state. + // SCL and SDA are already high at this point, so we can generate a START + // condition by making the SDA signal LOW. delayMicroseconds(half_period_usec); + gpio_set_level(sda_pin, 0); + + // From the specification: + // "A START condition immediately followed by a STOP condition (void + // message) is an illegal format. Many devices however are designed to + // operate properly under this condition." + // Finally, we'll bring the I2C bus into a starting state by generating + // a STOP condition. + delayMicroseconds(half_period_usec); + gpio_set_level(sda_pin, 1); + + recovery_result_ = RECOVERY_COMPLETED; } } // namespace i2c diff --git a/esphome/components/i2c/i2c_bus_esp_idf.h b/esphome/components/i2c/i2c_bus_esp_idf.h index ba5fbf25c5..13d996dbd8 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.h +++ b/esphome/components/i2c/i2c_bus_esp_idf.h @@ -9,6 +9,12 @@ namespace esphome { namespace i2c { +enum RecoveryCode { + RECOVERY_FAILED_SCL_LOW, + RECOVERY_FAILED_SDA_LOW, + RECOVERY_COMPLETED, +}; + class IDFI2CBus : public I2CBus, public Component { public: void setup() override; @@ -26,6 +32,7 @@ class IDFI2CBus : public I2CBus, public Component { private: void recover_(); + RecoveryCode recovery_result_; protected: i2c_port_t port_; From 87358e884392a717a3bd65a5b3ad39d3c2d3524a Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 4 Oct 2021 16:14:51 +0200 Subject: [PATCH 1489/1841] Fix esp32 no longer has Hash internal lib (#2441) --- esphome/components/ota/__init__.py | 1 - esphome/components/wifi/__init__.py | 2 ++ platformio.ini | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 7856d35580..bcfb28979d 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -102,7 +102,6 @@ async def to_code(config): cg.add_library("Update", None) elif CORE.is_esp32 and CORE.using_arduino: cg.add_library("Update", None) - cg.add_library("Hash", None) use_state_callback = False for conf in config.get(CONF_ON_STATE_CHANGE, []): diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 7d029bbea1..19e4046711 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -342,6 +342,8 @@ async def to_code(config): if CORE.is_esp8266: cg.add_library("ESP8266WiFi", None) + elif CORE.is_esp32 and CORE.using_arduino: + cg.add_library("WiFi", None) cg.add_define("USE_WIFI") diff --git a/platformio.ini b/platformio.ini index 6e605768fc..f38bbc78a9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -89,7 +89,6 @@ framework = arduino board = nodemcu-32s lib_deps = ${common:arduino.lib_deps} - Hash ; ota (Arduino built-in) esphome/AsyncTCP-esphome@1.2.2 ; async_tcp build_flags = ${common:arduino.build_flags} From 871d3b66fb5f3d8484456f4da414f56411989fad Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 4 Oct 2021 16:15:25 +0200 Subject: [PATCH 1490/1841] Fix restoring globals (#2442) --- esphome/cpp_generator.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index cf357f2814..937b6cceb4 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -638,7 +638,7 @@ async def process_lambda( :param return_type: The return type of the lambda. :return: The generated lambda expression. """ - from esphome.components.globals import GlobalsComponent + from esphome.components.globals import GlobalsComponent, RestoringGlobalsComponent if value is None: return @@ -648,7 +648,10 @@ async def process_lambda( if ( full_id is not None and isinstance(full_id.type, MockObjClass) - and full_id.type.inherits_from(GlobalsComponent) + and ( + full_id.type.inherits_from(GlobalsComponent) + or full_id.type.inherits_from(RestoringGlobalsComponent) + ) ): parts[i * 3 + 1] = var.value() continue From 8be40862243c7123b668ccb774f9ce3f910f4835 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 4 Oct 2021 16:59:15 +0200 Subject: [PATCH 1491/1841] Always upload using esptool (#2433) --- esphome/__main__.py | 35 +++++++++---- esphome/core/__init__.py | 3 ++ esphome/platformio_api.py | 102 ++++++++++++++++++++++++-------------- esphome/storage_json.py | 2 +- 4 files changed, 94 insertions(+), 48 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 6bc7e065ce..feb95e93c7 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -184,12 +184,30 @@ def compile_program(args, config): def upload_using_esptool(config, port): - path = CORE.firmware_bin + from esphome import platformio_api + first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get( "upload_speed", 460800 ) def run_esptool(baud_rate): + idedata = platformio_api.get_idedata(config) + + firmware_offset = "0x10000" if CORE.is_esp32 else "0x0" + flash_images = [ + platformio_api.FlashImage( + path=idedata.firmware_bin_path, + offset=firmware_offset, + ), + *idedata.extra_flash_images, + ] + + mcu = "esp8266" + if CORE.is_esp32: + from esphome.components.esp32 import get_esp32_variant + + mcu = get_esp32_variant().lower() + cmd = [ "esptool.py", "--before", @@ -198,14 +216,15 @@ def upload_using_esptool(config, port): "hard_reset", "--baud", str(baud_rate), - "--chip", - "esp8266", "--port", port, + "--chip", + mcu, "write_flash", - "0x0", - path, + "-z", ] + for img in flash_images: + cmd += [img.offset, img.path] if os.environ.get("ESPHOME_USE_SUBPROCESS") is None: import esptool @@ -229,11 +248,7 @@ def upload_using_esptool(config, port): def upload_program(config, args, host): # if upload is to a serial port use platformio, otherwise assume ota if get_port_type(host) == "SERIAL": - from esphome import platformio_api - - if CORE.is_esp8266: - return upload_using_esptool(config, host) - return platformio_api.run_upload(config, CORE.verbose, host) + return upload_using_esptool(config, host) from esphome import espota2 diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 8021fc2f53..8bdef3a4ea 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -542,6 +542,9 @@ class EsphomeCore: path_ = os.path.expanduser(os.path.join(*path)) return os.path.join(self.config_dir, path_) + def relative_internal_path(self, *path: str) -> str: + return self.relative_config_path(".esphome", *path) + def relative_build_path(self, *path): # pylint: disable=no-value-for-parameter path_ = os.path.expanduser(os.path.join(*path)) diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 100def3310..054c0cb1b0 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -1,12 +1,15 @@ +from dataclasses import dataclass import json -from typing import Union +from typing import List, Union +from pathlib import Path import logging import os import re import subprocess -from esphome.core import CORE +from esphome.const import KEY_CORE +from esphome.core import CORE, EsphomeError from esphome.util import run_external_command, run_external_process _LOGGER = logging.getLogger(__name__) @@ -96,36 +99,56 @@ def run_compile(config, verbose): return run_platformio_cli_run(config, verbose) -def run_upload(config, verbose, port): - return run_platformio_cli_run( - config, verbose, "-t", "upload", "--upload-port", port - ) - - -def run_idedata(config): +def _run_idedata(config): args = ["-t", "idedata"] stdout = run_platformio_cli_run(config, False, *args, capture_stdout=True) match = re.search(r'{\s*".*}', stdout) if match is None: - _LOGGER.debug("Could not match IDEData for %s", stdout) - return IDEData(None) + _LOGGER.error("Could not match idedata, please report this error") + _LOGGER.error("Stdout: %s", stdout) + raise EsphomeError + try: - return IDEData(json.loads(match.group())) + return json.loads(match.group()) except ValueError: - _LOGGER.debug("Could not load IDEData for %s", stdout, exc_info=1) - return IDEData(None) + _LOGGER.error("Could not parse idedata", exc_info=True) + _LOGGER.error("Stdout: %s", stdout) + raise -IDE_DATA = None +def _load_idedata(config): + platformio_ini = Path(CORE.relative_build_path("platformio.ini")) + temp_idedata = Path(CORE.relative_internal_path(CORE.name, "idedata.json")) + + changed = False + if not platformio_ini.is_file() or not temp_idedata.is_file(): + changed = True + elif platformio_ini.stat().st_mtime >= temp_idedata.stat().st_mtime: + changed = True + + if not changed: + try: + return json.loads(temp_idedata.read_text(encoding="utf-8")) + except ValueError: + pass + + temp_idedata.parent.mkdir(exist_ok=True, parents=True) + + data = _run_idedata(config) + + temp_idedata.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") + return data -def get_idedata(config): - global IDE_DATA +KEY_IDEDATA = "idedata" - if IDE_DATA is None: - _LOGGER.info("Need to fetch platformio IDE-data, please stand by") - IDE_DATA = run_idedata(config) - return IDE_DATA + +def get_idedata(config) -> "IDEData": + if KEY_IDEDATA in CORE.data[KEY_CORE]: + return CORE.data[KEY_CORE][KEY_IDEDATA] + idedata = IDEData(_load_idedata(config)) + CORE.data[KEY_CORE][KEY_IDEDATA] = idedata + return idedata # ESP logs stack trace decoder, based on https://github.com/me-no-dev/EspExceptionDecoder @@ -261,37 +284,42 @@ def process_stacktrace(config, line, backtrace_state): return backtrace_state +@dataclass +class FlashImage: + path: str + offset: str + + class IDEData: def __init__(self, raw): - if not isinstance(raw, dict): - self.raw = {} - else: - self.raw = raw + self.raw = raw @property def firmware_elf_path(self): - return self.raw.get("prog_path") + return self.raw["prog_path"] @property - def flash_extra_images(self): + def firmware_bin_path(self) -> str: + return str(Path(self.firmware_elf_path).with_suffix(".bin")) + + @property + def extra_flash_images(self) -> List[FlashImage]: return [ - (x["path"], x["offset"]) for x in self.raw.get("flash_extra_images", []) + FlashImage(path=entry["path"], offset=entry["offset"]) + for entry in self.raw["extra"]["flash_images"] ] @property - def cc_path(self): + def cc_path(self) -> str: # For example /Users//.platformio/packages/toolchain-xtensa32/bin/xtensa-esp32-elf-gcc - return self.raw.get("cc_path") + return self.raw["cc_path"] @property - def addr2line_path(self): - cc_path = self.cc_path - if cc_path is None: - return None + def addr2line_path(self) -> str: # replace gcc at end with addr2line # Windows - if cc_path.endswith(".exe"): - return f"{cc_path[:-7]}addr2line.exe" + if self.cc_path.endswith(".exe"): + return f"{self.cc_path[:-7]}addr2line.exe" - return f"{cc_path[:-3]}addr2line" + return f"{self.cc_path[:-3]}addr2line" diff --git a/esphome/storage_json.py b/esphome/storage_json.py index e70d3d6c93..3262559116 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -16,7 +16,7 @@ _LOGGER = logging.getLogger(__name__) def storage_path(): # type: () -> str - return CORE.relative_config_path(".esphome", f"{CORE.config_filename}.json") + return CORE.relative_internal_path(f"{CORE.config_filename}.json") def ext_storage_path(base_path, config_filename): # type: (str, str) -> str From 877367677b14b397fa023430504791736d47a781 Mon Sep 17 00:00:00 2001 From: NMC Date: Mon, 4 Oct 2021 18:56:34 -0400 Subject: [PATCH 1492/1841] Add support for Airthing Wave Mini (#2440) --- CODEOWNERS | 1 + .../airthings_wave_mini/__init__.py | 1 + .../airthings_wave_mini.cpp | 120 ++++++++++++++++++ .../airthings_wave_mini/airthings_wave_mini.h | 65 ++++++++++ .../components/airthings_wave_mini/sensor.py | 88 +++++++++++++ tests/test2.yaml | 14 ++ 6 files changed, 289 insertions(+) create mode 100644 esphome/components/airthings_wave_mini/__init__.py create mode 100644 esphome/components/airthings_wave_mini/airthings_wave_mini.cpp create mode 100644 esphome/components/airthings_wave_mini/airthings_wave_mini.h create mode 100644 esphome/components/airthings_wave_mini/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 2972b30b33..6576c78f0e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -15,6 +15,7 @@ esphome/components/ac_dimmer/* @glmnet esphome/components/adc/* @esphome/core esphome/components/addressable_light/* @justfalter esphome/components/airthings_ble/* @jeromelaban +esphome/components/airthings_wave_mini/* @ncareau esphome/components/airthings_wave_plus/* @jeromelaban esphome/components/am43/* @buxtronix esphome/components/am43/cover/* @buxtronix diff --git a/esphome/components/airthings_wave_mini/__init__.py b/esphome/components/airthings_wave_mini/__init__.py new file mode 100644 index 0000000000..022f35b4cf --- /dev/null +++ b/esphome/components/airthings_wave_mini/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@ncareau"] diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp new file mode 100644 index 0000000000..0ab0e65148 --- /dev/null +++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp @@ -0,0 +1,120 @@ +#include "airthings_wave_mini.h" + +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +namespace esphome { +namespace airthings_wave_mini { + +static const char *const TAG = "airthings_wave_mini"; + +void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) { + switch (event) { + case ESP_GATTC_OPEN_EVT: { + if (param->open.status == ESP_GATT_OK) { + ESP_LOGI(TAG, "Connected successfully!"); + } + break; + } + + case ESP_GATTC_DISCONNECT_EVT: { + ESP_LOGW(TAG, "Disconnected!"); + break; + } + + case ESP_GATTC_SEARCH_CMPL_EVT: { + this->handle_ = 0; + auto chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_); + if (chr == nullptr) { + ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(), + sensors_data_characteristic_uuid_.to_string().c_str()); + break; + } + this->handle_ = chr->handle; + this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; + + request_read_values_(); + break; + } + + case ESP_GATTC_READ_CHAR_EVT: { + if (param->read.conn_id != this->parent()->conn_id) + break; + if (param->read.status != ESP_GATT_OK) { + ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); + break; + } + if (param->read.handle == this->handle_) { + read_sensors_(param->read.value, param->read.value_len); + } + break; + } + + default: + break; + } +} + +void AirthingsWaveMini::read_sensors_(uint8_t *raw_value, uint16_t value_len) { + auto value = (WaveMiniReadings *) raw_value; + + if (sizeof(WaveMiniReadings) <= value_len) { + this->humidity_sensor_->publish_state(value->humidity / 100.0f); + this->pressure_sensor_->publish_state(value->pressure / 50.0f); + this->temperature_sensor_->publish_state(value->temperature / 100.0f - 273.15f); + if (is_valid_voc_value_(value->voc)) { + this->tvoc_sensor_->publish_state(value->voc); + } + + // This instance must not stay connected + // so other clients can connect to it (e.g. the + // mobile app). + parent()->set_enabled(false); + } +} + +bool AirthingsWaveMini::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; } + +void AirthingsWaveMini::loop() {} + +void AirthingsWaveMini::update() { + if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { + if (!parent()->enabled) { + ESP_LOGW(TAG, "Reconnecting to device"); + parent()->set_enabled(true); + parent()->connect(); + } else { + ESP_LOGW(TAG, "Connection in progress"); + } + } +} + +void AirthingsWaveMini::request_read_values_() { + auto status = + esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); + } +} + +void AirthingsWaveMini::dump_config() { + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); + LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); +} + +AirthingsWaveMini::AirthingsWaveMini() : PollingComponent(10000) { + auto service_bt = *BLEUUID::fromString(std::string("b42e3882-ade7-11e4-89d3-123b93f75cba")).getNative(); + auto characteristic_bt = *BLEUUID::fromString(std::string("b42e3b98-ade7-11e4-89d3-123b93f75cba")).getNative(); + + service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_uuid(service_bt); + sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_uuid(characteristic_bt); +} + +void AirthingsWaveMini::setup() {} + +} // namespace airthings_wave_mini +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.h b/esphome/components/airthings_wave_mini/airthings_wave_mini.h new file mode 100644 index 0000000000..5d1964d559 --- /dev/null +++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.h @@ -0,0 +1,65 @@ +#pragma once + +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +#include +#include +#include +#include +#include "esphome/core/component.h" +#include "esphome/core/log.h" +#include "esphome/components/ble_client/ble_client.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace airthings_wave_mini { + +class AirthingsWaveMini : public PollingComponent, public ble_client::BLEClientNode { + public: + AirthingsWaveMini(); + + void setup() override; + void dump_config() override; + void update() override; + void loop() override; + + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + + void set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; } + void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } + void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; } + void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } + + protected: + bool is_valid_voc_value_(uint16_t voc); + + void read_sensors_(uint8_t *value, uint16_t value_len); + void request_read_values_(); + + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; + sensor::Sensor *tvoc_sensor_{nullptr}; + + uint16_t handle_; + esp32_ble_tracker::ESPBTUUID service_uuid_; + esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_; + + struct WaveMiniReadings { + uint16_t unused01; + uint16_t temperature; + uint16_t pressure; + uint16_t humidity; + uint16_t voc; + uint16_t unused02; + uint32_t unused03; + uint32_t unused04; + }; +}; + +} // namespace airthings_wave_mini +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/airthings_wave_mini/sensor.py b/esphome/components/airthings_wave_mini/sensor.py new file mode 100644 index 0000000000..6a32cd8771 --- /dev/null +++ b/esphome/components/airthings_wave_mini/sensor.py @@ -0,0 +1,88 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, ble_client +from esphome.core import CORE + +from esphome.const import ( + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + STATE_CLASS_MEASUREMENT, + UNIT_PERCENT, + UNIT_CELSIUS, + UNIT_HECTOPASCAL, + CONF_ID, + CONF_HUMIDITY, + CONF_TVOC, + CONF_PRESSURE, + CONF_TEMPERATURE, + UNIT_PARTS_PER_BILLION, + ICON_RADIATOR, +) + +DEPENDENCIES = ["ble_client"] + +airthings_wave_mini_ns = cg.esphome_ns.namespace("airthings_wave_mini") +AirthingsWaveMini = airthings_wave_mini_ns.class_( + "AirthingsWaveMini", cg.PollingComponent, ble_client.BLEClientNode +) + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(AirthingsWaveMini), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=2, + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PRESSURE): sensor.sensor_schema( + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=2, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TVOC): sensor.sensor_schema( + unit_of_measurement=UNIT_PARTS_PER_BILLION, + icon=ICON_RADIATOR, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("5mins")) + .extend(ble_client.BLE_CLIENT_SCHEMA), + # Until BLEUUID reference removed + cv.only_with_arduino, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + await ble_client.register_ble_node(var, config) + + if CONF_HUMIDITY in config: + sens = await sensor.new_sensor(config[CONF_HUMIDITY]) + cg.add(var.set_humidity(sens)) + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature(sens)) + if CONF_PRESSURE in config: + sens = await sensor.new_sensor(config[CONF_PRESSURE]) + cg.add(var.set_pressure(sens)) + if CONF_TVOC in config: + sens = await sensor.new_sensor(config[CONF_TVOC]) + cg.add(var.set_tvoc(sens)) + + if CORE.is_esp32: + cg.add_library("ESP32 BLE Arduino", None) diff --git a/tests/test2.yaml b/tests/test2.yaml index 4541fba616..364bcec28f 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -281,6 +281,17 @@ sensor: name: "Wave Plus CO2" tvoc: name: "Wave Plus VOC" + - platform: airthings_wave_mini + ble_client_id: airthingsmini01 + update_interval: 5min + temperature: + name: "Wave Mini Temperature" + humidity: + name: "Wave Mini Humidity" + pressure: + name: "Wave Mini Pressure" + tvoc: + name: "Wave Mini VOC" time: - platform: homeassistant @@ -378,6 +389,9 @@ esp32_ble_tracker: ble_client: - mac_address: 01:02:03:04:05:06 id: airthings01 + - mac_address: 01:02:03:04:05:06 + id: airthingsmini01 + airthings_ble: From 6ec546a6a481ffb449bbd086a1d937cf53e5c1b0 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Tue, 5 Oct 2021 12:00:23 +1300 Subject: [PATCH 1493/1841] Improved validation for Addressable Light Partition Segments (#2439) Co-authored-by: Otto Winter --- esphome/components/partition/light.py | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/esphome/components/partition/light.py b/esphome/components/partition/light.py index ada83a123e..822b7ac306 100644 --- a/esphome/components/partition/light.py +++ b/esphome/components/partition/light.py @@ -1,11 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome.components import light from esphome.const import ( CONF_ADDRESSABLE_LIGHT_ID, CONF_FROM, CONF_ID, CONF_LIGHT_ID, + CONF_NUM_LEDS, CONF_SEGMENTS, CONF_SINGLE_LIGHT_ID, CONF_TO, @@ -31,6 +33,27 @@ def validate_from_to(value): return value +def validate_segment(config): + fconf = fv.full_config.get() + + if CONF_ID in config: # only validate addressable segments + path = fconf.get_path_for_id(config[CONF_ID])[:-1] + segment_light_config = fconf.get_config_for_path(path) + + if CONF_NUM_LEDS in segment_light_config: + segment_len = segment_light_config[CONF_NUM_LEDS] + if config[CONF_FROM] >= segment_len: + raise cv.Invalid( + f"FROM ({config[CONF_FROM]}) must be less than the number of LEDs in light '{config[CONF_ID]}' ({segment_len})", + [CONF_FROM], + ) + if config[CONF_TO] >= segment_len: + raise cv.Invalid( + f"TO ({config[CONF_TO]}) must be less than the number of LEDs in light '{config[CONF_ID]}' ({segment_len})", + [CONF_TO], + ) + + ADDRESSABLE_SEGMENT_SCHEMA = cv.Schema( { cv.Required(CONF_ID): cv.use_id(light.AddressableLightState), @@ -63,6 +86,13 @@ CONFIG_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend( } ) +FINAL_VALIDATE_SCHEMA = cv.Schema( + { + cv.Required(CONF_SEGMENTS): [validate_segment], + }, + extra=cv.ALLOW_EXTRA, +) + async def to_code(config): segments = [] From e09ee8f23d7a862f8026ccaf06797878a501a42b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Oct 2021 14:49:55 +0200 Subject: [PATCH 1494/1841] Bump aioesphomeapi from 9.1.2 to 9.1.4 (#2443) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ba2c43e5b7..176c623691 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.0 esptool==3.1 click==8.0.1 esphome-dashboard==20210927.0 -aioesphomeapi==9.1.2 +aioesphomeapi==9.1.4 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From e22f1fc044d30cd92628d53c0eaa8e4cd438ded0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Oct 2021 14:50:18 +0200 Subject: [PATCH 1495/1841] Bump pytest-cov from 2.12.1 to 3.0.0 (#2444) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 1d09bb6388..85456643bc 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -6,7 +6,7 @@ pre-commit # Unit tests pytest==6.2.5 -pytest-cov==2.12.1 +pytest-cov==3.0.0 pytest-mock==3.6.1 pytest-asyncio==0.15.1 asyncmock==0.4.2 From a57580b5ab972e97e1f24f077afd5691091a374f Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Tue, 5 Oct 2021 17:56:32 +0200 Subject: [PATCH 1496/1841] Fix compilation error (#2447) --- esphome/components/shutdown/shutdown_switch.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/shutdown/shutdown_switch.cpp b/esphome/components/shutdown/shutdown_switch.cpp index 0e5853cc46..a5f9a92982 100644 --- a/esphome/components/shutdown/shutdown_switch.cpp +++ b/esphome/components/shutdown/shutdown_switch.cpp @@ -1,4 +1,5 @@ #include "shutdown_switch.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" #include "esphome/core/application.h" From e083d7f4d0c233e27b1c0cc9cfdb98b1dd416e1f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 6 Oct 2021 11:26:18 +1300 Subject: [PATCH 1497/1841] Add log line to show if API encryption is being used (#2450) --- esphome/components/api/api_server.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 8728597537..4e2899d94f 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -133,6 +133,11 @@ void APIServer::loop() { void APIServer::dump_config() { ESP_LOGCONFIG(TAG, "API Server:"); ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->port_); +#ifdef USE_API_NOISE + ESP_LOGCONFIG(TAG, " Using noise encryption: YES"); +#else + ESP_LOGCONFIG(TAG, " Using noise encryption: NO"); +#endif } bool APIServer::uses_password() const { return !this->password_.empty(); } bool APIServer::check_password(const std::string &password) const { From b8b30599ee150c021c0d2350093e00f3c8ab4f86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Oct 2021 11:29:30 +1300 Subject: [PATCH 1498/1841] Bump aioesphomeapi from 9.1.4 to 9.1.5 (#2449) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 176c623691..202e38a21b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.0 esptool==3.1 click==8.0.1 esphome-dashboard==20210927.0 -aioesphomeapi==9.1.4 +aioesphomeapi==9.1.5 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 7bbb5213f3cbfd5265f19718b484bb5e32be8fdf Mon Sep 17 00:00:00 2001 From: Alex Iribarren Date: Wed, 6 Oct 2021 00:44:48 +0200 Subject: [PATCH 1499/1841] Only ping once every two seconds (#2448) --- esphome/dashboard/dashboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 492b86384d..eb698a7de1 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -582,7 +582,7 @@ class MDNSStatusThread(threading.Thread): class PingStatusThread(threading.Thread): def run(self): with multiprocessing.Pool(processes=8) as pool: - while not STOP_EVENT.is_set(): + while not STOP_EVENT.wait(2): # Only do pings if somebody has the dashboard open def callback(ret): From 9ff824080274fdb4eff49311926a6ac13b244ed6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 6 Oct 2021 11:45:01 +1300 Subject: [PATCH 1500/1841] Bump esphome-dashboard to 20211006.0 (#2451) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 202e38a21b..880faeddbe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.0 esptool==3.1 click==8.0.1 -esphome-dashboard==20210927.0 +esphome-dashboard==20211006.0 aioesphomeapi==9.1.5 # esp-idf requires this, but doesn't bundle it by default From 54a173dbf1aecae345e94a7eecdc664613f251e6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 6 Oct 2021 00:57:23 +0200 Subject: [PATCH 1501/1841] I2C re-introduce very verbose logging (#2446) --- esphome/components/i2c/i2c_bus_arduino.cpp | 49 ++++++++++++++++++- esphome/components/i2c/i2c_bus_esp_idf.cpp | 56 +++++++++++++++++++++- 2 files changed, 101 insertions(+), 4 deletions(-) diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index 4b519e4873..4afabbfa53 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -62,33 +62,75 @@ void ArduinoI2CBus::dump_config() { } } ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { - if (!initialized_) + // logging is only enabled with vv level, if warnings are shown the caller + // should log them + if (!initialized_) { + ESP_LOGVV(TAG, "i2c bus not initialized!"); return ERROR_NOT_INITIALIZED; + } size_t to_request = 0; for (size_t i = 0; i < cnt; i++) to_request += buffers[i].len; size_t ret = wire_->requestFrom((int) address, (int) to_request, 1); if (ret != to_request) { + ESP_LOGVV(TAG, "RX %u from %02X failed with error %u", to_request, address, ret); return ERROR_TIMEOUT; } + for (size_t i = 0; i < cnt; i++) { const auto &buf = buffers[i]; for (size_t j = 0; j < buf.len; j++) buf.data[j] = wire_->read(); } + +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE + char debug_buf[4]; + std::string debug_hex; + + for (size_t i = 0; i < cnt; i++) { + const auto &buf = buffers[i]; + for (size_t j = 0; j < buf.len; j++) { + snprintf(debug_buf, sizeof(debug_buf), "%02X", buf.data[j]); + debug_hex += debug_buf; + } + } + ESP_LOGVV(TAG, "0x%02X RX %s", address, debug_hex.c_str()); +#endif + return ERROR_OK; } ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { - if (!initialized_) + // logging is only enabled with vv level, if warnings are shown the caller + // should log them + if (!initialized_) { + ESP_LOGVV(TAG, "i2c bus not initialized!"); return ERROR_NOT_INITIALIZED; + } + +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE + char debug_buf[4]; + std::string debug_hex; + + for (size_t i = 0; i < cnt; i++) { + const auto &buf = buffers[i]; + for (size_t j = 0; j < buf.len; j++) { + snprintf(debug_buf, sizeof(debug_buf), "%02X", buf.data[j]); + debug_hex += debug_buf; + } + } + ESP_LOGVV(TAG, "0x%02X TX %s", address, debug_hex.c_str()); +#endif wire_->beginTransmission(address); + size_t written = 0; for (size_t i = 0; i < cnt; i++) { const auto &buf = buffers[i]; if (buf.len == 0) continue; size_t ret = wire_->write(buf.data, buf.len); + written += ret; if (ret != buf.len) { + ESP_LOGVV(TAG, "TX failed at %u", written); return ERROR_UNKNOWN; } } @@ -97,10 +139,13 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn return ERROR_OK; } else if (status == 1) { // transmit buffer not large enough + ESP_LOGVV(TAG, "TX failed: buffer not large enough"); return ERROR_UNKNOWN; } else if (status == 2 || status == 3) { + ESP_LOGVV(TAG, "TX failed: not acknowledged"); return ERROR_NOT_ACKNOWLEDGED; } + ESP_LOGVV(TAG, "TX failed: unknown error %u", status); return ERROR_UNKNOWN; } diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 91fd1499e9..f7ecfe5f7c 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -73,16 +73,22 @@ void IDFI2CBus::dump_config() { } } ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { - if (!initialized_) + // logging is only enabled with vv level, if warnings are shown the caller + // should log them + if (!initialized_) { + ESP_LOGVV(TAG, "i2c bus not initialized!"); return ERROR_NOT_INITIALIZED; + } i2c_cmd_handle_t cmd = i2c_cmd_link_create(); esp_err_t err = i2c_master_start(cmd); if (err != ESP_OK) { + ESP_LOGVV(TAG, "RX from %02X master start failed: %s", address, esp_err_to_name(err)); i2c_cmd_link_delete(cmd); return ERROR_UNKNOWN; } err = i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_READ, true); if (err != ESP_OK) { + ESP_LOGVV(TAG, "RX from %02X address write failed: %s", address, esp_err_to_name(err)); i2c_cmd_link_delete(cmd); return ERROR_UNKNOWN; } @@ -92,12 +98,14 @@ ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { continue; err = i2c_master_read(cmd, buf.data, buf.len, i == cnt - 1 ? I2C_MASTER_LAST_NACK : I2C_MASTER_ACK); if (err != ESP_OK) { + ESP_LOGVV(TAG, "RX from %02X data read failed: %s", address, esp_err_to_name(err)); i2c_cmd_link_delete(cmd); return ERROR_UNKNOWN; } } err = i2c_master_stop(cmd); if (err != ESP_OK) { + ESP_LOGVV(TAG, "RX from %02X stop failed: %s", address, esp_err_to_name(err)); i2c_cmd_link_delete(cmd); return ERROR_UNKNOWN; } @@ -105,25 +113,64 @@ ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { i2c_cmd_link_delete(cmd); if (err == ESP_FAIL) { // transfer not acked + ESP_LOGVV(TAG, "RX from %02X failed: not acked", address); return ERROR_NOT_ACKNOWLEDGED; } else if (err == ESP_ERR_TIMEOUT) { + ESP_LOGVV(TAG, "RX from %02X failed: timeout", address); return ERROR_TIMEOUT; } else if (err != ESP_OK) { + ESP_LOGVV(TAG, "RX from %02X failed: %s", address, esp_err_to_name(err)); return ERROR_UNKNOWN; } + +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE + char debug_buf[4]; + std::string debug_hex; + + for (size_t i = 0; i < cnt; i++) { + const auto &buf = buffers[i]; + for (size_t j = 0; j < buf.len; j++) { + snprintf(debug_buf, sizeof(debug_buf), "%02X", buf.data[j]); + debug_hex += debug_buf; + } + } + ESP_LOGVV(TAG, "0x%02X RX %s", address, debug_hex.c_str()); +#endif + return ERROR_OK; } ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { - if (!initialized_) + // logging is only enabled with vv level, if warnings are shown the caller + // should log them + if (!initialized_) { + ESP_LOGVV(TAG, "i2c bus not initialized!"); return ERROR_NOT_INITIALIZED; + } + +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE + char debug_buf[4]; + std::string debug_hex; + + for (size_t i = 0; i < cnt; i++) { + const auto &buf = buffers[i]; + for (size_t j = 0; j < buf.len; j++) { + snprintf(debug_buf, sizeof(debug_buf), "%02X", buf.data[j]); + debug_hex += debug_buf; + } + } + ESP_LOGVV(TAG, "0x%02X TX %s", address, debug_hex.c_str()); +#endif + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); esp_err_t err = i2c_master_start(cmd); if (err != ESP_OK) { + ESP_LOGVV(TAG, "TX to %02X master start failed: %s", address, esp_err_to_name(err)); i2c_cmd_link_delete(cmd); return ERROR_UNKNOWN; } err = i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, true); if (err != ESP_OK) { + ESP_LOGVV(TAG, "TX to %02X address write failed: %s", address, esp_err_to_name(err)); i2c_cmd_link_delete(cmd); return ERROR_UNKNOWN; } @@ -133,12 +180,14 @@ ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { continue; err = i2c_master_write(cmd, buf.data, buf.len, true); if (err != ESP_OK) { + ESP_LOGVV(TAG, "TX to %02X data write failed: %s", address, esp_err_to_name(err)); i2c_cmd_link_delete(cmd); return ERROR_UNKNOWN; } } err = i2c_master_stop(cmd); if (err != ESP_OK) { + ESP_LOGVV(TAG, "TX to %02X master stop failed: %s", address, esp_err_to_name(err)); i2c_cmd_link_delete(cmd); return ERROR_UNKNOWN; } @@ -146,10 +195,13 @@ ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { i2c_cmd_link_delete(cmd); if (err == ESP_FAIL) { // transfer not acked + ESP_LOGVV(TAG, "TX to %02X failed: not acked", address); return ERROR_NOT_ACKNOWLEDGED; } else if (err == ESP_ERR_TIMEOUT) { + ESP_LOGVV(TAG, "TX to %02X failed: timeout", address); return ERROR_TIMEOUT; } else if (err != ESP_OK) { + ESP_LOGVV(TAG, "TX to %02X failed: %s", address, esp_err_to_name(err)); return ERROR_UNKNOWN; } return ERROR_OK; From 955c96731ef35dcca9c8d0fec769f166df99bc66 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Wed, 6 Oct 2021 20:44:48 +1300 Subject: [PATCH 1502/1841] Add Safe Mode Restart Switch (#2437) --- CODEOWNERS | 1 + esphome/components/ota/ota_component.cpp | 38 ++++++++++++++++--- esphome/components/ota/ota_component.h | 7 ++++ esphome/components/safe_mode/__init__.py | 5 +++ .../components/safe_mode/switch/__init__.py | 36 ++++++++++++++++++ .../safe_mode/switch/safe_mode_switch.cpp | 29 ++++++++++++++ .../safe_mode/switch/safe_mode_switch.h | 21 ++++++++++ esphome/const.py | 1 + tests/test1.yaml | 2 + 9 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 esphome/components/safe_mode/__init__.py create mode 100644 esphome/components/safe_mode/switch/__init__.py create mode 100644 esphome/components/safe_mode/switch/safe_mode_switch.cpp create mode 100644 esphome/components/safe_mode/switch/safe_mode_switch.h diff --git a/CODEOWNERS b/CODEOWNERS index 6576c78f0e..49cb60c177 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -126,6 +126,7 @@ esphome/components/restart/* @esphome/core esphome/components/rf_bridge/* @jesserockz esphome/components/rgbct/* @jesserockz esphome/components/rtttl/* @glmnet +esphome/components/safe_mode/* @paulmonigatti esphome/components/scd4x/* @sjtrny esphome/components/script/* @esphome/core esphome/components/sdm_meter/* @jesserockz @polyfaces diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index e897d952ef..9ad3814f5c 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -89,7 +89,8 @@ void OTAComponent::dump_config() { ESP_LOGCONFIG(TAG, " Using Password."); } #endif - if (this->has_safe_mode_ && this->safe_mode_rtc_value_ > 1) { + if (this->has_safe_mode_ && this->safe_mode_rtc_value_ > 1 && + this->safe_mode_rtc_value_ != esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC) { ESP_LOGW(TAG, "Last Boot was an unhandled reset, will proceed to safe mode in %d restarts", this->safe_mode_num_attempts_ - this->safe_mode_rtc_value_); } @@ -401,6 +402,27 @@ bool OTAComponent::writeall_(const uint8_t *buf, size_t len) { float OTAComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; } uint16_t OTAComponent::get_port() const { return this->port_; } void OTAComponent::set_port(uint16_t port) { this->port_ = port; } + +void OTAComponent::set_safe_mode_pending(const bool &pending) { + if (!this->has_safe_mode_) + return; + + uint32_t current_rtc = this->read_rtc_(); + + if (pending && current_rtc != esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC) { + ESP_LOGI(TAG, "Device will enter safe mode on next boot."); + this->write_rtc_(esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC); + } + + if (!pending && current_rtc == esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC) { + ESP_LOGI(TAG, "Safe mode pending has been cleared"); + this->clean_rtc(); + } +} +bool OTAComponent::get_safe_mode_pending() { + return this->has_safe_mode_ && this->read_rtc_() == esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC; +} + bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time) { this->has_safe_mode_ = true; this->safe_mode_start_time_ = millis(); @@ -409,12 +431,18 @@ bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_ this->rtc_ = global_preferences->make_preference(233825507UL, false); this->safe_mode_rtc_value_ = this->read_rtc_(); - ESP_LOGCONFIG(TAG, "There have been %u suspected unsuccessful boot attempts.", this->safe_mode_rtc_value_); + bool is_manual_safe_mode = this->safe_mode_rtc_value_ == esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC; - if (this->safe_mode_rtc_value_ >= num_attempts) { + if (is_manual_safe_mode) + ESP_LOGI(TAG, "Safe mode has been entered manually"); + else + ESP_LOGCONFIG(TAG, "There have been %u suspected unsuccessful boot attempts.", this->safe_mode_rtc_value_); + + if (this->safe_mode_rtc_value_ >= num_attempts || is_manual_safe_mode) { this->clean_rtc(); - ESP_LOGE(TAG, "Boot loop detected. Proceeding to safe mode."); + if (!is_manual_safe_mode) + ESP_LOGE(TAG, "Boot loop detected. Proceeding to safe mode."); this->status_set_error(); this->set_timeout(enable_time, []() { @@ -445,7 +473,7 @@ uint32_t OTAComponent::read_rtc_() { } void OTAComponent::clean_rtc() { this->write_rtc_(0); } void OTAComponent::on_safe_shutdown() { - if (this->has_safe_mode_) + if (this->has_safe_mode_ && this->read_rtc_() != esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC) this->clean_rtc(); } diff --git a/esphome/components/ota/ota_component.h b/esphome/components/ota/ota_component.h index f76295735e..e08e187df6 100644 --- a/esphome/components/ota/ota_component.h +++ b/esphome/components/ota/ota_component.h @@ -48,6 +48,10 @@ class OTAComponent : public Component { bool should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time); + /// Set to true if the next startup will enter safe mode + void set_safe_mode_pending(const bool &pending); + bool get_safe_mode_pending(); + #ifdef USE_OTA_STATE_CALLBACK void add_on_state_callback(std::function &&callback); #endif @@ -89,6 +93,9 @@ class OTAComponent : public Component { uint8_t safe_mode_num_attempts_; ESPPreferenceObject rtc_; + static const uint32_t ENTER_SAFE_MODE_MAGIC = + 0x5afe5afe; ///< a magic number to indicate that safe mode should be entered on next boot + #ifdef USE_OTA_STATE_CALLBACK CallbackManager state_callback_{}; #endif diff --git a/esphome/components/safe_mode/__init__.py b/esphome/components/safe_mode/__init__.py new file mode 100644 index 0000000000..f150d6e086 --- /dev/null +++ b/esphome/components/safe_mode/__init__.py @@ -0,0 +1,5 @@ +import esphome.codegen as cg + +CODEOWNERS = ["@paulmonigatti"] + +safe_mode_ns = cg.esphome_ns.namespace("safe_mode") diff --git a/esphome/components/safe_mode/switch/__init__.py b/esphome/components/safe_mode/switch/__init__.py new file mode 100644 index 0000000000..0ad814ff4f --- /dev/null +++ b/esphome/components/safe_mode/switch/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch +from esphome.components.ota import OTAComponent +from esphome.const import ( + CONF_ID, + CONF_INVERTED, + CONF_ICON, + CONF_OTA, + ICON_RESTART_ALERT, +) +from .. import safe_mode_ns + +DEPENDENCIES = ["ota"] + +SafeModeSwitch = safe_mode_ns.class_("SafeModeSwitch", switch.Switch, cg.Component) + +CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SafeModeSwitch), + cv.GenerateID(CONF_OTA): cv.use_id(OTAComponent), + cv.Optional(CONF_INVERTED): cv.invalid( + "Safe Mode Restart switches do not support inverted mode!" + ), + cv.Optional(CONF_ICON, default=ICON_RESTART_ALERT): switch.icon, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await switch.register_switch(var, config) + + ota = await cg.get_variable(config[CONF_OTA]) + cg.add(var.set_ota(ota)) diff --git a/esphome/components/safe_mode/switch/safe_mode_switch.cpp b/esphome/components/safe_mode/switch/safe_mode_switch.cpp new file mode 100644 index 0000000000..a3979eec06 --- /dev/null +++ b/esphome/components/safe_mode/switch/safe_mode_switch.cpp @@ -0,0 +1,29 @@ +#include "safe_mode_switch.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace safe_mode { + +static const char *const TAG = "safe_mode_switch"; + +void SafeModeSwitch::set_ota(ota::OTAComponent *ota) { this->ota_ = ota; } + +void SafeModeSwitch::write_state(bool state) { + // Acknowledge + this->publish_state(false); + + if (state) { + ESP_LOGI(TAG, "Restarting device in safe mode..."); + this->ota_->set_safe_mode_pending(true); + + // Let MQTT settle a bit + delay(100); // NOLINT + App.safe_reboot(); + } +} +void SafeModeSwitch::dump_config() { LOG_SWITCH("", "Safe Mode Switch", this); } + +} // namespace safe_mode +} // namespace esphome diff --git a/esphome/components/safe_mode/switch/safe_mode_switch.h b/esphome/components/safe_mode/switch/safe_mode_switch.h new file mode 100644 index 0000000000..2772db3d84 --- /dev/null +++ b/esphome/components/safe_mode/switch/safe_mode_switch.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/ota/ota_component.h" +#include "esphome/components/switch/switch.h" + +namespace esphome { +namespace safe_mode { + +class SafeModeSwitch : public switch_::Switch, public Component { + public: + void dump_config() override; + void set_ota(ota::OTAComponent *ota); + + protected: + ota::OTAComponent *ota_; + void write_state(bool state) override; +}; + +} // namespace safe_mode +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 265576bfbe..a65285a4b5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -761,6 +761,7 @@ ICON_PULSE = "mdi:pulse" ICON_RADIATOR = "mdi:radiator" ICON_RADIOACTIVE = "mdi:radioactive" ICON_RESTART = "mdi:restart" +ICON_RESTART_ALERT = "mdi:restart-alert" ICON_ROTATE_RIGHT = "mdi:rotate-right" ICON_RULER = "mdi:ruler" ICON_SCALE = "mdi:scale" diff --git a/tests/test1.yaml b/tests/test1.yaml index 77f7da2b08..400cdb3b6b 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1905,6 +1905,8 @@ switch: state: yes - platform: restart name: 'Living Room Restart' + - platform: safe_mode + name: 'Living Room Restart (Safe Mode)' - platform: shutdown name: 'Living Room Shutdown' - platform: output From 22e3bc7cfe75501004148826c666f80fa3123016 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 6 Oct 2021 22:35:11 +1300 Subject: [PATCH 1503/1841] Add id() for restoring global (#2454) --- esphome/components/globals/globals_component.h | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/globals/globals_component.h b/esphome/components/globals/globals_component.h index b39c5f404b..3286e43575 100644 --- a/esphome/components/globals/globals_component.h +++ b/esphome/components/globals/globals_component.h @@ -76,6 +76,7 @@ template class GlobalVarSetAction : public Action T &id(GlobalsComponent *value) { return value->value(); } +template T &id(RestoringGlobalsComponent *value) { return value->value(); } } // namespace globals } // namespace esphome From d34a1c3ed6efa005341d3b43d03894b814ec8328 Mon Sep 17 00:00:00 2001 From: Alex Iribarren Date: Wed, 6 Oct 2021 21:56:07 +0200 Subject: [PATCH 1504/1841] Add timestamp to ESPHome dashboard/cli logs (#2455) --- esphome/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/log.py b/esphome/log.py index fa79efa833..abefcf6308 100644 --- a/esphome/log.py +++ b/esphome/log.py @@ -50,7 +50,7 @@ def color(col: str, msg: str, reset: bool = True) -> bool: class ESPHomeLogFormatter(logging.Formatter): def __init__(self): - super().__init__(fmt="%(levelname)s %(message)s", datefmt="%H:%M:%S", style="%") + super().__init__(fmt="%(asctime)s %(levelname)s %(message)s", style="%") def format(self, record): formatted = super().format(record) From 1c58b172358a898370de5188872ece374ecd0410 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 6 Oct 2021 22:36:12 +0200 Subject: [PATCH 1505/1841] API encryption switch to libsodium backend (#2456) --- esphome/components/api/__init__.py | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 3705f0d7ca..b0608a69dd 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -121,7 +121,7 @@ async def to_code(config): decoded = base64.b64decode(conf[CONF_KEY]) cg.add(var.set_noise_psk(list(decoded))) cg.add_define("USE_API_NOISE") - cg.add_library("esphome/noise-c", "0.1.1") + cg.add_library("esphome/noise-c", "0.1.3") else: cg.add_define("USE_API_PLAINTEXT") diff --git a/platformio.ini b/platformio.ini index f38bbc78a9..e038224f69 100644 --- a/platformio.ini +++ b/platformio.ini @@ -26,7 +26,7 @@ build_flags = [common] lib_deps = - esphome/noise-c@0.1.1 ; api + esphome/noise-c@0.1.3 ; api makuna/NeoPixelBus@2.6.7 ; neopixelbus build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE From 5461f87ff016cbb31a1ffe6876062dfee3f0ee63 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Thu, 7 Oct 2021 21:18:00 +0200 Subject: [PATCH 1506/1841] I2c fix (#2460) --- esphome/components/i2c/i2c.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/i2c/i2c.cpp b/esphome/components/i2c/i2c.cpp index 035c483344..82ab7bd09a 100644 --- a/esphome/components/i2c/i2c.cpp +++ b/esphome/components/i2c/i2c.cpp @@ -12,7 +12,7 @@ bool I2CDevice::write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t std::unique_ptr temp{new uint16_t[len]}; for (size_t i = 0; i < len; i++) temp[i] = htoi2cs(data[i]); - return write_register(a_register, reinterpret_cast(data), len * 2) == ERROR_OK; + return write_register(a_register, reinterpret_cast(temp.get()), len * 2) == ERROR_OK; } I2CRegister &I2CRegister::operator=(uint8_t value) { From 9bf72ff05f28533f42020fcb6e71ae1c5239a19c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 27 Sep 2021 11:40:28 +0200 Subject: [PATCH 1507/1841] Re-enable TCP nodelay for ESP32 (#2390) --- esphome/components/api/api_frame_helper.cpp | 15 +++++++++++ .../components/socket/lwip_raw_tcp_impl.cpp | 27 ++++++++++--------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 15014b7937..172e1040ad 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -126,6 +126,14 @@ APIError APINoiseFrameHelper::init() { return APIError::TCP_NONBLOCKING_FAILED; } + int enable = 1; + err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("Setting nodelay failed with errno %d", errno); + return APIError::TCP_NODELAY_FAILED; + } + // init prologue prologue_.insert(prologue_.end(), PROLOGUE_INIT, PROLOGUE_INIT + strlen(PROLOGUE_INIT)); @@ -721,6 +729,13 @@ APIError APIPlaintextFrameHelper::init() { HELPER_LOG("Setting nonblocking failed with errno %d", errno); return APIError::TCP_NONBLOCKING_FAILED; } + int enable = 1; + err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("Setting nodelay failed with errno %d", errno); + return APIError::TCP_NODELAY_FAILED; + } state_ = State::DATA; return APIError::OK; diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 3c225aeb82..0fe608b62e 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -256,7 +256,7 @@ class LWIPRawImpl : public Socket { errno = EINVAL; return -1; } - *reinterpret_cast(optval) = tcp_nagle_disabled(pcb_); + *reinterpret_cast(optval) = nodelay_; *optlen = 4; return 0; } @@ -285,11 +285,7 @@ class LWIPRawImpl : public Socket { return -1; } int val = *reinterpret_cast(optval); - if (val != 0) { - tcp_nagle_disable(pcb_); - } else { - tcp_nagle_enable(pcb_); - } + nodelay_ = val; return 0; } @@ -443,9 +439,11 @@ class LWIPRawImpl : public Socket { if (written == 0) // no need to output if nothing written return 0; - int err = internal_output(); - if (err == -1) - return -1; + if (nodelay_) { + int err = internal_output(); + if (err == -1) + return -1; + } return written; } ssize_t writev(const struct iovec *iov, int iovcnt) override { @@ -465,9 +463,11 @@ class LWIPRawImpl : public Socket { if (written == 0) // no need to output if nothing written return 0; - int err = internal_output(); - if (err == -1) - return -1; + if (nodelay_) { + int err = internal_output(); + if (err == -1) + return -1; + } return written; } int setblocking(bool blocking) override { @@ -549,6 +549,9 @@ class LWIPRawImpl : public Socket { bool rx_closed_ = false; pbuf *rx_buf_ = nullptr; size_t rx_buf_offset_ = 0; + // don't use lwip nodelay flag, it sometimes causes reconnect + // instead use it for determining whether to call lwip_output + bool nodelay_ = false; }; std::unique_ptr socket(int domain, int type, int protocol) { From e7477890cfff7b57ad6acab51ac603ea9e19c5b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Oct 2021 15:20:12 +0200 Subject: [PATCH 1508/1841] Bump aioesphomeapi from 9.1.1 to 9.1.2 (#2426) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bd90f31cbb..ee0a010a1c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,4 @@ platformio==5.2.0 esptool==3.1 click==7.1.2 esphome-dashboard==20210908.0 -aioesphomeapi==9.1.1 +aioesphomeapi==9.1.2 From 32a664eedcfbc6bef2f3ba281ad685bc22035e2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Oct 2021 14:49:55 +0200 Subject: [PATCH 1509/1841] Bump aioesphomeapi from 9.1.2 to 9.1.4 (#2443) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ee0a010a1c..b2708e2c87 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,4 @@ platformio==5.2.0 esptool==3.1 click==7.1.2 esphome-dashboard==20210908.0 -aioesphomeapi==9.1.2 +aioesphomeapi==9.1.4 From d9b2903d785bb5c30f5beaddd7ae28a04110146a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 6 Oct 2021 11:26:18 +1300 Subject: [PATCH 1510/1841] Add log line to show if API encryption is being used (#2450) --- esphome/components/api/api_server.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 33843f384b..82178e0c10 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -134,6 +134,11 @@ void APIServer::loop() { void APIServer::dump_config() { ESP_LOGCONFIG(TAG, "API Server:"); ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->port_); +#ifdef USE_API_NOISE + ESP_LOGCONFIG(TAG, " Using noise encryption: YES"); +#else + ESP_LOGCONFIG(TAG, " Using noise encryption: NO"); +#endif } bool APIServer::uses_password() const { return !this->password_.empty(); } bool APIServer::check_password(const std::string &password) const { From 95d7ad543fa2257df51d14a4b00ce4f0ea81f74c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 6 Oct 2021 22:36:12 +0200 Subject: [PATCH 1511/1841] API encryption switch to libsodium backend (#2456) --- esphome/components/api/__init__.py | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 3705f0d7ca..b0608a69dd 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -121,7 +121,7 @@ async def to_code(config): decoded = base64.b64decode(conf[CONF_KEY]) cg.add(var.set_noise_psk(list(decoded))) cg.add_define("USE_API_NOISE") - cg.add_library("esphome/noise-c", "0.1.1") + cg.add_library("esphome/noise-c", "0.1.3") else: cg.add_define("USE_API_PLAINTEXT") diff --git a/platformio.ini b/platformio.ini index 73d5595dcd..263172fe0b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -36,7 +36,7 @@ lib_deps = 6306@1.0.3 ; HM3301 glmnet/Dsmr@0.3 ; used by dsmr rweather/Crypto@0.2.0 ; used by dsmr - esphome/noise-c@0.1.1 ; used by api + esphome/noise-c@0.1.3 ; used by api dudanov/MideaUART@1.1.8 ; used by midea build_flags = From fc5798fa71d0d3df6ac45582b2db250c78083078 Mon Sep 17 00:00:00 2001 From: Otto winter Date: Thu, 7 Oct 2021 22:05:30 +0200 Subject: [PATCH 1512/1841] Bump version to 2021.9.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f69f6e91da..25f48da264 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.2" +__version__ = "2021.9.3" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 3f2d9abfe6434cfbfc32a8b7de7c100acb95b6ae Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sat, 9 Oct 2021 01:30:21 -0700 Subject: [PATCH 1513/1841] Correct I2C read() return val check in bh1750 component. (#2465) Co-authored-by: Maurice Makaay --- esphome/components/bh1750/bh1750.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/bh1750/bh1750.cpp b/esphome/components/bh1750/bh1750.cpp index 3645a45bf9..951fe3670c 100644 --- a/esphome/components/bh1750/bh1750.cpp +++ b/esphome/components/bh1750/bh1750.cpp @@ -71,7 +71,7 @@ void BH1750Sensor::update() { float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; } void BH1750Sensor::read_data_() { uint16_t raw_value; - if (!this->read(reinterpret_cast(&raw_value), 2)) { + if (this->read(reinterpret_cast(&raw_value), 2) != i2c::ERROR_OK) { this->status_set_warning(); return; } From a1b28cb36e60bbc20f68cacb017881be87eaaed6 Mon Sep 17 00:00:00 2001 From: davidmonro Date: Sun, 10 Oct 2021 02:44:16 +1100 Subject: [PATCH 1514/1841] atm90e32: make the total_increasing class sensors actually be increasing totals. (#2459) Co-authored-by: David Monro --- esphome/components/atm90e32/atm90e32.cpp | 42 ++++++++++++++++++++---- esphome/components/atm90e32/atm90e32.h | 2 ++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/esphome/components/atm90e32/atm90e32.cpp b/esphome/components/atm90e32/atm90e32.cpp index ebceaa817c..e4b8448da6 100644 --- a/esphome/components/atm90e32/atm90e32.cpp +++ b/esphome/components/atm90e32/atm90e32.cpp @@ -265,27 +265,57 @@ float ATM90E32Component::get_power_factor_c_() { } float ATM90E32Component::get_forward_active_energy_a_() { uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYA); - return (float) val * 10 / 3200; // convert register value to WattHours + if ((UINT32_MAX - this->phase_[0].cumulative_forward_active_energy_) > val) { + this->phase_[0].cumulative_forward_active_energy_ += val; + } else { + this->phase_[0].cumulative_forward_active_energy_ = val; + } + return ((float) this->phase_[0].cumulative_forward_active_energy_ * 10 / 3200); } float ATM90E32Component::get_forward_active_energy_b_() { uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYB); - return (float) val * 10 / 3200; + if (UINT32_MAX - this->phase_[1].cumulative_forward_active_energy_ > val) { + this->phase_[1].cumulative_forward_active_energy_ += val; + } else { + this->phase_[1].cumulative_forward_active_energy_ = val; + } + return ((float) this->phase_[1].cumulative_forward_active_energy_ * 10 / 3200); } float ATM90E32Component::get_forward_active_energy_c_() { uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYC); - return (float) val * 10 / 3200; + if (UINT32_MAX - this->phase_[2].cumulative_forward_active_energy_ > val) { + this->phase_[2].cumulative_forward_active_energy_ += val; + } else { + this->phase_[2].cumulative_forward_active_energy_ = val; + } + return ((float) this->phase_[2].cumulative_forward_active_energy_ * 10 / 3200); } float ATM90E32Component::get_reverse_active_energy_a_() { uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYA); - return (float) val * 10 / 3200; + if (UINT32_MAX - this->phase_[0].cumulative_reverse_active_energy_ > val) { + this->phase_[0].cumulative_reverse_active_energy_ += val; + } else { + this->phase_[0].cumulative_reverse_active_energy_ = val; + } + return ((float) this->phase_[0].cumulative_reverse_active_energy_ * 10 / 3200); } float ATM90E32Component::get_reverse_active_energy_b_() { uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYB); - return (float) val * 10 / 3200; + if (UINT32_MAX - this->phase_[1].cumulative_reverse_active_energy_ > val) { + this->phase_[1].cumulative_reverse_active_energy_ += val; + } else { + this->phase_[1].cumulative_reverse_active_energy_ = val; + } + return ((float) this->phase_[1].cumulative_reverse_active_energy_ * 10 / 3200); } float ATM90E32Component::get_reverse_active_energy_c_() { uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYC); - return (float) val * 10 / 3200; + if (UINT32_MAX - this->phase_[2].cumulative_reverse_active_energy_ > val) { + this->phase_[2].cumulative_reverse_active_energy_ += val; + } else { + this->phase_[2].cumulative_reverse_active_energy_ = val; + } + return ((float) this->phase_[2].cumulative_reverse_active_energy_ * 10 / 3200); } float ATM90E32Component::get_frequency_() { uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ); diff --git a/esphome/components/atm90e32/atm90e32.h b/esphome/components/atm90e32/atm90e32.h index 89d62adaf6..c9662df26e 100644 --- a/esphome/components/atm90e32/atm90e32.h +++ b/esphome/components/atm90e32/atm90e32.h @@ -77,6 +77,8 @@ class ATM90E32Component : public PollingComponent, sensor::Sensor *power_factor_sensor_{nullptr}; sensor::Sensor *forward_active_energy_sensor_{nullptr}; sensor::Sensor *reverse_active_energy_sensor_{nullptr}; + uint32_t cumulative_forward_active_energy_{0}; + uint32_t cumulative_reverse_active_energy_{0}; } phase_[3]; sensor::Sensor *freq_sensor_{nullptr}; sensor::Sensor *chip_temperature_sensor_{nullptr}; From e514a1fcd446f926b1f813971d22c450b75493f0 Mon Sep 17 00:00:00 2001 From: Ryan Mounce Date: Sun, 10 Oct 2021 18:58:37 +1030 Subject: [PATCH 1515/1841] Use enum for Tuya fan direction datapoint (#2471) Fix regression from PR2059. Tested with Arlec DCF5242HA. --- esphome/components/tuya/fan/tuya_fan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp index f060b18eba..d0c8809564 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -76,7 +76,7 @@ void TuyaFan::write_state() { if (this->direction_id_.has_value()) { bool enable = this->fan_->direction == fan::FAN_DIRECTION_REVERSE; ESP_LOGV(TAG, "Setting reverse direction: %s", ONOFF(enable)); - this->parent_->set_boolean_datapoint_value(*this->direction_id_, enable); + this->parent_->set_enum_datapoint_value(*this->direction_id_, enable); } if (this->speed_id_.has_value()) { ESP_LOGV(TAG, "Setting speed: %d", this->fan_->speed); From c092d92d45488dadadd5857fdb223bd0a8e92c5a Mon Sep 17 00:00:00 2001 From: definitio <37266727+definitio@users.noreply.github.com> Date: Sun, 10 Oct 2021 11:31:15 +0300 Subject: [PATCH 1516/1841] Fix cover state (#2468) --- esphome/components/mqtt/mqtt_cover.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index 6a0d2d1bc5..61c8fa2d94 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -70,6 +70,7 @@ void MQTTCoverComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCon root["optimistic"] = true; } if (traits.get_supports_position()) { + config.state_topic = false; root["position_topic"] = this->get_position_state_topic(); root["set_position_topic"] = this->get_position_command_topic(); } From 92b85f98e861638b70d6021b838f52f1998342cc Mon Sep 17 00:00:00 2001 From: Nate Lust Date: Sun, 10 Oct 2021 04:33:04 -0400 Subject: [PATCH 1517/1841] Sgp40 fix (#2462) * Sample from SGP40 sensor at the appropriate interval The spg40 sensor must be sampled at 1Hz for the VOC index algorithm to work correctly. This commit introduces a on device timer to sample correctly seperately from updating the public state of the component. * Add missing configuration values for SGP40 The SGP40 component was not printing all of it's configuration in dump_config, add in the missing store_baseline value. * Address review comments * Format according to clang-tidy * Attempt 2 at clang tidy --- esphome/components/sgp40/sgp40.cpp | 35 ++++++++++++++++++++++++------ esphome/components/sgp40/sgp40.h | 2 ++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp index fddd0255b8..a3d2c74eb7 100644 --- a/esphome/components/sgp40/sgp40.cpp +++ b/esphome/components/sgp40/sgp40.cpp @@ -77,6 +77,20 @@ void SGP40Component::setup() { } this->self_test_(); + + /* The official spec for this sensor at https://docs.rs-online.com/1956/A700000007055193.pdf + indicates this sensor should be driven at 1Hz. Comments from the developers at: + https://github.com/Sensirion/embedded-sgp/issues/136 indicate the algorithm should be a bit + resilient to slight timing variations so the software timer should be accurate enough for + this. + + This block starts sampling from the sensor at 1Hz, and is done seperately from the call + to the update method. This seperation is to support getting accurate measurements but + limit the amount of communication done over wifi for power consumption or to keep the + number of records reported from being overwhelming. + */ + ESP_LOGD(TAG, "Component requires sampling of 1Hz, setting up background sampler"); + this->set_interval(1000, [this]() { this->update_voc_index(); }); } void SGP40Component::self_test_() { @@ -224,21 +238,26 @@ uint8_t SGP40Component::generate_crc_(const uint8_t *data, uint8_t datalen) { return crc; } -void SGP40Component::update() { - this->seconds_since_last_store_ += this->update_interval_ / 1000; - - uint32_t voc_index = this->measure_voc_index_(); +void SGP40Component::update_voc_index() { + this->seconds_since_last_store_ += 1; + this->voc_index_ = this->measure_voc_index_(); if (this->samples_read_ < this->samples_to_stabalize_) { this->samples_read_++; ESP_LOGD(TAG, "Sensor has not collected enough samples yet. (%d/%d) VOC index is: %u", this->samples_read_, - this->samples_to_stabalize_, voc_index); + this->samples_to_stabalize_, this->voc_index_); + return; + } +} + +void SGP40Component::update() { + if (this->samples_read_ < this->samples_to_stabalize_) { return; } - if (voc_index != UINT16_MAX) { + if (this->voc_index_ != UINT16_MAX) { this->status_clear_warning(); - this->publish_state(voc_index); + this->publish_state(this->voc_index_); } else { this->status_set_warning(); } @@ -247,6 +266,8 @@ void SGP40Component::update() { void SGP40Component::dump_config() { ESP_LOGCONFIG(TAG, "SGP40:"); LOG_I2C_DEVICE(this); + ESP_LOGCONFIG(TAG, " store_baseline: %d", this->store_baseline_); + if (this->is_failed()) { switch (this->error_code_) { case COMMUNICATION_FAILED: diff --git a/esphome/components/sgp40/sgp40.h b/esphome/components/sgp40/sgp40.h index 62936102e7..bb68a1ffcf 100644 --- a/esphome/components/sgp40/sgp40.h +++ b/esphome/components/sgp40/sgp40.h @@ -46,6 +46,7 @@ class SGP40Component : public PollingComponent, public sensor::Sensor, public i2 void setup() override; void update() override; + void update_voc_index(); void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } void set_store_baseline(bool store_baseline) { store_baseline_ = store_baseline; } @@ -72,6 +73,7 @@ class SGP40Component : public PollingComponent, public sensor::Sensor, public i2 bool store_baseline_; int32_t state0_; int32_t state1_; + int32_t voc_index_ = 0; uint8_t samples_read_ = 0; uint8_t samples_to_stabalize_ = static_cast(VOC_ALGORITHM_INITIAL_BLACKOUT) * 2; From 471b82f727e2f698b8879fb51c4161a9cf49d34b Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Sun, 10 Oct 2021 21:37:05 +1300 Subject: [PATCH 1518/1841] EntityBase Refactor (#2418) * Renamed Nameable to EntityBase (cpp) * Renamed NAMEABLE_SCHEMA to ENTITY_BASE_SCHEMA (Python) * Renamed cg.Nameable to cg.EntityBase (Python) * Remove redundant use of CONF_NAME from esp32_touch * Remove redundant use of CONF_NAME from mcp3008 * Updated test * Moved EntityBase from Component.h and Component.cpp * Added icon property to EntityBase * Added CONF_ICON to ENTITY_BASE_SCHEMA and added setup_entity function to cpp_helpers * Added MQTT component getters for icon and disabled_by_default * Lint * Removed icon field from MQTT components * Code generation now uses setup_entity to setENTITY_BASE_SCHEMA fields * Removed unused import * Added cstdint include * Optimisation: don't set icon if it is empty * Remove icon from NumberTraits and SelectTraits * Removed unused import * Integration and Total Daily Energy sensors now inherit icons from their parents during code generation * Minor comment correction * Removed redundant icon-handling code from sensor, switch, and text_sensor * Update esphome/components/tsl2591/tsl2591.h Co-authored-by: Oxan van Leeuwen * Added icon property to binary sensor, climate, cover, and fan component tests * Added icons for Binary Sensor, Climate, Cover, Fan, and Light to API * Consolidated EntityBase fields in MQTT components Co-authored-by: Oxan van Leeuwen --- esphome/codegen.py | 2 +- esphome/components/api/api.proto | 5 ++ esphome/components/api/api_connection.cpp | 14 ++++-- esphome/components/api/api_pb2.cpp | 46 ++++++++++++++++- esphome/components/api/api_pb2.h | 5 ++ esphome/components/binary_sensor/__init__.py | 13 ++--- .../binary_sensor/binary_sensor.cpp | 2 +- .../components/binary_sensor/binary_sensor.h | 3 +- esphome/components/climate/__init__.py | 14 ++---- esphome/components/climate/climate.cpp | 2 +- esphome/components/climate/climate.h | 3 +- esphome/components/cover/__init__.py | 14 ++---- esphome/components/cover/cover.cpp | 2 +- esphome/components/cover/cover.h | 3 +- esphome/components/esp32_camera/__init__.py | 2 +- .../components/esp32_camera/esp32_camera.cpp | 2 +- .../components/esp32_camera/esp32_camera.h | 3 +- .../components/esp32_touch/binary_sensor.py | 2 - .../components/esp32_touch/esp32_touch.cpp | 5 +- esphome/components/esp32_touch/esp32_touch.h | 2 +- esphome/components/fan/__init__.py | 13 ++--- esphome/components/fan/fan_state.cpp | 2 +- esphome/components/fan/fan_state.h | 3 +- .../integration/integration_sensor.h | 1 - esphome/components/integration/sensor.py | 17 ++++++- esphome/components/light/__init__.py | 14 +++--- esphome/components/light/light_state.cpp | 3 +- esphome/components/light/light_state.h | 5 +- esphome/components/light/types.py | 2 +- esphome/components/mcp3008/mcp3008.cpp | 6 +-- esphome/components/mcp3008/mcp3008.h | 2 +- esphome/components/mcp3008/sensor.py | 3 +- .../components/mqtt/mqtt_binary_sensor.cpp | 3 +- esphome/components/mqtt/mqtt_binary_sensor.h | 3 +- esphome/components/mqtt/mqtt_climate.cpp | 4 +- esphome/components/mqtt/mqtt_climate.h | 3 +- esphome/components/mqtt/mqtt_component.cpp | 15 +++++- esphome/components/mqtt/mqtt_component.h | 18 +++++-- esphome/components/mqtt/mqtt_cover.cpp | 4 +- esphome/components/mqtt/mqtt_cover.h | 3 +- esphome/components/mqtt/mqtt_fan.cpp | 4 +- esphome/components/mqtt/mqtt_fan.h | 4 +- esphome/components/mqtt/mqtt_light.cpp | 4 +- esphome/components/mqtt/mqtt_light.h | 4 +- esphome/components/mqtt/mqtt_number.cpp | 5 +- esphome/components/mqtt/mqtt_number.h | 4 +- esphome/components/mqtt/mqtt_select.cpp | 5 +- esphome/components/mqtt/mqtt_select.h | 4 +- esphome/components/mqtt/mqtt_sensor.cpp | 7 +-- esphome/components/mqtt/mqtt_sensor.h | 5 +- esphome/components/mqtt/mqtt_switch.cpp | 6 +-- esphome/components/mqtt/mqtt_switch.h | 4 +- esphome/components/mqtt/mqtt_text_sensor.cpp | 6 +-- esphome/components/mqtt/mqtt_text_sensor.h | 6 +-- esphome/components/number/__init__.py | 17 ++----- esphome/components/number/number.h | 10 ++-- .../remote_receiver/binary_sensor.py | 3 -- esphome/components/select/__init__.py | 17 ++----- esphome/components/select/select.h | 10 ++-- esphome/components/sensor/__init__.py | 17 ++----- esphome/components/sensor/sensor.cpp | 10 +--- esphome/components/sensor/sensor.h | 12 +---- esphome/components/switch/__init__.py | 18 ++----- esphome/components/switch/switch.cpp | 10 +--- esphome/components/switch/switch.h | 19 +------ esphome/components/text_sensor/__init__.py | 17 ++----- .../components/text_sensor/text_sensor.cpp | 10 +--- esphome/components/text_sensor/text_sensor.h | 10 +--- .../components/total_daily_energy/sensor.py | 14 ++++++ .../total_daily_energy/total_daily_energy.h | 1 - esphome/components/tsl2591/tsl2591.h | 2 +- esphome/components/web_server/web_server.cpp | 7 +-- esphome/config_validation.py | 8 +-- esphome/core/component.cpp | 20 -------- esphome/core/component.h | 34 ------------- esphome/core/entity_base.cpp | 40 +++++++++++++++ esphome/core/entity_base.h | 50 +++++++++++++++++++ esphome/core/entity_helpers.py | 32 ++++++++++++ esphome/cpp_helpers.py | 14 ++++++ esphome/cpp_types.py | 2 +- tests/test1.yaml | 3 ++ tests/test5.yaml | 1 + tests/unit_tests/test_codegen.py | 2 +- 83 files changed, 395 insertions(+), 351 deletions(-) create mode 100644 esphome/core/entity_base.cpp create mode 100644 esphome/core/entity_base.h create mode 100644 esphome/core/entity_helpers.py diff --git a/esphome/codegen.py b/esphome/codegen.py index 1b38fd9ed2..4f9f67245d 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -67,7 +67,7 @@ from esphome.cpp_types import ( # noqa NAN, esphome_ns, App, - Nameable, + EntityBase, Component, ComponentPtr, PollingComponent, diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 7648ffeaa2..5a6eba004c 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -215,6 +215,7 @@ message ListEntitiesBinarySensorResponse { string device_class = 5; bool is_status_binary_sensor = 6; bool disabled_by_default = 7; + string icon = 8; } message BinarySensorStateResponse { option (id) = 21; @@ -245,6 +246,7 @@ message ListEntitiesCoverResponse { bool supports_tilt = 7; string device_class = 8; bool disabled_by_default = 9; + string icon = 10; } enum LegacyCoverState { @@ -313,6 +315,7 @@ message ListEntitiesFanResponse { bool supports_direction = 7; int32 supported_speed_count = 8; bool disabled_by_default = 9; + string icon = 10; } enum FanSpeed { FAN_SPEED_LOW = 0; @@ -388,6 +391,7 @@ message ListEntitiesLightResponse { float max_mireds = 10; repeated string effects = 11; bool disabled_by_default = 13; + string icon = 14; } message LightStateResponse { option (id) = 24; @@ -790,6 +794,7 @@ message ListEntitiesClimateResponse { repeated ClimatePreset supported_presets = 16; repeated string supported_custom_presets = 17; bool disabled_by_default = 18; + string icon = 19; } message ClimateStateResponse { option (id) = 47; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 450375f7cd..47171ba50f 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1,4 +1,5 @@ #include "api_connection.h" +#include "esphome/core/entity_base.h" #include "esphome/core/log.h" #include "esphome/components/network/util.h" #include "esphome/core/version.h" @@ -143,8 +144,8 @@ void APIConnection::loop() { } } -std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) { - return App.get_name() + component_type + nameable->get_object_id(); +std::string get_default_unique_id(const std::string &component_type, EntityBase *entity) { + return App.get_name() + component_type + entity->get_object_id(); } DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) { @@ -180,6 +181,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_ msg.device_class = binary_sensor->get_device_class(); msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); msg.disabled_by_default = binary_sensor->is_disabled_by_default(); + msg.icon = binary_sensor->get_icon(); return this->send_list_entities_binary_sensor_response(msg); } #endif @@ -212,6 +214,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) { msg.supports_tilt = traits.get_supports_tilt(); msg.device_class = cover->get_device_class(); msg.disabled_by_default = cover->is_disabled_by_default(); + msg.icon = cover->get_icon(); return this->send_list_entities_cover_response(msg); } void APIConnection::cover_command(const CoverCommandRequest &msg) { @@ -277,6 +280,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) { msg.supports_direction = traits.supports_direction(); msg.supported_speed_count = traits.supported_speed_count(); msg.disabled_by_default = fan->is_disabled_by_default(); + msg.icon = fan->get_icon(); return this->send_list_entities_fan_response(msg); } void APIConnection::fan_command(const FanCommandRequest &msg) { @@ -339,6 +343,7 @@ bool APIConnection::send_light_info(light::LightState *light) { msg.unique_id = get_default_unique_id("light", light); msg.disabled_by_default = light->is_disabled_by_default(); + msg.icon = light->get_icon(); for (auto mode : traits.get_supported_color_modes()) msg.supported_color_modes.push_back(static_cast(mode)); @@ -529,6 +534,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { msg.unique_id = get_default_unique_id("climate", climate); msg.disabled_by_default = climate->is_disabled_by_default(); + msg.icon = climate->get_icon(); msg.supports_current_temperature = traits.get_supports_current_temperature(); msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); @@ -601,7 +607,7 @@ bool APIConnection::send_number_info(number::Number *number) { msg.object_id = number->get_object_id(); msg.name = number->get_name(); msg.unique_id = get_default_unique_id("number", number); - msg.icon = number->traits.get_icon(); + msg.icon = number->get_icon(); msg.disabled_by_default = number->is_disabled_by_default(); msg.min_value = number->traits.get_min_value(); @@ -638,7 +644,7 @@ bool APIConnection::send_select_info(select::Select *select) { msg.object_id = select->get_object_id(); msg.name = select->get_name(); msg.unique_id = get_default_unique_id("select", select); - msg.icon = select->traits.get_icon(); + msg.icon = select->get_icon(); msg.disabled_by_default = select->is_disabled_by_default(); for (const auto &option : select->traits.get_options()) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 580e147245..6a87238186 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -2,7 +2,6 @@ // See scripts/api_protobuf/api_protobuf.py #include "api_pb2.h" #include "esphome/core/log.h" -#include namespace esphome { namespace api { @@ -532,6 +531,10 @@ bool ListEntitiesBinarySensorResponse::decode_length(uint32_t field_id, ProtoLen this->device_class = value.as_string(); return true; } + case 8: { + this->icon = value.as_string(); + return true; + } default: return false; } @@ -554,6 +557,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->device_class); buffer.encode_bool(6, this->is_status_binary_sensor); buffer.encode_bool(7, this->disabled_by_default); + buffer.encode_string(8, this->icon); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { @@ -587,6 +591,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -678,6 +686,10 @@ bool ListEntitiesCoverResponse::decode_length(uint32_t field_id, ProtoLengthDeli this->device_class = value.as_string(); return true; } + case 10: { + this->icon = value.as_string(); + return true; + } default: return false; } @@ -702,6 +714,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->supports_tilt); buffer.encode_string(8, this->device_class); buffer.encode_bool(9, this->disabled_by_default); + buffer.encode_string(10, this->icon); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { @@ -743,6 +756,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -949,6 +966,10 @@ bool ListEntitiesFanResponse::decode_length(uint32_t field_id, ProtoLengthDelimi this->unique_id = value.as_string(); return true; } + case 10: { + this->icon = value.as_string(); + return true; + } default: return false; } @@ -973,6 +994,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->supports_direction); buffer.encode_int32(8, this->supported_speed_count); buffer.encode_bool(9, this->disabled_by_default); + buffer.encode_string(10, this->icon); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesFanResponse::dump_to(std::string &out) const { @@ -1015,6 +1037,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -1263,6 +1289,10 @@ bool ListEntitiesLightResponse::decode_length(uint32_t field_id, ProtoLengthDeli this->effects.push_back(value.as_string()); return true; } + case 14: { + this->icon = value.as_string(); + return true; + } default: return false; } @@ -1303,6 +1333,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(11, it, true); } buffer.encode_bool(13, this->disabled_by_default); + buffer.encode_string(14, this->icon); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLightResponse::dump_to(std::string &out) const { @@ -1366,6 +1397,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -3073,6 +3108,10 @@ bool ListEntitiesClimateResponse::decode_length(uint32_t field_id, ProtoLengthDe this->supported_custom_presets.push_back(value.as_string()); return true; } + case 19: { + this->icon = value.as_string(); + return true; + } default: return false; } @@ -3130,6 +3169,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(17, it, true); } buffer.encode_bool(18, this->disabled_by_default); + buffer.encode_string(19, this->icon); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesClimateResponse::dump_to(std::string &out) const { @@ -3222,6 +3262,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 1371ab5248..13a21c4772 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -269,6 +269,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage { std::string device_class{}; bool is_status_binary_sensor{false}; bool disabled_by_default{false}; + std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -304,6 +305,7 @@ class ListEntitiesCoverResponse : public ProtoMessage { bool supports_tilt{false}; std::string device_class{}; bool disabled_by_default{false}; + std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -360,6 +362,7 @@ class ListEntitiesFanResponse : public ProtoMessage { bool supports_direction{false}; int32_t supported_speed_count{0}; bool disabled_by_default{false}; + std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -424,6 +427,7 @@ class ListEntitiesLightResponse : public ProtoMessage { float max_mireds{0.0f}; std::vector effects{}; bool disabled_by_default{false}; + std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -856,6 +860,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { std::vector supported_presets{}; std::vector supported_custom_presets{}; bool disabled_by_default{false}; + std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 60ac4303a7..ec199cc5fa 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -1,15 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome.cpp_helpers import setup_entity from esphome import automation, core from esphome.automation import Condition, maybe_simple_id from esphome.components import mqtt from esphome.const import ( CONF_DELAY, CONF_DEVICE_CLASS, - CONF_DISABLED_BY_DEFAULT, CONF_FILTERS, CONF_ID, - CONF_INTERNAL, CONF_INVALID_COOLDOWN, CONF_INVERTED, CONF_MAX_LENGTH, @@ -88,7 +87,7 @@ DEVICE_CLASSES = [ IS_PLATFORM_COMPONENT = True binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor") -BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.Nameable) +BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase) BinarySensorInitiallyOff = binary_sensor_ns.class_( "BinarySensorInitiallyOff", BinarySensor ) @@ -314,7 +313,7 @@ def validate_multi_click_timing(value): device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") -BINARY_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(BinarySensor), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id( @@ -375,10 +374,8 @@ BINARY_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).exten async def setup_binary_sensor_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) + if CONF_DEVICE_CLASS in config: cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) if CONF_INVERTED in config: diff --git a/esphome/components/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index 2e1f228be6..41da83aa3e 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -42,7 +42,7 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) { } } std::string BinarySensor::device_class() { return ""; } -BinarySensor::BinarySensor(const std::string &name) : Nameable(name), state(false) {} +BinarySensor::BinarySensor(const std::string &name) : EntityBase(name), state(false) {} BinarySensor::BinarySensor() : BinarySensor("") {} void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } std::string BinarySensor::get_device_class() { diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index 195badd798..9c0d43fa98 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/components/binary_sensor/filter.h" @@ -22,7 +23,7 @@ namespace binary_sensor { * The sub classes should notify the front-end of new states via the publish_state() method which * handles inverted inputs for you. */ -class BinarySensor : public Nameable { +class BinarySensor : public EntityBase { public: explicit BinarySensor(); /** Construct a binary sensor with the specified name diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index c2f07ce423..ca1ea6a756 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -1,14 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome.cpp_helpers import setup_entity from esphome import automation from esphome.components import mqtt from esphome.const import ( CONF_AWAY, CONF_CUSTOM_FAN_MODE, CONF_CUSTOM_PRESET, - CONF_DISABLED_BY_DEFAULT, CONF_ID, - CONF_INTERNAL, CONF_MAX_TEMPERATURE, CONF_MIN_TEMPERATURE, CONF_MODE, @@ -19,7 +18,6 @@ from esphome.const import ( CONF_TEMPERATURE_STEP, CONF_VISUAL, CONF_MQTT_ID, - CONF_NAME, CONF_FAN_MODE, CONF_SWING_MODE, ) @@ -30,7 +28,7 @@ IS_PLATFORM_COMPONENT = True CODEOWNERS = ["@esphome/core"] climate_ns = cg.esphome_ns.namespace("climate") -Climate = climate_ns.class_("Climate", cg.Nameable) +Climate = climate_ns.class_("Climate", cg.EntityBase) ClimateCall = climate_ns.class_("ClimateCall") ClimateTraits = climate_ns.class_("ClimateTraits") @@ -88,7 +86,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True) # Actions ControlAction = climate_ns.class_("ControlAction", automation.Action) -CLIMATE_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( +CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(Climate), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent), @@ -105,10 +103,8 @@ CLIMATE_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ext async def setup_climate_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) + visual = config[CONF_VISUAL] if CONF_MIN_TEMPERATURE in visual: cg.add(var.set_visual_min_temperature_override(visual[CONF_MIN_TEMPERATURE])) diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index a365b933bb..34e6328d8a 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -440,7 +440,7 @@ void Climate::set_visual_max_temperature_override(float visual_max_temperature_o void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) { this->visual_temperature_step_override_ = visual_temperature_step_override; } -Climate::Climate(const std::string &name) : Nameable(name) {} +Climate::Climate(const std::string &name) : EntityBase(name) {} Climate::Climate() : Climate("") {} ClimateCall Climate::make_call() { return ClimateCall(this); } diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 418db02485..852b76686c 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" #include "esphome/core/log.h" @@ -163,7 +164,7 @@ struct ClimateDeviceRestoreState { * mode etc). These are read-only for the user and rw for integrations. The reason these are public * is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_HEAT_COOL) ...` */ -class Climate : public Nameable { +class Climate : public EntityBase { public: /// Construct a climate device with empty name (will be set later). Climate(); diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 46b8906adb..eb57637283 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -4,18 +4,16 @@ from esphome import automation from esphome.automation import maybe_simple_id, Condition from esphome.components import mqtt from esphome.const import ( - CONF_DISABLED_BY_DEFAULT, CONF_ID, - CONF_INTERNAL, CONF_DEVICE_CLASS, CONF_STATE, CONF_POSITION, CONF_TILT, CONF_STOP, CONF_MQTT_ID, - CONF_NAME, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity IS_PLATFORM_COMPONENT = True @@ -36,7 +34,7 @@ DEVICE_CLASSES = [ cover_ns = cg.esphome_ns.namespace("cover") -Cover = cover_ns.class_("Cover", cg.Nameable) +Cover = cover_ns.class_("Cover", cg.EntityBase) COVER_OPEN = cover_ns.COVER_OPEN COVER_CLOSED = cover_ns.COVER_CLOSED @@ -65,7 +63,7 @@ CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action) CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition) CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition) -COVER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( +COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(Cover), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent), @@ -76,10 +74,8 @@ COVER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).exten async def setup_cover_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) + if CONF_DEVICE_CLASS in config: cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 863adb1d81..a8d3d691a4 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -31,7 +31,7 @@ const char *cover_operation_to_str(CoverOperation op) { } } -Cover::Cover(const std::string &name) : Nameable(name), position{COVER_OPEN} {} +Cover::Cover(const std::string &name) : EntityBase(name), position{COVER_OPEN} {} uint32_t Cover::hash_base() { return 1727367479UL; } diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index 8f98a88a42..a67f8d2393 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" #include "cover_traits.h" @@ -107,7 +108,7 @@ const char *cover_operation_to_str(CoverOperation op); * to control all values of the cover. Also implement get_traits() to return what operations * the cover supports. */ -class Cover : public Nameable { +class Cover : public EntityBase { public: explicit Cover(); explicit Cover(const std::string &name); diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index de61ab43cf..7f3aebe238 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -21,7 +21,7 @@ from esphome.components.esp32 import add_idf_sdkconfig_option DEPENDENCIES = ["esp32", "api"] esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") -ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.Nameable) +ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase) ESP32CameraFrameSize = esp32_camera_ns.enum("ESP32CameraFrameSize") FRAME_SIZES = { "160X120": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_160X120, diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 05445f024b..babfda4113 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -172,7 +172,7 @@ void ESP32Camera::framebuffer_task(void *pv) { esp_camera_fb_return(framebuffer); } } -ESP32Camera::ESP32Camera(const std::string &name) : Nameable(name) { +ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { this->config_.pin_pwdn = -1; this->config_.pin_reset = -1; this->config_.pin_xclk = -1; diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index 6246dc2f12..d0445607a4 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -3,6 +3,7 @@ #ifdef USE_ESP32 #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include #include @@ -50,7 +51,7 @@ enum ESP32CameraFrameSize { ESP32_CAMERA_SIZE_1600X1200, // UXGA }; -class ESP32Camera : public Component, public Nameable { +class ESP32Camera : public Component, public EntityBase { public: ESP32Camera(const std::string &name); void set_data_pins(std::array pins); diff --git a/esphome/components/esp32_touch/binary_sensor.py b/esphome/components/esp32_touch/binary_sensor.py index 93640334cd..bd3e06545d 100644 --- a/esphome/components/esp32_touch/binary_sensor.py +++ b/esphome/components/esp32_touch/binary_sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor from esphome.const import ( - CONF_NAME, CONF_PIN, CONF_THRESHOLD, CONF_ID, @@ -55,7 +54,6 @@ async def to_code(config): hub = await cg.get_variable(config[CONF_ESP32_TOUCH_ID]) var = cg.new_Pvariable( config[CONF_ID], - config[CONF_NAME], TOUCH_PADS[config[CONF_PIN]], config[CONF_THRESHOLD], config[CONF_WAKEUP_THRESHOLD], diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 801106ab6c..cb72820900 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -159,9 +159,8 @@ void ESP32TouchComponent::on_shutdown() { } } -ESP32TouchBinarySensor::ESP32TouchBinarySensor(const std::string &name, touch_pad_t touch_pad, uint16_t threshold, - uint16_t wakeup_threshold) - : BinarySensor(name), touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {} +ESP32TouchBinarySensor::ESP32TouchBinarySensor(touch_pad_t touch_pad, uint16_t threshold, uint16_t wakeup_threshold) + : BinarySensor(), touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {} } // namespace esp32_touch } // namespace esphome diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index c584a6d9bc..d49e4703a7 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -64,7 +64,7 @@ class ESP32TouchComponent : public Component { /// Simple helper class to expose a touch pad value as a binary sensor. class ESP32TouchBinarySensor : public binary_sensor::BinarySensor { public: - ESP32TouchBinarySensor(const std::string &name, touch_pad_t touch_pad, uint16_t threshold, uint16_t wakeup_threshold); + ESP32TouchBinarySensor(touch_pad_t touch_pad, uint16_t threshold, uint16_t wakeup_threshold); touch_pad_t get_touch_pad() const { return touch_pad_; } uint16_t get_threshold() const { return threshold_; } diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 15895976d1..52bec3b5b6 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -4,9 +4,7 @@ from esphome import automation from esphome.automation import maybe_simple_id from esphome.components import mqtt from esphome.const import ( - CONF_DISABLED_BY_DEFAULT, CONF_ID, - CONF_INTERNAL, CONF_MQTT_ID, CONF_OSCILLATING, CONF_OSCILLATION_COMMAND_TOPIC, @@ -16,7 +14,6 @@ from esphome.const import ( CONF_SPEED_LEVEL_STATE_TOPIC, CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC, - CONF_NAME, CONF_ON_SPEED_SET, CONF_ON_TURN_OFF, CONF_ON_TURN_ON, @@ -24,11 +21,12 @@ from esphome.const import ( CONF_DIRECTION, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity IS_PLATFORM_COMPONENT = True fan_ns = cg.esphome_ns.namespace("fan") -FanState = fan_ns.class_("FanState", cg.Nameable, cg.Component) +FanState = fan_ns.class_("FanState", cg.EntityBase, cg.Component) MakeFan = cg.Application.struct("MakeFan") FanDirection = fan_ns.enum("FanDirection") @@ -50,7 +48,7 @@ FanSpeedSetTrigger = fan_ns.class_("FanSpeedSetTrigger", automation.Trigger.temp FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template()) FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.template()) -FAN_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( +FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(FanState), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent), @@ -92,10 +90,7 @@ FAN_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( async def setup_fan_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) diff --git a/esphome/components/fan/fan_state.cpp b/esphome/components/fan/fan_state.cpp index 921b8d57e7..6ff4d3a833 100644 --- a/esphome/components/fan/fan_state.cpp +++ b/esphome/components/fan/fan_state.cpp @@ -12,7 +12,7 @@ void FanState::set_traits(const FanTraits &traits) { this->traits_ = traits; } void FanState::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } -FanState::FanState(const std::string &name) : Nameable(name) {} +FanState::FanState(const std::string &name) : EntityBase(name) {} FanStateCall FanState::turn_on() { return this->make_call().set_state(true); } FanStateCall FanState::turn_off() { return this->make_call().set_state(false); } diff --git a/esphome/components/fan/fan_state.h b/esphome/components/fan/fan_state.h index af00275df0..c5a6f59ac4 100644 --- a/esphome/components/fan/fan_state.h +++ b/esphome/components/fan/fan_state.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" #include "esphome/core/log.h" @@ -66,7 +67,7 @@ class FanStateCall { optional direction_{}; }; -class FanState : public Nameable, public Component { +class FanState : public EntityBase, public Component { public: FanState() = default; /// Construct the fan state with name. diff --git a/esphome/components/integration/integration_sensor.h b/esphome/components/integration/integration_sensor.h index a3bdfbbb2b..437649c1dd 100644 --- a/esphome/components/integration/integration_sensor.h +++ b/esphome/components/integration/integration_sensor.h @@ -64,7 +64,6 @@ class IntegrationSensor : public sensor::Sensor, public Component { this->rtc_.save(&result_f); } std::string unit_of_measurement() override; - std::string icon() override { return this->sensor_->get_icon(); } int8_t accuracy_decimals() override { return this->sensor_->get_accuracy_decimals() + 2; } sensor::Sensor *sensor_; diff --git a/esphome/components/integration/sensor.py b/esphome/components/integration/sensor.py index 460dd46619..26c7c2871a 100644 --- a/esphome/components/integration/sensor.py +++ b/esphome/components/integration/sensor.py @@ -2,7 +2,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation from esphome.components import sensor -from esphome.const import CONF_ID, CONF_SENSOR, CONF_RESTORE +from esphome.const import CONF_ICON, CONF_ID, CONF_SENSOR, CONF_RESTORE +from esphome.core.entity_helpers import inherit_property_from integration_ns = cg.esphome_ns.namespace("integration") IntegrationSensor = integration_ns.class_( @@ -29,7 +30,6 @@ CONF_TIME_UNIT = "time_unit" CONF_INTEGRATION_METHOD = "integration_method" CONF_MIN_SAVE_INTERVAL = "min_save_interval" - CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(IntegrationSensor), @@ -46,6 +46,19 @@ CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( ).extend(cv.COMPONENT_SCHEMA) +FINAL_VALIDATE_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(IntegrationSensor), + cv.Optional(CONF_ICON): cv.icon, + cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), + }, + extra=cv.ALLOW_EXTRA, + ), + inherit_property_from(CONF_ICON, CONF_SENSOR), +) + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index 69cb87e539..03224d4c10 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -5,13 +5,10 @@ from esphome.components import mqtt, power_supply from esphome.const import ( CONF_COLOR_CORRECT, CONF_DEFAULT_TRANSITION_LENGTH, - CONF_DISABLED_BY_DEFAULT, CONF_EFFECTS, CONF_FLASH_TRANSITION_LENGTH, CONF_GAMMA_CORRECT, CONF_ID, - CONF_INTERNAL, - CONF_NAME, CONF_MQTT_ID, CONF_POWER_SUPPLY, CONF_RESTORE_MODE, @@ -22,6 +19,7 @@ from esphome.const import ( CONF_WARM_WHITE_COLOR_TEMPERATURE, ) from esphome.core import coroutine_with_priority +from esphome.cpp_helpers import setup_entity from .automation import light_control_to_code # noqa from .effects import ( validate_effects, @@ -54,7 +52,7 @@ RESTORE_MODES = { "RESTORE_INVERTED_DEFAULT_ON": LightRestoreMode.LIGHT_RESTORE_INVERTED_DEFAULT_ON, } -LIGHT_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( +LIGHT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(LightState), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTJSONLightComponent), @@ -126,10 +124,10 @@ def validate_color_temperature_channels(value): async def setup_light_core_(light_var, output_var, config): - cg.add(light_var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) + await setup_entity(light_var, config) + cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE])) - if CONF_INTERNAL in config: - cg.add(light_var.set_internal(config[CONF_INTERNAL])) + if CONF_DEFAULT_TRANSITION_LENGTH in config: cg.add( light_var.set_default_transition_length( @@ -167,7 +165,7 @@ async def setup_light_core_(light_var, output_var, config): async def register_light(output_var, config): - light_var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME], output_var) + light_var = cg.new_Pvariable(config[CONF_ID], output_var) cg.add(cg.App.register_light(light_var)) await cg.register_component(light_var, config) await setup_light_core_(light_var, output_var, config) diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index f888006b17..5f16585c36 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -8,7 +8,8 @@ namespace light { static const char *const TAG = "light"; -LightState::LightState(const std::string &name, LightOutput *output) : Nameable(name), output_(output) {} +LightState::LightState(const std::string &name, LightOutput *output) : EntityBase(name), output_(output) {} +LightState::LightState(LightOutput *output) : output_(output) {} LightTraits LightState::get_traits() { return this->output_->get_traits(); } LightCall LightState::turn_on() { return this->make_call().set_state(true); } diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index f73a4c3b17..ae3711234d 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/optional.h" #include "esphome/core/preferences.h" #include "light_call.h" @@ -26,11 +27,13 @@ enum LightRestoreMode { /** This class represents the communication layer between the front-end MQTT layer and the * hardware output layer. */ -class LightState : public Nameable, public Component { +class LightState : public EntityBase, public Component { public: /// Construct this LightState using the provided traits and name. LightState(const std::string &name, LightOutput *output); + LightState(LightOutput *output); + LightTraits get_traits(); /// Make a light state call diff --git a/esphome/components/light/types.py b/esphome/components/light/types.py index 66329f7cf9..cf544e5435 100644 --- a/esphome/components/light/types.py +++ b/esphome/components/light/types.py @@ -3,7 +3,7 @@ from esphome import automation # Base light_ns = cg.esphome_ns.namespace("light") -LightState = light_ns.class_("LightState", cg.Nameable, cg.Component) +LightState = light_ns.class_("LightState", cg.EntityBase, cg.Component) # Fake class for addressable lights AddressableLightState = light_ns.class_("LightState", LightState) LightOutput = light_ns.class_("LightOutput") diff --git a/esphome/components/mcp3008/mcp3008.cpp b/esphome/components/mcp3008/mcp3008.cpp index bea24b5c6a..81abc4f012 100644 --- a/esphome/components/mcp3008/mcp3008.cpp +++ b/esphome/components/mcp3008/mcp3008.cpp @@ -37,10 +37,8 @@ float MCP3008::read_data(uint8_t pin) { return data / 1023.0f; } -MCP3008Sensor::MCP3008Sensor(MCP3008 *parent, const std::string &name, uint8_t pin, float reference_voltage) - : PollingComponent(1000), parent_(parent), pin_(pin), reference_voltage_(reference_voltage) { - this->set_name(name); -} +MCP3008Sensor::MCP3008Sensor(MCP3008 *parent, uint8_t pin, float reference_voltage) + : PollingComponent(1000), parent_(parent), pin_(pin), reference_voltage_(reference_voltage) {} float MCP3008Sensor::get_setup_priority() const { return setup_priority::DATA; } diff --git a/esphome/components/mcp3008/mcp3008.h b/esphome/components/mcp3008/mcp3008.h index 6f3dc576ea..5d8b823111 100644 --- a/esphome/components/mcp3008/mcp3008.h +++ b/esphome/components/mcp3008/mcp3008.h @@ -26,7 +26,7 @@ class MCP3008 : public Component, class MCP3008Sensor : public PollingComponent, public sensor::Sensor, public voltage_sampler::VoltageSampler { public: - MCP3008Sensor(MCP3008 *parent, const std::string &name, uint8_t pin, float reference_voltage); + MCP3008Sensor(MCP3008 *parent, uint8_t pin, float reference_voltage); void set_reference_voltage(float reference_voltage) { reference_voltage_ = reference_voltage; } void setup() override; diff --git a/esphome/components/mcp3008/sensor.py b/esphome/components/mcp3008/sensor.py index 4fc9b83afb..d4b9e979ce 100644 --- a/esphome/components/mcp3008/sensor.py +++ b/esphome/components/mcp3008/sensor.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, voltage_sampler -from esphome.const import CONF_ID, CONF_NUMBER, CONF_NAME +from esphome.const import CONF_ID, CONF_NUMBER from . import mcp3008_ns, MCP3008 AUTO_LOAD = ["voltage_sampler"] @@ -29,7 +29,6 @@ async def to_code(config): var = cg.new_Pvariable( config[CONF_ID], parent, - config[CONF_NAME], config[CONF_NUMBER], config[CONF_REFERENCE_VOLTAGE], ) diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index d7322298bb..188df0f7b9 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -10,6 +10,7 @@ namespace mqtt { static const char *const TAG = "mqtt.binary_sensor"; std::string MQTTBinarySensorComponent::component_type() const { return "binary_sensor"; } +const EntityBase *MQTTBinarySensorComponent::get_entity() const { return this->binary_sensor_; } void MQTTBinarySensorComponent::setup() { this->binary_sensor_->add_on_state_callback([this](bool state) { this->publish_state(state); }); @@ -25,7 +26,6 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor this->set_custom_state_topic(mqtt::global_mqtt_client->get_availability().topic); } } -std::string MQTTBinarySensorComponent::friendly_name() const { return this->binary_sensor_->get_name(); } void MQTTBinarySensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (!this->binary_sensor_->get_device_class().empty()) @@ -43,7 +43,6 @@ bool MQTTBinarySensorComponent::send_initial_state() { return true; } } -bool MQTTBinarySensorComponent::is_internal() { return this->binary_sensor_->is_internal(); } bool MQTTBinarySensorComponent::publish_state(bool state) { if (this->binary_sensor_->is_status_binary_sensor()) return true; diff --git a/esphome/components/mqtt/mqtt_binary_sensor.h b/esphome/components/mqtt/mqtt_binary_sensor.h index c459bea1f7..0efb490367 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.h +++ b/esphome/components/mqtt/mqtt_binary_sensor.h @@ -28,11 +28,10 @@ class MQTTBinarySensorComponent : public mqtt::MQTTComponent { bool send_initial_state() override; bool publish_state(bool state); - bool is_internal() override; protected: - std::string friendly_name() const override; std::string component_type() const override; + const EntityBase *get_entity() const override; binary_sensor::BinarySensor *binary_sensor_; }; diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 8519e296b0..47b6684dec 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -212,9 +212,9 @@ void MQTTClimateComponent::setup() { } MQTTClimateComponent::MQTTClimateComponent(Climate *device) : device_(device) {} bool MQTTClimateComponent::send_initial_state() { return this->publish_state_(); } -bool MQTTClimateComponent::is_internal() { return this->device_->is_internal(); } std::string MQTTClimateComponent::component_type() const { return "climate"; } -std::string MQTTClimateComponent::friendly_name() const { return this->device_->get_name(); } +const EntityBase *MQTTClimateComponent::get_entity() const { return this->device_; } + bool MQTTClimateComponent::publish_state_() { auto traits = this->device_->get_traits(); // mode diff --git a/esphome/components/mqtt/mqtt_climate.h b/esphome/components/mqtt/mqtt_climate.h index 8b8a8e866e..40ac4c18c1 100644 --- a/esphome/components/mqtt/mqtt_climate.h +++ b/esphome/components/mqtt/mqtt_climate.h @@ -16,7 +16,6 @@ class MQTTClimateComponent : public mqtt::MQTTComponent { MQTTClimateComponent(climate::Climate *device); void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; - bool is_internal() override; std::string component_type() const override; void setup() override; @@ -38,7 +37,7 @@ class MQTTClimateComponent : public mqtt::MQTTComponent { MQTT_COMPONENT_CUSTOM_TOPIC(swing_mode, command) protected: - std::string friendly_name() const override; + const EntityBase *get_entity() const override; bool publish_state_(); diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 96cda57914..0ece4b3501 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -68,8 +68,13 @@ bool MQTTComponent::send_discovery_() { this->send_discovery(root, config); - std::string name = this->friendly_name(); - root["name"] = name; + // Fields from EntityBase + root["name"] = this->friendly_name(); + if (this->is_disabled_by_default()) + root["enabled_by_default"] = false; + if (!this->get_icon().empty()) + root["icon"] = this->get_icon(); + if (config.state_topic) root["state_topic"] = this->get_state_topic_(); if (config.command_topic) @@ -199,6 +204,12 @@ void MQTTComponent::schedule_resend_state() { this->resend_state_ = true; } std::string MQTTComponent::unique_id() { return ""; } bool MQTTComponent::is_connected_() const { return global_mqtt_client->is_connected(); } +// Pull these properties from EntityBase if not overridden +std::string MQTTComponent::friendly_name() const { return this->get_entity()->get_name(); } +std::string MQTTComponent::get_icon() const { return this->get_entity()->get_icon(); } +bool MQTTComponent::is_disabled_by_default() const { return this->get_entity()->is_disabled_by_default(); } +bool MQTTComponent::is_internal() { return this->get_entity()->is_internal(); } + } // namespace mqtt } // namespace esphome diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index f07e752e02..657ab7b608 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -7,6 +7,7 @@ #include #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "mqtt_client.h" namespace esphome { @@ -73,7 +74,7 @@ class MQTTComponent : public Component { virtual bool send_initial_state() = 0; - virtual bool is_internal() = 0; + virtual bool is_internal(); /// Set whether state message should be retained. void set_retain(bool retain); @@ -148,8 +149,10 @@ class MQTTComponent : public Component { */ std::string get_default_topic_for_(const std::string &suffix) const; - /// Get the friendly name of this MQTT component. - virtual std::string friendly_name() const = 0; + /** + * Gets the Entity served by this MQTT component. + */ + virtual const EntityBase *get_entity() const = 0; /** A unique ID for this MQTT component, empty for no unique id. See unique ID requirements: * https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements @@ -158,6 +161,15 @@ class MQTTComponent : public Component { */ virtual std::string unique_id(); + /// Get the friendly name of this MQTT component. + virtual std::string friendly_name() const; + + /// Get the icon field of this component + virtual std::string get_icon() const; + + /// Get whether the underlying Entity is disabled by default + virtual bool is_disabled_by_default() const; + /// Get the MQTT topic that new states will be shared to. const std::string get_state_topic_() const; diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index 61c8fa2d94..e8bc7f0e30 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -84,9 +84,9 @@ void MQTTCoverComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCon } std::string MQTTCoverComponent::component_type() const { return "cover"; } -std::string MQTTCoverComponent::friendly_name() const { return this->cover_->get_name(); } +const EntityBase *MQTTCoverComponent::get_entity() const { return this->cover_; } + bool MQTTCoverComponent::send_initial_state() { return this->publish_state(); } -bool MQTTCoverComponent::is_internal() { return this->cover_->is_internal(); } bool MQTTCoverComponent::publish_state() { auto traits = this->cover_->get_traits(); bool success = true; diff --git a/esphome/components/mqtt/mqtt_cover.h b/esphome/components/mqtt/mqtt_cover.h index b8c9f2617c..149d46ac85 100644 --- a/esphome/components/mqtt/mqtt_cover.h +++ b/esphome/components/mqtt/mqtt_cover.h @@ -24,7 +24,6 @@ class MQTTCoverComponent : public mqtt::MQTTComponent { MQTT_COMPONENT_CUSTOM_TOPIC(tilt, state) bool send_initial_state() override; - bool is_internal() override; bool publish_state(); @@ -32,7 +31,7 @@ class MQTTCoverComponent : public mqtt::MQTTComponent { protected: std::string component_type() const override; - std::string friendly_name() const override; + const EntityBase *get_entity() const override; cover::Cover *cover_; }; diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index ed1ab605aa..898183cc58 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -16,6 +16,7 @@ MQTTFanComponent::MQTTFanComponent(FanState *state) : MQTTComponent(), state_(st FanState *MQTTFanComponent::get_state() const { return this->state_; } std::string MQTTFanComponent::component_type() const { return "fan"; } +const EntityBase *MQTTFanComponent::get_entity() const { return this->state_; } void MQTTFanComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { @@ -113,7 +114,7 @@ void MQTTFanComponent::dump_config() { } bool MQTTFanComponent::send_initial_state() { return this->publish_state(); } -std::string MQTTFanComponent::friendly_name() const { return this->state_->get_name(); } + void MQTTFanComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (this->state_->get_traits().supports_oscillation()) { root["oscillation_command_topic"] = this->get_oscillation_command_topic(); @@ -126,7 +127,6 @@ void MQTTFanComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfi root["speed_state_topic"] = this->get_speed_state_topic(); } } -bool MQTTFanComponent::is_internal() { return this->state_->is_internal(); } bool MQTTFanComponent::publish_state() { const char *state_s = this->state_->state ? "ON" : "OFF"; ESP_LOGD(TAG, "'%s' Sending state %s.", this->state_->get_name().c_str(), state_s); diff --git a/esphome/components/mqtt/mqtt_fan.h b/esphome/components/mqtt/mqtt_fan.h index 00263e13eb..a160d5366b 100644 --- a/esphome/components/mqtt/mqtt_fan.h +++ b/esphome/components/mqtt/mqtt_fan.h @@ -39,10 +39,8 @@ class MQTTFanComponent : public mqtt::MQTTComponent { fan::FanState *get_state() const; - bool is_internal() override; - protected: - std::string friendly_name() const override; + const EntityBase *get_entity() const override; fan::FanState *state_; }; diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index d028b1b037..a88358a6b2 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -13,6 +13,7 @@ static const char *const TAG = "mqtt.light"; using namespace esphome::light; std::string MQTTJSONLightComponent::component_type() const { return "light"; } +const EntityBase *MQTTJSONLightComponent::get_entity() const { return this->state_; } void MQTTJSONLightComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject &root) { @@ -32,7 +33,7 @@ bool MQTTJSONLightComponent::publish_state_() { [this](JsonObject &root) { LightJSONSchema::dump_json(*this->state_, root); }); } LightState *MQTTJSONLightComponent::get_state() const { return this->state_; } -std::string MQTTJSONLightComponent::friendly_name() const { return this->state_->get_name(); } + void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { root["schema"] = "json"; auto traits = this->state_->get_traits(); @@ -70,7 +71,6 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover } } bool MQTTJSONLightComponent::send_initial_state() { return this->publish_state_(); } -bool MQTTJSONLightComponent::is_internal() { return this->state_->is_internal(); } void MQTTJSONLightComponent::dump_config() { ESP_LOGCONFIG(TAG, "MQTT Light '%s':", this->state_->get_name().c_str()); LOG_MQTT_COMPONENT(true, true) diff --git a/esphome/components/mqtt/mqtt_light.h b/esphome/components/mqtt/mqtt_light.h index b0f3145900..192cba39b6 100644 --- a/esphome/components/mqtt/mqtt_light.h +++ b/esphome/components/mqtt/mqtt_light.h @@ -25,11 +25,9 @@ class MQTTJSONLightComponent : public mqtt::MQTTComponent { bool send_initial_state() override; - bool is_internal() override; - protected: - std::string friendly_name() const override; std::string component_type() const override; + const EntityBase *get_entity() const override; bool publish_state_(); diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index faa38056a9..674fd77bdf 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -33,13 +33,11 @@ void MQTTNumberComponent::dump_config() { } std::string MQTTNumberComponent::component_type() const { return "number"; } +const EntityBase *MQTTNumberComponent::get_entity() const { return this->number_; } -std::string MQTTNumberComponent::friendly_name() const { return this->number_->get_name(); } void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { const auto &traits = number_->traits; // https://www.home-assistant.io/integrations/number.mqtt/ - if (!traits.get_icon().empty()) - root["icon"] = traits.get_icon(); root["min"] = traits.get_min_value(); root["max"] = traits.get_max_value(); root["step"] = traits.get_step(); @@ -53,7 +51,6 @@ bool MQTTNumberComponent::send_initial_state() { return true; } } -bool MQTTNumberComponent::is_internal() { return this->number_->is_internal(); } bool MQTTNumberComponent::publish_state(float value) { char buffer[64]; snprintf(buffer, sizeof(buffer), "%f", value); diff --git a/esphome/components/mqtt/mqtt_number.h b/esphome/components/mqtt/mqtt_number.h index 46dc221e9e..66622d7c29 100644 --- a/esphome/components/mqtt/mqtt_number.h +++ b/esphome/components/mqtt/mqtt_number.h @@ -28,15 +28,13 @@ class MQTTNumberComponent : public mqtt::MQTTComponent { void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; - bool is_internal() override; bool publish_state(float value); protected: /// Override for MQTTComponent, returns "number". std::string component_type() const override; - - std::string friendly_name() const override; + const EntityBase *get_entity() const override; number::Number *number_; }; diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index 467a0cd84c..b499636006 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -28,13 +28,11 @@ void MQTTSelectComponent::dump_config() { } std::string MQTTSelectComponent::component_type() const { return "select"; } +const EntityBase *MQTTSelectComponent::get_entity() const { return this->select_; } -std::string MQTTSelectComponent::friendly_name() const { return this->select_->get_name(); } void MQTTSelectComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { const auto &traits = select_->traits; // https://www.home-assistant.io/integrations/select.mqtt/ - if (!traits.get_icon().empty()) - root["icon"] = traits.get_icon(); JsonArray &options = root.createNestedArray("options"); for (const auto &option : traits.get_options()) options.add(option); @@ -48,7 +46,6 @@ bool MQTTSelectComponent::send_initial_state() { return true; } } -bool MQTTSelectComponent::is_internal() { return this->select_->is_internal(); } bool MQTTSelectComponent::publish_state(const std::string &value) { return this->publish(this->get_state_topic_(), value); } diff --git a/esphome/components/mqtt/mqtt_select.h b/esphome/components/mqtt/mqtt_select.h index 0115c0a41e..d77d0cf513 100644 --- a/esphome/components/mqtt/mqtt_select.h +++ b/esphome/components/mqtt/mqtt_select.h @@ -28,15 +28,13 @@ class MQTTSelectComponent : public mqtt::MQTTComponent { void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; - bool is_internal() override; bool publish_state(const std::string &value); protected: /// Override for MQTTComponent, returns "select". std::string component_type() const override; - - std::string friendly_name() const override; + const EntityBase *get_entity() const override; select::Select *select_; }; diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index 72ec7c54ee..78710ff403 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -30,6 +30,7 @@ void MQTTSensorComponent::dump_config() { } std::string MQTTSensorComponent::component_type() const { return "sensor"; } +const EntityBase *MQTTSensorComponent::get_entity() const { return this->sensor_; } uint32_t MQTTSensorComponent::get_expire_after() const { if (this->expire_after_.has_value()) @@ -38,7 +39,7 @@ uint32_t MQTTSensorComponent::get_expire_after() const { } void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire_after_ = expire_after; } void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } -std::string MQTTSensorComponent::friendly_name() const { return this->sensor_->get_name(); } + void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (!this->sensor_->get_device_class().empty()) root["device_class"] = this->sensor_->get_device_class(); @@ -49,9 +50,6 @@ void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo if (this->get_expire_after() > 0) root["expire_after"] = this->get_expire_after() / 1000; - if (!this->sensor_->get_icon().empty()) - root["icon"] = this->sensor_->get_icon(); - if (this->sensor_->get_force_update()) root["force_update"] = true; @@ -67,7 +65,6 @@ bool MQTTSensorComponent::send_initial_state() { return true; } } -bool MQTTSensorComponent::is_internal() { return this->sensor_->is_internal(); } bool MQTTSensorComponent::publish_state(float value) { int8_t accuracy = this->sensor_->get_accuracy_decimals(); return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy)); diff --git a/esphome/components/mqtt/mqtt_sensor.h b/esphome/components/mqtt/mqtt_sensor.h index 2385529f4f..22609fdfef 100644 --- a/esphome/components/mqtt/mqtt_sensor.h +++ b/esphome/components/mqtt/mqtt_sensor.h @@ -41,14 +41,11 @@ class MQTTSensorComponent : public mqtt::MQTTComponent { bool publish_state(float value); bool send_initial_state() override; - bool is_internal() override; protected: /// Override for MQTTComponent, returns "sensor". std::string component_type() const override; - - std::string friendly_name() const override; - + const EntityBase *get_entity() const override; std::string unique_id() override; sensor::Sensor *sensor_; diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index b13ddd5d9d..16cf102f7e 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -41,15 +41,13 @@ void MQTTSwitchComponent::dump_config() { } std::string MQTTSwitchComponent::component_type() const { return "switch"; } +const EntityBase *MQTTSwitchComponent::get_entity() const { return this->switch_; } void MQTTSwitchComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { - if (!this->switch_->get_icon().empty()) - root["icon"] = this->switch_->get_icon(); if (this->switch_->assumed_state()) root["optimistic"] = true; } bool MQTTSwitchComponent::send_initial_state() { return this->publish_state(this->switch_->state); } -bool MQTTSwitchComponent::is_internal() { return this->switch_->is_internal(); } -std::string MQTTSwitchComponent::friendly_name() const { return this->switch_->get_name(); } + bool MQTTSwitchComponent::publish_state(bool state) { const char *state_s = state ? "ON" : "OFF"; return this->publish(this->get_state_topic_(), state_s); diff --git a/esphome/components/mqtt/mqtt_switch.h b/esphome/components/mqtt/mqtt_switch.h index 9959d21872..a0a7a23220 100644 --- a/esphome/components/mqtt/mqtt_switch.h +++ b/esphome/components/mqtt/mqtt_switch.h @@ -23,15 +23,13 @@ class MQTTSwitchComponent : public mqtt::MQTTComponent { void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; - bool is_internal() override; bool publish_state(bool state); protected: - std::string friendly_name() const override; - /// "switch" component type. std::string component_type() const override; + const EntityBase *get_entity() const override; switch_::Switch *switch_; }; diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index fd96cd0902..7b89915649 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -13,9 +13,6 @@ using namespace esphome::text_sensor; MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : MQTTComponent(), sensor_(sensor) {} void MQTTTextSensor::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { - if (!this->sensor_->get_icon().empty()) - root["icon"] = this->sensor_->get_icon(); - config.command_topic = false; } void MQTTTextSensor::setup() { @@ -35,9 +32,8 @@ bool MQTTTextSensor::send_initial_state() { return true; } } -bool MQTTTextSensor::is_internal() { return this->sensor_->is_internal(); } std::string MQTTTextSensor::component_type() const { return "sensor"; } -std::string MQTTTextSensor::friendly_name() const { return this->sensor_->get_name(); } +const EntityBase *MQTTTextSensor::get_entity() const { return this->sensor_; } std::string MQTTTextSensor::unique_id() { return this->sensor_->unique_id(); } } // namespace mqtt diff --git a/esphome/components/mqtt/mqtt_text_sensor.h b/esphome/components/mqtt/mqtt_text_sensor.h index 1d3f95b894..83743245cc 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.h +++ b/esphome/components/mqtt/mqtt_text_sensor.h @@ -25,13 +25,9 @@ class MQTTTextSensor : public mqtt::MQTTComponent { bool send_initial_state() override; - bool is_internal() override; - protected: std::string component_type() const override; - - std::string friendly_name() const override; - + const EntityBase *get_entity() const override; std::string unique_id() override; text_sensor::TextSensor *sensor_; diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 88153492d3..2856a25ee7 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -6,25 +6,21 @@ from esphome.components import mqtt from esphome.const import ( CONF_ABOVE, CONF_BELOW, - CONF_DISABLED_BY_DEFAULT, - CONF_ICON, CONF_ID, - CONF_INTERNAL, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, CONF_TRIGGER_ID, - CONF_NAME, CONF_MQTT_ID, CONF_VALUE, - ICON_EMPTY, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True number_ns = cg.esphome_ns.namespace("number") -Number = number_ns.class_("Number", cg.Nameable) +Number = number_ns.class_("Number", cg.EntityBase) NumberPtr = Number.operator("ptr") # Triggers @@ -46,11 +42,10 @@ NumberInRangeCondition = number_ns.class_( icon = cv.icon -NUMBER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent), cv.GenerateID(): cv.declare_id(Number), - cv.Optional(CONF_ICON, default=ICON_EMPTY): icon, cv.Optional(CONF_ON_VALUE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(NumberStateTrigger), @@ -71,12 +66,8 @@ NUMBER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( async def setup_number_core_( var, config, *, min_value: float, max_value: float, step: Optional[float] ): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) - cg.add(var.traits.set_icon(config[CONF_ICON])) cg.add(var.traits.set_min_value(min_value)) cg.add(var.traits.set_max_value(max_value)) if step is not None: diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index 945f174510..ed104fb477 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" namespace esphome { @@ -9,8 +10,8 @@ namespace number { #define LOG_NUMBER(prefix, type, obj) \ if ((obj) != nullptr) { \ ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ - if (!(obj)->traits.get_icon().empty()) { \ - ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->traits.get_icon().c_str()); \ + if (!(obj)->get_icon().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ } \ } @@ -40,21 +41,18 @@ class NumberTraits { float get_max_value() const { return max_value_; } void set_step(float step) { step_ = step; } float get_step() const { return step_; } - void set_icon(std::string icon) { icon_ = std::move(icon); } - const std::string &get_icon() const { return icon_; } protected: float min_value_ = NAN; float max_value_ = NAN; float step_ = NAN; - std::string icon_; }; /** Base-class for all numbers. * * A number can use publish_state to send out a new value. */ -class Number : public Nameable { +class Number : public EntityBase { public: float state; diff --git a/esphome/components/remote_receiver/binary_sensor.py b/esphome/components/remote_receiver/binary_sensor.py index 62c1d9cb27..218b40d6cc 100644 --- a/esphome/components/remote_receiver/binary_sensor.py +++ b/esphome/components/remote_receiver/binary_sensor.py @@ -1,6 +1,4 @@ -import esphome.codegen as cg from esphome.components import binary_sensor, remote_base -from esphome.const import CONF_NAME DEPENDENCIES = ["remote_receiver"] @@ -9,5 +7,4 @@ CONFIG_SCHEMA = remote_base.validate_binary_sensor async def to_code(config): var = await remote_base.build_binary_sensor(config) - cg.add(var.set_name(config[CONF_NAME])) await binary_sensor.register_binary_sensor(var, config) diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index d3ab344926..c156a63a86 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -4,24 +4,20 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import mqtt from esphome.const import ( - CONF_DISABLED_BY_DEFAULT, - CONF_ICON, CONF_ID, - CONF_INTERNAL, CONF_ON_VALUE, CONF_OPTION, CONF_TRIGGER_ID, - CONF_NAME, CONF_MQTT_ID, - ICON_EMPTY, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True select_ns = cg.esphome_ns.namespace("select") -Select = select_ns.class_("Select", cg.Nameable) +Select = select_ns.class_("Select", cg.EntityBase) SelectPtr = Select.operator("ptr") # Triggers @@ -35,11 +31,10 @@ SelectSetAction = select_ns.class_("SelectSetAction", automation.Action) icon = cv.icon -SELECT_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent), cv.GenerateID(): cv.declare_id(Select), - cv.Optional(CONF_ICON, default=ICON_EMPTY): icon, cv.Optional(CONF_ON_VALUE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SelectStateTrigger), @@ -50,12 +45,8 @@ SELECT_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( async def setup_select_core_(var, config, *, options: List[str]): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) - cg.add(var.traits.set_icon(config[CONF_ICON])) cg.add(var.traits.set_options(options)) for conf in config.get(CONF_ON_VALUE, []): diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index 0dec74b627..6113cca1fd 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -3,6 +3,7 @@ #include #include #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" namespace esphome { @@ -11,8 +12,8 @@ namespace select { #define LOG_SELECT(prefix, type, obj) \ if ((obj) != nullptr) { \ ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ - if (!(obj)->traits.get_icon().empty()) { \ - ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->traits.get_icon().c_str()); \ + if (!(obj)->get_icon().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ } \ } @@ -38,19 +39,16 @@ class SelectTraits { public: void set_options(std::vector options) { this->options_ = std::move(options); } const std::vector get_options() const { return this->options_; } - void set_icon(std::string icon) { icon_ = std::move(icon); } - const std::string &get_icon() const { return icon_; } protected: std::vector options_; - std::string icon_; }; /** Base-class for all selects. * * A select can use publish_state to send out a new value. */ -class Select : public Nameable { +class Select : public EntityBase { public: std::string state; diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index ce4aafb3b0..cb74a41119 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -10,13 +10,11 @@ from esphome.const import ( CONF_ACCURACY_DECIMALS, CONF_ALPHA, CONF_BELOW, - CONF_DISABLED_BY_DEFAULT, CONF_EXPIRE_AFTER, CONF_FILTERS, CONF_FROM, CONF_ICON, CONF_ID, - CONF_INTERNAL, CONF_ON_RAW_VALUE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, @@ -27,7 +25,6 @@ from esphome.const import ( CONF_TRIGGER_ID, CONF_UNIT_OF_MEASUREMENT, CONF_WINDOW_SIZE, - CONF_NAME, CONF_MQTT_ID, CONF_FORCE_UPDATE, DEVICE_CLASS_EMPTY, @@ -59,6 +56,7 @@ from esphome.const import ( DEVICE_CLASS_VOLTAGE, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity from esphome.util import Registry CODEOWNERS = ["@esphome/core"] @@ -136,7 +134,7 @@ def validate_datapoint(value): # Base sensor_ns = cg.esphome_ns.namespace("sensor") -Sensor = sensor_ns.class_("Sensor", cg.Nameable) +Sensor = sensor_ns.class_("Sensor", cg.EntityBase) SensorPtr = Sensor.operator("ptr") # Triggers @@ -180,12 +178,11 @@ validate_accuracy_decimals = cv.int_ validate_icon = cv.icon validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") -SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSensorComponent), cv.GenerateID(): cv.declare_id(Sensor), cv.Optional(CONF_UNIT_OF_MEASUREMENT): validate_unit_of_measurement, - cv.Optional(CONF_ICON): validate_icon, cv.Optional(CONF_ACCURACY_DECIMALS): validate_accuracy_decimals, cv.Optional(CONF_DEVICE_CLASS): validate_device_class, cv.Optional(CONF_STATE_CLASS): validate_state_class, @@ -495,18 +492,14 @@ async def build_filters(config): async def setup_sensor_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) + if CONF_DEVICE_CLASS in config: cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) if CONF_STATE_CLASS in config: cg.add(var.set_state_class(config[CONF_STATE_CLASS])) if CONF_UNIT_OF_MEASUREMENT in config: cg.add(var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT])) - if CONF_ICON in config: - cg.add(var.set_icon(config[CONF_ICON])) if CONF_ACCURACY_DECIMALS in config: cg.add(var.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS])) cg.add(var.set_force_update(config[CONF_FORCE_UPDATE])) diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 0dc0275715..793ae170c3 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -18,7 +18,7 @@ std::string state_class_to_string(StateClass state_class) { } } -Sensor::Sensor(const std::string &name) : Nameable(name), state(NAN), raw_state(NAN) {} +Sensor::Sensor(const std::string &name) : EntityBase(name), state(NAN), raw_state(NAN) {} Sensor::Sensor() : Sensor("") {} std::string Sensor::get_unit_of_measurement() { @@ -31,14 +31,6 @@ void Sensor::set_unit_of_measurement(const std::string &unit_of_measurement) { } std::string Sensor::unit_of_measurement() { return ""; } -std::string Sensor::get_icon() { - if (this->icon_.has_value()) - return *this->icon_; - return this->icon(); -} -void Sensor::set_icon(const std::string &icon) { this->icon_ = icon; } -std::string Sensor::icon() { return ""; } - int8_t Sensor::get_accuracy_decimals() { if (this->accuracy_decimals_.has_value()) return *this->accuracy_decimals_; diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index d284f931b1..6cab46f7f9 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -2,6 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/components/sensor/filter.h" @@ -43,7 +44,7 @@ std::string state_class_to_string(StateClass state_class); * * A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy. */ -class Sensor : public Nameable { +class Sensor : public EntityBase { public: explicit Sensor(); explicit Sensor(const std::string &name); @@ -53,11 +54,6 @@ class Sensor : public Nameable { /// Manually set the unit of measurement. void set_unit_of_measurement(const std::string &unit_of_measurement); - /// Get the icon. Uses the manual override if specified or the default value instead. - std::string get_icon(); - /// Manually set the icon, for example "mdi:flash". - void set_icon(const std::string &icon); - /// Get the accuracy in decimals, using the manual override if set. int8_t get_accuracy_decimals(); /// Manually set the accuracy in decimals. @@ -157,9 +153,6 @@ class Sensor : public Nameable { /// Override this to set the default unit of measurement. virtual std::string unit_of_measurement(); // NOLINT - /// Override this to set the default icon. - virtual std::string icon(); // NOLINT - /// Override this to set the default accuracy in decimals. virtual int8_t accuracy_decimals(); // NOLINT @@ -178,7 +171,6 @@ class Sensor : public Nameable { Filter *filter_list_{nullptr}; ///< Store all active filters. optional unit_of_measurement_; ///< Unit of measurement override - optional icon_; ///< Icon override optional accuracy_decimals_; ///< Accuracy in decimals override optional device_class_; ///< Device class override optional state_class_{STATE_CLASS_NONE}; ///< State class override diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py index 8aa213a9f6..88341e0add 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -4,24 +4,21 @@ from esphome import automation from esphome.automation import Condition, maybe_simple_id from esphome.components import mqtt from esphome.const import ( - CONF_DISABLED_BY_DEFAULT, - CONF_ICON, CONF_ID, - CONF_INTERNAL, CONF_INVERTED, CONF_ON_TURN_OFF, CONF_ON_TURN_ON, CONF_TRIGGER_ID, CONF_MQTT_ID, - CONF_NAME, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True switch_ns = cg.esphome_ns.namespace("switch_") -Switch = switch_ns.class_("Switch", cg.Nameable) +Switch = switch_ns.class_("Switch", cg.EntityBase) SwitchPtr = Switch.operator("ptr") ToggleAction = switch_ns.class_("ToggleAction", automation.Action) @@ -39,10 +36,9 @@ SwitchTurnOffTrigger = switch_ns.class_( icon = cv.icon -SWITCH_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( +SWITCH_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSwitchComponent), - cv.Optional(CONF_ICON): icon, cv.Optional(CONF_INVERTED): cv.boolean, cv.Optional(CONF_ON_TURN_ON): automation.validate_automation( { @@ -59,12 +55,8 @@ SWITCH_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).exte async def setup_switch_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) - if CONF_ICON in config: - cg.add(var.set_icon(config[CONF_ICON])) + await setup_entity(var, config) + if CONF_INVERTED in config: cg.add(var.set_inverted(config[CONF_INVERTED])) for conf in config.get(CONF_ON_TURN_ON, []): diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index 0e12f5af0f..e4d20719e1 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -6,17 +6,9 @@ namespace switch_ { static const char *const TAG = "switch"; -std::string Switch::icon() { return ""; } -Switch::Switch(const std::string &name) : Nameable(name), state(false) {} +Switch::Switch(const std::string &name) : EntityBase(name), state(false) {} Switch::Switch() : Switch("") {} -std::string Switch::get_icon() { - if (this->icon_.has_value()) - return *this->icon_; - return this->icon(); -} - -void Switch::set_icon(const std::string &icon) { this->icon_ = icon; } void Switch::turn_on() { ESP_LOGD(TAG, "'%s' Turning ON.", this->get_name().c_str()); this->write_state(!this->inverted_); diff --git a/esphome/components/switch/switch.h b/esphome/components/switch/switch.h index 8cfae3b6f8..071393003a 100644 --- a/esphome/components/switch/switch.h +++ b/esphome/components/switch/switch.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/preferences.h" #include "esphome/core/helpers.h" @@ -26,7 +27,7 @@ namespace switch_ { * A switch is basically just a combination of a binary sensor (for reporting switch values) * and a write_state method that writes a state to the hardware. */ -class Switch : public Nameable { +class Switch : public EntityBase { public: explicit Switch(); explicit Switch(const std::string &name); @@ -70,12 +71,6 @@ class Switch : public Nameable { */ void set_inverted(bool inverted); - /// Set the icon for this switch. "" for no icon. - void set_icon(const std::string &icon); - - /// Get the icon for this switch. Using icon() if not manually set - std::string get_icon(); - /** Set callback for state changes. * * @param callback The void(bool) callback. @@ -104,18 +99,8 @@ class Switch : public Nameable { */ virtual void write_state(bool state) = 0; - /** Override this to set the Home Assistant icon for this switch. - * - * Return "" to disable this feature. - * - * @return The icon of this switch, for example "mdi:fan". - */ - virtual std::string icon(); // NOLINT - uint32_t hash_base() override; - optional icon_{}; ///< The icon shown here. Not set means use default from switch. Empty means no icon. - CallbackManager state_callback_{}; bool inverted_{false}; Deduplicator publish_dedup_; diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index cdb4b85e9a..5c739e1d0a 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -3,21 +3,18 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import mqtt from esphome.const import ( - CONF_DISABLED_BY_DEFAULT, CONF_FILTERS, - CONF_ICON, CONF_ID, - CONF_INTERNAL, CONF_ON_VALUE, CONF_ON_RAW_VALUE, CONF_TRIGGER_ID, CONF_MQTT_ID, - CONF_NAME, CONF_STATE, CONF_FROM, CONF_TO, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity from esphome.util import Registry @@ -25,7 +22,7 @@ IS_PLATFORM_COMPONENT = True # pylint: disable=invalid-name text_sensor_ns = cg.esphome_ns.namespace("text_sensor") -TextSensor = text_sensor_ns.class_("TextSensor", cg.Nameable) +TextSensor = text_sensor_ns.class_("TextSensor", cg.EntityBase) TextSensorPtr = TextSensor.operator("ptr") TextSensorStateTrigger = text_sensor_ns.class_( @@ -111,10 +108,9 @@ async def substitute_filter_to_code(config, filter_id): icon = cv.icon -TEXT_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor), - cv.Optional(CONF_ICON): icon, cv.Optional(CONF_FILTERS): validate_filters, cv.Optional(CONF_ON_VALUE): automation.validate_automation( { @@ -137,12 +133,7 @@ async def build_filters(config): async def setup_text_sensor_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) - if CONF_ICON in config: - cg.add(var.set_icon(config[CONF_ICON])) + await setup_entity(var, config) if config.get(CONF_FILTERS): # must exist and not be empty filters = await build_filters(config[CONF_FILTERS]) diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 774f3a8cb6..0bcab90843 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -7,7 +7,7 @@ namespace text_sensor { static const char *const TAG = "text_sensor"; TextSensor::TextSensor() : TextSensor("") {} -TextSensor::TextSensor(const std::string &name) : Nameable(name) {} +TextSensor::TextSensor(const std::string &name) : EntityBase(name) {} void TextSensor::publish_state(const std::string &state) { this->raw_state = state; @@ -68,14 +68,6 @@ void TextSensor::internal_send_state_to_frontend(const std::string &state) { this->callback_.call(state); } -void TextSensor::set_icon(const std::string &icon) { this->icon_ = icon; } -std::string TextSensor::get_icon() { - if (this->icon_.has_value()) - return *this->icon_; - return this->icon(); -} -std::string TextSensor::icon() { return ""; } - std::string TextSensor::unique_id() { return ""; } bool TextSensor::has_state() { return this->has_state_; } uint32_t TextSensor::hash_base() { return 334300109UL; } diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index 7804deedb6..4bd77131d7 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/components/text_sensor/filter.h" @@ -18,7 +19,7 @@ namespace text_sensor { } \ } -class TextSensor : public Nameable { +class TextSensor : public EntityBase { public: explicit TextSensor(); explicit TextSensor(const std::string &name); @@ -30,8 +31,6 @@ class TextSensor : public Nameable { void publish_state(const std::string &state); - void set_icon(const std::string &icon); - /// Add a filter to the filter chain. Will be appended to the back. void add_filter(Filter *filter); @@ -53,10 +52,6 @@ class TextSensor : public Nameable { // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) - std::string get_icon(); - - virtual std::string icon(); - virtual std::string unique_id(); bool has_state(); @@ -71,7 +66,6 @@ class TextSensor : public Nameable { Filter *filter_list_{nullptr}; ///< Store all active filters. - optional icon_; bool has_state_{false}; }; diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index 46eaac98eb..6a8a416b81 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -2,12 +2,14 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, time from esphome.const import ( + CONF_ICON, CONF_ID, CONF_TIME_ID, DEVICE_CLASS_ENERGY, CONF_METHOD, STATE_CLASS_TOTAL_INCREASING, ) +from esphome.core.entity_helpers import inherit_property_from DEPENDENCIES = ["time"] @@ -45,6 +47,18 @@ CONFIG_SCHEMA = ( .extend(cv.COMPONENT_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(TotalDailyEnergy), + cv.Optional(CONF_ICON): cv.icon, + cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), + }, + extra=cv.ALLOW_EXTRA, + ), + inherit_property_from(CONF_ICON, CONF_POWER_ID), +) + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) diff --git a/esphome/components/total_daily_energy/total_daily_energy.h b/esphome/components/total_daily_energy/total_daily_energy.h index 9d2396d6e3..fedceafbd3 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.h +++ b/esphome/components/total_daily_energy/total_daily_energy.h @@ -25,7 +25,6 @@ class TotalDailyEnergy : public sensor::Sensor, public Component { void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } std::string unit_of_measurement() override { return this->parent_->get_unit_of_measurement() + "h"; } - std::string icon() override { return this->parent_->get_icon(); } int8_t accuracy_decimals() override { return this->parent_->get_accuracy_decimals() + 2; } void loop() override; diff --git a/esphome/components/tsl2591/tsl2591.h b/esphome/components/tsl2591/tsl2591.h index d377d082a8..19352a15c5 100644 --- a/esphome/components/tsl2591/tsl2591.h +++ b/esphome/components/tsl2591/tsl2591.h @@ -224,7 +224,7 @@ class TSL2591Component : public PollingComponent, public i2c::I2CDevice { float get_setup_priority() const override; protected: - const char *name_; // TODO: extend esphome::Nameable + const char *name_; sensor::Sensor *full_spectrum_sensor_; sensor::Sensor *infrared_sensor_; sensor::Sensor *visible_sensor_; diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index d72262b4d3..e99431be36 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -3,6 +3,7 @@ #include "web_server.h" #include "esphome/core/log.h" #include "esphome/core/application.h" +#include "esphome/core/entity_base.h" #include "esphome/core/util.h" #include "esphome/components/json/json_util.h" #include "esphome/components/network/util.h" @@ -28,8 +29,8 @@ namespace web_server { static const char *const TAG = "web_server"; -void write_row(AsyncResponseStream *stream, Nameable *obj, const std::string &klass, const std::string &action, - const std::function &action_func = nullptr) { +void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action, + const std::function &action_func = nullptr) { if (obj->is_internal()) return; stream->print(""); stream.print(""); diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 0d8f4f3b64..fcec74b245 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -18,6 +18,7 @@ from esphome.const import ( CONF_COMMAND_TOPIC, CONF_DISABLED_BY_DEFAULT, CONF_DISCOVERY, + CONF_ICON, CONF_ID, CONF_INTERNAL, CONF_NAME, @@ -1476,7 +1477,7 @@ class OnlyWith(Optional): pass -def _nameable_validator(config): +def _entity_base_validator(config): if CONF_NAME not in config and CONF_ID not in config: raise Invalid("At least one of 'id:' or 'name:' is required!") if CONF_NAME not in config: @@ -1587,15 +1588,16 @@ MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend( } ) -NAMEABLE_SCHEMA = Schema( +ENTITY_BASE_SCHEMA = Schema( { Optional(CONF_NAME): string, Optional(CONF_INTERNAL): boolean, Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean, + Optional(CONF_ICON): icon, } ) -NAMEABLE_SCHEMA.add_extra(_nameable_validator) +ENTITY_BASE_SCHEMA.add_extra(_entity_base_validator) COMPONENT_SCHEMA = Schema({Optional(CONF_SETUP_PRIORITY): float_}) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index c85b445b08..5692194a91 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -177,26 +177,6 @@ void PollingComponent::call_setup() { uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; } void PollingComponent::set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; } -const std::string &Nameable::get_name() const { return this->name_; } -void Nameable::set_name(const std::string &name) { - this->name_ = name; - this->calc_object_id_(); -} -Nameable::Nameable(std::string name) : name_(std::move(name)) { this->calc_object_id_(); } - -const std::string &Nameable::get_object_id() { return this->object_id_; } -bool Nameable::is_internal() const { return this->internal_; } -void Nameable::set_internal(bool internal) { this->internal_ = internal; } -void Nameable::calc_object_id_() { - this->object_id_ = sanitize_string_allowlist(to_lowercase_underscore(this->name_), HOSTNAME_CHARACTER_ALLOWLIST); - // FNV-1 hash - this->object_id_hash_ = fnv1_hash(this->object_id_); -} -uint32_t Nameable::get_object_id_hash() { return this->object_id_hash_; } - -bool Nameable::is_disabled_by_default() const { return this->disabled_by_default_; } -void Nameable::set_disabled_by_default(bool disabled_by_default) { this->disabled_by_default_ = disabled_by_default; } - WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component) : started_(millis()), component_(component) {} WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() { diff --git a/esphome/core/component.h b/esphome/core/component.h index 85256c0f0f..a1afc17c2c 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -264,40 +264,6 @@ class PollingComponent : public Component { uint32_t update_interval_; }; -/// Helper class that enables naming of objects so that it doesn't have to be re-implement every time. -class Nameable { - public: - Nameable() : Nameable("") {} - explicit Nameable(std::string name); - const std::string &get_name() const; - void set_name(const std::string &name); - /// Get the sanitized name of this nameable as an ID. Caching it internally. - const std::string &get_object_id(); - uint32_t get_object_id_hash(); - - bool is_internal() const; - void set_internal(bool internal); - - /** Check if this object is declared to be disabled by default. - * - * That means that when the device gets added to Home Assistant (or other clients) it should - * not be added to the default view by default, and a user action is necessary to manually add it. - */ - bool is_disabled_by_default() const; - void set_disabled_by_default(bool disabled_by_default); - - protected: - virtual uint32_t hash_base() = 0; - - void calc_object_id_(); - - std::string name_; - std::string object_id_; - uint32_t object_id_hash_; - bool internal_{false}; - bool disabled_by_default_{false}; -}; - class WarnIfComponentBlockingGuard { public: WarnIfComponentBlockingGuard(Component *component); diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp new file mode 100644 index 0000000000..bc94da85fe --- /dev/null +++ b/esphome/core/entity_base.cpp @@ -0,0 +1,40 @@ +#include "esphome/core/entity_base.h" +#include "esphome/core/helpers.h" + +namespace esphome { + +static const char *const TAG = "entity_base"; + +EntityBase::EntityBase(std::string name) : name_(std::move(name)) { this->calc_object_id_(); } + +// Entity Name +const std::string &EntityBase::get_name() const { return this->name_; } +void EntityBase::set_name(const std::string &name) { + this->name_ = name; + this->calc_object_id_(); +} + +// Entity Internal +bool EntityBase::is_internal() const { return this->internal_; } +void EntityBase::set_internal(bool internal) { this->internal_ = internal; } + +// Entity Disabled by Default +bool EntityBase::is_disabled_by_default() const { return this->disabled_by_default_; } +void EntityBase::set_disabled_by_default(bool disabled_by_default) { this->disabled_by_default_ = disabled_by_default; } + +// Entity Icon +const std::string &EntityBase::get_icon() const { return this->icon_; } +void EntityBase::set_icon(const std::string &name) { this->icon_ = name; } + +// Entity Object ID +const std::string &EntityBase::get_object_id() { return this->object_id_; } + +// Calculate Object ID Hash from Entity Name +void EntityBase::calc_object_id_() { + this->object_id_ = sanitize_string_allowlist(to_lowercase_underscore(this->name_), HOSTNAME_CHARACTER_ALLOWLIST); + // FNV-1 hash + this->object_id_hash_ = fnv1_hash(this->object_id_); +} +uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; } + +} // namespace esphome diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h new file mode 100644 index 0000000000..263747b721 --- /dev/null +++ b/esphome/core/entity_base.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +namespace esphome { + +// The generic Entity base class that provides an interface common to all Entities. +class EntityBase { + public: + EntityBase() : EntityBase("") {} + explicit EntityBase(std::string name); + + // Get/set the name of this Entity + const std::string &get_name() const; + void set_name(const std::string &name); + + // Get the sanitized name of this Entity as an ID. Caching it internally. + const std::string &get_object_id(); + + // Get the unique Object ID of this Entity + uint32_t get_object_id_hash(); + + // Get/set whether this Entity should be hidden from outside of ESPHome + bool is_internal() const; + void set_internal(bool internal); + + // Check if this object is declared to be disabled by default. + // That means that when the device gets added to Home Assistant (or other clients) it should + // not be added to the default view by default, and a user action is necessary to manually add it. + bool is_disabled_by_default() const; + void set_disabled_by_default(bool disabled_by_default); + + // Get/set this entity's icon + const std::string &get_icon() const; + void set_icon(const std::string &name); + + protected: + virtual uint32_t hash_base() = 0; + void calc_object_id_(); + + std::string name_; + std::string object_id_; + std::string icon_; + uint32_t object_id_hash_; + bool internal_{false}; + bool disabled_by_default_{false}; +}; + +} // namespace esphome diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py new file mode 100644 index 0000000000..b2dbe2116e --- /dev/null +++ b/esphome/core/entity_helpers.py @@ -0,0 +1,32 @@ +import esphome.final_validate as fv + +from esphome.const import CONF_ID + + +def inherit_property_from(property_to_inherit, parent_id_property): + """Validator that inherits a configuration property from another entity, for use with FINAL_VALIDATE_SCHEMA. + + If a property is already set, it will not be inherited. + + Keyword arguments: + property_to_inherit -- the name of the property to inherit, e.g. CONF_ICON + parent_id_property -- the name of the property that holds the ID of the parent, e.g. CONF_POWER_ID + """ + + def inherit_property(config): + if property_to_inherit not in config: + fconf = fv.full_config.get() + + # Get config for the parent entity + path = fconf.get_path_for_id(config[parent_id_property])[:-1] + parent_config = fconf.get_config_for_path(path) + + # If parent sensor has the property set, inherit it + if property_to_inherit in parent_config: + path = fconf.get_path_for_id(config[CONF_ID])[:-1] + this_config = fconf.get_config_for_path(path) + this_config[property_to_inherit] = parent_config[property_to_inherit] + + return config + + return inherit_property diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index a2eafaa0e8..5b081698ad 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -1,6 +1,10 @@ import logging from esphome.const import ( + CONF_DISABLED_BY_DEFAULT, + CONF_ICON, + CONF_INTERNAL, + CONF_NAME, CONF_SETUP_PRIORITY, CONF_UPDATE_INTERVAL, CONF_TYPE_ID, @@ -90,6 +94,16 @@ async def register_parented(var, value): add(var.set_parent(paren)) +async def setup_entity(var, config): + """Set up generic properties of an Entity""" + add(var.set_name(config[CONF_NAME])) + add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) + if CONF_INTERNAL in config: + add(var.set_internal(config[CONF_INTERNAL])) + if CONF_ICON in config: + add(var.set_icon(config[CONF_ICON])) + + def extract_registry_entry_config(registry, full_config): # type: (Registry, ConfigType) -> RegistryEntry key, config = next((k, v) for k, v in full_config.items() if k in registry) diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index 7a8eb8e04c..888c319024 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -19,7 +19,7 @@ const_char_ptr = global_ns.namespace("const char *") NAN = global_ns.namespace("NAN") esphome_ns = global_ns # using namespace esphome; App = esphome_ns.App -Nameable = esphome_ns.class_("Nameable") +EntityBase = esphome_ns.class_("EntityBase") Component = esphome_ns.class_("Component") ComponentPtr = Component.operator("ptr") PollingComponent = esphome_ns.class_("PollingComponent", Component) diff --git a/tests/test1.yaml b/tests/test1.yaml index 400cdb3b6b..c0bfbb8f0c 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1708,6 +1708,7 @@ climate: name: Anova cooker ble_client_id: ble_blah unit_of_measurement: c + icon: mdi:stove script: - id: climate_custom @@ -1986,6 +1987,7 @@ fan: direction_output: gpio_26 - platform: speed id: fan_speed + icon: mdi:weather-windy output: pca_6 speed_count: 10 name: 'Living Room Fan 2' @@ -2287,6 +2289,7 @@ cover: name: 'Test AM43' id: am43_test ble_client_id: ble_foo + icon: mdi:blinds debug: diff --git a/tests/test5.yaml b/tests/test5.yaml index aca8434fbf..72df3ed212 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -51,6 +51,7 @@ binary_sensor: - platform: gpio pin: GPIO0 id: io0_button + icon: mdi:gesture-tap-button tlc5947: data_pin: GPIO12 diff --git a/tests/unit_tests/test_codegen.py b/tests/unit_tests/test_codegen.py index 9f402465fa..32d82b3062 100644 --- a/tests/unit_tests/test_codegen.py +++ b/tests/unit_tests/test_codegen.py @@ -59,7 +59,7 @@ from esphome import codegen as cg "NAN", "esphome_ns", "App", - "Nameable", + "EntityBase", "Component", "ComponentPtr", # from cpp_types From c3b8c84131bc4ae4d62bc44f102abb0e3e6caccc Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Sun, 10 Oct 2021 03:53:58 -0500 Subject: [PATCH 1519/1841] Fix below freezing temperature for Inkbird sensors (#2466) --- esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp index c01fc274f4..76013e28ff 100644 --- a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp @@ -72,7 +72,7 @@ bool InkbirdIbstH1Mini::parse_device(const esp32_ble_tracker::ESPBTDevice &devic auto external_temperature = NAN; // Read bluetooth data into variable - auto measured_temperature = mnf_data.uuid.get_uuid().uuid.uuid16 / 100.0f; + auto measured_temperature = ((int16_t) mnf_data.uuid.get_uuid().uuid.uuid16) / 100.0f; // Set temperature or external_temperature based on which sensor is in use if (mnf_data.data[2] == 0) { From a1f9b0d7f2f746633e51881ba694522ab00bd568 Mon Sep 17 00:00:00 2001 From: definitio <37266727+definitio@users.noreply.github.com> Date: Sun, 10 Oct 2021 18:54:07 +0300 Subject: [PATCH 1520/1841] Add configuration for cover topics (#2472) --- esphome/components/cover/__init__.py | 32 +++++++++++++++++++++++++++- esphome/const.py | 4 ++++ tests/test1.yaml | 6 ++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index eb57637283..137aaac872 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -8,7 +8,11 @@ from esphome.const import ( CONF_DEVICE_CLASS, CONF_STATE, CONF_POSITION, + CONF_POSITION_COMMAND_TOPIC, + CONF_POSITION_STATE_TOPIC, CONF_TILT, + CONF_TILT_COMMAND_TOPIC, + CONF_TILT_STATE_TOPIC, CONF_STOP, CONF_MQTT_ID, ) @@ -68,7 +72,18 @@ COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ex cv.GenerateID(): cv.declare_id(Cover), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent), cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True), - # TODO: MQTT topic options + cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + cv.Optional(CONF_TILT_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + cv.Optional(CONF_TILT_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), } ) @@ -83,6 +98,21 @@ async def setup_cover_core_(var, config): mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) + if CONF_POSITION_STATE_TOPIC in config: + cg.add( + mqtt_.set_custom_position_state_topic(config[CONF_POSITION_STATE_TOPIC]) + ) + if CONF_POSITION_COMMAND_TOPIC in config: + cg.add( + mqtt_.set_custom_position_command_topic( + config[CONF_POSITION_COMMAND_TOPIC] + ) + ) + if CONF_TILT_STATE_TOPIC in config: + cg.add(mqtt_.set_custom_tilt_state_topic(config[CONF_TILT_STATE_TOPIC])) + if CONF_TILT_COMMAND_TOPIC in config: + cg.add(mqtt_.set_custom_tilt_command_topic(config[CONF_TILT_COMMAND_TOPIC])) + async def register_cover(var, config): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/const.py b/esphome/const.py index a65285a4b5..ac9e759ffd 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -497,6 +497,8 @@ CONF_PMC_4_0 = "pmc_4_0" CONF_PORT = "port" CONF_POSITION = "position" CONF_POSITION_ACTION = "position_action" +CONF_POSITION_COMMAND_TOPIC = "position_command_topic" +CONF_POSITION_STATE_TOPIC = "position_state_topic" CONF_POWER = "power" CONF_POWER_FACTOR = "power_factor" CONF_POWER_ON_VALUE = "power_on_value" @@ -654,7 +656,9 @@ CONF_THRESHOLD = "threshold" CONF_THROTTLE = "throttle" CONF_TILT = "tilt" CONF_TILT_ACTION = "tilt_action" +CONF_TILT_COMMAND_TOPIC = "tilt_command_topic" CONF_TILT_LAMBDA = "tilt_lambda" +CONF_TILT_STATE_TOPIC = "tilt_state_topic" CONF_TIME = "time" CONF_TIME_ID = "time_id" CONF_TIMEOUT = "timeout" diff --git a/tests/test1.yaml b/tests/test1.yaml index c0bfbb8f0c..537ef6b1c4 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2285,6 +2285,12 @@ cover: id: template_cover state: CLOSED assumed_state: no + has_position: yes + position_state_topic: position/state/topic + position_command_topic: position/command/topic + tilt_lambda: !lambda 'return 0.5;' + tilt_state_topic: tilt/state/topic + tilt_command_topic: tilt/command/topic - platform: am43 name: 'Test AM43' id: am43_test From 42739f0b22d02b97b6545d77e8731303447457ab Mon Sep 17 00:00:00 2001 From: definitio <37266727+definitio@users.noreply.github.com> Date: Sun, 10 Oct 2021 18:55:22 +0300 Subject: [PATCH 1521/1841] Add configuration for climate topics (#2473) --- esphome/components/climate/__init__.py | 145 ++++++++++++++++++++++++- esphome/const.py | 16 +++ tests/test1.yaml | 16 +++ 3 files changed, 174 insertions(+), 3 deletions(-) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index ca1ea6a756..7ff769e5cb 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -4,22 +4,38 @@ from esphome.cpp_helpers import setup_entity from esphome import automation from esphome.components import mqtt from esphome.const import ( + CONF_ACTION_STATE_TOPIC, CONF_AWAY, + CONF_AWAY_COMMAND_TOPIC, + CONF_AWAY_STATE_TOPIC, + CONF_CURRENT_TEMPERATURE_STATE_TOPIC, CONF_CUSTOM_FAN_MODE, CONF_CUSTOM_PRESET, + CONF_FAN_MODE, + CONF_FAN_MODE_COMMAND_TOPIC, + CONF_FAN_MODE_STATE_TOPIC, CONF_ID, CONF_MAX_TEMPERATURE, CONF_MIN_TEMPERATURE, CONF_MODE, + CONF_MODE_COMMAND_TOPIC, + CONF_MODE_STATE_TOPIC, CONF_PRESET, + CONF_SWING_MODE, + CONF_SWING_MODE_COMMAND_TOPIC, + CONF_SWING_MODE_STATE_TOPIC, CONF_TARGET_TEMPERATURE, + CONF_TARGET_TEMPERATURE_COMMAND_TOPIC, + CONF_TARGET_TEMPERATURE_STATE_TOPIC, CONF_TARGET_TEMPERATURE_HIGH, + CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC, + CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC, CONF_TARGET_TEMPERATURE_LOW, + CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC, + CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC, CONF_TEMPERATURE_STEP, CONF_VISUAL, CONF_MQTT_ID, - CONF_FAN_MODE, - CONF_SWING_MODE, ) from esphome.core import CORE, coroutine_with_priority @@ -97,7 +113,54 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA). cv.Optional(CONF_TEMPERATURE_STEP): cv.temperature, } ), - # TODO: MQTT topic options + cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_AWAY_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_AWAY_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_CURRENT_TEMPERATURE_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_FAN_MODE_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_FAN_MODE_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_MODE_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_MODE_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_SWING_MODE_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_TEMPERATURE_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_TEMPERATURE_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), } ) @@ -117,6 +180,82 @@ async def setup_climate_core_(var, config): mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) + if CONF_ACTION_STATE_TOPIC in config: + cg.add(mqtt_.set_custom_action_state_topic(config[CONF_ACTION_STATE_TOPIC])) + if CONF_AWAY_COMMAND_TOPIC in config: + cg.add(mqtt_.set_custom_away_command_topic(config[CONF_AWAY_COMMAND_TOPIC])) + if CONF_AWAY_STATE_TOPIC in config: + cg.add(mqtt_.set_custom_away_state_topic(config[CONF_AWAY_STATE_TOPIC])) + if CONF_CURRENT_TEMPERATURE_STATE_TOPIC in config: + cg.add( + mqtt_.set_custom_current_temperature_state_topic( + config[CONF_CURRENT_TEMPERATURE_STATE_TOPIC] + ) + ) + if CONF_FAN_MODE_COMMAND_TOPIC in config: + cg.add( + mqtt_.set_custom_fan_mode_command_topic( + config[CONF_FAN_MODE_COMMAND_TOPIC] + ) + ) + if CONF_FAN_MODE_STATE_TOPIC in config: + cg.add( + mqtt_.set_custom_fan_mode_state_topic(config[CONF_FAN_MODE_STATE_TOPIC]) + ) + if CONF_MODE_COMMAND_TOPIC in config: + cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC])) + if CONF_MODE_STATE_TOPIC in config: + cg.add(mqtt_.set_custom_state_topic(config[CONF_MODE_STATE_TOPIC])) + + if CONF_SWING_MODE_COMMAND_TOPIC in config: + cg.add( + mqtt_.set_custom_swing_mode_command_topic( + config[CONF_SWING_MODE_COMMAND_TOPIC] + ) + ) + if CONF_SWING_MODE_STATE_TOPIC in config: + cg.add( + mqtt_.set_custom_swing_mode_state_topic( + config[CONF_SWING_MODE_STATE_TOPIC] + ) + ) + if CONF_TARGET_TEMPERATURE_COMMAND_TOPIC in config: + cg.add( + mqtt_.set_custom_target_temperature_command_topic( + config[CONF_TARGET_TEMPERATURE_COMMAND_TOPIC] + ) + ) + if CONF_TARGET_TEMPERATURE_STATE_TOPIC in config: + cg.add( + mqtt_.set_custom_target_temperature_state_topic( + config[CONF_TARGET_TEMPERATURE_STATE_TOPIC] + ) + ) + if CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC in config: + cg.add( + mqtt_.set_custom_target_temperature_high_command_topic( + config[CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC] + ) + ) + if CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC in config: + cg.add( + mqtt_.set_custom_target_temperature_high_state_topic( + config[CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC] + ) + ) + if CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC in config: + cg.add( + mqtt_.set_custom_target_temperature_low_command_topic( + config[CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC] + ) + ) + if CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC in config: + cg.add( + mqtt_.set_custom_target_temperature_state_topic( + config[CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC] + ) + ) + async def register_climate(var, config): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/const.py b/esphome/const.py index ac9e759ffd..780f4f6e22 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -39,6 +39,7 @@ CONF_ACCELERATION_Z = "acceleration_z" CONF_ACCURACY = "accuracy" CONF_ACCURACY_DECIMALS = "accuracy_decimals" CONF_ACTION_ID = "action_id" +CONF_ACTION_STATE_TOPIC = "action_state_topic" CONF_ACTIVE_POWER = "active_power" CONF_ADDRESS = "address" CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id" @@ -60,7 +61,9 @@ CONF_AUTOCONF = "autoconf" CONF_AUTOMATION_ID = "automation_id" CONF_AVAILABILITY = "availability" CONF_AWAY = "away" +CONF_AWAY_COMMAND_TOPIC = "away_command_topic" CONF_AWAY_CONFIG = "away_config" +CONF_AWAY_STATE_TOPIC = "away_state_topic" CONF_BACKLIGHT_PIN = "backlight_pin" CONF_BASELINE = "baseline" CONF_BATTERY_LEVEL = "battery_level" @@ -141,6 +144,7 @@ CONF_CSS_URL = "css_url" CONF_CURRENT = "current" CONF_CURRENT_OPERATION = "current_operation" CONF_CURRENT_RESISTOR = "current_resistor" +CONF_CURRENT_TEMPERATURE_STATE_TOPIC = "current_temperature_state_topic" CONF_CUSTOM_FAN_MODE = "custom_fan_mode" CONF_CUSTOM_FAN_MODES = "custom_fan_modes" CONF_CUSTOM_PRESET = "custom_preset" @@ -209,6 +213,7 @@ CONF_FALLING_EDGE = "falling_edge" CONF_FAMILY = "family" CONF_FAN_MODE = "fan_mode" CONF_FAN_MODE_AUTO_ACTION = "fan_mode_auto_action" +CONF_FAN_MODE_COMMAND_TOPIC = "fan_mode_command_topic" CONF_FAN_MODE_DIFFUSE_ACTION = "fan_mode_diffuse_action" CONF_FAN_MODE_FOCUS_ACTION = "fan_mode_focus_action" CONF_FAN_MODE_HIGH_ACTION = "fan_mode_high_action" @@ -217,6 +222,7 @@ CONF_FAN_MODE_MEDIUM_ACTION = "fan_mode_medium_action" CONF_FAN_MODE_MIDDLE_ACTION = "fan_mode_middle_action" CONF_FAN_MODE_OFF_ACTION = "fan_mode_off_action" CONF_FAN_MODE_ON_ACTION = "fan_mode_on_action" +CONF_FAN_MODE_STATE_TOPIC = "fan_mode_state_topic" CONF_FAN_ONLY_ACTION = "fan_only_action" CONF_FAN_ONLY_ACTION_USES_FAN_MODE_TIMER = "fan_only_action_uses_fan_mode_timer" CONF_FAN_ONLY_COOLING = "fan_only_cooling" @@ -378,6 +384,8 @@ CONF_MINUTE = "minute" CONF_MINUTES = "minutes" CONF_MISO_PIN = "miso_pin" CONF_MODE = "mode" +CONF_MODE_COMMAND_TOPIC = "mode_command_topic" +CONF_MODE_STATE_TOPIC = "mode_state_topic" CONF_MODEL = "model" CONF_MOISTURE = "moisture" CONF_MONTHS = "months" @@ -636,6 +644,8 @@ CONF_SUPPORTS_HEAT = "supports_heat" CONF_SWING_BOTH_ACTION = "swing_both_action" CONF_SWING_HORIZONTAL_ACTION = "swing_horizontal_action" CONF_SWING_MODE = "swing_mode" +CONF_SWING_MODE_COMMAND_TOPIC = "swing_mode_command_topic" +CONF_SWING_MODE_STATE_TOPIC = "swing_mode_state_topic" CONF_SWING_OFF_ACTION = "swing_off_action" CONF_SWING_VERTICAL_ACTION = "swing_vertical_action" CONF_SWITCH_DATAPOINT = "switch_datapoint" @@ -646,8 +656,14 @@ CONF_TAG = "tag" CONF_TARGET = "target" CONF_TARGET_TEMPERATURE = "target_temperature" CONF_TARGET_TEMPERATURE_CHANGE_ACTION = "target_temperature_change_action" +CONF_TARGET_TEMPERATURE_COMMAND_TOPIC = "target_temperature_command_topic" CONF_TARGET_TEMPERATURE_HIGH = "target_temperature_high" +CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC = "target_temperature_high_command_topic" +CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC = "target_temperature_high_state_topic" CONF_TARGET_TEMPERATURE_LOW = "target_temperature_low" +CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC = "target_temperature_low_command_topic" +CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC = "target_temperature_low_state_topic" +CONF_TARGET_TEMPERATURE_STATE_TOPIC = "target_temperature_state_topic" CONF_TEMPERATURE = "temperature" CONF_TEMPERATURE_STEP = "temperature_step" CONF_TEXT_SENSORS = "text_sensors" diff --git a/tests/test1.yaml b/tests/test1.yaml index 537ef6b1c4..058da35d2c 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1642,6 +1642,22 @@ climate: sensor: ${sensorname}_sensor - platform: tcl112 name: TCL112 Climate + action_state_topic: action/state/topic + away_command_topic: away/command/topic + away_state_topic: away/state/topic + current_temperature_state_topic: current/temperature/state/topic + fan_mode_command_topic: fan_mode/mode/command/topic + fan_mode_state_topic: fan_mode/mode/state/topic + mode_command_topic: mode/command/topic + mode_state_topic: mode/state/topic + swing_mode_command_topic: swing_mode/command/topic + swing_mode_state_topic: swing_mode/state/topic + target_temperature_command_topic: target/temperature/command/topic + target_temperature_high_command_topic: target/temperature/high/command/topic + target_temperature_high_state_topic: target/temperature/high/state/topic + target_temperature_low_command_topic: target/temperature/low/command/topic + target_temperature_low_state_topic: target/temperature/low/state/topic + target_temperature_state_topic: target/temperature/state/topic - platform: coolix name: Coolix Climate With Sensor supports_heat: True From 2c517e3e8ccf727f9e4ae948dcec38bd5c1dc46f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 11 Oct 2021 10:38:45 +1300 Subject: [PATCH 1522/1841] Use arduino btStart for arduino framework (#2457) --- esphome/components/esp32_ble/ble.cpp | 7 +++++++ .../components/esp32_ble_beacon/esp32_ble_beacon.cpp | 11 +++++++++++ .../esp32_ble_tracker/esp32_ble_tracker.cpp | 7 +++++++ 3 files changed, 25 insertions(+) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 143be06e3b..ecd591d169 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -56,6 +56,12 @@ bool ESP32BLE::ble_setup_() { return false; } +#ifdef USE_ARDUINO + if (!btStart()) { + ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); + return false; + } +#else if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { // start bt controller if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) { @@ -80,6 +86,7 @@ bool ESP32BLE::ble_setup_() { return false; } } +#endif esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp index 96afadd19a..f6bab8e6df 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp @@ -12,6 +12,10 @@ #include #include "esphome/core/hal.h" +#ifdef USE_ARDUINO +#include +#endif + namespace esphome { namespace esp32_ble_beacon { @@ -70,6 +74,12 @@ void ESP32BLEBeacon::ble_setup() { return; } +#ifdef USE_ARDUINO + if (!btStart()) { + ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); + return; + } +#else if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { // start bt controller if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) { @@ -94,6 +104,7 @@ void ESP32BLEBeacon::ble_setup() { return; } } +#endif esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 9e987a994a..95176bb179 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -132,6 +132,12 @@ bool ESP32BLETracker::ble_setup() { return false; } +#ifdef USE_ARDUINO + if (!btStart()) { + ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); + return false; + } +#else if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { // start bt controller if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) { @@ -156,6 +162,7 @@ bool ESP32BLETracker::ble_setup() { return false; } } +#endif esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); From 11d286675510ed01430cd75938dc8c6d34bf4dc1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 16:45:27 +0200 Subject: [PATCH 1523/1841] Bump click from 8.0.1 to 8.0.3 (#2481) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 880faeddbe..3912f099df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.0 esptool==3.1 -click==8.0.1 +click==8.0.3 esphome-dashboard==20211006.0 aioesphomeapi==9.1.5 From 3cb4b4ca039914c53b8b2c8f26c0a17b5eeb2a1c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 16:52:42 +0200 Subject: [PATCH 1524/1841] Bump flake8 from 3.9.2 to 4.0.1 (#2483) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 85456643bc..8ebcf24d4d 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,5 +1,5 @@ pylint==2.11.1 -flake8==3.9.2 +flake8==4.0.1 black==21.9b0 pexpect==4.8.0 pre-commit From 55e9560e7400f43015de29cbd74015e5c2619111 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 16:58:51 +0200 Subject: [PATCH 1525/1841] Bump platformio from 5.2.0 to 5.2.1 (#2482) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3912f099df..4e32753ba3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ tornado==6.1 tzlocal==3.0 # from time tzdata>=2021.1 # from time pyserial==3.5 -platformio==5.2.0 +platformio==5.2.1 esptool==3.1 click==8.0.3 esphome-dashboard==20211006.0 From ea56a39e11aaaef20a2bdf1e48a8931330651f26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 17:21:04 +0200 Subject: [PATCH 1526/1841] Bump esphome-dashboard from 20211006.0 to 20211011.1 (#2484) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4e32753ba3..23a00d3755 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 esptool==3.1 click==8.0.3 -esphome-dashboard==20211006.0 +esphome-dashboard==20211011.1 aioesphomeapi==9.1.5 # esp-idf requires this, but doesn't bundle it by default From 039fbc677de4d4d7625413f22b06cdc01ffb270a Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Mon, 11 Oct 2021 22:44:05 +0100 Subject: [PATCH 1527/1841] Replace deprecated COLOR_BLACK constant (#2487) --- esphome/components/ili9341/ili9341_display.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/ili9341/ili9341_display.cpp b/esphome/components/ili9341/ili9341_display.cpp index 88f8bac272..ab5586fa28 100644 --- a/esphome/components/ili9341/ili9341_display.cpp +++ b/esphome/components/ili9341/ili9341_display.cpp @@ -142,7 +142,7 @@ void ILI9341Display::fill(Color color) { } void ILI9341Display::fill_internal_(Color color) { - if (color.raw_32 == COLOR_BLACK.raw_32) { + if (color.raw_32 == Color::BLACK.raw_32) { memset(transfer_buffer_, 0, sizeof(transfer_buffer_)); } else { uint8_t *dst = transfer_buffer_; From 85461a752acb7075ee9a4dc973bb31473f95ad7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Mon, 11 Oct 2021 23:56:35 +0200 Subject: [PATCH 1528/1841] Fix color temperature persistence on CWWW lights (#2486) --- esphome/components/light/light_call.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index dc1e7d39fb..9858590850 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -532,7 +532,8 @@ LightCall &LightCall::set_white_if_supported(float white) { return *this; } LightCall &LightCall::set_color_temperature_if_supported(float color_temperature) { - if (this->get_active_color_mode_() & ColorCapability::COLOR_TEMPERATURE) + if (this->get_active_color_mode_() & ColorCapability::COLOR_TEMPERATURE || + this->get_active_color_mode_() & ColorCapability::COLD_WARM_WHITE) this->set_color_temperature(color_temperature); return *this; } From d7ad1558856a9e795117e038d21b5616d9d1d86a Mon Sep 17 00:00:00 2001 From: niklasweber Date: Tue, 12 Oct 2021 00:11:04 +0200 Subject: [PATCH 1529/1841] Fix reset on http_request without network connection (#2474) * Fix reset problem when http_request is sent without network connection (#2501) * Fix format --- esphome/components/http_request/http_request.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index f88ee19e5c..309977a915 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -3,6 +3,7 @@ #include "http_request.h" #include "esphome/core/macros.h" #include "esphome/core/log.h" +#include "esphome/components/network/util.h" namespace esphome { namespace http_request { @@ -28,6 +29,13 @@ void HttpRequestComponent::set_url(std::string url) { } void HttpRequestComponent::send(const std::vector &response_triggers) { + if (!network::is_connected()) { + this->client_.end(); + this->status_set_warning(); + ESP_LOGW(TAG, "HTTP Request failed; Not connected to network"); + return; + } + bool begin_status = false; const String url = this->url_.c_str(); #ifdef USE_ESP32 From 04ec1c8b56d49353865f8eb8f616c40d4ee598f0 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Mon, 11 Oct 2021 23:14:04 +0100 Subject: [PATCH 1530/1841] Consolidate CONF_RAW_DATA_ID to const.py (#2491) --- esphome/components/animation/__init__.py | 4 +--- esphome/components/font/__init__.py | 3 +-- esphome/components/image/__init__.py | 11 ++++++++--- esphome/const.py | 1 + 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index 3ae3aa94f9..3f03e5c185 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -5,7 +5,7 @@ from esphome.components import display, font import esphome.components.image as espImage import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_FILE, CONF_ID, CONF_TYPE, CONF_RESIZE +from esphome.const import CONF_FILE, CONF_ID, CONF_RAW_DATA_ID, CONF_RESIZE, CONF_TYPE from esphome.core import CORE, HexInt _LOGGER = logging.getLogger(__name__) @@ -15,8 +15,6 @@ MULTI_CONF = True Animation_ = display.display_ns.class_("Animation") -CONF_RAW_DATA_ID = "raw_data_id" - ANIMATION_SCHEMA = cv.Schema( { cv.Required(CONF_ID): cv.declare_id(Animation_), diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 7225bf5bb9..6af5be45d4 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -4,7 +4,7 @@ from esphome import core from esphome.components import display import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_SIZE +from esphome.const import CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_RAW_DATA_ID, CONF_SIZE from esphome.core import CORE, HexInt DEPENDENCIES = ["display"] @@ -74,7 +74,6 @@ def validate_truetype_file(value): DEFAULT_GLYPHS = ( ' !"%()+=,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' ) -CONF_RAW_DATA_ID = "raw_data_id" CONF_RAW_GLYPH_ID = "raw_glyph_id" FONT_SCHEMA = cv.Schema( diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index b946a86bc4..a721263dff 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -4,7 +4,14 @@ from esphome import core from esphome.components import display, font import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_FILE, CONF_ID, CONF_TYPE, CONF_RESIZE, CONF_DITHER +from esphome.const import ( + CONF_DITHER, + CONF_FILE, + CONF_ID, + CONF_RAW_DATA_ID, + CONF_RESIZE, + CONF_TYPE, +) from esphome.core import CORE, HexInt _LOGGER = logging.getLogger(__name__) @@ -21,8 +28,6 @@ IMAGE_TYPE = { Image_ = display.display_ns.class_("Image") -CONF_RAW_DATA_ID = "raw_data_id" - IMAGE_SCHEMA = cv.Schema( { cv.Required(CONF_ID): cv.declare_id(Image_), diff --git a/esphome/const.py b/esphome/const.py index 780f4f6e22..a9eec3e249 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -533,6 +533,7 @@ CONF_RANGE_FROM = "range_from" CONF_RANGE_TO = "range_to" CONF_RATE = "rate" CONF_RAW = "raw" +CONF_RAW_DATA_ID = "raw_data_id" CONF_RC_CODE_1 = "rc_code_1" CONF_RC_CODE_2 = "rc_code_2" CONF_REACTIVE_POWER = "reactive_power" From 6a5eb43454dcbbcf8d41338fc2a526f77373b6e2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 12 Oct 2021 11:56:47 +1300 Subject: [PATCH 1531/1841] Update Airthings BLE (#2453) --- .../airthings_wave_mini.cpp | 19 ++----- .../airthings_wave_mini/airthings_wave_mini.h | 16 +++--- .../components/airthings_wave_mini/sensor.py | 8 +-- .../airthings_wave_plus.cpp | 19 ++----- .../airthings_wave_plus/airthings_wave_plus.h | 16 +++--- .../components/airthings_wave_plus/sensor.py | 8 +-- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 57 +++++++++++++++++++ .../esp32_ble_tracker/esp32_ble_tracker.h | 2 + 8 files changed, 89 insertions(+), 56 deletions(-) diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp index 0ab0e65148..6b6418f7e6 100644 --- a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp +++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp @@ -1,6 +1,6 @@ #include "airthings_wave_mini.h" -#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#ifdef USE_ESP32 namespace esphome { namespace airthings_wave_mini { @@ -75,8 +75,6 @@ void AirthingsWaveMini::read_sensors_(uint8_t *raw_value, uint16_t value_len) { bool AirthingsWaveMini::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; } -void AirthingsWaveMini::loop() {} - void AirthingsWaveMini::update() { if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { if (!parent()->enabled) { @@ -104,17 +102,12 @@ void AirthingsWaveMini::dump_config() { LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); } -AirthingsWaveMini::AirthingsWaveMini() : PollingComponent(10000) { - auto service_bt = *BLEUUID::fromString(std::string("b42e3882-ade7-11e4-89d3-123b93f75cba")).getNative(); - auto characteristic_bt = *BLEUUID::fromString(std::string("b42e3b98-ade7-11e4-89d3-123b93f75cba")).getNative(); - - service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_uuid(service_bt); - sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_uuid(characteristic_bt); -} - -void AirthingsWaveMini::setup() {} +AirthingsWaveMini::AirthingsWaveMini() + : PollingComponent(10000), + service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)), + sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {} } // namespace airthings_wave_mini } // namespace esphome -#endif // USE_ESP32_FRAMEWORK_ARDUINO +#endif // USE_ESP32 diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.h b/esphome/components/airthings_wave_mini/airthings_wave_mini.h index 5d1964d559..128774f9cb 100644 --- a/esphome/components/airthings_wave_mini/airthings_wave_mini.h +++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.h @@ -1,28 +1,28 @@ #pragma once -#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#ifdef USE_ESP32 +#include #include #include -#include -#include -#include "esphome/core/component.h" -#include "esphome/core/log.h" #include "esphome/components/ble_client/ble_client.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" +#include "esphome/core/log.h" namespace esphome { namespace airthings_wave_mini { +static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba"; +static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba"; + class AirthingsWaveMini : public PollingComponent, public ble_client::BLEClientNode { public: AirthingsWaveMini(); - void setup() override; void dump_config() override; void update() override; - void loop() override; void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override; @@ -62,4 +62,4 @@ class AirthingsWaveMini : public PollingComponent, public ble_client::BLEClientN } // namespace airthings_wave_mini } // namespace esphome -#endif // USE_ESP32_FRAMEWORK_ARDUINO +#endif // USE_ESP32 diff --git a/esphome/components/airthings_wave_mini/sensor.py b/esphome/components/airthings_wave_mini/sensor.py index 6a32cd8771..d38354fa84 100644 --- a/esphome/components/airthings_wave_mini/sensor.py +++ b/esphome/components/airthings_wave_mini/sensor.py @@ -1,7 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, ble_client -from esphome.core import CORE from esphome.const import ( DEVICE_CLASS_HUMIDITY, @@ -58,10 +57,8 @@ CONFIG_SCHEMA = cv.All( ), } ) - .extend(cv.polling_component_schema("5mins")) + .extend(cv.polling_component_schema("5min")) .extend(ble_client.BLE_CLIENT_SCHEMA), - # Until BLEUUID reference removed - cv.only_with_arduino, ) @@ -83,6 +80,3 @@ async def to_code(config): if CONF_TVOC in config: sens = await sensor.new_sensor(config[CONF_TVOC]) cg.add(var.set_tvoc(sens)) - - if CORE.is_esp32: - cg.add_library("ESP32 BLE Arduino", None) diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp index 0eaffbd889..79f2cb7741 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -1,6 +1,6 @@ #include "airthings_wave_plus.h" -#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#ifdef USE_ESP32 namespace esphome { namespace airthings_wave_plus { @@ -96,8 +96,6 @@ bool AirthingsWavePlus::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && v bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && co2 <= 16383; } -void AirthingsWavePlus::loop() {} - void AirthingsWavePlus::update() { if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { if (!parent()->enabled) { @@ -128,17 +126,12 @@ void AirthingsWavePlus::dump_config() { LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); } -AirthingsWavePlus::AirthingsWavePlus() : PollingComponent(10000) { - auto service_bt = *BLEUUID::fromString(std::string("b42e1c08-ade7-11e4-89d3-123b93f75cba")).getNative(); - auto characteristic_bt = *BLEUUID::fromString(std::string("b42e2a68-ade7-11e4-89d3-123b93f75cba")).getNative(); - - service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_uuid(service_bt); - sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_uuid(characteristic_bt); -} - -void AirthingsWavePlus::setup() {} +AirthingsWavePlus::AirthingsWavePlus() + : PollingComponent(10000), + service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)), + sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {} } // namespace airthings_wave_plus } // namespace esphome -#endif // USE_ESP32_FRAMEWORK_ARDUINO +#endif // USE_ESP32 diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.h b/esphome/components/airthings_wave_plus/airthings_wave_plus.h index 5677f05a62..9dd6ed92d5 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.h +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.h @@ -1,28 +1,28 @@ #pragma once -#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#ifdef USE_ESP32 +#include #include #include -#include -#include -#include "esphome/core/component.h" -#include "esphome/core/log.h" #include "esphome/components/ble_client/ble_client.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" +#include "esphome/core/log.h" namespace esphome { namespace airthings_wave_plus { +static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba"; +static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba"; + class AirthingsWavePlus : public PollingComponent, public ble_client::BLEClientNode { public: AirthingsWavePlus(); - void setup() override; void dump_config() override; void update() override; - void loop() override; void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override; @@ -72,4 +72,4 @@ class AirthingsWavePlus : public PollingComponent, public ble_client::BLEClientN } // namespace airthings_wave_plus } // namespace esphome -#endif // USE_ESP32_FRAMEWORK_ARDUINO +#endif // USE_ESP32 diff --git a/esphome/components/airthings_wave_plus/sensor.py b/esphome/components/airthings_wave_plus/sensor.py index 2100341536..727fbe15fb 100644 --- a/esphome/components/airthings_wave_plus/sensor.py +++ b/esphome/components/airthings_wave_plus/sensor.py @@ -1,7 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, ble_client -from esphome.core import CORE from esphome.const import ( DEVICE_CLASS_CARBON_DIOXIDE, @@ -83,10 +82,8 @@ CONFIG_SCHEMA = cv.All( ), } ) - .extend(cv.polling_component_schema("5mins")) + .extend(cv.polling_component_schema("5min")) .extend(ble_client.BLE_CLIENT_SCHEMA), - # Until BLEUUID reference removed - cv.only_with_arduino, ) @@ -117,6 +114,3 @@ async def to_code(config): if CONF_TVOC in config: sens = await sensor.new_sensor(config[CONF_TVOC]) cg.add(var.set_tvoc(sens)) - - if CORE.is_esp32: - cg.add_library("ESP32 BLE Arduino", None) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 95176bb179..65749f5124 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -317,6 +317,63 @@ ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) { ret.uuid_.uuid.uuid128[i] = data[i]; return ret; } +ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { + ESPBTUUID ret; + if (data.length() == 4) { + ret.uuid_.len = ESP_UUID_LEN_16; + ret.uuid_.uuid.uuid16 = 0; + for (int i = 0; i < data.length();) { + uint8_t msb = data.c_str()[i]; + uint8_t lsb = data.c_str()[i + 1]; + + if (msb > '9') + msb -= 7; + if (lsb > '9') + lsb -= 7; + ret.uuid_.uuid.uuid16 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (2 - i) * 4; + i += 2; + } + } else if (data.length() == 8) { + ret.uuid_.len = ESP_UUID_LEN_32; + ret.uuid_.uuid.uuid32 = 0; + for (int i = 0; i < data.length();) { + uint8_t msb = data.c_str()[i]; + uint8_t lsb = data.c_str()[i + 1]; + + if (msb > '9') + msb -= 7; + if (lsb > '9') + lsb -= 7; + ret.uuid_.uuid.uuid32 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (6 - i) * 4; + i += 2; + } + } else if (data.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be + // investigated (lack of time) + ret.uuid_.len = ESP_UUID_LEN_128; + memcpy(ret.uuid_.uuid.uuid128, (uint8_t *) data.data(), 16); + } else if (data.length() == 36) { + // If the length of the string is 36 bytes then we will assume it is a long hex string in + // UUID format. + ret.uuid_.len = ESP_UUID_LEN_128; + int n = 0; + for (int i = 0; i < data.length();) { + if (data.c_str()[i] == '-') + i++; + uint8_t msb = data.c_str()[i]; + uint8_t lsb = data.c_str()[i + 1]; + + if (msb > '9') + msb -= 7; + if (lsb > '9') + lsb -= 7; + ret.uuid_.uuid.uuid128[15 - n++] = ((msb & 0x0F) << 4) | (lsb & 0x0F); + i += 2; + } + } else { + ESP_LOGE(TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes - %s", data.c_str()); + } + return ret; +} ESPBTUUID ESPBTUUID::from_uuid(esp_bt_uuid_t uuid) { ESPBTUUID ret; ret.uuid_.len = uuid.len; diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 71885a564f..1308119df5 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -25,6 +25,8 @@ class ESPBTUUID { static ESPBTUUID from_raw(const uint8_t *data); + static ESPBTUUID from_raw(const std::string &data); + static ESPBTUUID from_uuid(esp_bt_uuid_t uuid); ESPBTUUID as_128bit() const; From b4f57972fb4e7f20e0d83632366f4ee32f348b7b Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Mon, 11 Oct 2021 21:39:21 -0500 Subject: [PATCH 1532/1841] Add on_open and on_closed triggers to cover (#2488) --- esphome/components/cover/__init__.py | 27 +++++++++++++++++++++++++++ esphome/components/cover/automation.h | 23 +++++++++++++++++++++++ tests/test1.yaml | 6 ++++++ 3 files changed, 56 insertions(+) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 137aaac872..0fd27f3f27 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -15,6 +15,7 @@ from esphome.const import ( CONF_TILT_STATE_TOPIC, CONF_STOP, CONF_MQTT_ID, + CONF_TRIGGER_ID, ) from esphome.core import CORE, coroutine_with_priority from esphome.cpp_helpers import setup_entity @@ -67,6 +68,15 @@ CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action) CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition) CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition) +# Triggers +CoverOpenTrigger = cover_ns.class_("CoverOpenTrigger", automation.Trigger.template()) +CoverClosedTrigger = cover_ns.class_( + "CoverClosedTrigger", automation.Trigger.template() +) + +CONF_ON_OPEN = "on_open" +CONF_ON_CLOSED = "on_closed" + COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(Cover), @@ -84,6 +94,16 @@ COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ex cv.Optional(CONF_TILT_STATE_TOPIC): cv.All( cv.requires_component("mqtt"), cv.subscribe_topic ), + cv.Optional(CONF_ON_OPEN): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger), + } + ), + cv.Optional(CONF_ON_CLOSED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger), + } + ), } ) @@ -94,6 +114,13 @@ async def setup_cover_core_(var, config): if CONF_DEVICE_CLASS in config: cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) + for conf in config.get(CONF_ON_OPEN, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_CLOSED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index 79bca6826e..6406ba52cb 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -99,6 +99,7 @@ template class CoverIsOpenCondition : public Condition { protected: Cover *cover_; }; + template class CoverIsClosedCondition : public Condition { public: CoverIsClosedCondition(Cover *cover) : cover_(cover) {} @@ -108,5 +109,27 @@ template class CoverIsClosedCondition : public Condition Cover *cover_; }; +class CoverOpenTrigger : public Trigger<> { + public: + CoverOpenTrigger(Cover *a_cover) { + a_cover->add_on_state_callback([this, a_cover]() { + if (a_cover->is_fully_open()) { + this->trigger(); + } + }); + } +}; + +class CoverClosedTrigger : public Trigger<> { + public: + CoverClosedTrigger(Cover *a_cover) { + a_cover->add_on_state_callback([this, a_cover]() { + if (a_cover->is_fully_closed()) { + this->trigger(); + } + }); + } +}; + } // namespace cover } // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index 058da35d2c..130022c14d 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2307,6 +2307,12 @@ cover: tilt_lambda: !lambda 'return 0.5;' tilt_state_topic: tilt/state/topic tilt_command_topic: tilt/command/topic + on_open: + then: + - lambda: 'ESP_LOGD("cover", "open");' + on_closed: + then: + - lambda: 'ESP_LOGD("cover", "closed");' - platform: am43 name: 'Test AM43' id: am43_test From d13134135bfde3cf2ddbc5f8df890737a83516f1 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Tue, 12 Oct 2021 13:51:41 +0200 Subject: [PATCH 1533/1841] Fix LoadProhibited crash for logger baud_rate 0 (#2498) Co-authored-by: Maurice Makaay --- esphome/components/logger/logger.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 4352b7e208..2d85969bf3 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -111,14 +111,15 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) { this->set_null_terminator_(); const char *msg = this->tx_buffer_ + offset; + if (this->baud_rate_ > 0) { #ifdef USE_ARDUINO - if (this->baud_rate_ > 0) this->hw_serial_->println(msg); #endif // USE_ARDUINO #ifdef USE_ESP_IDF - uart_write_bytes(uart_num_, msg, strlen(msg)); - uart_write_bytes(uart_num_, "\n", 1); + uart_write_bytes(uart_num_, msg, strlen(msg)); + uart_write_bytes(uart_num_, "\n", 1); #endif + } #ifdef USE_ESP32 // Suppress network-logging if memory constrained, but still log to serial From a3eb2a7ee0d16e1e0b7d3e05636372300791ab29 Mon Sep 17 00:00:00 2001 From: Rob Deutsch Date: Wed, 13 Oct 2021 05:38:19 +1100 Subject: [PATCH 1534/1841] Added heatpumpir support (#1343) Co-authored-by: Otto winter Co-authored-by: Oxan van Leeuwen --- CODEOWNERS | 1 + esphome/components/heatpumpir/__init__.py | 0 esphome/components/heatpumpir/climate.py | 114 +++++++++++ esphome/components/heatpumpir/heatpumpir.cpp | 183 ++++++++++++++++++ esphome/components/heatpumpir/heatpumpir.h | 116 +++++++++++ .../heatpumpir/ir_sender_esphome.cpp | 32 +++ .../components/heatpumpir/ir_sender_esphome.h | 27 +++ platformio.ini | 1 + tests/test1.yaml | 7 + 9 files changed, 481 insertions(+) create mode 100644 esphome/components/heatpumpir/__init__.py create mode 100644 esphome/components/heatpumpir/climate.py create mode 100644 esphome/components/heatpumpir/heatpumpir.cpp create mode 100644 esphome/components/heatpumpir/heatpumpir.h create mode 100644 esphome/components/heatpumpir/ir_sender_esphome.cpp create mode 100644 esphome/components/heatpumpir/ir_sender_esphome.h diff --git a/CODEOWNERS b/CODEOWNERS index 49cb60c177..4c3084d463 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -64,6 +64,7 @@ esphome/components/graph/* @synco esphome/components/havells_solar/* @sourabhjaiswal esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/light/* @DotNetDann +esphome/components/heatpumpir/* @rob-deutsch esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/homeassistant/* @OttoWinter esphome/components/hrxl_maxsonar_wr/* @netmikey diff --git a/esphome/components/heatpumpir/__init__.py b/esphome/components/heatpumpir/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py new file mode 100644 index 0000000000..36e56aa5da --- /dev/null +++ b/esphome/components/heatpumpir/climate.py @@ -0,0 +1,114 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate_ir +from esphome.const import ( + CONF_ID, + CONF_MAX_TEMPERATURE, + CONF_MIN_TEMPERATURE, + CONF_PROTOCOL, + CONF_VISUAL, +) + +CODEOWNERS = ["@rob-deutsch"] + +AUTO_LOAD = ["climate_ir"] + +heatpumpir_ns = cg.esphome_ns.namespace("heatpumpir") +HeatpumpIRClimate = heatpumpir_ns.class_("HeatpumpIRClimate", climate_ir.ClimateIR) + +Protocol = heatpumpir_ns.enum("Protocol") +PROTOCOLS = { + "aux": Protocol.PROTOCOL_AUX, + "ballu": Protocol.PROTOCOL_BALLU, + "carrier_mca": Protocol.PROTOCOL_CARRIER_MCA, + "carrier_nqv": Protocol.PROTOCOL_CARRIER_NQV, + "daikin_arc417": Protocol.PROTOCOL_DAIKIN_ARC417, + "daikin_arc480": Protocol.PROTOCOL_DAIKIN_ARC480, + "daikin": Protocol.PROTOCOL_DAIKIN, + "fuego": Protocol.PROTOCOL_FUEGO, + "fujitsu_awyz": Protocol.PROTOCOL_FUJITSU_AWYZ, + "gree": Protocol.PROTOCOL_GREE, + "greeya": Protocol.PROTOCOL_GREEYAA, + "greeyan": Protocol.PROTOCOL_GREEYAN, + "hisense_aud": Protocol.PROTOCOL_HISENSE_AUD, + "hitachi": Protocol.PROTOCOL_HITACHI, + "hyundai": Protocol.PROTOCOL_HYUNDAI, + "ivt": Protocol.PROTOCOL_IVT, + "midea": Protocol.PROTOCOL_MIDEA, + "mitsubishi_fa": Protocol.PROTOCOL_MITSUBISHI_FA, + "mitsubishi_fd": Protocol.PROTOCOL_MITSUBISHI_FD, + "mitsubishi_fe": Protocol.PROTOCOL_MITSUBISHI_FE, + "mitsubishi_heavy_fdtc": Protocol.PROTOCOL_MITSUBISHI_HEAVY_FDTC, + "mitsubishi_heavy_zj": Protocol.PROTOCOL_MITSUBISHI_HEAVY_ZJ, + "mitsubishi_heavy_zm": Protocol.PROTOCOL_MITSUBISHI_HEAVY_ZM, + "mitsubishi_heavy_zmp": Protocol.PROTOCOL_MITSUBISHI_HEAVY_ZMP, + "mitsubishi_heavy_kj": Protocol.PROTOCOL_MITSUBISHI_KJ, + "mitsubishi_msc": Protocol.PROTOCOL_MITSUBISHI_MSC, + "mitsubishi_msy": Protocol.PROTOCOL_MITSUBISHI_MSY, + "mitsubishi_sez": Protocol.PROTOCOL_MITSUBISHI_SEZ, + "panasonic_ckp": Protocol.PROTOCOL_PANASONIC_CKP, + "panasonic_dke": Protocol.PROTOCOL_PANASONIC_DKE, + "panasonic_jke": Protocol.PROTOCOL_PANASONIC_JKE, + "panasonic_lke": Protocol.PROTOCOL_PANASONIC_LKE, + "panasonic_nke": Protocol.PROTOCOL_PANASONIC_NKE, + "samsung_aqv": Protocol.PROTOCOL_SAMSUNG_AQV, + "samsung_fjm": Protocol.PROTOCOL_SAMSUNG_FJM, + "sharp": Protocol.PROTOCOL_SHARP, + "toshiba_daiseikai": Protocol.PROTOCOL_TOSHIBA_DAISEIKAI, + "toshiba": Protocol.PROTOCOL_TOSHIBA, +} + +CONF_HORIZONTAL_DEFAULT = "horizontal_default" +HorizontalDirections = heatpumpir_ns.enum("HorizontalDirections") +HORIZONTAL_DIRECTIONS = { + "auto": HorizontalDirections.HORIZONTAL_DIRECTION_AUTO, + "middle": HorizontalDirections.HORIZONTAL_DIRECTION_MIDDLE, + "left": HorizontalDirections.HORIZONTAL_DIRECTION_LEFT, + "mleft": HorizontalDirections.HORIZONTAL_DIRECTION_MLEFT, + "mright": HorizontalDirections.HORIZONTAL_DIRECTION_MRIGHT, + "right": HorizontalDirections.HORIZONTAL_DIRECTION_RIGHT, +} + +CONF_VERTICAL_DEFAULT = "vertical_default" +VerticalDirections = heatpumpir_ns.enum("VerticalDirections") +VERTICAL_DIRECTIONS = { + "auto": VerticalDirections.VERTICAL_DIRECTION_AUTO, + "up": VerticalDirections.VERTICAL_DIRECTION_UP, + "mup": VerticalDirections.VERTICAL_DIRECTION_MUP, + "middle": VerticalDirections.VERTICAL_DIRECTION_MIDDLE, + "mdown": VerticalDirections.VERTICAL_DIRECTION_MDOWN, + "down": VerticalDirections.VERTICAL_DIRECTION_DOWN, +} + +CONFIG_SCHEMA = cv.All( + climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(HeatpumpIRClimate), + cv.Required(CONF_PROTOCOL): cv.enum(PROTOCOLS), + cv.Required(CONF_HORIZONTAL_DEFAULT): cv.enum(HORIZONTAL_DIRECTIONS), + cv.Required(CONF_VERTICAL_DEFAULT): cv.enum(VERTICAL_DIRECTIONS), + cv.Required(CONF_MIN_TEMPERATURE): cv.temperature, + cv.Required(CONF_MAX_TEMPERATURE): cv.temperature, + } + ), + cv.only_with_arduino, +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + if CONF_VISUAL not in config: + config[CONF_VISUAL] = {} + visual = config[CONF_VISUAL] + if CONF_MAX_TEMPERATURE not in visual: + visual[CONF_MAX_TEMPERATURE] = config[CONF_MAX_TEMPERATURE] + if CONF_MIN_TEMPERATURE not in visual: + visual[CONF_MIN_TEMPERATURE] = config[CONF_MIN_TEMPERATURE] + yield climate_ir.register_climate_ir(var, config) + cg.add(var.set_protocol(config[CONF_PROTOCOL])) + cg.add(var.set_horizontal_default(config[CONF_HORIZONTAL_DEFAULT])) + cg.add(var.set_vertical_default(config[CONF_VERTICAL_DEFAULT])) + cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE])) + cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE])) + + cg.add_library("tonia/HeatpumpIR", "1.0.15") diff --git a/esphome/components/heatpumpir/heatpumpir.cpp b/esphome/components/heatpumpir/heatpumpir.cpp new file mode 100644 index 0000000000..8d9fc962c0 --- /dev/null +++ b/esphome/components/heatpumpir/heatpumpir.cpp @@ -0,0 +1,183 @@ +#include "heatpumpir.h" + +#ifdef USE_ARDUINO + +#include +#include "ir_sender_esphome.h" +#include "HeatpumpIRFactory.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace heatpumpir { + +static const char *const TAG = "heatpumpir.climate"; + +const std::map> PROTOCOL_CONSTRUCTOR_MAP = { + {PROTOCOL_AUX, []() { return new AUXHeatpumpIR(); }}, // NOLINT + {PROTOCOL_BALLU, []() { return new BalluHeatpumpIR(); }}, // NOLINT + {PROTOCOL_CARRIER_MCA, []() { return new CarrierMCAHeatpumpIR(); }}, // NOLINT + {PROTOCOL_CARRIER_NQV, []() { return new CarrierNQVHeatpumpIR(); }}, // NOLINT + {PROTOCOL_DAIKIN_ARC417, []() { return new DaikinHeatpumpARC417IR(); }}, // NOLINT + {PROTOCOL_DAIKIN_ARC480, []() { return new DaikinHeatpumpARC480A14IR(); }}, // NOLINT + {PROTOCOL_DAIKIN, []() { return new DaikinHeatpumpIR(); }}, // NOLINT + {PROTOCOL_FUEGO, []() { return new FuegoHeatpumpIR(); }}, // NOLINT + {PROTOCOL_FUJITSU_AWYZ, []() { return new FujitsuHeatpumpIR(); }}, // NOLINT + {PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }}, // NOLINT + {PROTOCOL_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }}, // NOLINT + {PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT + {PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT + {PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT + {PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT + {PROTOCOL_IVT, []() { return new IVTHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MIDEA, []() { return new MideaHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_FA, []() { return new MitsubishiFAHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_FD, []() { return new MitsubishiFDHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_FE, []() { return new MitsubishiFEHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_HEAVY_FDTC, []() { return new MitsubishiHeavyFDTCHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_HEAVY_ZJ, []() { return new MitsubishiHeavyZJHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_HEAVY_ZM, []() { return new MitsubishiHeavyZMHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_HEAVY_ZMP, []() { return new MitsubishiHeavyZMPHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_KJ, []() { return new MitsubishiKJHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_MSC, []() { return new MitsubishiMSCHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_MSY, []() { return new MitsubishiMSYHeatpumpIR(); }}, // NOLINT + {PROTOCOL_MITSUBISHI_SEZ, []() { return new MitsubishiSEZKDXXHeatpumpIR(); }}, // NOLINT + {PROTOCOL_PANASONIC_CKP, []() { return new PanasonicCKPHeatpumpIR(); }}, // NOLINT + {PROTOCOL_PANASONIC_DKE, []() { return new PanasonicDKEHeatpumpIR(); }}, // NOLINT + {PROTOCOL_PANASONIC_JKE, []() { return new PanasonicJKEHeatpumpIR(); }}, // NOLINT + {PROTOCOL_PANASONIC_LKE, []() { return new PanasonicLKEHeatpumpIR(); }}, // NOLINT + {PROTOCOL_PANASONIC_NKE, []() { return new PanasonicNKEHeatpumpIR(); }}, // NOLINT + {PROTOCOL_SAMSUNG_AQV, []() { return new SamsungAQVHeatpumpIR(); }}, // NOLINT + {PROTOCOL_SAMSUNG_FJM, []() { return new SamsungFJMHeatpumpIR(); }}, // NOLINT + {PROTOCOL_SHARP, []() { return new SharpHeatpumpIR(); }}, // NOLINT + {PROTOCOL_TOSHIBA_DAISEIKAI, []() { return new ToshibaDaiseikaiHeatpumpIR(); }}, // NOLINT + {PROTOCOL_TOSHIBA, []() { return new ToshibaHeatpumpIR(); }}, // NOLINT +}; + +void HeatpumpIRClimate::setup() { + auto protocol_constructor = PROTOCOL_CONSTRUCTOR_MAP.find(protocol_); + if (protocol_constructor == PROTOCOL_CONSTRUCTOR_MAP.end()) { + ESP_LOGE(TAG, "Invalid protocol"); + return; + } + this->heatpump_ir_ = protocol_constructor->second(); + climate_ir::ClimateIR::setup(); +} + +void HeatpumpIRClimate::transmit_state() { + uint8_t power_mode_cmd; + uint8_t operating_mode_cmd; + uint8_t temperature_cmd; + uint8_t fan_speed_cmd; + + uint8_t swing_v_cmd; + switch (default_vertical_direction_) { + case VERTICAL_DIRECTION_AUTO: + swing_v_cmd = VDIR_AUTO; + break; + case VERTICAL_DIRECTION_UP: + swing_v_cmd = VDIR_UP; + break; + case VERTICAL_DIRECTION_MUP: + swing_v_cmd = VDIR_MUP; + break; + case VERTICAL_DIRECTION_MIDDLE: + swing_v_cmd = VDIR_MIDDLE; + break; + case VERTICAL_DIRECTION_MDOWN: + swing_v_cmd = VDIR_MDOWN; + break; + case VERTICAL_DIRECTION_DOWN: + swing_v_cmd = VDIR_DOWN; + break; + default: + ESP_LOGE(TAG, "Invalid default vertical direction"); + return; + } + if ((this->swing_mode == climate::CLIMATE_SWING_VERTICAL) || (this->swing_mode == climate::CLIMATE_SWING_BOTH)) { + swing_v_cmd = VDIR_SWING; + } + + uint8_t swing_h_cmd; + switch (default_horizontal_direction_) { + case HORIZONTAL_DIRECTION_AUTO: + swing_h_cmd = HDIR_AUTO; + break; + case HORIZONTAL_DIRECTION_MIDDLE: + swing_h_cmd = HDIR_MIDDLE; + break; + case HORIZONTAL_DIRECTION_LEFT: + swing_h_cmd = HDIR_LEFT; + break; + case HORIZONTAL_DIRECTION_MLEFT: + swing_h_cmd = HDIR_MLEFT; + break; + case HORIZONTAL_DIRECTION_MRIGHT: + swing_h_cmd = HDIR_MRIGHT; + break; + case HORIZONTAL_DIRECTION_RIGHT: + swing_h_cmd = HDIR_RIGHT; + break; + default: + ESP_LOGE(TAG, "Invalid default horizontal direction"); + return; + } + if ((this->swing_mode == climate::CLIMATE_SWING_HORIZONTAL) || (this->swing_mode == climate::CLIMATE_SWING_BOTH)) { + swing_h_cmd = HDIR_SWING; + } + + switch (this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO)) { + case climate::CLIMATE_FAN_LOW: + fan_speed_cmd = FAN_2; + break; + case climate::CLIMATE_FAN_MEDIUM: + fan_speed_cmd = FAN_3; + break; + case climate::CLIMATE_FAN_HIGH: + fan_speed_cmd = FAN_4; + break; + case climate::CLIMATE_FAN_AUTO: + default: + fan_speed_cmd = FAN_AUTO; + break; + } + + switch (this->mode) { + case climate::CLIMATE_MODE_COOL: + power_mode_cmd = POWER_ON; + operating_mode_cmd = MODE_COOL; + break; + case climate::CLIMATE_MODE_HEAT: + power_mode_cmd = POWER_ON; + operating_mode_cmd = MODE_HEAT; + break; + case climate::CLIMATE_MODE_AUTO: + power_mode_cmd = POWER_ON; + operating_mode_cmd = MODE_AUTO; + break; + case climate::CLIMATE_MODE_FAN_ONLY: + power_mode_cmd = POWER_ON; + operating_mode_cmd = MODE_FAN; + break; + case climate::CLIMATE_MODE_DRY: + power_mode_cmd = POWER_ON; + operating_mode_cmd = MODE_DRY; + break; + case climate::CLIMATE_MODE_OFF: + default: + power_mode_cmd = POWER_OFF; + operating_mode_cmd = MODE_AUTO; + break; + } + + temperature_cmd = (uint8_t) clamp(this->target_temperature, this->min_temperature_, this->max_temperature_); + + IRSenderESPHome esp_sender(0, this->transmitter_); + + heatpump_ir_->send(esp_sender, power_mode_cmd, operating_mode_cmd, fan_speed_cmd, temperature_cmd, swing_v_cmd, + swing_h_cmd); +} + +} // namespace heatpumpir +} // namespace esphome + +#endif diff --git a/esphome/components/heatpumpir/heatpumpir.h b/esphome/components/heatpumpir/heatpumpir.h new file mode 100644 index 0000000000..e2d2b45dc4 --- /dev/null +++ b/esphome/components/heatpumpir/heatpumpir.h @@ -0,0 +1,116 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "esphome/components/climate_ir/climate_ir.h" + +// Forward-declare HeatpumpIR class from library. We cannot include its header here because it has unnamespaced defines +// that conflict with ESPHome. +class HeatpumpIR; + +namespace esphome { +namespace heatpumpir { + +// Simple enum to represent protocols. +enum Protocol { + PROTOCOL_AUX, + PROTOCOL_BALLU, + PROTOCOL_CARRIER_MCA, + PROTOCOL_CARRIER_NQV, + PROTOCOL_DAIKIN_ARC417, + PROTOCOL_DAIKIN_ARC480, + PROTOCOL_DAIKIN, + PROTOCOL_FUEGO, + PROTOCOL_FUJITSU_AWYZ, + PROTOCOL_GREE, + PROTOCOL_GREEYAA, + PROTOCOL_GREEYAN, + PROTOCOL_HISENSE_AUD, + PROTOCOL_HITACHI, + PROTOCOL_HYUNDAI, + PROTOCOL_IVT, + PROTOCOL_MIDEA, + PROTOCOL_MITSUBISHI_FA, + PROTOCOL_MITSUBISHI_FD, + PROTOCOL_MITSUBISHI_FE, + PROTOCOL_MITSUBISHI_HEAVY_FDTC, + PROTOCOL_MITSUBISHI_HEAVY_ZJ, + PROTOCOL_MITSUBISHI_HEAVY_ZM, + PROTOCOL_MITSUBISHI_HEAVY_ZMP, + PROTOCOL_MITSUBISHI_KJ, + PROTOCOL_MITSUBISHI_MSC, + PROTOCOL_MITSUBISHI_MSY, + PROTOCOL_MITSUBISHI_SEZ, + PROTOCOL_PANASONIC_CKP, + PROTOCOL_PANASONIC_DKE, + PROTOCOL_PANASONIC_JKE, + PROTOCOL_PANASONIC_LKE, + PROTOCOL_PANASONIC_NKE, + PROTOCOL_SAMSUNG_AQV, + PROTOCOL_SAMSUNG_FJM, + PROTOCOL_SHARP, + PROTOCOL_TOSHIBA_DAISEIKAI, + PROTOCOL_TOSHIBA, +}; + +// Simple enum to represent horizontal directios +enum HorizontalDirection { + HORIZONTAL_DIRECTION_AUTO = 0, + HORIZONTAL_DIRECTION_MIDDLE = 1, + HORIZONTAL_DIRECTION_LEFT = 2, + HORIZONTAL_DIRECTION_MLEFT = 3, + HORIZONTAL_DIRECTION_MRIGHT = 4, + HORIZONTAL_DIRECTION_RIGHT = 5, +}; + +// Simple enum to represent vertical directions +enum VerticalDirection { + VERTICAL_DIRECTION_AUTO = 0, + VERTICAL_DIRECTION_UP = 1, + VERTICAL_DIRECTION_MUP = 2, + VERTICAL_DIRECTION_MIDDLE = 3, + VERTICAL_DIRECTION_MDOWN = 4, + VERTICAL_DIRECTION_DOWN = 5, +}; + +// Temperature +const float TEMP_MIN = 0; // Celsius +const float TEMP_MAX = 100; // Celsius + +class HeatpumpIRClimate : public climate_ir::ClimateIR { + public: + HeatpumpIRClimate() + : climate_ir::ClimateIR( + TEMP_MIN, TEMP_MAX, 1.0f, true, true, + std::set{climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH, climate::CLIMATE_FAN_AUTO}, + std::set{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL, + climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_BOTH}) {} + void setup() override; + void set_protocol(Protocol protocol) { this->protocol_ = protocol; } + void set_horizontal_default(HorizontalDirection horizontal_direction) { + this->default_horizontal_direction_ = horizontal_direction; + } + void set_vertical_default(VerticalDirection vertical_direction) { + this->default_vertical_direction_ = vertical_direction; + } + + void set_max_temperature(float temperature) { this->max_temperature_ = temperature; } + void set_min_temperature(float temperature) { this->min_temperature_ = temperature; } + + protected: + HeatpumpIR *heatpump_ir_; + /// Transmit via IR the state of this climate controller. + void transmit_state() override; + Protocol protocol_; + HorizontalDirection default_horizontal_direction_; + VerticalDirection default_vertical_direction_; + + float max_temperature_; + float min_temperature_; +}; + +} // namespace heatpumpir +} // namespace esphome + +#endif diff --git a/esphome/components/heatpumpir/ir_sender_esphome.cpp b/esphome/components/heatpumpir/ir_sender_esphome.cpp new file mode 100644 index 0000000000..24c7933563 --- /dev/null +++ b/esphome/components/heatpumpir/ir_sender_esphome.cpp @@ -0,0 +1,32 @@ +#include "ir_sender_esphome.h" + +#ifdef USE_ARDUINO + +namespace esphome { +namespace heatpumpir { + +void IRSenderESPHome::setFrequency(int frequency) { // NOLINT(readability-identifier-naming) + auto data = transmit_.get_data(); + data->set_carrier_frequency(1000 * frequency); +} + +// Send an IR 'mark' symbol, i.e. transmitter ON +void IRSenderESPHome::mark(int mark_length) { + auto data = transmit_.get_data(); + data->mark(mark_length); +} + +// Send an IR 'space' symbol, i.e. transmitter OFF +void IRSenderESPHome::space(int space_length) { + if (space_length) { + auto data = transmit_.get_data(); + data->space(space_length); + } else { + transmit_.perform(); + } +} + +} // namespace heatpumpir +} // namespace esphome + +#endif diff --git a/esphome/components/heatpumpir/ir_sender_esphome.h b/esphome/components/heatpumpir/ir_sender_esphome.h new file mode 100644 index 0000000000..24e8ba9883 --- /dev/null +++ b/esphome/components/heatpumpir/ir_sender_esphome.h @@ -0,0 +1,27 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "esphome/components/remote_base/remote_base.h" +#include "esphome/components/remote_transmitter/remote_transmitter.h" +#include // arduino-heatpump library + +namespace esphome { +namespace heatpumpir { + +class IRSenderESPHome : public IRSender { + public: + IRSenderESPHome(uint8_t pin, remote_transmitter::RemoteTransmitterComponent *transmitter) + : IRSender(pin), transmit_(transmitter->transmit()){}; + void setFrequency(int frequency) override; // NOLINT(readability-identifier-naming) + void space(int space_length) override; + void mark(int mark_length) override; + + protected: + remote_transmitter::RemoteTransmitterComponent::TransmitCall transmit_; +}; + +} // namespace heatpumpir +} // namespace esphome + +#endif diff --git a/platformio.ini b/platformio.ini index e038224f69..9cc7477d51 100644 --- a/platformio.ini +++ b/platformio.ini @@ -49,6 +49,7 @@ lib_deps = glmnet/Dsmr@0.5 ; dsmr rweather/Crypto@0.2.0 ; dsmr dudanov/MideaUART@1.1.8 ; midea + tonia/HeatpumpIR@^1.0.15 ; heatpumpir build_flags = ${common.build_flags} -DUSE_ARDUINO diff --git a/tests/test1.yaml b/tests/test1.yaml index 130022c14d..fd142a63fd 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1681,6 +1681,13 @@ climate: name: Toshiba Climate - platform: hitachi_ac344 name: Hitachi Climate + - platform: heatpumpir + protocol: mitsubishi_heavy_zm + horizontal_default: left + vertical_default: up + name: HeatpumpIR Climate + min_temperature: 18 + max_temperature: 30 - platform: midea id: midea_unit uart_id: uart0 From 1184bbc976e27aebbfcdaee27219d2141c22efd2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 12 Oct 2021 21:22:38 +0200 Subject: [PATCH 1535/1841] Reduce IRAM usage in test3 (#2499) --- tests/test2.yaml | 6 ++++++ tests/test3.yaml | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test2.yaml b/tests/test2.yaml index 364bcec28f..7e71d1ab4e 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -61,6 +61,12 @@ mcp3008: - id: 'mcp3008_hub' cs_pin: GPIO12 +output: + - platform: ac_dimmer + id: dimmer1 + gate_pin: GPIO5 + zero_cross_pin: GPIO12 + sensor: - platform: homeassistant entity_id: sensor.hello_world diff --git a/tests/test3.yaml b/tests/test3.yaml index b261d6cc8e..9d2839a026 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -261,8 +261,6 @@ logger: level: DEBUG esp8266_store_log_strings_in_flash: true -web_server: - deep_sleep: run_duration: 20s sleep_duration: 50s @@ -1092,10 +1090,6 @@ output: return {s}; outputs: - id: custom_float - - platform: ac_dimmer - id: dimmer1 - gate_pin: GPIO5 - zero_cross_pin: GPIO12 - platform: slow_pwm pin: GPIO5 id: my_slow_pwm From 34db9d9ef2b0748abee992958e783ec88a69398b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Oct 2021 08:23:24 +1300 Subject: [PATCH 1536/1841] Add optional timeout for wait_until action (#2282) --- esphome/automation.py | 7 +++++++ esphome/core/base_automation.h | 12 ++++++++++++ tests/test1.yaml | 5 ++++- tests/test3.yaml | 12 +++++++----- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/esphome/automation.py b/esphome/automation.py index 71c564b906..0768bf8869 100644 --- a/esphome/automation.py +++ b/esphome/automation.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_ELSE, CONF_ID, CONF_THEN, + CONF_TIMEOUT, CONF_TRIGGER_ID, CONF_TYPE_ID, CONF_TIME, @@ -244,6 +245,9 @@ def validate_wait_until(value): schema = cv.Schema( { cv.Required(CONF_CONDITION): validate_potentially_and_condition, + cv.Optional(CONF_TIMEOUT): cv.templatable( + cv.positive_time_period_milliseconds + ), } ) if isinstance(value, dict) and CONF_CONDITION in value: @@ -255,6 +259,9 @@ def validate_wait_until(value): async def wait_until_action_to_code(config, action_id, template_arg, args): conditions = await build_condition(config[CONF_CONDITION], template_arg, args) var = cg.new_Pvariable(action_id, template_arg, conditions) + if CONF_TIMEOUT in config: + template_ = await cg.templatable(config[CONF_TIMEOUT], args, cg.uint32) + cg.add(var.set_timeout_value(template_)) await cg.register_component(var, {}) return var diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index fa49786d1d..d97d369d33 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -228,6 +228,8 @@ template class WaitUntilAction : public Action, public Co public: WaitUntilAction(Condition *condition) : condition_(condition) {} + TEMPLATABLE_VALUE(uint32_t, timeout_value) + void play_complex(Ts... x) override { this->num_running_++; // Check if we can continue immediately. @@ -238,6 +240,12 @@ template class WaitUntilAction : public Action, public Co return; } this->var_ = std::make_tuple(x...); + + if (this->timeout_value_.has_value()) { + auto f = std::bind(&WaitUntilAction::play_next_, this, x...); + this->set_timeout("timeout", this->timeout_value_.value(x...), f); + } + this->loop(); } @@ -249,6 +257,8 @@ template class WaitUntilAction : public Action, public Co return; } + this->cancel_timeout("timeout"); + this->play_next_tuple_(this->var_); } @@ -257,6 +267,8 @@ template class WaitUntilAction : public Action, public Co void play(Ts... x) override { /* ignore - see play_complex */ } + void stop() override { this->cancel_timeout("timeout"); } + protected: Condition *condition_; std::tuple var_{}; diff --git a/tests/test1.yaml b/tests/test1.yaml index fd142a63fd..540a715dde 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -364,8 +364,11 @@ sensor: then: - lambda: >- ESP_LOGD("main", "Got value range %f", x); + - wait_until: wifi.connected - wait_until: - binary_sensor.is_on: binary_sensor1 + condition: + binary_sensor.is_on: binary_sensor1 + timeout: 1s on_raw_value: - lambda: >- ESP_LOGD("main", "Got raw value %f", x); diff --git a/tests/test3.yaml b/tests/test3.yaml index 9d2839a026..73e314c94c 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -5,10 +5,13 @@ esphome: board: d1_mini build_path: build/test3 on_boot: - - wait_until: - - api.connected - - wifi.connected - - time.has_time + - if: + condition: + - api.connected + - wifi.connected + - time.has_time + then: + - logger.log: "Have time" includes: - custom.h @@ -1291,4 +1294,3 @@ dsmr: daly_bms: update_interval: 20s uart_id: uart1 - From c33077bc61a214df23d82753ae9077898c280786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Trevi=C3=B1o?= Date: Tue, 12 Oct 2021 21:42:51 +0200 Subject: [PATCH 1537/1841] Improves ct_clamp component accuracy (#2283) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafa Treviño --- .../components/ct_clamp/ct_clamp_sensor.cpp | 22 ++++++++++++------- esphome/components/ct_clamp/ct_clamp_sensor.h | 1 + 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/esphome/components/ct_clamp/ct_clamp_sensor.cpp b/esphome/components/ct_clamp/ct_clamp_sensor.cpp index 0052b1426d..51b0f1318c 100644 --- a/esphome/components/ct_clamp/ct_clamp_sensor.cpp +++ b/esphome/components/ct_clamp/ct_clamp_sensor.cpp @@ -31,19 +31,20 @@ void CTClampSensor::update() { return; } - float dc = this->sample_sum_ / this->num_samples_; - float var = (this->sample_squared_sum_ / this->num_samples_) - dc * dc; - float ac = std::sqrt(var); - ESP_LOGD(TAG, "'%s' - Got %d samples", this->name_.c_str(), this->num_samples_); - ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA", this->name_.c_str(), ac); - this->publish_state(ac); + const float rms_ac_dc_squared = this->sample_squared_sum_ / this->num_samples_; + const float rms_dc = this->sample_sum_ / this->num_samples_; + const float rms_ac = std::sqrt(rms_ac_dc_squared - rms_dc * rms_dc); + ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA after %d different samples (%d SPS)", this->name_.c_str(), rms_ac, + this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_); + this->publish_state(rms_ac); }); // Set sampling values - this->is_sampling_ = true; + this->last_value_ = 0.0; this->num_samples_ = 0; this->sample_sum_ = 0.0f; this->sample_squared_sum_ = 0.0f; + this->is_sampling_ = true; } void CTClampSensor::loop() { @@ -55,9 +56,14 @@ void CTClampSensor::loop() { if (std::isnan(value)) return; + // Assuming a sine wave, avoid requesting values faster than the ADC can provide them + if (this->last_value_ == value) + return; + this->last_value_ = value; + + this->num_samples_++; this->sample_sum_ += value; this->sample_squared_sum_ += value * value; - this->num_samples_++; } } // namespace ct_clamp diff --git a/esphome/components/ct_clamp/ct_clamp_sensor.h b/esphome/components/ct_clamp/ct_clamp_sensor.h index 10601ab852..db4dc1ea57 100644 --- a/esphome/components/ct_clamp/ct_clamp_sensor.h +++ b/esphome/components/ct_clamp/ct_clamp_sensor.h @@ -43,6 +43,7 @@ class CTClampSensor : public sensor::Sensor, public PollingComponent { * https://en.wikipedia.org/wiki/Root_mean_square */ + float last_value_ = 0.0f; float sample_sum_ = 0.0f; float sample_squared_sum_ = 0.0f; uint32_t num_samples_ = 0; From 4406a08fa7b88ef9b7a97e273c1c1d127d0c19aa Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Oct 2021 09:06:52 +1300 Subject: [PATCH 1538/1841] Allow multiple pn532_spi entries (#2489) --- esphome/components/pn532_spi/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/pn532_spi/__init__.py b/esphome/components/pn532_spi/__init__.py index 2683f34ad5..8a8ab1b175 100644 --- a/esphome/components/pn532_spi/__init__.py +++ b/esphome/components/pn532_spi/__init__.py @@ -6,6 +6,7 @@ from esphome.const import CONF_ID AUTO_LOAD = ["pn532"] CODEOWNERS = ["@OttoWinter", "@jesserockz"] DEPENDENCIES = ["spi"] +MULTI_CONF = True pn532_spi_ns = cg.esphome_ns.namespace("pn532_spi") PN532Spi = pn532_spi_ns.class_("PN532Spi", pn532.PN532, spi.SPIDevice) From 3dee057826cbccbdb90a0fe92b6664eb029c7d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mayoral=20Mart=C3=ADnez?= Date: Wed, 13 Oct 2021 00:35:30 +0200 Subject: [PATCH 1539/1841] Add throttle_average sensor filter (#2485) --- esphome/components/sensor/__init__.py | 10 ++++++++++ esphome/components/sensor/filter.cpp | 25 +++++++++++++++++++++++++ esphome/components/sensor/filter.h | 20 ++++++++++++++++++++ tests/test1.yaml | 1 + 4 files changed, 56 insertions(+) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index cb74a41119..4b2e9dc019 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -160,6 +160,7 @@ SlidingWindowMovingAverageFilter = sensor_ns.class_( ExponentialMovingAverageFilter = sensor_ns.class_( "ExponentialMovingAverageFilter", Filter ) +ThrottleAverageFilter = sensor_ns.class_("ThrottleAverageFilter", Filter, cg.Component) LambdaFilter = sensor_ns.class_("LambdaFilter", Filter) OffsetFilter = sensor_ns.class_("OffsetFilter", Filter) MultiplyFilter = sensor_ns.class_("MultiplyFilter", Filter) @@ -381,6 +382,15 @@ async def exponential_moving_average_filter_to_code(config, filter_id): return cg.new_Pvariable(filter_id, config[CONF_ALPHA], config[CONF_SEND_EVERY]) +@FILTER_REGISTRY.register( + "throttle_average", ThrottleAverageFilter, cv.positive_time_period_milliseconds +) +async def throttle_average_filter_to_code(config, filter_id): + var = cg.new_Pvariable(filter_id, config) + await cg.register_component(var, {}) + return var + + @FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda) async def lambda_filter_to_code(config, filter_id): lambda_ = await cg.process_lambda( diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index 63801e7996..321e3a4a4f 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -187,6 +187,31 @@ optional ExponentialMovingAverageFilter::new_value(float value) { void ExponentialMovingAverageFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; } void ExponentialMovingAverageFilter::set_alpha(float alpha) { this->alpha_ = alpha; } +// ThrottleAverageFilter +ThrottleAverageFilter::ThrottleAverageFilter(uint32_t time_period) : time_period_(time_period) {} + +optional ThrottleAverageFilter::new_value(float value) { + ESP_LOGVV(TAG, "ThrottleAverageFilter(%p)::new_value(value=%f)", this, value); + if (!std::isnan(value)) { + this->sum_ += value; + this->n_++; + } + return {}; +} +void ThrottleAverageFilter::setup() { + this->set_interval("throttle_average", this->time_period_, [this]() { + ESP_LOGVV(TAG, "ThrottleAverageFilter(%p)::interval(sum=%f, n=%i)", this, this->sum_, this->n_); + if (this->n_ == 0) { + this->output(NAN); + } else { + this->output(this->sum_ / this->n_); + this->sum_ = 0.0f; + this->n_ = 0; + } + }); +} +float ThrottleAverageFilter::get_setup_priority() const { return setup_priority::HARDWARE; } + // LambdaFilter LambdaFilter::LambdaFilter(lambda_filter_t lambda_filter) : lambda_filter_(std::move(lambda_filter)) {} const lambda_filter_t &LambdaFilter::get_lambda_filter() const { return this->lambda_filter_; } diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 29a6813ea9..d595e419a6 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -178,6 +178,26 @@ class ExponentialMovingAverageFilter : public Filter { float alpha_; }; +/** Simple throttle average filter. + * + * It takes the average of all the values received in a period of time. + */ +class ThrottleAverageFilter : public Filter, public Component { + public: + explicit ThrottleAverageFilter(uint32_t time_period); + + void setup() override; + + optional new_value(float value) override; + + float get_setup_priority() const override; + + protected: + uint32_t time_period_; + float sum_{0.0f}; + unsigned int n_{0}; +}; + using lambda_filter_t = std::function(float)>; /** This class allows for creation of simple template filters. diff --git a/tests/test1.yaml b/tests/test1.yaml index 540a715dde..157ccfc5d1 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -343,6 +343,7 @@ sensor: - exponential_moving_average: alpha: 0.1 send_every: 15 + - throttle_average: 60s - throttle: 1s - heartbeat: 5s - debounce: 0.1s From fe5a6847b5d51bd0e4120a4697eeaf7b2c468d92 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Oct 2021 16:40:46 +1300 Subject: [PATCH 1540/1841] Bump version to 2021.11.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a9eec3e249..4f7d95d694 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.10.0-dev" +__version__ = "2021.11.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From bb86db869a24ec88c0e0fd1d662d6f93ad5799be Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Oct 2021 22:09:38 +1300 Subject: [PATCH 1541/1841] Fix bad merge --- esphome/components/api/api_connection.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 81ff11fcd9..47171ba50f 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -161,19 +161,6 @@ void APIConnection::on_disconnect_response(const DisconnectResponse &value) { // pass } -DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) { - // remote initiated disconnect_client - // don't close yet, we still need to send the disconnect response - // close will happen on next loop - ESP_LOGD(TAG, "%s requested disconnected", client_info_.c_str()); - this->next_close_ = true; - DisconnectResponse resp; - return resp; -} -void APIConnection::on_disconnect_response(const DisconnectResponse &value) { - // pass -} - #ifdef USE_BINARY_SENSOR bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state) { if (!this->state_subscription_) From 534ce11d54437d1a8426be2392743ae712321ab0 Mon Sep 17 00:00:00 2001 From: razorback16 Date: Wed, 13 Oct 2021 09:45:41 -0700 Subject: [PATCH 1542/1841] TCS34725 BugFix and GA factor (#2445) - Fixed endianness bug on tcs34725 data read - Fixed lux adjustments based on gain, integration time and GA factor - Added glass attenuation factor to allow using this sensor behind semi transparent glass Co-authored-by: Razorback16 --- esphome/components/tcs34725/sensor.py | 19 ++- esphome/components/tcs34725/tcs34725.cpp | 180 +++++++++++++++++++---- esphome/components/tcs34725/tcs34725.h | 27 +++- tests/test3.yaml | 2 +- 4 files changed, 197 insertions(+), 31 deletions(-) diff --git a/esphome/components/tcs34725/sensor.py b/esphome/components/tcs34725/sensor.py index 6c74c86faf..fcc56e395f 100644 --- a/esphome/components/tcs34725/sensor.py +++ b/esphome/components/tcs34725/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_GAIN, CONF_ID, CONF_ILLUMINANCE, + CONF_GLASS_ATTENUATION_FACTOR, CONF_INTEGRATION_TIME, DEVICE_CLASS_ILLUMINANCE, ICON_LIGHTBULB, @@ -34,8 +35,20 @@ TCS34725_INTEGRATION_TIMES = { "24ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_24MS, "50ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_50MS, "101ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_101MS, + "120ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_120MS, "154ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_154MS, - "700ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_700MS, + "180ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_180MS, + "199ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_199MS, + "240ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_240MS, + "300ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_300MS, + "360ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_360MS, + "401ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_401MS, + "420ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_420MS, + "480ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_480MS, + "499ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_499MS, + "540ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_540MS, + "600ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_600MS, + "614ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_614MS, } TCS34725Gain = tcs34725_ns.enum("TCS34725Gain") @@ -79,6 +92,9 @@ CONFIG_SCHEMA = ( TCS34725_INTEGRATION_TIMES, lower=True ), cv.Optional(CONF_GAIN, default="1X"): cv.enum(TCS34725_GAINS, upper=True), + cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=1.0): cv.float_range( + min=1.0 + ), } ) .extend(cv.polling_component_schema("60s")) @@ -93,6 +109,7 @@ async def to_code(config): cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) cg.add(var.set_gain(config[CONF_GAIN])) + cg.add(var.set_glass_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR])) if CONF_RED_CHANNEL in config: sens = await sensor.new_sensor(config[CONF_RED_CHANNEL]) diff --git a/esphome/components/tcs34725/tcs34725.cpp b/esphome/components/tcs34725/tcs34725.cpp index 564d3dcda7..f7ffe2a97d 100644 --- a/esphome/components/tcs34725/tcs34725.cpp +++ b/esphome/components/tcs34725/tcs34725.cpp @@ -26,10 +26,8 @@ void TCS34725Component::setup() { return; } - uint8_t integration_reg = this->integration_time_; - uint8_t gain_reg = this->gain_; - if (!this->write_byte(TCS34725_REGISTER_ATIME, integration_reg) || - !this->write_byte(TCS34725_REGISTER_CONTROL, gain_reg)) { + if (!this->write_byte(TCS34725_REGISTER_ATIME, this->integration_reg_) || + !this->write_byte(TCS34725_REGISTER_CONTROL, this->gain_reg_)) { this->mark_failed(); return; } @@ -61,6 +59,114 @@ void TCS34725Component::dump_config() { LOG_SENSOR(" ", "Color Temperature", this->color_temperature_sensor_); } float TCS34725Component::get_setup_priority() const { return setup_priority::DATA; } + +/*! + * @brief Converts the raw R/G/B values to color temperature in degrees + * Kelvin using the algorithm described in DN40 from Taos (now AMS). + * @param r + * Red value + * @param g + * Green value + * @param b + * Blue value + * @param c + * Clear channel value + * @return Color temperature in degrees Kelvin + */ +void TCS34725Component::calculate_temperature_and_lux_(uint16_t r, uint16_t g, uint16_t b, uint16_t c) { + float r2, g2, b2; /* RGB values minus IR component */ + float sat; /* Digital saturation level */ + float ir; /* Inferred IR content */ + + this->illuminance_ = 0; // Assign 0 value before calculation + this->color_temperature_ = 0; + + const float ga = this->glass_attenuation_; // Glass Attenuation Factor + static const float DF = 310.f; // Device Factor + static const float R_COEF = 0.136f; // + static const float G_COEF = 1.f; // used in lux computation + static const float B_COEF = -0.444f; // + static const float CT_COEF = 3810.f; // Color Temperature Coefficient + static const float CT_OFFSET = 1391.f; // Color Temperatuer Offset + + if (c == 0) { + return; + } + + /* Analog/Digital saturation: + * + * (a) As light becomes brighter, the clear channel will tend to + * saturate first since R+G+B is approximately equal to C. + * (b) The TCS34725 accumulates 1024 counts per 2.4ms of integration + * time, up to a maximum values of 65535. This means analog + * saturation can occur up to an integration time of 153.6ms + * (64*2.4ms=153.6ms). + * (c) If the integration time is > 153.6ms, digital saturation will + * occur before analog saturation. Digital saturation occurs when + * the count reaches 65535. + */ + if ((256 - this->integration_reg_) > 63) { + /* Track digital saturation */ + sat = 65535.f; + } else { + /* Track analog saturation */ + sat = 1024.f * (256.f - this->integration_reg_); + } + + /* Ripple rejection: + * + * (a) An integration time of 50ms or multiples of 50ms are required to + * reject both 50Hz and 60Hz ripple. + * (b) If an integration time faster than 50ms is required, you may need + * to average a number of samples over a 50ms period to reject ripple + * from fluorescent and incandescent light sources. + * + * Ripple saturation notes: + * + * (a) If there is ripple in the received signal, the value read from C + * will be less than the max, but still have some effects of being + * saturated. This means that you can be below the 'sat' value, but + * still be saturating. At integration times >150ms this can be + * ignored, but <= 150ms you should calculate the 75% saturation + * level to avoid this problem. + */ + if (this->integration_time_ < 150) { + /* Adjust sat to 75% to avoid analog saturation if atime < 153.6ms */ + sat -= sat / 4.f; + } + + /* Check for saturation and mark the sample as invalid if true */ + if (c >= sat) { + return; + } + + /* AMS RGB sensors have no IR channel, so the IR content must be */ + /* calculated indirectly. */ + ir = ((r + g + b) > c) ? (r + g + b - c) / 2 : 0; + + /* Remove the IR component from the raw RGB values */ + r2 = r - ir; + g2 = g - ir; + b2 = b - ir; + + if (r2 == 0) { + return; + } + + // Lux Calculation (DN40 3.2) + + float g1 = R_COEF * r2 + G_COEF * g2 + B_COEF * b2; + float cpl = (this->integration_time_ * this->gain_) / (ga * DF); + this->illuminance_ = g1 / cpl; + + // Color Temperature Calculation (DN40) + /* A simple method of measuring color temp is to use the ratio of blue */ + /* to red light, taking IR cancellation into account. */ + this->color_temperature_ = (CT_COEF * b2) / /** Color temp coefficient. */ + r2 + + CT_OFFSET; /** Color temp offset. */ +} + void TCS34725Component::update() { uint16_t raw_c; uint16_t raw_r; @@ -74,6 +180,12 @@ void TCS34725Component::update() { return; } + // May need to fix endianness as the data read over I2C is big-endian, but most ESP platforms are little-endian + raw_c = i2c::i2ctohs(raw_c); + raw_r = i2c::i2ctohs(raw_r); + raw_g = i2c::i2ctohs(raw_g); + raw_b = i2c::i2ctohs(raw_b); + const float channel_c = raw_c / 655.35f; const float channel_r = raw_r / 655.35f; const float channel_g = raw_g / 655.35f; @@ -87,38 +199,54 @@ void TCS34725Component::update() { if (this->blue_sensor_ != nullptr) this->blue_sensor_->publish_state(channel_b); - // Formulae taken from Adafruit TCS35725 library - float illuminance = (-0.32466f * channel_r) + (1.57837f * channel_g) + (-0.73191f * channel_b); + if (this->illuminance_sensor_ || this->color_temperature_sensor_) { + calculate_temperature_and_lux_(raw_r, raw_g, raw_b, raw_c); + } + if (this->illuminance_sensor_ != nullptr) - this->illuminance_sensor_->publish_state(illuminance); + this->illuminance_sensor_->publish_state(this->illuminance_); - // Color temperature - // 1. Convert RGB to XYZ color space - const float x = (-0.14282f * raw_r) + (1.54924f * raw_g) + (-0.95641f * raw_b); - const float y = (-0.32466f * raw_r) + (1.57837f * raw_g) + (-0.73191f * raw_b); - const float z = (-0.68202f * raw_r) + (0.77073f * raw_g) + (0.56332f * raw_b); - - // 2. Calculate chromacity coordinates - const float xc = (x) / (x + y + z); - const float yc = (y) / (x + y + z); - - // 3. Use McCamy's formula to determine the color temperature - const float n = (xc - 0.3320f) / (0.1858f - yc); - - // 4. final color temperature in Kelvin. - const float color_temperature = (449.0f * powf(n, 3.0f)) + (3525.0f * powf(n, 2.0f)) + (6823.3f * n) + 5520.33f; if (this->color_temperature_sensor_ != nullptr) - this->color_temperature_sensor_->publish_state(color_temperature); + this->color_temperature_sensor_->publish_state(this->color_temperature_); ESP_LOGD(TAG, "Got R=%.1f%%,G=%.1f%%,B=%.1f%%,C=%.1f%% Illuminance=%.1flx Color Temperature=%.1fK", channel_r, - channel_g, channel_b, channel_c, illuminance, color_temperature); + channel_g, channel_b, channel_c, this->illuminance_, this->color_temperature_); this->status_clear_warning(); } void TCS34725Component::set_integration_time(TCS34725IntegrationTime integration_time) { - this->integration_time_ = integration_time; + this->integration_reg_ = integration_time; + this->integration_time_ = (256.f - integration_time) * 2.4f; +} +void TCS34725Component::set_gain(TCS34725Gain gain) { + this->gain_reg_ = gain; + switch (gain) { + case TCS34725Gain::TCS34725_GAIN_1X: + this->gain_ = 1.f; + break; + case TCS34725Gain::TCS34725_GAIN_4X: + this->gain_ = 4.f; + break; + case TCS34725Gain::TCS34725_GAIN_16X: + this->gain_ = 16.f; + break; + case TCS34725Gain::TCS34725_GAIN_60X: + this->gain_ = 60.f; + break; + default: + this->gain_ = 1.f; + break; + } +} + +void TCS34725Component::set_glass_attenuation_factor(float ga) { + // The Glass Attenuation (FA) factor used to compensate for lower light + // levels at the device due to the possible presence of glass. The GA is + // the inverse of the glass transmissivity (T), so GA = 1/T. A transmissivity + // of 50% gives GA = 1 / 0.50 = 2. If no glass is present, use GA = 1. + // See Application Note: DN40-Rev 1.0 + this->glass_attenuation_ = ga; } -void TCS34725Component::set_gain(TCS34725Gain gain) { this->gain_ = gain; } } // namespace tcs34725 } // namespace esphome diff --git a/esphome/components/tcs34725/tcs34725.h b/esphome/components/tcs34725/tcs34725.h index b914db0eb0..47ed2959c6 100644 --- a/esphome/components/tcs34725/tcs34725.h +++ b/esphome/components/tcs34725/tcs34725.h @@ -12,8 +12,20 @@ enum TCS34725IntegrationTime { TCS34725_INTEGRATION_TIME_24MS = 0xF6, TCS34725_INTEGRATION_TIME_50MS = 0xEB, TCS34725_INTEGRATION_TIME_101MS = 0xD5, + TCS34725_INTEGRATION_TIME_120MS = 0xCE, TCS34725_INTEGRATION_TIME_154MS = 0xC0, - TCS34725_INTEGRATION_TIME_700MS = 0x00, + TCS34725_INTEGRATION_TIME_180MS = 0xB5, + TCS34725_INTEGRATION_TIME_199MS = 0xAD, + TCS34725_INTEGRATION_TIME_240MS = 0x9C, + TCS34725_INTEGRATION_TIME_300MS = 0x83, + TCS34725_INTEGRATION_TIME_360MS = 0x6A, + TCS34725_INTEGRATION_TIME_401MS = 0x59, + TCS34725_INTEGRATION_TIME_420MS = 0x51, + TCS34725_INTEGRATION_TIME_480MS = 0x38, + TCS34725_INTEGRATION_TIME_499MS = 0x30, + TCS34725_INTEGRATION_TIME_540MS = 0x1F, + TCS34725_INTEGRATION_TIME_600MS = 0x06, + TCS34725_INTEGRATION_TIME_614MS = 0x00, }; enum TCS34725Gain { @@ -27,6 +39,7 @@ class TCS34725Component : public PollingComponent, public i2c::I2CDevice { public: void set_integration_time(TCS34725IntegrationTime integration_time); void set_gain(TCS34725Gain gain); + void set_glass_attenuation_factor(float ga); void set_clear_sensor(sensor::Sensor *clear_sensor) { clear_sensor_ = clear_sensor; } void set_red_sensor(sensor::Sensor *red_sensor) { red_sensor_ = red_sensor; } @@ -49,8 +62,16 @@ class TCS34725Component : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *blue_sensor_{nullptr}; sensor::Sensor *illuminance_sensor_{nullptr}; sensor::Sensor *color_temperature_sensor_{nullptr}; - TCS34725IntegrationTime integration_time_{TCS34725_INTEGRATION_TIME_2_4MS}; - TCS34725Gain gain_{TCS34725_GAIN_1X}; + float integration_time_{2.4}; + float gain_{1.0}; + float glass_attenuation_{1.0}; + float illuminance_; + float color_temperature_; + + private: + void calculate_temperature_and_lux_(uint16_t r, uint16_t g, uint16_t b, uint16_t c); + uint8_t integration_reg_{TCS34725_INTEGRATION_TIME_2_4MS}; + uint8_t gain_reg_{TCS34725_GAIN_1X}; }; } // namespace tcs34725 diff --git a/tests/test3.yaml b/tests/test3.yaml index 73e314c94c..4c76967842 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -403,7 +403,7 @@ sensor: name: Illuminance color_temperature: name: Color Temperature - integration_time: 700ms + integration_time: 614ms gain: 60x - platform: custom lambda: |- From 859e5083925edc541d4c20509f9adfe7497b3231 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Wed, 13 Oct 2021 18:50:27 +0200 Subject: [PATCH 1543/1841] change millis() to micros() in feed_wdt for 3ms check (#2492) --- esphome/core/application.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index a4d61f819c..f67fc826cf 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -109,8 +109,8 @@ void Application::loop() { void IRAM_ATTR HOT Application::feed_wdt() { static uint32_t last_feed = 0; - uint32_t now = millis(); - if (now - last_feed > 3) { + uint32_t now = micros(); + if (now - last_feed > 3000) { arch_feed_wdt(); last_feed = now; #ifdef USE_STATUS_LED From e06b6d7140ade52fa956de83b9a2944141a945aa Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 13 Oct 2021 21:22:57 +0200 Subject: [PATCH 1544/1841] Add ESP32 IDF as a test env for PRs (#2494) Co-authored-by: Maurice Makaay --- .github/PULL_REQUEST_TEMPLATE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index aa90ef365f..25411c19f5 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -16,6 +16,7 @@ Quick description and explanation of changes ## Test Environment - [ ] ESP32 +- [ ] ESP32 IDF - [ ] ESP8266 ## Example entry for `config.yaml`: From 05388d2dfcb6f2b49e13cefb2dae04f478d9f1d4 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 13 Oct 2021 21:53:00 +0200 Subject: [PATCH 1545/1841] Fix light state remaining on after turn off with transition (#2509) --- esphome/components/light/transformers.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/esphome/components/light/transformers.h b/esphome/components/light/transformers.h index 90646f4e61..c22846ceb1 100644 --- a/esphome/components/light/transformers.h +++ b/esphome/components/light/transformers.h @@ -18,10 +18,13 @@ class LightTransitionTransformer : public LightTransformer { this->start_values_.set_brightness(0.0f); } - // When turning light off from on state, use source state and only decrease brightness to zero. + // When turning light off from on state, use source state and only decrease brightness to zero. Use a second + // variable for transition end state, as overwriting target_values breaks LightState logic. if (this->start_values_.is_on() && !this->target_values_.is_on()) { - this->target_values_ = LightColorValues(this->start_values_); - this->target_values_.set_brightness(0.0f); + this->end_values_ = LightColorValues(this->start_values_); + this->end_values_.set_brightness(0.0f); + } else { + this->end_values_ = LightColorValues(this->target_values_); } // When changing color mode, go through off state, as color modes are orthogonal and there can't be two active. @@ -43,7 +46,7 @@ class LightTransitionTransformer : public LightTransformer { } LightColorValues &start = this->changing_color_mode_ && p > 0.5f ? this->intermediate_values_ : this->start_values_; - LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->target_values_; + LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->end_values_; if (this->changing_color_mode_) p = p < 0.5f ? p * 2 : (p - 0.5) * 2; @@ -57,6 +60,7 @@ class LightTransitionTransformer : public LightTransformer { static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); } bool changing_color_mode_{false}; + LightColorValues end_values_{}; LightColorValues intermediate_values_{}; }; From 867fecd157f45f591c11ae7e40c646f1111695d3 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 14 Oct 2021 08:59:52 +1300 Subject: [PATCH 1546/1841] Fix: Light flash not restoring previous LightState (#2383) * Update light state when transformer has finished * Revert writing direct to output * Correct handling of zero-length light transformers * Allow transformers to handle zero-length transitions, and check more boundary conditions when transitioning back to start state * Removed log.h * Fixed race condition between LightFlashTransformer.apply() and is_finished() * clang-format * Step progress from 0.0f to 1.0f at t=start_time for zero-length transforms to avoid divide-by-zero --- .../components/light/addressable_light.cpp | 2 +- esphome/components/light/light_transformer.h | 10 ++++- esphome/components/light/transformers.h | 37 ++++++++++--------- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index f3e6c0ef1d..a8e0c7b762 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -79,7 +79,7 @@ optional AddressableLightTransformer::apply() { // dynamically-calculated alpha values to match the look. float denom = (1.0f - smoothed_progress); - float alpha = denom == 0.0f ? 0.0f : (smoothed_progress - this->last_transition_progress_) / denom; + float alpha = denom == 0.0f ? 1.0f : (smoothed_progress - this->last_transition_progress_) / denom; // We need to use a low-resolution alpha here which makes the transition set in only after ~half of the length // We solve this by accumulating the fractional part of the alpha over time. diff --git a/esphome/components/light/light_transformer.h b/esphome/components/light/light_transformer.h index dd904d0eed..35b045d5b4 100644 --- a/esphome/components/light/light_transformer.h +++ b/esphome/components/light/light_transformer.h @@ -39,7 +39,15 @@ class LightTransformer { protected: /// The progress of this transition, on a scale of 0 to 1. - float get_progress_() { return clamp((millis() - this->start_time_) / float(this->length_), 0.0f, 1.0f); } + float get_progress_() { + uint32_t now = esphome::millis(); + if (now < this->start_time_) + return 0.0f; + if (now >= this->start_time_ + this->length_) + return 1.0f; + + return clamp((now - this->start_time_) / float(this->length_), 0.0f, 1.0f); + } uint32_t start_time_; uint32_t length_; diff --git a/esphome/components/light/transformers.h b/esphome/components/light/transformers.h index c22846ceb1..a557bd39b1 100644 --- a/esphome/components/light/transformers.h +++ b/esphome/components/light/transformers.h @@ -73,9 +73,7 @@ class LightFlashTransformer : public LightTransformer { if (this->transition_length_ * 2 > this->length_) this->transition_length_ = this->length_ / 2; - // do not create transition if length is 0 - if (this->transition_length_ == 0) - return; + this->begun_lightstate_restore_ = false; // first transition to original target this->transformer_ = this->state_.get_output()->create_default_transition(); @@ -83,40 +81,45 @@ class LightFlashTransformer : public LightTransformer { } optional apply() override { - // transition transformer does not handle 0 length as progress returns nan - if (this->transition_length_ == 0) - return this->target_values_; + optional result = {}; + + if (this->transformer_ == nullptr && millis() > this->start_time_ + this->length_ - this->transition_length_) { + // second transition back to start value + this->transformer_ = this->state_.get_output()->create_default_transition(); + this->transformer_->setup(this->state_.current_values, this->get_start_values(), this->transition_length_); + this->begun_lightstate_restore_ = true; + } if (this->transformer_ != nullptr) { - if (!this->transformer_->is_finished()) { - return this->transformer_->apply(); - } else { + result = this->transformer_->apply(); + + if (this->transformer_->is_finished()) { this->transformer_->stop(); this->transformer_ = nullptr; } } - if (millis() > this->start_time_ + this->length_ - this->transition_length_) { - // second transition back to start value - this->transformer_ = this->state_.get_output()->create_default_transition(); - this->transformer_->setup(this->state_.current_values, this->get_start_values(), this->transition_length_); - } - - // once transition is complete, don't change states until next transition - return optional(); + return result; } // Restore the original values after the flash. void stop() override { + if (this->transformer_ != nullptr) { + this->transformer_->stop(); + this->transformer_ = nullptr; + } this->state_.current_values = this->get_start_values(); this->state_.remote_values = this->get_start_values(); this->state_.publish_state(); } + bool is_finished() override { return this->begun_lightstate_restore_ && LightTransformer::is_finished(); } + protected: LightState &state_; uint32_t transition_length_; std::unique_ptr transformer_{nullptr}; + bool begun_lightstate_restore_; }; } // namespace light From 6bbb5e9b56bb71353aac609cea7fe325cd9b4c60 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 13 Oct 2021 22:21:43 +0200 Subject: [PATCH 1547/1841] Disallow using UART2 for logger on ESP-32 variants that lack it (#2510) --- esphome/components/esp32/__init__.py | 6 +++++- esphome/components/logger/__init__.py | 7 +++++++ esphome/components/logger/logger.cpp | 8 +++----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 704f9bb3e8..09eabe1fa7 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -21,12 +21,16 @@ from esphome.core import CORE, HexInt import esphome.config_validation as cv import esphome.codegen as cg -from .const import ( +from .const import ( # noqa KEY_BOARD, KEY_ESP32, KEY_SDKCONFIG_OPTIONS, KEY_VARIANT, + VARIANT_ESP32, + VARIANT_ESP32S2, + VARIANT_ESP32S3, VARIANT_ESP32C3, + VARIANT_ESP32H2, VARIANTS, ) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index bc1bc6bb41..fe2a3ec8f8 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -19,6 +19,7 @@ from esphome.const import ( CONF_TX_BUFFER_SIZE, ) from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority +from esphome.components.esp32 import get_esp32_variant, VARIANT_ESP32S2, VARIANT_ESP32C3 CODEOWNERS = ["@esphome/core"] logger_ns = cg.esphome_ns.namespace("logger") @@ -52,6 +53,10 @@ LOG_LEVEL_SEVERITY = [ "VERY_VERBOSE", ] +ESP32_REDUCED_VARIANTS = [VARIANT_ESP32C3, VARIANT_ESP32S2] + +UART_SELECTION_ESP32_REDUCED = ["UART0", "UART1"] + UART_SELECTION_ESP32 = ["UART0", "UART1", "UART2"] UART_SELECTION_ESP8266 = ["UART0", "UART0_SWAP", "UART1"] @@ -75,6 +80,8 @@ is_log_level = cv.one_of(*LOG_LEVELS, upper=True) def uart_selection(value): if CORE.is_esp32: + if get_esp32_variant() in ESP32_REDUCED_VARIANTS: + return cv.one_of(*UART_SELECTION_ESP32_REDUCED, upper=True)(value) return cv.one_of(*UART_SELECTION_ESP32, upper=True)(value) if CORE.is_esp8266: return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 2d85969bf3..b38c7f1a69 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -153,13 +153,9 @@ void Logger::pre_setup() { case UART_SELECTION_UART1: this->hw_serial_ = &Serial1; break; -#ifdef USE_ESP32 +#if defined(USE_ESP32) && !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32S2 case UART_SELECTION_UART2: -#if !CONFIG_IDF_TARGET_ESP32S2 && !CONFIG_IDF_TARGET_ESP32C3 - // FIXME: Validate in config that UART2 can't be set for ESP32-S2 (only has - // UART0-UART1) this->hw_serial_ = &Serial2; -#endif break; #endif } @@ -173,9 +169,11 @@ void Logger::pre_setup() { case UART_SELECTION_UART1: uart_num_ = UART_NUM_1; break; +#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32S2 case UART_SELECTION_UART2: uart_num_ = UART_NUM_2; break; +#endif } uart_config_t uart_config{}; uart_config.baud_rate = (int) baud_rate_; From 07b309e65d38bf420b89975aee56675314ab7a1d Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 14 Oct 2021 20:58:35 +1300 Subject: [PATCH 1548/1841] Fix BME680_BSEC compilation issue with ESP32 (#2516) --- esphome/components/bme680_bsec/__init__.py | 8 +++++++- tests/test1.yaml | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/esphome/components/bme680_bsec/__init__.py b/esphome/components/bme680_bsec/__init__.py index d258819aa4..38da18d702 100644 --- a/esphome/components/bme680_bsec/__init__.py +++ b/esphome/components/bme680_bsec/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c from esphome.const import CONF_ID +from esphome.core import CORE CODEOWNERS = ["@trvrnrth"] DEPENDENCIES = ["i2c"] @@ -44,7 +45,8 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional( CONF_STATE_SAVE_INTERVAL, default="6hours" ): cv.positive_time_period_minutes, - } + }, + cv.only_with_arduino, ).extend(i2c.i2c_device_schema(0x76)) @@ -60,5 +62,9 @@ async def to_code(config): var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds) ) + if CORE.is_esp32: + # Although this component does not use SPI, the BSEC library requires the SPI library + cg.add_library("SPI", None) + cg.add_define("USE_BSEC") cg.add_library("BSEC Software Library", "1.6.1480") diff --git a/tests/test1.yaml b/tests/test1.yaml index 157ccfc5d1..62fc781eca 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -265,6 +265,9 @@ wled: adalight: +bme680_bsec: + i2c_id: i2c_bus + esp32_ble_tracker: ble_client: @@ -478,6 +481,19 @@ sensor: duration: 150ms update_interval: 15s i2c_id: i2c_bus + - platform: bme680_bsec + temperature: + name: "BME680 Temperature" + pressure: + name: "BME680 Pressure" + humidity: + name: "BME680 Humidity" + iaq: + name: "BME680 IAQ" + co2_equivalent: + name: "BME680 CO2 Equivalent" + breath_voc_equivalent: + name: "BME680 Breath VOC Equivalent" - platform: bmp085 temperature: name: 'Outside Temperature' From 7e482901d93d56ee517690309da87aa75933023b Mon Sep 17 00:00:00 2001 From: Dmitriy Lopatko Date: Thu, 14 Oct 2021 10:00:53 +0200 Subject: [PATCH 1549/1841] add missing include in sgp30 (#2517) --- esphome/components/sgp30/sgp30.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index 1a64a12907..87cf0fa61a 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -1,4 +1,5 @@ #include "sgp30.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" #include "esphome/core/application.h" #include From 4896f870f0d6755bcdd570a47f0323b9e3640645 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 14 Oct 2021 21:04:50 +1300 Subject: [PATCH 1550/1841] Fix: Color modes not being correctly used in light partitions (#2513) --- .../light/addressable_light_wrapper.h | 89 +++++++++++++++++-- 1 file changed, 80 insertions(+), 9 deletions(-) diff --git a/esphome/components/light/addressable_light_wrapper.h b/esphome/components/light/addressable_light_wrapper.h index cd5bcabd47..d358502430 100644 --- a/esphome/components/light/addressable_light_wrapper.h +++ b/esphome/components/light/addressable_light_wrapper.h @@ -16,24 +16,94 @@ class AddressableLightWrapper : public light::AddressableLight { void clear_effect_data() override { this->wrapper_state_[4] = 0; } - light::LightTraits get_traits() override { return this->light_state_->get_traits(); } + light::LightTraits get_traits() override { + LightTraits traits; + + // Choose which color mode to use. + // This is ordered by how closely each color mode matches the underlying RGBW data structure used in LightPartition. + ColorMode color_mode_precedence[] = {ColorMode::RGB_WHITE, + ColorMode::RGB_COLD_WARM_WHITE, + ColorMode::RGB_COLOR_TEMPERATURE, + ColorMode::RGB, + ColorMode::WHITE, + ColorMode::COLD_WARM_WHITE, + ColorMode::COLOR_TEMPERATURE, + ColorMode::BRIGHTNESS, + ColorMode::ON_OFF, + ColorMode::UNKNOWN}; + + LightTraits parent_traits = this->light_state_->get_traits(); + for (auto cm : color_mode_precedence) { + if (parent_traits.supports_color_mode(cm)) { + this->color_mode_ = cm; + break; + } + } + + // Report a color mode that's compatible with both the partition and the underlying light + switch (this->color_mode_) { + case ColorMode::RGB_WHITE: + case ColorMode::RGB_COLD_WARM_WHITE: + case ColorMode::RGB_COLOR_TEMPERATURE: + traits.set_supported_color_modes({light::ColorMode::RGB_WHITE}); + break; + + case ColorMode::RGB: + traits.set_supported_color_modes({light::ColorMode::RGB}); + break; + + case ColorMode::WHITE: + case ColorMode::COLD_WARM_WHITE: + case ColorMode::COLOR_TEMPERATURE: + case ColorMode::BRIGHTNESS: + traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS}); + break; + + case ColorMode::ON_OFF: + traits.set_supported_color_modes({light::ColorMode::ON_OFF}); + break; + + default: + traits.set_supported_color_modes({light::ColorMode::UNKNOWN}); + } + + return traits; + } void write_state(light::LightState *state) override { + // Don't overwrite state if the underlying light is turned on + if (this->light_state_->remote_values.is_on()) { + this->mark_shown_(); + return; + } + float gamma = this->light_state_->get_gamma_correct(); float r = gamma_uncorrect(this->wrapper_state_[0] / 255.0f, gamma); float g = gamma_uncorrect(this->wrapper_state_[1] / 255.0f, gamma); float b = gamma_uncorrect(this->wrapper_state_[2] / 255.0f, gamma); float w = gamma_uncorrect(this->wrapper_state_[3] / 255.0f, gamma); - float brightness = fmaxf(r, fmaxf(g, b)); auto call = this->light_state_->make_call(); - call.set_state(true); - call.set_brightness_if_supported(1.0f); - call.set_color_brightness_if_supported(brightness); - call.set_red_if_supported(r); - call.set_green_if_supported(g); - call.set_blue_if_supported(b); - call.set_white_if_supported(w); + + float color_brightness = fmaxf(r, fmaxf(g, b)); + float brightness = fmaxf(color_brightness, w); + if (brightness == 0.0f) { + call.set_state(false); + } else { + color_brightness /= brightness; + w /= brightness; + + call.set_state(true); + call.set_color_mode_if_supported(this->color_mode_); + call.set_brightness_if_supported(brightness); + call.set_color_brightness_if_supported(color_brightness); + call.set_red_if_supported(r); + call.set_green_if_supported(g); + call.set_blue_if_supported(b); + call.set_white_if_supported(w); + call.set_warm_white_if_supported(w); + call.set_cold_white_if_supported(w); + } call.set_transition_length_if_supported(0); call.set_publish(false); call.set_save(false); @@ -50,6 +120,7 @@ class AddressableLightWrapper : public light::AddressableLight { light::LightState *light_state_; uint8_t *wrapper_state_; + ColorMode color_mode_{ColorMode::UNKNOWN}; }; } // namespace light From 882302450956b3070a13cd7bb701dba91b59f132 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Thu, 14 Oct 2021 11:24:57 +0200 Subject: [PATCH 1551/1841] Add pressure compensation during runtime (#2493) Co-authored-by: Oxan van Leeuwen --- esphome/components/scd4x/scd4x.cpp | 133 ++++++++++++++++++----------- esphome/components/scd4x/scd4x.h | 9 +- esphome/components/scd4x/sensor.py | 9 +- 3 files changed, 97 insertions(+), 54 deletions(-) diff --git a/esphome/components/scd4x/scd4x.cpp b/esphome/components/scd4x/scd4x.cpp index c91fd5e882..eacb39edf1 100644 --- a/esphome/components/scd4x/scd4x.cpp +++ b/esphome/components/scd4x/scd4x.cpp @@ -1,4 +1,5 @@ #include "scd4x.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" namespace esphome { @@ -38,6 +39,7 @@ void SCD4XComponent::setup() { return; } + uint32_t stop_measurement_delay = 0; // In order to query the device periodic measurement must be ceased if (raw_read_status[0]) { ESP_LOGD(TAG, "Sensor has data available, stopping periodic measurement"); @@ -46,68 +48,72 @@ void SCD4XComponent::setup() { this->mark_failed(); return; } + // According to the SCD4x datasheet the sensor will only respond to other commands after waiting 500 ms after + // issuing the stop_periodic_measurement command + stop_measurement_delay = 500; } + this->set_timeout(stop_measurement_delay, [this]() { + if (!this->write_command_(SCD4X_CMD_GET_SERIAL_NUMBER)) { + ESP_LOGE(TAG, "Failed to write get serial command"); + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } - if (!this->write_command_(SCD4X_CMD_GET_SERIAL_NUMBER)) { - ESP_LOGE(TAG, "Failed to write get serial command"); - this->error_code_ = COMMUNICATION_FAILED; - this->mark_failed(); - return; - } + uint16_t raw_serial_number[3]; + if (!this->read_data_(raw_serial_number, 3)) { + ESP_LOGE(TAG, "Failed to read serial number"); + this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED; + this->mark_failed(); + return; + } + ESP_LOGD(TAG, "Serial number %02d.%02d.%02d", (uint16_t(raw_serial_number[0]) >> 8), + uint16_t(raw_serial_number[0] & 0xFF), (uint16_t(raw_serial_number[1]) >> 8)); - uint16_t raw_serial_number[3]; - if (!this->read_data_(raw_serial_number, 3)) { - ESP_LOGE(TAG, "Failed to read serial number"); - this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED; - this->mark_failed(); - return; - } - ESP_LOGD(TAG, "Serial number %02d.%02d.%02d", (uint16_t(raw_serial_number[0]) >> 8), - uint16_t(raw_serial_number[0] & 0xFF), (uint16_t(raw_serial_number[1]) >> 8)); - - if (!this->write_command_(SCD4X_CMD_TEMPERATURE_OFFSET, - (uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) { - ESP_LOGE(TAG, "Error setting temperature offset."); - this->error_code_ = MEASUREMENT_INIT_FAILED; - this->mark_failed(); - return; - } - - // If pressure compensation available use it - // else use altitude - if (ambient_pressure_compensation_) { - if (!this->write_command_(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, ambient_pressure_compensation_)) { - ESP_LOGE(TAG, "Error setting ambient pressure compensation."); + if (!this->write_command_(SCD4X_CMD_TEMPERATURE_OFFSET, + (uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) { + ESP_LOGE(TAG, "Error setting temperature offset."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); return; } - } else { - if (!this->write_command_(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { - ESP_LOGE(TAG, "Error setting altitude compensation."); + + // If pressure compensation available use it + // else use altitude + if (ambient_pressure_compensation_) { + if (!this->update_ambient_pressure_compensation_(ambient_pressure_)) { + ESP_LOGE(TAG, "Error setting ambient pressure compensation."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } + } else { + if (!this->write_command_(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { + ESP_LOGE(TAG, "Error setting altitude compensation."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } + } + + if (!this->write_command_(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) { + ESP_LOGE(TAG, "Error setting automatic self calibration."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); return; } - } - if (!this->write_command_(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) { - ESP_LOGE(TAG, "Error setting automatic self calibration."); - this->error_code_ = MEASUREMENT_INIT_FAILED; - this->mark_failed(); - return; - } + // Finally start sensor measurements + if (!this->write_command_(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) { + ESP_LOGE(TAG, "Error starting continuous measurements."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } - // Finally start sensor measurements - if (!this->write_command_(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) { - ESP_LOGE(TAG, "Error starting continuous measurements."); - this->error_code_ = MEASUREMENT_INIT_FAILED; - this->mark_failed(); - return; - } - - initialized_ = true; - ESP_LOGD(TAG, "Sensor initialized"); + initialized_ = true; + ESP_LOGD(TAG, "Sensor initialized"); + }); }); } @@ -150,6 +156,13 @@ void SCD4XComponent::update() { return; } + if (this->ambient_pressure_source_ != nullptr) { + float pressure = this->ambient_pressure_source_->state / 1000.0f; + if (!std::isnan(pressure)) { + set_ambient_pressure_compensation(this->ambient_pressure_source_->state / 1000.0f); + } + } + // Check if data is ready if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_STATUS)) { this->status_set_warning(); @@ -191,6 +204,28 @@ void SCD4XComponent::update() { this->status_clear_warning(); } +// Note pressure in bar here. Convert to hPa +void SCD4XComponent::set_ambient_pressure_compensation(float pressure_in_bar) { + ambient_pressure_compensation_ = true; + uint16_t new_ambient_pressure = (uint16_t)(pressure_in_bar * 1000); + // remove millibar from comparison to avoid frequent updates +/- 10 millibar doesn't matter + if (initialized_ && (new_ambient_pressure / 10 != ambient_pressure_ / 10)) { + update_ambient_pressure_compensation_(new_ambient_pressure); + ambient_pressure_ = new_ambient_pressure; + } else { + ESP_LOGD(TAG, "ambient pressure compensation skipped - no change required"); + } +} + +bool SCD4XComponent::update_ambient_pressure_compensation_(uint16_t pressure_in_hpa) { + if (this->write_command_(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, pressure_in_hpa)) { + ESP_LOGD(TAG, "setting ambient pressure compensation to %d hPa", pressure_in_hpa); + return true; + } else { + ESP_LOGE(TAG, "Error setting ambient pressure compensation."); + return false; + } +} uint8_t SCD4XComponent::sht_crc_(uint8_t data1, uint8_t data2) { uint8_t bit; diff --git a/esphome/components/scd4x/scd4x.h b/esphome/components/scd4x/scd4x.h index 3c428b8623..4fe2bf14cc 100644 --- a/esphome/components/scd4x/scd4x.h +++ b/esphome/components/scd4x/scd4x.h @@ -18,10 +18,8 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { void set_automatic_self_calibration(bool asc) { enable_asc_ = asc; } void set_altitude_compensation(uint16_t altitude) { altitude_compensation_ = altitude; } - void set_ambient_pressure_compensation(float pressure) { - ambient_pressure_compensation_ = true; - ambient_pressure_ = (uint16_t)(pressure * 1000); - } + void set_ambient_pressure_compensation(float pressure_in_bar); + void set_ambient_pressure_source(sensor::Sensor *pressure) { ambient_pressure_source_ = pressure; } void set_temperature_offset(float offset) { temperature_offset_ = offset; }; void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; } @@ -33,6 +31,7 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { bool read_data_(uint16_t *data, uint8_t len); bool write_command_(uint16_t command); bool write_command_(uint16_t command, uint16_t data); + bool update_ambient_pressure_compensation_(uint16_t pressure_in_hpa); ERRORCODE error_code_; @@ -47,6 +46,8 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *co2_sensor_{nullptr}; sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; + // used for compensation + sensor::Sensor *ambient_pressure_source_{nullptr}; }; } // namespace scd4x diff --git a/esphome/components/scd4x/sensor.py b/esphome/components/scd4x/sensor.py index 0b1a960f6f..3e814ffe78 100644 --- a/esphome/components/scd4x/sensor.py +++ b/esphome/components/scd4x/sensor.py @@ -29,6 +29,7 @@ CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" CONF_ALTITUDE_COMPENSATION = "altitude_compensation" CONF_AMBIENT_PRESSURE_COMPENSATION = "ambient_pressure_compensation" CONF_TEMPERATURE_OFFSET = "temperature_offset" +CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE = "ambient_pressure_compensation_source" CONFIG_SCHEMA = ( cv.Schema( @@ -62,6 +63,9 @@ CONFIG_SCHEMA = ( ), cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION): cv.pressure, cv.Optional(CONF_TEMPERATURE_OFFSET, default="4°C"): cv.temperature, + cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE): cv.use_id( + sensor.Sensor + ), } ) .extend(cv.polling_component_schema("60s")) @@ -92,7 +96,10 @@ async def to_code(config): cg.add(getattr(var, funcName)(config[key])) for key, funcName in SENSOR_MAP.items(): - if key in config: sens = await sensor.new_sensor(config[key]) cg.add(getattr(var, funcName)(sens)) + + if CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE in config: + sens = await cg.get_variable(config[CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE]) + cg.add(var.set_ambient_pressure_source(sens)) From 63d6b610b82f77b88d42dc0865c1b7ae32709967 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 14 Oct 2021 11:25:10 +0200 Subject: [PATCH 1552/1841] Don't define UART_SELECTION_UART2 when UART2 is unavailable (#2512) --- esphome/components/logger/logger.cpp | 4 ++-- esphome/components/logger/logger.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index b38c7f1a69..97ad4c2cb9 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -153,7 +153,7 @@ void Logger::pre_setup() { case UART_SELECTION_UART1: this->hw_serial_ = &Serial1; break; -#if defined(USE_ESP32) && !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32S2 +#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) case UART_SELECTION_UART2: this->hw_serial_ = &Serial2; break; @@ -169,7 +169,7 @@ void Logger::pre_setup() { case UART_SELECTION_UART1: uart_num_ = UART_NUM_1; break; -#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32S2 +#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) case UART_SELECTION_UART2: uart_num_ = UART_NUM_2; break; diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index e6fa6e2058..8756bc2387 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -24,7 +24,7 @@ namespace logger { enum UARTSelection { UART_SELECTION_UART0 = 0, UART_SELECTION_UART1, -#ifdef USE_ESP32 +#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) UART_SELECTION_UART2, #endif #ifdef USE_ESP8266 From 1308236429d1564ed7cb14ee38c5d904f0c22916 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 14 Oct 2021 23:31:52 +1300 Subject: [PATCH 1553/1841] Revert "Added test for bme680_bsec" (#2518) This reverts commit 7f6a50d291b14935b17802b4dce52135fad1e49e due to BSEC library license restrictions. --- tests/test1.yaml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/test1.yaml b/tests/test1.yaml index 62fc781eca..157ccfc5d1 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -265,9 +265,6 @@ wled: adalight: -bme680_bsec: - i2c_id: i2c_bus - esp32_ble_tracker: ble_client: @@ -481,19 +478,6 @@ sensor: duration: 150ms update_interval: 15s i2c_id: i2c_bus - - platform: bme680_bsec - temperature: - name: "BME680 Temperature" - pressure: - name: "BME680 Pressure" - humidity: - name: "BME680 Humidity" - iaq: - name: "BME680 IAQ" - co2_equivalent: - name: "BME680 CO2 Equivalent" - breath_voc_equivalent: - name: "BME680 Breath VOC Equivalent" - platform: bmp085 temperature: name: 'Outside Temperature' From 7178f10bdaa793401ff2c2fc376e7b7d12fd91bc Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Fri, 15 Oct 2021 02:26:26 -0500 Subject: [PATCH 1554/1841] Fix Nextion HTTPClient error for ESP32 (#2524) --- esphome/components/nextion/display.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index f4b35fd56f..d95810bfbe 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -8,7 +8,7 @@ from esphome.const import ( CONF_BRIGHTNESS, CONF_TRIGGER_ID, ) - +from esphome.core import CORE from . import Nextion, nextion_ns, nextion_ref from .base_component import ( CONF_ON_SLEEP, @@ -76,6 +76,9 @@ async def to_code(config): if CONF_TFT_URL in config: cg.add_define("USE_NEXTION_TFT_UPLOAD") cg.add(var.set_tft_url(config[CONF_TFT_URL])) + if CORE.is_esp32: + cg.add_library("WiFiClientSecure", None) + cg.add_library("HTTPClient", None) if CONF_TOUCH_SLEEP_TIMEOUT in config: cg.add(var.set_touch_sleep_timeout_internal(config[CONF_TOUCH_SLEEP_TIMEOUT])) From 6beb9e568a3108a27a980478e7b5388e0ae4cad7 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Fri, 15 Oct 2021 09:27:56 +0200 Subject: [PATCH 1555/1841] Fix bug in register name definition (#2526) --- esphome/components/modbus_controller/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 7a69029dab..6b452ea25c 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -38,7 +38,7 @@ ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType") ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType") MODBUS_REGISTER_TYPE = { "coil": ModbusRegisterType.COIL, - "discrete_input": ModbusRegisterType.DISCRETE, + "discrete_input": ModbusRegisterType.DISCRETE_INPUT, "holding": ModbusRegisterType.HOLDING, "read": ModbusRegisterType.READ, } From dc15d1c8ecea36a21cf648322385a42c9f5fe703 Mon Sep 17 00:00:00 2001 From: Dmitriy Lopatko Date: Fri, 15 Oct 2021 16:52:03 +0200 Subject: [PATCH 1556/1841] use no hold master mode for si7021/htu21d (#2528) --- esphome/components/htu21d/htu21d.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/htu21d/htu21d.cpp b/esphome/components/htu21d/htu21d.cpp index b53284ae3f..a38ec73019 100644 --- a/esphome/components/htu21d/htu21d.cpp +++ b/esphome/components/htu21d/htu21d.cpp @@ -9,8 +9,8 @@ static const char *const TAG = "htu21d"; static const uint8_t HTU21D_ADDRESS = 0x40; static const uint8_t HTU21D_REGISTER_RESET = 0xFE; -static const uint8_t HTU21D_REGISTER_TEMPERATURE = 0xE3; -static const uint8_t HTU21D_REGISTER_HUMIDITY = 0xE5; +static const uint8_t HTU21D_REGISTER_TEMPERATURE = 0xF3; +static const uint8_t HTU21D_REGISTER_HUMIDITY = 0xF5; static const uint8_t HTU21D_REGISTER_STATUS = 0xE7; void HTU21DComponent::setup() { From 935992bcb32bad838aa90d7eab03dcd6f749d9fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Oct 2021 19:38:09 +0200 Subject: [PATCH 1557/1841] Bump pyyaml from 5.4.1 to 6.0 (#2521) Bumps [pyyaml](https://github.com/yaml/pyyaml) from 5.4.1 to 6.0. - [Release notes](https://github.com/yaml/pyyaml/releases) - [Changelog](https://github.com/yaml/pyyaml/blob/master/CHANGES) - [Commits](https://github.com/yaml/pyyaml/compare/5.4.1...6.0) --- updated-dependencies: - dependency-name: pyyaml dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 23a00d3755..b21bed87a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ voluptuous==0.12.2 -PyYAML==5.4.1 +PyYAML==6.0 paho-mqtt==1.5.1 colorama==0.4.4 tornado==6.1 From 85d2f24447dc79029b715d6082add1935d53b1ca Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Fri, 15 Oct 2021 19:51:42 +0200 Subject: [PATCH 1558/1841] Clarify statement at the cmd wizard tool, for new users (#2519) * Clarify next steps for the install wizard * Update wizard.py * Link to relevant section of guide * Formatting Co-authored-by: Oxan van Leeuwen --- esphome/wizard.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/wizard.py b/esphome/wizard.py index 5c35fac73a..6c87b66453 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -367,10 +367,9 @@ def wizard(path): ) safe_print() safe_print("Next steps:") + safe_print(" > Follow the rest of the getting started guide:") safe_print( - ' > Check your Home Assistant "integrations" screen. If all goes well, you ' - "should see your ESP being discovered automatically." + " > https://esphome.io/guides/getting_started_command_line.html#adding-some-features" ) - safe_print(" > Then follow the rest of the getting started guide:") - safe_print(" > https://esphome.io/guides/getting_started_command_line.html") + safe_print(" > to learn how to customize ESPHome and install it to your device.") return 0 From 884b7201de5256cd5692d1c992654d429866f53a Mon Sep 17 00:00:00 2001 From: Tom Matheussen Date: Fri, 15 Oct 2021 20:46:58 +0200 Subject: [PATCH 1559/1841] Continue ethernet setup if hostname fails (#2430) --- esphome/components/ethernet/ethernet_component.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index d55db0a7d8..00f68df2b4 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -168,7 +168,9 @@ void EthernetComponent::start_connect_() { esp_err_t err; err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str()); - ESPHL_ERROR_CHECK(err, "ETH set hostname error"); + if (err != ERR_OK) { + ESP_LOGW(TAG, "tcpip_adapter_set_hostname failed: %s", esp_err_to_name(err)); + } tcpip_adapter_ip_info_t info; if (this->manual_ip_.has_value()) { From 653a3d5d11a42e93158e7b65058806197713cc97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Oct 2021 22:05:04 +0200 Subject: [PATCH 1560/1841] Bump aioesphomeapi from 9.1.5 to 10.0.0 (#2508) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Oxan van Leeuwen --- esphome/components/api/client.py | 1 - requirements.txt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index 4a3944d33e..b2920f239b 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -23,7 +23,6 @@ async def async_run_logs(config, address): _LOGGER.info("Starting log output from %s using esphome API", address) zc = zeroconf.Zeroconf() cli = APIClient( - asyncio.get_event_loop(), address, port, password, diff --git a/requirements.txt b/requirements.txt index b21bed87a6..afc75efcc7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.1 esptool==3.1 click==8.0.3 esphome-dashboard==20211011.1 -aioesphomeapi==9.1.5 +aioesphomeapi==10.0.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From c82d5d63e37cb2eda25f5316160cfbebd0a06381 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Fri, 15 Oct 2021 22:05:11 +0200 Subject: [PATCH 1561/1841] Move TemplatableValue helper class to automation.h (#2511) --- .../components/api/homeassistant_service.h | 16 +++++- esphome/core/automation.h | 50 +++++++++++++--- esphome/core/helpers.h | 57 ------------------- 3 files changed, 58 insertions(+), 65 deletions(-) diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index 8a72765195..26269bcae4 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -8,6 +8,18 @@ namespace esphome { namespace api { +template class TemplatableStringValue : public TemplatableValue { + public: + TemplatableStringValue() : TemplatableValue() {} + + template::value, int> = 0> + TemplatableStringValue(F value) : TemplatableValue(value) {} + + template::value, int> = 0> + TemplatableStringValue(F f) + : TemplatableValue([f](X... x) -> std::string { return to_string(f(x...)); }) {} +}; + template class TemplatableKeyValuePair { public: template TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {} @@ -19,7 +31,8 @@ template class HomeAssistantServiceCallAction : public Action void set_service(T service) { this->service_ = service; } + template void add_data(std::string key, T value) { this->data_.push_back(TemplatableKeyValuePair(key, value)); } @@ -58,6 +71,7 @@ template class HomeAssistantServiceCallAction : public Action service_{}; std::vector> data_; std::vector> data_template_; std::vector> variables_; diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 6d79480f0f..e5460bef34 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -17,14 +17,50 @@ namespace esphome { #define TEMPLATABLE_VALUE(type, name) TEMPLATABLE_VALUE_(type, name) -#define TEMPLATABLE_STRING_VALUE_(name) \ - protected: \ - TemplatableStringValue name##_{}; \ -\ - public: \ - template void set_##name(V name) { this->name##_ = name; } +template class TemplatableValue { + public: + TemplatableValue() : type_(EMPTY) {} -#define TEMPLATABLE_STRING_VALUE(name) TEMPLATABLE_STRING_VALUE_(name) + template::value, int> = 0> + TemplatableValue(F value) : type_(VALUE), value_(value) {} + + template::value, int> = 0> + TemplatableValue(F f) : type_(LAMBDA), f_(f) {} + + bool has_value() { return this->type_ != EMPTY; } + + T value(X... x) { + if (this->type_ == LAMBDA) { + return this->f_(x...); + } + // return value also when empty + return this->value_; + } + + optional optional_value(X... x) { + if (!this->has_value()) { + return {}; + } + return this->value(x...); + } + + T value_or(X... x, T default_value) { + if (!this->has_value()) { + return default_value; + } + return this->value(x...); + } + + protected: + enum { + EMPTY, + VALUE, + LAMBDA, + } type_; + + T value_{}; + std::function f_{}; +}; /** Base class for all automation conditions. * diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 61cc9a9e4a..905cb76170 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -255,63 +255,6 @@ struct is_callable // NOLINT static constexpr auto value = decltype(test(nullptr))::value; // NOLINT }; -template class TemplatableValue { - public: - TemplatableValue() : type_(EMPTY) {} - - template::value, int> = 0> - TemplatableValue(F value) : type_(VALUE), value_(value) {} - - template::value, int> = 0> - TemplatableValue(F f) : type_(LAMBDA), f_(f) {} - - bool has_value() { return this->type_ != EMPTY; } - - T value(X... x) { - if (this->type_ == LAMBDA) { - return this->f_(x...); - } - // return value also when empty - return this->value_; - } - - optional optional_value(X... x) { - if (!this->has_value()) { - return {}; - } - return this->value(x...); - } - - T value_or(X... x, T default_value) { - if (!this->has_value()) { - return default_value; - } - return this->value(x...); - } - - protected: - enum { - EMPTY, - VALUE, - LAMBDA, - } type_; - - T value_{}; - std::function f_{}; -}; - -template class TemplatableStringValue : public TemplatableValue { - public: - TemplatableStringValue() : TemplatableValue() {} - - template::value, int> = 0> - TemplatableStringValue(F value) : TemplatableValue(value) {} - - template::value, int> = 0> - TemplatableStringValue(F f) - : TemplatableValue([f](X... x) -> std::string { return to_string(f(x...)); }) {} -}; - void delay_microseconds_accurate(uint32_t usec); template class Deduplicator { From 384f8d97d8d6ef665c72d635ce1e04c108fa2cff Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Fri, 15 Oct 2021 22:06:32 +0200 Subject: [PATCH 1562/1841] OTA firmware MD5 check + password support for esp-idf (#2507) Co-authored-by: Maurice Makaay --- CODEOWNERS | 1 + esphome/components/md5/__init__.py | 1 + esphome/components/md5/md5.cpp | 51 ++++++++++++++++ esphome/components/md5/md5.h | 58 +++++++++++++++++++ esphome/components/ota/__init__.py | 12 +--- .../ota/ota_backend_arduino_esp32.h | 1 + .../components/ota/ota_backend_esp_idf.cpp | 12 +++- esphome/components/ota/ota_backend_esp_idf.h | 3 + esphome/components/ota/ota_component.cpp | 27 ++++----- esphome/core/defines.h | 1 + 10 files changed, 139 insertions(+), 28 deletions(-) create mode 100644 esphome/components/md5/__init__.py create mode 100644 esphome/components/md5/md5.cpp create mode 100644 esphome/components/md5/md5.h diff --git a/CODEOWNERS b/CODEOWNERS index 4c3084d463..a7cf3a1b68 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -89,6 +89,7 @@ esphome/components/mcp23x17_base/* @jesserockz esphome/components/mcp23xxx_base/* @jesserockz esphome/components/mcp2515/* @danielschramm @mvturnho esphome/components/mcp9808/* @k7hpn +esphome/components/md5/* @esphome/core esphome/components/mdns/* @esphome/core esphome/components/midea/* @dudanov esphome/components/mitsubishi/* @RubyBailey diff --git a/esphome/components/md5/__init__.py b/esphome/components/md5/__init__.py new file mode 100644 index 0000000000..f70ffa9520 --- /dev/null +++ b/esphome/components/md5/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@esphome/core"] diff --git a/esphome/components/md5/md5.cpp b/esphome/components/md5/md5.cpp new file mode 100644 index 0000000000..c6ff783439 --- /dev/null +++ b/esphome/components/md5/md5.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "md5.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace md5 { + +void MD5Digest::init() { + memset(this->digest_, 0, 16); + MD5Init(&this->ctx_); +} + +void MD5Digest::add(const uint8_t *data, size_t len) { MD5Update(&this->ctx_, data, len); } + +void MD5Digest::calculate() { MD5Final(this->digest_, &this->ctx_); } + +void MD5Digest::get_bytes(uint8_t *output) { memcpy(output, this->digest_, 16); } + +void MD5Digest::get_hex(char *output) { + for (size_t i = 0; i < 16; i++) { + sprintf(output + i * 2, "%02x", this->digest_[i]); + } +} + +bool MD5Digest::equals_bytes(const char *expected) { + for (size_t i = 0; i < 16; i++) { + if (expected[i] != this->digest_[i]) { + return false; + } + } + return true; +} + +bool MD5Digest::equals_hex(const char *expected) { + for (size_t i = 0; i < 16; i++) { + auto high = parse_hex(expected[i * 2]); + auto low = parse_hex(expected[i * 2 + 1]); + if (!high.has_value() || !low.has_value()) { + return false; + } + auto value = (*high << 4) | *low; + if (value != this->digest_[i]) { + return false; + } + } + return true; +} + +} // namespace md5 +} // namespace esphome diff --git a/esphome/components/md5/md5.h b/esphome/components/md5/md5.h new file mode 100644 index 0000000000..e40f419347 --- /dev/null +++ b/esphome/components/md5/md5.h @@ -0,0 +1,58 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_ESP_IDF +#include "esp32/rom/md5_hash.h" +#define MD5_CTX_TYPE MD5Context +#endif + +#if defined(USE_ARDUINO) && defined(USE_ESP32) +#include "rom/md5_hash.h" +#define MD5_CTX_TYPE MD5Context +#endif + +#if defined(USE_ARDUINO) && defined(USE_ESP8266) +#include +#define MD5_CTX_TYPE md5_context_t +#endif + +namespace esphome { +namespace md5 { + +class MD5Digest { + public: + MD5Digest() = default; + ~MD5Digest() = default; + + /// Initialize a new MD5 digest computation. + void init(); + + /// Add bytes of data for the digest. + void add(const uint8_t *data, size_t len); + void add(const char *data, size_t len) { this->add((const uint8_t *) data, len); } + + /// Compute the digest, based on the provided data. + void calculate(); + + /// Retrieve the MD5 digest as bytes. + /// The output must be able to hold 16 bytes or more. + void get_bytes(uint8_t *output); + + /// Retrieve the MD5 digest as hex characters. + /// The output must be able to hold 32 bytes or more. + void get_hex(char *output); + + /// Compare the digest against a provided byte-encoded digest (16 bytes). + bool equals_bytes(const char *expected); + + /// Compare the digest against a provided hex-encoded digest (32 bytes). + bool equals_hex(const char *expected); + + protected: + MD5_CTX_TYPE ctx_{}; + uint8_t digest_[16]; +}; + +} // namespace md5 +} // namespace esphome diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index bcfb28979d..53b282c43e 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -15,7 +15,7 @@ from esphome.core import CORE, coroutine_with_priority CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] -AUTO_LOAD = ["socket"] +AUTO_LOAD = ["socket", "md5"] CONF_ON_STATE_CHANGE = "on_state_change" CONF_ON_BEGIN = "on_begin" @@ -35,20 +35,12 @@ OTAEndTrigger = ota_ns.class_("OTAEndTrigger", automation.Trigger.template()) OTAErrorTrigger = ota_ns.class_("OTAErrorTrigger", automation.Trigger.template()) -def validate_password_support(value): - if CORE.using_arduino: - return value - if CORE.using_esp_idf: - raise cv.Invalid("Password support is not implemented yet for ESP-IDF") - raise NotImplementedError - - CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(OTAComponent), cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean, cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232): cv.port, - cv.Optional(CONF_PASSWORD): cv.All(cv.string, validate_password_support), + cv.Optional(CONF_PASSWORD): cv.string, cv.Optional( CONF_REBOOT_TIMEOUT, default="5min" ): cv.positive_time_period_milliseconds, diff --git a/esphome/components/ota/ota_backend_arduino_esp32.h b/esphome/components/ota/ota_backend_arduino_esp32.h index 8343bdf94f..6b712502fb 100644 --- a/esphome/components/ota/ota_backend_arduino_esp32.h +++ b/esphome/components/ota/ota_backend_arduino_esp32.h @@ -9,6 +9,7 @@ namespace esphome { namespace ota { class ArduinoESP32OTABackend : public OTABackend { + public: OTAResponseTypes begin(size_t image_size) override; void set_update_md5(const char *md5) override; OTAResponseTypes write(uint8_t *data, size_t len) override; diff --git a/esphome/components/ota/ota_backend_esp_idf.cpp b/esphome/components/ota/ota_backend_esp_idf.cpp index 4eb17d82f1..336b3798d9 100644 --- a/esphome/components/ota/ota_backend_esp_idf.cpp +++ b/esphome/components/ota/ota_backend_esp_idf.cpp @@ -4,6 +4,7 @@ #include "ota_backend_esp_idf.h" #include "ota_component.h" #include +#include "esphome/components/md5/md5.h" namespace esphome { namespace ota { @@ -24,15 +25,15 @@ OTAResponseTypes IDFOTABackend::begin(size_t image_size) { } return OTA_RESPONSE_ERROR_UNKNOWN; } + this->md5_.init(); return OTA_RESPONSE_OK; } -void IDFOTABackend::set_update_md5(const char *md5) { - // pass -} +void IDFOTABackend::set_update_md5(const char *expected_md5) { memcpy(this->expected_bin_md5_, expected_md5, 32); } OTAResponseTypes IDFOTABackend::write(uint8_t *data, size_t len) { esp_err_t err = esp_ota_write(this->update_handle_, data, len); + this->md5_.add(data, len); if (err != ESP_OK) { if (err == ESP_ERR_OTA_VALIDATE_FAILED) { return OTA_RESPONSE_ERROR_MAGIC; @@ -45,6 +46,11 @@ OTAResponseTypes IDFOTABackend::write(uint8_t *data, size_t len) { } OTAResponseTypes IDFOTABackend::end() { + this->md5_.calculate(); + if (!this->md5_.equals_hex(this->expected_bin_md5_)) { + this->abort(); + return OTA_RESPONSE_ERROR_UPDATE_END; + } esp_err_t err = esp_ota_end(this->update_handle_); this->update_handle_ = 0; if (err == ESP_OK) { diff --git a/esphome/components/ota/ota_backend_esp_idf.h b/esphome/components/ota/ota_backend_esp_idf.h index d6e2e2742a..49c6e124fa 100644 --- a/esphome/components/ota/ota_backend_esp_idf.h +++ b/esphome/components/ota/ota_backend_esp_idf.h @@ -5,6 +5,7 @@ #include "ota_component.h" #include "ota_backend.h" #include +#include "esphome/components/md5/md5.h" namespace esphome { namespace ota { @@ -20,6 +21,8 @@ class IDFOTABackend : public OTABackend { private: esp_ota_handle_t update_handle_{0}; const esp_partition_t *partition_; + md5::MD5Digest md5_{}; + char expected_bin_md5_[32]; }; } // namespace ota diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 9ad3814f5c..89bee17452 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -8,15 +8,12 @@ #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/util.h" +#include "esphome/components/md5/md5.h" #include "esphome/components/network/util.h" #include #include -#ifdef USE_OTA_PASSWORD -#include -#endif - namespace esphome { namespace ota { @@ -173,12 +170,12 @@ void OTAComponent::handle_() { if (!this->password_.empty()) { buf[0] = OTA_RESPONSE_REQUEST_AUTH; this->writeall_(buf, 1); - MD5Builder md5_builder{}; - md5_builder.begin(); + md5::MD5Digest md5{}; + md5.init(); sprintf(sbuf, "%08X", random_uint32()); - md5_builder.add(sbuf); - md5_builder.calculate(); - md5_builder.getChars(sbuf); + md5.add(sbuf, 8); + md5.calculate(); + md5.get_hex(sbuf); ESP_LOGV(TAG, "Auth: Nonce is %s", sbuf); // Send nonce, 32 bytes hex MD5 @@ -188,10 +185,10 @@ void OTAComponent::handle_() { } // prepare challenge - md5_builder.begin(); - md5_builder.add(this->password_.c_str()); + md5.init(); + md5.add(this->password_.c_str(), this->password_.length()); // add nonce - md5_builder.add(sbuf); + md5.add(sbuf, 32); // Receive cnonce, 32 bytes hex MD5 if (!this->readall_(buf, 32)) { @@ -201,11 +198,11 @@ void OTAComponent::handle_() { sbuf[32] = '\0'; ESP_LOGV(TAG, "Auth: CNonce is %s", sbuf); // add cnonce - md5_builder.add(sbuf); + md5.add(sbuf, 32); // calculate result - md5_builder.calculate(); - md5_builder.getChars(sbuf); + md5.calculate(); + md5.get_hex(sbuf); ESP_LOGV(TAG, "Auth: Result is %s", sbuf); // Receive result, 32 bytes hex MD5 diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 7c2261920a..b44987a768 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -26,6 +26,7 @@ #define USE_LOGGER #define USE_MDNS #define USE_NUMBER +#define USE_OTA_PASSWORD #define USE_OTA_STATE_CALLBACK #define USE_POWER_SUPPLY #define USE_PROMETHEUS From 7cca67390201f391198f063baf5cd897dd99adac Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Fri, 15 Oct 2021 22:06:49 +0200 Subject: [PATCH 1563/1841] [esp-idf fix] increase FreeRTOS ticker loop from 100Hz to 1kHz (#2527) Co-authored-by: Otto Winter --- esphome/components/esp32/__init__.py | 2 ++ sdkconfig.defaults | 1 + 2 files changed, 3 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 09eabe1fa7..68653d68ff 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -302,6 +302,8 @@ async def to_code(config): ) add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False) add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True) + # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms + add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000) cg.add_platformio_option("board_build.partitions", "partitions.csv") diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 6b2d6f8f2e..26db4705b8 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -8,6 +8,7 @@ CONFIG_COMPILER_OPTIMIZATION_SIZE=y CONFIG_PARTITION_TABLE_CUSTOM=y #CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_SINGLE_APP=n +CONFIG_FREERTOS_HZ=1000 # esp32_ble CONFIG_BT_ENABLED=y From 94d518a41871f941944eeaf66ceed4d497666fc2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Fri, 15 Oct 2021 22:07:05 +0200 Subject: [PATCH 1564/1841] Replace framework version_hint with source option (#2529) --- esphome/components/esp32/__init__.py | 134 +++++++++++-------------- esphome/components/esp8266/__init__.py | 75 ++++++-------- esphome/core/config.py | 11 +- 3 files changed, 97 insertions(+), 123 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 68653d68ff..8a4b8b478a 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -7,6 +7,7 @@ from esphome.helpers import write_file_if_changed from esphome.const import ( CONF_BOARD, CONF_FRAMEWORK, + CONF_SOURCE, CONF_TYPE, CONF_VARIANT, CONF_VERSION, @@ -53,7 +54,7 @@ def set_core_data(config): elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( - config[CONF_FRAMEWORK][CONF_VERSION_HINT] + config[CONF_FRAMEWORK][CONF_VERSION] ) CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD] CORE.data[KEY_ESP32][KEY_VARIANT] = config[CONF_VARIANT] @@ -94,6 +95,13 @@ def _format_framework_arduino_version(ver: cv.Version) -> str: return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" +def _format_framework_espidf_version(ver: cv.Version) -> str: + # format the given arduino (https://github.com/espressif/esp-idf/releases) version to + # a PIO platformio/framework-espidf value + # List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf + return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + + # NOTE: Keep this in mind when updating the recommended version: # * New framework historically have had some regressions, especially for WiFi. # The new version needs to be thoroughly validated before changing the @@ -123,119 +131,97 @@ ESP_IDF_PLATFORM_VERSION = cv.Version(3, 3, 2) def _arduino_check_versions(value): value = value.copy() lookups = { - "dev": ("https://github.com/espressif/arduino-esp32.git", cv.Version(2, 0, 0)), - "latest": ("", cv.Version(1, 0, 3)), - "recommended": ( - _format_framework_arduino_version(RECOMMENDED_ARDUINO_FRAMEWORK_VERSION), - RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, - ), + "dev": (cv.Version(2, 0, 0), "https://github.com/espressif/arduino-esp32.git"), + "latest": (cv.Version(1, 0, 6), None), + "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), } - ver_value = value[CONF_VERSION] - default_ver_hint = None - if ver_value.lower() in lookups: - default_ver_hint = str(lookups[ver_value.lower()][1]) - ver_value = lookups[ver_value.lower()][0] + + if value[CONF_VERSION] in lookups: + if CONF_SOURCE in value: + raise cv.Invalid( + "Framework version needs to be explicitly specified when custom source is used." + ) + + version, source = lookups[value[CONF_VERSION]] else: - with cv.suppress_invalid(): - ver = cv.Version.parse(cv.version_number(value)) - if ver <= cv.Version(1, 0, 3): - ver_value = f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" - else: - ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" - default_ver_hint = str(ver) - value[CONF_VERSION] = ver_value + version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) + source = value.get(CONF_SOURCE, None) - if CONF_VERSION_HINT not in value and default_ver_hint is None: - raise cv.Invalid("Needs a version hint to understand the framework version") + value[CONF_VERSION] = str(version) + value[CONF_SOURCE] = source or _format_framework_arduino_version(version) - ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint) - value[CONF_VERSION_HINT] = ver_hint_s - plat_ver = value.get(CONF_PLATFORM_VERSION, ARDUINO_PLATFORM_VERSION) - value[CONF_PLATFORM_VERSION] = str(plat_ver) + platform_version = value.get(CONF_PLATFORM_VERSION, ARDUINO_PLATFORM_VERSION) + value[CONF_PLATFORM_VERSION] = str(platform_version) - if cv.Version.parse(ver_hint_s) != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: + if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: _LOGGER.warning( - "The selected arduino framework version is not the recommended one" - ) - _LOGGER.warning( - "If there are connectivity or build issues please remove the manual version" + "The selected Arduino framework version is not the recommended one. " + "If there are connectivity or build issues please remove the manual version." ) return value -def _format_framework_espidf_version(ver: cv.Version) -> str: - # format the given arduino (https://github.com/espressif/esp-idf/releases) version to - # a PIO platformio/framework-espidf value - # List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf - return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" - - def _esp_idf_check_versions(value): value = value.copy() lookups = { - "dev": ("https://github.com/espressif/esp-idf.git", cv.Version(4, 3, 1)), - "latest": ("", cv.Version(4, 3, 0)), - "recommended": ( - _format_framework_espidf_version(RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION), - RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, - ), + "dev": (cv.Version(4, 3, 1), "https://github.com/espressif/esp-idf.git"), + "latest": (cv.Version(4, 3, 0), None), + "recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None), } - ver_value = value[CONF_VERSION] - default_ver_hint = None - if ver_value.lower() in lookups: - default_ver_hint = str(lookups[ver_value.lower()][1]) - ver_value = lookups[ver_value.lower()][0] + + if value[CONF_VERSION] in lookups: + if CONF_SOURCE in value: + raise cv.Invalid( + "Framework version needs to be explicitly specified when custom source is used." + ) + + version, source = lookups[value[CONF_VERSION]] else: - with cv.suppress_invalid(): - ver = cv.Version.parse(cv.version_number(value)) - ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" - default_ver_hint = str(ver) - value[CONF_VERSION] = ver_value + version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) + source = value.get(CONF_SOURCE, None) - if CONF_VERSION_HINT not in value and default_ver_hint is None: - raise cv.Invalid("Needs a version hint to understand the framework version") + if version < cv.Version(4, 0, 0): + raise cv.Invalid("Only ESP-IDF 4.0+ is supported.") - ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint) - value[CONF_VERSION_HINT] = ver_hint_s - if cv.Version.parse(ver_hint_s) < cv.Version(4, 0, 0): - raise cv.Invalid("Only ESP-IDF 4.0+ is supported") - if cv.Version.parse(ver_hint_s) != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION: + value[CONF_VERSION] = str(version) + value[CONF_SOURCE] = source or _format_framework_espidf_version(version) + + platform_version = value.get(CONF_PLATFORM_VERSION, ESP_IDF_PLATFORM_VERSION) + value[CONF_PLATFORM_VERSION] = str(platform_version) + + if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: _LOGGER.warning( - "The selected esp-idf framework version is not the recommended one" + "The selected ESP-IDF framework version is not the recommended one. " + "If there are connectivity or build issues please remove the manual version." ) - _LOGGER.warning( - "If there are connectivity or build issues please remove the manual version" - ) - - plat_ver = value.get(CONF_PLATFORM_VERSION, ESP_IDF_PLATFORM_VERSION) - value[CONF_PLATFORM_VERSION] = str(plat_ver) return value -CONF_VERSION_HINT = "version_hint" CONF_PLATFORM_VERSION = "platform_version" + ARDUINO_FRAMEWORK_SCHEMA = cv.All( cv.Schema( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, - cv.Optional(CONF_VERSION_HINT): cv.version_number, + cv.Optional(CONF_SOURCE): cv.string_strict, cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, } ), _arduino_check_versions, ) + CONF_SDKCONFIG_OPTIONS = "sdkconfig_options" ESP_IDF_FRAMEWORK_SCHEMA = cv.All( cv.Schema( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, - cv.Optional(CONF_VERSION_HINT): cv.version_number, + cv.Optional(CONF_SOURCE): cv.string_strict, + cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): { cv.string_strict: cv.string_strict }, - cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, cv.Optional(CONF_ADVANCED, default={}): cv.Schema( { cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean, @@ -293,7 +279,7 @@ async def to_code(config): cg.add_build_flag("-Wno-nonnull-compare") cg.add_platformio_option( "platform_packages", - [f"platformio/framework-espidf @ {conf[CONF_VERSION]}"], + [f"platformio/framework-espidf @ {conf[CONF_SOURCE]}"], ) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) @@ -325,7 +311,7 @@ async def to_code(config): cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") cg.add_platformio_option( "platform_packages", - [f"platformio/framework-arduinoespressif32 @ {conf[CONF_VERSION]}"], + [f"platformio/framework-arduinoespressif32 @ {conf[CONF_SOURCE]}"], ) cg.add_platformio_option("board_build.partitions", "partitions.csv") diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 93a461ba1f..a5323db8bf 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -4,6 +4,7 @@ from esphome.const import ( CONF_BOARD, CONF_BOARD_FLASH_MODE, CONF_FRAMEWORK, + CONF_SOURCE, CONF_VERSION, KEY_CORE, KEY_FRAMEWORK_VERSION, @@ -31,7 +32,7 @@ def set_core_data(config): CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "esp8266" CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( - config[CONF_FRAMEWORK][CONF_VERSION_HINT] + config[CONF_FRAMEWORK][CONF_VERSION] ) CORE.data[KEY_ESP8266][KEY_BOARD] = config[CONF_BOARD] return config @@ -70,66 +71,50 @@ ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 0, 2) def _arduino_check_versions(value): value = value.copy() lookups = { - "dev": ("https://github.com/esp8266/Arduino.git", cv.Version(3, 0, 2)), - "latest": ("", cv.Version(3, 0, 2)), - "recommended": ( - _format_framework_arduino_version(RECOMMENDED_ARDUINO_FRAMEWORK_VERSION), - RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, - ), + "dev": (cv.Version(3, 0, 2), "https://github.com/esp8266/Arduino.git"), + "latest": (cv.Version(3, 0, 2), None), + "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), } - ver_value = value[CONF_VERSION] - default_ver_hint = None - if ver_value.lower() in lookups: - default_ver_hint = str(lookups[ver_value.lower()][1]) - ver_value = lookups[ver_value.lower()][0] + + if value[CONF_VERSION] in lookups: + if CONF_SOURCE in value: + raise cv.Invalid( + "Framework version needs to be explicitly specified when custom source is used." + ) + + version, source = lookups[value[CONF_VERSION]] else: - with cv.suppress_invalid(): - ver = cv.Version.parse(cv.version_number(value)) - if ver <= cv.Version(2, 4, 1): - ver_value = f"~1.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" - elif ver <= cv.Version(2, 6, 2): - ver_value = f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" - else: - ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" - default_ver_hint = str(ver) + version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) + source = value.get(CONF_SOURCE, None) - value[CONF_VERSION] = ver_value + value[CONF_VERSION] = str(version) + value[CONF_SOURCE] = source or _format_framework_arduino_version(version) - if CONF_VERSION_HINT not in value and default_ver_hint is None: - raise cv.Invalid("Needs a version hint to understand the framework version") - - ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint) - value[CONF_VERSION_HINT] = ver_hint_s - plat_ver = value.get(CONF_PLATFORM_VERSION) - - if plat_ver is None: - ver_hint = cv.Version.parse(ver_hint_s) - if ver_hint >= cv.Version(3, 0, 0): - plat_ver = ARDUINO_3_PLATFORM_VERSION - elif ver_hint >= cv.Version(2, 5, 0): - plat_ver = ARDUINO_2_PLATFORM_VERSION + platform_version = value.get(CONF_PLATFORM_VERSION) + if platform_version is None: + if version >= cv.Version(3, 0, 0): + platform_version = ARDUINO_3_PLATFORM_VERSION + elif version >= cv.Version(2, 5, 0): + platform_version = ARDUINO_2_PLATFORM_VERSION else: - plat_ver = cv.Version(1, 8, 0) - value[CONF_PLATFORM_VERSION] = str(plat_ver) + platform_version = cv.Version(1, 8, 0) + value[CONF_PLATFORM_VERSION] = str(platform_version) - if cv.Version.parse(ver_hint_s) != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: + if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: _LOGGER.warning( - "The selected arduino framework version is not the recommended one" - ) - _LOGGER.warning( - "If there are connectivity or build issues please remove the manual version" + "The selected Arduino framework version is not the recommended one. " + "If there are connectivity or build issues please remove the manual version." ) return value -CONF_VERSION_HINT = "version_hint" CONF_PLATFORM_VERSION = "platform_version" ARDUINO_FRAMEWORK_SCHEMA = cv.All( cv.Schema( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, - cv.Optional(CONF_VERSION_HINT): cv.version_number, + cv.Optional(CONF_SOURCE): cv.string_strict, cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, } ), @@ -167,7 +152,7 @@ async def to_code(config): cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO") cg.add_platformio_option( "platform_packages", - [f"platformio/framework-arduinoespressif8266 @ {conf[CONF_VERSION]}"], + [f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"], ) cg.add_platformio_option( "platform", f"platformio/espressif8266 @ {conf[CONF_PLATFORM_VERSION]}" diff --git a/esphome/core/config.py b/esphome/core/config.py index bbdfcf124c..c495fefddd 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -23,6 +23,7 @@ from esphome.const import ( CONF_PLATFORMIO_OPTIONS, CONF_PRIORITY, CONF_PROJECT, + CONF_SOURCE, CONF_TRIGGER_ID, CONF_TYPE, CONF_VERSION, @@ -181,10 +182,12 @@ def preload_core_config(config, result): if CONF_BOARD_FLASH_MODE in conf: plat_conf[CONF_BOARD_FLASH_MODE] = conf.pop(CONF_BOARD_FLASH_MODE) if CONF_ARDUINO_VERSION in conf: - plat_conf[CONF_FRAMEWORK] = { - CONF_TYPE: "arduino", - CONF_VERSION: conf.pop(CONF_ARDUINO_VERSION), - } + plat_conf[CONF_FRAMEWORK] = {CONF_TYPE: "arduino"} + try: + cv.Version.parse(conf[CONF_ARDUINO_VERSION]) + plat_conf[CONF_FRAMEWORK][CONF_VERSION] = conf.pop(CONF_ARDUINO_VERSION) + except ValueError: + plat_conf[CONF_FRAMEWORK][CONF_SOURCE] = conf.pop(CONF_ARDUINO_VERSION) if CONF_BOARD in conf: plat_conf[CONF_BOARD] = conf.pop(CONF_BOARD) # Insert generated target platform config to main config From 65d2b37496577b35d6b2a73931731f6d3a8bca83 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 17 Oct 2021 08:53:49 +0200 Subject: [PATCH 1565/1841] Fix bitshift on read in ADE7953 (#2537) --- esphome/components/ade7953/ade7953.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/ade7953/ade7953.h b/esphome/components/ade7953/ade7953.h index c6fb383ed8..bb160cd8eb 100644 --- a/esphome/components/ade7953/ade7953.h +++ b/esphome/components/ade7953/ade7953.h @@ -76,9 +76,9 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent { return err; *value = 0; *value |= ((uint32_t) recv[0]) << 24; - *value |= ((uint32_t) recv[1]) << 24; - *value |= ((uint32_t) recv[2]) << 24; - *value |= ((uint32_t) recv[3]) << 24; + *value |= ((uint32_t) recv[1]) << 16; + *value |= ((uint32_t) recv[2]) << 8; + *value |= ((uint32_t) recv[3]); return i2c::ERROR_OK; } From 0991ab3543d67c5d7dfbe64bcf4aedd44cb5430f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sun, 17 Oct 2021 19:54:09 +1300 Subject: [PATCH 1566/1841] Allow downloading all bin files from backend in dashboard (#2514) Co-authored-by: Otto Winter --- esphome/__main__.py | 29 +++++++++++- esphome/dashboard/dashboard.py | 82 +++++++++++++++++++++++++++++----- 2 files changed, 99 insertions(+), 12 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index feb95e93c7..1b9c601091 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -180,7 +180,11 @@ def compile_program(args, config): from esphome import platformio_api _LOGGER.info("Compiling app...") - return platformio_api.run_compile(config, CORE.verbose) + rc = platformio_api.run_compile(config, CORE.verbose) + if rc != 0: + return rc + idedata = platformio_api.get_idedata(config) + return 0 if idedata is not None else 1 def upload_using_esptool(config, port): @@ -458,6 +462,21 @@ def command_update_all(args): return failed +def command_idedata(args, config): + from esphome import platformio_api + import json + + logging.disable(logging.INFO) + logging.disable(logging.WARNING) + + idedata = platformio_api.get_idedata(config) + if idedata is None: + return 1 + + print(json.dumps(idedata.raw, indent=2) + "\n") + return 0 + + PRE_CONFIG_ACTIONS = { "wizard": command_wizard, "version": command_version, @@ -475,6 +494,7 @@ POST_CONFIG_ACTIONS = { "clean-mqtt": command_clean_mqtt, "mqtt-fingerprint": command_mqtt_fingerprint, "clean": command_clean, + "idedata": command_idedata, } @@ -650,6 +670,11 @@ def parse_args(argv): "configuration", help="Your YAML configuration file directories.", nargs="+" ) + parser_idedata = subparsers.add_parser("idedata") + parser_idedata.add_argument( + "configuration", help="Your YAML configuration file(s).", nargs=1 + ) + # Keep backward compatibility with the old command line format of # esphome . # @@ -762,7 +787,7 @@ def run_esphome(argv): config = read_config(dict(args.substitution) if args.substitution else {}) if config is None: - return 1 + return 2 CORE.config = config if args.command not in POST_CONFIG_ACTIONS: diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index eb698a7de1..501666b100 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -9,6 +9,7 @@ import json import logging import multiprocessing import os +from pathlib import Path import secrets import shutil import subprocess @@ -26,7 +27,7 @@ import tornado.process import tornado.web import tornado.websocket -from esphome import const, util +from esphome import const, platformio_api, util from esphome.helpers import mkdir_p, get_bool_env, run_system_command from esphome.storage_json import ( EsphomeStorageJSON, @@ -398,17 +399,45 @@ class DownloadBinaryRequestHandler(BaseHandler): @authenticated @bind_config def get(self, configuration=None): - # pylint: disable=no-value-for-parameter - storage_path = ext_storage_path(settings.config_dir, configuration) - storage_json = StorageJSON.load(storage_path) - if storage_json is None: - self.send_error() + type = self.get_argument("type", "firmware.bin") + + if type == "firmware.bin": + storage_path = ext_storage_path(settings.config_dir, configuration) + storage_json = StorageJSON.load(storage_path) + if storage_json is None: + self.send_error(404) + return + filename = f"{storage_json.name}.bin" + path = storage_json.firmware_bin_path + + else: + args = ["esphome", "idedata", settings.rel_path(configuration)] + rc, stdout, _ = run_system_command(*args) + + if rc != 0: + self.send_error(404 if rc == 2 else 500) + return + + idedata = platformio_api.IDEData(json.loads(stdout)) + + found = False + for image in idedata.extra_flash_images: + if image.path.endswith(type): + path = image.path + filename = type + found = True + break + + if not found: + self.send_error(404) + return + + self.set_header("Content-Type", "application/octet-stream") + self.set_header("Content-Disposition", f'attachment; filename="{filename}"') + if not Path(path).is_file(): + self.send_error(404) return - path = storage_json.firmware_bin_path - self.set_header("Content-Type", "application/octet-stream") - filename = f"{storage_json.name}.bin" - self.set_header("Content-Disposition", f'attachment; filename="{filename}"') with open(path, "rb") as f: while True: data = f.read(16384) @@ -418,6 +447,38 @@ class DownloadBinaryRequestHandler(BaseHandler): self.finish() +class ManifestRequestHandler(BaseHandler): + @authenticated + @bind_config + def get(self, configuration=None): + args = ["esphome", "idedata", settings.rel_path(configuration)] + rc, stdout, _ = run_system_command(*args) + + if rc != 0: + self.send_error(404 if rc == 2 else 500) + return + + idedata = platformio_api.IDEData(json.loads(stdout)) + + firmware_offset = "0x10000" if idedata.extra_flash_images else "0x0" + flash_images = [ + { + "path": f"./download.bin?configuration={configuration}&type=firmware.bin", + "offset": firmware_offset, + } + ] + [ + { + "path": f"./download.bin?configuration={configuration}&type={os.path.basename(image.path)}", + "offset": image.offset, + } + for image in idedata.extra_flash_images + ] + + self.set_header("Content-Type", "application/json") + self.write(json.dumps(flash_images)) + self.finish() + + def _list_dashboard_entries(): files = settings.list_yaml_files() return [DashboardEntry(file) for file in files] @@ -862,6 +923,7 @@ def make_app(debug=get_bool_env(ENV_DEV)): (f"{rel}info", InfoRequestHandler), (f"{rel}edit", EditRequestHandler), (f"{rel}download.bin", DownloadBinaryRequestHandler), + (f"{rel}manifest.json", ManifestRequestHandler), (f"{rel}serial-ports", SerialPortRequestHandler), (f"{rel}ping", PingRequestHandler), (f"{rel}delete", DeleteRequestHandler), From 12fce7a08d615b3bd52f62a6452d34e247c31636 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 17 Oct 2021 00:26:59 -0700 Subject: [PATCH 1567/1841] Bump dashboard to 20211015.0 (#2525) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index afc75efcc7..6615d4bd3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 esptool==3.1 click==8.0.3 -esphome-dashboard==20211011.1 +esphome-dashboard==20211015.0 aioesphomeapi==10.0.0 # esp-idf requires this, but doesn't bundle it by default From 5425e45851169ee58edc175b12a31417317dcba0 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 17 Oct 2021 21:01:51 +0200 Subject: [PATCH 1568/1841] Only show timestamp for dashboard access logs (#2540) --- esphome/__main__.py | 7 ++++++- esphome/log.py | 14 ++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 1b9c601091..97059154fd 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -758,7 +758,12 @@ def run_esphome(argv): args = parse_args(argv) CORE.dashboard = args.dashboard - setup_log(args.verbose, args.quiet) + setup_log( + args.verbose, + args.quiet, + # Show timestamp for dashboard access logs + args.command == "dashboard", + ) if args.deprecated_argv_suggestion is not None and args.command != "vscode": _LOGGER.warning( "Calling ESPHome with the configuration before the command is deprecated " diff --git a/esphome/log.py b/esphome/log.py index abefcf6308..e7ba0fdd82 100644 --- a/esphome/log.py +++ b/esphome/log.py @@ -49,8 +49,10 @@ def color(col: str, msg: str, reset: bool = True) -> bool: class ESPHomeLogFormatter(logging.Formatter): - def __init__(self): - super().__init__(fmt="%(asctime)s %(levelname)s %(message)s", style="%") + def __init__(self, *, include_timestamp: bool): + fmt = "%(asctime)s " if include_timestamp else "" + fmt += "%(levelname)s %(message)s" + super().__init__(fmt=fmt, style="%") def format(self, record): formatted = super().format(record) @@ -64,7 +66,9 @@ class ESPHomeLogFormatter(logging.Formatter): return f"{prefix}{formatted}{Style.RESET_ALL}" -def setup_log(debug=False, quiet=False): +def setup_log( + debug: bool = False, quiet: bool = False, include_timestamp: bool = False +) -> None: import colorama if debug: @@ -79,4 +83,6 @@ def setup_log(debug=False, quiet=False): logging.getLogger("urllib3").setLevel(logging.WARNING) colorama.init() - logging.getLogger().handlers[0].setFormatter(ESPHomeLogFormatter()) + logging.getLogger().handlers[0].setFormatter( + ESPHomeLogFormatter(include_timestamp=include_timestamp) + ) From 644ce2a26c8a161a3005471d5faae0dd0cd925b6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 18 Oct 2021 09:55:35 +1300 Subject: [PATCH 1569/1841] Fix const used for IDF recommended version (#2542) --- esphome/components/esp32/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 8a4b8b478a..d36471f0d4 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -190,7 +190,7 @@ def _esp_idf_check_versions(value): platform_version = value.get(CONF_PLATFORM_VERSION, ESP_IDF_PLATFORM_VERSION) value[CONF_PLATFORM_VERSION] = str(platform_version) - if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: + if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION: _LOGGER.warning( "The selected ESP-IDF framework version is not the recommended one. " "If there are connectivity or build issues please remove the manual version." From 6b9c084162496aacf5896edfa0b9867593017d6b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 18 Oct 2021 09:56:31 +1300 Subject: [PATCH 1570/1841] Fix Bluetooth setup_priorities (#2458) Co-authored-by: Otto Winter --- esphome/components/ble_client/ble_client.cpp | 2 ++ esphome/components/ble_client/ble_client.h | 1 + esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp | 2 +- esphome/components/esp32_ble_server/ble_server.cpp | 2 +- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp | 2 ++ esphome/components/esp32_ble_tracker/esp32_ble_tracker.h | 1 + 6 files changed, 8 insertions(+), 2 deletions(-) diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 8ff516d735..e6cdb0c23d 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -11,6 +11,8 @@ namespace ble_client { static const char *const TAG = "ble_client"; +float BLEClient::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } + void BLEClient::setup() { auto ret = esp_ble_gattc_app_register(this->app_id); if (ret) { diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index 4a17ccb79b..23123914e8 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -81,6 +81,7 @@ class BLEClient : public espbt::ESPBTClient, public Component { void setup() override; void dump_config() override; void loop() override; + float get_setup_priority() const override; void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override; diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp index f6bab8e6df..955bc8595f 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp @@ -57,7 +57,7 @@ void ESP32BLEBeacon::setup() { ); } -float ESP32BLEBeacon::get_setup_priority() const { return setup_priority::DATA; } +float ESP32BLEBeacon::get_setup_priority() const { return setup_priority::BLUETOOTH; } void ESP32BLEBeacon::ble_core_task(void *params) { ble_setup(); diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index 0b91c238c3..e0fb80f94b 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -154,7 +154,7 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga } } -float BLEServer::get_setup_priority() const { return setup_priority::BLUETOOTH - 10; } +float BLEServer::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } void BLEServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Server:"); } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 65749f5124..303cb34aa7 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -40,6 +40,8 @@ uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) { return u; } +float ESP32BLETracker::get_setup_priority() const { return setup_priority::BLUETOOTH; } + void ESP32BLETracker::setup() { global_esp32_ble_tracker = this; this->scan_result_lock_ = xSemaphoreCreateMutex(); diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 1308119df5..02e102f06c 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -171,6 +171,7 @@ class ESP32BLETracker : public Component { /// Setup the FreeRTOS task and the Bluetooth stack. void setup() override; void dump_config() override; + float get_setup_priority() const override; void loop() override; From ced11bc707a8aaecdd2d2d6df22d64372e0d3ba8 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 18 Oct 2021 02:36:18 +0200 Subject: [PATCH 1571/1841] Autodetect ESP32 variant (#2530) Co-authored-by: Otto winter --- esphome/components/esp32/__init__.py | 25 ++++-- esphome/components/esp32/boards.py | 124 ++++++++++++++++++++++++++ esphome/components/logger/__init__.py | 3 +- 3 files changed, 144 insertions(+), 8 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index d36471f0d4..db24b9aa29 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -27,13 +27,10 @@ from .const import ( # noqa KEY_ESP32, KEY_SDKCONFIG_OPTIONS, KEY_VARIANT, - VARIANT_ESP32, - VARIANT_ESP32S2, - VARIANT_ESP32S3, VARIANT_ESP32C3, - VARIANT_ESP32H2, VARIANTS, ) +from .boards import BOARD_TO_VARIANT # force import gpio to register pin schema from .gpio import esp32_pin_to_code # noqa @@ -199,6 +196,21 @@ def _esp_idf_check_versions(value): return value +def _detect_variant(value): + if CONF_VARIANT not in value: + board = value[CONF_BOARD] + if board not in BOARD_TO_VARIANT: + raise cv.Invalid( + "This board is unknown, please set the variant manually", + path=[CONF_BOARD], + ) + + value = value.copy() + value[CONF_VARIANT] = BOARD_TO_VARIANT[board] + + return value + + CONF_PLATFORM_VERSION = "platform_version" ARDUINO_FRAMEWORK_SCHEMA = cv.All( @@ -250,12 +262,11 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.Required(CONF_BOARD): cv.string_strict, - cv.Optional(CONF_VARIANT, default="ESP32"): cv.one_of( - *VARIANTS, upper=True - ), + cv.Optional(CONF_VARIANT): cv.one_of(*VARIANTS, upper=True), cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA, } ), + _detect_variant, set_core_data, ) diff --git a/esphome/components/esp32/boards.py b/esphome/components/esp32/boards.py index ddf4bf2026..7f7bb2259f 100644 --- a/esphome/components/esp32/boards.py +++ b/esphome/components/esp32/boards.py @@ -1,3 +1,5 @@ +from .const import VARIANT_ESP32, VARIANT_ESP32S2, VARIANT_ESP32C3 + ESP32_BASE_PINS = { "TX": 1, "RX": 3, @@ -925,3 +927,125 @@ ESP32_BOARD_PINS = { }, "xinabox_cw02": {"LED": 27}, } + +""" +BOARD_TO_VARIANT generated with: + +git clone https://github.com/platformio/platform-espressif32 +for x in platform-espressif32/boards/*.json; do + mcu=$(jq -r .build.mcu <"$x"); + fname=$(basename "$x") + board="${fname%.*}" + variant=$(echo "$mcu" | tr '[:lower:]' '[:upper:]') + echo " \"$board\": VARIANT_${variant}," +done | sort +""" + +BOARD_TO_VARIANT = { + "alksesp32": VARIANT_ESP32, + "az-delivery-devkit-v4": VARIANT_ESP32, + "bpi-bit": VARIANT_ESP32, + "briki_abc_esp32": VARIANT_ESP32, + "briki_mbc-wb_esp32": VARIANT_ESP32, + "d-duino-32": VARIANT_ESP32, + "esp320": VARIANT_ESP32, + "esp32-c3-devkitm-1": VARIANT_ESP32C3, + "esp32cam": VARIANT_ESP32, + "esp32-devkitlipo": VARIANT_ESP32, + "esp32dev": VARIANT_ESP32, + "esp32doit-devkit-v1": VARIANT_ESP32, + "esp32doit-espduino": VARIANT_ESP32, + "esp32-evb": VARIANT_ESP32, + "esp32-gateway": VARIANT_ESP32, + "esp32-poe-iso": VARIANT_ESP32, + "esp32-poe": VARIANT_ESP32, + "esp32-pro": VARIANT_ESP32, + "esp32-s2-kaluga-1": VARIANT_ESP32S2, + "esp32-s2-saola-1": VARIANT_ESP32S2, + "esp32thing_plus": VARIANT_ESP32, + "esp32thing": VARIANT_ESP32, + "esp32vn-iot-uno": VARIANT_ESP32, + "espea32": VARIANT_ESP32, + "espectro32": VARIANT_ESP32, + "espino32": VARIANT_ESP32, + "esp-wrover-kit": VARIANT_ESP32, + "etboard": VARIANT_ESP32, + "featheresp32-s2": VARIANT_ESP32S2, + "featheresp32": VARIANT_ESP32, + "firebeetle32": VARIANT_ESP32, + "fm-devkit": VARIANT_ESP32, + "frogboard": VARIANT_ESP32, + "healthypi4": VARIANT_ESP32, + "heltec_wifi_kit_32_v2": VARIANT_ESP32, + "heltec_wifi_kit_32": VARIANT_ESP32, + "heltec_wifi_lora_32_V2": VARIANT_ESP32, + "heltec_wifi_lora_32": VARIANT_ESP32, + "heltec_wireless_stick_lite": VARIANT_ESP32, + "heltec_wireless_stick": VARIANT_ESP32, + "honeylemon": VARIANT_ESP32, + "hornbill32dev": VARIANT_ESP32, + "hornbill32minima": VARIANT_ESP32, + "imbrios-logsens-v1p1": VARIANT_ESP32, + "inex_openkb": VARIANT_ESP32, + "intorobot": VARIANT_ESP32, + "iotaap_magnolia": VARIANT_ESP32, + "iotbusio": VARIANT_ESP32, + "iotbusproteus": VARIANT_ESP32, + "kits-edu": VARIANT_ESP32, + "labplus_mpython": VARIANT_ESP32, + "lolin32_lite": VARIANT_ESP32, + "lolin32": VARIANT_ESP32, + "lolin_d32_pro": VARIANT_ESP32, + "lolin_d32": VARIANT_ESP32, + "lopy4": VARIANT_ESP32, + "lopy": VARIANT_ESP32, + "m5stack-atom": VARIANT_ESP32, + "m5stack-core2": VARIANT_ESP32, + "m5stack-core-esp32": VARIANT_ESP32, + "m5stack-coreink": VARIANT_ESP32, + "m5stack-fire": VARIANT_ESP32, + "m5stack-grey": VARIANT_ESP32, + "m5stack-timer-cam": VARIANT_ESP32, + "m5stick-c": VARIANT_ESP32, + "magicbit": VARIANT_ESP32, + "mgbot-iotik32a": VARIANT_ESP32, + "mgbot-iotik32b": VARIANT_ESP32, + "mhetesp32devkit": VARIANT_ESP32, + "mhetesp32minikit": VARIANT_ESP32, + "microduino-core-esp32": VARIANT_ESP32, + "nano32": VARIANT_ESP32, + "nina_w10": VARIANT_ESP32, + "node32s": VARIANT_ESP32, + "nodemcu-32s": VARIANT_ESP32, + "nscreen-32": VARIANT_ESP32, + "odroid_esp32": VARIANT_ESP32, + "onehorse32dev": VARIANT_ESP32, + "oroca_edubot": VARIANT_ESP32, + "pico32": VARIANT_ESP32, + "piranha_esp32": VARIANT_ESP32, + "pocket_32": VARIANT_ESP32, + "pycom_gpy": VARIANT_ESP32, + "qchip": VARIANT_ESP32, + "quantum": VARIANT_ESP32, + "sensesiot_weizen": VARIANT_ESP32, + "sg-o_airMon": VARIANT_ESP32, + "s_odi_ultra": VARIANT_ESP32, + "sparkfun_lora_gateway_1-channel": VARIANT_ESP32, + "tinypico": VARIANT_ESP32, + "ttgo-lora32-v1": VARIANT_ESP32, + "ttgo-lora32-v21": VARIANT_ESP32, + "ttgo-lora32-v2": VARIANT_ESP32, + "ttgo-t1": VARIANT_ESP32, + "ttgo-t7-v13-mini32": VARIANT_ESP32, + "ttgo-t7-v14-mini32": VARIANT_ESP32, + "ttgo-t-beam": VARIANT_ESP32, + "ttgo-t-watch": VARIANT_ESP32, + "turta_iot_node": VARIANT_ESP32, + "vintlabs-devkit-v1": VARIANT_ESP32, + "wemosbat": VARIANT_ESP32, + "wemos_d1_mini32": VARIANT_ESP32, + "wesp32": VARIANT_ESP32, + "widora-air": VARIANT_ESP32, + "wifiduino32": VARIANT_ESP32, + "xinabox_cw02": VARIANT_ESP32, +} diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index fe2a3ec8f8..20a0b0f792 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -19,7 +19,8 @@ from esphome.const import ( CONF_TX_BUFFER_SIZE, ) from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority -from esphome.components.esp32 import get_esp32_variant, VARIANT_ESP32S2, VARIANT_ESP32C3 +from esphome.components.esp32 import get_esp32_variant +from esphome.components.esp32.const import VARIANT_ESP32S2, VARIANT_ESP32C3 CODEOWNERS = ["@esphome/core"] logger_ns = cg.esphome_ns.namespace("logger") From 03cfd78c591b660db15ae649bb530e60adf2797c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 19 Oct 2021 15:16:39 +1300 Subject: [PATCH 1572/1841] Bump dashboard to 20211019.0 (#2549) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6615d4bd3d..23642adf9a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 esptool==3.1 click==8.0.3 -esphome-dashboard==20211015.0 +esphome-dashboard==20211019.0 aioesphomeapi==10.0.0 # esp-idf requires this, but doesn't bundle it by default From 5b5ead872b677bc21e5eb62ac660d388b05894d6 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 19 Oct 2021 12:56:49 +0200 Subject: [PATCH 1573/1841] Fix ADC pin validation on ESP32-C3 (#2551) --- esphome/components/adc/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 9a0407d0f4..26ef504c1a 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -35,7 +35,7 @@ def validate_adc_pin(value): if is_esp32c3(): if not (0 <= value <= 4): # ADC1 raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.") - if not (32 <= value <= 39): # ADC1 + elif not (32 <= value <= 39): # ADC1 raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.") elif CORE.is_esp8266: from esphome.components.esp8266.gpio import CONF_ANALOG From e2a812fa4b43489bfc3813c1982a8ac50784a2c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Oct 2021 20:28:48 +0200 Subject: [PATCH 1574/1841] Bump pytest-asyncio from 0.15.1 to 0.16.0 (#2547) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 8ebcf24d4d..e40d51f4bf 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -8,6 +8,6 @@ pre-commit pytest==6.2.5 pytest-cov==3.0.0 pytest-mock==3.6.1 -pytest-asyncio==0.15.1 +pytest-asyncio==0.16.0 asyncmock==0.4.2 hypothesis==5.49.0 From f5441a87e3f308c76fef424551bc9fea27f6d8ad Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Tue, 19 Oct 2021 23:10:24 +0200 Subject: [PATCH 1575/1841] ignore exception when not waiting for a response (#2552) --- esphome/components/modbus/modbus.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 1f6d868baf..45c5bfb603 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -96,23 +96,27 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { ESP_LOGW(TAG, "Modbus CRC Check failed! %02X!=%02X", computed_crc, remote_crc); return false; } - - waiting_for_response = 0; std::vector data(this->rx_buffer_.begin() + data_offset, this->rx_buffer_.begin() + data_offset + data_len); - bool found = false; for (auto *device : this->devices_) { if (device->address_ == address) { // Is it an error response? if ((function_code & 0x80) == 0x80) { - ESP_LOGW(TAG, "Modbus error function code: 0x%X exception: %d", function_code, raw[2]); - device->on_modbus_error(function_code & 0x7F, raw[2]); + ESP_LOGD(TAG, "Modbus error function code: 0x%X exception: %d", function_code, raw[2]); + if (waiting_for_response != 0) { + device->on_modbus_error(function_code & 0x7F, raw[2]); + } else { + // Ignore modbus exception not related to a pending command + ESP_LOGD(TAG, "Ignoring Modbus error - not expecting a response"); + } } else { device->on_modbus_data(data); } found = true; } } + waiting_for_response = 0; + if (!found) { ESP_LOGW(TAG, "Got Modbus frame from unknown address 0x%02X! ", address); } @@ -196,6 +200,7 @@ void Modbus::send_raw(const std::vector &payload) { if (this->flow_control_pin_ != nullptr) this->flow_control_pin_->digital_write(false); waiting_for_response = payload[0]; + ESP_LOGV(TAG, "Modbus write raw: %s", hexencode(payload).c_str()); last_send_ = millis(); } From cb48394e8a776beb8f873cd70a2c0e3c9b28dcd3 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Oct 2021 10:53:49 +1300 Subject: [PATCH 1576/1841] Bump dashboard to 20211020.0 (#2556) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 23642adf9a..08f3e8e4e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 esptool==3.1 click==8.0.3 -esphome-dashboard==20211019.0 +esphome-dashboard==20211020.0 aioesphomeapi==10.0.0 # esp-idf requires this, but doesn't bundle it by default From 8b11e5aeb1ea4072a76412cb9da3fd2345159766 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Oct 2021 13:25:00 +1300 Subject: [PATCH 1577/1841] Fix HA addon so it does not have logout button (#2558) --- esphome/dashboard/dashboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 501666b100..63378a38b5 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -597,7 +597,7 @@ class MainRequestHandler(BaseHandler): get_template_path("index"), begin=begin, **template_args(), - login_enabled=settings.using_auth, + login_enabled=settings.using_password, ) From bcc77c73e1fb42ab0b3bbbf2ad704dbe98457ab1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Oct 2021 16:17:00 +1300 Subject: [PATCH 1578/1841] Bump esphome-dashboard to 20211020.1 (#2559) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 08f3e8e4e5..a32cb1e123 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 esptool==3.1 click==8.0.3 -esphome-dashboard==20211020.0 +esphome-dashboard==20211020.1 aioesphomeapi==10.0.0 # esp-idf requires this, but doesn't bundle it by default From e79f7ce29016a68df3cc2b685dd8ef4dbf3081d0 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Wed, 20 Oct 2021 19:44:51 +0200 Subject: [PATCH 1579/1841] [ESP32] ADC auto-range setting (#2541) --- esphome/components/adc/adc_sensor.cpp | 160 ++++++++++++++++---------- esphome/components/adc/adc_sensor.h | 4 + esphome/components/adc/sensor.py | 6 +- 3 files changed, 110 insertions(+), 60 deletions(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index c8f8b0e0f6..0c24c615f3 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -1,5 +1,6 @@ #include "adc_sensor.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" #ifdef USE_ESP8266 #ifdef USE_ADC_SENSOR_VCC @@ -16,8 +17,6 @@ namespace adc { static const char *const TAG = "adc"; #ifdef USE_ESP32 -void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } - inline adc1_channel_t gpio_to_adc1(uint8_t pin) { #if CONFIG_IDF_TARGET_ESP32 switch (pin) { @@ -57,6 +56,8 @@ inline adc1_channel_t gpio_to_adc1(uint8_t pin) { } #endif } +void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } +void ADCSensor::set_autorange(bool autorange) { this->autorange_ = autorange; } #endif void ADCSensor::setup() { @@ -66,6 +67,8 @@ void ADCSensor::setup() { #endif #ifdef USE_ESP32 + if (this->autorange_) + this->attenuation_ = ADC_ATTEN_DB_11; adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), attenuation_); adc1_config_width(ADC_WIDTH_BIT_12); #if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 @@ -84,22 +87,25 @@ void ADCSensor::dump_config() { #endif #ifdef USE_ESP32 LOG_PIN(" Pin: ", pin_); - switch (this->attenuation_) { - case ADC_ATTEN_DB_0: - ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); - break; - case ADC_ATTEN_DB_2_5: - ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); - break; - case ADC_ATTEN_DB_6: - ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); - break; - case ADC_ATTEN_DB_11: - ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)"); - break; - default: // This is to satisfy the unused ADC_ATTEN_MAX - break; - } + if (autorange_) + ESP_LOGCONFIG(TAG, " Attenuation: auto"); + else + switch (this->attenuation_) { + case ADC_ATTEN_DB_0: + ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); + break; + case ADC_ATTEN_DB_2_5: + ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); + break; + case ADC_ATTEN_DB_6: + ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); + break; + case ADC_ATTEN_DB_11: + ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)"); + break; + default: // This is to satisfy the unused ADC_ATTEN_MAX + break; + } #endif LOG_UPDATE_INTERVAL(this); } @@ -109,56 +115,92 @@ void ADCSensor::update() { ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v); this->publish_state(value_v); } -float ADCSensor::sample() { +uint16_t ADCSensor::read_raw_() { #ifdef USE_ESP32 - int raw = adc1_get_raw(gpio_to_adc1(pin_->get_pin())); - float value_v = raw / 4095.0f; -#if CONFIG_IDF_TARGET_ESP32 - switch (this->attenuation_) { - case ADC_ATTEN_DB_0: - value_v *= 1.1; - break; - case ADC_ATTEN_DB_2_5: - value_v *= 1.5; - break; - case ADC_ATTEN_DB_6: - value_v *= 2.2; - break; - case ADC_ATTEN_DB_11: - value_v *= 3.9; - break; - default: // This is to satisfy the unused ADC_ATTEN_MAX - break; - } -#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 - switch (this->attenuation_) { - case ADC_ATTEN_DB_0: - value_v *= 0.84; - break; - case ADC_ATTEN_DB_2_5: - value_v *= 1.13; - break; - case ADC_ATTEN_DB_6: - value_v *= 1.56; - break; - case ADC_ATTEN_DB_11: - value_v *= 3.0; - break; - default: // This is to satisfy the unused ADC_ATTEN_MAX - break; - } -#endif - return value_v; + return adc1_get_raw(gpio_to_adc1(pin_->get_pin())); #endif #ifdef USE_ESP8266 #ifdef USE_ADC_SENSOR_VCC - return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance) + return ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance) #else - return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT + return analogRead(this->pin_->get_pin()); // NOLINT #endif #endif } +uint32_t ADCSensor::raw_to_microvolts_(uint16_t raw) { +#ifdef USE_ESP32 +#if CONFIG_IDF_TARGET_ESP32 + switch (this->attenuation_) { + case ADC_ATTEN_DB_0: + return raw * 269; // 1e6 * 1.1 / 4095 + case ADC_ATTEN_DB_2_5: + return raw * 366; // 1e6 * 1.5 / 4095 + case ADC_ATTEN_DB_6: + return raw * 537; // 1e6 * 2.2 / 4095 + case ADC_ATTEN_DB_11: + return raw * 952; // 1e6 * 3.9 / 4095 + default: // This is to satisfy the unused ADC_ATTEN_MAX + return raw * 244; // 1e6 * 1.0 / 4095 + } +#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 + switch (this->attenuation_) { + case ADC_ATTEN_DB_0: + return raw * 205; // 1e6 * 0.84 / 4095 + case ADC_ATTEN_DB_2_5: + return raw * 276; // 1e6 * 1.13 / 4095 + case ADC_ATTEN_DB_6: + return raw * 381; // 1e6 * 1.56 / 4095 + case ADC_ATTEN_DB_11: + return raw * 733; // 1e6 * 3.0 / 4095 + default: // This is to satisfy the unused ADC_ATTEN_MAX + return raw * 244; // 1e6 * 1.0 / 4095 + } +#endif +#endif + +#ifdef USE_ESP8266 + return raw * 977; // 1e6 / 1024 +#endif +} +float ADCSensor::sample() { + int raw = this->read_raw_(); + uint32_t v = this->raw_to_microvolts_(raw); +#ifdef USE_ESP32 + if (autorange_) { + int raw11 = raw, raw6 = 4095, raw2 = 4095, raw0 = 4095; + uint32_t v11 = v, v6 = 0, v2 = 0, v0 = 0; + if (raw11 < 4095) { // Progressively read all attenuation ranges + adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_6); + raw6 = this->read_raw_(); + v6 = this->raw_to_microvolts_(raw6); + if (raw6 < 4095) { + adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_2_5); + raw2 = this->read_raw_(); + v2 = this->raw_to_microvolts_(raw2); + if (raw2 < 4095) { + adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_0); + raw0 = this->read_raw_(); + v0 = this->raw_to_microvolts_(raw0); + } + } + adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_11); + } // Contribution coefficients (normalized to 2048) + uint16_t c11 = clamp(raw11, 0, 2048); // high 1, middle 1, low 0 + uint16_t c6 = (2048 - abs(raw6 - 2048)); // high 0, middle 1, low 0 + uint16_t c2 = (2048 - abs(raw2 - 2048)); // high 0, middle 1, low 0 + uint16_t c0 = clamp(4095 - raw0, 0, 2048); // high 0, middle 1, low 1 + uint32_t csum = c11 + c6 + c2 + c0; // sum to normalize the final result + if (csum > 0) + v = (v11 * c11) + (v6 * c6) + (v2 * c2) + (v0 * c0); + else // in case of error, this keeps the 11db output (v) + csum = 1; + csum *= 1e6; // include the 1e6 microvolts->volts conversion factor + return (float) v / (float) csum; // normalize, convert & return + } +#endif + return v / (float) 1e6; // convert from microvolts to volts +} #ifdef USE_ESP8266 std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } #endif diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index b8c702be4e..fafb0d5ca0 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -18,6 +18,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage #ifdef USE_ESP32 /// Set the attenuation for this pin. Only available on the ESP32. void set_attenuation(adc_atten_t attenuation); + void set_autorange(bool autorange); #endif /// Update adc values. @@ -36,9 +37,12 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage protected: InternalGPIOPin *pin_; + uint16_t read_raw_(); + uint32_t raw_to_microvolts_(uint16_t raw); #ifdef USE_ESP32 adc_atten_t attenuation_{ADC_ATTEN_DB_0}; + bool autorange_{false}; #endif }; diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 26ef504c1a..0265f52d31 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -21,6 +21,7 @@ ATTENUATION_MODES = { "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5, "6db": cg.global_ns.ADC_ATTEN_DB_6, "11db": cg.global_ns.ADC_ATTEN_DB_11, + "auto": "auto", } @@ -92,4 +93,7 @@ async def to_code(config): cg.add(var.set_pin(pin)) if CONF_ATTENUATION in config: - cg.add(var.set_attenuation(config[CONF_ATTENUATION])) + if config[CONF_ATTENUATION] == "auto": + cg.add(var.set_autorange(cg.global_ns.true)) + else: + cg.add(var.set_attenuation(config[CONF_ATTENUATION])) From e4d17e0b15433e264023e00eb4c776bc4f543a33 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 21 Oct 2021 06:45:10 +1300 Subject: [PATCH 1580/1841] A few esp32_ble_server/improv fixes (#2562) --- .../components/esp32_ble_server/ble_server.cpp | 17 +++++++++-------- .../components/esp32_ble_server/ble_server.h | 14 ++++++++------ .../esp32_improv/esp32_improv_component.h | 2 +- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index e0fb80f94b..15bea07021 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -98,19 +98,20 @@ bool BLEServer::create_device_characteristics_() { return true; } -BLEService *BLEServer::create_service(const uint8_t *uuid, bool advertise) { +std::shared_ptr BLEServer::create_service(const uint8_t *uuid, bool advertise) { return this->create_service(ESPBTUUID::from_raw(uuid), advertise); } -BLEService *BLEServer::create_service(uint16_t uuid, bool advertise) { +std::shared_ptr BLEServer::create_service(uint16_t uuid, bool advertise) { return this->create_service(ESPBTUUID::from_uint16(uuid), advertise); } -BLEService *BLEServer::create_service(const std::string &uuid, bool advertise) { +std::shared_ptr BLEServer::create_service(const std::string &uuid, bool advertise) { return this->create_service(ESPBTUUID::from_raw(uuid), advertise); } -BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles, uint8_t inst_id) { +std::shared_ptr BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles, + uint8_t inst_id) { ESP_LOGV(TAG, "Creating service - %s", uuid.to_string().c_str()); - BLEService *service = new BLEService(uuid, num_handles, inst_id); // NOLINT(cppcoreguidelines-owning-memory) - this->services_.push_back(service); + std::shared_ptr service = std::make_shared(uuid, num_handles, inst_id); + this->services_.emplace_back(service); if (advertise) { esp32_ble::global_ble->get_advertising()->add_service_uuid(uuid); } @@ -149,12 +150,12 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga break; } - for (auto *service : this->services_) { + for (const auto &service : this->services_) { service->gatts_event_handler(event, gatts_if, param); } } -float BLEServer::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } +float BLEServer::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH + 10; } void BLEServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Server:"); } diff --git a/esphome/components/esp32_ble_server/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h index 9f7e8b8fc0..d275eeab01 100644 --- a/esphome/components/esp32_ble_server/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -11,6 +11,7 @@ #include "esphome/core/preferences.h" #include +#include #ifdef USE_ESP32 @@ -43,10 +44,11 @@ class BLEServer : public Component { void set_manufacturer(const std::string &manufacturer) { this->manufacturer_ = manufacturer; } void set_model(const std::string &model) { this->model_ = model; } - BLEService *create_service(const uint8_t *uuid, bool advertise = false); - BLEService *create_service(uint16_t uuid, bool advertise = false); - BLEService *create_service(const std::string &uuid, bool advertise = false); - BLEService *create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15, uint8_t inst_id = 0); + std::shared_ptr create_service(const uint8_t *uuid, bool advertise = false); + std::shared_ptr create_service(uint16_t uuid, bool advertise = false); + std::shared_ptr create_service(const std::string &uuid, bool advertise = false); + std::shared_ptr create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15, + uint8_t inst_id = 0); esp_gatt_if_t get_gatts_if() { return this->gatts_if_; } uint32_t get_connected_client_count() { return this->connected_clients_; } @@ -74,8 +76,8 @@ class BLEServer : public Component { uint32_t connected_clients_{0}; std::map clients_; - std::vector services_; - BLEService *device_information_service_; + std::vector> services_; + std::shared_ptr device_information_service_; std::vector service_components_; diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index 53cda5f399..3a5d150fbe 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -48,7 +48,7 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent { std::vector incoming_data_; wifi::WiFiAP connecting_sta_; - BLEService *service_; + std::shared_ptr service_; BLECharacteristic *status_; BLECharacteristic *error_; BLECharacteristic *rpc_; From 7cfede5b835cb72a652cf07ab3ce6129bfba778f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 21 Oct 2021 07:08:34 +1300 Subject: [PATCH 1581/1841] Bump esphome-dashboard to 20211021.0 (#2564) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a32cb1e123..b946019f18 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 esptool==3.1 click==8.0.3 -esphome-dashboard==20211020.1 +esphome-dashboard==20211021.0 aioesphomeapi==10.0.0 # esp-idf requires this, but doesn't bundle it by default From 64a45dc6a67d261023a5823f5cfddfc0e4fcdab7 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 20 Oct 2021 20:15:09 +0200 Subject: [PATCH 1582/1841] Move running process log line to debug level (#2565) --- esphome/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/util.py b/esphome/util.py index 527e370ad8..0f168cade3 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -178,7 +178,7 @@ def run_external_command( orig_argv = sys.argv orig_exit = sys.exit # mock sys.exit full_cmd = " ".join(shlex_quote(x) for x in cmd) - _LOGGER.info("Running: %s", full_cmd) + _LOGGER.debug("Running: %s", full_cmd) orig_stdout = sys.stdout sys.stdout = RedirectText(sys.stdout, filter_lines=filter_lines) @@ -214,7 +214,7 @@ def run_external_command( def run_external_process(*cmd, **kwargs): full_cmd = " ".join(shlex_quote(x) for x in cmd) - _LOGGER.info("Running: %s", full_cmd) + _LOGGER.debug("Running: %s", full_cmd) filter_lines = kwargs.get("filter_lines") capture_stdout = kwargs.get("capture_stdout", False) From 15b596841858af676c4adb3d0abee2e87e92947e Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 20 Oct 2021 20:31:13 +0200 Subject: [PATCH 1583/1841] Revert nextion clang-tidy changes (#2566) --- .../binary_sensor/nextion_binarysensor.cpp | 4 +- .../binary_sensor/nextion_binarysensor.h | 3 +- esphome/components/nextion/nextion.cpp | 73 ++++++++++++------- esphome/components/nextion/nextion.h | 11 ++- esphome/components/nextion/nextion_base.h | 9 +-- .../nextion/nextion_component_base.h | 3 +- esphome/components/nextion/nextion_upload.cpp | 14 ++-- .../nextion/sensor/nextion_sensor.cpp | 8 +- .../nextion/sensor/nextion_sensor.h | 5 +- .../nextion/switch/nextion_switch.cpp | 4 +- .../nextion/switch/nextion_switch.h | 5 +- .../text_sensor/nextion_textsensor.cpp | 4 +- .../nextion/text_sensor/nextion_textsensor.h | 5 +- 13 files changed, 78 insertions(+), 70 deletions(-) diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp index c5bfa78efe..bf6e74cb38 100644 --- a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp @@ -33,7 +33,7 @@ void NextionBinarySensor::update() { if (this->variable_name_.empty()) // This is a touch component return; - this->nextion_->add_to_get_queue(shared_from_this()); + this->nextion_->add_to_get_queue(this); } void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nextion) { @@ -48,7 +48,7 @@ void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nexti this->needs_to_send_update_ = true; } else { this->needs_to_send_update_ = false; - this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), (int) state); + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); } } diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.h b/esphome/components/nextion/binary_sensor/nextion_binarysensor.h index b86ee74013..b6b23ada85 100644 --- a/esphome/components/nextion/binary_sensor/nextion_binarysensor.h +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.h @@ -10,8 +10,7 @@ class NextionBinarySensor; class NextionBinarySensor : public NextionComponent, public binary_sensor::BinarySensorInitiallyOff, - public PollingComponent, - public std::enable_shared_from_this { + public PollingComponent { public: NextionBinarySensor(NextionBase *nextion) { this->nextion_ = nextion; } diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index d56c370412..f23f55c9bb 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -196,7 +196,7 @@ void Nextion::print_queue_members_() { ESP_LOGN(TAG, "print_queue_members_ (top 10) size %zu", this->nextion_queue_.size()); ESP_LOGN(TAG, "*******************************************"); int count = 0; - for (auto &i : this->nextion_queue_) { + for (auto *i : this->nextion_queue_) { if (count++ == 10) break; @@ -257,9 +257,8 @@ bool Nextion::remove_from_q_(bool report_empty) { return false; } - auto nb = std::move(this->nextion_queue_.front()); - this->nextion_queue_.pop_front(); - auto &component = nb->component; + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; ESP_LOGN(TAG, "Removing %s from the queue", component->get_variable_name().c_str()); @@ -267,8 +266,10 @@ bool Nextion::remove_from_q_(bool report_empty) { if (component->get_variable_name() == "sleep_wake") { this->is_sleeping_ = false; } + delete component; // NOLINT(cppcoreguidelines-owning-memory) } - + delete nb; // NOLINT(cppcoreguidelines-owning-memory) + this->nextion_queue_.pop_front(); return true; } @@ -357,7 +358,7 @@ void Nextion::process_nextion_commands_() { int index = 0; int found = -1; for (auto &nb : this->nextion_queue_) { - auto &component = nb->component; + NextionComponentBase *component = nb->component; if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { ESP_LOGW(TAG, "Nextion reported invalid Waveform ID %d or Channel # %d was used!", @@ -368,6 +369,9 @@ void Nextion::process_nextion_commands_() { found = index; + delete component; // NOLINT(cppcoreguidelines-owning-memory) + delete nb; // NOLINT(cppcoreguidelines-owning-memory) + break; } ++index; @@ -464,9 +468,8 @@ void Nextion::process_nextion_commands_() { break; } - auto nb = std::move(this->nextion_queue_.front()); - this->nextion_queue_.pop_front(); - auto &component = nb->component; + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; if (component->get_queue_type() != NextionQueueType::TEXT_SENSOR) { ESP_LOGE(TAG, "ERROR: Received string return but next in queue \"%s\" is not a text sensor", @@ -477,6 +480,9 @@ void Nextion::process_nextion_commands_() { component->set_state_from_string(to_process, true, false); } + delete nb; // NOLINT(cppcoreguidelines-owning-memory) + this->nextion_queue_.pop_front(); + break; } // 0x71 0x01 0x02 0x03 0x04 0xFF 0xFF 0xFF @@ -505,9 +511,8 @@ void Nextion::process_nextion_commands_() { ++dataindex; } - auto nb = std::move(this->nextion_queue_.front()); - this->nextion_queue_.pop_front(); - auto &component = nb->component; + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; if (component->get_queue_type() != NextionQueueType::SENSOR && component->get_queue_type() != NextionQueueType::BINARY_SENSOR && @@ -521,6 +526,9 @@ void Nextion::process_nextion_commands_() { component->set_state_from_int(value, true, false); } + delete nb; // NOLINT(cppcoreguidelines-owning-memory) + this->nextion_queue_.pop_front(); + break; } @@ -682,7 +690,7 @@ void Nextion::process_nextion_commands_() { int index = 0; int found = -1; for (auto &nb : this->nextion_queue_) { - auto &component = nb->component; + auto component = nb->component; if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { size_t buffer_to_send = component->get_wave_buffer().size() < 255 ? component->get_wave_buffer().size() : 255; // ADDT command can only send 255 @@ -699,6 +707,8 @@ void Nextion::process_nextion_commands_() { component->get_wave_buffer().begin() + buffer_to_send); } found = index; + delete component; // NOLINT(cppcoreguidelines-owning-memory) + delete nb; // NOLINT(cppcoreguidelines-owning-memory) break; } ++index; @@ -727,7 +737,7 @@ void Nextion::process_nextion_commands_() { if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) { for (int i = 0; i < this->nextion_queue_.size(); i++) { - auto &component = this->nextion_queue_[i]->component; + NextionComponentBase *component = this->nextion_queue_[i]->component; if (this->nextion_queue_[i]->queue_time + this->max_q_age_ms_ < ms) { if (this->nextion_queue_[i]->queue_time == 0) ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\" queue_time 0", @@ -744,8 +754,11 @@ void Nextion::process_nextion_commands_() { if (component->get_variable_name() == "sleep_wake") { this->is_sleeping_ = false; } + delete component; // NOLINT(cppcoreguidelines-owning-memory) } + delete this->nextion_queue_[i]; // NOLINT(cppcoreguidelines-owning-memory) + this->nextion_queue_.erase(this->nextion_queue_.begin() + i); i--; @@ -899,16 +912,18 @@ uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool * @param variable_name Name for the queue */ void Nextion::add_no_result_to_queue_(const std::string &variable_name) { - auto nextion_queue = make_unique(); + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; - nextion_queue->component = make_unique(); + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + nextion_queue->component = new nextion::NextionComponentBase; nextion_queue->component->set_variable_name(variable_name); nextion_queue->queue_time = millis(); - ESP_LOGN(TAG, "Add to queue type: NORESULT component %s", nextion_queue->component->get_variable_name().c_str()); + this->nextion_queue_.push_back(nextion_queue); - this->nextion_queue_.push_back(std::move(nextion_queue)); + ESP_LOGN(TAG, "Add to queue type: NORESULT component %s", nextion_queue->component->get_variable_name().c_str()); } /** @@ -979,7 +994,7 @@ bool Nextion::add_no_result_to_queue_with_printf_(const std::string &variable_na * @param is_sleep_safe The command is safe to send when the Nextion is sleeping */ -void Nextion::add_no_result_to_queue_with_set(std::shared_ptr component, int state_value) { +void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) { this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(), state_value); } @@ -1007,8 +1022,7 @@ void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &varia * @param state_value String value to set * @param is_sleep_safe The command is safe to send when the Nextion is sleeping */ -void Nextion::add_no_result_to_queue_with_set(std::shared_ptr component, - const std::string &state_value) { +void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) { this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(), state_value); } @@ -1028,11 +1042,12 @@ void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &varia state_value.c_str()); } -void Nextion::add_to_get_queue(std::shared_ptr component) { +void Nextion::add_to_get_queue(NextionComponentBase *component) { if ((!this->is_setup() && !this->ignore_is_setup_)) return; - auto nextion_queue = make_unique(); + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; nextion_queue->component = component; nextion_queue->queue_time = millis(); @@ -1043,7 +1058,7 @@ void Nextion::add_to_get_queue(std::shared_ptr component) std::string command = "get " + component->get_variable_name_to_send(); if (this->send_command_(command)) { - this->nextion_queue_.push_back(std::move(nextion_queue)); + this->nextion_queue_.push_back(nextion_queue); } } @@ -1055,13 +1070,15 @@ void Nextion::add_to_get_queue(std::shared_ptr component) * @param buffer_to_send The buffer size * @param buffer_size The buffer data */ -void Nextion::add_addt_command_to_queue(std::shared_ptr component) { +void Nextion::add_addt_command_to_queue(NextionComponentBase *component) { if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) return; - auto nextion_queue = make_unique(); + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; - nextion_queue->component = std::make_shared(); + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + nextion_queue->component = new nextion::NextionComponentBase; nextion_queue->queue_time = millis(); size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() @@ -1070,7 +1087,7 @@ void Nextion::add_addt_command_to_queue(std::shared_ptr co std::string command = "addt " + to_string(component->get_component_id()) + "," + to_string(component->get_wave_channel_id()) + "," + to_string(buffer_to_send); if (this->send_command_(command)) { - this->nextion_queue_.push_back(std::move(nextion_queue)); + this->nextion_queue_.push_back(nextion_queue); } } diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 1bee41f6cf..285b3ac9a3 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -707,18 +707,17 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state); void set_nextion_text_state(const std::string &name, const std::string &state); - void add_no_result_to_queue_with_set(std::shared_ptr component, int state_value) override; + void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) override; void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, int state_value) override; - void add_no_result_to_queue_with_set(std::shared_ptr component, - const std::string &state_value) override; + void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) override; void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, const std::string &state_value) override; - void add_to_get_queue(std::shared_ptr component) override; + void add_to_get_queue(NextionComponentBase *component) override; - void add_addt_command_to_queue(std::shared_ptr component) override; + void add_addt_command_to_queue(NextionComponentBase *component) override; void update_components_by_prefix(const std::string &prefix); @@ -729,7 +728,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; } protected: - std::deque> nextion_queue_; + std::deque nextion_queue_; uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag); void all_components_send_state_(bool force_update = false); uint64_t comok_sent_ = 0; diff --git a/esphome/components/nextion/nextion_base.h b/esphome/components/nextion/nextion_base.h index d91c70c960..a24fd74060 100644 --- a/esphome/components/nextion/nextion_base.h +++ b/esphome/components/nextion/nextion_base.h @@ -24,19 +24,18 @@ class NextionBase; class NextionBase { public: - virtual void add_no_result_to_queue_with_set(std::shared_ptr component, int state_value) = 0; + virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) = 0; virtual void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, int state_value) = 0; - virtual void add_no_result_to_queue_with_set(std::shared_ptr component, - const std::string &state_value) = 0; + virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) = 0; virtual void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, const std::string &state_value) = 0; - virtual void add_addt_command_to_queue(std::shared_ptr component) = 0; + virtual void add_addt_command_to_queue(NextionComponentBase *component) = 0; - virtual void add_to_get_queue(std::shared_ptr component) = 0; + virtual void add_to_get_queue(NextionComponentBase *component) = 0; virtual void set_component_background_color(const char *component, Color color) = 0; virtual void set_component_pressed_background_color(const char *component, Color color) = 0; diff --git a/esphome/components/nextion/nextion_component_base.h b/esphome/components/nextion/nextion_component_base.h index 2725d5a30c..71ad803bc4 100644 --- a/esphome/components/nextion/nextion_component_base.h +++ b/esphome/components/nextion/nextion_component_base.h @@ -1,6 +1,5 @@ #pragma once #include -#include #include "esphome/core/defines.h" namespace esphome { @@ -23,7 +22,7 @@ class NextionComponentBase; class NextionQueue { public: virtual ~NextionQueue() = default; - std::shared_ptr component; + NextionComponentBase *component; uint32_t queue_time = 0; }; diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index cebdbec31a..cd1c073320 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -281,12 +281,14 @@ void Nextion::upload_tft() { #endif // NOLINTNEXTLINE(readability-static-accessed-through-instance) ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap()); - this->transfer_buffer_ = new (std::nothrow) uint8_t[chunk_size]; // NOLINT(cppcoreguidelines-owning-memory) - if (this->transfer_buffer_ == nullptr) { // Try a smaller size + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + this->transfer_buffer_ = new (std::nothrow) uint8_t[chunk_size]; + if (this->transfer_buffer_ == nullptr) { // Try a smaller size ESP_LOGD(TAG, "Could not allocate buffer size: %d trying 4096 instead", chunk_size); chunk_size = 4096; ESP_LOGD(TAG, "Allocating %d buffer", chunk_size); - this->transfer_buffer_ = new (std::nothrow) uint8_t[chunk_size]; // NOLINT(cppcoreguidelines-owning-memory) + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + this->transfer_buffer_ = new uint8_t[chunk_size]; if (!this->transfer_buffer_) this->upload_end_(); @@ -330,7 +332,8 @@ void Nextion::upload_end_() { WiFiClient *Nextion::get_wifi_client_() { if (this->tft_url_.compare(0, 6, "https:") == 0) { if (this->wifi_client_secure_ == nullptr) { - this->wifi_client_secure_ = new BearSSL::WiFiClientSecure(); // NOLINT(cppcoreguidelines-owning-memory) + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + this->wifi_client_secure_ = new BearSSL::WiFiClientSecure(); this->wifi_client_secure_->setInsecure(); this->wifi_client_secure_->setBufferSizes(512, 512); } @@ -338,7 +341,8 @@ WiFiClient *Nextion::get_wifi_client_() { } if (this->wifi_client_ == nullptr) { - this->wifi_client_ = new WiFiClient(); // NOLINT(cppcoreguidelines-owning-memory) + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + this->wifi_client_ = new WiFiClient(); } return this->wifi_client_; } diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp index e983ebcc6f..4b7532d32d 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.cpp +++ b/esphome/components/nextion/sensor/nextion_sensor.cpp @@ -34,7 +34,7 @@ void NextionSensor::update() { return; if (this->wave_chan_id_ == UINT8_MAX) { - this->nextion_->add_to_get_queue(shared_from_this()); + this->nextion_->add_to_get_queue(this); } else { if (this->send_last_value_) { this->add_to_wave_buffer(this->last_value_); @@ -62,9 +62,9 @@ void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) { double to_multiply = pow(10, this->precision_); int state_value = (int) (state * to_multiply); - this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), (int) state_value); + this->nextion_->add_no_result_to_queue_with_set(this, (int) state_value); } else { - this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), (int) state); + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); } } } @@ -103,7 +103,7 @@ void NextionSensor::wave_update_() { buffer_to_send, this->wave_buffer_.size(), this->component_id_, this->wave_chan_id_); #endif - this->nextion_->add_addt_command_to_queue(shared_from_this()); + this->nextion_->add_addt_command_to_queue(this); } } // namespace nextion diff --git a/esphome/components/nextion/sensor/nextion_sensor.h b/esphome/components/nextion/sensor/nextion_sensor.h index 068ff0451b..e4dde9a513 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.h +++ b/esphome/components/nextion/sensor/nextion_sensor.h @@ -8,10 +8,7 @@ namespace esphome { namespace nextion { class NextionSensor; -class NextionSensor : public NextionComponent, - public sensor::Sensor, - public PollingComponent, - public std::enable_shared_from_this { +class NextionSensor : public NextionComponent, public sensor::Sensor, public PollingComponent { public: NextionSensor(NextionBase *nextion) { this->nextion_ = nextion; } void send_state_to_nextion() override { this->set_state(this->state, false, true); }; diff --git a/esphome/components/nextion/switch/nextion_switch.cpp b/esphome/components/nextion/switch/nextion_switch.cpp index 0bd958e0d8..1f32ad3425 100644 --- a/esphome/components/nextion/switch/nextion_switch.cpp +++ b/esphome/components/nextion/switch/nextion_switch.cpp @@ -20,7 +20,7 @@ void NextionSwitch::process_bool(const std::string &variable_name, bool on) { void NextionSwitch::update() { if (!this->nextion_->is_setup()) return; - this->nextion_->add_to_get_queue(shared_from_this()); + this->nextion_->add_to_get_queue(this); } void NextionSwitch::set_state(bool state, bool publish, bool send_to_nextion) { @@ -32,7 +32,7 @@ void NextionSwitch::set_state(bool state, bool publish, bool send_to_nextion) { this->needs_to_send_update_ = true; } else { this->needs_to_send_update_ = false; - this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), (int) state); + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); } } if (publish) { diff --git a/esphome/components/nextion/switch/nextion_switch.h b/esphome/components/nextion/switch/nextion_switch.h index d7783e5c51..1548287473 100644 --- a/esphome/components/nextion/switch/nextion_switch.h +++ b/esphome/components/nextion/switch/nextion_switch.h @@ -8,10 +8,7 @@ namespace esphome { namespace nextion { class NextionSwitch; -class NextionSwitch : public NextionComponent, - public switch_::Switch, - public PollingComponent, - public std::enable_shared_from_this { +class NextionSwitch : public NextionComponent, public switch_::Switch, public PollingComponent { public: NextionSwitch(NextionBase *nextion) { this->nextion_ = nextion; } diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.cpp b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp index fa7cb35025..08f032df74 100644 --- a/esphome/components/nextion/text_sensor/nextion_textsensor.cpp +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp @@ -18,7 +18,7 @@ void NextionTextSensor::process_text(const std::string &variable_name, const std void NextionTextSensor::update() { if (!this->nextion_->is_setup()) return; - this->nextion_->add_to_get_queue(shared_from_this()); + this->nextion_->add_to_get_queue(this); } void NextionTextSensor::set_state(const std::string &state, bool publish, bool send_to_nextion) { @@ -29,7 +29,7 @@ void NextionTextSensor::set_state(const std::string &state, bool publish, bool s if (this->nextion_->is_sleeping() || !this->visible_) { this->needs_to_send_update_ = true; } else { - this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), state); + this->nextion_->add_no_result_to_queue_with_set(this, state); } } diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.h b/esphome/components/nextion/text_sensor/nextion_textsensor.h index 762797727d..5716d0a008 100644 --- a/esphome/components/nextion/text_sensor/nextion_textsensor.h +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.h @@ -8,10 +8,7 @@ namespace esphome { namespace nextion { class NextionTextSensor; -class NextionTextSensor : public NextionComponent, - public text_sensor::TextSensor, - public PollingComponent, - public std::enable_shared_from_this { +class NextionTextSensor : public NextionComponent, public text_sensor::TextSensor, public PollingComponent { public: NextionTextSensor(NextionBase *nextion) { this->nextion_ = nextion; } void update() override; From c51b5095014f7f303e5407ad48dee798b69469a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Oct 2021 22:59:46 +0200 Subject: [PATCH 1584/1841] Bump paho-mqtt from 1.5.1 to 1.6.0 (#2568) Bumps [paho-mqtt](https://github.com/eclipse/paho.mqtt.python) from 1.5.1 to 1.6.0. - [Release notes](https://github.com/eclipse/paho.mqtt.python/releases) - [Changelog](https://github.com/eclipse/paho.mqtt.python/blob/master/ChangeLog.txt) - [Commits](https://github.com/eclipse/paho.mqtt.python/commits) --- updated-dependencies: - dependency-name: paho-mqtt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b946019f18..7a2eeb58d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ voluptuous==0.12.2 PyYAML==6.0 -paho-mqtt==1.5.1 +paho-mqtt==1.6.0 colorama==0.4.4 tornado==6.1 tzlocal==3.0 # from time From 34606b0f1fb8fcb3b453d3f44c7121caeeeab4f3 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Thu, 21 Oct 2021 12:23:21 +0200 Subject: [PATCH 1585/1841] Fix MDNS for ESP8266 devices (#2571) Co-authored-by: Maurice Makaay Co-authored-by: Otto winter Co-authored-by: Maurice Makaay --- esphome/components/mdns/mdns_component.cpp | 6 +++--- esphome/components/mdns/mdns_component.h | 4 ++++ esphome/components/mdns/mdns_esp8266.cpp | 16 ++++++++++++++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 372d980eb0..631af9eba9 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -23,7 +23,7 @@ std::vector MDNSComponent::compile_services_() { #ifdef USE_API if (api::global_api_server != nullptr) { MDNSService service{}; - service.service_type = "esphomelib"; + service.service_type = "_esphomelib"; service.proto = "_tcp"; service.port = api::global_api_server->get_port(); service.txt_records.push_back({"version", ESPHOME_VERSION}); @@ -57,7 +57,7 @@ std::vector MDNSComponent::compile_services_() { #ifdef USE_PROMETHEUS { MDNSService service{}; - service.service_type = "prometheus-http"; + service.service_type = "_prometheus-http"; service.proto = "_tcp"; service.port = WEBSERVER_PORT; res.push_back(service); @@ -68,7 +68,7 @@ std::vector MDNSComponent::compile_services_() { // Publish "http" service if not using native API // This is just to have *some* mDNS service so that .local resolution works MDNSService service{}; - service.service_type = "http"; + service.service_type = "_http"; service.proto = "_tcp"; service.port = WEBSERVER_PORT; service.txt_records.push_back({"version", ESPHOME_VERSION}); diff --git a/esphome/components/mdns/mdns_component.h b/esphome/components/mdns/mdns_component.h index 985947d99c..679fb1a768 100644 --- a/esphome/components/mdns/mdns_component.h +++ b/esphome/components/mdns/mdns_component.h @@ -13,7 +13,11 @@ struct MDNSTXTRecord { }; struct MDNSService { + // service name _including_ underscore character prefix + // as defined in RFC6763 Section 7 std::string service_type; + // second label indicating protocol _including_ underscore character prefix + // as defined in RFC6763 Section 7, like "_tcp" or "_udp" std::string proto; uint16_t port; std::vector txt_records; diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp index 48f31f1bbf..1a73e4b5b3 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -17,9 +17,21 @@ void MDNSComponent::setup() { auto services = compile_services_(); for (const auto &service : services) { - MDNS.addService(service.service_type.c_str(), service.proto.c_str(), service.port); + // Strip the leading underscore from the proto and service_type. While it is + // part of the wire protocol to have an underscore, and for example ESP-IDF + // expects the underscore to be there, the ESP8266 implementation always adds + // the underscore itself. + auto proto = service.proto.c_str(); + while (*proto == '_') { + proto++; + } + auto service_type = service.service_type.c_str(); + while (*service_type == '_') { + service_type++; + } + MDNS.addService(service_type, proto, service.port); for (const auto &record : service.txt_records) { - MDNS.addServiceTxt(service.service_type.c_str(), service.proto.c_str(), record.key.c_str(), record.value.c_str()); + MDNS.addServiceTxt(service_type, proto, record.key.c_str(), record.value.c_str()); } } } From 7f34561e53b1146f47a4288bd2992aa073c7bb2d Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 12:23:37 +0200 Subject: [PATCH 1586/1841] Fix ESP8266 GPIO0 Pullup Validation (#2572) --- esphome/components/esp8266/gpio.py | 4 ++-- tests/test3.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp8266/gpio.py b/esphome/components/esp8266/gpio.py index 0ebfbd6f69..fa5c94dff5 100644 --- a/esphome/components/esp8266/gpio.py +++ b/esphome/components/esp8266/gpio.py @@ -107,9 +107,9 @@ def validate_supports(value): raise cv.Invalid( "Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN] ) - if is_pullup and num == 0: + if is_pullup and num == 16: raise cv.Invalid( - "GPIO Pin 0 does not support pullup pin mode. " + "GPIO Pin 16 does not support pullup pin mode. " "Please choose another pin.", [CONF_MODE, CONF_PULLUP], ) diff --git a/tests/test3.yaml b/tests/test3.yaml index 4c76967842..836e895374 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1150,7 +1150,7 @@ servo: ttp229_lsf: ttp229_bsf: - sdo_pin: D0 + sdo_pin: D2 scl_pin: D1 sim800l: From e39f314e7ab30756d23a269996c2f01885dd5f0e Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 12:24:01 +0200 Subject: [PATCH 1587/1841] Fix wifi ble coexistence check (#2573) --- esphome/components/wifi/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 19e4046711..faf3cca280 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -159,8 +159,15 @@ def final_validate_power_esp32_ble(value): "esp32_ble_server", "esp32_ble_tracker", ]: + if conflicting not in fv.full_config.get(): + continue + try: - cv.require_framework_version(esp32_arduino=cv.Version(1, 0, 5))(None) + # Only arduino 1.0.5+ and esp-idf impacted + cv.require_framework_version( + esp32_arduino=cv.Version(1, 0, 5), + esp_idf=cv.Version(4, 0, 0), + )(None) except cv.Invalid: pass else: From f41f7994a3e05cac5012e71744547fbb4e8e991a Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 14:20:23 +0200 Subject: [PATCH 1588/1841] Arduino global delay/millis/... symbols workaround (#2575) --- esphome/core/config.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/esphome/core/config.py b/esphome/core/config.py index c495fefddd..3c53d81784 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -206,6 +206,31 @@ def include_file(path, basename): cg.add_global(cg.RawStatement(f'#include "{basename}"')) +ARDUINO_GLUE_CODE = """\ +#define yield() esphome::yield() +#define millis() esphome::millis() +#define delay(x) esphome::delay(x) +#define delayMicroseconds(x) esphome::delayMicroseconds(x) +""" + + +@coroutine_with_priority(-999.0) +async def add_arduino_global_workaround(): + # The Arduino framework defined these itself in the global + # namespace. For the esphome codebase that is not a problem, + # but when custom code + # 1. writes `millis()` for example AND + # 2. has `using namespace esphome;` like our guides suggest + # Then the compiler will complain that the call is ambiguous + # Define a hacky macro so that the call is never ambiguous + # and always uses the esphome namespace one. + # See also https://github.com/esphome/issues/issues/2510 + # Priority -999 so that it runs before adding includes, as those + # also might reference these symbols + for line in ARDUINO_GLUE_CODE.splitlines(): + cg.add_global(cg.RawStatement(line)) + + @coroutine_with_priority(-1000.0) async def add_includes(includes): # Add includes at the very end, so that the included files can access global variables @@ -287,6 +312,9 @@ async def to_code(config): cg.add_build_flag("-Wno-unused-but-set-variable") cg.add_build_flag("-Wno-sign-compare") + if CORE.using_arduino: + CORE.add_job(add_arduino_global_workaround) + if config[CONF_INCLUDES]: CORE.add_job(add_includes, config[CONF_INCLUDES]) From 1caabb641927c787cc17aabe4d1ab7bbd6ac2249 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 14:20:57 +0200 Subject: [PATCH 1589/1841] Fix ESP8266 OTA adds unnecessary Update library (#2579) --- esphome/components/ota/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 53b282c43e..b3d3b7ad23 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -90,9 +90,7 @@ async def to_code(config): ) cg.add(RawExpression(f"if ({condition}) return")) - if CORE.is_esp8266: - cg.add_library("Update", None) - elif CORE.is_esp32 and CORE.using_arduino: + if CORE.is_esp32 and CORE.using_arduino: cg.add_library("Update", None) use_state_callback = False From c615dc573a223b7a20a363d868bf075d3a50ebfb Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 14:39:36 +0200 Subject: [PATCH 1590/1841] Fix ESP8266 dallas GPIO16 INPUT_PULLUP (#2581) --- esphome/components/esp8266/gpio.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index cb703c18e1..7805889b40 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -50,6 +50,13 @@ void ESP8266GPIOPin::pin_mode(gpio::Flags flags) { mode = OUTPUT; } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { mode = INPUT_PULLUP; + if (pin_ == 16) { + // GPIO16 doesn't have a pullup, so pinMode would fail. + // However, sometimes this method is called with pullup mode anyway + // for example from dallas one_wire. For those cases convert this + // to a INPUT mode. + mode = INPUT; + } } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { mode = INPUT_PULLDOWN_16; } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { From c248ba40438a3c1c83c5187ed80064db161072ad Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 14:53:08 +0200 Subject: [PATCH 1591/1841] Fix platformio version in Dockerfile doesn't match requirements (#2582) --- docker/Dockerfile | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index e66c3e1d95..7928ff8901 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -43,7 +43,7 @@ RUN \ # Ubuntu python3-pip is missing wheel pip3 install --no-cache-dir \ wheel==0.36.2 \ - platformio==5.2.0 \ + platformio==5.2.1 \ # Change some platformio settings && platformio settings set enable_telemetry No \ && platformio settings set check_libraries_interval 1000000 \ diff --git a/requirements.txt b/requirements.txt index 7a2eeb58d6..69058e108b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ tornado==6.1 tzlocal==3.0 # from time tzdata>=2021.1 # from time pyserial==3.5 -platformio==5.2.1 +platformio==5.2.1 # When updating platformio, also update Dockerfile esptool==3.1 click==8.0.3 esphome-dashboard==20211021.0 From 156104d5f5d83970970a15c1f2e663533495a150 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 15:29:32 +0200 Subject: [PATCH 1592/1841] Fix platformio_install_deps no longer installing all lib_deps (#2584) --- docker/platformio_install_deps.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docker/platformio_install_deps.py b/docker/platformio_install_deps.py index 5625bd4d01..c7b11cf321 100755 --- a/docker/platformio_install_deps.py +++ b/docker/platformio_install_deps.py @@ -8,6 +8,23 @@ import sys config = configparser.ConfigParser(inline_comment_prefixes=(';', )) config.read(sys.argv[1]) -libs = [x for x in config['common']['lib_deps'].splitlines() if len(x) != 0] + +libs = [] +# Extract from every lib_deps key in all sections +for section in config.sections(): + conf = config[section] + if "lib_deps" not in conf: + continue + for lib_dep in conf["lib_deps"].splitlines(): + if not lib_dep: + # Empty line or comment + continue + if lib_dep.startswith("${"): + # Extending from another section + continue + if "@" not in lib_dep: + # No version pinned, this is an internal lib + continue + libs.append(lib_dep) subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs]) From cac5b356db7deab24f5f66375b8ad4a6ee28d289 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 16:01:33 +0200 Subject: [PATCH 1593/1841] ESP32 ADC use factory calibration data (#2574) --- esphome/components/adc/adc_sensor.cpp | 224 +++++++++++--------------- esphome/components/adc/adc_sensor.h | 8 +- esphome/components/adc/sensor.py | 91 +++++++++-- 3 files changed, 177 insertions(+), 146 deletions(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 0c24c615f3..9b7d0437e1 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -16,50 +16,6 @@ namespace adc { static const char *const TAG = "adc"; -#ifdef USE_ESP32 -inline adc1_channel_t gpio_to_adc1(uint8_t pin) { -#if CONFIG_IDF_TARGET_ESP32 - switch (pin) { - case 36: - return ADC1_CHANNEL_0; - case 37: - return ADC1_CHANNEL_1; - case 38: - return ADC1_CHANNEL_2; - case 39: - return ADC1_CHANNEL_3; - case 32: - return ADC1_CHANNEL_4; - case 33: - return ADC1_CHANNEL_5; - case 34: - return ADC1_CHANNEL_6; - case 35: - return ADC1_CHANNEL_7; - default: - return ADC1_CHANNEL_MAX; - } -#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 - switch (pin) { - case 0: - return ADC1_CHANNEL_0; - case 1: - return ADC1_CHANNEL_1; - case 2: - return ADC1_CHANNEL_2; - case 3: - return ADC1_CHANNEL_3; - case 4: - return ADC1_CHANNEL_4; - default: - return ADC1_CHANNEL_MAX; - } -#endif -} -void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } -void ADCSensor::set_autorange(bool autorange) { this->autorange_ = autorange; } -#endif - void ADCSensor::setup() { ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); #ifndef USE_ADC_SENSOR_VCC @@ -67,15 +23,36 @@ void ADCSensor::setup() { #endif #ifdef USE_ESP32 - if (this->autorange_) - this->attenuation_ = ADC_ATTEN_DB_11; - adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), attenuation_); adc1_config_width(ADC_WIDTH_BIT_12); -#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 - adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_->get_pin())); -#endif + if (!autorange_) { + adc1_config_channel_atten(channel_, attenuation_); + } + + // load characteristics for each attenuation + for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) { + auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_BIT_12, + 1100, // default vref + &cal_characteristics_[i]); + switch (cal_value) { + case ESP_ADC_CAL_VAL_EFUSE_VREF: + ESP_LOGV(TAG, "Using eFuse Vref for calibration"); + break; + case ESP_ADC_CAL_VAL_EFUSE_TP: + ESP_LOGV(TAG, "Using two-point eFuse Vref for calibration"); + break; + case ESP_ADC_CAL_VAL_DEFAULT_VREF: + default: + break; + } + } + + // adc_gpio_init doesn't exist on ESP32-C3 or ESP32-H2 +#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32H2) + adc_gpio_init(ADC_UNIT_1, (adc_channel_t) channel_); #endif +#endif // USE_ESP32 } + void ADCSensor::dump_config() { LOG_SENSOR("", "ADC Sensor", this); #ifdef USE_ESP8266 @@ -84,7 +61,8 @@ void ADCSensor::dump_config() { #else LOG_PIN(" Pin: ", pin_); #endif -#endif +#endif // USE_ESP8266 + #ifdef USE_ESP32 LOG_PIN(" Pin: ", pin_); if (autorange_) @@ -106,101 +84,81 @@ void ADCSensor::dump_config() { default: // This is to satisfy the unused ADC_ATTEN_MAX break; } -#endif +#endif // USE_ESP32 LOG_UPDATE_INTERVAL(this); } + float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } void ADCSensor::update() { float value_v = this->sample(); ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v); this->publish_state(value_v); } -uint16_t ADCSensor::read_raw_() { -#ifdef USE_ESP32 - return adc1_get_raw(gpio_to_adc1(pin_->get_pin())); -#endif #ifdef USE_ESP8266 -#ifdef USE_ADC_SENSOR_VCC - return ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance) -#else - return analogRead(this->pin_->get_pin()); // NOLINT -#endif -#endif -} -uint32_t ADCSensor::raw_to_microvolts_(uint16_t raw) { -#ifdef USE_ESP32 -#if CONFIG_IDF_TARGET_ESP32 - switch (this->attenuation_) { - case ADC_ATTEN_DB_0: - return raw * 269; // 1e6 * 1.1 / 4095 - case ADC_ATTEN_DB_2_5: - return raw * 366; // 1e6 * 1.5 / 4095 - case ADC_ATTEN_DB_6: - return raw * 537; // 1e6 * 2.2 / 4095 - case ADC_ATTEN_DB_11: - return raw * 952; // 1e6 * 3.9 / 4095 - default: // This is to satisfy the unused ADC_ATTEN_MAX - return raw * 244; // 1e6 * 1.0 / 4095 - } -#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 - switch (this->attenuation_) { - case ADC_ATTEN_DB_0: - return raw * 205; // 1e6 * 0.84 / 4095 - case ADC_ATTEN_DB_2_5: - return raw * 276; // 1e6 * 1.13 / 4095 - case ADC_ATTEN_DB_6: - return raw * 381; // 1e6 * 1.56 / 4095 - case ADC_ATTEN_DB_11: - return raw * 733; // 1e6 * 3.0 / 4095 - default: // This is to satisfy the unused ADC_ATTEN_MAX - return raw * 244; // 1e6 * 1.0 / 4095 - } -#endif -#endif - -#ifdef USE_ESP8266 - return raw * 977; // 1e6 / 1024 -#endif -} float ADCSensor::sample() { - int raw = this->read_raw_(); - uint32_t v = this->raw_to_microvolts_(raw); -#ifdef USE_ESP32 - if (autorange_) { - int raw11 = raw, raw6 = 4095, raw2 = 4095, raw0 = 4095; - uint32_t v11 = v, v6 = 0, v2 = 0, v0 = 0; - if (raw11 < 4095) { // Progressively read all attenuation ranges - adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_6); - raw6 = this->read_raw_(); - v6 = this->raw_to_microvolts_(raw6); - if (raw6 < 4095) { - adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_2_5); - raw2 = this->read_raw_(); - v2 = this->raw_to_microvolts_(raw2); - if (raw2 < 4095) { - adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_0); - raw0 = this->read_raw_(); - v0 = this->raw_to_microvolts_(raw0); - } - } - adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_11); - } // Contribution coefficients (normalized to 2048) - uint16_t c11 = clamp(raw11, 0, 2048); // high 1, middle 1, low 0 - uint16_t c6 = (2048 - abs(raw6 - 2048)); // high 0, middle 1, low 0 - uint16_t c2 = (2048 - abs(raw2 - 2048)); // high 0, middle 1, low 0 - uint16_t c0 = clamp(4095 - raw0, 0, 2048); // high 0, middle 1, low 1 - uint32_t csum = c11 + c6 + c2 + c0; // sum to normalize the final result - if (csum > 0) - v = (v11 * c11) + (v6 * c6) + (v2 * c2) + (v0 * c0); - else // in case of error, this keeps the 11db output (v) - csum = 1; - csum *= 1e6; // include the 1e6 microvolts->volts conversion factor - return (float) v / (float) csum; // normalize, convert & return - } +#ifdef USE_ADC_SENSOR_VCC + return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance) +#else + return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT #endif - return v / (float) 1e6; // convert from microvolts to volts } +#endif + +#ifdef USE_ESP32 +float ADCSensor::sample() { + if (!autorange_) { + int raw = adc1_get_raw(channel_); + if (raw == -1) { + return NAN; + } + uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]); + return mv / 1000.0f; + } + + int raw11, raw6 = 4095, raw2 = 4095, raw0 = 4095; + adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11); + raw11 = adc1_get_raw(channel_); + if (raw11 < 4095) { + adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6); + raw6 = adc1_get_raw(channel_); + if (raw6 < 4095) { + adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5); + raw2 = adc1_get_raw(channel_); + if (raw2 < 4095) { + adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0); + raw0 = adc1_get_raw(channel_); + } + } + } + + if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) { + return NAN; + } + // prevent divide by zero + if (raw0 == 0 && raw2 == 0 && raw6 == 0 && raw11 == 0) { + return 0; + } + + uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]); + uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]); + uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]); + uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]); + + // Contribution of each value, in range 0-2048 + uint32_t c11 = std::min(raw11, 2048); + uint32_t c6 = 2048 - std::abs(raw6 - 2048); + uint32_t c2 = 2048 - std::abs(raw2 - 2048); + uint32_t c0 = std::min(4095 - raw0, 2048); + // max theoretical csum value is 2048*4 = 8192 + uint32_t csum = c11 + c6 + c2 + c0; + + // each mv is max 3900; so max value is 3900*2048*4, fits in unsigned + uint32_t mv_scaled = (mv11 * c11) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0); + return mv_scaled / (float) (csum * 1000U); +} +#endif // USE_ESP32 + #ifdef USE_ESP8266 std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } #endif diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index fafb0d5ca0..9984c72819 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -8,6 +8,7 @@ #ifdef USE_ESP32 #include "driver/adc.h" +#include #endif namespace esphome { @@ -17,8 +18,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage public: #ifdef USE_ESP32 /// Set the attenuation for this pin. Only available on the ESP32. - void set_attenuation(adc_atten_t attenuation); - void set_autorange(bool autorange); + void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; } + void set_channel(adc1_channel_t channel) { channel_ = channel; } + void set_autorange(bool autorange) { autorange_ = autorange; } #endif /// Update adc values. @@ -42,7 +44,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage #ifdef USE_ESP32 adc_atten_t attenuation_{ADC_ATTEN_DB_0}; + adc1_channel_t channel_{}; bool autorange_{false}; + esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {}; #endif }; diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 0265f52d31..9fdddaa0a6 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -6,12 +6,21 @@ from esphome.const import ( CONF_ATTENUATION, CONF_ID, CONF_INPUT, + CONF_NUMBER, CONF_PIN, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT, UNIT_VOLT, ) from esphome.core import CORE +from esphome.components.esp32 import get_esp32_variant +from esphome.components.esp32.const import ( + VARIANT_ESP32, + VARIANT_ESP32C3, + VARIANT_ESP32H2, + VARIANT_ESP32S2, + VARIANT_ESP32S3, +) AUTO_LOAD = ["voltage_sampler"] @@ -24,21 +33,77 @@ ATTENUATION_MODES = { "auto": "auto", } +adc1_channel_t = cg.global_ns.enum("adc1_channel_t") + +# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h +# pin to adc1 channel mapping +ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { + VARIANT_ESP32: { + 36: adc1_channel_t.ADC1_CHANNEL_0, + 37: adc1_channel_t.ADC1_CHANNEL_1, + 38: adc1_channel_t.ADC1_CHANNEL_2, + 39: adc1_channel_t.ADC1_CHANNEL_3, + 32: adc1_channel_t.ADC1_CHANNEL_4, + 33: adc1_channel_t.ADC1_CHANNEL_5, + 34: adc1_channel_t.ADC1_CHANNEL_6, + 35: adc1_channel_t.ADC1_CHANNEL_7, + }, + VARIANT_ESP32S2: { + 1: adc1_channel_t.ADC1_CHANNEL_0, + 2: adc1_channel_t.ADC1_CHANNEL_1, + 3: adc1_channel_t.ADC1_CHANNEL_2, + 4: adc1_channel_t.ADC1_CHANNEL_3, + 5: adc1_channel_t.ADC1_CHANNEL_4, + 6: adc1_channel_t.ADC1_CHANNEL_5, + 7: adc1_channel_t.ADC1_CHANNEL_6, + 8: adc1_channel_t.ADC1_CHANNEL_7, + 9: adc1_channel_t.ADC1_CHANNEL_8, + 10: adc1_channel_t.ADC1_CHANNEL_9, + }, + VARIANT_ESP32S3: { + 1: adc1_channel_t.ADC1_CHANNEL_0, + 2: adc1_channel_t.ADC1_CHANNEL_1, + 3: adc1_channel_t.ADC1_CHANNEL_2, + 4: adc1_channel_t.ADC1_CHANNEL_3, + 5: adc1_channel_t.ADC1_CHANNEL_4, + 6: adc1_channel_t.ADC1_CHANNEL_5, + 7: adc1_channel_t.ADC1_CHANNEL_6, + 8: adc1_channel_t.ADC1_CHANNEL_7, + 9: adc1_channel_t.ADC1_CHANNEL_8, + 10: adc1_channel_t.ADC1_CHANNEL_9, + }, + VARIANT_ESP32C3: { + 0: adc1_channel_t.ADC1_CHANNEL_0, + 1: adc1_channel_t.ADC1_CHANNEL_1, + 2: adc1_channel_t.ADC1_CHANNEL_2, + 3: adc1_channel_t.ADC1_CHANNEL_3, + 4: adc1_channel_t.ADC1_CHANNEL_4, + }, + VARIANT_ESP32H2: { + 0: adc1_channel_t.ADC1_CHANNEL_0, + 1: adc1_channel_t.ADC1_CHANNEL_1, + 2: adc1_channel_t.ADC1_CHANNEL_2, + 3: adc1_channel_t.ADC1_CHANNEL_3, + 4: adc1_channel_t.ADC1_CHANNEL_4, + }, +} + def validate_adc_pin(value): if str(value).upper() == "VCC": return cv.only_on_esp8266("VCC") if CORE.is_esp32: - from esphome.components.esp32 import is_esp32c3 - value = pins.internal_gpio_input_pin_number(value) - if is_esp32c3(): - if not (0 <= value <= 4): # ADC1 - raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.") - elif not (32 <= value <= 39): # ADC1 - raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.") - elif CORE.is_esp8266: + variant = get_esp32_variant() + if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL: + raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported") + + if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]: + raise cv.Invalid(f"{variant} doesn't support ADC on this pin") + return pins.internal_gpio_input_pin_schema(value) + + if CORE.is_esp8266: from esphome.components.esp8266.gpio import CONF_ANALOG value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})( @@ -50,10 +115,8 @@ def validate_adc_pin(value): return pins.gpio_pin_schema( {CONF_ANALOG: True, CONF_INPUT: True}, internal=True )(value) - else: - raise NotImplementedError - return pins.internal_gpio_input_pin_schema(value) + raise NotImplementedError adc_ns = cg.esphome_ns.namespace("adc") @@ -97,3 +160,9 @@ async def to_code(config): cg.add(var.set_autorange(cg.global_ns.true)) else: cg.add(var.set_attenuation(config[CONF_ATTENUATION])) + + if CORE.is_esp32: + variant = get_esp32_variant() + pin_num = config[CONF_PIN][CONF_NUMBER] + chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num] + cg.add(var.set_channel(chan)) From f2ebfe7aef849b3d39c43a24566a03b64e35e7b5 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Thu, 21 Oct 2021 16:02:28 +0200 Subject: [PATCH 1594/1841] Add mDNS config dump (#2576) --- esphome/components/mdns/mdns_component.cpp | 29 ++++++++++++++----- esphome/components/mdns/mdns_component.h | 6 ++-- .../components/mdns/mdns_esp32_arduino.cpp | 9 +++--- esphome/components/mdns/mdns_esp8266.cpp | 11 ++++--- esphome/components/mdns/mdns_esp_idf.cpp | 11 +++---- 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 631af9eba9..13f65edfc4 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -13,13 +13,16 @@ namespace esphome { namespace mdns { +static const char *const TAG = "mdns"; + #ifndef WEBSERVER_PORT #define WEBSERVER_PORT 80 // NOLINT #endif -std::vector MDNSComponent::compile_services_() { - std::vector res; +void MDNSComponent::compile_records_() { + this->hostname_ = App.get_name(); + this->services_.clear(); #ifdef USE_API if (api::global_api_server != nullptr) { MDNSService service{}; @@ -50,7 +53,7 @@ std::vector MDNSComponent::compile_services_() { service.txt_records.push_back({"package_import_url", dashboard_import::get_package_import_url()}); #endif - res.push_back(service); + this->services_.push_back(service); } #endif // USE_API @@ -60,11 +63,11 @@ std::vector MDNSComponent::compile_services_() { service.service_type = "_prometheus-http"; service.proto = "_tcp"; service.port = WEBSERVER_PORT; - res.push_back(service); + this->services_.push_back(service); } #endif - if (res.empty()) { + if (this->services_.empty()) { // Publish "http" service if not using native API // This is just to have *some* mDNS service so that .local resolution works MDNSService service{}; @@ -72,11 +75,21 @@ std::vector MDNSComponent::compile_services_() { service.proto = "_tcp"; service.port = WEBSERVER_PORT; service.txt_records.push_back({"version", ESPHOME_VERSION}); - res.push_back(service); + this->services_.push_back(service); + } +} + +void MDNSComponent::dump_config() { + ESP_LOGCONFIG(TAG, "mDNS:"); + ESP_LOGCONFIG(TAG, " Hostname: %s", this->hostname_.c_str()); + ESP_LOGV(TAG, " Services:"); + for (const auto &service : this->services_) { + ESP_LOGV(TAG, " - %s, %s, %d", service.service_type.c_str(), service.proto.c_str(), service.port); + for (const auto &record : service.txt_records) { + ESP_LOGV(TAG, " TXT: %s = %s", record.key.c_str(), record.value.c_str()); + } } - return res; } -std::string MDNSComponent::compile_hostname_() { return App.get_name(); } } // namespace mdns } // namespace esphome diff --git a/esphome/components/mdns/mdns_component.h b/esphome/components/mdns/mdns_component.h index 679fb1a768..45614d509a 100644 --- a/esphome/components/mdns/mdns_component.h +++ b/esphome/components/mdns/mdns_component.h @@ -26,6 +26,7 @@ struct MDNSService { class MDNSComponent : public Component { public: void setup() override; + void dump_config() override; #if defined(USE_ESP8266) && defined(USE_ARDUINO) void loop() override; @@ -33,8 +34,9 @@ class MDNSComponent : public Component { float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } protected: - std::vector compile_services_(); - std::string compile_hostname_(); + std::vector services_{}; + std::string hostname_; + void compile_records_(); }; } // namespace mdns diff --git a/esphome/components/mdns/mdns_esp32_arduino.cpp b/esphome/components/mdns/mdns_esp32_arduino.cpp index 4d13b7321a..6a66beef92 100644 --- a/esphome/components/mdns/mdns_esp32_arduino.cpp +++ b/esphome/components/mdns/mdns_esp32_arduino.cpp @@ -7,13 +7,12 @@ namespace esphome { namespace mdns { -static const char *const TAG = "mdns"; - void MDNSComponent::setup() { - MDNS.begin(compile_hostname_().c_str()); + this->compile_records_(); - auto services = compile_services_(); - for (const auto &service : services) { + MDNS.begin(this->hostname_.c_str()); + + for (const auto &service : this->services_) { MDNS.addService(service.service_type.c_str(), service.proto.c_str(), service.port); for (const auto &record : service.txt_records) { MDNS.addServiceTxt(service.service_type.c_str(), service.proto.c_str(), record.key.c_str(), record.value.c_str()); diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp index 1a73e4b5b3..ff305f907a 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -9,14 +9,13 @@ namespace esphome { namespace mdns { -static const char *const TAG = "mdns"; - void MDNSComponent::setup() { - network::IPAddress addr = network::get_ip_address(); - MDNS.begin(compile_hostname_().c_str(), (uint32_t) addr); + this->compile_records_(); - auto services = compile_services_(); - for (const auto &service : services) { + network::IPAddress addr = network::get_ip_address(); + MDNS.begin(this->hostname_.c_str(), (uint32_t) addr); + + for (const auto &service : this->services_) { // Strip the leading underscore from the proto and service_type. While it is // part of the wire protocol to have an underscore, and for example ESP-IDF // expects the underscore to be there, the ESP8266 implementation always adds diff --git a/esphome/components/mdns/mdns_esp_idf.cpp b/esphome/components/mdns/mdns_esp_idf.cpp index 17874f1ffe..40d9f1d5f3 100644 --- a/esphome/components/mdns/mdns_esp_idf.cpp +++ b/esphome/components/mdns/mdns_esp_idf.cpp @@ -11,18 +11,19 @@ namespace mdns { static const char *const TAG = "mdns"; void MDNSComponent::setup() { + this->compile_records_(); + esp_err_t err = mdns_init(); if (err != ESP_OK) { - ESP_LOGW(TAG, "MDNS init failed: %s", esp_err_to_name(err)); + ESP_LOGW(TAG, "mDNS init failed: %s", esp_err_to_name(err)); this->mark_failed(); return; } - mdns_hostname_set(compile_hostname_().c_str()); - mdns_instance_name_set(compile_hostname_().c_str()); + mdns_hostname_set(this->hostname_.c_str()); + mdns_instance_name_set(this->hostname_.c_str()); - auto services = compile_services_(); - for (const auto &service : services) { + for (const auto &service : this->services_) { std::vector txt_records; for (const auto &record : service.txt_records) { mdns_txt_item_t it{}; From eccdef82116b8878541ca96555b6f64857012e1c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 18:53:08 +0200 Subject: [PATCH 1595/1841] Fix mDNS ESP8266 log not included (#2589) --- esphome/components/mdns/mdns_component.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 13f65edfc4..915c640b06 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" #include "esphome/core/version.h" #include "esphome/core/application.h" +#include "esphome/core/log.h" #ifdef USE_API #include "esphome/components/api/api_server.h" From ca59dd13022ac9b217baf86046213dbd51cd6fb6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 18:57:03 +0200 Subject: [PATCH 1596/1841] Fix HeatpumpIR pin (#2585) --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 9cc7477d51..6a8b342314 100644 --- a/platformio.ini +++ b/platformio.ini @@ -49,7 +49,7 @@ lib_deps = glmnet/Dsmr@0.5 ; dsmr rweather/Crypto@0.2.0 ; dsmr dudanov/MideaUART@1.1.8 ; midea - tonia/HeatpumpIR@^1.0.15 ; heatpumpir + tonia/HeatpumpIR@1.0.15 ; heatpumpir build_flags = ${common.build_flags} -DUSE_ARDUINO From 8735d3b83e41b5b72084290db3bc1803680e9019 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 21 Oct 2021 18:59:49 +0200 Subject: [PATCH 1597/1841] Fix PlatformIO version for latest Arduino framework (#2590) --- esphome/components/esp8266/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index a5323db8bf..ddaeee6ab7 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -65,7 +65,7 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 7, 4) # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif8266 ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 2) # for arduino 3 framework versions -ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 0, 2) +ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 2, 0) def _arduino_check_versions(value): From c0fc5b48aec6c99e9e95bb74f050c54f54a9fe68 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 21 Oct 2021 19:55:19 +0200 Subject: [PATCH 1598/1841] Fix pin/component switchup in SX1509 pin configuration (#2593) --- esphome/components/sx1509/__init__.py | 4 ++-- esphome/core/helpers.cpp | 1 + tests/test4.yaml | 10 ++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/esphome/components/sx1509/__init__.py b/esphome/components/sx1509/__init__.py index f1b7d5f424..879ced2fb3 100644 --- a/esphome/components/sx1509/__init__.py +++ b/esphome/components/sx1509/__init__.py @@ -80,8 +80,8 @@ def validate_mode(value): CONF_SX1509 = "sx1509" SX1509_PIN_SCHEMA = cv.All( { - cv.GenerateID(): cv.declare_id(SX1509Component), - cv.Required(CONF_SX1509): cv.use_id(SX1509GPIOPin), + cv.GenerateID(): cv.declare_id(SX1509GPIOPin), + cv.Required(CONF_SX1509): cv.use_id(SX1509Component), cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15), cv.Optional(CONF_MODE, default={}): cv.All( { diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 780df3ca6d..bc97259a71 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -358,6 +358,7 @@ template T clamp(const T val, const T min, const T max) { return max; return val; } +template uint8_t clamp(uint8_t, uint8_t, uint8_t); template float clamp(float, float, float); template int clamp(int, int, int); diff --git a/tests/test4.yaml b/tests/test4.yaml index 4f2025ad74..bc249c5ecb 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -59,6 +59,10 @@ tuya: pipsolar: id: inverter0 +sx1509: + - id: sx1509_hub + address: 0x3E + sensor: - platform: homeassistant entity_id: sensor.hello_world @@ -209,6 +213,7 @@ sensor: - or: - throttle: "20min" - delta: 0.02 + # # platform sensor.apds9960 requires component apds9960 # @@ -308,6 +313,11 @@ binary_sensor: y_max: 212 on_state: - lambda: 'ESP_LOGI("main", "key0: %s", (x ? "touch" : "release"));' + - platform: gpio + name: GPIO SX1509 test + pin: + sx1509: sx1509_hub + number: 3 climate: - platform: tuya From 27d7d7ca699bf4a8008d9a36e22f956e16c1870b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 21 Oct 2021 19:56:47 +0200 Subject: [PATCH 1599/1841] Fix old-style `arduino_version` on ESP8266 and with magic values (#2591) --- esphome/const.py | 5 ++++- esphome/core/config.py | 9 +++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/esphome/const.py b/esphome/const.py index 4f7d95d694..e00eebb1a4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,10 @@ __version__ = "2021.11.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" -TARGET_PLATFORMS = ["esp32", "esp8266"] +PLATFORM_ESP32 = "esp32" +PLATFORM_ESP8266 = "esp8266" + +TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266] TARGET_FRAMEWORKS = ["arduino", "esp-idf"] # See also https://github.com/platformio/platform-espressif8266/releases diff --git a/esphome/core/config.py b/esphome/core/config.py index 3c53d81784..235e0eeb84 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -29,6 +29,7 @@ from esphome.const import ( CONF_VERSION, KEY_CORE, TARGET_PLATFORMS, + PLATFORM_ESP8266, ) from esphome.core import CORE, coroutine_with_priority from esphome.helpers import copy_file_if_changed, walk_files @@ -182,9 +183,13 @@ def preload_core_config(config, result): if CONF_BOARD_FLASH_MODE in conf: plat_conf[CONF_BOARD_FLASH_MODE] = conf.pop(CONF_BOARD_FLASH_MODE) if CONF_ARDUINO_VERSION in conf: - plat_conf[CONF_FRAMEWORK] = {CONF_TYPE: "arduino"} + plat_conf[CONF_FRAMEWORK] = {} + if plat != PLATFORM_ESP8266: + plat_conf[CONF_FRAMEWORK][CONF_TYPE] = "arduino" + try: - cv.Version.parse(conf[CONF_ARDUINO_VERSION]) + if conf[CONF_ARDUINO_VERSION] not in ("recommended", "latest", "dev"): + cv.Version.parse(conf[CONF_ARDUINO_VERSION]) plat_conf[CONF_FRAMEWORK][CONF_VERSION] = conf.pop(CONF_ARDUINO_VERSION) except ValueError: plat_conf[CONF_FRAMEWORK][CONF_SOURCE] = conf.pop(CONF_ARDUINO_VERSION) From f1f2640d0e752c053dc7cf26810ec8922d1985be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 20:01:03 +0200 Subject: [PATCH 1600/1841] Bump esphome-dashboard from 20211021.0 to 20211021.1 (#2594) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 69058e108b..9a64272df5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 # When updating platformio, also update Dockerfile esptool==3.1 click==8.0.3 -esphome-dashboard==20211021.0 +esphome-dashboard==20211021.1 aioesphomeapi==10.0.0 # esp-idf requires this, but doesn't bundle it by default From f408f074c49c7127892640c3bcb919896e5000ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 20:01:27 +0200 Subject: [PATCH 1601/1841] Bump platformio from 5.2.1 to 5.2.2 (#2569) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Oxan van Leeuwen --- docker/Dockerfile | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 7928ff8901..7c4a6a270a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -43,7 +43,7 @@ RUN \ # Ubuntu python3-pip is missing wheel pip3 install --no-cache-dir \ wheel==0.36.2 \ - platformio==5.2.1 \ + platformio==5.2.2 \ # Change some platformio settings && platformio settings set enable_telemetry No \ && platformio settings set check_libraries_interval 1000000 \ diff --git a/requirements.txt b/requirements.txt index 9a64272df5..1eb76e51e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ tornado==6.1 tzlocal==3.0 # from time tzdata>=2021.1 # from time pyserial==3.5 -platformio==5.2.1 # When updating platformio, also update Dockerfile +platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.1 click==8.0.3 esphome-dashboard==20211021.1 From 07a9cb910fd13e98e0e5cb67f9590db163d74db4 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 21 Oct 2021 20:07:37 +0200 Subject: [PATCH 1602/1841] Fix validation of addressable light IDs (#2588) --- esphome/components/light/addressable_light.h | 6 ++++++ esphome/components/light/types.py | 5 ++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index fea7508515..2b2c0ca7e4 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -22,6 +22,12 @@ using ESPColor ESPDEPRECATED("esphome::light::ESPColor is deprecated, use esphom /// Convert the color information from a `LightColorValues` object to a `Color` object (does not apply brightness). Color color_from_light_color_values(LightColorValues val); +/// Use a custom state class for addressable lights, to allow type system to discriminate between addressable and +/// non-addressable lights. +class AddressableLightState : public LightState { + using LightState::LightState; +}; + class AddressableLight : public LightOutput, public Component { public: virtual int32_t size() const = 0; diff --git a/esphome/components/light/types.py b/esphome/components/light/types.py index cf544e5435..bc20cd5555 100644 --- a/esphome/components/light/types.py +++ b/esphome/components/light/types.py @@ -4,10 +4,9 @@ from esphome import automation # Base light_ns = cg.esphome_ns.namespace("light") LightState = light_ns.class_("LightState", cg.EntityBase, cg.Component) -# Fake class for addressable lights -AddressableLightState = light_ns.class_("LightState", LightState) +AddressableLightState = light_ns.class_("AddressableLightState", LightState) LightOutput = light_ns.class_("LightOutput") -AddressableLight = light_ns.class_("AddressableLight", cg.Component) +AddressableLight = light_ns.class_("AddressableLight", LightOutput, cg.Component) AddressableLightRef = AddressableLight.operator("ref") Color = cg.esphome_ns.class_("Color") From 4765173778a7fde84bd77449f49174b72729fdc8 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 20:07:44 +0200 Subject: [PATCH 1603/1841] Update docker base images (#2583) --- docker/Dockerfile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 7c4a6a270a..0d30bb0267 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,12 +5,12 @@ # One of "docker", "hassio" ARG BASEIMGTYPE=docker -FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.0 AS base-hassio-amd64 -FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.0 AS base-hassio-arm64 -FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.0 AS base-hassio-armv7 -FROM debian:bullseye-20210902-slim AS base-docker-amd64 -FROM debian:bullseye-20210902-slim AS base-docker-arm64 -FROM debian:bullseye-20210902-slim AS base-docker-armv7 +FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.1 AS base-hassio-amd64 +FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.1 AS base-hassio-arm64 +FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.1 AS base-hassio-armv7 +FROM debian:bullseye-20211011-slim AS base-docker-amd64 +FROM debian:bullseye-20211011-slim AS base-docker-arm64 +FROM debian:bullseye-20211011-slim AS base-docker-armv7 # Use TARGETARCH/TARGETVARIANT defined by docker # https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope From 5389382798129f7ae78d7074f89b1f3a86c2f2ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 20:09:29 +0200 Subject: [PATCH 1604/1841] Bump paho-mqtt from 1.6.0 to 1.6.1 (#2596) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1eb76e51e0..3b6378c06f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ voluptuous==0.12.2 PyYAML==6.0 -paho-mqtt==1.6.0 +paho-mqtt==1.6.1 colorama==0.4.4 tornado==6.1 tzlocal==3.0 # from time From a88c0224065513176eeeac85f07bf3297df33809 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 22 Oct 2021 07:53:06 +1300 Subject: [PATCH 1605/1841] Logging a proper url allows terminals to make it clickable (#2554) --- esphome/dashboard/dashboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 63378a38b5..084dd84a07 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -963,7 +963,7 @@ def start_web_server(args): server.add_socket(socket) else: _LOGGER.info( - "Starting dashboard web server on port %s and configuration dir %s...", + "Starting dashboard web server on http://0.0.0.0:%s and configuration dir %s...", args.port, settings.config_dir, ) From b141aea4c0936e7983ac31c5cd80c34569142420 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 20:54:12 +0200 Subject: [PATCH 1606/1841] Bump aioesphomeapi from 10.0.0 to 10.0.3 (#2595) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3b6378c06f..fa95f2156c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.1 click==8.0.3 esphome-dashboard==20211021.1 -aioesphomeapi==10.0.0 +aioesphomeapi==10.0.3 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 1468acfced9e43e3201bec46e4819630e5cbbc1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 22:46:05 +0200 Subject: [PATCH 1607/1841] Bump tzlocal from 3.0 to 4.0.1 (#2553) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Oxan van Leeuwen --- esphome/components/time/__init__.py | 8 ++------ requirements.txt | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index 5c2155d764..2d73d0aef9 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -1,7 +1,6 @@ import logging from importlib import resources from typing import Optional -from datetime import timezone import tzlocal @@ -66,14 +65,11 @@ def _extract_tz_string(tzfile: bytes) -> str: def detect_tz() -> str: - localzone = tzlocal.get_localzone() - if localzone is timezone.utc: - return "UTC0" - if not hasattr(localzone, "key"): + iana_key = tzlocal.get_localzone_name() + if iana_key is None: raise cv.Invalid( "Could not automatically determine timezone, please set timezone manually." ) - iana_key = localzone.key _LOGGER.info("Detected timezone '%s'", iana_key) tzfile = _load_tzdata(iana_key) if tzfile is None: diff --git a/requirements.txt b/requirements.txt index fa95f2156c..3ed53d0c90 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ PyYAML==6.0 paho-mqtt==1.6.1 colorama==0.4.4 tornado==6.1 -tzlocal==3.0 # from time +tzlocal==4.0.1 # from time tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile From 68c854706752f387d51caf5adbf2d9091cf04b58 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 22:48:28 +0200 Subject: [PATCH 1608/1841] Add IDF support to dallas (#2578) --- esphome/components/dallas/__init__.py | 26 ++--- .../components/dallas/dallas_component.cpp | 66 +++++------ esphome/components/dallas/dallas_component.h | 4 +- esphome/components/dallas/esp_one_wire.cpp | 103 ++++++++++-------- esphome/components/dallas/esp_one_wire.h | 7 +- esphome/components/esp32/gpio_arduino.cpp | 37 ++++--- esphome/components/esp32/gpio_idf.cpp | 87 +++++++++------ esphome/components/esp32/gpio_idf.h | 3 +- esphome/components/esp8266/gpio.cpp | 50 +++++---- esphome/core/gpio.h | 1 + 10 files changed, 201 insertions(+), 183 deletions(-) diff --git a/esphome/components/dallas/__init__.py b/esphome/components/dallas/__init__.py index 2dbc69b8e2..0f71399a7c 100644 --- a/esphome/components/dallas/__init__.py +++ b/esphome/components/dallas/__init__.py @@ -6,26 +6,20 @@ from esphome.const import CONF_ID, CONF_PIN MULTI_CONF = True AUTO_LOAD = ["sensor"] -CONF_ONE_WIRE_ID = "one_wire_id" dallas_ns = cg.esphome_ns.namespace("dallas") DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent) -ESPOneWire = dallas_ns.class_("ESPOneWire") -CONFIG_SCHEMA = cv.All( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(DallasComponent), - cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire), - cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, - } - ).extend(cv.polling_component_schema("60s")), - # pin_mode call logs in esp-idf, but InterruptLock is active -> crash - cv.only_with_arduino, -) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(DallasComponent), + cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, + } +).extend(cv.polling_component_schema("60s")) async def to_code(config): - pin = await cg.gpio_pin_expression(config[CONF_PIN]) - one_wire = cg.new_Pvariable(config[CONF_ONE_WIRE_ID], pin) - var = cg.new_Pvariable(config[CONF_ID], one_wire) + var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + + pin = await cg.gpio_pin_expression(config[CONF_PIN]) + cg.add(var.set_pin(pin)) diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index 0fc4108687..8d7f2e4deb 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -31,12 +31,11 @@ uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion() const { void DallasComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up DallasComponent..."); - yield(); + pin_->setup(); + one_wire_ = new ESPOneWire(pin_); // NOLINT(cppcoreguidelines-owning-memory) + std::vector raw_sensors; - { - InterruptLock lock; - raw_sensors = this->one_wire_->search_vec(); - } + raw_sensors = this->one_wire_->search_vec(); for (auto &address : raw_sensors) { std::string s = uint64_to_string(address); @@ -70,7 +69,7 @@ void DallasComponent::setup() { } void DallasComponent::dump_config() { ESP_LOGCONFIG(TAG, "DallasComponent:"); - LOG_PIN(" Pin: ", this->one_wire_->get_pin()); + LOG_PIN(" Pin: ", this->pin_); LOG_UPDATE_INTERVAL(this); if (this->found_sensors_.empty()) { @@ -102,15 +101,12 @@ void DallasComponent::update() { this->status_clear_warning(); bool result; - { - InterruptLock lock; - if (!this->one_wire_->reset()) { - result = false; - } else { - result = true; - this->one_wire_->skip(); - this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION); - } + if (!this->one_wire_->reset()) { + result = false; + } else { + result = true; + this->one_wire_->skip(); + this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION); } if (!result) { @@ -121,11 +117,7 @@ void DallasComponent::update() { for (auto *sensor : this->sensors_) { this->set_timeout(sensor->get_address_name(), sensor->millis_to_wait_for_conversion(), [this, sensor] { - bool res; - { - InterruptLock lock; - res = sensor->read_scratch_pad(); - } + bool res = sensor->read_scratch_pad(); if (!res) { ESP_LOGW(TAG, "'%s' - Resetting bus for read failed!", sensor->get_name().c_str()); @@ -146,7 +138,6 @@ void DallasComponent::update() { }); } } -DallasComponent::DallasComponent(ESPOneWire *one_wire) : one_wire_(one_wire) {} void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; } uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; } @@ -162,7 +153,7 @@ const std::string &DallasTemperatureSensor::get_address_name() { return this->address_name_; } bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() { - ESPOneWire *wire = this->parent_->one_wire_; + auto *wire = this->parent_->one_wire_; if (!wire->reset()) { return false; } @@ -176,11 +167,7 @@ bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() { return true; } bool DallasTemperatureSensor::setup_sensor() { - bool r; - { - InterruptLock lock; - r = this->read_scratch_pad(); - } + bool r = this->read_scratch_pad(); if (!r) { ESP_LOGE(TAG, "Reading scratchpad failed: reset"); @@ -214,21 +201,18 @@ bool DallasTemperatureSensor::setup_sensor() { break; } - ESPOneWire *wire = this->parent_->one_wire_; - { - InterruptLock lock; - if (wire->reset()) { - wire->select(this->address_); - wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD); - wire->write8(this->scratch_pad_[2]); // high alarm temp - wire->write8(this->scratch_pad_[3]); // low alarm temp - wire->write8(this->scratch_pad_[4]); // resolution - wire->reset(); + auto *wire = this->parent_->one_wire_; + if (wire->reset()) { + wire->select(this->address_); + wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD); + wire->write8(this->scratch_pad_[2]); // high alarm temp + wire->write8(this->scratch_pad_[3]); // low alarm temp + wire->write8(this->scratch_pad_[4]); // resolution + wire->reset(); - // write value to EEPROM - wire->select(this->address_); - wire->write8(0x48); - } + // write value to EEPROM + wire->select(this->address_); + wire->write8(0x48); } delay(20); // allow it to finish operation diff --git a/esphome/components/dallas/dallas_component.h b/esphome/components/dallas/dallas_component.h index 8d405f6eab..37c098283a 100644 --- a/esphome/components/dallas/dallas_component.h +++ b/esphome/components/dallas/dallas_component.h @@ -11,8 +11,7 @@ class DallasTemperatureSensor; class DallasComponent : public PollingComponent { public: - explicit DallasComponent(ESPOneWire *one_wire); - + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } void register_sensor(DallasTemperatureSensor *sensor); void setup() override; @@ -24,6 +23,7 @@ class DallasComponent : public PollingComponent { protected: friend DallasTemperatureSensor; + InternalGPIOPin *pin_; ESPOneWire *one_wire_; std::vector sensors_; std::vector found_sensors_; diff --git a/esphome/components/dallas/esp_one_wire.cpp b/esphome/components/dallas/esp_one_wire.cpp index 9278b83f7f..a0ab10f8a4 100644 --- a/esphome/components/dallas/esp_one_wire.cpp +++ b/esphome/components/dallas/esp_one_wire.cpp @@ -10,115 +10,123 @@ static const char *const TAG = "dallas.one_wire"; const uint8_t ONE_WIRE_ROM_SELECT = 0x55; const int ONE_WIRE_ROM_SEARCH = 0xF0; -ESPOneWire::ESPOneWire(GPIOPin *pin) : pin_(pin) {} +ESPOneWire::ESPOneWire(InternalGPIOPin *pin) { pin_ = pin->to_isr(); } bool HOT IRAM_ATTR ESPOneWire::reset() { - uint8_t retries = 125; + // See reset here: + // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html + InterruptLock lock; - // Wait for communication to clear - this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + // Wait for communication to clear (delay G) + pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + uint8_t retries = 125; do { if (--retries == 0) return false; delayMicroseconds(2); - } while (!this->pin_->digital_read()); + } while (!pin_.digital_read()); - // Send 480µs LOW TX reset pulse - this->pin_->pin_mode(gpio::FLAG_OUTPUT); - this->pin_->digital_write(false); + // Send 480µs LOW TX reset pulse (drive bus low, delay H) + pin_.pin_mode(gpio::FLAG_OUTPUT); + pin_.digital_write(false); delayMicroseconds(480); - // Switch into RX mode, letting the pin float - this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); - // after 15µs-60µs wait time, responder pulls low for 60µs-240µs - // let's have 70µs just in case + // Release the bus, delay I + pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); delayMicroseconds(70); - bool r = !this->pin_->digital_read(); + // sample bus, 0=device(s) present, 1=no device present + bool r = !pin_.digital_read(); + // delay J delayMicroseconds(410); return r; } void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) { - // Initiate write/read by pulling low. - this->pin_->pin_mode(gpio::FLAG_OUTPUT); - this->pin_->digital_write(false); + // See write 1/0 bit here: + // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html + InterruptLock lock; - // bus sampled within 15µs and 60µs after pulling LOW. - if (bit) { - // pull high/release within 15µs - delayMicroseconds(10); - this->pin_->digital_write(true); - // in total minimum of 60µs long - delayMicroseconds(55); - } else { - // continue pulling LOW for at least 60µs - delayMicroseconds(65); - this->pin_->digital_write(true); - // grace period, 1µs recovery time - delayMicroseconds(5); - } + // drive bus low + pin_.pin_mode(gpio::FLAG_OUTPUT); + pin_.digital_write(false); + + uint32_t delay0 = bit ? 10 : 65; + uint32_t delay1 = bit ? 55 : 5; + + // delay A/C + delayMicroseconds(delay0); + // release bus + pin_.digital_write(true); + // delay B/D + delayMicroseconds(delay1); } bool HOT IRAM_ATTR ESPOneWire::read_bit() { - // Initiate read slot by pulling LOW for at least 1µs - this->pin_->pin_mode(gpio::FLAG_OUTPUT); - this->pin_->digital_write(false); + // See read bit here: + // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html + InterruptLock lock; + + // drive bus low, delay A + pin_.pin_mode(gpio::FLAG_OUTPUT); + pin_.digital_write(false); delayMicroseconds(3); - // release bus, we have to sample within 15µs of pulling low - this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + // release bus, delay E + pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); delayMicroseconds(10); - bool r = this->pin_->digital_read(); - // read time slot at least 60µs long + 1µs recovery time between slots + // sample bus to read bit from peer + bool r = pin_.digital_read(); + + // delay F delayMicroseconds(53); return r; } -void IRAM_ATTR ESPOneWire::write8(uint8_t val) { +void ESPOneWire::write8(uint8_t val) { for (uint8_t i = 0; i < 8; i++) { this->write_bit(bool((1u << i) & val)); } } -void IRAM_ATTR ESPOneWire::write64(uint64_t val) { +void ESPOneWire::write64(uint64_t val) { for (uint8_t i = 0; i < 64; i++) { this->write_bit(bool((1ULL << i) & val)); } } -uint8_t IRAM_ATTR ESPOneWire::read8() { +uint8_t ESPOneWire::read8() { uint8_t ret = 0; for (uint8_t i = 0; i < 8; i++) { ret |= (uint8_t(this->read_bit()) << i); } return ret; } -uint64_t IRAM_ATTR ESPOneWire::read64() { +uint64_t ESPOneWire::read64() { uint64_t ret = 0; for (uint8_t i = 0; i < 8; i++) { ret |= (uint64_t(this->read_bit()) << i); } return ret; } -void IRAM_ATTR ESPOneWire::select(uint64_t address) { +void ESPOneWire::select(uint64_t address) { this->write8(ONE_WIRE_ROM_SELECT); this->write64(address); } -void IRAM_ATTR ESPOneWire::reset_search() { +void ESPOneWire::reset_search() { this->last_discrepancy_ = 0; this->last_device_flag_ = false; this->last_family_discrepancy_ = 0; this->rom_number_ = 0; } -uint64_t HOT IRAM_ATTR ESPOneWire::search() { +uint64_t ESPOneWire::search() { if (this->last_device_flag_) { return 0u; } if (!this->reset()) { - // Reset failed + // Reset failed or no devices present this->reset_search(); return 0u; } @@ -196,7 +204,7 @@ uint64_t HOT IRAM_ATTR ESPOneWire::search() { return this->rom_number_; } -std::vector IRAM_ATTR ESPOneWire::search_vec() { +std::vector ESPOneWire::search_vec() { std::vector res; this->reset_search(); @@ -206,10 +214,9 @@ std::vector IRAM_ATTR ESPOneWire::search_vec() { return res; } -void IRAM_ATTR ESPOneWire::skip() { +void ESPOneWire::skip() { this->write8(0xCC); // skip ROM } -GPIOPin *ESPOneWire::get_pin() { return this->pin_; } uint8_t IRAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast(&this->rom_number_); } diff --git a/esphome/components/dallas/esp_one_wire.h b/esphome/components/dallas/esp_one_wire.h index 728fa127d3..ef6f079f02 100644 --- a/esphome/components/dallas/esp_one_wire.h +++ b/esphome/components/dallas/esp_one_wire.h @@ -1,6 +1,5 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/core/hal.h" #include @@ -12,7 +11,7 @@ extern const int ONE_WIRE_ROM_SEARCH; class ESPOneWire { public: - explicit ESPOneWire(GPIOPin *pin); + explicit ESPOneWire(InternalGPIOPin *pin); /** Reset the bus, should be done before all write operations. * @@ -55,13 +54,11 @@ class ESPOneWire { /// Helper that wraps search in a std::vector. std::vector search_vec(); - GPIOPin *get_pin(); - protected: /// Helper to get the internal 64-bit unsigned rom number as a 8-bit integer pointer. inline uint8_t *rom_number8_(); - GPIOPin *pin_; + ISRInternalGPIOPin pin_; uint8_t last_discrepancy_{0}; uint8_t last_family_discrepancy_{0}; bool last_device_flag_{false}; diff --git a/esphome/components/esp32/gpio_arduino.cpp b/esphome/components/esp32/gpio_arduino.cpp index c4bb21a0aa..ba92894f97 100644 --- a/esphome/components/esp32/gpio_arduino.cpp +++ b/esphome/components/esp32/gpio_arduino.cpp @@ -9,6 +9,22 @@ namespace esp32 { static const char *const TAG = "esp32"; +static int IRAM_ATTR flags_to_mode(gpio::Flags flags) { + if (flags == gpio::FLAG_INPUT) { + return INPUT; + } else if (flags == gpio::FLAG_OUTPUT) { + return OUTPUT; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + return INPUT_PULLUP; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { + return INPUT_PULLDOWN; + } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + return OUTPUT_OPEN_DRAIN; + } else { + return 0; + } +} + struct ISRPinArg { uint8_t pin; bool inverted; @@ -43,22 +59,9 @@ void ArduinoInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, g attachInterruptArg(pin_, func, arg, arduino_mode); } + void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) { - uint8_t mode; - if (flags == gpio::FLAG_INPUT) { - mode = INPUT; - } else if (flags == gpio::FLAG_OUTPUT) { - mode = OUTPUT; - } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { - mode = INPUT_PULLUP; - } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { - mode = INPUT_PULLDOWN; - } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { - mode = OUTPUT_OPEN_DRAIN; - } else { - return; - } - pinMode(pin_, mode); // NOLINT + pinMode(pin_, flags_to_mode(flags)); // NOLINT } std::string ArduinoInternalGPIOPin::dump_summary() const { @@ -101,6 +104,10 @@ void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { } #endif } +void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { + auto *arg = reinterpret_cast(arg_); + pinMode(arg->pin, flags_to_mode(flags)); // NOLINT +} } // namespace esphome diff --git a/esphome/components/esp32/gpio_idf.cpp b/esphome/components/esp32/gpio_idf.cpp index d1853e1f8b..498843ebff 100644 --- a/esphome/components/esp32/gpio_idf.cpp +++ b/esphome/components/esp32/gpio_idf.cpp @@ -10,38 +10,7 @@ static const char *const TAG = "esp32"; bool IDFInternalGPIOPin::isr_service_installed = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -struct ISRPinArg { - gpio_num_t pin; - bool inverted; -}; - -ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const { - auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory) - arg->pin = pin_; - arg->inverted = inverted_; - return ISRInternalGPIOPin((void *) arg); -} - -void IDFInternalGPIOPin::setup() { - pin_mode(flags_); - gpio_set_drive_capability(pin_, drive_strength_); -} - -void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) { - gpio_config_t conf{}; - conf.pin_bit_mask = 1ULL << static_cast(pin_); - conf.mode = flags_to_mode(flags); - conf.pull_up_en = flags & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE; - conf.pull_down_en = flags & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE; - conf.intr_type = GPIO_INTR_DISABLE; - gpio_config(&conf); -} - -bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; } - -void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); } - -gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) { +static gpio_mode_t IRAM_ATTR flags_to_mode(gpio::Flags flags) { flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)); if (flags == gpio::FLAG_NONE) { return GPIO_MODE_DISABLE; @@ -61,6 +30,18 @@ gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) { } } +struct ISRPinArg { + gpio_num_t pin; + bool inverted; +}; + +ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const { + auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory) + arg->pin = pin_; + arg->inverted = inverted_; + return ISRInternalGPIOPin((void *) arg); +} + void IDFInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE; switch (type) { @@ -99,6 +80,35 @@ std::string IDFInternalGPIOPin::dump_summary() const { return buffer; } +void IDFInternalGPIOPin::setup() { + gpio_config_t conf{}; + conf.pin_bit_mask = 1ULL << static_cast(pin_); + conf.mode = flags_to_mode(flags_); + conf.pull_up_en = flags_ & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE; + conf.pull_down_en = flags_ & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE; + conf.intr_type = GPIO_INTR_DISABLE; + gpio_config(&conf); + gpio_set_drive_capability(pin_, drive_strength_); +} + +void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) { + // can't call gpio_config here because that logs in esp-idf which may cause issues + gpio_set_direction(pin_, flags_to_mode(flags)); + gpio_pull_mode_t pull_mode = GPIO_FLOATING; + if (flags & (gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)) { + pull_mode = GPIO_PULLUP_PULLDOWN; + } else if (flags & gpio::FLAG_PULLUP) { + pull_mode = GPIO_PULLUP_ONLY; + } else if (flags & gpio::FLAG_PULLDOWN) { + pull_mode = GPIO_PULLDOWN_ONLY; + } + gpio_set_pull_mode(pin_, pull_mode); +} + +bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; } +void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); } +void IDFInternalGPIOPin::detach_interrupt() const { gpio_intr_disable(pin_); } + } // namespace esp32 using namespace esp32; @@ -114,6 +124,19 @@ void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { // not supported } +void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { + auto *arg = reinterpret_cast(arg_); + gpio_set_direction(arg->pin, flags_to_mode(flags)); + gpio_pull_mode_t pull_mode = GPIO_FLOATING; + if (flags & (gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)) { + pull_mode = GPIO_PULLUP_PULLDOWN; + } else if (flags & gpio::FLAG_PULLUP) { + pull_mode = GPIO_PULLUP_ONLY; + } else if (flags & gpio::FLAG_PULLDOWN) { + pull_mode = GPIO_PULLDOWN_ONLY; + } + gpio_set_pull_mode(arg->pin, pull_mode); +} } // namespace esphome diff --git a/esphome/components/esp32/gpio_idf.h b/esphome/components/esp32/gpio_idf.h index a99571cc46..a07d11378a 100644 --- a/esphome/components/esp32/gpio_idf.h +++ b/esphome/components/esp32/gpio_idf.h @@ -18,13 +18,12 @@ class IDFInternalGPIOPin : public InternalGPIOPin { bool digital_read() override; void digital_write(bool value) override; std::string dump_summary() const override; - void detach_interrupt() const override { gpio_intr_disable(pin_); } + void detach_interrupt() const override; ISRInternalGPIOPin to_isr() const override; uint8_t get_pin() const override { return (uint8_t) pin_; } bool is_inverted() const override { return inverted_; } protected: - static gpio_mode_t flags_to_mode(gpio::Flags flags); void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; gpio_num_t pin_; diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index 7805889b40..2660318182 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -8,6 +8,29 @@ namespace esp8266 { static const char *const TAG = "esp8266"; +static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) { + if (flags == gpio::FLAG_INPUT) { + return INPUT; + } else if (flags == gpio::FLAG_OUTPUT) { + return OUTPUT; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + if (pin == 16) { + // GPIO16 doesn't have a pullup, so pinMode would fail. + // However, sometimes this method is called with pullup mode anyway + // for example from dallas one_wire. For those cases convert this + // to a INPUT mode. + return INPUT; + } + return INPUT_PULLUP; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { + return INPUT_PULLDOWN_16; + } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + return OUTPUT_OPEN_DRAIN; + } else { + return 0; + } +} + struct ISRPinArg { uint8_t pin; bool inverted; @@ -43,28 +66,7 @@ void ESP8266GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::Int attachInterruptArg(pin_, func, arg, arduino_mode); } void ESP8266GPIOPin::pin_mode(gpio::Flags flags) { - uint8_t mode; - if (flags == gpio::FLAG_INPUT) { - mode = INPUT; - } else if (flags == gpio::FLAG_OUTPUT) { - mode = OUTPUT; - } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { - mode = INPUT_PULLUP; - if (pin_ == 16) { - // GPIO16 doesn't have a pullup, so pinMode would fail. - // However, sometimes this method is called with pullup mode anyway - // for example from dallas one_wire. For those cases convert this - // to a INPUT mode. - mode = INPUT; - } - } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { - mode = INPUT_PULLDOWN_16; - } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { - mode = OUTPUT_OPEN_DRAIN; - } else { - return; - } - pinMode(pin_, mode); // NOLINT + pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT } std::string ESP8266GPIOPin::dump_summary() const { @@ -97,6 +99,10 @@ void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { auto *arg = reinterpret_cast(arg_); GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin); } +void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { + auto *arg = reinterpret_cast(arg_); + pinMode(arg->pin, flags_to_mode(flags, arg->pin)); // NOLINT +} } // namespace esphome diff --git a/esphome/core/gpio.h b/esphome/core/gpio.h index 1d3fb89805..04658d567c 100644 --- a/esphome/core/gpio.h +++ b/esphome/core/gpio.h @@ -70,6 +70,7 @@ class ISRInternalGPIOPin { bool digital_read(); void digital_write(bool value); void clear_interrupt(); + void pin_mode(gpio::Flags flags); protected: void *arg_ = nullptr; From 9220d9fc5222b90187c4a4413d7def262f5c8ec3 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 10:46:44 +0200 Subject: [PATCH 1609/1841] Fix socket connection closed not detected (#2587) --- esphome/components/api/api_connection.cpp | 2 + esphome/components/api/api_frame_helper.cpp | 48 ++++++++++++++----- esphome/components/api/api_frame_helper.h | 1 + esphome/components/ota/ota_component.cpp | 9 ++++ .../components/socket/lwip_raw_tcp_impl.cpp | 8 +++- 5 files changed, 53 insertions(+), 15 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 47171ba50f..c87ccf4dc0 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -78,6 +78,8 @@ void APIConnection::loop() { on_fatal_error(); if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str()); + } else if (err == APIError::CONNECTION_CLOSED) { + ESP_LOGW(TAG, "%s: Connection closed", client_info_.c_str()); } else { ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); } diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 4971272f41..c0e37ec90d 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -10,7 +10,7 @@ namespace api { static const char *const TAG = "api.socket"; -/// Is the given return value (from read/write syscalls) a wouldblock error? +/// Is the given return value (from write syscalls) a wouldblock error? bool is_would_block(ssize_t ret) { if (ret == -1) { return errno == EWOULDBLOCK || errno == EAGAIN; @@ -64,6 +64,8 @@ const char *api_error_to_str(APIError err) { return "HANDSHAKESTATE_SPLIT_FAILED"; } else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) { return "BAD_HANDSHAKE_ERROR_BYTE"; + } else if (err == APIError::CONNECTION_CLOSED) { + return "CONNECTION_CLOSED"; } return "UNKNOWN"; } @@ -185,12 +187,17 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { // no header information yet size_t to_read = 3 - rx_header_buf_len_; ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read); - if (is_would_block(received)) { - return APIError::WOULD_BLOCK; - } else if (received == -1) { + if (received == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + return APIError::WOULD_BLOCK; + } state_ = State::FAILED; HELPER_LOG("Socket read failed with errno %d", errno); return APIError::SOCKET_READ_FAILED; + } else if (received == 0) { + state_ = State::FAILED; + HELPER_LOG("Connection closed"); + return APIError::CONNECTION_CLOSED; } rx_header_buf_len_ += received; if (received != to_read) { @@ -227,12 +234,17 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { // more data to read size_t to_read = msg_size - rx_buf_len_; ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read); - if (is_would_block(received)) { - return APIError::WOULD_BLOCK; - } else if (received == -1) { + if (received == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + return APIError::WOULD_BLOCK; + } state_ = State::FAILED; HELPER_LOG("Socket read failed with errno %d", errno); return APIError::SOCKET_READ_FAILED; + } else if (received == 0) { + state_ = State::FAILED; + HELPER_LOG("Connection closed"); + return APIError::CONNECTION_CLOSED; } rx_buf_len_ += received; if (received != to_read) { @@ -778,12 +790,17 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { while (!rx_header_parsed_) { uint8_t data; ssize_t received = socket_->read(&data, 1); - if (is_would_block(received)) { - return APIError::WOULD_BLOCK; - } else if (received == -1) { + if (received == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + return APIError::WOULD_BLOCK; + } state_ = State::FAILED; HELPER_LOG("Socket read failed with errno %d", errno); return APIError::SOCKET_READ_FAILED; + } else if (received == 0) { + state_ = State::FAILED; + HELPER_LOG("Connection closed"); + return APIError::CONNECTION_CLOSED; } rx_header_buf_.push_back(data); @@ -824,12 +841,17 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { // more data to read size_t to_read = rx_header_parsed_len_ - rx_buf_len_; ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read); - if (is_would_block(received)) { - return APIError::WOULD_BLOCK; - } else if (received == -1) { + if (received == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + return APIError::WOULD_BLOCK; + } state_ = State::FAILED; HELPER_LOG("Socket read failed with errno %d", errno); return APIError::SOCKET_READ_FAILED; + } else if (received == 0) { + state_ = State::FAILED; + HELPER_LOG("Connection closed"); + return APIError::CONNECTION_CLOSED; } rx_buf_len_ += received; if (received != to_read) { diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 7fdb26fd40..57e3c961d5 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -53,6 +53,7 @@ enum class APIError : int { HANDSHAKESTATE_SETUP_FAILED = 1019, HANDSHAKESTATE_SPLIT_FAILED = 1020, BAD_HANDSHAKE_ERROR_BYTE = 1021, + CONNECTION_CLOSED = 1022, }; const char *api_error_to_str(APIError err); diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 89bee17452..6d51087882 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -275,6 +275,12 @@ void OTAComponent::handle_() { } ESP_LOGW(TAG, "Error receiving data for update, errno: %d", errno); goto error; + } else if (read == 0) { + // $ man recv + // "When a stream socket peer has performed an orderly shutdown, the return value will + // be 0 (the traditional "end-of-file" return)." + ESP_LOGW(TAG, "Remote end closed connection"); + goto error; } error_code = backend->write(buf, read); @@ -362,6 +368,9 @@ bool OTAComponent::readall_(uint8_t *buf, size_t len) { } ESP_LOGW(TAG, "Failed to read %d bytes of data, errno: %d", len, errno); return false; + } else if (read == 0) { + ESP_LOGW(TAG, "Remote closed connection"); + return false; } else { at += read; } diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 54dfddac3f..922d895ff4 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -320,8 +320,7 @@ class LWIPRawImpl : public Socket { return -1; } if (rx_closed_ && rx_buf_ == nullptr) { - errno = ECONNRESET; - return -1; + return 0; } if (len == 0) { return 0; @@ -366,6 +365,11 @@ class LWIPRawImpl : public Socket { read += copysize; } + if (read == 0) { + errno = EWOULDBLOCK; + return -1; + } + return read; } ssize_t readv(const struct iovec *iov, int iovcnt) override { From f7b3f52731c6842dd9c44feea5ff91aeafef0497 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Fri, 22 Oct 2021 12:09:47 +0200 Subject: [PATCH 1610/1841] Limit hostnames to 31 characters (#2531) --- esphome/config_validation.py | 18 +--- esphome/core/config.py | 95 ++++++++++++++-------- tests/unit_tests/test_config_validation.py | 22 ----- 3 files changed, 62 insertions(+), 73 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index fcec74b245..5d4ff64193 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -903,21 +903,9 @@ def validate_bytes(value): def hostname(value): value = string(value) - warned_underscore = False - if len(value) > 63: - raise Invalid("Hostnames can only be 63 characters long") - for c in value: - if not (c.isalnum() or c in "-_"): - raise Invalid("Hostname can only have alphanumeric characters and -") - if c in "_" and not warned_underscore: - _LOGGER.warning( - "'%s': Using the '_' (underscore) character in the hostname is discouraged " - "as it can cause problems with some DHCP and local name services. " - "For more information, see https://esphome.io/guides/faq.html#why-shouldn-t-i-use-underscores-in-my-device-name", - value, - ) - warned_underscore = True - return value + if re.match(r"^[a-z0-9-]{1,63}$", value, re.IGNORECASE) is not None: + return value + raise Invalid(f"Invalid hostname: {value}") def domain(value): diff --git a/esphome/core/config.py b/esphome/core/config.py index 235e0eeb84..ad132968b9 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -55,6 +55,24 @@ CONF_NAME_ADD_MAC_SUFFIX = "name_add_mac_suffix" VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} +def validate_hostname(config): + max_length = 31 + if config[CONF_NAME_ADD_MAC_SUFFIX]: + max_length -= 7 # "-AABBCC" is appended when add mac suffix option is used + if len(config[CONF_NAME]) > max_length: + raise cv.Invalid( + f"Hostnames can only be {max_length} characters long", path=[CONF_NAME] + ) + if "_" in config[CONF_NAME]: + _LOGGER.warning( + "'%s': Using the '_' (underscore) character in the hostname is discouraged " + "as it can cause problems with some DHCP and local name services. " + "For more information, see https://esphome.io/guides/faq.html#why-shouldn-t-i-use-underscores-in-my-device-name", + config[CONF_NAME], + ) + return config + + def valid_include(value): try: return cv.directory(value) @@ -79,42 +97,47 @@ def valid_project_name(value: str): CONF_ESP8266_RESTORE_FROM_FLASH = "esp8266_restore_from_flash" -CONFIG_SCHEMA = cv.Schema( - { - cv.Required(CONF_NAME): cv.hostname, - cv.Optional(CONF_COMMENT): cv.string, - cv.Required(CONF_BUILD_PATH): cv.string, - cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema( - { - cv.string_strict: cv.Any([cv.string], cv.string), - } - ), - cv.Optional(CONF_ON_BOOT): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StartupTrigger), - cv.Optional(CONF_PRIORITY, default=600.0): cv.float_, - } - ), - cv.Optional(CONF_ON_SHUTDOWN): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ShutdownTrigger), - } - ), - cv.Optional(CONF_ON_LOOP): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LoopTrigger), - } - ), - cv.Optional(CONF_INCLUDES, default=[]): cv.ensure_list(valid_include), - cv.Optional(CONF_LIBRARIES, default=[]): cv.ensure_list(cv.string_strict), - cv.Optional(CONF_NAME_ADD_MAC_SUFFIX, default=False): cv.boolean, - cv.Optional(CONF_PROJECT): cv.Schema( - { - cv.Required(CONF_NAME): cv.All(cv.string_strict, valid_project_name), - cv.Required(CONF_VERSION): cv.string_strict, - } - ), - } +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_NAME): cv.valid_name, + cv.Optional(CONF_COMMENT): cv.string, + cv.Required(CONF_BUILD_PATH): cv.string, + cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema( + { + cv.string_strict: cv.Any([cv.string], cv.string), + } + ), + cv.Optional(CONF_ON_BOOT): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StartupTrigger), + cv.Optional(CONF_PRIORITY, default=600.0): cv.float_, + } + ), + cv.Optional(CONF_ON_SHUTDOWN): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ShutdownTrigger), + } + ), + cv.Optional(CONF_ON_LOOP): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LoopTrigger), + } + ), + cv.Optional(CONF_INCLUDES, default=[]): cv.ensure_list(valid_include), + cv.Optional(CONF_LIBRARIES, default=[]): cv.ensure_list(cv.string_strict), + cv.Optional(CONF_NAME_ADD_MAC_SUFFIX, default=False): cv.boolean, + cv.Optional(CONF_PROJECT): cv.Schema( + { + cv.Required(CONF_NAME): cv.All( + cv.string_strict, valid_project_name + ), + cv.Required(CONF_VERSION): cv.string_strict, + } + ), + } + ), + validate_hostname, ) PRELOAD_CONFIG_SCHEMA = cv.Schema( diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index e34c7064fa..16cfb16e94 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -40,28 +40,6 @@ def test_valid_name__invalid(value): config_validation.valid_name(value) -@pytest.mark.parametrize("value", ("foo", "bar123", "foo-bar")) -def test_hostname__valid(value): - actual = config_validation.hostname(value) - - assert actual == value - - -@pytest.mark.parametrize("value", ("foo bar", "foobar ", "foo#bar")) -def test_hostname__invalid(value): - with pytest.raises(Invalid): - config_validation.hostname(value) - - -def test_hostname__warning(caplog): - actual = config_validation.hostname("foo_bar") - assert actual == "foo_bar" - assert ( - "Using the '_' (underscore) character in the hostname is discouraged" - in caplog.text - ) - - @given(one_of(integers(), text())) def test_string__valid(value): actual = config_validation.string(value) From be3cb9ef004991b91b68e86f96243dbb500fdac3 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Fri, 22 Oct 2021 23:10:29 +1300 Subject: [PATCH 1611/1841] Add EntityBase properties to ESP32 Camera (#2600) --- esphome/components/api/api.proto | 1 + esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 9 +++++++++ esphome/components/api/api_pb2.h | 1 + esphome/components/esp32_camera/__init__.py | 11 ++++------- esphome/components/esp32_camera/esp32_camera.cpp | 1 + esphome/components/esp32_camera/esp32_camera.h | 1 + 7 files changed, 18 insertions(+), 7 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 5a6eba004c..3c6a36f032 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -701,6 +701,7 @@ message ListEntitiesCameraResponse { string name = 3; string unique_id = 4; bool disabled_by_default = 5; + string icon = 6; } message CameraImageResponse { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index c87ccf4dc0..2151d6165c 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -680,6 +680,7 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { msg.name = camera->get_name(); msg.unique_id = get_default_unique_id("camera", camera); msg.disabled_by_default = camera->is_disabled_by_default(); + msg.icon = camera->get_icon(); return this->send_list_entities_camera_response(msg); } void APIConnection::camera_image(const CameraImageRequest &msg) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 6a87238186..17fc14c868 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -2910,6 +2910,10 @@ bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDel this->unique_id = value.as_string(); return true; } + case 6: { + this->icon = value.as_string(); + return true; + } default: return false; } @@ -2930,6 +2934,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(3, this->name); buffer.encode_string(4, this->unique_id); buffer.encode_bool(5, this->disabled_by_default); + buffer.encode_string(6, this->icon); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCameraResponse::dump_to(std::string &out) const { @@ -2955,6 +2960,10 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 13a21c4772..eea9ab06f6 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -803,6 +803,7 @@ class ListEntitiesCameraResponse : public ProtoMessage { std::string name{}; std::string unique_id{}; bool disabled_by_default{false}; + std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 7f3aebe238..1a8b3a37ec 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -2,10 +2,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins from esphome.const import ( - CONF_DISABLED_BY_DEFAULT, CONF_FREQUENCY, CONF_ID, - CONF_NAME, CONF_PIN, CONF_SCL, CONF_SDA, @@ -17,6 +15,7 @@ from esphome.const import ( ) from esphome.core import CORE from esphome.components.esp32 import add_idf_sdkconfig_option +from esphome.cpp_helpers import setup_entity DEPENDENCIES = ["esp32", "api"] @@ -63,11 +62,9 @@ CONF_TEST_PATTERN = "test_pattern" camera_range_param = cv.int_range(min=-2, max=2) -CONFIG_SCHEMA = cv.Schema( +CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(ESP32Camera), - cv.Required(CONF_NAME): cv.string, - cv.Optional(CONF_DISABLED_BY_DEFAULT, default=False): cv.boolean, cv.Required(CONF_DATA_PINS): cv.All( [pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8) ), @@ -127,8 +124,8 @@ SETTERS = { async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME]) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) + var = cg.new_Pvariable(config[CONF_ID]) + await setup_entity(var, config) await cg.register_component(var, config) for key, setter in SETTERS.items(): diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index babfda4113..ad4304d89f 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -185,6 +185,7 @@ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { global_esp32_camera = this; } +ESP32Camera::ESP32Camera() : ESP32Camera("") {} void ESP32Camera::set_data_pins(std::array pins) { this->config_.pin_d0 = pins[0]; this->config_.pin_d1 = pins[1]; diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index d0445607a4..84f8d9cbea 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -54,6 +54,7 @@ enum ESP32CameraFrameSize { class ESP32Camera : public Component, public EntityBase { public: ESP32Camera(const std::string &name); + ESP32Camera(); void set_data_pins(std::array pins); void set_vsync_pin(uint8_t pin); void set_href_pin(uint8_t pin); From c08b21b7cd5ec3101b0ade57d1b1f5c2462af308 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 12:12:07 +0200 Subject: [PATCH 1612/1841] Bump noise-c from 0.1.3 to 0.1.4 (#2602) --- esphome/components/api/__init__.py | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index b0608a69dd..6b2e7fd06b 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -121,7 +121,7 @@ async def to_code(config): decoded = base64.b64decode(conf[CONF_KEY]) cg.add(var.set_noise_psk(list(decoded))) cg.add_define("USE_API_NOISE") - cg.add_library("esphome/noise-c", "0.1.3") + cg.add_library("esphome/noise-c", "0.1.4") else: cg.add_define("USE_API_PLAINTEXT") diff --git a/platformio.ini b/platformio.ini index 6a8b342314..ac5144fc37 100644 --- a/platformio.ini +++ b/platformio.ini @@ -26,7 +26,7 @@ build_flags = [common] lib_deps = - esphome/noise-c@0.1.3 ; api + esphome/noise-c@0.1.4 ; api makuna/NeoPixelBus@2.6.7 ; neopixelbus build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE From 0d90ef94aeabe45f7229696728acec24cdb55122 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 13:02:55 +0200 Subject: [PATCH 1613/1841] Add OTA upload compression for ESP8266 (#2601) --- esphome/components/ota/ota_backend.h | 1 + .../ota/ota_backend_arduino_esp32.h | 1 + .../ota/ota_backend_arduino_esp8266.h | 1 + esphome/components/ota/ota_backend_esp_idf.h | 1 + esphome/components/ota/ota_component.cpp | 9 +++- esphome/components/ota/ota_component.h | 1 + esphome/espota2.py | 44 ++++++++++++------- 7 files changed, 42 insertions(+), 16 deletions(-) diff --git a/esphome/components/ota/ota_backend.h b/esphome/components/ota/ota_backend.h index c253e009c6..5c5b61a278 100644 --- a/esphome/components/ota/ota_backend.h +++ b/esphome/components/ota/ota_backend.h @@ -12,6 +12,7 @@ class OTABackend { virtual OTAResponseTypes write(uint8_t *data, size_t len) = 0; virtual OTAResponseTypes end() = 0; virtual void abort() = 0; + virtual bool supports_compression() = 0; }; } // namespace ota diff --git a/esphome/components/ota/ota_backend_arduino_esp32.h b/esphome/components/ota/ota_backend_arduino_esp32.h index 6b712502fb..f86a70d678 100644 --- a/esphome/components/ota/ota_backend_arduino_esp32.h +++ b/esphome/components/ota/ota_backend_arduino_esp32.h @@ -15,6 +15,7 @@ class ArduinoESP32OTABackend : public OTABackend { OTAResponseTypes write(uint8_t *data, size_t len) override; OTAResponseTypes end() override; void abort() override; + bool supports_compression() override { return false; } }; } // namespace ota diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.h b/esphome/components/ota/ota_backend_arduino_esp8266.h index d1195af911..cf29a90fc1 100644 --- a/esphome/components/ota/ota_backend_arduino_esp8266.h +++ b/esphome/components/ota/ota_backend_arduino_esp8266.h @@ -16,6 +16,7 @@ class ArduinoESP8266OTABackend : public OTABackend { OTAResponseTypes write(uint8_t *data, size_t len) override; OTAResponseTypes end() override; void abort() override; + bool supports_compression() override { return true; } }; } // namespace ota diff --git a/esphome/components/ota/ota_backend_esp_idf.h b/esphome/components/ota/ota_backend_esp_idf.h index 49c6e124fa..af09d0d693 100644 --- a/esphome/components/ota/ota_backend_esp_idf.h +++ b/esphome/components/ota/ota_backend_esp_idf.h @@ -17,6 +17,7 @@ class IDFOTABackend : public OTABackend { OTAResponseTypes write(uint8_t *data, size_t len) override; OTAResponseTypes end() override; void abort() override; + bool supports_compression() override { return false; } private: esp_ota_handle_t update_handle_{0}; diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 6d51087882..e49c108320 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -104,6 +104,8 @@ void OTAComponent::loop() { } } +static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01; + void OTAComponent::handle_() { OTAResponseTypes error_code = OTA_RESPONSE_ERROR_UNKNOWN; bool update_started = false; @@ -154,6 +156,8 @@ void OTAComponent::handle_() { buf[1] = OTA_VERSION_1_0; this->writeall_(buf, 2); + backend = make_ota_backend(); + // Read features - 1 byte if (!this->readall_(buf, 1)) { ESP_LOGW(TAG, "Reading features failed!"); @@ -164,6 +168,10 @@ void OTAComponent::handle_() { // Acknowledge header - 1 byte buf[0] = OTA_RESPONSE_HEADER_OK; + if ((ota_features & FEATURE_SUPPORTS_COMPRESSION) != 0 && backend->supports_compression()) { + buf[0] = OTA_RESPONSE_SUPPORTS_COMPRESSION; + } + this->writeall_(buf, 1); #ifdef USE_OTA_PASSWORD @@ -241,7 +249,6 @@ void OTAComponent::handle_() { } ESP_LOGV(TAG, "OTA size is %u bytes", ota_size); - backend = make_ota_backend(); error_code = backend->begin(ota_size); if (error_code != OTA_RESPONSE_OK) goto error; diff --git a/esphome/components/ota/ota_component.h b/esphome/components/ota/ota_component.h index e08e187df6..5647d52eeb 100644 --- a/esphome/components/ota/ota_component.h +++ b/esphome/components/ota/ota_component.h @@ -19,6 +19,7 @@ enum OTAResponseTypes { OTA_RESPONSE_BIN_MD5_OK = 67, OTA_RESPONSE_RECEIVE_OK = 68, OTA_RESPONSE_UPDATE_END_OK = 69, + OTA_RESPONSE_SUPPORTS_COMPRESSION = 70, OTA_RESPONSE_ERROR_MAGIC = 128, OTA_RESPONSE_ERROR_UPDATE_PREPARE = 129, diff --git a/esphome/espota2.py b/esphome/espota2.py index f8a2fab94c..8f299395dd 100644 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -4,6 +4,7 @@ import random import socket import sys import time +import gzip from esphome.core import EsphomeError from esphome.helpers import is_ip_address, resolve_ip_address @@ -17,6 +18,7 @@ RESPONSE_UPDATE_PREPARE_OK = 66 RESPONSE_BIN_MD5_OK = 67 RESPONSE_RECEIVE_OK = 68 RESPONSE_UPDATE_END_OK = 69 +RESPONSE_SUPPORTS_COMPRESSION = 70 RESPONSE_ERROR_MAGIC = 128 RESPONSE_ERROR_UPDATE_PREPARE = 129 @@ -34,6 +36,8 @@ OTA_VERSION_1_0 = 1 MAGIC_BYTES = [0x6C, 0x26, 0xF7, 0x5C, 0x45] +FEATURE_SUPPORTS_COMPRESSION = 0x01 + _LOGGER = logging.getLogger(__name__) @@ -170,11 +174,9 @@ def send_check(sock, data, msg): def perform_ota(sock, password, file_handle, filename): - file_md5 = hashlib.md5(file_handle.read()).hexdigest() - file_size = file_handle.tell() + file_contents = file_handle.read() + file_size = len(file_contents) _LOGGER.info("Uploading %s (%s bytes)", filename, file_size) - file_handle.seek(0) - _LOGGER.debug("MD5 of binary is %s", file_md5) # Enable nodelay, we need it for phase 1 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) @@ -185,8 +187,16 @@ def perform_ota(sock, password, file_handle, filename): raise OTAError(f"Unsupported OTA version {version}") # Features - send_check(sock, 0x00, "features") - receive_exactly(sock, 1, "features", RESPONSE_HEADER_OK) + send_check(sock, FEATURE_SUPPORTS_COMPRESSION, "features") + features = receive_exactly( + sock, 1, "features", [RESPONSE_HEADER_OK, RESPONSE_SUPPORTS_COMPRESSION] + )[0] + + if features == RESPONSE_SUPPORTS_COMPRESSION: + upload_contents = gzip.compress(file_contents, compresslevel=9) + _LOGGER.info("Compressed to %s bytes", len(upload_contents)) + else: + upload_contents = file_contents (auth,) = receive_exactly( sock, 1, "auth", [RESPONSE_REQUEST_AUTH, RESPONSE_AUTH_OK] @@ -213,16 +223,20 @@ def perform_ota(sock, password, file_handle, filename): send_check(sock, result, "auth result") receive_exactly(sock, 1, "auth result", RESPONSE_AUTH_OK) - file_size_encoded = [ - (file_size >> 24) & 0xFF, - (file_size >> 16) & 0xFF, - (file_size >> 8) & 0xFF, - (file_size >> 0) & 0xFF, + upload_size = len(upload_contents) + upload_size_encoded = [ + (upload_size >> 24) & 0xFF, + (upload_size >> 16) & 0xFF, + (upload_size >> 8) & 0xFF, + (upload_size >> 0) & 0xFF, ] - send_check(sock, file_size_encoded, "binary size") + send_check(sock, upload_size_encoded, "binary size") receive_exactly(sock, 1, "binary size", RESPONSE_UPDATE_PREPARE_OK) - send_check(sock, file_md5, "file checksum") + upload_md5 = hashlib.md5(upload_contents).hexdigest() + _LOGGER.debug("MD5 of upload is %s", upload_md5) + + send_check(sock, upload_md5, "file checksum") receive_exactly(sock, 1, "file checksum", RESPONSE_BIN_MD5_OK) # Disable nodelay for transfer @@ -236,7 +250,7 @@ def perform_ota(sock, password, file_handle, filename): offset = 0 progress = ProgressBar() while True: - chunk = file_handle.read(1024) + chunk = upload_contents[offset : offset + 1024] if not chunk: break offset += len(chunk) @@ -247,7 +261,7 @@ def perform_ota(sock, password, file_handle, filename): sys.stderr.write("\n") raise OTAError(f"Error sending data: {err}") from err - progress.update(offset / float(file_size)) + progress.update(offset / upload_size) progress.done() # Enable nodelay for last checks From b5b3914bbfd28f7bbf2a3aa55113752e0df1a1fe Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 14:14:07 +0200 Subject: [PATCH 1614/1841] Re-raise keyboardinterrupt (#2603) --- esphome/util.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/util.py b/esphome/util.py index 0f168cade3..937635fa43 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -192,8 +192,8 @@ def run_external_command( sys.argv = list(cmd) sys.exit = mock_exit return func() or 0 - except KeyboardInterrupt: - return 1 + except KeyboardInterrupt: # pylint: disable=try-except-raise + raise except SystemExit as err: return err.args[0] except Exception as err: # pylint: disable=broad-except @@ -227,6 +227,8 @@ def run_external_process(*cmd, **kwargs): try: return subprocess.call(cmd, stdout=sub_stdout, stderr=sub_stderr) + except KeyboardInterrupt: # pylint: disable=try-except-raise + raise except Exception as err: # pylint: disable=broad-except _LOGGER.error("Running command failed: %s", err) _LOGGER.error("Please try running %s locally.", full_cmd) From 83bef854157e20210b7917baf54c86e645a7869c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 14:14:14 +0200 Subject: [PATCH 1615/1841] Add owner to all libraries used (#2604) --- esphome/components/bme680_bsec/__init__.py | 2 +- esphome/components/mqtt/__init__.py | 2 +- esphome/components/web_server_base/__init__.py | 2 +- platformio.ini | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/bme680_bsec/__init__.py b/esphome/components/bme680_bsec/__init__.py index 38da18d702..2f844fa666 100644 --- a/esphome/components/bme680_bsec/__init__.py +++ b/esphome/components/bme680_bsec/__init__.py @@ -67,4 +67,4 @@ async def to_code(config): cg.add_library("SPI", None) cg.add_define("USE_BSEC") - cg.add_library("BSEC Software Library", "1.6.1480") + cg.add_library("boschsensortec/BSEC Software Library", "1.6.1480") diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 8f02f8d437..3d52dab67f 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -215,7 +215,7 @@ async def to_code(config): await cg.register_component(var, config) # https://github.com/OttoWinter/async-mqtt-client/blob/master/library.json - cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.4") + cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.6") cg.add_define("USE_MQTT") cg.add_global(mqtt_ns.using) diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 95d59a863e..019f31954f 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -28,4 +28,4 @@ async def to_code(config): cg.add_library("FS", None) cg.add_library("Update", None) # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json - cg.add_library("esphome/ESPAsyncWebServer-esphome", "1.3.0") + cg.add_library("esphome/ESPAsyncWebServer-esphome", "1.3.1") diff --git a/platformio.ini b/platformio.ini index ac5144fc37..3d720e24ac 100644 --- a/platformio.ini +++ b/platformio.ini @@ -39,9 +39,9 @@ src_filter = extends = common lib_deps = ${common.lib_deps} - ottowinter/AsyncMqttClient-esphome@0.8.4 ; mqtt + ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt ottowinter/ArduinoJson-esphomelib@5.13.3 ; json - esphome/ESPAsyncWebServer-esphome@1.3.0 ; web_server_base + esphome/ESPAsyncWebServer-esphome@1.3.1 ; web_server_base fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps freekode/TM1651@1.0.1 ; tm1651 From 6db9d1122f417c67054ba6e4dad25c9e95276a94 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 16:52:43 +0200 Subject: [PATCH 1616/1841] Fix compiler warnings and update platformio line filter (#2607) --- esphome/components/climate/climate.cpp | 4 ++++ esphome/components/mqtt/mqtt_fan.cpp | 6 ++++++ esphome/components/web_server/web_server.cpp | 6 ++++++ esphome/components/web_server_base/__init__.py | 2 +- esphome/platformio_api.py | 17 ++++++++++++----- platformio.ini | 2 +- 6 files changed, 30 insertions(+), 7 deletions(-) diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 34e6328d8a..ebea20ed1f 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -440,7 +440,11 @@ void Climate::set_visual_max_temperature_override(float visual_max_temperature_o void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) { this->visual_temperature_step_override_ = visual_temperature_step_override; } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" Climate::Climate(const std::string &name) : EntityBase(name) {} +#pragma GCC diagnostic pop + Climate::Climate() : Climate("") {} ClimateCall Climate::make_call() { return ClimateCall(this); } diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index 898183cc58..1703343a77 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -88,9 +88,12 @@ void MQTTFanComponent::setup() { if (this->state_->get_traits().supports_speed()) { this->subscribe(this->get_speed_command_topic(), [this](const std::string &topic, const std::string &payload) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" this->state_->make_call() .set_speed(payload.c_str()) // NOLINT(clang-diagnostic-deprecated-declarations) .perform(); +#pragma GCC diagnostic pop }); } @@ -145,6 +148,8 @@ bool MQTTFanComponent::publish_state() { } if (traits.supports_speed()) { const char *payload; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) switch (fan::speed_level_to_enum(this->state_->speed, traits.supported_speed_count())) { case FAN_SPEED_LOW: { // NOLINT(clang-diagnostic-deprecated-declarations) @@ -161,6 +166,7 @@ bool MQTTFanComponent::publish_state() { break; } } +#pragma GCC diagnostic pop bool success = this->publish(this->get_speed_state_topic(), payload); failed = failed || !success; } diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index e99431be36..44ace38990 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -414,6 +414,8 @@ std::string WebServer::fan_json(fan::FanState *obj) { const auto traits = obj->get_traits(); if (traits.supports_speed()) { root["speed_level"] = obj->speed; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) switch (fan::speed_level_to_enum(obj->speed, traits.supported_speed_count())) { case fan::FAN_SPEED_LOW: // NOLINT(clang-diagnostic-deprecated-declarations) @@ -426,6 +428,7 @@ std::string WebServer::fan_json(fan::FanState *obj) { root["speed"] = "high"; break; } +#pragma GCC diagnostic pop } if (obj->get_traits().supports_oscillation()) root["oscillation"] = obj->oscillating; @@ -448,7 +451,10 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc auto call = obj->turn_on(); if (request->hasParam("speed")) { String speed = request->getParam("speed")->value(); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" call.set_speed(speed.c_str()); // NOLINT(clang-diagnostic-deprecated-declarations) +#pragma GCC diagnostic pop } if (request->hasParam("speed_level")) { String speed_level = request->getParam("speed_level")->value(); diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 019f31954f..4da94d990a 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -28,4 +28,4 @@ async def to_code(config): cg.add_library("FS", None) cg.add_library("Update", None) # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json - cg.add_library("esphome/ESPAsyncWebServer-esphome", "1.3.1") + cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.0.0") diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 054c0cb1b0..70e4430e71 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -46,24 +46,31 @@ IGNORE_LIB_WARNINGS = f"(?:{'|'.join(['Hash', 'Update'])})" FILTER_PLATFORMIO_LINES = [ r"Verbose mode can be enabled via `-v, --verbose` option.*", r"CONFIGURATION: https://docs.platformio.org/.*", - r"PLATFORM: .*", r"DEBUG: Current.*", - r"PACKAGES: .*", + r"LDF Modes:.*", r"LDF: Library Dependency Finder -> http://bit.ly/configure-pio-ldf.*", - r"LDF Modes: Finder ~ chain, Compatibility ~ soft.*", f"Looking for {IGNORE_LIB_WARNINGS} library in registry", f"Warning! Library `.*'{IGNORE_LIB_WARNINGS}.*` has not been found in PlatformIO Registry.", f"You can ignore this message, if `.*{IGNORE_LIB_WARNINGS}.*` is a built-in library.*", r"Scanning dependencies...", r"Found \d+ compatible libraries", r"Memory Usage -> http://bit.ly/pio-memory-usage", - r"esptool.py v.*", r"Found: https://platformio.org/lib/show/.*", r"Using cache: .*", r"Installing dependencies", - r".* @ .* is already installed", + r"Library Manager: Already installed, built-in library", r"Building in .* mode", r"Advanced Memory Usage is available via .*", + r"Merged .* ELF section", + r"esptool.py v.*", + r"Checking size .*", + r"Retrieving maximum program size .*", + r"PLATFORM: .*", + r"PACKAGES:.*", + r" - framework-arduinoespressif.* \(.*\)", + r" - tool-esptool.* \(.*\)", + r" - toolchain-.* \(.*\)", + r"Creating BIN file .*", ] diff --git a/platformio.ini b/platformio.ini index 3d720e24ac..ee895ed882 100644 --- a/platformio.ini +++ b/platformio.ini @@ -41,7 +41,7 @@ lib_deps = ${common.lib_deps} ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt ottowinter/ArduinoJson-esphomelib@5.13.3 ; json - esphome/ESPAsyncWebServer-esphome@1.3.1 ; web_server_base + esphome/ESPAsyncWebServer-esphome@2.0.0 ; web_server_base fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps freekode/TM1651@1.0.1 ; tm1651 From 77a6461c9d873a932db9db44af8d31a872f7230c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 17:23:31 +0200 Subject: [PATCH 1617/1841] Fix ESP8266 OTA compression only starting framework v2.7.0 (#2610) --- esphome/components/ota/ota_backend_arduino_esp8266.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.h b/esphome/components/ota/ota_backend_arduino_esp8266.h index cf29a90fc1..329f2cf0f2 100644 --- a/esphome/components/ota/ota_backend_arduino_esp8266.h +++ b/esphome/components/ota/ota_backend_arduino_esp8266.h @@ -5,6 +5,7 @@ #include "ota_component.h" #include "ota_backend.h" +#include "esphome/core/macros.h" namespace esphome { namespace ota { @@ -16,7 +17,11 @@ class ArduinoESP8266OTABackend : public OTABackend { OTAResponseTypes write(uint8_t *data, size_t len) override; OTAResponseTypes end() override; void abort() override; +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) bool supports_compression() override { return true; } +#else + bool supports_compression() override { return false; } +#endif }; } // namespace ota From 83400d0417d3df46ab8e17e76e6912823e16ce35 Mon Sep 17 00:00:00 2001 From: Andreas Hergert <36455093+andreashergert1984@users.noreply.github.com> Date: Fri, 22 Oct 2021 18:20:57 +0200 Subject: [PATCH 1618/1841] Bugfix tca9548a and idf refactor anh (#2612) Co-authored-by: Andreas Hergert --- esphome/components/tca9548a/tca9548a.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tca9548a/tca9548a.cpp b/esphome/components/tca9548a/tca9548a.cpp index 5117ad8969..e902eb5ed4 100644 --- a/esphome/components/tca9548a/tca9548a.cpp +++ b/esphome/components/tca9548a/tca9548a.cpp @@ -22,7 +22,7 @@ i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffer void TCA9548AComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up TCA9548A..."); uint8_t status = 0; - if (!this->read_register(0x00, &status, 1)) { + if (this->read_register(0x00, &status, 1) != i2c::ERROR_OK) { ESP_LOGI(TAG, "TCA9548A failed"); this->mark_failed(); return; From 1c4700f447df78ebc5ff1f587d2fb66caa5575de Mon Sep 17 00:00:00 2001 From: Andreas Hergert <36455093+andreashergert1984@users.noreply.github.com> Date: Fri, 22 Oct 2021 18:52:47 +0200 Subject: [PATCH 1619/1841] fixed dependency for pca9685 component (#2614) Co-authored-by: Otto Winter Co-authored-by: Andreas --- esphome/components/pca9685/pca9685_output.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/pca9685/pca9685_output.cpp b/esphome/components/pca9685/pca9685_output.cpp index 1ad6f4a665..957f4062fc 100644 --- a/esphome/components/pca9685/pca9685_output.cpp +++ b/esphome/components/pca9685/pca9685_output.cpp @@ -1,6 +1,7 @@ #include "pca9685_output.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" namespace esphome { namespace pca9685 { From d85b7a6bd079cba75f4a28380965f1d4975c0bf8 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 23 Oct 2021 12:37:50 +0200 Subject: [PATCH 1620/1841] Bump platform-espressif8266 from 2.6.2 to 2.6.3 (#2620) --- esphome/components/esp8266/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index ddaeee6ab7..b2706bd4fb 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -63,7 +63,7 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 7, 4) # The platformio/espressif8266 version to use for arduino 2 framework versions # - https://github.com/platformio/platform-espressif8266/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif8266 -ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 2) +ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 3) # for arduino 3 framework versions ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 2, 0) From 1a6a063e044cd0ad335e75db778377ab78ab5a62 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 23 Oct 2021 12:38:57 +0200 Subject: [PATCH 1621/1841] Move default build path to .esphome directory (#2586) --- esphome/core/config.py | 2 +- esphome/platformio_api.py | 2 +- esphome/writer.py | 77 ++------------------------------------- 3 files changed, 6 insertions(+), 75 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index ad132968b9..04cac29344 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -165,7 +165,7 @@ def preload_core_config(config, result): CORE.data[KEY_CORE] = {} if CONF_BUILD_PATH not in conf: - conf[CONF_BUILD_PATH] = CORE.name + conf[CONF_BUILD_PATH] = f".esphome/build/{CORE.name}" CORE.build_path = CORE.relative_config_path(conf[CONF_BUILD_PATH]) has_oldstyle = CONF_PLATFORM in conf diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 70e4430e71..2072e25ec5 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -125,7 +125,7 @@ def _run_idedata(config): def _load_idedata(config): platformio_ini = Path(CORE.relative_build_path("platformio.ini")) - temp_idedata = Path(CORE.relative_internal_path(CORE.name, "idedata.json")) + temp_idedata = Path(CORE.relative_internal_path("idedata", f"{CORE.name}.json")) changed = False if not platformio_ini.is_file() or not temp_idedata.is_file(): diff --git a/esphome/writer.py b/esphome/writer.py index 29532d4f64..8963572752 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -38,10 +38,8 @@ CPP_BASE_FORMAT = ( """" void setup() { - // ===== DO NOT EDIT ANYTHING BELOW THIS LINE ===== """, """ - // ========= YOU CAN EDIT AFTER THIS LINE ========= App.setup(); } @@ -59,10 +57,8 @@ lib_deps = build_flags = upload_flags = -; ===== DO NOT EDIT ANYTHING BELOW THIS LINE ===== """, """ -; ========= YOU CAN EDIT AFTER THIS LINE ========= """, ) @@ -102,61 +98,6 @@ def replace_file_content(text, pattern, repl): return content_new, count -def migrate_src_version_0_to_1(): - main_cpp = CORE.relative_build_path("src", "main.cpp") - if not os.path.isfile(main_cpp): - return - - content = read_file(main_cpp) - - if CPP_INCLUDE_BEGIN in content: - return - - content, count = replace_file_content(content, r"\s*delay\((?:16|20)\);", "") - if count != 0: - _LOGGER.info( - "Migration: Removed %s occurrence of 'delay(16);' in %s", count, main_cpp - ) - - content, count = replace_file_content(content, r"using namespace esphomelib;", "") - if count != 0: - _LOGGER.info( - "Migration: Removed %s occurrence of 'using namespace esphomelib;' " - "in %s", - count, - main_cpp, - ) - - if CPP_INCLUDE_BEGIN not in content: - content, count = replace_file_content( - content, - r'#include "esphomelib/application.h"', - f"{CPP_INCLUDE_BEGIN}\n{CPP_INCLUDE_END}", - ) - if count == 0: - _LOGGER.error( - "Migration failed. ESPHome 1.10.0 needs to have a new auto-generated " - "include section in the %s file. Please remove %s and let it be " - "auto-generated again.", - main_cpp, - main_cpp, - ) - _LOGGER.info("Migration: Added include section to %s", main_cpp) - - write_file_if_changed(main_cpp, content) - - -def migrate_src_version(old, new): - if old == new: - return - if old > new: - _LOGGER.warning("The source version rolled backwards! Ignoring.") - return - - if old == 0: - migrate_src_version_0_to_1() - - def storage_should_clean(old, new): # type: (StorageJSON, StorageJSON) -> bool if old is None: return True @@ -175,9 +116,6 @@ def update_storage_json(): if old == new: return - old_src_version = old.src_version if old is not None else 0 - migrate_src_version(old_src_version, new.src_version) - if storage_should_clean(old, new): _LOGGER.info("Core config or version changed, cleaning build files...") clean_build() @@ -277,12 +215,12 @@ VERSION_H_TARGET = "esphome/core/version.h" ESPHOME_README_TXT = """ THIS DIRECTORY IS AUTO-GENERATED, DO NOT MODIFY -ESPHome automatically populates the esphome/ directory, and any +ESPHome automatically populates the build directory, and any changes to this directory will be removed the next time esphome is run. -For modifying esphome's core files, please use a development esphome install -or use the custom_components folder. +For modifying esphome's core files, please use a development esphome install, +the custom_components folder or the external_components feature. """ @@ -339,9 +277,7 @@ def copy_src_tree(): write_file_if_changed( CORE.relative_src_path("esphome", "core", "defines.h"), generate_defines_h() ) - write_file_if_changed( - CORE.relative_src_path("esphome", "README.txt"), ESPHOME_README_TXT - ) + write_file_if_changed(CORE.relative_build_path("README.txt"), ESPHOME_README_TXT) write_file_if_changed( CORE.relative_src_path("esphome.h"), ESPHOME_H_FORMAT.format(include_s) ) @@ -413,11 +349,6 @@ GITIGNORE_CONTENT = """# Gitignore settings for ESPHome # This is an example and may include too much for your use-case. # You can modify this file to suit your needs. /.esphome/ -**/.pioenvs/ -**/.piolibdeps/ -**/lib/ -**/src/ -**/platformio.ini /secrets.yaml """ From b9e5c7eb35678fe1147575685fc03030c3f1713d Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sat, 23 Oct 2021 13:25:46 +0200 Subject: [PATCH 1622/1841] Autodetect flash size (#2615) --- esphome/__main__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/__main__.py b/esphome/__main__.py index 97059154fd..c2a6dd343f 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -226,6 +226,8 @@ def upload_using_esptool(config, port): mcu, "write_flash", "-z", + "--flash_size", + "detect", ] for img in flash_images: cmd += [img.offset, img.path] From a687b083aed516cbfe93ed25dc51a31837f95fb0 Mon Sep 17 00:00:00 2001 From: 0hax <43876620+0hax@users.noreply.github.com> Date: Sat, 23 Oct 2021 19:01:23 +0200 Subject: [PATCH 1623/1841] Teleinfo ptec (#2599) * teleinfo: handle historical mode correctly. In historical mode, tags like PTEC leads to an issue where we detect a timestamp wheras this is not possible in historical mode. PTEC teleinfo tag looks like: PTEC HP.. Instead of the usual format IINST1 001 I This make our data parsing fails. While at here, make sure we continue parsing other tags even if parsing one of the tag fails. Signed-off-by: 0hax <0hax@protonmail.com> * teleinfo: fix compilation with loglevel set to debug. Signed-off-by: 0hax <0hax@protonmail.com> --- .../teleinfo/sensor/teleinfo_sensor.cpp | 2 +- esphome/components/teleinfo/teleinfo.cpp | 17 +++++++++-------- .../text_sensor/teleinfo_text_sensor.cpp | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp b/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp index 661c149c09..4e4cd9f9e6 100644 --- a/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp +++ b/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp @@ -9,6 +9,6 @@ void TeleInfoSensor::publish_val(const std::string &val) { auto newval = parse_float(val); publish_state(*newval); } -void TeleInfoSensor::dump_config() { LOG_SENSOR(" ", tag.c_str(), this); } +void TeleInfoSensor::dump_config() { LOG_SENSOR(" ", "Teleinfo Sensor", this); } } // namespace teleinfo } // namespace esphome diff --git a/esphome/components/teleinfo/teleinfo.cpp b/esphome/components/teleinfo/teleinfo.cpp index badd66ae83..5a1e44ac8b 100644 --- a/esphome/components/teleinfo/teleinfo.cpp +++ b/esphome/components/teleinfo/teleinfo.cpp @@ -141,21 +141,22 @@ void TeleInfo::loop() { field_len = get_field(tag_, buf_finger, grp_end, separator_, MAX_TAG_SIZE); if (!field_len || field_len >= MAX_TAG_SIZE) { ESP_LOGE(TAG, "Invalid tag."); - break; + continue; } /* Advance buf_finger to after the tag and the separator. */ buf_finger += field_len + 1; /* - * If there is two separators and the tag is not equal to "DATE", - * it means there is a timestamp to read first. + * If there is two separators and the tag is not equal to "DATE" or + * historical mode is not in use (separator_ != 0x20), it means there is a + * timestamp to read first. */ - if (std::count(buf_finger, grp_end, separator_) == 2 && strcmp(tag_, "DATE") != 0) { + if (std::count(buf_finger, grp_end, separator_) == 2 && strcmp(tag_, "DATE") != 0 && separator_ != 0x20) { field_len = get_field(timestamp_, buf_finger, grp_end, separator_, MAX_TIMESTAMP_SIZE); if (!field_len || field_len >= MAX_TIMESTAMP_SIZE) { - ESP_LOGE(TAG, "Invalid Timestamp"); - break; + ESP_LOGE(TAG, "Invalid timestamp for tag %s", timestamp_); + continue; } /* Advance buf_finger to after the first data and the separator. */ @@ -164,8 +165,8 @@ void TeleInfo::loop() { field_len = get_field(val_, buf_finger, grp_end, separator_, MAX_VAL_SIZE); if (!field_len || field_len >= MAX_VAL_SIZE) { - ESP_LOGE(TAG, "Invalid Value"); - break; + ESP_LOGE(TAG, "Invalid value for tag %s", tag_); + continue; } /* Advance buf_finger to end of group */ diff --git a/esphome/components/teleinfo/text_sensor/teleinfo_text_sensor.cpp b/esphome/components/teleinfo/text_sensor/teleinfo_text_sensor.cpp index 1adbd9ce13..87cf0dea17 100644 --- a/esphome/components/teleinfo/text_sensor/teleinfo_text_sensor.cpp +++ b/esphome/components/teleinfo/text_sensor/teleinfo_text_sensor.cpp @@ -6,6 +6,6 @@ namespace teleinfo { static const char *const TAG = "teleinfo_text_sensor"; TeleInfoTextSensor::TeleInfoTextSensor(const char *tag) { this->tag = std::string(tag); } void TeleInfoTextSensor::publish_val(const std::string &val) { publish_state(val); } -void TeleInfoTextSensor::dump_config() { LOG_TEXT_SENSOR(" ", tag.c_str(), this); } +void TeleInfoTextSensor::dump_config() { LOG_TEXT_SENSOR(" ", "Teleinfo Text Sensor", this); } } // namespace teleinfo } // namespace esphome From 8e77e3c685b4f6a37fde1b04a31fdf87e6f504a2 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 23 Oct 2021 19:25:53 +0200 Subject: [PATCH 1624/1841] Fix glue code missing micros() (#2623) --- esphome/core/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/core/config.py b/esphome/core/config.py index 04cac29344..68c253f7b4 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -237,6 +237,7 @@ def include_file(path, basename): ARDUINO_GLUE_CODE = """\ #define yield() esphome::yield() #define millis() esphome::millis() +#define micros() esphome::micros() #define delay(x) esphome::delay(x) #define delayMicroseconds(x) esphome::delayMicroseconds(x) """ From de06a781ff205d9b5c03ca7e0518210df1117c03 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 23 Oct 2021 19:44:55 +0200 Subject: [PATCH 1625/1841] ESP8266 disable PIO LDF (#2608) --- esphome/components/captive_portal/__init__.py | 2 ++ esphome/components/esp8266/__init__.py | 2 ++ esphome/components/http_request/__init__.py | 2 ++ esphome/components/spi/__init__.py | 4 +++- esphome/components/web_server_base/__init__.py | 2 +- platformio.ini | 2 +- 6 files changed, 11 insertions(+), 3 deletions(-) diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index 384a3f23a0..f024c94b01 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -36,3 +36,5 @@ async def to_code(config): if CORE.is_esp32: cg.add_library("DNSServer", None) cg.add_library("WiFi", None) + if CORE.is_esp8266: + cg.add_library("DNSServer", None) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index b2706bd4fb..7c7758b943 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -142,6 +142,8 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): cg.add(esp8266_ns.setup_preferences()) + cg.add_platformio_option("lib_ldf_mode", "off") + cg.add_platformio_option("board", config[CONF_BOARD]) cg.add_build_flag("-DUSE_ESP8266") cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index 6e249c4247..774d6a0f91 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -96,6 +96,8 @@ async def to_code(config): if CORE.is_esp32: cg.add_library("WiFiClientSecure", None) cg.add_library("HTTPClient", None) + if CORE.is_esp8266: + cg.add_library("ESP8266HTTPClient", None) await cg.register_component(var, config) diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index 3a96cce99b..c917fe1ad8 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -46,7 +46,9 @@ async def to_code(config): mosi = await cg.gpio_pin_expression(config[CONF_MOSI_PIN]) cg.add(var.set_mosi(mosi)) - if CORE.is_esp32: + if CORE.is_esp32 and CORE.using_arduino: + cg.add_library("SPI", None) + if CORE.is_esp8266: cg.add_library("SPI", None) diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 4da94d990a..dc1a2bc2f0 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -28,4 +28,4 @@ async def to_code(config): cg.add_library("FS", None) cg.add_library("Update", None) # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json - cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.0.0") + cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.0.1") diff --git a/platformio.ini b/platformio.ini index ee895ed882..fa8944bf9a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -41,7 +41,7 @@ lib_deps = ${common.lib_deps} ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt ottowinter/ArduinoJson-esphomelib@5.13.3 ; json - esphome/ESPAsyncWebServer-esphome@2.0.0 ; web_server_base + esphome/ESPAsyncWebServer-esphome@2.0.1 ; web_server_base fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps freekode/TM1651@1.0.1 ; tm1651 From 49b17c5a2d0a2a2eab215d1cc2991298e38019f4 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 23 Oct 2021 21:58:04 +0200 Subject: [PATCH 1626/1841] Switch issue-close-app to GH Actions and workflow cleanup (#2624) --- .github/issue-close-app.yml | 7 ------- .github/lock.yml | 36 ------------------------------------ .github/workflows/lock.yml | 14 ++++++++++---- .github/workflows/stale.yml | 20 +++++++++++++++++++- 4 files changed, 29 insertions(+), 48 deletions(-) delete mode 100644 .github/issue-close-app.yml delete mode 100644 .github/lock.yml diff --git a/.github/issue-close-app.yml b/.github/issue-close-app.yml deleted file mode 100644 index 5f5fb7572d..0000000000 --- a/.github/issue-close-app.yml +++ /dev/null @@ -1,7 +0,0 @@ -comment: >- - https://github.com/esphome/esphome/issues/430 -issueConfigs: -- content: - - "OTHERWISE THE ISSUE WILL BE CLOSED AUTOMATICALLY" - -caseInsensitive: false diff --git a/.github/lock.yml b/.github/lock.yml deleted file mode 100644 index 0680577b2e..0000000000 --- a/.github/lock.yml +++ /dev/null @@ -1,36 +0,0 @@ -# Configuration for Lock Threads - https://github.com/dessant/lock-threads - -# Number of days of inactivity before a closed issue or pull request is locked -daysUntilLock: 7 - -# Skip issues and pull requests created before a given timestamp. Timestamp must -# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable -skipCreatedBefore: false - -# Issues and pull requests with these labels will be ignored. Set to `[]` to disable -exemptLabels: - - keep-open - -# Label to add before locking, such as `outdated`. Set to `false` to disable -lockLabel: false - -# Comment to post before locking. Set to `false` to disable -lockComment: false - -# Assign `resolved` as the reason for locking. Set to `false` to disable -setLockReason: false - -# Limit to only `issues` or `pulls` -# only: issues - -# Optionally, specify configuration settings just for `issues` or `pulls` -# issues: -# exemptLabels: -# - help-wanted -# lockLabel: outdated - -# pulls: -# daysUntilLock: 30 - -# Repository to extend settings from -# _extends: repo diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 375b8f1db4..ceb45b2a91 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -9,13 +9,19 @@ permissions: issues: write pull-requests: write +concurrency: + group: lock + jobs: lock: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v2 + - uses: dessant/lock-threads@v3 with: - github-token: ${{ github.token }} - pr-lock-inactive-days: "1" + pr-inactive-days: "1" pr-lock-reason: "" - process-only: prs + exclude-any-pr-labels: keep-open + + issue-inactive-days: "7" + issue-lock-reason: "" + exclude-any-issue-labels: keep-open diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 712ae1a289..c3e450d0cf 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -9,13 +9,15 @@ permissions: issues: write pull-requests: write +concurrency: + group: lock + jobs: stale: runs-on: ubuntu-latest steps: - uses: actions/stale@v4 with: - repo-token: ${{ github.token }} days-before-pr-stale: 90 days-before-pr-close: 7 days-before-issue-stale: -1 @@ -28,3 +30,19 @@ jobs: pull request has been automatically marked as stale because of that and will be closed if no further activity occurs within 7 days. Thank you for your contributions. + + # Use stale to automatically close issues with a reference to the issue tracker + close-issues: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v4 + with: + days-before-pr-stale: -1 + days-before-pr-close: -1 + days-before-issue-stale: 1 + days-before-issue-close: 1 + remove-stale-when-updated: true + stale-issue-label: "stale" + exempt-issue-labels: "not-stale" + stale-issue-message: > + https://github.com/esphome/esphome/issues/430 From 81c11ba1f71249ef8b3c9bb6d72e65dcda4c6960 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 25 Oct 2021 21:53:47 +0200 Subject: [PATCH 1627/1841] relax max entities checking (#2629) --- esphome/components/modbus/modbus.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 45c5bfb603..9524f9daf4 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -139,7 +139,9 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address uint8_t payload_len, const uint8_t *payload) { static const size_t MAX_VALUES = 128; - if (number_of_entities > MAX_VALUES) { + // Only check max number of registers for standard function codes + // Some devices use non standard codes like 0x43 + if (number_of_entities > MAX_VALUES && function_code <= 0x10) { ESP_LOGE(TAG, "send too many values %d max=%zu", number_of_entities, MAX_VALUES); return; } From 87328686a0b0ad81f0061094e904068579d11539 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 26 Oct 2021 10:55:09 +0200 Subject: [PATCH 1628/1841] Allow setting URL as platform_version (#2598) --- esphome/components/esp32/__init__.py | 31 ++++++++++++++++---------- esphome/components/esp8266/__init__.py | 23 ++++++++++++------- esphome/config_validation.py | 19 ++++++++++++++++ 3 files changed, 53 insertions(+), 20 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index db24b9aa29..d84663b2d6 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -147,8 +147,9 @@ def _arduino_check_versions(value): value[CONF_VERSION] = str(version) value[CONF_SOURCE] = source or _format_framework_arduino_version(version) - platform_version = value.get(CONF_PLATFORM_VERSION, ARDUINO_PLATFORM_VERSION) - value[CONF_PLATFORM_VERSION] = str(platform_version) + value[CONF_PLATFORM_VERSION] = value.get( + CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION)) + ) if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: _LOGGER.warning( @@ -184,8 +185,9 @@ def _esp_idf_check_versions(value): value[CONF_VERSION] = str(version) value[CONF_SOURCE] = source or _format_framework_espidf_version(version) - platform_version = value.get(CONF_PLATFORM_VERSION, ESP_IDF_PLATFORM_VERSION) - value[CONF_PLATFORM_VERSION] = str(platform_version) + value[CONF_PLATFORM_VERSION] = value.get( + CONF_PLATFORM_VERSION, _parse_platform_version(str(ESP_IDF_PLATFORM_VERSION)) + ) if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION: _LOGGER.warning( @@ -196,6 +198,15 @@ def _esp_idf_check_versions(value): return value +def _parse_platform_version(value): + try: + # if platform version is a valid version constraint, prefix the default package + cv.platformio_version_constraint(value) + return f"platformio/espressif32 @ {value}" + except cv.Invalid: + return value + + def _detect_variant(value): if CONF_VARIANT not in value: board = value[CONF_BOARD] @@ -218,7 +229,7 @@ ARDUINO_FRAMEWORK_SCHEMA = cv.All( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict, - cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, + cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, } ), _arduino_check_versions, @@ -230,7 +241,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict, - cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, + cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): { cv.string_strict: cv.string_strict }, @@ -280,10 +291,9 @@ async def to_code(config): cg.add_platformio_option("lib_ldf_mode", "off") conf = config[CONF_FRAMEWORK] + cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) + if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: - cg.add_platformio_option( - "platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}" - ) cg.add_platformio_option("framework", "espidf") cg.add_build_flag("-DUSE_ESP_IDF") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF") @@ -314,9 +324,6 @@ async def to_code(config): ) elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: - cg.add_platformio_option( - "platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}" - ) cg.add_platformio_option("framework", "arduino") cg.add_build_flag("-DUSE_ARDUINO") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 7c7758b943..5b97d2d9d5 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -93,12 +93,12 @@ def _arduino_check_versions(value): platform_version = value.get(CONF_PLATFORM_VERSION) if platform_version is None: if version >= cv.Version(3, 0, 0): - platform_version = ARDUINO_3_PLATFORM_VERSION + platform_version = _parse_platform_version(str(ARDUINO_3_PLATFORM_VERSION)) elif version >= cv.Version(2, 5, 0): - platform_version = ARDUINO_2_PLATFORM_VERSION + platform_version = _parse_platform_version(str(ARDUINO_2_PLATFORM_VERSION)) else: - platform_version = cv.Version(1, 8, 0) - value[CONF_PLATFORM_VERSION] = str(platform_version) + platform_version = _parse_platform_version(str(cv.Version(1, 8, 0))) + value[CONF_PLATFORM_VERSION] = platform_version if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: _LOGGER.warning( @@ -109,13 +109,22 @@ def _arduino_check_versions(value): return value +def _parse_platform_version(value): + try: + # if platform version is a valid version constraint, prefix the default package + cv.platformio_version_constraint(value) + return f"platformio/espressif8266 @ {value}" + except cv.Invalid: + return value + + CONF_PLATFORM_VERSION = "platform_version" ARDUINO_FRAMEWORK_SCHEMA = cv.All( cv.Schema( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict, - cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, + cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, } ), _arduino_check_versions, @@ -152,13 +161,11 @@ async def to_code(config): cg.add_platformio_option("framework", "arduino") cg.add_build_flag("-DUSE_ARDUINO") cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO") + cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) cg.add_platformio_option( "platform_packages", [f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"], ) - cg.add_platformio_option( - "platform", f"platformio/espressif8266 @ {conf[CONF_PLATFORM_VERSION]}" - ) # Default for platformio is LWIP2_LOW_MEMORY with: # - MSS=536 diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 5d4ff64193..fcd014f62d 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1668,6 +1668,25 @@ def version_number(value): raise Invalid("Not a version number") from e +def platformio_version_constraint(value): + # for documentation on valid version constraints: + # https://docs.platformio.org/en/latest/core/userguide/platforms/cmd_install.html#cmd-platform-install + + value = string_strict(value) + constraints = [] + for item in value.split(","): + # find and strip prefix operator + op = None + for test_op in ("^", "~", ">=", ">", "<=", "<", "!="): + if item.startswith(test_op): + op = test_op + item = item[len(test_op) :] + break + + constraints.append((op, version_number(item))) + return constraints + + def require_framework_version( *, esp_idf=None, From a01f5f5cf19213eec4501d7166e5dc64a3f34054 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 26 Oct 2021 21:55:20 +1300 Subject: [PATCH 1629/1841] Remove power and energy from sensors that are not true power (#2628) --- esphome/components/dsmr/sensor.py | 16 ++++++++-------- esphome/components/havells_solar/sensor.py | 4 +--- esphome/components/pipsolar/sensor/__init__.py | 4 ++-- esphome/components/sdm_meter/sensor.py | 4 ---- esphome/components/selec_meter/sensor.py | 8 -------- 5 files changed, 11 insertions(+), 25 deletions(-) diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 761009c766..d809d0d105 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -75,14 +75,14 @@ CONFIG_SCHEMA = cv.Schema( UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, ICON_EMPTY, 3, - DEVICE_CLASS_ENERGY, + DEVICE_CLASS_EMPTY, STATE_CLASS_NONE, ), cv.Optional("total_exported_energy"): sensor.sensor_schema( UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, ICON_EMPTY, 3, - DEVICE_CLASS_ENERGY, + DEVICE_CLASS_EMPTY, STATE_CLASS_NONE, ), cv.Optional("power_delivered"): sensor.sensor_schema( @@ -166,42 +166,42 @@ CONFIG_SCHEMA = cv.Schema( UNIT_KILOVOLT_AMPS_REACTIVE, ICON_EMPTY, 3, - DEVICE_CLASS_POWER, + DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l2"): sensor.sensor_schema( UNIT_KILOVOLT_AMPS_REACTIVE, ICON_EMPTY, 3, - DEVICE_CLASS_POWER, + DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l3"): sensor.sensor_schema( UNIT_KILOVOLT_AMPS_REACTIVE, ICON_EMPTY, 3, - DEVICE_CLASS_POWER, + DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l1"): sensor.sensor_schema( UNIT_KILOVOLT_AMPS_REACTIVE, ICON_EMPTY, 3, - DEVICE_CLASS_POWER, + DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l2"): sensor.sensor_schema( UNIT_KILOVOLT_AMPS_REACTIVE, ICON_EMPTY, 3, - DEVICE_CLASS_POWER, + DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l3"): sensor.sensor_schema( UNIT_KILOVOLT_AMPS_REACTIVE, ICON_EMPTY, 3, - DEVICE_CLASS_POWER, + DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l1"): sensor.sensor_schema( diff --git a/esphome/components/havells_solar/sensor.py b/esphome/components/havells_solar/sensor.py index 3ec12d5b83..d7c8d544f9 100644 --- a/esphome/components/havells_solar/sensor.py +++ b/esphome/components/havells_solar/sensor.py @@ -93,13 +93,12 @@ PV_SENSORS = { CONF_VOLTAGE_SAMPLED_BY_SECONDARY_CPU: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=0, - device_class=DEVICE_CLASS_POWER, + device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, ), CONF_INSULATION_OF_P_TO_GROUND: sensor.sensor_schema( unit_of_measurement=UNIT_KOHM, accuracy_decimals=0, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), } @@ -135,7 +134,6 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, accuracy_decimals=2, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ENERGY_PRODUCTION_DAY): sensor.sensor_schema( diff --git a/esphome/components/pipsolar/sensor/__init__.py b/esphome/components/pipsolar/sensor/__init__.py index 5e4dd6c40c..a206e41988 100644 --- a/esphome/components/pipsolar/sensor/__init__.py +++ b/esphome/components/pipsolar/sensor/__init__.py @@ -89,7 +89,7 @@ TYPES = { UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT ), CONF_AC_OUTPUT_RATING_APPARENT_POWER: sensor.sensor_schema( - UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_POWER + UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY ), CONF_AC_OUTPUT_RATING_ACTIVE_POWER: sensor.sensor_schema( UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER @@ -159,7 +159,7 @@ TYPES = { UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY ), CONF_AC_OUTPUT_APPARENT_POWER: sensor.sensor_schema( - UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_POWER + UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY ), CONF_AC_OUTPUT_ACTIVE_POWER: sensor.sensor_schema( UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER diff --git a/esphome/components/sdm_meter/sensor.py b/esphome/components/sdm_meter/sensor.py index 8a0d9674a7..87c99c9152 100644 --- a/esphome/components/sdm_meter/sensor.py +++ b/esphome/components/sdm_meter/sensor.py @@ -64,13 +64,11 @@ PHASE_SENSORS = { CONF_APPARENT_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS, accuracy_decimals=2, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), CONF_REACTIVE_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, accuracy_decimals=2, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), CONF_POWER_FACTOR: sensor.sensor_schema( @@ -115,13 +113,11 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_IMPORT_REACTIVE_ENERGY): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, - device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_EXPORT_REACTIVE_ENERGY): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, - device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), } diff --git a/esphome/components/selec_meter/sensor.py b/esphome/components/selec_meter/sensor.py index 168d3a3db2..e698255c25 100644 --- a/esphome/components/selec_meter/sensor.py +++ b/esphome/components/selec_meter/sensor.py @@ -71,25 +71,21 @@ SENSORS = { CONF_TOTAL_REACTIVE_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, - device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_IMPORT_REACTIVE_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, - device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_EXPORT_REACTIVE_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, - device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_APPARENT_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_HOURS, accuracy_decimals=2, - device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_ACTIVE_POWER: sensor.sensor_schema( @@ -101,13 +97,11 @@ SENSORS = { CONF_REACTIVE_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, accuracy_decimals=3, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), CONF_APPARENT_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS, accuracy_decimals=3, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), CONF_VOLTAGE: sensor.sensor_schema( @@ -142,13 +136,11 @@ SENSORS = { CONF_MAXIMUM_DEMAND_REACTIVE_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, accuracy_decimals=3, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), CONF_MAXIMUM_DEMAND_APPARENT_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS, accuracy_decimals=3, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), } From c612a3bf6074cf6cd2161a970d3e7d670344b0cf Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 26 Oct 2021 10:55:27 +0200 Subject: [PATCH 1630/1841] Constrain GH Actions workflows permissions (#2625) --- .github/workflows/ci-docker.yml | 4 ++++ .github/workflows/ci.yml | 3 +++ .github/workflows/release.yml | 9 +++++++++ 3 files changed, 16 insertions(+) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 12f5a7dfc2..1d1cc169b2 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -17,6 +17,10 @@ on: - 'requirements*.txt' - 'platformio.ini' +permissions: + contents: read + packages: read + jobs: check-docker: name: Build docker containers diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45e2f2735c..02b64d2bf5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,9 @@ on: pull_request: +permissions: + contents: read + jobs: ci: name: ${{ matrix.name }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index afd893d065..d6895becc0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,9 @@ on: schedule: - cron: "0 2 * * *" +permissions: + contents: read + jobs: init: name: Initialize build @@ -52,6 +55,9 @@ jobs: deploy-docker: name: Build and publish docker containers if: github.repository == 'esphome/esphome' + permissions: + contents: read + packages: write runs-on: ubuntu-latest needs: [init] strategy: @@ -93,6 +99,9 @@ jobs: deploy-docker-manifest: if: github.repository == 'esphome/esphome' + permissions: + contents: read + packages: write runs-on: ubuntu-latest needs: [init, deploy-docker] strategy: From 2f85c27a053a6b4553670bea90800a81b13d87f2 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Tue, 26 Oct 2021 11:30:25 +0200 Subject: [PATCH 1631/1841] fix modbus output (#2630) --- .../components/modbus_controller/__init__.py | 15 +++++++++++++++ .../modbus_controller/number/__init__.py | 17 +---------------- .../modbus_controller/output/__init__.py | 13 ++++++++++++- .../modbus_controller/output/modbus_output.h | 3 ++- .../modbus_controller/sensor/__init__.py | 18 +----------------- 5 files changed, 31 insertions(+), 35 deletions(-) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 6b452ea25c..8499cec561 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -61,6 +61,21 @@ SENSOR_VALUE_TYPE = { "FP32_R": SensorValueType.FP32_R, } +TYPE_REGISTER_MAP = { + "RAW": 1, + "U_WORD": 1, + "S_WORD": 1, + "U_DWORD": 2, + "U_DWORD_R": 2, + "S_DWORD": 2, + "S_DWORD_R": 2, + "U_QWORD": 4, + "U_QWORDU_R": 4, + "S_QWORD": 4, + "U_QWORD_R": 4, + "FP32": 2, + "FP32_R": 2, +} MULTI_CONF = True diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py index c7919bb972..afb69f8798 100644 --- a/esphome/components/modbus_controller/number/__init__.py +++ b/esphome/components/modbus_controller/number/__init__.py @@ -17,6 +17,7 @@ from .. import ( ModbusController, SENSOR_VALUE_TYPE, SensorItem, + TYPE_REGISTER_MAP, ) @@ -39,22 +40,6 @@ ModbusNumber = modbus_controller_ns.class_( "ModbusNumber", cg.Component, number.Number, SensorItem ) -TYPE_REGISTER_MAP = { - "RAW": 1, - "U_WORD": 1, - "S_WORD": 1, - "U_DWORD": 2, - "U_DWORD_R": 2, - "S_DWORD": 2, - "S_DWORD_R": 2, - "U_QWORD": 4, - "U_QWORDU_R": 4, - "S_QWORD": 4, - "U_QWORD_R": 4, - "FP32": 2, - "FP32_R": 2, -} - def validate_min_max(config): if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]: diff --git a/esphome/components/modbus_controller/output/__init__.py b/esphome/components/modbus_controller/output/__init__.py index 9c41fc011c..4aca4db64f 100644 --- a/esphome/components/modbus_controller/output/__init__.py +++ b/esphome/components/modbus_controller/output/__init__.py @@ -13,11 +13,13 @@ from .. import ( SensorItem, modbus_controller_ns, ModbusController, + TYPE_REGISTER_MAP, ) from ..const import ( CONF_BYTE_OFFSET, CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_COUNT, CONF_VALUE_TYPE, CONF_WRITE_LAMBDA, ) @@ -40,6 +42,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_OFFSET, default=0): cv.positive_int, cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), + cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int, cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, } @@ -54,8 +57,16 @@ async def to_code(config): # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET if CONF_BYTE_OFFSET in config: byte_offset = config[CONF_BYTE_OFFSET] + value_type = config[CONF_VALUE_TYPE] + reg_count = config[CONF_REGISTER_COUNT] + if reg_count == 0: + reg_count = TYPE_REGISTER_MAP[value_type] var = cg.new_Pvariable( - config[CONF_ID], config[CONF_ADDRESS], byte_offset, config[CONF_VALUE_TYPE] + config[CONF_ID], + config[CONF_ADDRESS], + byte_offset, + value_type, + reg_count, ) await output.register_output(var, config) cg.add(var.set_write_multiply(config[CONF_MULTIPLY])) diff --git a/esphome/components/modbus_controller/output/modbus_output.h b/esphome/components/modbus_controller/output/modbus_output.h index 053186a321..6e8521854b 100644 --- a/esphome/components/modbus_controller/output/modbus_output.h +++ b/esphome/components/modbus_controller/output/modbus_output.h @@ -11,12 +11,13 @@ using value_to_data_t = std::function(float); class ModbusOutput : public output::FloatOutput, public Component, public SensorItem { public: - ModbusOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type) + ModbusOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type, int register_count) : output::FloatOutput(), Component() { this->register_type = ModbusRegisterType::HOLDING; this->start_address = start_address; this->offset = offset; this->bitmask = bitmask; + this->register_count = register_count; this->sensor_value_type = value_type; this->skip_updates = 0; this->start_address += offset; diff --git a/esphome/components/modbus_controller/sensor/__init__.py b/esphome/components/modbus_controller/sensor/__init__.py index 687f3d82fb..82acfe120b 100644 --- a/esphome/components/modbus_controller/sensor/__init__.py +++ b/esphome/components/modbus_controller/sensor/__init__.py @@ -9,6 +9,7 @@ from .. import ( ModbusController, MODBUS_REGISTER_TYPE, SENSOR_VALUE_TYPE, + TYPE_REGISTER_MAP, ) from ..const import ( CONF_BITMASK, @@ -29,23 +30,6 @@ ModbusSensor = modbus_controller_ns.class_( "ModbusSensor", cg.Component, sensor.Sensor, SensorItem ) -TYPE_REGISTER_MAP = { - "RAW": 1, - "U_WORD": 1, - "S_WORD": 1, - "U_DWORD": 2, - "U_DWORD_R": 2, - "S_DWORD": 2, - "S_DWORD_R": 2, - "U_QWORD": 4, - "U_QWORDU_R": 4, - "S_QWORD": 4, - "U_QWORD_R": 4, - "FP32": 2, - "FP32_R": 2, -} - - CONFIG_SCHEMA = cv.All( sensor.SENSOR_SCHEMA.extend( { From 9f625ee7d1095b4a70605cbae45eca5441b27c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Tue, 26 Oct 2021 18:10:45 +0200 Subject: [PATCH 1632/1841] Fix pin number validation for sn74hc595 (#2621) --- esphome/components/sn74hc595/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sn74hc595/__init__.py b/esphome/components/sn74hc595/__init__.py index 0d1ff6ecba..630abc8bca 100644 --- a/esphome/components/sn74hc595/__init__.py +++ b/esphome/components/sn74hc595/__init__.py @@ -60,7 +60,7 @@ SN74HC595_PIN_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(SN74HC595GPIOPin), cv.Required(CONF_SN74HC595): cv.use_id(SN74HC595Component), - cv.Required(CONF_NUMBER): cv.int_range(min=0, max=7), + cv.Required(CONF_NUMBER): cv.int_range(min=0, max=31), cv.Optional(CONF_MODE, default={}): cv.All( { cv.Optional(CONF_OUTPUT, default=True): cv.All( From c2623a08e35e5d1560399315246c0cf110a0a20a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 27 Oct 2021 08:27:51 +1300 Subject: [PATCH 1633/1841] Fix select.set using lambda (#2633) --- esphome/components/select/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index c156a63a86..7e4047d3c8 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -90,6 +90,6 @@ async def to_code(config): async def select_set_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) - template_ = await cg.templatable(config[CONF_OPTION], args, str) + template_ = await cg.templatable(config[CONF_OPTION], args, cg.std_string) cg.add(var.set_option(template_)) return var From 2f4b9263c3b1c451bec78b20172b9529d059fd55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Oct 2021 22:12:37 +0200 Subject: [PATCH 1634/1841] Bump tzlocal from 4.0.1 to 4.0.2 (#2631) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3ed53d0c90..266f61369b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ PyYAML==6.0 paho-mqtt==1.6.1 colorama==0.4.4 tornado==6.1 -tzlocal==4.0.1 # from time +tzlocal==4.0.2 # from time tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile From 68316cbcf996bbf20b078eac6022aad7ce9c4999 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Oct 2021 22:14:44 +0200 Subject: [PATCH 1635/1841] Bump esptool from 3.1 to 3.2 (#2632) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 266f61369b..7f3470e467 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ tzlocal==4.0.2 # from time tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile -esptool==3.1 +esptool==3.2 click==8.0.3 esphome-dashboard==20211021.1 aioesphomeapi==10.0.3 From 980c2d4caef4c58fd3938144314958270b1c3e66 Mon Sep 17 00:00:00 2001 From: niklasweber Date: Wed, 27 Oct 2021 21:09:37 +0200 Subject: [PATCH 1636/1841] Add publish_initial_value option to rotary encoder (#2503) --- esphome/components/rotary_encoder/rotary_encoder.cpp | 3 ++- esphome/components/rotary_encoder/rotary_encoder.h | 2 ++ esphome/components/rotary_encoder/sensor.py | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index 7c95fac98e..e1f2584641 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -189,9 +189,10 @@ void RotaryEncoderSensor::loop() { this->store_.counter = 0; } int counter = this->store_.counter; - if (this->store_.last_read != counter) { + if (this->store_.last_read != counter || this->publish_initial_value_) { this->store_.last_read = counter; this->publish_state(counter); + this->publish_initial_value_ = false; } } diff --git a/esphome/components/rotary_encoder/rotary_encoder.h b/esphome/components/rotary_encoder/rotary_encoder.h index 4825e472a1..a134043152 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.h +++ b/esphome/components/rotary_encoder/rotary_encoder.h @@ -58,6 +58,7 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { void set_reset_pin(GPIOPin *pin_i) { this->pin_i_ = pin_i; } void set_min_value(int32_t min_value); void set_max_value(int32_t max_value); + void set_publish_initial_value(bool publish_initial_value) { publish_initial_value_ = publish_initial_value; } // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) @@ -79,6 +80,7 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { InternalGPIOPin *pin_a_; InternalGPIOPin *pin_b_; GPIOPin *pin_i_{nullptr}; /// Index pin, if this is not nullptr, the counter will reset to 0 once this pin is HIGH. + bool publish_initial_value_; RotaryEncoderSensorStore store_{}; diff --git a/esphome/components/rotary_encoder/sensor.py b/esphome/components/rotary_encoder/sensor.py index ef1110c6d8..d868438fd3 100644 --- a/esphome/components/rotary_encoder/sensor.py +++ b/esphome/components/rotary_encoder/sensor.py @@ -27,6 +27,7 @@ RESOLUTIONS = { CONF_PIN_RESET = "pin_reset" CONF_ON_CLOCKWISE = "on_clockwise" CONF_ON_ANTICLOCKWISE = "on_anticlockwise" +CONF_PUBLISH_INITIAL_VALUE = "publish_initial_value" RotaryEncoderSensor = rotary_encoder_ns.class_( "RotaryEncoderSensor", sensor.Sensor, cg.Component @@ -70,6 +71,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_RESOLUTION, default=1): cv.enum(RESOLUTIONS, int=True), cv.Optional(CONF_MIN_VALUE): cv.int_, cv.Optional(CONF_MAX_VALUE): cv.int_, + cv.Optional(CONF_PUBLISH_INITIAL_VALUE, default=False): cv.boolean, cv.Optional(CONF_ON_CLOCKWISE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -99,6 +101,7 @@ async def to_code(config): cg.add(var.set_pin_a(pin_a)) pin_b = await cg.gpio_pin_expression(config[CONF_PIN_B]) cg.add(var.set_pin_b(pin_b)) + cg.add(var.set_publish_initial_value(config[CONF_PUBLISH_INITIAL_VALUE])) if CONF_PIN_RESET in config: pin_i = await cg.gpio_pin_expression(config[CONF_PIN_RESET]) From 2147bcbc29687decea02bbf606727dc01f00ffdf Mon Sep 17 00:00:00 2001 From: Sean Brogan Date: Wed, 27 Oct 2021 13:16:12 -0700 Subject: [PATCH 1637/1841] Remove autoload of xiaomi_ble and ruuvi_ble (#2617) --- esphome/components/esp32_ble_tracker/__init__.py | 1 - tests/test2.yaml | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index e3d52f345a..e647b74a8f 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -18,7 +18,6 @@ from esphome.core import CORE from esphome.components.esp32 import add_idf_sdkconfig_option DEPENDENCIES = ["esp32"] -AUTO_LOAD = ["xiaomi_ble", "ruuvi_ble"] CONF_ESP32_BLE_ID = "esp32_ble_id" CONF_SCAN_PARAMETERS = "scan_parameters" diff --git a/tests/test2.yaml b/tests/test2.yaml index 7e71d1ab4e..6869eeecb1 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -401,6 +401,11 @@ ble_client: airthings_ble: +ruuvi_ble: + +xiaomi_ble: + + #esp32_ble_beacon: # type: iBeacon # uuid: 'c29ce823-e67a-4e71-bff2-abaa32e77a98' From b3d7cc637be49e50867bf6703ede12fcee6d5c35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Oct 2021 09:17:30 +1300 Subject: [PATCH 1638/1841] Bump aioesphomeapi from 10.0.3 to 10.1.0 (#2638) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7f3470e467..99efa6798f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 esphome-dashboard==20211021.1 -aioesphomeapi==10.0.3 +aioesphomeapi==10.1.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 0d3e6b2c4c4924901e6891426b5495e2211d7daa Mon Sep 17 00:00:00 2001 From: Alex Iribarren Date: Thu, 28 Oct 2021 00:46:55 +0200 Subject: [PATCH 1639/1841] Expose web_server port via the API (#2467) --- esphome/core/__init__.py | 14 ++++++++++++++ esphome/dashboard/dashboard.py | 7 +++++++ esphome/storage_json.py | 9 +++++++++ 3 files changed, 30 insertions(+) diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 8bdef3a4ea..addecf1326 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_USE_ADDRESS, CONF_ETHERNET, CONF_WIFI, + CONF_PORT, KEY_CORE, KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, @@ -519,6 +520,19 @@ class EsphomeCore: return None + @property + def web_port(self) -> Optional[int]: + if self.config is None: + raise ValueError("Config has not been loaded yet") + + if "web_server" in self.config: + try: + return self.config["web_server"][CONF_PORT] + except KeyError: + return 80 + + return None + @property def comment(self) -> Optional[str]: if self.config is None: diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 084dd84a07..11571ec889 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -509,6 +509,12 @@ class DashboardEntry: return None return self.storage.address + @property + def web_port(self): + if self.storage is None: + return None + return self.storage.web_port + @property def name(self): if self.storage is None: @@ -569,6 +575,7 @@ class ListDevicesHandler(BaseHandler): "path": entry.path, "comment": entry.comment, "address": entry.address, + "web_port": entry.web_port, "target_platform": entry.target_platform, } for entry in entries diff --git a/esphome/storage_json.py b/esphome/storage_json.py index 3262559116..207a3edf57 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -41,6 +41,7 @@ class StorageJSON: esphome_version, src_version, address, + web_port, target_platform, build_path, firmware_bin_path, @@ -60,6 +61,9 @@ class StorageJSON: self.src_version = src_version # type: int # Address of the ESP, for example livingroom.local or a static IP self.address = address # type: str + # Web server port of the ESP, for example 80 + assert web_port is None or isinstance(web_port, int) + self.web_port = web_port # type: int # The type of ESP in use, either ESP32 or ESP8266 self.target_platform = target_platform # type: str # The absolute path to the platformio project @@ -78,6 +82,7 @@ class StorageJSON: "esphome_version": self.esphome_version, "src_version": self.src_version, "address": self.address, + "web_port": self.web_port, "esp_platform": self.target_platform, "build_path": self.build_path, "firmware_bin_path": self.firmware_bin_path, @@ -101,6 +106,7 @@ class StorageJSON: esphome_version=const.__version__, src_version=1, address=esph.address, + web_port=esph.web_port, target_platform=esph.target_platform, build_path=esph.build_path, firmware_bin_path=esph.firmware_bin, @@ -117,6 +123,7 @@ class StorageJSON: esphome_version=const.__version__, src_version=1, address=address, + web_port=None, target_platform=esp_platform, build_path=None, firmware_bin_path=None, @@ -135,6 +142,7 @@ class StorageJSON: ) src_version = storage.get("src_version") address = storage.get("address") + web_port = storage.get("web_port") esp_platform = storage.get("esp_platform") build_path = storage.get("build_path") firmware_bin_path = storage.get("firmware_bin_path") @@ -146,6 +154,7 @@ class StorageJSON: esphome_version, src_version, address, + web_port, esp_platform, build_path, firmware_bin_path, From 73accf747f64daf32c25a127b8410d87335b615d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 29 Oct 2021 07:12:05 +1300 Subject: [PATCH 1640/1841] Allow cloning/fetching Github PR branches in external_components (#2639) --- .../external_components/__init__.py | 24 ++++++++++++------- esphome/git.py | 10 +++++++- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index 110a8d95ed..e0548e8981 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -43,19 +43,27 @@ def validate_source_shorthand(value): # Regex for GitHub repo name with optional branch/tag # Note: git allows other branch/tag names as well, but never seen them used before m = re.match( - r"github://([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)(?:@([a-zA-Z0-9\-_.\./]+))?", + r"github://(?:([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)(?:@([a-zA-Z0-9\-_.\./]+))?|pr#([0-9]+))", value, ) if m is None: raise cv.Invalid( - "Source is not a file system path or in expected github://username/name[@branch-or-tag] format!" + "Source is not a file system path, in expected github://username/name[@branch-or-tag] or github://pr#1234 format!" ) - conf = { - CONF_TYPE: TYPE_GIT, - CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git", - } - if m.group(3): - conf[CONF_REF] = m.group(3) + if m.group(4): + conf = { + CONF_TYPE: TYPE_GIT, + CONF_URL: "https://github.com/esphome/esphome.git", + CONF_REF: f"pull/{m.group(4)}/head", + } + else: + conf = { + CONF_TYPE: TYPE_GIT, + CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git", + } + if m.group(3): + conf[CONF_REF] = m.group(3) + return SOURCE_SCHEMA(conf) diff --git a/esphome/git.py b/esphome/git.py index 12c6b41648..b64aa6a864 100644 --- a/esphome/git.py +++ b/esphome/git.py @@ -40,15 +40,23 @@ def clone_or_update( ) -> Path: key = f"{url}@{ref}" repo_dir = _compute_destination_path(key, domain) + fetch_pr_branch = ref.startswith("pull/") if not repo_dir.is_dir(): _LOGGER.info("Cloning %s", key) _LOGGER.debug("Location: %s", repo_dir) cmd = ["git", "clone", "--depth=1"] - if ref is not None: + if ref is not None and not fetch_pr_branch: cmd += ["--branch", ref] cmd += ["--", url, str(repo_dir)] run_git_command(cmd) + if fetch_pr_branch: + # We need to fetch the PR branch first, otherwise git will complain + # about missing objects + _LOGGER.info("Fetching %s", ref) + run_git_command(["git", "fetch", "--", "origin", ref], str(repo_dir)) + run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir)) + else: # Check refresh needed file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD") From 2350c5054c691dcbb54c7c5a979e3f6f7a615de3 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Thu, 28 Oct 2021 20:57:39 +0200 Subject: [PATCH 1641/1841] use update_interval for sntp synchronization (#2563) * use update_interval for sntp synchronization * revert override of default interval --- esphome/components/sntp/sntp_component.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index 2b6cd10e80..96be0e9709 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -3,6 +3,9 @@ #ifdef USE_ESP32 #include "lwip/apps/sntp.h" +#ifdef USE_ESP_IDF +#include "esp_sntp.h" +#endif #endif #ifdef USE_ESP8266 #include "sntp.h" @@ -37,6 +40,9 @@ void SNTPComponent::setup() { if (!this->server_3_.empty()) { sntp_setservername(2, strdup(this->server_3_.c_str())); } +#ifdef USE_ESP_IDF + sntp_set_sync_interval(this->get_update_interval()); +#endif sntp_init(); } @@ -47,7 +53,16 @@ void SNTPComponent::dump_config() { ESP_LOGCONFIG(TAG, " Server 3: '%s'", this->server_3_.c_str()); ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); } -void SNTPComponent::update() {} +void SNTPComponent::update() { +#ifndef USE_ESP_IDF + // force resync + if (sntp_enabled()) { + sntp_stop(); + this->has_time_ = false; + sntp_init(); + } +#endif +} void SNTPComponent::loop() { if (this->has_time_) return; @@ -56,7 +71,7 @@ void SNTPComponent::loop() { if (!time.is_valid()) return; - ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%d:%d", time.year, time.month, time.day_of_month, time.hour, + ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour, time.minute, time.second); this->time_sync_callback_.call(); this->has_time_ = true; From 77dbf84e55f404ceea06fde1dfde33213209f6c7 Mon Sep 17 00:00:00 2001 From: Arturo Casal Date: Thu, 28 Oct 2021 20:58:48 +0200 Subject: [PATCH 1642/1841] Add support for CSE7761 sensor (#2546) * Add CSE7761 sensor support * CSE7761: Added test at test3.yaml * CSE7761: changed string style * CSE7761: fixed cpp lint * CSE7761: Added codeowners * Lots of code cleanup * Revert incorrect setup_priority suggestion * Added error log in read with retries. Co-authored-by: Oxan van Leeuwen * Improved log messages Co-authored-by: Oxan van Leeuwen --- CODEOWNERS | 1 + esphome/components/cse7761/__init__.py | 0 esphome/components/cse7761/cse7761.cpp | 244 +++++++++++++++++++++++++ esphome/components/cse7761/cse7761.h | 52 ++++++ esphome/components/cse7761/sensor.py | 90 +++++++++ tests/test3.yaml | 16 ++ 6 files changed, 403 insertions(+) create mode 100644 esphome/components/cse7761/__init__.py create mode 100644 esphome/components/cse7761/cse7761.cpp create mode 100644 esphome/components/cse7761/cse7761.h create mode 100644 esphome/components/cse7761/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index a7cf3a1b68..664bf9ad6b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -39,6 +39,7 @@ esphome/components/color_temperature/* @jesserockz esphome/components/coolix/* @glmnet esphome/components/cover/* @esphome/core esphome/components/cs5460a/* @balrog-kun +esphome/components/cse7761/* @berfenger esphome/components/ct_clamp/* @jesserockz esphome/components/current_based/* @djwmarcx esphome/components/daly_bms/* @s1lvi0 diff --git a/esphome/components/cse7761/__init__.py b/esphome/components/cse7761/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/cse7761/cse7761.cpp b/esphome/components/cse7761/cse7761.cpp new file mode 100644 index 0000000000..3b8364f0bc --- /dev/null +++ b/esphome/components/cse7761/cse7761.cpp @@ -0,0 +1,244 @@ +#include "cse7761.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace cse7761 { + +static const char *const TAG = "cse7761"; + +/*********************************************************************************************\ + * CSE7761 - Energy (Sonoff Dual R3 Pow v1.x) + * + * Based on Tasmota source code + * See https://github.com/arendst/Tasmota/discussions/10793 + * https://github.com/arendst/Tasmota/blob/development/tasmota/xnrg_19_cse7761.ino +\*********************************************************************************************/ + +static const int CSE7761_UREF = 42563; // RmsUc +static const int CSE7761_IREF = 52241; // RmsIAC +static const int CSE7761_PREF = 44513; // PowerPAC + +static const uint8_t CSE7761_REG_SYSCON = 0x00; // (2) System Control Register (0x0A04) +static const uint8_t CSE7761_REG_EMUCON = 0x01; // (2) Metering control register (0x0000) +static const uint8_t CSE7761_REG_EMUCON2 = 0x13; // (2) Metering control register 2 (0x0001) +static const uint8_t CSE7761_REG_PULSE1SEL = 0x1D; // (2) Pin function output select register (0x3210) + +static const uint8_t CSE7761_REG_RMSIA = 0x24; // (3) The effective value of channel A current (0x000000) +static const uint8_t CSE7761_REG_RMSIB = 0x25; // (3) The effective value of channel B current (0x000000) +static const uint8_t CSE7761_REG_RMSU = 0x26; // (3) Voltage RMS (0x000000) +static const uint8_t CSE7761_REG_POWERPA = 0x2C; // (4) Channel A active power, update rate 27.2Hz (0x00000000) +static const uint8_t CSE7761_REG_POWERPB = 0x2D; // (4) Channel B active power, update rate 27.2Hz (0x00000000) +static const uint8_t CSE7761_REG_SYSSTATUS = 0x43; // (1) System status register + +static const uint8_t CSE7761_REG_COEFFCHKSUM = 0x6F; // (2) Coefficient checksum +static const uint8_t CSE7761_REG_RMSIAC = 0x70; // (2) Channel A effective current conversion coefficient + +static const uint8_t CSE7761_SPECIAL_COMMAND = 0xEA; // Start special command +static const uint8_t CSE7761_CMD_RESET = 0x96; // Reset command, after receiving the command, the chip resets +static const uint8_t CSE7761_CMD_CLOSE_WRITE = 0xDC; // Close write operation +static const uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5; // Enable write operation + +enum CSE7761 { RMS_IAC, RMS_IBC, RMS_UC, POWER_PAC, POWER_PBC, POWER_SC, ENERGY_AC, ENERGY_BC }; + +void CSE7761Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up CSE7761..."); + this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_RESET); + uint16_t syscon = this->read_(0x00, 2); // Default 0x0A04 + if ((0x0A04 == syscon) && this->chip_init_()) { + this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_CLOSE_WRITE); + ESP_LOGD(TAG, "CSE7761 found"); + this->data_.ready = true; + } else { + this->mark_failed(); + } +} + +void CSE7761Component::dump_config() { + ESP_LOGCONFIG(TAG, "CSE7761:"); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with CSE7761 failed!"); + } + LOG_UPDATE_INTERVAL(this); + this->check_uart_settings(38400, 1, uart::UART_CONFIG_PARITY_EVEN, 8); +} + +float CSE7761Component::get_setup_priority() const { return setup_priority::DATA; } + +void CSE7761Component::update() { + if (this->data_.ready) { + this->get_data_(); + } +} + +void CSE7761Component::write_(uint8_t reg, uint16_t data) { + uint8_t buffer[5]; + + buffer[0] = 0xA5; + buffer[1] = reg; + uint32_t len = 2; + if (data) { + if (data < 0xFF) { + buffer[2] = data & 0xFF; + len = 3; + } else { + buffer[2] = (data >> 8) & 0xFF; + buffer[3] = data & 0xFF; + len = 4; + } + uint8_t crc = 0; + for (uint32_t i = 0; i < len; i++) { + crc += buffer[i]; + } + buffer[len] = ~crc; + len++; + } + + this->write_array(buffer, len); +} + +bool CSE7761Component::read_once_(uint8_t reg, uint8_t size, uint32_t *value) { + while (this->available()) { + this->read(); + } + + this->write_(reg, 0); + + uint8_t buffer[8] = {0}; + uint32_t rcvd = 0; + + for (uint32_t i = 0; i <= size; i++) { + int value = this->read(); + if (value > -1 && rcvd < sizeof(buffer) - 1) { + buffer[rcvd++] = value; + } + } + + if (!rcvd) { + ESP_LOGD(TAG, "Received 0 bytes for register %hhu", reg); + return false; + } + + rcvd--; + uint32_t result = 0; + // CRC check + uint8_t crc = 0xA5 + reg; + for (uint32_t i = 0; i < rcvd; i++) { + result = (result << 8) | buffer[i]; + crc += buffer[i]; + } + crc = ~crc; + if (crc != buffer[rcvd]) { + return false; + } + + *value = result; + return true; +} + +uint32_t CSE7761Component::read_(uint8_t reg, uint8_t size) { + bool result = false; // Start loop + uint8_t retry = 3; // Retry up to three times + uint32_t value = 0; // Default no value + while (!result && retry > 0) { + retry--; + if (this->read_once_(reg, size, &value)) + return value; + } + ESP_LOGE(TAG, "Reading register %hhu failed!", reg); + return value; +} + +uint32_t CSE7761Component::coefficient_by_unit_(uint32_t unit) { + switch (unit) { + case RMS_UC: + return 0x400000 * 100 / this->data_.coefficient[RMS_UC]; + case RMS_IAC: + return (0x800000 * 100 / this->data_.coefficient[RMS_IAC]) * 10; // Stay within 32 bits + case POWER_PAC: + return 0x80000000 / this->data_.coefficient[POWER_PAC]; + } + return 0; +} + +bool CSE7761Component::chip_init_() { + uint16_t calc_chksum = 0xFFFF; + for (uint32_t i = 0; i < 8; i++) { + this->data_.coefficient[i] = this->read_(CSE7761_REG_RMSIAC + i, 2); + calc_chksum += this->data_.coefficient[i]; + } + calc_chksum = ~calc_chksum; + uint16_t coeff_chksum = this->read_(CSE7761_REG_COEFFCHKSUM, 2); + if ((calc_chksum != coeff_chksum) || (!calc_chksum)) { + ESP_LOGD(TAG, "Default calibration"); + this->data_.coefficient[RMS_IAC] = CSE7761_IREF; + this->data_.coefficient[RMS_UC] = CSE7761_UREF; + this->data_.coefficient[POWER_PAC] = CSE7761_PREF; + } + + this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_ENABLE_WRITE); + + uint8_t sys_status = this->read_(CSE7761_REG_SYSSTATUS, 1); + if (sys_status & 0x10) { // Write enable to protected registers (WREN) + this->write_(CSE7761_REG_SYSCON | 0x80, 0xFF04); + this->write_(CSE7761_REG_EMUCON | 0x80, 0x1183); + this->write_(CSE7761_REG_EMUCON2 | 0x80, 0x0FC1); + this->write_(CSE7761_REG_PULSE1SEL | 0x80, 0x3290); + } else { + ESP_LOGD(TAG, "Write failed at chip_init"); + return false; + } + return true; +} + +void CSE7761Component::get_data_() { + // The effective value of current and voltage Rms is a 24-bit signed number, + // the highest bit is 0 for valid data, + // and when the highest bit is 1, the reading will be processed as zero + // The active power parameter PowerA/B is in two’s complement format, 32-bit + // data, the highest bit is Sign bit. + uint32_t value = this->read_(CSE7761_REG_RMSU, 3); + this->data_.voltage_rms = (value >= 0x800000) ? 0 : value; + + value = this->read_(CSE7761_REG_RMSIA, 3); + this->data_.current_rms[0] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA + value = this->read_(CSE7761_REG_POWERPA, 4); + this->data_.active_power[0] = (0 == this->data_.current_rms[0]) ? 0 : ((uint32_t) abs((int) value)); + + value = this->read_(CSE7761_REG_RMSIB, 3); + this->data_.current_rms[1] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA + value = this->read_(CSE7761_REG_POWERPB, 4); + this->data_.active_power[1] = (0 == this->data_.current_rms[1]) ? 0 : ((uint32_t) abs((int) value)); + + // convert values and publish to sensors + + float voltage = (float) this->data_.voltage_rms / this->coefficient_by_unit_(RMS_UC); + if (this->voltage_sensor_ != nullptr) { + this->voltage_sensor_->publish_state(voltage); + } + + for (uint32_t channel = 0; channel < 2; channel++) { + // Active power = PowerPA * PowerPAC * 1000 / 0x80000000 + float active_power = (float) this->data_.active_power[channel] / this->coefficient_by_unit_(POWER_PAC); // W + float amps = (float) this->data_.current_rms[channel] / this->coefficient_by_unit_(RMS_IAC); // A + ESP_LOGD(TAG, "Channel %d power %f W, current %f A", channel + 1, active_power, amps); + if (channel == 0) { + if (this->power_sensor_1_ != nullptr) { + this->power_sensor_1_->publish_state(active_power); + } + if (this->current_sensor_1_ != nullptr) { + this->current_sensor_1_->publish_state(amps); + } + } else if (channel == 1) { + if (this->power_sensor_2_ != nullptr) { + this->power_sensor_2_->publish_state(active_power); + } + if (this->current_sensor_2_ != nullptr) { + this->current_sensor_2_->publish_state(amps); + } + } + } +} + +} // namespace cse7761 +} // namespace esphome diff --git a/esphome/components/cse7761/cse7761.h b/esphome/components/cse7761/cse7761.h new file mode 100644 index 0000000000..71846cdcab --- /dev/null +++ b/esphome/components/cse7761/cse7761.h @@ -0,0 +1,52 @@ +#pragma once + +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uart/uart.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace cse7761 { + +struct CSE7761DataStruct { + uint32_t frequency = 0; + uint32_t voltage_rms = 0; + uint32_t current_rms[2] = {0}; + uint32_t energy[2] = {0}; + uint32_t active_power[2] = {0}; + uint16_t coefficient[8] = {0}; + uint8_t energy_update = 0; + bool ready = false; +}; + +/// This class implements support for the CSE7761 UART power sensor. +class CSE7761Component : public PollingComponent, public uart::UARTDevice { + public: + void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } + void set_active_power_1_sensor(sensor::Sensor *power_sensor_1) { power_sensor_1_ = power_sensor_1; } + void set_current_1_sensor(sensor::Sensor *current_sensor_1) { current_sensor_1_ = current_sensor_1; } + void set_active_power_2_sensor(sensor::Sensor *power_sensor_2) { power_sensor_2_ = power_sensor_2; } + void set_current_2_sensor(sensor::Sensor *current_sensor_2) { current_sensor_2_ = current_sensor_2; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + + protected: + // Sensors + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *power_sensor_1_{nullptr}; + sensor::Sensor *current_sensor_1_{nullptr}; + sensor::Sensor *power_sensor_2_{nullptr}; + sensor::Sensor *current_sensor_2_{nullptr}; + CSE7761DataStruct data_; + + void write_(uint8_t reg, uint16_t data); + bool read_once_(uint8_t reg, uint8_t size, uint32_t *value); + uint32_t read_(uint8_t reg, uint8_t size); + uint32_t coefficient_by_unit_(uint32_t unit); + bool chip_init_(); + void get_data_(); +}; + +} // namespace cse7761 +} // namespace esphome diff --git a/esphome/components/cse7761/sensor.py b/esphome/components/cse7761/sensor.py new file mode 100644 index 0000000000..c5ec3e5b71 --- /dev/null +++ b/esphome/components/cse7761/sensor.py @@ -0,0 +1,90 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, uart +from esphome.const import ( + CONF_ID, + CONF_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, +) + +CODEOWNERS = ["@berfenger"] +DEPENDENCIES = ["uart"] + +cse7761_ns = cg.esphome_ns.namespace("cse7761") +CSE7761Component = cse7761_ns.class_( + "CSE7761Component", cg.PollingComponent, uart.UARTDevice +) + +CONF_CURRENT_1 = "current_1" +CONF_CURRENT_2 = "current_2" +CONF_ACTIVE_POWER_1 = "active_power_1" +CONF_ACTIVE_POWER_2 = "active_power_2" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(CSE7761Component), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT_1): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT_2): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ACTIVE_POWER_1): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ACTIVE_POWER_2): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(uart.UART_DEVICE_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "cse7761", baud_rate=38400, require_rx=True, require_tx=True +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + + for key in [ + CONF_VOLTAGE, + CONF_CURRENT_1, + CONF_CURRENT_2, + CONF_ACTIVE_POWER_1, + CONF_ACTIVE_POWER_2, + ]: + if key not in config: + continue + conf = config[key] + sens = await sensor.new_sensor(conf) + cg.add(getattr(var, f"set_{key}_sensor")(sens)) diff --git a/tests/test3.yaml b/tests/test3.yaml index 836e895374..0b7f1ad71e 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -250,6 +250,10 @@ uart: tx_pin: GPIO4 rx_pin: GPIO5 baud_rate: 9600 + - id: uart7 + tx_pin: GPIO4 + rx_pin: GPIO5 + baud_rate: 38400 modbus: uart_id: uart1 @@ -549,6 +553,18 @@ sensor: name: 'PMS Humidity' formaldehyde: name: 'PMS Formaldehyde Concentration' + - platform: cse7761 + uart_id: uart7 + voltage: + name: 'CSE7761 Voltage' + current_1: + name: 'CSE7761 Current 1' + current_2: + name: 'CSE7761 Current 2' + active_power_1: + name: 'CSE7761 Active Power 1' + active_power_2: + name: 'CSE7761 Active Power 2' - platform: cse7766 uart_id: uart3 voltage: From 7e54f97003688742e0e09242eb5d0433764c410b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Oct 2021 21:24:48 +0200 Subject: [PATCH 1643/1841] Bump aioesphomeapi from 10.1.0 to 10.2.0 (#2642) Bumps [aioesphomeapi](https://github.com/esphome/aioesphomeapi) from 10.1.0 to 10.2.0. - [Release notes](https://github.com/esphome/aioesphomeapi/releases) - [Commits](https://github.com/esphome/aioesphomeapi/compare/v10.1.0...v10.2.0) --- updated-dependencies: - dependency-name: aioesphomeapi dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 99efa6798f..2c3220e825 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 esphome-dashboard==20211021.1 -aioesphomeapi==10.1.0 +aioesphomeapi==10.2.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 696643d037751cac1c85a44fb8d9a8cbb8beaaec Mon Sep 17 00:00:00 2001 From: Ed <89483561+kixtarter@users.noreply.github.com> Date: Fri, 29 Oct 2021 00:51:57 +0200 Subject: [PATCH 1644/1841] BH1750: Fix a too high default H-res2 mode value (#2536) --- esphome/components/bh1750/bh1750.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/bh1750/bh1750.cpp b/esphome/components/bh1750/bh1750.cpp index 951fe3670c..4e6bb3c563 100644 --- a/esphome/components/bh1750/bh1750.cpp +++ b/esphome/components/bh1750/bh1750.cpp @@ -79,6 +79,9 @@ void BH1750Sensor::read_data_() { float lx = float(raw_value) / 1.2f; lx *= 69.0f / this->measurement_duration_; + if (this->resolution_ == BH1750_RESOLUTION_0P5_LX) { + lx /= 2.0f; + } ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx); this->publish_state(lx); this->status_clear_warning(); From b12c7432e0ef751e2a0d4b4c45bbc73e447ff930 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 30 Oct 2021 20:12:57 +0200 Subject: [PATCH 1645/1841] Bump tzlocal from 4.0.2 to 4.1 (#2645) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2c3220e825..6e1fe56057 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ PyYAML==6.0 paho-mqtt==1.6.1 colorama==0.4.4 tornado==6.1 -tzlocal==4.0.2 # from time +tzlocal==4.1 # from time tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile From 7eee3cdc7fde4de32da80d5a79328895d0e9c360 Mon Sep 17 00:00:00 2001 From: Geoffrey Van Landeghem Date: Sun, 31 Oct 2021 03:29:22 +0100 Subject: [PATCH 1646/1841] convert SCD30 into Component, polls dataready register (#2308) Co-authored-by: Oxan van Leeuwen Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/scd30/scd30.cpp | 37 +++++++++++++++++++++++------- esphome/components/scd30/scd30.h | 7 ++++-- esphome/components/scd30/sensor.py | 14 +++++++++-- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index d1246d9766..e6d6ec1c1a 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -60,7 +60,7 @@ void SCD30Component::setup() { // According ESP32 clock stretching is typically 30ms and up to 150ms "due to // internal calibration processes". The I2C peripheral only supports 13ms (at // least when running at 80MHz). - // In practise it seems that clock stretching occurs during this calibration + // In practice it seems that clock stretching occurs during this calibration // calls. It also seems that delays in between calls makes them // disappear/shorter. Hence work around with delays for ESP32. // @@ -69,6 +69,16 @@ void SCD30Component::setup() { delay(30); #endif + if (!this->write_command_(SCD30_CMD_MEASUREMENT_INTERVAL, update_interval_)) { + ESP_LOGE(TAG, "Sensor SCD30 error setting update interval."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } +#ifdef USE_ESP32 + delay(30); +#endif + // The start measurement command disables the altitude compensation, if any, so we only set it if it's turned on if (this->altitude_compensation_ != 0xFFFF) { if (!this->write_command_(SCD30_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { @@ -99,6 +109,12 @@ void SCD30Component::setup() { this->mark_failed(); return; } + + // check each 500ms if data is ready, and read it in that case + this->set_interval("status-check", 500, [this]() { + if (this->is_data_ready_()) + this->update(); + }); } void SCD30Component::dump_config() { @@ -128,19 +144,13 @@ void SCD30Component::dump_config() { ESP_LOGCONFIG(TAG, " Automatic self calibration: %s", ONOFF(this->enable_asc_)); ESP_LOGCONFIG(TAG, " Ambient pressure compensation: %dmBar", this->ambient_pressure_compensation_); ESP_LOGCONFIG(TAG, " Temperature offset: %.2f °C", this->temperature_offset_); - LOG_UPDATE_INTERVAL(this); + ESP_LOGCONFIG(TAG, " Update interval: %ds", this->update_interval_); LOG_SENSOR(" ", "CO2", this->co2_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); } void SCD30Component::update() { - /// Check if measurement is ready before reading the value - if (!this->write_command_(SCD30_CMD_GET_DATA_READY_STATUS)) { - this->status_set_warning(); - return; - } - uint16_t raw_read_status[1]; if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) { this->status_set_warning(); @@ -186,6 +196,17 @@ void SCD30Component::update() { }); } +bool SCD30Component::is_data_ready_() { + if (!this->write_command_(SCD30_CMD_GET_DATA_READY_STATUS)) { + return false; + } + uint16_t is_data_ready; + if (!this->read_data_(&is_data_ready, 1)) { + return false; + } + return is_data_ready == 1; +} + bool SCD30Component::write_command_(uint16_t command) { // Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit. return this->write_byte(command >> 8, command & 0xFF); diff --git a/esphome/components/scd30/scd30.h b/esphome/components/scd30/scd30.h index f11b7cc1f4..64193d0cb6 100644 --- a/esphome/components/scd30/scd30.h +++ b/esphome/components/scd30/scd30.h @@ -8,7 +8,7 @@ namespace esphome { namespace scd30 { /// This class implements support for the Sensirion scd30 i2c GAS (VOC and CO2eq) sensors. -class SCD30Component : public PollingComponent, public i2c::I2CDevice { +class SCD30Component : public Component, public i2c::I2CDevice { public: void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; } void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } @@ -19,9 +19,10 @@ class SCD30Component : public PollingComponent, public i2c::I2CDevice { ambient_pressure_compensation_ = (uint16_t)(pressure * 1000); } void set_temperature_offset(float offset) { temperature_offset_ = offset; } + void set_update_interval(uint16_t interval) { update_interval_ = interval; } void setup() override; - void update() override; + void update(); void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } @@ -30,6 +31,7 @@ class SCD30Component : public PollingComponent, public i2c::I2CDevice { bool write_command_(uint16_t command, uint16_t data); bool read_data_(uint16_t *data, uint8_t len); uint8_t sht_crc_(uint8_t data1, uint8_t data2); + bool is_data_ready_(); enum ErrorCode { COMMUNICATION_FAILED, @@ -41,6 +43,7 @@ class SCD30Component : public PollingComponent, public i2c::I2CDevice { uint16_t altitude_compensation_{0xFFFF}; uint16_t ambient_pressure_compensation_{0x0000}; float temperature_offset_{0.0}; + uint16_t update_interval_{0xFFFF}; sensor::Sensor *co2_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; diff --git a/esphome/components/scd30/sensor.py b/esphome/components/scd30/sensor.py index c0317c96e0..cd25649f2a 100644 --- a/esphome/components/scd30/sensor.py +++ b/esphome/components/scd30/sensor.py @@ -1,3 +1,4 @@ +from esphome import core import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor @@ -6,6 +7,7 @@ from esphome.const import ( CONF_HUMIDITY, CONF_TEMPERATURE, CONF_CO2, + CONF_UPDATE_INTERVAL, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, @@ -18,7 +20,7 @@ from esphome.const import ( DEPENDENCIES = ["i2c"] scd30_ns = cg.esphome_ns.namespace("scd30") -SCD30Component = scd30_ns.class_("SCD30Component", cg.PollingComponent, i2c.I2CDevice) +SCD30Component = scd30_ns.class_("SCD30Component", cg.Component, i2c.I2CDevice) CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" CONF_ALTITUDE_COMPENSATION = "altitude_compensation" @@ -55,9 +57,15 @@ CONFIG_SCHEMA = ( ), cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION, default=0): cv.pressure, cv.Optional(CONF_TEMPERATURE_OFFSET): cv.temperature, + cv.Optional(CONF_UPDATE_INTERVAL, default="60s"): cv.All( + cv.positive_time_period_seconds, + cv.Range( + min=core.TimePeriod(seconds=1), max=core.TimePeriod(seconds=1800) + ), + ), } ) - .extend(cv.polling_component_schema("60s")) + .extend(cv.COMPONENT_SCHEMA) .extend(i2c.i2c_device_schema(0x61)) ) @@ -81,6 +89,8 @@ async def to_code(config): if CONF_TEMPERATURE_OFFSET in config: cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET])) + cg.add(var.set_update_interval(config[CONF_UPDATE_INTERVAL])) + if CONF_CO2 in config: sens = await sensor.new_sensor(config[CONF_CO2]) cg.add(var.set_co2_sensor(sens)) From 331a3ac387e881e886d0a0f651ff1728c593b895 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Sun, 31 Oct 2021 15:34:08 +1300 Subject: [PATCH 1647/1841] Add option to use MQTT abbreviations (#2641) --- esphome/components/mqtt/__init__.py | 5 + .../components/mqtt/mqtt_binary_sensor.cpp | 8 +- esphome/components/mqtt/mqtt_climate.cpp | 42 +- esphome/components/mqtt/mqtt_component.cpp | 40 +- esphome/components/mqtt/mqtt_const.h | 518 ++++++++++++++++++ esphome/components/mqtt/mqtt_cover.cpp | 14 +- esphome/components/mqtt/mqtt_fan.cpp | 10 +- esphome/components/mqtt/mqtt_light.cpp | 6 +- esphome/components/mqtt/mqtt_number.cpp | 8 +- esphome/components/mqtt/mqtt_select.cpp | 4 +- esphome/components/mqtt/mqtt_sensor.cpp | 12 +- esphome/components/mqtt/mqtt_switch.cpp | 4 +- esphome/const.py | 1 + tests/test1.yaml | 1 + 14 files changed, 609 insertions(+), 64 deletions(-) create mode 100644 esphome/components/mqtt/mqtt_const.h diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 3d52dab67f..0f7d246473 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -34,6 +34,7 @@ from esphome.const import ( CONF_TOPIC, CONF_TOPIC_PREFIX, CONF_TRIGGER_ID, + CONF_USE_ABBREVIATIONS, CONF_USERNAME, CONF_WILL_MESSAGE, ) @@ -152,6 +153,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional( CONF_DISCOVERY_PREFIX, default="homeassistant" ): cv.publish_topic, + cv.Optional(CONF_USE_ABBREVIATIONS, default=True): cv.boolean, cv.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA, cv.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA, cv.Optional(CONF_SHUTDOWN_MESSAGE): MQTT_MESSAGE_SCHEMA, @@ -239,6 +241,9 @@ async def to_code(config): cg.add(var.set_topic_prefix(config[CONF_TOPIC_PREFIX])) + if config[CONF_USE_ABBREVIATIONS]: + cg.add_define("USE_MQTT_ABBREVIATIONS") + birth_message = config[CONF_BIRTH_MESSAGE] if not birth_message: cg.add(var.disable_birth_message()) diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index 188df0f7b9..0a161f89a1 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -1,6 +1,8 @@ #include "mqtt_binary_sensor.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_BINARY_SENSOR @@ -29,11 +31,11 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor void MQTTBinarySensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (!this->binary_sensor_->get_device_class().empty()) - root["device_class"] = this->binary_sensor_->get_device_class(); + root[MQTT_DEVICE_CLASS] = this->binary_sensor_->get_device_class(); if (this->binary_sensor_->is_status_binary_sensor()) - root["payload_on"] = mqtt::global_mqtt_client->get_availability().payload_available; + root[MQTT_PAYLOAD_ON] = mqtt::global_mqtt_client->get_availability().payload_available; if (this->binary_sensor_->is_status_binary_sensor()) - root["payload_off"] = mqtt::global_mqtt_client->get_availability().payload_not_available; + root[MQTT_PAYLOAD_OFF] = mqtt::global_mqtt_client->get_availability().payload_not_available; config.command_topic = false; } bool MQTTBinarySensorComponent::send_initial_state() { diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 47b6684dec..a63eb9c4ff 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -1,6 +1,8 @@ #include "mqtt_climate.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_CLIMATE @@ -16,14 +18,14 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC // current_temperature_topic if (traits.get_supports_current_temperature()) { // current_temperature_topic - root["curr_temp_t"] = this->get_current_temperature_state_topic(); + root[MQTT_CURRENT_TEMPERATURE_TOPIC] = this->get_current_temperature_state_topic(); } // mode_command_topic - root["mode_cmd_t"] = this->get_mode_command_topic(); + root[MQTT_MODE_COMMAND_TOPIC] = this->get_mode_command_topic(); // mode_state_topic - root["mode_stat_t"] = this->get_mode_state_topic(); + root[MQTT_MODE_STATE_TOPIC] = this->get_mode_state_topic(); // modes - JsonArray &modes = root.createNestedArray("modes"); + JsonArray &modes = root.createNestedArray(MQTT_MODES); // sort array for nice UI in HA if (traits.supports_mode(CLIMATE_MODE_AUTO)) modes.add("auto"); @@ -41,45 +43,45 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC if (traits.get_supports_two_point_target_temperature()) { // temperature_low_command_topic - root["temp_lo_cmd_t"] = this->get_target_temperature_low_command_topic(); + root[MQTT_TEMPERATURE_LOW_COMMAND_TOPIC] = this->get_target_temperature_low_command_topic(); // temperature_low_state_topic - root["temp_lo_stat_t"] = this->get_target_temperature_low_state_topic(); + root[MQTT_TEMPERATURE_LOW_STATE_TOPIC] = this->get_target_temperature_low_state_topic(); // temperature_high_command_topic - root["temp_hi_cmd_t"] = this->get_target_temperature_high_command_topic(); + root[MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC] = this->get_target_temperature_high_command_topic(); // temperature_high_state_topic - root["temp_hi_stat_t"] = this->get_target_temperature_high_state_topic(); + root[MQTT_TEMPERATURE_HIGH_STATE_TOPIC] = this->get_target_temperature_high_state_topic(); } else { // temperature_command_topic - root["temp_cmd_t"] = this->get_target_temperature_command_topic(); + root[MQTT_TEMPERATURE_COMMAND_TOPIC] = this->get_target_temperature_command_topic(); // temperature_state_topic - root["temp_stat_t"] = this->get_target_temperature_state_topic(); + root[MQTT_TEMPERATURE_STATE_TOPIC] = this->get_target_temperature_state_topic(); } // min_temp - root["min_temp"] = traits.get_visual_min_temperature(); + root[MQTT_MIN_TEMP] = traits.get_visual_min_temperature(); // max_temp - root["max_temp"] = traits.get_visual_max_temperature(); + root[MQTT_MAX_TEMP] = traits.get_visual_max_temperature(); // temp_step root["temp_step"] = traits.get_visual_temperature_step(); // temperature units are always coerced to Celsius internally - root["temp_unit"] = "C"; + root[MQTT_TEMPERATURE_UNIT] = "C"; if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { // away_mode_command_topic - root["away_mode_cmd_t"] = this->get_away_command_topic(); + root[MQTT_AWAY_MODE_COMMAND_TOPIC] = this->get_away_command_topic(); // away_mode_state_topic - root["away_mode_stat_t"] = this->get_away_state_topic(); + root[MQTT_AWAY_MODE_STATE_TOPIC] = this->get_away_state_topic(); } if (traits.get_supports_action()) { // action_topic - root["act_t"] = this->get_action_state_topic(); + root[MQTT_ACTION_TOPIC] = this->get_action_state_topic(); } if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) { // fan_mode_command_topic - root["fan_mode_cmd_t"] = this->get_fan_mode_command_topic(); + root[MQTT_FAN_MODE_COMMAND_TOPIC] = this->get_fan_mode_command_topic(); // fan_mode_state_topic - root["fan_mode_stat_t"] = this->get_fan_mode_state_topic(); + root[MQTT_FAN_MODE_STATE_TOPIC] = this->get_fan_mode_state_topic(); // fan_modes JsonArray &fan_modes = root.createNestedArray("fan_modes"); if (traits.supports_fan_mode(CLIMATE_FAN_ON)) @@ -106,9 +108,9 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC if (traits.get_supports_swing_modes()) { // swing_mode_command_topic - root["swing_mode_cmd_t"] = this->get_swing_mode_command_topic(); + root[MQTT_SWING_MODE_COMMAND_TOPIC] = this->get_swing_mode_command_topic(); // swing_mode_state_topic - root["swing_mode_stat_t"] = this->get_swing_mode_state_topic(); + root[MQTT_SWING_MODE_STATE_TOPIC] = this->get_swing_mode_state_topic(); // swing_modes JsonArray &swing_modes = root.createNestedArray("swing_modes"); if (traits.supports_swing_mode(CLIMATE_SWING_OFF)) diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 0ece4b3501..be1018d97d 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -7,6 +7,8 @@ #include "esphome/core/helpers.h" #include "esphome/core/version.h" +#include "mqtt_const.h" + namespace esphome { namespace mqtt { @@ -69,49 +71,49 @@ bool MQTTComponent::send_discovery_() { this->send_discovery(root, config); // Fields from EntityBase - root["name"] = this->friendly_name(); + root[MQTT_NAME] = this->friendly_name(); if (this->is_disabled_by_default()) - root["enabled_by_default"] = false; + root[MQTT_ENABLED_BY_DEFAULT] = false; if (!this->get_icon().empty()) - root["icon"] = this->get_icon(); + root[MQTT_ICON] = this->get_icon(); if (config.state_topic) - root["state_topic"] = this->get_state_topic_(); + root[MQTT_STATE_TOPIC] = this->get_state_topic_(); if (config.command_topic) - root["command_topic"] = this->get_command_topic_(); + root[MQTT_COMMAND_TOPIC] = this->get_command_topic_(); if (this->availability_ == nullptr) { if (!global_mqtt_client->get_availability().topic.empty()) { - root["availability_topic"] = global_mqtt_client->get_availability().topic; + root[MQTT_AVAILABILITY_TOPIC] = global_mqtt_client->get_availability().topic; if (global_mqtt_client->get_availability().payload_available != "online") - root["payload_available"] = global_mqtt_client->get_availability().payload_available; + root[MQTT_PAYLOAD_AVAILABLE] = global_mqtt_client->get_availability().payload_available; if (global_mqtt_client->get_availability().payload_not_available != "offline") - root["payload_not_available"] = global_mqtt_client->get_availability().payload_not_available; + root[MQTT_PAYLOAD_NOT_AVAILABLE] = global_mqtt_client->get_availability().payload_not_available; } } else if (!this->availability_->topic.empty()) { - root["availability_topic"] = this->availability_->topic; + root[MQTT_AVAILABILITY_TOPIC] = this->availability_->topic; if (this->availability_->payload_available != "online") - root["payload_available"] = this->availability_->payload_available; + root[MQTT_PAYLOAD_AVAILABLE] = this->availability_->payload_available; if (this->availability_->payload_not_available != "offline") - root["payload_not_available"] = this->availability_->payload_not_available; + root[MQTT_PAYLOAD_NOT_AVAILABLE] = this->availability_->payload_not_available; } const std::string &node_name = App.get_name(); std::string unique_id = this->unique_id(); if (!unique_id.empty()) { - root["unique_id"] = unique_id; + root[MQTT_UNIQUE_ID] = unique_id; } else { // default to almost-unique ID. It's a hack but the only way to get that // gorgeous device registry view. - root["unique_id"] = "ESP" + this->component_type() + this->get_default_object_id_(); + root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_(); } - JsonObject &device_info = root.createNestedObject("device"); - device_info["identifiers"] = get_mac_address(); - device_info["name"] = node_name; - device_info["sw_version"] = "esphome v" ESPHOME_VERSION " " + App.get_compilation_time(); - device_info["model"] = ESPHOME_BOARD; - device_info["manufacturer"] = "espressif"; + JsonObject &device_info = root.createNestedObject(MQTT_DEVICE); + device_info[MQTT_DEVICE_IDENTIFIERS] = get_mac_address(); + device_info[MQTT_DEVICE_NAME] = node_name; + device_info[MQTT_DEVICE_SW_VERSION] = "esphome v" ESPHOME_VERSION " " + App.get_compilation_time(); + device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD; + device_info[MQTT_DEVICE_MANUFACTURER] = "espressif"; }, 0, discovery_info.retain); } diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h new file mode 100644 index 0000000000..df5465ce9a --- /dev/null +++ b/esphome/components/mqtt/mqtt_const.h @@ -0,0 +1,518 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_MQTT + +namespace esphome { +namespace mqtt { + +#ifdef USE_MQTT_ABBREVIATIONS + +constexpr const char *const MQTT_ACTION_TOPIC = "act_t"; +constexpr const char *const MQTT_ACTION_TEMPLATE = "act_tpl"; +constexpr const char *const MQTT_AUTOMATION_TYPE = "atype"; +constexpr const char *const MQTT_AUX_COMMAND_TOPIC = "aux_cmd_t"; +constexpr const char *const MQTT_AUX_STATE_TEMPLATE = "aux_stat_tpl"; +constexpr const char *const MQTT_AUX_STATE_TOPIC = "aux_stat_t"; +constexpr const char *const MQTT_AVAILABILITY = "avty"; +constexpr const char *const MQTT_AVAILABILITY_MODE = "avty_mode"; +constexpr const char *const MQTT_AVAILABILITY_TOPIC = "avty_t"; +constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC = "away_mode_cmd_t"; +constexpr const char *const MQTT_AWAY_MODE_STATE_TEMPLATE = "away_mode_stat_tpl"; +constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC = "away_mode_stat_t"; +constexpr const char *const MQTT_BLUE_TEMPLATE = "b_tpl"; +constexpr const char *const MQTT_BRIGHTNESS_COMMAND_TOPIC = "bri_cmd_t"; +constexpr const char *const MQTT_BRIGHTNESS_SCALE = "bri_scl"; +constexpr const char *const MQTT_BRIGHTNESS_STATE_TOPIC = "bri_stat_t"; +constexpr const char *const MQTT_BRIGHTNESS_TEMPLATE = "bri_tpl"; +constexpr const char *const MQTT_BRIGHTNESS_VALUE_TEMPLATE = "bri_val_tpl"; +constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "clr_temp_cmd_tpl"; +constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "bat_lev_t"; +constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "bat_lev_tpl"; +constexpr const char *const MQTT_CONFIGURATION_URL = "cu"; +constexpr const char *const MQTT_CHARGING_TOPIC = "chrg_t"; +constexpr const char *const MQTT_CHARGING_TEMPLATE = "chrg_tpl"; +constexpr const char *const MQTT_COLOR_MODE = "clrm"; +constexpr const char *const MQTT_COLOR_MODE_STATE_TOPIC = "clrm_stat_t"; +constexpr const char *const MQTT_COLOR_MODE_VALUE_TEMPLATE = "clrm_val_tpl"; +constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TOPIC = "clr_temp_cmd_t"; +constexpr const char *const MQTT_COLOR_TEMP_STATE_TOPIC = "clr_temp_stat_t"; +constexpr const char *const MQTT_COLOR_TEMP_TEMPLATE = "clr_temp_tpl"; +constexpr const char *const MQTT_COLOR_TEMP_VALUE_TEMPLATE = "clr_temp_val_tpl"; +constexpr const char *const MQTT_CLEANING_TOPIC = "cln_t"; +constexpr const char *const MQTT_CLEANING_TEMPLATE = "cln_tpl"; +constexpr const char *const MQTT_COMMAND_OFF_TEMPLATE = "cmd_off_tpl"; +constexpr const char *const MQTT_COMMAND_ON_TEMPLATE = "cmd_on_tpl"; +constexpr const char *const MQTT_COMMAND_TOPIC = "cmd_t"; +constexpr const char *const MQTT_COMMAND_TEMPLATE = "cmd_tpl"; +constexpr const char *const MQTT_CODE_ARM_REQUIRED = "cod_arm_req"; +constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "cod_dis_req"; +constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "curr_temp_t"; +constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "curr_temp_tpl"; +constexpr const char *const MQTT_DEVICE = "dev"; +constexpr const char *const MQTT_DEVICE_CLASS = "dev_cla"; +constexpr const char *const MQTT_DOCKED_TOPIC = "dock_t"; +constexpr const char *const MQTT_DOCKED_TEMPLATE = "dock_tpl"; +constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "en"; +constexpr const char *const MQTT_ERROR_TOPIC = "err_t"; +constexpr const char *const MQTT_ERROR_TEMPLATE = "err_tpl"; +constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fanspd_t"; +constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fanspd_tpl"; +constexpr const char *const MQTT_FAN_SPEED_LIST = "fanspd_lst"; +constexpr const char *const MQTT_FLASH_TIME_LONG = "flsh_tlng"; +constexpr const char *const MQTT_FLASH_TIME_SHORT = "flsh_tsht"; +constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "fx_cmd_t"; +constexpr const char *const MQTT_EFFECT_LIST = "fx_list"; +constexpr const char *const MQTT_EFFECT_STATE_TOPIC = "fx_stat_t"; +constexpr const char *const MQTT_EFFECT_TEMPLATE = "fx_tpl"; +constexpr const char *const MQTT_EFFECT_VALUE_TEMPLATE = "fx_val_tpl"; +constexpr const char *const MQTT_EXPIRE_AFTER = "exp_aft"; +constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_cmd_tpl"; +constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_cmd_t"; +constexpr const char *const MQTT_FAN_MODE_STATE_TEMPLATE = "fan_mode_stat_tpl"; +constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC = "fan_mode_stat_t"; +constexpr const char *const MQTT_FORCE_UPDATE = "frc_upd"; +constexpr const char *const MQTT_GREEN_TEMPLATE = "g_tpl"; +constexpr const char *const MQTT_HOLD_COMMAND_TEMPLATE = "hold_cmd_tpl"; +constexpr const char *const MQTT_HOLD_COMMAND_TOPIC = "hold_cmd_t"; +constexpr const char *const MQTT_HOLD_STATE_TEMPLATE = "hold_stat_tpl"; +constexpr const char *const MQTT_HOLD_STATE_TOPIC = "hold_stat_t"; +constexpr const char *const MQTT_HS_COMMAND_TOPIC = "hs_cmd_t"; +constexpr const char *const MQTT_HS_STATE_TOPIC = "hs_stat_t"; +constexpr const char *const MQTT_HS_VALUE_TEMPLATE = "hs_val_tpl"; +constexpr const char *const MQTT_ICON = "ic"; +constexpr const char *const MQTT_INITIAL = "init"; +constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "hum_cmd_t"; +constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "hum_cmd_tpl"; +constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "hum_stat_t"; +constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "hum_state_tpl"; +constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attr"; +constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attr_t"; +constexpr const char *const MQTT_JSON_ATTRIBUTES_TEMPLATE = "json_attr_tpl"; +constexpr const char *const MQTT_LAST_RESET_TOPIC = "lrst_t"; +constexpr const char *const MQTT_LAST_RESET_VALUE_TEMPLATE = "lrst_val_tpl"; +constexpr const char *const MQTT_MAX = "max"; +constexpr const char *const MQTT_MIN = "min"; +constexpr const char *const MQTT_MAX_HUMIDITY = "max_hum"; +constexpr const char *const MQTT_MIN_HUMIDITY = "min_hum"; +constexpr const char *const MQTT_MAX_MIREDS = "max_mirs"; +constexpr const char *const MQTT_MIN_MIREDS = "min_mirs"; +constexpr const char *const MQTT_MAX_TEMP = "max_temp"; +constexpr const char *const MQTT_MIN_TEMP = "min_temp"; +constexpr const char *const MQTT_MODE_COMMAND_TEMPLATE = "mode_cmd_tpl"; +constexpr const char *const MQTT_MODE_COMMAND_TOPIC = "mode_cmd_t"; +constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_stat_t"; +constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_stat_tpl"; +constexpr const char *const MQTT_MODES = "modes"; +constexpr const char *const MQTT_NAME = "name"; +constexpr const char *const MQTT_OFF_DELAY = "off_dly"; +constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_cmd_type"; +constexpr const char *const MQTT_OPTIONS = "ops"; +constexpr const char *const MQTT_OPTIMISTIC = "opt"; +constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "osc_cmd_t"; +constexpr const char *const MQTT_OSCILLATION_COMMAND_TEMPLATE = "osc_cmd_tpl"; +constexpr const char *const MQTT_OSCILLATION_STATE_TOPIC = "osc_stat_t"; +constexpr const char *const MQTT_OSCILLATION_VALUE_TEMPLATE = "osc_val_tpl"; +constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "pct_cmd_t"; +constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "pct_cmd_tpl"; +constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "pct_stat_t"; +constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "pct_val_tpl"; +constexpr const char *const MQTT_PAYLOAD = "pl"; +constexpr const char *const MQTT_PAYLOAD_ARM_AWAY = "pl_arm_away"; +constexpr const char *const MQTT_PAYLOAD_ARM_HOME = "pl_arm_home"; +constexpr const char *const MQTT_PAYLOAD_ARM_NIGHT = "pl_arm_nite"; +constexpr const char *const MQTT_PAYLOAD_ARM_VACATION = "pl_arm_vacation"; +constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "pl_arm_custom_b"; +constexpr const char *const MQTT_PAYLOAD_AVAILABLE = "pl_avail"; +constexpr const char *const MQTT_PAYLOAD_CLEAN_SPOT = "pl_cln_sp"; +constexpr const char *const MQTT_PAYLOAD_CLOSE = "pl_cls"; +constexpr const char *const MQTT_PAYLOAD_DISARM = "pl_disarm"; +constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "pl_hi_spd"; +constexpr const char *const MQTT_PAYLOAD_HOME = "pl_home"; +constexpr const char *const MQTT_PAYLOAD_LOCK = "pl_lock"; +constexpr const char *const MQTT_PAYLOAD_LOCATE = "pl_loc"; +constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "pl_lo_spd"; +constexpr const char *const MQTT_PAYLOAD_MEDIUM_SPEED = "pl_med_spd"; +constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE = "pl_not_avail"; +constexpr const char *const MQTT_PAYLOAD_NOT_HOME = "pl_not_home"; +constexpr const char *const MQTT_PAYLOAD_OFF = "pl_off"; +constexpr const char *const MQTT_PAYLOAD_OFF_SPEED = "pl_off_spd"; +constexpr const char *const MQTT_PAYLOAD_ON = "pl_on"; +constexpr const char *const MQTT_PAYLOAD_OPEN = "pl_open"; +constexpr const char *const MQTT_PAYLOAD_OSCILLATION_OFF = "pl_osc_off"; +constexpr const char *const MQTT_PAYLOAD_OSCILLATION_ON = "pl_osc_on"; +constexpr const char *const MQTT_PAYLOAD_PAUSE = "pl_paus"; +constexpr const char *const MQTT_PAYLOAD_RESET = "pl_rst"; +constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "pl_rst_hum"; +constexpr const char *const MQTT_PAYLOAD_RESET_MODE = "pl_rst_mode"; +constexpr const char *const MQTT_PAYLOAD_RESET_PERCENTAGE = "pl_rst_pct"; +constexpr const char *const MQTT_PAYLOAD_RESET_PRESET_MODE = "pl_rst_pr_mode"; +constexpr const char *const MQTT_PAYLOAD_STOP = "pl_stop"; +constexpr const char *const MQTT_PAYLOAD_START = "pl_strt"; +constexpr const char *const MQTT_PAYLOAD_START_PAUSE = "pl_stpa"; +constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "pl_ret"; +constexpr const char *const MQTT_PAYLOAD_TURN_OFF = "pl_toff"; +constexpr const char *const MQTT_PAYLOAD_TURN_ON = "pl_ton"; +constexpr const char *const MQTT_PAYLOAD_UNLOCK = "pl_unlk"; +constexpr const char *const MQTT_POSITION_CLOSED = "pos_clsd"; +constexpr const char *const MQTT_POSITION_OPEN = "pos_open"; +constexpr const char *const MQTT_POWER_COMMAND_TOPIC = "pow_cmd_t"; +constexpr const char *const MQTT_POWER_STATE_TOPIC = "pow_stat_t"; +constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "pow_stat_tpl"; +constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "pr_mode_cmd_t"; +constexpr const char *const MQTT_PRESET_MODE_COMMAND_TEMPLATE = "pr_mode_cmd_tpl"; +constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "pr_mode_stat_t"; +constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "pr_mode_val_tpl"; +constexpr const char *const MQTT_PRESET_MODES = "pr_modes"; +constexpr const char *const MQTT_RED_TEMPLATE = "r_tpl"; +constexpr const char *const MQTT_RETAIN = "ret"; +constexpr const char *const MQTT_RGB_COMMAND_TEMPLATE = "rgb_cmd_tpl"; +constexpr const char *const MQTT_RGB_COMMAND_TOPIC = "rgb_cmd_t"; +constexpr const char *const MQTT_RGB_STATE_TOPIC = "rgb_stat_t"; +constexpr const char *const MQTT_RGB_VALUE_TEMPLATE = "rgb_val_tpl"; +constexpr const char *const MQTT_RGBW_COMMAND_TEMPLATE = "rgbw_cmd_tpl"; +constexpr const char *const MQTT_RGBW_COMMAND_TOPIC = "rgbw_cmd_t"; +constexpr const char *const MQTT_RGBW_STATE_TOPIC = "rgbw_stat_t"; +constexpr const char *const MQTT_RGBW_VALUE_TEMPLATE = "rgbw_val_tpl"; +constexpr const char *const MQTT_RGBWW_COMMAND_TEMPLATE = "rgbww_cmd_tpl"; +constexpr const char *const MQTT_RGBWW_COMMAND_TOPIC = "rgbww_cmd_t"; +constexpr const char *const MQTT_RGBWW_STATE_TOPIC = "rgbww_stat_t"; +constexpr const char *const MQTT_RGBWW_VALUE_TEMPLATE = "rgbww_val_tpl"; +constexpr const char *const MQTT_SEND_COMMAND_TOPIC = "send_cmd_t"; +constexpr const char *const MQTT_SEND_IF_OFF = "send_if_off"; +constexpr const char *const MQTT_SET_FAN_SPEED_TOPIC = "set_fan_spd_t"; +constexpr const char *const MQTT_SET_POSITION_TEMPLATE = "set_pos_tpl"; +constexpr const char *const MQTT_SET_POSITION_TOPIC = "set_pos_t"; +constexpr const char *const MQTT_POSITION_TOPIC = "pos_t"; +constexpr const char *const MQTT_POSITION_TEMPLATE = "pos_tpl"; +constexpr const char *const MQTT_SPEED_COMMAND_TOPIC = "spd_cmd_t"; +constexpr const char *const MQTT_SPEED_STATE_TOPIC = "spd_stat_t"; +constexpr const char *const MQTT_SPEED_RANGE_MIN = "spd_rng_min"; +constexpr const char *const MQTT_SPEED_RANGE_MAX = "spd_rng_max"; +constexpr const char *const MQTT_SPEED_VALUE_TEMPLATE = "spd_val_tpl"; +constexpr const char *const MQTT_SPEEDS = "spds"; +constexpr const char *const MQTT_SOURCE_TYPE = "src_type"; +constexpr const char *const MQTT_STATE_CLASS = "stat_cla"; +constexpr const char *const MQTT_STATE_CLOSED = "stat_clsd"; +constexpr const char *const MQTT_STATE_CLOSING = "stat_closing"; +constexpr const char *const MQTT_STATE_OFF = "stat_off"; +constexpr const char *const MQTT_STATE_ON = "stat_on"; +constexpr const char *const MQTT_STATE_OPEN = "stat_open"; +constexpr const char *const MQTT_STATE_OPENING = "stat_opening"; +constexpr const char *const MQTT_STATE_STOPPED = "stat_stopped"; +constexpr const char *const MQTT_STATE_LOCKED = "stat_locked"; +constexpr const char *const MQTT_STATE_UNLOCKED = "stat_unlocked"; +constexpr const char *const MQTT_STATE_TOPIC = "stat_t"; +constexpr const char *const MQTT_STATE_TEMPLATE = "stat_tpl"; +constexpr const char *const MQTT_STATE_VALUE_TEMPLATE = "stat_val_tpl"; +constexpr const char *const MQTT_STEP = "step"; +constexpr const char *const MQTT_SUBTYPE = "stype"; +constexpr const char *const MQTT_SUPPORTED_FEATURES = "sup_feat"; +constexpr const char *const MQTT_SUPPORTED_COLOR_MODES = "sup_clrm"; +constexpr const char *const MQTT_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_cmd_tpl"; +constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC = "swing_mode_cmd_t"; +constexpr const char *const MQTT_SWING_MODE_STATE_TEMPLATE = "swing_mode_stat_tpl"; +constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC = "swing_mode_stat_t"; +constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temp_cmd_tpl"; +constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temp_cmd_t"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temp_hi_cmd_tpl"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC = "temp_hi_cmd_t"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE = "temp_hi_stat_tpl"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TOPIC = "temp_hi_stat_t"; +constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE = "temp_lo_cmd_tpl"; +constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TOPIC = "temp_lo_cmd_t"; +constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TEMPLATE = "temp_lo_stat_tpl"; +constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TOPIC = "temp_lo_stat_t"; +constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temp_stat_tpl"; +constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC = "temp_stat_t"; +constexpr const char *const MQTT_TEMPERATURE_UNIT = "temp_unit"; +constexpr const char *const MQTT_TILT_CLOSED_VALUE = "tilt_clsd_val"; +constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_cmd_t"; +constexpr const char *const MQTT_TILT_COMMAND_TEMPLATE = "tilt_cmd_tpl"; +constexpr const char *const MQTT_TILT_INVERT_STATE = "tilt_inv_stat"; +constexpr const char *const MQTT_TILT_MAX = "tilt_max"; +constexpr const char *const MQTT_TILT_MIN = "tilt_min"; +constexpr const char *const MQTT_TILT_OPENED_VALUE = "tilt_opnd_val"; +constexpr const char *const MQTT_TILT_OPTIMISTIC = "tilt_opt"; +constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_t"; +constexpr const char *const MQTT_TILT_STATUS_TEMPLATE = "tilt_status_tpl"; +constexpr const char *const MQTT_TOPIC = "t"; +constexpr const char *const MQTT_UNIQUE_ID = "uniq_id"; +constexpr const char *const MQTT_UNIT_OF_MEASUREMENT = "unit_of_meas"; +constexpr const char *const MQTT_VALUE_TEMPLATE = "val_tpl"; +constexpr const char *const MQTT_WHITE_COMMAND_TOPIC = "whit_cmd_t"; +constexpr const char *const MQTT_WHITE_SCALE = "whit_scl"; +constexpr const char *const MQTT_WHITE_VALUE_COMMAND_TOPIC = "whit_val_cmd_t"; +constexpr const char *const MQTT_WHITE_VALUE_SCALE = "whit_val_scl"; +constexpr const char *const MQTT_WHITE_VALUE_STATE_TOPIC = "whit_val_stat_t"; +constexpr const char *const MQTT_WHITE_VALUE_TEMPLATE = "whit_val_tpl"; +constexpr const char *const MQTT_XY_COMMAND_TOPIC = "xy_cmd_t"; +constexpr const char *const MQTT_XY_STATE_TOPIC = "xy_stat_t"; +constexpr const char *const MQTT_XY_VALUE_TEMPLATE = "xy_val_tpl"; + +constexpr const char *const MQTT_DEVICE_CONNECTIONS = "cns"; +constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "ids"; +constexpr const char *const MQTT_DEVICE_NAME = "name"; +constexpr const char *const MQTT_DEVICE_MANUFACTURER = "mf"; +constexpr const char *const MQTT_DEVICE_MODEL = "mdl"; +constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw"; +constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "sa"; + +#else + +constexpr const char *const MQTT_ACTION_TOPIC = "action_topic"; +constexpr const char *const MQTT_ACTION_TEMPLATE = "action_template"; +constexpr const char *const MQTT_AUTOMATION_TYPE = "automation_type"; +constexpr const char *const MQTT_AUX_COMMAND_TOPIC = "aux_command_topic"; +constexpr const char *const MQTT_AUX_STATE_TEMPLATE = "aux_state_template"; +constexpr const char *const MQTT_AUX_STATE_TOPIC = "aux_state_topic"; +constexpr const char *const MQTT_AVAILABILITY = "availability"; +constexpr const char *const MQTT_AVAILABILITY_MODE = "availability_mode"; +constexpr const char *const MQTT_AVAILABILITY_TOPIC = "availability_topic"; +constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC = "away_mode_command_topic"; +constexpr const char *const MQTT_AWAY_MODE_STATE_TEMPLATE = "away_mode_state_template"; +constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC = "away_mode_state_topic"; +constexpr const char *const MQTT_BLUE_TEMPLATE = "blue_template"; +constexpr const char *const MQTT_BRIGHTNESS_COMMAND_TOPIC = "brightness_command_topic"; +constexpr const char *const MQTT_BRIGHTNESS_SCALE = "brightness_scale"; +constexpr const char *const MQTT_BRIGHTNESS_STATE_TOPIC = "brightness_state_topic"; +constexpr const char *const MQTT_BRIGHTNESS_TEMPLATE = "brightness_template"; +constexpr const char *const MQTT_BRIGHTNESS_VALUE_TEMPLATE = "brightness_value_template"; +constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "color_temp_command_template"; +constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "battery_level_topic"; +constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "battery_level_template"; +constexpr const char *const MQTT_CONFIGURATION_URL = "configuration_url"; +constexpr const char *const MQTT_CHARGING_TOPIC = "charging_topic"; +constexpr const char *const MQTT_CHARGING_TEMPLATE = "charging_template"; +constexpr const char *const MQTT_COLOR_MODE = "color_mode"; +constexpr const char *const MQTT_COLOR_MODE_STATE_TOPIC = "color_mode_state_topic"; +constexpr const char *const MQTT_COLOR_MODE_VALUE_TEMPLATE = "color_mode_value_template"; +constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TOPIC = "color_temp_command_topic"; +constexpr const char *const MQTT_COLOR_TEMP_STATE_TOPIC = "color_temp_state_topic"; +constexpr const char *const MQTT_COLOR_TEMP_TEMPLATE = "color_temp_template"; +constexpr const char *const MQTT_COLOR_TEMP_VALUE_TEMPLATE = "color_temp_value_template"; +constexpr const char *const MQTT_CLEANING_TOPIC = "cleaning_topic"; +constexpr const char *const MQTT_CLEANING_TEMPLATE = "cleaning_template"; +constexpr const char *const MQTT_COMMAND_OFF_TEMPLATE = "command_off_template"; +constexpr const char *const MQTT_COMMAND_ON_TEMPLATE = "command_on_template"; +constexpr const char *const MQTT_COMMAND_TOPIC = "command_topic"; +constexpr const char *const MQTT_COMMAND_TEMPLATE = "command_template"; +constexpr const char *const MQTT_CODE_ARM_REQUIRED = "code_arm_required"; +constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "code_disarm_required"; +constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "current_temperature_topic"; +constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "current_temperature_template"; +constexpr const char *const MQTT_DEVICE = "device"; +constexpr const char *const MQTT_DEVICE_CLASS = "device_class"; +constexpr const char *const MQTT_DOCKED_TOPIC = "docked_topic"; +constexpr const char *const MQTT_DOCKED_TEMPLATE = "docked_template"; +constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "enabled_by_default"; +constexpr const char *const MQTT_ERROR_TOPIC = "error_topic"; +constexpr const char *const MQTT_ERROR_TEMPLATE = "error_template"; +constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fan_speed_topic"; +constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fan_speed_template"; +constexpr const char *const MQTT_FAN_SPEED_LIST = "fan_speed_list"; +constexpr const char *const MQTT_FLASH_TIME_LONG = "flash_time_long"; +constexpr const char *const MQTT_FLASH_TIME_SHORT = "flash_time_short"; +constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "effect_command_topic"; +constexpr const char *const MQTT_EFFECT_LIST = "effect_list"; +constexpr const char *const MQTT_EFFECT_STATE_TOPIC = "effect_state_topic"; +constexpr const char *const MQTT_EFFECT_TEMPLATE = "effect_template"; +constexpr const char *const MQTT_EFFECT_VALUE_TEMPLATE = "effect_value_template"; +constexpr const char *const MQTT_EXPIRE_AFTER = "expire_after"; +constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_command_template"; +constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_command_topic"; +constexpr const char *const MQTT_FAN_MODE_STATE_TEMPLATE = "fan_mode_state_template"; +constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC = "fan_mode_state_topic"; +constexpr const char *const MQTT_FORCE_UPDATE = "force_update"; +constexpr const char *const MQTT_GREEN_TEMPLATE = "green_template"; +constexpr const char *const MQTT_HOLD_COMMAND_TEMPLATE = "hold_command_template"; +constexpr const char *const MQTT_HOLD_COMMAND_TOPIC = "hold_command_topic"; +constexpr const char *const MQTT_HOLD_STATE_TEMPLATE = "hold_state_template"; +constexpr const char *const MQTT_HOLD_STATE_TOPIC = "hold_state_topic"; +constexpr const char *const MQTT_HS_COMMAND_TOPIC = "hs_command_topic"; +constexpr const char *const MQTT_HS_STATE_TOPIC = "hs_state_topic"; +constexpr const char *const MQTT_HS_VALUE_TEMPLATE = "hs_value_template"; +constexpr const char *const MQTT_ICON = "icon"; +constexpr const char *const MQTT_INITIAL = "initial"; +constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "target_humidity_command_topic"; +constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "target_humidity_command_template"; +constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "target_humidity_state_topic"; +constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "target_humidity_state_template"; +constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attributes"; +constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attributes_topic"; +constexpr const char *const MQTT_JSON_ATTRIBUTES_TEMPLATE = "json_attributes_template"; +constexpr const char *const MQTT_LAST_RESET_TOPIC = "last_reset_topic"; +constexpr const char *const MQTT_LAST_RESET_VALUE_TEMPLATE = "last_reset_value_template"; +constexpr const char *const MQTT_MAX = "max"; +constexpr const char *const MQTT_MIN = "min"; +constexpr const char *const MQTT_MAX_HUMIDITY = "max_humidity"; +constexpr const char *const MQTT_MIN_HUMIDITY = "min_humidity"; +constexpr const char *const MQTT_MAX_MIREDS = "max_mireds"; +constexpr const char *const MQTT_MIN_MIREDS = "min_mireds"; +constexpr const char *const MQTT_MAX_TEMP = "max_temp"; +constexpr const char *const MQTT_MIN_TEMP = "min_temp"; +constexpr const char *const MQTT_MODE_COMMAND_TEMPLATE = "mode_command_template"; +constexpr const char *const MQTT_MODE_COMMAND_TOPIC = "mode_command_topic"; +constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_state_topic"; +constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_state_template"; +constexpr const char *const MQTT_MODES = "modes"; +constexpr const char *const MQTT_NAME = "name"; +constexpr const char *const MQTT_OFF_DELAY = "off_delay"; +constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_command_type"; +constexpr const char *const MQTT_OPTIONS = "options"; +constexpr const char *const MQTT_OPTIMISTIC = "optimistic"; +constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "oscillation_command_topic"; +constexpr const char *const MQTT_OSCILLATION_COMMAND_TEMPLATE = "oscillation_command_template"; +constexpr const char *const MQTT_OSCILLATION_STATE_TOPIC = "oscillation_state_topic"; +constexpr const char *const MQTT_OSCILLATION_VALUE_TEMPLATE = "oscillation_value_template"; +constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "percentage_command_topic"; +constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "percentage_command_template"; +constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "percentage_state_topic"; +constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "percentage_value_template"; +constexpr const char *const MQTT_PAYLOAD = "payload"; +constexpr const char *const MQTT_PAYLOAD_ARM_AWAY = "payload_arm_away"; +constexpr const char *const MQTT_PAYLOAD_ARM_HOME = "payload_arm_home"; +constexpr const char *const MQTT_PAYLOAD_ARM_NIGHT = "payload_arm_night"; +constexpr const char *const MQTT_PAYLOAD_ARM_VACATION = "payload_arm_vacation"; +constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "payload_arm_custom_bypass"; +constexpr const char *const MQTT_PAYLOAD_AVAILABLE = "payload_available"; +constexpr const char *const MQTT_PAYLOAD_CLEAN_SPOT = "payload_clean_spot"; +constexpr const char *const MQTT_PAYLOAD_CLOSE = "payload_close"; +constexpr const char *const MQTT_PAYLOAD_DISARM = "payload_disarm"; +constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "payload_high_speed"; +constexpr const char *const MQTT_PAYLOAD_HOME = "payload_home"; +constexpr const char *const MQTT_PAYLOAD_LOCK = "payload_lock"; +constexpr const char *const MQTT_PAYLOAD_LOCATE = "payload_locate"; +constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "payload_low_speed"; +constexpr const char *const MQTT_PAYLOAD_MEDIUM_SPEED = "payload_medium_speed"; +constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE = "payload_not_available"; +constexpr const char *const MQTT_PAYLOAD_NOT_HOME = "payload_not_home"; +constexpr const char *const MQTT_PAYLOAD_OFF = "payload_off"; +constexpr const char *const MQTT_PAYLOAD_OFF_SPEED = "payload_off_speed"; +constexpr const char *const MQTT_PAYLOAD_ON = "payload_on"; +constexpr const char *const MQTT_PAYLOAD_OPEN = "payload_open"; +constexpr const char *const MQTT_PAYLOAD_OSCILLATION_OFF = "payload_oscillation_off"; +constexpr const char *const MQTT_PAYLOAD_OSCILLATION_ON = "payload_oscillation_on"; +constexpr const char *const MQTT_PAYLOAD_PAUSE = "payload_pause"; +constexpr const char *const MQTT_PAYLOAD_RESET = "payload_reset"; +constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "payload_reset_humidity"; +constexpr const char *const MQTT_PAYLOAD_RESET_MODE = "payload_reset_mode"; +constexpr const char *const MQTT_PAYLOAD_RESET_PERCENTAGE = "payload_reset_percentage"; +constexpr const char *const MQTT_PAYLOAD_RESET_PRESET_MODE = "payload_reset_preset_mode"; +constexpr const char *const MQTT_PAYLOAD_STOP = "payload_stop"; +constexpr const char *const MQTT_PAYLOAD_START = "payload_start"; +constexpr const char *const MQTT_PAYLOAD_START_PAUSE = "payload_start_pause"; +constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "payload_return_to_base"; +constexpr const char *const MQTT_PAYLOAD_TURN_OFF = "payload_turn_off"; +constexpr const char *const MQTT_PAYLOAD_TURN_ON = "payload_turn_on"; +constexpr const char *const MQTT_PAYLOAD_UNLOCK = "payload_unlock"; +constexpr const char *const MQTT_POSITION_CLOSED = "position_closed"; +constexpr const char *const MQTT_POSITION_OPEN = "position_open"; +constexpr const char *const MQTT_POWER_COMMAND_TOPIC = "power_command_topic"; +constexpr const char *const MQTT_POWER_STATE_TOPIC = "power_state_topic"; +constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "power_state_template"; +constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "preset_mode_command_topic"; +constexpr const char *const MQTT_PRESET_MODE_COMMAND_TEMPLATE = "preset_mode_command_template"; +constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "preset_mode_state_topic"; +constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "preset_mode_value_template"; +constexpr const char *const MQTT_PRESET_MODES = "preset_modes"; +constexpr const char *const MQTT_RED_TEMPLATE = "red_template"; +constexpr const char *const MQTT_RETAIN = "retain"; +constexpr const char *const MQTT_RGB_COMMAND_TEMPLATE = "rgb_command_template"; +constexpr const char *const MQTT_RGB_COMMAND_TOPIC = "rgb_command_topic"; +constexpr const char *const MQTT_RGB_STATE_TOPIC = "rgb_state_topic"; +constexpr const char *const MQTT_RGB_VALUE_TEMPLATE = "rgb_value_template"; +constexpr const char *const MQTT_RGBW_COMMAND_TEMPLATE = "rgbw_command_template"; +constexpr const char *const MQTT_RGBW_COMMAND_TOPIC = "rgbw_command_topic"; +constexpr const char *const MQTT_RGBW_STATE_TOPIC = "rgbw_state_topic"; +constexpr const char *const MQTT_RGBW_VALUE_TEMPLATE = "rgbw_value_template"; +constexpr const char *const MQTT_RGBWW_COMMAND_TEMPLATE = "rgbww_command_template"; +constexpr const char *const MQTT_RGBWW_COMMAND_TOPIC = "rgbww_command_topic"; +constexpr const char *const MQTT_RGBWW_STATE_TOPIC = "rgbww_state_topic"; +constexpr const char *const MQTT_RGBWW_VALUE_TEMPLATE = "rgbww_value_template"; +constexpr const char *const MQTT_SEND_COMMAND_TOPIC = "send_command_topic"; +constexpr const char *const MQTT_SEND_IF_OFF = "send_if_off"; +constexpr const char *const MQTT_SET_FAN_SPEED_TOPIC = "set_fan_speed_topic"; +constexpr const char *const MQTT_SET_POSITION_TEMPLATE = "set_position_template"; +constexpr const char *const MQTT_SET_POSITION_TOPIC = "set_position_topic"; +constexpr const char *const MQTT_POSITION_TOPIC = "position_topic"; +constexpr const char *const MQTT_POSITION_TEMPLATE = "position_template"; +constexpr const char *const MQTT_SPEED_COMMAND_TOPIC = "speed_command_topic"; +constexpr const char *const MQTT_SPEED_STATE_TOPIC = "speed_state_topic"; +constexpr const char *const MQTT_SPEED_RANGE_MIN = "speed_range_min"; +constexpr const char *const MQTT_SPEED_RANGE_MAX = "speed_range_max"; +constexpr const char *const MQTT_SPEED_VALUE_TEMPLATE = "speed_value_template"; +constexpr const char *const MQTT_SPEEDS = "speeds"; +constexpr const char *const MQTT_SOURCE_TYPE = "source_type"; +constexpr const char *const MQTT_STATE_CLASS = "state_class"; +constexpr const char *const MQTT_STATE_CLOSED = "state_closed"; +constexpr const char *const MQTT_STATE_CLOSING = "state_closing"; +constexpr const char *const MQTT_STATE_OFF = "state_off"; +constexpr const char *const MQTT_STATE_ON = "state_on"; +constexpr const char *const MQTT_STATE_OPEN = "state_open"; +constexpr const char *const MQTT_STATE_OPENING = "state_opening"; +constexpr const char *const MQTT_STATE_STOPPED = "state_stopped"; +constexpr const char *const MQTT_STATE_LOCKED = "state_locked"; +constexpr const char *const MQTT_STATE_UNLOCKED = "state_unlocked"; +constexpr const char *const MQTT_STATE_TOPIC = "state_topic"; +constexpr const char *const MQTT_STATE_TEMPLATE = "state_template"; +constexpr const char *const MQTT_STATE_VALUE_TEMPLATE = "state_value_template"; +constexpr const char *const MQTT_STEP = "step"; +constexpr const char *const MQTT_SUBTYPE = "subtype"; +constexpr const char *const MQTT_SUPPORTED_FEATURES = "supported_features"; +constexpr const char *const MQTT_SUPPORTED_COLOR_MODES = "supported_color_modes"; +constexpr const char *const MQTT_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_command_template"; +constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC = "swing_mode_command_topic"; +constexpr const char *const MQTT_SWING_MODE_STATE_TEMPLATE = "swing_mode_state_template"; +constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC = "swing_mode_state_topic"; +constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temperature_command_template"; +constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temperature_command_topic"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temperature_high_command_template"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC = "temperature_high_command_topic"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE = "temperature_high_state_template"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TOPIC = "temperature_high_state_topic"; +constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE = "temperature_low_command_template"; +constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TOPIC = "temperature_low_command_topic"; +constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TEMPLATE = "temperature_low_state_template"; +constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TOPIC = "temperature_low_state_topic"; +constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temperature_state_template"; +constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC = "temperature_state_topic"; +constexpr const char *const MQTT_TEMPERATURE_UNIT = "temperature_unit"; +constexpr const char *const MQTT_TILT_CLOSED_VALUE = "tilt_closed_value"; +constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_command_topic"; +constexpr const char *const MQTT_TILT_COMMAND_TEMPLATE = "tilt_command_template"; +constexpr const char *const MQTT_TILT_INVERT_STATE = "tilt_invert_state"; +constexpr const char *const MQTT_TILT_MAX = "tilt_max"; +constexpr const char *const MQTT_TILT_MIN = "tilt_min"; +constexpr const char *const MQTT_TILT_OPENED_VALUE = "tilt_opened_value"; +constexpr const char *const MQTT_TILT_OPTIMISTIC = "tilt_optimistic"; +constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_topic"; +constexpr const char *const MQTT_TILT_STATUS_TEMPLATE = "tilt_status_template"; +constexpr const char *const MQTT_TOPIC = "topic"; +constexpr const char *const MQTT_UNIQUE_ID = "unique_id"; +constexpr const char *const MQTT_UNIT_OF_MEASUREMENT = "unit_of_measurement"; +constexpr const char *const MQTT_VALUE_TEMPLATE = "value_template"; +constexpr const char *const MQTT_WHITE_COMMAND_TOPIC = "white_command_topic"; +constexpr const char *const MQTT_WHITE_SCALE = "white_scale"; +constexpr const char *const MQTT_WHITE_VALUE_COMMAND_TOPIC = "white_value_command_topic"; +constexpr const char *const MQTT_WHITE_VALUE_SCALE = "white_value_scale"; +constexpr const char *const MQTT_WHITE_VALUE_STATE_TOPIC = "white_value_state_topic"; +constexpr const char *const MQTT_WHITE_VALUE_TEMPLATE = "white_value_template"; +constexpr const char *const MQTT_XY_COMMAND_TOPIC = "xy_command_topic"; +constexpr const char *const MQTT_XY_STATE_TOPIC = "xy_state_topic"; +constexpr const char *const MQTT_XY_VALUE_TEMPLATE = "xy_value_template"; + +constexpr const char *const MQTT_DEVICE_CONNECTIONS = "connections"; +constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "identifiers"; +constexpr const char *const MQTT_DEVICE_NAME = "name"; +constexpr const char *const MQTT_DEVICE_MANUFACTURER = "manufacturer"; +constexpr const char *const MQTT_DEVICE_MODEL = "model"; +constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw_version"; +constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area"; +#endif + +} // namespace mqtt +} // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index e8bc7f0e30..7bf3204222 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -1,6 +1,8 @@ #include "mqtt_cover.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_COVER @@ -63,20 +65,20 @@ void MQTTCoverComponent::dump_config() { } void MQTTCoverComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (!this->cover_->get_device_class().empty()) - root["device_class"] = this->cover_->get_device_class(); + root[MQTT_DEVICE_CLASS] = this->cover_->get_device_class(); auto traits = this->cover_->get_traits(); if (traits.get_is_assumed_state()) { - root["optimistic"] = true; + root[MQTT_OPTIMISTIC] = true; } if (traits.get_supports_position()) { config.state_topic = false; - root["position_topic"] = this->get_position_state_topic(); - root["set_position_topic"] = this->get_position_command_topic(); + root[MQTT_POSITION_TOPIC] = this->get_position_state_topic(); + root[MQTT_SET_POSITION_TOPIC] = this->get_position_command_topic(); } if (traits.get_supports_tilt()) { - root["tilt_status_topic"] = this->get_tilt_state_topic(); - root["tilt_command_topic"] = this->get_tilt_command_topic(); + root[MQTT_TILT_STATUS_TOPIC] = this->get_tilt_state_topic(); + root[MQTT_TILT_COMMAND_TOPIC] = this->get_tilt_command_topic(); } if (traits.get_supports_tilt() && !traits.get_supports_position()) { config.command_topic = false; diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index 1703343a77..a9d77789e1 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -1,6 +1,8 @@ #include "mqtt_fan.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_FAN #include "esphome/components/fan/fan_helpers.h" @@ -120,14 +122,14 @@ bool MQTTFanComponent::send_initial_state() { return this->publish_state(); } void MQTTFanComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (this->state_->get_traits().supports_oscillation()) { - root["oscillation_command_topic"] = this->get_oscillation_command_topic(); - root["oscillation_state_topic"] = this->get_oscillation_state_topic(); + root[MQTT_OSCILLATION_COMMAND_TOPIC] = this->get_oscillation_command_topic(); + root[MQTT_OSCILLATION_STATE_TOPIC] = this->get_oscillation_state_topic(); } if (this->state_->get_traits().supports_speed()) { root["speed_level_command_topic"] = this->get_speed_level_command_topic(); root["speed_level_state_topic"] = this->get_speed_level_state_topic(); - root["speed_command_topic"] = this->get_speed_command_topic(); - root["speed_state_topic"] = this->get_speed_state_topic(); + root[MQTT_SPEED_COMMAND_TOPIC] = this->get_speed_command_topic(); + root[MQTT_SPEED_STATE_TOPIC] = this->get_speed_state_topic(); } } bool MQTTFanComponent::publish_state() { diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index a88358a6b2..54204a9e7f 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -1,6 +1,8 @@ #include "mqtt_light.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_LIGHT @@ -38,7 +40,7 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover root["schema"] = "json"; auto traits = this->state_->get_traits(); - root["color_mode"] = true; + root[MQTT_COLOR_MODE] = true; JsonArray &color_modes = root.createNestedArray("supported_color_modes"); if (traits.supports_color_mode(ColorMode::ON_OFF)) color_modes.add("onoff"); @@ -64,7 +66,7 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover if (this->state_->supports_effects()) { root["effect"] = true; - JsonArray &effect_list = root.createNestedArray("effect_list"); + JsonArray &effect_list = root.createNestedArray(MQTT_EFFECT_LIST); for (auto *effect : this->state_->get_effects()) effect_list.add(effect->get_name()); effect_list.add("None"); diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 674fd77bdf..9b2292cd76 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -1,6 +1,8 @@ #include "mqtt_number.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_NUMBER @@ -38,9 +40,9 @@ const EntityBase *MQTTNumberComponent::get_entity() const { return this->number_ void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { const auto &traits = number_->traits; // https://www.home-assistant.io/integrations/number.mqtt/ - root["min"] = traits.get_min_value(); - root["max"] = traits.get_max_value(); - root["step"] = traits.get_step(); + root[MQTT_MIN] = traits.get_min_value(); + root[MQTT_MAX] = traits.get_max_value(); + root[MQTT_STEP] = traits.get_step(); config.command_topic = true; } diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index b499636006..b8371de00e 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -1,6 +1,8 @@ #include "mqtt_select.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_SELECT @@ -33,7 +35,7 @@ const EntityBase *MQTTSelectComponent::get_entity() const { return this->select_ void MQTTSelectComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { const auto &traits = select_->traits; // https://www.home-assistant.io/integrations/select.mqtt/ - JsonArray &options = root.createNestedArray("options"); + JsonArray &options = root.createNestedArray(MQTT_OPTIONS); for (const auto &option : traits.get_options()) options.add(option); diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index 78710ff403..dd6423e8f3 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -1,6 +1,8 @@ #include "mqtt_sensor.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_SENSOR @@ -42,19 +44,19 @@ void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (!this->sensor_->get_device_class().empty()) - root["device_class"] = this->sensor_->get_device_class(); + root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); if (!this->sensor_->get_unit_of_measurement().empty()) - root["unit_of_measurement"] = this->sensor_->get_unit_of_measurement(); + root[MQTT_UNIT_OF_MEASUREMENT] = this->sensor_->get_unit_of_measurement(); if (this->get_expire_after() > 0) - root["expire_after"] = this->get_expire_after() / 1000; + root[MQTT_EXPIRE_AFTER] = this->get_expire_after() / 1000; if (this->sensor_->get_force_update()) - root["force_update"] = true; + root[MQTT_FORCE_UPDATE] = true; if (this->sensor_->get_state_class() != STATE_CLASS_NONE) - root["state_class"] = state_class_to_string(this->sensor_->get_state_class()); + root[MQTT_STATE_CLASS] = state_class_to_string(this->sensor_->get_state_class()); config.command_topic = false; } diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index 16cf102f7e..edaa6e7859 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -1,6 +1,8 @@ #include "mqtt_switch.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_SWITCH @@ -44,7 +46,7 @@ std::string MQTTSwitchComponent::component_type() const { return "switch"; } const EntityBase *MQTTSwitchComponent::get_entity() const { return this->switch_; } void MQTTSwitchComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (this->switch_->assumed_state()) - root["optimistic"] = true; + root[MQTT_OPTIMISTIC] = true; } bool MQTTSwitchComponent::send_initial_state() { return this->publish_state(this->switch_->state); } diff --git a/esphome/const.py b/esphome/const.py index e00eebb1a4..bac446a7c6 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -709,6 +709,7 @@ CONF_UNIT_OF_MEASUREMENT = "unit_of_measurement" CONF_UPDATE_INTERVAL = "update_interval" CONF_UPDATE_ON_BOOT = "update_on_boot" CONF_URL = "url" +CONF_USE_ABBREVIATIONS = "use_abbreviations" CONF_USE_ADDRESS = "use_address" CONF_USERNAME = "username" CONF_UUID = "uuid" diff --git a/tests/test1.yaml b/tests/test1.yaml index 157ccfc5d1..d8075e980b 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -94,6 +94,7 @@ mqtt: username: 'debug' password: 'debug' client_id: someclient + use_abbreviations: false discovery: True discovery_retain: False discovery_prefix: discovery From 2b04152482da3e9faaa4f6d0fd3370134d792fd1 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 31 Oct 2021 16:07:06 +0100 Subject: [PATCH 1648/1841] Fix deep sleep invert_wakeup mode (#2644) --- esphome/components/deep_sleep/deep_sleep_component.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index e4b1edfb7b..0998a57af3 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -78,8 +78,9 @@ void DeepSleepComponent::begin_sleep(bool manual) { esp_sleep_enable_timer_wakeup(*this->sleep_duration_); if (this->wakeup_pin_ != nullptr) { bool level = this->wakeup_pin_->is_inverted(); - if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) + if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && !this->wakeup_pin_->digital_read()) { level = !level; + } esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level); } if (this->ext1_wakeup_.has_value()) { From d8b3af3815dcb4bed73bebf5c1659b6dd325c78e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 1 Nov 2021 09:33:04 +1300 Subject: [PATCH 1649/1841] Expose webserver_port to the native API (#2640) --- esphome/components/api/api.proto | 2 ++ esphome/components/api/api_connection.cpp | 3 +++ esphome/components/api/api_pb2.cpp | 10 ++++++++++ esphome/components/api/api_pb2.h | 1 + esphome/components/web_server/__init__.py | 1 + esphome/core/defines.h | 3 +++ 6 files changed, 20 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 3c6a36f032..b1ac998608 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -182,6 +182,8 @@ message DeviceInfoResponse { // The esphome project details if set string project_name = 8; string project_version = 9; + + uint32 webserver_port = 10; } message ListEntitiesRequest { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 2151d6165c..79c53ee840 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -759,6 +759,9 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { #ifdef ESPHOME_PROJECT_NAME resp.project_name = ESPHOME_PROJECT_NAME; resp.project_version = ESPHOME_PROJECT_VERSION; +#endif +#ifdef USE_WEBSERVER + resp.webserver_port = WEBSERVER_PORT; #endif return resp; } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 17fc14c868..1d59d98f52 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -396,6 +396,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->has_deep_sleep = value.as_bool(); return true; } + case 10: { + this->webserver_port = value.as_uint32(); + return true; + } default: return false; } @@ -444,6 +448,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->has_deep_sleep); buffer.encode_string(8, this->project_name); buffer.encode_string(9, this->project_version); + buffer.encode_uint32(10, this->webserver_port); } #ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoResponse::dump_to(std::string &out) const { @@ -484,6 +489,11 @@ void DeviceInfoResponse::dump_to(std::string &out) const { out.append(" project_version: "); out.append("'").append(this->project_version).append("'"); out.append("\n"); + + out.append(" webserver_port: "); + sprintf(buffer, "%u", this->webserver_port); + out.append(buffer); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index eea9ab06f6..af85ed6856 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -224,6 +224,7 @@ class DeviceInfoResponse : public ProtoMessage { bool has_deep_sleep{false}; std::string project_name{}; std::string project_version{}; + uint32_t webserver_port{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 240ba7c8a0..ba2d866593 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -54,6 +54,7 @@ async def to_code(config): cg.add(paren.set_port(config[CONF_PORT])) cg.add_define("WEBSERVER_PORT", config[CONF_PORT]) + cg.add_define("USE_WEBSERVER") cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) if CONF_AUTH in config: diff --git a/esphome/core/defines.h b/esphome/core/defines.h index b44987a768..dc07bde196 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -36,8 +36,11 @@ #define USE_SWITCH #define USE_TEXT_SENSOR #define USE_TIME +#define USE_WEBSERVER #define USE_WIFI +#define WEBSERVER_PORT 80 // NOLINT + // Arduino-specific feature flags #ifdef USE_ARDUINO #define USE_CAPTIVE_PORTAL From d54b4e7c4466487b23c54c0e5f72892bdaed0a00 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Mon, 1 Nov 2021 20:27:57 +0100 Subject: [PATCH 1650/1841] Fix for noise in pulse_counter and duty_cycle components (#2646) --- .../duty_cycle/duty_cycle_sensor.cpp | 22 +++++++++---------- .../components/duty_cycle/duty_cycle_sensor.h | 2 +- .../pulse_counter/pulse_counter_sensor.cpp | 12 ++++++---- .../pulse_counter/pulse_counter_sensor.h | 3 ++- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.cpp b/esphome/components/duty_cycle/duty_cycle_sensor.cpp index 3d7f731d5d..aed22312a7 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.cpp +++ b/esphome/components/duty_cycle/duty_cycle_sensor.cpp @@ -12,7 +12,6 @@ void DutyCycleSensor::setup() { this->pin_->setup(); this->store_.pin = this->pin_->to_isr(); this->store_.last_level = this->pin_->digital_read(); - this->last_update_ = micros(); this->store_.last_interrupt = micros(); this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); @@ -24,19 +23,20 @@ void DutyCycleSensor::dump_config() { } void DutyCycleSensor::update() { const uint32_t now = micros(); - const bool level = this->store_.last_level; - const uint32_t last_interrupt = this->store_.last_interrupt; - uint32_t on_time = this->store_.on_time; + if (this->last_update_ != 0) { + const bool level = this->store_.last_level; + const uint32_t last_interrupt = this->store_.last_interrupt; + uint32_t on_time = this->store_.on_time; - if (level) - on_time += now - last_interrupt; + if (level) + on_time += now - last_interrupt; - const float total_time = float(now - this->last_update_); - - const float value = (on_time / total_time) * 100.0f; - ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value); - this->publish_state(value); + const float total_time = float(now - this->last_update_); + const float value = (on_time / total_time) * 100.0f; + ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value); + this->publish_state(value); + } this->store_.on_time = 0; this->store_.last_interrupt = now; this->last_update_ = now; diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.h b/esphome/components/duty_cycle/duty_cycle_sensor.h index 22d3588fb7..ffb1802e14 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.h +++ b/esphome/components/duty_cycle/duty_cycle_sensor.h @@ -30,7 +30,7 @@ class DutyCycleSensor : public sensor::Sensor, public PollingComponent { InternalGPIOPin *pin_; DutyCycleSensorStore store_{}; - uint32_t last_update_; + uint32_t last_update_{0}; }; } // namespace duty_cycle diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.cpp b/esphome/components/pulse_counter/pulse_counter_sensor.cpp index f538a4c905..d9f198f4fc 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.cpp +++ b/esphome/components/pulse_counter/pulse_counter_sensor.cpp @@ -155,16 +155,20 @@ void PulseCounterSensor::dump_config() { void PulseCounterSensor::update() { pulse_counter_t raw = this->storage_.read_raw_value(); - float value = (60000.0f * raw) / float(this->get_update_interval()); // per minute - - ESP_LOGD(TAG, "'%s': Retrieved counter: %0.2f pulses/min", this->get_name().c_str(), value); - this->publish_state(value); + uint32_t now = millis(); + if (this->last_time_ != 0) { + uint32_t interval = now - this->last_time_; + float value = (60000.0f * raw) / float(interval); // per minute + ESP_LOGD(TAG, "'%s': Retrieved counter: %0.2f pulses/min", this->get_name().c_str(), value); + this->publish_state(value); + } if (this->total_sensor_ != nullptr) { current_total_ += raw; ESP_LOGD(TAG, "'%s': Total : %i pulses", this->get_name().c_str(), current_total_); this->total_sensor_->publish_state(current_total_); } + this->last_time_ = now; } } // namespace pulse_counter diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.h b/esphome/components/pulse_counter/pulse_counter_sensor.h index 94e37bc232..9ed2159ae3 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.h +++ b/esphome/components/pulse_counter/pulse_counter_sensor.h @@ -65,7 +65,8 @@ class PulseCounterSensor : public sensor::Sensor, public PollingComponent { protected: InternalGPIOPin *pin_; PulseCounterStorage storage_; - uint32_t current_total_ = 0; + uint32_t last_time_{0}; + uint32_t current_total_{0}; sensor::Sensor *total_sensor_; }; From 5ea77894b72c0e36ab6fe589d00ee78a341111f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 21:03:23 +0100 Subject: [PATCH 1651/1841] Bump black from 21.9b0 to 21.10b0 (#2650) Bumps [black](https://github.com/psf/black) from 21.9b0 to 21.10b0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits) --- updated-dependencies: - dependency-name: black dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index e40d51f4bf..03879c5d0e 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==2.11.1 flake8==4.0.1 -black==21.9b0 +black==21.10b0 pexpect==4.8.0 pre-commit From 379c3e98f5b03af91a941fd7dbc1d19067851880 Mon Sep 17 00:00:00 2001 From: niklasweber Date: Tue, 2 Nov 2021 19:32:24 +0100 Subject: [PATCH 1652/1841] Add restore_mode to rotary_encoder (#2643) --- .../rotary_encoder/rotary_encoder.cpp | 34 +++++++++++++++++++ .../rotary_encoder/rotary_encoder.h | 17 ++++++++++ esphome/components/rotary_encoder/sensor.py | 12 +++++++ 3 files changed, 63 insertions(+) diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index e1f2584641..aff8fc381c 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -125,6 +125,22 @@ void IRAM_ATTR HOT RotaryEncoderSensorStore::gpio_intr(RotaryEncoderSensorStore void RotaryEncoderSensor::setup() { ESP_LOGCONFIG(TAG, "Setting up Rotary Encoder '%s'...", this->name_.c_str()); + + int32_t initial_value = 0; + switch (this->restore_mode_) { + case ROTARY_ENCODER_RESTORE_DEFAULT_ZERO: + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); + if (!this->rtc_.load(&initial_value)) { + initial_value = 0; + } + break; + case ROTARY_ENCODER_ALWAYS_ZERO: + initial_value = 0; + break; + } + this->store_.counter = initial_value; + this->store_.last_read = initial_value; + this->pin_a_->setup(); this->store_.pin_a = this->pin_a_->to_isr(); this->pin_b_->setup(); @@ -142,6 +158,18 @@ void RotaryEncoderSensor::dump_config() { LOG_PIN(" Pin A: ", this->pin_a_); LOG_PIN(" Pin B: ", this->pin_b_); LOG_PIN(" Pin I: ", this->pin_i_); + + const LogString *restore_mode = LOG_STR(""); + switch (this->restore_mode_) { + case ROTARY_ENCODER_RESTORE_DEFAULT_ZERO: + restore_mode = LOG_STR("Restore (Defaults to zero)"); + break; + case ROTARY_ENCODER_ALWAYS_ZERO: + restore_mode = LOG_STR("Always zero"); + break; + } + ESP_LOGCONFIG(TAG, " Restore Mode: %s", LOG_STR_ARG(restore_mode)); + switch (this->store_.resolution) { case ROTARY_ENCODER_1_PULSE_PER_CYCLE: ESP_LOGCONFIG(TAG, " Resolution: 1 Pulse Per Cycle"); @@ -190,6 +218,9 @@ void RotaryEncoderSensor::loop() { } int counter = this->store_.counter; if (this->store_.last_read != counter || this->publish_initial_value_) { + if (this->restore_mode_ == ROTARY_ENCODER_RESTORE_DEFAULT_ZERO) { + this->rtc_.save(&counter); + } this->store_.last_read = counter; this->publish_state(counter); this->publish_initial_value_ = false; @@ -197,6 +228,9 @@ void RotaryEncoderSensor::loop() { } float RotaryEncoderSensor::get_setup_priority() const { return setup_priority::DATA; } +void RotaryEncoderSensor::set_restore_mode(RotaryEncoderRestoreMode restore_mode) { + this->restore_mode_ = restore_mode; +} void RotaryEncoderSensor::set_resolution(RotaryEncoderResolution mode) { this->store_.resolution = mode; } void RotaryEncoderSensor::set_min_value(int32_t min_value) { this->store_.min_value = min_value; } void RotaryEncoderSensor::set_max_value(int32_t max_value) { this->store_.max_value = max_value; } diff --git a/esphome/components/rotary_encoder/rotary_encoder.h b/esphome/components/rotary_encoder/rotary_encoder.h index a134043152..a69d738fa8 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.h +++ b/esphome/components/rotary_encoder/rotary_encoder.h @@ -10,6 +10,12 @@ namespace esphome { namespace rotary_encoder { +/// All possible restore modes for the rotary encoder +enum RotaryEncoderRestoreMode { + ROTARY_ENCODER_RESTORE_DEFAULT_ZERO, /// try to restore counter, otherwise set to zero + ROTARY_ENCODER_ALWAYS_ZERO, /// do not restore counter, always set to zero +}; + /// All possible resolutions for the rotary encoder enum RotaryEncoderResolution { ROTARY_ENCODER_1_PULSE_PER_CYCLE = @@ -40,6 +46,15 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { void set_pin_a(InternalGPIOPin *pin_a) { pin_a_ = pin_a; } void set_pin_b(InternalGPIOPin *pin_b) { pin_b_ = pin_b; } + /** Set the restore mode of the rotary encoder. + * + * By default (if possible) the last known counter state is restored. Otherwise the value 0 is used. + * Restoring the state can also be turned off. + * + * @param restore_mode The restore mode to use. + */ + void set_restore_mode(RotaryEncoderRestoreMode restore_mode); + /** Set the resolution of the rotary encoder. * * By default, this component will increment the counter by 1 with every A-B input cycle. @@ -81,6 +96,8 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { InternalGPIOPin *pin_b_; GPIOPin *pin_i_{nullptr}; /// Index pin, if this is not nullptr, the counter will reset to 0 once this pin is HIGH. bool publish_initial_value_; + ESPPreferenceObject rtc_; + RotaryEncoderRestoreMode restore_mode_{ROTARY_ENCODER_RESTORE_DEFAULT_ZERO}; RotaryEncoderSensorStore store_{}; diff --git a/esphome/components/rotary_encoder/sensor.py b/esphome/components/rotary_encoder/sensor.py index d868438fd3..cd747264b3 100644 --- a/esphome/components/rotary_encoder/sensor.py +++ b/esphome/components/rotary_encoder/sensor.py @@ -14,9 +14,17 @@ from esphome.const import ( CONF_PIN_A, CONF_PIN_B, CONF_TRIGGER_ID, + CONF_RESTORE_MODE, ) rotary_encoder_ns = cg.esphome_ns.namespace("rotary_encoder") + +RotaryEncoderRestoreMode = rotary_encoder_ns.enum("RotaryEncoderRestoreMode") +RESTORE_MODES = { + "RESTORE_DEFAULT_ZERO": RotaryEncoderRestoreMode.ROTARY_ENCODER_RESTORE_DEFAULT_ZERO, + "ALWAYS_ZERO": RotaryEncoderRestoreMode.ROTARY_ENCODER_ALWAYS_ZERO, +} + RotaryEncoderResolution = rotary_encoder_ns.enum("RotaryEncoderResolution") RESOLUTIONS = { 1: RotaryEncoderResolution.ROTARY_ENCODER_1_PULSE_PER_CYCLE, @@ -72,6 +80,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_MIN_VALUE): cv.int_, cv.Optional(CONF_MAX_VALUE): cv.int_, cv.Optional(CONF_PUBLISH_INITIAL_VALUE, default=False): cv.boolean, + cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_ZERO"): cv.enum( + RESTORE_MODES, upper=True, space="_" + ), cv.Optional(CONF_ON_CLOCKWISE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -102,6 +113,7 @@ async def to_code(config): pin_b = await cg.gpio_pin_expression(config[CONF_PIN_B]) cg.add(var.set_pin_b(pin_b)) cg.add(var.set_publish_initial_value(config[CONF_PUBLISH_INITIAL_VALUE])) + cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) if CONF_PIN_RESET in config: pin_i = await cg.gpio_pin_expression(config[CONF_PIN_RESET]) From 11f1e28139cf07aafbb4ff1f8257ad5caa369780 Mon Sep 17 00:00:00 2001 From: Tim Niemueller Date: Wed, 3 Nov 2021 17:56:09 +0100 Subject: [PATCH 1653/1841] Make per-loop display clearing optional (#2626) Currently, in each loop during DisplayBuffer::update_() the display is cleared by calling DisplayBuffer::clear(). This prevents more efficient display usages that do not render the screen in each loop, but only if necessary. This can be helpful, for example, if images are rendered. This would cause the loop time to be exceeded frequently. This change adds a new optional flag "auto_clear" that can be used to control the clearing behavior. If unset, the DisplayBuffer defaults to enabled auto clearing, the current behavior and thus backward compatible. This flag applies to displays that use DisplayBuffer. Example excerpt: globals: - id: state type: bool restore_value: no initial_value: "false" - id: state_processed type: bool restore_value: no initial_value: "false" switch: - platform: template name: "State" id: state_switch lambda: |- return id(state); turn_on_action: - globals.set: id: state value: "true" - globals.set: id: state_processed value: "false" turn_off_action: - globals.set: id: state value: "false" - globals.set: id: state_processed value: "false" display: - platform: ili9341 # ... auto_clear_enabled: false lambda: |- if (!id(state_processed)) { it.fill(COLOR_WHITE); if (id(state)) { it.image(80, 20, id(image1)); } else { it.image(80, 20, id(image2)); } id(state_processed) = true; } Co-authored-by: Tim Niemueller --- esphome/components/display/__init__.py | 6 ++++ esphome/components/display/display_buffer.cpp | 4 ++- esphome/components/display/display_buffer.h | 4 +++ esphome/const.py | 1 + tests/test1.yaml | 29 +++++++++++++++++++ 5 files changed, 43 insertions(+), 1 deletion(-) diff --git a/esphome/components/display/__init__.py b/esphome/components/display/__init__.py index 947b09a258..0d403f99f0 100644 --- a/esphome/components/display/__init__.py +++ b/esphome/components/display/__init__.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome import core, automation from esphome.automation import maybe_simple_id from esphome.const import ( + CONF_AUTO_CLEAR_ENABLED, CONF_ID, CONF_LAMBDA, CONF_PAGES, @@ -79,6 +80,7 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend( cv.Optional(CONF_TO): cv.use_id(DisplayPage), } ), + cv.Optional(CONF_AUTO_CLEAR_ENABLED, default=True): cv.boolean, } ) @@ -86,6 +88,10 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend( async def setup_display_core_(var, config): if CONF_ROTATION in config: cg.add(var.set_rotation(DISPLAY_ROTATIONS[config[CONF_ROTATION]])) + + if CONF_AUTO_CLEAR_ENABLED in config: + cg.add(var.set_auto_clear(config[CONF_AUTO_CLEAR_ENABLED])) + if CONF_PAGES in config: pages = [] for conf in config[CONF_PAGES]: diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 2ee06e379f..ac806611b5 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -336,7 +336,9 @@ void DisplayBuffer::show_page(DisplayPage *page) { void DisplayBuffer::show_next_page() { this->page_->show_next(); } void DisplayBuffer::show_prev_page() { this->page_->show_prev(); } void DisplayBuffer::do_update_() { - this->clear(); + if (this->auto_clear_enabled_) { + this->clear(); + } if (this->page_ != nullptr) { this->page_->get_writer()(*this); } else if (this->writer_.has_value()) { diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index 54488f18f7..c803180a2d 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -333,6 +333,9 @@ class DisplayBuffer { /// Internal method to set the display rotation with. void set_rotation(DisplayRotation rotation); + // Internal method to set display auto clearing. + void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; } + protected: void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg); @@ -352,6 +355,7 @@ class DisplayBuffer { DisplayPage *page_{nullptr}; DisplayPage *previous_page_{nullptr}; std::vector on_page_change_triggers_; + bool auto_clear_enabled_{true}; }; class DisplayPage { diff --git a/esphome/const.py b/esphome/const.py index bac446a7c6..eb7d56d7e1 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -59,6 +59,7 @@ CONF_AT = "at" CONF_ATTENUATION = "attenuation" CONF_ATTRIBUTE = "attribute" CONF_AUTH = "auth" +CONF_AUTO_CLEAR_ENABLED = "auto_clear_enabled" CONF_AUTO_MODE = "auto_mode" CONF_AUTOCONF = "autoconf" CONF_AUTOMATION_ID = "automation_id" diff --git a/tests/test1.yaml b/tests/test1.yaml index d8075e980b..585e01635f 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2214,6 +2214,31 @@ display: row_start: 0 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: ili9341 + model: "TFT 2.4" + cs_pin: GPIO5 + dc_pin: GPIO4 + reset_pin: GPIO22 + led_pin: + number: GPIO15 + inverted: true + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: ili9341 + model: "TFT 2.4" + cs_pin: GPIO5 + dc_pin: GPIO4 + reset_pin: GPIO22 + led_pin: + number: GPIO15 + inverted: true + auto_clear_enabled: false + rotation: 90 + lambda: |- + if (!id(glob_bool_processed)) { + it.fill(Color::WHITE); + id(glob_bool_processed) = true; + } tm1651: id: tm1651_battery @@ -2393,6 +2418,10 @@ globals: type: std::string restore_value: no # initial_value: "" + - id: glob_bool_processed + type: bool + restore_value: no + initial_value: 'false' text_sensor: - platform: mqtt_subscribe From d536509a63189490f314e5923471828ca28ba26d Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Thu, 4 Nov 2021 18:52:38 -0300 Subject: [PATCH 1654/1841] Allow esp8266 to compile with no wifi (#2664) --- esphome/core/helpers.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index bc97259a71..ada9a48c3b 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -6,7 +6,9 @@ #include #if defined(USE_ESP8266) +#ifdef USE_WIFI #include +#endif #include #elif defined(USE_ESP32_FRAMEWORK_ARDUINO) #include @@ -39,7 +41,7 @@ void get_mac_address_raw(uint8_t *mac) { esp_efuse_mac_get_default(mac); #endif #endif -#ifdef USE_ESP8266 +#if (defined USE_ESP8266 && defined USE_WIFI) WiFi.macAddress(mac); #endif } @@ -48,7 +50,11 @@ std::string get_mac_address() { char tmp[20]; uint8_t mac[6]; get_mac_address_raw(mac); +#ifdef USE_WIFI sprintf(tmp, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +#else + return ""; +#endif return std::string(tmp); } @@ -475,8 +481,13 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green } #ifdef USE_ESP8266 +#ifdef USE_WIFI IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } +#else +IRAM_ATTR InterruptLock::InterruptLock() {} +IRAM_ATTR InterruptLock::~InterruptLock() {} +#endif #endif #ifdef USE_ESP32 IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } From b450d4c734e18c1cfe04c07da09431b57ffdc8e3 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sat, 6 Nov 2021 22:52:04 +0100 Subject: [PATCH 1655/1841] Fix CRC error during DSMR chunked message reading (#2622) * DSMR chunk size from 50 to 500 * Still a few CRC errors with 500, upping to 1024. * Adding timers to measure how long processing DSMR takes * Handle chunked output from smart meter. * Cleaning up and commenting the new chunk handling code * Remove debug code. * Fixing clang-tidy issues. * Implementing chunked reading support for encrypted telegrams. * Remove redundant extra delay for encrypted reader * Beware not to flush crypted telegram headers * Use insane data timeout for testing * Improve logging * Make clang-tidy happy Co-authored-by: Maurice Makaay Co-authored-by: Maurice Makaay --- esphome/components/dsmr/dsmr.cpp | 104 ++++++++++++++++++------------- esphome/components/dsmr/dsmr.h | 14 ++++- 2 files changed, 71 insertions(+), 47 deletions(-) diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index b798fe5d44..031fb275f5 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -19,14 +19,30 @@ void Dsmr::loop() { this->receive_encrypted_(); } +bool Dsmr::available_within_timeout_() { + uint8_t tries = READ_TIMEOUT_MS / 5; + while (tries--) { + delay(5); + if (available()) { + return true; + } + } + return false; +} + void Dsmr::receive_telegram_() { - int count = MAX_BYTES_PER_LOOP; - while (available() && count-- > 0) { + while (true) { + if (!available()) { + if (!header_found_ || !available_within_timeout_()) { + return; + } + } + const char c = read(); // Find a new telegram header, i.e. forward slash. if (c == '/') { - ESP_LOGV(TAG, "Header found"); + ESP_LOGV(TAG, "Header of telegram found"); header_found_ = true; footer_found_ = false; telegram_len_ = 0; @@ -38,7 +54,7 @@ void Dsmr::receive_telegram_() { if (telegram_len_ >= MAX_TELEGRAM_LENGTH) { header_found_ = false; footer_found_ = false; - ESP_LOGE(TAG, "Error: Message larger than buffer"); + ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH); return; } @@ -54,7 +70,7 @@ void Dsmr::receive_telegram_() { // Check for a footer, i.e. exlamation mark, followed by a hex checksum. if (c == '!') { - ESP_LOGV(TAG, "Footer found"); + ESP_LOGV(TAG, "Footer of telegram found"); footer_found_ = true; continue; } @@ -62,8 +78,8 @@ void Dsmr::receive_telegram_() { if (footer_found_ && c == '\n') { header_found_ = false; // Parse the telegram and publish sensor values. - if (parse_telegram()) - return; + parse_telegram(); + return; } } } @@ -72,41 +88,46 @@ void Dsmr::receive_encrypted_() { // Encrypted buffer uint8_t buffer[MAX_TELEGRAM_LENGTH]; size_t buffer_length = 0; - size_t packet_size = 0; - while (available()) { - const char c = read(); - if (!header_found_) { - if ((uint8_t) c == 0xdb) { - ESP_LOGV(TAG, "Start byte 0xDB found"); - header_found_ = true; + while (true) { + if (!available()) { + if (!header_found_) { + return; + } + if (!available_within_timeout_()) { + ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram"); + return; } } - // Sanity check - if (!header_found_ || buffer_length >= MAX_TELEGRAM_LENGTH) { - if (buffer_length == 0) { - ESP_LOGE(TAG, "First byte of encrypted telegram should be 0xDB, aborting."); - } else { - ESP_LOGW(TAG, "Unexpected data"); + const char c = read(); + + // Find a new telegram start byte. + if (!header_found_) { + if ((uint8_t) c == 0xDB) { + ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found"); + header_found_ = true; } - this->status_momentary_warning("unexpected_data"); - this->flush(); - while (available()) - read(); + continue; + } + + // Check for buffer overflow. + if (buffer_length >= MAX_TELEGRAM_LENGTH) { + header_found_ = false; + ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH); return; } buffer[buffer_length++] = c; if (packet_size == 0 && buffer_length > 20) { - // Complete header + a few bytes of data - packet_size = buffer[11] << 8 | buffer[12]; + // Complete header + data bytes + packet_size = 13 + (buffer[11] << 8 | buffer[12]); + ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size); } - if (buffer_length == packet_size + 13 && packet_size > 0) { - ESP_LOGV(TAG, "Encrypted data: %d bytes", buffer_length); - + if (buffer_length == packet_size && packet_size > 0) { + ESP_LOGV(TAG, "End of encrypted telegram found"); GCM *gcmaes128{new GCM()}; gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize()); // the iv is 8 bytes of the system title + 4 bytes frame counter @@ -123,28 +144,21 @@ void Dsmr::receive_encrypted_() { delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory) telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_)); - ESP_LOGV(TAG, "Decrypted data length: %d", telegram_len_); - ESP_LOGVV(TAG, "Decrypted data %s", this->telegram_); + ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_); + ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_); + + header_found_ = false; + telegram_len_ = 0; parse_telegram(); - telegram_len_ = 0; return; } - - if (!available()) { - // baud rate is 115200 for encrypted data, this means a few byte should arrive every time - // program runs faster than buffer loading then available() might return false in the middle - delay(4); // Wait for data - } - } - if (buffer_length > 0) { - ESP_LOGW(TAG, "Timeout while waiting for encrypted data or invalid data received."); } } bool Dsmr::parse_telegram() { MyData data; - ESP_LOGV(TAG, "Trying to parse"); + ESP_LOGV(TAG, "Trying to parse telegram"); ::dsmr::ParseResult res = ::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false, this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. @@ -161,7 +175,7 @@ bool Dsmr::parse_telegram() { } void Dsmr::dump_config() { - ESP_LOGCONFIG(TAG, "dsmr:"); + ESP_LOGCONFIG(TAG, "DSMR:"); #define DSMR_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s_##s##_); DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, ) @@ -178,12 +192,12 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) { } if (decryption_key.length() != 32) { - ESP_LOGE(TAG, "Error, decryption key must be 32 character long."); + ESP_LOGE(TAG, "Error, decryption key must be 32 character long"); return; } this->decryption_key_.clear(); - ESP_LOGI(TAG, "Decryption key is set."); + ESP_LOGI(TAG, "Decryption key is set"); // Verbose level prints decryption key ESP_LOGV(TAG, "Using decryption key: %s", decryption_key.c_str()); diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index 4f9a66b3d0..ca2c0f0877 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -17,8 +17,7 @@ namespace esphome { namespace dsmr { static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500; -static constexpr uint32_t MAX_BYTES_PER_LOOP = 50; -static constexpr uint32_t POLL_TIMEOUT = 1000; +static constexpr uint32_t READ_TIMEOUT_MS = 200; using namespace ::dsmr::fields; @@ -86,6 +85,17 @@ class Dsmr : public Component, public uart::UARTDevice { void receive_telegram_(); void receive_encrypted_(); + /// Wait for UART data to become available within the read timeout. + /// + /// The smart meter might provide data in chunks, causing available() to + /// return 0. When we're already reading a telegram, then we don't return + /// right away (to handle further data in an upcoming loop) but wait a + /// little while using this method to see if more data are incoming. + /// By not returning, we prevent other components from taking so much + /// time that the UART RX buffer overflows and bytes of the telegram get + /// lost in the process. + bool available_within_timeout_(); + // Telegram buffer char telegram_[MAX_TELEGRAM_LENGTH]; int telegram_len_{0}; From 3c0414c42027d8cc3cab8e59c878116f62d8fac7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 8 Nov 2021 07:24:52 +1300 Subject: [PATCH 1656/1841] Add Entity categories for Home Assistant (#2636) --- esphome/codegen.py | 1 + esphome/components/am43/sensor.py | 12 +- esphome/components/api/api.proto | 19 +++ esphome/components/api/api_connection.cpp | 12 +- esphome/components/api/api_pb2.cpp | 111 ++++++++++++++++++ esphome/components/api/api_pb2.h | 16 +++ .../components/atc_mithermometer/sensor.py | 3 + esphome/components/atm90e32/sensor.py | 2 + esphome/components/b_parasite/sensor.py | 2 + esphome/components/binary_sensor/__init__.py | 1 + esphome/components/fingerprint_grow/sensor.py | 7 ++ .../components/inkbird_ibsth1_mini/sensor.py | 2 + esphome/components/number/__init__.py | 1 - .../components/pvvx_mithermometer/sensor.py | 3 + esphome/components/restart/switch.py | 12 +- esphome/components/ruuvitag/sensor.py | 5 + .../components/safe_mode/switch/__init__.py | 5 + esphome/components/select/__init__.py | 1 - esphome/components/sensor/__init__.py | 11 +- esphome/components/shutdown/switch.py | 12 +- esphome/components/status/binary_sensor.py | 11 +- esphome/components/switch/__init__.py | 1 + esphome/components/text_sensor/__init__.py | 1 + esphome/components/uptime/sensor.py | 2 + esphome/components/version/text_sensor.py | 12 +- esphome/components/wifi_info/text_sensor.py | 17 +++ esphome/components/wifi_signal/sensor.py | 2 + esphome/components/xiaomi_cgd1/sensor.py | 2 + esphome/components/xiaomi_cgdk2/sensor.py | 2 + esphome/components/xiaomi_cgg1/sensor.py | 2 + .../components/xiaomi_cgpr1/binary_sensor.py | 16 ++- esphome/components/xiaomi_hhccjcy01/sensor.py | 2 + esphome/components/xiaomi_jqjcy01ym/sensor.py | 2 + esphome/components/xiaomi_lywsd02/sensor.py | 2 + .../components/xiaomi_lywsd03mmc/sensor.py | 2 + esphome/components/xiaomi_lywsdcgq/sensor.py | 2 + esphome/components/xiaomi_mhoc401/sensor.py | 2 + .../xiaomi_mjyd02yla/binary_sensor.py | 2 + .../components/xiaomi_wx08zm/binary_sensor.py | 2 + esphome/config_validation.py | 17 +++ esphome/const.py | 10 ++ esphome/core/entity_base.cpp | 4 + esphome/core/entity_base.h | 11 ++ esphome/cpp_helpers.py | 3 + esphome/cpp_types.py | 1 + 45 files changed, 354 insertions(+), 14 deletions(-) diff --git a/esphome/codegen.py b/esphome/codegen.py index 4f9f67245d..5e1e934e58 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -81,4 +81,5 @@ from esphome.cpp_types import ( # noqa GPIOPin, InternalGPIOPin, gpio_Flags, + EntityCategory, ) diff --git a/esphome/components/am43/sensor.py b/esphome/components/am43/sensor.py index c88e529a0c..68c85d0e9c 100644 --- a/esphome/components/am43/sensor.py +++ b/esphome/components/am43/sensor.py @@ -4,7 +4,8 @@ from esphome.components import sensor, ble_client from esphome.const import ( CONF_ID, CONF_BATTERY_LEVEL, - ICON_BATTERY, + DEVICE_CLASS_BATTERY, + ENTITY_CATEGORY_DIAGNOSTIC, CONF_ILLUMINANCE, ICON_BRIGHTNESS_5, UNIT_PERCENT, @@ -20,10 +21,15 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(Am43), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, ICON_BATTERY, 0 + unit_of_measurement=UNIT_PERCENT, + device_class=DEVICE_CLASS_BATTERY, + accuracy_decimals=0, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( - UNIT_PERCENT, ICON_BRIGHTNESS_5, 0 + unit_of_measurement=UNIT_PERCENT, + icon=ICON_BRIGHTNESS_5, + accuracy_decimals=0, ), } ) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index b1ac998608..0eb7ead735 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -203,6 +203,14 @@ message SubscribeStatesRequest { // Empty } +// ==================== COMMON ===================== + +enum EntityCategory { + ENTITY_CATEGORY_NONE = 0; + ENTITY_CATEGORY_CONFIG = 1; + ENTITY_CATEGORY_DIAGNOSTIC = 2; +} + // ==================== BINARY SENSOR ==================== message ListEntitiesBinarySensorResponse { option (id) = 12; @@ -218,6 +226,7 @@ message ListEntitiesBinarySensorResponse { bool is_status_binary_sensor = 6; bool disabled_by_default = 7; string icon = 8; + EntityCategory entity_category = 9; } message BinarySensorStateResponse { option (id) = 21; @@ -249,6 +258,7 @@ message ListEntitiesCoverResponse { string device_class = 8; bool disabled_by_default = 9; string icon = 10; + EntityCategory entity_category = 11; } enum LegacyCoverState { @@ -318,6 +328,7 @@ message ListEntitiesFanResponse { int32 supported_speed_count = 8; bool disabled_by_default = 9; string icon = 10; + EntityCategory entity_category = 11; } enum FanSpeed { FAN_SPEED_LOW = 0; @@ -394,6 +405,7 @@ message ListEntitiesLightResponse { repeated string effects = 11; bool disabled_by_default = 13; string icon = 14; + EntityCategory entity_category = 15; } message LightStateResponse { option (id) = 24; @@ -482,6 +494,7 @@ message ListEntitiesSensorResponse { // Last reset type removed in 2021.9.0 SensorLastResetType legacy_last_reset_type = 11; bool disabled_by_default = 12; + EntityCategory entity_category = 13; } message SensorStateResponse { option (id) = 25; @@ -510,6 +523,7 @@ message ListEntitiesSwitchResponse { string icon = 5; bool assumed_state = 6; bool disabled_by_default = 7; + EntityCategory entity_category = 8; } message SwitchStateResponse { option (id) = 26; @@ -543,6 +557,7 @@ message ListEntitiesTextSensorResponse { string icon = 5; bool disabled_by_default = 6; + EntityCategory entity_category = 7; } message TextSensorStateResponse { option (id) = 27; @@ -704,6 +719,7 @@ message ListEntitiesCameraResponse { string unique_id = 4; bool disabled_by_default = 5; string icon = 6; + EntityCategory entity_category = 7; } message CameraImageResponse { @@ -798,6 +814,7 @@ message ListEntitiesClimateResponse { repeated string supported_custom_presets = 17; bool disabled_by_default = 18; string icon = 19; + EntityCategory entity_category = 20; } message ClimateStateResponse { option (id) = 47; @@ -866,6 +883,7 @@ message ListEntitiesNumberResponse { float max_value = 7; float step = 8; bool disabled_by_default = 9; + EntityCategory entity_category = 10; } message NumberStateResponse { option (id) = 50; @@ -903,6 +921,7 @@ message ListEntitiesSelectResponse { string icon = 5; repeated string options = 6; bool disabled_by_default = 7; + EntityCategory entity_category = 8; } message SelectStateResponse { option (id) = 53; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 79c53ee840..715a4f48c1 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -184,6 +184,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_ msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); msg.disabled_by_default = binary_sensor->is_disabled_by_default(); msg.icon = binary_sensor->get_icon(); + msg.entity_category = static_cast(binary_sensor->get_entity_category()); return this->send_list_entities_binary_sensor_response(msg); } #endif @@ -217,6 +218,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) { msg.device_class = cover->get_device_class(); msg.disabled_by_default = cover->is_disabled_by_default(); msg.icon = cover->get_icon(); + msg.entity_category = static_cast(cover->get_entity_category()); return this->send_list_entities_cover_response(msg); } void APIConnection::cover_command(const CoverCommandRequest &msg) { @@ -283,6 +285,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) { msg.supported_speed_count = traits.supported_speed_count(); msg.disabled_by_default = fan->is_disabled_by_default(); msg.icon = fan->get_icon(); + msg.entity_category = static_cast(fan->get_entity_category()); return this->send_list_entities_fan_response(msg); } void APIConnection::fan_command(const FanCommandRequest &msg) { @@ -346,6 +349,7 @@ bool APIConnection::send_light_info(light::LightState *light) { msg.disabled_by_default = light->is_disabled_by_default(); msg.icon = light->get_icon(); + msg.entity_category = static_cast(light->get_entity_category()); for (auto mode : traits.get_supported_color_modes()) msg.supported_color_modes.push_back(static_cast(mode)); @@ -432,7 +436,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { msg.device_class = sensor->get_device_class(); msg.state_class = static_cast(sensor->get_state_class()); msg.disabled_by_default = sensor->is_disabled_by_default(); - + msg.entity_category = static_cast(sensor->get_entity_category()); return this->send_list_entities_sensor_response(msg); } #endif @@ -456,6 +460,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) { msg.icon = a_switch->get_icon(); msg.assumed_state = a_switch->assumed_state(); msg.disabled_by_default = a_switch->is_disabled_by_default(); + msg.entity_category = static_cast(a_switch->get_entity_category()); return this->send_list_entities_switch_response(msg); } void APIConnection::switch_command(const SwitchCommandRequest &msg) { @@ -491,6 +496,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) msg.unique_id = get_default_unique_id("text_sensor", text_sensor); msg.icon = text_sensor->get_icon(); msg.disabled_by_default = text_sensor->is_disabled_by_default(); + msg.entity_category = static_cast(text_sensor->get_entity_category()); return this->send_list_entities_text_sensor_response(msg); } #endif @@ -537,6 +543,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { msg.disabled_by_default = climate->is_disabled_by_default(); msg.icon = climate->get_icon(); + msg.entity_category = static_cast(climate->get_entity_category()); msg.supports_current_temperature = traits.get_supports_current_temperature(); msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); @@ -611,6 +618,7 @@ bool APIConnection::send_number_info(number::Number *number) { msg.unique_id = get_default_unique_id("number", number); msg.icon = number->get_icon(); msg.disabled_by_default = number->is_disabled_by_default(); + msg.entity_category = static_cast(number->get_entity_category()); msg.min_value = number->traits.get_min_value(); msg.max_value = number->traits.get_max_value(); @@ -648,6 +656,7 @@ bool APIConnection::send_select_info(select::Select *select) { msg.unique_id = get_default_unique_id("select", select); msg.icon = select->get_icon(); msg.disabled_by_default = select->is_disabled_by_default(); + msg.entity_category = static_cast(select->get_entity_category()); for (const auto &option : select->traits.get_options()) msg.options.push_back(option); @@ -681,6 +690,7 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { msg.unique_id = get_default_unique_id("camera", camera); msg.disabled_by_default = camera->is_disabled_by_default(); msg.icon = camera->get_icon(); + msg.entity_category = static_cast(camera->get_entity_category()); return this->send_list_entities_camera_response(msg); } void APIConnection::camera_image(const CameraImageRequest &msg) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 1d59d98f52..7bfa1e9edb 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -6,6 +6,18 @@ namespace esphome { namespace api { +template<> const char *proto_enum_to_string(enums::EntityCategory value) { + switch (value) { + case enums::ENTITY_CATEGORY_NONE: + return "ENTITY_CATEGORY_NONE"; + case enums::ENTITY_CATEGORY_CONFIG: + return "ENTITY_CATEGORY_CONFIG"; + case enums::ENTITY_CATEGORY_DIAGNOSTIC: + return "ENTITY_CATEGORY_DIAGNOSTIC"; + default: + return "UNKNOWN"; + } +} template<> const char *proto_enum_to_string(enums::LegacyCoverState value) { switch (value) { case enums::LEGACY_COVER_STATE_OPEN: @@ -519,6 +531,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar this->disabled_by_default = value.as_bool(); return true; } + case 9: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -568,6 +584,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->is_status_binary_sensor); buffer.encode_bool(7, this->disabled_by_default); buffer.encode_string(8, this->icon); + buffer.encode_enum(9, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { @@ -605,6 +622,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -674,6 +695,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val this->disabled_by_default = value.as_bool(); return true; } + case 11: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -725,6 +750,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(8, this->device_class); buffer.encode_bool(9, this->disabled_by_default); buffer.encode_string(10, this->icon); + buffer.encode_enum(11, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { @@ -770,6 +796,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -958,6 +988,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value this->disabled_by_default = value.as_bool(); return true; } + case 11: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -1005,6 +1039,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_int32(8, this->supported_speed_count); buffer.encode_bool(9, this->disabled_by_default); buffer.encode_string(10, this->icon); + buffer.encode_enum(11, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesFanResponse::dump_to(std::string &out) const { @@ -1051,6 +1086,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -1277,6 +1316,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val this->disabled_by_default = value.as_bool(); return true; } + case 15: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -1344,6 +1387,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_bool(13, this->disabled_by_default); buffer.encode_string(14, this->icon); + buffer.encode_enum(15, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLightResponse::dump_to(std::string &out) const { @@ -1411,6 +1455,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -1870,6 +1918,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->disabled_by_default = value.as_bool(); return true; } + case 13: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -1927,6 +1979,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(10, this->state_class); buffer.encode_enum(11, this->legacy_last_reset_type); buffer.encode_bool(12, this->disabled_by_default); + buffer.encode_enum(13, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSensorResponse::dump_to(std::string &out) const { @@ -1981,6 +2034,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -2043,6 +2100,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->disabled_by_default = value.as_bool(); return true; } + case 8: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -2087,6 +2148,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->assumed_state); buffer.encode_bool(7, this->disabled_by_default); + buffer.encode_enum(8, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSwitchResponse::dump_to(std::string &out) const { @@ -2120,6 +2182,10 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -2207,6 +2273,10 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn this->disabled_by_default = value.as_bool(); return true; } + case 7: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -2250,6 +2320,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(4, this->unique_id); buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); + buffer.encode_enum(7, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { @@ -2279,6 +2350,10 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -2902,6 +2977,10 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->disabled_by_default = value.as_bool(); return true; } + case 7: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -2945,6 +3024,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(4, this->unique_id); buffer.encode_bool(5, this->disabled_by_default); buffer.encode_string(6, this->icon); + buffer.encode_enum(7, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCameraResponse::dump_to(std::string &out) const { @@ -2974,6 +3054,10 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -3101,6 +3185,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v this->disabled_by_default = value.as_bool(); return true; } + case 20: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -3189,6 +3277,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_bool(18, this->disabled_by_default); buffer.encode_string(19, this->icon); + buffer.encode_enum(20, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesClimateResponse::dump_to(std::string &out) const { @@ -3285,6 +3374,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -3661,6 +3754,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->disabled_by_default = value.as_bool(); return true; } + case 10: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -3719,6 +3816,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(7, this->max_value); buffer.encode_float(8, this->step); buffer.encode_bool(9, this->disabled_by_default); + buffer.encode_enum(10, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { @@ -3763,6 +3861,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -3855,6 +3957,10 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->disabled_by_default = value.as_bool(); return true; } + case 8: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -3905,6 +4011,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(6, it, true); } buffer.encode_bool(7, this->disabled_by_default); + buffer.encode_enum(8, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSelectResponse::dump_to(std::string &out) const { @@ -3940,6 +4047,10 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index af85ed6856..ec11732c7d 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -9,6 +9,11 @@ namespace api { namespace enums { +enum EntityCategory : uint32_t { + ENTITY_CATEGORY_NONE = 0, + ENTITY_CATEGORY_CONFIG = 1, + ENTITY_CATEGORY_DIAGNOSTIC = 2, +}; enum LegacyCoverState : uint32_t { LEGACY_COVER_STATE_OPEN = 0, LEGACY_COVER_STATE_CLOSED = 1, @@ -271,6 +276,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage { bool is_status_binary_sensor{false}; bool disabled_by_default{false}; std::string icon{}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -307,6 +313,7 @@ class ListEntitiesCoverResponse : public ProtoMessage { std::string device_class{}; bool disabled_by_default{false}; std::string icon{}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -364,6 +371,7 @@ class ListEntitiesFanResponse : public ProtoMessage { int32_t supported_speed_count{0}; bool disabled_by_default{false}; std::string icon{}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -429,6 +437,7 @@ class ListEntitiesLightResponse : public ProtoMessage { std::vector effects{}; bool disabled_by_default{false}; std::string icon{}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -517,6 +526,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { enums::SensorStateClass state_class{}; enums::SensorLastResetType legacy_last_reset_type{}; bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -550,6 +560,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage { std::string icon{}; bool assumed_state{false}; bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -594,6 +605,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage { std::string unique_id{}; std::string icon{}; bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -805,6 +817,7 @@ class ListEntitiesCameraResponse : public ProtoMessage { std::string unique_id{}; bool disabled_by_default{false}; std::string icon{}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -863,6 +876,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { std::vector supported_custom_presets{}; bool disabled_by_default{false}; std::string icon{}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -942,6 +956,7 @@ class ListEntitiesNumberResponse : public ProtoMessage { float max_value{0.0f}; float step{0.0f}; bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -987,6 +1002,7 @@ class ListEntitiesSelectResponse : public ProtoMessage { std::string icon{}; std::vector options{}; bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/atc_mithermometer/sensor.py b/esphome/components/atc_mithermometer/sensor.py index 0f6cc1abcb..bde83c28b6 100644 --- a/esphome/components/atc_mithermometer/sensor.py +++ b/esphome/components/atc_mithermometer/sensor.py @@ -12,6 +12,7 @@ from esphome.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -49,12 +50,14 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/atm90e32/sensor.py b/esphome/components/atm90e32/sensor.py index 05e5250d89..9c876bb62c 100644 --- a/esphome/components/atm90e32/sensor.py +++ b/esphome/components/atm90e32/sensor.py @@ -17,6 +17,7 @@ from esphome.const import ( DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_DIAGNOSTIC, ICON_LIGHTBULB, ICON_CURRENT_AC, STATE_CLASS_MEASUREMENT, @@ -125,6 +126,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True), cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum( diff --git a/esphome/components/b_parasite/sensor.py b/esphome/components/b_parasite/sensor.py index d51c48c602..201685adc4 100644 --- a/esphome/components/b_parasite/sensor.py +++ b/esphome/components/b_parasite/sensor.py @@ -13,6 +13,7 @@ from esphome.const import ( DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_LUX, @@ -51,6 +52,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_MOISTURE): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index ec199cc5fa..faafcddd06 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -313,6 +313,7 @@ def validate_multi_click_timing(value): device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") + BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(BinarySensor), diff --git a/esphome/components/fingerprint_grow/sensor.py b/esphome/components/fingerprint_grow/sensor.py index f359a10348..4ae670743d 100644 --- a/esphome/components/fingerprint_grow/sensor.py +++ b/esphome/components/fingerprint_grow/sensor.py @@ -8,6 +8,7 @@ from esphome.const import ( CONF_LAST_FINGER_ID, CONF_SECURITY_LEVEL, CONF_STATUS, + ENTITY_CATEGORY_DIAGNOSTIC, ICON_ACCOUNT, ICON_ACCOUNT_CHECK, ICON_DATABASE, @@ -26,30 +27,36 @@ CONFIG_SCHEMA = cv.Schema( icon=ICON_FINGERPRINT, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_STATUS): sensor.sensor_schema( accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_CAPACITY): sensor.sensor_schema( icon=ICON_DATABASE, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_SECURITY_LEVEL): sensor.sensor_schema( icon=ICON_SECURITY, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_LAST_FINGER_ID): sensor.sensor_schema( icon=ICON_ACCOUNT, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_LAST_CONFIDENCE): sensor.sensor_schema( icon=ICON_ACCOUNT_CHECK, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/inkbird_ibsth1_mini/sensor.py b/esphome/components/inkbird_ibsth1_mini/sensor.py index 0ab9f8b3e0..aa11fb3172 100644 --- a/esphome/components/inkbird_ibsth1_mini/sensor.py +++ b/esphome/components/inkbird_ibsth1_mini/sensor.py @@ -9,6 +9,7 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -53,6 +54,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 2856a25ee7..bb3427e4bd 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -41,7 +41,6 @@ NumberInRangeCondition = number_ns.class_( icon = cv.icon - NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent), diff --git a/esphome/components/pvvx_mithermometer/sensor.py b/esphome/components/pvvx_mithermometer/sensor.py index b17878f01b..12090bddba 100644 --- a/esphome/components/pvvx_mithermometer/sensor.py +++ b/esphome/components/pvvx_mithermometer/sensor.py @@ -12,6 +12,7 @@ from esphome.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -49,12 +50,14 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/restart/switch.py b/esphome/components/restart/switch.py index 4f1904e273..de30392b45 100644 --- a/esphome/components/restart/switch.py +++ b/esphome/components/restart/switch.py @@ -1,7 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import switch -from esphome.const import CONF_ID, CONF_INVERTED, CONF_ICON, ICON_RESTART +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ID, + CONF_INVERTED, + CONF_ICON, + ENTITY_CATEGORY_CONFIG, + ICON_RESTART, +) restart_ns = cg.esphome_ns.namespace("restart") RestartSwitch = restart_ns.class_("RestartSwitch", switch.Switch, cg.Component) @@ -13,6 +20,9 @@ CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( "Restart switches do not support inverted mode!" ), cv.Optional(CONF_ICON, default=ICON_RESTART): switch.icon, + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG + ): cv.entity_category, } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/ruuvitag/sensor.py b/esphome/components/ruuvitag/sensor.py index 342a5eff24..2bb9549195 100644 --- a/esphome/components/ruuvitag/sensor.py +++ b/esphome/components/ruuvitag/sensor.py @@ -19,6 +19,7 @@ from esphome.const import ( DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, UNIT_CELSIUS, @@ -95,22 +96,26 @@ CONFIG_SCHEMA = ( accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_TX_POWER): sensor.sensor_schema( unit_of_measurement=UNIT_DECIBEL_MILLIWATT, accuracy_decimals=0, device_class=DEVICE_CLASS_SIGNAL_STRENGTH, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_MOVEMENT_COUNTER): sensor.sensor_schema( icon=ICON_GAUGE, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_MEASUREMENT_SEQUENCE_NUMBER): sensor.sensor_schema( icon=ICON_GAUGE, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/safe_mode/switch/__init__.py b/esphome/components/safe_mode/switch/__init__.py index 0ad814ff4f..b6c3e852f6 100644 --- a/esphome/components/safe_mode/switch/__init__.py +++ b/esphome/components/safe_mode/switch/__init__.py @@ -3,10 +3,12 @@ import esphome.config_validation as cv from esphome.components import switch from esphome.components.ota import OTAComponent from esphome.const import ( + CONF_ENTITY_CATEGORY, CONF_ID, CONF_INVERTED, CONF_ICON, CONF_OTA, + ENTITY_CATEGORY_CONFIG, ICON_RESTART_ALERT, ) from .. import safe_mode_ns @@ -23,6 +25,9 @@ CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( "Safe Mode Restart switches do not support inverted mode!" ), cv.Optional(CONF_ICON, default=ICON_RESTART_ALERT): switch.icon, + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG + ): cv.entity_category, } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index 7e4047d3c8..8ea159d657 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -30,7 +30,6 @@ SelectSetAction = select_ns.class_("SelectSetAction", automation.Action) icon = cv.icon - SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent), diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 4b2e9dc019..d9d226aab6 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_ACCURACY_DECIMALS, CONF_ALPHA, CONF_BELOW, + CONF_ENTITY_CATEGORY, CONF_EXPIRE_AFTER, CONF_FILTERS, CONF_FROM, @@ -133,7 +134,6 @@ def validate_datapoint(value): # Base -sensor_ns = cg.esphome_ns.namespace("sensor") Sensor = sensor_ns.class_("Sensor", cg.EntityBase) SensorPtr = Sensor.operator("ptr") @@ -226,6 +226,7 @@ def sensor_schema( accuracy_decimals: int = _UNDEF, device_class: str = _UNDEF, state_class: str = _UNDEF, + entity_category: str = _UNDEF, ) -> cv.Schema: schema = SENSOR_SCHEMA if unit_of_measurement is not _UNDEF: @@ -258,6 +259,14 @@ def sensor_schema( schema = schema.extend( {cv.Optional(CONF_STATE_CLASS, default=state_class): validate_state_class} ) + if entity_category is not _UNDEF: + schema = schema.extend( + { + cv.Optional( + CONF_ENTITY_CATEGORY, default=entity_category + ): cv.entity_category + } + ) return schema diff --git a/esphome/components/shutdown/switch.py b/esphome/components/shutdown/switch.py index 30c2bc2b74..49970b4c2f 100644 --- a/esphome/components/shutdown/switch.py +++ b/esphome/components/shutdown/switch.py @@ -1,7 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import switch -from esphome.const import CONF_ID, CONF_INVERTED, CONF_ICON, ICON_POWER +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ID, + CONF_INVERTED, + CONF_ICON, + ENTITY_CATEGORY_CONFIG, + ICON_POWER, +) shutdown_ns = cg.esphome_ns.namespace("shutdown") ShutdownSwitch = shutdown_ns.class_("ShutdownSwitch", switch.Switch, cg.Component) @@ -13,6 +20,9 @@ CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( "Shutdown switches do not support inverted mode!" ), cv.Optional(CONF_ICON, default=ICON_POWER): switch.icon, + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG + ): cv.entity_category, } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/status/binary_sensor.py b/esphome/components/status/binary_sensor.py index e462bc5385..9367706388 100644 --- a/esphome/components/status/binary_sensor.py +++ b/esphome/components/status/binary_sensor.py @@ -1,7 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_ID, CONF_DEVICE_CLASS, DEVICE_CLASS_CONNECTIVITY +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ID, + CONF_DEVICE_CLASS, + DEVICE_CLASS_CONNECTIVITY, + ENTITY_CATEGORY_DIAGNOSTIC, +) status_ns = cg.esphome_ns.namespace("status") StatusBinarySensor = status_ns.class_( @@ -14,6 +20,9 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( cv.Optional( CONF_DEVICE_CLASS, default=DEVICE_CLASS_CONNECTIVITY ): binary_sensor.device_class, + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py index 88341e0add..08cbccbe35 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -36,6 +36,7 @@ SwitchTurnOffTrigger = switch_ns.class_( icon = cv.icon + SWITCH_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSwitchComponent), diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index 5c739e1d0a..a070e6f86d 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -108,6 +108,7 @@ async def substitute_filter_to_code(config, filter_id): icon = cv.icon + TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor), diff --git a/esphome/components/uptime/sensor.py b/esphome/components/uptime/sensor.py index 7989f3befc..16a1e4c125 100644 --- a/esphome/components/uptime/sensor.py +++ b/esphome/components/uptime/sensor.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( CONF_ID, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_TOTAL_INCREASING, UNIT_SECOND, ICON_TIMER, @@ -17,6 +18,7 @@ CONFIG_SCHEMA = ( icon=ICON_TIMER, accuracy_decimals=0, state_class=STATE_CLASS_TOTAL_INCREASING, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ) .extend( { diff --git a/esphome/components/version/text_sensor.py b/esphome/components/version/text_sensor.py index e67f881d32..4835caf35b 100644 --- a/esphome/components/version/text_sensor.py +++ b/esphome/components/version/text_sensor.py @@ -1,7 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor -from esphome.const import CONF_ID, CONF_ICON, ICON_NEW_BOX, CONF_HIDE_TIMESTAMP +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ID, + CONF_ICON, + ENTITY_CATEGORY_DIAGNOSTIC, + ICON_NEW_BOX, + CONF_HIDE_TIMESTAMP, +) version_ns = cg.esphome_ns.namespace("version") VersionTextSensor = version_ns.class_( @@ -13,6 +20,9 @@ CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( cv.GenerateID(): cv.declare_id(VersionTextSensor), cv.Optional(CONF_ICON, default=ICON_NEW_BOX): text_sensor.icon, cv.Optional(CONF_HIDE_TIMESTAMP, default=False): cv.boolean, + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index 1922502204..706a8967be 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -3,11 +3,13 @@ import esphome.config_validation as cv from esphome.components import text_sensor from esphome.const import ( CONF_BSSID, + CONF_ENTITY_CATEGORY, CONF_ID, CONF_IP_ADDRESS, CONF_SCAN_RESULTS, CONF_SSID, CONF_MAC_ADDRESS, + ENTITY_CATEGORY_DIAGNOSTIC, ) DEPENDENCIES = ["wifi"] @@ -32,26 +34,41 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_IP_ADDRESS): text_sensor.TEXT_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(IPAddressWiFiInfo), + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ), cv.Optional(CONF_SCAN_RESULTS): text_sensor.TEXT_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(ScanResultsWiFiInfo), + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ).extend(cv.polling_component_schema("60s")), cv.Optional(CONF_SSID): text_sensor.TEXT_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(SSIDWiFiInfo), + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ), cv.Optional(CONF_BSSID): text_sensor.TEXT_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(BSSIDWiFiInfo), + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ), cv.Optional(CONF_MAC_ADDRESS): text_sensor.TEXT_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(MacAddressWifiInfo), + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ), } diff --git a/esphome/components/wifi_signal/sensor.py b/esphome/components/wifi_signal/sensor.py index 37bee75928..2097c21bd7 100644 --- a/esphome/components/wifi_signal/sensor.py +++ b/esphome/components/wifi_signal/sensor.py @@ -4,6 +4,7 @@ from esphome.components import sensor from esphome.const import ( CONF_ID, DEVICE_CLASS_SIGNAL_STRENGTH, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_DECIBEL_MILLIWATT, ) @@ -20,6 +21,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_SIGNAL_STRENGTH, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ) .extend( { diff --git a/esphome/components/xiaomi_cgd1/sensor.py b/esphome/components/xiaomi_cgd1/sensor.py index 774c87fee9..5b88121d7c 100644 --- a/esphome/components/xiaomi_cgd1/sensor.py +++ b/esphome/components/xiaomi_cgd1/sensor.py @@ -10,6 +10,7 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -47,6 +48,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_cgdk2/sensor.py b/esphome/components/xiaomi_cgdk2/sensor.py index d4e7230fd0..ac487d87fc 100644 --- a/esphome/components/xiaomi_cgdk2/sensor.py +++ b/esphome/components/xiaomi_cgdk2/sensor.py @@ -9,6 +9,7 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -47,6 +48,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_cgg1/sensor.py b/esphome/components/xiaomi_cgg1/sensor.py index 4e606d95f8..a4f9a39aff 100644 --- a/esphome/components/xiaomi_cgg1/sensor.py +++ b/esphome/components/xiaomi_cgg1/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -47,6 +48,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_cgpr1/binary_sensor.py b/esphome/components/xiaomi_cgpr1/binary_sensor.py index a7f6c41225..7f0aac873d 100644 --- a/esphome/components/xiaomi_cgpr1/binary_sensor.py +++ b/esphome/components/xiaomi_cgpr1/binary_sensor.py @@ -10,6 +10,8 @@ from esphome.const import ( DEVICE_CLASS_EMPTY, DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_MOTION, + ENTITY_CATEGORY_DIAGNOSTIC, ICON_EMPTY, UNIT_PERCENT, CONF_IDLE_TIME, @@ -37,13 +39,21 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_BINDKEY): cv.bind_key, cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional( - CONF_DEVICE_CLASS, default="motion" + CONF_DEVICE_CLASS, + default=DEVICE_CLASS_MOTION, ): binary_sensor.device_class, cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_IDLE_TIME): sensor.sensor_schema( - UNIT_MINUTE, ICON_TIMELAPSE, 0, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_MINUTE, + icon=ICON_TIMELAPSE, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( UNIT_LUX, ICON_EMPTY, 0, DEVICE_CLASS_ILLUMINANCE diff --git a/esphome/components/xiaomi_hhccjcy01/sensor.py b/esphome/components/xiaomi_hhccjcy01/sensor.py index 1818731a0f..535316e246 100644 --- a/esphome/components/xiaomi_hhccjcy01/sensor.py +++ b/esphome/components/xiaomi_hhccjcy01/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_TEMPERATURE, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, ICON_WATER_PERCENT, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, @@ -63,6 +64,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_jqjcy01ym/sensor.py b/esphome/components/xiaomi_jqjcy01ym/sensor.py index 40991c3d0f..f4d2b342fd 100644 --- a/esphome/components/xiaomi_jqjcy01ym/sensor.py +++ b/esphome/components/xiaomi_jqjcy01ym/sensor.py @@ -9,6 +9,7 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -54,6 +55,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_lywsd02/sensor.py b/esphome/components/xiaomi_lywsd02/sensor.py index 339c5e673a..20629a0a9c 100644 --- a/esphome/components/xiaomi_lywsd02/sensor.py +++ b/esphome/components/xiaomi_lywsd02/sensor.py @@ -7,6 +7,7 @@ from esphome.const import ( CONF_MAC_ADDRESS, CONF_TEMPERATURE, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -45,6 +46,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_lywsd03mmc/sensor.py b/esphome/components/xiaomi_lywsd03mmc/sensor.py index f27cee3800..b2784e58fc 100644 --- a/esphome/components/xiaomi_lywsd03mmc/sensor.py +++ b/esphome/components/xiaomi_lywsd03mmc/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -49,6 +50,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_lywsdcgq/sensor.py b/esphome/components/xiaomi_lywsdcgq/sensor.py index 39a207327e..80f24ac0ef 100644 --- a/esphome/components/xiaomi_lywsdcgq/sensor.py +++ b/esphome/components/xiaomi_lywsdcgq/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -45,6 +46,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_mhoc401/sensor.py b/esphome/components/xiaomi_mhoc401/sensor.py index 57b2190150..9e92e34230 100644 --- a/esphome/components/xiaomi_mhoc401/sensor.py +++ b/esphome/components/xiaomi_mhoc401/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -48,6 +49,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py index fd4bae60c1..1bedae26cf 100644 --- a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py +++ b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_BATTERY_LEVEL, DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, UNIT_PERCENT, @@ -51,6 +52,7 @@ CONFIG_SCHEMA = cv.All( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( unit_of_measurement=UNIT_LUX, diff --git a/esphome/components/xiaomi_wx08zm/binary_sensor.py b/esphome/components/xiaomi_wx08zm/binary_sensor.py index d2b353beff..8667794923 100644 --- a/esphome/components/xiaomi_wx08zm/binary_sensor.py +++ b/esphome/components/xiaomi_wx08zm/binary_sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_MAC_ADDRESS, CONF_TABLET, DEVICE_CLASS_BATTERY, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_PERCENT, ICON_BUG, @@ -40,6 +41,7 @@ CONFIG_SCHEMA = cv.All( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index fcd014f62d..3e5c7940a4 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -12,12 +12,14 @@ from string import ascii_letters, digits import voluptuous as vol from esphome import core +import esphome.codegen as cg from esphome.const import ( ALLOWED_NAME_CHARS, CONF_AVAILABILITY, CONF_COMMAND_TOPIC, CONF_DISABLED_BY_DEFAULT, CONF_DISCOVERY, + CONF_ENTITY_CATEGORY, CONF_ICON, CONF_ID, CONF_INTERNAL, @@ -35,6 +37,9 @@ from esphome.const import ( CONF_UPDATE_INTERVAL, CONF_TYPE_ID, CONF_TYPE, + ENTITY_CATEGORY_CONFIG, + ENTITY_CATEGORY_DIAGNOSTIC, + ENTITY_CATEGORY_NONE, KEY_CORE, KEY_FRAMEWORK_VERSION, KEY_TARGET_FRAMEWORK, @@ -1551,6 +1556,17 @@ def maybe_simple_value(*validators, **kwargs): return validate +_ENTITY_CATEGORIES = { + ENTITY_CATEGORY_NONE: cg.EntityCategory.ENTITY_CATEGORY_NONE, + ENTITY_CATEGORY_CONFIG: cg.EntityCategory.ENTITY_CATEGORY_CONFIG, + ENTITY_CATEGORY_DIAGNOSTIC: cg.EntityCategory.ENTITY_CATEGORY_DIAGNOSTIC, +} + + +def entity_category(value): + return enum(_ENTITY_CATEGORIES, lower=True)(value) + + MQTT_COMPONENT_AVAILABILITY_SCHEMA = Schema( { Required(CONF_TOPIC): subscribe_topic, @@ -1582,6 +1598,7 @@ ENTITY_BASE_SCHEMA = Schema( Optional(CONF_INTERNAL): boolean, Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean, Optional(CONF_ICON): icon, + Optional(CONF_ENTITY_CATEGORY): entity_category, } ) diff --git a/esphome/const.py b/esphome/const.py index eb7d56d7e1..ff8510b40e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -203,6 +203,7 @@ CONF_ELSE = "else" CONF_ENABLE_PIN = "enable_pin" CONF_ENABLE_TIME = "enable_time" CONF_ENERGY = "energy" +CONF_ENTITY_CATEGORY = "entity_category" CONF_ENTITY_ID = "entity_id" CONF_ESP8266_DISABLE_SSL_SUPPORT = "esp8266_disable_ssl_support" CONF_ESPHOME = "esphome" @@ -919,3 +920,12 @@ KEY_CORE = "core" KEY_TARGET_PLATFORM = "target_platform" KEY_TARGET_FRAMEWORK = "target_framework" KEY_FRAMEWORK_VERSION = "framework_version" + +# Entity categories +ENTITY_CATEGORY_NONE = "" + +# The entity category for configuration values/controls +ENTITY_CATEGORY_CONFIG = "config" + +# The entity category for read only diagnostic values, for example RSSI, uptime or MAC Address +ENTITY_CATEGORY_DIAGNOSTIC = "diagnostic" diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index bc94da85fe..41f08b28a6 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -26,6 +26,10 @@ void EntityBase::set_disabled_by_default(bool disabled_by_default) { this->disab const std::string &EntityBase::get_icon() const { return this->icon_; } void EntityBase::set_icon(const std::string &name) { this->icon_ = name; } +// Entity Category +EntityCategory EntityBase::get_entity_category() const { return this->entity_category_; } +void EntityBase::set_entity_category(EntityCategory entity_category) { this->entity_category_ = entity_category; } + // Entity Object ID const std::string &EntityBase::get_object_id() { return this->object_id_; } diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 263747b721..c489d71910 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -5,6 +5,12 @@ namespace esphome { +enum EntityCategory : uint8_t { + ENTITY_CATEGORY_NONE = 0, + ENTITY_CATEGORY_CONFIG = 1, + ENTITY_CATEGORY_DIAGNOSTIC = 2, +}; + // The generic Entity base class that provides an interface common to all Entities. class EntityBase { public: @@ -31,6 +37,10 @@ class EntityBase { bool is_disabled_by_default() const; void set_disabled_by_default(bool disabled_by_default); + // Get/set the entity category. + EntityCategory get_entity_category() const; + void set_entity_category(EntityCategory entity_category); + // Get/set this entity's icon const std::string &get_icon() const; void set_icon(const std::string &name); @@ -45,6 +55,7 @@ class EntityBase { uint32_t object_id_hash_; bool internal_{false}; bool disabled_by_default_{false}; + EntityCategory entity_category_{ENTITY_CATEGORY_NONE}; }; } // namespace esphome diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 5b081698ad..9127f88e39 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -2,6 +2,7 @@ import logging from esphome.const import ( CONF_DISABLED_BY_DEFAULT, + CONF_ENTITY_CATEGORY, CONF_ICON, CONF_INTERNAL, CONF_NAME, @@ -102,6 +103,8 @@ async def setup_entity(var, config): add(var.set_internal(config[CONF_INTERNAL])) if CONF_ICON in config: add(var.set_icon(config[CONF_ICON])) + if CONF_ENTITY_CATEGORY in config: + add(var.set_entity_category(config[CONF_ENTITY_CATEGORY])) def extract_registry_entry_config(registry, full_config): diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index 888c319024..13d088e1cb 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -34,3 +34,4 @@ GPIOPin = esphome_ns.class_("GPIOPin") InternalGPIOPin = esphome_ns.class_("InternalGPIOPin", GPIOPin) gpio_ns = esphome_ns.namespace("gpio") gpio_Flags = gpio_ns.enum("Flags", is_class=True) +EntityCategory = esphome_ns.enum("EntityCategory") From 96a50f5c6b3113ee774875929a2552c2bc35baeb Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sun, 7 Nov 2021 19:31:41 +0100 Subject: [PATCH 1657/1841] Add SPI lib for ESP8266 and only add lib for ESP32 when using Arduino (#2677) * Add SPI lib for ESP8266 and only add lib for ESP32 when using Arduino * Make inclusion of the SPI library unconditional As suggested by @Oxan. Because the component requires Arduino anyway, there is no need to make the inclusion conditional. Co-authored-by: Oxan van Leeuwen * Fix Python lint issue Co-authored-by: Maurice Makaay Co-authored-by: Oxan van Leeuwen --- esphome/components/bme680_bsec/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/esphome/components/bme680_bsec/__init__.py b/esphome/components/bme680_bsec/__init__.py index 2f844fa666..83e519f8aa 100644 --- a/esphome/components/bme680_bsec/__init__.py +++ b/esphome/components/bme680_bsec/__init__.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c from esphome.const import CONF_ID -from esphome.core import CORE CODEOWNERS = ["@trvrnrth"] DEPENDENCIES = ["i2c"] @@ -62,9 +61,8 @@ async def to_code(config): var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds) ) - if CORE.is_esp32: - # Although this component does not use SPI, the BSEC library requires the SPI library - cg.add_library("SPI", None) + # Although this component does not use SPI, the BSEC library requires the SPI library + cg.add_library("SPI", None) cg.add_define("USE_BSEC") cg.add_library("boschsensortec/BSEC Software Library", "1.6.1480") From be9439f10d5468c0c864722a4e66047aea5dee20 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Mon, 8 Nov 2021 00:39:16 +0100 Subject: [PATCH 1658/1841] Fix for encrypted DSMR regression (#2679) Co-authored-by: Maurice Makaay --- esphome/components/dsmr/dsmr.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index 031fb275f5..ea852e626e 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -76,9 +76,10 @@ void Dsmr::receive_telegram_() { } // Check for the end of the hex checksum, i.e. a newline. if (footer_found_ && c == '\n') { - header_found_ = false; // Parse the telegram and publish sensor values. parse_telegram(); + + header_found_ = false; return; } } @@ -105,11 +106,11 @@ void Dsmr::receive_encrypted_() { // Find a new telegram start byte. if (!header_found_) { - if ((uint8_t) c == 0xDB) { - ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found"); - header_found_ = true; + if ((uint8_t) c != 0xDB) { + continue; } - continue; + ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found"); + header_found_ = true; } // Check for buffer overflow. @@ -147,10 +148,10 @@ void Dsmr::receive_encrypted_() { ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_); ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_); + parse_telegram(); + header_found_ = false; telegram_len_ = 0; - - parse_telegram(); return; } } From a17a6d53465b12b7e86e3262cc92c10d25f80b2d Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Mon, 8 Nov 2021 22:03:30 +1300 Subject: [PATCH 1659/1841] Add HA Entity Category support to MQTT (#2678) --- esphome/components/mqtt/mqtt_component.cpp | 11 +++++++++++ esphome/components/mqtt/mqtt_const.h | 3 +++ 2 files changed, 14 insertions(+) diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index be1018d97d..cebb8dd086 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -77,6 +77,17 @@ bool MQTTComponent::send_discovery_() { if (!this->get_icon().empty()) root[MQTT_ICON] = this->get_icon(); + switch (this->get_entity()->get_entity_category()) { + case ENTITY_CATEGORY_NONE: + break; + case ENTITY_CATEGORY_CONFIG: + root[MQTT_ENTITY_CATEGORY] = "config"; + break; + case ENTITY_CATEGORY_DIAGNOSTIC: + root[MQTT_ENTITY_CATEGORY] = "diagnostic"; + break; + } + if (config.state_topic) root[MQTT_STATE_TOPIC] = this->get_state_topic_(); if (config.command_topic) diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h index df5465ce9a..1d5e22efde 100644 --- a/esphome/components/mqtt/mqtt_const.h +++ b/esphome/components/mqtt/mqtt_const.h @@ -512,6 +512,9 @@ constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw_version"; constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area"; #endif +// Additional MQTT fields where no abbreviation is defined in HA source +constexpr const char *const MQTT_ENTITY_CATEGORY = "entity_category"; + } // namespace mqtt } // namespace esphome From add484a2ea06ba2bf2091306d3a53bd8967214bc Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 8 Nov 2021 19:29:28 +0100 Subject: [PATCH 1660/1841] Fix gpio validation for esp32 variants (#2609) Co-authored-by: Otto Winter Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/esp32/gpio.py | 96 +++++++++++++---------- esphome/components/esp32/gpio_esp32.py | 77 ++++++++++++++++++ esphome/components/esp32/gpio_esp32_c3.py | 53 +++++++++++++ esphome/components/esp32/gpio_esp32_h2.py | 11 +++ esphome/components/esp32/gpio_esp32_s2.py | 80 +++++++++++++++++++ esphome/components/esp32/gpio_esp32_s3.py | 74 +++++++++++++++++ 6 files changed, 348 insertions(+), 43 deletions(-) create mode 100644 esphome/components/esp32/gpio_esp32.py create mode 100644 esphome/components/esp32/gpio_esp32_c3.py create mode 100644 esphome/components/esp32/gpio_esp32_h2.py create mode 100644 esphome/components/esp32/gpio_esp32_s2.py create mode 100644 esphome/components/esp32/gpio_esp32_s3.py diff --git a/esphome/components/esp32/gpio.py b/esphome/components/esp32/gpio.py index 93ab17db22..5819943f37 100644 --- a/esphome/components/esp32/gpio.py +++ b/esphome/components/esp32/gpio.py @@ -1,4 +1,5 @@ -import logging +from dataclasses import dataclass +from typing import Any from esphome.const import ( CONF_ID, @@ -17,10 +18,24 @@ import esphome.config_validation as cv import esphome.codegen as cg from . import boards -from .const import KEY_BOARD, KEY_ESP32, esp32_ns +from .const import ( + KEY_BOARD, + KEY_ESP32, + KEY_VARIANT, + VARIANT_ESP32, + VARIANT_ESP32C3, + VARIANT_ESP32S2, + VARIANT_ESP32S3, + VARIANT_ESP32H2, + esp32_ns, +) -_LOGGER = logging.getLogger(__name__) +from .gpio_esp32 import esp32_validate_gpio_pin, esp32_validate_supports +from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports +from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports +from .gpio_esp32_s3 import esp32_s3_validate_gpio_pin, esp32_s3_validate_supports +from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports IDFInternalGPIOPin = esp32_ns.class_("IDFInternalGPIOPin", cg.InternalGPIOPin) @@ -59,65 +74,61 @@ def _translate_pin(value): return _lookup_pin(value) -_ESP_SDIO_PINS = { - 6: "Flash Clock", - 7: "Flash Data 0", - 8: "Flash Data 1", - 11: "Flash Command", +@dataclass +class ESP32ValidationFunctions: + pin_validation: Any + usage_validation: Any + + +_esp32_validations = { + VARIANT_ESP32: ESP32ValidationFunctions( + pin_validation=esp32_validate_gpio_pin, usage_validation=esp32_validate_supports + ), + VARIANT_ESP32S2: ESP32ValidationFunctions( + pin_validation=esp32_s2_validate_gpio_pin, + usage_validation=esp32_s2_validate_supports, + ), + VARIANT_ESP32C3: ESP32ValidationFunctions( + pin_validation=esp32_c3_validate_gpio_pin, + usage_validation=esp32_c3_validate_supports, + ), + VARIANT_ESP32S3: ESP32ValidationFunctions( + pin_validation=esp32_s3_validate_gpio_pin, + usage_validation=esp32_s3_validate_supports, + ), + VARIANT_ESP32H2: ESP32ValidationFunctions( + pin_validation=esp32_h2_validate_gpio_pin, + usage_validation=esp32_h2_validate_supports, + ), } def validate_gpio_pin(value): value = _translate_pin(value) - if value < 0 or value > 39: - raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)") - if value in _ESP_SDIO_PINS: - raise cv.Invalid( - f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" - ) - if 9 <= value <= 10: - _LOGGER.warning( - "Pin %s (9-10) might already be used by the " - "flash interface in QUAD IO flash mode.", - value, - ) - if value in (20, 24, 28, 29, 30, 31): - # These pins are not exposed in GPIO mux (reason unknown) - # but they're missing from IO_MUX list in datasheet - raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.") - return value + variant = CORE.data[KEY_ESP32][KEY_VARIANT] + if variant not in _esp32_validations: + raise cv.Invalid("Unsupported ESP32 variant {variant}") + + return _esp32_validations[variant].pin_validation(value) def validate_supports(value): - num = value[CONF_NUMBER] mode = value[CONF_MODE] is_input = mode[CONF_INPUT] is_output = mode[CONF_OUTPUT] is_open_drain = mode[CONF_OPEN_DRAIN] is_pullup = mode[CONF_PULLUP] is_pulldown = mode[CONF_PULLDOWN] + variant = CORE.data[KEY_ESP32][KEY_VARIANT] + if variant not in _esp32_validations: + raise cv.Invalid("Unsupported ESP32 variant {variant}") - if is_input: - # All ESP32 pins support input mode - pass - if is_output and 34 <= num <= 39: - raise cv.Invalid( - f"GPIO{num} (34-39) does not support output pin mode.", - [CONF_MODE, CONF_OUTPUT], - ) if is_open_drain and not is_output: raise cv.Invalid( "Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN] ) - if is_pullup and 34 <= num <= 39: - raise cv.Invalid( - f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP] - ) - if is_pulldown and 34 <= num <= 39: - raise cv.Invalid( - f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN] - ) + value = _esp32_validations[variant].usage_validation(value) if CORE.using_arduino: # (input, output, open_drain, pullup, pulldown) supported_modes = { @@ -138,7 +149,6 @@ def validate_supports(value): "This pin mode is not supported on ESP32 for arduino frameworks", [CONF_MODE], ) - return value diff --git a/esphome/components/esp32/gpio_esp32.py b/esphome/components/esp32/gpio_esp32.py new file mode 100644 index 0000000000..425d77b343 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32.py @@ -0,0 +1,77 @@ +import logging + +from esphome.const import ( + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, + CONF_OUTPUT, + CONF_PULLDOWN, + CONF_PULLUP, +) +import esphome.config_validation as cv + + +_ESP_SDIO_PINS = { + 6: "Flash Clock", + 7: "Flash Data 0", + 8: "Flash Data 1", + 11: "Flash Command", +} + +_ESP32_STRAPPING_PINS = {0, 2, 4, 15} +_LOGGER = logging.getLogger(__name__) + + +def esp32_validate_gpio_pin(value): + if value < 0 or value > 39: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)") + if value in _ESP_SDIO_PINS: + raise cv.Invalid( + f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" + ) + if 9 <= value <= 10: + _LOGGER.warning( + "Pin %s (9-10) might already be used by the " + "flash interface in QUAD IO flash mode.", + value, + ) + if value in _ESP32_STRAPPING_PINS: + _LOGGER.warning( + "GPIO%d is a Strapping PIN and should be avoided.\n" + "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" + "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + value, + ) + if value in (20, 24, 28, 29, 30, 31): + # These pins are not exposed in GPIO mux (reason unknown) + # but they're missing from IO_MUX list in datasheet + raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.") + return value + + +def esp32_validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + is_output = mode[CONF_OUTPUT] + is_pullup = mode[CONF_PULLUP] + is_pulldown = mode[CONF_PULLDOWN] + + if is_input: + # All ESP32 pins support input mode + pass + if is_output and 34 <= num <= 39: + raise cv.Invalid( + f"GPIO{num} (34-39) does not support output pin mode.", + [CONF_MODE, CONF_OUTPUT], + ) + if is_pullup and 34 <= num <= 39: + raise cv.Invalid( + f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP] + ) + if is_pulldown and 34 <= num <= 39: + raise cv.Invalid( + f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN] + ) + + return value diff --git a/esphome/components/esp32/gpio_esp32_c3.py b/esphome/components/esp32/gpio_esp32_c3.py new file mode 100644 index 0000000000..fc1cef29e5 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_c3.py @@ -0,0 +1,53 @@ +import logging + +from esphome.const import ( + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, +) +import esphome.config_validation as cv + +_ESP32C3_SPI_PSRAM_PINS = { + 12: "SPIHD", + 13: "SPIWP", + 14: "SPICS0", + 15: "SPICLK", + 16: "SPID", + 17: "SPIQ", +} + +_ESP32C3_STRAPPING_PINS = {2, 8, 9} + +_LOGGER = logging.getLogger(__name__) + + +def esp32_c3_validate_gpio_pin(value): + if value < 0 or value > 21: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)") + if value in _ESP32C3_SPI_PSRAM_PINS: + raise cv.Invalid( + f"This pin cannot be used on ESP32-C3s and is already used by the SPI/PSRAM interface (function: {_ESP32C3_SPI_PSRAM_PINS[value]})" + ) + if value in _ESP32C3_STRAPPING_PINS: + _LOGGER.warning( + "GPIO%d is a Strapping PIN and should be avoided.\n" + "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" + "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + value, + ) + + return value + + +def esp32_c3_validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + + if num < 0 or num > 21: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)") + + if is_input: + # All ESP32 pins support input mode + pass + return value diff --git a/esphome/components/esp32/gpio_esp32_h2.py b/esphome/components/esp32/gpio_esp32_h2.py new file mode 100644 index 0000000000..5196ef0c09 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_h2.py @@ -0,0 +1,11 @@ +import esphome.config_validation as cv + + +def esp32_h2_validate_gpio_pin(value): + # ESP32-H2 not yet supported + raise cv.Invalid("ESP32-H2 isn't supported yet") + + +def esp32_h2_validate_supports(value): + # ESP32-H2 not yet supported + raise cv.Invalid("ESP32-H2 isn't supported yet") diff --git a/esphome/components/esp32/gpio_esp32_s2.py b/esphome/components/esp32/gpio_esp32_s2.py new file mode 100644 index 0000000000..db244b6259 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_s2.py @@ -0,0 +1,80 @@ +import logging + +from esphome.const import ( + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, + CONF_OUTPUT, + CONF_PULLDOWN, + CONF_PULLUP, +) + +import esphome.config_validation as cv + +_ESP32S2_SPI_PSRAM_PINS = { + 26: "SPICS1", + 27: "SPIHD", + 28: "SPIWP", + 29: "SPICS0", + 30: "SPICLK", + 31: "SPIQ", + 32: "SPID", +} + +_ESP32S2_STRAPPING_PINS = {0, 45, 46} + +_LOGGER = logging.getLogger(__name__) + + +def esp32_s2_validate_gpio_pin(value): + if value < 0 or value > 46: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)") + + if value in _ESP32S2_SPI_PSRAM_PINS: + raise cv.Invalid( + f"This pin cannot be used on ESP32-S2s and is already used by the SPI/PSRAM interface (function: {_ESP32S2_SPI_PSRAM_PINS[value]})" + ) + if value in _ESP32S2_STRAPPING_PINS: + _LOGGER.warning( + "GPIO%d is a Strapping PIN and should be avoided.\n" + "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" + "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + value, + ) + + if value in (22, 23, 24, 25): + # These pins are not exposed in GPIO mux (reason unknown) + # but they're missing from IO_MUX list in datasheet + raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S2s.") + + return value + + +def esp32_s2_validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + is_output = mode[CONF_OUTPUT] + is_pullup = mode[CONF_PULLUP] + is_pulldown = mode[CONF_PULLDOWN] + + if num < 0 or num > 46: + raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)") + if is_input: + # All ESP32 pins support input mode + pass + if is_output and num == 46: + raise cv.Invalid( + f"GPIO{num} does not support output pin mode.", + [CONF_MODE, CONF_OUTPUT], + ) + if is_pullup and num == 46: + raise cv.Invalid( + f"GPIO{num} does not support pullups.", [CONF_MODE, CONF_PULLUP] + ) + if is_pulldown and num == 46: + raise cv.Invalid( + f"GPIO{num} does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN] + ) + + return value diff --git a/esphome/components/esp32/gpio_esp32_s3.py b/esphome/components/esp32/gpio_esp32_s3.py new file mode 100644 index 0000000000..f729a757c2 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_s3.py @@ -0,0 +1,74 @@ +import logging + +from esphome.const import ( + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, +) + +import esphome.config_validation as cv + +_ESP_32S3_SPI_PSRAM_PINS = { + 26: "SPICS1", + 27: "SPIHD", + 28: "SPIWP", + 29: "SPICS0", + 30: "SPICLK", + 31: "SPIQ", + 32: "SPID", +} + +_ESP_32_ESP32_S3R8_PSRAM_PINS = { + 33: "SPIIO4", + 34: "SPIIO5", + 35: "SPIIO6", + 36: "SPIIO7", + 37: "SPIDQS", +} + +_ESP_32S3_STRAPPING_PINS = {0, 3, 45, 46} + +_LOGGER = logging.getLogger(__name__) + + +def esp32_s3_validate_gpio_pin(value): + if value < 0 or value > 48: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)") + + if value in _ESP_32S3_SPI_PSRAM_PINS: + raise cv.Invalid( + f"This pin cannot be used on ESP32-S3s and is already used by the SPI/PSRAM interface(function: {_ESP_32S3_SPI_PSRAM_PINS[value]})" + ) + if value in _ESP_32_ESP32_S3R8_PSRAM_PINS: + _LOGGER.warning( + "GPIO%d is used by the PSRAM interface on ESP32-S3R8 / ESP32-S3R8V and should be avoided on these models", + value, + ) + + if value in _ESP_32S3_STRAPPING_PINS: + _LOGGER.warning( + "GPIO%d is a Strapping PIN and should be avoided.\n" + "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" + "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + value, + ) + + if value in (22, 23, 24, 25): + # These pins are not exposed in GPIO mux (reason unknown) + # but they're missing from IO_MUX list in datasheet + raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S3s.") + + return value + + +def esp32_s3_validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + + if num < 0 or num > 48: + raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)") + if is_input: + # All ESP32 pins support input mode + pass + return value From a509f6ccd29b84c2fb894c9d72f00553fbbc7ce5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 9 Nov 2021 17:14:08 +1300 Subject: [PATCH 1661/1841] Fix when package url has no branch/ref (#2683) --- esphome/git.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/git.py b/esphome/git.py index b64aa6a864..25d893b2f5 100644 --- a/esphome/git.py +++ b/esphome/git.py @@ -40,7 +40,7 @@ def clone_or_update( ) -> Path: key = f"{url}@{ref}" repo_dir = _compute_destination_path(key, domain) - fetch_pr_branch = ref.startswith("pull/") + fetch_pr_branch = ref is not None and ref.startswith("pull/") if not repo_dir.is_dir(): _LOGGER.info("Cloning %s", key) _LOGGER.debug("Location: %s", repo_dir) From f72389147d79f282f577b7f0884905c6e1d06296 Mon Sep 17 00:00:00 2001 From: ychieux Date: Tue, 9 Nov 2021 20:47:19 +0300 Subject: [PATCH 1662/1841] SSD1306_base: Add support for 64x32 size and fix flip functions (#2682) * Add support for SSD1306 OLED display 0.42inch 64x32 and fix a typo in __init__.py preventing flip functions to operate as intended * convert tab to spaces * fix typo on filename for __init__.py --- esphome/components/ssd1306_base/__init__.py | 3 ++- esphome/components/ssd1306_base/ssd1306_base.cpp | 6 ++++++ esphome/components/ssd1306_base/ssd1306_base.h | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/components/ssd1306_base/__init__.py b/esphome/components/ssd1306_base/__init__.py index bc2e558f1b..e4f62e5ff9 100644 --- a/esphome/components/ssd1306_base/__init__.py +++ b/esphome/components/ssd1306_base/__init__.py @@ -26,6 +26,7 @@ MODELS = { "SSD1306_128X64": SSD1306Model.SSD1306_MODEL_128_64, "SSD1306_96X16": SSD1306Model.SSD1306_MODEL_96_16, "SSD1306_64X48": SSD1306Model.SSD1306_MODEL_64_48, + "SSD1306_64X32": SSD1306Model.SSD1306_MODEL_64_32, "SH1106_128X32": SSD1306Model.SH1106_MODEL_128_32, "SH1106_128X64": SSD1306Model.SH1106_MODEL_128_64, "SH1106_96X16": SSD1306Model.SH1106_MODEL_96_16, @@ -84,7 +85,7 @@ async def setup_ssd1306(var, config): if CONF_FLIP_X in config: cg.add(var.init_flip_x(config[CONF_FLIP_X])) if CONF_FLIP_Y in config: - cg.add(var.init_flip_y(config[CONF_FLIP_X])) + cg.add(var.init_flip_y(config[CONF_FLIP_Y])) if CONF_OFFSET_X in config: cg.add(var.init_offset_x(config[CONF_OFFSET_X])) if CONF_OFFSET_Y in config: diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index b1a2538ebd..2537133605 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -94,6 +94,7 @@ void SSD1306::setup() { case SSD1306_MODEL_128_64: case SH1106_MODEL_128_64: case SSD1306_MODEL_64_48: + case SSD1306_MODEL_64_32: case SH1106_MODEL_64_48: case SSD1305_MODEL_128_32: case SSD1305_MODEL_128_64: @@ -141,6 +142,7 @@ void SSD1306::display() { this->command(SSD1306_COMMAND_COLUMN_ADDRESS); switch (this->model_) { case SSD1306_MODEL_64_48: + case SSD1306_MODEL_64_32: this->command(0x20 + this->offset_x_); this->command(0x20 + this->offset_x_ + this->get_width_internal() - 1); break; @@ -197,6 +199,7 @@ void SSD1306::turn_off() { int SSD1306::get_height_internal() { switch (this->model_) { case SSD1306_MODEL_128_32: + case SSD1306_MODEL_64_32: case SH1106_MODEL_128_32: case SSD1305_MODEL_128_32: return 32; @@ -227,6 +230,7 @@ int SSD1306::get_width_internal() { case SH1106_MODEL_96_16: return 96; case SSD1306_MODEL_64_48: + case SSD1306_MODEL_64_32: case SH1106_MODEL_64_48: return 64; default: @@ -271,6 +275,8 @@ const char *SSD1306::model_str_() { return "SSD1306 128x32"; case SSD1306_MODEL_128_64: return "SSD1306 128x64"; + case SSD1306_MODEL_64_32: + return "SSD1306 64x32"; case SSD1306_MODEL_96_16: return "SSD1306 96x16"; case SSD1306_MODEL_64_48: diff --git a/esphome/components/ssd1306_base/ssd1306_base.h b/esphome/components/ssd1306_base/ssd1306_base.h index 09417a2c10..c77b1985e4 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.h +++ b/esphome/components/ssd1306_base/ssd1306_base.h @@ -12,6 +12,7 @@ enum SSD1306Model { SSD1306_MODEL_128_64, SSD1306_MODEL_96_16, SSD1306_MODEL_64_48, + SSD1306_MODEL_64_32, SH1106_MODEL_128_32, SH1106_MODEL_128_64, SH1106_MODEL_96_16, From d6717c0032a2370364141bca66aae1011714f39a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 10 Nov 2021 08:38:20 +1300 Subject: [PATCH 1663/1841] Fix dashboard imports for adoption (#2684) --- .../components/dashboard_import/__init__.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/esphome/components/dashboard_import/__init__.py b/esphome/components/dashboard_import/__init__.py index 2b884d3b9a..d483c77c61 100644 --- a/esphome/components/dashboard_import/__init__.py +++ b/esphome/components/dashboard_import/__init__.py @@ -29,6 +29,14 @@ CONFIG_SCHEMA = cv.Schema( } ) +WIFI_MESSAGE = """ + +# Do not forget to add your own wifi configuration before installing this configuration +# wifi: +# ssid: !secret wifi_ssid +# password: !secret wifi_password +""" + async def to_code(config): cg.add_define("USE_DASHBOARD_IMPORT") @@ -41,5 +49,12 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N if p.exists(): raise FileExistsError - config = {"substitutions": {"name": name}, "packages": {project_name: import_url}} - p.write_text(dump(config), encoding="utf8") + config = { + "substitutions": {"name": name}, + "packages": {project_name: import_url}, + "esphome": {"name_add_mac_suffix": False}, + } + p.write_text( + dump(config) + WIFI_MESSAGE, + encoding="utf8", + ) From fb57ab0adde56b24a056c925bec3205278472b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Wed, 10 Nov 2021 01:10:07 +0100 Subject: [PATCH 1664/1841] Add `esp32_camera_web_server:` to expose mjpg/jpg images (#2237) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/api/api_server.cpp | 2 +- esphome/components/esp32_camera/__init__.py | 2 +- .../components/esp32_camera/esp32_camera.cpp | 1 + .../esp32_camera_web_server/__init__.py | 28 ++ .../camera_web_server.cpp | 239 ++++++++++++++++++ .../camera_web_server.h | 51 ++++ tests/test4.yaml | 6 + 8 files changed, 328 insertions(+), 2 deletions(-) create mode 100644 esphome/components/esp32_camera_web_server/__init__.py create mode 100644 esphome/components/esp32_camera_web_server/camera_web_server.cpp create mode 100644 esphome/components/esp32_camera_web_server/camera_web_server.h diff --git a/CODEOWNERS b/CODEOWNERS index 664bf9ad6b..e535608db3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -52,6 +52,7 @@ esphome/components/dsmr/* @glmnet @zuidwijk esphome/components/esp32/* @esphome/core esphome/components/esp32_ble/* @jesserockz esphome/components/esp32_ble_server/* @jesserockz +esphome/components/esp32_camera_web_server/* @ayufan esphome/components/esp32_improv/* @jesserockz esphome/components/esp8266/* @esphome/core esphome/components/exposure_notifications/* @OttoWinter diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 4e2899d94f..25081a809a 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -77,7 +77,7 @@ void APIServer::setup() { this->last_connected_ = millis(); #ifdef USE_ESP32_CAMERA - if (esp32_camera::global_esp32_camera != nullptr) { + if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) { esp32_camera::global_esp32_camera->add_image_callback( [this](const std::shared_ptr &image) { for (auto &c : this->clients_) diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 1a8b3a37ec..2b1890267f 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -17,7 +17,7 @@ from esphome.core import CORE from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.cpp_helpers import setup_entity -DEPENDENCIES = ["esp32", "api"] +DEPENDENCIES = ["esp32"] esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase) diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index ad4304d89f..6f93532f47 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -45,6 +45,7 @@ void ESP32Camera::dump_config() { auto conf = this->config_; ESP_LOGCONFIG(TAG, "ESP32 Camera:"); ESP_LOGCONFIG(TAG, " Name: %s", this->name_.c_str()); + ESP_LOGCONFIG(TAG, " Internal: %s", YESNO(this->internal_)); #ifdef USE_ARDUINO ESP_LOGCONFIG(TAG, " Board Has PSRAM: %s", YESNO(psramFound())); #endif // USE_ARDUINO diff --git a/esphome/components/esp32_camera_web_server/__init__.py b/esphome/components/esp32_camera_web_server/__init__.py new file mode 100644 index 0000000000..d8afea27b4 --- /dev/null +++ b/esphome/components/esp32_camera_web_server/__init__.py @@ -0,0 +1,28 @@ +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import CONF_ID, CONF_PORT, CONF_MODE + +CODEOWNERS = ["@ayufan"] +DEPENDENCIES = ["esp32_camera"] +MULTI_CONF = True + +esp32_camera_web_server_ns = cg.esphome_ns.namespace("esp32_camera_web_server") +CameraWebServer = esp32_camera_web_server_ns.class_("CameraWebServer", cg.Component) +Mode = esp32_camera_web_server_ns.enum("Mode") + +MODES = {"STREAM": Mode.STREAM, "SNAPSHOT": Mode.SNAPSHOT} + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(CameraWebServer), + cv.Required(CONF_PORT): cv.port, + cv.Required(CONF_MODE): cv.enum(MODES, upper=True), + }, +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + server = cg.new_Pvariable(config[CONF_ID]) + cg.add(server.set_port(config[CONF_PORT])) + cg.add(server.set_mode(config[CONF_MODE])) + await cg.register_component(server, config) diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.cpp b/esphome/components/esp32_camera_web_server/camera_web_server.cpp new file mode 100644 index 0000000000..ecaef78b77 --- /dev/null +++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp @@ -0,0 +1,239 @@ +#ifdef USE_ESP32 + +#include "camera_web_server.h" +#include "esphome/core/application.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/util.h" + +#include +#include +#include + +namespace esphome { +namespace esp32_camera_web_server { + +static const int IMAGE_REQUEST_TIMEOUT = 2000; +static const char *const TAG = "esp32_camera_web_server"; + +#define PART_BOUNDARY "123456789000000000000987654321" +#define CONTENT_TYPE "image/jpeg" +#define CONTENT_LENGTH "Content-Length" + +static const char *const STREAM_HEADER = + "HTTP/1.1 200\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY + "\r\n"; +static const char *const STREAM_500 = "HTTP/1.1 500\r\nContent-Type: text/plain\r\n\r\nNo frames send.\r\n"; +static const char *const STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; +static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n"; + +CameraWebServer::CameraWebServer() {} + +CameraWebServer::~CameraWebServer() {} + +void CameraWebServer::setup() { + if (!esp32_camera::global_esp32_camera || esp32_camera::global_esp32_camera->is_failed()) { + this->mark_failed(); + return; + } + + this->semaphore_ = xSemaphoreCreateBinary(); + + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.server_port = this->port_; + config.ctrl_port = this->port_; + config.max_open_sockets = 1; + config.backlog_conn = 2; + + if (httpd_start(&this->httpd_, &config) != ESP_OK) { + mark_failed(); + return; + } + + httpd_uri_t uri = { + .uri = "/", + .method = HTTP_GET, + .handler = [](struct httpd_req *req) { return ((CameraWebServer *) req->user_ctx)->handler_(req); }, + .user_ctx = this}; + + httpd_register_uri_handler(this->httpd_, &uri); + + esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr image) { + if (this->running_) { + this->image_ = std::move(image); + xSemaphoreGive(this->semaphore_); + } + }); +} + +void CameraWebServer::on_shutdown() { + this->running_ = false; + this->image_ = nullptr; + httpd_stop(this->httpd_); + this->httpd_ = nullptr; + vSemaphoreDelete(this->semaphore_); + this->semaphore_ = nullptr; +} + +void CameraWebServer::dump_config() { + ESP_LOGCONFIG(TAG, "ESP32 Camera Web Server:"); + ESP_LOGCONFIG(TAG, " Port: %d", this->port_); + if (this->mode_ == STREAM) + ESP_LOGCONFIG(TAG, " Mode: stream"); + else + ESP_LOGCONFIG(TAG, " Mode: snapshot"); + + if (this->is_failed()) { + ESP_LOGE(TAG, " Setup Failed"); + } +} + +float CameraWebServer::get_setup_priority() const { return setup_priority::LATE; } + +void CameraWebServer::loop() { + if (!this->running_) { + this->image_ = nullptr; + } +} + +std::shared_ptr CameraWebServer::wait_for_image_() { + std::shared_ptr image; + image.swap(this->image_); + + if (!image) { + // retry as we might still be fetching image + xSemaphoreTake(this->semaphore_, IMAGE_REQUEST_TIMEOUT / portTICK_PERIOD_MS); + image.swap(this->image_); + } + + return image; +} + +esp_err_t CameraWebServer::handler_(struct httpd_req *req) { + esp_err_t res = ESP_FAIL; + + this->image_ = nullptr; + this->running_ = true; + + switch (this->mode_) { + case STREAM: + res = this->streaming_handler_(req); + break; + + case SNAPSHOT: + res = this->snapshot_handler_(req); + break; + } + + this->running_ = false; + this->image_ = nullptr; + return res; +} + +static esp_err_t httpd_send_all(httpd_req_t *r, const char *buf, size_t buf_len) { + int ret; + + while (buf_len > 0) { + ret = httpd_send(r, buf, buf_len); + if (ret < 0) { + return ESP_FAIL; + } + buf += ret; + buf_len -= ret; + } + return ESP_OK; +} + +esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) { + esp_err_t res = ESP_OK; + char part_buf[64]; + + // This manually constructs HTTP response to avoid chunked encoding + // which is not supported by some clients + + res = httpd_send_all(req, STREAM_HEADER, strlen(STREAM_HEADER)); + if (res != ESP_OK) { + ESP_LOGW(TAG, "STREAM: failed to set HTTP header"); + return res; + } + + uint32_t last_frame = millis(); + uint32_t frames = 0; + + while (res == ESP_OK && this->running_) { + if (esp32_camera::global_esp32_camera != nullptr) { + esp32_camera::global_esp32_camera->request_stream(); + } + + auto image = this->wait_for_image_(); + + if (!image) { + ESP_LOGW(TAG, "STREAM: failed to acquire frame"); + res = ESP_FAIL; + } + if (res == ESP_OK) { + res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY)); + } + if (res == ESP_OK) { + size_t hlen = snprintf(part_buf, 64, STREAM_PART, image->get_data_length()); + res = httpd_send_all(req, part_buf, hlen); + } + if (res == ESP_OK) { + res = httpd_send_all(req, (const char *) image->get_data_buffer(), image->get_data_length()); + } + if (res == ESP_OK) { + frames++; + int64_t frame_time = millis() - last_frame; + last_frame = millis(); + + ESP_LOGD(TAG, "MJPG: %uB %ums (%.1ffps)", (uint32_t) image->get_data_length(), (uint32_t) frame_time, + 1000.0 / (uint32_t) frame_time); + } + } + + if (!frames) { + res = httpd_send_all(req, STREAM_500, strlen(STREAM_500)); + } + + ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames); + + return res; +} + +esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) { + esp_err_t res = ESP_OK; + + if (esp32_camera::global_esp32_camera != nullptr) { + esp32_camera::global_esp32_camera->request_image(); + } + + auto image = this->wait_for_image_(); + + if (!image) { + ESP_LOGW(TAG, "SNAPSHOT: failed to acquire frame"); + httpd_resp_send_500(req); + res = ESP_FAIL; + return res; + } + + res = httpd_resp_set_type(req, CONTENT_TYPE); + if (res != ESP_OK) { + ESP_LOGW(TAG, "SNAPSHOT: failed to set HTTP response type"); + return res; + } + + httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg"); + + if (res == ESP_OK) { + res = httpd_resp_set_hdr(req, CONTENT_LENGTH, esphome::to_string(image->get_data_length()).c_str()); + } + if (res == ESP_OK) { + res = httpd_resp_send(req, (const char *) image->get_data_buffer(), image->get_data_length()); + } + return res; +} + +} // namespace esp32_camera_web_server +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.h b/esphome/components/esp32_camera_web_server/camera_web_server.h new file mode 100644 index 0000000000..df30a43ed2 --- /dev/null +++ b/esphome/components/esp32_camera_web_server/camera_web_server.h @@ -0,0 +1,51 @@ +#pragma once + +#ifdef USE_ESP32 + +#include +#include + +#include "esphome/components/esp32_camera/esp32_camera.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "esphome/core/preferences.h" + +struct httpd_req; + +namespace esphome { +namespace esp32_camera_web_server { + +enum Mode { STREAM, SNAPSHOT }; + +class CameraWebServer : public Component { + public: + CameraWebServer(); + ~CameraWebServer(); + + void setup() override; + void on_shutdown() override; + void dump_config() override; + float get_setup_priority() const override; + void set_port(uint16_t port) { this->port_ = port; } + void set_mode(Mode mode) { this->mode_ = mode; } + void loop() override; + + protected: + std::shared_ptr wait_for_image_(); + esp_err_t handler_(struct httpd_req *req); + esp_err_t streaming_handler_(struct httpd_req *req); + esp_err_t snapshot_handler_(struct httpd_req *req); + + protected: + uint16_t port_{0}; + void *httpd_{nullptr}; + SemaphoreHandle_t semaphore_; + std::shared_ptr image_; + bool running_{false}; + Mode mode_{STREAM}; +}; + +} // namespace esp32_camera_web_server +} // namespace esphome + +#endif // USE_ESP32 diff --git a/tests/test4.yaml b/tests/test4.yaml index bc249c5ecb..4228c7494c 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -481,6 +481,12 @@ esp32_camera: resolution: 640x480 jpeg_quality: 10 +esp32_camera_web_server: + - port: 8080 + mode: stream + - port: 8081 + mode: snapshot + external_components: - source: github://esphome/esphome@dev refresh: 1d From 57b07441a1fed3fbab934cdae35ff3098471c537 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Tue, 9 Nov 2021 21:15:02 -0300 Subject: [PATCH 1665/1841] fix esp32 rmt receiver item array length (#2671) --- .../remote_receiver/remote_receiver_esp32.cpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/esphome/components/remote_receiver/remote_receiver_esp32.cpp b/esphome/components/remote_receiver/remote_receiver_esp32.cpp index dde9b843c9..5a7fb3c985 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp32.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp32.cpp @@ -78,6 +78,7 @@ void RemoteReceiverComponent::loop() { if (this->temp_.empty()) return; + this->temp_.push_back(-this->idle_us_); this->call_listeners_dumpers_(); } } @@ -86,9 +87,10 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { uint32_t prev_length = 0; this->temp_.clear(); int32_t multiplier = this->pin_->is_inverted() ? -1 : 1; + size_t item_count = len / sizeof(rmt_item32_t); ESP_LOGVV(TAG, "START:"); - for (size_t i = 0; i < len; i++) { + for (size_t i = 0; i < item_count; i++) { if (item[i].level0) { ESP_LOGVV(TAG, "%u A: ON %uus (%u ticks)", i, this->to_microseconds_(item[i].duration0), item[i].duration0); } else { @@ -102,8 +104,8 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { } ESP_LOGVV(TAG, "\n"); - this->temp_.reserve(len / 4); - for (size_t i = 0; i < len; i++) { + this->temp_.reserve(item_count * 2); // each RMT item has 2 pulses + for (size_t i = 0; i < item_count; i++) { if (item[i].duration0 == 0u) { // Do nothing } else if (bool(item[i].level0) == prev_level) { @@ -120,10 +122,6 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { prev_length = item[i].duration0; } - if (this->to_microseconds_(prev_length) > this->idle_us_) { - break; - } - if (item[i].duration1 == 0u) { // Do nothing } else if (bool(item[i].level1) == prev_level) { @@ -139,10 +137,6 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { prev_level = bool(item[i].level1); prev_length = item[i].duration1; } - - if (this->to_microseconds_(prev_length) > this->idle_us_) { - break; - } } if (prev_length > 0) { if (prev_level) { From 366552a9692ef5e80ffc986d6b38bec900633646 Mon Sep 17 00:00:00 2001 From: cvwillegen Date: Wed, 10 Nov 2021 04:11:35 +0100 Subject: [PATCH 1666/1841] Remote base add pronto protocol (#2619) --- esphome/components/remote_base/__init__.py | 43 ++++++ .../remote_base/pronto_protocol.cpp | 135 ++++++++++++++++++ .../components/remote_base/pronto_protocol.h | 40 ++++++ 3 files changed, 218 insertions(+) create mode 100644 esphome/components/remote_base/pronto_protocol.cpp create mode 100644 esphome/components/remote_base/pronto_protocol.h diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index d2b848600d..914ce42efe 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -439,6 +439,49 @@ async def pioneer_action(var, config, args): cg.add(var.set_rc_code_2(template_)) +# Pronto +( + ProntoData, + ProntoBinarySensor, + ProntoTrigger, + ProntoAction, + ProntoDumper, +) = declare_protocol("Pronto") +PRONTO_SCHEMA = cv.Schema( + { + cv.Required(CONF_DATA): cv.string, + } +) + + +@register_binary_sensor("pronto", ProntoBinarySensor, PRONTO_SCHEMA) +def pronto_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + ProntoData, + ("data", config[CONF_DATA]), + ) + ) + ) + + +@register_trigger("pronto", ProntoTrigger, ProntoData) +def pronto_trigger(var, config): + pass + + +@register_dumper("pronto", ProntoDumper) +def pronto_dumper(var, config): + pass + + +@register_action("pronto", ProntoAction, PRONTO_SCHEMA) +async def pronto_action(var, config, args): + template_ = await cg.templatable(config[CONF_DATA], args, cg.std_string) + cg.add(var.set_data(template_)) + + # Sony SonyData, SonyBinarySensor, SonyTrigger, SonyAction, SonyDumper = declare_protocol( "Sony" diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp new file mode 100644 index 0000000000..11aebb6c5d --- /dev/null +++ b/esphome/components/remote_base/pronto_protocol.cpp @@ -0,0 +1,135 @@ +/* + * @file irPronto.cpp + * @brief In this file, the functions IRrecv::compensateAndPrintPronto and IRsend::sendPronto are defined. + * + * See http://www.harctoolbox.org/Glossary.html#ProntoSemantics + * Pronto database http://www.remotecentral.com/search.htm + * + * This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote. + * + ************************************************************************************ + * MIT License + * + * Copyright (c) 2020 Bengt Martensson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************************ + */ + +#include "pronto_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.pronto"; + +// DO NOT EXPORT from this file +static const uint16_t MICROSECONDS_T_MAX = 0xFFFFU; +static const uint16_t LEARNED_TOKEN = 0x0000U; +static const uint16_t LEARNED_NON_MODULATED_TOKEN = 0x0100U; +static const uint16_t BITS_IN_HEXADECIMAL = 4U; +static const uint16_t DIGITS_IN_PRONTO_NUMBER = 4U; +static const uint16_t NUMBERS_IN_PREAMBLE = 4U; +static const uint16_t HEX_MASK = 0xFU; +static const uint32_t REFERENCE_FREQUENCY = 4145146UL; +static const uint16_t FALLBACK_FREQUENCY = 64767U; // To use with frequency = 0; +static const uint32_t MICROSECONDS_IN_SECONDS = 1000000UL; +static const uint16_t PRONTO_DEFAULT_GAP = 45000; + +static uint16_t to_frequency_k_hz(uint16_t code) { + if (code == 0) + return 0; + + return ((REFERENCE_FREQUENCY / code) + 500) / 1000; +} + +/* + * Parse the string given as Pronto Hex, and send it a number of times given as argument. + */ +void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::vector &data) { + if (data.size() < 4) + return; + + uint16_t timebase = (MICROSECONDS_IN_SECONDS * data[1] + REFERENCE_FREQUENCY / 2) / REFERENCE_FREQUENCY; + uint16_t khz; + switch (data[0]) { + case LEARNED_TOKEN: // normal, "learned" + khz = to_frequency_k_hz(data[1]); + break; + case LEARNED_NON_MODULATED_TOKEN: // non-demodulated, "learned" + khz = 0U; + break; + default: + return; // There are other types, but they are not handled yet. + } + ESP_LOGD(TAG, "Send Pronto: frequency=%dkHz", khz); + dst->set_carrier_frequency(khz * 1000); + + uint16_t intros = 2 * data[2]; + uint16_t repeats = 2 * data[3]; + ESP_LOGD(TAG, "Send Pronto: intros=%d", intros); + ESP_LOGD(TAG, "Send Pronto: repeats=%d", repeats); + if (NUMBERS_IN_PREAMBLE + intros + repeats != data.size()) { // inconsistent sizes + return; + } + + /* + * Generate a new microseconds timing array for sendRaw. + * If recorded by IRremote, intro contains the whole IR data and repeat is empty + */ + dst->reserve(intros + repeats); + + for (uint16_t i = 0; i < intros + repeats; i += 2) { + uint32_t duration0 = ((uint32_t) data[i + 0 + NUMBERS_IN_PREAMBLE]) * timebase; + duration0 = duration0 < MICROSECONDS_T_MAX ? duration0 : MICROSECONDS_T_MAX; + + uint32_t duration1 = ((uint32_t) data[i + 1 + NUMBERS_IN_PREAMBLE]) * timebase; + duration1 = duration1 < MICROSECONDS_T_MAX ? duration1 : MICROSECONDS_T_MAX; + + dst->item(duration0, duration1); + } +} + +void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &str) { + size_t len = str.length() / (DIGITS_IN_PRONTO_NUMBER + 1) + 1; + std::vector data; + const char *p = str.c_str(); + char *endptr[1]; + + for (uint16_t i = 0; i < len; i++) { + uint16_t x = strtol(p, endptr, 16); + if (x == 0 && i >= NUMBERS_IN_PREAMBLE) { + // Alignment error?, bail immediately (often right result). + break; + } + data.push_back(x); // If input is conforming, there can be no overflow! + p = *endptr; + } + send_pronto_(dst, data); +} + +void ProntoProtocol::encode(RemoteTransmitData *dst, const ProntoData &data) { send_pronto_(dst, data.data); } + +optional ProntoProtocol::decode(RemoteReceiveData src) { return {}; } + +void ProntoProtocol::dump(const ProntoData &data) { ESP_LOGD(TAG, "Received Pronto: data=%s", data.data.c_str()); } + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/pronto_protocol.h b/esphome/components/remote_base/pronto_protocol.h new file mode 100644 index 0000000000..e96511383f --- /dev/null +++ b/esphome/components/remote_base/pronto_protocol.h @@ -0,0 +1,40 @@ +#pragma once + +#include "esphome/core/component.h" +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +struct ProntoData { + std::string data; + + bool operator==(const ProntoData &rhs) const { return data == rhs.data; } +}; + +class ProntoProtocol : public RemoteProtocol { + private: + void send_pronto_(RemoteTransmitData *dst, const std::vector &data); + void send_pronto_(RemoteTransmitData *dst, const std::string &str); + + public: + void encode(RemoteTransmitData *dst, const ProntoData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const ProntoData &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(Pronto) + +template class ProntoAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(std::string, data) + + void encode(RemoteTransmitData *dst, Ts... x) override { + ProntoData data{}; + data.data = this->data_.value(x...); + ProntoProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome From 97eaf3d4a1f009a33d05262ee32449fa423fdbb6 Mon Sep 17 00:00:00 2001 From: Duncan Findlay Date: Tue, 9 Nov 2021 19:12:20 -0800 Subject: [PATCH 1667/1841] Set up output_switch at priority DATA instead of HARDWARE. (#2648) --- esphome/components/output/switch/output_switch.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/output/switch/output_switch.h b/esphome/components/output/switch/output_switch.h index fc9540fede..a184a342fe 100644 --- a/esphome/components/output/switch/output_switch.h +++ b/esphome/components/output/switch/output_switch.h @@ -12,7 +12,7 @@ class OutputSwitch : public switch_::Switch, public Component { void set_output(BinaryOutput *output) { output_ = output; } void setup() override; - float get_setup_priority() const override { return setup_priority::HARDWARE; } + float get_setup_priority() const override { return setup_priority::HARDWARE - 1.0f; } void dump_config() override; protected: From 6e5cfac927c8d222d3557b959924c33ee4870833 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Wed, 10 Nov 2021 00:15:15 -0300 Subject: [PATCH 1668/1841] fix rc switch protocol 6 (#2672) --- esphome/components/remote_base/rc_switch_protocol.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/esphome/components/remote_base/rc_switch_protocol.cpp b/esphome/components/remote_base/rc_switch_protocol.cpp index 6b7d1b725a..1dc094d552 100644 --- a/esphome/components/remote_base/rc_switch_protocol.cpp +++ b/esphome/components/remote_base/rc_switch_protocol.cpp @@ -101,10 +101,13 @@ bool RCSwitchBase::expect_sync(RemoteReceiveData &src) const { if (!src.peek_space(this->sync_low_, 1)) return false; } else { - if (!src.peek_space(this->sync_high_)) - return false; - if (!src.peek_mark(this->sync_low_, 1)) + // We cant peek a space at the beginning because signals starts with a low to high transition. + // this long space at the beginning is the separation between the transmissions itself, so it is actually + // added at the end kind of artificially (by the value given to "idle:" option by the user in the yaml) + if (!src.peek_mark(this->sync_low_)) return false; + src.advance(1); + return true; } src.advance(2); return true; From 875b803483a064f08457a821dc090807149bd81b Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Wed, 10 Nov 2021 04:22:00 +0100 Subject: [PATCH 1669/1841] Remove "delay_microseconds_accurate()" and improve systemwide delayMicroseconds() (#2497) --- esphome/components/aht10/aht10.cpp | 2 +- esphome/components/esp32/core.cpp | 6 +----- esphome/components/esp8266/core.cpp | 2 +- .../remote_transmitter_esp32.cpp | 6 ++---- .../remote_transmitter_esp8266.cpp | 13 ++++++------ esphome/components/sdp3x/sdp3x.cpp | 3 ++- esphome/core/helpers.cpp | 21 ++++++++++--------- esphome/core/helpers.h | 2 +- 8 files changed, 25 insertions(+), 30 deletions(-) diff --git a/esphome/components/aht10/aht10.cpp b/esphome/components/aht10/aht10.cpp index 713199212c..e5e04ac181 100644 --- a/esphome/components/aht10/aht10.cpp +++ b/esphome/components/aht10/aht10.cpp @@ -73,7 +73,7 @@ void AHT10Component::update() { bool success = false; for (int i = 0; i < AHT10_ATTEMPTS; ++i) { ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis()); - delay_microseconds_accurate(4); + delayMicroseconds(4); uint8_t reg = 0; if (this->write(®, 1) != i2c::ERROR_OK) { diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index 96047df535..359999120f 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -21,11 +21,7 @@ void IRAM_ATTR HOT yield() { vPortYield(); } uint32_t IRAM_ATTR HOT millis() { return (uint32_t)(esp_timer_get_time() / 1000ULL); } void IRAM_ATTR HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); } uint32_t IRAM_ATTR HOT micros() { return (uint32_t) esp_timer_get_time(); } -void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { - auto start = (uint64_t) esp_timer_get_time(); - while (((uint64_t) esp_timer_get_time()) - start < us) - ; -} +void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); } void arch_restart() { esp_restart(); // restart() doesn't always end execution diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp index b78600e7a3..51f3ca50ec 100644 --- a/esphome/components/esp8266/core.cpp +++ b/esphome/components/esp8266/core.cpp @@ -12,7 +12,7 @@ void IRAM_ATTR HOT yield() { ::yield(); } uint32_t IRAM_ATTR HOT millis() { return ::millis(); } void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); } uint32_t IRAM_ATTR HOT micros() { return ::micros(); } -void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { ::delayMicroseconds(us); } +void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); } void arch_restart() { ESP.restart(); // NOLINT(readability-static-accessed-through-instance) // restart() doesn't always end execution diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index 500d7193f3..c3b61b72c2 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -121,10 +121,8 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen } else { this->status_clear_warning(); } - if (i + 1 < send_times) { - delay(send_wait / 1000UL); - delayMicroseconds(send_wait % 1000UL); - } + if (i + 1 < send_times) + delayMicroseconds(send_wait); } } diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp index 33c01985d7..74e62d4e3b 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp @@ -36,7 +36,7 @@ void RemoteTransmitterComponent::calculate_on_off_time_(uint32_t carrier_frequen void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint32_t usec) { if (this->carrier_duty_percent_ == 100 || (on_time == 0 && off_time == 0)) { this->pin_->digital_write(true); - delay_microseconds_accurate(usec); + delayMicroseconds(usec); this->pin_->digital_write(false); return; } @@ -48,19 +48,19 @@ void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint const uint32_t elapsed = current_time - start_time; this->pin_->digital_write(true); - delay_microseconds_accurate(std::min(on_time, usec - elapsed)); + delayMicroseconds(std::min(on_time, usec - elapsed)); this->pin_->digital_write(false); if (elapsed + on_time >= usec) return; - delay_microseconds_accurate(std::min(usec - elapsed - on_time, off_time)); + delayMicroseconds(std::min(usec - elapsed - on_time, off_time)); current_time = micros(); } } void RemoteTransmitterComponent::space_(uint32_t usec) { this->pin_->digital_write(false); - delay_microseconds_accurate(usec); + delayMicroseconds(usec); } void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { ESP_LOGD(TAG, "Sending remote code..."); @@ -81,9 +81,8 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen } } - if (i + 1 < send_times) { - delay_microseconds_accurate(send_wait); - } + if (i + 1 < send_times) + delayMicroseconds(send_wait); } } diff --git a/esphome/components/sdp3x/sdp3x.cpp b/esphome/components/sdp3x/sdp3x.cpp index ba7a028f8e..b0d8bcc6c4 100644 --- a/esphome/components/sdp3x/sdp3x.cpp +++ b/esphome/components/sdp3x/sdp3x.cpp @@ -1,5 +1,6 @@ #include "sdp3x.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" namespace esphome { @@ -25,7 +26,7 @@ void SDP3XComponent::setup() { ESP_LOGW(TAG, "Soft Reset SDP3X failed!"); // This sometimes fails for no good reason } - delay_microseconds_accurate(20000); + delayMicroseconds(20000); if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Read ID1 SDP3X failed!"); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index ada9a48c3b..8cf972e4db 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -209,17 +209,18 @@ uint8_t crc8(uint8_t *data, uint8_t len) { return crc; } -void delay_microseconds_accurate(uint32_t usec) { - if (usec == 0) - return; - if (usec < 5000UL) { - delayMicroseconds(usec); - return; - } - uint32_t start = micros(); - while (micros() - start < usec) { - delay(0); +void delay_microseconds_safe(uint32_t us) { // avoids CPU locks that could trigger WDT or affect WiFi/BT stability + auto start = micros(); + const uint32_t lag = 5000; // microseconds, specifies the maximum time for a CPU busy-loop. + // it must be larger than the worst-case duration of a delay(1) call (hardware tasks) + // 5ms is conservative, it could be reduced when exact BT/WiFi stack delays are known + if (us > lag) { + delay((us - lag) / 1000UL); // note: in disabled-interrupt contexts delay() won't actually sleep + while (micros() - start < us - lag) + delay(1); // in those cases, this loop allows to yield for BT/WiFi stack tasks } + while (micros() - start < us) // fine delay the remaining usecs + ; } uint8_t reverse_bits_8(uint8_t x) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 905cb76170..040780e072 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -255,7 +255,7 @@ struct is_callable // NOLINT static constexpr auto value = decltype(test(nullptr))::value; // NOLINT }; -void delay_microseconds_accurate(uint32_t usec); +void delay_microseconds_safe(uint32_t us); template class Deduplicator { public: From 662773b075331d3efdbb59c02458c733e7005105 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Wed, 10 Nov 2021 04:24:44 +0100 Subject: [PATCH 1670/1841] modbus_controller: remove hard coded register size (#2654) --- .../modbus_controller/modbus_controller.h | 32 +++---------------- .../text_sensor/modbus_textsensor.h | 7 ---- 2 files changed, 4 insertions(+), 35 deletions(-) diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 4b5f4337db..222ebbd020 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -260,35 +260,11 @@ struct SensorItem { virtual void parse_and_publish(const std::vector &data) = 0; uint64_t getkey() const { return calc_key(register_type, start_address, offset, bitmask); } - size_t virtual get_register_size() const { - size_t size = 0; - switch (sensor_value_type) { - case SensorValueType::BIT: - size = 1; - break; - case SensorValueType::U_WORD: - case SensorValueType::S_WORD: - size = 2; - break; - case SensorValueType::U_DWORD: - case SensorValueType::S_DWORD: - case SensorValueType::U_DWORD_R: - case SensorValueType::S_DWORD_R: - case SensorValueType::FP32: - case SensorValueType::FP32_R: - size = 4; - break; - case SensorValueType::U_QWORD: - case SensorValueType::U_QWORD_R: - case SensorValueType::S_QWORD: - case SensorValueType::S_QWORD_R: - size = 8; - break; - case SensorValueType::RAW: - size = this->register_count * 2; - } - return size; + if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) + return 1; + else + return register_count * 2; } }; diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h index 28d0f0b241..77b5b9363a 100644 --- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h @@ -25,13 +25,6 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi this->sensor_value_type = SensorValueType::RAW; this->force_new_range = force_new_range; } - size_t get_register_size() const override { - if (sensor_value_type == SensorValueType::RAW) { - return this->response_bytes_; - } else { - return SensorItem::get_register_size(); - } - } void dump_config() override; From 710866ff4e0525949fdb1d5ae6334e68a707923b Mon Sep 17 00:00:00 2001 From: Sam Hughes Date: Wed, 10 Nov 2021 17:52:49 +0000 Subject: [PATCH 1671/1841] CAP1188 Capacitive Touch Sensor Support (#2653) --- CODEOWNERS | 1 + esphome/components/cap1188/__init__.py | 45 +++++++++++ esphome/components/cap1188/binary_sensor.py | 25 ++++++ esphome/components/cap1188/cap1188.cpp | 88 +++++++++++++++++++++ esphome/components/cap1188/cap1188.h | 68 ++++++++++++++++ tests/test2.yaml | 6 ++ 6 files changed, 233 insertions(+) create mode 100644 esphome/components/cap1188/__init__.py create mode 100644 esphome/components/cap1188/binary_sensor.py create mode 100644 esphome/components/cap1188/cap1188.cpp create mode 100644 esphome/components/cap1188/cap1188.h diff --git a/CODEOWNERS b/CODEOWNERS index e535608db3..8f98fe1f7f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -31,6 +31,7 @@ esphome/components/binary_sensor/* @esphome/core esphome/components/ble_client/* @buxtronix esphome/components/bme680_bsec/* @trvrnrth esphome/components/canbus/* @danielschramm @mvturnho +esphome/components/cap1188/* @MrEditor97 esphome/components/captive_portal/* @OttoWinter esphome/components/ccs811/* @habbie esphome/components/climate/* @esphome/core diff --git a/esphome/components/cap1188/__init__.py b/esphome/components/cap1188/__init__.py new file mode 100644 index 0000000000..80794c5146 --- /dev/null +++ b/esphome/components/cap1188/__init__.py @@ -0,0 +1,45 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c +from esphome.const import CONF_ID, CONF_RESET_PIN +from esphome import pins + +CONF_TOUCH_THRESHOLD = "touch_threshold" +CONF_ALLOW_MULTIPLE_TOUCHES = "allow_multiple_touches" + +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["binary_sensor", "output"] +CODEOWNERS = ["@MrEditor97"] + +cap1188_ns = cg.esphome_ns.namespace("cap1188") +CONF_CAP1188_ID = "cap1188_id" +CAP1188Component = cap1188_ns.class_("CAP1188Component", cg.Component, i2c.I2CDevice) + +MULTI_CONF = True +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(CAP1188Component), + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_TOUCH_THRESHOLD, default=0x20): cv.int_range( + min=0x01, max=0x80 + ), + cv.Optional(CONF_ALLOW_MULTIPLE_TOUCHES, default=False): cv.boolean, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x29)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD])) + cg.add(var.set_allow_multiple_touches(config[CONF_ALLOW_MULTIPLE_TOUCHES])) + + if CONF_RESET_PIN in config: + pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) + cg.add(var.set_reset_pin(pin)) + + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/cap1188/binary_sensor.py b/esphome/components/cap1188/binary_sensor.py new file mode 100644 index 0000000000..c249eb7330 --- /dev/null +++ b/esphome/components/cap1188/binary_sensor.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import CONF_CHANNEL, CONF_ID +from . import cap1188_ns, CAP1188Component, CONF_CAP1188_ID + +DEPENDENCIES = ["cap1188"] +CAP1188Channel = cap1188_ns.class_("CAP1188Channel", binary_sensor.BinarySensor) + +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CAP1188Channel), + cv.GenerateID(CONF_CAP1188_ID): cv.use_id(CAP1188Component), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7), + } +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await binary_sensor.register_binary_sensor(var, config) + hub = await cg.get_variable(config[CONF_CAP1188_ID]) + cg.add(var.set_channel(config[CONF_CHANNEL])) + + cg.add(hub.register_channel(var)) diff --git a/esphome/components/cap1188/cap1188.cpp b/esphome/components/cap1188/cap1188.cpp new file mode 100644 index 0000000000..10d8325537 --- /dev/null +++ b/esphome/components/cap1188/cap1188.cpp @@ -0,0 +1,88 @@ +#include "cap1188.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace cap1188 { + +static const char *const TAG = "cap1188"; + +void CAP1188Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up CAP1188..."); + + // Reset device using the reset pin + if (this->reset_pin_ != nullptr) { + this->reset_pin_->setup(); + this->reset_pin_->digital_write(false); + delay(100); // NOLINT + this->reset_pin_->digital_write(true); + delay(100); // NOLINT + this->reset_pin_->digital_write(false); + delay(100); // NOLINT + } + + // Check if CAP1188 is actually connected + this->read_byte(CAP1188_PRODUCT_ID, &this->cap1188_product_id_); + this->read_byte(CAP1188_MANUFACTURE_ID, &this->cap1188_manufacture_id_); + this->read_byte(CAP1188_REVISION, &this->cap1188_revision_); + + if ((this->cap1188_product_id_ != 0x50) || (this->cap1188_manufacture_id_ != 0x5D)) { + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + + // Set sensitivity + uint8_t sensitivity = 0; + this->read_byte(CAP1188_SENSITVITY, &sensitivity); + sensitivity = sensitivity & 0x0f; + this->write_byte(CAP1188_SENSITVITY, sensitivity | this->touch_threshold_); + + // Allow multiple touches + this->write_byte(CAP1188_MULTI_TOUCH, this->allow_multiple_touches_); + + // Have LEDs follow touches + this->write_byte(CAP1188_LED_LINK, 0xFF); + + // Speed up a bit + this->write_byte(CAP1188_STAND_BY_CONFIGURATION, 0x30); +} + +void CAP1188Component::dump_config() { + ESP_LOGCONFIG(TAG, "CAP1188:"); + LOG_I2C_DEVICE(this); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + ESP_LOGCONFIG(TAG, " Product ID: 0x%x", this->cap1188_product_id_); + ESP_LOGCONFIG(TAG, " Manufacture ID: 0x%x", this->cap1188_manufacture_id_); + ESP_LOGCONFIG(TAG, " Revision ID: 0x%x", this->cap1188_revision_); + + switch (this->error_code_) { + case COMMUNICATION_FAILED: + ESP_LOGE(TAG, "Product ID or Manufacture ID of the connected device does not match a known CAP1188."); + break; + case NONE: + default: + break; + } +} + +void CAP1188Component::loop() { + uint8_t touched = 0; + + this->read_register(CAP1188_SENSOR_INPUT_STATUS, &touched, 1); + + if (touched) { + uint8_t data = 0; + this->read_register(CAP1188_MAIN, &data, 1); + data = data & ~CAP1188_MAIN_INT; + + this->write_register(CAP1188_MAIN, &data, 2); + } + + for (auto *channel : this->channels_) { + channel->process(touched); + } +} + +} // namespace cap1188 +} // namespace esphome diff --git a/esphome/components/cap1188/cap1188.h b/esphome/components/cap1188/cap1188.h new file mode 100644 index 0000000000..a1433deb0f --- /dev/null +++ b/esphome/components/cap1188/cap1188.h @@ -0,0 +1,68 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/output/binary_output.h" +#include "esphome/components/binary_sensor/binary_sensor.h" + +namespace esphome { +namespace cap1188 { + +enum { + CAP1188_I2CADDR = 0x29, + CAP1188_SENSOR_INPUT_STATUS = 0x3, + CAP1188_MULTI_TOUCH = 0x2A, + CAP1188_LED_LINK = 0x72, + CAP1188_PRODUCT_ID = 0xFD, + CAP1188_MANUFACTURE_ID = 0xFE, + CAP1188_STAND_BY_CONFIGURATION = 0x41, + CAP1188_REVISION = 0xFF, + CAP1188_MAIN = 0x00, + CAP1188_MAIN_INT = 0x01, + CAP1188_LEDPOL = 0x73, + CAP1188_INTERUPT_REPEAT = 0x28, + CAP1188_SENSITVITY = 0x1f, +}; + +class CAP1188Channel : public binary_sensor::BinarySensor { + public: + void set_channel(uint8_t channel) { channel_ = channel; } + void process(uint8_t data) { this->publish_state(static_cast(data & (1 << this->channel_))); } + + protected: + uint8_t channel_{0}; +}; + +class CAP1188Component : public Component, public i2c::I2CDevice { + public: + void register_channel(CAP1188Channel *channel) { this->channels_.push_back(channel); } + void set_touch_threshold(uint8_t touch_threshold) { this->touch_threshold_ = touch_threshold; }; + void set_allow_multiple_touches(bool allow_multiple_touches) { + this->allow_multiple_touches_ = allow_multiple_touches ? 0x41 : 0x80; + }; + void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void loop() override; + + protected: + std::vector channels_{}; + uint8_t touch_threshold_{0x20}; + uint8_t allow_multiple_touches_{0x80}; + + GPIOPin *reset_pin_{nullptr}; + + uint8_t cap1188_product_id_{0}; + uint8_t cap1188_manufacture_id_{0}; + uint8_t cap1188_revision_{0}; + + enum ErrorCode { + NONE = 0, + COMMUNICATION_FAILED, + } error_code_{NONE}; +}; + +} // namespace cap1188 +} // namespace esphome diff --git a/tests/test2.yaml b/tests/test2.yaml index 6869eeecb1..f90e522b1e 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -504,3 +504,9 @@ interval: display: +cap1188: + id: cap1188_component + address: 0x29 + touch_threshold: 0x20 + allow_multiple_touches: true + reset_pin: 14 From 2ac232e6349e573f892e3183bb44cf87ff627c4b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 19:09:10 +0100 Subject: [PATCH 1672/1841] Add missing hal.h include in esp32_camera_web_server (#2689) --- esphome/components/esp32_camera_web_server/camera_web_server.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.cpp b/esphome/components/esp32_camera_web_server/camera_web_server.cpp index ecaef78b77..c9a684c7e5 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.cpp +++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp @@ -2,6 +2,7 @@ #include "camera_web_server.h" #include "esphome/core/application.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/util.h" From 219b225ac08cdfb32e94a92e700cbf3d5c729fa9 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Wed, 10 Nov 2021 19:12:57 +0100 Subject: [PATCH 1673/1841] [ESP32 ADC] Add option for raw uncalibrated output (#2663) --- esphome/components/adc/adc_sensor.cpp | 17 ++++++++++------- esphome/components/adc/adc_sensor.h | 4 ++-- esphome/components/adc/sensor.py | 16 ++++++++++++++-- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 9b7d0437e1..c8242ce008 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -91,17 +91,21 @@ void ADCSensor::dump_config() { float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } void ADCSensor::update() { float value_v = this->sample(); - ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v); + ESP_LOGD(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); this->publish_state(value_v); } #ifdef USE_ESP8266 float ADCSensor::sample() { #ifdef USE_ADC_SENSOR_VCC - return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance) + int raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance) #else - return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT + int raw = analogRead(this->pin_->get_pin()); // NOLINT #endif + if (output_raw_) { + return raw; + } + return raw / 1024.0f; } #endif @@ -112,6 +116,9 @@ float ADCSensor::sample() { if (raw == -1) { return NAN; } + if (output_raw_) { + return raw; + } uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]); return mv / 1000.0f; } @@ -135,10 +142,6 @@ float ADCSensor::sample() { if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) { return NAN; } - // prevent divide by zero - if (raw0 == 0 && raw2 == 0 && raw6 == 0 && raw11 == 0) { - return 0; - } uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]); uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]); diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index 9984c72819..12272a1577 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -31,6 +31,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage /// `HARDWARE_LATE` setup priority. float get_setup_priority() const override; void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } + void set_output_raw(bool output_raw) { output_raw_ = output_raw; } float sample() override; #ifdef USE_ESP8266 @@ -39,8 +40,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage protected: InternalGPIOPin *pin_; - uint16_t read_raw_(); - uint32_t raw_to_microvolts_(uint16_t raw); + bool output_raw_{false}; #ifdef USE_ESP32 adc_atten_t attenuation_{ADC_ATTEN_DB_0}; diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 9fdddaa0a6..c812e67a68 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -4,6 +4,7 @@ from esphome import pins from esphome.components import sensor, voltage_sampler from esphome.const import ( CONF_ATTENUATION, + CONF_RAW, CONF_ID, CONF_INPUT, CONF_NUMBER, @@ -119,12 +120,18 @@ def validate_adc_pin(value): raise NotImplementedError +def validate_config(config): + if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto": + raise cv.Invalid("Automatic attenuation cannot be used when raw output is set.") + return config + + adc_ns = cg.esphome_ns.namespace("adc") ADCSensor = adc_ns.class_( "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler ) -CONFIG_SCHEMA = ( +CONFIG_SCHEMA = cv.All( sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=2, @@ -135,12 +142,14 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(ADCSensor), cv.Required(CONF_PIN): validate_adc_pin, + cv.Optional(CONF_RAW, default=False): cv.boolean, cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All( cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True) ), } ) - .extend(cv.polling_component_schema("60s")) + .extend(cv.polling_component_schema("60s")), + validate_config, ) @@ -155,6 +164,9 @@ async def to_code(config): pin = await cg.gpio_pin_expression(config[CONF_PIN]) cg.add(var.set_pin(pin)) + if CONF_RAW in config: + cg.add(var.set_output_raw(config[CONF_RAW])) + if CONF_ATTENUATION in config: if config[CONF_ATTENUATION] == "auto": cg.add(var.set_autorange(cg.global_ns.true)) From 15f9677d33c1bb39bbbf04795d4dc1c64089f98c Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 19:15:06 +0100 Subject: [PATCH 1674/1841] Introduce parse_number() helper function (#2659) --- esphome/components/anova/anova_base.cpp | 6 +-- esphome/components/ezo/ezo.cpp | 2 +- .../sensor/homeassistant_sensor.cpp | 2 +- .../hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp | 2 +- esphome/components/mqtt/mqtt_climate.cpp | 6 +-- esphome/components/mqtt/mqtt_cover.cpp | 4 +- esphome/components/mqtt/mqtt_fan.cpp | 2 +- esphome/components/mqtt/mqtt_number.cpp | 2 +- .../sensor/mqtt_subscribe_sensor.cpp | 2 +- esphome/components/pipsolar/pipsolar.cpp | 2 +- esphome/components/sim800l/sim800l.cpp | 4 +- .../teleinfo/sensor/teleinfo_sensor.cpp | 4 +- esphome/components/web_server/web_server.cpp | 2 +- esphome/core/helpers.cpp | 14 ----- esphome/core/helpers.h | 52 ++++++++++++++++++- 15 files changed, 70 insertions(+), 36 deletions(-) diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp index 811a34a27a..d55404089e 100644 --- a/esphome/components/anova/anova_base.cpp +++ b/esphome/components/anova/anova_base.cpp @@ -103,21 +103,21 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) { break; } case READ_TARGET_TEMPERATURE: { - this->target_temp_ = strtof(this->buf_, nullptr); + this->target_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f); if (this->fahrenheit_) this->target_temp_ = ftoc(this->target_temp_); this->has_target_temp_ = true; break; } case SET_TARGET_TEMPERATURE: { - this->target_temp_ = strtof(this->buf_, nullptr); + this->target_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f); if (this->fahrenheit_) this->target_temp_ = ftoc(this->target_temp_); this->has_target_temp_ = true; break; } case READ_CURRENT_TEMPERATURE: { - this->current_temp_ = strtof(this->buf_, nullptr); + this->current_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f); if (this->fahrenheit_) this->current_temp_ = ftoc(this->current_temp_); this->has_current_temp_ = true; diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index 81597f3466..7f7a41fb41 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -74,7 +74,7 @@ void EZOSensor::loop() { if (buf[0] != 1) return; - float val = strtof((char *) &buf[1], nullptr); + float val = parse_number((char *) &buf[1], sizeof(buf) - 1).value_or(0); this->publish_state(val); } diff --git a/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp b/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp index b6f2acdbe4..f5e73c8854 100644 --- a/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp +++ b/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "homeassistant.sensor"; void HomeassistantSensor::setup() { api::global_api_server->subscribe_home_assistant_state( this->entity_id_, this->attribute_, [this](const std::string &state) { - auto val = parse_float(state); + auto val = parse_number(state); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str()); this->publish_state(NAN); diff --git a/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp b/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp index cf6c9eea65..bd1c82c96b 100644 --- a/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp +++ b/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp @@ -44,7 +44,7 @@ void HrxlMaxsonarWrComponent::check_buffer_() { if (this->buffer_.length() == MAX_DATA_LENGTH_BYTES && this->buffer_[0] == 'R' && this->buffer_.back() == static_cast(ASCII_CR)) { - int millimeters = strtol(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2).c_str(), nullptr, 10); + int millimeters = parse_number(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2)).value_or(0); float meters = float(millimeters) / 1000.0; ESP_LOGV(TAG, "Distance from sensor: %d mm, %f m", millimeters, meters); this->publish_state(meters); diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index a63eb9c4ff..ebc708f444 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -137,7 +137,7 @@ void MQTTClimateComponent::setup() { if (traits.get_supports_two_point_target_temperature()) { this->subscribe(this->get_target_temperature_low_command_topic(), [this](const std::string &topic, const std::string &payload) { - auto val = parse_float(payload); + auto val = parse_number(payload); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str()); return; @@ -148,7 +148,7 @@ void MQTTClimateComponent::setup() { }); this->subscribe(this->get_target_temperature_high_command_topic(), [this](const std::string &topic, const std::string &payload) { - auto val = parse_float(payload); + auto val = parse_number(payload); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str()); return; @@ -160,7 +160,7 @@ void MQTTClimateComponent::setup() { } else { this->subscribe(this->get_target_temperature_command_topic(), [this](const std::string &topic, const std::string &payload) { - auto val = parse_float(payload); + auto val = parse_number(payload); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str()); return; diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index 7bf3204222..7e42abcd05 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -24,7 +24,7 @@ void MQTTCoverComponent::setup() { }); if (traits.get_supports_position()) { this->subscribe(this->get_position_command_topic(), [this](const std::string &topic, const std::string &payload) { - auto value = parse_float(payload); + auto value = parse_number(payload); if (!value.has_value()) { ESP_LOGW(TAG, "Invalid position value: '%s'", payload.c_str()); return; @@ -36,7 +36,7 @@ void MQTTCoverComponent::setup() { } if (traits.get_supports_tilt()) { this->subscribe(this->get_tilt_command_topic(), [this](const std::string &topic, const std::string &payload) { - auto value = parse_float(payload); + auto value = parse_number(payload); if (!value.has_value()) { ESP_LOGW(TAG, "Invalid tilt value: '%s'", payload.c_str()); return; diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index a9d77789e1..d58e3abc88 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -71,7 +71,7 @@ void MQTTFanComponent::setup() { if (this->state_->get_traits().supports_speed()) { this->subscribe(this->get_speed_level_command_topic(), [this](const std::string &topic, const std::string &payload) { - optional speed_level_opt = parse_int(payload); + optional speed_level_opt = parse_number(payload); if (speed_level_opt.has_value()) { const int speed_level = speed_level_opt.value(); if (speed_level >= 0 && speed_level <= this->state_->get_traits().supported_speed_count()) { diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 9b2292cd76..337013055a 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -17,7 +17,7 @@ MQTTNumberComponent::MQTTNumberComponent(Number *number) : MQTTComponent(), numb void MQTTNumberComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) { - auto val = parse_float(state); + auto val = parse_number(state); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str()); return; diff --git a/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp b/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp index e1accf3c70..273de10376 100644 --- a/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp +++ b/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp @@ -13,7 +13,7 @@ void MQTTSubscribeSensor::setup() { mqtt::global_mqtt_client->subscribe( this->topic_, [this](const std::string &topic, const std::string &payload) { - auto val = parse_float(payload); + auto val = parse_number(payload); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str()); this->publish_state(NAN); diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index 7dbbd798ad..9f8b57003a 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -656,7 +656,7 @@ void Pipsolar::loop() { case 32: fc = tmp[i]; fc += tmp[i + 1]; - this->value_fault_code_ = strtol(fc.c_str(), nullptr, 10); + this->value_fault_code_ = parse_number(fc).value_or(0); break; case 34: this->value_warnung_low_pv_energy_ = enabled; diff --git a/esphome/components/sim800l/sim800l.cpp b/esphome/components/sim800l/sim800l.cpp index e48b1ac9bd..eb6d62ca33 100644 --- a/esphome/components/sim800l/sim800l.cpp +++ b/esphome/components/sim800l/sim800l.cpp @@ -128,7 +128,7 @@ void Sim800LComponent::parse_cmd_(std::string message) { if (message.compare(0, 5, "+CSQ:") == 0) { size_t comma = message.find(',', 6); if (comma != 6) { - this->rssi_ = strtol(message.substr(6, comma - 6).c_str(), nullptr, 10); + this->rssi_ = parse_number(message.substr(6, comma - 6)).value_or(0); ESP_LOGD(TAG, "RSSI: %d", this->rssi_); } } @@ -146,7 +146,7 @@ void Sim800LComponent::parse_cmd_(std::string message) { while (end != start) { item++; if (item == 1) { // Slot Index - this->parse_index_ = strtol(message.substr(start, end - start).c_str(), nullptr, 10); + this->parse_index_ = parse_number(message.substr(start, end - start)).value_or(0); } // item 2 = STATUS, usually "REC UNERAD" if (item == 3) { // recipient diff --git a/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp b/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp index 4e4cd9f9e6..ad9c6dae00 100644 --- a/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp +++ b/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp @@ -6,8 +6,8 @@ namespace teleinfo { static const char *const TAG = "teleinfo_sensor"; TeleInfoSensor::TeleInfoSensor(const char *tag) { this->tag = std::string(tag); } void TeleInfoSensor::publish_val(const std::string &val) { - auto newval = parse_float(val); - publish_state(*newval); + auto newval = parse_number(val).value_or(0.0f); + publish_state(newval); } void TeleInfoSensor::dump_config() { LOG_SENSOR(" ", "Teleinfo Sensor", this); } } // namespace teleinfo diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 44ace38990..17b17fcc3c 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -458,7 +458,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc } if (request->hasParam("speed_level")) { String speed_level = request->getParam("speed_level")->value(); - auto val = parse_int(speed_level.c_str()); + auto val = parse_number(speed_level.c_str()); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", speed_level.c_str()); return; diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 8cf972e4db..3047facf45 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -279,20 +279,6 @@ std::string to_string(long double val) { sprintf(buf, "%Lf", val); return buf; } -optional parse_float(const std::string &str) { - char *end; - float value = ::strtof(str.c_str(), &end); - if (end == nullptr || end != str.end().base()) - return {}; - return value; -} -optional parse_int(const std::string &str) { - char *end; - int value = ::strtol(str.c_str(), &end, 10); - if (end == nullptr || end != str.end().base()) - return {}; - return value; -} optional parse_hex(const char chr) { int out = chr; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 040780e072..fde631514b 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -51,8 +53,6 @@ std::string to_string(unsigned long long val); // NOLINT std::string to_string(float val); std::string to_string(double val); std::string to_string(long double val); -optional parse_float(const std::string &str); -optional parse_int(const std::string &str); optional parse_hex(const std::string &str, size_t start, size_t length); optional parse_hex(char chr); /// Sanitize the hostname by removing characters that are not in the allowlist and truncating it to 63 chars. @@ -304,4 +304,52 @@ template T *new_buffer(size_t length) { return buffer; } +// --------------------------------------------------------------------------------------------------------------------- + +/// @name Parsing & formatting +///@{ + +/// Parse a unsigned decimal number. +template::value && std::is_unsigned::value), int> = 0> +optional parse_number(const char *str, size_t len) { + char *end = nullptr; + unsigned long value = ::strtoul(str, &end, 10); // NOLINT(google-runtime-int) + if (end == nullptr || end != str + len || value > std::numeric_limits::max()) + return {}; + return value; +} +template::value && std::is_unsigned::value), int> = 0> +optional parse_number(const std::string &str) { + return parse_number(str.c_str(), str.length()); +} +/// Parse a signed decimal number. +template::value && std::is_signed::value), int> = 0> +optional parse_number(const char *str, size_t len) { + char *end = nullptr; + signed long value = ::strtol(str, &end, 10); // NOLINT(google-runtime-int) + if (end == nullptr || end != str + len || value < std::numeric_limits::min() || + value > std::numeric_limits::max()) + return {}; + return value; +} +template::value && std::is_signed::value), int> = 0> +optional parse_number(const std::string &str) { + return parse_number(str.c_str(), str.length()); +} +/// Parse a decimal floating-point number. +template::value), int> = 0> +optional parse_number(const char *str, size_t len) { + char *end = nullptr; + float value = ::strtof(str, &end); + if (end == nullptr || end != str + len || value == HUGE_VALF) + return {}; + return value; +} +template::value), int> = 0> +optional parse_number(const std::string &str) { + return parse_number(str.c_str(), str.length()); +} + +///@} + } // namespace esphome From d8e33c5a69aa0053ae01352341e57265df7df802 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 19:30:07 +0100 Subject: [PATCH 1675/1841] Add repeat action for automations (#2538) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/automation.py | 21 +++++++++++++++++++++ esphome/core/base_automation.h | 33 +++++++++++++++++++++++++++++++++ tests/test5.yaml | 8 ++++++++ 3 files changed, 62 insertions(+) diff --git a/esphome/automation.py b/esphome/automation.py index 0768bf8869..fab998527f 100644 --- a/esphome/automation.py +++ b/esphome/automation.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome.const import ( CONF_AUTOMATION_ID, CONF_CONDITION, + CONF_COUNT, CONF_ELSE, CONF_ID, CONF_THEN, @@ -66,6 +67,7 @@ DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component) LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) IfAction = cg.esphome_ns.class_("IfAction", Action) WhileAction = cg.esphome_ns.class_("WhileAction", Action) +RepeatAction = cg.esphome_ns.class_("RepeatAction", Action) WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component) UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action) Automation = cg.esphome_ns.class_("Automation") @@ -241,6 +243,25 @@ async def while_action_to_code(config, action_id, template_arg, args): return var +@register_action( + "repeat", + RepeatAction, + cv.Schema( + { + cv.Required(CONF_COUNT): cv.templatable(cv.positive_not_null_int), + cv.Required(CONF_THEN): validate_action_list, + } + ), +) +async def repeat_action_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32) + cg.add(var.set_count(count_template)) + actions = await build_action_list(config[CONF_THEN], template_arg, args) + cg.add(var.add_then(actions)) + return var + + def validate_wait_until(value): schema = cv.Schema( { diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index d97d369d33..e87a4a2765 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -224,6 +224,39 @@ template class WhileAction : public Action { std::tuple var_{}; }; +template class RepeatAction : public Action { + public: + TEMPLATABLE_VALUE(uint32_t, count) + + void add_then(const std::vector *> &actions) { + this->then_.add_actions(actions); + this->then_.add_action(new LambdaAction([this](Ts... x) { + this->iteration_++; + if (this->iteration_ == this->count_.value(x...)) + this->play_next_tuple_(this->var_); + else + this->then_.play_tuple(this->var_); + })); + } + + void play_complex(Ts... x) override { + this->num_running_++; + this->var_ = std::make_tuple(x...); + this->iteration_ = 0; + this->then_.play_tuple(this->var_); + } + + void play(Ts... x) override { /* ignore - see play_complex */ + } + + void stop() override { this->then_.stop(); } + + protected: + uint32_t iteration_; + ActionList then_; + std::tuple var_; +}; + template class WaitUntilAction : public Action, public Component { public: WaitUntilAction(Condition *condition) : condition_(condition) {} diff --git a/tests/test5.yaml b/tests/test5.yaml index 72df3ed212..f1fb786fe5 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -173,3 +173,11 @@ sensor: uart_id: uart2 co2: name: CO2 Sensor + +script: + - id: automation_test + then: + - repeat: + count: 5 + then: + - logger.log: "looping!" From 8aa72f4c1efeb46aaa3728bfe553713d644db8d0 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 10 Nov 2021 19:35:31 +0100 Subject: [PATCH 1676/1841] Neopixelbus redo method definitions (#2616) --- esphome/components/neopixelbus/_methods.py | 418 +++++++++++++++++++++ esphome/components/neopixelbus/const.py | 42 +++ esphome/components/neopixelbus/light.py | 232 ++++++------ esphome/config_validation.py | 2 +- platformio.ini | 2 +- script/clang-tidy | 1 + 6 files changed, 585 insertions(+), 112 deletions(-) create mode 100644 esphome/components/neopixelbus/_methods.py create mode 100644 esphome/components/neopixelbus/const.py diff --git a/esphome/components/neopixelbus/_methods.py b/esphome/components/neopixelbus/_methods.py new file mode 100644 index 0000000000..b03544f246 --- /dev/null +++ b/esphome/components/neopixelbus/_methods.py @@ -0,0 +1,418 @@ +from dataclasses import dataclass +from typing import Any, List +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import ( + CONF_CHANNEL, + CONF_CLOCK_PIN, + CONF_DATA_PIN, + CONF_METHOD, + CONF_PIN, + CONF_SPEED, +) +from esphome.components.esp32 import get_esp32_variant +from esphome.components.esp32.const import ( + VARIANT_ESP32, + VARIANT_ESP32S2, + VARIANT_ESP32C3, +) +from esphome.core import CORE +from .const import ( + CONF_ASYNC, + CONF_BUS, + CHIP_400KBPS, + CHIP_800KBPS, + CHIP_APA106, + CHIP_DOTSTAR, + CHIP_LC8812, + CHIP_LPD6803, + CHIP_LPD8806, + CHIP_P9813, + CHIP_SK6812, + CHIP_TM1814, + CHIP_TM1829, + CHIP_TM1914, + CHIP_WS2801, + CHIP_WS2811, + CHIP_WS2812, + CHIP_WS2812X, + CHIP_WS2813, + ONE_WIRE_CHIPS, + TWO_WIRE_CHIPS, +) + +METHOD_BIT_BANG = "bit_bang" +METHOD_ESP8266_UART = "esp8266_uart" +METHOD_ESP8266_DMA = "esp8266_dma" +METHOD_ESP32_RMT = "esp32_rmt" +METHOD_ESP32_I2S = "esp32_i2s" +METHOD_SPI = "spi" + +CHANNEL_DYNAMIC = "dynamic" +BUS_DYNAMIC = "dynamic" +SPI_BUS_VSPI = "vspi" +SPI_BUS_HSPI = "hspi" +SPI_SPEEDS = [40e6, 20e6, 10e6, 5e6, 2e6, 1e6, 500e3] + + +def _esp32_rmt_default_channel(): + return { + VARIANT_ESP32S2: 1, + VARIANT_ESP32C3: 1, + }.get(get_esp32_variant(), 6) + + +def _validate_esp32_rmt_channel(value): + if isinstance(value, str) and value.lower() == CHANNEL_DYNAMIC: + value = CHANNEL_DYNAMIC + else: + value = cv.int_(value) + variant_channels = { + VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7, CHANNEL_DYNAMIC], + VARIANT_ESP32S2: [0, 1, 2, 3, CHANNEL_DYNAMIC], + VARIANT_ESP32C3: [0, 1, CHANNEL_DYNAMIC], + } + variant = get_esp32_variant() + if variant not in variant_channels: + raise cv.Invalid(f"{variant} does not support the rmt method") + if value not in variant_channels[variant]: + raise cv.Invalid(f"{variant} does not support rmt channel {value}") + return value + + +def _esp32_i2s_default_bus(): + return { + VARIANT_ESP32: 1, + VARIANT_ESP32S2: 0, + }.get(get_esp32_variant(), 0) + + +def _validate_esp32_i2s_bus(value): + if isinstance(value, str) and value.lower() == CHANNEL_DYNAMIC: + value = CHANNEL_DYNAMIC + else: + value = cv.int_(value) + variant_buses = { + VARIANT_ESP32: [0, 1, BUS_DYNAMIC], + VARIANT_ESP32S2: [0, BUS_DYNAMIC], + } + variant = get_esp32_variant() + if variant not in variant_buses: + raise cv.Invalid(f"{variant} does not support the i2s method") + if value not in variant_buses[variant]: + raise cv.Invalid(f"{variant} does not support i2s bus {value}") + return value + + +neo_ns = cg.global_ns + + +def _bit_bang_to_code(config, chip: str, inverted: bool): + # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEspBitBangMethod.h + # Some chips are only aliases + chip = { + CHIP_WS2813: CHIP_WS2812X, + CHIP_LC8812: CHIP_SK6812, + CHIP_TM1914: CHIP_TM1814, + CHIP_WS2812: CHIP_800KBPS, + }.get(chip, chip) + + lookup = { + CHIP_WS2811: (neo_ns.NeoEspBitBangSpeedWs2811, False), + CHIP_WS2812X: (neo_ns.NeoEspBitBangSpeedWs2812x, False), + CHIP_SK6812: (neo_ns.NeoEspBitBangSpeedSk6812, False), + CHIP_TM1814: (neo_ns.NeoEspBitBangSpeedTm1814, True), + CHIP_TM1829: (neo_ns.NeoEspBitBangSpeedTm1829, True), + CHIP_800KBPS: (neo_ns.NeoEspBitBangSpeed800Kbps, False), + CHIP_400KBPS: (neo_ns.NeoEspBitBangSpeed400Kbps, False), + CHIP_APA106: (neo_ns.NeoEspBitBangSpeedApa106, False), + } + # For tm variants opposite of inverted is needed + speed, pinset_inverted = lookup[chip] + pinset = { + False: neo_ns.NeoEspPinset, + True: neo_ns.NeoEspPinsetInverted, + }[inverted != pinset_inverted] + return neo_ns.NeoEspBitBangMethodBase.template(speed, pinset) + + +def _bit_bang_extra_validate(config): + pin = config[CONF_PIN] + if CORE.is_esp8266 and not (0 <= pin <= 15): + # Due to use of w1ts + raise cv.Invalid("Bit bang only supports pins GPIO0-GPIO15 on ESP8266") + if CORE.is_esp32 and not (0 <= pin <= 31): + raise cv.Invalid("Bit bang only supports pins GPIO0-GPIO31 on ESP32") + + +def _esp8266_uart_to_code(config, chip: str, inverted: bool): + # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp8266UartMethod.h + uart_context, uart_base = { + False: (neo_ns.NeoEsp8266UartContext, neo_ns.NeoEsp8266Uart), + True: (neo_ns.NeoEsp8266UartInterruptContext, neo_ns.NeoEsp8266AsyncUart), + }[config[CONF_ASYNC]] + uart_feature = { + 0: neo_ns.UartFeature0, + 1: neo_ns.UartFeature1, + }[config[CONF_BUS]] + # Some chips are only aliases + chip = { + CHIP_WS2811: CHIP_WS2812X, + CHIP_WS2813: CHIP_WS2812X, + CHIP_LC8812: CHIP_SK6812, + CHIP_TM1914: CHIP_TM1814, + CHIP_WS2812: CHIP_800KBPS, + }.get(chip, chip) + + lookup = { + CHIP_WS2812X: (neo_ns.NeoEsp8266UartSpeedWs2812x, False), + CHIP_SK6812: (neo_ns.NeoEsp8266UartSpeedSk6812, False), + CHIP_TM1814: (neo_ns.NeoEsp8266UartSpeedTm1814, True), + CHIP_TM1829: (neo_ns.NeoEsp8266UartSpeedTm1829, True), + CHIP_800KBPS: (neo_ns.NeoEsp8266UartSpeed800Kbps, False), + CHIP_400KBPS: (neo_ns.NeoEsp8266UartSpeed400Kbps, False), + CHIP_APA106: (neo_ns.NeoEsp8266UartSpeedApa106, False), + } + speed, uart_inverted = lookup[chip] + # For tm variants opposite of inverted is needed + inv = { + False: neo_ns.NeoEsp8266UartNotInverted, + True: neo_ns.NeoEsp8266UartInverted, + }[inverted != uart_inverted] + return neo_ns.NeoEsp8266UartMethodBase.template( + speed, uart_base.template(uart_feature, uart_context), inv + ) + + +def _esp8266_uart_extra_validate(config): + pin = config[CONF_PIN] + bus = config[CONF_METHOD][CONF_BUS] + right_pin = { + 0: 1, # U0TXD + 1: 2, # U1TXD + }[bus] + if pin != right_pin: + raise cv.Invalid(f"ESP8266 uart bus {bus} only supports pin GPIO{right_pin}") + + +def _esp8266_dma_to_code(config, chip: str, inverted: bool): + # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp8266DmaMethod.h + # Some chips are only aliases + chip = { + CHIP_WS2811: CHIP_WS2812X, + CHIP_WS2813: CHIP_WS2812X, + CHIP_LC8812: CHIP_SK6812, + CHIP_TM1914: CHIP_TM1814, + CHIP_WS2812: CHIP_800KBPS, + }.get(chip, chip) + + lookup = { + (CHIP_WS2812X, False): neo_ns.NeoEsp8266DmaSpeedWs2812x, + (CHIP_SK6812, False): neo_ns.NeoEsp8266DmaSpeedSk6812, + (CHIP_TM1814, True): neo_ns.NeoEsp8266DmaInvertedSpeedTm1814, + (CHIP_TM1829, True): neo_ns.NeoEsp8266DmaInvertedSpeedTm1829, + (CHIP_800KBPS, False): neo_ns.NeoEsp8266DmaSpeed800Kbps, + (CHIP_400KBPS, False): neo_ns.NeoEsp8266DmaSpeed400Kbps, + (CHIP_APA106, False): neo_ns.NeoEsp8266DmaSpeedApa106, + (CHIP_WS2812X, True): neo_ns.NeoEsp8266DmaInvertedSpeedWs2812x, + (CHIP_SK6812, True): neo_ns.NeoEsp8266DmaInvertedSpeedSk6812, + (CHIP_TM1814, False): neo_ns.NeoEsp8266DmaSpeedTm1814, + (CHIP_TM1829, False): neo_ns.NeoEsp8266DmaSpeedTm1829, + (CHIP_800KBPS, True): neo_ns.NeoEsp8266DmaInvertedSpeed800Kbps, + (CHIP_400KBPS, True): neo_ns.NeoEsp8266DmaInvertedSpeed400Kbps, + (CHIP_APA106, True): neo_ns.NeoEsp8266DmaInvertedSpeedApa106, + } + speed = lookup[(chip, inverted)] + return neo_ns.NeoEsp8266DmaMethodBase.template(speed) + + +def _esp8266_dma_extra_validate(config): + if config[CONF_PIN] != 3: + raise cv.Invalid("ESP8266 dma method only supports pin GPIO3") + + +def _esp32_rmt_to_code(config, chip: str, inverted: bool): + # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp32RmtMethod.h + channel = { + 0: neo_ns.NeoEsp32RmtChannel0, + 1: neo_ns.NeoEsp32RmtChannel1, + 2: neo_ns.NeoEsp32RmtChannel2, + 3: neo_ns.NeoEsp32RmtChannel3, + 4: neo_ns.NeoEsp32RmtChannel4, + 5: neo_ns.NeoEsp32RmtChannel5, + 6: neo_ns.NeoEsp32RmtChannel6, + 7: neo_ns.NeoEsp32RmtChannel7, + CHANNEL_DYNAMIC: neo_ns.NeoEsp32RmtChannelN, + }[config[CONF_CHANNEL]] + # Some chips are only aliases + chip = { + CHIP_WS2813: CHIP_WS2812X, + CHIP_LC8812: CHIP_SK6812, + CHIP_WS2812: CHIP_800KBPS, + }.get(chip, chip) + + lookup = { + (CHIP_WS2811, False): neo_ns.NeoEsp32RmtSpeedWs2811, + (CHIP_WS2812X, False): neo_ns.NeoEsp32RmtSpeedWs2812x, + (CHIP_SK6812, False): neo_ns.NeoEsp32RmtSpeedSk6812, + (CHIP_TM1814, False): neo_ns.NeoEsp32RmtSpeedTm1814, + (CHIP_TM1829, False): neo_ns.NeoEsp32RmtSpeedTm1829, + (CHIP_TM1914, False): neo_ns.NeoEsp32RmtSpeedTm1914, + (CHIP_800KBPS, False): neo_ns.NeoEsp32RmtSpeed800Kbps, + (CHIP_400KBPS, False): neo_ns.NeoEsp32RmtSpeed400Kbps, + (CHIP_APA106, False): neo_ns.NeoEsp32RmtSpeedApa106, + (CHIP_WS2811, True): neo_ns.NeoEsp32RmtInvertedSpeedWs2811, + (CHIP_WS2812X, True): neo_ns.NeoEsp32RmtInvertedSpeedWs2812x, + (CHIP_SK6812, True): neo_ns.NeoEsp32RmtInvertedSpeedSk6812, + (CHIP_TM1814, True): neo_ns.NeoEsp32RmtInvertedSpeedTm1814, + (CHIP_TM1829, True): neo_ns.NeoEsp32RmtInvertedSpeedTm1829, + (CHIP_TM1914, True): neo_ns.NeoEsp32RmtInvertedSpeedTm1914, + (CHIP_800KBPS, True): neo_ns.NeoEsp32RmtInvertedSpeed800Kbps, + (CHIP_400KBPS, True): neo_ns.NeoEsp32RmtInvertedSpeed400Kbps, + (CHIP_APA106, True): neo_ns.NeoEsp32RmtInvertedSpeedApa106, + } + speed = lookup[(chip, inverted)] + return neo_ns.NeoEsp32RmtMethodBase.template(speed, channel) + + +def _esp32_i2s_to_code(config, chip: str, inverted: bool): + # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp32I2sMethod.h + bus = { + 0: neo_ns.NeoEsp32I2sBusZero, + 1: neo_ns.NeoEsp32I2sBusOne, + BUS_DYNAMIC: neo_ns.NeoEsp32I2sBusN, + }[config[CONF_BUS]] + # Some chips are only aliases + chip = { + CHIP_WS2811: CHIP_WS2812X, + CHIP_WS2813: CHIP_WS2812X, + CHIP_LC8812: CHIP_SK6812, + CHIP_WS2812: CHIP_800KBPS, + }.get(chip, chip) + + lookup = { + CHIP_WS2812X: (neo_ns.NeoEsp32I2sSpeedWs2812x, False), + CHIP_SK6812: (neo_ns.NeoEsp32I2sSpeedSk6812, False), + CHIP_TM1814: (neo_ns.NeoEsp32I2sSpeedTm1814, True), + CHIP_TM1914: (neo_ns.NeoEsp32I2sSpeedTm1914, True), + CHIP_TM1829: (neo_ns.NeoEsp32I2sSpeedTm1829, True), + CHIP_800KBPS: (neo_ns.NeoEsp32I2sSpeed800Kbps, False), + CHIP_400KBPS: (neo_ns.NeoEsp32I2sSpeed400Kbps, False), + CHIP_APA106: (neo_ns.NeoEsp32I2sSpeedApa106, False), + } + speed, inv_inverted = lookup[chip] + # For tm variants opposite of inverted is needed + inv = { + False: neo_ns.NeoEsp32I2sNotInverted, + True: neo_ns.NeoEsp32I2sInverted, + }[inverted != inv_inverted] + return neo_ns.NeoEsp32I2sMethodBase.template(speed, bus, inv) + + +def _spi_to_code(config, chip: str, inverted: bool): + # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/TwoWireSpiImple.h + spi_imple = { + None: neo_ns.TwoWireSpiImple, + SPI_BUS_VSPI: neo_ns.TwoWireSpiImple, + SPI_BUS_HSPI: neo_ns.TwoWireHspiImple, + }[config.get(CONF_BUS)] + spi_speed = { + 40e6: neo_ns.SpiSpeed40Mhz, + 20e6: neo_ns.SpiSpeed20Mhz, + 10e6: neo_ns.SpiSpeed10Mhz, + 5e6: neo_ns.SpiSpeed5Mhz, + 2e6: neo_ns.SpiSpeed2Mhz, + 1e6: neo_ns.SpiSpeed1Mhz, + 500e3: neo_ns.SpiSpeed500Khz, + }[config[CONF_SPEED]] + chip_method_base = { + CHIP_DOTSTAR: neo_ns.DotStarMethodBase, + CHIP_LPD6803: neo_ns.Lpd6803MethodBase, + CHIP_LPD8806: neo_ns.Lpd8806MethodBase, + CHIP_WS2801: neo_ns.Ws2801MethodBase, + CHIP_P9813: neo_ns.P9813MethodBase, + }[chip] + return chip_method_base.template(spi_imple.template(spi_speed)) + + +def _spi_extra_validate(config): + if CORE.is_esp32: + return + + if config[CONF_DATA_PIN] != 13 and config[CONF_CLOCK_PIN] != 14: + raise cv.Invalid( + "SPI only supports pins GPIO13 for data and GPIO14 for clock on ESP8266" + ) + + +@dataclass +class MethodDescriptor: + method_schema: Any + to_code: Any + supported_chips: List[str] + extra_validate: Any = None + + +METHODS = { + METHOD_BIT_BANG: MethodDescriptor( + method_schema={}, + to_code=_bit_bang_to_code, + extra_validate=_bit_bang_extra_validate, + supported_chips=ONE_WIRE_CHIPS, + ), + METHOD_ESP8266_UART: MethodDescriptor( + method_schema=cv.All( + cv.only_on_esp8266, + { + cv.Optional(CONF_ASYNC, default=False): cv.boolean, + cv.Optional(CONF_BUS, default=1): cv.int_range(min=0, max=1), + }, + ), + extra_validate=_esp8266_uart_extra_validate, + to_code=_esp8266_uart_to_code, + supported_chips=ONE_WIRE_CHIPS, + ), + METHOD_ESP8266_DMA: MethodDescriptor( + method_schema=cv.All(cv.only_on_esp8266, {}), + extra_validate=_esp8266_dma_extra_validate, + to_code=_esp8266_dma_to_code, + supported_chips=ONE_WIRE_CHIPS, + ), + METHOD_ESP32_RMT: MethodDescriptor( + method_schema=cv.All( + cv.only_on_esp32, + { + cv.Optional( + CONF_CHANNEL, default=_esp32_rmt_default_channel + ): _validate_esp32_rmt_channel, + }, + ), + to_code=_esp32_rmt_to_code, + supported_chips=ONE_WIRE_CHIPS, + ), + METHOD_ESP32_I2S: MethodDescriptor( + method_schema=cv.All( + cv.only_on_esp32, + { + cv.Optional( + CONF_BUS, default=_esp32_i2s_default_bus + ): _validate_esp32_i2s_bus, + }, + ), + to_code=_esp32_i2s_to_code, + supported_chips=ONE_WIRE_CHIPS, + ), + METHOD_SPI: MethodDescriptor( + method_schema={ + cv.Optional(CONF_BUS): cv.All( + cv.only_on_esp32, cv.one_of(SPI_BUS_VSPI, SPI_BUS_HSPI, lower=True) + ), + cv.Optional(CONF_SPEED, default="10MHz"): cv.All( + cv.frequency, cv.one_of(*SPI_SPEEDS) + ), + }, + to_code=_spi_to_code, + extra_validate=_spi_extra_validate, + supported_chips=TWO_WIRE_CHIPS, + ), +} diff --git a/esphome/components/neopixelbus/const.py b/esphome/components/neopixelbus/const.py new file mode 100644 index 0000000000..ec1bd74c29 --- /dev/null +++ b/esphome/components/neopixelbus/const.py @@ -0,0 +1,42 @@ +CHIP_DOTSTAR = "dotstar" +CHIP_WS2801 = "ws2801" +CHIP_WS2811 = "ws2811" +CHIP_WS2812 = "ws2812" +CHIP_WS2812X = "ws2812x" +CHIP_WS2813 = "ws2813" +CHIP_SK6812 = "sk6812" +CHIP_TM1814 = "tm1814" +CHIP_TM1829 = "tm1829" +CHIP_TM1914 = "tm1914" +CHIP_800KBPS = "800kbps" +CHIP_400KBPS = "400kbps" +CHIP_APA106 = "apa106" +CHIP_LC8812 = "lc8812" +CHIP_LPD8806 = "lpd8806" +CHIP_LPD6803 = "lpd6803" +CHIP_P9813 = "p9813" + +ONE_WIRE_CHIPS = [ + CHIP_WS2811, + CHIP_WS2812, + CHIP_WS2812X, + CHIP_WS2813, + CHIP_SK6812, + CHIP_TM1814, + CHIP_TM1829, + CHIP_TM1914, + CHIP_800KBPS, + CHIP_400KBPS, + CHIP_APA106, + CHIP_LC8812, +] +TWO_WIRE_CHIPS = [ + CHIP_DOTSTAR, + CHIP_WS2801, + CHIP_LPD6803, + CHIP_LPD8806, + CHIP_P9813, +] +CHIP_TYPES = [*ONE_WIRE_CHIPS, *TWO_WIRE_CHIPS] +CONF_ASYNC = "async" +CONF_BUS = "bus" diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index 0117f1b063..6bb1bc8f99 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import light from esphome.const import ( + CONF_CHANNEL, CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_METHOD, @@ -13,7 +14,26 @@ from esphome.const import ( CONF_OUTPUT_ID, CONF_INVERT, ) +from esphome.components.esp32 import get_esp32_variant +from esphome.components.esp32.const import ( + VARIANT_ESP32C3, +) from esphome.core import CORE +from ._methods import ( + METHODS, + METHOD_SPI, + METHOD_ESP8266_UART, + METHOD_BIT_BANG, + METHOD_ESP32_I2S, + METHOD_ESP32_RMT, + METHOD_ESP8266_DMA, +) +from .const import ( + CHIP_TYPES, + CONF_ASYNC, + CONF_BUS, + ONE_WIRE_CHIPS, +) neopixelbus_ns = cg.esphome_ns.namespace("neopixelbus") NeoPixelBusLightOutputBase = neopixelbus_ns.class_( @@ -46,127 +66,115 @@ def validate_type(value): return value -def validate_variant(value): - value = cv.string(value).upper() - if value == "WS2813": - value = "WS2812X" - if value == "WS2812": - value = "800KBPS" - if value == "LC8812": - value = "SK6812" - return cv.one_of(*VARIANTS)(value) +def _choose_default_method(config): + if CONF_METHOD in config: + return config + config = config.copy() + if CONF_PIN not in config: + config[CONF_METHOD] = _validate_method(METHOD_SPI) + return config - -def validate_method(value): - if value is None: - if CORE.is_esp32: - return "ESP32_I2S_1" - if CORE.is_esp8266: - return "ESP8266_DMA" - raise NotImplementedError - - if CORE.is_esp32: - return cv.one_of(*ESP32_METHODS, upper=True, space="_")(value) + pin = config[CONF_PIN] if CORE.is_esp8266: - return cv.one_of(*ESP8266_METHODS, upper=True, space="_")(value) - raise NotImplementedError - - -def validate_method_pin(value): - method = value[CONF_METHOD] - method_pins = { - "ESP8266_DMA": [3], - "ESP8266_UART0": [1], - "ESP8266_ASYNC_UART0": [1], - "ESP8266_UART1": [2], - "ESP8266_ASYNC_UART1": [2], - "ESP32_I2S_0": list(range(0, 32)), - "ESP32_I2S_1": list(range(0, 32)), - } - if CORE.is_esp8266: - method_pins["BIT_BANG"] = list(range(0, 16)) - elif CORE.is_esp32: - method_pins["BIT_BANG"] = list(range(0, 32)) - pins_ = method_pins.get(method) - if pins_ is None: - # all pins allowed for this method - return value - - for opt in (CONF_PIN, CONF_CLOCK_PIN, CONF_DATA_PIN): - if opt in value and value[opt] not in pins_: - raise cv.Invalid( - f"Method {method} only supports pin(s) {', '.join(f'GPIO{x}' for x in pins_)}", - path=[CONF_METHOD], + if pin == 3: + config[CONF_METHOD] = _validate_method(METHOD_ESP8266_DMA) + elif pin == 1: + config[CONF_METHOD] = _validate_method( + { + CONF_TYPE: METHOD_ESP8266_UART, + CONF_BUS: 0, + } + ) + elif pin == 2: + config[CONF_METHOD] = _validate_method( + { + CONF_TYPE: METHOD_ESP8266_UART, + CONF_BUS: 1, + } ) - return value - - -VARIANTS = { - "WS2812X": "Ws2812x", - "SK6812": "Sk6812", - "800KBPS": "800Kbps", - "400KBPS": "400Kbps", -} - -ESP8266_METHODS = { - "ESP8266_DMA": "NeoEsp8266Dma{}Method", - "ESP8266_UART0": "NeoEsp8266Uart0{}Method", - "ESP8266_UART1": "NeoEsp8266Uart1{}Method", - "ESP8266_ASYNC_UART0": "NeoEsp8266AsyncUart0{}Method", - "ESP8266_ASYNC_UART1": "NeoEsp8266AsyncUart1{}Method", - "BIT_BANG": "NeoEsp8266BitBang{}Method", -} -ESP32_METHODS = { - "ESP32_I2S_0": "NeoEsp32I2s0{}Method", - "ESP32_I2S_1": "NeoEsp32I2s1{}Method", - "ESP32_RMT_0": "NeoEsp32Rmt0{}Method", - "ESP32_RMT_1": "NeoEsp32Rmt1{}Method", - "ESP32_RMT_2": "NeoEsp32Rmt2{}Method", - "ESP32_RMT_3": "NeoEsp32Rmt3{}Method", - "ESP32_RMT_4": "NeoEsp32Rmt4{}Method", - "ESP32_RMT_5": "NeoEsp32Rmt5{}Method", - "ESP32_RMT_6": "NeoEsp32Rmt6{}Method", - "ESP32_RMT_7": "NeoEsp32Rmt7{}Method", - "BIT_BANG": "NeoEsp32BitBang{}Method", -} - - -def format_method(config): - variant = VARIANTS[config[CONF_VARIANT]] - method = config[CONF_METHOD] - - if config[CONF_INVERT]: - if method == "ESP8266_DMA": - variant = f"Inverted{variant}" else: - variant += "Inverted" + config[CONF_METHOD] = _validate_method(METHOD_BIT_BANG) - if CORE.is_esp8266: - return ESP8266_METHODS[method].format(variant) if CORE.is_esp32: - return ESP32_METHODS[method].format(variant) - raise NotImplementedError + if get_esp32_variant() == VARIANT_ESP32C3: + config[CONF_METHOD] = _validate_method(METHOD_ESP32_RMT) + else: + config[CONF_METHOD] = _validate_method(METHOD_ESP32_I2S) + + return config def _validate(config): - if CONF_PIN in config: + variant = config[CONF_VARIANT] + if variant in ONE_WIRE_CHIPS: + if CONF_PIN not in config: + raise cv.Invalid( + f"Chip {variant} is a 1-wire chip and needs the [pin] option." + ) if CONF_CLOCK_PIN in config or CONF_DATA_PIN in config: - raise cv.Invalid("Cannot specify both 'pin' and 'clock_pin'+'data_pin'") - return config - if CONF_CLOCK_PIN in config: - if CONF_DATA_PIN not in config: - raise cv.Invalid("If you give clock_pin, you must also specify data_pin") - return config - raise cv.Invalid("Must specify at least one of 'pin' or 'clock_pin'+'data_pin'") + raise cv.Invalid( + f"Chip {variant} is a 1-wire chip, you need to set [pin] instead of ." + ) + else: + if CONF_PIN in config: + raise cv.Invalid( + f"Chip {variant} is a 2-wire chip and needs the [data_pin]+[clock_pin] option instead of [pin]." + ) + if CONF_CLOCK_PIN not in config or CONF_DATA_PIN not in config: + raise cv.Invalid( + f"Chip {variant} is a 2-wire chip, you need to set [data_pin]+[clock_pin]." + ) + + method_type = config[CONF_METHOD][CONF_TYPE] + method_desc = METHODS[method_type] + if variant not in method_desc.supported_chips: + raise cv.Invalid(f"Method {method_type} does not support {variant}") + if method_desc.extra_validate is not None: + method_desc.extra_validate(config) + + return config + + +def _validate_method(value): + if value is None: + # default method is determined afterwards because it depends on the chip type chosen + return None + + compat_methods = {} + for bus in [0, 1]: + for is_async in [False, True]: + compat_methods[f"ESP8266{'_ASYNC' if is_async else ''}_UART{bus}"] = { + CONF_TYPE: METHOD_ESP8266_UART, + CONF_BUS: bus, + CONF_ASYNC: is_async, + } + compat_methods[f"ESP32_I2S_{bus}"] = { + CONF_TYPE: METHOD_ESP32_I2S, + CONF_BUS: bus, + } + for channel in range(8): + compat_methods[f"ESP32_RMT_{channel}"] = { + CONF_TYPE: METHOD_ESP32_RMT, + CONF_CHANNEL: channel, + } + + if isinstance(value, str): + if value.upper() in compat_methods: + return _validate_method(compat_methods[value.upper()]) + return _validate_method({CONF_TYPE: value}) + return cv.typed_schema( + {k: v.method_schema for k, v in METHODS.items()}, lower=True + )(value) CONFIG_SCHEMA = cv.All( + cv.only_with_arduino, light.ADDRESSABLE_LIGHT_SCHEMA.extend( { cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(NeoPixelBusLightOutputBase), cv.Optional(CONF_TYPE, default="GRB"): validate_type, - cv.Optional(CONF_VARIANT, default="800KBPS"): validate_variant, - cv.Optional(CONF_METHOD, default=None): validate_method, + cv.Required(CONF_VARIANT): cv.one_of(*CHIP_TYPES, lower=True), + cv.Optional(CONF_METHOD): _validate_method, cv.Optional(CONF_INVERT, default="no"): cv.boolean, cv.Optional(CONF_PIN): pins.internal_gpio_output_pin_number, cv.Optional(CONF_CLOCK_PIN): pins.internal_gpio_output_pin_number, @@ -174,19 +182,23 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, } ).extend(cv.COMPONENT_SCHEMA), + _choose_default_method, _validate, - validate_method_pin, - cv.only_with_arduino, ) async def to_code(config): has_white = "W" in config[CONF_TYPE] - template = cg.TemplateArguments(getattr(cg.global_ns, format_method(config))) + method = config[CONF_METHOD] + + method_template = METHODS[method[CONF_TYPE]].to_code( + method, config[CONF_VARIANT], config[CONF_INVERT] + ) + if has_white: - out_type = NeoPixelRGBWLightOutput.template(template) + out_type = NeoPixelRGBWLightOutput.template(method_template) else: - out_type = NeoPixelRGBLightOutput.template(template) + out_type = NeoPixelRGBLightOutput.template(method_template) rhs = out_type.new() var = cg.Pvariable(config[CONF_OUTPUT_ID], rhs, out_type) await light.register_light(var, config) @@ -204,4 +216,4 @@ async def to_code(config): cg.add(var.set_pixel_order(getattr(ESPNeoPixelOrder, config[CONF_TYPE]))) # https://github.com/Makuna/NeoPixelBus/blob/master/library.json - cg.add_library("makuna/NeoPixelBus", "2.6.7") + cg.add_library("makuna/NeoPixelBus", "2.6.9") diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 3e5c7940a4..2bb45487fa 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1399,7 +1399,7 @@ def typed_schema(schemas, **kwargs): if schema_option is None: raise Invalid(f"{key} not specified!") key_v = key_validator(schema_option) - value = schemas[key_v](value) + value = Schema(schemas[key_v])(value) value[key] = key_v return value diff --git a/platformio.ini b/platformio.ini index fa8944bf9a..0dd32268e0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -27,7 +27,7 @@ build_flags = [common] lib_deps = esphome/noise-c@0.1.4 ; api - makuna/NeoPixelBus@2.6.7 ; neopixelbus + makuna/NeoPixelBus@2.6.9 ; neopixelbus build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = diff --git a/script/clang-tidy b/script/clang-tidy index 87ba1c84b5..ad5fdfeb04 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -32,6 +32,7 @@ def clang_options(idedata): '-D_PGMSPACE_H_', '-Dpgm_read_byte(s)=(*(const uint8_t *)(s))', '-Dpgm_read_byte_near(s)=(*(const uint8_t *)(s))', + '-Dpgm_read_word(s)=(*(const uint16_t *)(s))', '-Dpgm_read_dword(s)=(*(const uint32_t *)(s))', '-DPROGMEM=', '-DPGM_P=const char *', From c422b2fb0b6fb98641dbc6c444c17b284ef8986a Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 19:40:18 +0100 Subject: [PATCH 1677/1841] Introduce byteswap helpers (#2661) * Backport std::byteswap() in helpers.h * Introduce convert_big_endian() function * Use convert_big_endian() in i2c byte swap functions --- esphome/components/i2c/i2c.h | 13 +++---------- esphome/core/helpers.h | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/esphome/components/i2c/i2c.h b/esphome/components/i2c/i2c.h index 7ee4cdd811..50a0b3ae50 100644 --- a/esphome/components/i2c/i2c.h +++ b/esphome/components/i2c/i2c.h @@ -1,6 +1,7 @@ #pragma once #include "i2c_bus.h" +#include "esphome/core/helpers.h" #include "esphome/core/optional.h" #include #include @@ -32,16 +33,8 @@ class I2CRegister { // like ntohs/htons but without including networking headers. // ("i2c" byte order is big-endian) -inline uint16_t i2ctohs(uint16_t i2cshort) { - union { - uint16_t x; - uint8_t y[2]; - } conv; - conv.x = i2cshort; - return ((uint16_t) conv.y[0] << 8) | ((uint16_t) conv.y[1] << 0); -} - -inline uint16_t htoi2cs(uint16_t hostshort) { return i2ctohs(hostshort); } +inline uint16_t i2ctohs(uint16_t i2cshort) { return convert_big_endian(i2cshort); } +inline uint16_t htoi2cs(uint16_t hostshort) { return convert_big_endian(hostshort); } class I2CDevice { public: diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index fde631514b..f29af06d89 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -306,6 +306,31 @@ template T *new_buffer(size_t length) { // --------------------------------------------------------------------------------------------------------------------- +/// @name STL backports +///@{ + +// std::byteswap is from C++23 and technically should be a template, but this will do for now. +constexpr uint8_t byteswap(uint8_t n) { return n; } +constexpr uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); } +constexpr uint32_t byteswap(uint32_t n) { return __builtin_bswap32(n); } +constexpr uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); } + +///@} + +/// @name Bit manipulation +///@{ + +/// Convert a value between host byte order and big endian (most significant byte first) order. +template::value, int> = 0> constexpr T convert_big_endian(T val) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + return byteswap(val); +#else + return val; +#endif +} + +///@} + /// @name Parsing & formatting ///@{ From 92321e219ab298045c0948dad3ef50f28993e426 Mon Sep 17 00:00:00 2001 From: TVDLoewe Date: Wed, 10 Nov 2021 19:41:04 +0100 Subject: [PATCH 1678/1841] Max7219digit multiline (#1622) Co-authored-by: Otto winter Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/max7219digit/display.py | 20 +++- .../components/max7219digit/max7219digit.cpp | 112 +++++++++++------- .../components/max7219digit/max7219digit.h | 25 +++- 3 files changed, 110 insertions(+), 47 deletions(-) diff --git a/esphome/components/max7219digit/display.py b/esphome/components/max7219digit/display.py index e1ca128699..2753f70eef 100644 --- a/esphome/components/max7219digit/display.py +++ b/esphome/components/max7219digit/display.py @@ -13,10 +13,20 @@ CONF_SCROLL_DELAY = "scroll_delay" CONF_SCROLL_ENABLE = "scroll_enable" CONF_SCROLL_MODE = "scroll_mode" CONF_REVERSE_ENABLE = "reverse_enable" +CONF_NUM_CHIP_LINES = "num_chip_lines" +CONF_CHIP_LINES_STYLE = "chip_lines_style" +integration_ns = cg.esphome_ns.namespace("max7219digit") +ChipLinesStyle = integration_ns.enum("ChipLinesStyle") +CHIP_LINES_STYLE = { + "ZIGZAG": ChipLinesStyle.ZIGZAG, + "SNAKE": ChipLinesStyle.SNAKE, +} + +ScrollMode = integration_ns.enum("ScrollMode") SCROLL_MODES = { - "CONTINUOUS": 0, - "STOP": 1, + "CONTINUOUS": ScrollMode.CONTINUOUS, + "STOP": ScrollMode.STOP, } CHIP_MODES = { @@ -37,6 +47,10 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(MAX7219Component), cv.Optional(CONF_NUM_CHIPS, default=4): cv.int_range(min=1, max=255), + cv.Optional(CONF_NUM_CHIP_LINES, default=1): cv.int_range(min=1, max=255), + cv.Optional(CONF_CHIP_LINES_STYLE, default="SNAKE"): cv.enum( + CHIP_LINES_STYLE, upper=True + ), cv.Optional(CONF_INTENSITY, default=15): cv.int_range(min=0, max=15), cv.Optional(CONF_ROTATE_CHIP, default="0"): cv.enum(CHIP_MODES, upper=True), cv.Optional(CONF_SCROLL_MODE, default="CONTINUOUS"): cv.enum( @@ -67,6 +81,8 @@ async def to_code(config): await display.register_display(var, config) cg.add(var.set_num_chips(config[CONF_NUM_CHIPS])) + cg.add(var.set_num_chip_lines(config[CONF_NUM_CHIP_LINES])) + cg.add(var.set_chip_lines_style(config[CONF_CHIP_LINES_STYLE])) cg.add(var.set_intensity(config[CONF_INTENSITY])) cg.add(var.set_chip_orientation(config[CONF_ROTATE_CHIP])) cg.add(var.set_scroll_speed(config[CONF_SCROLL_SPEED])) diff --git a/esphome/components/max7219digit/max7219digit.cpp b/esphome/components/max7219digit/max7219digit.cpp index 4fedd3d312..0f86ac635c 100644 --- a/esphome/components/max7219digit/max7219digit.cpp +++ b/esphome/components/max7219digit/max7219digit.cpp @@ -26,9 +26,12 @@ void MAX7219Component::setup() { ESP_LOGCONFIG(TAG, "Setting up MAX7219_DIGITS..."); this->spi_setup(); this->stepsleft_ = 0; - this->max_displaybuffer_.reserve(500); // Create base space to write buffer - // Initialize buffer with 0 for display so all non written pixels are blank - this->max_displaybuffer_.resize(this->num_chips_ * 8, 0); + for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { + std::vector vec(1); + this->max_displaybuffer_.push_back(vec); + // Initialize buffer with 0 for display so all non written pixels are blank + this->max_displaybuffer_[chip_line].resize(get_width_internal(), 0); + } // let's assume the user has all 8 digits connected, only important in daisy chained setups anyway this->send_to_all_(MAX7219_REGISTER_SCAN_LIMIT, 7); // let's use our own ASCII -> led pattern encoding @@ -46,6 +49,8 @@ void MAX7219Component::setup() { void MAX7219Component::dump_config() { ESP_LOGCONFIG(TAG, "MAX7219DIGIT:"); ESP_LOGCONFIG(TAG, " Number of Chips: %u", this->num_chips_); + ESP_LOGCONFIG(TAG, " Number of Chips Lines: %u", this->num_chip_lines_); + ESP_LOGCONFIG(TAG, " Chips Lines Style : %u", this->chip_lines_style_); ESP_LOGCONFIG(TAG, " Intensity: %u", this->intensity_); ESP_LOGCONFIG(TAG, " Scroll Mode: %u", this->scroll_mode_); ESP_LOGCONFIG(TAG, " Scroll Speed: %u", this->scroll_speed_); @@ -59,19 +64,19 @@ void MAX7219Component::loop() { uint32_t now = millis(); // check if the buffer has shrunk past the current position since last update - if ((this->max_displaybuffer_.size() >= this->old_buffer_size_ + 3) || - (this->max_displaybuffer_.size() <= this->old_buffer_size_ - 3)) { + if ((this->max_displaybuffer_[0].size() >= this->old_buffer_size_ + 3) || + (this->max_displaybuffer_[0].size() <= this->old_buffer_size_ - 3)) { this->stepsleft_ = 0; this->display(); - this->old_buffer_size_ = this->max_displaybuffer_.size(); + this->old_buffer_size_ = this->max_displaybuffer_[0].size(); } // Reset the counter back to 0 when full string has been displayed. - if (this->stepsleft_ > this->max_displaybuffer_.size()) + if (this->stepsleft_ > this->max_displaybuffer_[0].size()) this->stepsleft_ = 0; // Return if there is no need to scroll or scroll is off - if (!this->scroll_ || (this->max_displaybuffer_.size() <= this->num_chips_ * 8)) { + if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= get_width_internal())) { this->display(); return; } @@ -82,8 +87,8 @@ void MAX7219Component::loop() { } // Dwell time at end of string in case of stop at end - if (this->scroll_mode_ == 1) { - if (this->stepsleft_ >= this->max_displaybuffer_.size() - this->num_chips_ * 8 + 1) { + if (this->scroll_mode_ == ScrollMode::STOP) { + if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - get_width_internal() + 1) { if (now - this->last_scroll_ >= this->scroll_dwell_) { this->stepsleft_ = 0; this->last_scroll_ = now; @@ -107,30 +112,53 @@ void MAX7219Component::display() { // Run this routine for the rows of every chip 8x row 0 top to 7 bottom // Fill the pixel parameter with display data // Send the data to the chip - for (uint8_t i = 0; i < this->num_chips_; i++) { - for (uint8_t j = 0; j < 8; j++) { - if (this->reverse_) { - pixels[j] = this->max_displaybuffer_[(this->num_chips_ - i - 1) * 8 + j]; - } else { - pixels[j] = this->max_displaybuffer_[i * 8 + j]; + for (uint8_t chip = 0; chip < this->num_chips_ / this->num_chip_lines_; chip++) { + for (uint8_t chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { + for (uint8_t j = 0; j < 8; j++) { + bool reverse = + chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE ? !this->reverse_ : this->reverse_; + if (reverse) { + pixels[j] = + this->max_displaybuffer_[chip_line][(this->num_chips_ / this->num_chip_lines_ - chip - 1) * 8 + j]; + } else { + pixels[j] = this->max_displaybuffer_[chip_line][chip * 8 + j]; + } } + if (chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE) + this->orientation_ = orientation_180_(); + this->send64pixels(chip_line * this->num_chips_ / this->num_chip_lines_ + chip, pixels); + if (chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE) + this->orientation_ = orientation_180_(); } - this->send64pixels(i, pixels); + } +} + +uint8_t MAX7219Component::orientation_180_() { + switch (this->orientation_) { + case 0: + return 2; + case 1: + return 3; + case 2: + return 0; + case 3: + return 1; + default: + return 0; } } int MAX7219Component::get_height_internal() { - return 8; // TO BE DONE -> STACK TWO DISPLAYS ON TOP OF EACH OTHER - // TO BE DONE -> CREATE Virtual size of screen and scroll + return 8 * this->num_chip_lines_; // TO BE DONE -> CREATE Virtual size of screen and scroll } -int MAX7219Component::get_width_internal() { return this->num_chips_ * 8; } - -size_t MAX7219Component::get_buffer_length_() { return this->num_chips_ * 8; } +int MAX7219Component::get_width_internal() { return this->num_chips_ / this->num_chip_lines_ * 8; } void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color color) { - if (x + 1 > this->max_displaybuffer_.size()) { // Extend the display buffer in case required - this->max_displaybuffer_.resize(x + 1, this->bckgrnd_); + if (x + 1 > this->max_displaybuffer_[0].size()) { // Extend the display buffer in case required + for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { + this->max_displaybuffer_[chip_line].resize(x + 1, this->bckgrnd_); + } } if ((y >= this->get_height_internal()) || (y < 0) || (x < 0)) // If pixel is outside display then dont draw @@ -140,9 +168,9 @@ void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color colo uint8_t subpos = y; // Y is starting at 0 top left if (color.is_on()) { - this->max_displaybuffer_[pos] |= (1 << subpos); + this->max_displaybuffer_[subpos / 8][pos] |= (1 << subpos % 8); } else { - this->max_displaybuffer_[pos] &= ~(1 << subpos); + this->max_displaybuffer_[subpos / 8][pos] &= ~(1 << subpos % 8); } } @@ -158,8 +186,10 @@ void MAX7219Component::send_to_all_(uint8_t a_register, uint8_t data) { } void MAX7219Component::update() { this->update_ = true; - this->max_displaybuffer_.clear(); - this->max_displaybuffer_.resize(this->num_chips_ * 8, this->bckgrnd_); + for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { + this->max_displaybuffer_[chip_line].clear(); + this->max_displaybuffer_[chip_line].resize(get_width_internal(), this->bckgrnd_); + } if (this->writer_local_.has_value()) // insert Labda function if available (*this->writer_local_)(*this); } @@ -175,7 +205,7 @@ void MAX7219Component::turn_on_off(bool on_off) { } } -void MAX7219Component::scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_t delay, uint16_t dwell) { +void MAX7219Component::scroll(bool on_off, ScrollMode mode, uint16_t speed, uint16_t delay, uint16_t dwell) { this->set_scroll(on_off); this->set_scroll_mode(mode); this->set_scroll_speed(speed); @@ -183,7 +213,7 @@ void MAX7219Component::scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_ this->set_scroll_delay(delay); } -void MAX7219Component::scroll(bool on_off, uint8_t mode) { +void MAX7219Component::scroll(bool on_off, ScrollMode mode) { this->set_scroll(on_off); this->set_scroll_mode(mode); } @@ -196,24 +226,26 @@ void MAX7219Component::intensity(uint8_t intensity) { void MAX7219Component::scroll(bool on_off) { this->set_scroll(on_off); } void MAX7219Component::scroll_left() { - if (this->update_) { - this->max_displaybuffer_.push_back(this->bckgrnd_); - for (uint16_t i = 0; i < this->stepsleft_; i++) { - this->max_displaybuffer_.push_back(this->max_displaybuffer_.front()); - this->max_displaybuffer_.erase(this->max_displaybuffer_.begin()); - this->update_ = false; + for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { + if (this->update_) { + this->max_displaybuffer_[chip_line].push_back(this->bckgrnd_); + for (uint16_t i = 0; i < this->stepsleft_; i++) { + this->max_displaybuffer_[chip_line].push_back(this->max_displaybuffer_[chip_line].front()); + this->max_displaybuffer_[chip_line].erase(this->max_displaybuffer_[chip_line].begin()); + } + } else { + this->max_displaybuffer_[chip_line].push_back(this->max_displaybuffer_[chip_line].front()); + this->max_displaybuffer_[chip_line].erase(this->max_displaybuffer_[chip_line].begin()); } - } else { - this->max_displaybuffer_.push_back(this->max_displaybuffer_.front()); - this->max_displaybuffer_.erase(this->max_displaybuffer_.begin()); } + this->update_ = false; this->stepsleft_++; } void MAX7219Component::send_char(uint8_t chip, uint8_t data) { // get this character from PROGMEM for (uint8_t i = 0; i < 8; i++) - this->max_displaybuffer_[chip * 8 + i] = progmem_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]); + this->max_displaybuffer_[0][chip * 8 + i] = progmem_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]); } // end of send_char // send one character (data) to position (chip) diff --git a/esphome/components/max7219digit/max7219digit.h b/esphome/components/max7219digit/max7219digit.h index 02fe8b6f42..3bf934632f 100644 --- a/esphome/components/max7219digit/max7219digit.h +++ b/esphome/components/max7219digit/max7219digit.h @@ -12,6 +12,16 @@ namespace esphome { namespace max7219digit { +enum ChipLinesStyle { + ZIGZAG = 0, + SNAKE, +}; + +enum ScrollMode { + CONTINUOUS = 0, + STOP, +}; + class MAX7219Component; using max7219_writer_t = std::function; @@ -46,20 +56,22 @@ class MAX7219Component : public PollingComponent, void set_intensity(uint8_t intensity) { this->intensity_ = intensity; }; void set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; }; + void set_num_chip_lines(uint8_t num_chip_lines) { this->num_chip_lines_ = num_chip_lines; }; + void set_chip_lines_style(ChipLinesStyle chip_lines_style) { this->chip_lines_style_ = chip_lines_style; }; void set_chip_orientation(uint8_t rotate) { this->orientation_ = rotate; }; void set_scroll_speed(uint16_t speed) { this->scroll_speed_ = speed; }; void set_scroll_dwell(uint16_t dwell) { this->scroll_dwell_ = dwell; }; void set_scroll_delay(uint16_t delay) { this->scroll_delay_ = delay; }; void set_scroll(bool on_off) { this->scroll_ = on_off; }; - void set_scroll_mode(uint8_t mode) { this->scroll_mode_ = mode; }; + void set_scroll_mode(ScrollMode mode) { this->scroll_mode_ = mode; }; void set_reverse(bool on_off) { this->reverse_ = on_off; }; void send_char(uint8_t chip, uint8_t data); void send64pixels(uint8_t chip, const uint8_t pixels[8]); void scroll_left(); - void scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_t delay, uint16_t dwell); - void scroll(bool on_off, uint8_t mode); + void scroll(bool on_off, ScrollMode mode, uint16_t speed, uint16_t delay, uint16_t dwell); + void scroll(bool on_off, ScrollMode mode); void scroll(bool on_off); void intensity(uint8_t intensity); @@ -84,9 +96,12 @@ class MAX7219Component : public PollingComponent, protected: void send_byte_(uint8_t a_register, uint8_t data); void send_to_all_(uint8_t a_register, uint8_t data); + uint8_t orientation_180_(); uint8_t intensity_; /// Intensity of the display from 0 to 15 (most) uint8_t num_chips_; + uint8_t num_chip_lines_; + ChipLinesStyle chip_lines_style_; bool scroll_; bool reverse_; bool update_{false}; @@ -94,11 +109,11 @@ class MAX7219Component : public PollingComponent, uint16_t scroll_delay_; uint16_t scroll_dwell_; uint16_t old_buffer_size_ = 0; - uint8_t scroll_mode_; + ScrollMode scroll_mode_; bool invert_ = false; uint8_t orientation_; uint8_t bckgrnd_ = 0x0; - std::vector max_displaybuffer_; + std::vector> max_displaybuffer_; uint32_t last_scroll_ = 0; uint16_t stepsleft_; size_t get_buffer_length_(); From 4d433968359d54b6d48c3e1cbb6a024aeaffe08f Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 19:42:41 +0100 Subject: [PATCH 1679/1841] Clean-up string sanitation helpers (#2660) --- esphome/components/mqtt/mqtt_component.cpp | 4 +- esphome/core/entity_base.cpp | 2 +- esphome/core/helpers.cpp | 47 +++++++++------------- esphome/core/helpers.h | 28 ++++++------- 4 files changed, 37 insertions(+), 44 deletions(-) diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index cebb8dd086..e3ae4dea50 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -17,7 +17,7 @@ static const char *const TAG = "mqtt.component"; void MQTTComponent::set_retain(bool retain) { this->retain_ = retain; } std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const { - std::string sanitized_name = sanitize_string_allowlist(App.get_name(), HOSTNAME_CHARACTER_ALLOWLIST); + std::string sanitized_name = str_sanitize(App.get_name()); return discovery_info.prefix + "/" + this->component_type() + "/" + sanitized_name + "/" + this->get_default_object_id_() + "/config"; } @@ -136,7 +136,7 @@ bool MQTTComponent::is_discovery_enabled() const { } std::string MQTTComponent::get_default_object_id_() const { - return sanitize_string_allowlist(to_lowercase_underscore(this->friendly_name()), HOSTNAME_CHARACTER_ALLOWLIST); + return str_sanitize(str_snake_case(this->friendly_name())); } void MQTTComponent::subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos) { diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 41f08b28a6..a9e1414018 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -35,7 +35,7 @@ const std::string &EntityBase::get_object_id() { return this->object_id_; } // Calculate Object ID Hash from Entity Name void EntityBase::calc_object_id_() { - this->object_id_ = sanitize_string_allowlist(to_lowercase_underscore(this->name_), HOSTNAME_CHARACTER_ALLOWLIST); + this->object_id_ = str_sanitize(str_snake_case(this->name_)); // FNV-1 hash this->object_id_hash_ = fnv1_hash(this->object_id_); } diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 3047facf45..edd2f74c12 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -128,31 +128,6 @@ float gamma_uncorrect(float value, float gamma) { return powf(value, 1 / gamma); } -std::string to_lowercase_underscore(std::string s) { - std::transform(s.begin(), s.end(), s.begin(), ::tolower); - std::replace(s.begin(), s.end(), ' ', '_'); - return s; -} - -std::string sanitize_string_allowlist(const std::string &s, const std::string &allowlist) { - std::string out(s); - out.erase(std::remove_if(out.begin(), out.end(), - [&allowlist](const char &c) { return allowlist.find(c) == std::string::npos; }), - out.end()); - return out; -} - -std::string sanitize_hostname(const std::string &hostname) { - std::string s = sanitize_string_allowlist(hostname, HOSTNAME_CHARACTER_ALLOWLIST); - return truncate_string(s, 63); -} - -std::string truncate_string(const std::string &s, size_t length) { - if (s.length() > length) - return s.substr(0, length); - return s; -} - std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { if (accuracy_decimals < 0) { auto multiplier = powf(10.0f, accuracy_decimals); @@ -191,8 +166,6 @@ ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { return PARSE_NONE; } -const char *const HOSTNAME_CHARACTER_ALLOWLIST = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"; - uint8_t crc8(uint8_t *data, uint8_t len) { uint8_t crc = 0; @@ -481,4 +454,24 @@ IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } #endif +// --------------------------------------------------------------------------------------------------------------------- + +std::string str_truncate(const std::string &str, size_t length) { + return str.length() > length ? str.substr(0, length) : str; +} +std::string str_snake_case(const std::string &str) { + std::string result; + result.resize(str.length()); + std::transform(str.begin(), str.end(), result.begin(), ::tolower); + std::replace(result.begin(), result.end(), ' ', '_'); + return result; +} +std::string str_sanitize(const std::string &str) { + std::string out; + std::copy_if(str.begin(), str.end(), std::back_inserter(out), [](const char &c) { + return c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + }); + return out; +} + } // namespace esphome diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index f29af06d89..9a60d036e4 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -25,9 +25,6 @@ namespace esphome { -/// The characters that are allowed in a hostname. -extern const char *const HOSTNAME_CHARACTER_ALLOWLIST; - /// Read the raw MAC address into the provided byte array (6 bytes). void get_mac_address_raw(uint8_t *mac); @@ -55,14 +52,6 @@ std::string to_string(double val); std::string to_string(long double val); optional parse_hex(const std::string &str, size_t start, size_t length); optional parse_hex(char chr); -/// Sanitize the hostname by removing characters that are not in the allowlist and truncating it to 63 chars. -std::string sanitize_hostname(const std::string &hostname); - -/// Truncate a string to a specific length -std::string truncate_string(const std::string &s, size_t length); - -/// Convert the string to lowercase_underscore. -std::string to_lowercase_underscore(std::string s); /// Compare string a to string b (ignoring case) and return whether they are equal. bool str_equals_case_insensitive(const std::string &a, const std::string &b); @@ -145,9 +134,6 @@ std::string uint64_to_string(uint64_t num); /// Convert a uint32_t to a hex string std::string uint32_to_string(uint32_t num); -/// Sanitizes the input string with the allowlist. -std::string sanitize_string_allowlist(const std::string &s, const std::string &allowlist); - uint8_t reverse_bits_8(uint8_t x); uint16_t reverse_bits_16(uint16_t x); uint32_t reverse_bits_32(uint32_t x); @@ -331,6 +317,20 @@ template::value, int> = 0> constexpr ///@} +/// @name Strings +///@{ + +/// Truncate a string to a specific length. +std::string str_truncate(const std::string &str, size_t length); + +/// Convert the string to snake case (lowercase with underscores). +std::string str_snake_case(const std::string &str); + +/// Sanitizes the input string by removing all characters but alphanumerics, dashes and underscores. +std::string str_sanitize(const std::string &str); + +///@} + /// @name Parsing & formatting ///@{ From 99c775d8cbad9afe3be4ae6c9a446dde34c1f3cd Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 19:44:01 +0100 Subject: [PATCH 1680/1841] Introduce encode_value/decode_value() template functions (#2662) --- esphome/components/ccs811/ccs811.cpp | 2 +- esphome/core/helpers.cpp | 11 -------- esphome/core/helpers.h | 42 +++++++++++++++++++++++----- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/esphome/components/ccs811/ccs811.cpp b/esphome/components/ccs811/ccs811.cpp index 11a66f5100..f8cee79c55 100644 --- a/esphome/components/ccs811/ccs811.cpp +++ b/esphome/components/ccs811/ccs811.cpp @@ -52,7 +52,7 @@ void CCS811Component::setup() { if (this->baseline_.has_value()) { // baseline available, write to sensor - this->write_bytes(0x11, decode_uint16(*this->baseline_)); + this->write_bytes(0x11, decode_value(*this->baseline_)); } auto hardware_version_data = this->read_bytes<1>(0x21); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index edd2f74c12..daca3ffd32 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -350,17 +350,6 @@ std::string str_sprintf(const char *fmt, ...) { return str; } -uint16_t encode_uint16(uint8_t msb, uint8_t lsb) { return (uint16_t(msb) << 8) | uint16_t(lsb); } -std::array decode_uint16(uint16_t value) { - uint8_t msb = (value >> 8) & 0xFF; - uint8_t lsb = (value >> 0) & 0xFF; - return {msb, lsb}; -} - -uint32_t encode_uint32(uint8_t msb, uint8_t byte2, uint8_t byte3, uint8_t lsb) { - return (uint32_t(msb) << 24) | (uint32_t(byte2) << 16) | (uint32_t(byte3) << 8) | uint32_t(lsb); -} - std::string hexencode(const uint8_t *data, uint32_t len) { char buf[20]; std::string res; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 9a60d036e4..c67ad8eea3 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -138,13 +138,6 @@ uint8_t reverse_bits_8(uint8_t x); uint16_t reverse_bits_16(uint16_t x); uint32_t reverse_bits_32(uint32_t x); -/// Encode a 16-bit unsigned integer given a most and least-significant byte. -uint16_t encode_uint16(uint8_t msb, uint8_t lsb); -/// Decode a 16-bit unsigned integer into an array of two values: most significant byte, least significant byte. -std::array decode_uint16(uint16_t value); -/// Encode a 32-bit unsigned integer given four bytes in MSB -> LSB order -uint32_t encode_uint32(uint8_t msb, uint8_t byte2, uint8_t byte3, uint8_t lsb); - /// Convert RGB floats (0-1) to hue (0-360) & saturation/value percentage (0-1) void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value); /// Convert hue (0-360) & saturation/value percentage (0-1) to RGB floats (0-1) @@ -306,6 +299,41 @@ constexpr uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); } /// @name Bit manipulation ///@{ +/// Encode a 16-bit value given the most and least significant byte. +constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb) { + return (static_cast(msb) << 8) | (static_cast(lsb)); +} +/// Encode a 32-bit value given four bytes in most to least significant byte order. +constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4) { + return (static_cast(byte1) << 24) | (static_cast(byte2) << 16) | + (static_cast(byte3) << 8) | (static_cast(byte4)); +} + +/// Encode a value from its constituent bytes (from most to least significant) in an array with length sizeof(T). +template::value, int> = 0> inline T encode_value(const uint8_t *bytes) { + T val = 0; + for (size_t i = 0; i < sizeof(T); i++) { + val <<= 8; + val |= bytes[i]; + } + return val; +} +/// Encode a value from its constituent bytes (from most to least significant) in an std::array with length sizeof(T). +template::value, int> = 0> +inline T encode_value(const std::array bytes) { + return encode_value(bytes.data()); +} +/// Decode a value into its constituent bytes (from most to least significant). +template::value, int> = 0> +inline std::array decode_value(T val) { + std::array ret{}; + for (size_t i = sizeof(T); i > 0; i--) { + ret[i - 1] = val & 0xFF; + val >>= 8; + } + return ret; +} + /// Convert a value between host byte order and big endian (most significant byte first) order. template::value, int> = 0> constexpr T convert_big_endian(T val) { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ From 0bdb48bcac9244df6700410b9cf4ec581dbbda38 Mon Sep 17 00:00:00 2001 From: Laszlo Gazdag Date: Wed, 10 Nov 2021 20:31:22 +0100 Subject: [PATCH 1681/1841] Make OTA function switchable in web_server component (#2685) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/web_server/__init__.py | 3 +++ esphome/components/web_server/web_server.cpp | 16 +++++++++++----- esphome/components/web_server/web_server.h | 7 +++++++ tests/test1.yaml | 1 + tests/test4.yaml | 1 + 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index ba2d866593..61b1fa5ad6 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -12,6 +12,7 @@ from esphome.const import ( CONF_AUTH, CONF_USERNAME, CONF_PASSWORD, + CONF_OTA, ) from esphome.core import CORE, coroutine_with_priority @@ -41,6 +42,7 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( web_server_base.WebServerBase ), + cv.Optional(CONF_OTA, default=True): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA) @@ -57,6 +59,7 @@ async def to_code(config): cg.add_define("USE_WEBSERVER") cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) + cg.add(var.set_allow_ota(config[CONF_OTA])) if CONF_AUTH in config: cg.add(paren.set_auth_username(config[CONF_AUTH][CONF_USERNAME])) cg.add(paren.set_auth_password(config[CONF_AUTH][CONF_PASSWORD])) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 17b17fcc3c..6f47f460af 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -152,7 +152,9 @@ void WebServer::setup() { #endif this->base_->add_handler(&this->events_); this->base_->add_handler(this); - this->base_->add_ota_handler(); + + if (this->allow_ota_) + this->base_->add_ota_handler(); this->set_interval(10000, [this]() { this->events_.send("", "ping", millis(), 30000); }); } @@ -240,10 +242,14 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { #endif stream->print(F("

    See ESPHome Web API for " - "REST API documentation.

    " - "

    OTA Update

    " - "

    Debug Log

    "));
    +                  "REST API documentation.

    ")); + if (this->allow_ota_) { + stream->print( + F("

    OTA Update

    ")); + } + stream->print(F("

    Debug Log

    "));
    +
     #ifdef WEBSERVER_JS_INCLUDE
       if (this->js_include_ != nullptr) {
         stream->print(F(""));
    diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h
    index 021d5a0646..cdfec51cf1 100644
    --- a/esphome/components/web_server/web_server.h
    +++ b/esphome/components/web_server/web_server.h
    @@ -58,6 +58,12 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
        */
       void set_js_include(const char *js_include);
     
    +  /** Set whether or not the webserver should expose the OTA form and handler.
    +   *
    +   * @param allow_ota.
    +   */
    +  void set_allow_ota(bool allow_ota) { this->allow_ota_ = allow_ota; }
    +
       // ========== INTERNAL METHODS ==========
       // (In most use cases you won't need these)
       /// Setup the internal web server and register handlers.
    @@ -182,6 +188,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
       const char *css_include_{nullptr};
       const char *js_url_{nullptr};
       const char *js_include_{nullptr};
    +  bool allow_ota_{true};
     };
     
     }  // namespace web_server
    diff --git a/tests/test1.yaml b/tests/test1.yaml
    index 585e01635f..dee7493bf2 100644
    --- a/tests/test1.yaml
    +++ b/tests/test1.yaml
    @@ -234,6 +234,7 @@ logger:
     
     web_server:
       port: 8080
    +  ota: true
       css_url: https://esphome.io/_static/webserver-v1.min.css
       js_url: https://esphome.io/_static/webserver-v1.min.js
     
    diff --git a/tests/test4.yaml b/tests/test4.yaml
    index 4228c7494c..938145235a 100644
    --- a/tests/test4.yaml
    +++ b/tests/test4.yaml
    @@ -45,6 +45,7 @@ logger:
       level: DEBUG
     
     web_server:
    +  ota: false
       auth:
         username: admin
         password: admin
    
    From 5ff7c8418c584fc10f49caaaf9736ffffb321c00 Mon Sep 17 00:00:00 2001
    From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Date: Thu, 11 Nov 2021 08:55:45 +1300
    Subject: [PATCH 1682/1841] Implement Improv via Serial component (#2423)
    
    Co-authored-by: Paulus Schoutsen 
    ---
     CODEOWNERS                                    |   1 +
     .../captive_portal/captive_portal.cpp         |   1 +
     esphome/components/esp32/__init__.py          |   2 +
     esphome/components/esp32/const.py             |   8 +
     esphome/components/esp8266/__init__.py        |   1 +
     esphome/components/improv/improv.cpp          |  12 +-
     esphome/components/improv/improv.h            |  10 +-
     esphome/components/improv_serial/__init__.py  |  33 +++
     .../improv_serial/improv_serial_component.cpp | 250 ++++++++++++++++++
     .../improv_serial/improv_serial_component.h   |  69 +++++
     esphome/components/logger/logger.cpp          |   2 +-
     esphome/components/web_server/__init__.py     |   2 +
     esphome/components/wifi/__init__.py           |   3 +-
     esphome/components/wifi/wifi_component.cpp    |   2 -
     esphome/core/defines.h                        |   1 +
     script/ci-custom.py                           |   6 +-
     tests/test3.yaml                              |   2 +
     17 files changed, 391 insertions(+), 14 deletions(-)
     create mode 100644 esphome/components/improv_serial/__init__.py
     create mode 100644 esphome/components/improv_serial/improv_serial_component.cpp
     create mode 100644 esphome/components/improv_serial/improv_serial_component.h
    
    diff --git a/CODEOWNERS b/CODEOWNERS
    index 8f98fe1f7f..18b4564280 100644
    --- a/CODEOWNERS
    +++ b/CODEOWNERS
    @@ -73,6 +73,7 @@ esphome/components/homeassistant/* @OttoWinter
     esphome/components/hrxl_maxsonar_wr/* @netmikey
     esphome/components/i2c/* @esphome/core
     esphome/components/improv/* @jesserockz
    +esphome/components/improv_serial/* @esphome/core
     esphome/components/inkbird_ibsth1_mini/* @fkirill
     esphome/components/inkplate6/* @jesserockz
     esphome/components/integration/* @OttoWinter
    diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp
    index 9e00adae3d..ad4c32bb1f 100644
    --- a/esphome/components/captive_portal/captive_portal.cpp
    +++ b/esphome/components/captive_portal/captive_portal.cpp
    @@ -67,6 +67,7 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
       ESP_LOGI(TAG, "  SSID='%s'", ssid.c_str());
       ESP_LOGI(TAG, "  Password=" LOG_SECRET("'%s'"), psk.c_str());
       wifi::global_wifi_component->save_wifi_sta(ssid, psk);
    +  wifi::global_wifi_component->start_scanning();
       request->redirect("/?save=true");
     }
     
    diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py
    index d84663b2d6..1c249476e7 100644
    --- a/esphome/components/esp32/__init__.py
    +++ b/esphome/components/esp32/__init__.py
    @@ -28,6 +28,7 @@ from .const import (  # noqa
         KEY_SDKCONFIG_OPTIONS,
         KEY_VARIANT,
         VARIANT_ESP32C3,
    +    VARIANT_FRIENDLY,
         VARIANTS,
     )
     from .boards import BOARD_TO_VARIANT
    @@ -287,6 +288,7 @@ async def to_code(config):
         cg.add_build_flag("-DUSE_ESP32")
         cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
         cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}")
    +    cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]])
     
         cg.add_platformio_option("lib_ldf_mode", "off")
     
    diff --git a/esphome/components/esp32/const.py b/esphome/components/esp32/const.py
    index b82f03bf68..d92b449ee9 100644
    --- a/esphome/components/esp32/const.py
    +++ b/esphome/components/esp32/const.py
    @@ -18,4 +18,12 @@ VARIANTS = [
         VARIANT_ESP32H2,
     ]
     
    +VARIANT_FRIENDLY = {
    +    VARIANT_ESP32: "ESP32",
    +    VARIANT_ESP32S2: "ESP32-S2",
    +    VARIANT_ESP32S3: "ESP32-S3",
    +    VARIANT_ESP32C3: "ESP32-C3",
    +    VARIANT_ESP32H2: "ESP32-H2",
    +}
    +
     esp32_ns = cg.esphome_ns.namespace("esp32")
    diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py
    index 5b97d2d9d5..34c792499d 100644
    --- a/esphome/components/esp8266/__init__.py
    +++ b/esphome/components/esp8266/__init__.py
    @@ -156,6 +156,7 @@ async def to_code(config):
         cg.add_platformio_option("board", config[CONF_BOARD])
         cg.add_build_flag("-DUSE_ESP8266")
         cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
    +    cg.add_define("ESPHOME_VARIANT", "ESP8266")
     
         conf = config[CONF_FRAMEWORK]
         cg.add_platformio_option("framework", "arduino")
    diff --git a/esphome/components/improv/improv.cpp b/esphome/components/improv/improv.cpp
    index 4f6ed7702d..94068bc626 100644
    --- a/esphome/components/improv/improv.cpp
    +++ b/esphome/components/improv/improv.cpp
    @@ -7,11 +7,13 @@ ImprovCommand parse_improv_data(const std::vector &data) {
     }
     
     ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
    +  ImprovCommand improv_command;
       Command command = (Command) data[0];
       uint8_t data_length = data[1];
     
       if (data_length != length - 3) {
    -    return {.command = UNKNOWN};
    +    improv_command.command = UNKNOWN;
    +    return improv_command;
       }
     
       uint8_t checksum = data[length - 1];
    @@ -22,7 +24,8 @@ ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
       }
     
       if ((uint8_t) calculated_checksum != checksum) {
    -    return {.command = BAD_CHECKSUM};
    +    improv_command.command = BAD_CHECKSUM;
    +    return improv_command;
       }
     
       if (command == WIFI_SETTINGS) {
    @@ -39,9 +42,8 @@ ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
         return {.command = command, .ssid = ssid, .password = password};
       }
     
    -  return {
    -      .command = command,
    -  };
    +  improv_command.command = command;
    +  return improv_command;
     }
     
     std::vector build_rpc_response(Command command, const std::vector &datum) {
    diff --git a/esphome/components/improv/improv.h b/esphome/components/improv/improv.h
    index 0ead80e2cf..542eb82bd3 100644
    --- a/esphome/components/improv/improv.h
    +++ b/esphome/components/improv/improv.h
    @@ -1,8 +1,8 @@
     #pragma once
     
    -#ifdef USE_ARDUINO
    +#ifdef ARDUINO
     #include "WString.h"
    -#endif  // USE_ARDUINO
    +#endif  // ARDUINO
     
     #include 
     #include 
    @@ -38,6 +38,8 @@ enum Command : uint8_t {
       UNKNOWN = 0x00,
       WIFI_SETTINGS = 0x01,
       IDENTIFY = 0x02,
    +  GET_CURRENT_STATE = 0x02,
    +  GET_DEVICE_INFO = 0x03,
       BAD_CHECKSUM = 0xFF,
     };
     
    @@ -53,8 +55,8 @@ ImprovCommand parse_improv_data(const std::vector &data);
     ImprovCommand parse_improv_data(const uint8_t *data, size_t length);
     
     std::vector build_rpc_response(Command command, const std::vector &datum);
    -#ifdef USE_ARDUINO
    +#ifdef ARDUINO
     std::vector build_rpc_response(Command command, const std::vector &datum);
    -#endif  // USE_ARDUINO
    +#endif  // ARDUINO
     
     }  // namespace improv
    diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py
    new file mode 100644
    index 0000000000..b1cdc2d93e
    --- /dev/null
    +++ b/esphome/components/improv_serial/__init__.py
    @@ -0,0 +1,33 @@
    +from esphome.const import CONF_BAUD_RATE, CONF_ID, CONF_LOGGER
    +import esphome.codegen as cg
    +import esphome.config_validation as cv
    +import esphome.final_validate as fv
    +
    +CODEOWNERS = ["@esphome/core"]
    +DEPENDENCIES = ["logger", "wifi"]
    +AUTO_LOAD = ["improv"]
    +
    +improv_serial_ns = cg.esphome_ns.namespace("improv_serial")
    +
    +ImprovSerialComponent = improv_serial_ns.class_("ImprovSerialComponent", cg.Component)
    +
    +CONFIG_SCHEMA = cv.Schema(
    +    {
    +        cv.GenerateID(): cv.declare_id(ImprovSerialComponent),
    +    }
    +).extend(cv.COMPONENT_SCHEMA)
    +
    +
    +def validate_logger_baud_rate(config):
    +    logger_conf = fv.full_config.get()[CONF_LOGGER]
    +    if logger_conf[CONF_BAUD_RATE] == 0:
    +        raise cv.Invalid("improv_serial requires the logger baud_rate to be not 0")
    +    return config
    +
    +
    +FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate
    +
    +
    +async def to_code(config):
    +    var = cg.new_Pvariable(config[CONF_ID])
    +    await cg.register_component(var, config)
    diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp
    new file mode 100644
    index 0000000000..a12f1bd83b
    --- /dev/null
    +++ b/esphome/components/improv_serial/improv_serial_component.cpp
    @@ -0,0 +1,250 @@
    +#include "improv_serial_component.h"
    +
    +#include "esphome/core/application.h"
    +#include "esphome/core/defines.h"
    +#include "esphome/core/hal.h"
    +#include "esphome/core/log.h"
    +#include "esphome/core/version.h"
    +
    +#include "esphome/components/logger/logger.h"
    +
    +namespace esphome {
    +namespace improv_serial {
    +
    +static const char *const TAG = "improv_serial";
    +
    +void ImprovSerialComponent::setup() {
    +  global_improv_serial_component = this;
    +#ifdef USE_ARDUINO
    +  this->hw_serial_ = logger::global_logger->get_hw_serial();
    +#endif
    +#ifdef USE_ESP_IDF
    +  this->uart_num_ = logger::global_logger->get_uart_num();
    +#endif
    +
    +  if (wifi::global_wifi_component->has_sta()) {
    +    this->state_ = improv::STATE_PROVISIONED;
    +  }
    +}
    +
    +void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:"); }
    +
    +int ImprovSerialComponent::available_() {
    +#ifdef USE_ARDUINO
    +  return this->hw_serial_->available();
    +#endif
    +#ifdef USE_ESP_IDF
    +  size_t available;
    +  uart_get_buffered_data_len(this->uart_num_, &available);
    +  return available;
    +#endif
    +}
    +
    +uint8_t ImprovSerialComponent::read_byte_() {
    +  uint8_t data;
    +#ifdef USE_ARDUINO
    +  this->hw_serial_->readBytes(&data, 1);
    +#endif
    +#ifdef USE_ESP_IDF
    +  uart_read_bytes(this->uart_num_, &data, 1, 20 / portTICK_RATE_MS);
    +#endif
    +  return data;
    +}
    +
    +void ImprovSerialComponent::write_data_(std::vector &data) {
    +  data.push_back('\n');
    +#ifdef USE_ARDUINO
    +  this->hw_serial_->write(data.data(), data.size());
    +#endif
    +#ifdef USE_ESP_IDF
    +  uart_write_bytes(this->uart_num_, data.data(), data.size());
    +#endif
    +}
    +
    +void ImprovSerialComponent::loop() {
    +  const uint32_t now = millis();
    +  if (now - this->last_read_byte_ > 50) {
    +    this->rx_buffer_.clear();
    +    this->last_read_byte_ = now;
    +  }
    +
    +  while (this->available_()) {
    +    uint8_t byte = this->read_byte_();
    +    if (this->parse_improv_serial_byte_(byte)) {
    +      this->last_read_byte_ = now;
    +    } else {
    +      this->rx_buffer_.clear();
    +    }
    +  }
    +
    +  if (this->state_ == improv::STATE_PROVISIONING) {
    +    if (wifi::global_wifi_component->is_connected()) {
    +      wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(),
    +                                                 this->connecting_sta_.get_password());
    +      this->connecting_sta_ = {};
    +      this->cancel_timeout("wifi-connect-timeout");
    +      this->set_state_(improv::STATE_PROVISIONED);
    +
    +      std::vector url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
    +      this->send_response_(url);
    +    }
    +  }
    +}
    +
    +std::vector ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
    +  std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
    +  std::vector urls = {url};
    +#ifdef USE_WEBSERVER
    +  auto ip = wifi::global_wifi_component->wifi_sta_ip();
    +  std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
    +  urls.push_back(webserver_url);
    +#endif
    +  std::vector data = improv::build_rpc_response(command, urls);
    +  return data;
    +}
    +
    +std::vector ImprovSerialComponent::build_version_info_() {
    +  std::vector infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
    +  std::vector data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos);
    +  return data;
    +};
    +
    +bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
    +  size_t at = this->rx_buffer_.size();
    +  this->rx_buffer_.push_back(byte);
    +  ESP_LOGD(TAG, "Improv Serial byte: 0x%02X", byte);
    +  const uint8_t *raw = &this->rx_buffer_[0];
    +  if (at == 0)
    +    return byte == 'I';
    +  if (at == 1)
    +    return byte == 'M';
    +  if (at == 2)
    +    return byte == 'P';
    +  if (at == 3)
    +    return byte == 'R';
    +  if (at == 4)
    +    return byte == 'O';
    +  if (at == 5)
    +    return byte == 'V';
    +
    +  if (at == 6)
    +    return byte == IMPROV_SERIAL_VERSION;
    +
    +  if (at == 7)
    +    return true;
    +  uint8_t type = raw[7];
    +
    +  if (at == 8)
    +    return true;
    +  uint8_t data_len = raw[8];
    +
    +  if (at < 8 + data_len)
    +    return true;
    +
    +  if (at == 8 + data_len) {
    +    if (type == TYPE_RPC) {
    +      this->set_error_(improv::ERROR_NONE);
    +      auto command = improv::parse_improv_data(&raw[9], data_len);
    +      return this->parse_improv_payload_(command);
    +    }
    +  }
    +  return true;
    +}
    +
    +bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
    +  switch (command.command) {
    +    case improv::BAD_CHECKSUM:
    +      ESP_LOGW(TAG, "Error decoding Improv payload");
    +      this->set_error_(improv::ERROR_INVALID_RPC);
    +      return false;
    +    case improv::WIFI_SETTINGS: {
    +      wifi::WiFiAP sta{};
    +      sta.set_ssid(command.ssid);
    +      sta.set_password(command.password);
    +      this->connecting_sta_ = sta;
    +
    +      wifi::global_wifi_component->set_sta(sta);
    +      wifi::global_wifi_component->start_scanning();
    +      this->set_state_(improv::STATE_PROVISIONING);
    +      ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
    +               command.password.c_str());
    +
    +      auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
    +      this->set_timeout("wifi-connect-timeout", 30000, f);
    +      return true;
    +    }
    +    case improv::GET_CURRENT_STATE:
    +      this->set_state_(this->state_);
    +      if (this->state_ == improv::STATE_PROVISIONED) {
    +        std::vector url = this->build_rpc_settings_response_(improv::GET_CURRENT_STATE);
    +        this->send_response_(url);
    +      }
    +      return true;
    +    case improv::GET_DEVICE_INFO: {
    +      std::vector info = this->build_version_info_();
    +      this->send_response_(info);
    +      return true;
    +    }
    +    default: {
    +      ESP_LOGW(TAG, "Unknown Improv payload");
    +      this->set_error_(improv::ERROR_UNKNOWN_RPC);
    +      return false;
    +    }
    +  }
    +}
    +
    +void ImprovSerialComponent::set_state_(improv::State state) {
    +  this->state_ = state;
    +
    +  std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'};
    +  data.resize(11);
    +  data[6] = IMPROV_SERIAL_VERSION;
    +  data[7] = TYPE_CURRENT_STATE;
    +  data[8] = 1;
    +  data[9] = state;
    +
    +  uint8_t checksum = 0x00;
    +  for (uint8_t d : data)
    +    checksum += d;
    +  data[10] = checksum;
    +
    +  this->write_data_(data);
    +}
    +
    +void ImprovSerialComponent::set_error_(improv::Error error) {
    +  std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'};
    +  data.resize(11);
    +  data[6] = IMPROV_SERIAL_VERSION;
    +  data[7] = TYPE_ERROR_STATE;
    +  data[8] = 1;
    +  data[9] = error;
    +
    +  uint8_t checksum = 0x00;
    +  for (uint8_t d : data)
    +    checksum += d;
    +  data[10] = checksum;
    +  this->write_data_(data);
    +}
    +
    +void ImprovSerialComponent::send_response_(std::vector &response) {
    +  std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'};
    +  data.resize(9);
    +  data[6] = IMPROV_SERIAL_VERSION;
    +  data[7] = TYPE_RPC_RESPONSE;
    +  data[8] = response.size();
    +  data.insert(data.end(), response.begin(), response.end());
    +  this->write_data_(data);
    +}
    +
    +void ImprovSerialComponent::on_wifi_connect_timeout_() {
    +  this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
    +  this->set_state_(improv::STATE_AUTHORIZED);
    +  ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
    +  wifi::global_wifi_component->clear_sta();
    +}
    +
    +ImprovSerialComponent *global_improv_serial_component =  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
    +    nullptr;                                             // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
    +
    +}  // namespace improv_serial
    +}  // namespace esphome
    diff --git a/esphome/components/improv_serial/improv_serial_component.h b/esphome/components/improv_serial/improv_serial_component.h
    new file mode 100644
    index 0000000000..539674e2d3
    --- /dev/null
    +++ b/esphome/components/improv_serial/improv_serial_component.h
    @@ -0,0 +1,69 @@
    +#pragma once
    +
    +#include "esphome/components/improv/improv.h"
    +#include "esphome/components/wifi/wifi_component.h"
    +#include "esphome/core/component.h"
    +#include "esphome/core/defines.h"
    +#include "esphome/core/helpers.h"
    +
    +#ifdef USE_ARDUINO
    +#include 
    +#endif
    +#ifdef USE_ESP_IDF
    +#include 
    +#endif
    +
    +namespace esphome {
    +namespace improv_serial {
    +
    +enum ImprovSerialType : uint8_t {
    +  TYPE_CURRENT_STATE = 0x01,
    +  TYPE_ERROR_STATE = 0x02,
    +  TYPE_RPC = 0x03,
    +  TYPE_RPC_RESPONSE = 0x04
    +};
    +
    +static const uint8_t IMPROV_SERIAL_VERSION = 1;
    +
    +class ImprovSerialComponent : public Component {
    + public:
    +  void setup() override;
    +  void loop() override;
    +  void dump_config() override;
    +
    +  float get_setup_priority() const override { return setup_priority::HARDWARE; }
    +
    + protected:
    +  bool parse_improv_serial_byte_(uint8_t byte);
    +  bool parse_improv_payload_(improv::ImprovCommand &command);
    +
    +  void set_state_(improv::State state);
    +  void set_error_(improv::Error error);
    +  void send_response_(std::vector &response);
    +  void on_wifi_connect_timeout_();
    +
    +  std::vector build_rpc_settings_response_(improv::Command command);
    +  std::vector build_version_info_();
    +
    +  int available_();
    +  uint8_t read_byte_();
    +  void write_data_(std::vector &data);
    +
    +#ifdef USE_ARDUINO
    +  HardwareSerial *hw_serial_{nullptr};
    +#endif
    +#ifdef USE_ESP_IDF
    +  uart_port_t uart_num_;
    +#endif
    +
    +  std::vector rx_buffer_;
    +  uint32_t last_read_byte_{0};
    +  wifi::WiFiAP connecting_sta_;
    +  improv::State state_{improv::STATE_AUTHORIZED};
    +};
    +
    +extern ImprovSerialComponent
    +    *global_improv_serial_component;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
    +
    +}  // namespace improv_serial
    +}  // namespace esphome
    diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp
    index 97ad4c2cb9..11c0733701 100644
    --- a/esphome/components/logger/logger.cpp
    +++ b/esphome/components/logger/logger.cpp
    @@ -221,7 +221,7 @@ UARTSelection Logger::get_uart() const { return this->uart_; }
     void Logger::add_on_log_callback(std::function &&callback) {
       this->log_callback_.add(std::move(callback));
     }
    -float Logger::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
    +float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
     const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
     #ifdef USE_ESP32
     const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART2"};
    diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py
    index 61b1fa5ad6..dc652e0312 100644
    --- a/esphome/components/web_server/__init__.py
    +++ b/esphome/components/web_server/__init__.py
    @@ -54,6 +54,8 @@ async def to_code(config):
         var = cg.new_Pvariable(config[CONF_ID], paren)
         await cg.register_component(var, config)
     
    +    cg.add_define("USE_WEBSERVER")
    +
         cg.add(paren.set_port(config[CONF_PORT]))
         cg.add_define("WEBSERVER_PORT", config[CONF_PORT])
         cg.add_define("USE_WEBSERVER")
    diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py
    index faf3cca280..7a9319f5e0 100644
    --- a/esphome/components/wifi/__init__.py
    +++ b/esphome/components/wifi/__init__.py
    @@ -140,7 +140,8 @@ def final_validate(config):
         has_sta = bool(config.get(CONF_NETWORKS, True))
         has_ap = CONF_AP in config
         has_improv = "esp32_improv" in fv.full_config.get()
    -    if (not has_sta) and (not has_ap) and (not has_improv):
    +    has_improv_serial = "improv_serial" in fv.full_config.get()
    +    if not (has_sta or has_ap or has_improv or has_improv_serial):
             raise cv.Invalid(
                 "Please specify at least an SSID or an Access Point to create."
             )
    diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp
    index 703afa99bc..36944e3633 100644
    --- a/esphome/components/wifi/wifi_component.cpp
    +++ b/esphome/components/wifi/wifi_component.cpp
    @@ -239,8 +239,6 @@ void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &pa
       sta.set_ssid(ssid);
       sta.set_password(password);
       this->set_sta(sta);
    -
    -  this->start_scanning();
     }
     
     void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) {
    diff --git a/esphome/core/defines.h b/esphome/core/defines.h
    index dc07bde196..94fac73906 100644
    --- a/esphome/core/defines.h
    +++ b/esphome/core/defines.h
    @@ -9,6 +9,7 @@
     #define ESPHOME_BOARD "dummy_board"
     #define ESPHOME_PROJECT_NAME "dummy project"
     #define ESPHOME_PROJECT_VERSION "v2"
    +#define ESPHOME_VARIANT "ESP32"
     
     // Feature flags
     #define USE_API
    diff --git a/script/ci-custom.py b/script/ci-custom.py
    index 8e9ca487a6..89550afd3d 100755
    --- a/script/ci-custom.py
    +++ b/script/ci-custom.py
    @@ -263,7 +263,11 @@ def highlight(s):
     @lint_re_check(
         r"^#define\s+([a-zA-Z0-9_]+)\s+([0-9bx]+)" + CPP_RE_EOL,
         include=cpp_include,
    -    exclude=["esphome/core/log.h", "esphome/components/socket/headers.h"],
    +    exclude=[
    +        "esphome/core/log.h",
    +        "esphome/components/socket/headers.h",
    +        "esphome/core/defines.h",
    +    ],
     )
     def lint_no_defines(fname, match):
         s = highlight(
    diff --git a/tests/test3.yaml b/tests/test3.yaml
    index 0b7f1ad71e..cf80c06aa8 100644
    --- a/tests/test3.yaml
    +++ b/tests/test3.yaml
    @@ -268,6 +268,8 @@ logger:
       level: DEBUG
       esp8266_store_log_strings_in_flash: true
     
    +improv_serial:
    +
     deep_sleep:
       run_duration: 20s
       sleep_duration: 50s
    
    From f310cacd411c743b0850b6bc26fcc3ada7078a1d Mon Sep 17 00:00:00 2001
    From: anatoly-savchenkov
     <48646998+anatoly-savchenkov@users.noreply.github.com>
    Date: Thu, 11 Nov 2021 00:01:47 +0300
    Subject: [PATCH 1683/1841] [ms5611] Re-implement conversion from ADC readings
     to sensor values (#2665)
    
    ---
     esphome/components/ms5611/ms5611.cpp | 52 +++++++++++++++++++---------
     1 file changed, 35 insertions(+), 17 deletions(-)
    
    diff --git a/esphome/components/ms5611/ms5611.cpp b/esphome/components/ms5611/ms5611.cpp
    index 1d7516dbe8..4b34e1d71a 100644
    --- a/esphome/components/ms5611/ms5611.cpp
    +++ b/esphome/components/ms5611/ms5611.cpp
    @@ -75,30 +75,48 @@ void MS5611Component::read_pressure_(uint32_t raw_temperature) {
       const uint32_t raw_pressure = (uint32_t(bytes[0]) << 16) | (uint32_t(bytes[1]) << 8) | (uint32_t(bytes[2]));
       this->calculate_values_(raw_temperature, raw_pressure);
     }
    +
    +// Calculations are taken from the datasheet which can be found here:
    +// https://www.te.com/commerce/DocumentDelivery/DDEController?Action=showdoc&DocId=Data+Sheet%7FMS5611-01BA03%7FB3%7Fpdf%7FEnglish%7FENG_DS_MS5611-01BA03_B3.pdf%7FCAT-BLPS0036
    +// Sections PRESSURE AND TEMPERATURE CALCULATION and SECOND ORDER TEMPERATURE COMPENSATION
    +// Variable names below match variable names from the datasheet but lowercased
     void MS5611Component::calculate_values_(uint32_t raw_temperature, uint32_t raw_pressure) {
    -  const int32_t d_t = int32_t(raw_temperature) - (uint32_t(this->prom_[4]) << 8);
    -  float temperature = (2000 + (int64_t(d_t) * this->prom_[5]) / 8388608.0f) / 100.0f;
    +  const uint32_t c1 = uint32_t(this->prom_[0]);
    +  const uint32_t c2 = uint32_t(this->prom_[1]);
    +  const uint16_t c3 = uint16_t(this->prom_[2]);
    +  const uint16_t c4 = uint16_t(this->prom_[3]);
    +  const int32_t c5 = int32_t(this->prom_[4]);
    +  const uint16_t c6 = uint16_t(this->prom_[5]);
    +  const uint32_t d1 = raw_pressure;
    +  const int32_t d2 = raw_temperature;
     
    -  float pressure_offset = (uint32_t(this->prom_[1]) << 16) + ((this->prom_[3] * d_t) >> 7);
    -  float pressure_sensitivity = (uint32_t(this->prom_[0]) << 15) + ((this->prom_[2] * d_t) >> 8);
    +  // Promote dt to 64 bit here to make the math below cleaner
    +  const int64_t dt = d2 - (c5 << 8);
    +  int32_t temp = (2000 + ((dt * c6) >> 23));
     
    -  if (temperature < 20.0f) {
    -    const float t2 = (d_t * d_t) / 2147483648.0f;
    -    const float temp20 = (temperature - 20.0f) * 100.0f;
    -    float pressure_offset_2 = 2.5f * temp20 * temp20;
    -    float pressure_sensitivity_2 = 1.25f * temp20 * temp20;
    -    if (temp20 < -15.0f) {
    -      const float temp15 = (temperature + 15.0f) * 100.0f;
    -      pressure_offset_2 += 7.0f * temp15;
    -      pressure_sensitivity_2 += 5.5f * temp15;
    +  int64_t off = (c2 << 16) + ((dt * c4) >> 7);
    +  int64_t sens = (c1 << 15) + ((dt * c3) >> 8);
    +
    +  if (temp < 2000) {
    +    const int32_t t2 = (dt * dt) >> 31;
    +    int32_t off2 = ((5 * (temp - 2000) * (temp - 2000)) >> 1);
    +    int32_t sens2 = ((5 * (temp - 2000) * (temp - 2000)) >> 2);
    +    if (temp < -1500) {
    +      off2 = (off2 + 7 * (temp + 1500) * (temp + 1500));
    +      sens2 = sens2 + ((11 * (temp + 1500) * (temp + 1500)) >> 1);
         }
    -    temperature -= t2;
    -    pressure_offset -= pressure_offset_2;
    -    pressure_sensitivity -= pressure_sensitivity_2;
    +    temp = temp - t2;
    +    off = off - off2;
    +    sens = sens - sens2;
       }
     
    -  const float pressure = ((raw_pressure * pressure_sensitivity) / 2097152.0f - pressure_offset) / 3276800.0f;
    +  // Here we multiply unsigned 32-bit by signed 64-bit using signed 64-bit math.
    +  // Possible ranges of D1 and SENS from the datasheet guarantee
    +  // that this multiplication does not overflow
    +  const int32_t p = ((((d1 * sens) >> 21) - off) >> 15);
     
    +  const float temperature = temp / 100.0f;
    +  const float pressure = p / 100.0f;
       ESP_LOGD(TAG, "Got temperature=%0.02f°C pressure=%0.01fhPa", temperature, pressure);
     
       if (this->temperature_sensor_ != nullptr)
    
    From 04ba53c870344ef23d2eb1a19b7573310485770b Mon Sep 17 00:00:00 2001
    From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Date: Thu, 11 Nov 2021 10:10:05 +1300
    Subject: [PATCH 1684/1841] Bump version to 2021.12.0-dev
    
    ---
     esphome/const.py | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/esphome/const.py b/esphome/const.py
    index ff8510b40e..99cb53fe4b 100644
    --- a/esphome/const.py
    +++ b/esphome/const.py
    @@ -1,6 +1,6 @@
     """Constants used by esphome."""
     
    -__version__ = "2021.11.0-dev"
    +__version__ = "2021.12.0-dev"
     
     ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
     
    
    From 4395d6156daa3c283a55a4f16950455598c32120 Mon Sep 17 00:00:00 2001
    From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Date: Thu, 11 Nov 2021 11:24:48 +1300
    Subject: [PATCH 1685/1841] Fix template number initial value being NaN (#2692)
    
    ---
     esphome/components/template/number/__init__.py | 6 ++++--
     1 file changed, 4 insertions(+), 2 deletions(-)
    
    diff --git a/esphome/components/template/number/__init__.py b/esphome/components/template/number/__init__.py
    index 887f6b15ad..3dec7066d3 100644
    --- a/esphome/components/template/number/__init__.py
    +++ b/esphome/components/template/number/__init__.py
    @@ -35,6 +35,9 @@ def validate(config):
                 raise cv.Invalid("initial_value cannot be used with lambda")
             if CONF_RESTORE_VALUE in config:
                 raise cv.Invalid("restore_value cannot be used with lambda")
    +    elif CONF_INITIAL_VALUE not in config:
    +        config[CONF_INITIAL_VALUE] = config[CONF_MIN_VALUE]
    +
         if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config:
             raise cv.Invalid(
                 "Either optimistic mode must be enabled, or set_action must be set, to handle the number being set."
    @@ -80,8 +83,7 @@ async def to_code(config):
     
         else:
             cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
    -        if CONF_INITIAL_VALUE in config:
    -            cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE]))
    +        cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE]))
             if CONF_RESTORE_VALUE in config:
                 cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
     
    
    From abf3708cc2b80f69d061800e7aa6c85293440499 Mon Sep 17 00:00:00 2001
    From: Carlos Garcia Saura 
    Date: Wed, 10 Nov 2021 23:28:45 +0100
    Subject: [PATCH 1686/1841] [remote_transmitter] accurate pulse timing for
     ESP8266 (#2476)
    
    ---
     .../remote_transmitter/remote_transmitter.h   |  3 +
     .../remote_transmitter_esp8266.cpp            | 78 ++++++++++---------
     2 files changed, 46 insertions(+), 35 deletions(-)
    
    diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h
    index 733ac5e50d..a4235e875f 100644
    --- a/esphome/components/remote_transmitter/remote_transmitter.h
    +++ b/esphome/components/remote_transmitter/remote_transmitter.h
    @@ -32,6 +32,9 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
       void mark_(uint32_t on_time, uint32_t off_time, uint32_t usec);
     
       void space_(uint32_t usec);
    +
    +  void await_target_time_();
    +  uint32_t target_time_;
     #endif
     
     #ifdef USE_ESP32
    diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp
    index 74e62d4e3b..39752cac5b 100644
    --- a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp
    +++ b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp
    @@ -33,56 +33,64 @@ void RemoteTransmitterComponent::calculate_on_off_time_(uint32_t carrier_frequen
       *off_time_period = period - *on_time_period;
     }
     
    +void RemoteTransmitterComponent::await_target_time_() {
    +  const uint32_t current_time = micros();
    +  if (this->target_time_ == 0)
    +    this->target_time_ = current_time;
    +  else if (this->target_time_ > current_time)
    +    delayMicroseconds(this->target_time_ - current_time);
    +}
    +
     void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint32_t usec) {
    -  if (this->carrier_duty_percent_ == 100 || (on_time == 0 && off_time == 0)) {
    -    this->pin_->digital_write(true);
    -    delayMicroseconds(usec);
    -    this->pin_->digital_write(false);
    -    return;
    -  }
    -
    -  const uint32_t start_time = micros();
    -  uint32_t current_time = start_time;
    -
    -  while (current_time - start_time < usec) {
    -    const uint32_t elapsed = current_time - start_time;
    -    this->pin_->digital_write(true);
    -
    -    delayMicroseconds(std::min(on_time, usec - elapsed));
    -    this->pin_->digital_write(false);
    -    if (elapsed + on_time >= usec)
    -      return;
    -
    -    delayMicroseconds(std::min(usec - elapsed - on_time, off_time));
    -
    -    current_time = micros();
    +  this->await_target_time_();
    +  this->pin_->digital_write(true);
    +
    +  const uint32_t target = this->target_time_ + usec;
    +  if (this->carrier_duty_percent_ < 100 && (on_time > 0 || off_time > 0)) {
    +    while (true) {  // Modulate with carrier frequency
    +      this->target_time_ += on_time;
    +      if (this->target_time_ >= target)
    +        break;
    +      this->await_target_time_();
    +      this->pin_->digital_write(false);
    +
    +      this->target_time_ += off_time;
    +      if (this->target_time_ >= target)
    +        break;
    +      this->await_target_time_();
    +      this->pin_->digital_write(true);
    +    }
       }
    +  this->target_time_ = target;
     }
    +
     void RemoteTransmitterComponent::space_(uint32_t usec) {
    +  this->await_target_time_();
       this->pin_->digital_write(false);
    -  delayMicroseconds(usec);
    +  this->target_time_ += usec;
     }
    +
     void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
       ESP_LOGD(TAG, "Sending remote code...");
       uint32_t on_time, off_time;
       this->calculate_on_off_time_(this->temp_.get_carrier_frequency(), &on_time, &off_time);
    +  this->target_time_ = 0;
       for (uint32_t i = 0; i < send_times; i++) {
    -    {
    -      InterruptLock lock;
    -      for (int32_t item : this->temp_.get_data()) {
    -        if (item > 0) {
    -          const auto length = uint32_t(item);
    -          this->mark_(on_time, off_time, length);
    -        } else {
    -          const auto length = uint32_t(-item);
    -          this->space_(length);
    -        }
    -        App.feed_wdt();
    +    for (int32_t item : this->temp_.get_data()) {
    +      if (item > 0) {
    +        const auto length = uint32_t(item);
    +        this->mark_(on_time, off_time, length);
    +      } else {
    +        const auto length = uint32_t(-item);
    +        this->space_(length);
           }
    +      App.feed_wdt();
         }
    +    this->await_target_time_();  // wait for duration of last pulse
    +    this->pin_->digital_write(false);
     
         if (i + 1 < send_times)
    -      delayMicroseconds(send_wait);
    +      this->target_time_ += send_wait;
       }
     }
     
    
    From e99af991ecc63c942b64dfba17ad1d70261f415e Mon Sep 17 00:00:00 2001
    From: Maurice Makaay 
    Date: Wed, 10 Nov 2021 23:34:17 +0100
    Subject: [PATCH 1687/1841] Uart debugging support (#2478)
    
    Co-authored-by: Maurice Makaay 
    Co-authored-by: Maurice Makaay 
    Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    ---
     esphome/components/uart/__init__.py           |  69 +++++++
     esphome/components/uart/uart.h                |   6 +-
     esphome/components/uart/uart_component.h      |  21 ++
     .../uart/uart_component_esp32_arduino.cpp     |  12 +-
     .../uart/uart_component_esp8266.cpp           |   9 +-
     .../uart/uart_component_esp_idf.cpp           |  12 +-
     esphome/components/uart/uart_debugger.cpp     | 193 ++++++++++++++++++
     esphome/components/uart/uart_debugger.h       | 101 +++++++++
     esphome/const.py                              |   7 +
     esphome/core/defines.h                        |   1 +
     tests/test1.yaml                              |  12 ++
     11 files changed, 430 insertions(+), 13 deletions(-)
     create mode 100644 esphome/components/uart/uart_debugger.cpp
     create mode 100644 esphome/components/uart/uart_debugger.h
    
    diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py
    index 35af3eedf7..53209dfc7b 100644
    --- a/esphome/components/uart/__init__.py
    +++ b/esphome/components/uart/__init__.py
    @@ -14,6 +14,16 @@ from esphome.const import (
         CONF_DATA,
         CONF_RX_BUFFER_SIZE,
         CONF_INVERT,
    +    CONF_TRIGGER_ID,
    +    CONF_SEQUENCE,
    +    CONF_TIMEOUT,
    +    CONF_DEBUG,
    +    CONF_DIRECTION,
    +    CONF_AFTER,
    +    CONF_BYTES,
    +    CONF_DELIMITER,
    +    CONF_DUMMY_RECEIVER,
    +    CONF_DUMMY_RECEIVER_ID,
     )
     from esphome.core import CORE
     
    @@ -31,6 +41,8 @@ ESP8266UartComponent = uart_ns.class_(
     
     UARTDevice = uart_ns.class_("UARTDevice")
     UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action)
    +UARTDebugger = uart_ns.class_("UARTDebugger", cg.Component, automation.Action)
    +UARTDummyReceiver = uart_ns.class_("UARTDummyReceiver", cg.Component)
     MULTI_CONF = True
     
     
    @@ -75,6 +87,34 @@ CONF_STOP_BITS = "stop_bits"
     CONF_DATA_BITS = "data_bits"
     CONF_PARITY = "parity"
     
    +UARTDirection = uart_ns.enum("UARTDirection")
    +UART_DIRECTIONS = {
    +    "RX": UARTDirection.UART_DIRECTION_RX,
    +    "TX": UARTDirection.UART_DIRECTION_TX,
    +    "BOTH": UARTDirection.UART_DIRECTION_BOTH,
    +}
    +
    +DEBUG_SCHEMA = cv.Schema(
    +    {
    +        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UARTDebugger),
    +        cv.Optional(CONF_DIRECTION, default="BOTH"): cv.enum(
    +            UART_DIRECTIONS, upper=True
    +        ),
    +        cv.Optional(CONF_AFTER): cv.Schema(
    +            {
    +                cv.Optional(CONF_BYTES, default=256): cv.validate_bytes,
    +                cv.Optional(
    +                    CONF_TIMEOUT, default="100ms"
    +                ): cv.positive_time_period_milliseconds,
    +                cv.Optional(CONF_DELIMITER): cv.templatable(validate_raw_data),
    +            }
    +        ),
    +        cv.Required(CONF_SEQUENCE): automation.validate_automation(),
    +        cv.Optional(CONF_DUMMY_RECEIVER, default=False): cv.boolean,
    +        cv.GenerateID(CONF_DUMMY_RECEIVER_ID): cv.declare_id(UARTDummyReceiver),
    +    }
    +)
    +
     CONFIG_SCHEMA = cv.All(
         cv.Schema(
             {
    @@ -91,12 +131,38 @@ CONFIG_SCHEMA = cv.All(
                 cv.Optional(CONF_INVERT): cv.invalid(
                     "This option has been removed. Please instead use invert in the tx/rx pin schemas."
                 ),
    +            cv.Optional(CONF_DEBUG): DEBUG_SCHEMA,
             }
         ).extend(cv.COMPONENT_SCHEMA),
         cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN),
     )
     
     
    +async def debug_to_code(config, parent):
    +    trigger = cg.new_Pvariable(config[CONF_TRIGGER_ID], parent)
    +    await cg.register_component(trigger, config)
    +    for action in config[CONF_SEQUENCE]:
    +        await automation.build_automation(
    +            trigger,
    +            [(UARTDirection, "direction"), (cg.std_vector.template(cg.uint8), "bytes")],
    +            action,
    +        )
    +    cg.add(trigger.set_direction(config[CONF_DIRECTION]))
    +    after = config[CONF_AFTER]
    +    cg.add(trigger.set_after_bytes(after[CONF_BYTES]))
    +    cg.add(trigger.set_after_timeout(after[CONF_TIMEOUT]))
    +    if CONF_DELIMITER in after:
    +        data = after[CONF_DELIMITER]
    +        if isinstance(data, bytes):
    +            data = list(data)
    +        for byte in after[CONF_DELIMITER]:
    +            cg.add(trigger.add_delimiter_byte(byte))
    +    if config[CONF_DUMMY_RECEIVER]:
    +        dummy = cg.new_Pvariable(config[CONF_DUMMY_RECEIVER_ID], parent)
    +        await cg.register_component(dummy, {})
    +    cg.add_define("USE_UART_DEBUGGER")
    +
    +
     async def to_code(config):
         cg.add_global(uart_ns.using)
         var = cg.new_Pvariable(config[CONF_ID])
    @@ -115,6 +181,9 @@ async def to_code(config):
         cg.add(var.set_data_bits(config[CONF_DATA_BITS]))
         cg.add(var.set_parity(config[CONF_PARITY]))
     
    +    if CONF_DEBUG in config:
    +        await debug_to_code(config[CONF_DEBUG], var)
    +
     
     # A schema to use for all UART devices, all UART integrations must extend this!
     UART_DEVICE_SCHEMA = cv.Schema(
    diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h
    index c368f9ed6b..d41dbe26e6 100644
    --- a/esphome/components/uart/uart.h
    +++ b/esphome/components/uart/uart.h
    @@ -45,17 +45,17 @@ class UARTDevice {
       // Compat APIs
       int read() {
         uint8_t data;
    -    if (!read_byte(&data))
    +    if (!this->read_byte(&data))
           return -1;
         return data;
       }
       size_t write(uint8_t data) {
    -    write_byte(data);
    +    this->write_byte(data);
         return 1;
       }
       int peek() {
         uint8_t data;
    -    if (!peek_byte(&data))
    +    if (!this->peek_byte(&data))
           return -1;
         return data;
       }
    diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h
    index de85cd2ca3..73694d3db7 100644
    --- a/esphome/components/uart/uart_component.h
    +++ b/esphome/components/uart/uart_component.h
    @@ -2,9 +2,13 @@
     
     #include 
     #include 
    +#include "esphome/core/defines.h"
     #include "esphome/core/component.h"
     #include "esphome/core/hal.h"
     #include "esphome/core/log.h"
    +#ifdef USE_UART_DEBUGGER
    +#include "esphome/core/automation.h"
    +#endif
     
     namespace esphome {
     namespace uart {
    @@ -15,6 +19,14 @@ enum UARTParityOptions {
       UART_CONFIG_PARITY_ODD,
     };
     
    +#ifdef USE_UART_DEBUGGER
    +enum UARTDirection {
    +  UART_DIRECTION_RX,
    +  UART_DIRECTION_TX,
    +  UART_DIRECTION_BOTH,
    +};
    +#endif
    +
     const LogString *parity_to_str(UARTParityOptions parity);
     
     class UARTComponent {
    @@ -50,6 +62,12 @@ class UARTComponent {
       void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; }
       uint32_t get_baud_rate() const { return baud_rate_; }
     
    +#ifdef USE_UART_DEBUGGER
    +  void add_debug_callback(std::function &&callback) {
    +    this->debug_callback_.add(std::move(callback));
    +  }
    +#endif
    +
      protected:
       virtual void check_logger_conflict() = 0;
       bool check_read_timeout_(size_t len = 1);
    @@ -61,6 +79,9 @@ class UARTComponent {
       uint8_t stop_bits_;
       uint8_t data_bits_;
       UARTParityOptions parity_;
    +#ifdef USE_UART_DEBUGGER
    +  CallbackManager debug_callback_{};
    +#endif
     };
     
     }  // namespace uart
    diff --git a/esphome/components/uart/uart_component_esp32_arduino.cpp b/esphome/components/uart/uart_component_esp32_arduino.cpp
    index 1b1ce382f2..95cdde4a43 100644
    --- a/esphome/components/uart/uart_component_esp32_arduino.cpp
    +++ b/esphome/components/uart/uart_component_esp32_arduino.cpp
    @@ -117,26 +117,32 @@ void ESP32ArduinoUARTComponent::dump_config() {
     
     void ESP32ArduinoUARTComponent::write_array(const uint8_t *data, size_t len) {
       this->hw_serial_->write(data, len);
    +#ifdef USE_UART_DEBUGGER
       for (size_t i = 0; i < len; i++) {
    -    ESP_LOGVV(TAG, "    Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]);
    +    this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
       }
    +#endif
     }
    +
     bool ESP32ArduinoUARTComponent::peek_byte(uint8_t *data) {
       if (!this->check_read_timeout_())
         return false;
       *data = this->hw_serial_->peek();
       return true;
     }
    +
     bool ESP32ArduinoUARTComponent::read_array(uint8_t *data, size_t len) {
       if (!this->check_read_timeout_(len))
         return false;
       this->hw_serial_->readBytes(data, len);
    +#ifdef USE_UART_DEBUGGER
       for (size_t i = 0; i < len; i++) {
    -    ESP_LOGVV(TAG, "    Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]);
    +    this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
       }
    -
    +#endif
       return true;
     }
    +
     int ESP32ArduinoUARTComponent::available() { return this->hw_serial_->available(); }
     void ESP32ArduinoUARTComponent::flush() {
       ESP_LOGVV(TAG, "    Flushing...");
    diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp
    index 973306cde2..c367de05bb 100644
    --- a/esphome/components/uart/uart_component_esp8266.cpp
    +++ b/esphome/components/uart/uart_component_esp8266.cpp
    @@ -130,9 +130,11 @@ void ESP8266UartComponent::write_array(const uint8_t *data, size_t len) {
         for (size_t i = 0; i < len; i++)
           this->sw_serial_->write_byte(data[i]);
       }
    +#ifdef USE_UART_DEBUGGER
       for (size_t i = 0; i < len; i++) {
    -    ESP_LOGVV(TAG, "    Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]);
    +    this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
       }
    +#endif
     }
     bool ESP8266UartComponent::peek_byte(uint8_t *data) {
       if (!this->check_read_timeout_())
    @@ -153,10 +155,11 @@ bool ESP8266UartComponent::read_array(uint8_t *data, size_t len) {
         for (size_t i = 0; i < len; i++)
           data[i] = this->sw_serial_->read_byte();
       }
    +#ifdef USE_UART_DEBUGGER
       for (size_t i = 0; i < len; i++) {
    -    ESP_LOGVV(TAG, "    Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]);
    +    this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
       }
    -
    +#endif
       return true;
     }
     int ESP8266UartComponent::available() {
    diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp
    index 1cccd5821e..4d6a6af0fc 100644
    --- a/esphome/components/uart/uart_component_esp_idf.cpp
    +++ b/esphome/components/uart/uart_component_esp_idf.cpp
    @@ -130,10 +130,13 @@ void IDFUARTComponent::write_array(const uint8_t *data, size_t len) {
       xSemaphoreTake(this->lock_, portMAX_DELAY);
       uart_write_bytes(this->uart_num_, data, len);
       xSemaphoreGive(this->lock_);
    +#ifdef USE_UART_DEBUGGER
       for (size_t i = 0; i < len; i++) {
    -    ESP_LOGVV(TAG, "    Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]);
    +    this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
       }
    +#endif
     }
    +
     bool IDFUARTComponent::peek_byte(uint8_t *data) {
       if (!this->check_read_timeout_())
         return false;
    @@ -152,6 +155,7 @@ bool IDFUARTComponent::peek_byte(uint8_t *data) {
       xSemaphoreGive(this->lock_);
       return true;
     }
    +
     bool IDFUARTComponent::read_array(uint8_t *data, size_t len) {
       size_t length_to_read = len;
       if (!this->check_read_timeout_(len))
    @@ -165,12 +169,12 @@ bool IDFUARTComponent::read_array(uint8_t *data, size_t len) {
       }
       if (length_to_read > 0)
         uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_RATE_MS);
    -
       xSemaphoreGive(this->lock_);
    +#ifdef USE_UART_DEBUGGER
       for (size_t i = 0; i < len; i++) {
    -    ESP_LOGVV(TAG, "    Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]);
    +    this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
       }
    -
    +#endif
       return true;
     }
     
    diff --git a/esphome/components/uart/uart_debugger.cpp b/esphome/components/uart/uart_debugger.cpp
    new file mode 100644
    index 0000000000..9a535656a2
    --- /dev/null
    +++ b/esphome/components/uart/uart_debugger.cpp
    @@ -0,0 +1,193 @@
    +#include "esphome/core/defines.h"
    +#ifdef USE_UART_DEBUGGER
    +
    +#include 
    +#include "uart_debugger.h"
    +#include "esphome/core/helpers.h"
    +#include "esphome/core/log.h"
    +
    +namespace esphome {
    +namespace uart {
    +
    +static const char *const TAG = "uart_debug";
    +
    +UARTDebugger::UARTDebugger(UARTComponent *parent) {
    +  parent->add_debug_callback([this](UARTDirection direction, uint8_t byte) {
    +    if (!this->is_my_direction_(direction) || this->is_recursive_()) {
    +      return;
    +    }
    +    this->trigger_after_direction_change_(direction);
    +    this->store_byte_(direction, byte);
    +    this->trigger_after_delimiter_(byte);
    +    this->trigger_after_bytes_();
    +  });
    +}
    +
    +void UARTDebugger::loop() { this->trigger_after_timeout_(); }
    +
    +bool UARTDebugger::is_my_direction_(UARTDirection direction) {
    +  return this->for_direction_ == UART_DIRECTION_BOTH || this->for_direction_ == direction;
    +}
    +
    +bool UARTDebugger::is_recursive_() { return this->is_triggering_; }
    +
    +void UARTDebugger::trigger_after_direction_change_(UARTDirection direction) {
    +  if (this->has_buffered_bytes_() && this->for_direction_ == UART_DIRECTION_BOTH &&
    +      this->last_direction_ != direction) {
    +    this->fire_trigger_();
    +  }
    +}
    +
    +void UARTDebugger::store_byte_(UARTDirection direction, uint8_t byte) {
    +  this->bytes_.push_back(byte);
    +  this->last_direction_ = direction;
    +  this->last_time_ = millis();
    +}
    +
    +void UARTDebugger::trigger_after_delimiter_(uint8_t byte) {
    +  if (this->after_delimiter_.empty() || !this->has_buffered_bytes_()) {
    +    return;
    +  }
    +  if (this->after_delimiter_[this->after_delimiter_pos_] != byte) {
    +    this->after_delimiter_pos_ = 0;
    +    return;
    +  }
    +  this->after_delimiter_pos_++;
    +  if (this->after_delimiter_pos_ == this->after_delimiter_.size()) {
    +    this->fire_trigger_();
    +    this->after_delimiter_pos_ = 0;
    +  }
    +}
    +
    +void UARTDebugger::trigger_after_bytes_() {
    +  if (this->has_buffered_bytes_() && this->after_bytes_ > 0 && this->bytes_.size() >= this->after_bytes_) {
    +    this->fire_trigger_();
    +  }
    +}
    +
    +void UARTDebugger::trigger_after_timeout_() {
    +  if (this->has_buffered_bytes_() && this->after_timeout_ > 0 && millis() - this->last_time_ >= this->after_timeout_) {
    +    this->fire_trigger_();
    +  }
    +}
    +
    +bool UARTDebugger::has_buffered_bytes_() { return !this->bytes_.empty(); }
    +
    +void UARTDebugger::fire_trigger_() {
    +  this->is_triggering_ = true;
    +  trigger(this->last_direction_, this->bytes_);
    +  this->bytes_.clear();
    +  this->is_triggering_ = false;
    +}
    +
    +void UARTDummyReceiver::loop() {
    +  // Reading up to a limited number of bytes, to make sure that this loop()
    +  // won't lock up the system on a continuous incoming stream of bytes.
    +  uint8_t data;
    +  int count = 50;
    +  while (this->available() && count--) {
    +    this->read_byte(&data);
    +  }
    +}
    +
    +void UARTDebug::log_hex(UARTDirection direction, std::vector bytes, uint8_t separator) {
    +  std::string res;
    +  if (direction == UART_DIRECTION_RX) {
    +    res += "<<< ";
    +  } else {
    +    res += ">>> ";
    +  }
    +  size_t len = bytes.size();
    +  char buf[5];
    +  for (size_t i = 0; i < len; i++) {
    +    if (i > 0) {
    +      res += separator;
    +    }
    +    sprintf(buf, "%02X", bytes[i]);
    +    res += buf;
    +  }
    +  ESP_LOGD(TAG, "%s", res.c_str());
    +}
    +
    +void UARTDebug::log_string(UARTDirection direction, std::vector bytes) {
    +  std::string res;
    +  if (direction == UART_DIRECTION_RX) {
    +    res += "<<< \"";
    +  } else {
    +    res += ">>> \"";
    +  }
    +  size_t len = bytes.size();
    +  char buf[5];
    +  for (size_t i = 0; i < len; i++) {
    +    if (bytes[i] == 7) {
    +      res += "\\a";
    +    } else if (bytes[i] == 8) {
    +      res += "\\b";
    +    } else if (bytes[i] == 9) {
    +      res += "\\t";
    +    } else if (bytes[i] == 10) {
    +      res += "\\n";
    +    } else if (bytes[i] == 11) {
    +      res += "\\v";
    +    } else if (bytes[i] == 12) {
    +      res += "\\f";
    +    } else if (bytes[i] == 13) {
    +      res += "\\r";
    +    } else if (bytes[i] == 27) {
    +      res += "\\e";
    +    } else if (bytes[i] == 34) {
    +      res += "\\\"";
    +    } else if (bytes[i] == 39) {
    +      res += "\\'";
    +    } else if (bytes[i] == 92) {
    +      res += "\\\\";
    +    } else if (bytes[i] < 32 || bytes[i] > 127) {
    +      sprintf(buf, "\\x%02X", bytes[i]);
    +      res += buf;
    +    } else {
    +      res += bytes[i];
    +    }
    +  }
    +  res += '"';
    +  ESP_LOGD(TAG, "%s", res.c_str());
    +}
    +
    +void UARTDebug::log_int(UARTDirection direction, std::vector bytes, uint8_t separator) {
    +  std::string res;
    +  size_t len = bytes.size();
    +  if (direction == UART_DIRECTION_RX) {
    +    res += "<<< ";
    +  } else {
    +    res += ">>> ";
    +  }
    +  for (size_t i = 0; i < len; i++) {
    +    if (i > 0) {
    +      res += separator;
    +    }
    +    res += to_string(bytes[i]);
    +  }
    +  ESP_LOGD(TAG, "%s", res.c_str());
    +}
    +
    +void UARTDebug::log_binary(UARTDirection direction, std::vector bytes, uint8_t separator) {
    +  std::string res;
    +  size_t len = bytes.size();
    +  if (direction == UART_DIRECTION_RX) {
    +    res += "<<< ";
    +  } else {
    +    res += ">>> ";
    +  }
    +  char buf[20];
    +  for (size_t i = 0; i < len; i++) {
    +    if (i > 0) {
    +      res += separator;
    +    }
    +    sprintf(buf, "0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(bytes[i]), bytes[i]);
    +    res += buf;
    +  }
    +  ESP_LOGD(TAG, "%s", res.c_str());
    +}
    +
    +}  // namespace uart
    +}  // namespace esphome
    +#endif
    diff --git a/esphome/components/uart/uart_debugger.h b/esphome/components/uart/uart_debugger.h
    new file mode 100644
    index 0000000000..6e84bbe450
    --- /dev/null
    +++ b/esphome/components/uart/uart_debugger.h
    @@ -0,0 +1,101 @@
    +#pragma once
    +#include "esphome/core/defines.h"
    +#ifdef USE_UART_DEBUGGER
    +
    +#include 
    +#include "esphome/core/component.h"
    +#include "esphome/core/automation.h"
    +#include "uart.h"
    +#include "uart_component.h"
    +
    +namespace esphome {
    +namespace uart {
    +
    +/// The UARTDebugger class adds debugging support to a UART bus.
    +///
    +/// It accumulates bytes that travel over the UART bus and triggers one or
    +/// more actions that can log the data at an appropriate time. What
    +/// 'appropriate time' means exactly, is determined by a number of
    +/// configurable constraints. E.g. when a given number of bytes is gathered
    +/// and/or when no more data has been seen for a given time interval.
    +class UARTDebugger : public Component, public Trigger> {
    + public:
    +  explicit UARTDebugger(UARTComponent *parent);
    +  void loop() override;
    +
    +  /// Set the direction in which to inspect the bytes: incoming, outgoing
    +  /// or both. When debugging in both directions, logging will be triggered
    +  /// when the direction of the data stream changes.
    +  void set_direction(UARTDirection direction) { this->for_direction_ = direction; }
    +
    +  /// Set the maximum number of bytes to accumulate. When the number of bytes
    +  /// is reached, logging will be triggered.
    +  void set_after_bytes(size_t size) { this->after_bytes_ = size; }
    +
    +  /// Set a timeout for the data stream. When no new bytes are seen during
    +  /// this timeout, logging will be triggered.
    +  void set_after_timeout(uint32_t timeout) { this->after_timeout_ = timeout; }
    +
    +  /// Add a delimiter byte. This can be called multiple times to setup a
    +  /// multi-byte delimiter (a typical example would be '\r\n').
    +  /// When the constructued byte sequence is found in the data stream,
    +  /// logging will be triggered.
    +  void add_delimiter_byte(uint8_t byte) { this->after_delimiter_.push_back(byte); }
    +
    + protected:
    +  UARTDirection for_direction_;
    +  UARTDirection last_direction_{};
    +  std::vector bytes_{};
    +  size_t after_bytes_;
    +  uint32_t after_timeout_;
    +  uint32_t last_time_{};
    +  std::vector after_delimiter_{};
    +  size_t after_delimiter_pos_{};
    +  bool is_triggering_{false};
    +
    +  bool is_my_direction_(UARTDirection direction);
    +  bool is_recursive_();
    +  void store_byte_(UARTDirection direction, uint8_t byte);
    +  void trigger_after_direction_change_(UARTDirection direction);
    +  void trigger_after_delimiter_(uint8_t byte);
    +  void trigger_after_bytes_();
    +  void trigger_after_timeout_();
    +  bool has_buffered_bytes_();
    +  void fire_trigger_();
    +};
    +
    +/// This UARTDevice is used by the serial debugger to read data from a
    +/// serial interface when the 'dummy_receiver' option is enabled.
    +/// The data are not stored, nor processed. This is most useful when the
    +/// debugger is used to reverse engineer a serial protocol, for which no
    +/// specific UARTDevice implementation exists (yet), but for which the
    +/// incoming bytes must be read to drive the debugger.
    +class UARTDummyReceiver : public Component, public UARTDevice {
    + public:
    +  UARTDummyReceiver(UARTComponent *parent) : UARTDevice(parent) {}
    +  void loop() override;
    +};
    +
    +/// This class contains some static methods, that can be used to easily
    +/// create a logging action for the debugger.
    +class UARTDebug {
    + public:
    +  /// Log the bytes as hex values, separated by the provided separator
    +  /// character.
    +  static void log_hex(UARTDirection direction, std::vector bytes, uint8_t separator);
    +
    +  /// Log the bytes as string values, escaping unprintable characters.
    +  static void log_string(UARTDirection direction, std::vector bytes);
    +
    +  /// Log the bytes as integer values, separated by the provided separator
    +  /// character.
    +  static void log_int(UARTDirection direction, std::vector bytes, uint8_t separator);
    +
    +  /// Log the bytes as ' ()' values, separated by the provided
    +  /// separator.
    +  static void log_binary(UARTDirection direction, std::vector bytes, uint8_t separator);
    +};
    +
    +}  // namespace uart
    +}  // namespace esphome
    +#endif
    diff --git a/esphome/const.py b/esphome/const.py
    index 99cb53fe4b..9951747ee2 100644
    --- a/esphome/const.py
    +++ b/esphome/const.py
    @@ -34,6 +34,7 @@ ARDUINO_VERSION_ESP8266 = {
     SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"}
     HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}
     
    +
     CONF_ABOVE = "above"
     CONF_ACCELERATION = "acceleration"
     CONF_ACCELERATION_X = "acceleration_x"
    @@ -47,6 +48,7 @@ CONF_ACTIVE_POWER = "active_power"
     CONF_ADDRESS = "address"
     CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id"
     CONF_ADVANCED = "advanced"
    +CONF_AFTER = "after"
     CONF_ALPHA = "alpha"
     CONF_ALTITUDE = "altitude"
     CONF_AND = "and"
    @@ -93,6 +95,7 @@ CONF_BUFFER_SIZE = "buffer_size"
     CONF_BUILD_PATH = "build_path"
     CONF_BUS_VOLTAGE = "bus_voltage"
     CONF_BUSY_PIN = "busy_pin"
    +CONF_BYTES = "bytes"
     CONF_CALCULATED_LUX = "calculated_lux"
     CONF_CALIBRATE_LINEAR = "calibrate_linear"
     CONF_CALIBRATION = "calibration"
    @@ -164,6 +167,7 @@ CONF_DAYS_OF_WEEK = "days_of_week"
     CONF_DC_PIN = "dc_pin"
     CONF_DEASSERT_RTS_DTR = "deassert_rts_dtr"
     CONF_DEBOUNCE = "debounce"
    +CONF_DEBUG = "debug"
     CONF_DECAY_MODE = "decay_mode"
     CONF_DECELERATION = "deceleration"
     CONF_DEFAULT_MODE = "default_mode"
    @@ -171,6 +175,7 @@ CONF_DEFAULT_TARGET_TEMPERATURE_HIGH = "default_target_temperature_high"
     CONF_DEFAULT_TARGET_TEMPERATURE_LOW = "default_target_temperature_low"
     CONF_DEFAULT_TRANSITION_LENGTH = "default_transition_length"
     CONF_DELAY = "delay"
    +CONF_DELIMITER = "delimiter"
     CONF_DELTA = "delta"
     CONF_DEVICE = "device"
     CONF_DEVICE_CLASS = "device_class"
    @@ -192,6 +197,8 @@ CONF_DNS2 = "dns2"
     CONF_DOMAIN = "domain"
     CONF_DRY_ACTION = "dry_action"
     CONF_DRY_MODE = "dry_mode"
    +CONF_DUMMY_RECEIVER = "dummy_receiver"
    +CONF_DUMMY_RECEIVER_ID = "dummy_receiver_id"
     CONF_DUMP = "dump"
     CONF_DURATION = "duration"
     CONF_EAP = "eap"
    diff --git a/esphome/core/defines.h b/esphome/core/defines.h
    index 94fac73906..e679fe1cef 100644
    --- a/esphome/core/defines.h
    +++ b/esphome/core/defines.h
    @@ -37,6 +37,7 @@
     #define USE_SWITCH
     #define USE_TEXT_SENSOR
     #define USE_TIME
    +#define USE_UART_DEBUGGER
     #define USE_WEBSERVER
     #define USE_WIFI
     
    diff --git a/tests/test1.yaml b/tests/test1.yaml
    index dee7493bf2..04d928e1b8 100644
    --- a/tests/test1.yaml
    +++ b/tests/test1.yaml
    @@ -193,6 +193,18 @@ uart:
         data_bits: 8
         stop_bits: 1
         rx_buffer_size: 512
    +    debug:
    +      dummy_receiver: true
    +      direction: both
    +      after:
    +        bytes: 50
    +        timeout: 500ms
    +        delimiter: "\r\n"
    +      sequence:
    +        - lambda: UARTDebug::log_hex(direction, bytes, ':');
    +        - lambda: UARTDebug::log_string(direction, bytes);
    +        - lambda: UARTDebug::log_int(direction, bytes, ',');
    +        - lambda: UARTDebug::log_binary(direction, bytes, ';');
     
       - id: adalight_uart
         tx_pin: GPIO25
    
    From bb9793d5b7b7238074ce2fdc91a4f9ecee728d7d Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Wed, 10 Nov 2021 23:53:25 +0100
    Subject: [PATCH 1688/1841] Enable addressable light power supply based on raw
     values (#2690)
    
    ---
     esphome/components/light/addressable_light.h | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h
    index 2b2c0ca7e4..8302239d6a 100644
    --- a/esphome/components/light/addressable_light.h
    +++ b/esphome/components/light/addressable_light.h
    @@ -87,7 +87,7 @@ class AddressableLight : public LightOutput, public Component {
       void mark_shown_() {
     #ifdef USE_POWER_SUPPLY
         for (const auto &c : *this) {
    -      if (c.get().is_on()) {
    +      if (c.get_red_raw() > 0 || c.get_green_raw() > 0 || c.get_blue_raw() > 0 || c.get_white_raw() > 0) {
             this->power_.request();
             return;
           }
    
    From f11220da3a24d2ff1ca5d83d5f26674d8033049f Mon Sep 17 00:00:00 2001
    From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Date: Thu, 11 Nov 2021 15:15:37 +1300
    Subject: [PATCH 1689/1841] Remove my.ha links from improv (#2695)
    
    ---
     .../components/esp32_improv/esp32_improv_component.cpp | 10 ++++++++--
     .../improv_serial/improv_serial_component.cpp          |  3 +--
     2 files changed, 9 insertions(+), 4 deletions(-)
    
    diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp
    index faa9ab7df6..22bebdfe98 100644
    --- a/esphome/components/esp32_improv/esp32_improv_component.cpp
    +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp
    @@ -11,6 +11,7 @@ namespace esphome {
     namespace esp32_improv {
     
     static const char *const TAG = "esp32_improv.component";
    +static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
     
     ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; }
     
    @@ -124,8 +125,13 @@ void ESP32ImprovComponent::loop() {
             this->cancel_timeout("wifi-connect-timeout");
             this->set_state_(improv::STATE_PROVISIONED);
     
    -        std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
    -        std::vector data = improv::build_rpc_response(improv::WIFI_SETTINGS, {url});
    +        std::vector urls = {ESPHOME_MY_LINK};
    +#ifdef USE_WEBSERVER
    +        auto ip = wifi::global_wifi_component->wifi_sta_ip();
    +        std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
    +        urls.push_back(webserver_url);
    +#endif
    +        std::vector data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
             this->send_response_(data);
             this->set_timeout("end-service", 1000, [this] {
               this->service_->stop();
    diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp
    index a12f1bd83b..abbb76ab11 100644
    --- a/esphome/components/improv_serial/improv_serial_component.cpp
    +++ b/esphome/components/improv_serial/improv_serial_component.cpp
    @@ -92,8 +92,7 @@ void ImprovSerialComponent::loop() {
     }
     
     std::vector ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
    -  std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
    -  std::vector urls = {url};
    +  std::vector urls;
     #ifdef USE_WEBSERVER
       auto ip = wifi::global_wifi_component->wifi_sta_ip();
       std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
    
    From a6873c1520eaf21e85ad455fb15049c5761b32eb Mon Sep 17 00:00:00 2001
    From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Date: Thu, 11 Nov 2021 22:56:35 +1300
    Subject: [PATCH 1690/1841] Only allow prometheus when using arduino (#2697)
    
    ---
     esphome/components/prometheus/__init__.py | 3 ++-
     1 file changed, 2 insertions(+), 1 deletion(-)
    
    diff --git a/esphome/components/prometheus/__init__.py b/esphome/components/prometheus/__init__.py
    index f5f166d085..45345f06e8 100644
    --- a/esphome/components/prometheus/__init__.py
    +++ b/esphome/components/prometheus/__init__.py
    @@ -15,7 +15,8 @@ CONFIG_SCHEMA = cv.Schema(
             cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
                 web_server_base.WebServerBase
             ),
    -    }
    +    },
    +    cv.only_with_arduino,
     ).extend(cv.COMPONENT_SCHEMA)
     
     
    
    From 0372e12b81db6a78d7397a1d6ab274dbce7ee3e1 Mon Sep 17 00:00:00 2001
    From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Date: Thu, 11 Nov 2021 22:56:54 +1300
    Subject: [PATCH 1691/1841] Defines tidy (#2696)
    
    * Move webserver defines inside arduino block
    
    * Move esp8266 flash define
    
    * Move prometheus define
    ---
     esphome/core/defines.h | 9 ++++-----
     1 file changed, 4 insertions(+), 5 deletions(-)
    
    diff --git a/esphome/core/defines.h b/esphome/core/defines.h
    index e679fe1cef..8dcae9fa31 100644
    --- a/esphome/core/defines.h
    +++ b/esphome/core/defines.h
    @@ -19,7 +19,6 @@
     #define USE_CLIMATE
     #define USE_COVER
     #define USE_DEEP_SLEEP
    -#define USE_ESP8266_PREFERENCES_FLASH
     #define USE_FAN
     #define USE_GRAPH
     #define USE_HOMEASSISTANT_TIME
    @@ -30,7 +29,6 @@
     #define USE_OTA_PASSWORD
     #define USE_OTA_STATE_CALLBACK
     #define USE_POWER_SUPPLY
    -#define USE_PROMETHEUS
     #define USE_SELECT
     #define USE_SENSOR
     #define USE_STATUS_LED
    @@ -38,18 +36,18 @@
     #define USE_TEXT_SENSOR
     #define USE_TIME
     #define USE_UART_DEBUGGER
    -#define USE_WEBSERVER
     #define USE_WIFI
     
    -#define WEBSERVER_PORT 80  // NOLINT
    -
     // Arduino-specific feature flags
     #ifdef USE_ARDUINO
     #define USE_CAPTIVE_PORTAL
     #define USE_JSON
     #define USE_NEXTION_TFT_UPLOAD
     #define USE_MQTT
    +#define USE_PROMETHEUS
    +#define USE_WEBSERVER
     #define USE_WIFI_WPA2_EAP
    +#define WEBSERVER_PORT 80  // NOLINT
     #endif
     
     // ESP32-specific feature flags
    @@ -68,6 +66,7 @@
     // ESP8266-specific feature flags
     #ifdef USE_ESP8266
     #define USE_ADC_SENSOR_VCC
    +#define USE_ESP8266_PREFERENCES_FLASH
     #define USE_HTTP_REQUEST_ESP8266_HTTPS
     #define USE_SOCKET_IMPL_LWIP_TCP
     #endif
    
    From 7bb7456a8b034ce0f04aef4994db341b646af7b7 Mon Sep 17 00:00:00 2001
    From: lcavalli 
    Date: Fri, 12 Nov 2021 01:17:10 +0100
    Subject: [PATCH 1692/1841] Update device classes for binary sensors (#2703)
    
    ---
     esphome/components/binary_sensor/__init__.py | 6 ++++--
     esphome/const.py                             | 3 ++-
     2 files changed, 6 insertions(+), 3 deletions(-)
    
    diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py
    index faafcddd06..3f11e18e45 100644
    --- a/esphome/components/binary_sensor/__init__.py
    +++ b/esphome/components/binary_sensor/__init__.py
    @@ -44,10 +44,11 @@ from esphome.const import (
         DEVICE_CLASS_POWER,
         DEVICE_CLASS_PRESENCE,
         DEVICE_CLASS_PROBLEM,
    +    DEVICE_CLASS_RUNNING,
         DEVICE_CLASS_SAFETY,
         DEVICE_CLASS_SMOKE,
         DEVICE_CLASS_SOUND,
    -    DEVICE_CLASS_UPDATE,
    +    DEVICE_CLASS_TAMPER,
         DEVICE_CLASS_VIBRATION,
         DEVICE_CLASS_WINDOW,
     )
    @@ -76,10 +77,11 @@ DEVICE_CLASSES = [
         DEVICE_CLASS_POWER,
         DEVICE_CLASS_PRESENCE,
         DEVICE_CLASS_PROBLEM,
    +    DEVICE_CLASS_RUNNING,
         DEVICE_CLASS_SAFETY,
         DEVICE_CLASS_SMOKE,
         DEVICE_CLASS_SOUND,
    -    DEVICE_CLASS_UPDATE,
    +    DEVICE_CLASS_TAMPER,
         DEVICE_CLASS_VIBRATION,
         DEVICE_CLASS_WINDOW,
     ]
    diff --git a/esphome/const.py b/esphome/const.py
    index 9951747ee2..017feb0268 100644
    --- a/esphome/const.py
    +++ b/esphome/const.py
    @@ -878,10 +878,11 @@ DEVICE_CLASS_OPENING = "opening"
     DEVICE_CLASS_PLUG = "plug"
     DEVICE_CLASS_PRESENCE = "presence"
     DEVICE_CLASS_PROBLEM = "problem"
    +DEVICE_CLASS_RUNNING = "running"
     DEVICE_CLASS_SAFETY = "safety"
     DEVICE_CLASS_SMOKE = "smoke"
     DEVICE_CLASS_SOUND = "sound"
    -DEVICE_CLASS_UPDATE = "update"
    +DEVICE_CLASS_TAMPER = "tamper"
     DEVICE_CLASS_VIBRATION = "vibration"
     DEVICE_CLASS_WINDOW = "window"
     # device classes of both binary_sensor and sensor component
    
    From 2e0c89409d487eb4e382d307e6e2fe836aec8ee1 Mon Sep 17 00:00:00 2001
    From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Date: Sat, 13 Nov 2021 21:22:32 +1300
    Subject: [PATCH 1693/1841] Bump ESPAsyncWebServer to 2.1.0 (#2686)
    
    ---
     esphome/components/web_server_base/__init__.py | 2 +-
     platformio.ini                                 | 2 +-
     2 files changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py
    index dc1a2bc2f0..14fb033a56 100644
    --- a/esphome/components/web_server_base/__init__.py
    +++ b/esphome/components/web_server_base/__init__.py
    @@ -28,4 +28,4 @@ async def to_code(config):
             cg.add_library("FS", None)
             cg.add_library("Update", None)
         # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json
    -    cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.0.1")
    +    cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.1.0")
    diff --git a/platformio.ini b/platformio.ini
    index 0dd32268e0..5e89afe8e6 100644
    --- a/platformio.ini
    +++ b/platformio.ini
    @@ -41,7 +41,7 @@ lib_deps =
         ${common.lib_deps}
         ottowinter/AsyncMqttClient-esphome@0.8.6              ; mqtt
         ottowinter/ArduinoJson-esphomelib@5.13.3              ; json
    -    esphome/ESPAsyncWebServer-esphome@2.0.1               ; web_server_base
    +    esphome/ESPAsyncWebServer-esphome@2.1.0               ; web_server_base
         fastled/FastLED@3.3.2                                 ; fastled_base
         mikalhart/TinyGPSPlus@1.0.2                           ; gps
         freekode/TM1651@1.0.1                                 ; tm1651
    
    From 582567696ebb5e4711a062325238a643c5aa5176 Mon Sep 17 00:00:00 2001
    From: NeoAcheron <12508986+NeoAcheron@users.noreply.github.com>
    Date: Sat, 13 Nov 2021 15:14:23 +0100
    Subject: [PATCH 1694/1841] pmsx003: add support for PMS5003S device (#2710)
    
    ---
     esphome/components/pmsx003/pmsx003.cpp | 14 ++++++++++----
     esphome/components/pmsx003/pmsx003.h   |  1 +
     esphome/components/pmsx003/sensor.py   | 10 ++++++----
     3 files changed, 17 insertions(+), 8 deletions(-)
    
    diff --git a/esphome/components/pmsx003/pmsx003.cpp b/esphome/components/pmsx003/pmsx003.cpp
    index 0474d6ffd0..5de94699f0 100644
    --- a/esphome/components/pmsx003/pmsx003.cpp
    +++ b/esphome/components/pmsx003/pmsx003.cpp
    @@ -96,6 +96,7 @@ optional PMSX003Component::check_byte_() {
             length_matches = payload_length == 28 || payload_length == 20;
             break;
           case PMSX003_TYPE_5003T:
    +      case PMSX003_TYPE_5003S:
             length_matches = payload_length == 28;
             break;
           case PMSX003_TYPE_5003ST:
    @@ -133,20 +134,25 @@ optional PMSX003Component::check_byte_() {
     void PMSX003Component::parse_data_() {
       switch (this->type_) {
         case PMSX003_TYPE_5003ST: {
    -      uint16_t formaldehyde = this->get_16_bit_uint_(28);
           float temperature = this->get_16_bit_uint_(30) / 10.0f;
           float humidity = this->get_16_bit_uint_(32) / 10.0f;
     
    -      ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%% Formaldehyde: %u µg/m^3", temperature, humidity,
    -               formaldehyde);
    +      ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%%", temperature, humidity);
     
           if (this->temperature_sensor_ != nullptr)
             this->temperature_sensor_->publish_state(temperature);
           if (this->humidity_sensor_ != nullptr)
             this->humidity_sensor_->publish_state(humidity);
    +      // The rest of the PMS5003ST matches the PMS5003S, continue on
    +    }
    +    case PMSX003_TYPE_5003S: {
    +      uint16_t formaldehyde = this->get_16_bit_uint_(28);
    +
    +      ESP_LOGD(TAG, "Got Formaldehyde: %u µg/m^3", formaldehyde);
    +
           if (this->formaldehyde_sensor_ != nullptr)
             this->formaldehyde_sensor_->publish_state(formaldehyde);
    -      // The rest of the PMS5003ST matches the PMS5003, continue on
    +      // The rest of the PMS5003S matches the PMS5003, continue on
         }
         case PMSX003_TYPE_X003: {
           uint16_t pm_1_0_std_concentration = this->get_16_bit_uint_(4);
    diff --git a/esphome/components/pmsx003/pmsx003.h b/esphome/components/pmsx003/pmsx003.h
    index a5adecb534..fd6364c70c 100644
    --- a/esphome/components/pmsx003/pmsx003.h
    +++ b/esphome/components/pmsx003/pmsx003.h
    @@ -11,6 +11,7 @@ enum PMSX003Type {
       PMSX003_TYPE_X003 = 0,
       PMSX003_TYPE_5003T,
       PMSX003_TYPE_5003ST,
    +  PMSX003_TYPE_5003S,
     };
     
     class PMSX003Component : public uart::UARTDevice, public Component {
    diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py
    index 350117a235..56a91d22fc 100644
    --- a/esphome/components/pmsx003/sensor.py
    +++ b/esphome/components/pmsx003/sensor.py
    @@ -42,21 +42,23 @@ PMSX003Sensor = pmsx003_ns.class_("PMSX003Sensor", sensor.Sensor)
     TYPE_PMSX003 = "PMSX003"
     TYPE_PMS5003T = "PMS5003T"
     TYPE_PMS5003ST = "PMS5003ST"
    +TYPE_PMS5003S = "PMS5003S"
     
     PMSX003Type = pmsx003_ns.enum("PMSX003Type")
     PMSX003_TYPES = {
         TYPE_PMSX003: PMSX003Type.PMSX003_TYPE_X003,
         TYPE_PMS5003T: PMSX003Type.PMSX003_TYPE_5003T,
         TYPE_PMS5003ST: PMSX003Type.PMSX003_TYPE_5003ST,
    +    TYPE_PMS5003S: PMSX003Type.PMSX003_TYPE_5003S,
     }
     
     SENSORS_TO_TYPE = {
    -    CONF_PM_1_0: [TYPE_PMSX003, TYPE_PMS5003ST],
    -    CONF_PM_2_5: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST],
    -    CONF_PM_10_0: [TYPE_PMSX003, TYPE_PMS5003ST],
    +    CONF_PM_1_0: [TYPE_PMSX003, TYPE_PMS5003ST, TYPE_PMS5003S],
    +    CONF_PM_2_5: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S],
    +    CONF_PM_10_0: [TYPE_PMSX003, TYPE_PMS5003ST, TYPE_PMS5003S],
         CONF_TEMPERATURE: [TYPE_PMS5003T, TYPE_PMS5003ST],
         CONF_HUMIDITY: [TYPE_PMS5003T, TYPE_PMS5003ST],
    -    CONF_FORMALDEHYDE: [TYPE_PMS5003ST],
    +    CONF_FORMALDEHYDE: [TYPE_PMS5003ST, TYPE_PMS5003S],
     }
     
     
    
    From aae63a7ff3b64054860384a461314dea31a22e80 Mon Sep 17 00:00:00 2001
    From: "Sergey V. DUDANOV" 
    Date: Sat, 13 Nov 2021 18:42:15 +0400
    Subject: [PATCH 1695/1841] Add climate on_state trigger (#2707)
    
    ---
     esphome/components/climate/__init__.py  | 12 ++++++++++++
     esphome/components/climate/automation.h |  7 +++++++
     tests/test1.yaml                        |  2 ++
     3 files changed, 21 insertions(+)
    
    diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py
    index 7ff769e5cb..b0f9146012 100644
    --- a/esphome/components/climate/__init__.py
    +++ b/esphome/components/climate/__init__.py
    @@ -20,6 +20,7 @@ from esphome.const import (
         CONF_MODE,
         CONF_MODE_COMMAND_TOPIC,
         CONF_MODE_STATE_TOPIC,
    +    CONF_ON_STATE,
         CONF_PRESET,
         CONF_SWING_MODE,
         CONF_SWING_MODE_COMMAND_TOPIC,
    @@ -34,6 +35,7 @@ from esphome.const import (
         CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC,
         CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC,
         CONF_TEMPERATURE_STEP,
    +    CONF_TRIGGER_ID,
         CONF_VISUAL,
         CONF_MQTT_ID,
     )
    @@ -101,6 +103,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
     
     # Actions
     ControlAction = climate_ns.class_("ControlAction", automation.Action)
    +StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template())
     
     CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
         {
    @@ -161,6 +164,11 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
             cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
                 cv.requires_component("mqtt"), cv.publish_topic
             ),
    +        cv.Optional(CONF_ON_STATE): automation.validate_automation(
    +            {
    +                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
    +            }
    +        ),
         }
     )
     
    @@ -256,6 +264,10 @@ async def setup_climate_core_(var, config):
                     )
                 )
     
    +    for conf in config.get(CONF_ON_STATE, []):
    +        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
    +        await automation.build_automation(trigger, [], conf)
    +
     
     async def register_climate(var, config):
         if not CORE.has_id(config[CONF_ID]):
    diff --git a/esphome/components/climate/automation.h b/esphome/components/climate/automation.h
    index 49a87027f2..3145358dab 100644
    --- a/esphome/components/climate/automation.h
    +++ b/esphome/components/climate/automation.h
    @@ -42,5 +42,12 @@ template class ControlAction : public Action {
       Climate *climate_;
     };
     
    +class StateTrigger : public Trigger<> {
    + public:
    +  StateTrigger(Climate *climate) {
    +    climate->add_on_state_callback([this]() { this->trigger(); });
    +  }
    +};
    +
     }  // namespace climate
     }  // namespace esphome
    diff --git a/tests/test1.yaml b/tests/test1.yaml
    index 04d928e1b8..f10922a7b9 100644
    --- a/tests/test1.yaml
    +++ b/tests/test1.yaml
    @@ -1707,6 +1707,8 @@ climate:
         min_temperature: 18
         max_temperature: 30
       - platform: midea
    +    on_state:
    +      logger.log: "State changed!"
         id: midea_unit
         uart_id: uart0
         name: Midea Climate
    
    From f643a46bbf764c138875190393ec437b8ba78c02 Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?Krzysztof=20Bia=C5=82ek?= 
    Date: Sun, 14 Nov 2021 14:59:34 +0100
    Subject: [PATCH 1696/1841] Allow setting custom command_topic for Select and
     Number components (#2714)
    
    ---
     esphome/components/number/__init__.py |  2 +-
     esphome/components/select/__init__.py |  2 +-
     tests/test1.yaml                      | 20 ++++++++++++++++++++
     3 files changed, 22 insertions(+), 2 deletions(-)
    
    diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py
    index bb3427e4bd..1da25caafe 100644
    --- a/esphome/components/number/__init__.py
    +++ b/esphome/components/number/__init__.py
    @@ -41,7 +41,7 @@ NumberInRangeCondition = number_ns.class_(
     
     icon = cv.icon
     
    -NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
    +NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
         {
             cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent),
             cv.GenerateID(): cv.declare_id(Number),
    diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py
    index 8ea159d657..c15036e9f9 100644
    --- a/esphome/components/select/__init__.py
    +++ b/esphome/components/select/__init__.py
    @@ -30,7 +30,7 @@ SelectSetAction = select_ns.class_("SelectSetAction", automation.Action)
     
     icon = cv.icon
     
    -SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
    +SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
         {
             cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent),
             cv.GenerateID(): cv.declare_id(Select),
    diff --git a/tests/test1.yaml b/tests/test1.yaml
    index f10922a7b9..263754dc4f 100644
    --- a/tests/test1.yaml
    +++ b/tests/test1.yaml
    @@ -2514,3 +2514,23 @@ teleinfo:
       uart_id: uart0
       update_interval: 60s
       historical_mode: true
    +
    +number:
    +  - platform: template
    +    id: test_number
    +    state_topic: livingroom/custom_state_topic
    +    command_topic: livingroom/custom_command_topic
    +    min_value: 0
    +    step: 1
    +    max_value: 10
    +    optimistic: true
    +
    +select:
    +  - platform: template
    +    id: test_select
    +    state_topic: livingroom/custom_state_topic
    +    command_topic: livingroom/custom_command_topic
    +    options:
    +      - one
    +      - two
    +    optimistic: true
    
    From 4eaa6afa4d43299aedd277d7508b6c3ca012a0cf Mon Sep 17 00:00:00 2001
    From: Clifford Roche 
    Date: Sun, 14 Nov 2021 10:11:21 -0500
    Subject: [PATCH 1697/1841] Add greeyac protocol to IR Climate / HeatpumpIR
     (#2694)
    
    ---
     esphome/components/heatpumpir/climate.py        |  5 ++++-
     esphome/components/heatpumpir/heatpumpir.cpp    | 17 +++++++++++++++--
     esphome/components/heatpumpir/heatpumpir.h      |  1 +
     .../components/heatpumpir/ir_sender_esphome.h   |  4 ++--
     platformio.ini                                  |  4 +++-
     5 files changed, 25 insertions(+), 6 deletions(-)
    
    diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py
    index 36e56aa5da..592e03f959 100644
    --- a/esphome/components/heatpumpir/climate.py
    +++ b/esphome/components/heatpumpir/climate.py
    @@ -30,6 +30,7 @@ PROTOCOLS = {
         "gree": Protocol.PROTOCOL_GREE,
         "greeya": Protocol.PROTOCOL_GREEYAA,
         "greeyan": Protocol.PROTOCOL_GREEYAN,
    +    "greeyac": Protocol.PROTOCOL_GREEYAC,
         "hisense_aud": Protocol.PROTOCOL_HISENSE_AUD,
         "hitachi": Protocol.PROTOCOL_HITACHI,
         "hyundai": Protocol.PROTOCOL_HYUNDAI,
    @@ -111,4 +112,6 @@ def to_code(config):
         cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE]))
         cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE]))
     
    -    cg.add_library("tonia/HeatpumpIR", "1.0.15")
    +    # PIO isn't updating releases, so referencing the release tag directly. See:
    +    # https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd
    +    cg.add_library("", "", "https://github.com/ToniA/arduino-heatpumpir.git#1.0.18")
    diff --git a/esphome/components/heatpumpir/heatpumpir.cpp b/esphome/components/heatpumpir/heatpumpir.cpp
    index 8d9fc962c0..ad3731b955 100644
    --- a/esphome/components/heatpumpir/heatpumpir.cpp
    +++ b/esphome/components/heatpumpir/heatpumpir.cpp
    @@ -25,6 +25,7 @@ const std::map> PROTOCOL_CONSTRUCTOR_MAP
         {PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }},                           // NOLINT
         {PROTOCOL_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }},                            // NOLINT
         {PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }},                            // NOLINT
    +    {PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }},                            // NOLINT
         {PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }},                        // NOLINT
         {PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }},                            // NOLINT
         {PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }},                            // NOLINT
    @@ -61,6 +62,19 @@ void HeatpumpIRClimate::setup() {
       }
       this->heatpump_ir_ = protocol_constructor->second();
       climate_ir::ClimateIR::setup();
    +  if (this->sensor_) {
    +    this->sensor_->add_on_state_callback([this](float state) {
    +      this->current_temperature = state;
    +
    +      IRSenderESPHome esp_sender(this->transmitter_);
    +      this->heatpump_ir_->send(esp_sender, uint8_t(lround(this->current_temperature + 0.5)));
    +
    +      // current temperature changed, publish state
    +      this->publish_state();
    +    });
    +    this->current_temperature = this->sensor_->state;
    +  } else
    +    this->current_temperature = NAN;
     }
     
     void HeatpumpIRClimate::transmit_state() {
    @@ -171,8 +185,7 @@ void HeatpumpIRClimate::transmit_state() {
     
       temperature_cmd = (uint8_t) clamp(this->target_temperature, this->min_temperature_, this->max_temperature_);
     
    -  IRSenderESPHome esp_sender(0, this->transmitter_);
    -
    +  IRSenderESPHome esp_sender(this->transmitter_);
       heatpump_ir_->send(esp_sender, power_mode_cmd, operating_mode_cmd, fan_speed_cmd, temperature_cmd, swing_v_cmd,
                          swing_h_cmd);
     }
    diff --git a/esphome/components/heatpumpir/heatpumpir.h b/esphome/components/heatpumpir/heatpumpir.h
    index e2d2b45dc4..18d9b5040f 100644
    --- a/esphome/components/heatpumpir/heatpumpir.h
    +++ b/esphome/components/heatpumpir/heatpumpir.h
    @@ -25,6 +25,7 @@ enum Protocol {
       PROTOCOL_GREE,
       PROTOCOL_GREEYAA,
       PROTOCOL_GREEYAN,
    +  PROTOCOL_GREEYAC,
       PROTOCOL_HISENSE_AUD,
       PROTOCOL_HITACHI,
       PROTOCOL_HYUNDAI,
    diff --git a/esphome/components/heatpumpir/ir_sender_esphome.h b/esphome/components/heatpumpir/ir_sender_esphome.h
    index 24e8ba9883..7546d990ea 100644
    --- a/esphome/components/heatpumpir/ir_sender_esphome.h
    +++ b/esphome/components/heatpumpir/ir_sender_esphome.h
    @@ -11,8 +11,8 @@ namespace heatpumpir {
     
     class IRSenderESPHome : public IRSender {
      public:
    -  IRSenderESPHome(uint8_t pin, remote_transmitter::RemoteTransmitterComponent *transmitter)
    -      : IRSender(pin), transmit_(transmitter->transmit()){};
    +  IRSenderESPHome(remote_transmitter::RemoteTransmitterComponent *transmitter)
    +      : IRSender(0), transmit_(transmitter->transmit()){};
       void setFrequency(int frequency) override;  // NOLINT(readability-identifier-naming)
       void space(int space_length) override;
       void mark(int mark_length) override;
    diff --git a/platformio.ini b/platformio.ini
    index 5e89afe8e6..2ac6d0bfc8 100644
    --- a/platformio.ini
    +++ b/platformio.ini
    @@ -49,7 +49,9 @@ lib_deps =
         glmnet/Dsmr@0.5                                       ; dsmr
         rweather/Crypto@0.2.0                                 ; dsmr
         dudanov/MideaUART@1.1.8                               ; midea
    -    tonia/HeatpumpIR@1.0.15                               ; heatpumpir
    +    ; PIO isn't update releases correctly, see:
    +    ; https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd
    +    https://github.com/ToniA/arduino-heatpumpir.git#1.0.18  ; heatpumpir
     build_flags =
         ${common.build_flags}
         -DUSE_ARDUINO
    
    From 108b8e6705a6f5080fa2cdc408437d2b4d3c525a Mon Sep 17 00:00:00 2001
    From: Maurice Makaay 
    Date: Sun, 14 Nov 2021 16:17:13 +0100
    Subject: [PATCH 1698/1841] Fix rom/rtc.h deprecation compile warning for debug
     component (#2520)
    
    ---
     esphome/components/debug/debug_component.cpp | 15 +++++++++------
     1 file changed, 9 insertions(+), 6 deletions(-)
    
    diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp
    index b856733121..40eb20fa6e 100644
    --- a/esphome/components/debug/debug_component.cpp
    +++ b/esphome/components/debug/debug_component.cpp
    @@ -4,20 +4,23 @@
     #include "esphome/core/defines.h"
     #include "esphome/core/version.h"
     
    +#ifdef USE_ESP_IDF
    +#include 
    +#include 
    +#endif
    +
     #ifdef USE_ESP32
    +#if ESP_IDF_VERSION_MAJOR >= 4
    +#include 
    +#else
     #include 
    -#include 
    +#endif
     #endif
     
     #ifdef USE_ARDUINO
     #include 
     #endif
     
    -#ifdef USE_ESP_IDF
    -#include 
    -#include 
    -#endif
    -
     namespace esphome {
     namespace debug {
     
    
    From 66cebfc992120212bb6032f3cd6f8ce6eff5f80a Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Sun, 14 Nov 2021 20:05:11 +0100
    Subject: [PATCH 1699/1841] Restore InterruptLock on wifi-less ESP8266 (#2712)
    
    ---
     esphome/core/helpers.cpp | 6 +-----
     1 file changed, 1 insertion(+), 5 deletions(-)
    
    diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp
    index daca3ffd32..27608a84c1 100644
    --- a/esphome/core/helpers.cpp
    +++ b/esphome/core/helpers.cpp
    @@ -9,6 +9,7 @@
     #ifdef USE_WIFI
     #include 
     #endif
    +#include 
     #include 
     #elif defined(USE_ESP32_FRAMEWORK_ARDUINO)
     #include 
    @@ -430,13 +431,8 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green
     }
     
     #ifdef USE_ESP8266
    -#ifdef USE_WIFI
     IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); }
     IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); }
    -#else
    -IRAM_ATTR InterruptLock::InterruptLock() {}
    -IRAM_ATTR InterruptLock::~InterruptLock() {}
    -#endif
     #endif
     #ifdef USE_ESP32
     IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }
    
    From 14299bb2cc9af365d0b45080c0eb31d1a2edc11a Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Sun, 14 Nov 2021 20:07:58 +0100
    Subject: [PATCH 1700/1841] Drop unused constants from const.py (#2718)
    
    ---
     esphome/const.py | 22 ----------------------
     1 file changed, 22 deletions(-)
    
    diff --git a/esphome/const.py b/esphome/const.py
    index 017feb0268..fb241f04b5 100644
    --- a/esphome/const.py
    +++ b/esphome/const.py
    @@ -8,29 +8,7 @@ PLATFORM_ESP32 = "esp32"
     PLATFORM_ESP8266 = "esp8266"
     
     TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266]
    -TARGET_FRAMEWORKS = ["arduino", "esp-idf"]
     
    -# See also https://github.com/platformio/platform-espressif8266/releases
    -ARDUINO_VERSION_ESP8266 = {
    -    "dev": "https://github.com/platformio/platform-espressif8266.git",
    -    "3.0.1": "platformio/espressif8266@3.1.0",
    -    "3.0.0": "platformio/espressif8266@3.0.0",
    -    "2.7.4": "platformio/espressif8266@2.6.2",
    -    "2.7.3": "platformio/espressif8266@2.6.1",
    -    "2.7.2": "platformio/espressif8266@2.6.0",
    -    "2.7.1": "platformio/espressif8266@2.5.3",
    -    "2.7.0": "platformio/espressif8266@2.5.0",
    -    "2.6.3": "platformio/espressif8266@2.4.0",
    -    "2.6.2": "platformio/espressif8266@2.3.1",
    -    "2.6.1": "platformio/espressif8266@2.3.0",
    -    "2.5.2": "platformio/espressif8266@2.2.3",
    -    "2.5.1": "platformio/espressif8266@2.1.1",
    -    "2.5.0": "platformio/espressif8266@2.0.4",
    -    "2.4.2": "platformio/espressif8266@1.8.0",
    -    "2.4.1": "platformio/espressif8266@1.7.3",
    -    "2.4.0": "platformio/espressif8266@1.6.0",
    -    "2.3.0": "platformio/espressif8266@1.5.0",
    -}
     SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"}
     HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}
     
    
    From 6a7440f7d325cbb9a53c48df3df89df00ed55e52 Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Sun, 14 Nov 2021 21:45:25 +0100
    Subject: [PATCH 1701/1841] Feed WDT between doing ESP32 touchpad measurements
     (#2720)
    
    ---
     esphome/components/esp32_touch/esp32_touch.cpp | 3 +++
     1 file changed, 3 insertions(+)
    
    diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp
    index cb72820900..85f4058eee 100644
    --- a/esphome/components/esp32_touch/esp32_touch.cpp
    +++ b/esphome/components/esp32_touch/esp32_touch.cpp
    @@ -1,6 +1,7 @@
     #ifdef USE_ESP32
     
     #include "esp32_touch.h"
    +#include "esphome/core/application.h"
     #include "esphome/core/log.h"
     #include "esphome/core/hal.h"
     
    @@ -125,6 +126,8 @@ void ESP32TouchComponent::loop() {
         if (should_print) {
           ESP_LOGD(TAG, "Touch Pad '%s' (T%u): %u", child->get_name().c_str(), child->get_touch_pad(), value);
         }
    +
    +    App.feed_wdt();
       }
     
       if (should_print) {
    
    From 04740fbcbb4ea7ccff9ebbe89a801bda90f46168 Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Sun, 14 Nov 2021 22:04:43 +0100
    Subject: [PATCH 1702/1841] Install test requirements in lint Docker image
     (#2719)
    
    ---
     docker/Dockerfile | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/docker/Dockerfile b/docker/Dockerfile
    index 0d30bb0267..62a64c851d 100644
    --- a/docker/Dockerfile
    +++ b/docker/Dockerfile
    @@ -147,9 +147,9 @@ RUN \
             /var/{cache,log}/* \
             /var/lib/apt/lists/*
     
    -COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
    +COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini /
     RUN \
    -    pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
    +    pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \
         && /platformio_install_deps.py /platformio.ini
     
     VOLUME ["/esphome"]
    
    From d99c5ed8901279def13815d9f2b22f9f8a203a9f Mon Sep 17 00:00:00 2001
    From: "Sergey V. DUDANOV" 
    Date: Mon, 15 Nov 2021 01:40:35 +0400
    Subject: [PATCH 1703/1841] RemoteTransmitter fix. Bug from version 2021.10.
     Some changes. (#2706)
    
    ---
     .../midea/{adapter.cpp => ac_adapter.cpp}     |   4 +-
     .../midea/{adapter.h => ac_adapter.h}         |   6 +-
     .../midea/{automations.h => ac_automations.h} |   2 +
     esphome/components/midea/air_conditioner.cpp  |  13 +--
     esphome/components/midea/air_conditioner.h    |  21 +++-
     esphome/components/midea/appliance_base.h     | 109 ++++++++++--------
     esphome/components/midea/climate.py           |  20 ++--
     .../midea/{midea_ir.h => ir_transmitter.h}    |  15 +++
     8 files changed, 122 insertions(+), 68 deletions(-)
     rename esphome/components/midea/{adapter.cpp => ac_adapter.cpp} (98%)
     rename esphome/components/midea/{adapter.h => ac_adapter.h} (95%)
     rename esphome/components/midea/{automations.h => ac_automations.h} (97%)
     rename esphome/components/midea/{midea_ir.h => ir_transmitter.h} (73%)
    
    diff --git a/esphome/components/midea/adapter.cpp b/esphome/components/midea/ac_adapter.cpp
    similarity index 98%
    rename from esphome/components/midea/adapter.cpp
    rename to esphome/components/midea/ac_adapter.cpp
    index a3f19dbda8..2837713c35 100644
    --- a/esphome/components/midea/adapter.cpp
    +++ b/esphome/components/midea/ac_adapter.cpp
    @@ -1,10 +1,11 @@
     #ifdef USE_ARDUINO
     
     #include "esphome/core/log.h"
    -#include "adapter.h"
    +#include "ac_adapter.h"
     
     namespace esphome {
     namespace midea {
    +namespace ac {
     
     const char *const Constants::TAG = "midea";
     const std::string Constants::FREEZE_PROTECTION = "freeze protection";
    @@ -171,6 +172,7 @@ void Converters::to_climate_traits(ClimateTraits &traits, const dudanov::midea::
         traits.add_supported_custom_preset(Constants::FREEZE_PROTECTION);
     }
     
    +}  // namespace ac
     }  // namespace midea
     }  // namespace esphome
     
    diff --git a/esphome/components/midea/adapter.h b/esphome/components/midea/ac_adapter.h
    similarity index 95%
    rename from esphome/components/midea/adapter.h
    rename to esphome/components/midea/ac_adapter.h
    index 2497cbbe5b..c17894ae31 100644
    --- a/esphome/components/midea/adapter.h
    +++ b/esphome/components/midea/ac_adapter.h
    @@ -2,12 +2,15 @@
     
     #ifdef USE_ARDUINO
     
    +// MideaUART
     #include 
    +
     #include "esphome/components/climate/climate_traits.h"
    -#include "appliance_base.h"
    +#include "air_conditioner.h"
     
     namespace esphome {
     namespace midea {
    +namespace ac {
     
     using MideaMode = dudanov::midea::ac::Mode;
     using MideaSwingMode = dudanov::midea::ac::SwingMode;
    @@ -41,6 +44,7 @@ class Converters {
       static void to_climate_traits(ClimateTraits &traits, const dudanov::midea::ac::Capabilities &capabilities);
     };
     
    +}  // namespace ac
     }  // namespace midea
     }  // namespace esphome
     
    diff --git a/esphome/components/midea/automations.h b/esphome/components/midea/ac_automations.h
    similarity index 97%
    rename from esphome/components/midea/automations.h
    rename to esphome/components/midea/ac_automations.h
    index 5b638286ac..d4ed2e7168 100644
    --- a/esphome/components/midea/automations.h
    +++ b/esphome/components/midea/ac_automations.h
    @@ -7,6 +7,7 @@
     
     namespace esphome {
     namespace midea {
    +namespace ac {
     
     template class MideaActionBase : public Action {
      public:
    @@ -55,6 +56,7 @@ template class PowerOffAction : public MideaActionBase {
       void play(Ts... x) override { this->parent_->do_power_off(); }
     };
     
    +}  // namespace ac
     }  // namespace midea
     }  // namespace esphome
     
    diff --git a/esphome/components/midea/air_conditioner.cpp b/esphome/components/midea/air_conditioner.cpp
    index 103b852936..dd48f640a2 100644
    --- a/esphome/components/midea/air_conditioner.cpp
    +++ b/esphome/components/midea/air_conditioner.cpp
    @@ -2,13 +2,11 @@
     
     #include "esphome/core/log.h"
     #include "air_conditioner.h"
    -#include "adapter.h"
    -#ifdef USE_REMOTE_TRANSMITTER
    -#include "midea_ir.h"
    -#endif
    +#include "ac_adapter.h"
     
     namespace esphome {
     namespace midea {
    +namespace ac {
     
     static void set_sensor(Sensor *sensor, float value) {
       if (sensor != nullptr && (!sensor->has_state() || sensor->get_raw_state() != value))
    @@ -122,7 +120,7 @@ void AirConditioner::dump_config() {
     void AirConditioner::do_follow_me(float temperature, bool beeper) {
     #ifdef USE_REMOTE_TRANSMITTER
       IrFollowMeData data(static_cast(lroundf(temperature)), beeper);
    -  this->transmit_ir(data);
    +  this->transmitter_.transmit(data);
     #else
       ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
     #endif
    @@ -131,7 +129,7 @@ void AirConditioner::do_follow_me(float temperature, bool beeper) {
     void AirConditioner::do_swing_step() {
     #ifdef USE_REMOTE_TRANSMITTER
       IrSpecialData data(0x01);
    -  this->transmit_ir(data);
    +  this->transmitter_.transmit(data);
     #else
       ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
     #endif
    @@ -143,13 +141,14 @@ void AirConditioner::do_display_toggle() {
       } else {
     #ifdef USE_REMOTE_TRANSMITTER
         IrSpecialData data(0x08);
    -    this->transmit_ir(data);
    +    this->transmitter_.transmit(data);
     #else
         ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
     #endif
       }
     }
     
    +}  // namespace ac
     }  // namespace midea
     }  // namespace esphome
     
    diff --git a/esphome/components/midea/air_conditioner.h b/esphome/components/midea/air_conditioner.h
    index 8dfb9dcb3d..a6023b78bb 100644
    --- a/esphome/components/midea/air_conditioner.h
    +++ b/esphome/components/midea/air_conditioner.h
    @@ -2,17 +2,25 @@
     
     #ifdef USE_ARDUINO
     
    +// MideaUART
     #include 
    +
     #include "appliance_base.h"
     #include "esphome/components/sensor/sensor.h"
     
     namespace esphome {
     namespace midea {
    +namespace ac {
     
     using sensor::Sensor;
     using climate::ClimateCall;
    +using climate::ClimatePreset;
    +using climate::ClimateTraits;
    +using climate::ClimateMode;
    +using climate::ClimateSwingMode;
    +using climate::ClimateFanMode;
     
    -class AirConditioner : public ApplianceBase {
    +class AirConditioner : public ApplianceBase, public climate::Climate {
      public:
       void dump_config() override;
       void set_outdoor_temperature_sensor(Sensor *sensor) { this->outdoor_sensor_ = sensor; }
    @@ -31,15 +39,26 @@ class AirConditioner : public ApplianceBase
       void do_beeper_off() { this->set_beeper_feedback(false); }
       void do_power_on() { this->base_.setPowerState(true); }
       void do_power_off() { this->base_.setPowerState(false); }
    +  void set_supported_modes(const std::set &modes) { this->supported_modes_ = modes; }
    +  void set_supported_swing_modes(const std::set &modes) { this->supported_swing_modes_ = modes; }
    +  void set_supported_presets(const std::set &presets) { this->supported_presets_ = presets; }
    +  void set_custom_presets(const std::set &presets) { this->supported_custom_presets_ = presets; }
    +  void set_custom_fan_modes(const std::set &modes) { this->supported_custom_fan_modes_ = modes; }
     
      protected:
       void control(const ClimateCall &call) override;
       ClimateTraits traits() override;
    +  std::set supported_modes_{};
    +  std::set supported_swing_modes_{};
    +  std::set supported_presets_{};
    +  std::set supported_custom_presets_{};
    +  std::set supported_custom_fan_modes_{};
       Sensor *outdoor_sensor_{nullptr};
       Sensor *humidity_sensor_{nullptr};
       Sensor *power_sensor_{nullptr};
     };
     
    +}  // namespace ac
     }  // namespace midea
     }  // namespace esphome
     
    diff --git a/esphome/components/midea/appliance_base.h b/esphome/components/midea/appliance_base.h
    index 88a722e389..060cbd996b 100644
    --- a/esphome/components/midea/appliance_base.h
    +++ b/esphome/components/midea/appliance_base.h
    @@ -2,84 +2,97 @@
     
     #ifdef USE_ARDUINO
     
    +// MideaUART
    +#include 
    +#include 
    +
    +// Include global defines
    +#include "esphome/core/defines.h"
    +
     #include "esphome/core/component.h"
     #include "esphome/core/log.h"
     #include "esphome/components/uart/uart.h"
     #include "esphome/components/climate/climate.h"
    -#ifdef USE_REMOTE_TRANSMITTER
    -#include "esphome/components/remote_base/midea_protocol.h"
    -#include "esphome/components/remote_transmitter/remote_transmitter.h"
    -#endif
    -#include 
    -#include 
    +#include "ir_transmitter.h"
     
     namespace esphome {
     namespace midea {
     
    -using climate::ClimatePreset;
    -using climate::ClimateTraits;
    -using climate::ClimateMode;
    -using climate::ClimateSwingMode;
    -using climate::ClimateFanMode;
    +/* Stream from UART component */
    +class UARTStream : public Stream {
    + public:
    +  void set_uart(uart::UARTComponent *uart) { this->uart_ = uart; }
     
    -template
    -class ApplianceBase : public Component, public uart::UARTDevice, public climate::Climate, public Stream {
    +  /* Stream interface implementation */
    +
    +  int available() override { return this->uart_->available(); }
    +  int read() override {
    +    uint8_t data;
    +    this->uart_->read_byte(&data);
    +    return data;
    +  }
    +  int peek() override {
    +    uint8_t data;
    +    this->uart_->peek_byte(&data);
    +    return data;
    +  }
    +  size_t write(uint8_t data) override {
    +    this->uart_->write_byte(data);
    +    return 1;
    +  }
    +  size_t write(const uint8_t *data, size_t size) override {
    +    this->uart_->write_array(data, size);
    +    return size;
    +  }
    +  void flush() override { this->uart_->flush(); }
    +
    + protected:
    +  uart::UARTComponent *uart_;
    +};
    +
    +template class ApplianceBase : public Component {
       static_assert(std::is_base_of::value,
                     "T must derive from dudanov::midea::ApplianceBase class");
     
      public:
       ApplianceBase() {
    -    this->base_.setStream(this);
    +    this->base_.setStream(&this->stream_);
         this->base_.addOnStateCallback(std::bind(&ApplianceBase::on_status_change, this));
         dudanov::midea::ApplianceBase::setLogger(
             [](int level, const char *tag, int line, const String &format, va_list args) {
               esp_log_vprintf_(level, tag, line, format.c_str(), args);
             });
       }
    -  bool can_proceed() override {
    -    return this->base_.getAutoconfStatus() != dudanov::midea::AutoconfStatus::AUTOCONF_PROGRESS;
    -  }
    -  float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; }
    -  void setup() override { this->base_.setup(); }
    -  void loop() override { this->base_.loop(); }
    +
    +#ifdef USE_REMOTE_TRANSMITTER
    +  void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_.set_transmitter(transmitter); }
    +#endif
    +
    +  /* UART communication */
    +
    +  void set_uart_parent(uart::UARTComponent *parent) { this->stream_.set_uart(parent); }
       void set_period(uint32_t ms) { this->base_.setPeriod(ms); }
       void set_response_timeout(uint32_t ms) { this->base_.setTimeout(ms); }
       void set_request_attempts(uint32_t attempts) { this->base_.setNumAttempts(attempts); }
    +
    +  /* Component methods */
    +
    +  void setup() override { this->base_.setup(); }
    +  void loop() override { this->base_.loop(); }
    +  float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; }
    +  bool can_proceed() override {
    +    return this->base_.getAutoconfStatus() != dudanov::midea::AutoconfStatus::AUTOCONF_PROGRESS;
    +  }
    +
       void set_beeper_feedback(bool state) { this->base_.setBeeper(state); }
       void set_autoconf(bool value) { this->base_.setAutoconf(value); }
    -  void set_supported_modes(const std::set &modes) { this->supported_modes_ = modes; }
    -  void set_supported_swing_modes(const std::set &modes) { this->supported_swing_modes_ = modes; }
    -  void set_supported_presets(const std::set &presets) { this->supported_presets_ = presets; }
    -  void set_custom_presets(const std::set &presets) { this->supported_custom_presets_ = presets; }
    -  void set_custom_fan_modes(const std::set &modes) { this->supported_custom_fan_modes_ = modes; }
       virtual void on_status_change() = 0;
    -#ifdef USE_REMOTE_TRANSMITTER
    -  void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) {
    -    this->transmitter_ = transmitter;
    -  }
    -  void transmit_ir(remote_base::MideaData &data) {
    -    data.finalize();
    -    auto transmit = this->transmitter_->transmit();
    -    remote_base::MideaProtocol().encode(transmit.get_data(), data);
    -    transmit.perform();
    -  }
    -#endif
    -
    -  int available() override { return uart::UARTDevice::available(); }
    -  int read() override { return uart::UARTDevice::read(); }
    -  int peek() override { return uart::UARTDevice::peek(); }
    -  void flush() override { uart::UARTDevice::flush(); }
    -  size_t write(uint8_t data) override { return uart::UARTDevice::write(data); }
     
      protected:
       T base_;
    -  std::set supported_modes_{};
    -  std::set supported_swing_modes_{};
    -  std::set supported_presets_{};
    -  std::set supported_custom_presets_{};
    -  std::set supported_custom_fan_modes_{};
    +  UARTStream stream_;
     #ifdef USE_REMOTE_TRANSMITTER
    -  remote_transmitter::RemoteTransmitterComponent *transmitter_{nullptr};
    +  IrTransmitter transmitter_;
     #endif
     };
     
    diff --git a/esphome/components/midea/climate.py b/esphome/components/midea/climate.py
    index 08e82025b6..46c0019efa 100644
    --- a/esphome/components/midea/climate.py
    +++ b/esphome/components/midea/climate.py
    @@ -40,9 +40,9 @@ AUTO_LOAD = ["sensor"]
     CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature"
     CONF_POWER_USAGE = "power_usage"
     CONF_HUMIDITY_SETPOINT = "humidity_setpoint"
    -midea_ns = cg.esphome_ns.namespace("midea")
    -AirConditioner = midea_ns.class_("AirConditioner", climate.Climate, cg.Component)
    -Capabilities = midea_ns.namespace("Constants")
    +midea_ac_ns = cg.esphome_ns.namespace("midea").namespace("ac")
    +AirConditioner = midea_ac_ns.class_("AirConditioner", climate.Climate, cg.Component)
    +Capabilities = midea_ac_ns.namespace("Constants")
     
     
     def templatize(value):
    @@ -156,13 +156,13 @@ CONFIG_SCHEMA = cv.All(
     )
     
     # Actions
    -FollowMeAction = midea_ns.class_("FollowMeAction", automation.Action)
    -DisplayToggleAction = midea_ns.class_("DisplayToggleAction", automation.Action)
    -SwingStepAction = midea_ns.class_("SwingStepAction", automation.Action)
    -BeeperOnAction = midea_ns.class_("BeeperOnAction", automation.Action)
    -BeeperOffAction = midea_ns.class_("BeeperOffAction", automation.Action)
    -PowerOnAction = midea_ns.class_("PowerOnAction", automation.Action)
    -PowerOffAction = midea_ns.class_("PowerOffAction", automation.Action)
    +FollowMeAction = midea_ac_ns.class_("FollowMeAction", automation.Action)
    +DisplayToggleAction = midea_ac_ns.class_("DisplayToggleAction", automation.Action)
    +SwingStepAction = midea_ac_ns.class_("SwingStepAction", automation.Action)
    +BeeperOnAction = midea_ac_ns.class_("BeeperOnAction", automation.Action)
    +BeeperOffAction = midea_ac_ns.class_("BeeperOffAction", automation.Action)
    +PowerOnAction = midea_ac_ns.class_("PowerOnAction", automation.Action)
    +PowerOffAction = midea_ac_ns.class_("PowerOffAction", automation.Action)
     
     MIDEA_ACTION_BASE_SCHEMA = cv.Schema(
         {
    diff --git a/esphome/components/midea/midea_ir.h b/esphome/components/midea/ir_transmitter.h
    similarity index 73%
    rename from esphome/components/midea/midea_ir.h
    rename to esphome/components/midea/ir_transmitter.h
    index abd4324bcc..34a9f8498e 100644
    --- a/esphome/components/midea/midea_ir.h
    +++ b/esphome/components/midea/ir_transmitter.h
    @@ -7,6 +7,7 @@
     namespace esphome {
     namespace midea {
     
    +using remote_base::RemoteTransmitterBase;
     using IrData = remote_base::MideaData;
     
     class IrFollowMeData : public IrData {
    @@ -38,6 +39,20 @@ class IrSpecialData : public IrData {
       IrSpecialData(uint8_t code) : IrData({MIDEA_TYPE_SPECIAL, code, 0xFF, 0xFF, 0xFF}) {}
     };
     
    +class IrTransmitter {
    + public:
    +  void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_ = transmitter; }
    +  void transmit(IrData &data) {
    +    data.finalize();
    +    auto transmit = this->transmitter_->transmit();
    +    remote_base::MideaProtocol().encode(transmit.get_data(), data);
    +    transmit.perform();
    +  }
    +
    + protected:
    +  RemoteTransmitterBase *transmitter_{nullptr};
    +};
    +
     }  // namespace midea
     }  // namespace esphome
     
    
    From 7333123ba4e108f9e03b021af418abb6877330ea Mon Sep 17 00:00:00 2001
    From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Date: Mon, 15 Nov 2021 10:59:48 +1300
    Subject: [PATCH 1704/1841] Fix indentation of write_lambda for
     modbus_controller number (#2722)
    
    ---
     .../modbus_controller/number/__init__.py      | 22 +++++++++----------
     1 file changed, 11 insertions(+), 11 deletions(-)
    
    diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py
    index afb69f8798..4de0ffbcea 100644
    --- a/esphome/components/modbus_controller/number/__init__.py
    +++ b/esphome/components/modbus_controller/number/__init__.py
    @@ -129,14 +129,14 @@ async def to_code(config):
                 return_type=cg.optional.template(float),
             )
             cg.add(var.set_template(template_))
    -        if CONF_WRITE_LAMBDA in config:
    -            template_ = await cg.process_lambda(
    -                config[CONF_WRITE_LAMBDA],
    -                [
    -                    (ModbusNumber.operator("ptr"), "item"),
    -                    (cg.float_, "x"),
    -                    (cg.std_vector.template(cg.uint16).operator("ref"), "payload"),
    -                ],
    -                return_type=cg.optional.template(float),
    -            )
    -            cg.add(var.set_write_template(template_))
    +    if CONF_WRITE_LAMBDA in config:
    +        template_ = await cg.process_lambda(
    +            config[CONF_WRITE_LAMBDA],
    +            [
    +                (ModbusNumber.operator("ptr"), "item"),
    +                (cg.float_, "x"),
    +                (cg.std_vector.template(cg.uint16).operator("ref"), "payload"),
    +            ],
    +            return_type=cg.optional.template(float),
    +        )
    +        cg.add(var.set_write_template(template_))
    
    From 0b193eee432cd9c11dc0ac42d1429b9c48966db2 Mon Sep 17 00:00:00 2001
    From: Alexandre-Jacques St-Jacques 
    Date: Sun, 14 Nov 2021 17:58:22 -0500
    Subject: [PATCH 1705/1841] Remove unnecessary duplicate touch_pad_filter_start
     (#2724)
    
    ---
     esphome/components/esp32_touch/esp32_touch.cpp | 1 -
     1 file changed, 1 deletion(-)
    
    diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp
    index 85f4058eee..b225ae1a8a 100644
    --- a/esphome/components/esp32_touch/esp32_touch.cpp
    +++ b/esphome/components/esp32_touch/esp32_touch.cpp
    @@ -94,7 +94,6 @@ void ESP32TouchComponent::dump_config() {
     
       if (this->iir_filter_enabled_()) {
         ESP_LOGCONFIG(TAG, "    IIR Filter: %ums", this->iir_filter_);
    -    touch_pad_filter_start(this->iir_filter_);
       } else {
         ESP_LOGCONFIG(TAG, "  IIR Filter DISABLED");
       }
    
    From 5404163be08ec98b95970de204105ab424d21aaf Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Mon, 15 Nov 2021 15:48:16 +0100
    Subject: [PATCH 1706/1841] Clean-up MAC address helpers (#2713)
    
    ---
     esphome/core/helpers.cpp | 28 +++++++++-------------------
     esphome/core/helpers.h   |  7 +++----
     2 files changed, 12 insertions(+), 23 deletions(-)
    
    diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp
    index 27608a84c1..3e614eb515 100644
    --- a/esphome/core/helpers.cpp
    +++ b/esphome/core/helpers.cpp
    @@ -6,11 +6,10 @@
     #include 
     
     #if defined(USE_ESP8266)
    -#ifdef USE_WIFI
    -#include 
    -#endif
    -#include 
     #include 
    +#include 
    +// for xt_rsil()/xt_wsr_ps()
    +#include 
     #elif defined(USE_ESP32_FRAMEWORK_ARDUINO)
     #include 
     #elif defined(USE_ESP_IDF)
    @@ -31,8 +30,8 @@ namespace esphome {
     static const char *const TAG = "helpers";
     
     void get_mac_address_raw(uint8_t *mac) {
    -#ifdef USE_ESP32
    -#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC
    +#if defined(USE_ESP32)
    +#if defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC)
       // On some devices, the MAC address that is burnt into EFuse does not
       // match the CRC that goes along with it. For those devices, this
       // work-around reads and uses the MAC address as-is from EFuse,
    @@ -41,30 +40,21 @@ void get_mac_address_raw(uint8_t *mac) {
     #else
       esp_efuse_mac_get_default(mac);
     #endif
    -#endif
    -#if (defined USE_ESP8266 && defined USE_WIFI)
    -  WiFi.macAddress(mac);
    +#elif defined(USE_ESP8266)
    +  wifi_get_macaddr(STATION_IF, mac);
     #endif
     }
     
     std::string get_mac_address() {
    -  char tmp[20];
       uint8_t mac[6];
       get_mac_address_raw(mac);
    -#ifdef USE_WIFI
    -  sprintf(tmp, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    -#else
    -  return "";
    -#endif
    -  return std::string(tmp);
    +  return str_sprintf("%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
     }
     
     std::string get_mac_address_pretty() {
    -  char tmp[20];
       uint8_t mac[6];
       get_mac_address_raw(mac);
    -  sprintf(tmp, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    -  return std::string(tmp);
    +  return str_sprintf("%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
     }
     
     #ifdef USE_ESP32
    diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h
    index c67ad8eea3..120642c62e 100644
    --- a/esphome/core/helpers.h
    +++ b/esphome/core/helpers.h
    @@ -25,14 +25,13 @@
     
     namespace esphome {
     
    -/// Read the raw MAC address into the provided byte array (6 bytes).
    +/// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes).
     void get_mac_address_raw(uint8_t *mac);
     
    -/// Get the MAC address as a string, using lower case hex notation.
    -/// This can be used as way to identify this ESP.
    +/// Get the device MAC address as a string, in lowercase hex notation.
     std::string get_mac_address();
     
    -/// Get the MAC address as a string, using colon-separated upper case hex notation.
    +/// Get the device MAC address as a string, in colon-separated uppercase hex notation.
     std::string get_mac_address_pretty();
     
     #ifdef USE_ESP32
    
    From 515519bc8745a627dd02cf33c53933d5d4e4eeb1 Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?Krzysztof=20Bia=C5=82ek?= 
    Date: Mon, 15 Nov 2021 15:49:18 +0100
    Subject: [PATCH 1707/1841] Provide an option to select MQTT unique_id
     generator (#2701)
    
    Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Co-authored-by: Oxan van Leeuwen 
    ---
     esphome/components/mqtt/__init__.py        | 23 ++++++++++++++++++++--
     esphome/components/mqtt/mqtt_client.cpp    |  4 +++-
     esphome/components/mqtt/mqtt_client.h      | 11 ++++++++++-
     esphome/components/mqtt/mqtt_component.cpp | 14 ++++++++++---
     esphome/const.py                           |  1 +
     tests/test1.yaml                           |  1 +
     6 files changed, 47 insertions(+), 7 deletions(-)
    
    diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py
    index 0f7d246473..73af0bad90 100644
    --- a/esphome/components/mqtt/__init__.py
    +++ b/esphome/components/mqtt/__init__.py
    @@ -14,6 +14,7 @@ from esphome.const import (
         CONF_DISCOVERY,
         CONF_DISCOVERY_PREFIX,
         CONF_DISCOVERY_RETAIN,
    +    CONF_DISCOVERY_UNIQUE_ID_GENERATOR,
         CONF_ID,
         CONF_KEEPALIVE,
         CONF_LEVEL,
    @@ -95,6 +96,12 @@ MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent)
     MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent)
     MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent)
     
    +MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator")
    +MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = {
    +    "legacy": MQTTDiscoveryUniqueIdGenerator.MQTT_LEGACY_UNIQUE_ID_GENERATOR,
    +    "mac": MQTTDiscoveryUniqueIdGenerator.MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR,
    +}
    +
     
     def validate_config(value):
         # Populate default fields
    @@ -153,6 +160,9 @@ CONFIG_SCHEMA = cv.All(
                 cv.Optional(
                     CONF_DISCOVERY_PREFIX, default="homeassistant"
                 ): cv.publish_topic,
    +            cv.Optional(CONF_DISCOVERY_UNIQUE_ID_GENERATOR, default="legacy"): cv.enum(
    +                MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS
    +            ),
                 cv.Optional(CONF_USE_ABBREVIATIONS, default=True): cv.boolean,
                 cv.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA,
                 cv.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA,
    @@ -231,13 +241,22 @@ async def to_code(config):
         discovery = config[CONF_DISCOVERY]
         discovery_retain = config[CONF_DISCOVERY_RETAIN]
         discovery_prefix = config[CONF_DISCOVERY_PREFIX]
    +    discovery_unique_id_generator = config[CONF_DISCOVERY_UNIQUE_ID_GENERATOR]
     
         if not discovery:
             cg.add(var.disable_discovery())
         elif discovery == "CLEAN":
    -        cg.add(var.set_discovery_info(discovery_prefix, discovery_retain, True))
    +        cg.add(
    +            var.set_discovery_info(
    +                discovery_prefix, discovery_unique_id_generator, discovery_retain, True
    +            )
    +        )
         elif CONF_DISCOVERY_RETAIN in config or CONF_DISCOVERY_PREFIX in config:
    -        cg.add(var.set_discovery_info(discovery_prefix, discovery_retain))
    +        cg.add(
    +            var.set_discovery_info(
    +                discovery_prefix, discovery_unique_id_generator, discovery_retain
    +            )
    +        )
     
         cg.add(var.set_topic_prefix(config[CONF_TOPIC_PREFIX]))
     
    diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp
    index 040b0001fe..43c49e9f7f 100644
    --- a/esphome/components/mqtt/mqtt_client.cpp
    +++ b/esphome/components/mqtt/mqtt_client.cpp
    @@ -535,8 +535,10 @@ void MQTTClientComponent::set_birth_message(MQTTMessage &&message) {
     
     void MQTTClientComponent::set_shutdown_message(MQTTMessage &&message) { this->shutdown_message_ = std::move(message); }
     
    -void MQTTClientComponent::set_discovery_info(std::string &&prefix, bool retain, bool clean) {
    +void MQTTClientComponent::set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator,
    +                                             bool retain, bool clean) {
       this->discovery_info_.prefix = std::move(prefix);
    +  this->discovery_info_.unique_id_generator = unique_id_generator;
       this->discovery_info_.retain = retain;
       this->discovery_info_.clean = clean;
     }
    diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h
    index fa689eaa04..d6194da794 100644
    --- a/esphome/components/mqtt/mqtt_client.h
    +++ b/esphome/components/mqtt/mqtt_client.h
    @@ -55,6 +55,12 @@ struct Availability {
       std::string payload_not_available;
     };
     
    +/// available discovery unique_id generators
    +enum MQTTDiscoveryUniqueIdGenerator {
    +  MQTT_LEGACY_UNIQUE_ID_GENERATOR = 0,
    +  MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR,
    +};
    +
     /** Internal struct for MQTT Home Assistant discovery
      *
      * See MQTT Discovery.
    @@ -63,6 +69,7 @@ struct MQTTDiscoveryInfo {
       std::string prefix;  ///< The Home Assistant discovery prefix. Empty means disabled.
       bool retain;         ///< Whether to retain discovery messages.
       bool clean;
    +  MQTTDiscoveryUniqueIdGenerator unique_id_generator;
     };
     
     enum MQTTClientState {
    @@ -98,9 +105,11 @@ class MQTTClientComponent : public Component {
        *
        * See MQTT Discovery.
        * @param prefix The Home Assistant discovery prefix.
    +   * @param unique_id_generator Controls how UniqueId is generated.
        * @param retain Whether to retain discovery messages.
        */
    -  void set_discovery_info(std::string &&prefix, bool retain, bool clean = false);
    +  void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, bool retain,
    +                          bool clean = false);
       /// Get Home Assistant discovery info.
       const MQTTDiscoveryInfo &get_discovery_info() const;
       /// Globally disable Home Assistant discovery.
    diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp
    index e3ae4dea50..bf9f5e34b8 100644
    --- a/esphome/components/mqtt/mqtt_component.cpp
    +++ b/esphome/components/mqtt/mqtt_component.cpp
    @@ -114,9 +114,17 @@ bool MQTTComponent::send_discovery_() {
             if (!unique_id.empty()) {
               root[MQTT_UNIQUE_ID] = unique_id;
             } else {
    -          // default to almost-unique ID. It's a hack but the only way to get that
    -          // gorgeous device registry view.
    -          root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_();
    +          const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
    +          if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
    +            char friendly_name_hash[9];
    +            sprintf(friendly_name_hash, "%08x", fnv1_hash(this->friendly_name()));
    +            friendly_name_hash[8] = 0;  // ensure the hash-string ends with null
    +            root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash;
    +          } else {
    +            // default to almost-unique ID. It's a hack but the only way to get that
    +            // gorgeous device registry view.
    +            root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_();
    +          }
             }
     
             JsonObject &device_info = root.createNestedObject(MQTT_DEVICE);
    diff --git a/esphome/const.py b/esphome/const.py
    index fb241f04b5..9a778edfeb 100644
    --- a/esphome/const.py
    +++ b/esphome/const.py
    @@ -167,6 +167,7 @@ CONF_DISABLED_BY_DEFAULT = "disabled_by_default"
     CONF_DISCOVERY = "discovery"
     CONF_DISCOVERY_PREFIX = "discovery_prefix"
     CONF_DISCOVERY_RETAIN = "discovery_retain"
    +CONF_DISCOVERY_UNIQUE_ID_GENERATOR = "discovery_unique_id_generator"
     CONF_DISTANCE = "distance"
     CONF_DITHER = "dither"
     CONF_DIV_RATIO = "div_ratio"
    diff --git a/tests/test1.yaml b/tests/test1.yaml
    index 263754dc4f..18c6610b08 100644
    --- a/tests/test1.yaml
    +++ b/tests/test1.yaml
    @@ -98,6 +98,7 @@ mqtt:
       discovery: True
       discovery_retain: False
       discovery_prefix: discovery
    +  discovery_unique_id_generator: legacy
       topic_prefix: helloworld
       log_topic:
         topic: helloworld/hi
    
    From b386284180f33c0446fbb5033a99fe8e85d2fdd9 Mon Sep 17 00:00:00 2001
    From: cvwillegen 
    Date: Mon, 15 Nov 2021 20:06:55 +0100
    Subject: [PATCH 1708/1841] Ignore secrets.yaml on command line (#2715)
    
    ---
     esphome/__main__.py | 16 ++++++++--------
     esphome/const.py    |  1 +
     2 files changed, 9 insertions(+), 8 deletions(-)
    
    diff --git a/esphome/__main__.py b/esphome/__main__.py
    index c2a6dd343f..7c7c22dd1f 100644
    --- a/esphome/__main__.py
    +++ b/esphome/__main__.py
    @@ -18,6 +18,7 @@ from esphome.const import (
         CONF_PORT,
         CONF_ESPHOME,
         CONF_PLATFORMIO_OPTIONS,
    +    SECRETS_FILES,
     )
     from esphome.core import CORE, EsphomeError, coroutine
     from esphome.helpers import indent
    @@ -200,8 +201,7 @@ def upload_using_esptool(config, port):
             firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
             flash_images = [
                 platformio_api.FlashImage(
    -                path=idedata.firmware_bin_path,
    -                offset=firmware_offset,
    +                path=idedata.firmware_bin_path, offset=firmware_offset
                 ),
                 *idedata.extra_flash_images,
             ]
    @@ -607,10 +607,7 @@ def parse_args(argv):
             "wizard",
             help="A helpful setup wizard that will guide you through setting up ESPHome.",
         )
    -    parser_wizard.add_argument(
    -        "configuration",
    -        help="Your YAML configuration file.",
    -    )
    +    parser_wizard.add_argument("configuration", help="Your YAML configuration file.")
     
         parser_fingerprint = subparsers.add_parser(
             "mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
    @@ -632,8 +629,7 @@ def parse_args(argv):
             "dashboard", help="Create a simple web server for a dashboard."
         )
         parser_dashboard.add_argument(
    -        "configuration",
    -        help="Your YAML configuration file directory.",
    +        "configuration", help="Your YAML configuration file directory."
         )
         parser_dashboard.add_argument(
             "--port",
    @@ -789,6 +785,10 @@ def run_esphome(argv):
                 return 1
     
         for conf_path in args.configuration:
    +        if any(os.path.basename(conf_path) == x for x in SECRETS_FILES):
    +            _LOGGER.warning("Skipping secrets file %s", conf_path)
    +            continue
    +
             CORE.config_path = conf_path
             CORE.dashboard = args.dashboard
     
    diff --git a/esphome/const.py b/esphome/const.py
    index 9a778edfeb..2a7942a2b4 100644
    --- a/esphome/const.py
    +++ b/esphome/const.py
    @@ -11,6 +11,7 @@ TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266]
     
     SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"}
     HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}
    +SECRETS_FILES = {"secrets.yaml", "secrets.yml"}
     
     
     CONF_ABOVE = "above"
    
    From 0809673ba9fa9d3a024653522d5b9aa8aea470b2 Mon Sep 17 00:00:00 2001
    From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Date: Tue, 16 Nov 2021 09:53:52 +1300
    Subject: [PATCH 1709/1841] Add zeroconf as a direct dependency and lock the
     version (#2729)
    
    ---
     requirements.txt | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/requirements.txt b/requirements.txt
    index 6e1fe56057..c4b211283d 100644
    --- a/requirements.txt
    +++ b/requirements.txt
    @@ -11,6 +11,7 @@ esptool==3.2
     click==8.0.3
     esphome-dashboard==20211021.1
     aioesphomeapi==10.2.0
    +zeroconf==0.36.13
     
     # esp-idf requires this, but doesn't bundle it by default
     # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24
    
    From 9e4fa5dcf1b7f8526fe40f68cbbec3e1155dfa18 Mon Sep 17 00:00:00 2001
    From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Date: Tue, 16 Nov 2021 11:02:45 +1300
    Subject: [PATCH 1710/1841] Improv serial/checksum changes (#2731)
    
    Co-authored-by: Paulus Schoutsen 
    ---
     esphome/components/improv/improv.cpp          | 54 ++++++++++---------
     esphome/components/improv/improv.h            |  9 ++--
     .../improv_serial/improv_serial_component.cpp | 35 ++++++++----
     3 files changed, 61 insertions(+), 37 deletions(-)
    
    diff --git a/esphome/components/improv/improv.cpp b/esphome/components/improv/improv.cpp
    index 94068bc626..759962b51a 100644
    --- a/esphome/components/improv/improv.cpp
    +++ b/esphome/components/improv/improv.cpp
    @@ -2,30 +2,32 @@
     
     namespace improv {
     
    -ImprovCommand parse_improv_data(const std::vector &data) {
    -  return parse_improv_data(data.data(), data.size());
    +ImprovCommand parse_improv_data(const std::vector &data, bool check_checksum) {
    +  return parse_improv_data(data.data(), data.size(), check_checksum);
     }
     
    -ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
    +ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum) {
       ImprovCommand improv_command;
       Command command = (Command) data[0];
       uint8_t data_length = data[1];
     
    -  if (data_length != length - 3) {
    +  if (data_length != length - 2 - check_checksum) {
         improv_command.command = UNKNOWN;
         return improv_command;
       }
     
    -  uint8_t checksum = data[length - 1];
    +  if (check_checksum) {
    +    uint8_t checksum = data[length - 1];
     
    -  uint32_t calculated_checksum = 0;
    -  for (uint8_t i = 0; i < length - 1; i++) {
    -    calculated_checksum += data[i];
    -  }
    +    uint32_t calculated_checksum = 0;
    +    for (uint8_t i = 0; i < length - 1; i++) {
    +      calculated_checksum += data[i];
    +    }
     
    -  if ((uint8_t) calculated_checksum != checksum) {
    -    improv_command.command = BAD_CHECKSUM;
    -    return improv_command;
    +    if ((uint8_t) calculated_checksum != checksum) {
    +      improv_command.command = BAD_CHECKSUM;
    +      return improv_command;
    +    }
       }
     
       if (command == WIFI_SETTINGS) {
    @@ -46,7 +48,7 @@ ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
       return improv_command;
     }
     
    -std::vector build_rpc_response(Command command, const std::vector &datum) {
    +std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum) {
       std::vector out;
       uint32_t length = 0;
       out.push_back(command);
    @@ -58,17 +60,19 @@ std::vector build_rpc_response(Command command, const std::vector build_rpc_response(Command command, const std::vector &datum) {
    +#ifdef ARDUINO
    +std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum) {
       std::vector out;
       uint32_t length = 0;
       out.push_back(command);
    @@ -80,14 +84,16 @@ std::vector build_rpc_response(Command command, const std::vector &data);
    -ImprovCommand parse_improv_data(const uint8_t *data, size_t length);
    +ImprovCommand parse_improv_data(const std::vector &data, bool check_checksum = true);
    +ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum = true);
     
    -std::vector build_rpc_response(Command command, const std::vector &datum);
    +std::vector build_rpc_response(Command command, const std::vector &datum,
    +                                        bool add_checksum = true);
     #ifdef ARDUINO
    -std::vector build_rpc_response(Command command, const std::vector &datum);
    +std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum = true);
     #endif  // ARDUINO
     
     }  // namespace improv
    diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp
    index abbb76ab11..a9a7467125 100644
    --- a/esphome/components/improv_serial/improv_serial_component.cpp
    +++ b/esphome/components/improv_serial/improv_serial_component.cpp
    @@ -98,13 +98,13 @@ std::vector ImprovSerialComponent::build_rpc_settings_response_(improv:
       std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
       urls.push_back(webserver_url);
     #endif
    -  std::vector data = improv::build_rpc_response(command, urls);
    +  std::vector data = improv::build_rpc_response(command, urls, false);
       return data;
     }
     
     std::vector ImprovSerialComponent::build_version_info_() {
       std::vector infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
    -  std::vector data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos);
    +  std::vector data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
       return data;
     };
     
    @@ -140,22 +140,33 @@ bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
       if (at < 8 + data_len)
         return true;
     
    -  if (at == 8 + data_len) {
    +  if (at == 8 + data_len)
    +    return true;
    +
    +  if (at == 8 + data_len + 1) {
    +    uint8_t checksum = 0x00;
    +    for (uint8_t i = 0; i < at; i++)
    +      checksum += raw[i];
    +
    +    if (checksum != byte) {
    +      ESP_LOGW(TAG, "Error decoding Improv payload");
    +      this->set_error_(improv::ERROR_INVALID_RPC);
    +      return false;
    +    }
    +
         if (type == TYPE_RPC) {
           this->set_error_(improv::ERROR_NONE);
    -      auto command = improv::parse_improv_data(&raw[9], data_len);
    +      auto command = improv::parse_improv_data(&raw[9], data_len, false);
           return this->parse_improv_payload_(command);
         }
       }
    -  return true;
    +
    +  // If we got here then the command coming is is improv, but not an RPC command
    +  return false;
     }
     
     bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
       switch (command.command) {
    -    case improv::BAD_CHECKSUM:
    -      ESP_LOGW(TAG, "Error decoding Improv payload");
    -      this->set_error_(improv::ERROR_INVALID_RPC);
    -      return false;
         case improv::WIFI_SETTINGS: {
           wifi::WiFiAP sta{};
           sta.set_ssid(command.ssid);
    @@ -232,6 +243,12 @@ void ImprovSerialComponent::send_response_(std::vector &response) {
       data[7] = TYPE_RPC_RESPONSE;
       data[8] = response.size();
       data.insert(data.end(), response.begin(), response.end());
    +
    +  uint8_t checksum = 0x00;
    +  for (uint8_t d : data)
    +    checksum += d;
    +  data.push_back(checksum);
    +
       this->write_data_(data);
     }
     
    
    From f1954df57337fe4ed08cbd9a59d4bd156278113f Mon Sep 17 00:00:00 2001
    From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Date: Tue, 16 Nov 2021 12:47:06 +1300
    Subject: [PATCH 1711/1841] Fix zeroconf time comparisons (#2733)
    
    Co-authored-by: J. Nick Koston 
    ---
     esphome/zeroconf.py | 7 ++++---
     1 file changed, 4 insertions(+), 3 deletions(-)
    
    diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py
    index a19fc143ec..1fbdf7e93f 100644
    --- a/esphome/zeroconf.py
    +++ b/esphome/zeroconf.py
    @@ -13,8 +13,9 @@ from zeroconf import (
         RecordUpdateListener,
         Zeroconf,
         ServiceBrowser,
    +    ServiceStateChange,
    +    current_time_millis,
     )
    -from zeroconf._services import ServiceStateChange
     
     _CLASS_IN = 1
     _FLAGS_QR_QUERY = 0x0000  # query
    @@ -88,7 +89,7 @@ class DashboardStatus(threading.Thread):
             entries = self.zc.cache.entries_with_name(key)
             if not entries:
                 return False
    -        now = time.time() * 1000
    +        now = current_time_millis()
     
             return any(
                 (entry.created + DashboardStatus.OFFLINE_AFTER) >= now for entry in entries
    @@ -99,7 +100,7 @@ class DashboardStatus(threading.Thread):
                 self.on_update(
                     {key: self.host_status(host) for key, host in self.key_to_host.items()}
                 )
    -            now = time.time() * 1000
    +            now = current_time_millis()
                 for host in self.query_hosts:
                     entries = self.zc.cache.entries_with_name(host)
                     if not entries or all(
    
    From b35f5097848b94e14f45816ddfd8036057dcb770 Mon Sep 17 00:00:00 2001
    From: Jan Harkes 
    Date: Tue, 16 Nov 2021 03:16:43 -0500
    Subject: [PATCH 1712/1841] Allow for subsecond sampling of hmc5883l (#2735)
    
    ---
     esphome/components/hmc5883l/sensor.py | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/esphome/components/hmc5883l/sensor.py b/esphome/components/hmc5883l/sensor.py
    index 73e7472dcf..9d8701079e 100644
    --- a/esphome/components/hmc5883l/sensor.py
    +++ b/esphome/components/hmc5883l/sensor.py
    @@ -114,8 +114,8 @@ CONFIG_SCHEMA = (
     
     
     def auto_data_rate(config):
    -    interval_sec = config[CONF_UPDATE_INTERVAL].seconds
    -    interval_hz = 1.0 / interval_sec
    +    interval_msec = config[CONF_UPDATE_INTERVAL].total_milliseconds
    +    interval_hz = 1000.0 / interval_msec
         for datarate in sorted(HMC5883LDatarates.keys()):
             if float(datarate) >= interval_hz:
                 return HMC5883LDatarates[datarate]
    
    From 8ece639987339a7082fdcea68a4d766906e786d9 Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?H=2E=20=C3=81rkosi=20R=C3=B3bert?= 
    Date: Tue, 16 Nov 2021 11:28:12 +0100
    Subject: [PATCH 1713/1841] Change log level from DEBUG to INFO for sniffing
     services (#2736)
    
    Sniffing for codes only happens if the user deliberately asked for it with the related service through HA - to find out the codes present in the air. The resulted data shouldn't be printed out only in debug mode, as this is information required to be known on demand for later use, not actually a debug info. Changing log level from DEBUG to INFO for sniffing services has two benefits:
    - no need to run firmware with DEBUG enabled for occasional sniffing with devices in production (no need to flash back and forth with different log levels set just for this reason)
    - if the user still wants DEBUG enabled, sniffed data appears in different color, it's easier to find between the lines.
    ---
     esphome/components/rf_bridge/rf_bridge.cpp | 12 ++++++------
     1 file changed, 6 insertions(+), 6 deletions(-)
    
    diff --git a/esphome/components/rf_bridge/rf_bridge.cpp b/esphome/components/rf_bridge/rf_bridge.cpp
    index a4259e5aa2..d8c8047496 100644
    --- a/esphome/components/rf_bridge/rf_bridge.cpp
    +++ b/esphome/components/rf_bridge/rf_bridge.cpp
    @@ -52,7 +52,7 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) {
           if (action == RF_CODE_LEARN_OK)
             ESP_LOGD(TAG, "Learning success");
     
    -      ESP_LOGD(TAG, "Received RFBridge Code: sync=0x%04X low=0x%04X high=0x%04X code=0x%06X", data.sync, data.low,
    +      ESP_LOGI(TAG, "Received RFBridge Code: sync=0x%04X low=0x%04X high=0x%04X code=0x%06X", data.sync, data.low,
                    data.high, data.code);
           this->data_callback_.call(data);
           break;
    @@ -73,7 +73,7 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) {
             data.code += next_byte;
           }
     
    -      ESP_LOGD(TAG, "Received RFBridge Advanced Code: length=0x%02X protocol=0x%02X code=0x%s", data.length,
    +      ESP_LOGI(TAG, "Received RFBridge Advanced Code: length=0x%02X protocol=0x%02X code=0x%s", data.length,
                    data.protocol, data.code.c_str());
           this->advanced_data_callback_.call(data);
           break;
    @@ -97,7 +97,7 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) {
               str += " ";
             }
           }
    -      ESP_LOGD(TAG, "Received RFBridge Bucket: %s", str.c_str());
    +      ESP_LOGI(TAG, "Received RFBridge Bucket: %s", str.c_str());
           break;
         }
         default:
    @@ -186,7 +186,7 @@ void RFBridgeComponent::dump_config() {
     }
     
     void RFBridgeComponent::start_advanced_sniffing() {
    -  ESP_LOGD(TAG, "Advanced Sniffing on");
    +  ESP_LOGI(TAG, "Advanced Sniffing on");
       this->write(RF_CODE_START);
       this->write(RF_CODE_SNIFFING_ON);
       this->write(RF_CODE_STOP);
    @@ -194,7 +194,7 @@ void RFBridgeComponent::start_advanced_sniffing() {
     }
     
     void RFBridgeComponent::stop_advanced_sniffing() {
    -  ESP_LOGD(TAG, "Advanced Sniffing off");
    +  ESP_LOGI(TAG, "Advanced Sniffing off");
       this->write(RF_CODE_START);
       this->write(RF_CODE_SNIFFING_OFF);
       this->write(RF_CODE_STOP);
    @@ -202,7 +202,7 @@ void RFBridgeComponent::stop_advanced_sniffing() {
     }
     
     void RFBridgeComponent::start_bucket_sniffing() {
    -  ESP_LOGD(TAG, "Raw Bucket Sniffing on");
    +  ESP_LOGI(TAG, "Raw Bucket Sniffing on");
       this->write(RF_CODE_START);
       this->write(RF_CODE_RFIN_BUCKET);
       this->write(RF_CODE_STOP);
    
    From f565ff5def4620afe0ced65499310275d81d86aa Mon Sep 17 00:00:00 2001
    From: Ryan Hoffman 
    Date: Tue, 16 Nov 2021 12:53:36 -0500
    Subject: [PATCH 1714/1841] Use as_reversed_hex_array in ble_sensor to fix UUID
     parsing (#2737)
    
    #1627 renamed as_hex_array to as_reversed_hex_array but forgot to rename these users.
    ---
     esphome/components/ble_client/sensor/__init__.py | 10 +++++++---
     1 file changed, 7 insertions(+), 3 deletions(-)
    
    diff --git a/esphome/components/ble_client/sensor/__init__.py b/esphome/components/ble_client/sensor/__init__.py
    index efe4bf0e9a..4aa6a92ba5 100644
    --- a/esphome/components/ble_client/sensor/__init__.py
    +++ b/esphome/components/ble_client/sensor/__init__.py
    @@ -67,7 +67,7 @@ async def to_code(config):
                 var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
             )
         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
    -        uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
    +        uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
             cg.add(var.set_service_uuid128(uuid128))
     
         if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
    @@ -87,7 +87,9 @@ async def to_code(config):
         elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
             esp32_ble_tracker.bt_uuid128_format
         ):
    -        uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_CHARACTERISTIC_UUID])
    +        uuid128 = esp32_ble_tracker.as_reversed_hex_array(
    +            config[CONF_CHARACTERISTIC_UUID]
    +        )
             cg.add(var.set_char_uuid128(uuid128))
     
         if CONF_DESCRIPTOR_UUID in config:
    @@ -108,7 +110,9 @@ async def to_code(config):
             elif len(config[CONF_DESCRIPTOR_UUID]) == len(
                 esp32_ble_tracker.bt_uuid128_format
             ):
    -            uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_DESCRIPTOR_UUID])
    +            uuid128 = esp32_ble_tracker.as_reversed_hex_array(
    +                config[CONF_DESCRIPTOR_UUID]
    +            )
                 cg.add(var.set_descr_uuid128(uuid128))
     
         if CONF_LAMBDA in config:
    
    From 57bdc2b88540402f57dd2ef7774b162191f5b95e Mon Sep 17 00:00:00 2001
    From: Ryan Hoffman 
    Date: Tue, 16 Nov 2021 13:30:42 -0500
    Subject: [PATCH 1715/1841] Add ble_client binary_output (#2200)
    
    Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    ---
     esphome/components/ble_client/ble_client.cpp  |  9 +++
     esphome/components/ble_client/ble_client.h    |  2 +-
     .../components/ble_client/output/__init__.py  | 67 +++++++++++++++++
     .../ble_client/output/ble_binary_output.cpp   | 71 +++++++++++++++++++
     .../ble_client/output/ble_binary_output.h     | 39 ++++++++++
     5 files changed, 187 insertions(+), 1 deletion(-)
     create mode 100644 esphome/components/ble_client/output/__init__.py
     create mode 100644 esphome/components/ble_client/output/ble_binary_output.cpp
     create mode 100644 esphome/components/ble_client/output/ble_binary_output.h
    
    diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp
    index e6cdb0c23d..407f1a1d17 100644
    --- a/esphome/components/ble_client/ble_client.cpp
    +++ b/esphome/components/ble_client/ble_client.cpp
    @@ -388,6 +388,15 @@ BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
       return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
     }
     
    +void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
    +  auto client = this->service->client;
    +  auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val,
    +                                         ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
    +  if (status) {
    +    ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
    +  }
    +}
    +
     }  // namespace ble_client
     }  // namespace esphome
     
    diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h
    index 23123914e8..5680b69f72 100644
    --- a/esphome/components/ble_client/ble_client.h
    +++ b/esphome/components/ble_client/ble_client.h
    @@ -59,7 +59,7 @@ class BLECharacteristic {
       void parse_descriptors();
       BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
       BLEDescriptor *get_descriptor(uint16_t uuid);
    -
    +  void write_value(uint8_t *new_val, int16_t new_val_size);
       BLEService *service;
     };
     
    diff --git a/esphome/components/ble_client/output/__init__.py b/esphome/components/ble_client/output/__init__.py
    new file mode 100644
    index 0000000000..fe5835ca82
    --- /dev/null
    +++ b/esphome/components/ble_client/output/__init__.py
    @@ -0,0 +1,67 @@
    +import esphome.codegen as cg
    +import esphome.config_validation as cv
    +from esphome.components import output, ble_client, esp32_ble_tracker
    +from esphome.const import CONF_ID, CONF_SERVICE_UUID
    +from .. import ble_client_ns
    +
    +
    +DEPENDENCIES = ["ble_client"]
    +
    +CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
    +
    +BLEBinaryOutput = ble_client_ns.class_(
    +    "BLEBinaryOutput", output.BinaryOutput, ble_client.BLEClientNode, cg.Component
    +)
    +
    +CONFIG_SCHEMA = cv.All(
    +    output.BINARY_OUTPUT_SCHEMA.extend(
    +        {
    +            cv.Required(CONF_ID): cv.declare_id(BLEBinaryOutput),
    +            cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
    +            cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
    +        }
    +    )
    +    .extend(cv.COMPONENT_SCHEMA)
    +    .extend(ble_client.BLE_CLIENT_SCHEMA)
    +)
    +
    +
    +def to_code(config):
    +    var = cg.new_Pvariable(config[CONF_ID])
    +    if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
    +        cg.add(
    +            var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
    +        )
    +    elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
    +        cg.add(
    +            var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
    +        )
    +    elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
    +        uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
    +        cg.add(var.set_service_uuid128(uuid128))
    +
    +    if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
    +        cg.add(
    +            var.set_char_uuid16(
    +                esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
    +            )
    +        )
    +    elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
    +        esp32_ble_tracker.bt_uuid32_format
    +    ):
    +        cg.add(
    +            var.set_char_uuid32(
    +                esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
    +            )
    +        )
    +    elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
    +        esp32_ble_tracker.bt_uuid128_format
    +    ):
    +        uuid128 = esp32_ble_tracker.as_reversed_hex_array(
    +            config[CONF_CHARACTERISTIC_UUID]
    +        )
    +        cg.add(var.set_char_uuid128(uuid128))
    +
    +    yield output.register_output(var, config)
    +    yield ble_client.register_ble_node(var, config)
    +    yield cg.register_component(var, config)
    diff --git a/esphome/components/ble_client/output/ble_binary_output.cpp b/esphome/components/ble_client/output/ble_binary_output.cpp
    new file mode 100644
    index 0000000000..ff3711e842
    --- /dev/null
    +++ b/esphome/components/ble_client/output/ble_binary_output.cpp
    @@ -0,0 +1,71 @@
    +#include "ble_binary_output.h"
    +#include "esphome/core/log.h"
    +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
    +
    +#ifdef USE_ESP32
    +namespace esphome {
    +namespace ble_client {
    +
    +static const char *const TAG = "ble_binary_output";
    +
    +void BLEBinaryOutput::dump_config() {
    +  ESP_LOGCONFIG(TAG, "BLE Binary Output:");
    +  ESP_LOGCONFIG(TAG, "  MAC address        : %s", this->parent_->address_str().c_str());
    +  ESP_LOGCONFIG(TAG, "  Service UUID       : %s", this->service_uuid_.to_string().c_str());
    +  ESP_LOGCONFIG(TAG, "  Characteristic UUID: %s", this->char_uuid_.to_string().c_str());
    +  LOG_BINARY_OUTPUT(this);
    +}
    +
    +void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
    +                                          esp_ble_gattc_cb_param_t *param) {
    +  switch (event) {
    +    case ESP_GATTC_OPEN_EVT:
    +      this->client_state_ = espbt::ClientState::ESTABLISHED;
    +      ESP_LOGW(TAG, "[%s] Connected successfully!", this->char_uuid_.to_string().c_str());
    +      break;
    +    case ESP_GATTC_DISCONNECT_EVT:
    +      ESP_LOGW(TAG, "[%s] Disconnected", this->char_uuid_.to_string().c_str());
    +      this->client_state_ = espbt::ClientState::IDLE;
    +      break;
    +    case ESP_GATTC_WRITE_CHAR_EVT: {
    +      if (param->write.status == 0) {
    +        break;
    +      }
    +
    +      auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
    +      if (chr == nullptr) {
    +        ESP_LOGW(TAG, "[%s] Characteristic not found.", this->char_uuid_.to_string().c_str());
    +        break;
    +      }
    +      if (param->write.handle == chr->handle) {
    +        ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status);
    +      }
    +      break;
    +    }
    +    default:
    +      break;
    +  }
    +}
    +
    +void BLEBinaryOutput::write_state(bool state) {
    +  if (this->client_state_ != espbt::ClientState::ESTABLISHED) {
    +    ESP_LOGW(TAG, "[%s] Not connected to BLE client.  State update can not be written.",
    +             this->char_uuid_.to_string().c_str());
    +    return;
    +  }
    +
    +  auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
    +  if (chr == nullptr) {
    +    ESP_LOGW(TAG, "[%s] Characteristic not found.  State update can not be written.",
    +             this->char_uuid_.to_string().c_str());
    +    return;
    +  }
    +
    +  uint8_t state_as_uint = (uint8_t) state;
    +  ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint);
    +  chr->write_value(&state_as_uint, sizeof(state_as_uint));
    +}
    +
    +}  // namespace ble_client
    +}  // namespace esphome
    +#endif
    diff --git a/esphome/components/ble_client/output/ble_binary_output.h b/esphome/components/ble_client/output/ble_binary_output.h
    new file mode 100644
    index 0000000000..e1d62a267b
    --- /dev/null
    +++ b/esphome/components/ble_client/output/ble_binary_output.h
    @@ -0,0 +1,39 @@
    +#pragma once
    +
    +#include "esphome/core/component.h"
    +#include "esphome/components/ble_client/ble_client.h"
    +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
    +#include "esphome/components/output/binary_output.h"
    +
    +#ifdef USE_ESP32
    +#include 
    +namespace esphome {
    +namespace ble_client {
    +
    +namespace espbt = esphome::esp32_ble_tracker;
    +
    +class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, public Component {
    + public:
    +  void dump_config() override;
    +  void loop() override {}
    +  float get_setup_priority() const override { return setup_priority::DATA; }
    +  void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
    +  void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
    +  void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
    +  void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
    +  void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
    +  void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
    +  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
    +                           esp_ble_gattc_cb_param_t *param) override;
    +
    + protected:
    +  void write_state(bool state) override;
    +  espbt::ESPBTUUID service_uuid_;
    +  espbt::ESPBTUUID char_uuid_;
    +  espbt::ClientState client_state_;
    +};
    +
    +}  // namespace ble_client
    +}  // namespace esphome
    +
    +#endif
    
    From df68403b6d7fbde9677015189dc657ebff056adb Mon Sep 17 00:00:00 2001
    From: rotarykite 
    Date: Wed, 17 Nov 2021 02:57:03 +0800
    Subject: [PATCH 1716/1841] Fix senseair component uart read timeout (#2658)
    
    Co-authored-by: DAVe3283 
    Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Co-authored-by: Chua Jun Chieh 
    ---
     esphome/components/senseair/senseair.cpp | 10 +++++++---
     1 file changed, 7 insertions(+), 3 deletions(-)
    
    diff --git a/esphome/components/senseair/senseair.cpp b/esphome/components/senseair/senseair.cpp
    index 610892dd9e..50b9e01f17 100644
    --- a/esphome/components/senseair/senseair.cpp
    +++ b/esphome/components/senseair/senseair.cpp
    @@ -141,12 +141,16 @@ void SenseAirComponent::abc_get_period() {
     }
     
     bool SenseAirComponent::senseair_write_command_(const uint8_t *command, uint8_t *response, uint8_t response_length) {
    +  // Verify we have somewhere to store the response
    +  if (response == nullptr) {
    +    return false;
    +  }
    +  // Write wake up byte required by some S8 sensor models
    +  this->write_byte(0);
       this->flush();
    +  delay(5);
       this->write_array(command, SENSEAIR_REQUEST_LENGTH);
     
    -  if (response == nullptr)
    -    return true;
    -
       bool ret = this->read_array(response, response_length);
       this->flush();
       return ret;
    
    From dbcfa7b5990d7b9f0e2b70b9d807b16e8e6a8f64 Mon Sep 17 00:00:00 2001
    From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Date: Wed, 17 Nov 2021 16:22:38 +1300
    Subject: [PATCH 1717/1841] Remove duplicated const data in esp8266 boards
     (#2740)
    
    ---
     esphome/components/esp8266/boards.py | 58 ----------------------------
     1 file changed, 58 deletions(-)
    
    diff --git a/esphome/components/esp8266/boards.py b/esphome/components/esp8266/boards.py
    index c49aae4ffa..410e934615 100644
    --- a/esphome/components/esp8266/boards.py
    +++ b/esphome/components/esp8266/boards.py
    @@ -206,61 +206,3 @@ ESP8266_BOARD_PINS = {
         "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,
    -    "espmxdevkit": FLASH_SIZE_1_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,
    -    "sonoff_basic": FLASH_SIZE_1_MB,
    -    "sonoff_s20": FLASH_SIZE_1_MB,
    -    "sonoff_sv": FLASH_SIZE_1_MB,
    -    "sonoff_th": FLASH_SIZE_1_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"),
    -}
    
    From 0469e19f54e10a5f55e18db19f7686fc6474b04c Mon Sep 17 00:00:00 2001
    From: Evgeny 
    Date: Wed, 17 Nov 2021 09:52:40 +0100
    Subject: [PATCH 1718/1841] Fix HM3301 AQI index calculator (#2739)
    
    ---
     esphome/components/hm3301/aqi_calculator.h  | 2 +-
     esphome/components/hm3301/caqi_calculator.h | 4 +---
     2 files changed, 2 insertions(+), 4 deletions(-)
    
    diff --git a/esphome/components/hm3301/aqi_calculator.h b/esphome/components/hm3301/aqi_calculator.h
    index 1410eac72b..a3839b643c 100644
    --- a/esphome/components/hm3301/aqi_calculator.h
    +++ b/esphome/components/hm3301/aqi_calculator.h
    @@ -33,7 +33,7 @@ class AQICalculator : public AbstractAQICalculator {
         int conc_lo = array[grid_index][0];
         int conc_hi = array[grid_index][1];
     
    -    return ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo;
    +    return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
       }
     
       int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
    diff --git a/esphome/components/hm3301/caqi_calculator.h b/esphome/components/hm3301/caqi_calculator.h
    index 51158454d0..a7f5460e0a 100644
    --- a/esphome/components/hm3301/caqi_calculator.h
    +++ b/esphome/components/hm3301/caqi_calculator.h
    @@ -37,9 +37,7 @@ class CAQICalculator : public AbstractAQICalculator {
         int conc_lo = array[grid_index][0];
         int conc_hi = array[grid_index][1];
     
    -    int aqi = ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo;
    -
    -    return aqi;
    +    return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
       }
     
       int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
    
    From 6c1ef398bb7e981a6cd281175c89f702b21f19a0 Mon Sep 17 00:00:00 2001
    From: Franck Nijhof 
    Date: Wed, 17 Nov 2021 11:28:31 +0100
    Subject: [PATCH 1719/1841] Re-instate device class update for binary sensors
     (#2743)
    
    ---
     esphome/components/binary_sensor/__init__.py | 2 ++
     esphome/const.py                             | 1 +
     2 files changed, 3 insertions(+)
    
    diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py
    index 3f11e18e45..1eab76d54e 100644
    --- a/esphome/components/binary_sensor/__init__.py
    +++ b/esphome/components/binary_sensor/__init__.py
    @@ -49,6 +49,7 @@ from esphome.const import (
         DEVICE_CLASS_SMOKE,
         DEVICE_CLASS_SOUND,
         DEVICE_CLASS_TAMPER,
    +    DEVICE_CLASS_UPDATE,
         DEVICE_CLASS_VIBRATION,
         DEVICE_CLASS_WINDOW,
     )
    @@ -82,6 +83,7 @@ DEVICE_CLASSES = [
         DEVICE_CLASS_SMOKE,
         DEVICE_CLASS_SOUND,
         DEVICE_CLASS_TAMPER,
    +    DEVICE_CLASS_UPDATE,
         DEVICE_CLASS_VIBRATION,
         DEVICE_CLASS_WINDOW,
     ]
    diff --git a/esphome/const.py b/esphome/const.py
    index 2a7942a2b4..f7beee8245 100644
    --- a/esphome/const.py
    +++ b/esphome/const.py
    @@ -863,6 +863,7 @@ DEVICE_CLASS_SAFETY = "safety"
     DEVICE_CLASS_SMOKE = "smoke"
     DEVICE_CLASS_SOUND = "sound"
     DEVICE_CLASS_TAMPER = "tamper"
    +DEVICE_CLASS_UPDATE = "update"
     DEVICE_CLASS_VIBRATION = "vibration"
     DEVICE_CLASS_WINDOW = "window"
     # device classes of both binary_sensor and sensor component
    
    From df6730be55a19b5a3e1d8974beb4740fdc29e8fc Mon Sep 17 00:00:00 2001
    From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Date: Thu, 18 Nov 2021 06:23:17 +1300
    Subject: [PATCH 1720/1841] Move to use improv lib from platformio (#2741)
    
    ---
     CODEOWNERS                                    |  1 -
     esphome/components/esp32_improv/__init__.py   |  3 +-
     .../esp32_improv/esp32_improv_component.h     |  5 +-
     esphome/components/improv/__init__.py         |  1 -
     esphome/components/improv/improv.cpp          | 99 -------------------
     esphome/components/improv/improv.h            | 63 ------------
     esphome/components/improv_serial/__init__.py  |  2 +-
     .../improv_serial/improv_serial_component.h   |  3 +-
     platformio.ini                                |  1 +
     9 files changed, 9 insertions(+), 169 deletions(-)
     delete mode 100644 esphome/components/improv/__init__.py
     delete mode 100644 esphome/components/improv/improv.cpp
     delete mode 100644 esphome/components/improv/improv.h
    
    diff --git a/CODEOWNERS b/CODEOWNERS
    index 18b4564280..80c5dc34c1 100644
    --- a/CODEOWNERS
    +++ b/CODEOWNERS
    @@ -72,7 +72,6 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal
     esphome/components/homeassistant/* @OttoWinter
     esphome/components/hrxl_maxsonar_wr/* @netmikey
     esphome/components/i2c/* @esphome/core
    -esphome/components/improv/* @jesserockz
     esphome/components/improv_serial/* @esphome/core
     esphome/components/inkbird_ibsth1_mini/* @fkirill
     esphome/components/inkplate6/* @jesserockz
    diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py
    index 0b0214c63e..80c53f7c2a 100644
    --- a/esphome/components/esp32_improv/__init__.py
    +++ b/esphome/components/esp32_improv/__init__.py
    @@ -4,7 +4,7 @@ from esphome.components import binary_sensor, output, esp32_ble_server
     from esphome.const import CONF_ID
     
     
    -AUTO_LOAD = ["binary_sensor", "output", "improv", "esp32_ble_server"]
    +AUTO_LOAD = ["binary_sensor", "output", "esp32_ble_server"]
     CODEOWNERS = ["@jesserockz"]
     CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
     DEPENDENCIES = ["wifi", "esp32"]
    @@ -56,6 +56,7 @@ async def to_code(config):
         cg.add(ble_server.register_service_component(var))
     
         cg.add_define("USE_IMPROV")
    +    cg.add_library("esphome/Improv", "1.0.0")
     
         cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION]))
         cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))
    diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h
    index 3a5d150fbe..45639f2f63 100644
    --- a/esphome/components/esp32_improv/esp32_improv_component.h
    +++ b/esphome/components/esp32_improv/esp32_improv_component.h
    @@ -1,9 +1,8 @@
     #pragma once
     
     #include "esphome/components/binary_sensor/binary_sensor.h"
    -#include "esphome/components/esp32_ble_server/ble_server.h"
     #include "esphome/components/esp32_ble_server/ble_characteristic.h"
    -#include "esphome/components/improv/improv.h"
    +#include "esphome/components/esp32_ble_server/ble_server.h"
     #include "esphome/components/output/binary_output.h"
     #include "esphome/components/wifi/wifi_component.h"
     #include "esphome/core/component.h"
    @@ -12,6 +11,8 @@
     
     #ifdef USE_ESP32
     
    +#include 
    +
     namespace esphome {
     namespace esp32_improv {
     
    diff --git a/esphome/components/improv/__init__.py b/esphome/components/improv/__init__.py
    deleted file mode 100644
    index b1de57df8f..0000000000
    --- a/esphome/components/improv/__init__.py
    +++ /dev/null
    @@ -1 +0,0 @@
    -CODEOWNERS = ["@jesserockz"]
    diff --git a/esphome/components/improv/improv.cpp b/esphome/components/improv/improv.cpp
    deleted file mode 100644
    index 759962b51a..0000000000
    --- a/esphome/components/improv/improv.cpp
    +++ /dev/null
    @@ -1,99 +0,0 @@
    -#include "improv.h"
    -
    -namespace improv {
    -
    -ImprovCommand parse_improv_data(const std::vector &data, bool check_checksum) {
    -  return parse_improv_data(data.data(), data.size(), check_checksum);
    -}
    -
    -ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum) {
    -  ImprovCommand improv_command;
    -  Command command = (Command) data[0];
    -  uint8_t data_length = data[1];
    -
    -  if (data_length != length - 2 - check_checksum) {
    -    improv_command.command = UNKNOWN;
    -    return improv_command;
    -  }
    -
    -  if (check_checksum) {
    -    uint8_t checksum = data[length - 1];
    -
    -    uint32_t calculated_checksum = 0;
    -    for (uint8_t i = 0; i < length - 1; i++) {
    -      calculated_checksum += data[i];
    -    }
    -
    -    if ((uint8_t) calculated_checksum != checksum) {
    -      improv_command.command = BAD_CHECKSUM;
    -      return improv_command;
    -    }
    -  }
    -
    -  if (command == WIFI_SETTINGS) {
    -    uint8_t ssid_length = data[2];
    -    uint8_t ssid_start = 3;
    -    size_t ssid_end = ssid_start + ssid_length;
    -
    -    uint8_t pass_length = data[ssid_end];
    -    size_t pass_start = ssid_end + 1;
    -    size_t pass_end = pass_start + pass_length;
    -
    -    std::string ssid(data + ssid_start, data + ssid_end);
    -    std::string password(data + pass_start, data + pass_end);
    -    return {.command = command, .ssid = ssid, .password = password};
    -  }
    -
    -  improv_command.command = command;
    -  return improv_command;
    -}
    -
    -std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum) {
    -  std::vector out;
    -  uint32_t length = 0;
    -  out.push_back(command);
    -  for (auto str : datum) {
    -    uint8_t len = str.length();
    -    length += len;
    -    out.push_back(len);
    -    out.insert(out.end(), str.begin(), str.end());
    -  }
    -  out.insert(out.begin() + 1, length);
    -
    -  if (add_checksum) {
    -    uint32_t calculated_checksum = 0;
    -
    -    for (uint8_t byte : out) {
    -      calculated_checksum += byte;
    -    }
    -    out.push_back(calculated_checksum);
    -  }
    -  return out;
    -}
    -
    -#ifdef ARDUINO
    -std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum) {
    -  std::vector out;
    -  uint32_t length = 0;
    -  out.push_back(command);
    -  for (auto str : datum) {
    -    uint8_t len = str.length();
    -    length += len;
    -    out.push_back(len);
    -    out.insert(out.end(), str.begin(), str.end());
    -  }
    -  out.insert(out.begin() + 1, length);
    -
    -  if (add_checksum) {
    -    uint32_t calculated_checksum = 0;
    -
    -    for (uint8_t byte : out) {
    -      calculated_checksum += byte;
    -    }
    -    out.push_back(calculated_checksum);
    -  }
    -  return out;
    -}
    -#endif  // ARDUINO
    -
    -}  // namespace improv
    diff --git a/esphome/components/improv/improv.h b/esphome/components/improv/improv.h
    deleted file mode 100644
    index 9d1886ddaf..0000000000
    --- a/esphome/components/improv/improv.h
    +++ /dev/null
    @@ -1,63 +0,0 @@
    -#pragma once
    -
    -#ifdef ARDUINO
    -#include "WString.h"
    -#endif  // ARDUINO
    -
    -#include 
    -#include 
    -#include 
    -
    -namespace improv {
    -
    -static const char *const SERVICE_UUID = "00467768-6228-2272-4663-277478268000";
    -static const char *const STATUS_UUID = "00467768-6228-2272-4663-277478268001";
    -static const char *const ERROR_UUID = "00467768-6228-2272-4663-277478268002";
    -static const char *const RPC_COMMAND_UUID = "00467768-6228-2272-4663-277478268003";
    -static const char *const RPC_RESULT_UUID = "00467768-6228-2272-4663-277478268004";
    -static const char *const CAPABILITIES_UUID = "00467768-6228-2272-4663-277478268005";
    -
    -enum Error : uint8_t {
    -  ERROR_NONE = 0x00,
    -  ERROR_INVALID_RPC = 0x01,
    -  ERROR_UNKNOWN_RPC = 0x02,
    -  ERROR_UNABLE_TO_CONNECT = 0x03,
    -  ERROR_NOT_AUTHORIZED = 0x04,
    -  ERROR_UNKNOWN = 0xFF,
    -};
    -
    -enum State : uint8_t {
    -  STATE_STOPPED = 0x00,
    -  STATE_AWAITING_AUTHORIZATION = 0x01,
    -  STATE_AUTHORIZED = 0x02,
    -  STATE_PROVISIONING = 0x03,
    -  STATE_PROVISIONED = 0x04,
    -};
    -
    -enum Command : uint8_t {
    -  UNKNOWN = 0x00,
    -  WIFI_SETTINGS = 0x01,
    -  IDENTIFY = 0x02,
    -  GET_CURRENT_STATE = 0x02,
    -  GET_DEVICE_INFO = 0x03,
    -  BAD_CHECKSUM = 0xFF,
    -};
    -
    -static const uint8_t CAPABILITY_IDENTIFY = 0x01;
    -
    -struct ImprovCommand {
    -  Command command;
    -  std::string ssid;
    -  std::string password;
    -};
    -
    -ImprovCommand parse_improv_data(const std::vector &data, bool check_checksum = true);
    -ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum = true);
    -
    -std::vector build_rpc_response(Command command, const std::vector &datum,
    -                                        bool add_checksum = true);
    -#ifdef ARDUINO
    -std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum = true);
    -#endif  // ARDUINO
    -
    -}  // namespace improv
    diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py
    index b1cdc2d93e..ed7c382a2f 100644
    --- a/esphome/components/improv_serial/__init__.py
    +++ b/esphome/components/improv_serial/__init__.py
    @@ -5,7 +5,6 @@ import esphome.final_validate as fv
     
     CODEOWNERS = ["@esphome/core"]
     DEPENDENCIES = ["logger", "wifi"]
    -AUTO_LOAD = ["improv"]
     
     improv_serial_ns = cg.esphome_ns.namespace("improv_serial")
     
    @@ -31,3 +30,4 @@ FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate
     async def to_code(config):
         var = cg.new_Pvariable(config[CONF_ID])
         await cg.register_component(var, config)
    +    cg.add_library("esphome/Improv", "1.0.0")
    diff --git a/esphome/components/improv_serial/improv_serial_component.h b/esphome/components/improv_serial/improv_serial_component.h
    index 539674e2d3..304afdaf75 100644
    --- a/esphome/components/improv_serial/improv_serial_component.h
    +++ b/esphome/components/improv_serial/improv_serial_component.h
    @@ -1,11 +1,12 @@
     #pragma once
     
    -#include "esphome/components/improv/improv.h"
     #include "esphome/components/wifi/wifi_component.h"
     #include "esphome/core/component.h"
     #include "esphome/core/defines.h"
     #include "esphome/core/helpers.h"
     
    +#include 
    +
     #ifdef USE_ARDUINO
     #include 
     #endif
    diff --git a/platformio.ini b/platformio.ini
    index 2ac6d0bfc8..b49438b097 100644
    --- a/platformio.ini
    +++ b/platformio.ini
    @@ -28,6 +28,7 @@ build_flags =
     lib_deps =
         esphome/noise-c@0.1.4     ; api
         makuna/NeoPixelBus@2.6.9  ; neopixelbus
    +    esphome/Improv@1.0.0      ; improv_serial / esp32_improv
     build_flags =
         -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE
     src_filter =
    
    From dee5d639e2771606906b0bf94b94f0f3331a6132 Mon Sep 17 00:00:00 2001
    From: Maurice Makaay 
    Date: Wed, 17 Nov 2021 18:24:02 +0100
    Subject: [PATCH 1721/1841] Add max_telegram_length option to dsmr (#2674)
    
    Co-authored-by: Maurice Makaay 
    Co-authored-by: Oxan van Leeuwen 
    ---
     esphome/components/dsmr/__init__.py |  3 ++
     esphome/components/dsmr/dsmr.cpp    | 63 +++++++++++++++++------------
     esphome/components/dsmr/dsmr.h      | 10 ++++-
     tests/test3.yaml                    |  1 +
     4 files changed, 50 insertions(+), 27 deletions(-)
    
    diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py
    index dd6e6051aa..1cfc21a4ac 100644
    --- a/esphome/components/dsmr/__init__.py
    +++ b/esphome/components/dsmr/__init__.py
    @@ -15,6 +15,7 @@ CONF_DSMR_ID = "dsmr_id"
     CONF_DECRYPTION_KEY = "decryption_key"
     CONF_CRC_CHECK = "crc_check"
     CONF_GAS_MBUS_ID = "gas_mbus_id"
    +CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length"
     
     # Hack to prevent compile error due to ambiguity with lib namespace
     dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr")
    @@ -46,6 +47,7 @@ CONFIG_SCHEMA = cv.All(
                 cv.Optional(CONF_DECRYPTION_KEY): _validate_key,
                 cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean,
                 cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_,
    +            cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_,
             }
         ).extend(uart.UART_DEVICE_SCHEMA),
         cv.only_with_arduino,
    @@ -55,6 +57,7 @@ CONFIG_SCHEMA = cv.All(
     async def to_code(config):
         uart_component = await cg.get_variable(config[CONF_UART_ID])
         var = cg.new_Pvariable(config[CONF_ID], uart_component, config[CONF_CRC_CHECK])
    +    cg.add(var.set_max_telegram_length(config[CONF_MAX_TELEGRAM_LENGTH]))
         if CONF_DECRYPTION_KEY in config:
             cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY]))
         await cg.register_component(var, config)
    diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp
    index ea852e626e..631b18a1f4 100644
    --- a/esphome/components/dsmr/dsmr.cpp
    +++ b/esphome/components/dsmr/dsmr.cpp
    @@ -12,11 +12,15 @@ namespace dsmr {
     
     static const char *const TAG = "dsmr";
     
    +void Dsmr::setup() {
    +  telegram_ = new char[max_telegram_len_];  // NOLINT
    +}
    +
     void Dsmr::loop() {
    -  if (this->decryption_key_.empty())
    -    this->receive_telegram_();
    +  if (decryption_key_.empty())
    +    receive_telegram_();
       else
    -    this->receive_encrypted_();
    +    receive_encrypted_();
     }
     
     bool Dsmr::available_within_timeout_() {
    @@ -51,10 +55,10 @@ void Dsmr::receive_telegram_() {
           continue;
     
         // Check for buffer overflow.
    -    if (telegram_len_ >= MAX_TELEGRAM_LENGTH) {
    +    if (telegram_len_ >= max_telegram_len_) {
           header_found_ = false;
           footer_found_ = false;
    -      ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH);
    +      ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", max_telegram_len_);
           return;
         }
     
    @@ -86,9 +90,7 @@ void Dsmr::receive_telegram_() {
     }
     
     void Dsmr::receive_encrypted_() {
    -  // Encrypted buffer
    -  uint8_t buffer[MAX_TELEGRAM_LENGTH];
    -  size_t buffer_length = 0;
    +  encrypted_telegram_len_ = 0;
       size_t packet_size = 0;
     
       while (true) {
    @@ -114,39 +116,39 @@ void Dsmr::receive_encrypted_() {
         }
     
         // Check for buffer overflow.
    -    if (buffer_length >= MAX_TELEGRAM_LENGTH) {
    +    if (encrypted_telegram_len_ >= max_telegram_len_) {
           header_found_ = false;
    -      ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH);
    +      ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", max_telegram_len_);
           return;
         }
     
    -    buffer[buffer_length++] = c;
    +    encrypted_telegram_[encrypted_telegram_len_++] = c;
     
    -    if (packet_size == 0 && buffer_length > 20) {
    +    if (packet_size == 0 && encrypted_telegram_len_ > 20) {
           // Complete header + data bytes
    -      packet_size = 13 + (buffer[11] << 8 | buffer[12]);
    +      packet_size = 13 + (encrypted_telegram_[11] << 8 | encrypted_telegram_[12]);
           ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size);
         }
    -    if (buffer_length == packet_size && packet_size > 0) {
    +    if (encrypted_telegram_len_ == packet_size && packet_size > 0) {
           ESP_LOGV(TAG, "End of encrypted telegram found");
           GCM *gcmaes128{new GCM()};
    -      gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
    +      gcmaes128->setKey(decryption_key_.data(), gcmaes128->keySize());
           // the iv is 8 bytes of the system title + 4 bytes frame counter
           // system title is at byte 2 and frame counter at byte 15
           for (int i = 10; i < 14; i++)
    -        buffer[i] = buffer[i + 4];
    +        encrypted_telegram_[i] = encrypted_telegram_[i + 4];
           constexpr uint16_t iv_size{12};
    -      gcmaes128->setIV(&buffer[2], iv_size);
    -      gcmaes128->decrypt(reinterpret_cast(this->telegram_),
    +      gcmaes128->setIV(&encrypted_telegram_[2], iv_size);
    +      gcmaes128->decrypt(reinterpret_cast(telegram_),
                              // the ciphertext start at byte 18
    -                         &buffer[18],
    +                         &encrypted_telegram_[18],
                              // cipher size
    -                         buffer_length - 17);
    +                         encrypted_telegram_len_ - 17);
           delete gcmaes128;  // NOLINT(cppcoreguidelines-owning-memory)
     
    -      telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_));
    +      telegram_len_ = strnlen(telegram_, max_telegram_len_);
           ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_);
    -      ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
    +      ESP_LOGVV(TAG, "Decrypted telegram: %s", telegram_);
     
           parse_telegram();
     
    @@ -162,7 +164,7 @@ bool Dsmr::parse_telegram() {
       ESP_LOGV(TAG, "Trying to parse telegram");
       ::dsmr::ParseResult res =
           ::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false,
    -                              this->crc_check_);  // Parse telegram according to data definition. Ignore unknown values.
    +                              crc_check_);  // Parse telegram according to data definition. Ignore unknown values.
       if (res.err) {
         // Parsing error, show it
         auto err_str = res.fullError(telegram_, telegram_ + telegram_len_);
    @@ -177,6 +179,7 @@ bool Dsmr::parse_telegram() {
     
     void Dsmr::dump_config() {
       ESP_LOGCONFIG(TAG, "DSMR:");
    +  ESP_LOGCONFIG(TAG, "  Max telegram length: %d", max_telegram_len_);
     
     #define DSMR_LOG_SENSOR(s) LOG_SENSOR("  ", #s, this->s_##s##_);
       DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, )
    @@ -188,7 +191,11 @@ void Dsmr::dump_config() {
     void Dsmr::set_decryption_key(const std::string &decryption_key) {
       if (decryption_key.length() == 0) {
         ESP_LOGI(TAG, "Disabling decryption");
    -    this->decryption_key_.clear();
    +    decryption_key_.clear();
    +    if (encrypted_telegram_ != nullptr) {
    +      delete[] encrypted_telegram_;
    +      encrypted_telegram_ = nullptr;
    +    }
         return;
       }
     
    @@ -196,7 +203,7 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
         ESP_LOGE(TAG, "Error, decryption key must be 32 character long");
         return;
       }
    -  this->decryption_key_.clear();
    +  decryption_key_.clear();
     
       ESP_LOGI(TAG, "Decryption key is set");
       // Verbose level prints decryption key
    @@ -207,8 +214,14 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
         strncpy(temp, &(decryption_key.c_str()[i * 2]), 2);
         decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
       }
    +
    +  if (encrypted_telegram_ == nullptr) {
    +    encrypted_telegram_ = new uint8_t[max_telegram_len_];  // NOLINT
    +  }
     }
     
    +void Dsmr::set_max_telegram_length(size_t length) { max_telegram_len_ = length; }
    +
     }  // namespace dsmr
     }  // namespace esphome
     
    diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h
    index ca2c0f0877..5943e4d47f 100644
    --- a/esphome/components/dsmr/dsmr.h
    +++ b/esphome/components/dsmr/dsmr.h
    @@ -16,7 +16,6 @@
     namespace esphome {
     namespace dsmr {
     
    -static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500;
     static constexpr uint32_t READ_TIMEOUT_MS = 200;
     
     using namespace ::dsmr::fields;
    @@ -52,6 +51,8 @@ class Dsmr : public Component, public uart::UARTDevice {
      public:
       Dsmr(uart::UARTComponent *uart, bool crc_check) : uart::UARTDevice(uart), crc_check_(crc_check) {}
     
    +  void setup() override;
    +
       void loop() override;
     
       bool parse_telegram();
    @@ -72,6 +73,8 @@ class Dsmr : public Component, public uart::UARTDevice {
     
       void set_decryption_key(const std::string &decryption_key);
     
    +  void set_max_telegram_length(size_t length);
    +
     // Sensor setters
     #define DSMR_SET_SENSOR(s) \
       void set_##s(sensor::Sensor *sensor) { s_##s##_ = sensor; }
    @@ -97,8 +100,11 @@ class Dsmr : public Component, public uart::UARTDevice {
       bool available_within_timeout_();
     
       // Telegram buffer
    -  char telegram_[MAX_TELEGRAM_LENGTH];
    +  size_t max_telegram_len_;
    +  char *telegram_{nullptr};
       int telegram_len_{0};
    +  uint8_t *encrypted_telegram_{nullptr};
    +  int encrypted_telegram_len_{0};
     
       // Serial parser
       bool header_found_{false};
    diff --git a/tests/test3.yaml b/tests/test3.yaml
    index cf80c06aa8..0b9297a0b8 100644
    --- a/tests/test3.yaml
    +++ b/tests/test3.yaml
    @@ -1308,6 +1308,7 @@ fingerprint_grow:
     dsmr:
       decryption_key: 00112233445566778899aabbccddeeff
       uart_id: uart6
    +  max_telegram_length: 1000
     
     daly_bms:
       update_interval: 20s
    
    From 06994c0dfc45270cb34327e61f758d7c55b7dded Mon Sep 17 00:00:00 2001
    From: spattinson 
    Date: Wed, 17 Nov 2021 17:28:36 +0000
    Subject: [PATCH 1722/1841] Change LUT for ttgo t5 2.13inch to improve partial
     refresh (#2475)
    
    ---
     esphome/components/waveshare_epaper/waveshare_epaper.cpp | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp
    index 92fa289cfa..ee3fb2fe47 100644
    --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp
    +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp
    @@ -1183,7 +1183,7 @@ static const uint8_t PART_UPDATE_LUT_TTGO_DKE[LUT_SIZE_TTGO_DKE_PART] = {
         0x0, 0x40, 0x0, 0x0, 0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0,  0x0, 0x0,
         0x0, 0x0,  0x0, 0x0, 0x40, 0x40, 0x0,  0x0,  0x0,  0x0,  0x0, 0x0, 0x0,  0x0,  0x0, 0x0, 0x0, 0x80, 0x0, 0x0,
         0x0, 0x0,  0x0, 0x0, 0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0, 0x0, 0x0,  0x0,  0x0, 0x0, 0x0, 0x0,  0x0, 0x0,
    -    0xF, 0x0,  0x0, 0x0, 0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0, 0x0, 0x0,  0x0,  0x0, 0x0, 0x0, 0x0,  0x0, 0x0,
    +    0xF, 0x0,  0x0, 0x0, 0x0,  0x0,  0x0,  0x4,  0x0,  0x0,  0x0, 0x0, 0x0,  0x0,  0x0, 0x0, 0x0, 0x0,  0x0, 0x0,
         0x0, 0x0,  0x0, 0x0, 0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0, 0x0, 0x0,  0x0,  0x0, 0x0, 0x0, 0x0,  0x0, 0x0,
         0x0, 0x0,  0x0, 0x0, 0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0, 0x0, 0x0,  0x0,  0x0, 0x0, 0x0, 0x0,  0x0, 0x0,
         0x0, 0x0,  0x1, 0x0, 0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0, 0x0, 0x0,  0x0,  0x0, 0x0, 0x0, 0x0,  0x0, 0x0,
    
    From 6f9439e1bc60807d7fe7d8f2d009b8fadda4c02c Mon Sep 17 00:00:00 2001
    From: "Sergey V. DUDANOV" 
    Date: Wed, 17 Nov 2021 21:35:50 +0400
    Subject: [PATCH 1723/1841] Fix byte order in NEC protocol implementation
     (#2534)
    
    ---
     esphome/components/remote_base/nec_protocol.cpp | 8 ++++----
     1 file changed, 4 insertions(+), 4 deletions(-)
    
    diff --git a/esphome/components/remote_base/nec_protocol.cpp b/esphome/components/remote_base/nec_protocol.cpp
    index 79a30903a4..47b4d676dd 100644
    --- a/esphome/components/remote_base/nec_protocol.cpp
    +++ b/esphome/components/remote_base/nec_protocol.cpp
    @@ -17,14 +17,14 @@ void NECProtocol::encode(RemoteTransmitData *dst, const NECData &data) {
       dst->set_carrier_frequency(38000);
     
       dst->item(HEADER_HIGH_US, HEADER_LOW_US);
    -  for (uint32_t mask = 1UL << 15; mask; mask >>= 1) {
    +  for (uint16_t mask = 1; mask; mask <<= 1) {
         if (data.address & mask)
           dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
         else
           dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
       }
     
    -  for (uint32_t mask = 1UL << 15; mask; mask >>= 1) {
    +  for (uint16_t mask = 1; mask; mask <<= 1) {
         if (data.command & mask)
           dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
         else
    @@ -41,7 +41,7 @@ optional NECProtocol::decode(RemoteReceiveData src) {
       if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US))
         return {};
     
    -  for (uint32_t mask = 1UL << 15; mask != 0; mask >>= 1) {
    +  for (uint16_t mask = 1; mask; mask <<= 1) {
         if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
           data.address |= mask;
         } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
    @@ -51,7 +51,7 @@ optional NECProtocol::decode(RemoteReceiveData src) {
         }
       }
     
    -  for (uint32_t mask = 1UL << 15; mask != 0; mask >>= 1) {
    +  for (uint16_t mask = 1; mask; mask <<= 1) {
         if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
           data.command |= mask;
         } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
    
    From 8267f01ccdcb4d9564d262f3ec0a02fb0650c107 Mon Sep 17 00:00:00 2001
    From: Martin <25747549+martgras@users.noreply.github.com>
    Date: Wed, 17 Nov 2021 20:03:46 +0100
    Subject: [PATCH 1724/1841] Remove arduino dependency from hm3301 (#2745)
    
    ---
     .../hm3301/abstract_aqi_calculator.h           |  3 ---
     esphome/components/hm3301/aqi_calculator.h     |  4 ----
     .../components/hm3301/aqi_calculator_factory.h |  4 ----
     esphome/components/hm3301/caqi_calculator.h    |  4 ----
     esphome/components/hm3301/hm3301.cpp           | 13 +++----------
     esphome/components/hm3301/hm3301.h             | 18 ++++++++----------
     esphome/components/hm3301/sensor.py            |  4 ----
     platformio.ini                                 |  1 -
     8 files changed, 11 insertions(+), 40 deletions(-)
    
    diff --git a/esphome/components/hm3301/abstract_aqi_calculator.h b/esphome/components/hm3301/abstract_aqi_calculator.h
    index fb41b921d9..42d900a262 100644
    --- a/esphome/components/hm3301/abstract_aqi_calculator.h
    +++ b/esphome/components/hm3301/abstract_aqi_calculator.h
    @@ -1,6 +1,5 @@
     #pragma once
     
    -#ifdef USE_ARDUINO
     #include 
     
     namespace esphome {
    @@ -13,5 +12,3 @@ class AbstractAQICalculator {
     
     }  // namespace hm3301
     }  // namespace esphome
    -
    -#endif  // USE_ARDUINO
    diff --git a/esphome/components/hm3301/aqi_calculator.h b/esphome/components/hm3301/aqi_calculator.h
    index a3839b643c..08d1dc2921 100644
    --- a/esphome/components/hm3301/aqi_calculator.h
    +++ b/esphome/components/hm3301/aqi_calculator.h
    @@ -1,7 +1,5 @@
     #pragma once
     
    -#ifdef USE_ARDUINO
    -
     #include "abstract_aqi_calculator.h"
     
     namespace esphome {
    @@ -48,5 +46,3 @@ class AQICalculator : public AbstractAQICalculator {
     
     }  // namespace hm3301
     }  // namespace esphome
    -
    -#endif  // USE_ARDUINO
    diff --git a/esphome/components/hm3301/aqi_calculator_factory.h b/esphome/components/hm3301/aqi_calculator_factory.h
    index 3c6f9709b6..55608b6e51 100644
    --- a/esphome/components/hm3301/aqi_calculator_factory.h
    +++ b/esphome/components/hm3301/aqi_calculator_factory.h
    @@ -1,7 +1,5 @@
     #pragma once
     
    -#ifdef USE_ARDUINO
    -
     #include "caqi_calculator.h"
     #include "aqi_calculator.h"
     
    @@ -29,5 +27,3 @@ class AQICalculatorFactory {
     
     }  // namespace hm3301
     }  // namespace esphome
    -
    -#endif  // USE_ARDUINO
    diff --git a/esphome/components/hm3301/caqi_calculator.h b/esphome/components/hm3301/caqi_calculator.h
    index a7f5460e0a..1ec61f2416 100644
    --- a/esphome/components/hm3301/caqi_calculator.h
    +++ b/esphome/components/hm3301/caqi_calculator.h
    @@ -1,7 +1,5 @@
     #pragma once
     
    -#ifdef USE_ARDUINO
    -
     #include "esphome/core/log.h"
     #include "abstract_aqi_calculator.h"
     
    @@ -52,5 +50,3 @@ class CAQICalculator : public AbstractAQICalculator {
     
     }  // namespace hm3301
     }  // namespace esphome
    -
    -#endif  // USE_ARDUINO
    diff --git a/esphome/components/hm3301/hm3301.cpp b/esphome/components/hm3301/hm3301.cpp
    index 759157f330..a2bef2a01d 100644
    --- a/esphome/components/hm3301/hm3301.cpp
    +++ b/esphome/components/hm3301/hm3301.cpp
    @@ -1,5 +1,3 @@
    -#ifdef USE_ARDUINO
    -
     #include "esphome/core/log.h"
     #include "hm3301.h"
     
    @@ -14,9 +12,8 @@ static const uint8_t PM_10_0_VALUE_INDEX = 7;
     
     void HM3301Component::setup() {
       ESP_LOGCONFIG(TAG, "Setting up HM3301...");
    -  hm3301_ = make_unique();
    -  error_code_ = hm3301_->init();
    -  if (error_code_ != NO_ERROR) {
    +  if (i2c::ERROR_OK != this->write(&SELECT_COMM_CMD, 1)) {
    +    error_code_ = ERROR_COMM;
         this->mark_failed();
         return;
       }
    @@ -38,7 +35,7 @@ void HM3301Component::dump_config() {
     float HM3301Component::get_setup_priority() const { return setup_priority::DATA; }
     
     void HM3301Component::update() {
    -  if (!this->read_sensor_value_(data_buffer_)) {
    +  if (this->read(data_buffer_, 29) != i2c::ERROR_OK) {
         ESP_LOGW(TAG, "Read result failed");
         this->status_set_warning();
         return;
    @@ -87,8 +84,6 @@ void HM3301Component::update() {
       this->status_clear_warning();
     }
     
    -bool HM3301Component::read_sensor_value_(uint8_t *data) { return !hm3301_->read_sensor_value(data, 29); }
    -
     bool HM3301Component::validate_checksum_(const uint8_t *data) {
       uint8_t sum = 0;
       for (int i = 0; i < 28; i++) {
    @@ -104,5 +99,3 @@ uint16_t HM3301Component::get_sensor_value_(const uint8_t *data, uint8_t i) {
     
     }  // namespace hm3301
     }  // namespace esphome
    -
    -#endif  // USE_ARDUINO
    diff --git a/esphome/components/hm3301/hm3301.h b/esphome/components/hm3301/hm3301.h
    index 61bbf7e4ab..e13ffa466e 100644
    --- a/esphome/components/hm3301/hm3301.h
    +++ b/esphome/components/hm3301/hm3301.h
    @@ -1,17 +1,15 @@
     #pragma once
     
    -#ifdef USE_ARDUINO
    -
     #include "esphome/core/component.h"
     #include "esphome/components/sensor/sensor.h"
     #include "esphome/components/i2c/i2c.h"
     #include "aqi_calculator_factory.h"
     
    -#include 
    -
     namespace esphome {
     namespace hm3301 {
     
    +static const uint8_t SELECT_COMM_CMD = 0X88;
    +
     class HM3301Component : public PollingComponent, public i2c::I2CDevice {
      public:
       HM3301Component() = default;
    @@ -29,9 +27,12 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice {
       void update() override;
     
      protected:
    -  std::unique_ptr hm3301_;
    -
    -  HM330XErrorCode error_code_{NO_ERROR};
    +  enum {
    +    NO_ERROR = 0,
    +    ERROR_PARAM = -1,
    +    ERROR_COMM = -2,
    +    ERROR_OTHERS = -128,
    +  } error_code_{NO_ERROR};
     
       uint8_t data_buffer_[30];
     
    @@ -43,12 +44,9 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice {
       AQICalculatorType aqi_calc_type_;
       AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory();
     
    -  bool read_sensor_value_(uint8_t *);
       bool validate_checksum_(const uint8_t *);
       uint16_t get_sensor_value_(const uint8_t *, uint8_t);
     };
     
     }  // namespace hm3301
     }  // namespace esphome
    -
    -#endif  // USE_ARDUINO
    diff --git a/esphome/components/hm3301/sensor.py b/esphome/components/hm3301/sensor.py
    index 976a0488e1..8e9ee4c6fb 100644
    --- a/esphome/components/hm3301/sensor.py
    +++ b/esphome/components/hm3301/sensor.py
    @@ -84,7 +84,6 @@ CONFIG_SCHEMA = cv.All(
         .extend(cv.polling_component_schema("60s"))
         .extend(i2c.i2c_device_schema(0x40)),
         _validate,
    -    cv.only_with_arduino,
     )
     
     
    @@ -109,6 +108,3 @@ async def to_code(config):
             sens = await sensor.new_sensor(config[CONF_AQI])
             cg.add(var.set_aqi_sensor(sens))
             cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE]))
    -
    -    # https://platformio.org/lib/show/6306/Grove%20-%20Laser%20PM2.5%20Sensor%20HM3301
    -    cg.add_library("seeed-studio/Grove - Laser PM2.5 Sensor HM3301", "1.0.3")
    diff --git a/platformio.ini b/platformio.ini
    index b49438b097..0f80d6d8d3 100644
    --- a/platformio.ini
    +++ b/platformio.ini
    @@ -46,7 +46,6 @@ lib_deps =
         fastled/FastLED@3.3.2                                 ; fastled_base
         mikalhart/TinyGPSPlus@1.0.2                           ; gps
         freekode/TM1651@1.0.1                                 ; tm1651
    -    seeed-studio/Grove - Laser PM2.5 Sensor HM3301@1.0.3  ; hm3301
         glmnet/Dsmr@0.5                                       ; dsmr
         rweather/Crypto@0.2.0                                 ; dsmr
         dudanov/MideaUART@1.1.8                               ; midea
    
    From 448e1690aac902cca7d614b2902514fa59e8d6ab Mon Sep 17 00:00:00 2001
    From: Martin <25747549+martgras@users.noreply.github.com>
    Date: Wed, 17 Nov 2021 23:59:40 +0100
    Subject: [PATCH 1725/1841] Add retry handler (#2721)
    
    Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Co-authored-by: Oxan van Leeuwen 
    ---
     esphome/core/component.cpp | 13 ++++++++
     esphome/core/component.h   | 34 +++++++++++++++++++-
     esphome/core/scheduler.cpp | 63 ++++++++++++++++++++++++++++++--------
     esphome/core/scheduler.h   | 27 ++++++++++++++--
     4 files changed, 122 insertions(+), 15 deletions(-)
    
    diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp
    index 5692194a91..f97818cfb1 100644
    --- a/esphome/core/component.cpp
    +++ b/esphome/core/component.cpp
    @@ -55,6 +55,15 @@ bool Component::cancel_interval(const std::string &name) {  // NOLINT
       return App.scheduler.cancel_interval(this, name);
     }
     
    +void Component::set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts,
    +                          std::function &&f, float backoff_increase_factor) {  // NOLINT
    +  App.scheduler.set_retry(this, name, initial_wait_time, max_attempts, std::move(f), backoff_increase_factor);
    +}
    +
    +bool Component::cancel_retry(const std::string &name) {  // NOLINT
    +  return App.scheduler.cancel_retry(this, name);
    +}
    +
     void Component::set_timeout(const std::string &name, uint32_t timeout, std::function &&f) {  // NOLINT
       return App.scheduler.set_timeout(this, name, timeout, std::move(f));
     }
    @@ -120,6 +129,10 @@ void Component::set_timeout(uint32_t timeout, std::function &&f) {  // N
     void Component::set_interval(uint32_t interval, std::function &&f) {  // NOLINT
       App.scheduler.set_interval(this, "", interval, std::move(f));
     }
    +void Component::set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function &&f,
    +                          float backoff_increase_factor) {  // NOLINT
    +  App.scheduler.set_retry(this, "", initial_wait_time, max_attempts, std::move(f), backoff_increase_factor);
    +}
     bool Component::is_failed() { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED; }
     bool Component::can_proceed() { return true; }
     bool Component::status_has_warning() { return this->component_state_ & STATUS_LED_WARNING; }
    diff --git a/esphome/core/component.h b/esphome/core/component.h
    index a1afc17c2c..c3a4ac3782 100644
    --- a/esphome/core/component.h
    +++ b/esphome/core/component.h
    @@ -61,6 +61,8 @@ extern const uint32_t STATUS_LED_OK;
     extern const uint32_t STATUS_LED_WARNING;
     extern const uint32_t STATUS_LED_ERROR;
     
    +enum RetryResult { DONE, RETRY };
    +
     class Component {
      public:
       /** Where the component's initialization should happen.
    @@ -180,7 +182,35 @@ class Component {
        */
       bool cancel_interval(const std::string &name);  // NOLINT
     
    -  void set_timeout(uint32_t timeout, std::function &&f);  // NOLINT
    +  /** Set an retry function with a unique name. Empty name means no cancelling possible.
    +   *
    +   * This will call f. If f returns RetryResult::RETRY f is called again after initial_wait_time ms.
    +   * f should return RetryResult::DONE if no repeat is required. The initial wait time will be increased
    +   * by backoff_increase_factor for each iteration. Default is doubling the time between iterations
    +   * Can be cancelled via cancel_retry().
    +   *
    +   * IMPORTANT: Do not rely on this having correct timing. This is only called from
    +   * loop() and therefore can be significantly delayed.
    +   *
    +   * @param name The identifier for this retry function.
    +   * @param initial_wait_time The time in ms before f is called again
    +   * @param max_attempts The maximum number of retries
    +   * @param f The function (or lambda) that should be called
    +   * @param backoff_increase_factor time between retries is increased by this factor on every retry
    +   * @see cancel_retry()
    +   */
    +  void set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts,  // NOLINT
    +                 std::function &&f, float backoff_increase_factor = 1.0f);    // NOLINT
    +
    +  void set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function &&f,  // NOLINT
    +                 float backoff_increase_factor = 1.0f);                                               // NOLINT
    +
    +  /** Cancel a retry function.
    +   *
    +   * @param name The identifier for this retry function.
    +   * @return Whether a retry function was deleted.
    +   */
    +  bool cancel_retry(const std::string &name);  // NOLINT
     
       /** Set a timeout function with a unique name.
        *
    @@ -198,6 +228,8 @@ class Component {
        */
       void set_timeout(const std::string &name, uint32_t timeout, std::function &&f);  // NOLINT
     
    +  void set_timeout(uint32_t timeout, std::function &&f);  // NOLINT
    +
       /** Cancel a timeout function.
        *
        * @param name The identifier for this timeout function.
    diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp
    index a6d3e0307e..3fe07f94b5 100644
    --- a/esphome/core/scheduler.cpp
    +++ b/esphome/core/scheduler.cpp
    @@ -32,7 +32,7 @@ void HOT Scheduler::set_timeout(Component *component, const std::string &name, u
       item->timeout = timeout;
       item->last_execution = now;
       item->last_execution_major = this->millis_major_;
    -  item->f = std::move(func);
    +  item->void_callback = std::move(func);
       item->remove = false;
       this->push_(std::move(item));
     }
    @@ -65,13 +65,47 @@ void HOT Scheduler::set_interval(Component *component, const std::string &name,
       item->last_execution_major = this->millis_major_;
       if (item->last_execution > now)
         item->last_execution_major--;
    -  item->f = std::move(func);
    +  item->void_callback = std::move(func);
       item->remove = false;
       this->push_(std::move(item));
     }
     bool HOT Scheduler::cancel_interval(Component *component, const std::string &name) {
       return this->cancel_item_(component, name, SchedulerItem::INTERVAL);
     }
    +
    +void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time,
    +                              uint8_t max_attempts, std::function &&func,
    +                              float backoff_increase_factor) {
    +  const uint32_t now = this->millis_();
    +
    +  if (!name.empty())
    +    this->cancel_retry(component, name);
    +
    +  if (initial_wait_time == SCHEDULER_DONT_RUN)
    +    return;
    +
    +  ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%u,max_attempts=%u, backoff_factor=%0.1f)", name.c_str(),
    +            initial_wait_time, max_attempts, backoff_increase_factor);
    +
    +  auto item = make_unique();
    +  item->component = component;
    +  item->name = name;
    +  item->type = SchedulerItem::RETRY;
    +  item->interval = initial_wait_time;
    +  item->retry_countdown = max_attempts;
    +  item->backoff_multiplier = backoff_increase_factor;
    +  item->last_execution = now - initial_wait_time;
    +  item->last_execution_major = this->millis_major_;
    +  if (item->last_execution > now)
    +    item->last_execution_major--;
    +  item->retry_callback = std::move(func);
    +  item->remove = false;
    +  this->push_(std::move(item));
    +}
    +bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) {
    +  return this->cancel_item_(component, name, SchedulerItem::RETRY);
    +}
    +
     optional HOT Scheduler::next_schedule_in() {
       if (this->empty_())
         return {};
    @@ -95,10 +129,9 @@ void IRAM_ATTR HOT Scheduler::call() {
         ESP_LOGVV(TAG, "Items: count=%u, now=%u", this->items_.size(), now);
         while (!this->empty_()) {
           auto item = std::move(this->items_[0]);
    -      const char *type = item->type == SchedulerItem::INTERVAL ? "interval" : "timeout";
    -      ESP_LOGVV(TAG, "  %s '%s' interval=%u last_execution=%u (%u) next=%u (%u)", type, item->name.c_str(),
    -                item->interval, item->last_execution, item->last_execution_major, item->next_execution(),
    -                item->next_execution_major());
    +      ESP_LOGVV(TAG, "  %s '%s' interval=%u last_execution=%u (%u) next=%u (%u)", item->get_type_str(),
    +                item->name.c_str(), item->interval, item->last_execution, item->last_execution_major,
    +                item->next_execution(), item->next_execution_major());
     
           this->pop_raw_();
           old_items.push_back(std::move(item));
    @@ -129,6 +162,7 @@ void IRAM_ATTR HOT Scheduler::call() {
       }
     
       while (!this->empty_()) {
    +    RetryResult retry_result = RETRY;
         // use scoping to indicate visibility of `item` variable
         {
           // Don't copy-by value yet
    @@ -147,17 +181,19 @@ void IRAM_ATTR HOT Scheduler::call() {
           }
     
     #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
    -      const char *type = item->type == SchedulerItem::INTERVAL ? "interval" : "timeout";
    -      ESP_LOGVV(TAG, "Running %s '%s' with interval=%u last_execution=%u (now=%u)", type, item->name.c_str(),
    -                item->interval, item->last_execution, now);
    +      ESP_LOGVV(TAG, "Running %s '%s' with interval=%u last_execution=%u (now=%u)", item->get_type_str(),
    +                item->name.c_str(), item->interval, item->last_execution, now);
     #endif
     
    -      // Warning: During f(), a lot of stuff can happen, including:
    +      // Warning: During callback(), a lot of stuff can happen, including:
           //  - timeouts/intervals get added, potentially invalidating vector pointers
           //  - timeouts/intervals get cancelled
           {
             WarnIfComponentBlockingGuard guard{item->component};
    -        item->f();
    +        if (item->type == SchedulerItem::RETRY)
    +          retry_result = item->retry_callback();
    +        else
    +          item->void_callback();
           }
         }
     
    @@ -175,13 +211,16 @@ void IRAM_ATTR HOT Scheduler::call() {
             continue;
           }
     
    -      if (item->type == SchedulerItem::INTERVAL) {
    +      if (item->type == SchedulerItem::INTERVAL ||
    +          (item->type == SchedulerItem::RETRY && (--item->retry_countdown > 0 && retry_result != RetryResult::DONE))) {
             if (item->interval != 0) {
               const uint32_t before = item->last_execution;
               const uint32_t amount = (now - item->last_execution) / item->interval;
               item->last_execution += amount * item->interval;
               if (item->last_execution < before)
                 item->last_execution_major++;
    +          if (item->type == SchedulerItem::RETRY)
    +            item->interval *= item->backoff_multiplier;
             }
             this->push_(std::move(item));
           }
    diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h
    index d1839cb4a7..dc96d58329 100644
    --- a/esphome/core/scheduler.h
    +++ b/esphome/core/scheduler.h
    @@ -15,6 +15,10 @@ class Scheduler {
       void set_interval(Component *component, const std::string &name, uint32_t interval, std::function &&func);
       bool cancel_interval(Component *component, const std::string &name);
     
    +  void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts,
    +                 std::function &&func, float backoff_increase_factor = 1.0f);
    +  bool cancel_retry(Component *component, const std::string &name);
    +
       optional next_schedule_in();
     
       void call();
    @@ -25,13 +29,20 @@ class Scheduler {
       struct SchedulerItem {
         Component *component;
         std::string name;
    -    enum Type { TIMEOUT, INTERVAL } type;
    +    enum Type { TIMEOUT, INTERVAL, RETRY } type;
         union {
           uint32_t interval;
           uint32_t timeout;
         };
         uint32_t last_execution;
    -    std::function f;
    +    // Ideally this should be a union or std::variant
    +    // but unions don't work with object like std::function
    +    //  union CallBack_{
    +    std::function void_callback;
    +    std::function retry_callback;
    +    //  };
    +    uint8_t retry_countdown{3};
    +    float backoff_multiplier{1.0f};
         bool remove;
         uint8_t last_execution_major;
     
    @@ -45,6 +56,18 @@ class Scheduler {
         }
     
         static bool cmp(const std::unique_ptr &a, const std::unique_ptr &b);
    +    const char *get_type_str() {
    +      switch (this->type) {
    +        case SchedulerItem::INTERVAL:
    +          return "interval";
    +        case SchedulerItem::RETRY:
    +          return "retry";
    +        case SchedulerItem::TIMEOUT:
    +          return "timeout";
    +        default:
    +          return "";
    +      }
    +    }
       };
     
       uint32_t millis_();
    
    From 9e1c3e8f01c82f8ee5c227ff0c3b6d8359bf9c3b Mon Sep 17 00:00:00 2001
    From: Maurice Makaay 
    Date: Thu, 18 Nov 2021 22:41:26 +0100
    Subject: [PATCH 1726/1841] Allow UART debug configuration with no after:
     definition (#2753)
    
    ---
     esphome/components/uart/__init__.py | 10 +++++++---
     tests/test2.yaml                    |  6 ++++++
     2 files changed, 13 insertions(+), 3 deletions(-)
    
    diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py
    index 53209dfc7b..159b08d2d9 100644
    --- a/esphome/components/uart/__init__.py
    +++ b/esphome/components/uart/__init__.py
    @@ -94,17 +94,21 @@ UART_DIRECTIONS = {
         "BOTH": UARTDirection.UART_DIRECTION_BOTH,
     }
     
    +AFTER_DEFAULTS = {CONF_BYTES: 256, CONF_TIMEOUT: "100ms"}
    +
     DEBUG_SCHEMA = cv.Schema(
         {
             cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UARTDebugger),
             cv.Optional(CONF_DIRECTION, default="BOTH"): cv.enum(
                 UART_DIRECTIONS, upper=True
             ),
    -        cv.Optional(CONF_AFTER): cv.Schema(
    +        cv.Optional(CONF_AFTER, default=AFTER_DEFAULTS): cv.Schema(
                 {
    -                cv.Optional(CONF_BYTES, default=256): cv.validate_bytes,
                     cv.Optional(
    -                    CONF_TIMEOUT, default="100ms"
    +                    CONF_BYTES, default=AFTER_DEFAULTS[CONF_BYTES]
    +                ): cv.validate_bytes,
    +                cv.Optional(
    +                    CONF_TIMEOUT, default=AFTER_DEFAULTS[CONF_TIMEOUT]
                     ): cv.positive_time_period_milliseconds,
                     cv.Optional(CONF_DELIMITER): cv.templatable(validate_raw_data),
                 }
    diff --git a/tests/test2.yaml b/tests/test2.yaml
    index f90e522b1e..3afef9501d 100644
    --- a/tests/test2.yaml
    +++ b/tests/test2.yaml
    @@ -39,6 +39,12 @@ uart:
       tx_pin: GPIO22
       rx_pin: GPIO23
       baud_rate: 115200
    +  # Specifically added for testing debug with no after: definition.
    +  debug:
    +    dummy_receiver: false
    +    direction: rx
    +    sequence:
    +      - lambda: UARTDebug::log_hex(direction, bytes, ':');
     
     ota:
       safe_mode: True
    
    From e5cb5756aa1feb7ccb47dfc20d9039b0985b09cb Mon Sep 17 00:00:00 2001
    From: Dave T <17680170+davet2001@users.noreply.github.com>
    Date: Thu, 18 Nov 2021 22:20:32 +0000
    Subject: [PATCH 1727/1841] Fix frame scaling for animated gifs (#2750)
    
    ---
     esphome/components/animation/__init__.py | 10 ++++++++++
     1 file changed, 10 insertions(+)
    
    diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py
    index 3f03e5c185..1780bdf72e 100644
    --- a/esphome/components/animation/__init__.py
    +++ b/esphome/components/animation/__init__.py
    @@ -60,6 +60,10 @@ async def to_code(config):
                 image.seek(frameIndex)
                 frame = image.convert("L", dither=Image.NONE)
                 pixels = list(frame.getdata())
    +            if len(pixels) != height * width:
    +                raise core.EsphomeError(
    +                    f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}"
    +                )
                 for pix in pixels:
                     data[pos] = pix
                     pos += 1
    @@ -69,8 +73,14 @@ async def to_code(config):
             pos = 0
             for frameIndex in range(frames):
                 image.seek(frameIndex)
    +            if CONF_RESIZE in config:
    +                image.thumbnail(config[CONF_RESIZE])
                 frame = image.convert("RGB")
                 pixels = list(frame.getdata())
    +            if len(pixels) != height * width:
    +                raise core.EsphomeError(
    +                    f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}"
    +                )
                 for pix in pixels:
                     data[pos] = pix[0]
                     pos += 1
    
    From 61ec16cdfc2ffeb69a7bbdc06c67f88ea79793fe Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= 
    Date: Mon, 22 Nov 2021 00:09:11 +0100
    Subject: [PATCH 1728/1841] esp32_camera_web_server: Improve support for
     MotionEye (#2777)
    
    ---
     .../camera_web_server.cpp                     | 26 ++++++++++++-------
     1 file changed, 17 insertions(+), 9 deletions(-)
    
    diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.cpp b/esphome/components/esp32_camera_web_server/camera_web_server.cpp
    index c9a684c7e5..653a274bf4 100644
    --- a/esphome/components/esp32_camera_web_server/camera_web_server.cpp
    +++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp
    @@ -21,12 +21,19 @@ static const char *const TAG = "esp32_camera_web_server";
     #define CONTENT_TYPE "image/jpeg"
     #define CONTENT_LENGTH "Content-Length"
     
    -static const char *const STREAM_HEADER =
    -    "HTTP/1.1 200\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY
    -    "\r\n";
    -static const char *const STREAM_500 = "HTTP/1.1 500\r\nContent-Type: text/plain\r\n\r\nNo frames send.\r\n";
    -static const char *const STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
    +static const char *const STREAM_HEADER = "HTTP/1.0 200 OK\r\n"
    +                                         "Access-Control-Allow-Origin: *\r\n"
    +                                         "Connection: close\r\n"
    +                                         "Content-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY "\r\n"
    +                                         "\r\n"
    +                                         "--" PART_BOUNDARY "\r\n";
    +static const char *const STREAM_ERROR = "Content-Type: text/plain\r\n"
    +                                        "\r\n"
    +                                        "No frames send.\r\n"
    +                                        "--" PART_BOUNDARY "\r\n";
     static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n";
    +static const char *const STREAM_BOUNDARY = "\r\n"
    +                                           "--" PART_BOUNDARY "\r\n";
     
     CameraWebServer::CameraWebServer() {}
     
    @@ -45,6 +52,7 @@ void CameraWebServer::setup() {
       config.ctrl_port = this->port_;
       config.max_open_sockets = 1;
       config.backlog_conn = 2;
    +  config.lru_purge_enable = true;
     
       if (httpd_start(&this->httpd_, &config) != ESP_OK) {
         mark_failed();
    @@ -172,9 +180,6 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
           ESP_LOGW(TAG, "STREAM: failed to acquire frame");
           res = ESP_FAIL;
         }
    -    if (res == ESP_OK) {
    -      res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
    -    }
         if (res == ESP_OK) {
           size_t hlen = snprintf(part_buf, 64, STREAM_PART, image->get_data_length());
           res = httpd_send_all(req, part_buf, hlen);
    @@ -182,6 +187,9 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
         if (res == ESP_OK) {
           res = httpd_send_all(req, (const char *) image->get_data_buffer(), image->get_data_length());
         }
    +    if (res == ESP_OK) {
    +      res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
    +    }
         if (res == ESP_OK) {
           frames++;
           int64_t frame_time = millis() - last_frame;
    @@ -193,7 +201,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
       }
     
       if (!frames) {
    -    res = httpd_send_all(req, STREAM_500, strlen(STREAM_500));
    +    res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR));
       }
     
       ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames);
    
    From 1424091ee51a31d75558f8e4a029ea80edb4d415 Mon Sep 17 00:00:00 2001
    From: Samuel Sieb 
    Date: Sun, 21 Nov 2021 15:11:36 -0800
    Subject: [PATCH 1729/1841] Remove floating point ops from the ISR (#2751)
    
    Co-authored-by: Samuel Sieb 
    ---
     esphome/components/zyaura/zyaura.cpp | 40 +++++++++++++++++-----------
     esphome/components/zyaura/zyaura.h   |  8 +++---
     2 files changed, 28 insertions(+), 20 deletions(-)
    
    diff --git a/esphome/components/zyaura/zyaura.cpp b/esphome/components/zyaura/zyaura.cpp
    index 11643a5c23..621439aa0c 100644
    --- a/esphome/components/zyaura/zyaura.cpp
    +++ b/esphome/components/zyaura/zyaura.cpp
    @@ -57,38 +57,46 @@ void IRAM_ATTR ZaSensorStore::interrupt(ZaSensorStore *arg) {
     void IRAM_ATTR ZaSensorStore::set_data_(ZaMessage *message) {
       switch (message->type) {
         case HUMIDITY:
    -      this->humidity = (message->value > 10000) ? NAN : (message->value / 100.0f);
    +      this->humidity = message->value;
           break;
    -
         case TEMPERATURE:
    -      this->temperature = (message->value > 5970) ? NAN : (message->value / 16.0f - 273.15f);
    +      this->temperature = message->value;
           break;
    -
         case CO2:
    -      this->co2 = (message->value > 10000) ? NAN : message->value;
    -      break;
    -
    -    default:
    +      this->co2 = message->value;
           break;
       }
     }
     
    -bool ZyAuraSensor::publish_state_(sensor::Sensor *sensor, float *value) {
    -  // Sensor doesn't added to configuration
    +bool ZyAuraSensor::publish_state_(ZaDataType data_type, sensor::Sensor *sensor, uint16_t *data_value) {
    +  // Sensor wasn't added to configuration
       if (sensor == nullptr) {
         return true;
       }
     
    -  sensor->publish_state(*value);
    +  float value = NAN;
    +  switch (data_type) {
    +    case HUMIDITY:
    +      value = (*data_value > 10000) ? NAN : (*data_value / 100.0f);
    +      break;
    +    case TEMPERATURE:
    +      value = (*data_value > 5970) ? NAN : (*data_value / 16.0f - 273.15f);
    +      break;
    +    case CO2:
    +      value = (*data_value > 10000) ? NAN : *data_value;
    +      break;
    +  }
    +
    +  sensor->publish_state(value);
     
       // Sensor reported wrong value
    -  if (std::isnan(*value)) {
    +  if (std::isnan(value)) {
         ESP_LOGW(TAG, "Sensor reported invalid data. Is the update interval too small?");
         this->status_set_warning();
         return false;
       }
     
    -  *value = NAN;
    +  *data_value = -1;
       return true;
     }
     
    @@ -104,9 +112,9 @@ void ZyAuraSensor::dump_config() {
     }
     
     void ZyAuraSensor::update() {
    -  bool co2_result = this->publish_state_(this->co2_sensor_, &this->store_.co2);
    -  bool temperature_result = this->publish_state_(this->temperature_sensor_, &this->store_.temperature);
    -  bool humidity_result = this->publish_state_(this->humidity_sensor_, &this->store_.humidity);
    +  bool co2_result = this->publish_state_(CO2, this->co2_sensor_, &this->store_.co2);
    +  bool temperature_result = this->publish_state_(TEMPERATURE, this->temperature_sensor_, &this->store_.temperature);
    +  bool humidity_result = this->publish_state_(HUMIDITY, this->humidity_sensor_, &this->store_.humidity);
     
       if (co2_result && temperature_result && humidity_result) {
         this->status_clear_warning();
    diff --git a/esphome/components/zyaura/zyaura.h b/esphome/components/zyaura/zyaura.h
    index 2b9e3fbb35..85c31ec75a 100644
    --- a/esphome/components/zyaura/zyaura.h
    +++ b/esphome/components/zyaura/zyaura.h
    @@ -42,9 +42,9 @@ class ZaDataProcessor {
     
     class ZaSensorStore {
      public:
    -  float co2 = NAN;
    -  float temperature = NAN;
    -  float humidity = NAN;
    +  uint16_t co2 = -1;
    +  uint16_t temperature = -1;
    +  uint16_t humidity = -1;
     
       void setup(InternalGPIOPin *pin_clock, InternalGPIOPin *pin_data);
       static void interrupt(ZaSensorStore *arg);
    @@ -79,7 +79,7 @@ class ZyAuraSensor : public PollingComponent {
       sensor::Sensor *temperature_sensor_{nullptr};
       sensor::Sensor *humidity_sensor_{nullptr};
     
    -  bool publish_state_(sensor::Sensor *sensor, float *value);
    +  bool publish_state_(ZaDataType data_type, sensor::Sensor *sensor, uint16_t *data_value);
     };
     
     }  // namespace zyaura
    
    From 897277992b3bf6608c1622780de5c5f209ca3102 Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Tue, 23 Nov 2021 08:30:49 +0100
    Subject: [PATCH 1730/1841] Introduce str_snprintf helper function (#2780)
    
    ---
     esphome/core/helpers.cpp | 18 ++++++++++++++++--
     esphome/core/helpers.h   |  5 ++++-
     2 files changed, 20 insertions(+), 3 deletions(-)
    
    diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp
    index 3e614eb515..37d9cdc24b 100644
    --- a/esphome/core/helpers.cpp
    +++ b/esphome/core/helpers.cpp
    @@ -48,13 +48,13 @@ void get_mac_address_raw(uint8_t *mac) {
     std::string get_mac_address() {
       uint8_t mac[6];
       get_mac_address_raw(mac);
    -  return str_sprintf("%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    +  return str_snprintf("%02x%02x%02x%02x%02x%02x", 12, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
     }
     
     std::string get_mac_address_pretty() {
       uint8_t mac[6];
       get_mac_address_raw(mac);
    -  return str_sprintf("%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    +  return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
     }
     
     #ifdef USE_ESP32
    @@ -325,6 +325,20 @@ bool str_startswith(const std::string &full, const std::string &start) { return
     bool str_endswith(const std::string &full, const std::string &ending) {
       return full.rfind(ending) == (full.size() - ending.size());
     }
    +std::string str_snprintf(const char *fmt, size_t length, ...) {
    +  std::string str;
    +  va_list args;
    +
    +  str.resize(length);
    +  va_start(args, length);
    +  size_t out_length = vsnprintf(&str[0], length + 1, fmt, args);
    +  va_end(args);
    +
    +  if (out_length < length)
    +    str.resize(out_length);
    +
    +  return str;
    +}
     std::string str_sprintf(const char *fmt, ...) {
       std::string str;
       va_list args;
    diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h
    index 120642c62e..4ce1f08074 100644
    --- a/esphome/core/helpers.h
    +++ b/esphome/core/helpers.h
    @@ -57,7 +57,10 @@ bool str_equals_case_insensitive(const std::string &a, const std::string &b);
     bool str_startswith(const std::string &full, const std::string &start);
     bool str_endswith(const std::string &full, const std::string &ending);
     
    -/// sprintf-like function returning std::string instead of writing to char array.
    +/// snprintf-like function returning std::string with a given maximum length.
    +std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t length, ...);
    +
    +/// sprintf-like function returning std::string.
     std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...);
     
     class HighFrequencyLoopRequester {
    
    From 3e5331a263fcf585c88c359cdb0105ea3db79897 Mon Sep 17 00:00:00 2001
    From: cvwillegen 
    Date: Tue, 23 Nov 2021 09:20:20 +0100
    Subject: [PATCH 1731/1841] Prettier date time display after time sync (#2778)
    
    ---
     esphome/components/sntp/sntp_component.cpp  | 2 +-
     esphome/components/time/real_time_clock.cpp | 2 +-
     2 files changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp
    index 96be0e9709..21fcb96842 100644
    --- a/esphome/components/sntp/sntp_component.cpp
    +++ b/esphome/components/sntp/sntp_component.cpp
    @@ -71,7 +71,7 @@ void SNTPComponent::loop() {
       if (!time.is_valid())
         return;
     
    -  ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour,
    +  ESP_LOGD(TAG, "Synchronized time: %04d-%02d-%02d %02d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour,
                time.minute, time.second);
       this->time_sync_callback_.call();
       this->has_time_ = true;
    diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp
    index 064e6f899c..6f6739d293 100644
    --- a/esphome/components/time/real_time_clock.cpp
    +++ b/esphome/components/time/real_time_clock.cpp
    @@ -35,7 +35,7 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
       }
     
       auto time = this->now();
    -  ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%d:%d", time.year, time.month, time.day_of_month, time.hour,
    +  ESP_LOGD(TAG, "Synchronized time: %04d-%02d-%02d %02d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour,
                time.minute, time.second);
     
       this->time_sync_callback_.call();
    
    From 07b882c80194020182c41afd91f4dc2486b4f8cb Mon Sep 17 00:00:00 2001
    From: Dave T <17680170+davet2001@users.noreply.github.com>
    Date: Tue, 23 Nov 2021 08:20:36 +0000
    Subject: [PATCH 1732/1841] Fix distorted gif frames when resizing (#2774)
    
    ---
     esphome/components/animation/__init__.py | 17 +++++++++++------
     1 file changed, 11 insertions(+), 6 deletions(-)
    
    diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py
    index 1780bdf72e..7c9ff07f97 100644
    --- a/esphome/components/animation/__init__.py
    +++ b/esphome/components/animation/__init__.py
    @@ -44,8 +44,9 @@ async def to_code(config):
         width, height = image.size
         frames = image.n_frames
         if CONF_RESIZE in config:
    -        image.thumbnail(config[CONF_RESIZE])
    -        width, height = image.size
    +        new_width_max, new_height_max = config[CONF_RESIZE]
    +        ratio = min(new_width_max / width, new_height_max / height)
    +        width, height = int(width * ratio), int(height * ratio)
         else:
             if width > 500 or height > 500:
                 _LOGGER.warning(
    @@ -59,10 +60,12 @@ async def to_code(config):
             for frameIndex in range(frames):
                 image.seek(frameIndex)
                 frame = image.convert("L", dither=Image.NONE)
    +            if CONF_RESIZE in config:
    +                frame = frame.resize([width, height])
                 pixels = list(frame.getdata())
                 if len(pixels) != height * width:
                     raise core.EsphomeError(
    -                    f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}"
    +                    f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
                     )
                 for pix in pixels:
                     data[pos] = pix
    @@ -73,13 +76,13 @@ async def to_code(config):
             pos = 0
             for frameIndex in range(frames):
                 image.seek(frameIndex)
    -            if CONF_RESIZE in config:
    -                image.thumbnail(config[CONF_RESIZE])
                 frame = image.convert("RGB")
    +            if CONF_RESIZE in config:
    +                frame = frame.resize([width, height])
                 pixels = list(frame.getdata())
                 if len(pixels) != height * width:
                     raise core.EsphomeError(
    -                    f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}"
    +                    f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
                     )
                 for pix in pixels:
                     data[pos] = pix[0]
    @@ -95,6 +98,8 @@ async def to_code(config):
             for frameIndex in range(frames):
                 image.seek(frameIndex)
                 frame = image.convert("1", dither=Image.NONE)
    +            if CONF_RESIZE in config:
    +                frame = frame.resize([width, height])
                 for y in range(height):
                     for x in range(width):
                         if frame.getpixel((x, y)):
    
    From 710096b1c6035d71c7ed4912e8940fdb83ead3ae Mon Sep 17 00:00:00 2001
    From: Andreas Hergert <36455093+andreashergert1984@users.noreply.github.com>
    Date: Tue, 23 Nov 2021 09:20:55 +0100
    Subject: [PATCH 1733/1841] Fixed wrong setup of tc9548a (#2766)
    
    ---
     esphome/components/tca9548a/tca9548a.cpp | 2 +-
     esphome/components/tca9548a/tca9548a.h   | 1 +
     2 files changed, 2 insertions(+), 1 deletion(-)
    
    diff --git a/esphome/components/tca9548a/tca9548a.cpp b/esphome/components/tca9548a/tca9548a.cpp
    index e902eb5ed4..f3f8685287 100644
    --- a/esphome/components/tca9548a/tca9548a.cpp
    +++ b/esphome/components/tca9548a/tca9548a.cpp
    @@ -22,7 +22,7 @@ i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffer
     void TCA9548AComponent::setup() {
       ESP_LOGCONFIG(TAG, "Setting up TCA9548A...");
       uint8_t status = 0;
    -  if (this->read_register(0x00, &status, 1) != i2c::ERROR_OK) {
    +  if (this->read(&status, 1) != i2c::ERROR_OK) {
         ESP_LOGI(TAG, "TCA9548A failed");
         this->mark_failed();
         return;
    diff --git a/esphome/components/tca9548a/tca9548a.h b/esphome/components/tca9548a/tca9548a.h
    index 314346d317..39d07c2eb4 100644
    --- a/esphome/components/tca9548a/tca9548a.h
    +++ b/esphome/components/tca9548a/tca9548a.h
    @@ -24,6 +24,7 @@ class TCA9548AComponent : public Component, public i2c::I2CDevice {
      public:
       void setup() override;
       void dump_config() override;
    +  float get_setup_priority() const override { return setup_priority::IO; }
       void update();
     
       i2c::ErrorCode switch_to_channel(uint8_t channel);
    
    From 05fe5db030a05d9bc2877927494d8cd6b30b2bdd Mon Sep 17 00:00:00 2001
    From: Paul Monigatti 
    Date: Tue, 23 Nov 2021 21:21:14 +1300
    Subject: [PATCH 1734/1841] Relax the icon validator to allow non-mdi icons
     (#2764)
    
    ---
     esphome/config_validation.py               | 6 ++++--
     tests/unit_tests/test_config_validation.py | 4 ++--
     2 files changed, 6 insertions(+), 4 deletions(-)
    
    diff --git a/esphome/config_validation.py b/esphome/config_validation.py
    index 2bb45487fa..8df74ba861 100644
    --- a/esphome/config_validation.py
    +++ b/esphome/config_validation.py
    @@ -296,9 +296,11 @@ def icon(value):
         value = string_strict(value)
         if not value:
             return value
    -    if value.startswith("mdi:"):
    +    if re.match("^[\\w\\-]+:[\\w\\-]+$", value):
             return value
    -    raise Invalid('Icons should start with prefix "mdi:"')
    +    raise Invalid(
    +        'Icons must match the format "[icon pack]:[icon]", e.g. "mdi:home-assistant"'
    +    )
     
     
     def boolean(value):
    diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py
    index 16cfb16e94..9e9af52d00 100644
    --- a/tests/unit_tests/test_config_validation.py
    +++ b/tests/unit_tests/test_config_validation.py
    @@ -66,7 +66,7 @@ def test_string_string__invalid(value):
             config_validation.string_strict(value)
     
     
    -@given(builds(lambda v: "mdi:" + v, text()))
    +@given(builds(lambda v: "mdi:" + v, text(alphabet=string.ascii_letters + string.digits + "-_", min_size=1, max_size=20)))
     @example("")
     def test_icon__valid(value):
         actual = config_validation.icon(value)
    @@ -75,7 +75,7 @@ def test_icon__valid(value):
     
     
     def test_icon__invalid():
    -    with pytest.raises(Invalid, match="Icons should start with prefix"):
    +    with pytest.raises(Invalid, match="Icons must match the format "):
             config_validation.icon("foo")
     
     
    
    From 335e69e6cd887ead7c22d3d76ddd9391dedef6cc Mon Sep 17 00:00:00 2001
    From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
    Date: Tue, 23 Nov 2021 09:24:28 +0100
    Subject: [PATCH 1735/1841] Bump black from 21.10b0 to 21.11b1 (#2760)
    
    ---
     requirements_test.txt | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/requirements_test.txt b/requirements_test.txt
    index 03879c5d0e..cc3fcfb2ec 100644
    --- a/requirements_test.txt
    +++ b/requirements_test.txt
    @@ -1,6 +1,6 @@
     pylint==2.11.1
     flake8==4.0.1
    -black==21.10b0
    +black==21.11b1
     pexpect==4.8.0
     pre-commit
     
    
    From 598f5b241f3f1b4746559ea82178f8da058031cf Mon Sep 17 00:00:00 2001
    From: krunkel 
    Date: Tue, 23 Nov 2021 09:26:16 +0100
    Subject: [PATCH 1736/1841] Remove unnecessary write in AHT10 update (#2675)
    
    ---
     esphome/components/aht10/aht10.cpp | 7 -------
     1 file changed, 7 deletions(-)
    
    diff --git a/esphome/components/aht10/aht10.cpp b/esphome/components/aht10/aht10.cpp
    index e5e04ac181..78f98cb14f 100644
    --- a/esphome/components/aht10/aht10.cpp
    +++ b/esphome/components/aht10/aht10.cpp
    @@ -73,13 +73,6 @@ void AHT10Component::update() {
       bool success = false;
       for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
         ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis());
    -    delayMicroseconds(4);
    -
    -    uint8_t reg = 0;
    -    if (this->write(®, 1) != i2c::ERROR_OK) {
    -      ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
    -      continue;
    -    }
         delay(delay_ms);
         if (this->read(data, 6) != i2c::ERROR_OK) {
           ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
    
    From 15cd602e8b196b5a0948a8abb5e65a16d530ecf9 Mon Sep 17 00:00:00 2001
    From: Maurice Makaay 
    Date: Tue, 23 Nov 2021 09:34:10 +0100
    Subject: [PATCH 1737/1841] Add support for P1 Data Request pin control (#2676)
    
    ---
     esphome/components/dsmr/__init__.py |  17 ++-
     esphome/components/dsmr/dsmr.cpp    | 210 +++++++++++++++++++---------
     esphome/components/dsmr/dsmr.h      |  14 +-
     tests/test3.yaml                    |   2 +
     4 files changed, 174 insertions(+), 69 deletions(-)
    
    diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py
    index 1cfc21a4ac..06b022c513 100644
    --- a/esphome/components/dsmr/__init__.py
    +++ b/esphome/components/dsmr/__init__.py
    @@ -1,5 +1,6 @@
     import esphome.codegen as cg
     import esphome.config_validation as cv
    +from esphome import pins
     from esphome.components import uart
     from esphome.const import (
         CONF_ID,
    @@ -11,11 +12,13 @@ CODEOWNERS = ["@glmnet", "@zuidwijk"]
     DEPENDENCIES = ["uart"]
     AUTO_LOAD = ["sensor", "text_sensor"]
     
    -CONF_DSMR_ID = "dsmr_id"
    -CONF_DECRYPTION_KEY = "decryption_key"
     CONF_CRC_CHECK = "crc_check"
    +CONF_DECRYPTION_KEY = "decryption_key"
    +CONF_DSMR_ID = "dsmr_id"
     CONF_GAS_MBUS_ID = "gas_mbus_id"
     CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length"
    +CONF_REQUEST_INTERVAL = "request_interval"
    +CONF_REQUEST_PIN = "request_pin"
     
     # Hack to prevent compile error due to ambiguity with lib namespace
     dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr")
    @@ -48,6 +51,8 @@ CONFIG_SCHEMA = cv.All(
                 cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean,
                 cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_,
                 cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_,
    +            cv.Optional(CONF_REQUEST_PIN): pins.gpio_output_pin_schema,
    +            cv.Optional(CONF_REQUEST_INTERVAL): cv.positive_time_period_milliseconds,
             }
         ).extend(uart.UART_DEVICE_SCHEMA),
         cv.only_with_arduino,
    @@ -62,6 +67,14 @@ async def to_code(config):
             cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY]))
         await cg.register_component(var, config)
     
    +    if CONF_REQUEST_PIN in config:
    +        request_pin = await cg.gpio_pin_expression(config[CONF_REQUEST_PIN])
    +        cg.add(var.set_request_pin(request_pin))
    +    if CONF_REQUEST_INTERVAL in config:
    +        cg.add(
    +            var.set_request_interval(config[CONF_REQUEST_INTERVAL].total_milliseconds)
    +        )
    +
         cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID])
     
         # DSMR Parser
    diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp
    index 631b18a1f4..03e418662a 100644
    --- a/esphome/components/dsmr/dsmr.cpp
    +++ b/esphome/components/dsmr/dsmr.cpp
    @@ -13,147 +13,217 @@ namespace dsmr {
     static const char *const TAG = "dsmr";
     
     void Dsmr::setup() {
    -  telegram_ = new char[max_telegram_len_];  // NOLINT
    +  this->telegram_ = new char[this->max_telegram_len_];  // NOLINT
    +  if (this->request_pin_ != nullptr) {
    +    this->request_pin_->setup();
    +  }
     }
     
     void Dsmr::loop() {
    -  if (decryption_key_.empty())
    -    receive_telegram_();
    -  else
    -    receive_encrypted_();
    +  if (this->ready_to_request_data_()) {
    +    if (this->decryption_key_.empty()) {
    +      this->receive_telegram_();
    +    } else {
    +      this->receive_encrypted_();
    +    }
    +  }
    +}
    +
    +bool Dsmr::ready_to_request_data_() {
    +  // When using a request pin, then wait for the next request interval.
    +  if (this->request_pin_ != nullptr) {
    +    if (!this->requesting_data_ && this->request_interval_reached_()) {
    +      this->start_requesting_data_();
    +    }
    +  }
    +  // Otherwise, sink serial data until next request interval.
    +  else {
    +    if (this->request_interval_reached_()) {
    +      this->start_requesting_data_();
    +    }
    +    if (!this->requesting_data_) {
    +      while (this->available()) {
    +        this->read();
    +      }
    +    }
    +  }
    +  return this->requesting_data_;
    +}
    +
    +bool Dsmr::request_interval_reached_() {
    +  if (this->last_request_time_ == 0) {
    +    return true;
    +  }
    +  return millis() - this->last_request_time_ > this->request_interval_;
     }
     
     bool Dsmr::available_within_timeout_() {
       uint8_t tries = READ_TIMEOUT_MS / 5;
       while (tries--) {
         delay(5);
    -    if (available()) {
    +    if (this->available()) {
           return true;
         }
       }
       return false;
     }
     
    +void Dsmr::start_requesting_data_() {
    +  if (!this->requesting_data_) {
    +    if (this->request_pin_ != nullptr) {
    +      ESP_LOGV(TAG, "Start requesting data from P1 port");
    +      this->request_pin_->digital_write(true);
    +    } else {
    +      ESP_LOGV(TAG, "Start reading data from P1 port");
    +    }
    +    this->requesting_data_ = true;
    +    this->last_request_time_ = millis();
    +  }
    +}
    +
    +void Dsmr::stop_requesting_data_() {
    +  if (this->requesting_data_) {
    +    if (this->request_pin_ != nullptr) {
    +      ESP_LOGV(TAG, "Stop requesting data from P1 port");
    +      this->request_pin_->digital_write(false);
    +    } else {
    +      ESP_LOGV(TAG, "Stop reading data from P1 port");
    +    }
    +    while (this->available()) {
    +      this->read();
    +    }
    +    this->requesting_data_ = false;
    +  }
    +}
    +
     void Dsmr::receive_telegram_() {
       while (true) {
    -    if (!available()) {
    -      if (!header_found_ || !available_within_timeout_()) {
    +    if (!this->available()) {
    +      if (!this->header_found_ || !this->available_within_timeout_()) {
             return;
           }
         }
     
    -    const char c = read();
    +    const char c = this->read();
     
         // Find a new telegram header, i.e. forward slash.
         if (c == '/') {
           ESP_LOGV(TAG, "Header of telegram found");
    -      header_found_ = true;
    -      footer_found_ = false;
    -      telegram_len_ = 0;
    +      this->header_found_ = true;
    +      this->footer_found_ = false;
    +      this->telegram_len_ = 0;
         }
    -    if (!header_found_)
    +    if (!this->header_found_)
           continue;
     
         // Check for buffer overflow.
    -    if (telegram_len_ >= max_telegram_len_) {
    -      header_found_ = false;
    -      footer_found_ = false;
    -      ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", max_telegram_len_);
    +    if (this->telegram_len_ >= this->max_telegram_len_) {
    +      this->header_found_ = false;
    +      this->footer_found_ = false;
    +      ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_);
           return;
         }
     
         // Some v2.2 or v3 meters will send a new value which starts with '('
    -    // in a new line while the value belongs to the previous ObisId. For
    -    // proper parsing remove these new line characters
    -    while (c == '(' && (telegram_[telegram_len_ - 1] == '\n' || telegram_[telegram_len_ - 1] == '\r'))
    -      telegram_len_--;
    +    // in a new line, while the value belongs to the previous ObisId. For
    +    // proper parsing, remove these new line characters.
    +    if (c == '(') {
    +      while (true) {
    +        auto previous_char = this->telegram_[this->telegram_len_ - 1];
    +        if (previous_char == '\n' || previous_char == '\r') {
    +          this->telegram_len_--;
    +        } else {
    +          break;
    +        }
    +      }
    +    }
     
         // Store the byte in the buffer.
    -    telegram_[telegram_len_] = c;
    -    telegram_len_++;
    +    this->telegram_[this->telegram_len_] = c;
    +    this->telegram_len_++;
     
         // Check for a footer, i.e. exlamation mark, followed by a hex checksum.
         if (c == '!') {
           ESP_LOGV(TAG, "Footer of telegram found");
    -      footer_found_ = true;
    +      this->footer_found_ = true;
           continue;
         }
         // Check for the end of the hex checksum, i.e. a newline.
    -    if (footer_found_ && c == '\n') {
    +    if (this->footer_found_ && c == '\n') {
           // Parse the telegram and publish sensor values.
    -      parse_telegram();
    +      this->parse_telegram();
     
    -      header_found_ = false;
    +      this->header_found_ = false;
           return;
         }
       }
     }
     
     void Dsmr::receive_encrypted_() {
    -  encrypted_telegram_len_ = 0;
    +  this->encrypted_telegram_len_ = 0;
       size_t packet_size = 0;
     
       while (true) {
    -    if (!available()) {
    -      if (!header_found_) {
    +    if (!this->available()) {
    +      if (!this->header_found_) {
             return;
           }
    -      if (!available_within_timeout_()) {
    +      if (!this->available_within_timeout_()) {
             ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram");
             return;
           }
         }
     
    -    const char c = read();
    +    const char c = this->read();
     
         // Find a new telegram start byte.
    -    if (!header_found_) {
    +    if (!this->header_found_) {
           if ((uint8_t) c != 0xDB) {
             continue;
           }
           ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found");
    -      header_found_ = true;
    +      this->header_found_ = true;
         }
     
         // Check for buffer overflow.
    -    if (encrypted_telegram_len_ >= max_telegram_len_) {
    -      header_found_ = false;
    -      ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", max_telegram_len_);
    +    if (this->encrypted_telegram_len_ >= this->max_telegram_len_) {
    +      this->header_found_ = false;
    +      ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_);
           return;
         }
     
    -    encrypted_telegram_[encrypted_telegram_len_++] = c;
    +    this->encrypted_telegram_[this->encrypted_telegram_len_++] = c;
     
    -    if (packet_size == 0 && encrypted_telegram_len_ > 20) {
    +    if (packet_size == 0 && this->encrypted_telegram_len_ > 20) {
           // Complete header + data bytes
    -      packet_size = 13 + (encrypted_telegram_[11] << 8 | encrypted_telegram_[12]);
    +      packet_size = 13 + (this->encrypted_telegram_[11] << 8 | this->encrypted_telegram_[12]);
           ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size);
         }
    -    if (encrypted_telegram_len_ == packet_size && packet_size > 0) {
    +    if (this->encrypted_telegram_len_ == packet_size && packet_size > 0) {
           ESP_LOGV(TAG, "End of encrypted telegram found");
           GCM *gcmaes128{new GCM()};
    -      gcmaes128->setKey(decryption_key_.data(), gcmaes128->keySize());
    +      gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
           // the iv is 8 bytes of the system title + 4 bytes frame counter
           // system title is at byte 2 and frame counter at byte 15
           for (int i = 10; i < 14; i++)
    -        encrypted_telegram_[i] = encrypted_telegram_[i + 4];
    +        this->encrypted_telegram_[i] = this->encrypted_telegram_[i + 4];
           constexpr uint16_t iv_size{12};
    -      gcmaes128->setIV(&encrypted_telegram_[2], iv_size);
    -      gcmaes128->decrypt(reinterpret_cast(telegram_),
    +      gcmaes128->setIV(&this->encrypted_telegram_[2], iv_size);
    +      gcmaes128->decrypt(reinterpret_cast(this->telegram_),
                              // the ciphertext start at byte 18
    -                         &encrypted_telegram_[18],
    +                         &this->encrypted_telegram_[18],
                              // cipher size
    -                         encrypted_telegram_len_ - 17);
    +                         this->encrypted_telegram_len_ - 17);
           delete gcmaes128;  // NOLINT(cppcoreguidelines-owning-memory)
     
    -      telegram_len_ = strnlen(telegram_, max_telegram_len_);
    -      ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_);
    -      ESP_LOGVV(TAG, "Decrypted telegram: %s", telegram_);
    +      this->telegram_len_ = strnlen(this->telegram_, this->max_telegram_len_);
    +      ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->telegram_len_);
    +      ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
     
    -      parse_telegram();
    +      this->parse_telegram();
     
    -      header_found_ = false;
    -      telegram_len_ = 0;
    +      this->header_found_ = false;
    +      this->telegram_len_ = 0;
           return;
         }
       }
    @@ -162,24 +232,32 @@ void Dsmr::receive_encrypted_() {
     bool Dsmr::parse_telegram() {
       MyData data;
       ESP_LOGV(TAG, "Trying to parse telegram");
    +  this->stop_requesting_data_();
       ::dsmr::ParseResult res =
    -      ::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false,
    -                              crc_check_);  // Parse telegram according to data definition. Ignore unknown values.
    +      ::dsmr::P1Parser::parse(&data, this->telegram_, this->telegram_len_, false,
    +                              this->crc_check_);  // Parse telegram according to data definition. Ignore unknown values.
       if (res.err) {
         // Parsing error, show it
    -    auto err_str = res.fullError(telegram_, telegram_ + telegram_len_);
    +    auto err_str = res.fullError(this->telegram_, this->telegram_ + this->telegram_len_);
         ESP_LOGE(TAG, "%s", err_str.c_str());
         return false;
       } else {
         this->status_clear_warning();
    -    publish_sensors(data);
    +    this->publish_sensors(data);
         return true;
       }
     }
     
     void Dsmr::dump_config() {
       ESP_LOGCONFIG(TAG, "DSMR:");
    -  ESP_LOGCONFIG(TAG, "  Max telegram length: %d", max_telegram_len_);
    +  ESP_LOGCONFIG(TAG, "  Max telegram length: %d", this->max_telegram_len_);
    +
    +  if (this->request_pin_ != nullptr) {
    +    LOG_PIN("  Request Pin: ", this->request_pin_);
    +  }
    +  if (this->request_interval_ > 0) {
    +    ESP_LOGCONFIG(TAG, "  Request Interval: %.1fs", this->request_interval_ / 1e3f);
    +  }
     
     #define DSMR_LOG_SENSOR(s) LOG_SENSOR("  ", #s, this->s_##s##_);
       DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, )
    @@ -191,10 +269,10 @@ void Dsmr::dump_config() {
     void Dsmr::set_decryption_key(const std::string &decryption_key) {
       if (decryption_key.length() == 0) {
         ESP_LOGI(TAG, "Disabling decryption");
    -    decryption_key_.clear();
    -    if (encrypted_telegram_ != nullptr) {
    -      delete[] encrypted_telegram_;
    -      encrypted_telegram_ = nullptr;
    +    this->decryption_key_.clear();
    +    if (this->encrypted_telegram_ != nullptr) {
    +      delete[] this->encrypted_telegram_;
    +      this->encrypted_telegram_ = nullptr;
         }
         return;
       }
    @@ -203,7 +281,7 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
         ESP_LOGE(TAG, "Error, decryption key must be 32 character long");
         return;
       }
    -  decryption_key_.clear();
    +  this->decryption_key_.clear();
     
       ESP_LOGI(TAG, "Decryption key is set");
       // Verbose level prints decryption key
    @@ -212,11 +290,11 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
       char temp[3] = {0};
       for (int i = 0; i < 16; i++) {
         strncpy(temp, &(decryption_key.c_str()[i * 2]), 2);
    -    decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
    +    this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
       }
     
    -  if (encrypted_telegram_ == nullptr) {
    -    encrypted_telegram_ = new uint8_t[max_telegram_len_];  // NOLINT
    +  if (this->encrypted_telegram_ == nullptr) {
    +    this->encrypted_telegram_ = new uint8_t[this->max_telegram_len_];  // NOLINT
       }
     }
     
    diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h
    index 5943e4d47f..0430eb93ed 100644
    --- a/esphome/components/dsmr/dsmr.h
    +++ b/esphome/components/dsmr/dsmr.h
    @@ -52,7 +52,6 @@ class Dsmr : public Component, public uart::UARTDevice {
       Dsmr(uart::UARTComponent *uart, bool crc_check) : uart::UARTDevice(uart), crc_check_(crc_check) {}
     
       void setup() override;
    -
       void loop() override;
     
       bool parse_telegram();
    @@ -75,6 +74,9 @@ class Dsmr : public Component, public uart::UARTDevice {
     
       void set_max_telegram_length(size_t length);
     
    +  void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; }
    +  void set_request_interval(uint32_t interval) { this->request_interval_ = interval; }
    +
     // Sensor setters
     #define DSMR_SET_SENSOR(s) \
       void set_##s(sensor::Sensor *sensor) { s_##s##_ = sensor; }
    @@ -99,6 +101,16 @@ class Dsmr : public Component, public uart::UARTDevice {
       /// lost in the process.
       bool available_within_timeout_();
     
    +  // Data request
    +  GPIOPin *request_pin_{nullptr};
    +  uint32_t request_interval_{0};
    +  uint32_t last_request_time_{0};
    +  bool requesting_data_{false};
    +  bool ready_to_request_data_();
    +  bool request_interval_reached_();
    +  void start_requesting_data_();
    +  void stop_requesting_data_();
    +
       // Telegram buffer
       size_t max_telegram_len_;
       char *telegram_{nullptr};
    diff --git a/tests/test3.yaml b/tests/test3.yaml
    index 0b9297a0b8..27a0c148d9 100644
    --- a/tests/test3.yaml
    +++ b/tests/test3.yaml
    @@ -1309,6 +1309,8 @@ dsmr:
       decryption_key: 00112233445566778899aabbccddeeff
       uart_id: uart6
       max_telegram_length: 1000
    +  request_pin: D5
    +  request_interval: 20s
     
     daly_bms:
       update_interval: 20s
    
    From 7e8012c1a02b2e9c10f9a76ac36f786d8ba5a24a Mon Sep 17 00:00:00 2001
    From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Date: Thu, 25 Nov 2021 07:59:32 +1300
    Subject: [PATCH 1738/1841] Allow specifying the dashboard bind address (#2787)
    
    ---
     esphome/__main__.py            | 6 ++++++
     esphome/dashboard/dashboard.py | 7 ++++---
     2 files changed, 10 insertions(+), 3 deletions(-)
    
    diff --git a/esphome/__main__.py b/esphome/__main__.py
    index 7c7c22dd1f..2f06a71b5f 100644
    --- a/esphome/__main__.py
    +++ b/esphome/__main__.py
    @@ -637,6 +637,12 @@ def parse_args(argv):
             type=int,
             default=6052,
         )
    +    parser_dashboard.add_argument(
    +        "--address",
    +        help="The address to bind to.",
    +        type=str,
    +        default="0.0.0.0",
    +    )
         parser_dashboard.add_argument(
             "--username",
             help="The optional username to require for authentication.",
    diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py
    index 11571ec889..c98047d9e5 100644
    --- a/esphome/dashboard/dashboard.py
    +++ b/esphome/dashboard/dashboard.py
    @@ -970,16 +970,17 @@ def start_web_server(args):
             server.add_socket(socket)
         else:
             _LOGGER.info(
    -            "Starting dashboard web server on http://0.0.0.0:%s and configuration dir %s...",
    +            "Starting dashboard web server on http://%s:%s and configuration dir %s...",
    +            args.address,
                 args.port,
                 settings.config_dir,
             )
    -        app.listen(args.port)
    +        app.listen(args.port, args.address)
     
             if args.open_ui:
                 import webbrowser
     
    -            webbrowser.open(f"localhost:{args.port}")
    +            webbrowser.open(f"http://{args.address}:{args.port}")
     
         if settings.status_use_ping:
             status_thread = PingStatusThread()
    
    From 4b1d73791d4a13bd1486083a60b0940bea266c63 Mon Sep 17 00:00:00 2001
    From: Martin <25747549+martgras@users.noreply.github.com>
    Date: Wed, 24 Nov 2021 20:06:08 +0100
    Subject: [PATCH 1739/1841] remove LEDC_HIGH_SPEED_MODE for C3, S2, S3 (#2791)
    
    ---
     esphome/components/ledc/ledc_output.cpp | 18 +++++++++++++++---
     1 file changed, 15 insertions(+), 3 deletions(-)
    
    diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp
    index 21a747e34d..13ad32ab70 100644
    --- a/esphome/components/ledc/ledc_output.cpp
    +++ b/esphome/components/ledc/ledc_output.cpp
    @@ -15,6 +15,18 @@ namespace ledc {
     
     static const char *const TAG = "ledc.output";
     
    +#ifdef USE_ESP_IDF
    +#if SOC_LEDC_SUPPORT_HS_MODE
    +// Only ESP32 has LEDC_HIGH_SPEED_MODE
    +inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; }
    +#else
    +// S2, C3, S3 only support LEDC_LOW_SPEED_MODE
    +// See
    +// https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html#functionality-overview
    +inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; }
    +#endif
    +#endif
    +
     float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); }
     float ledc_min_frequency_for_bit_depth(uint8_t bit_depth) {
       const float max_div_num = ((1 << 20) - 1) / 256.0f;
    @@ -48,7 +60,7 @@ void LEDCOutput::write_state(float state) {
       ledcWrite(this->channel_, duty);
     #endif
     #ifdef USE_ESP_IDF
    -  auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE;
    +  auto speed_mode = get_speed_mode(channel_);
       auto chan_num = static_cast(channel_ % 8);
       ledc_set_duty(speed_mode, chan_num, duty);
       ledc_update_duty(speed_mode, chan_num);
    @@ -63,7 +75,7 @@ void LEDCOutput::setup() {
       ledcAttachPin(this->pin_->get_pin(), this->channel_);
     #endif
     #ifdef USE_ESP_IDF
    -  auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE;
    +  auto speed_mode = get_speed_mode(channel_);
       auto timer_num = static_cast((channel_ % 8) / 2);
       auto chan_num = static_cast(channel_ % 8);
     
    @@ -114,7 +126,7 @@ void LEDCOutput::update_frequency(float frequency) {
         ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!");
         return;
       }
    -  auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE;
    +  auto speed_mode = get_speed_mode(channel_);
       auto timer_num = static_cast((channel_ % 8) / 2);
     
       ledc_timer_config_t timer_conf{};
    
    From 290da8df2ded1cc82268d2a22e24a163e2a27881 Mon Sep 17 00:00:00 2001
    From: rsumner 
    Date: Wed, 24 Nov 2021 16:22:51 -0600
    Subject: [PATCH 1740/1841] Fix LEDC resolution calculation on ESP32-C3/S2/S3
     (#2794)
    
    Co-authored-by: Oxan van Leeuwen 
    Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    ---
     esphome/components/ledc/ledc_output.cpp | 22 +++++++++++++++++-----
     1 file changed, 17 insertions(+), 5 deletions(-)
    
    diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp
    index 13ad32ab70..a56dccfd72 100644
    --- a/esphome/components/ledc/ledc_output.cpp
    +++ b/esphome/components/ledc/ledc_output.cpp
    @@ -16,6 +16,7 @@ namespace ledc {
     static const char *const TAG = "ledc.output";
     
     #ifdef USE_ESP_IDF
    +static const int MAX_RES_BITS = LEDC_TIMER_BIT_MAX - 1;
     #if SOC_LEDC_SUPPORT_HS_MODE
     // Only ESP32 has LEDC_HIGH_SPEED_MODE
     inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; }
    @@ -25,19 +26,26 @@ inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_H
     // https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html#functionality-overview
     inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; }
     #endif
    +#else
    +static const int MAX_RES_BITS = 20;
     #endif
     
     float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); }
    -float ledc_min_frequency_for_bit_depth(uint8_t bit_depth) {
    -  const float max_div_num = ((1 << 20) - 1) / 256.0f;
    +
    +float ledc_min_frequency_for_bit_depth(uint8_t bit_depth, bool low_frequency) {
    +  const float max_div_num = ((1 << MAX_RES_BITS) - 1) / (low_frequency ? 32.0f : 256.0f);
       return 80e6f / (max_div_num * float(1 << bit_depth));
     }
    +
     optional ledc_bit_depth_for_frequency(float frequency) {
    -  for (int i = 20; i >= 1; i--) {
    -    const float min_frequency = ledc_min_frequency_for_bit_depth(i);
    +  ESP_LOGD(TAG, "Calculating resolution bit-depth for frequency %f", frequency);
    +  for (int i = MAX_RES_BITS; i >= 1; i--) {
    +    const float min_frequency = ledc_min_frequency_for_bit_depth(i, (frequency < 100));
         const float max_frequency = ledc_max_frequency_for_bit_depth(i);
    -    if (min_frequency <= frequency && frequency <= max_frequency)
    +    if (min_frequency <= frequency && frequency <= max_frequency) {
    +      ESP_LOGD(TAG, "Resolution calculated as %d", i);
           return i;
    +    }
       }
       return {};
     }
    @@ -80,6 +88,10 @@ void LEDCOutput::setup() {
       auto chan_num = static_cast(channel_ % 8);
     
       bit_depth_ = *ledc_bit_depth_for_frequency(frequency_);
    +  if (bit_depth_ < 1) {
    +    ESP_LOGW(TAG, "Frequency %f can't be achieved with any bit depth", frequency_);
    +    this->status_set_warning();
    +  }
     
       ledc_timer_config_t timer_conf{};
       timer_conf.speed_mode = speed_mode;
    
    From ccfa1e23f0211f5421bef22b328ec3fb5b4204ee Mon Sep 17 00:00:00 2001
    From: Martin <25747549+martgras@users.noreply.github.com>
    Date: Wed, 24 Nov 2021 23:28:19 +0100
    Subject: [PATCH 1741/1841] Add support for sdp8xx (#2779)
    
    ---
     esphome/components/sdp3x/sdp3x.cpp | 106 ++++++++++++++++++-----------
     esphome/components/sdp3x/sdp3x.h   |   6 +-
     esphome/components/sdp3x/sensor.py |  12 ++++
     3 files changed, 83 insertions(+), 41 deletions(-)
    
    diff --git a/esphome/components/sdp3x/sdp3x.cpp b/esphome/components/sdp3x/sdp3x.cpp
    index b0d8bcc6c4..107ed2902f 100644
    --- a/esphome/components/sdp3x/sdp3x.cpp
    +++ b/esphome/components/sdp3x/sdp3x.cpp
    @@ -11,6 +11,7 @@ static const uint8_t SDP3X_SOFT_RESET[2] = {0x00, 0x06};
     static const uint8_t SDP3X_READ_ID1[2] = {0x36, 0x7C};
     static const uint8_t SDP3X_READ_ID2[2] = {0xE1, 0x02};
     static const uint8_t SDP3X_START_DP_AVG[2] = {0x36, 0x15};
    +static const uint8_t SDP3X_START_MASS_FLOW_AVG[2] = {0x36, 0x03};
     static const uint8_t SDP3X_STOP_MEAS[2] = {0x3F, 0xF9};
     
     void SDP3XComponent::update() { this->read_pressure_(); }
    @@ -26,46 +27,69 @@ void SDP3XComponent::setup() {
         ESP_LOGW(TAG, "Soft Reset SDP3X failed!");  // This sometimes fails for no good reason
       }
     
    -  delayMicroseconds(20000);
    +  this->set_timeout(20, [this] {
    +    if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) {
    +      ESP_LOGE(TAG, "Read ID1 SDP3X failed!");
    +      this->mark_failed();
    +      return;
    +    }
    +    if (this->write(SDP3X_READ_ID2, 2) != i2c::ERROR_OK) {
    +      ESP_LOGE(TAG, "Read ID2 SDP3X failed!");
    +      this->mark_failed();
    +      return;
    +    }
     
    -  if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) {
    -    ESP_LOGE(TAG, "Read ID1 SDP3X failed!");
    -    this->mark_failed();
    -    return;
    -  }
    -  if (this->write(SDP3X_READ_ID2, 2) != i2c::ERROR_OK) {
    -    ESP_LOGE(TAG, "Read ID2 SDP3X failed!");
    -    this->mark_failed();
    -    return;
    -  }
    +    uint8_t data[18];
    +    if (this->read(data, 18) != i2c::ERROR_OK) {
    +      ESP_LOGE(TAG, "Read ID SDP3X failed!");
    +      this->mark_failed();
    +      return;
    +    }
    +    if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]))) {
    +      ESP_LOGE(TAG, "CRC ID SDP3X failed!");
    +      this->mark_failed();
    +      return;
    +    }
     
    -  uint8_t data[18];
    -  if (this->read(data, 18) != i2c::ERROR_OK) {
    -    ESP_LOGE(TAG, "Read ID SDP3X failed!");
    -    this->mark_failed();
    -    return;
    -  }
    +    // SDP8xx
    +    // ref:
    +    // https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/8_Differential_Pressure/Datasheets/Sensirion_Differential_Pressure_Datasheet_SDP8xx_Digital.pdf
    +    if (data[2] == 0x02) {
    +      switch (data[3]) {
    +        case 0x01:  // SDP800-500Pa
    +          ESP_LOGCONFIG(TAG, "Sensor is SDP800-500Pa");
    +          break;
    +        case 0x0A:  // SDP810-500Pa
    +          ESP_LOGCONFIG(TAG, "Sensor is SDP810-500Pa");
    +          break;
    +        case 0x04:  // SDP801-500Pa
    +          ESP_LOGCONFIG(TAG, "Sensor is SDP801-500Pa");
    +          break;
    +        case 0x0D:  // SDP811-500Pa
    +          ESP_LOGCONFIG(TAG, "Sensor is SDP811-500Pa");
    +          break;
    +        case 0x02:  // SDP800-125Pa
    +          ESP_LOGCONFIG(TAG, "Sensor is SDP800-125Pa");
    +          break;
    +        case 0x0B:  // SDP810-125Pa
    +          ESP_LOGCONFIG(TAG, "Sensor is SDP810-125Pa");
    +          break;
    +      }
    +    } else if (data[2] == 0x01) {
    +      if (data[3] == 0x01) {
    +        ESP_LOGCONFIG(TAG, "Sensor is SDP31-500Pa");
    +      } else if (data[3] == 0x02) {
    +        ESP_LOGCONFIG(TAG, "Sensor is SDP32-125Pa");
    +      }
    +    }
     
    -  if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]))) {
    -    ESP_LOGE(TAG, "CRC ID SDP3X failed!");
    -    this->mark_failed();
    -    return;
    -  }
    -
    -  if (data[3] == 0x01) {
    -    ESP_LOGCONFIG(TAG, "SDP3X is SDP31");
    -    pressure_scale_factor_ = 60.0f * 100.0f;  // Scale factors converted to hPa per count
    -  } else if (data[3] == 0x02) {
    -    ESP_LOGCONFIG(TAG, "SDP3X is SDP32");
    -    pressure_scale_factor_ = 240.0f * 100.0f;
    -  }
    -
    -  if (this->write(SDP3X_START_DP_AVG, 2) != i2c::ERROR_OK) {
    -    ESP_LOGE(TAG, "Start Measurements SDP3X failed!");
    -    this->mark_failed();
    -    return;
    -  }
    -  ESP_LOGCONFIG(TAG, "SDP3X started!");
    +    if (this->write(measurement_mode_ == DP_AVG ? SDP3X_START_DP_AVG : SDP3X_START_MASS_FLOW_AVG, 2) != i2c::ERROR_OK) {
    +      ESP_LOGE(TAG, "Start Measurements SDP3X failed!");
    +      this->mark_failed();
    +      return;
    +    }
    +    ESP_LOGCONFIG(TAG, "SDP3X started!");
    +  });
     }
     void SDP3XComponent::dump_config() {
       LOG_SENSOR("  ", "SDP3X", this);
    @@ -91,8 +115,12 @@ void SDP3XComponent::read_pressure_() {
       }
     
       int16_t pressure_raw = encode_uint16(data[0], data[1]);
    -  float pressure = pressure_raw / pressure_scale_factor_;
    -  ESP_LOGV(TAG, "Got raw pressure=%d, scale factor =%.3f ", pressure_raw, pressure_scale_factor_);
    +  int16_t temperature_raw = encode_uint16(data[3], data[4]);
    +  int16_t scale_factor_raw = encode_uint16(data[6], data[7]);
    +  // scale factor is in Pa - convert to hPa
    +  float pressure = pressure_raw / (scale_factor_raw * 100.0f);
    +  ESP_LOGV(TAG, "Got raw pressure=%d, raw scale factor =%d, raw temperature=%d ", pressure_raw, scale_factor_raw,
    +           temperature_raw);
       ESP_LOGD(TAG, "Got Pressure=%.3f hPa", pressure);
     
       this->publish_state(pressure);
    diff --git a/esphome/components/sdp3x/sdp3x.h b/esphome/components/sdp3x/sdp3x.h
    index 51c9973c61..0e74d0883d 100644
    --- a/esphome/components/sdp3x/sdp3x.h
    +++ b/esphome/components/sdp3x/sdp3x.h
    @@ -7,6 +7,8 @@
     namespace esphome {
     namespace sdp3x {
     
    +enum MeasurementMode { MASS_FLOW_AVG, DP_AVG };
    +
     class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor {
      public:
       /// Schedule temperature+pressure readings.
    @@ -16,14 +18,14 @@ class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public se
       void dump_config() override;
     
       float get_setup_priority() const override;
    +  void set_measurement_mode(MeasurementMode mode) { measurement_mode_ = mode; }
     
      protected:
       /// Internal method to read the pressure from the component after it has been scheduled.
       void read_pressure_();
     
       bool check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum);
    -
    -  float pressure_scale_factor_ = 0.0f;  // hPa per count
    +  MeasurementMode measurement_mode_;
     };
     
     }  // namespace sdp3x
    diff --git a/esphome/components/sdp3x/sensor.py b/esphome/components/sdp3x/sensor.py
    index 08d7250f6e..45f5cc4d9a 100644
    --- a/esphome/components/sdp3x/sensor.py
    +++ b/esphome/components/sdp3x/sensor.py
    @@ -14,6 +14,14 @@ CODEOWNERS = ["@Azimath"]
     sdp3x_ns = cg.esphome_ns.namespace("sdp3x")
     SDP3XComponent = sdp3x_ns.class_("SDP3XComponent", cg.PollingComponent, i2c.I2CDevice)
     
    +
    +MeasurementMode = sdp3x_ns.enum("MeasurementMode")
    +MEASUREMENT_MODE = {
    +    "mass_flow": MeasurementMode.MASS_FLOW_AVG,
    +    "differential_pressure": MeasurementMode.DP_AVG,
    +}
    +CONF_MEASUREMENT_MODE = "measurement_mode"
    +
     CONFIG_SCHEMA = (
         sensor.sensor_schema(
             unit_of_measurement=UNIT_HECTOPASCAL,
    @@ -24,6 +32,9 @@ CONFIG_SCHEMA = (
         .extend(
             {
                 cv.GenerateID(): cv.declare_id(SDP3XComponent),
    +            cv.Optional(
    +                CONF_MEASUREMENT_MODE, default="differential_pressure"
    +            ): cv.enum(MEASUREMENT_MODE),
             }
         )
         .extend(cv.polling_component_schema("60s"))
    @@ -36,3 +47,4 @@ async def to_code(config):
         await cg.register_component(var, config)
         await i2c.register_i2c_device(var, config)
         await sensor.register_sensor(var, config)
    +    cg.add(var.set_measurement_mode(config[CONF_MEASUREMENT_MODE]))
    
    From ceb9b1d1ff0b989dc196eeb624963d6e6c644427 Mon Sep 17 00:00:00 2001
    From: Maurice Makaay 
    Date: Wed, 24 Nov 2021 23:51:56 +0100
    Subject: [PATCH 1742/1841] Allow empty UART debug: option, logging in hex
     format by default (#2771)
    
    Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Co-authored-by: Maurice Makaay 
    ---
     esphome/components/uart/__init__.py | 29 ++++++++++++++++++++++++++---
     tests/test3.yaml                    |  2 ++
     2 files changed, 28 insertions(+), 3 deletions(-)
    
    diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py
    index 159b08d2d9..61b54044d7 100644
    --- a/esphome/components/uart/__init__.py
    +++ b/esphome/components/uart/__init__.py
    @@ -3,6 +3,7 @@ from typing import Optional
     import esphome.codegen as cg
     import esphome.config_validation as cv
     import esphome.final_validate as fv
    +from esphome.yaml_util import make_data_base
     from esphome import pins, automation
     from esphome.const import (
         CONF_BAUD_RATE,
    @@ -24,6 +25,7 @@ from esphome.const import (
         CONF_DELIMITER,
         CONF_DUMMY_RECEIVER,
         CONF_DUMMY_RECEIVER_ID,
    +    CONF_LAMBDA,
     )
     from esphome.core import CORE
     
    @@ -94,7 +96,26 @@ UART_DIRECTIONS = {
         "BOTH": UARTDirection.UART_DIRECTION_BOTH,
     }
     
    -AFTER_DEFAULTS = {CONF_BYTES: 256, CONF_TIMEOUT: "100ms"}
    +# The reason for having CONF_BYTES at 150 by default:
    +#
    +# The log message buffer size is 512 bytes by default. About 35 bytes are
    +# used for the log prefix. That leaves us with 477 bytes for logging data.
    +# The default log output is hex, which uses 3 characters per represented
    +# byte (2 hex chars + 1 separator). That means that 477 / 3 = 159 bytes
    +# can be represented in a single log line. Using 150, because people love
    +# round numbers.
    +AFTER_DEFAULTS = {CONF_BYTES: 150, CONF_TIMEOUT: "100ms"}
    +
    +# By default, log in hex format when no specific sequence is provided.
    +DEFAULT_DEBUG_OUTPUT = "UARTDebug::log_hex(direction, bytes, ':');"
    +DEFAULT_SEQUENCE = [{CONF_LAMBDA: make_data_base(DEFAULT_DEBUG_OUTPUT)}]
    +
    +
    +def maybe_empty_debug(value):
    +    if value is None:
    +        value = {}
    +    return DEBUG_SCHEMA(value)
    +
     
     DEBUG_SCHEMA = cv.Schema(
         {
    @@ -113,7 +134,9 @@ DEBUG_SCHEMA = cv.Schema(
                     cv.Optional(CONF_DELIMITER): cv.templatable(validate_raw_data),
                 }
             ),
    -        cv.Required(CONF_SEQUENCE): automation.validate_automation(),
    +        cv.Optional(
    +            CONF_SEQUENCE, default=DEFAULT_SEQUENCE
    +        ): automation.validate_automation(),
             cv.Optional(CONF_DUMMY_RECEIVER, default=False): cv.boolean,
             cv.GenerateID(CONF_DUMMY_RECEIVER_ID): cv.declare_id(UARTDummyReceiver),
         }
    @@ -135,7 +158,7 @@ CONFIG_SCHEMA = cv.All(
                 cv.Optional(CONF_INVERT): cv.invalid(
                     "This option has been removed. Please instead use invert in the tx/rx pin schemas."
                 ),
    -            cv.Optional(CONF_DEBUG): DEBUG_SCHEMA,
    +            cv.Optional(CONF_DEBUG): maybe_empty_debug,
             }
         ).extend(cv.COMPONENT_SCHEMA),
         cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN),
    diff --git a/tests/test3.yaml b/tests/test3.yaml
    index 27a0c148d9..61c7a3dcad 100644
    --- a/tests/test3.yaml
    +++ b/tests/test3.yaml
    @@ -254,6 +254,8 @@ uart:
         tx_pin: GPIO4
         rx_pin: GPIO5
         baud_rate: 38400
    +    # Specifically added for testing debug with no options at all.
    +    debug:
     
     modbus:
       uart_id: uart1
    
    From 2aea27d2726d06599290bc7afa3288700edfcd3d Mon Sep 17 00:00:00 2001
    From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
    Date: Thu, 25 Nov 2021 20:34:11 +0100
    Subject: [PATCH 1743/1841] Bump pylint from 2.11.1 to 2.12.1 (#2798)
    
    ---
     requirements_test.txt | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/requirements_test.txt b/requirements_test.txt
    index cc3fcfb2ec..ad98ec2a4f 100644
    --- a/requirements_test.txt
    +++ b/requirements_test.txt
    @@ -1,4 +1,4 @@
    -pylint==2.11.1
    +pylint==2.12.1
     flake8==4.0.1
     black==21.11b1
     pexpect==4.8.0
    
    From 3637be251e594769080ff6cb683d4afa3221c3ff Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Thu, 25 Nov 2021 21:00:49 +0100
    Subject: [PATCH 1744/1841] Fix parsing numbers from null-terminated buffers
     (#2755)
    
    ---
     esphome/components/anova/anova_base.cpp | 23 ++++++++++++-----------
     esphome/components/anova/anova_base.h   |  1 -
     esphome/components/ezo/ezo.cpp          |  4 ++--
     esphome/core/helpers.h                  | 22 ++++++++++++----------
     4 files changed, 26 insertions(+), 24 deletions(-)
    
    diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp
    index d55404089e..dcef75e483 100644
    --- a/esphome/components/anova/anova_base.cpp
    +++ b/esphome/components/anova/anova_base.cpp
    @@ -73,51 +73,52 @@ AnovaPacket *AnovaCodec::get_stop_request() {
     }
     
     void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
    -  memset(this->buf_, 0, 32);
    -  strncpy(this->buf_, (char *) data, length);
    +  char buf[32];
    +  memset(buf, 0, sizeof(buf));
    +  strncpy(buf, (char *) data, std::min(length, sizeof(buf) - 1));
       this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false;
       switch (this->current_query_) {
         case READ_DEVICE_STATUS: {
    -      if (!strncmp(this->buf_, "stopped", 7)) {
    +      if (!strncmp(buf, "stopped", 7)) {
             this->has_running_ = true;
             this->running_ = false;
           }
    -      if (!strncmp(this->buf_, "running", 7)) {
    +      if (!strncmp(buf, "running", 7)) {
             this->has_running_ = true;
             this->running_ = true;
           }
           break;
         }
         case START: {
    -      if (!strncmp(this->buf_, "start", 5)) {
    +      if (!strncmp(buf, "start", 5)) {
             this->has_running_ = true;
             this->running_ = true;
           }
           break;
         }
         case STOP: {
    -      if (!strncmp(this->buf_, "stop", 4)) {
    +      if (!strncmp(buf, "stop", 4)) {
             this->has_running_ = true;
             this->running_ = false;
           }
           break;
         }
         case READ_TARGET_TEMPERATURE: {
    -      this->target_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f);
    +      this->target_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f);
           if (this->fahrenheit_)
             this->target_temp_ = ftoc(this->target_temp_);
           this->has_target_temp_ = true;
           break;
         }
         case SET_TARGET_TEMPERATURE: {
    -      this->target_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f);
    +      this->target_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f);
           if (this->fahrenheit_)
             this->target_temp_ = ftoc(this->target_temp_);
           this->has_target_temp_ = true;
           break;
         }
         case READ_CURRENT_TEMPERATURE: {
    -      this->current_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f);
    +      this->current_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f);
           if (this->fahrenheit_)
             this->current_temp_ = ftoc(this->current_temp_);
           this->has_current_temp_ = true;
    @@ -125,8 +126,8 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
         }
         case SET_UNIT:
         case READ_UNIT: {
    -      this->unit_ = this->buf_[0];
    -      this->fahrenheit_ = this->buf_[0] == 'f';
    +      this->unit_ = buf[0];
    +      this->fahrenheit_ = buf[0] == 'f';
           this->has_unit_ = true;
           break;
         }
    diff --git a/esphome/components/anova/anova_base.h b/esphome/components/anova/anova_base.h
    index 7c1383512d..b831157849 100644
    --- a/esphome/components/anova/anova_base.h
    +++ b/esphome/components/anova/anova_base.h
    @@ -70,7 +70,6 @@ class AnovaCodec {
       bool has_current_temp_;
       bool has_unit_;
       bool has_running_;
    -  char buf_[32];
       bool fahrenheit_;
     
       CurrentQuery current_query_;
    diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp
    index 7f7a41fb41..426f2807c1 100644
    --- a/esphome/components/ezo/ezo.cpp
    +++ b/esphome/components/ezo/ezo.cpp
    @@ -32,7 +32,7 @@ void EZOSensor::update() {
     }
     
     void EZOSensor::loop() {
    -  uint8_t buf[20];
    +  uint8_t buf[21];
       if (!(this->state_ & EZO_STATE_WAIT)) {
         if (this->state_ & EZO_STATE_SEND_TEMP) {
           int len = sprintf((char *) buf, "T,%0.3f", this->tempcomp_);
    @@ -74,7 +74,7 @@ void EZOSensor::loop() {
       if (buf[0] != 1)
         return;
     
    -  float val = parse_number((char *) &buf[1], sizeof(buf) - 1).value_or(0);
    +  float val = parse_number((char *) &buf[1], sizeof(buf) - 2).value_or(0);
       this->publish_state(val);
     }
     
    diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h
    index 4ce1f08074..1faf1ac3aa 100644
    --- a/esphome/core/helpers.h
    +++ b/esphome/core/helpers.h
    @@ -364,45 +364,47 @@ std::string str_sanitize(const std::string &str);
     /// @name Parsing & formatting
     ///@{
     
    -/// Parse a unsigned decimal number.
    +/// Parse an unsigned decimal number (requires null-terminated string).
     template::value && std::is_unsigned::value), int> = 0>
     optional parse_number(const char *str, size_t len) {
       char *end = nullptr;
       unsigned long value = ::strtoul(str, &end, 10);  // NOLINT(google-runtime-int)
    -  if (end == nullptr || end != str + len || value > std::numeric_limits::max())
    +  if (end == str || *end != '\0' || value > std::numeric_limits::max())
         return {};
       return value;
     }
    +/// Parse an unsigned decimal number.
     template::value && std::is_unsigned::value), int> = 0>
     optional parse_number(const std::string &str) {
    -  return parse_number(str.c_str(), str.length());
    +  return parse_number(str.c_str(), str.length() + 1);
     }
    -/// Parse a signed decimal number.
    +/// Parse a signed decimal number (requires null-terminated string).
     template::value && std::is_signed::value), int> = 0>
     optional parse_number(const char *str, size_t len) {
       char *end = nullptr;
       signed long value = ::strtol(str, &end, 10);  // NOLINT(google-runtime-int)
    -  if (end == nullptr || end != str + len || value < std::numeric_limits::min() ||
    -      value > std::numeric_limits::max())
    +  if (end == str || *end != '\0' || value < std::numeric_limits::min() || value > std::numeric_limits::max())
         return {};
       return value;
     }
    +/// Parse a signed decimal number.
     template::value && std::is_signed::value), int> = 0>
     optional parse_number(const std::string &str) {
    -  return parse_number(str.c_str(), str.length());
    +  return parse_number(str.c_str(), str.length() + 1);
     }
    -/// Parse a decimal floating-point number.
    +/// Parse a decimal floating-point number (requires null-terminated string).
     template::value), int> = 0>
     optional parse_number(const char *str, size_t len) {
       char *end = nullptr;
       float value = ::strtof(str, &end);
    -  if (end == nullptr || end != str + len || value == HUGE_VALF)
    +  if (end == str || *end != '\0' || value == HUGE_VALF)
         return {};
       return value;
     }
    +/// Parse a decimal floating-point number.
     template::value), int> = 0>
     optional parse_number(const std::string &str) {
    -  return parse_number(str.c_str(), str.length());
    +  return parse_number(str.c_str(), str.length() + 1);
     }
     
     ///@}
    
    From 2a78c2970da865bcea82df7f6f8e04afab47d34e Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Thu, 25 Nov 2021 21:27:34 +0100
    Subject: [PATCH 1745/1841] Fix CI cache key for test3.yaml compile (#2757)
    
    ---
     .github/workflows/ci.yml | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
    index 02b64d2bf5..b3e06a3367 100644
    --- a/.github/workflows/ci.yml
    +++ b/.github/workflows/ci.yml
    @@ -34,7 +34,7 @@ jobs:
               - id: test
                 file: tests/test3.yaml
                 name: Test tests/test3.yaml
    -            pio_cache_key: test1
    +            pio_cache_key: test3
               - id: test
                 file: tests/test4.yaml
                 name: Test tests/test4.yaml
    
    From 4e448b21ff2eaa9f158889f658d2269ee8f8dce0 Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Thu, 25 Nov 2021 21:27:53 +0100
    Subject: [PATCH 1746/1841] Drop obsolete comment from CI workflow file (#2758)
    
    ---
     .github/workflows/ci.yml | 2 --
     1 file changed, 2 deletions(-)
    
    diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
    index b3e06a3367..d723424326 100644
    --- a/.github/workflows/ci.yml
    +++ b/.github/workflows/ci.yml
    @@ -1,5 +1,3 @@
    -# THESE JOBS ARE COPIED IN release.yml and release-dev.yml
    -# PLEASE ALSO UPDATE THOSE FILES WHEN CHANGING LINES HERE
     name: CI
     
     on:
    
    From d50bdf619f38ec86e4a026a96a657e04050ac3e1 Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Thu, 25 Nov 2021 21:29:10 +0100
    Subject: [PATCH 1747/1841] Cache virtualenv instead of pip cache between CI
     runs (#2759)
    
    ---
     .github/workflows/ci.yml | 19 ++++++++++++-------
     1 file changed, 12 insertions(+), 7 deletions(-)
    
    diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
    index d723424326..3daabecc9f 100644
    --- a/.github/workflows/ci.yml
    +++ b/.github/workflows/ci.yml
    @@ -78,18 +78,23 @@ jobs:
             with:
               python-version: '3.7'
     
    -      - name: Cache pip modules
    +      - name: Cache virtualenv
             uses: actions/cache@v2
             with:
    -          path: ~/.cache/pip
    -          key: pip-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
    +          path: .venv
    +          key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
               restore-keys: |
    -            pip-${{ steps.python.outputs.python-version }}-
    +            venv-${{ steps.python.outputs.python-version }}-
     
    -      - name: Set up python environment
    +      - name: Set up virtualenv
             run: |
    -          pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
    -          pip3 install -e .
    +          python -m venv .venv
    +          source .venv/bin/activate
    +          pip install -U pip
    +          pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
    +          pip install -e .
    +          echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH
    +          echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV
     
           # Use per check platformio cache because checks use different parts
           - name: Cache platformio
    
    From b5f660398c509ca73dae1f44a821b591646e9f52 Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Thu, 25 Nov 2021 21:35:33 +0100
    Subject: [PATCH 1748/1841] Add map filter for text sensors (#2761)
    
    ---
     esphome/components/text_sensor/__init__.py | 38 ++++++++++++----------
     esphome/components/text_sensor/filter.cpp  |  6 ++++
     esphome/components/text_sensor/filter.h    | 11 +++++++
     tests/test2.yaml                           | 10 ++++++
     4 files changed, 48 insertions(+), 17 deletions(-)
    
    diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py
    index a070e6f86d..e0fc6af19c 100644
    --- a/esphome/components/text_sensor/__init__.py
    +++ b/esphome/components/text_sensor/__init__.py
    @@ -49,6 +49,7 @@ ToLowerFilter = text_sensor_ns.class_("ToLowerFilter", Filter)
     AppendFilter = text_sensor_ns.class_("AppendFilter", Filter)
     PrependFilter = text_sensor_ns.class_("PrependFilter", Filter)
     SubstituteFilter = text_sensor_ns.class_("SubstituteFilter", Filter)
    +MapFilter = text_sensor_ns.class_("MapFilter", Filter)
     
     
     @FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda)
    @@ -79,26 +80,21 @@ async def prepend_filter_to_code(config, filter_id):
         return cg.new_Pvariable(filter_id, config)
     
     
    -def validate_substitute(value):
    -    if isinstance(value, dict):
    -        return cv.Schema(
    -            {
    -                cv.Required(CONF_FROM): cv.string,
    -                cv.Required(CONF_TO): cv.string,
    -            }
    -        )(value)
    -    value = cv.string(value)
    -    if "->" not in value:
    -        raise cv.Invalid("Substitute mapping must contain '->'")
    -    a, b = value.split("->", 1)
    -    a, b = a.strip(), b.strip()
    -    return validate_substitute({CONF_FROM: cv.string(a), CONF_TO: cv.string(b)})
    +def validate_mapping(value):
    +    if not isinstance(value, dict):
    +        value = cv.string(value)
    +        if "->" not in value:
    +            raise cv.Invalid("Mapping must contain '->'")
    +        a, b = value.split("->", 1)
    +        value = {CONF_FROM: a.strip(), CONF_TO: b.strip()}
    +
    +    return cv.Schema(
    +        {cv.Required(CONF_FROM): cv.string, cv.Required(CONF_TO): cv.string}
    +    )(value)
     
     
     @FILTER_REGISTRY.register(
    -    "substitute",
    -    SubstituteFilter,
    -    cv.All(cv.ensure_list(validate_substitute), cv.Length(min=2)),
    +    "substitute", SubstituteFilter, cv.ensure_list(validate_mapping)
     )
     async def substitute_filter_to_code(config, filter_id):
         from_strings = [conf[CONF_FROM] for conf in config]
    @@ -106,6 +102,14 @@ async def substitute_filter_to_code(config, filter_id):
         return cg.new_Pvariable(filter_id, from_strings, to_strings)
     
     
    +@FILTER_REGISTRY.register("map", MapFilter, cv.ensure_list(validate_mapping))
    +async def map_filter_to_code(config, filter_id):
    +    map_ = cg.std_ns.class_("map").template(cg.std_string, cg.std_string)
    +    return cg.new_Pvariable(
    +        filter_id, map_([(item[CONF_FROM], item[CONF_TO]) for item in config])
    +    )
    +
    +
     icon = cv.icon
     
     
    diff --git a/esphome/components/text_sensor/filter.cpp b/esphome/components/text_sensor/filter.cpp
    index 14df6238ff..a692378e8b 100644
    --- a/esphome/components/text_sensor/filter.cpp
    +++ b/esphome/components/text_sensor/filter.cpp
    @@ -70,5 +70,11 @@ optional SubstituteFilter::new_value(std::string value) {
       return value;
     }
     
    +// Map
    +optional MapFilter::new_value(std::string value) {
    +  auto item = mappings_.find(value);
    +  return item == mappings_.end() ? value : item->second;
    +}
    +
     }  // namespace text_sensor
     }  // namespace esphome
    diff --git a/esphome/components/text_sensor/filter.h b/esphome/components/text_sensor/filter.h
    index 6a1d9ab04e..38f35e6172 100644
    --- a/esphome/components/text_sensor/filter.h
    +++ b/esphome/components/text_sensor/filter.h
    @@ -4,6 +4,7 @@
     #include "esphome/core/helpers.h"
     #include 
     #include 
    +#include 
     
     namespace esphome {
     namespace text_sensor {
    @@ -108,5 +109,15 @@ class SubstituteFilter : public Filter {
       std::vector to_strings_;
     };
     
    +/// A filter that maps values from one set to another
    +class MapFilter : public Filter {
    + public:
    +  MapFilter(std::map mappings) : mappings_(std::move(mappings)) {}
    +  optional new_value(std::string value) override;
    +
    + protected:
    +  std::map mappings_;
    +};
    +
     }  // namespace text_sensor
     }  // namespace esphome
    diff --git a/tests/test2.yaml b/tests/test2.yaml
    index 3afef9501d..50743dc643 100644
    --- a/tests/test2.yaml
    +++ b/tests/test2.yaml
    @@ -458,6 +458,16 @@ text_sensor:
         name: 'Template Text Sensor'
         lambda: |-
           return {"Hello World"};
    +    filters:
    +      - to_upper:
    +      - to_lower:
    +      - append: "xyz"
    +      - prepend: "abcd"
    +      - substitute:
    +          - Hello -> Goodbye
    +      - map:
    +          - red -> green
    +      - lambda: return {"1234"};
       - platform: homeassistant
         entity_id: sensor.hello_world2
         id: ha_hello_world2
    
    From 5e631bc6ba6ded3fa61df88327b4a74d9d691f36 Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Thu, 25 Nov 2021 21:36:42 +0100
    Subject: [PATCH 1749/1841] Only match GCC warnings from ESPHome source files
     in CI (#2756)
    
    ---
     .github/workflows/matchers/gcc.json | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/.github/workflows/matchers/gcc.json b/.github/workflows/matchers/gcc.json
    index 899239f816..a00d9c33f4 100644
    --- a/.github/workflows/matchers/gcc.json
    +++ b/.github/workflows/matchers/gcc.json
    @@ -5,7 +5,7 @@
           "severity": "error",
           "pattern": [
             {
    -          "regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
    +          "regexp": "^src/(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
               "file": 1,
               "line": 2,
               "column": 3,
    
    From 9681dfb45891f891789990f525b6212cd0961552 Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Thu, 25 Nov 2021 21:37:27 +0100
    Subject: [PATCH 1750/1841] Correct constant for dynamic I2S bus in NeoPixelBus
     (#2797)
    
    ---
     esphome/components/neopixelbus/_methods.py | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/esphome/components/neopixelbus/_methods.py b/esphome/components/neopixelbus/_methods.py
    index b03544f246..4e3c3ca778 100644
    --- a/esphome/components/neopixelbus/_methods.py
    +++ b/esphome/components/neopixelbus/_methods.py
    @@ -88,8 +88,8 @@ def _esp32_i2s_default_bus():
     
     
     def _validate_esp32_i2s_bus(value):
    -    if isinstance(value, str) and value.lower() == CHANNEL_DYNAMIC:
    -        value = CHANNEL_DYNAMIC
    +    if isinstance(value, str) and value.lower() == BUS_DYNAMIC:
    +        value = BUS_DYNAMIC
         else:
             value = cv.int_(value)
         variant_buses = {
    
    From 00965fe19e857a83cb7f500da640c0c80694e79e Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Thu, 25 Nov 2021 21:54:11 +0100
    Subject: [PATCH 1751/1841] Consistently format errors in CI scripts (#2762)
    
    ---
     .github/workflows/matchers/ci-custom.json   |  2 +-
     .github/workflows/matchers/lint-python.json |  4 +--
     requirements_test.txt                       |  1 -
     script/ci-custom.py                         | 16 +++++-----
     script/clang-format                         | 16 ++++------
     script/clang-tidy                           | 33 +++++++++------------
     script/helpers.py                           | 19 +++++++-----
     script/lint-python                          | 21 ++++++++-----
     8 files changed, 57 insertions(+), 55 deletions(-)
    
    diff --git a/.github/workflows/matchers/ci-custom.json b/.github/workflows/matchers/ci-custom.json
    index 4e1eafff5e..9888dbb440 100644
    --- a/.github/workflows/matchers/ci-custom.json
    +++ b/.github/workflows/matchers/ci-custom.json
    @@ -4,7 +4,7 @@
                 "owner": "ci-custom",
                 "pattern": [
                     {
    -                    "regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$",
    +                    "regexp": "^(.*):(\\d+):(\\d+):\\s+(.*)$",
                         "file": 1,
                         "line": 2,
                         "column": 3,
    diff --git a/.github/workflows/matchers/lint-python.json b/.github/workflows/matchers/lint-python.json
    index decbe36c4a..e88f74f6f6 100644
    --- a/.github/workflows/matchers/lint-python.json
    +++ b/.github/workflows/matchers/lint-python.json
    @@ -5,7 +5,7 @@
           "severity": "error",
           "pattern": [
               {
    -          "regexp": "^(.*):(\\d+) - ([EFCDNW]\\d{3}.*)$",
    +          "regexp": "^(.*):(\\d+): ([EFCDNW]\\d{3}.*)$",
               "file": 1,
               "line": 2,
               "message": 3
    @@ -17,7 +17,7 @@
           "severity": "error",
           "pattern": [
             {
    -          "regexp": "^(.*):(\\d+) - (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
    +          "regexp": "^(.*):(\\d+): (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
               "file": 1,
               "line": 2,
               "message": 3
    diff --git a/requirements_test.txt b/requirements_test.txt
    index ad98ec2a4f..b916e8bb1b 100644
    --- a/requirements_test.txt
    +++ b/requirements_test.txt
    @@ -1,7 +1,6 @@
     pylint==2.12.1
     flake8==4.0.1
     black==21.11b1
    -pexpect==4.8.0
     pre-commit
     
     # Unit tests
    diff --git a/script/ci-custom.py b/script/ci-custom.py
    index 89550afd3d..3f01fb81bf 100755
    --- a/script/ci-custom.py
    +++ b/script/ci-custom.py
    @@ -1,16 +1,16 @@
     #!/usr/bin/env python3
     
    -from helpers import git_ls_files, filter_changed
    +from helpers import styled, print_error_for_file, git_ls_files, filter_changed
    +import argparse
     import codecs
     import collections
    +import colorama
     import fnmatch
    +import functools
     import os.path
     import re
    -import subprocess
     import sys
     import time
    -import functools
    -import argparse
     
     sys.path.append(os.path.dirname(__file__))
     
    @@ -30,6 +30,8 @@ def find_all(a_str, sub):
                 column += len(sub)
     
     
    +colorama.init()
    +
     parser = argparse.ArgumentParser()
     parser.add_argument(
         "files", nargs="*", default=[], help="files to be processed (regex on path)"
    @@ -657,10 +659,8 @@ for fname in files:
     run_checks(LINT_POST_CHECKS, "POST")
     
     for f, errs in sorted(errors.items()):
    -    print(f"\033[0;32m************* File \033[1;32m{f}\033[0m")
    -    for lineno, col, msg in errs:
    -        print(f"ERROR {f}:{lineno}:{col} - {msg}")
    -    print()
    +    err_str = (f"{styled(colorama.Style.BRIGHT, f'{f}:{lineno}:{col}:')} {msg}\n" for lineno, col, msg in errs)
    +    print_error_for_file(f, "\n".join(err_str))
     
     if args.print_slowest:
         lint_times = []
    diff --git a/script/clang-format b/script/clang-format
    index d6588f1ccb..515df4c027 100755
    --- a/script/clang-format
    +++ b/script/clang-format
    @@ -1,6 +1,9 @@
     #!/usr/bin/env python3
     
    +from helpers import print_error_for_file, get_output, git_ls_files, filter_changed
     import argparse
    +import click
    +import colorama
     import multiprocessing
     import os
     import queue
    @@ -9,11 +12,6 @@ import subprocess
     import sys
     import threading
     
    -import click
    -
    -sys.path.append(os.path.dirname(__file__))
    -from helpers import get_output, git_ls_files, filter_changed
    -
     
     def run_format(args, queue, lock, failed_files):
         """Takes filenames out of queue and runs clang-format on them."""
    @@ -29,11 +27,7 @@ def run_format(args, queue, lock, failed_files):
             proc = subprocess.run(invocation, capture_output=True, encoding='utf-8')
             if proc.returncode != 0:
                 with lock:
    -                print()
    -                print("\033[0;32m************* File \033[1;32m{}\033[0m".format(path))
    -                print(proc.stdout)
    -                print(proc.stderr)
    -                print()
    +                print_error_for_file(path, proc.stderr)
                     failed_files.append(path)
             queue.task_done()
     
    @@ -43,6 +37,8 @@ def progress_bar_show(value):
     
     
     def main():
    +    colorama.init()
    +
         parser = argparse.ArgumentParser()
         parser.add_argument('-j', '--jobs', type=int,
                             default=multiprocessing.cpu_count(),
    diff --git a/script/clang-tidy b/script/clang-tidy
    index ad5fdfeb04..7450084634 100755
    --- a/script/clang-tidy
    +++ b/script/clang-tidy
    @@ -1,7 +1,10 @@
     #!/usr/bin/env python3
     
    +from helpers import print_error_for_file, get_output, filter_grep, \
    +    build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata, basepath
     import argparse
    -import json
    +import click
    +import colorama
     import multiprocessing
     import os
     import queue
    @@ -12,13 +15,6 @@ import sys
     import tempfile
     import threading
     
    -import click
    -import pexpect
    -
    -sys.path.append(os.path.dirname(__file__))
    -from helpers import shlex_quote, get_output, filter_grep, \
    -    build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata, basepath
    -
     
     def clang_options(idedata):
         cmd = [
    @@ -87,23 +83,20 @@ def run_tidy(args, options, tmpdir, queue, lock, failed_files):
                 invocation.append(name)
     
             if args.quiet:
    -            invocation.append('-quiet')
    +            invocation.append('--quiet')
    +
    +        if sys.stdout.isatty():
    +            invocation.append('--use-color')
     
    -        invocation.append(os.path.abspath(path))
             invocation.append(f"--header-filter={os.path.abspath(basepath)}/.*")
    +        invocation.append(os.path.abspath(path))
             invocation.append('--')
             invocation.extend(options)
    -        invocation_s = ' '.join(shlex_quote(x) for x in invocation)
     
    -        # Use pexpect for a pseudy-TTY with colored output
    -        output, rc = pexpect.run(invocation_s, withexitstatus=True, encoding='utf-8',
    -                                 timeout=15 * 60)
    -        if rc != 0:
    +        proc = subprocess.run(invocation, capture_output=True, encoding='utf-8')
    +        if proc.returncode != 0:
                 with lock:
    -                print()
    -                print("\033[0;32m************* File \033[1;32m{}\033[0m".format(path))
    -                print(output)
    -                print()
    +                print_error_for_file(path, proc.stdout)
                     failed_files.append(path)
             queue.task_done()
     
    @@ -119,6 +112,8 @@ def split_list(a, n):
     
     
     def main():
    +    colorama.init()
    +
         parser = argparse.ArgumentParser()
         parser.add_argument('-j', '--jobs', type=int,
                             default=multiprocessing.cpu_count(),
    diff --git a/script/helpers.py b/script/helpers.py
    index 430d8a8e7f..abf970b8a2 100644
    --- a/script/helpers.py
    +++ b/script/helpers.py
    @@ -1,4 +1,4 @@
    -import codecs
    +import colorama
     import os.path
     import re
     import subprocess
    @@ -11,13 +11,18 @@ temp_folder = os.path.join(root_path, ".temp")
     temp_header_file = os.path.join(temp_folder, "all-include.cpp")
     
     
    -def shlex_quote(s):
    -    if not s:
    -        return "''"
    -    if re.search(r"[^\w@%+=:,./-]", s) is None:
    -        return s
    +def styled(color, msg, reset=True):
    +    prefix = ''.join(color) if isinstance(color, tuple) else color
    +    suffix = colorama.Style.RESET_ALL if reset else ''
    +    return prefix + msg + suffix
     
    -    return "'" + s.replace("'", "'\"'\"'") + "'"
    +
    +def print_error_for_file(file, body):
    +    print(styled(colorama.Fore.GREEN, "### File ") + styled((colorama.Fore.GREEN, colorama.Style.BRIGHT), file))
    +    print()
    +    if body is not None:
    +        print(body)
    +        print()
     
     
     def build_all_include():
    diff --git a/script/lint-python b/script/lint-python
    index 41885b9672..8ee038a661 100755
    --- a/script/lint-python
    +++ b/script/lint-python
    @@ -1,15 +1,13 @@
     #!/usr/bin/env python3
     
     from __future__ import print_function
    -from helpers import get_output, get_err, git_ls_files, filter_changed
    -
    +from helpers import styled, print_error_for_file, get_output, get_err, git_ls_files, filter_changed
     import argparse
    +import colorama
     import os
     import re
     import sys
     
    -sys.path.append(os.path.dirname(__file__))
    -
     curfile = None
     
     
    @@ -17,14 +15,18 @@ def print_error(file, lineno, msg):
         global curfile
     
         if curfile != file:
    -        print()
    -        print("\033[0;32m************* File \033[1;32m{}\033[0m".format(file))
    +        print_error_for_file(file, None)
             curfile = file
     
    -    print("{}:{} - {}".format(file, lineno, msg))
    +    if lineno is not None:
    +        print(f"{styled(colorama.Style.BRIGHT, f'{file}:{lineno}:')} {msg}")
    +    else:
    +        print(f"{styled(colorama.Style.BRIGHT, f'{file}:')} {msg}")
     
     
     def main():
    +    colorama.init()
    +
         parser = argparse.ArgumentParser()
         parser.add_argument(
             "files", nargs="*", default=[], help="files to be processed (regex on path)"
    @@ -56,6 +58,7 @@ def main():
     
         cmd = ["black", "--verbose", "--check"] + files
         print("Running black...")
    +    print()
         log = get_err(*cmd)
         for line in log.splitlines():
             WOULD_REFORMAT = "would reformat"
    @@ -65,7 +68,9 @@ def main():
                 errors += 1
     
         cmd = ["flake8"] + files
    +    print()
         print("Running flake8...")
    +    print()
         log = get_output(*cmd)
         for line in log.splitlines():
             line = line.split(":", 4)
    @@ -78,7 +83,9 @@ def main():
             errors += 1
     
         cmd = ["pylint", "-f", "parseable", "--persistent=n"] + files
    +    print()
         print("Running pylint...")
    +    print()
         log = get_output(*cmd)
         for line in log.splitlines():
             line = line.split(":", 3)
    
    From 2347e043a9dcc5e265328a2d63ffdf140e0281de Mon Sep 17 00:00:00 2001
    From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Date: Fri, 26 Nov 2021 10:02:39 +1300
    Subject: [PATCH 1752/1841] Cancel previous workflows for PRs and branches
     (#2800)
    
    ---
     .github/workflows/ci.yml | 4 ++++
     1 file changed, 4 insertions(+)
    
    diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
    index 3daabecc9f..93a29874f7 100644
    --- a/.github/workflows/ci.yml
    +++ b/.github/workflows/ci.yml
    @@ -9,6 +9,10 @@ on:
     permissions:
       contents: read
     
    +concurrency:
    +  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
    +  cancel-in-progress: true
    +
     jobs:
       ci:
         name: ${{ matrix.name }}
    
    From e7827a69976a48aff3f9306eba5ae28f9ec81259 Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?Adri=C3=A1n=20Panella?= 
    Date: Thu, 25 Nov 2021 15:35:36 -0600
    Subject: [PATCH 1753/1841] total_daily_energy: allow to disable restore mode
     (#2795)
    
    ---
     esphome/components/total_daily_energy/sensor.py      |  3 +++
     .../total_daily_energy/total_daily_energy.cpp        | 12 ++++++------
     .../total_daily_energy/total_daily_energy.h          |  2 ++
     3 files changed, 11 insertions(+), 6 deletions(-)
    
    diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py
    index 6a8a416b81..0c20ccd27c 100644
    --- a/esphome/components/total_daily_energy/sensor.py
    +++ b/esphome/components/total_daily_energy/sensor.py
    @@ -4,6 +4,7 @@ from esphome.components import sensor, time
     from esphome.const import (
         CONF_ICON,
         CONF_ID,
    +    CONF_RESTORE,
         CONF_TIME_ID,
         DEVICE_CLASS_ENERGY,
         CONF_METHOD,
    @@ -36,6 +37,7 @@ CONFIG_SCHEMA = (
                 cv.GenerateID(): cv.declare_id(TotalDailyEnergy),
                 cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
                 cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor),
    +            cv.Optional(CONF_RESTORE, default=True): cv.boolean,
                 cv.Optional(
                     CONF_MIN_SAVE_INTERVAL, default="0s"
                 ): cv.positive_time_period_milliseconds,
    @@ -70,5 +72,6 @@ async def to_code(config):
         cg.add(var.set_parent(sens))
         time_ = await cg.get_variable(config[CONF_TIME_ID])
         cg.add(var.set_time(time_))
    +    cg.add(var.set_restore(config[CONF_RESTORE]))
         cg.add(var.set_min_save_interval(config[CONF_MIN_SAVE_INTERVAL]))
         cg.add(var.set_method(config[CONF_METHOD]))
    diff --git a/esphome/components/total_daily_energy/total_daily_energy.cpp b/esphome/components/total_daily_energy/total_daily_energy.cpp
    index 178dc7cbe0..3746301715 100644
    --- a/esphome/components/total_daily_energy/total_daily_energy.cpp
    +++ b/esphome/components/total_daily_energy/total_daily_energy.cpp
    @@ -7,14 +7,14 @@ namespace total_daily_energy {
     static const char *const TAG = "total_daily_energy";
     
     void TotalDailyEnergy::setup() {
    -  this->pref_ = global_preferences->make_preference(this->get_object_id_hash());
    +  float initial_value = 0;
     
    -  float recovered;
    -  if (this->pref_.load(&recovered)) {
    -    this->publish_state_and_save(recovered);
    -  } else {
    -    this->publish_state_and_save(0);
    +  if (this->restore_) {
    +    this->pref_ = global_preferences->make_preference(this->get_object_id_hash());
    +    this->pref_.load(&initial_value);
       }
    +  this->publish_state_and_save(initial_value);
    +
       this->last_update_ = millis();
       this->last_save_ = this->last_update_;
     
    diff --git a/esphome/components/total_daily_energy/total_daily_energy.h b/esphome/components/total_daily_energy/total_daily_energy.h
    index fedceafbd3..498f65891e 100644
    --- a/esphome/components/total_daily_energy/total_daily_energy.h
    +++ b/esphome/components/total_daily_energy/total_daily_energy.h
    @@ -17,6 +17,7 @@ enum TotalDailyEnergyMethod {
     
     class TotalDailyEnergy : public sensor::Sensor, public Component {
      public:
    +  void set_restore(bool restore) { restore_ = restore; }
       void set_min_save_interval(uint32_t min_interval) { this->min_save_interval_ = min_interval; }
       void set_time(time::RealTimeClock *time) { time_ = time; }
       void set_parent(Sensor *parent) { parent_ = parent; }
    @@ -41,6 +42,7 @@ class TotalDailyEnergy : public sensor::Sensor, public Component {
       uint32_t last_update_{0};
       uint32_t last_save_{0};
       uint32_t min_save_interval_{0};
    +  bool restore_;
       float total_energy_{0.0f};
       float last_power_state_{0.0f};
     };
    
    From 17a37b1de929ccdd9485269a934da3801380da8c Mon Sep 17 00:00:00 2001
    From: Martin <25747549+martgras@users.noreply.github.com>
    Date: Fri, 26 Nov 2021 00:48:52 +0100
    Subject: [PATCH 1754/1841] Modbus_controller: Add custom command. (#2680)
    
    ---
     .../components/modbus_controller/__init__.py  | 111 ++++++++++++++++--
     .../binary_sensor/__init__.py                 |  49 +++-----
     esphome/components/modbus_controller/const.py |   1 +
     .../modbus_controller/modbus_controller.cpp   |  59 +++++++---
     .../modbus_controller/modbus_controller.h     |  33 ++++--
     .../modbus_controller/number/__init__.py      |  64 ++++------
     .../modbus_controller/output/__init__.py      |  33 ++----
     .../modbus_controller/sensor/__init__.py      |  55 +++------
     .../modbus_controller/sensor/modbus_sensor.h  |   1 +
     .../modbus_controller/switch/__init__.py      |  59 +++++-----
     .../switch/modbus_switch.cpp                  |  56 ++++++---
     .../modbus_controller/switch/modbus_switch.h  |   5 +
     .../modbus_controller/text_sensor/__init__.py |  50 +++-----
     13 files changed, 315 insertions(+), 261 deletions(-)
    
    diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py
    index 8499cec561..825b91280e 100644
    --- a/esphome/components/modbus_controller/__init__.py
    +++ b/esphome/components/modbus_controller/__init__.py
    @@ -1,10 +1,20 @@
    +import binascii
     import esphome.codegen as cg
     import esphome.config_validation as cv
     from esphome.components import modbus
    -from esphome.const import CONF_ID, CONF_ADDRESS
    +from esphome.const import CONF_ADDRESS, CONF_ID, CONF_NAME, CONF_LAMBDA, CONF_OFFSET
     from esphome.cpp_helpers import logging
     from .const import (
    +    CONF_BITMASK,
    +    CONF_BYTE_OFFSET,
         CONF_COMMAND_THROTTLE,
    +    CONF_CUSTOM_COMMAND,
    +    CONF_FORCE_NEW_RANGE,
    +    CONF_MODBUS_CONTROLLER_ID,
    +    CONF_REGISTER_COUNT,
    +    CONF_REGISTER_TYPE,
    +    CONF_SKIP_UPDATES,
    +    CONF_VALUE_TYPE,
     )
     
     CODEOWNERS = ["@martgras"]
    @@ -37,6 +47,7 @@ MODBUS_FUNCTION_CODE = {
     ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType")
     ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType")
     MODBUS_REGISTER_TYPE = {
    +    "custom": ModbusRegisterType.CUSTOM,
         "coil": ModbusRegisterType.COIL,
         "discrete_input": ModbusRegisterType.DISCRETE_INPUT,
         "holding": ModbusRegisterType.HOLDING,
    @@ -95,6 +106,96 @@ CONFIG_SCHEMA = cv.All(
     )
     
     
    +ModbusItemBaseSchema = cv.Schema(
    +    {
    +        cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
    +        cv.Optional(CONF_ADDRESS): cv.positive_int,
    +        cv.Optional(CONF_CUSTOM_COMMAND): cv.ensure_list(cv.hex_uint8_t),
    +        cv.Exclusive(
    +            CONF_OFFSET,
    +            "offset",
    +            f"{CONF_OFFSET} and {CONF_BYTE_OFFSET} can't be used together",
    +        ): cv.positive_int,
    +        cv.Exclusive(
    +            CONF_BYTE_OFFSET,
    +            "offset",
    +            f"{CONF_OFFSET} and {CONF_BYTE_OFFSET} can't be used together",
    +        ): cv.positive_int,
    +        cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t,
    +        cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
    +        cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
    +        cv.Optional(CONF_LAMBDA): cv.returning_lambda,
    +    },
    +)
    +
    +
    +def validate_modbus_register(config):
    +    if CONF_CUSTOM_COMMAND not in config and CONF_ADDRESS not in config:
    +        raise cv.Invalid(
    +            f" {CONF_ADDRESS} is a required property if '{CONF_CUSTOM_COMMAND}:' isn't used"
    +        )
    +    if CONF_CUSTOM_COMMAND in config and CONF_REGISTER_TYPE in config:
    +        raise cv.Invalid(
    +            f"can't use '{CONF_REGISTER_TYPE}:' together with '{CONF_CUSTOM_COMMAND}:'",
    +        )
    +
    +    if CONF_CUSTOM_COMMAND not in config and CONF_REGISTER_TYPE not in config:
    +        raise cv.Invalid(
    +            f" {CONF_REGISTER_TYPE} is a required property if '{CONF_CUSTOM_COMMAND}:' isn't used"
    +        )
    +    return config
    +
    +
    +def modbus_calc_properties(config):
    +    byte_offset = 0
    +    reg_count = 0
    +    if CONF_OFFSET in config:
    +        byte_offset = config[CONF_OFFSET]
    +    # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
    +    if CONF_BYTE_OFFSET in config:
    +        byte_offset = config[CONF_BYTE_OFFSET]
    +    if CONF_REGISTER_COUNT in config:
    +        reg_count = config[CONF_REGISTER_COUNT]
    +    if CONF_VALUE_TYPE in config:
    +        value_type = config[CONF_VALUE_TYPE]
    +        if reg_count == 0:
    +            reg_count = TYPE_REGISTER_MAP[value_type]
    +    if CONF_CUSTOM_COMMAND in config:
    +        if CONF_ADDRESS not in config:
    +            # generate a unique modbus address using the hash of the name
    +            # CONF_NAME set even if only CONF_ID is used.
    +            # a modbus register address is required to add the item to sensormap
    +            value = config[CONF_NAME]
    +            if isinstance(value, str):
    +                value = value.encode()
    +            config[CONF_ADDRESS] = binascii.crc_hqx(value, 0)
    +        config[CONF_REGISTER_TYPE] = ModbusRegisterType.CUSTOM
    +        config[CONF_FORCE_NEW_RANGE] = True
    +    return byte_offset, reg_count
    +
    +
    +async def add_modbus_base_properties(
    +    var, config, sensor_type, lamdba_param_type=cg.float_, lamdba_return_type=float
    +):
    +    if CONF_CUSTOM_COMMAND in config:
    +        cg.add(var.set_custom_data(config[CONF_CUSTOM_COMMAND]))
    +
    +    if CONF_LAMBDA in config:
    +        template_ = await cg.process_lambda(
    +            config[CONF_LAMBDA],
    +            [
    +                (sensor_type.operator("ptr"), "item"),
    +                (lamdba_param_type, "x"),
    +                (
    +                    cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
    +                    "data",
    +                ),
    +            ],
    +            return_type=cg.optional.template(lamdba_return_type),
    +        )
    +        cg.add(var.set_template(template_))
    +
    +
     async def to_code(config):
         var = cg.new_Pvariable(config[CONF_ID], config[CONF_COMMAND_THROTTLE])
         cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE]))
    @@ -119,11 +220,3 @@ def function_code_to_register(function_code):
             "write_multiple_registers": ModbusRegisterType.HOLDING,
         }
         return FUNCTION_CODE_TYPE_MAP[function_code]
    -
    -
    -def find_by_value(dict, find_value):
    -    for (key, value) in MODBUS_REGISTER_TYPE.items():
    -        print(find_value, value)
    -        if find_value == value:
    -            return key
    -    return "not found"
    diff --git a/esphome/components/modbus_controller/binary_sensor/__init__.py b/esphome/components/modbus_controller/binary_sensor/__init__.py
    index d46ff71f2d..99d56fed67 100644
    --- a/esphome/components/modbus_controller/binary_sensor/__init__.py
    +++ b/esphome/components/modbus_controller/binary_sensor/__init__.py
    @@ -2,16 +2,18 @@ from esphome.components import binary_sensor
     import esphome.config_validation as cv
     import esphome.codegen as cg
     
    -from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OFFSET
    +from esphome.const import CONF_ADDRESS, CONF_ID
     from .. import (
    -    SensorItem,
    +    add_modbus_base_properties,
         modbus_controller_ns,
    -    ModbusController,
    +    modbus_calc_properties,
    +    validate_modbus_register,
    +    ModbusItemBaseSchema,
    +    SensorItem,
         MODBUS_REGISTER_TYPE,
     )
     from ..const import (
         CONF_BITMASK,
    -    CONF_BYTE_OFFSET,
         CONF_FORCE_NEW_RANGE,
         CONF_MODBUS_CONTROLLER_ID,
         CONF_REGISTER_TYPE,
    @@ -27,30 +29,20 @@ ModbusBinarySensor = modbus_controller_ns.class_(
     )
     
     CONFIG_SCHEMA = cv.All(
    -    binary_sensor.BINARY_SENSOR_SCHEMA.extend(
    +    binary_sensor.BINARY_SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA)
    +    .extend(ModbusItemBaseSchema)
    +    .extend(
             {
                 cv.GenerateID(): cv.declare_id(ModbusBinarySensor),
    -            cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
    -            cv.Required(CONF_ADDRESS): cv.positive_int,
    -            cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
    -            cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
    -            cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
    -            cv.Optional(CONF_BITMASK, default=0x1): cv.hex_uint32_t,
    -            cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
    -            cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
    -            cv.Optional(CONF_LAMBDA): cv.returning_lambda,
    +            cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
             }
    -    ).extend(cv.COMPONENT_SCHEMA),
    +    ),
    +    validate_modbus_register,
     )
     
     
     async def to_code(config):
    -    byte_offset = 0
    -    if CONF_OFFSET in config:
    -        byte_offset = config[CONF_OFFSET]
    -    # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
    -    if CONF_BYTE_OFFSET in config:
    -        byte_offset = config[CONF_BYTE_OFFSET]
    +    byte_offset, _ = modbus_calc_properties(config)
         var = cg.new_Pvariable(
             config[CONF_ID],
             config[CONF_REGISTER_TYPE],
    @@ -65,17 +57,4 @@ async def to_code(config):
     
         paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
         cg.add(paren.add_sensor_item(var))
    -    if CONF_LAMBDA in config:
    -        template_ = await cg.process_lambda(
    -            config[CONF_LAMBDA],
    -            [
    -                (ModbusBinarySensor.operator("ptr"), "item"),
    -                (cg.float_, "x"),
    -                (
    -                    cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
    -                    "data",
    -                ),
    -            ],
    -            return_type=cg.optional.template(bool),
    -        )
    -        cg.add(var.set_template(template_))
    +    await add_modbus_base_properties(var, config, ModbusBinarySensor, cg.float_, bool)
    diff --git a/esphome/components/modbus_controller/const.py b/esphome/components/modbus_controller/const.py
    index 3cd114e673..8d1676dd38 100644
    --- a/esphome/components/modbus_controller/const.py
    +++ b/esphome/components/modbus_controller/const.py
    @@ -1,6 +1,7 @@
     CONF_BITMASK = "bitmask"
     CONF_BYTE_OFFSET = "byte_offset"
     CONF_COMMAND_THROTTLE = "command_throttle"
    +CONF_CUSTOM_COMMAND = "custom_command"
     CONF_FORCE_NEW_RANGE = "force_new_range"
     CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id"
     CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode"
    diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp
    index 70b5bf8eae..8b96c20691 100644
    --- a/esphome/components/modbus_controller/modbus_controller.cpp
    +++ b/esphome/components/modbus_controller/modbus_controller.cpp
    @@ -28,7 +28,10 @@ bool ModbusController::send_next_command_() {
                  command->register_address, command->register_count);
         command->send();
         this->last_command_timestamp_ = millis();
    -    if (!command->on_data_func) {  // No handler remove from queue directly after sending
    +    // remove from queue if no handler is defined or command was sent too often
    +    if (!command->on_data_func || command->send_countdown < 1) {
    +      ESP_LOGD(TAG, "Modbus command to device=%d register=0x%02X countdown=%d removed from queue after send",
    +               this->address_, command->register_address, command->send_countdown);
           command_queue_.pop_front();
         }
       }
    @@ -69,24 +72,30 @@ void ModbusController::on_modbus_error(uint8_t function_code, uint8_t exception_
       }
     }
     
    -void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address,
    -                                        const std::vector &data) {
    -  ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address);
    -
    +std::map::iterator ModbusController::find_register_(ModbusRegisterType register_type,
    +                                                                            uint16_t start_address) {
       auto vec_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) {
         return (r.start_address == start_address && r.register_type == register_type);
       });
     
       if (vec_it == register_ranges_.end()) {
    -    ESP_LOGE(TAG, "Handle incoming data : No matching range for sensor found - start_address :  0x%X", start_address);
    -    return;
    -  }
    -  auto map_it = sensormap_.find(vec_it->first_sensorkey);
    -  if (map_it == sensormap_.end()) {
    -    ESP_LOGE(TAG, "Handle incoming data : No sensor found in at start_address :  0x%X (0x%llX)", start_address,
    -             vec_it->first_sensorkey);
    -    return;
    +    ESP_LOGE(TAG, "No matching range for sensor found - start_address :  0x%X", start_address);
    +  } else {
    +    auto map_it = sensormap_.find(vec_it->first_sensorkey);
    +    if (map_it == sensormap_.end()) {
    +      ESP_LOGE(TAG, "No sensor found in at start_address :  0x%X (0x%llX)", start_address, vec_it->first_sensorkey);
    +    } else {
    +      return sensormap_.find(vec_it->first_sensorkey);
    +    }
       }
    +  // not found
    +  return std::end(sensormap_);
    +}
    +void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address,
    +                                        const std::vector &data) {
    +  ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address);
    +
    +  auto map_it = find_register_(register_type, start_address);
       // loop through all sensors with the same start address
       while (map_it != sensormap_.end() && map_it->second->start_address == start_address) {
         if (map_it->second->register_type == register_type) {
    @@ -116,9 +125,23 @@ void ModbusController::update_range_(RegisterRange &r) {
       ESP_LOGV(TAG, "Range : %X Size: %x (%d) skip: %d", r.start_address, r.register_count, (int) r.register_type,
                r.skip_updates_counter);
       if (r.skip_updates_counter == 0) {
    -    ModbusCommandItem command_item =
    -        ModbusCommandItem::create_read_command(this, r.register_type, r.start_address, r.register_count);
    -    queue_command(command_item);
    +    // if a custom command is used the user supplied custom_data is only available in the SensorItem.
    +    if (r.register_type == ModbusRegisterType::CUSTOM) {
    +      auto it = this->find_register_(r.register_type, r.start_address);
    +      if (it != sensormap_.end()) {
    +        auto command_item = ModbusCommandItem::create_custom_command(
    +            this, it->second->custom_data,
    +            [this](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) {
    +              this->on_register_data(ModbusRegisterType::CUSTOM, start_address, data);
    +            });
    +        command_item.register_address = it->second->start_address;
    +        command_item.register_count = it->second->register_count;
    +        command_item.function_code = ModbusFunctionCode::CUSTOM;
    +        queue_command(command_item);
    +      }
    +    } else {
    +      queue_command(ModbusCommandItem::create_read_command(this, r.register_type, r.start_address, r.register_count));
    +    }
         r.skip_updates_counter = r.skip_updates;  // reset counter to config value
       } else {
         r.skip_updates_counter--;
    @@ -422,6 +445,7 @@ bool ModbusCommandItem::send() {
         modbusdevice->send_raw(this->payload);
       }
       ESP_LOGV(TAG, "Command sent %d 0x%X %d", uint8_t(this->function_code), this->register_address, this->register_count);
    +  send_countdown--;
       return true;
     }
     
    @@ -549,6 +573,9 @@ float payload_to_float(const std::vector &data, SensorValueType sensor_
           ESP_LOGD(TAG, "FP32_R = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value);
           result = raw_to_float.float_value;
         } break;
    +    case SensorValueType::RAW:
    +      result = NAN;
    +      break;
         default:
           break;
       }
    diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h
    index 222ebbd020..39c0d8026f 100644
    --- a/esphome/components/modbus_controller/modbus_controller.h
    +++ b/esphome/components/modbus_controller/modbus_controller.h
    @@ -247,18 +247,11 @@ float payload_to_float(const std::vector &data, SensorValueType sensor_
     
     class ModbusController;
     
    -struct SensorItem {
    -  ModbusRegisterType register_type;
    -  SensorValueType sensor_value_type;
    -  uint16_t start_address;
    -  uint32_t bitmask;
    -  uint8_t offset;
    -  uint8_t register_count;
    -  uint8_t skip_updates;
    -  bool force_new_range{false};
    -
    +class SensorItem {
    + public:
       virtual void parse_and_publish(const std::vector &data) = 0;
     
    +  void set_custom_data(const std::vector &data) { custom_data = data; }
       uint64_t getkey() const { return calc_key(register_type, start_address, offset, bitmask); }
       size_t virtual get_register_size() const {
         if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT)
    @@ -266,10 +259,22 @@ struct SensorItem {
         else
           return register_count * 2;
       }
    +
    +  ModbusRegisterType register_type;
    +  SensorValueType sensor_value_type;
    +  uint16_t start_address;
    +  uint32_t bitmask;
    +  uint8_t offset;
    +  uint8_t register_count;
    +  uint8_t skip_updates;
    +  std::vector custom_data{};
    +  bool force_new_range{false};
     };
     
    -struct ModbusCommandItem {
    +class ModbusCommandItem {
    + public:
       static const size_t MAX_PAYLOAD_BYTES = 240;
    +  static const uint8_t MAX_SEND_REPEATS = 5;
       ModbusController *modbusdevice;
       uint16_t register_address;
       uint16_t register_count;
    @@ -279,7 +284,9 @@ struct ModbusCommandItem {
           on_data_func;
       std::vector payload = {};
       bool send();
    -
    +  // wrong commands (esp. custom commands) can block the send queue
    +  // limit the number of repeats
    +  uint8_t send_countdown{MAX_SEND_REPEATS};
       /// factory methods
       /** Create modbus read command
        *  Function code 02-04
    @@ -392,6 +399,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
      protected:
       /// parse sensormap_ and create range of sequential addresses
       size_t create_register_ranges_();
    +  // find register in sensormap. Returns iterator with all registers having the same start address
    +  std::map::iterator find_register_(ModbusRegisterType register_type, uint16_t start_address);
       /// submit the read command for the address range to the send queue
       void update_range_(RegisterRange &r);
       /// parse incoming modbus data
    diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py
    index 4de0ffbcea..3c5db9b9c8 100644
    --- a/esphome/components/modbus_controller/number/__init__.py
    +++ b/esphome/components/modbus_controller/number/__init__.py
    @@ -4,29 +4,26 @@ from esphome.components import number
     from esphome.const import (
         CONF_ADDRESS,
         CONF_ID,
    -    CONF_LAMBDA,
         CONF_MAX_VALUE,
         CONF_MIN_VALUE,
         CONF_MULTIPLY,
    -    CONF_OFFSET,
         CONF_STEP,
     )
     
     from .. import (
    +    add_modbus_base_properties,
         modbus_controller_ns,
    -    ModbusController,
    -    SENSOR_VALUE_TYPE,
    +    modbus_calc_properties,
    +    ModbusItemBaseSchema,
         SensorItem,
    -    TYPE_REGISTER_MAP,
    +    SENSOR_VALUE_TYPE,
     )
     
    -
     from ..const import (
         CONF_BITMASK,
    -    CONF_BYTE_OFFSET,
    +    CONF_CUSTOM_COMMAND,
         CONF_FORCE_NEW_RANGE,
         CONF_MODBUS_CONTROLLER_ID,
    -    CONF_REGISTER_COUNT,
         CONF_SKIP_UPDATES,
         CONF_VALUE_TYPE,
         CONF_WRITE_LAMBDA,
    @@ -51,22 +48,21 @@ def validate_min_max(config):
         return config
     
     
    +def validate_modbus_number(config):
    +    if CONF_CUSTOM_COMMAND not in config and CONF_ADDRESS not in config:
    +        raise cv.Invalid(
    +            f" {CONF_ADDRESS} is a required property if '{CONF_CUSTOM_COMMAND}:' isn't used"
    +        )
    +    return config
    +
    +
     CONFIG_SCHEMA = cv.All(
    -    number.NUMBER_SCHEMA.extend(
    +    number.NUMBER_SCHEMA.extend(ModbusItemBaseSchema)
    +    .extend(
             {
                 cv.GenerateID(): cv.declare_id(ModbusNumber),
    -            cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
    -            cv.Required(CONF_ADDRESS): cv.positive_int,
    -            cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
    -            cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
    -            cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t,
                 cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
    -            cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
    -            cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
    -            cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
    -            cv.Optional(CONF_LAMBDA): cv.returning_lambda,
                 cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
    -            cv.GenerateID(): cv.declare_id(ModbusNumber),
                 # 24 bits are the maximum value for fp32 before precison is lost
                 # 0x00FFFFFF = 16777215
                 cv.Optional(CONF_MAX_VALUE, default=16777215.0): cv.float_,
    @@ -74,22 +70,15 @@ CONFIG_SCHEMA = cv.All(
                 cv.Optional(CONF_STEP, default=1): cv.positive_float,
                 cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_,
             }
    -    ).extend(cv.polling_component_schema("60s")),
    +    )
    +    .extend(cv.polling_component_schema("60s")),
         validate_min_max,
    +    validate_modbus_number,
     )
     
     
     async def to_code(config):
    -    byte_offset = 0
    -    if CONF_OFFSET in config:
    -        byte_offset = config[CONF_OFFSET]
    -    # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
    -    if CONF_BYTE_OFFSET in config:
    -        byte_offset = config[CONF_BYTE_OFFSET]
    -    value_type = config[CONF_VALUE_TYPE]
    -    reg_count = config[CONF_REGISTER_COUNT]
    -    if reg_count == 0:
    -        reg_count = TYPE_REGISTER_MAP[value_type]
    +    byte_offset, reg_count = modbus_calc_properties(config)
         var = cg.new_Pvariable(
             config[CONF_ID],
             config[CONF_ADDRESS],
    @@ -115,20 +104,7 @@ async def to_code(config):
     
         cg.add(var.set_parent(parent))
         cg.add(parent.add_sensor_item(var))
    -    if CONF_LAMBDA in config:
    -        template_ = await cg.process_lambda(
    -            config[CONF_LAMBDA],
    -            [
    -                (ModbusNumber.operator("ptr"), "item"),
    -                (cg.float_, "x"),
    -                (
    -                    cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
    -                    "data",
    -                ),
    -            ],
    -            return_type=cg.optional.template(float),
    -        )
    -        cg.add(var.set_template(template_))
    +    await add_modbus_base_properties(var, config, ModbusNumber)
         if CONF_WRITE_LAMBDA in config:
             template_ = await cg.process_lambda(
                 config[CONF_WRITE_LAMBDA],
    diff --git a/esphome/components/modbus_controller/output/__init__.py b/esphome/components/modbus_controller/output/__init__.py
    index 4aca4db64f..eacd96579f 100644
    --- a/esphome/components/modbus_controller/output/__init__.py
    +++ b/esphome/components/modbus_controller/output/__init__.py
    @@ -6,24 +6,21 @@ from esphome.const import (
         CONF_ADDRESS,
         CONF_ID,
         CONF_MULTIPLY,
    -    CONF_OFFSET,
     )
     
     from .. import (
    -    SensorItem,
         modbus_controller_ns,
    -    ModbusController,
    -    TYPE_REGISTER_MAP,
    +    modbus_calc_properties,
    +    validate_modbus_register,
    +    ModbusItemBaseSchema,
    +    SensorItem,
     )
     
     from ..const import (
    -    CONF_BYTE_OFFSET,
         CONF_MODBUS_CONTROLLER_ID,
    -    CONF_REGISTER_COUNT,
         CONF_VALUE_TYPE,
         CONF_WRITE_LAMBDA,
     )
    -from ..sensor import SENSOR_VALUE_TYPE
     
     DEPENDENCIES = ["modbus_controller"]
     CODEOWNERS = ["@martgras"]
    @@ -34,38 +31,24 @@ ModbusOutput = modbus_controller_ns.class_(
     )
     
     CONFIG_SCHEMA = cv.All(
    -    output.FLOAT_OUTPUT_SCHEMA.extend(
    +    output.FLOAT_OUTPUT_SCHEMA.extend(ModbusItemBaseSchema).extend(
             {
    -            cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
                 cv.GenerateID(): cv.declare_id(ModbusOutput),
    -            cv.Required(CONF_ADDRESS): cv.positive_int,
    -            cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
    -            cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
    -            cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
    -            cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
                 cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
                 cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_,
             }
         ),
    +    validate_modbus_register,
     )
     
     
     async def to_code(config):
    -    byte_offset = 0
    -    if CONF_OFFSET in config:
    -        byte_offset = config[CONF_OFFSET]
    -    # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
    -    if CONF_BYTE_OFFSET in config:
    -        byte_offset = config[CONF_BYTE_OFFSET]
    -    value_type = config[CONF_VALUE_TYPE]
    -    reg_count = config[CONF_REGISTER_COUNT]
    -    if reg_count == 0:
    -        reg_count = TYPE_REGISTER_MAP[value_type]
    +    byte_offset, reg_count = modbus_calc_properties(config)
         var = cg.new_Pvariable(
             config[CONF_ID],
             config[CONF_ADDRESS],
             byte_offset,
    -        value_type,
    +        config[CONF_VALUE_TYPE],
             reg_count,
         )
         await output.register_output(var, config)
    diff --git a/esphome/components/modbus_controller/sensor/__init__.py b/esphome/components/modbus_controller/sensor/__init__.py
    index 82acfe120b..da7b8928b4 100644
    --- a/esphome/components/modbus_controller/sensor/__init__.py
    +++ b/esphome/components/modbus_controller/sensor/__init__.py
    @@ -2,18 +2,19 @@ from esphome.components import sensor
     import esphome.config_validation as cv
     import esphome.codegen as cg
     
    -from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET
    +from esphome.const import CONF_ID, CONF_ADDRESS
     from .. import (
    -    SensorItem,
    +    add_modbus_base_properties,
         modbus_controller_ns,
    -    ModbusController,
    +    modbus_calc_properties,
    +    validate_modbus_register,
    +    ModbusItemBaseSchema,
    +    SensorItem,
         MODBUS_REGISTER_TYPE,
         SENSOR_VALUE_TYPE,
    -    TYPE_REGISTER_MAP,
     )
     from ..const import (
         CONF_BITMASK,
    -    CONF_BYTE_OFFSET,
         CONF_FORCE_NEW_RANGE,
         CONF_MODBUS_CONTROLLER_ID,
         CONF_REGISTER_COUNT,
    @@ -31,43 +32,30 @@ ModbusSensor = modbus_controller_ns.class_(
     )
     
     CONFIG_SCHEMA = cv.All(
    -    sensor.SENSOR_SCHEMA.extend(
    +    sensor.SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA)
    +    .extend(ModbusItemBaseSchema)
    +    .extend(
             {
                 cv.GenerateID(): cv.declare_id(ModbusSensor),
    -            cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
    -            cv.Required(CONF_ADDRESS): cv.positive_int,
    -            cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
    -            cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
    -            cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
    -            cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t,
    +            cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
                 cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
                 cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
    -            cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
    -            cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
    -            cv.Optional(CONF_LAMBDA): cv.returning_lambda,
             }
    -    ).extend(cv.COMPONENT_SCHEMA),
    +    ),
    +    validate_modbus_register,
     )
     
     
     async def to_code(config):
    -    byte_offset = 0
    -    if CONF_OFFSET in config:
    -        byte_offset = config[CONF_OFFSET]
    -    # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
    -    if CONF_BYTE_OFFSET in config:
    -        byte_offset = config[CONF_BYTE_OFFSET]
    +    byte_offset, reg_count = modbus_calc_properties(config)
         value_type = config[CONF_VALUE_TYPE]
    -    reg_count = config[CONF_REGISTER_COUNT]
    -    if reg_count == 0:
    -        reg_count = TYPE_REGISTER_MAP[value_type]
         var = cg.new_Pvariable(
             config[CONF_ID],
             config[CONF_REGISTER_TYPE],
             config[CONF_ADDRESS],
             byte_offset,
             config[CONF_BITMASK],
    -        config[CONF_VALUE_TYPE],
    +        value_type,
             reg_count,
             config[CONF_SKIP_UPDATES],
             config[CONF_FORCE_NEW_RANGE],
    @@ -77,17 +65,4 @@ async def to_code(config):
     
         paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
         cg.add(paren.add_sensor_item(var))
    -    if CONF_LAMBDA in config:
    -        template_ = await cg.process_lambda(
    -            config[CONF_LAMBDA],
    -            [
    -                (ModbusSensor.operator("ptr"), "item"),
    -                (cg.float_, "x"),
    -                (
    -                    cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
    -                    "data",
    -                ),
    -            ],
    -            return_type=cg.optional.template(float),
    -        )
    -        cg.add(var.set_template(template_))
    +    await add_modbus_base_properties(var, config, ModbusSensor)
    diff --git a/esphome/components/modbus_controller/sensor/modbus_sensor.h b/esphome/components/modbus_controller/sensor/modbus_sensor.h
    index 4f48c2a4dd..37ea9d0dd0 100644
    --- a/esphome/components/modbus_controller/sensor/modbus_sensor.h
    +++ b/esphome/components/modbus_controller/sensor/modbus_sensor.h
    @@ -25,6 +25,7 @@ class ModbusSensor : public Component, public sensor::Sensor, public SensorItem
       void parse_and_publish(const std::vector &data) override;
       void dump_config() override;
       using transform_func_t = std::function(ModbusSensor *, float, const std::vector &)>;
    +
       void set_template(transform_func_t &&f) { this->transform_func_ = f; }
     
      protected:
    diff --git a/esphome/components/modbus_controller/switch/__init__.py b/esphome/components/modbus_controller/switch/__init__.py
    index e03b0d37be..df11b268ac 100644
    --- a/esphome/components/modbus_controller/switch/__init__.py
    +++ b/esphome/components/modbus_controller/switch/__init__.py
    @@ -3,21 +3,25 @@ import esphome.config_validation as cv
     import esphome.codegen as cg
     
     
    -from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET
    +from esphome.const import CONF_ID, CONF_ADDRESS
     from .. import (
    -    MODBUS_REGISTER_TYPE,
    -    SensorItem,
    +    add_modbus_base_properties,
         modbus_controller_ns,
    -    ModbusController,
    +    modbus_calc_properties,
    +    validate_modbus_register,
    +    ModbusItemBaseSchema,
    +    SensorItem,
    +    MODBUS_REGISTER_TYPE,
     )
     from ..const import (
         CONF_BITMASK,
    -    CONF_BYTE_OFFSET,
         CONF_FORCE_NEW_RANGE,
         CONF_MODBUS_CONTROLLER_ID,
         CONF_REGISTER_TYPE,
    +    CONF_WRITE_LAMBDA,
     )
     
    +CONF_USE_WRITE_MULTIPLE = "use_write_multiple"
     DEPENDENCIES = ["modbus_controller"]
     CODEOWNERS = ["@martgras"]
     
    @@ -26,31 +30,23 @@ ModbusSwitch = modbus_controller_ns.class_(
         "ModbusSwitch", cg.Component, switch.Switch, SensorItem
     )
     
    -
     CONFIG_SCHEMA = cv.All(
    -    switch.SWITCH_SCHEMA.extend(
    +    switch.SWITCH_SCHEMA.extend(cv.COMPONENT_SCHEMA)
    +    .extend(ModbusItemBaseSchema)
    +    .extend(
             {
                 cv.GenerateID(): cv.declare_id(ModbusSwitch),
    -            cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
    -            cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
    -            cv.Required(CONF_ADDRESS): cv.positive_int,
    -            cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
    -            cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
    -            cv.Optional(CONF_BITMASK, default=0x1): cv.hex_uint32_t,
    -            cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
    -            cv.Optional(CONF_LAMBDA): cv.returning_lambda,
    +            cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
    +            cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean,
    +            cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
             }
    -    ).extend(cv.COMPONENT_SCHEMA),
    +    ),
    +    validate_modbus_register,
     )
     
     
     async def to_code(config):
    -    byte_offset = 0
    -    if CONF_OFFSET in config:
    -        byte_offset = config[CONF_OFFSET]
    -    # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
    -    if CONF_BYTE_OFFSET in config:
    -        byte_offset = config[CONF_BYTE_OFFSET]
    +    byte_offset, _ = modbus_calc_properties(config)
         var = cg.new_Pvariable(
             config[CONF_ID],
             config[CONF_REGISTER_TYPE],
    @@ -63,19 +59,18 @@ async def to_code(config):
         await switch.register_switch(var, config)
     
         paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
    -    cg.add(paren.add_sensor_item(var))
         cg.add(var.set_parent(paren))
    -    if CONF_LAMBDA in config:
    -        publish_template_ = await cg.process_lambda(
    -            config[CONF_LAMBDA],
    +    cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE]))
    +    cg.add(paren.add_sensor_item(var))
    +    if CONF_WRITE_LAMBDA in config:
    +        template_ = await cg.process_lambda(
    +            config[CONF_WRITE_LAMBDA],
                 [
                     (ModbusSwitch.operator("ptr"), "item"),
    -                (bool, "x"),
    -                (
    -                    cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
    -                    "data",
    -                ),
    +                (cg.bool_, "x"),
    +                (cg.std_vector.template(cg.uint8).operator("ref"), "payload"),
                 ],
                 return_type=cg.optional.template(bool),
             )
    -        cg.add(var.set_template(publish_template_))
    +        cg.add(var.set_write_template(template_))
    +    await add_modbus_base_properties(var, config, ModbusSwitch, bool, bool)
    diff --git a/esphome/components/modbus_controller/switch/modbus_switch.cpp b/esphome/components/modbus_controller/switch/modbus_switch.cpp
    index ce9557e6c4..c7c3c419d4 100644
    --- a/esphome/components/modbus_controller/switch/modbus_switch.cpp
    +++ b/esphome/components/modbus_controller/switch/modbus_switch.cpp
    @@ -45,22 +45,50 @@ void ModbusSwitch::parse_and_publish(const std::vector &data) {
     void ModbusSwitch::write_state(bool state) {
       // This will be called every time the user requests a state change.
       ModbusCommandItem cmd;
    -  ESP_LOGV(TAG, "write_state '%s': new value = %s type = %d address = %X offset = %x", this->get_name().c_str(),
    -           ONOFF(state), (int) this->register_type, this->start_address, this->offset);
    -  switch (this->register_type) {
    -    case ModbusRegisterType::COIL:
    +  std::vector data;
    +  // Is there are lambda configured?
    +  if (this->write_transform_func_.has_value()) {
    +    // data is passed by reference
    +    // the lambda can fill the empty vector directly
    +    // in that case the return value is ignored
    +    auto val = (*this->write_transform_func_)(this, state, data);
    +    if (val.has_value()) {
    +      ESP_LOGV(TAG, "Value overwritten by lambda");
    +      state = val.value();
    +    } else {
    +      ESP_LOGV(TAG, "Communication handled by lambda - exiting control");
    +      return;
    +    }
    +  }
    +  if (!data.empty()) {
    +    ESP_LOGV(TAG, "Modbus Switch write raw: %s", hexencode(data).c_str());
    +    cmd = ModbusCommandItem::create_custom_command(
    +        this->parent_, data,
    +        [this, cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) {
    +          this->parent_->on_write_register_response(cmd.register_type, this->start_address, data);
    +        });
    +  } else {
    +    ESP_LOGV(TAG, "write_state '%s': new value = %s type = %d address = %X offset = %x", this->get_name().c_str(),
    +             ONOFF(state), (int) this->register_type, this->start_address, this->offset);
    +    if (this->register_type == ModbusRegisterType::COIL) {
           // offset for coil and discrete inputs is the coil/register number not bytes
    -      cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state);
    -      break;
    -    case ModbusRegisterType::DISCRETE_INPUT:
    -      cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, state);
    -      break;
    -
    -    default:
    +      if (this->use_write_multiple_) {
    +        std::vector states{state};
    +        cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states);
    +      } else {
    +        cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state);
    +      }
    +    } else {
           // since offset is in bytes and a register is 16 bits we get the start by adding offset/2
    -      cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2,
    -                                                           state ? 0xFFFF & this->bitmask : 0);
    -      break;
    +      if (this->use_write_multiple_) {
    +        std::vector bool_states(1, state ? (0xFFFF & this->bitmask) : 0);
    +        cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, 1,
    +                                                               bool_states);
    +      } else {
    +        cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2,
    +                                                             state ? 0xFFFF & this->bitmask : 0u);
    +      }
    +    }
       }
       this->parent_->queue_command(cmd);
       publish_state(state);
    diff --git a/esphome/components/modbus_controller/switch/modbus_switch.h b/esphome/components/modbus_controller/switch/modbus_switch.h
    index a38668fabb..5ac2af01a1 100644
    --- a/esphome/components/modbus_controller/switch/modbus_switch.h
    +++ b/esphome/components/modbus_controller/switch/modbus_switch.h
    @@ -33,11 +33,16 @@ class ModbusSwitch : public Component, public switch_::Switch, public SensorItem
       void set_parent(ModbusController *parent) { this->parent_ = parent; }
     
       using transform_func_t = std::function(ModbusSwitch *, bool, const std::vector &)>;
    +  using write_transform_func_t = std::function(ModbusSwitch *, bool, std::vector &)>;
       void set_template(transform_func_t &&f) { this->publish_transform_func_ = f; }
    +  void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
    +  void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
     
      protected:
       ModbusController *parent_;
    +  bool use_write_multiple_;
       optional publish_transform_func_{nullopt};
    +  optional write_transform_func_{nullopt};
     };
     
     }  // namespace modbus_controller
    diff --git a/esphome/components/modbus_controller/text_sensor/__init__.py b/esphome/components/modbus_controller/text_sensor/__init__.py
    index 2c02c86795..5cc85af5bc 100644
    --- a/esphome/components/modbus_controller/text_sensor/__init__.py
    +++ b/esphome/components/modbus_controller/text_sensor/__init__.py
    @@ -3,15 +3,17 @@ import esphome.config_validation as cv
     import esphome.codegen as cg
     
     
    -from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET
    +from esphome.const import CONF_ADDRESS, CONF_ID
     from .. import (
    -    SensorItem,
    +    add_modbus_base_properties,
         modbus_controller_ns,
    -    ModbusController,
    +    modbus_calc_properties,
    +    validate_modbus_register,
    +    ModbusItemBaseSchema,
    +    SensorItem,
         MODBUS_REGISTER_TYPE,
     )
     from ..const import (
    -    CONF_BYTE_OFFSET,
         CONF_FORCE_NEW_RANGE,
         CONF_MODBUS_CONTROLLER_ID,
         CONF_REGISTER_COUNT,
    @@ -38,32 +40,23 @@ RAW_ENCODING = {
     }
     
     CONFIG_SCHEMA = cv.All(
    -    text_sensor.TEXT_SENSOR_SCHEMA.extend(
    +    text_sensor.TEXT_SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA)
    +    .extend(ModbusItemBaseSchema)
    +    .extend(
             {
                 cv.GenerateID(): cv.declare_id(ModbusTextSensor),
    -            cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
    -            cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
    -            cv.Required(CONF_ADDRESS): cv.positive_int,
    -            cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
    -            cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
    +            cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
                 cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
                 cv.Optional(CONF_RESPONSE_SIZE, default=2): cv.positive_int,
                 cv.Optional(CONF_RAW_ENCODE, default="NONE"): cv.enum(RAW_ENCODING),
    -            cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
    -            cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
    -            cv.Optional(CONF_LAMBDA): cv.returning_lambda,
             }
    -    ).extend(cv.COMPONENT_SCHEMA),
    +    ),
    +    validate_modbus_register,
     )
     
     
     async def to_code(config):
    -    byte_offset = 0
    -    if CONF_OFFSET in config:
    -        byte_offset = config[CONF_OFFSET]
    -    # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
    -    if CONF_BYTE_OFFSET in config:
    -        byte_offset = config[CONF_BYTE_OFFSET]
    +    byte_offset, reg_count = modbus_calc_properties(config)
         response_size = config[CONF_RESPONSE_SIZE]
         reg_count = config[CONF_REGISTER_COUNT]
         if reg_count == 0:
    @@ -85,17 +78,6 @@ async def to_code(config):
     
         paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
         cg.add(paren.add_sensor_item(var))
    -    if CONF_LAMBDA in config:
    -        template_ = await cg.process_lambda(
    -            config[CONF_LAMBDA],
    -            [
    -                (ModbusTextSensor.operator("ptr"), "item"),
    -                (cg.std_string.operator("const").operator("ref"), "x"),
    -                (
    -                    cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
    -                    "data",
    -                ),
    -            ],
    -            return_type=cg.optional.template(cg.std_string),
    -        )
    -        cg.add(var.set_template(template_))
    +    await add_modbus_base_properties(
    +        var, config, ModbusTextSensor, cg.std_string, cg.std_string
    +    )
    
    From 5946c379256a3b652eec9334f1401d5ceb96d955 Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Fri, 26 Nov 2021 09:16:39 +0100
    Subject: [PATCH 1755/1841] Fix usage of deprecated climate method in anova
     (#2801)
    
    ---
     esphome/components/anova/anova.h | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/esphome/components/anova/anova.h b/esphome/components/anova/anova.h
    index 2e6910f326..4f8f0d0ee2 100644
    --- a/esphome/components/anova/anova.h
    +++ b/esphome/components/anova/anova.h
    @@ -30,7 +30,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode
       climate::ClimateTraits traits() override {
         auto traits = climate::ClimateTraits();
         traits.set_supports_current_temperature(true);
    -    traits.set_supports_heat_mode(true);
    +    traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::ClimateMode::CLIMATE_MODE_HEAT});
         traits.set_visual_min_temperature(25.0);
         traits.set_visual_max_temperature(100.0);
         traits.set_visual_temperature_step(0.1);
    
    From 671d68bc2ceecb74756894cd634d1d3cf2653d6e Mon Sep 17 00:00:00 2001
    From: Maurice Makaay 
    Date: Fri, 26 Nov 2021 21:25:58 +0100
    Subject: [PATCH 1756/1841] Add missing nvs_flash_init() to ESP32 preferences
     code (#2805)
    
    Co-authored-by: Maurice Makaay 
    ---
     esphome/components/esp32/preferences.cpp | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp
    index 96b7e7809e..8c2b67a942 100644
    --- a/esphome/components/esp32/preferences.cpp
    +++ b/esphome/components/esp32/preferences.cpp
    @@ -76,6 +76,7 @@ class ESP32Preferences : public ESPPreferences {
       uint32_t current_offset = 0;
     
       void open() {
    +    nvs_flash_init();
         esp_err_t err = nvs_open("esphome", NVS_READWRITE, &nvs_handle);
         if (err == 0)
           return;
    
    From 7a564b222d8aa7a3ab1a29d10693eea0c85bf0b5 Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Sun, 28 Nov 2021 19:59:30 +0100
    Subject: [PATCH 1757/1841] Make clang-tidy suggest stdint.h int types (#2820)
    
    ---
     .clang-tidy | 2 ++
     1 file changed, 2 insertions(+)
    
    diff --git a/.clang-tidy b/.clang-tidy
    index 79276f81c3..1c7e65b762 100644
    --- a/.clang-tidy
    +++ b/.clang-tidy
    @@ -101,6 +101,8 @@ CheckOptions:
         value:           '10'
       - key:             google-readability-namespace-comments.SpacesBeforeComments
         value:           '2'
    +  - key:             google-runtime-int.TypeSuffix
    +    value:           '_t'
       - key:             modernize-loop-convert.MaxCopySize
         value:           '16'
       - key:             modernize-loop-convert.MinConfidence
    
    From 10a2a7e0fc8e957010a32a3842b51547134300eb Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Sun, 28 Nov 2021 20:00:29 +0100
    Subject: [PATCH 1758/1841] Fix parsing numbers in Anova (#2816)
    
    ---
     esphome/components/anova/anova_base.cpp | 6 +++---
     esphome/core/helpers.cpp                | 5 +++++
     esphome/core/helpers.h                  | 6 ++++++
     3 files changed, 14 insertions(+), 3 deletions(-)
    
    diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp
    index dcef75e483..cb877bef35 100644
    --- a/esphome/components/anova/anova_base.cpp
    +++ b/esphome/components/anova/anova_base.cpp
    @@ -104,21 +104,21 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
           break;
         }
         case READ_TARGET_TEMPERATURE: {
    -      this->target_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f);
    +      this->target_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f);
           if (this->fahrenheit_)
             this->target_temp_ = ftoc(this->target_temp_);
           this->has_target_temp_ = true;
           break;
         }
         case SET_TARGET_TEMPERATURE: {
    -      this->target_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f);
    +      this->target_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f);
           if (this->fahrenheit_)
             this->target_temp_ = ftoc(this->target_temp_);
           this->has_target_temp_ = true;
           break;
         }
         case READ_CURRENT_TEMPERATURE: {
    -      this->current_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f);
    +      this->current_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f);
           if (this->fahrenheit_)
             this->current_temp_ = ftoc(this->current_temp_);
           this->has_current_temp_ = true;
    diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp
    index 37d9cdc24b..60849fcae7 100644
    --- a/esphome/core/helpers.cpp
    +++ b/esphome/core/helpers.cpp
    @@ -448,6 +448,11 @@ IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); }
     std::string str_truncate(const std::string &str, size_t length) {
       return str.length() > length ? str.substr(0, length) : str;
     }
    +std::string str_until(const char *str, char ch) {
    +  char *pos = strchr(str, ch);
    +  return pos == nullptr ? std::string(str) : std::string(str, pos - str);
    +}
    +std::string str_until(const std::string &str, char ch) { return str.substr(0, str.find(ch)); }
     std::string str_snake_case(const std::string &str) {
       std::string result;
       result.resize(str.length());
    diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h
    index 1faf1ac3aa..63aa4123ae 100644
    --- a/esphome/core/helpers.h
    +++ b/esphome/core/helpers.h
    @@ -353,6 +353,12 @@ template::value, int> = 0> constexpr
     /// Truncate a string to a specific length.
     std::string str_truncate(const std::string &str, size_t length);
     
    +/// Extract the part of the string until either the first occurence of the specified character, or the end (requires str
    +/// to be null-terminated).
    +std::string str_until(const char *str, char ch);
    +/// Extract the part of the string until either the first occurence of the specified character, or the end.
    +std::string str_until(const std::string &str, char ch);
    +
     /// Convert the string to snake case (lowercase with underscores).
     std::string str_snake_case(const std::string &str);
     
    
    From 2b504068569f599b0297536f7c7e287368c83f8d Mon Sep 17 00:00:00 2001
    From: Oxan van Leeuwen 
    Date: Sun, 28 Nov 2021 20:02:10 +0100
    Subject: [PATCH 1759/1841] Fix parsing of multiple values in EZO sensor
     (#2814)
    
    Co-authored-by: Lydia Sevelt 
    ---
     esphome/components/ezo/ezo.cpp | 5 +++++
     1 file changed, 5 insertions(+)
    
    diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp
    index 426f2807c1..ca6f121dbb 100644
    --- a/esphome/components/ezo/ezo.cpp
    +++ b/esphome/components/ezo/ezo.cpp
    @@ -74,6 +74,11 @@ void EZOSensor::loop() {
       if (buf[0] != 1)
         return;
     
    +  // some sensors return multiple comma-separated values, terminate string after first one
    +  for (int i = 1; i < sizeof(buf) - 1; i++)
    +    if (buf[i] == ',')
    +      buf[i] = '\0';
    +
       float val = parse_number((char *) &buf[1], sizeof(buf) - 2).value_or(0);
       this->publish_state(val);
     }
    
    From 7a5c3aa7edbac0e448977710ec958c5302eb840c Mon Sep 17 00:00:00 2001
    From: Carlos Garcia Saura 
    Date: Sun, 28 Nov 2021 20:06:53 +0100
    Subject: [PATCH 1760/1841] Fix compilation error for WPA enterprise in ESP-IDF
     (#2815)
    
    ---
     esphome/components/wifi/wifi_component_esp_idf.cpp | 3 +--
     1 file changed, 1 insertion(+), 2 deletions(-)
    
    diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp
    index 7f71b7078c..1d346c0a8e 100644
    --- a/esphome/components/wifi/wifi_component_esp_idf.cpp
    +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp
    @@ -375,8 +375,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
             ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_password failed! %d", err);
           }
         }
    -    esp_wpa2_config_t wpa2_config = WPA2_CONFIG_INIT_DEFAULT();
    -    err = esp_wifi_sta_wpa2_ent_enable(&wpa2_config);
    +    err = esp_wifi_sta_wpa2_ent_enable();
         if (err != ESP_OK) {
           ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_enable failed! %d", err);
         }
    
    From 10f830c3efcddeb0a40348f100db1ef0344b4971 Mon Sep 17 00:00:00 2001
    From: Dave T <17680170+davet2001@users.noreply.github.com>
    Date: Sun, 28 Nov 2021 19:12:40 +0000
    Subject: [PATCH 1761/1841] Correct bitmask for third color (blue) scaling.
     (#2817)
    
    ---
     esphome/components/display/display_color_utils.h | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/esphome/components/display/display_color_utils.h b/esphome/components/display/display_color_utils.h
    index 8fc3b0adb9..202de912de 100644
    --- a/esphome/components/display/display_color_utils.h
    +++ b/esphome/components/display/display_color_utils.h
    @@ -42,7 +42,7 @@ class ColorUtil {
                            ? esp_scale(((colorcode >> third_bits) & ((1 << second_bits) - 1)), ((1 << second_bits) - 1))
                            : esp_scale(((colorcode >> 8) & 0xFF), ((1 << second_bits) - 1));
     
    -    third_color = (right_bit_aligned ? esp_scale(((colorcode >> 0) & 0xFF), ((1 << third_bits) - 1))
    +    third_color = (right_bit_aligned ? esp_scale(((colorcode >> 0) & ((1 << third_bits) - 1)), ((1 << third_bits) - 1))
                                          : esp_scale(((colorcode >> 0) & 0xFF), (1 << third_bits) - 1));
     
         Color color_return;
    
    From 7afcb0fb043945b1172cfdb45d33657d923100cf Mon Sep 17 00:00:00 2001
    From: Conclusio 
    Date: Sun, 28 Nov 2021 20:13:42 +0100
    Subject: [PATCH 1762/1841] Add delay to improve stability (#2793)
    
    ---
     esphome/components/scd30/scd30.cpp | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp
    index e6d6ec1c1a..272ee75e30 100644
    --- a/esphome/components/scd30/scd30.cpp
    +++ b/esphome/components/scd30/scd30.cpp
    @@ -200,6 +200,7 @@ bool SCD30Component::is_data_ready_() {
       if (!this->write_command_(SCD30_CMD_GET_DATA_READY_STATUS)) {
         return false;
       }
    +  delay(4);
       uint16_t is_data_ready;
       if (!this->read_data_(&is_data_ready, 1)) {
         return false;
    
    From cae283dc8678789ae70b11fdab7ee9fca19964c8 Mon Sep 17 00:00:00 2001
    From: anatoly-savchenkov
     <48646998+anatoly-savchenkov@users.noreply.github.com>
    Date: Sun, 28 Nov 2021 22:31:15 +0300
    Subject: [PATCH 1763/1841] Fixed data type inside fast_random_8() routine
     (#2818)
    
    ---
     esphome/core/helpers.cpp | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp
    index 60849fcae7..6678eddbff 100644
    --- a/esphome/core/helpers.cpp
    +++ b/esphome/core/helpers.cpp
    @@ -98,7 +98,7 @@ uint16_t fast_random_16() {
       return (rand32 & 0xFFFF) + (rand32 >> 16);
     }
     uint8_t fast_random_8() {
    -  uint8_t rand32 = fast_random_32();
    +  uint32_t rand32 = fast_random_32();
       return (rand32 & 0xFF) + ((rand32 >> 8) & 0xFF);
     }
     
    
    From adf48246a9b73bd3abe78f9e41d00ea6f3f0b202 Mon Sep 17 00:00:00 2001
    From: Maurice Makaay 
    Date: Mon, 29 Nov 2021 16:40:53 +0100
    Subject: [PATCH 1764/1841] Improve DSMR read timeout handling (#2699)
    
    ---
     esphome/components/dsmr/__init__.py       |  14 +-
     esphome/components/dsmr/dsmr.cpp          | 181 ++++++++++++----------
     esphome/components/dsmr/dsmr.h            |  30 ++--
     esphome/components/uart/uart_component.h  |   1 +
     esphome/components/uart/uart_debugger.cpp |   9 ++
     tests/test3.yaml                          |   1 +
     6 files changed, 136 insertions(+), 100 deletions(-)
    
    diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py
    index 06b022c513..7a7681082e 100644
    --- a/esphome/components/dsmr/__init__.py
    +++ b/esphome/components/dsmr/__init__.py
    @@ -5,6 +5,7 @@ from esphome.components import uart
     from esphome.const import (
         CONF_ID,
         CONF_UART_ID,
    +    CONF_RECEIVE_TIMEOUT,
     )
     
     CODEOWNERS = ["@glmnet", "@zuidwijk"]
    @@ -52,7 +53,12 @@ CONFIG_SCHEMA = cv.All(
                 cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_,
                 cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_,
                 cv.Optional(CONF_REQUEST_PIN): pins.gpio_output_pin_schema,
    -            cv.Optional(CONF_REQUEST_INTERVAL): cv.positive_time_period_milliseconds,
    +            cv.Optional(
    +                CONF_REQUEST_INTERVAL, default="0ms"
    +            ): cv.positive_time_period_milliseconds,
    +            cv.Optional(
    +                CONF_RECEIVE_TIMEOUT, default="200ms"
    +            ): cv.positive_time_period_milliseconds,
             }
         ).extend(uart.UART_DEVICE_SCHEMA),
         cv.only_with_arduino,
    @@ -70,10 +76,8 @@ async def to_code(config):
         if CONF_REQUEST_PIN in config:
             request_pin = await cg.gpio_pin_expression(config[CONF_REQUEST_PIN])
             cg.add(var.set_request_pin(request_pin))
    -    if CONF_REQUEST_INTERVAL in config:
    -        cg.add(
    -            var.set_request_interval(config[CONF_REQUEST_INTERVAL].total_milliseconds)
    -        )
    +    cg.add(var.set_request_interval(config[CONF_REQUEST_INTERVAL].total_milliseconds))
    +    cg.add(var.set_receive_timeout(config[CONF_RECEIVE_TIMEOUT].total_milliseconds))
     
         cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID])
     
    diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp
    index 03e418662a..7b339e5fe0 100644
    --- a/esphome/components/dsmr/dsmr.cpp
    +++ b/esphome/components/dsmr/dsmr.cpp
    @@ -24,7 +24,7 @@ void Dsmr::loop() {
         if (this->decryption_key_.empty()) {
           this->receive_telegram_();
         } else {
    -      this->receive_encrypted_();
    +      this->receive_encrypted_telegram_();
         }
       }
     }
    @@ -57,14 +57,42 @@ bool Dsmr::request_interval_reached_() {
       return millis() - this->last_request_time_ > this->request_interval_;
     }
     
    +bool Dsmr::receive_timeout_reached_() { return millis() - this->last_read_time_ > this->receive_timeout_; }
    +
     bool Dsmr::available_within_timeout_() {
    -  uint8_t tries = READ_TIMEOUT_MS / 5;
    -  while (tries--) {
    -    delay(5);
    -    if (this->available()) {
    -      return true;
    +  // Data are available for reading on the UART bus?
    +  // Then we can start reading right away.
    +  if (this->available()) {
    +    this->last_read_time_ = millis();
    +    return true;
    +  }
    +  // When we're not in the process of reading a telegram, then there is
    +  // no need to actively wait for new data to come in.
    +  if (!header_found_) {
    +    return false;
    +  }
    +  // A telegram is being read. The smart meter might not deliver a telegram
    +  // in one go, but instead send it in chunks with small pauses in between.
    +  // When the UART RX buffer cannot hold a full telegram, then make sure
    +  // that the UART read buffer does not overflow while other components
    +  // perform their work in their loop. Do this by not returning control to
    +  // the main loop, until the read timeout is reached.
    +  if (this->parent_->get_rx_buffer_size() < this->max_telegram_len_) {
    +    while (!this->receive_timeout_reached_()) {
    +      delay(5);
    +      if (this->available()) {
    +        this->last_read_time_ = millis();
    +        return true;
    +      }
         }
       }
    +  // No new data has come in during the read timeout? Then stop reading the
    +  // telegram and start waiting for the next one to arrive.
    +  if (this->receive_timeout_reached_()) {
    +    ESP_LOGW(TAG, "Timeout while reading data for telegram");
    +    this->reset_telegram_();
    +  }
    +
       return false;
     }
     
    @@ -96,30 +124,31 @@ void Dsmr::stop_requesting_data_() {
       }
     }
     
    -void Dsmr::receive_telegram_() {
    -  while (true) {
    -    if (!this->available()) {
    -      if (!this->header_found_ || !this->available_within_timeout_()) {
    -        return;
    -      }
    -    }
    +void Dsmr::reset_telegram_() {
    +  this->header_found_ = false;
    +  this->footer_found_ = false;
    +  this->bytes_read_ = 0;
    +  this->crypt_bytes_read_ = 0;
    +  this->crypt_telegram_len_ = 0;
    +  this->last_read_time_ = 0;
    +}
     
    +void Dsmr::receive_telegram_() {
    +  while (this->available_within_timeout_()) {
         const char c = this->read();
     
         // Find a new telegram header, i.e. forward slash.
         if (c == '/') {
           ESP_LOGV(TAG, "Header of telegram found");
    +      this->reset_telegram_();
           this->header_found_ = true;
    -      this->footer_found_ = false;
    -      this->telegram_len_ = 0;
         }
         if (!this->header_found_)
           continue;
     
         // Check for buffer overflow.
    -    if (this->telegram_len_ >= this->max_telegram_len_) {
    -      this->header_found_ = false;
    -      this->footer_found_ = false;
    +    if (this->bytes_read_ >= this->max_telegram_len_) {
    +      this->reset_telegram_();
           ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_);
           return;
         }
    @@ -129,9 +158,9 @@ void Dsmr::receive_telegram_() {
         // proper parsing, remove these new line characters.
         if (c == '(') {
           while (true) {
    -        auto previous_char = this->telegram_[this->telegram_len_ - 1];
    +        auto previous_char = this->telegram_[this->bytes_read_ - 1];
             if (previous_char == '\n' || previous_char == '\r') {
    -          this->telegram_len_--;
    +          this->bytes_read_--;
             } else {
               break;
             }
    @@ -139,8 +168,8 @@ void Dsmr::receive_telegram_() {
         }
     
         // Store the byte in the buffer.
    -    this->telegram_[this->telegram_len_] = c;
    -    this->telegram_len_++;
    +    this->telegram_[this->bytes_read_] = c;
    +    this->bytes_read_++;
     
         // Check for a footer, i.e. exlamation mark, followed by a hex checksum.
         if (c == '!') {
    @@ -152,28 +181,14 @@ void Dsmr::receive_telegram_() {
         if (this->footer_found_ && c == '\n') {
           // Parse the telegram and publish sensor values.
           this->parse_telegram();
    -
    -      this->header_found_ = false;
    +      this->reset_telegram_();
           return;
         }
       }
     }
     
    -void Dsmr::receive_encrypted_() {
    -  this->encrypted_telegram_len_ = 0;
    -  size_t packet_size = 0;
    -
    -  while (true) {
    -    if (!this->available()) {
    -      if (!this->header_found_) {
    -        return;
    -      }
    -      if (!this->available_within_timeout_()) {
    -        ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram");
    -        return;
    -      }
    -    }
    -
    +void Dsmr::receive_encrypted_telegram_() {
    +  while (this->available_within_timeout_()) {
         const char c = this->read();
     
         // Find a new telegram start byte.
    @@ -182,50 +197,58 @@ void Dsmr::receive_encrypted_() {
             continue;
           }
           ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found");
    +      this->reset_telegram_();
           this->header_found_ = true;
         }
     
         // Check for buffer overflow.
    -    if (this->encrypted_telegram_len_ >= this->max_telegram_len_) {
    -      this->header_found_ = false;
    +    if (this->crypt_bytes_read_ >= this->max_telegram_len_) {
    +      this->reset_telegram_();
           ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_);
           return;
         }
     
    -    this->encrypted_telegram_[this->encrypted_telegram_len_++] = c;
    +    // Store the byte in the buffer.
    +    this->crypt_telegram_[this->crypt_bytes_read_] = c;
    +    this->crypt_bytes_read_++;
     
    -    if (packet_size == 0 && this->encrypted_telegram_len_ > 20) {
    +    // Read the length of the incoming encrypted telegram.
    +    if (this->crypt_telegram_len_ == 0 && this->crypt_bytes_read_ > 20) {
           // Complete header + data bytes
    -      packet_size = 13 + (this->encrypted_telegram_[11] << 8 | this->encrypted_telegram_[12]);
    -      ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size);
    +      this->crypt_telegram_len_ = 13 + (this->crypt_telegram_[11] << 8 | this->crypt_telegram_[12]);
    +      ESP_LOGV(TAG, "Encrypted telegram length: %d bytes", this->crypt_telegram_len_);
         }
    -    if (this->encrypted_telegram_len_ == packet_size && packet_size > 0) {
    -      ESP_LOGV(TAG, "End of encrypted telegram found");
    -      GCM *gcmaes128{new GCM()};
    -      gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
    -      // the iv is 8 bytes of the system title + 4 bytes frame counter
    -      // system title is at byte 2 and frame counter at byte 15
    -      for (int i = 10; i < 14; i++)
    -        this->encrypted_telegram_[i] = this->encrypted_telegram_[i + 4];
    -      constexpr uint16_t iv_size{12};
    -      gcmaes128->setIV(&this->encrypted_telegram_[2], iv_size);
    -      gcmaes128->decrypt(reinterpret_cast(this->telegram_),
    -                         // the ciphertext start at byte 18
    -                         &this->encrypted_telegram_[18],
    -                         // cipher size
    -                         this->encrypted_telegram_len_ - 17);
    -      delete gcmaes128;  // NOLINT(cppcoreguidelines-owning-memory)
     
    -      this->telegram_len_ = strnlen(this->telegram_, this->max_telegram_len_);
    -      ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->telegram_len_);
    -      ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
    -
    -      this->parse_telegram();
    -
    -      this->header_found_ = false;
    -      this->telegram_len_ = 0;
    -      return;
    +    // Check for the end of the encrypted telegram.
    +    if (this->crypt_telegram_len_ == 0 || this->crypt_bytes_read_ != this->crypt_telegram_len_) {
    +      continue;
         }
    +    ESP_LOGV(TAG, "End of encrypted telegram found");
    +
    +    // Decrypt the encrypted telegram.
    +    GCM *gcmaes128{new GCM()};
    +    gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
    +    // the iv is 8 bytes of the system title + 4 bytes frame counter
    +    // system title is at byte 2 and frame counter at byte 15
    +    for (int i = 10; i < 14; i++)
    +      this->crypt_telegram_[i] = this->crypt_telegram_[i + 4];
    +    constexpr uint16_t iv_size{12};
    +    gcmaes128->setIV(&this->crypt_telegram_[2], iv_size);
    +    gcmaes128->decrypt(reinterpret_cast(this->telegram_),
    +                       // the ciphertext start at byte 18
    +                       &this->crypt_telegram_[18],
    +                       // cipher size
    +                       this->crypt_bytes_read_ - 17);
    +    delete gcmaes128;  // NOLINT(cppcoreguidelines-owning-memory)
    +
    +    this->bytes_read_ = strnlen(this->telegram_, this->max_telegram_len_);
    +    ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->bytes_read_);
    +    ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
    +
    +    // Parse the decrypted telegram and publish sensor values.
    +    this->parse_telegram();
    +    this->reset_telegram_();
    +    return;
       }
     }
     
    @@ -234,11 +257,11 @@ bool Dsmr::parse_telegram() {
       ESP_LOGV(TAG, "Trying to parse telegram");
       this->stop_requesting_data_();
       ::dsmr::ParseResult res =
    -      ::dsmr::P1Parser::parse(&data, this->telegram_, this->telegram_len_, false,
    +      ::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, false,
                                   this->crc_check_);  // Parse telegram according to data definition. Ignore unknown values.
       if (res.err) {
         // Parsing error, show it
    -    auto err_str = res.fullError(this->telegram_, this->telegram_ + this->telegram_len_);
    +    auto err_str = res.fullError(this->telegram_, this->telegram_ + this->bytes_read_);
         ESP_LOGE(TAG, "%s", err_str.c_str());
         return false;
       } else {
    @@ -251,7 +274,7 @@ bool Dsmr::parse_telegram() {
     void Dsmr::dump_config() {
       ESP_LOGCONFIG(TAG, "DSMR:");
       ESP_LOGCONFIG(TAG, "  Max telegram length: %d", this->max_telegram_len_);
    -
    +  ESP_LOGCONFIG(TAG, "  Receive timeout: %.1fs", this->receive_timeout_ / 1e3f);
       if (this->request_pin_ != nullptr) {
         LOG_PIN("  Request Pin: ", this->request_pin_);
       }
    @@ -270,9 +293,9 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
       if (decryption_key.length() == 0) {
         ESP_LOGI(TAG, "Disabling decryption");
         this->decryption_key_.clear();
    -    if (this->encrypted_telegram_ != nullptr) {
    -      delete[] this->encrypted_telegram_;
    -      this->encrypted_telegram_ = nullptr;
    +    if (this->crypt_telegram_ != nullptr) {
    +      delete[] this->crypt_telegram_;
    +      this->crypt_telegram_ = nullptr;
         }
         return;
       }
    @@ -293,13 +316,11 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
         this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
       }
     
    -  if (this->encrypted_telegram_ == nullptr) {
    -    this->encrypted_telegram_ = new uint8_t[this->max_telegram_len_];  // NOLINT
    +  if (this->crypt_telegram_ == nullptr) {
    +    this->crypt_telegram_ = new uint8_t[this->max_telegram_len_];  // NOLINT
       }
     }
     
    -void Dsmr::set_max_telegram_length(size_t length) { max_telegram_len_ = length; }
    -
     }  // namespace dsmr
     }  // namespace esphome
     
    diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h
    index 0430eb93ed..db0bf95ca1 100644
    --- a/esphome/components/dsmr/dsmr.h
    +++ b/esphome/components/dsmr/dsmr.h
    @@ -16,8 +16,6 @@
     namespace esphome {
     namespace dsmr {
     
    -static constexpr uint32_t READ_TIMEOUT_MS = 200;
    -
     using namespace ::dsmr::fields;
     
     // DSMR_**_LIST generated by ESPHome and written in esphome/core/defines
    @@ -71,11 +69,10 @@ class Dsmr : public Component, public uart::UARTDevice {
       void dump_config() override;
     
       void set_decryption_key(const std::string &decryption_key);
    -
    -  void set_max_telegram_length(size_t length);
    -
    +  void set_max_telegram_length(size_t length) { this->max_telegram_len_ = length; }
       void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; }
       void set_request_interval(uint32_t interval) { this->request_interval_ = interval; }
    +  void set_receive_timeout(uint32_t timeout) { this->receive_timeout_ = timeout; }
     
     // Sensor setters
     #define DSMR_SET_SENSOR(s) \
    @@ -88,7 +85,8 @@ class Dsmr : public Component, public uart::UARTDevice {
     
      protected:
       void receive_telegram_();
    -  void receive_encrypted_();
    +  void receive_encrypted_telegram_();
    +  void reset_telegram_();
     
       /// Wait for UART data to become available within the read timeout.
       ///
    @@ -101,24 +99,26 @@ class Dsmr : public Component, public uart::UARTDevice {
       /// lost in the process.
       bool available_within_timeout_();
     
    -  // Data request
    +  // Request telegram
    +  uint32_t request_interval_;
    +  bool request_interval_reached_();
       GPIOPin *request_pin_{nullptr};
    -  uint32_t request_interval_{0};
       uint32_t last_request_time_{0};
       bool requesting_data_{false};
       bool ready_to_request_data_();
    -  bool request_interval_reached_();
       void start_requesting_data_();
       void stop_requesting_data_();
     
    -  // Telegram buffer
    +  // Read telegram
    +  uint32_t receive_timeout_;
    +  bool receive_timeout_reached_();
       size_t max_telegram_len_;
       char *telegram_{nullptr};
    -  int telegram_len_{0};
    -  uint8_t *encrypted_telegram_{nullptr};
    -  int encrypted_telegram_len_{0};
    -
    -  // Serial parser
    +  int bytes_read_{0};
    +  uint8_t *crypt_telegram_{nullptr};
    +  size_t crypt_telegram_len_{0};
    +  int crypt_bytes_read_{0};
    +  uint32_t last_read_time_{0};
       bool header_found_{false};
       bool footer_found_{false};
     
    diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h
    index 73694d3db7..42702cf5b8 100644
    --- a/esphome/components/uart/uart_component.h
    +++ b/esphome/components/uart/uart_component.h
    @@ -52,6 +52,7 @@ class UARTComponent {
       void set_tx_pin(InternalGPIOPin *tx_pin) { this->tx_pin_ = tx_pin; }
       void set_rx_pin(InternalGPIOPin *rx_pin) { this->rx_pin_ = rx_pin; }
       void set_rx_buffer_size(size_t rx_buffer_size) { this->rx_buffer_size_ = rx_buffer_size; }
    +  size_t get_rx_buffer_size() { return this->rx_buffer_size_; }
     
       void set_stop_bits(uint8_t stop_bits) { this->stop_bits_ = stop_bits; }
       uint8_t get_stop_bits() const { return this->stop_bits_; }
    diff --git a/esphome/components/uart/uart_debugger.cpp b/esphome/components/uart/uart_debugger.cpp
    index 9a535656a2..e2d92eac60 100644
    --- a/esphome/components/uart/uart_debugger.cpp
    +++ b/esphome/components/uart/uart_debugger.cpp
    @@ -90,6 +90,11 @@ void UARTDummyReceiver::loop() {
       }
     }
     
    +// In the upcoming log functions, a delay was added after all log calls.
    +// This is done to allow the system to ship the log lines via the API
    +// TCP connection(s). Without these delays, debug log lines could go
    +// missing when UART devices block the main loop for too long.
    +
     void UARTDebug::log_hex(UARTDirection direction, std::vector bytes, uint8_t separator) {
       std::string res;
       if (direction == UART_DIRECTION_RX) {
    @@ -107,6 +112,7 @@ void UARTDebug::log_hex(UARTDirection direction, std::vector bytes, uin
         res += buf;
       }
       ESP_LOGD(TAG, "%s", res.c_str());
    +  delay(10);
     }
     
     void UARTDebug::log_string(UARTDirection direction, std::vector bytes) {
    @@ -150,6 +156,7 @@ void UARTDebug::log_string(UARTDirection direction, std::vector bytes)
       }
       res += '"';
       ESP_LOGD(TAG, "%s", res.c_str());
    +  delay(10);
     }
     
     void UARTDebug::log_int(UARTDirection direction, std::vector bytes, uint8_t separator) {
    @@ -167,6 +174,7 @@ void UARTDebug::log_int(UARTDirection direction, std::vector bytes, uin
         res += to_string(bytes[i]);
       }
       ESP_LOGD(TAG, "%s", res.c_str());
    +  delay(10);
     }
     
     void UARTDebug::log_binary(UARTDirection direction, std::vector bytes, uint8_t separator) {
    @@ -186,6 +194,7 @@ void UARTDebug::log_binary(UARTDirection direction, std::vector bytes,
         res += buf;
       }
       ESP_LOGD(TAG, "%s", res.c_str());
    +  delay(10);
     }
     
     }  // namespace uart
    diff --git a/tests/test3.yaml b/tests/test3.yaml
    index 61c7a3dcad..8ae4a383e0 100644
    --- a/tests/test3.yaml
    +++ b/tests/test3.yaml
    @@ -1313,6 +1313,7 @@ dsmr:
       max_telegram_length: 1000
       request_pin: D5
       request_interval: 20s
    +  receive_timeout: 100ms
     
     daly_bms:
       update_interval: 20s
    
    From 6f07421911bd1390cbf776956e3f4c4f45301f6b Mon Sep 17 00:00:00 2001
    From: mechanarchy <1166756+mechanarchy@users.noreply.github.com>
    Date: Tue, 30 Nov 2021 02:52:20 +1100
    Subject: [PATCH 1765/1841] Optionally show internal components on the web
     server (#2627)
    
    Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
    Co-authored-by: Oxan van Leeuwen 
    ---
     esphome/components/web_server/__init__.py    |   3 +
     esphome/components/web_server/web_server.cpp | 109 ++++++++-----------
     esphome/components/web_server/web_server.h   |   7 ++
     esphome/const.py                             |   1 +
     esphome/core/controller.cpp                  |  22 ++--
     esphome/core/controller.h                    |   2 +-
     tests/test4.yaml                             |   1 +
     7 files changed, 67 insertions(+), 78 deletions(-)
    
    diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py
    index dc652e0312..d9ff84d501 100644
    --- a/esphome/components/web_server/__init__.py
    +++ b/esphome/components/web_server/__init__.py
    @@ -12,6 +12,7 @@ from esphome.const import (
         CONF_AUTH,
         CONF_USERNAME,
         CONF_PASSWORD,
    +    CONF_INCLUDE_INTERNAL,
         CONF_OTA,
     )
     from esphome.core import CORE, coroutine_with_priority
    @@ -42,6 +43,7 @@ CONFIG_SCHEMA = cv.Schema(
             cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
                 web_server_base.WebServerBase
             ),
    +        cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean,
             cv.Optional(CONF_OTA, default=True): cv.boolean,
         }
     ).extend(cv.COMPONENT_SCHEMA)
    @@ -75,3 +77,4 @@ async def to_code(config):
             path = CORE.relative_config_path(config[CONF_JS_INCLUDE])
             with open(file=path, mode="r", encoding="utf-8") as myfile:
                 cg.add(var.set_js_include(myfile.read()))
    +    cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL]))
    diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp
    index 6f47f460af..3f74c2e8a1 100644
    --- a/esphome/components/web_server/web_server.cpp
    +++ b/esphome/components/web_server/web_server.cpp
    @@ -31,10 +31,10 @@ static const char *const TAG = "web_server";
     
     void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action,
                    const std::function &action_func = nullptr) {
    -  if (obj->is_internal())
    -    return;
       stream->print("print(klass.c_str());
    +  if (obj->is_internal())
    +    stream->print(" internal");
       stream->print("\" id=\"");
       stream->print(klass.c_str());
       stream->print("-");
    @@ -83,7 +83,7 @@ void WebServer::set_js_include(const char *js_include) { this->js_include_ = js_
     
     void WebServer::setup() {
       ESP_LOGCONFIG(TAG, "Setting up web server...");
    -  this->setup_controller();
    +  this->setup_controller(this->include_internal_);
       this->base_->init();
     
       this->events_.onConnect([this](AsyncEventSourceClient *client) {
    @@ -92,55 +92,55 @@ void WebServer::setup() {
     
     #ifdef USE_SENSOR
         for (auto *obj : App.get_sensors())
    -      if (!obj->is_internal())
    +      if (this->include_internal_ || !obj->is_internal())
             client->send(this->sensor_json(obj, obj->state).c_str(), "state");
     #endif
     
     #ifdef USE_SWITCH
         for (auto *obj : App.get_switches())
    -      if (!obj->is_internal())
    +      if (this->include_internal_ || !obj->is_internal())
             client->send(this->switch_json(obj, obj->state).c_str(), "state");
     #endif
     
     #ifdef USE_BINARY_SENSOR
         for (auto *obj : App.get_binary_sensors())
    -      if (!obj->is_internal())
    +      if (this->include_internal_ || !obj->is_internal())
             client->send(this->binary_sensor_json(obj, obj->state).c_str(), "state");
     #endif
     
     #ifdef USE_FAN
         for (auto *obj : App.get_fans())
    -      if (!obj->is_internal())
    +      if (this->include_internal_ || !obj->is_internal())
             client->send(this->fan_json(obj).c_str(), "state");
     #endif
     
     #ifdef USE_LIGHT
         for (auto *obj : App.get_lights())
    -      if (!obj->is_internal())
    +      if (this->include_internal_ || !obj->is_internal())
             client->send(this->light_json(obj).c_str(), "state");
     #endif
     
     #ifdef USE_TEXT_SENSOR
         for (auto *obj : App.get_text_sensors())
    -      if (!obj->is_internal())
    +      if (this->include_internal_ || !obj->is_internal())
             client->send(this->text_sensor_json(obj, obj->state).c_str(), "state");
     #endif
     
     #ifdef USE_COVER
         for (auto *obj : App.get_covers())
    -      if (!obj->is_internal())
    +      if (this->include_internal_ || !obj->is_internal())
             client->send(this->cover_json(obj).c_str(), "state");
     #endif
     
     #ifdef USE_NUMBER
         for (auto *obj : App.get_numbers())
    -      if (!obj->is_internal())
    +      if (this->include_internal_ || !obj->is_internal())
             client->send(this->number_json(obj, obj->state).c_str(), "state");
     #endif
     
     #ifdef USE_SELECT
         for (auto *obj : App.get_selects())
    -      if (!obj->is_internal())
    +      if (this->include_internal_ || !obj->is_internal())
             client->send(this->select_json(obj, obj->state).c_str(), "state");
     #endif
       });
    @@ -188,57 +188,66 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
     
     #ifdef USE_SENSOR
       for (auto *obj : App.get_sensors())
    -    write_row(stream, obj, "sensor", "");
    +    if (this->include_internal_ || !obj->is_internal())
    +      write_row(stream, obj, "sensor", "");
     #endif
     
     #ifdef USE_SWITCH
       for (auto *obj : App.get_switches())
    -    write_row(stream, obj, "switch", "");
    +    if (this->include_internal_ || !obj->is_internal())
    +      write_row(stream, obj, "switch", "");
     #endif
     
     #ifdef USE_BINARY_SENSOR
       for (auto *obj : App.get_binary_sensors())
    -    write_row(stream, obj, "binary_sensor", "");
    +    if (this->include_internal_ || !obj->is_internal())
    +      write_row(stream, obj, "binary_sensor", "");
     #endif
     
     #ifdef USE_FAN
       for (auto *obj : App.get_fans())
    -    write_row(stream, obj, "fan", "");
    +    if (this->include_internal_ || !obj->is_internal())
    +      write_row(stream, obj, "fan", "");
     #endif
     
     #ifdef USE_LIGHT
       for (auto *obj : App.get_lights())
    -    write_row(stream, obj, "light", "");
    +    if (this->include_internal_ || !obj->is_internal())
    +      write_row(stream, obj, "light", "");
     #endif
     
     #ifdef USE_TEXT_SENSOR
       for (auto *obj : App.get_text_sensors())
    -    write_row(stream, obj, "text_sensor", "");
    +    if (this->include_internal_ || !obj->is_internal())
    +      write_row(stream, obj, "text_sensor", "");
     #endif
     
     #ifdef USE_COVER
       for (auto *obj : App.get_covers())
    -    write_row(stream, obj, "cover", "");
    +    if (this->include_internal_ || !obj->is_internal())
    +      write_row(stream, obj, "cover", "");
     #endif
     
     #ifdef USE_NUMBER
       for (auto *obj : App.get_numbers())
    -    write_row(stream, obj, "number", "");
    +    if (this->include_internal_ || !obj->is_internal())
    +      write_row(stream, obj, "number", "");
     #endif
     
     #ifdef USE_SELECT
       for (auto *obj : App.get_selects())
    -    write_row(stream, obj, "select", "", [](AsyncResponseStream &stream, EntityBase *obj) {
    -      select::Select *select = (select::Select *) obj;
    -      stream.print("");
    -    });
    +    if (this->include_internal_ || !obj->is_internal())
    +      write_row(stream, obj, "select", "", [](AsyncResponseStream &stream, EntityBase *obj) {
    +        select::Select *select = (select::Select *) obj;
    +        stream.print("");
    +      });
     #endif
     
       stream->print(F("

    See ESPHome Web API for " @@ -293,8 +302,6 @@ void WebServer::on_sensor_update(sensor::Sensor *obj, float state) { } void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (sensor::Sensor *obj : App.get_sensors()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; std::string data = this->sensor_json(obj, obj->state); @@ -321,8 +328,6 @@ void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::s } void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (text_sensor::TextSensor *obj : App.get_text_sensors()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; std::string data = this->text_sensor_json(obj, obj->state); @@ -353,8 +358,6 @@ std::string WebServer::switch_json(switch_::Switch *obj, bool value) { } void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (switch_::Switch *obj : App.get_switches()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; @@ -381,8 +384,6 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM #ifdef USE_BINARY_SENSOR void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { - if (obj->is_internal()) - return; this->events_.send(this->binary_sensor_json(obj, state).c_str(), "state"); } std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value) { @@ -394,8 +395,6 @@ std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool } void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; std::string data = this->binary_sensor_json(obj, obj->state); @@ -407,11 +406,7 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con #endif #ifdef USE_FAN -void WebServer::on_fan_update(fan::FanState *obj) { - if (obj->is_internal()) - return; - this->events_.send(this->fan_json(obj).c_str(), "state"); -} +void WebServer::on_fan_update(fan::FanState *obj) { this->events_.send(this->fan_json(obj).c_str(), "state"); } std::string WebServer::fan_json(fan::FanState *obj) { return json::build_json([obj](JsonObject &root) { root["id"] = "fan-" + obj->get_object_id(); @@ -442,8 +437,6 @@ std::string WebServer::fan_json(fan::FanState *obj) { } void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (fan::FanState *obj : App.get_fans()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; @@ -504,15 +497,9 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc #endif #ifdef USE_LIGHT -void WebServer::on_light_update(light::LightState *obj) { - if (obj->is_internal()) - return; - this->events_.send(this->light_json(obj).c_str(), "state"); -} +void WebServer::on_light_update(light::LightState *obj) { this->events_.send(this->light_json(obj).c_str(), "state"); } void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (light::LightState *obj : App.get_lights()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; @@ -579,15 +566,9 @@ std::string WebServer::light_json(light::LightState *obj) { #endif #ifdef USE_COVER -void WebServer::on_cover_update(cover::Cover *obj) { - if (obj->is_internal()) - return; - this->events_.send(this->cover_json(obj).c_str(), "state"); -} +void WebServer::on_cover_update(cover::Cover *obj) { this->events_.send(this->cover_json(obj).c_str(), "state"); } void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (cover::Cover *obj : App.get_covers()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; @@ -646,8 +627,6 @@ void WebServer::on_number_update(number::Number *obj, float state) { } void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_numbers()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; std::string data = this->number_json(obj, obj->state); @@ -673,8 +652,6 @@ void WebServer::on_select_update(select::Select *obj, const std::string &state) } void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_selects()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index cdfec51cf1..66fd082d19 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -58,6 +58,12 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { */ void set_js_include(const char *js_include); + /** Determine whether internal components should be displayed on the web server. + * Defaults to false. + * + * @param include_internal Whether internal components should be displayed. + */ + void set_include_internal(bool include_internal) { include_internal_ = include_internal; } /** Set whether or not the webserver should expose the OTA form and handler. * * @param allow_ota. @@ -188,6 +194,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { const char *css_include_{nullptr}; const char *js_url_{nullptr}; const char *js_include_{nullptr}; + bool include_internal_{false}; bool allow_ota_{true}; }; diff --git a/esphome/const.py b/esphome/const.py index f7beee8245..9006145dfe 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -286,6 +286,7 @@ CONF_ILLUMINANCE = "illuminance" CONF_IMPEDANCE = "impedance" CONF_IMPORT_ACTIVE_ENERGY = "import_active_energy" CONF_IMPORT_REACTIVE_ENERGY = "import_reactive_energy" +CONF_INCLUDE_INTERNAL = "include_internal" CONF_INCLUDES = "includes" CONF_INDEX = "index" CONF_INDOOR = "indoor" diff --git a/esphome/core/controller.cpp b/esphome/core/controller.cpp index 1d25be41f2..6d3a76a292 100644 --- a/esphome/core/controller.cpp +++ b/esphome/core/controller.cpp @@ -4,64 +4,64 @@ namespace esphome { -void Controller::setup_controller() { +void Controller::setup_controller(bool include_internal) { #ifdef USE_BINARY_SENSOR for (auto *obj : App.get_binary_sensors()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](bool state) { this->on_binary_sensor_update(obj, state); }); } #endif #ifdef USE_FAN for (auto *obj : App.get_fans()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj]() { this->on_fan_update(obj); }); } #endif #ifdef USE_LIGHT for (auto *obj : App.get_lights()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_new_remote_values_callback([this, obj]() { this->on_light_update(obj); }); } #endif #ifdef USE_SENSOR for (auto *obj : App.get_sensors()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](float state) { this->on_sensor_update(obj, state); }); } #endif #ifdef USE_SWITCH for (auto *obj : App.get_switches()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](bool state) { this->on_switch_update(obj, state); }); } #endif #ifdef USE_COVER for (auto *obj : App.get_covers()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj]() { this->on_cover_update(obj); }); } #endif #ifdef USE_TEXT_SENSOR for (auto *obj : App.get_text_sensors()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](const std::string &state) { this->on_text_sensor_update(obj, state); }); } #endif #ifdef USE_CLIMATE for (auto *obj : App.get_climates()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj]() { this->on_climate_update(obj); }); } #endif #ifdef USE_NUMBER for (auto *obj : App.get_numbers()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](float state) { this->on_number_update(obj, state); }); } #endif #ifdef USE_SELECT for (auto *obj : App.get_selects()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](const std::string &state) { this->on_select_update(obj, state); }); } #endif diff --git a/esphome/core/controller.h b/esphome/core/controller.h index 0de8f7ea19..f53924cd23 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -36,7 +36,7 @@ namespace esphome { class Controller { public: - void setup_controller(); + void setup_controller(bool include_internal = false); #ifdef USE_BINARY_SENSOR virtual void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state){}; #endif diff --git a/tests/test4.yaml b/tests/test4.yaml index 938145235a..ee88b422f2 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -49,6 +49,7 @@ web_server: auth: username: admin password: admin + include_internal: true time: - platform: sntp From f50e40e0b893618250e8537fa69cee4fad29e5f1 Mon Sep 17 00:00:00 2001 From: definitio <37266727+definitio@users.noreply.github.com> Date: Mon, 29 Nov 2021 20:09:09 +0300 Subject: [PATCH 1766/1841] Fix custom mode_state_topic (#2827) --- esphome/components/climate/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index b0f9146012..87b9a4b3e2 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -213,7 +213,7 @@ async def setup_climate_core_(var, config): if CONF_MODE_COMMAND_TOPIC in config: cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC])) if CONF_MODE_STATE_TOPIC in config: - cg.add(mqtt_.set_custom_state_topic(config[CONF_MODE_STATE_TOPIC])) + cg.add(mqtt_.set_custom_mode_state_topic(config[CONF_MODE_STATE_TOPIC])) if CONF_SWING_MODE_COMMAND_TOPIC in config: cg.add( From b5639a6472fdd06e2a6d72252a0f3097ab4b163a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 30 Nov 2021 08:00:51 +1300 Subject: [PATCH 1767/1841] Add support for button entities (#2824) --- CODEOWNERS | 1 + esphome/components/api/api.proto | 25 ++++ esphome/components/api/api_connection.cpp | 21 ++++ esphome/components/api/api_connection.h | 4 + esphome/components/api/api_pb2.cpp | 112 ++++++++++++++++++ esphome/components/api/api_pb2.h | 30 +++++ esphome/components/api/api_pb2_service.cpp | 34 ++++++ esphome/components/api/api_pb2_service.h | 12 ++ esphome/components/api/list_entities.cpp | 3 + esphome/components/api/list_entities.h | 3 + esphome/components/api/subscribe_state.h | 3 + esphome/components/api/util.cpp | 15 +++ esphome/components/api/util.h | 6 + esphome/components/button/__init__.py | 104 ++++++++++++++++ esphome/components/button/automation.h | 28 +++++ esphome/components/button/button.cpp | 21 ++++ esphome/components/button/button.h | 50 ++++++++ esphome/components/mqtt/__init__.py | 1 + esphome/components/mqtt/mqtt_button.cpp | 40 +++++++ esphome/components/mqtt/mqtt_button.h | 40 +++++++ esphome/components/restart/button/__init__.py | 23 ++++ .../restart/button/restart_button.cpp | 20 ++++ .../restart/button/restart_button.h | 18 +++ .../restart/{switch.py => switch/__init__.py} | 0 .../restart/{ => switch}/restart_switch.cpp | 0 .../restart/{ => switch}/restart_switch.h | 0 .../components/template/button/__init__.py | 13 ++ esphome/components/web_server/web_server.cpp | 37 ++++++ esphome/components/web_server/web_server.h | 5 + esphome/core/application.h | 19 +++ esphome/core/controller.h | 3 + esphome/core/defines.h | 1 + script/ci-custom.py | 1 + tests/test4.yaml | 4 + 34 files changed, 697 insertions(+) create mode 100644 esphome/components/button/__init__.py create mode 100644 esphome/components/button/automation.h create mode 100644 esphome/components/button/button.cpp create mode 100644 esphome/components/button/button.h create mode 100644 esphome/components/mqtt/mqtt_button.cpp create mode 100644 esphome/components/mqtt/mqtt_button.h create mode 100644 esphome/components/restart/button/__init__.py create mode 100644 esphome/components/restart/button/restart_button.cpp create mode 100644 esphome/components/restart/button/restart_button.h rename esphome/components/restart/{switch.py => switch/__init__.py} (100%) rename esphome/components/restart/{ => switch}/restart_switch.cpp (100%) rename esphome/components/restart/{ => switch}/restart_switch.h (100%) create mode 100644 esphome/components/template/button/__init__.py diff --git a/CODEOWNERS b/CODEOWNERS index 80c5dc34c1..687fad3948 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -30,6 +30,7 @@ esphome/components/bang_bang/* @OttoWinter esphome/components/binary_sensor/* @esphome/core esphome/components/ble_client/* @buxtronix esphome/components/bme680_bsec/* @trvrnrth +esphome/components/button/* @esphome/core esphome/components/canbus/* @danielschramm @mvturnho esphome/components/cap1188/* @MrEditor97 esphome/components/captive_portal/* @OttoWinter diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 0eb7ead735..eaad4b8d07 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -40,6 +40,7 @@ service APIConnection { rpc climate_command (ClimateCommandRequest) returns (void) {} rpc number_command (NumberCommandRequest) returns (void) {} rpc select_command (SelectCommandRequest) returns (void) {} + rpc button_command (ButtonCommandRequest) returns (void) {} } @@ -944,3 +945,27 @@ message SelectCommandRequest { fixed32 key = 1; string state = 2; } + +// ==================== BUTTON ==================== +message ListEntitiesButtonResponse { + option (id) = 61; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BUTTON"; + + string object_id = 1; + fixed32 key = 2; + string name = 3; + string unique_id = 4; + + string icon = 5; + bool disabled_by_default = 6; + EntityCategory entity_category = 7; +} +message ButtonCommandRequest { + option (id) = 62; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BUTTON"; + option (no_delay) = true; + + fixed32 key = 1; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 715a4f48c1..22b896a788 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -674,6 +674,27 @@ void APIConnection::select_command(const SelectCommandRequest &msg) { } #endif +#ifdef USE_BUTTON +bool APIConnection::send_button_info(button::Button *button) { + ListEntitiesButtonResponse msg; + msg.key = button->get_object_id_hash(); + msg.object_id = button->get_object_id(); + msg.name = button->get_name(); + msg.unique_id = get_default_unique_id("button", button); + msg.icon = button->get_icon(); + msg.disabled_by_default = button->is_disabled_by_default(); + msg.entity_category = static_cast(button->get_entity_category()); + return this->send_list_entities_button_response(msg); +} +void APIConnection::button_command(const ButtonCommandRequest &msg) { + button::Button *button = App.get_button_by_key(msg.key); + if (button == nullptr) + return; + + button->press(); +} +#endif + #ifdef USE_ESP32_CAMERA void APIConnection::send_camera_state(std::shared_ptr image) { if (!this->state_subscription_) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index a1f1769a19..72697b5911 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -73,6 +73,10 @@ class APIConnection : public APIServerConnection { bool send_select_state(select::Select *select, std::string state); bool send_select_info(select::Select *select); void select_command(const SelectCommandRequest &msg) override; +#endif +#ifdef USE_BUTTON + bool send_button_info(button::Button *button); + void button_command(const ButtonCommandRequest &msg) override; #endif bool send_log_message(int level, const char *tag, const char *line); void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 7bfa1e9edb..169349d995 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -4147,6 +4147,118 @@ void SelectCommandRequest::dump_to(std::string &out) const { out.append("}"); } #endif +bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 6: { + this->disabled_by_default = value.as_bool(); + return true; + } + case 7: { + this->entity_category = value.as_enum(); + return true; + } + default: + return false; + } +} +bool ListEntitiesButtonResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: { + this->object_id = value.as_string(); + return true; + } + case 3: { + this->name = value.as_string(); + return true; + } + case 4: { + this->unique_id = value.as_string(); + return true; + } + case 5: { + this->icon = value.as_string(); + return true; + } + default: + return false; + } +} +bool ListEntitiesButtonResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 2: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->object_id); + buffer.encode_fixed32(2, this->key); + buffer.encode_string(3, this->name); + buffer.encode_string(4, this->unique_id); + buffer.encode_string(5, this->icon); + buffer.encode_bool(6, this->disabled_by_default); + buffer.encode_enum(7, this->entity_category); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void ListEntitiesButtonResponse::dump_to(std::string &out) const { + char buffer[64]; + out.append("ListEntitiesButtonResponse {\n"); + out.append(" object_id: "); + out.append("'").append(this->object_id).append("'"); + out.append("\n"); + + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" name: "); + out.append("'").append(this->name).append("'"); + out.append("\n"); + + out.append(" unique_id: "); + out.append("'").append(this->unique_id).append("'"); + out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); + + out.append(" disabled_by_default: "); + out.append(YESNO(this->disabled_by_default)); + out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); + out.append("}"); +} +#endif +bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void ButtonCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); } +#ifdef HAS_PROTO_MESSAGE_DUMP +void ButtonCommandRequest::dump_to(std::string &out) const { + char buffer[64]; + out.append("ButtonCommandRequest {\n"); + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index ec11732c7d..82fd8de687 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1041,6 +1041,36 @@ class SelectCommandRequest : public ProtoMessage { bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; }; +class ListEntitiesButtonResponse : public ProtoMessage { + public: + std::string object_id{}; + uint32_t key{0}; + std::string name{}; + std::string unique_id{}; + std::string icon{}; + bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class ButtonCommandRequest : public ProtoMessage { + public: + uint32_t key{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; +}; } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index ad2413ea57..567fbf02c9 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -282,6 +282,16 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon #endif #ifdef USE_SELECT #endif +#ifdef USE_BUTTON +bool APIServerConnectionBase::send_list_entities_button_response(const ListEntitiesButtonResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_list_entities_button_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 61); +} +#endif +#ifdef USE_BUTTON +#endif bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { switch (msg_type) { case 1: { @@ -513,6 +523,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str()); #endif this->on_select_command_request(msg); +#endif + break; + } + case 62: { +#ifdef USE_BUTTON + ButtonCommandRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_button_command_request: %s", msg.dump().c_str()); +#endif + this->on_button_command_request(msg); #endif break; } @@ -737,6 +758,19 @@ void APIServerConnection::on_select_command_request(const SelectCommandRequest & this->select_command(msg); } #endif +#ifdef USE_BUTTON +void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->button_command(msg); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 1b8d990b05..50b08d3ec4 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -129,6 +129,12 @@ class APIServerConnectionBase : public ProtoService { #endif #ifdef USE_SELECT virtual void on_select_command_request(const SelectCommandRequest &value){}; +#endif +#ifdef USE_BUTTON + bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg); +#endif +#ifdef USE_BUTTON + virtual void on_button_command_request(const ButtonCommandRequest &value){}; #endif protected: bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; @@ -171,6 +177,9 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_SELECT virtual void select_command(const SelectCommandRequest &msg) = 0; +#endif +#ifdef USE_BUTTON + virtual void button_command(const ButtonCommandRequest &msg) = 0; #endif protected: void on_hello_request(const HelloRequest &msg) override; @@ -209,6 +218,9 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_SELECT void on_select_command_request(const SelectCommandRequest &msg) override; #endif +#ifdef USE_BUTTON + void on_button_command_request(const ButtonCommandRequest &msg) override; +#endif }; } // namespace api diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index 745dd92c89..cb97df8ca1 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -27,6 +27,9 @@ bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->clie #ifdef USE_SWITCH bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); } #endif +#ifdef USE_BUTTON +bool ListEntitiesIterator::on_button(button::Button *button) { return this->client_->send_button_info(button); } +#endif #ifdef USE_TEXT_SENSOR bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { return this->client_->send_text_sensor_info(text_sensor); diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index c728fb0a97..714edaa91f 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -30,6 +30,9 @@ class ListEntitiesIterator : public ComponentIterator { #ifdef USE_SWITCH bool on_switch(switch_::Switch *a_switch) override; #endif +#ifdef USE_BUTTON + bool on_button(button::Button *button) override; +#endif #ifdef USE_TEXT_SENSOR bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; #endif diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index beb9b947d4..d3f2d3aa45 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -31,6 +31,9 @@ class InitialStateIterator : public ComponentIterator { #ifdef USE_SWITCH bool on_switch(switch_::Switch *a_switch) override; #endif +#ifdef USE_BUTTON + bool on_button(button::Button *button) override { return true; }; +#endif #ifdef USE_TEXT_SENSOR bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; #endif diff --git a/esphome/components/api/util.cpp b/esphome/components/api/util.cpp index 5085994607..f5fd752101 100644 --- a/esphome/components/api/util.cpp +++ b/esphome/components/api/util.cpp @@ -116,6 +116,21 @@ void ComponentIterator::advance() { } break; #endif +#ifdef USE_BUTTON + case IteratorState::BUTTON: + if (this->at_ >= App.get_buttons().size()) { + advance_platform = true; + } else { + auto *button = App.get_buttons()[this->at_]; + if (button->is_internal()) { + success = true; + break; + } else { + success = this->on_button(button); + } + } + break; +#endif #ifdef USE_TEXT_SENSOR case IteratorState::TEXT_SENSOR: if (this->at_ >= App.get_text_sensors().size()) { diff --git a/esphome/components/api/util.h b/esphome/components/api/util.h index e404a95619..7849b3e028 100644 --- a/esphome/components/api/util.h +++ b/esphome/components/api/util.h @@ -38,6 +38,9 @@ class ComponentIterator { #ifdef USE_SWITCH virtual bool on_switch(switch_::Switch *a_switch) = 0; #endif +#ifdef USE_BUTTON + virtual bool on_button(button::Button *button) = 0; +#endif #ifdef USE_TEXT_SENSOR virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0; #endif @@ -78,6 +81,9 @@ class ComponentIterator { #ifdef USE_SWITCH SWITCH, #endif +#ifdef USE_BUTTON + BUTTON, +#endif #ifdef USE_TEXT_SENSOR TEXT_SENSOR, #endif diff --git a/esphome/components/button/__init__.py b/esphome/components/button/__init__.py new file mode 100644 index 0000000000..495a85b6b4 --- /dev/null +++ b/esphome/components/button/__init__.py @@ -0,0 +1,104 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.automation import maybe_simple_id +from esphome.components import mqtt +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_ON_PRESS, + CONF_TRIGGER_ID, + CONF_MQTT_ID, +) +from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity + +CODEOWNERS = ["@esphome/core"] +IS_PLATFORM_COMPONENT = True + +button_ns = cg.esphome_ns.namespace("button") +Button = button_ns.class_("Button", cg.EntityBase) +ButtonPtr = Button.operator("ptr") + +PressAction = button_ns.class_("PressAction", automation.Action) + +ButtonPressTrigger = button_ns.class_( + "ButtonPressTrigger", automation.Trigger.template() +) + + +BUTTON_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent), + cv.Optional(CONF_ON_PRESS): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger), + } + ), + } +) + +_UNDEF = object() + + +def button_schema( + icon: str = _UNDEF, + entity_category: str = _UNDEF, +) -> cv.Schema: + schema = BUTTON_SCHEMA + if icon is not _UNDEF: + schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon}) + if entity_category is not _UNDEF: + schema = schema.extend( + { + cv.Optional( + CONF_ENTITY_CATEGORY, default=entity_category + ): cv.entity_category + } + ) + return schema + + +async def setup_button_core_(var, config): + await setup_entity(var, config) + + for conf in config.get(CONF_ON_PRESS, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + if CONF_MQTT_ID in config: + mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) + await mqtt.register_mqtt_component(mqtt_, config) + + +async def register_button(var, config): + if not CORE.has_id(config[CONF_ID]): + var = cg.Pvariable(config[CONF_ID], var) + cg.add(cg.App.register_button(var)) + await setup_button_core_(var, config) + + +async def new_button(config): + var = cg.new_Pvariable(config[CONF_ID]) + await register_button(var, config) + return var + + +BUTTON_PRESS_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(Button), + } +) + + +@automation.register_action("button.press", PressAction, BUTTON_PRESS_SCHEMA) +async def button_press_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) + + +@coroutine_with_priority(100.0) +async def to_code(config): + cg.add_global(button_ns.using) + cg.add_define("USE_BUTTON") diff --git a/esphome/components/button/automation.h b/esphome/components/button/automation.h new file mode 100644 index 0000000000..a5fb9f35b7 --- /dev/null +++ b/esphome/components/button/automation.h @@ -0,0 +1,28 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace button { + +template class PressAction : public Action { + public: + explicit PressAction(Button *button) : button_(button) {} + + void play(Ts... x) override { this->button_->press(); } + + protected: + Button *button_; +}; + +class ButtonPressTrigger : public Trigger<> { + public: + ButtonPressTrigger(Button *button) { + button->add_on_press_callback([this]() { this->trigger(); }); + } +}; + +} // namespace button +} // namespace esphome diff --git a/esphome/components/button/button.cpp b/esphome/components/button/button.cpp new file mode 100644 index 0000000000..fe39b1e458 --- /dev/null +++ b/esphome/components/button/button.cpp @@ -0,0 +1,21 @@ +#include "button.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace button { + +static const char *const TAG = "button"; + +Button::Button(const std::string &name) : EntityBase(name) {} +Button::Button() : Button("") {} + +void Button::press() { + ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str()); + this->press_action(); + this->press_callback_.call(); +} +void Button::add_on_press_callback(std::function &&callback) { this->press_callback_.add(std::move(callback)); } +uint32_t Button::hash_base() { return 1495763804UL; } + +} // namespace button +} // namespace esphome diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h new file mode 100644 index 0000000000..954afa0ab9 --- /dev/null +++ b/esphome/components/button/button.h @@ -0,0 +1,50 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/entity_base.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace button { + +#define LOG_BUTTON(prefix, type, obj) \ + if ((obj) != nullptr) { \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ + if (!(obj)->get_icon().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ + } \ + } + +/** Base class for all buttons. + * + * A button is just a momentary switch that does not have a state, only a trigger. + */ +class Button : public EntityBase { + public: + explicit Button(); + explicit Button(const std::string &name); + + /** Press this button. This is called by the front-end. + * + * For implementing buttons, please override press_action. + */ + void press(); + + /** Set callback for state changes. + * + * @param callback The void() callback. + */ + void add_on_press_callback(std::function &&callback); + + protected: + /** You should implement this virtual method if you want to create your own button. + */ + virtual void press_action(){}; + + uint32_t hash_base() override; + + CallbackManager press_callback_{}; +}; + +} // namespace button +} // namespace esphome diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 73af0bad90..d677d54d23 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -95,6 +95,7 @@ MQTTSwitchComponent = mqtt_ns.class_("MQTTSwitchComponent", MQTTComponent) MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent) MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent) MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent) +MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent) MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator") MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = { diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp new file mode 100644 index 0000000000..5a0d14b648 --- /dev/null +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -0,0 +1,40 @@ +#include "mqtt_button.h" +#include "esphome/core/log.h" + +#include "mqtt_const.h" + +#ifdef USE_MQTT +#ifdef USE_BUTTON + +namespace esphome { +namespace mqtt { + +static const char *const TAG = "mqtt.button"; + +using namespace esphome::button; + +MQTTButtonComponent::MQTTButtonComponent(button::Button *button) : MQTTComponent(), button_(button) {} + +void MQTTButtonComponent::setup() { + this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { + if (payload == "press") { + this->button_->press(); + } else { + ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str()); + this->status_momentary_warning("state", 5000); + } + }); +} +void MQTTButtonComponent::dump_config() { + ESP_LOGCONFIG(TAG, "MQTT Button '%s': ", this->button_->get_name().c_str()); + LOG_MQTT_COMPONENT(true, true); +} + +std::string MQTTButtonComponent::component_type() const { return "button"; } +const EntityBase *MQTTButtonComponent::get_entity() const { return this->button_; } + +} // namespace mqtt +} // namespace esphome + +#endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_button.h b/esphome/components/mqtt/mqtt_button.h new file mode 100644 index 0000000000..a7e60db380 --- /dev/null +++ b/esphome/components/mqtt/mqtt_button.h @@ -0,0 +1,40 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_MQTT +#ifdef USE_BUTTON + +#include "esphome/components/button/button.h" +#include "mqtt_component.h" + +namespace esphome { +namespace mqtt { + +class MQTTButtonComponent : public mqtt::MQTTComponent { + public: + explicit MQTTButtonComponent(button::Button *button); + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + void setup() override; + void dump_config() override; + + /// Buttons do not send a state so just return true. + bool send_initial_state() override { return true; } + + void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override {} + + protected: + /// "button" component type. + std::string component_type() const override; + const EntityBase *get_entity() const override; + + button::Button *button_; +}; + +} // namespace mqtt +} // namespace esphome + +#endif +#endif // USE_MQTT diff --git a/esphome/components/restart/button/__init__.py b/esphome/components/restart/button/__init__.py new file mode 100644 index 0000000000..257a8e35f7 --- /dev/null +++ b/esphome/components/restart/button/__init__.py @@ -0,0 +1,23 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import button +from esphome.const import ( + CONF_ID, + ENTITY_CATEGORY_CONFIG, + ICON_RESTART, +) + +restart_ns = cg.esphome_ns.namespace("restart") +RestartButton = restart_ns.class_("RestartButton", button.Button, cg.Component) + +CONFIG_SCHEMA = ( + button.button_schema(icon=ICON_RESTART, entity_category=ENTITY_CATEGORY_CONFIG) + .extend({cv.GenerateID(): cv.declare_id(RestartButton)}) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await button.register_button(var, config) diff --git a/esphome/components/restart/button/restart_button.cpp b/esphome/components/restart/button/restart_button.cpp new file mode 100644 index 0000000000..d8ff061355 --- /dev/null +++ b/esphome/components/restart/button/restart_button.cpp @@ -0,0 +1,20 @@ +#include "restart_button.h" +#include "esphome/core/application.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace restart { + +static const char *const TAG = "restart.button"; + +void RestartButton::press_action() { + ESP_LOGI(TAG, "Restarting device..."); + // Let MQTT settle a bit + delay(100); // NOLINT + App.safe_reboot(); +} +void RestartButton::dump_config() { LOG_BUTTON("", "Restart Button", this); } + +} // namespace restart +} // namespace esphome diff --git a/esphome/components/restart/button/restart_button.h b/esphome/components/restart/button/restart_button.h new file mode 100644 index 0000000000..db18f1dadc --- /dev/null +++ b/esphome/components/restart/button/restart_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace restart { + +class RestartButton : public button::Button, public Component { + public: + void dump_config() override; + + protected: + void press_action() override; +}; + +} // namespace restart +} // namespace esphome diff --git a/esphome/components/restart/switch.py b/esphome/components/restart/switch/__init__.py similarity index 100% rename from esphome/components/restart/switch.py rename to esphome/components/restart/switch/__init__.py diff --git a/esphome/components/restart/restart_switch.cpp b/esphome/components/restart/switch/restart_switch.cpp similarity index 100% rename from esphome/components/restart/restart_switch.cpp rename to esphome/components/restart/switch/restart_switch.cpp diff --git a/esphome/components/restart/restart_switch.h b/esphome/components/restart/switch/restart_switch.h similarity index 100% rename from esphome/components/restart/restart_switch.h rename to esphome/components/restart/switch/restart_switch.h diff --git a/esphome/components/template/button/__init__.py b/esphome/components/template/button/__init__.py new file mode 100644 index 0000000000..aa192d118e --- /dev/null +++ b/esphome/components/template/button/__init__.py @@ -0,0 +1,13 @@ +import esphome.config_validation as cv +from esphome.components import button + + +CONFIG_SCHEMA = button.BUTTON_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(button.Button), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + await button.new_button(config) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 3f74c2e8a1..29cb4827bd 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -198,6 +198,11 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { write_row(stream, obj, "switch", ""); #endif +#ifdef USE_BUTTON + for (auto *obj : App.get_buttons()) + write_row(stream, obj, "button", ""); +#endif + #ifdef USE_BINARY_SENSOR for (auto *obj : App.get_binary_sensors()) if (this->include_internal_ || !obj->is_internal()) @@ -382,6 +387,26 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM } #endif +#ifdef USE_BUTTON +void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) { + for (button::Button *obj : App.get_buttons()) { + if (obj->is_internal()) + continue; + if (obj->get_object_id() != match.id) + continue; + + if (request->method() == HTTP_POST && match.method == "press") { + this->defer([obj]() { obj->press(); }); + request->send(200); + } else { + request->send(404); + } + return; + } + request->send(404); +} +#endif + #ifdef USE_BINARY_SENSOR void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { this->events_.send(this->binary_sensor_json(obj, state).c_str(), "state"); @@ -715,6 +740,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return true; #endif +#ifdef USE_BUTTON + if (request->method() == HTTP_POST && match.domain == "button") + return true; +#endif + #ifdef USE_BINARY_SENSOR if (request->method() == HTTP_GET && match.domain == "binary_sensor") return true; @@ -787,6 +817,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { } #endif +#ifdef USE_BUTTON + if (match.domain == "button") { + this->handle_button_request(request, match); + return; + } +#endif + #ifdef USE_BINARY_SENSOR if (match.domain == "binary_sensor") { this->handle_binary_sensor_request(request, match); diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 66fd082d19..8edb4237a2 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -112,6 +112,11 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { std::string switch_json(switch_::Switch *obj, bool value); #endif +#ifdef USE_BUTTON + /// Handle a button request under '/button//press'. + void handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match); +#endif + #ifdef USE_BINARY_SENSOR void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override; diff --git a/esphome/core/application.h b/esphome/core/application.h index 5c1483d301..ace0c9ad6d 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -17,6 +17,9 @@ #ifdef USE_SWITCH #include "esphome/components/switch/switch.h" #endif +#ifdef USE_BUTTON +#include "esphome/components/button/button.h" +#endif #ifdef USE_TEXT_SENSOR #include "esphome/components/text_sensor/text_sensor.h" #endif @@ -67,6 +70,10 @@ class Application { void register_switch(switch_::Switch *a_switch) { this->switches_.push_back(a_switch); } #endif +#ifdef USE_BUTTON + void register_button(button::Button *button) { this->buttons_.push_back(button); } +#endif + #ifdef USE_TEXT_SENSOR void register_text_sensor(text_sensor::TextSensor *sensor) { this->text_sensors_.push_back(sensor); } #endif @@ -167,6 +174,15 @@ class Application { return nullptr; } #endif +#ifdef USE_BUTTON + const std::vector &get_buttons() { return this->buttons_; } + button::Button *get_button_by_key(uint32_t key, bool include_internal = false) { + for (auto *obj : this->buttons_) + if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) + return obj; + return nullptr; + } +#endif #ifdef USE_SENSOR const std::vector &get_sensors() { return this->sensors_; } sensor::Sensor *get_sensor_by_key(uint32_t key, bool include_internal = false) { @@ -260,6 +276,9 @@ class Application { #ifdef USE_SWITCH std::vector switches_{}; #endif +#ifdef USE_BUTTON + std::vector buttons_{}; +#endif #ifdef USE_SENSOR std::vector sensors_{}; #endif diff --git a/esphome/core/controller.h b/esphome/core/controller.h index f53924cd23..0c3722855c 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -22,6 +22,9 @@ #ifdef USE_SWITCH #include "esphome/components/switch/switch.h" #endif +#ifdef USE_BUTTON +#include "esphome/components/button/button.h" +#endif #ifdef USE_CLIMATE #include "esphome/components/climate/climate.h" #endif diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 8dcae9fa31..a74755f651 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -16,6 +16,7 @@ #define USE_API_NOISE #define USE_API_PLAINTEXT #define USE_BINARY_SENSOR +#define USE_BUTTON #define USE_CLIMATE #define USE_COVER #define USE_DEEP_SLEEP diff --git a/script/ci-custom.py b/script/ci-custom.py index 3f01fb81bf..de2dfda44d 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -603,6 +603,7 @@ def lint_inclusive_language(fname, match): "esphome/components/switch/switch.h", "esphome/components/text_sensor/text_sensor.h", "esphome/components/climate/climate.h", + "esphome/components/button/button.h", "esphome/core/component.h", "esphome/core/gpio.h", "esphome/core/log.h", diff --git a/tests/test4.yaml b/tests/test4.yaml index ee88b422f2..b4708acf65 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -520,3 +520,7 @@ xpt2046: id(touchscreen).y_raw, id(touchscreen).z_raw ); + +button: + - platform: restart + name: Restart Button From 939fb313df622d3fabc14ea837e296c7aa5ec0ec Mon Sep 17 00:00:00 2001 From: dentra Date: Mon, 29 Nov 2021 22:08:52 +0300 Subject: [PATCH 1768/1841] Tuya text_sensor and raw data usage (#1812) --- CODEOWNERS | 1 + esphome/components/tuya/__init__.py | 89 ++++++++++++++++++- esphome/components/tuya/automation.cpp | 66 ++++++++++++++ esphome/components/tuya/automation.h | 53 +++++++++++ .../components/tuya/binary_sensor/__init__.py | 4 +- esphome/components/tuya/sensor/__init__.py | 4 +- .../components/tuya/text_sensor/__init__.py | 29 ++++++ .../tuya/text_sensor/tuya_text_sensor.cpp | 35 ++++++++ .../tuya/text_sensor/tuya_text_sensor.h | 24 +++++ esphome/const.py | 1 + 10 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 esphome/components/tuya/automation.cpp create mode 100644 esphome/components/tuya/automation.h create mode 100644 esphome/components/tuya/text_sensor/__init__.py create mode 100644 esphome/components/tuya/text_sensor/tuya_text_sensor.cpp create mode 100644 esphome/components/tuya/text_sensor/tuya_text_sensor.h diff --git a/CODEOWNERS b/CODEOWNERS index 687fad3948..6dbdef12ec 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -181,6 +181,7 @@ esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/climate/* @jesserockz esphome/components/tuya/sensor/* @jesserockz esphome/components/tuya/switch/* @jesserockz +esphome/components/tuya/text_sensor/* @dentra esphome/components/uart/* @esphome/core esphome/components/ultrasonic/* @OttoWinter esphome/components/version/* @esphome/core diff --git a/esphome/components/tuya/__init__.py b/esphome/components/tuya/__init__.py index 436759979a..965893e012 100644 --- a/esphome/components/tuya/__init__.py +++ b/esphome/components/tuya/__init__.py @@ -1,16 +1,84 @@ from esphome.components import time +from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import uart -from esphome.const import CONF_ID, CONF_TIME_ID +from esphome.const import CONF_ID, CONF_TIME_ID, CONF_TRIGGER_ID, CONF_SENSOR_DATAPOINT DEPENDENCIES = ["uart"] CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS = "ignore_mcu_update_on_datapoints" +CONF_ON_DATAPOINT_UPDATE = "on_datapoint_update" +CONF_DATAPOINT_TYPE = "datapoint_type" + tuya_ns = cg.esphome_ns.namespace("tuya") Tuya = tuya_ns.class_("Tuya", cg.Component, uart.UARTDevice) +DPTYPE_ANY = "any" +DPTYPE_RAW = "raw" +DPTYPE_BOOL = "bool" +DPTYPE_INT = "int" +DPTYPE_UINT = "uint" +DPTYPE_STRING = "string" +DPTYPE_ENUM = "enum" +DPTYPE_BITMASK = "bitmask" + +DATAPOINT_TYPES = { + DPTYPE_ANY: tuya_ns.struct("TuyaDatapoint"), + DPTYPE_RAW: cg.std_vector.template(cg.uint8), + DPTYPE_BOOL: cg.bool_, + DPTYPE_INT: cg.int_, + DPTYPE_UINT: cg.uint32, + DPTYPE_STRING: cg.std_string, + DPTYPE_ENUM: cg.uint8, + DPTYPE_BITMASK: cg.uint32, +} + +DATAPOINT_TRIGGERS = { + DPTYPE_ANY: tuya_ns.class_( + "TuyaDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_ANY]), + ), + DPTYPE_RAW: tuya_ns.class_( + "TuyaRawDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_RAW]), + ), + DPTYPE_BOOL: tuya_ns.class_( + "TuyaBoolDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_BOOL]), + ), + DPTYPE_INT: tuya_ns.class_( + "TuyaIntDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_INT]), + ), + DPTYPE_UINT: tuya_ns.class_( + "TuyaUIntDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_UINT]), + ), + DPTYPE_STRING: tuya_ns.class_( + "TuyaStringDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_STRING]), + ), + DPTYPE_ENUM: tuya_ns.class_( + "TuyaEnumDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_ENUM]), + ), + DPTYPE_BITMASK: tuya_ns.class_( + "TuyaBitmaskDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_BITMASK]), + ), +} + + +def assign_declare_id(value): + value = value.copy() + value[CONF_TRIGGER_ID] = cv.declare_id( + DATAPOINT_TRIGGERS[value[CONF_DATAPOINT_TYPE]] + )(value[CONF_TRIGGER_ID].id) + return value + + CONF_TUYA_ID = "tuya_id" CONFIG_SCHEMA = ( cv.Schema( @@ -20,6 +88,18 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS): cv.ensure_list( cv.uint8_t ), + cv.Optional(CONF_ON_DATAPOINT_UPDATE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + DATAPOINT_TRIGGERS[DPTYPE_ANY] + ), + cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_DATAPOINT_TYPE, default=DPTYPE_ANY): cv.one_of( + *DATAPOINT_TRIGGERS, lower=True + ), + }, + extra_validators=assign_declare_id, + ), } ) .extend(cv.COMPONENT_SCHEMA) @@ -37,3 +117,10 @@ async def to_code(config): if CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS in config: for dp in config[CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS]: cg.add(var.add_ignore_mcu_update_on_datapoints(dp)) + for conf in config.get(CONF_ON_DATAPOINT_UPDATE, []): + trigger = cg.new_Pvariable( + conf[CONF_TRIGGER_ID], var, conf[CONF_SENSOR_DATAPOINT] + ) + await automation.build_automation( + trigger, [(DATAPOINT_TYPES[conf[CONF_DATAPOINT_TYPE]], "x")], conf + ) diff --git a/esphome/components/tuya/automation.cpp b/esphome/components/tuya/automation.cpp new file mode 100644 index 0000000000..eb1c170083 --- /dev/null +++ b/esphome/components/tuya/automation.cpp @@ -0,0 +1,66 @@ +#include "esphome/core/log.h" + +#include "automation.h" + +static const char *const TAG = "tuya.automation"; + +namespace esphome { +namespace tuya { + +void check_expected_datapoint(const TuyaDatapoint &dp, TuyaDatapointType expected) { + if (dp.type != expected) { + ESP_LOGW(TAG, "Tuya sensor %u expected datapoint type %#02hhX but got %#02hhX", dp.id, expected, dp.type); + } +} + +TuyaRawDatapointUpdateTrigger::TuyaRawDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::RAW); + this->trigger(dp.value_raw); + }); +} + +TuyaBoolDatapointUpdateTrigger::TuyaBoolDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::BOOLEAN); + this->trigger(dp.value_bool); + }); +} + +TuyaIntDatapointUpdateTrigger::TuyaIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::INTEGER); + this->trigger(dp.value_int); + }); +} + +TuyaUIntDatapointUpdateTrigger::TuyaUIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::INTEGER); + this->trigger(dp.value_uint); + }); +} + +TuyaStringDatapointUpdateTrigger::TuyaStringDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::STRING); + this->trigger(dp.value_string); + }); +} + +TuyaEnumDatapointUpdateTrigger::TuyaEnumDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::ENUM); + this->trigger(dp.value_enum); + }); +} + +TuyaBitmaskDatapointUpdateTrigger::TuyaBitmaskDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::BITMASK); + this->trigger(dp.value_bitmask); + }); +} + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/automation.h b/esphome/components/tuya/automation.h new file mode 100644 index 0000000000..d7706e1d60 --- /dev/null +++ b/esphome/components/tuya/automation.h @@ -0,0 +1,53 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "tuya.h" + +namespace esphome { +namespace tuya { + +class TuyaDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { this->trigger(dp); }); + } +}; + +class TuyaRawDatapointUpdateTrigger : public Trigger> { + public: + explicit TuyaRawDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaBoolDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaBoolDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaIntDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaUIntDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaUIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaStringDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaStringDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaEnumDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaEnumDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaBitmaskDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaBitmaskDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/binary_sensor/__init__.py b/esphome/components/tuya/binary_sensor/__init__.py index 65f13ea422..cd4a2db89f 100644 --- a/esphome/components/tuya/binary_sensor/__init__.py +++ b/esphome/components/tuya/binary_sensor/__init__.py @@ -1,14 +1,12 @@ from esphome.components import binary_sensor import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ["tuya"] CODEOWNERS = ["@jesserockz"] -CONF_SENSOR_DATAPOINT = "sensor_datapoint" - TuyaBinarySensor = tuya_ns.class_( "TuyaBinarySensor", binary_sensor.BinarySensor, cg.Component ) diff --git a/esphome/components/tuya/sensor/__init__.py b/esphome/components/tuya/sensor/__init__.py index d87a2e7ce4..441400fa43 100644 --- a/esphome/components/tuya/sensor/__init__.py +++ b/esphome/components/tuya/sensor/__init__.py @@ -1,14 +1,12 @@ from esphome.components import sensor import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ["tuya"] CODEOWNERS = ["@jesserockz"] -CONF_SENSOR_DATAPOINT = "sensor_datapoint" - TuyaSensor = tuya_ns.class_("TuyaSensor", sensor.Sensor, cg.Component) CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( diff --git a/esphome/components/tuya/text_sensor/__init__.py b/esphome/components/tuya/text_sensor/__init__.py new file mode 100644 index 0000000000..1989ca10e3 --- /dev/null +++ b/esphome/components/tuya/text_sensor/__init__.py @@ -0,0 +1,29 @@ +from esphome.components import text_sensor +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT +from .. import tuya_ns, CONF_TUYA_ID, Tuya + +DEPENDENCIES = ["tuya"] +CODEOWNERS = ["@dentra"] + +TuyaTextSensor = tuya_ns.class_("TuyaTextSensor", text_sensor.TextSensor, cg.Component) + +CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TuyaTextSensor), + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await text_sensor.register_text_sensor(var, config) + + paren = await cg.get_variable(config[CONF_TUYA_ID]) + cg.add(var.set_tuya_parent(paren)) + + cg.add(var.set_sensor_id(config[CONF_SENSOR_DATAPOINT])) diff --git a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp new file mode 100644 index 0000000000..e939225453 --- /dev/null +++ b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp @@ -0,0 +1,35 @@ +#include "esphome/core/log.h" +#include "tuya_text_sensor.h" + +namespace esphome { +namespace tuya { + +static const char *const TAG = "tuya.text_sensor"; + +void TuyaTextSensor::setup() { + this->parent_->register_listener(this->sensor_id_, [this](const TuyaDatapoint &datapoint) { + switch (datapoint.type) { + case TuyaDatapointType::STRING: + ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, datapoint.value_string.c_str()); + this->publish_state(datapoint.value_string); + break; + case TuyaDatapointType::RAW: { + std::string data = hexencode(datapoint.value_raw); + ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, data.c_str()); + this->publish_state(data); + break; + } + default: + ESP_LOGW(TAG, "Unsupported data type for tuya text sensor %u: %#02hhX", datapoint.id, datapoint.type); + break; + } + }); +} + +void TuyaTextSensor::dump_config() { + ESP_LOGCONFIG(TAG, "Tuya Text Sensor:"); + ESP_LOGCONFIG(TAG, " Text Sensor has datapoint ID %u", this->sensor_id_); +} + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/text_sensor/tuya_text_sensor.h b/esphome/components/tuya/text_sensor/tuya_text_sensor.h new file mode 100644 index 0000000000..502ae5e8c7 --- /dev/null +++ b/esphome/components/tuya/text_sensor/tuya_text_sensor.h @@ -0,0 +1,24 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/tuya/tuya.h" +#include "esphome/components/text_sensor/text_sensor.h" + +namespace esphome { +namespace tuya { + +class TuyaTextSensor : public text_sensor::TextSensor, public Component { + public: + void setup() override; + void dump_config() override; + void set_sensor_id(uint8_t sensor_id) { this->sensor_id_ = sensor_id; } + + void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } + + protected: + Tuya *parent_; + uint8_t sensor_id_{0}; +}; + +} // namespace tuya +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 9006145dfe..3510e500f5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -579,6 +579,7 @@ CONF_SEND_EVERY = "send_every" CONF_SEND_FIRST_AT = "send_first_at" CONF_SENSING_PIN = "sensing_pin" CONF_SENSOR = "sensor" +CONF_SENSOR_DATAPOINT = "sensor_datapoint" CONF_SENSOR_ID = "sensor_id" CONF_SENSORS = "sensors" CONF_SEQUENCE = "sequence" From 556d071e7fa4ac62082aeecfe039f5875dec464c Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Tue, 30 Nov 2021 00:30:45 -0600 Subject: [PATCH 1769/1841] Fix 8266 SPI Clock Polarity Setting (#2836) --- esphome/components/spi/spi.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 601a5c5a7e..6c3fd17e56 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -195,7 +195,14 @@ class SPIComponent : public Component { void enable(GPIOPin *cs) { #ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { - uint8_t data_mode = (uint8_t(CLOCK_POLARITY) << 1) | uint8_t(CLOCK_PHASE); + uint8_t data_mode = SPI_MODE0; + if (!CLOCK_POLARITY && CLOCK_PHASE) { + data_mode = SPI_MODE1; + } else if (CLOCK_POLARITY && !CLOCK_PHASE) { + data_mode = SPI_MODE2; + } else if (CLOCK_POLARITY && CLOCK_PHASE) { + data_mode = SPI_MODE3; + } SPISettings settings(DATA_RATE, BIT_ORDER, data_mode); this->hw_spi_->beginTransaction(settings); } else { From ab027a6ae2df3fabb851686efe82b7ea2f350fd9 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 30 Nov 2021 09:35:52 +0100 Subject: [PATCH 1770/1841] Fix too-broad matcher for custom CI script (#2829) --- .github/workflows/matchers/ci-custom.json | 2 +- script/ci-custom.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/matchers/ci-custom.json b/.github/workflows/matchers/ci-custom.json index 9888dbb440..1d5f2551cd 100644 --- a/.github/workflows/matchers/ci-custom.json +++ b/.github/workflows/matchers/ci-custom.json @@ -4,7 +4,7 @@ "owner": "ci-custom", "pattern": [ { - "regexp": "^(.*):(\\d+):(\\d+):\\s+(.*)$", + "regexp": "^(.*):(\\d+):(\\d+):\\s+lint:\\s+(.*)$", "file": 1, "line": 2, "column": 3, diff --git a/script/ci-custom.py b/script/ci-custom.py index de2dfda44d..9acfbcdc23 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -660,7 +660,9 @@ for fname in files: run_checks(LINT_POST_CHECKS, "POST") for f, errs in sorted(errors.items()): - err_str = (f"{styled(colorama.Style.BRIGHT, f'{f}:{lineno}:{col}:')} {msg}\n" for lineno, col, msg in errs) + bold = functools.partial(styled, colorama.Style.BRIGHT) + bold_red = functools.partial(styled, (colorama.Style.BRIGHT, colorama.Fore.RED)) + err_str = (f"{bold(f'{f}:{lineno}:{col}:')} {bold_red('lint:')} {msg}\n" for lineno, col, msg in errs) print_error_for_file(f, "\n".join(err_str)) if args.print_slowest: From 24dfecb6f03e66fa4e88c462d6092c5ba7e61372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Panella?= Date: Tue, 30 Nov 2021 09:08:00 -0600 Subject: [PATCH 1771/1841] cse7766: add energy sensor (#2822) --- esphome/components/cse7766/cse7766.cpp | 16 ++++++++++++++++ esphome/components/cse7766/cse7766.h | 4 ++++ esphome/components/cse7766/sensor.py | 14 ++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index 87bc4c4bdf..55e1ec82cf 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -90,6 +90,7 @@ void CSE7766Component::parse_data_() { uint32_t power_cycle = this->get_24_bit_uint_(17); uint8_t adj = this->raw_data_[20]; + uint32_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22]; bool power_ok = true; bool voltage_ok = true; @@ -127,6 +128,18 @@ void CSE7766Component::parse_data_() { power = power_calib / float(power_cycle); this->power_acc_ += power; this->power_counts_ += 1; + + uint32_t difference; + if (this->cf_pulses_last_ == 0) + this->cf_pulses_last_ = cf_pulses; + + if (cf_pulses < this->cf_pulses_last_) { + difference = cf_pulses + (0x10000 - this->cf_pulses_last_); + } else { + difference = cf_pulses - this->cf_pulses_last_; + } + this->cf_pulses_last_ = cf_pulses; + this->energy_total_ += difference * float(power_calib) / 1000000.0 / 3600.0; } if ((adj & 0x20) == 0x20 && current_ok && voltage_ok && power != 0.0) { @@ -152,6 +165,8 @@ void CSE7766Component::update() { this->current_sensor_->publish_state(current); if (this->power_sensor_ != nullptr) this->power_sensor_->publish_state(power); + if (this->energy_sensor_ != nullptr) + this->energy_sensor_->publish_state(this->energy_total_); this->voltage_acc_ = 0.0f; this->current_acc_ = 0.0f; @@ -172,6 +187,7 @@ void CSE7766Component::dump_config() { LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); LOG_SENSOR(" ", "Current", this->current_sensor_); LOG_SENSOR(" ", "Power", this->power_sensor_); + LOG_SENSOR(" ", "Energy", this->energy_sensor_); this->check_uart_settings(4800); } diff --git a/esphome/components/cse7766/cse7766.h b/esphome/components/cse7766/cse7766.h index 6cacfee072..d6062c251c 100644 --- a/esphome/components/cse7766/cse7766.h +++ b/esphome/components/cse7766/cse7766.h @@ -12,6 +12,7 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice { void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } + void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } void loop() override; float get_setup_priority() const override; @@ -29,9 +30,12 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice { sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *current_sensor_{nullptr}; sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; float voltage_acc_{0.0f}; float current_acc_{0.0f}; float power_acc_{0.0f}; + float energy_total_{0.0f}; + uint32_t cf_pulses_last_{0}; uint32_t voltage_counts_{0}; uint32_t current_counts_{0}; uint32_t power_counts_{0}; diff --git a/esphome/components/cse7766/sensor.py b/esphome/components/cse7766/sensor.py index 1c8efc4f72..2f48aff0aa 100644 --- a/esphome/components/cse7766/sensor.py +++ b/esphome/components/cse7766/sensor.py @@ -3,16 +3,20 @@ import esphome.config_validation as cv from esphome.components import sensor, uart from esphome.const import ( CONF_CURRENT, + CONF_ENERGY, CONF_ID, CONF_POWER, CONF_VOLTAGE, DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, + UNIT_WATT_HOURS, ) DEPENDENCIES = ["uart"] @@ -44,6 +48,12 @@ CONFIG_SCHEMA = ( device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_ENERGY): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), } ) .extend(cv.polling_component_schema("60s")) @@ -71,3 +81,7 @@ async def to_code(config): conf = config[CONF_POWER] sens = await sensor.new_sensor(conf) cg.add(var.set_power_sensor(sens)) + if CONF_ENERGY in config: + conf = config[CONF_ENERGY] + sens = await sensor.new_sensor(conf) + cg.add(var.set_energy_sensor(sens)) From cd018ad3a59e941a2bc45d2757c10dd886187d01 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Tue, 30 Nov 2021 16:12:52 +0100 Subject: [PATCH 1772/1841] Burst read for BME280, to reduce spurious spikes (#2809) --- esphome/components/bme280/bme280.cpp | 33 ++++++++++++++-------------- esphome/components/bme280/bme280.h | 6 ++--- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/esphome/components/bme280/bme280.cpp b/esphome/components/bme280/bme280.cpp index 18386430a2..627072443e 100644 --- a/esphome/components/bme280/bme280.cpp +++ b/esphome/components/bme280/bme280.cpp @@ -33,6 +33,7 @@ static const uint8_t BME280_REGISTER_CONTROLHUMID = 0xF2; static const uint8_t BME280_REGISTER_STATUS = 0xF3; static const uint8_t BME280_REGISTER_CONTROL = 0xF4; static const uint8_t BME280_REGISTER_CONFIG = 0xF5; +static const uint8_t BME280_REGISTER_MEASUREMENTS = 0xF7; static const uint8_t BME280_REGISTER_PRESSUREDATA = 0xF7; static const uint8_t BME280_REGISTER_TEMPDATA = 0xFA; static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD; @@ -178,21 +179,27 @@ void BME280Component::update() { return; } - float meas_time = 1.5; + float meas_time = 1.5f; meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_); meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f; meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f; this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() { + uint8_t data[8]; + if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) { + ESP_LOGW(TAG, "Error reading registers."); + this->status_set_warning(); + return; + } int32_t t_fine = 0; - float temperature = this->read_temperature_(&t_fine); + float temperature = this->read_temperature_(data, &t_fine); if (std::isnan(temperature)) { ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values."); this->status_set_warning(); return; } - float pressure = this->read_pressure_(t_fine); - float humidity = this->read_humidity_(t_fine); + float pressure = this->read_pressure_(data, t_fine); + float humidity = this->read_humidity_(data, t_fine); ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity); if (this->temperature_sensor_ != nullptr) @@ -204,11 +211,8 @@ void BME280Component::update() { this->status_clear_warning(); }); } -float BME280Component::read_temperature_(int32_t *t_fine) { - uint8_t data[3]; - if (!this->read_bytes(BME280_REGISTER_TEMPDATA, data, 3)) - return NAN; - int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); +float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) { + int32_t adc = ((data[3] & 0xFF) << 16) | ((data[4] & 0xFF) << 8) | (data[5] & 0xFF); adc >>= 4; if (adc == 0x80000) // temperature was disabled @@ -226,10 +230,7 @@ float BME280Component::read_temperature_(int32_t *t_fine) { return temperature / 100.0f; } -float BME280Component::read_pressure_(int32_t t_fine) { - uint8_t data[3]; - if (!this->read_bytes(BME280_REGISTER_PRESSUREDATA, data, 3)) - return NAN; +float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) { int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); adc >>= 4; if (adc == 0x80000) @@ -265,9 +266,9 @@ float BME280Component::read_pressure_(int32_t t_fine) { return (p / 256.0f) / 100.0f; } -float BME280Component::read_humidity_(int32_t t_fine) { - uint16_t raw_adc; - if (!this->read_byte_16(BME280_REGISTER_HUMIDDATA, &raw_adc) || raw_adc == 0x8000) +float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) { + uint16_t raw_adc = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF); + if (raw_adc == 0x8000) return NAN; int32_t adc = raw_adc; diff --git a/esphome/components/bme280/bme280.h b/esphome/components/bme280/bme280.h index 82724d6887..8511f73382 100644 --- a/esphome/components/bme280/bme280.h +++ b/esphome/components/bme280/bme280.h @@ -82,11 +82,11 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice { protected: /// Read the temperature value and store the calculated ambient temperature in t_fine. - float read_temperature_(int32_t *t_fine); + float read_temperature_(const uint8_t *data, int32_t *t_fine); /// Read the pressure value in hPa using the provided t_fine value. - float read_pressure_(int32_t t_fine); + float read_pressure_(const uint8_t *data, int32_t t_fine); /// Read the humidity value in % using the provided t_fine value. - float read_humidity_(int32_t t_fine); + float read_humidity_(const uint8_t *data, int32_t t_fine); uint8_t read_u8_(uint8_t a_register); uint16_t read_u16_le_(uint8_t a_register); int16_t read_s16_le_(uint8_t a_register); From 0f47ffd9085ee7655d49b4f567e2d9702337b32f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Nov 2021 16:17:48 +0100 Subject: [PATCH 1773/1841] Bump aioesphomeapi from 10.2.0 to 10.6.0 (#2840) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c4b211283d..c99c5efad9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 esphome-dashboard==20211021.1 -aioesphomeapi==10.2.0 +aioesphomeapi==10.6.0 zeroconf==0.36.13 # esp-idf requires this, but doesn't bundle it by default From b32b918936cc1cdc796de27f2e022b743fa6c421 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 1 Dec 2021 04:18:21 +1300 Subject: [PATCH 1774/1841] Button device class (#2835) --- esphome/components/api/api.proto | 1 + esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 9 ++++++++ esphome/components/api/api_pb2.h | 1 + esphome/components/button/__init__.py | 23 +++++++++++++++++++ esphome/components/button/button.cpp | 7 ++++++ esphome/components/button/button.h | 7 ++++++ esphome/components/mqtt/mqtt_button.cpp | 5 ++++ esphome/components/mqtt/mqtt_button.h | 2 +- esphome/components/restart/button/__init__.py | 6 +++-- esphome/const.py | 6 ++++- 11 files changed, 64 insertions(+), 4 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index eaad4b8d07..3e2c806135 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -960,6 +960,7 @@ message ListEntitiesButtonResponse { string icon = 5; bool disabled_by_default = 6; EntityCategory entity_category = 7; + string device_class = 8; } message ButtonCommandRequest { option (id) = 62; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 22b896a788..8367afc042 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -684,6 +684,7 @@ bool APIConnection::send_button_info(button::Button *button) { msg.icon = button->get_icon(); msg.disabled_by_default = button->is_disabled_by_default(); msg.entity_category = static_cast(button->get_entity_category()); + msg.device_class = button->get_device_class(); return this->send_list_entities_button_response(msg); } void APIConnection::button_command(const ButtonCommandRequest &msg) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 169349d995..b6974de08e 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -4179,6 +4179,10 @@ bool ListEntitiesButtonResponse::decode_length(uint32_t field_id, ProtoLengthDel this->icon = value.as_string(); return true; } + case 8: { + this->device_class = value.as_string(); + return true; + } default: return false; } @@ -4201,6 +4205,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); + buffer.encode_string(8, this->device_class); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesButtonResponse::dump_to(std::string &out) const { @@ -4234,6 +4239,10 @@ void ListEntitiesButtonResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" device_class: "); + out.append("'").append(this->device_class).append("'"); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 82fd8de687..4d1f658910 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1050,6 +1050,7 @@ class ListEntitiesButtonResponse : public ProtoMessage { std::string icon{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; + std::string device_class{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/button/__init__.py b/esphome/components/button/__init__.py index 495a85b6b4..1e248ddf07 100644 --- a/esphome/components/button/__init__.py +++ b/esphome/components/button/__init__.py @@ -4,12 +4,15 @@ from esphome import automation from esphome.automation import maybe_simple_id from esphome.components import mqtt from esphome.const import ( + CONF_DEVICE_CLASS, CONF_ENTITY_CATEGORY, CONF_ICON, CONF_ID, CONF_ON_PRESS, CONF_TRIGGER_ID, CONF_MQTT_ID, + DEVICE_CLASS_RESTART, + DEVICE_CLASS_UPDATE, ) from esphome.core import CORE, coroutine_with_priority from esphome.cpp_helpers import setup_entity @@ -17,6 +20,11 @@ from esphome.cpp_helpers import setup_entity CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True +DEVICE_CLASSES = [ + DEVICE_CLASS_RESTART, + DEVICE_CLASS_UPDATE, +] + button_ns = cg.esphome_ns.namespace("button") Button = button_ns.class_("Button", cg.EntityBase) ButtonPtr = Button.operator("ptr") @@ -27,10 +35,13 @@ ButtonPressTrigger = button_ns.class_( "ButtonPressTrigger", automation.Trigger.template() ) +validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") + BUTTON_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent), + cv.Optional(CONF_DEVICE_CLASS): validate_device_class, cv.Optional(CONF_ON_PRESS): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger), @@ -45,6 +56,7 @@ _UNDEF = object() def button_schema( icon: str = _UNDEF, entity_category: str = _UNDEF, + device_class: str = _UNDEF, ) -> cv.Schema: schema = BUTTON_SCHEMA if icon is not _UNDEF: @@ -57,6 +69,14 @@ def button_schema( ): cv.entity_category } ) + if device_class is not _UNDEF: + schema = schema.extend( + { + cv.Optional( + CONF_DEVICE_CLASS, default=device_class + ): validate_device_class + } + ) return schema @@ -67,6 +87,9 @@ async def setup_button_core_(var, config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + if CONF_DEVICE_CLASS in config: + cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) + if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) diff --git a/esphome/components/button/button.cpp b/esphome/components/button/button.cpp index fe39b1e458..d57b46e9aa 100644 --- a/esphome/components/button/button.cpp +++ b/esphome/components/button/button.cpp @@ -17,5 +17,12 @@ void Button::press() { void Button::add_on_press_callback(std::function &&callback) { this->press_callback_.add(std::move(callback)); } uint32_t Button::hash_base() { return 1495763804UL; } +void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } +std::string Button::get_device_class() { + if (this->device_class_.has_value()) + return *this->device_class_; + return ""; +} + } // namespace button } // namespace esphome diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h index 954afa0ab9..b21a96b8e1 100644 --- a/esphome/components/button/button.h +++ b/esphome/components/button/button.h @@ -36,6 +36,12 @@ class Button : public EntityBase { */ void add_on_press_callback(std::function &&callback); + /// Set the Home Assistant device class (see button::device_class). + void set_device_class(const std::string &device_class); + + /// Get the device class for this button. + std::string get_device_class(); + protected: /** You should implement this virtual method if you want to create your own button. */ @@ -44,6 +50,7 @@ class Button : public EntityBase { uint32_t hash_base() override; CallbackManager press_callback_{}; + optional device_class_{}; }; } // namespace button diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index 5a0d14b648..25ff327cf9 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -30,6 +30,11 @@ void MQTTButtonComponent::dump_config() { LOG_MQTT_COMPONENT(true, true); } +void MQTTButtonComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { + if (!this->button_->get_device_class().empty()) + root[MQTT_DEVICE_CLASS] = this->button_->get_device_class(); +} + std::string MQTTButtonComponent::component_type() const { return "button"; } const EntityBase *MQTTButtonComponent::get_entity() const { return this->button_; } diff --git a/esphome/components/mqtt/mqtt_button.h b/esphome/components/mqtt/mqtt_button.h index a7e60db380..66e4b2609f 100644 --- a/esphome/components/mqtt/mqtt_button.h +++ b/esphome/components/mqtt/mqtt_button.h @@ -23,7 +23,7 @@ class MQTTButtonComponent : public mqtt::MQTTComponent { /// Buttons do not send a state so just return true. bool send_initial_state() override { return true; } - void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override {} + void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; protected: /// "button" component type. diff --git a/esphome/components/restart/button/__init__.py b/esphome/components/restart/button/__init__.py index 257a8e35f7..1a0e9cdc3d 100644 --- a/esphome/components/restart/button/__init__.py +++ b/esphome/components/restart/button/__init__.py @@ -3,15 +3,17 @@ import esphome.config_validation as cv from esphome.components import button from esphome.const import ( CONF_ID, + DEVICE_CLASS_RESTART, ENTITY_CATEGORY_CONFIG, - ICON_RESTART, ) restart_ns = cg.esphome_ns.namespace("restart") RestartButton = restart_ns.class_("RestartButton", button.Button, cg.Component) CONFIG_SCHEMA = ( - button.button_schema(icon=ICON_RESTART, entity_category=ENTITY_CATEGORY_CONFIG) + button.button_schema( + device_class=DEVICE_CLASS_RESTART, entity_category=ENTITY_CATEGORY_CONFIG + ) .extend({cv.GenerateID(): cv.declare_id(RestartButton)}) .extend(cv.COMPONENT_SCHEMA) ) diff --git a/esphome/const.py b/esphome/const.py index 3510e500f5..740e38cf44 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -865,7 +865,6 @@ DEVICE_CLASS_SAFETY = "safety" DEVICE_CLASS_SMOKE = "smoke" DEVICE_CLASS_SOUND = "sound" DEVICE_CLASS_TAMPER = "tamper" -DEVICE_CLASS_UPDATE = "update" DEVICE_CLASS_VIBRATION = "vibration" DEVICE_CLASS_WINDOW = "window" # device classes of both binary_sensor and sensor component @@ -897,6 +896,11 @@ DEVICE_CLASS_TEMPERATURE = "temperature" DEVICE_CLASS_TIMESTAMP = "timestamp" DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds" DEVICE_CLASS_VOLTAGE = "voltage" +# device classes of both binary_sensor and button component +DEVICE_CLASS_UPDATE = "update" +# device classes of button component +DEVICE_CLASS_RESTART = "restart" + # state classes STATE_CLASS_NONE = "" From b5a0e8b2c0d016ea384f4f962e0f0e4c61a8dc85 Mon Sep 17 00:00:00 2001 From: puuu Date: Wed, 1 Dec 2021 00:20:59 +0900 Subject: [PATCH 1775/1841] Implement unit_of_measurement for number component (#2804) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/api/api.proto | 1 + esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 9 +++++++++ esphome/components/api/api_pb2.h | 1 + esphome/components/mqtt/mqtt_number.cpp | 2 ++ esphome/components/number/__init__.py | 4 ++++ esphome/components/number/number.cpp | 9 +++++++++ esphome/components/number/number.h | 9 +++++++++ tests/test5.yaml | 1 + 9 files changed, 37 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 3e2c806135..0f7a5839ab 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -885,6 +885,7 @@ message ListEntitiesNumberResponse { float step = 8; bool disabled_by_default = 9; EntityCategory entity_category = 10; + string unit_of_measurement = 11; } message NumberStateResponse { option (id) = 50; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 8367afc042..b41a7633a8 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -619,6 +619,7 @@ bool APIConnection::send_number_info(number::Number *number) { msg.icon = number->get_icon(); msg.disabled_by_default = number->is_disabled_by_default(); msg.entity_category = static_cast(number->get_entity_category()); + msg.unit_of_measurement = number->traits.get_unit_of_measurement(); msg.min_value = number->traits.get_min_value(); msg.max_value = number->traits.get_max_value(); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index b6974de08e..62cecb7818 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -3780,6 +3780,10 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel this->icon = value.as_string(); return true; } + case 11: { + this->unit_of_measurement = value.as_string(); + return true; + } default: return false; } @@ -3817,6 +3821,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(8, this->step); buffer.encode_bool(9, this->disabled_by_default); buffer.encode_enum(10, this->entity_category); + buffer.encode_string(11, this->unit_of_measurement); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { @@ -3865,6 +3870,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" unit_of_measurement: "); + out.append("'").append(this->unit_of_measurement).append("'"); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 4d1f658910..4866f50c9b 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -957,6 +957,7 @@ class ListEntitiesNumberResponse : public ProtoMessage { float step{0.0f}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; + std::string unit_of_measurement{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 337013055a..bf8b6b39c5 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -43,6 +43,8 @@ void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo root[MQTT_MIN] = traits.get_min_value(); root[MQTT_MAX] = traits.get_max_value(); root[MQTT_STEP] = traits.get_step(); + if (!this->number_->traits.get_unit_of_measurement().empty()) + root[MQTT_UNIT_OF_MEASUREMENT] = this->number_->traits.get_unit_of_measurement(); config.command_topic = true; } diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 1da25caafe..ae15704a91 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_ON_VALUE, CONF_ON_VALUE_RANGE, CONF_TRIGGER_ID, + CONF_UNIT_OF_MEASUREMENT, CONF_MQTT_ID, CONF_VALUE, ) @@ -58,6 +59,7 @@ NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e }, cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), ), + cv.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string_strict, } ) @@ -86,6 +88,8 @@ async def setup_number_core_( cg.add(trigger.set_max(template_)) await automation.build_automation(trigger, [(float, "x")], conf) + if CONF_UNIT_OF_MEASUREMENT in config: + cg.add(var.traits.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT])) if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index 57a5c7c4bd..99a2c04a22 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -41,6 +41,15 @@ void Number::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } +std::string NumberTraits::get_unit_of_measurement() { + if (this->unit_of_measurement_.has_value()) + return *this->unit_of_measurement_; + return ""; +} +void NumberTraits::set_unit_of_measurement(const std::string &unit_of_measurement) { + this->unit_of_measurement_ = unit_of_measurement; +} + uint32_t Number::hash_base() { return 2282307003UL; } } // namespace number diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index ed104fb477..b3214913d9 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -13,6 +13,9 @@ namespace number { if (!(obj)->get_icon().empty()) { \ ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ } \ + if (!(obj)->traits.get_unit_of_measurement().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->traits.get_unit_of_measurement().c_str()); \ + } \ } class Number; @@ -42,10 +45,16 @@ class NumberTraits { void set_step(float step) { step_ = step; } float get_step() const { return step_; } + /// Get the unit of measurement, using the manual override if set. + std::string get_unit_of_measurement(); + /// Manually set the unit of measurement. + void set_unit_of_measurement(const std::string &unit_of_measurement); + protected: float min_value_ = NAN; float max_value_ = NAN; float step_ = NAN; + optional unit_of_measurement_; ///< Unit of measurement override }; /** Base-class for all numbers. diff --git a/tests/test5.yaml b/tests/test5.yaml index f1fb786fe5..708db55044 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -97,6 +97,7 @@ number: max_value: 100 min_value: 0 step: 5 + unit_of_measurement: '%' select: - platform: template From d9513e5ff2905d654a35249722df4528158a6bc3 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 1 Dec 2021 08:11:38 +1300 Subject: [PATCH 1776/1841] Number mode (#2838) --- esphome/components/api/api.proto | 6 ++++++ esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 21 +++++++++++++++++++++ esphome/components/api/api_pb2.h | 6 ++++++ esphome/components/mqtt/mqtt_const.h | 1 + esphome/components/mqtt/mqtt_number.cpp | 10 ++++++++++ esphome/components/number/__init__.py | 12 ++++++++++++ esphome/components/number/number.h | 11 +++++++++++ tests/test5.yaml | 3 ++- 9 files changed, 70 insertions(+), 1 deletion(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 0f7a5839ab..dca722dca5 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -869,6 +869,11 @@ message ClimateCommandRequest { } // ==================== NUMBER ==================== +enum NumberMode { + NUMBER_MODE_AUTO = 0; + NUMBER_MODE_BOX = 1; + NUMBER_MODE_SLIDER = 2; +} message ListEntitiesNumberResponse { option (id) = 49; option (source) = SOURCE_SERVER; @@ -886,6 +891,7 @@ message ListEntitiesNumberResponse { bool disabled_by_default = 9; EntityCategory entity_category = 10; string unit_of_measurement = 11; + NumberMode mode = 12; } message NumberStateResponse { option (id) = 50; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index b41a7633a8..92699df0da 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -620,6 +620,7 @@ bool APIConnection::send_number_info(number::Number *number) { msg.disabled_by_default = number->is_disabled_by_default(); msg.entity_category = static_cast(number->get_entity_category()); msg.unit_of_measurement = number->traits.get_unit_of_measurement(); + msg.mode = static_cast(number->traits.get_mode()); msg.min_value = number->traits.get_min_value(); msg.max_value = number->traits.get_max_value(); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 62cecb7818..9228be0860 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -266,6 +266,18 @@ template<> const char *proto_enum_to_string(enums::Climate return "UNKNOWN"; } } +template<> const char *proto_enum_to_string(enums::NumberMode value) { + switch (value) { + case enums::NUMBER_MODE_AUTO: + return "NUMBER_MODE_AUTO"; + case enums::NUMBER_MODE_BOX: + return "NUMBER_MODE_BOX"; + case enums::NUMBER_MODE_SLIDER: + return "NUMBER_MODE_SLIDER"; + default: + return "UNKNOWN"; + } +} bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -3758,6 +3770,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->entity_category = value.as_enum(); return true; } + case 12: { + this->mode = value.as_enum(); + return true; + } default: return false; } @@ -3822,6 +3838,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(9, this->disabled_by_default); buffer.encode_enum(10, this->entity_category); buffer.encode_string(11, this->unit_of_measurement); + buffer.encode_enum(12, this->mode); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { @@ -3874,6 +3891,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append(" unit_of_measurement: "); out.append("'").append(this->unit_of_measurement).append("'"); out.append("\n"); + + out.append(" mode: "); + out.append(proto_enum_to_string(this->mode)); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 4866f50c9b..e92b2fa4b6 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -123,6 +123,11 @@ enum ClimatePreset : uint32_t { CLIMATE_PRESET_SLEEP = 6, CLIMATE_PRESET_ACTIVITY = 7, }; +enum NumberMode : uint32_t { + NUMBER_MODE_AUTO = 0, + NUMBER_MODE_BOX = 1, + NUMBER_MODE_SLIDER = 2, +}; } // namespace enums @@ -958,6 +963,7 @@ class ListEntitiesNumberResponse : public ProtoMessage { bool disabled_by_default{false}; enums::EntityCategory entity_category{}; std::string unit_of_measurement{}; + enums::NumberMode mode{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h index 1d5e22efde..8134a6b53e 100644 --- a/esphome/components/mqtt/mqtt_const.h +++ b/esphome/components/mqtt/mqtt_const.h @@ -514,6 +514,7 @@ constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area"; // Additional MQTT fields where no abbreviation is defined in HA source constexpr const char *const MQTT_ENTITY_CATEGORY = "entity_category"; +constexpr const char *const MQTT_MODE = "mode"; } // namespace mqtt } // namespace esphome diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index bf8b6b39c5..18e3a61417 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -45,6 +45,16 @@ void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo root[MQTT_STEP] = traits.get_step(); if (!this->number_->traits.get_unit_of_measurement().empty()) root[MQTT_UNIT_OF_MEASUREMENT] = this->number_->traits.get_unit_of_measurement(); + switch (this->number_->traits.get_mode()) { + case NUMBER_MODE_AUTO: + break; + case NUMBER_MODE_BOX: + root[MQTT_MODE] = "box"; + break; + case NUMBER_MODE_SLIDER: + root[MQTT_MODE] = "slider"; + break; + } config.command_topic = true; } diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index ae15704a91..71e288a4cc 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -7,6 +7,7 @@ from esphome.const import ( CONF_ABOVE, CONF_BELOW, CONF_ID, + CONF_MODE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, CONF_TRIGGER_ID, @@ -40,6 +41,14 @@ NumberInRangeCondition = number_ns.class_( "NumberInRangeCondition", automation.Condition ) +NumberMode = number_ns.enum("NumberMode") + +NUMBER_MODES = { + "AUTO": NumberMode.NUMBER_MODE_AUTO, + "BOX": NumberMode.NUMBER_MODE_BOX, + "SLIDER": NumberMode.NUMBER_MODE_SLIDER, +} + icon = cv.icon NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( @@ -60,6 +69,7 @@ NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), ), cv.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string_strict, + cv.Optional(CONF_MODE, default="AUTO"): cv.enum(NUMBER_MODES, upper=True), } ) @@ -74,6 +84,8 @@ async def setup_number_core_( if step is not None: cg.add(var.traits.set_step(step)) + cg.add(var.traits.set_mode(config[CONF_MODE])) + for conf in config.get(CONF_ON_VALUE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [(float, "x")], conf) diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index b3214913d9..40fdfceec1 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -36,6 +36,12 @@ class NumberCall { optional value_; }; +enum NumberMode : uint8_t { + NUMBER_MODE_AUTO = 0, + NUMBER_MODE_BOX = 1, + NUMBER_MODE_SLIDER = 2, +}; + class NumberTraits { public: void set_min_value(float min_value) { min_value_ = min_value; } @@ -50,11 +56,16 @@ class NumberTraits { /// Manually set the unit of measurement. void set_unit_of_measurement(const std::string &unit_of_measurement); + // Get/set the frontend mode. + NumberMode get_mode() const { return this->mode_; } + void set_mode(NumberMode mode) { this->mode_ = mode; } + protected: float min_value_ = NAN; float max_value_ = NAN; float step_ = NAN; optional unit_of_measurement_; ///< Unit of measurement override + NumberMode mode_{NUMBER_MODE_AUTO}; }; /** Base-class for all numbers. diff --git a/tests/test5.yaml b/tests/test5.yaml index 708db55044..aa3d057252 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -10,7 +10,7 @@ esp32: framework: type: esp-idf advanced: - ignore_efuse_mac_crc: true + ignore_efuse_mac_crc: true wifi: networks: @@ -98,6 +98,7 @@ number: min_value: 0 step: 5 unit_of_measurement: '%' + mode: slider select: - platform: template From 5719cc1a248f8be62fbfa3a4551a18b4194e52fe Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 1 Dec 2021 16:54:30 +1300 Subject: [PATCH 1777/1841] Bump esphome-dashboard to 20211201.0 (#2842) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c99c5efad9..6061476802 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20211021.1 +esphome-dashboard==20211201.0 aioesphomeapi==10.6.0 zeroconf==0.36.13 From 08cbb97ec9bfd7b5dec872b3cb25bc7c2b9684ca Mon Sep 17 00:00:00 2001 From: mechanarchy <1166756+mechanarchy@users.noreply.github.com> Date: Wed, 1 Dec 2021 15:10:25 +1100 Subject: [PATCH 1778/1841] Allow Git credentials to be loaded from secrets (#2825) --- .../components/external_components/__init__.py | 6 ++++++ esphome/components/packages/__init__.py | 6 ++++++ esphome/git.py | 15 ++++++++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index e0548e8981..d0153f6104 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -12,6 +12,8 @@ from esphome.const import ( CONF_TYPE, CONF_EXTERNAL_COMPONENTS, CONF_PATH, + CONF_USERNAME, + CONF_PASSWORD, ) from esphome.core import CORE from esphome import git, loader @@ -27,6 +29,8 @@ TYPE_LOCAL = "local" GIT_SCHEMA = { cv.Required(CONF_URL): cv.url, cv.Optional(CONF_REF): cv.git_ref, + cv.Optional(CONF_USERNAME): cv.string, + cv.Optional(CONF_PASSWORD): cv.string, } LOCAL_SCHEMA = { cv.Required(CONF_PATH): cv.directory, @@ -99,6 +103,8 @@ def _process_git_config(config: dict, refresh) -> str: ref=config.get(CONF_REF), refresh=refresh, domain=DOMAIN, + username=config.get(CONF_USERNAME), + password=config.get(CONF_PASSWORD), ) if (repo_dir / "esphome" / "components").is_dir(): diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index df0f0de13d..7483d65b9d 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -10,6 +10,8 @@ from esphome.const import ( CONF_REF, CONF_REFRESH, CONF_URL, + CONF_USERNAME, + CONF_PASSWORD, ) import esphome.config_validation as cv @@ -93,6 +95,8 @@ BASE_SCHEMA = cv.All( cv.Schema( { cv.Required(CONF_URL): cv.url, + cv.Optional(CONF_USERNAME): cv.string, + cv.Optional(CONF_PASSWORD): cv.string, cv.Exclusive(CONF_FILE, "files"): validate_yaml_filename, cv.Exclusive(CONF_FILES, "files"): cv.All( cv.ensure_list(validate_yaml_filename), @@ -124,6 +128,8 @@ def _process_base_package(config: dict) -> dict: ref=config.get(CONF_REF), refresh=config[CONF_REFRESH], domain=DOMAIN, + username=config.get(CONF_USERNAME), + password=config.get(CONF_PASSWORD), ) files: str = config[CONF_FILES] diff --git a/esphome/git.py b/esphome/git.py index 25d893b2f5..64c8d6a6b7 100644 --- a/esphome/git.py +++ b/esphome/git.py @@ -2,6 +2,7 @@ from pathlib import Path import subprocess import hashlib import logging +import urllib.parse from datetime import datetime @@ -36,9 +37,21 @@ def _compute_destination_path(key: str, domain: str) -> Path: def clone_or_update( - *, url: str, ref: str = None, refresh: TimePeriodSeconds, domain: str + *, + url: str, + ref: str = None, + refresh: TimePeriodSeconds, + domain: str, + username: str = None, + password: str = None, ) -> Path: key = f"{url}@{ref}" + + if username is not None and password is not None: + url = url.replace( + "://", f"://{urllib.parse.quote(username)}:{urllib.parse.quote(password)}@" + ) + repo_dir = _compute_destination_path(key, domain) fetch_pr_branch = ref is not None and ref.startswith("pull/") if not repo_dir.is_dir(): From cbc1334b8dd5e541fa7f934cc613675625c90934 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 05:11:21 +0100 Subject: [PATCH 1779/1841] Fix compile warning in Tuya automations (#2837) --- esphome/components/tuya/automation.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/tuya/automation.cpp b/esphome/components/tuya/automation.cpp index eb1c170083..a8cfd098f1 100644 --- a/esphome/components/tuya/automation.cpp +++ b/esphome/components/tuya/automation.cpp @@ -9,7 +9,8 @@ namespace tuya { void check_expected_datapoint(const TuyaDatapoint &dp, TuyaDatapointType expected) { if (dp.type != expected) { - ESP_LOGW(TAG, "Tuya sensor %u expected datapoint type %#02hhX but got %#02hhX", dp.id, expected, dp.type); + ESP_LOGW(TAG, "Tuya sensor %u expected datapoint type %#02hhX but got %#02hhX", dp.id, + static_cast(expected), static_cast(dp.type)); } } From bfeb0b36397661e72c5441c1a399357f590ad2ca Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 05:12:14 +0100 Subject: [PATCH 1780/1841] Add problem matcher for Python formatting errors (#2833) --- .github/workflows/matchers/lint-python.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/matchers/lint-python.json b/.github/workflows/matchers/lint-python.json index e88f74f6f6..6a09f04770 100644 --- a/.github/workflows/matchers/lint-python.json +++ b/.github/workflows/matchers/lint-python.json @@ -1,5 +1,16 @@ { "problemMatcher": [ + { + "owner": "black", + "severity": "error", + "pattern": [ + { + "regexp": "^(.*): (Please format this file with the black formatter)", + "file": 1, + "message": 2 + } + ] + }, { "owner": "flake8", "severity": "error", From c9190574a95de84bfca91e017944c4ccd8dd2001 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 05:14:25 +0100 Subject: [PATCH 1781/1841] Fix CI check for Windows line endings (#2831) --- script/ci-custom.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/script/ci-custom.py b/script/ci-custom.py index 9acfbcdc23..52ac4025ca 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -20,7 +20,7 @@ def find_all(a_str, sub): # Optimization: If str is not in whole text, then do not try # on each line return - for i, line in enumerate(a_str.splitlines()): + for i, line in enumerate(a_str.split('\n')): column = 0 while True: column = line.find(sub, column) @@ -172,7 +172,7 @@ def lint_re_check(regex, **kwargs): return decorator -def lint_content_find_check(find, **kwargs): +def lint_content_find_check(find, only_first=False, **kwargs): decor = lint_content_check(**kwargs) def decorator(func): @@ -185,6 +185,8 @@ def lint_content_find_check(find, **kwargs): for line, col in find_all(content, find_): err = func(fname) errors.append((line + 1, col + 1, err)) + if only_first: + break return errors return decor(new_func) @@ -234,6 +236,7 @@ def lint_executable_bit(fname): @lint_content_find_check( "\t", + only_first=True, exclude=[ "esphome/dashboard/static/ace.js", "esphome/dashboard/static/ext-searchbox.js", @@ -243,9 +246,9 @@ def lint_tabs(fname): return "File contains tab character. Please convert tabs to spaces." -@lint_content_find_check("\r") +@lint_content_find_check("\r", only_first=True) def lint_newline(fname): - return "File contains windows newline. Please set your editor to unix newline mode." + return "File contains Windows newline. Please set your editor to Unix newline mode." @lint_content_check(exclude=["*.svg"]) From ca8db7696e2965c6ffffad1c462891ad1ca4e0ab Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 05:21:19 +0100 Subject: [PATCH 1782/1841] Don't enable namespace comment clang-tidy check twice (#2830) --- .clang-tidy | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 1c7e65b762..05858c8e52 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -51,6 +51,7 @@ Checks: >- -google-explicit-constructor, -google-readability-braces-around-statements, -google-readability-casting, + -google-readability-namespace-comments, -google-readability-todo, -google-runtime-references, -hicpp-*, @@ -97,12 +98,12 @@ CheckOptions: value: '1' - key: google-readability-function-size.StatementThreshold value: '800' - - key: google-readability-namespace-comments.ShortNamespaceLines - value: '10' - - key: google-readability-namespace-comments.SpacesBeforeComments - value: '2' - key: google-runtime-int.TypeSuffix value: '_t' + - key: llvm-namespace-comment.ShortNamespaceLines + value: '10' + - key: llvm-namespace-comment.SpacesBeforeComments + value: '2' - key: modernize-loop-convert.MaxCopySize value: '16' - key: modernize-loop-convert.MinConfidence From 1ec3140759f2a6c124b88a6a7318ad750f47e56c Mon Sep 17 00:00:00 2001 From: Yuval Brik Date: Wed, 1 Dec 2021 10:38:58 +0200 Subject: [PATCH 1783/1841] ESP32 Deep Sleep: correct level value (#2812) Upon registering for ESP32 deep sleep, DeepSleepComponent::begin_sleep calculates the level value to wake up on. As part of PR #2303, the level was changed to be based on `inverted` instead of `!inverted`: Before: https://github.com/esphome/esphome/blob/1e8e471dec19ceafba1997b1d9663f7912f244a2/esphome/components/deep_sleep/deep_sleep_component.cpp#L76 After: https://github.com/esphome/esphome/blob/2b04152482da3e9faaa4f6d0fd3370134d792fd1/esphome/components/deep_sleep/deep_sleep_component.cpp#L80 The level argument to `esp_sleep_enable_ext0_wakeup(pin, level)` [0] should be 0 when the inverted property is true (low triggers wakeup), and 1 when inverted property is false (high triggers wakeup). Also revert the changes of #2644. [0] https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/sleep_modes.html#_CPPv428esp_sleep_enable_ext0_wakeup10gpio_num_ti --- esphome/components/deep_sleep/deep_sleep_component.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 0998a57af3..c854b6da6e 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -77,8 +77,8 @@ void DeepSleepComponent::begin_sleep(bool manual) { if (this->sleep_duration_.has_value()) esp_sleep_enable_timer_wakeup(*this->sleep_duration_); if (this->wakeup_pin_ != nullptr) { - bool level = this->wakeup_pin_->is_inverted(); - if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && !this->wakeup_pin_->digital_read()) { + bool level = !this->wakeup_pin_->is_inverted(); + if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) { level = !level; } esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level); From 24a5325db35a49d0aa3c4a9a8b0258da12c4688d Mon Sep 17 00:00:00 2001 From: Mark Dietzer Date: Wed, 1 Dec 2021 01:01:15 -0800 Subject: [PATCH 1784/1841] Declare arch_get_cpu_cycle_count for esp8266 as IRAM (#2843) --- esphome/components/esp8266/core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp index 51f3ca50ec..137d4382b4 100644 --- a/esphome/components/esp8266/core.cpp +++ b/esphome/components/esp8266/core.cpp @@ -27,7 +27,7 @@ void IRAM_ATTR HOT arch_feed_wdt() { uint8_t progmem_read_byte(const uint8_t *addr) { return pgm_read_byte(addr); // NOLINT } -uint32_t arch_get_cpu_cycle_count() { +uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { return ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) } uint32_t arch_get_cpu_freq_hz() { return F_CPU; } From fbe1bca1b9896ba8c8b754c5a4faf790bffd887b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 17:37:24 +0100 Subject: [PATCH 1785/1841] Fix compilation using subprocesses (#2834) --- esphome/util.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/esphome/util.py b/esphome/util.py index 937635fa43..b2ba0c22c3 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -219,24 +219,23 @@ def run_external_process(*cmd, **kwargs): capture_stdout = kwargs.get("capture_stdout", False) if capture_stdout: - sub_stdout = io.BytesIO() + sub_stdout = subprocess.PIPE else: sub_stdout = RedirectText(sys.stdout, filter_lines=filter_lines) sub_stderr = RedirectText(sys.stderr, filter_lines=filter_lines) try: - return subprocess.call(cmd, stdout=sub_stdout, stderr=sub_stderr) + proc = subprocess.run( + cmd, stdout=sub_stdout, stderr=sub_stderr, encoding="utf-8", check=False + ) + return proc.stdout if capture_stdout else proc.returncode except KeyboardInterrupt: # pylint: disable=try-except-raise raise except Exception as err: # pylint: disable=broad-except _LOGGER.error("Running command failed: %s", err) _LOGGER.error("Please try running %s locally.", full_cmd) return 1 - finally: - if capture_stdout: - # pylint: disable=lost-exception - return sub_stdout.getvalue() def is_dev_esphome_version(): From 11330af05f0b5da70333d299b2f080518e7ae0ab Mon Sep 17 00:00:00 2001 From: Leon Loopik <489021+Lewn@users.noreply.github.com> Date: Wed, 1 Dec 2021 20:31:04 +0100 Subject: [PATCH 1786/1841] Expand uart invert feature to ESP8266 (#1727) --- esphome/components/uart/__init__.py | 15 +++++++++++++++ .../components/uart/uart_component_esp8266.cpp | 5 +++++ tests/test3.yaml | 4 +++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 61b54044d7..a63b220fc7 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -14,6 +14,7 @@ from esphome.const import ( CONF_UART_ID, CONF_DATA, CONF_RX_BUFFER_SIZE, + CONF_INVERTED, CONF_INVERT, CONF_TRIGGER_ID, CONF_SEQUENCE, @@ -67,6 +68,19 @@ def validate_rx_pin(value): return value +def validate_invert_esp32(config): + if ( + CORE.is_esp32 + and CONF_TX_PIN in config + and CONF_RX_PIN in config + and config[CONF_TX_PIN][CONF_INVERTED] != config[CONF_RX_PIN][CONF_INVERTED] + ): + raise cv.Invalid( + "Different invert values for TX and RX pin are not (yet) supported for ESP32." + ) + return config + + def _uart_declare_type(value): if CORE.is_esp8266: return cv.declare_id(ESP8266UartComponent)(value) @@ -162,6 +176,7 @@ CONFIG_SCHEMA = cv.All( } ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN), + validate_invert_esp32, ) diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index c367de05bb..408c83a0db 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -45,6 +45,11 @@ uint32_t ESP8266UartComponent::get_config() { else config |= UART_NB_STOP_BIT_2; + if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) + config |= BIT(22); + if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) + config |= BIT(19); + return config; } diff --git a/tests/test3.yaml b/tests/test3.yaml index 8ae4a383e0..50cd6d6cf6 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -227,7 +227,9 @@ spi: uart: - id: uart1 - tx_pin: GPIO1 + tx_pin: + number: GPIO1 + inverted: yes rx_pin: GPIO3 baud_rate: 115200 - id: uart2 From f58828cb8219d0e4e17e2fc66786eedd987ff37b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 20:55:27 +0100 Subject: [PATCH 1787/1841] Support setting manual_ip under networks option (#2839) --- esphome/components/wifi/__init__.py | 19 ++++++++++++++++--- tests/test5.yaml | 4 ++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 7a9319f5e0..a24791b458 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -221,10 +221,22 @@ def _validate(config): raise cv.Invalid("Fast connect can only be used with one network!") if CONF_USE_ADDRESS not in config: + use_address = CORE.name + config[CONF_DOMAIN] if CONF_MANUAL_IP in config: use_address = str(config[CONF_MANUAL_IP][CONF_STATIC_IP]) - else: - use_address = CORE.name + config[CONF_DOMAIN] + elif CONF_NETWORKS in config: + ips = set( + str(net[CONF_MANUAL_IP][CONF_STATIC_IP]) + for net in config[CONF_NETWORKS] + if CONF_MANUAL_IP in net + ) + if len(ips) > 1: + raise cv.Invalid( + "Must specify use_address when using multiple static IP addresses." + ) + if len(ips) == 1: + use_address = next(iter(ips)) + config[CONF_USE_ADDRESS] = use_address return config @@ -334,7 +346,8 @@ async def to_code(config): cg.add(var.set_use_address(config[CONF_USE_ADDRESS])) for network in config.get(CONF_NETWORKS, []): - cg.add(var.add_sta(wifi_network(network, config.get(CONF_MANUAL_IP)))) + ip_config = network.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP)) + cg.add(var.add_sta(wifi_network(network, ip_config))) if CONF_AP in config: conf = config[CONF_AP] diff --git a/tests/test5.yaml b/tests/test5.yaml index aa3d057252..37e65e7da2 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -16,6 +16,10 @@ wifi: networks: - ssid: 'MySSID' password: 'password1' + manual_ip: + static_ip: 192.168.1.23 + gateway: 192.168.1.1 + subnet: 255.255.255.0 api: From 607601b3a4be5f8e5941d59744f438177c015815 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 21:03:51 +0100 Subject: [PATCH 1788/1841] Enable a bunch of clang-tidy checks (#2149) --- .clang-tidy | 8 +- .../adalight/adalight_light_effect.cpp | 2 +- .../adalight/adalight_light_effect.h | 2 +- esphome/components/aht10/aht10.cpp | 4 +- esphome/components/am2320/am2320.cpp | 4 +- esphome/components/anova/anova_base.cpp | 8 +- esphome/components/api/api_connection.cpp | 2 +- esphome/components/api/api_frame_helper.cpp | 22 +---- esphome/components/api/api_pb2.cpp | 92 +++++++++---------- .../captive_portal/captive_portal.cpp | 9 +- esphome/components/cs5460a/cs5460a.cpp | 2 - esphome/components/cse7766/cse7766.cpp | 6 +- esphome/components/daikin/daikin.cpp | 2 +- esphome/components/display/display_buffer.cpp | 6 +- esphome/components/dsmr/dsmr.h | 4 +- esphome/components/esp8266/gpio.cpp | 2 +- esphome/components/esp8266/preferences.cpp | 4 +- esphome/components/ezo/ezo.cpp | 2 +- esphome/components/graph/graph.cpp | 16 ++-- .../hitachi_ac344/hitachi_ac344.cpp | 4 +- .../hitachi_ac424/hitachi_ac424.cpp | 4 +- .../components/ili9341/ili9341_display.cpp | 4 +- .../improv_serial/improv_serial_component.cpp | 2 +- .../components/lcd_gpio/gpio_lcd_display.cpp | 2 +- .../light/addressable_light_effect.h | 4 +- esphome/components/light/light_call.cpp | 2 +- esphome/components/ltr390/ltr390.cpp | 2 +- esphome/components/max31865/max31865.cpp | 12 +-- .../components/max7219digit/max7219digit.cpp | 6 +- esphome/components/mcp23s08/mcp23s08.cpp | 1 - esphome/components/mcp2515/mcp2515.cpp | 3 - .../binary_sensor/modbus_binarysensor.cpp | 2 - .../modbus_controller/modbus_controller.h | 4 +- .../number/modbus_number.cpp | 11 --- .../output/modbus_output.cpp | 5 - .../sensor/modbus_sensor.cpp | 5 - esphome/components/nextion/nextion.cpp | 24 ++--- esphome/components/nextion/nextion_upload.cpp | 4 +- .../nextion/sensor/nextion_sensor.cpp | 2 +- esphome/components/nfc/ndef_message.cpp | 2 +- esphome/components/nfc/nfc.cpp | 4 +- esphome/components/ota/ota_component.cpp | 28 +++--- esphome/components/pid/pid_autotuner.cpp | 2 +- esphome/components/pipsolar/pipsolar.cpp | 6 +- esphome/components/pn532/pn532.cpp | 4 +- .../pulse_meter/pulse_meter_sensor.cpp | 2 +- esphome/components/rc522/rc522.cpp | 4 +- .../remote_base/pronto_protocol.cpp | 2 +- .../components/remote_base/remote_base.cpp | 2 +- .../remote_transmitter_esp32.cpp | 2 +- esphome/components/rtttl/rtttl.cpp | 2 +- esphome/components/sgp30/sgp30.cpp | 4 +- .../sgp40/sensirion_voc_algorithm.cpp | 2 +- esphome/components/sgp40/sgp40.cpp | 4 +- esphome/components/sgp40/sgp40.h | 2 +- esphome/components/sm300d2/sm300d2.cpp | 8 +- .../components/socket/lwip_raw_tcp_impl.cpp | 4 +- esphome/components/spi/spi.cpp | 8 +- .../components/ssd1306_base/ssd1306_base.cpp | 2 +- .../components/ssd1306_i2c/ssd1306_i2c.cpp | 4 +- .../components/ssd1306_spi/ssd1306_spi.cpp | 4 +- esphome/components/st7735/st7735.cpp | 4 +- esphome/components/st7920/st7920.cpp | 2 +- esphome/components/teleinfo/teleinfo.cpp | 2 +- esphome/components/text_sensor/filter.cpp | 2 +- .../components/tof10120/tof10120_sensor.cpp | 2 +- esphome/components/toshiba/toshiba.cpp | 4 +- esphome/components/tuya/tuya.cpp | 4 +- esphome/components/tx20/tx20.cpp | 2 +- .../uart/uart_component_esp8266.cpp | 4 +- .../waveshare_epaper/waveshare_epaper.cpp | 41 ++++----- .../waveshare_epaper/waveshare_epaper.h | 6 +- .../wifi/wifi_component_esp_idf.cpp | 10 +- esphome/components/xiaomi_ble/xiaomi_ble.cpp | 6 +- esphome/core/application.cpp | 2 +- esphome/core/application.h | 2 +- esphome/core/component.cpp | 2 +- platformio.ini | 3 +- script/api_protobuf/api_protobuf.py | 2 +- 79 files changed, 206 insertions(+), 296 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 05858c8e52..b40e606121 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -5,11 +5,8 @@ Checks: >- -altera-*, -android-*, -boost-*, - -bugprone-branch-clone, - -bugprone-easily-swappable-parameters, -bugprone-narrowing-conversions, -bugprone-signed-char-misuse, - -bugprone-too-small-loop-variable, -cert-dcl50-cpp, -cert-err58-cpp, -cert-oop57-cpp, @@ -19,12 +16,10 @@ Checks: >- -clang-diagnostic-delete-abstract-non-virtual-dtor, -clang-diagnostic-delete-non-abstract-non-virtual-dtor, -clang-diagnostic-shadow-field, - -clang-diagnostic-sign-compare, - -clang-diagnostic-unused-variable, -clang-diagnostic-unused-const-variable, + -clang-diagnostic-unused-parameter, -concurrency-*, -cppcoreguidelines-avoid-c-arrays, - -cppcoreguidelines-avoid-goto, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-init-variables, -cppcoreguidelines-macro-usage, @@ -41,7 +36,6 @@ Checks: >- -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-special-member-functions, - -fuchsia-default-arguments, -fuchsia-multiple-inheritance, -fuchsia-overloaded-operator, -fuchsia-statically-constructed-objects, diff --git a/esphome/components/adalight/adalight_light_effect.cpp b/esphome/components/adalight/adalight_light_effect.cpp index d9c2892d21..35e98d7360 100644 --- a/esphome/components/adalight/adalight_light_effect.cpp +++ b/esphome/components/adalight/adalight_light_effect.cpp @@ -25,7 +25,7 @@ void AdalightLightEffect::stop() { AddressableLightEffect::stop(); } -int AdalightLightEffect::get_frame_size_(int led_count) const { +unsigned int AdalightLightEffect::get_frame_size_(int led_count) const { // 3 bytes: Ada // 2 bytes: LED count // 1 byte: checksum diff --git a/esphome/components/adalight/adalight_light_effect.h b/esphome/components/adalight/adalight_light_effect.h index c1df55659b..b757191864 100644 --- a/esphome/components/adalight/adalight_light_effect.h +++ b/esphome/components/adalight/adalight_light_effect.h @@ -25,7 +25,7 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U CONSUMED, }; - int get_frame_size_(int led_count) const; + unsigned int get_frame_size_(int led_count) const; void reset_frame_(light::AddressableLight &it); void blank_all_leds_(light::AddressableLight &it); Frame parse_frame_(light::AddressableLight &it); diff --git a/esphome/components/aht10/aht10.cpp b/esphome/components/aht10/aht10.cpp index 78f98cb14f..3c690c39b5 100644 --- a/esphome/components/aht10/aht10.cpp +++ b/esphome/components/aht10/aht10.cpp @@ -110,12 +110,12 @@ void AHT10Component::update() { uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]; uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4; - float temperature = ((200.0 * (float) raw_temperature) / 1048576.0) - 50.0; + float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f; float humidity; if (raw_humidity == 0) { // unrealistic value humidity = NAN; } else { - humidity = (float) raw_humidity * 100.0 / 1048576.0; + humidity = (float) raw_humidity * 100.0f / 1048576.0f; } if (this->temperature_sensor_ != nullptr) { diff --git a/esphome/components/am2320/am2320.cpp b/esphome/components/am2320/am2320.cpp index b53eb69464..c06a2a34d7 100644 --- a/esphome/components/am2320/am2320.cpp +++ b/esphome/components/am2320/am2320.cpp @@ -38,9 +38,9 @@ void AM2320Component::update() { return; } - float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0; + float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0f; temperature = (data[4] & 0x80) ? -temperature : temperature; - float humidity = ((data[2] << 8) + data[3]) / 10.0; + float humidity = ((data[2] << 8) + data[3]) / 10.0f; ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity); if (this->temperature_sensor_ != nullptr) diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp index cb877bef35..ce4febbe37 100644 --- a/esphome/components/anova/anova_base.cpp +++ b/esphome/components/anova/anova_base.cpp @@ -103,13 +103,7 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) { } break; } - case READ_TARGET_TEMPERATURE: { - this->target_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f); - if (this->fahrenheit_) - this->target_temp_ = ftoc(this->target_temp_); - this->has_target_temp_ = true; - break; - } + case READ_TARGET_TEMPERATURE: case SET_TARGET_TEMPERATURE: { this->target_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f); if (this->fahrenheit_) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 92699df0da..f615815023 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -132,7 +132,7 @@ void APIConnection::loop() { if (state_subs_at_ != -1) { const auto &subs = this->parent_->get_state_subs(); - if (state_subs_at_ >= subs.size()) { + if (state_subs_at_ >= (int) subs.size()) { state_subs_at_ = -1; } else { auto &it = subs[state_subs_at_]; diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index c0e37ec90d..23766ec1b1 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -174,9 +174,6 @@ APIError APINoiseFrameHelper::loop() { * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase. */ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { - int err; - APIError aerr; - if (frame == nullptr) { HELPER_LOG("Bad argument for try_read_frame_"); return APIError::BAD_ARG; @@ -200,7 +197,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { return APIError::CONNECTION_CLOSED; } rx_header_buf_len_ += received; - if (received != to_read) { + if ((size_t) received != to_read) { // not a full read return APIError::WOULD_BLOCK; } @@ -247,7 +244,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { return APIError::CONNECTION_CLOSED; } rx_buf_len_ += received; - if (received != to_read) { + if ((size_t) received != to_read) { // not all read return APIError::WOULD_BLOCK; } @@ -544,7 +541,6 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() { APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { if (iovcnt == 0) return APIError::OK; - int err; APIError aerr; size_t total_write_len = 0; @@ -584,7 +580,7 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); return APIError::SOCKET_WRITE_FAILED; - } else if (sent != total_write_len) { + } else if ((size_t) sent != total_write_len) { // partially sent, add end to tx_buf size_t to_consume = sent; for (int i = 0; i < iovcnt; i++) { @@ -778,9 +774,6 @@ APIError APIPlaintextFrameHelper::loop() { * error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame. */ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { - int err; - APIError aerr; - if (frame == nullptr) { HELPER_LOG("Bad argument for try_read_frame_"); return APIError::BAD_ARG; @@ -854,7 +847,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { return APIError::CONNECTION_CLOSED; } rx_buf_len_ += received; - if (received != to_read) { + if ((size_t) received != to_read) { // not all read return APIError::WOULD_BLOCK; } @@ -874,7 +867,6 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { } APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { - int err; APIError aerr; if (state_ != State::DATA) { @@ -894,9 +886,6 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { } bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) { - int err; - APIError aerr; - if (state_ != State::DATA) { return APIError::BAD_STATE; } @@ -940,7 +929,6 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() { APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { if (iovcnt == 0) return APIError::OK; - int err; APIError aerr; size_t total_write_len = 0; @@ -980,7 +968,7 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); return APIError::SOCKET_WRITE_FAILED; - } else if (sent != total_write_len) { + } else if ((size_t) sent != total_write_len) { // partially sent, add end to tx_buf size_t to_consume = sent; for (int i = 0; i < iovcnt; i++) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 9228be0860..5b6853c276 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -291,7 +291,7 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); } #ifdef HAS_PROTO_MESSAGE_DUMP void HelloRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HelloRequest {\n"); out.append(" client_info: "); out.append("'").append(this->client_info).append("'"); @@ -330,7 +330,7 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void HelloResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HelloResponse {\n"); out.append(" api_version_major: "); sprintf(buffer, "%u", this->api_version_major); @@ -361,7 +361,7 @@ bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->password); } #ifdef HAS_PROTO_MESSAGE_DUMP void ConnectRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ConnectRequest {\n"); out.append(" password: "); out.append("'").append(this->password).append("'"); @@ -382,7 +382,7 @@ bool ConnectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); } #ifdef HAS_PROTO_MESSAGE_DUMP void ConnectResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ConnectResponse {\n"); out.append(" invalid_password: "); out.append(YESNO(this->invalid_password)); @@ -476,7 +476,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("DeviceInfoResponse {\n"); out.append(" uses_password: "); out.append(YESNO(this->uses_password)); @@ -600,7 +600,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesBinarySensorResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -672,7 +672,7 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void BinarySensorStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("BinarySensorStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -766,7 +766,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesCoverResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -856,7 +856,7 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void CoverStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("CoverStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -939,7 +939,7 @@ void CoverCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void CoverCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("CoverCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1055,7 +1055,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesFanResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesFanResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -1151,7 +1151,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void FanStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("FanStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1252,7 +1252,7 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void FanCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("FanCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1403,7 +1403,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLightResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesLightResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -1561,7 +1561,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void LightStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("LightStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1784,7 +1784,7 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void LightCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("LightCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1995,7 +1995,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSensorResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesSensorResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -2084,7 +2084,7 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SensorStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SensorStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -2164,7 +2164,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSwitchResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesSwitchResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -2227,7 +2227,7 @@ void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SwitchStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SwitchStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -2266,7 +2266,7 @@ void SwitchCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SwitchCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SwitchCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -2336,7 +2336,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesTextSensorResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -2406,7 +2406,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void TextSensorStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("TextSensorStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -2443,7 +2443,7 @@ void SubscribeLogsRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeLogsRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SubscribeLogsRequest {\n"); out.append(" level: "); out.append(proto_enum_to_string(this->level)); @@ -2486,7 +2486,7 @@ void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeLogsResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SubscribeLogsResponse {\n"); out.append(" level: "); out.append(proto_enum_to_string(this->level)); @@ -2528,7 +2528,7 @@ void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void HomeassistantServiceMap::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HomeassistantServiceMap {\n"); out.append(" key: "); out.append("'").append(this->key).append("'"); @@ -2587,7 +2587,7 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void HomeassistantServiceResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HomeassistantServiceResponse {\n"); out.append(" service: "); out.append("'").append(this->service).append("'"); @@ -2643,7 +2643,7 @@ void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const } #ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SubscribeHomeAssistantStateResponse {\n"); out.append(" entity_id: "); out.append("'").append(this->entity_id).append("'"); @@ -2680,7 +2680,7 @@ void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void HomeAssistantStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HomeAssistantStateResponse {\n"); out.append(" entity_id: "); out.append("'").append(this->entity_id).append("'"); @@ -2713,7 +2713,7 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); } #ifdef HAS_PROTO_MESSAGE_DUMP void GetTimeResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("GetTimeResponse {\n"); out.append(" epoch_seconds: "); sprintf(buffer, "%u", this->epoch_seconds); @@ -2748,7 +2748,7 @@ void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesServicesArgument::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesServicesArgument {\n"); out.append(" name: "); out.append("'").append(this->name).append("'"); @@ -2793,7 +2793,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesServicesResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesServicesResponse {\n"); out.append(" name: "); out.append("'").append(this->name).append("'"); @@ -2887,7 +2887,7 @@ void ExecuteServiceArgument::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ExecuteServiceArgument::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ExecuteServiceArgument {\n"); out.append(" bool_: "); out.append(YESNO(this->bool_)); @@ -2968,7 +2968,7 @@ void ExecuteServiceRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ExecuteServiceRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ExecuteServiceRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3040,7 +3040,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCameraResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesCameraResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -3110,7 +3110,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void CameraImageResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("CameraImageResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3147,7 +3147,7 @@ void CameraImageRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void CameraImageRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("CameraImageRequest {\n"); out.append(" single: "); out.append(YESNO(this->single)); @@ -3293,7 +3293,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesClimateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesClimateResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -3480,7 +3480,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ClimateStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ClimateStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3668,7 +3668,7 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ClimateCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ClimateCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3842,7 +3842,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesNumberResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -3929,7 +3929,7 @@ void NumberStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void NumberStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("NumberStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3967,7 +3967,7 @@ void NumberCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void NumberCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("NumberCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -4045,7 +4045,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSelectResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesSelectResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -4121,7 +4121,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SelectStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SelectStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -4164,7 +4164,7 @@ void SelectCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SelectCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SelectCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index ad4c32bb1f..d4e37f62f2 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -85,14 +85,7 @@ void CaptivePortal::start() { this->dns_server_->start(53, "*", (uint32_t) ip); this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { - bool not_found = false; - if (!this->active_) { - not_found = true; - } else if (req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) { - not_found = true; - } - - if (not_found) { + if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) { req->send(404, "text/html", "File not found"); return; } diff --git a/esphome/components/cs5460a/cs5460a.cpp b/esphome/components/cs5460a/cs5460a.cpp index a172bcdf56..b0c0531936 100644 --- a/esphome/components/cs5460a/cs5460a.cpp +++ b/esphome/components/cs5460a/cs5460a.cpp @@ -102,8 +102,6 @@ void CS5460AComponent::hw_init_() { /* Doesn't reset the register values etc., just restarts the "computation cycle" */ void CS5460AComponent::restart_() { - int cnt; - this->enable(); /* Stop running conversion, wake up if needed */ this->write_byte(CMD_POWER_UP); diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index 55e1ec82cf..25d75da3e6 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -149,9 +149,9 @@ void CSE7766Component::parse_data_() { } } void CSE7766Component::update() { - float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0; - float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0; - float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0; + float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0f; + float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0f; + float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0f; ESP_LOGV(TAG, "Got voltage_acc=%.2f current_acc=%.2f power_acc=%.2f", this->voltage_acc_, this->current_acc_, this->power_acc_); diff --git a/esphome/components/daikin/daikin.cpp b/esphome/components/daikin/daikin.cpp index 5f8d0288e2..83d0253691 100644 --- a/esphome/components/daikin/daikin.cpp +++ b/esphome/components/daikin/daikin.cpp @@ -231,7 +231,7 @@ bool DaikinClimate::on_receive(remote_base::RemoteReceiveData data) { // frame header if (byte != 0x27) return false; - } else if (pos == 3) { + } else if (pos == 3) { // NOLINT(bugprone-branch-clone) // frame header if (byte != 0x00) return false; diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index ac806611b5..1458629acd 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -496,7 +496,7 @@ bool Animation::get_pixel(int x, int y) const { return false; const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; const uint32_t frame_index = this->height_ * width_8 * this->current_frame_; - if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) + if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_)) return false; const uint32_t pos = x + y * width_8 + frame_index; return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); @@ -505,7 +505,7 @@ Color Animation::get_color_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) return Color::BLACK; const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_; - if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) + if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_)) return Color::BLACK; const uint32_t pos = (x + y * this->width_ + frame_index) * 3; const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) | @@ -517,7 +517,7 @@ Color Animation::get_grayscale_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) return Color::BLACK; const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_; - if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) + if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_)) return Color::BLACK; const uint32_t pos = (x + y * this->width_ + frame_index); const uint8_t gray = progmem_read_byte(this->data_start_ + pos); diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index db0bf95ca1..76f79ee55c 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -114,10 +114,10 @@ class Dsmr : public Component, public uart::UARTDevice { bool receive_timeout_reached_(); size_t max_telegram_len_; char *telegram_{nullptr}; - int bytes_read_{0}; + size_t bytes_read_{0}; uint8_t *crypt_telegram_{nullptr}; size_t crypt_telegram_len_{0}; - int crypt_bytes_read_{0}; + size_t crypt_bytes_read_{0}; uint32_t last_read_time_{0}; bool header_found_{false}; bool footer_found_{false}; diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index 2660318182..a24f217756 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -9,7 +9,7 @@ namespace esp8266 { static const char *const TAG = "esp8266"; static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) { - if (flags == gpio::FLAG_INPUT) { + if (flags == gpio::FLAG_INPUT) { // NOLINT(bugprone-branch-clone) return INPUT; } else if (flags == gpio::FLAG_OUTPUT) { return OUTPUT; diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index 041736943b..a8f8bd0d41 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -55,7 +55,7 @@ static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) { extern "C" uint32_t _SPIFFS_end; // NOLINT -static const uint32_t get_esp8266_flash_sector() { +static uint32_t get_esp8266_flash_sector() { union { uint32_t *ptr; uint32_t uint; @@ -63,7 +63,7 @@ static const uint32_t get_esp8266_flash_sector() { data.ptr = &_SPIFFS_end; return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE; } -static const uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; } +static uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; } template uint32_t calculate_crc(It first, It last, uint32_t type) { uint32_t crc = type; diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index ca6f121dbb..3c1b6e33e8 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -75,7 +75,7 @@ void EZOSensor::loop() { return; // some sensors return multiple comma-separated values, terminate string after first one - for (int i = 1; i < sizeof(buf) - 1; i++) + for (size_t i = 1; i < sizeof(buf) - 1; i++) if (buf[i] == ',') buf[i] = '\0'; diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index a9daad4ab9..daff89e0a6 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -86,7 +86,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo // Look back in trace data to best-fit into local range float mx = NAN; float mn = NAN; - for (int16_t i = 0; i < this->width_; i++) { + for (uint32_t i = 0; i < this->width_; i++) { for (auto *trace : traces_) { float v = trace->get_tracedata()->get_value(i); if (!std::isnan(v)) { @@ -132,7 +132,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo if (!std::isnan(this->gridspacing_y_)) { for (int y = yn; y <= ym; y++) { int16_t py = (int16_t) roundf((this->height_ - 1) * (1.0 - (float) (y - yn) / (ym - yn))); - for (int x = 0; x < this->width_; x += 2) { + for (uint32_t x = 0; x < this->width_; x += 2) { buff->draw_pixel_at(x_offset + x, y_offset + py, color); } } @@ -147,7 +147,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo ESP_LOGW(TAG, "Graphing reducing x-scale to prevent too many gridlines"); } for (int i = 0; i <= n; i++) { - for (int y = 0; y < this->height_; y += 2) { + for (uint32_t y = 0; y < this->height_; y += 2) { buff->draw_pixel_at(x_offset + i * (this->width_ - 1) / n, y_offset + y, color); } } @@ -158,14 +158,14 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo for (auto *trace : traces_) { Color c = trace->get_line_color(); uint16_t thick = trace->get_line_thickness(); - for (int16_t i = 0; i < this->width_; i++) { + for (uint32_t i = 0; i < this->width_; i++) { float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange; if (!std::isnan(v) && (thick > 0)) { int16_t x = this->width_ - 1 - i; uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2; - for (int16_t t = 0; t < thick; t++) { + for (uint16_t t = 0; t < thick; t++) { buff->draw_pixel_at(x_offset + x, y_offset + y + t, c); } } @@ -179,8 +179,8 @@ void GraphLegend::init(Graph *g) { parent_ = g; // Determine maximum expected text and value width / height - int txtw = 0, txtos = 0, txtbl = 0, txth = 0; - int valw = 0, valos = 0, valbl = 0, valh = 0; + int txtw = 0, txth = 0; + int valw = 0, valh = 0; int lt = 0; for (auto *trace : g->traces_) { std::string txtstr = trace->get_name(); @@ -320,7 +320,7 @@ void Graph::draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_ if (legend_->lines_) { uint16_t thick = trace->get_line_thickness(); - for (int16_t i = 0; i < legend_->x0_ * 4 / 3; i++) { + for (int i = 0; i < legend_->x0_ * 4 / 3; i++) { uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { buff->vertical_line(x - legend_->x0_ * 2 / 3 + i, y + legend_->yl_ - thick / 2, thick, diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.cpp b/esphome/components/hitachi_ac344/hitachi_ac344.cpp index 067ea39d07..7702baf312 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.cpp +++ b/esphome/components/hitachi_ac344/hitachi_ac344.cpp @@ -299,9 +299,7 @@ bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) { GETBITS8(remote_state[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE); ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC344_SWINGH_BYTE], swing_modeh); - if ((swing_modeh & 0x7) == 0x0) { - this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; - } else if ((swing_modeh & 0x3) == 0x3) { + if ((swing_modeh & 0x3) == 0x3) { this->swing_mode = climate::CLIMATE_SWING_OFF; } else { this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; diff --git a/esphome/components/hitachi_ac424/hitachi_ac424.cpp b/esphome/components/hitachi_ac424/hitachi_ac424.cpp index 2e5423a37a..713bc0be25 100644 --- a/esphome/components/hitachi_ac424/hitachi_ac424.cpp +++ b/esphome/components/hitachi_ac424/hitachi_ac424.cpp @@ -300,9 +300,7 @@ bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) { HITACHI_AC424_SWINGH_SIZE); ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC424_SWINGH_BYTE], swing_modeh); - if ((swing_modeh & 0x7) == 0x0) { - this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; - } else if ((swing_modeh & 0x3) == 0x3) { + if ((swing_modeh & 0x3) == 0x3) { this->swing_mode = climate::CLIMATE_SWING_OFF; } else { this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; diff --git a/esphome/components/ili9341/ili9341_display.cpp b/esphome/components/ili9341/ili9341_display.cpp index ab5586fa28..a24f0bbb64 100644 --- a/esphome/components/ili9341/ili9341_display.cpp +++ b/esphome/components/ili9341/ili9341_display.cpp @@ -86,8 +86,8 @@ void ILI9341Display::update() { void ILI9341Display::display_() { // we will only update the changed window to the display - int w = this->x_high_ - this->x_low_ + 1; - int h = this->y_high_ - this->y_low_ + 1; + uint16_t w = this->x_high_ - this->x_low_ + 1; + uint16_t h = this->y_high_ - this->y_low_ + 1; set_addr_window_(this->x_low_, this->y_low_, w, h); this->start_data_(); diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index a9a7467125..b4d1d88370 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -145,7 +145,7 @@ bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) { if (at == 8 + data_len + 1) { uint8_t checksum = 0x00; - for (uint8_t i = 0; i < at; i++) + for (size_t i = 0; i < at; i++) checksum += raw[i]; if (checksum != byte) { diff --git a/esphome/components/lcd_gpio/gpio_lcd_display.cpp b/esphome/components/lcd_gpio/gpio_lcd_display.cpp index b0344d313c..94ddc34051 100644 --- a/esphome/components/lcd_gpio/gpio_lcd_display.cpp +++ b/esphome/components/lcd_gpio/gpio_lcd_display.cpp @@ -17,7 +17,7 @@ void GPIOLCDDisplay::setup() { this->enable_pin_->setup(); // OUTPUT this->enable_pin_->digital_write(false); - for (uint8_t i = 0; i < (this->is_four_bit_mode() ? 4 : 8); i++) { + for (uint8_t i = 0; i < (uint8_t)(this->is_four_bit_mode() ? 4u : 8u); i++) { this->data_pins_[i]->setup(); // OUTPUT this->data_pins_[i]->digital_write(false); } diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index 358fe69c23..5091bae2d5 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -167,7 +167,7 @@ class AddressableScanEffect : public AddressableLightEffect { this->last_move_ = now; it.all() = Color::BLACK; - for (auto i = 0; i < this->scan_width_; i++) { + for (uint32_t i = 0; i < this->scan_width_; i++) { it[this->at_led_ + i] = current_color; } @@ -178,7 +178,7 @@ class AddressableScanEffect : public AddressableLightEffect { uint32_t move_interval_{}; uint32_t scan_width_{1}; uint32_t last_move_{0}; - int at_led_{0}; + uint32_t at_led_{0}; bool direction_{true}; }; diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 9858590850..3f1b8aef30 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -98,7 +98,7 @@ void LightCall::perform() { // EFFECT auto effect = this->effect_; const char *effect_s; - if (effect == 0) + if (effect == 0u) effect_s = "None"; else effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str(); diff --git a/esphome/components/ltr390/ltr390.cpp b/esphome/components/ltr390/ltr390.cpp index 36f3835724..959af68235 100644 --- a/esphome/components/ltr390/ltr390.cpp +++ b/esphome/components/ltr390/ltr390.cpp @@ -97,7 +97,7 @@ void LTR390Component::read_mode_(int mode_index) { // If there are more modes to read then begin the next // otherwise stop - if (mode_index + 1 < this->mode_funcs_.size()) { + if (mode_index + 1 < (int) this->mode_funcs_.size()) { this->read_mode_(mode_index + 1); } else { this->reading_ = false; diff --git a/esphome/components/max31865/max31865.cpp b/esphome/components/max31865/max31865.cpp index 91946cde2c..126915dc15 100644 --- a/esphome/components/max31865/max31865.cpp +++ b/esphome/components/max31865/max31865.cpp @@ -203,16 +203,16 @@ float MAX31865Sensor::calc_temperature_(float rtd_ratio) { rtd_resistance *= 100; } float rpoly = rtd_resistance; - float neg_temp = -242.02; - neg_temp += 2.2228 * rpoly; + float neg_temp = -242.02f; + neg_temp += 2.2228f * rpoly; rpoly *= rtd_resistance; // square - neg_temp += 2.5859e-3 * rpoly; + neg_temp += 2.5859e-3f * rpoly; rpoly *= rtd_resistance; // ^3 - neg_temp -= 4.8260e-6 * rpoly; + neg_temp -= 4.8260e-6f * rpoly; rpoly *= rtd_resistance; // ^4 - neg_temp -= 2.8183e-8 * rpoly; + neg_temp -= 2.8183e-8f * rpoly; rpoly *= rtd_resistance; // ^5 - neg_temp += 1.5243e-10 * rpoly; + neg_temp += 1.5243e-10f * rpoly; return neg_temp; } diff --git a/esphome/components/max7219digit/max7219digit.cpp b/esphome/components/max7219digit/max7219digit.cpp index 0f86ac635c..2368c17448 100644 --- a/esphome/components/max7219digit/max7219digit.cpp +++ b/esphome/components/max7219digit/max7219digit.cpp @@ -76,7 +76,7 @@ void MAX7219Component::loop() { this->stepsleft_ = 0; // Return if there is no need to scroll or scroll is off - if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= get_width_internal())) { + if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= (size_t) get_width_internal())) { this->display(); return; } @@ -88,7 +88,7 @@ void MAX7219Component::loop() { // Dwell time at end of string in case of stop at end if (this->scroll_mode_ == ScrollMode::STOP) { - if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - get_width_internal() + 1) { + if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - (size_t) get_width_internal() + 1) { if (now - this->last_scroll_ >= this->scroll_dwell_) { this->stepsleft_ = 0; this->last_scroll_ = now; @@ -155,7 +155,7 @@ int MAX7219Component::get_height_internal() { int MAX7219Component::get_width_internal() { return this->num_chips_ / this->num_chip_lines_ * 8; } void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color color) { - if (x + 1 > this->max_displaybuffer_[0].size()) { // Extend the display buffer in case required + if (x + 1 > (int) this->max_displaybuffer_[0].size()) { // Extend the display buffer in case required for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { this->max_displaybuffer_[chip_line].resize(x + 1, this->bckgrnd_); } diff --git a/esphome/components/mcp23s08/mcp23s08.cpp b/esphome/components/mcp23s08/mcp23s08.cpp index b7adeb94d2..af834b4c40 100644 --- a/esphome/components/mcp23s08/mcp23s08.cpp +++ b/esphome/components/mcp23s08/mcp23s08.cpp @@ -35,7 +35,6 @@ void MCP23S08::dump_config() { } bool MCP23S08::read_reg(uint8_t reg, uint8_t *value) { - uint8_t data; this->enable(); this->transfer_byte(this->device_opcode_ | 1); this->transfer_byte(reg); diff --git a/esphome/components/mcp2515/mcp2515.cpp b/esphome/components/mcp2515/mcp2515.cpp index ce451cbb33..e845c79a64 100644 --- a/esphome/components/mcp2515/mcp2515.cpp +++ b/esphome/components/mcp2515/mcp2515.cpp @@ -127,9 +127,6 @@ canbus::Error MCP2515::set_mode_(const CanctrlReqopMode mode) { } canbus::Error MCP2515::set_clk_out_(const CanClkOut divisor) { - canbus::Error res; - uint8_t cfg3; - if (divisor == CLKOUT_DISABLE) { /* Turn off CLKEN */ modify_register_(MCP_CANCTRL, CANCTRL_CLKEN, 0x00); diff --git a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp index 81066b3f5c..c3eb3d4411 100644 --- a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp +++ b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp @@ -13,8 +13,6 @@ void ModbusBinarySensor::parse_and_publish(const std::vector &data) { switch (this->register_type) { case ModbusRegisterType::DISCRETE_INPUT: - value = coil_from_vector(this->offset, data); - break; case ModbusRegisterType::COIL: // offset for coil is the actual number of the coil not the byte offset value = coil_from_vector(this->offset, data); diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 39c0d8026f..045075f5e0 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -102,8 +102,6 @@ inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_ return ModbusFunctionCode::READ_WRITE_MULTIPLE_REGISTERS; break; case ModbusRegisterType::READ: - return ModbusFunctionCode::CUSTOM; - break; default: return ModbusFunctionCode::CUSTOM; break; @@ -221,7 +219,7 @@ template N mask_and_shift_by_rightbit(N data, uint32_t mask) { if (result == 0) { return result; } - for (int pos = 0; pos < sizeof(N) << 3; pos++) { + for (size_t pos = 0; pos < sizeof(N) << 3; pos++) { if ((mask & (1 << pos)) != 0) return result >> pos; } diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index 95c6ac6f6a..ba2ffdd09f 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -8,11 +8,6 @@ namespace modbus_controller { static const char *const TAG = "modbus.number"; void ModbusNumber::parse_and_publish(const std::vector &data) { - union { - float float_value; - uint32_t raw; - } raw_to_float; - float result = payload_to_float(data, *this); // Is there a lambda registered @@ -31,13 +26,7 @@ void ModbusNumber::parse_and_publish(const std::vector &data) { } void ModbusNumber::control(float value) { - union { - float float_value; - uint32_t raw; - } raw_to_float; - std::vector data; - auto original_value = value; // Is there are lambda configured? if (this->write_transform_func_.has_value()) { // data is passed by reference diff --git a/esphome/components/modbus_controller/output/modbus_output.cpp b/esphome/components/modbus_controller/output/modbus_output.cpp index f7d7c42342..d2b5d02bda 100644 --- a/esphome/components/modbus_controller/output/modbus_output.cpp +++ b/esphome/components/modbus_controller/output/modbus_output.cpp @@ -13,11 +13,6 @@ void ModbusOutput::setup() {} * */ void ModbusOutput::write_state(float value) { - union { - float float_value; - uint32_t raw; - } raw_to_float; - std::vector data; auto original_value = value; // Is there are lambda configured? diff --git a/esphome/components/modbus_controller/sensor/modbus_sensor.cpp b/esphome/components/modbus_controller/sensor/modbus_sensor.cpp index dbd0525347..a21fd91032 100644 --- a/esphome/components/modbus_controller/sensor/modbus_sensor.cpp +++ b/esphome/components/modbus_controller/sensor/modbus_sensor.cpp @@ -10,11 +10,6 @@ static const char *const TAG = "modbus_controller.sensor"; void ModbusSensor::dump_config() { LOG_SENSOR(TAG, "Modbus Controller Sensor", this); } void ModbusSensor::parse_and_publish(const std::vector &data) { - union { - float float_value; - uint32_t raw; - } raw_to_float; - float result = payload_to_float(data, *this); // Is there a lambda registered diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index f23f55c9bb..494765db4d 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -64,7 +64,7 @@ bool Nextion::check_connect_() { if (response.empty() || response.find("comok") == std::string::npos) { #ifdef NEXTION_PROTOCOL_LOG ESP_LOGN(TAG, "Bad connect request %s", response.c_str()); - for (int i = 0; i < response.length(); i++) { + for (size_t i = 0; i < response.length(); i++) { ESP_LOGN(TAG, "response %s %d %d %c", response.c_str(), i, response[i], response[i]); } #endif @@ -563,11 +563,10 @@ void Nextion::process_nextion_commands_() { // FF FF FF - End case 0x90: { // Switched component std::string variable_name; - uint8_t index = 0; // Get variable name - index = to_process.find('\0'); - if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + auto index = to_process.find('\0'); + if (index == std::string::npos || (to_process_length - index - 1) < 1) { ESP_LOGE(TAG, "Bad switch component data received for 0x90 event!"); ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); break; @@ -591,10 +590,9 @@ void Nextion::process_nextion_commands_() { // FF FF FF - End case 0x91: { // Sensor component std::string variable_name; - uint8_t index = 0; - index = to_process.find('\0'); - if (static_cast(index) == std::string::npos || (to_process_length - index - 1) != 4) { + auto index = to_process.find('\0'); + if (index == std::string::npos || (to_process_length - index - 1) != 4) { ESP_LOGE(TAG, "Bad sensor component data received for 0x91 event!"); ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); break; @@ -626,11 +624,10 @@ void Nextion::process_nextion_commands_() { case 0x92: { // Text Sensor Component std::string variable_name; std::string text_value; - uint8_t index = 0; // Get variable name - index = to_process.find('\0'); - if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + auto index = to_process.find('\0'); + if (index == std::string::npos || (to_process_length - index - 1) < 1) { ESP_LOGE(TAG, "Bad text sensor component data received for 0x92 event!"); ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); break; @@ -660,11 +657,10 @@ void Nextion::process_nextion_commands_() { // FF FF FF - End case 0x93: { // Binary Sensor component std::string variable_name; - uint8_t index = 0; // Get variable name - index = to_process.find('\0'); - if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + auto index = to_process.find('\0'); + if (index == std::string::npos || (to_process_length - index - 1) < 1) { ESP_LOGE(TAG, "Bad binary sensor component data received for 0x92 event!"); ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); break; @@ -736,7 +732,7 @@ void Nextion::process_nextion_commands_() { uint32_t ms = millis(); if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) { - for (int i = 0; i < this->nextion_queue_.size(); i++) { + for (size_t i = 0; i < this->nextion_queue_.size(); i++) { NextionComponentBase *component = this->nextion_queue_[i]->component; if (this->nextion_queue_[i]->queue_time + this->max_q_age_ms_ < ms) { if (this->nextion_queue_[i]->queue_time == 0) diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index cd1c073320..b16f2fe7eb 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -95,7 +95,7 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { } http->end(); ESP_LOGN(TAG, "this->content_length_ %d sent %d", this->content_length_, sent); - for (uint32_t i = 0; i < range; i += 4096) { + for (int i = 0; i < range; i += 4096) { this->write_array(&this->transfer_buffer_[i], 4096); this->content_length_ -= 4096; ESP_LOGN(TAG, "this->content_length_ %d range %d range_end %d range_start %d", this->content_length_, range, @@ -238,7 +238,7 @@ void Nextion::upload_tft() { // The Nextion display will, if it's ready to accept data, send a 0x05 byte. ESP_LOGD(TAG, "Upgrade response is %s %zu", response.c_str(), response.length()); - for (int i = 0; i < response.length(); i++) { + for (size_t i = 0; i < response.length(); i++) { ESP_LOGD(TAG, "Available %d : 0x%02X", i, response[i]); } diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp index 4b7532d32d..32bfccf9f8 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.cpp +++ b/esphome/components/nextion/sensor/nextion_sensor.cpp @@ -24,7 +24,7 @@ void NextionSensor::add_to_wave_buffer(float state) { wave_buffer_.push_back(wave_state); - if (this->wave_buffer_.size() > this->wave_max_length_) { + if (this->wave_buffer_.size() > (size_t) this->wave_max_length_) { this->wave_buffer_.erase(this->wave_buffer_.begin()); } } diff --git a/esphome/components/nfc/ndef_message.cpp b/esphome/components/nfc/ndef_message.cpp index d8c940254e..d7d134aedb 100644 --- a/esphome/components/nfc/ndef_message.cpp +++ b/esphome/components/nfc/ndef_message.cpp @@ -93,7 +93,7 @@ bool NdefMessage::add_uri_record(const std::string &uri) { return this->add_reco std::vector NdefMessage::encode() { std::vector data; - for (uint8_t i = 0; i < this->records_.size(); i++) { + for (size_t i = 0; i < this->records_.size(); i++) { auto encoded_record = this->records_[i]->encode(i == 0, (i + 1) == this->records_.size()); data.insert(data.end(), encoded_record.begin(), encoded_record.end()); } diff --git a/esphome/components/nfc/nfc.cpp b/esphome/components/nfc/nfc.cpp index 706c09a5aa..09dbdcfe94 100644 --- a/esphome/components/nfc/nfc.cpp +++ b/esphome/components/nfc/nfc.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "nfc"; std::string format_uid(std::vector &uid) { char buf[(uid.size() * 2) + uid.size() - 1]; int offset = 0; - for (uint8_t i = 0; i < uid.size(); i++) { + for (size_t i = 0; i < uid.size(); i++) { const char *format = "%02X"; if (i + 1 < uid.size()) format = "%02X-"; @@ -22,7 +22,7 @@ std::string format_uid(std::vector &uid) { std::string format_bytes(std::vector &bytes) { char buf[(bytes.size() * 2) + bytes.size() - 1]; int offset = 0; - for (uint8_t i = 0; i < bytes.size(); i++) { + for (size_t i = 0; i < bytes.size(); i++) { const char *format = "%02X"; if (i + 1 < bytes.size()) format = "%02X "; diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index e49c108320..79edd91173 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -141,14 +141,14 @@ void OTAComponent::handle_() { if (!this->readall_(buf, 5)) { ESP_LOGW(TAG, "Reading magic bytes failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } // 0x6C, 0x26, 0xF7, 0x5C, 0x45 if (buf[0] != 0x6C || buf[1] != 0x26 || buf[2] != 0xF7 || buf[3] != 0x5C || buf[4] != 0x45) { ESP_LOGW(TAG, "Magic bytes do not match! 0x%02X-0x%02X-0x%02X-0x%02X-0x%02X", buf[0], buf[1], buf[2], buf[3], buf[4]); error_code = OTA_RESPONSE_ERROR_MAGIC; - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } // Send OK and version - 2 bytes @@ -161,7 +161,7 @@ void OTAComponent::handle_() { // Read features - 1 byte if (!this->readall_(buf, 1)) { ESP_LOGW(TAG, "Reading features failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } ota_features = buf[0]; // NOLINT ESP_LOGV(TAG, "OTA features is 0x%02X", ota_features); @@ -189,7 +189,7 @@ void OTAComponent::handle_() { // Send nonce, 32 bytes hex MD5 if (!this->writeall_(reinterpret_cast(sbuf), 32)) { ESP_LOGW(TAG, "Auth: Writing nonce failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } // prepare challenge @@ -201,7 +201,7 @@ void OTAComponent::handle_() { // Receive cnonce, 32 bytes hex MD5 if (!this->readall_(buf, 32)) { ESP_LOGW(TAG, "Auth: Reading cnonce failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } sbuf[32] = '\0'; ESP_LOGV(TAG, "Auth: CNonce is %s", sbuf); @@ -216,7 +216,7 @@ void OTAComponent::handle_() { // Receive result, 32 bytes hex MD5 if (!this->readall_(buf + 64, 32)) { ESP_LOGW(TAG, "Auth: Reading response failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } sbuf[64 + 32] = '\0'; ESP_LOGV(TAG, "Auth: Response is %s", sbuf + 64); @@ -228,7 +228,7 @@ void OTAComponent::handle_() { if (!matches) { ESP_LOGW(TAG, "Auth failed! Passwords do not match!"); error_code = OTA_RESPONSE_ERROR_AUTH_INVALID; - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } } #endif // USE_OTA_PASSWORD @@ -240,7 +240,7 @@ void OTAComponent::handle_() { // Read size, 4 bytes MSB first if (!this->readall_(buf, 4)) { ESP_LOGW(TAG, "Reading size failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } ota_size = 0; for (uint8_t i = 0; i < 4; i++) { @@ -251,7 +251,7 @@ void OTAComponent::handle_() { error_code = backend->begin(ota_size); if (error_code != OTA_RESPONSE_OK) - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) update_started = true; // Acknowledge prepare OK - 1 byte @@ -261,7 +261,7 @@ void OTAComponent::handle_() { // Read binary MD5, 32 bytes if (!this->readall_(buf, 32)) { ESP_LOGW(TAG, "Reading binary MD5 checksum failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } sbuf[32] = '\0'; ESP_LOGV(TAG, "Update: Binary MD5 is %s", sbuf); @@ -281,19 +281,19 @@ void OTAComponent::handle_() { continue; } ESP_LOGW(TAG, "Error receiving data for update, errno: %d", errno); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } else if (read == 0) { // $ man recv // "When a stream socket peer has performed an orderly shutdown, the return value will // be 0 (the traditional "end-of-file" return)." ESP_LOGW(TAG, "Remote end closed connection"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } error_code = backend->write(buf, read); if (error_code != OTA_RESPONSE_OK) { ESP_LOGW(TAG, "Error writing binary data to flash!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } total += read; @@ -317,7 +317,7 @@ void OTAComponent::handle_() { error_code = backend->end(); if (error_code != OTA_RESPONSE_OK) { ESP_LOGW(TAG, "Error ending OTA!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } // Acknowledge Update end OK - 1 byte diff --git a/esphome/components/pid/pid_autotuner.cpp b/esphome/components/pid/pid_autotuner.cpp index 15c1c5f076..fc012aaa39 100644 --- a/esphome/components/pid/pid_autotuner.cpp +++ b/esphome/components/pid/pid_autotuner.cpp @@ -330,7 +330,7 @@ bool PIDAutotuner::OscillationAmplitudeDetector::has_enough_data() const { float PIDAutotuner::OscillationAmplitudeDetector::get_mean_oscillation_amplitude() const { float total_amplitudes = 0; size_t total_amplitudes_n = 0; - for (int i = 1; i < std::min(phase_mins.size(), phase_maxs.size()) - 1; i++) { + for (size_t i = 1; i < std::min(phase_mins.size(), phase_maxs.size()) - 1; i++) { total_amplitudes += std::abs(phase_maxs[i] - phase_mins[i + 1]); total_amplitudes_n++; } diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index 9f8b57003a..13a08bbd16 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -413,8 +413,6 @@ void Pipsolar::loop() { this->state_ = STATE_IDLE; break; case POLLING_QT: - this->state_ = STATE_IDLE; - break; case POLLING_QMN: this->state_ = STATE_IDLE; break; @@ -481,7 +479,7 @@ void Pipsolar::loop() { ESP_LOGD(TAG, "Decode QFLAG"); // result like:"(EbkuvxzDajy" // get through all char: ignore first "(" Enable flag on 'E', Disable on 'D') else set the corresponding value - for (int i = 1; i < strlen(tmp); i++) { + for (size_t i = 1; i < strlen(tmp); i++) { switch (tmp[i]) { case 'E': enabled = true; @@ -530,7 +528,7 @@ void Pipsolar::loop() { this->value_warnings_present_ = false; this->value_faults_present_ = true; - for (int i = 1; i < strlen(tmp); i++) { + for (size_t i = 1; i < strlen(tmp); i++) { enabled = tmp[i] == '1'; switch (i) { case 1: diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index ed2a2c1e35..0c46ff8a57 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -145,7 +145,7 @@ void PN532::loop() { if (nfcid.size() == this->current_uid_.size()) { bool same_uid = false; - for (uint8_t i = 0; i < nfcid.size(); i++) + for (size_t i = 0; i < nfcid.size(); i++) same_uid |= nfcid[i] == this->current_uid_[i]; if (same_uid) return; @@ -367,7 +367,7 @@ bool PN532BinarySensor::process(std::vector &data) { if (data.size() != this->uid_.size()) return false; - for (uint8_t i = 0; i < data.size(); i++) { + for (size_t i = 0; i < data.size(); i++) { if (data[i] != this->uid_[i]) return false; } diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.cpp b/esphome/components/pulse_meter/pulse_meter_sensor.cpp index fd1403b4fd..7d526b241b 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.cpp +++ b/esphome/components/pulse_meter/pulse_meter_sensor.cpp @@ -35,7 +35,7 @@ void PulseMeterSensor::loop() { this->publish_state(0); } else { // Calculate pulses/min from the pulse width in ms - this->publish_state((60.0 * 1000.0) / pulse_width_ms); + this->publish_state((60.0f * 1000.0f) / pulse_width_ms); } } diff --git a/esphome/components/rc522/rc522.cpp b/esphome/components/rc522/rc522.cpp index 385641fea0..d203b3ce8f 100644 --- a/esphome/components/rc522/rc522.cpp +++ b/esphome/components/rc522/rc522.cpp @@ -28,7 +28,7 @@ std::string format_buffer(uint8_t *b, uint8_t len) { std::string format_uid(std::vector &uid) { char buf[32]; int offset = 0; - for (uint8_t i = 0; i < uid.size(); i++) { + for (size_t i = 0; i < uid.size(); i++) { const char *format = "%02X"; if (i + 1 < uid.size()) format = "%02X-"; @@ -479,7 +479,7 @@ bool RC522BinarySensor::process(std::vector &data) { if (data.size() != this->uid_.size()) result = false; else { - for (uint8_t i = 0; i < data.size(); i++) { + for (size_t i = 0; i < data.size(); i++) { if (data[i] != this->uid_[i]) { result = false; break; diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp index 11aebb6c5d..4f6ace720c 100644 --- a/esphome/components/remote_base/pronto_protocol.cpp +++ b/esphome/components/remote_base/pronto_protocol.cpp @@ -113,7 +113,7 @@ void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &st const char *p = str.c_str(); char *endptr[1]; - for (uint16_t i = 0; i < len; i++) { + for (size_t i = 0; i < len; i++) { uint16_t x = strtol(p, endptr, 16); if (x == 0 && i >= NUMBERS_IN_PREAMBLE) { // Alignment error?, bail immediately (often right result). diff --git a/esphome/components/remote_base/remote_base.cpp b/esphome/components/remote_base/remote_base.cpp index a853c9849e..97ee027b84 100644 --- a/esphome/components/remote_base/remote_base.cpp +++ b/esphome/components/remote_base/remote_base.cpp @@ -33,7 +33,7 @@ void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) { uint32_t buffer_offset = 0; buffer_offset += sprintf(buffer, "Sending times=%u wait=%ums: ", send_times, send_wait); - for (int32_t i = 0; i < vec.size(); i++) { + for (size_t i = 0; i < vec.size(); i++) { const int32_t value = vec[i]; const uint32_t remaining_length = sizeof(buffer) - buffer_offset; int written; diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index c3b61b72c2..368b21f892 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -113,7 +113,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen this->rmt_temp_.push_back(rmt_item); } - for (uint16_t i = 0; i < send_times; i++) { + for (uint32_t i = 0; i < send_times; i++) { esp_err_t error = rmt_write_items(this->channel_, this->rmt_temp_.data(), this->rmt_temp_.size(), true); if (error != ESP_OK) { ESP_LOGW(TAG, "rmt_write_items failed: %s", esp_err_to_name(error)); diff --git a/esphome/components/rtttl/rtttl.cpp b/esphome/components/rtttl/rtttl.cpp index d571c2f287..c76d4a89b0 100644 --- a/esphome/components/rtttl/rtttl.cpp +++ b/esphome/components/rtttl/rtttl.cpp @@ -159,7 +159,7 @@ void Rtttl::loop() { // Now play the note if (note) { auto note_index = (scale - 4) * 12 + note; - if (note_index < 0 || note_index >= sizeof(NOTES)) { + if (note_index < 0 || note_index >= (int) sizeof(NOTES)) { ESP_LOGE(TAG, "Note out of valid range"); return; } diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index 87cf0fa61a..4157fd55cf 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -147,8 +147,8 @@ void SGP30Component::read_iaq_baseline_() { // much if (this->store_baseline_ && (this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL || - abs(this->baselines_storage_.eco2 - this->eco2_baseline_) > MAXIMUM_STORAGE_DIFF || - abs(this->baselines_storage_.tvoc - this->tvoc_baseline_) > MAXIMUM_STORAGE_DIFF)) { + (uint32_t) abs(this->baselines_storage_.eco2 - this->eco2_baseline_) > MAXIMUM_STORAGE_DIFF || + (uint32_t) abs(this->baselines_storage_.tvoc - this->tvoc_baseline_) > MAXIMUM_STORAGE_DIFF)) { this->seconds_since_last_store_ = 0; this->baselines_storage_.eco2 = this->eco2_baseline_; this->baselines_storage_.tvoc = this->tvoc_baseline_; diff --git a/esphome/components/sgp40/sensirion_voc_algorithm.cpp b/esphome/components/sgp40/sensirion_voc_algorithm.cpp index f3cdeee35b..d76b776641 100644 --- a/esphome/components/sgp40/sensirion_voc_algorithm.cpp +++ b/esphome/components/sgp40/sensirion_voc_algorithm.cpp @@ -149,7 +149,7 @@ static fix16_t fix16_div(fix16_t a, fix16_t b) { /* Figure out the sign of result */ if ((a ^ b) & 0x80000000) { #ifndef FIXMATH_NO_OVERFLOW - if (result == FIX16_MINIMUM) + if (result == FIX16_MINIMUM) // NOLINT(clang-diagnostic-sign-compare) return FIX16_OVERFLOW; #endif diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp index a3d2c74eb7..9561efcde2 100644 --- a/esphome/components/sgp40/sgp40.cpp +++ b/esphome/components/sgp40/sgp40.cpp @@ -144,8 +144,8 @@ int32_t SGP40Component::measure_voc_index_() { // much if (this->store_baseline_ && this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL) { voc_algorithm_get_states(&voc_algorithm_params_, &this->state0_, &this->state1_); - if (abs(this->baselines_storage_.state0 - this->state0_) > MAXIMUM_STORAGE_DIFF || - abs(this->baselines_storage_.state1 - this->state1_) > MAXIMUM_STORAGE_DIFF) { + if ((uint32_t) abs(this->baselines_storage_.state0 - this->state0_) > MAXIMUM_STORAGE_DIFF || + (uint32_t) abs(this->baselines_storage_.state1 - this->state1_) > MAXIMUM_STORAGE_DIFF) { this->seconds_since_last_store_ = 0; this->baselines_storage_.state0 = this->state0_; this->baselines_storage_.state1 = this->state1_; diff --git a/esphome/components/sgp40/sgp40.h b/esphome/components/sgp40/sgp40.h index bb68a1ffcf..c854b21060 100644 --- a/esphome/components/sgp40/sgp40.h +++ b/esphome/components/sgp40/sgp40.h @@ -66,7 +66,7 @@ class SGP40Component : public PollingComponent, public sensor::Sensor, public i2 uint8_t generate_crc_(const uint8_t *data, uint8_t datalen); uint16_t measure_raw_(); ESPPreferenceObject pref_; - int32_t seconds_since_last_store_; + uint32_t seconds_since_last_store_; SGP40Baselines baselines_storage_; VocAlgorithmParams voc_algorithm_params_; bool self_test_complete_; diff --git a/esphome/components/sm300d2/sm300d2.cpp b/esphome/components/sm300d2/sm300d2.cpp index e41a4855db..c726faec48 100644 --- a/esphome/components/sm300d2/sm300d2.cpp +++ b/esphome/components/sm300d2/sm300d2.cpp @@ -50,10 +50,10 @@ void SM300D2Sensor::update() { const uint16_t pm_2_5 = (response[8] * 256) + response[9]; const uint16_t pm_10_0 = (response[10] * 256) + response[11]; // A negative value is indicated by adding 0x80 (128) to the temperature value - const float temperature = ((response[12] + (response[13] * 0.1)) > 128) - ? (((response[12] + (response[13] * 0.1)) - 128) * -1) - : response[12] + (response[13] * 0.1); - const float humidity = response[14] + (response[15] * 0.1); + const float temperature = ((response[12] + (response[13] * 0.1f)) > 128) + ? (((response[12] + (response[13] * 0.1f)) - 128) * -1) + : response[12] + (response[13] * 0.1f); + const float humidity = response[14] + (response[15] * 0.1f); ESP_LOGD(TAG, "Received CO₂: %u ppm", co2); if (this->co2_sensor_ != nullptr) diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 922d895ff4..d57413c739 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -383,7 +383,7 @@ class LWIPRawImpl : public Socket { return err; } ret += err; - if (err != iov[i].iov_len) + if ((size_t) err != iov[i].iov_len) break; } return ret; @@ -462,7 +462,7 @@ class LWIPRawImpl : public Socket { return err; } written += err; - if (err != iov[i].iov_len) + if ((size_t) err != iov[i].iov_len) break; } if (written == 0) diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index d883142c81..d427e2c91b 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -55,13 +55,9 @@ void SPIComponent::setup() { } } #ifdef USE_ESP8266 - if (clk_pin == 6 && miso_pin == 7 && mosi_pin == 8) { - // pass - } else if (clk_pin == 14 && (!has_miso || miso_pin == 12) && (!has_mosi || mosi_pin == 13)) { - // pass - } else { + if (!(clk_pin == 6 && miso_pin == 7 && mosi_pin == 8) && + !(clk_pin == 14 && (!has_miso || miso_pin == 12) && (!has_mosi || mosi_pin == 13))) use_hw_spi = false; - } if (use_hw_spi) { this->hw_spi_ = &SPI; diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index 2537133605..4b9feb10ce 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -292,7 +292,7 @@ const char *SSD1306::model_str_() { case SSD1305_MODEL_128_32: return "SSD1305 128x32"; case SSD1305_MODEL_128_64: - return "SSD1305 128x32"; + return "SSD1305 128x64"; default: return "Unknown"; } diff --git a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp index fddea25fc8..64b09c0672 100644 --- a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp +++ b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp @@ -40,12 +40,12 @@ void I2CSSD1306::command(uint8_t value) { this->write_byte(0x00, value); } void HOT I2CSSD1306::write_display_data() { if (this->is_sh1106_()) { uint32_t i = 0; - for (uint8_t page = 0; page < this->get_height_internal() / 8; page++) { + for (uint8_t page = 0; page < (uint8_t) this->get_height_internal() / 8; page++) { this->command(0xB0 + page); // row this->command(0x02); // lower column this->command(0x10); // higher column - for (uint8_t x = 0; x < this->get_width_internal() / 16; x++) { + for (uint8_t x = 0; x < (uint8_t) this->get_width_internal() / 16; x++) { uint8_t data[16]; for (uint8_t &j : data) j = this->buffer_[i++]; diff --git a/esphome/components/ssd1306_spi/ssd1306_spi.cpp b/esphome/components/ssd1306_spi/ssd1306_spi.cpp index 33d474a8ee..7f025d77cd 100644 --- a/esphome/components/ssd1306_spi/ssd1306_spi.cpp +++ b/esphome/components/ssd1306_spi/ssd1306_spi.cpp @@ -37,12 +37,12 @@ void SPISSD1306::command(uint8_t value) { } void HOT SPISSD1306::write_display_data() { if (this->is_sh1106_()) { - for (uint8_t y = 0; y < this->get_height_internal() / 8; y++) { + for (uint8_t y = 0; y < (uint8_t) this->get_height_internal() / 8; y++) { this->command(0xB0 + y); this->command(0x02); this->command(0x10); this->dc_pin_->digital_write(true); - for (uint8_t x = 0; x < this->get_width_internal(); x++) { + for (uint8_t x = 0; x < (uint8_t) this->get_width_internal(); x++) { this->enable(); this->write_byte(this->buffer_[x + y * this->get_width_internal()]); this->disable(); diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index 8490aa1fe4..c5178986f3 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -275,7 +275,7 @@ void ST7735::setup() { uint8_t data = 0; if (this->model_ != INITR_HALLOWING) { - uint8_t data = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY; + data = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY; } if (this->usebgr_) { data = data | ST7735_MADCTL_BGR; @@ -446,7 +446,7 @@ void HOT ST7735::write_display_data_() { this->dc_pin_->digital_write(true); if (this->eightbitcolor_) { - for (int line = 0; line < this->get_buffer_length(); line = line + this->get_width_internal()) { + for (size_t line = 0; line < this->get_buffer_length(); line = line + this->get_width_internal()) { for (int index = 0; index < this->get_width_internal(); ++index) { auto color332 = display::ColorUtil::to_color(this->buffer_[index + line], display::ColorOrder::COLOR_ORDER_RGB, display::ColorBitness::COLOR_BITNESS_332, true); diff --git a/esphome/components/st7920/st7920.cpp b/esphome/components/st7920/st7920.cpp index d985b0a426..63fa0ba72f 100644 --- a/esphome/components/st7920/st7920.cpp +++ b/esphome/components/st7920/st7920.cpp @@ -74,7 +74,7 @@ void ST7920::goto_xy_(uint16_t x, uint16_t y) { void HOT ST7920::write_display_data() { uint8_t i, j, b; - for (j = 0; j < this->get_height_internal() / 2; j++) { + for (j = 0; j < (uint8_t)(this->get_height_internal() / 2); j++) { this->goto_xy_(0, j); this->enable(); for (i = 0; i < 16; i++) { // 16 bytes from line #0+ diff --git a/esphome/components/teleinfo/teleinfo.cpp b/esphome/components/teleinfo/teleinfo.cpp index 5a1e44ac8b..d9f80134f4 100644 --- a/esphome/components/teleinfo/teleinfo.cpp +++ b/esphome/components/teleinfo/teleinfo.cpp @@ -118,7 +118,7 @@ void TeleInfo::loop() { * */ while ((buf_finger = static_cast(memchr(buf_finger, (int) 0xa, buf_index_ - 1))) && - ((buf_finger - buf_) < buf_index_)) { + ((buf_finger - buf_) < buf_index_)) { // NOLINT(clang-diagnostic-sign-compare) /* * Make sure timesamp is nullified between each tag as some tags don't * have a timestamp diff --git a/esphome/components/text_sensor/filter.cpp b/esphome/components/text_sensor/filter.cpp index a692378e8b..c6cbcd7c1e 100644 --- a/esphome/components/text_sensor/filter.cpp +++ b/esphome/components/text_sensor/filter.cpp @@ -64,7 +64,7 @@ optional PrependFilter::new_value(std::string value) { return this- // Substitute optional SubstituteFilter::new_value(std::string value) { std::size_t pos; - for (int i = 0; i < this->from_strings_.size(); i++) + for (size_t i = 0; i < this->from_strings_.size(); i++) while ((pos = value.find(this->from_strings_[i])) != std::string::npos) value.replace(pos, this->from_strings_[i].size(), this->to_strings_[i]); return value; diff --git a/esphome/components/tof10120/tof10120_sensor.cpp b/esphome/components/tof10120/tof10120_sensor.cpp index 4ba591f9c4..5cd086938e 100644 --- a/esphome/components/tof10120/tof10120_sensor.cpp +++ b/esphome/components/tof10120/tof10120_sensor.cpp @@ -50,7 +50,7 @@ void TOF10120Sensor::update() { ESP_LOGW(TAG, "Distance measurement out of range"); this->publish_state(NAN); } else { - this->publish_state(distance_mm / 1000.0); + this->publish_state(distance_mm / 1000.0f); } this->status_clear_warning(); } diff --git a/esphome/components/toshiba/toshiba.cpp b/esphome/components/toshiba/toshiba.cpp index 25528abbe1..975a149b52 100644 --- a/esphome/components/toshiba/toshiba.cpp +++ b/esphome/components/toshiba/toshiba.cpp @@ -580,13 +580,13 @@ bool ToshibaClimate::on_receive(remote_base::RemoteReceiveData data) { temperature_code = (message[4] >> 4) | (message[14] & RAC_PT1411HWRU_FLAG_FRAC) | (message[15] & RAC_PT1411HWRU_FLAG_NEG); if (message[15] & RAC_PT1411HWRU_FLAG_FAH) { - for (uint8_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_F.size(); i++) { + for (size_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_F.size(); i++) { if (RAC_PT1411HWRU_TEMPERATURE_F[i] == temperature_code) { this->target_temperature = static_cast((i + TOSHIBA_RAC_PT1411HWRU_TEMP_F_MIN - 32) * 5) / 9; } } } else { - for (uint8_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_C.size(); i++) { + for (size_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_C.size(); i++) { if (RAC_PT1411HWRU_TEMPERATURE_C[i] == temperature_code) { this->target_temperature = i + TOSHIBA_RAC_PT1411HWRU_TEMP_C_MIN; } diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 4f65fa7118..2677731224 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -143,7 +143,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff case TuyaCommandType::PRODUCT_QUERY: { // check it is a valid string made up of printable characters bool valid = true; - for (int i = 0; i < len; i++) { + for (size_t i = 0; i < len; i++) { if (!std::isprint(buffer[i])) { valid = false; break; @@ -339,8 +339,6 @@ void Tuya::send_raw_command_(TuyaCommand command) { this->expected_response_ = TuyaCommandType::CONF_QUERY; break; case TuyaCommandType::DATAPOINT_DELIVER: - this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT; - break; case TuyaCommandType::DATAPOINT_QUERY: this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT; break; diff --git a/esphome/components/tx20/tx20.cpp b/esphome/components/tx20/tx20.cpp index 6e0b6343d1..fefcc8f4d5 100644 --- a/esphome/components/tx20/tx20.cpp +++ b/esphome/components/tx20/tx20.cpp @@ -113,7 +113,7 @@ void Tx20Component::decode_and_publish_() { if (tx20_sa == 4) { if (chk == tx20_sd) { if (tx20_sf == tx20_sc) { - tx20_wind_speed_kmh = float(tx20_sc) * 0.36; + tx20_wind_speed_kmh = float(tx20_sc) * 0.36f; ESP_LOGV(TAG, "WindSpeed %f", tx20_wind_speed_kmh); if (this->wind_speed_sensor_ != nullptr) this->wind_speed_sensor_->publish_state(tx20_wind_speed_kmh); diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index 408c83a0db..370adad779 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -214,9 +214,7 @@ void IRAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg) { /* If parity is enabled, just read it and ignore it. */ /* TODO: Should we check parity? Or is it too slow for nothing added..*/ - if (arg->parity_ == UART_CONFIG_PARITY_EVEN) - arg->read_bit_(&wait, start); - else if (arg->parity_ == UART_CONFIG_PARITY_ODD) + if (arg->parity_ == UART_CONFIG_PARITY_EVEN || arg->parity_ == UART_CONFIG_PARITY_ODD) arg->read_bit_(&wait, start); // Stop bit diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index ee3fb2fe47..322c375f0e 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -360,15 +360,18 @@ void HOT WaveshareEPaperTypeA::display() { // COMMAND DISPLAY UPDATE CONTROL 2 this->command(0x22); - if (this->model_ == WAVESHARE_EPAPER_2_9_IN_V2 || this->model_ == WAVESHARE_EPAPER_1_54_IN_V2) { - this->data(full_update ? 0xF7 : 0xFF); - } else if (this->model_ == TTGO_EPAPER_2_13_IN_B73) { - this->data(0xC7); - } else if (this->model_ == TTGO_EPAPER_2_13_IN_B74) { - // this->data(0xC7); - this->data(full_update ? 0xF7 : 0xFF); - } else { - this->data(0xC4); + switch (this->model_) { + case WAVESHARE_EPAPER_2_9_IN_V2: + case WAVESHARE_EPAPER_1_54_IN_V2: + case TTGO_EPAPER_2_13_IN_B74: + this->data(full_update ? 0xF7 : 0xFF); + break; + case TTGO_EPAPER_2_13_IN_B73: + this->data(0xC7); + break; + default: + this->data(0xC4); + break; } // COMMAND MASTER ACTIVATION @@ -381,20 +384,14 @@ void HOT WaveshareEPaperTypeA::display() { int WaveshareEPaperTypeA::get_width_internal() { switch (this->model_) { case WAVESHARE_EPAPER_1_54_IN: - return 200; case WAVESHARE_EPAPER_1_54_IN_V2: return 200; case WAVESHARE_EPAPER_2_13_IN: - return 128; case TTGO_EPAPER_2_13_IN: - return 128; case TTGO_EPAPER_2_13_IN_B73: case TTGO_EPAPER_2_13_IN_B74: - return 128; case TTGO_EPAPER_2_13_IN_B1: - return 128; case WAVESHARE_EPAPER_2_9_IN: - return 128; case WAVESHARE_EPAPER_2_9_IN_V2: return 128; } @@ -403,20 +400,15 @@ int WaveshareEPaperTypeA::get_width_internal() { int WaveshareEPaperTypeA::get_height_internal() { switch (this->model_) { case WAVESHARE_EPAPER_1_54_IN: - return 200; case WAVESHARE_EPAPER_1_54_IN_V2: return 200; case WAVESHARE_EPAPER_2_13_IN: - return 250; case TTGO_EPAPER_2_13_IN: - return 250; case TTGO_EPAPER_2_13_IN_B73: case TTGO_EPAPER_2_13_IN_B74: - return 250; case TTGO_EPAPER_2_13_IN_B1: return 250; case WAVESHARE_EPAPER_2_9_IN: - return 296; case WAVESHARE_EPAPER_2_9_IN_V2: return 296; } @@ -433,11 +425,10 @@ void WaveshareEPaperTypeA::set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; } -int WaveshareEPaperTypeA::idle_timeout_() { +uint32_t WaveshareEPaperTypeA::idle_timeout_() { switch (this->model_) { case TTGO_EPAPER_2_13_IN_B1: return 2500; - break; default: return WaveshareEPaper::idle_timeout_(); } @@ -646,7 +637,7 @@ void HOT WaveshareEPaper2P9InB::display() { this->command(0x13); delay(2); this->start_data_(); - for (int i = 0; i < this->get_buffer_length_(); i++) + for (size_t i = 0; i < this->get_buffer_length_(); i++) this->write_byte(0x00); this->end_data_(); delay(2); @@ -825,7 +816,7 @@ void HOT WaveshareEPaper4P2InBV2::display() { // COMMAND DATA START TRANSMISSION 2 (RED data) this->command(0x13); this->start_data_(); - for (int i = 0; i < this->get_buffer_length_(); i++) + for (size_t i = 0; i < this->get_buffer_length_(); i++) this->write_byte(0xFF); this->end_data_(); delay(2); @@ -1293,7 +1284,7 @@ void HOT WaveshareEPaper2P13InDKE::display() { int WaveshareEPaper2P13InDKE::get_width_internal() { return 128; } int WaveshareEPaper2P13InDKE::get_height_internal() { return 250; } -int WaveshareEPaper2P13InDKE::idle_timeout_() { return 5000; } +uint32_t WaveshareEPaper2P13InDKE::idle_timeout_() { return 5000; } void WaveshareEPaper2P13InDKE::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper", this); ESP_LOGCONFIG(TAG, " Model: 2.13inDKE"); diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index b50596643d..a1e2f6037a 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -61,7 +61,7 @@ class WaveshareEPaper : public PollingComponent, GPIOPin *reset_pin_{nullptr}; GPIOPin *dc_pin_; GPIOPin *busy_pin_{nullptr}; - virtual int idle_timeout_() { return 1000; } // NOLINT(readability-identifier-naming) + virtual uint32_t idle_timeout_() { return 1000u; } // NOLINT(readability-identifier-naming) }; enum WaveshareEPaperTypeAModel { @@ -110,7 +110,7 @@ class WaveshareEPaperTypeA : public WaveshareEPaper { uint32_t full_update_every_{30}; uint32_t at_update_{0}; WaveshareEPaperTypeAModel model_; - int idle_timeout_() override; + uint32_t idle_timeout_() override; }; enum WaveshareEPaperTypeBModel { @@ -346,7 +346,7 @@ class WaveshareEPaper2P13InDKE : public WaveshareEPaper { int get_height_internal() override; - int idle_timeout_() override; + uint32_t idle_timeout_() override; uint32_t full_update_every_{30}; uint32_t at_update_{0}; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 1d346c0a8e..d2217eb88a 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -67,9 +67,9 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi memset(&event, 0, sizeof(IDFWiFiEvent)); event.event_base = event_base; event.event_id = event_id; - if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { // NOLINT(bugprone-branch-clone) // no data - } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_STOP) { + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_STOP) { // NOLINT(bugprone-branch-clone) // no data } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_AUTHMODE_CHANGE) { memcpy(&event.data.sta_authmode_change, event_data, sizeof(wifi_event_sta_authmode_change_t)); @@ -79,13 +79,13 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi memcpy(&event.data.sta_disconnected, event_data, sizeof(wifi_event_sta_disconnected_t)); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { memcpy(&event.data.ip_got_ip, event_data, sizeof(ip_event_got_ip_t)); - } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_LOST_IP) { + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_LOST_IP) { // NOLINT(bugprone-branch-clone) // no data } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) { memcpy(&event.data.sta_scan_done, event_data, sizeof(wifi_event_sta_scan_done_t)); - } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_START) { + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_START) { // NOLINT(bugprone-branch-clone) // no data - } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STOP) { + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STOP) { // NOLINT(bugprone-branch-clone) // no data } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_PROBEREQRECVED) { memcpy(&event.data.ap_probe_req_rx, event_data, sizeof(wifi_event_ap_probe_req_rx_t)); diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 884969f793..7588198c70 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -171,10 +171,8 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service result.type = XiaomiParseResult::TYPE_MUE4094RT; result.name = "MUE4094RT"; result.raw_offset -= 6; - } else if ((raw[2] == 0x47) && (raw[3] == 0x03)) { // ClearGrass-branded, round body, e-ink display - result.type = XiaomiParseResult::TYPE_CGG1; - result.name = "CGG1"; - } else if ((raw[2] == 0x48) && (raw[3] == 0x0B)) { // Qingping-branded, round body, e-ink display — with bindkeys + } else if ((raw[2] == 0x47 && raw[3] == 0x03) || // ClearGrass-branded, round body, e-ink display + (raw[2] == 0x48 && raw[3] == 0x0B)) { // Qingping-branded, round body, e-ink display — with bindkeys result.type = XiaomiParseResult::TYPE_CGG1; result.name = "CGG1"; } else if ((raw[2] == 0xbc) && (raw[3] == 0x03)) { // VegTrug Grow Care Garden diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index f67fc826cf..1bef99e868 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -94,7 +94,7 @@ void Application::loop() { } this->last_loop_ = now; - if (this->dump_config_at_ >= 0 && this->dump_config_at_ < this->components_.size()) { + if (this->dump_config_at_ < this->components_.size()) { if (this->dump_config_at_ == 0) { ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", this->compilation_time_.c_str()); #ifdef ESPHOME_PROJECT_NAME diff --git a/esphome/core/application.h b/esphome/core/application.h index ace0c9ad6d..f4fe571490 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -309,7 +309,7 @@ class Application { bool name_add_mac_suffix_; uint32_t last_loop_{0}; uint32_t loop_interval_{16}; - int dump_config_at_{-1}; + size_t dump_config_at_{SIZE_MAX}; uint32_t app_state_{0}; }; diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index f97818cfb1..591c9943b5 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -96,7 +96,7 @@ void Component::call() { // State loop: Call loop this->call_loop(); break; - case COMPONENT_STATE_FAILED: + case COMPONENT_STATE_FAILED: // NOLINT(bugprone-branch-clone) // State failed: Do nothing break; default: diff --git a/platformio.ini b/platformio.ini index 0f80d6d8d3..3c0b725d65 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,7 +11,6 @@ include_dir = [runtime] ; This are the flags as set by the runtime. build_flags = - -Wno-unused-variable -Wno-unused-but-set-variable -Wno-sign-compare @@ -19,10 +18,12 @@ build_flags = ; This are the flags for clang-tidy. build_flags = -Wall + -Wextra -Wunreachable-code -Wfor-loop-analysis -Wshadow-field -Wshadow-field-in-constructor + -Wshadow-uncaptured-local [common] lib_deps = diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 7ccdc5a24e..016a0995b9 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -649,7 +649,7 @@ def build_message_type(desc): o += f" {dump[0]} " else: o += "\n" - o += f" char buffer[64];\n" + o += f" __attribute__((unused)) char buffer[64];\n" o += f' out.append("{desc.name} {{\\n");\n' o += indent("\n".join(dump)) + "\n" o += f' out.append("}}");\n' From 54106179a11ec9ff4586f73f8246507165ba9390 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 1 Dec 2021 21:05:42 +0100 Subject: [PATCH 1789/1841] Set ESP32 watchdog to loop task (#2846) --- esphome/components/esp32/__init__.py | 7 ++++++ esphome/components/esp32/core.cpp | 35 ++++++++++++++++------------ esphome/components/esp8266/core.cpp | 1 + esphome/core/application.h | 2 ++ esphome/core/hal.h | 1 + sdkconfig.defaults | 4 ++++ 6 files changed, 35 insertions(+), 15 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 1c249476e7..d6f1180aa7 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -311,9 +311,16 @@ async def to_code(config): ) add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False) add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True) + # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000) + # Setup watchdog + add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT", True) + add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_PANIC", True) + add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0", False) + add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1", False) + cg.add_platformio_option("board_build.partitions", "partitions.csv") for name, value in conf[CONF_SDKCONFIG_OPTIONS].items(): diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index 359999120f..a9756b41cd 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -6,12 +6,17 @@ #include #include #include +#include #include #if ESP_IDF_VERSION_MAJOR >= 4 #include #endif +#ifdef USE_ARDUINO +#include +#endif + void setup(); void loop(); @@ -29,24 +34,24 @@ void arch_restart() { yield(); } } -void IRAM_ATTR HOT arch_feed_wdt() { -#ifdef USE_ARDUINO -#if CONFIG_ARDUINO_RUNNING_CORE == 0 -#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 - // ESP32 uses "Task Watchdog" which is hooked to the FreeRTOS idle task. - // To cause the Watchdog to be triggered we need to put the current task - // to sleep to get the idle task scheduled. - delay(1); -#endif -#endif -#endif // USE_ARDUINO -#ifdef USE_ESP_IDF -#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 - delay(1); +void arch_init() { + // Enable the task watchdog only on the loop task (from which we're currently running) +#if defined(USE_ESP_IDF) + esp_task_wdt_add(nullptr); + // Idle task watchdog is disabled on ESP-IDF +#elif defined(USE_ARDUINO) + enableLoopWDT(); + // Disable idle task watchdog on the core we're using (Arduino pins the process to a core) +#if CONFIG_ARDUINO_RUNNING_CORE == 0 + disableCore0WDT(); +#endif +#if CONFIG_ARDUINO_RUNNING_CORE == 1 + disableCore1WDT(); +#endif #endif -#endif // USE_ESP_IDF } +void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); } uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } uint32_t arch_get_cpu_cycle_count() { diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp index 137d4382b4..828d71a3bd 100644 --- a/esphome/components/esp8266/core.cpp +++ b/esphome/components/esp8266/core.cpp @@ -20,6 +20,7 @@ void arch_restart() { yield(); } } +void arch_init() {} void IRAM_ATTR HOT arch_feed_wdt() { ESP.wdtFeed(); // NOLINT(readability-static-accessed-through-instance) } diff --git a/esphome/core/application.h b/esphome/core/application.h index f4fe571490..2a20793c19 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -5,6 +5,7 @@ #include "esphome/core/defines.h" #include "esphome/core/preferences.h" #include "esphome/core/component.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/scheduler.h" @@ -47,6 +48,7 @@ namespace esphome { class Application { public: void pre_setup(const std::string &name, const char *compilation_time, bool name_add_mac_suffix) { + arch_init(); this->name_add_mac_suffix_ = name_add_mac_suffix; if (name_add_mac_suffix) { this->name_ = name + "-" + get_mac_address().substr(6); diff --git a/esphome/core/hal.h b/esphome/core/hal.h index a86dbf2534..034f9d692f 100644 --- a/esphome/core/hal.h +++ b/esphome/core/hal.h @@ -39,6 +39,7 @@ uint32_t micros(); void delay(uint32_t ms); void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming) void __attribute__((noreturn)) arch_restart(); +void arch_init(); void arch_feed_wdt(); uint32_t arch_get_cpu_cycle_count(); uint32_t arch_get_cpu_freq_hz(); diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 26db4705b8..72ca3f6e9c 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -9,6 +9,10 @@ CONFIG_PARTITION_TABLE_CUSTOM=y #CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_SINGLE_APP=n CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT=y +CONFIG_ESP_TASK_WDT_PANIC=y +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n # esp32_ble CONFIG_BT_ENABLED=y From caf352ff066275c0952b7dbf96a7b73ef9e6b5b7 Mon Sep 17 00:00:00 2001 From: Paul Nicholls Date: Thu, 2 Dec 2021 15:26:56 +1300 Subject: [PATCH 1790/1841] Tuya Cover improvements (#2637) --- esphome/components/tuya/cover/__init__.py | 24 +++++ esphome/components/tuya/cover/tuya_cover.cpp | 108 ++++++++++++++++--- esphome/components/tuya/cover/tuya_cover.h | 17 ++- esphome/components/tuya/tuya.cpp | 103 ++++++++++++------ esphome/components/tuya/tuya.h | 16 ++- 5 files changed, 215 insertions(+), 53 deletions(-) diff --git a/esphome/components/tuya/cover/__init__.py b/esphome/components/tuya/cover/__init__.py index 5a654841f7..f886c7030f 100644 --- a/esphome/components/tuya/cover/__init__.py +++ b/esphome/components/tuya/cover/__init__.py @@ -5,16 +5,27 @@ from esphome.const import ( CONF_OUTPUT_ID, CONF_MIN_VALUE, CONF_MAX_VALUE, + CONF_RESTORE_MODE, ) from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ["tuya"] +CONF_CONTROL_DATAPOINT = "control_datapoint" +CONF_DIRECTION_DATAPOINT = "direction_datapoint" CONF_POSITION_DATAPOINT = "position_datapoint" +CONF_POSITION_REPORT_DATAPOINT = "position_report_datapoint" CONF_INVERT_POSITION = "invert_position" TuyaCover = tuya_ns.class_("TuyaCover", cover.Cover, cg.Component) +TuyaCoverRestoreMode = tuya_ns.enum("TuyaCoverRestoreMode") +RESTORE_MODES = { + "NO_RESTORE": TuyaCoverRestoreMode.COVER_NO_RESTORE, + "RESTORE": TuyaCoverRestoreMode.COVER_RESTORE, + "RESTORE_AND_CALL": TuyaCoverRestoreMode.COVER_RESTORE_AND_CALL, +} + def validate_range(config): if config[CONF_MIN_VALUE] > config[CONF_MAX_VALUE]: @@ -29,10 +40,16 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaCover), cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Optional(CONF_CONTROL_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_DIRECTION_DATAPOINT): cv.uint8_t, cv.Required(CONF_POSITION_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_POSITION_REPORT_DATAPOINT): cv.uint8_t, cv.Optional(CONF_MIN_VALUE, default=0): cv.int_, cv.Optional(CONF_MAX_VALUE, default=100): cv.int_, cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, + cv.Optional(CONF_RESTORE_MODE, default="RESTORE"): cv.enum( + RESTORE_MODES, upper=True + ), }, ).extend(cv.COMPONENT_SCHEMA), validate_range, @@ -44,9 +61,16 @@ async def to_code(config): await cg.register_component(var, config) await cover.register_cover(var, config) + if CONF_CONTROL_DATAPOINT in config: + cg.add(var.set_control_id(config[CONF_CONTROL_DATAPOINT])) + if CONF_DIRECTION_DATAPOINT in config: + cg.add(var.set_direction_id(config[CONF_DIRECTION_DATAPOINT])) cg.add(var.set_position_id(config[CONF_POSITION_DATAPOINT])) + if CONF_POSITION_REPORT_DATAPOINT in config: + cg.add(var.set_position_report_id(config[CONF_POSITION_REPORT_DATAPOINT])) cg.add(var.set_min_value(config[CONF_MIN_VALUE])) cg.add(var.set_max_value(config[CONF_MAX_VALUE])) cg.add(var.set_invert_position(config[CONF_INVERT_POSITION])) + cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) paren = await cg.get_variable(config[CONF_TUYA_ID]) cg.add(var.set_tuya_parent(paren)) diff --git a/esphome/components/tuya/cover/tuya_cover.cpp b/esphome/components/tuya/cover/tuya_cover.cpp index 7da1312938..b63eb9109d 100644 --- a/esphome/components/tuya/cover/tuya_cover.cpp +++ b/esphome/components/tuya/cover/tuya_cover.cpp @@ -4,48 +4,122 @@ namespace esphome { namespace tuya { +const uint8_t COMMAND_OPEN = 0x00; +const uint8_t COMMAND_CLOSE = 0x02; +const uint8_t COMMAND_STOP = 0x01; + +using namespace esphome::cover; + static const char *const TAG = "tuya.cover"; void TuyaCover::setup() { this->value_range_ = this->max_value_ - this->min_value_; - if (this->position_id_.has_value()) { - this->parent_->register_listener(*this->position_id_, [this](const TuyaDatapoint &datapoint) { - auto pos = float(datapoint.value_uint - this->min_value_) / this->value_range_; - if (this->invert_position_) - pos = 1.0f - pos; - this->position = pos; - this->publish_state(); - }); + + this->parent_->add_on_initialized_callback([this]() { + // Set the direction (if configured/supported). + this->set_direction_(this->invert_position_); + + // Handle configured restore mode. + switch (this->restore_mode_) { + case COVER_NO_RESTORE: + break; + case COVER_RESTORE: { + auto restore = this->restore_state_(); + if (restore.has_value()) + restore->apply(this); + break; + } + case COVER_RESTORE_AND_CALL: { + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->to_call(this).perform(); + } + break; + } + } + }); + + uint8_t report_id = *this->position_id_; + if (this->position_report_id_.has_value()) { + // A position report datapoint is configured; listen to that instead. + report_id = *this->position_report_id_; } + + this->parent_->register_listener(report_id, [this](const TuyaDatapoint &datapoint) { + if (datapoint.value_int == 123) { + ESP_LOGD(TAG, "Ignoring MCU position report - not calibrated"); + return; + } + auto pos = float(datapoint.value_uint - this->min_value_) / this->value_range_; + this->position = 1.0f - pos; + this->publish_state(); + }); } void TuyaCover::control(const cover::CoverCall &call) { if (call.get_stop()) { - auto pos = this->position; - if (this->invert_position_) + if (this->control_id_.has_value()) { + this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_STOP); + } else { + auto pos = this->position; pos = 1.0f - pos; - auto position_int = static_cast(pos * this->value_range_); - position_int = position_int + this->min_value_; + auto position_int = static_cast(pos * this->value_range_); + position_int = position_int + this->min_value_; - parent_->set_integer_datapoint_value(*this->position_id_, position_int); + parent_->set_integer_datapoint_value(*this->position_id_, position_int); + } } if (call.get_position().has_value()) { auto pos = *call.get_position(); - if (this->invert_position_) + if (this->control_id_.has_value() && (pos == COVER_OPEN || pos == COVER_CLOSED)) { + if (pos == COVER_OPEN) { + this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_OPEN); + } else { + this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_CLOSE); + } + } else { pos = 1.0f - pos; - auto position_int = static_cast(pos * this->value_range_); - position_int = position_int + this->min_value_; + auto position_int = static_cast(pos * this->value_range_); + position_int = position_int + this->min_value_; - parent_->set_integer_datapoint_value(*this->position_id_, position_int); + parent_->set_integer_datapoint_value(*this->position_id_, position_int); + } } this->publish_state(); } +void TuyaCover::set_direction_(bool inverted) { + if (!this->direction_id_.has_value()) { + return; + } + + if (inverted) { + ESP_LOGD(TAG, "Setting direction: inverted"); + } else { + ESP_LOGD(TAG, "Setting direction: normal"); + } + + this->parent_->set_boolean_datapoint_value(*this->direction_id_, inverted); +} + void TuyaCover::dump_config() { ESP_LOGCONFIG(TAG, "Tuya Cover:"); + if (this->invert_position_) { + if (this->direction_id_.has_value()) { + ESP_LOGCONFIG(TAG, " Inverted"); + } else { + ESP_LOGCONFIG(TAG, " Configured as Inverted, but direction_datapoint isn't configured"); + } + } + if (this->control_id_.has_value()) + ESP_LOGCONFIG(TAG, " Control has datapoint ID %u", *this->control_id_); + if (this->direction_id_.has_value()) + ESP_LOGCONFIG(TAG, " Direction has datapoint ID %u", *this->direction_id_); if (this->position_id_.has_value()) ESP_LOGCONFIG(TAG, " Position has datapoint ID %u", *this->position_id_); + if (this->position_report_id_.has_value()) + ESP_LOGCONFIG(TAG, " Position Report has datapoint ID %u", *this->position_report_id_); } cover::CoverTraits TuyaCover::get_traits() { diff --git a/esphome/components/tuya/cover/tuya_cover.h b/esphome/components/tuya/cover/tuya_cover.h index c3b0c3e069..87c72b0e66 100644 --- a/esphome/components/tuya/cover/tuya_cover.h +++ b/esphome/components/tuya/cover/tuya_cover.h @@ -7,22 +7,37 @@ namespace esphome { namespace tuya { +enum TuyaCoverRestoreMode { + COVER_NO_RESTORE, + COVER_RESTORE, + COVER_RESTORE_AND_CALL, +}; + class TuyaCover : public cover::Cover, public Component { public: void setup() override; void dump_config() override; - void set_position_id(uint8_t dimmer_id) { this->position_id_ = dimmer_id; } + void set_control_id(uint8_t control_id) { this->control_id_ = control_id; } + void set_direction_id(uint8_t direction_id) { this->direction_id_ = direction_id; } + void set_position_id(uint8_t position_id) { this->position_id_ = position_id; } + void set_position_report_id(uint8_t position_report_id) { this->position_report_id_ = position_report_id; } void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } void set_min_value(uint32_t min_value) { min_value_ = min_value; } void set_max_value(uint32_t max_value) { max_value_ = max_value; } void set_invert_position(bool invert_position) { invert_position_ = invert_position; } + void set_restore_mode(TuyaCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; } protected: void control(const cover::CoverCall &call) override; + void set_direction_(bool inverted); cover::CoverTraits get_traits() override; Tuya *parent_; + TuyaCoverRestoreMode restore_mode_{}; + optional control_id_{}; + optional direction_id_{}; optional position_id_{}; + optional position_report_id_{}; uint32_t min_value_; uint32_t max_value_; uint32_t value_range_; diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 2677731224..404a70a80e 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -195,6 +195,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff if (this->init_state_ == TuyaInitState::INIT_DATAPOINT) { this->init_state_ = TuyaInitState::INIT_DONE; this->set_timeout("datapoint_dump", 1000, [this] { this->dump_config(); }); + this->initialized_callback_.call(); } this->handle_datapoint_(buffer, len); break; @@ -439,53 +440,51 @@ void Tuya::send_local_time_() { #endif void Tuya::set_raw_datapoint_value(uint8_t datapoint_id, const std::vector &value) { - ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, hexencode(value).c_str()); - optional datapoint = this->get_datapoint_(datapoint_id); - if (!datapoint.has_value()) { - ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); - } else if (datapoint->type != TuyaDatapointType::RAW) { - ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); - return; - } else if (datapoint->value_raw == value) { - ESP_LOGV(TAG, "Not sending unchanged value"); - return; - } - this->send_datapoint_command_(datapoint_id, TuyaDatapointType::RAW, value); + this->set_raw_datapoint_value_(datapoint_id, value, false); } void Tuya::set_boolean_datapoint_value(uint8_t datapoint_id, bool value) { - this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1); + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1, false); } void Tuya::set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) { - this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4); + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4, false); } void Tuya::set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) { - ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, value.c_str()); - optional datapoint = this->get_datapoint_(datapoint_id); - if (!datapoint.has_value()) { - ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); - } else if (datapoint->type != TuyaDatapointType::STRING) { - ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); - return; - } else if (datapoint->value_string == value) { - ESP_LOGV(TAG, "Not sending unchanged value"); - return; - } - std::vector data; - for (char const &c : value) { - data.push_back(c); - } - this->send_datapoint_command_(datapoint_id, TuyaDatapointType::STRING, data); + this->set_string_datapoint_value_(datapoint_id, value, false); } void Tuya::set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) { - this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1); + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1, false); } void Tuya::set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) { - this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length); + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length, false); +} + +void Tuya::force_set_raw_datapoint_value(uint8_t datapoint_id, const std::vector &value) { + this->set_raw_datapoint_value_(datapoint_id, value, true); +} + +void Tuya::force_set_boolean_datapoint_value(uint8_t datapoint_id, bool value) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1, true); +} + +void Tuya::force_set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4, true); +} + +void Tuya::force_set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) { + this->set_string_datapoint_value_(datapoint_id, value, true); +} + +void Tuya::force_set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1, true); +} + +void Tuya::force_set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length, true); } optional Tuya::get_datapoint_(uint8_t datapoint_id) { @@ -496,7 +495,7 @@ optional Tuya::get_datapoint_(uint8_t datapoint_id) { } void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, const uint32_t value, - uint8_t length) { + uint8_t length, bool forced) { ESP_LOGD(TAG, "Setting datapoint %u to %u", datapoint_id, value); optional datapoint = this->get_datapoint_(datapoint_id); if (!datapoint.has_value()) { @@ -504,7 +503,7 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType } else if (datapoint->type != datapoint_type) { ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); return; - } else if (datapoint->value_uint == value) { + } else if (!forced && datapoint->value_uint == value) { ESP_LOGV(TAG, "Not sending unchanged value"); return; } @@ -526,6 +525,40 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType this->send_datapoint_command_(datapoint_id, datapoint_type, data); } +void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector &value, bool forced) { + ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, hexencode(value).c_str()); + optional datapoint = this->get_datapoint_(datapoint_id); + if (!datapoint.has_value()) { + ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); + } else if (datapoint->type != TuyaDatapointType::RAW) { + ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); + return; + } else if (!forced && datapoint->value_raw == value) { + ESP_LOGV(TAG, "Not sending unchanged value"); + return; + } + this->send_datapoint_command_(datapoint_id, TuyaDatapointType::RAW, value); +} + +void Tuya::set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced) { + ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, value.c_str()); + optional datapoint = this->get_datapoint_(datapoint_id); + if (!datapoint.has_value()) { + ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); + } else if (datapoint->type != TuyaDatapointType::STRING) { + ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); + return; + } else if (!forced && datapoint->value_string == value) { + ESP_LOGV(TAG, "Not sending unchanged value"); + return; + } + std::vector data; + for (char const &c : value) { + data.push_back(c); + } + this->send_datapoint_command_(datapoint_id, TuyaDatapointType::STRING, data); +} + void Tuya::send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector data) { std::vector buffer; buffer.push_back(datapoint_id); @@ -550,5 +583,7 @@ void Tuya::register_listener(uint8_t datapoint_id, const std::functioninit_state_; } + } // namespace tuya } // namespace esphome diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 785399502b..c46d61119e 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" #include "esphome/components/uart/uart.h" #ifdef USE_TIME @@ -81,12 +82,22 @@ class Tuya : public Component, public uart::UARTDevice { void set_string_datapoint_value(uint8_t datapoint_id, const std::string &value); void set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value); void set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length); + void force_set_raw_datapoint_value(uint8_t datapoint_id, const std::vector &value); + void force_set_boolean_datapoint_value(uint8_t datapoint_id, bool value); + void force_set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value); + void force_set_string_datapoint_value(uint8_t datapoint_id, const std::string &value); + void force_set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value); + void force_set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length); + TuyaInitState get_init_state(); #ifdef USE_TIME void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; } #endif void add_ignore_mcu_update_on_datapoints(uint8_t ignore_mcu_update_on_datapoints) { this->ignore_mcu_update_on_datapoints_.push_back(ignore_mcu_update_on_datapoints); } + void add_on_initialized_callback(std::function callback) { + this->initialized_callback_.add(std::move(callback)); + } protected: void handle_char_(uint8_t c); @@ -100,7 +111,9 @@ class Tuya : public Component, public uart::UARTDevice { void send_command_(const TuyaCommand &command); void send_empty_command_(TuyaCommandType command); void set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, uint32_t value, - uint8_t length); + uint8_t length, bool forced); + void set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced); + void set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector &value, bool forced); void send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector data); void send_wifi_status_(); @@ -122,6 +135,7 @@ class Tuya : public Component, public uart::UARTDevice { std::vector command_queue_; optional expected_response_{}; uint8_t wifi_status_ = -1; + CallbackManager initialized_callback_{}; }; } // namespace tuya From 1b88b7a1664396cabb3a339eecbf12f1c0fe2c29 Mon Sep 17 00:00:00 2001 From: Alexandre-Jacques St-Jacques Date: Wed, 1 Dec 2021 21:33:48 -0500 Subject: [PATCH 1791/1841] Fix wifi not working with manual_ip using esp-idf (#2849) --- esphome/components/wifi/wifi_component_esp_idf.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index d2217eb88a..5a81fd0a39 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -430,7 +430,7 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { info.netmask.addr = static_cast(manual_ip->subnet); err = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); - if (err != ESP_OK) { + if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) { ESP_LOGV(TAG, "tcpip_adapter_dhcpc_stop failed: %s", esp_err_to_name(err)); return false; } From 9ca4e8f32a78d9ec0bbb247ba67504682092c371 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Thu, 2 Dec 2021 03:45:11 +0100 Subject: [PATCH 1792/1841] modbus_controller: bugfix: enable overriding calculated register size (#2845) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/modbus_controller/__init__.py | 5 +++++ esphome/components/modbus_controller/modbus_controller.h | 8 +++++--- .../modbus_controller/text_sensor/modbus_textsensor.cpp | 2 +- .../modbus_controller/text_sensor/modbus_textsensor.h | 3 +-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 825b91280e..b927faf9a7 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -13,6 +13,7 @@ from .const import ( CONF_MODBUS_CONTROLLER_ID, CONF_REGISTER_COUNT, CONF_REGISTER_TYPE, + CONF_RESPONSE_SIZE, CONF_SKIP_UPDATES, CONF_VALUE_TYPE, ) @@ -125,6 +126,7 @@ ModbusItemBaseSchema = cv.Schema( cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_RESPONSE_SIZE, default=0): cv.positive_int, }, ) @@ -180,6 +182,9 @@ async def add_modbus_base_properties( if CONF_CUSTOM_COMMAND in config: cg.add(var.set_custom_data(config[CONF_CUSTOM_COMMAND])) + if config[CONF_RESPONSE_SIZE] > 0: + cg.add(var.set_register_size(config[CONF_RESPONSE_SIZE])) + if CONF_LAMBDA in config: template_ = await cg.process_lambda( config[CONF_LAMBDA], diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 045075f5e0..f4948e6ff9 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -254,16 +254,18 @@ class SensorItem { size_t virtual get_register_size() const { if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) return 1; - else - return register_count * 2; + else // if CONF_RESPONSE_BYTES is used override the default + return response_bytes > 0 ? response_bytes : register_count * 2; } - + // Override register size for modbus devices not using 1 register for one dword + void set_register_size(uint8_t register_size) { response_bytes = register_size; } ModbusRegisterType register_type; SensorValueType sensor_value_type; uint16_t start_address; uint32_t bitmask; uint8_t offset; uint8_t register_count; + uint8_t response_bytes{0}; uint8_t skip_updates; std::vector custom_data{}; bool force_new_range{false}; diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp index a06d44e90b..25b79474e8 100644 --- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp @@ -13,7 +13,7 @@ void ModbusTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Modbus Controller Te void ModbusTextSensor::parse_and_publish(const std::vector &data) { std::ostringstream output; - uint8_t max_items = this->response_bytes_; + uint8_t max_items = this->response_bytes; char buffer[4]; bool add_comma = false; for (auto b : data) { diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h index 77b5b9363a..3db4d94a45 100644 --- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h @@ -17,7 +17,7 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi this->register_type = register_type; this->start_address = start_address; this->offset = offset; - this->response_bytes_ = response_bytes; + this->response_bytes = response_bytes; this->register_count = register_count; this->encode_ = encode; this->skip_updates = skip_updates; @@ -38,7 +38,6 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi protected: RawEncoding encode_; - uint16_t response_bytes_; }; } // namespace modbus_controller From 6a0b34328909d30ea23cb49e3c5295e4e5f652c1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 2 Dec 2021 19:38:49 +1300 Subject: [PATCH 1793/1841] Bump version to 2022.1.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 740e38cf44..df86216447 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.12.0-dev" +__version__ = "2022.1.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From f0bcf81a98efa57f721bbc6648d48189fe830126 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 2 Dec 2021 21:23:11 +1300 Subject: [PATCH 1794/1841] Add a simple helper to remap values (#2850) --- esphome/core/helpers.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 63aa4123ae..8b61e6aa38 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -415,4 +415,14 @@ optional parse_number(const std::string &str) { ///@} +/// @name Number manipulation +///@{ + +/// Remap a number from one range to another. +template T remap(U value, U min, U max, T min_out, T max_out) { + return (value - min) * (max_out - min_out) / (max - min) + min_out; +} + +///@} + } // namespace esphome From 40c017fd5461287116909c8d003869d5117f9544 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 2 Dec 2021 19:52:56 +0100 Subject: [PATCH 1795/1841] Update ota_component.cpp (#2852) --- esphome/components/ota/ota_component.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 79edd91173..0cf5ea242c 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -277,6 +277,7 @@ void OTAComponent::handle_() { ssize_t read = this->client_->read(buf, requested); if (read == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { + App.feed_wdt(); delay(1); continue; } @@ -305,8 +306,9 @@ void OTAComponent::handle_() { #ifdef USE_OTA_STATE_CALLBACK this->state_callback_.call(OTA_IN_PROGRESS, percentage, 0); #endif - // slow down OTA update to avoid getting killed by task watchdog (task_wdt) - delay(10); + // feed watchdog and give other tasks a chance to run + App.feed_wdt(); + yield(); } } From 06da540ab03f9b25df876d004b02116fd0fb2674 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Dec 2021 13:29:08 +0100 Subject: [PATCH 1796/1841] Bump pylint from 2.12.1 to 2.12.2 (#2858) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index b916e8bb1b..abfd07f022 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.12.1 +pylint==2.12.2 flake8==4.0.1 black==21.11b1 pre-commit From ef44acbf10322e8d3ed1013ee050aa57befd6003 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Dec 2021 19:42:49 +1300 Subject: [PATCH 1797/1841] Bump esphome-dashboard to 20211206.0 (#2870) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6061476802..22cfeecd5d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20211201.0 +esphome-dashboard==20211206.0 aioesphomeapi==10.6.0 zeroconf==0.36.13 From 10e89a7dbbcb1b8a7894333c35cfc49acbc3ffce Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 6 Dec 2021 07:54:46 +0100 Subject: [PATCH 1798/1841] tlc59208f : fix compilation error (#2867) --- esphome/components/tlc59208f/tlc59208f_output.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/tlc59208f/tlc59208f_output.cpp b/esphome/components/tlc59208f/tlc59208f_output.cpp index 59fb9f98ed..bd62f8de6d 100644 --- a/esphome/components/tlc59208f/tlc59208f_output.cpp +++ b/esphome/components/tlc59208f/tlc59208f_output.cpp @@ -1,6 +1,7 @@ #include "tlc59208f_output.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" namespace esphome { namespace tlc59208f { From c84efe64d3f66d3a6eb63871c56ae7ba49b8210e Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Mon, 6 Dec 2021 07:56:53 +0100 Subject: [PATCH 1799/1841] ADC: Turn verbose the debugging "got voltage" (#2863) --- esphome/components/adc/adc_sensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index c8242ce008..0a439f8b8d 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -91,7 +91,7 @@ void ADCSensor::dump_config() { float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } void ADCSensor::update() { float value_v = this->sample(); - ESP_LOGD(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); + ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); this->publish_state(value_v); } From 14f6ae75ea0efdd7118d82286b591ba7f8d746d0 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 6 Dec 2021 07:58:26 +0100 Subject: [PATCH 1800/1841] SPS30 : fix i2c read size (#2866) --- esphome/components/sps30/sps30.cpp | 26 ++++++++++++++++---------- esphome/components/sps30/sps30.h | 1 + 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/esphome/components/sps30/sps30.cpp b/esphome/components/sps30/sps30.cpp index 472b7606ed..6160120564 100644 --- a/esphome/components/sps30/sps30.cpp +++ b/esphome/components/sps30/sps30.cpp @@ -32,14 +32,11 @@ void SPS30Component::setup() { return; } - uint16_t raw_firmware_version[4]; - if (!this->read_data_(raw_firmware_version, 4)) { + if (!this->read_data_(&raw_firmware_version_, 1)) { this->error_code_ = FIRMWARE_VERSION_READ_FAILED; this->mark_failed(); return; } - ESP_LOGD(TAG, " Firmware version v%0d.%02d", (raw_firmware_version[0] >> 8), - uint16_t(raw_firmware_version[0] & 0xFF)); /// Serial number identification if (!this->write_command_(SPS30_CMD_GET_SERIAL_NUMBER)) { this->error_code_ = SERIAL_NUMBER_REQUEST_FAILED; @@ -59,6 +56,8 @@ void SPS30Component::setup() { this->serial_number_[i * 2 + 1] = uint16_t(uint16_t(raw_serial_number[i] & 0xFF)); } ESP_LOGD(TAG, " Serial Number: '%s'", this->serial_number_); + this->status_clear_warning(); + this->skipped_data_read_cycles_ = 0; this->start_continuous_measurement_(); }); } @@ -93,10 +92,17 @@ void SPS30Component::dump_config() { } LOG_UPDATE_INTERVAL(this); ESP_LOGCONFIG(TAG, " Serial Number: '%s'", this->serial_number_); - LOG_SENSOR(" ", "PM1.0", this->pm_1_0_sensor_); - LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_); - LOG_SENSOR(" ", "PM4", this->pm_4_0_sensor_); - LOG_SENSOR(" ", "PM10", this->pm_10_0_sensor_); + ESP_LOGCONFIG(TAG, " Firmware version v%0d.%0d", (raw_firmware_version_ >> 8), + uint16_t(raw_firmware_version_ & 0xFF)); + LOG_SENSOR(" ", "PM1.0 Weight Concentration", this->pm_1_0_sensor_); + LOG_SENSOR(" ", "PM2.5 Weight Concentration", this->pm_2_5_sensor_); + LOG_SENSOR(" ", "PM4 Weight Concentration", this->pm_4_0_sensor_); + LOG_SENSOR(" ", "PM10 Weight Concentration", this->pm_10_0_sensor_); + LOG_SENSOR(" ", "PM1.0 Number Concentration", this->pmc_1_0_sensor_); + LOG_SENSOR(" ", "PM2.5 Number Concentration", this->pmc_2_5_sensor_); + LOG_SENSOR(" ", "PM4 Number Concentration", this->pmc_4_0_sensor_); + LOG_SENSOR(" ", "PM10 Number Concentration", this->pmc_10_0_sensor_); + LOG_SENSOR(" ", "PM typical size", this->pm_size_sensor_); } void SPS30Component::update() { @@ -123,8 +129,8 @@ void SPS30Component::update() { return; } - uint16_t raw_read_status[1]; - if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) { + uint16_t raw_read_status; + if (!this->read_data_(&raw_read_status, 1) || raw_read_status == 0x00) { ESP_LOGD(TAG, "Sensor measurement not ready yet."); this->skipped_data_read_cycles_++; /// The following logic is required to address the cases when a sensor is quickly replaced before it's marked diff --git a/esphome/components/sps30/sps30.h b/esphome/components/sps30/sps30.h index 2f977252a5..bae33a46e1 100644 --- a/esphome/components/sps30/sps30.h +++ b/esphome/components/sps30/sps30.h @@ -33,6 +33,7 @@ class SPS30Component : public PollingComponent, public i2c::I2CDevice { bool read_data_(uint16_t *data, uint8_t len); uint8_t sht_crc_(uint8_t data1, uint8_t data2); char serial_number_[17] = {0}; /// Terminating NULL character + uint16_t raw_firmware_version_; bool start_continuous_measurement_(); uint8_t skipped_data_read_cycles_ = 0; From d3e48e296f81f47c65de8d005023e272e64510af Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 6 Dec 2021 07:59:50 +0100 Subject: [PATCH 1801/1841] Fix MCP23x17 not disabling pullup after config change (#2855) --- esphome/components/mcp23x17_base/mcp23x17_base.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/mcp23x17_base/mcp23x17_base.cpp b/esphome/components/mcp23x17_base/mcp23x17_base.cpp index e975670faa..744f2fbe9c 100644 --- a/esphome/components/mcp23x17_base/mcp23x17_base.cpp +++ b/esphome/components/mcp23x17_base/mcp23x17_base.cpp @@ -24,6 +24,7 @@ void MCP23X17Base::pin_mode(uint8_t pin, gpio::Flags flags) { uint8_t gppu = pin < 8 ? mcp23x17_base::MCP23X17_GPPUA : mcp23x17_base::MCP23X17_GPPUB; if (flags == gpio::FLAG_INPUT) { this->update_reg(pin, true, iodir); + this->update_reg(pin, false, gppu); } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { this->update_reg(pin, true, iodir); this->update_reg(pin, true, gppu); From ffc112c9d04e9ffd642ac2cf7284fc302aaa207c Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 6 Dec 2021 08:01:14 +0100 Subject: [PATCH 1802/1841] Don't disable idle task WDT when it's not enabled (#2856) --- esphome/components/esp32/core.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index a9756b41cd..6123d83a34 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -42,11 +42,11 @@ void arch_init() { // Idle task watchdog is disabled on ESP-IDF #elif defined(USE_ARDUINO) enableLoopWDT(); - // Disable idle task watchdog on the core we're using (Arduino pins the process to a core) -#if CONFIG_ARDUINO_RUNNING_CORE == 0 + // Disable idle task watchdog on the core we're using (Arduino pins the task to a core) +#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0) && CONFIG_ARDUINO_RUNNING_CORE == 0 disableCore0WDT(); #endif -#if CONFIG_ARDUINO_RUNNING_CORE == 1 +#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1) && CONFIG_ARDUINO_RUNNING_CORE == 1 disableCore1WDT(); #endif #endif From 71fe2f7ed334c84d209fd641ec99bd090e1b79da Mon Sep 17 00:00:00 2001 From: Massimiliano Ravelli Date: Mon, 6 Dec 2021 08:01:50 +0100 Subject: [PATCH 1803/1841] Ignore already stopped dhcp for ethernet (#2862) Co-authored-by: Oxan van Leeuwen Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/ethernet/ethernet_component.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 00f68df2b4..384a31ed2f 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -184,7 +184,9 @@ void EthernetComponent::start_connect_() { } err = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_ETH); - ESPHL_ERROR_CHECK(err, "DHCPC stop error"); + if (err != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED) { + ESPHL_ERROR_CHECK(err, "DHCPC stop error"); + } err = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_ETH, &info); ESPHL_ERROR_CHECK(err, "DHCPC set IP info error"); From 55db1908758ff3b5585b01b8563c2549d9784eb9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Dec 2021 20:15:34 +1300 Subject: [PATCH 1804/1841] Add endpoint to fetch secrets keys (#2873) --- esphome/dashboard/dashboard.py | 25 ++++++++++++++++++++++++- esphome/yaml_util.py | 7 ++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index c98047d9e5..5e5cc4ecd2 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -27,7 +27,7 @@ import tornado.process import tornado.web import tornado.websocket -from esphome import const, platformio_api, util +from esphome import const, platformio_api, util, yaml_util from esphome.helpers import mkdir_p, get_bool_env, run_system_command from esphome.storage_json import ( EsphomeStorageJSON, @@ -836,6 +836,28 @@ class LogoutHandler(BaseHandler): self.redirect("./login") +class SecretKeysRequestHandler(BaseHandler): + @authenticated + def get(self): + + filename = None + + for secret_filename in const.SECRETS_FILES: + relative_filename = settings.rel_path(secret_filename) + if os.path.isfile(relative_filename): + filename = relative_filename + break + + if filename is None: + self.send_error(404) + return + + secret_keys = list(yaml_util.load_yaml(filename, clear_secrets=False)) + + self.set_header("content-type", "application/json") + self.write(json.dumps(secret_keys)) + + def get_base_frontend_path(): if ENV_DEV not in os.environ: import esphome_dashboard @@ -939,6 +961,7 @@ def make_app(debug=get_bool_env(ENV_DEV)): (f"{rel}static/(.*)", StaticFileHandler, {"path": get_static_path()}), (f"{rel}devices", ListDevicesHandler), (f"{rel}import", ImportRequestHandler), + (f"{rel}secret_keys", SecretKeysRequestHandler), ], **app_settings, ) diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index bdadbbd43a..57009be57e 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -329,9 +329,10 @@ ESPHomeLoader.add_constructor("!lambda", ESPHomeLoader.construct_lambda) ESPHomeLoader.add_constructor("!force", ESPHomeLoader.construct_force) -def load_yaml(fname): - _SECRET_VALUES.clear() - _SECRET_CACHE.clear() +def load_yaml(fname, clear_secrets=True): + if clear_secrets: + _SECRET_VALUES.clear() + _SECRET_CACHE.clear() return _load_yaml_internal(fname) From 49932747b31c8fb8fdc3b7650abb4911a026f308 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Dec 2021 20:57:56 +1300 Subject: [PATCH 1805/1841] Adopt using wifi secrets that should exist at this point (#2874) --- esphome/components/dashboard_import/__init__.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/esphome/components/dashboard_import/__init__.py b/esphome/components/dashboard_import/__init__.py index d483c77c61..4c47c32ccc 100644 --- a/esphome/components/dashboard_import/__init__.py +++ b/esphome/components/dashboard_import/__init__.py @@ -29,12 +29,11 @@ CONFIG_SCHEMA = cv.Schema( } ) -WIFI_MESSAGE = """ +WIFI_CONFIG = """ -# Do not forget to add your own wifi configuration before installing this configuration -# wifi: -# ssid: !secret wifi_ssid -# password: !secret wifi_password +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password """ @@ -55,6 +54,6 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N "esphome": {"name_add_mac_suffix": False}, } p.write_text( - dump(config) + WIFI_MESSAGE, + dump(config) + WIFI_CONFIG, encoding="utf8", ) From 1db7043a4de5491a8fd68be2f9a24e50de11337e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Dec 2021 20:58:51 +1300 Subject: [PATCH 1806/1841] Allow wizard to specify secrets (#2875) --- esphome/wizard.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/esphome/wizard.py b/esphome/wizard.py index 6c87b66453..5f4f347ba7 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -86,12 +86,11 @@ def wizard_file(**kwargs): config += "\n\nwifi:\n" if "ssid" in kwargs: - # pylint: disable=consider-using-f-string - config += """ ssid: "{ssid}" - password: "{psk}" -""".format( - **kwargs - ) + if kwargs["ssid"].startswith("!secret"): + template = " ssid: {ssid}\n password: {psk}\n" + else: + template = """ ssid: "{ssid}"\n password: "{psk}"\n""" + config += template.format(**kwargs) else: config += """ # ssid: "My SSID" # password: "mypassword" From 12467a18e63f38ec600f97d4dfe7e76baa916aec Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 6 Dec 2021 19:24:20 +0100 Subject: [PATCH 1807/1841] Feed watchdog when no component loops (#2857) --- esphome/core/application.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 1bef99e868..a423397453 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -37,6 +37,7 @@ void Application::setup() { component->call(); this->scheduler.process_to_add(); + this->feed_wdt(); if (component->can_proceed()) continue; @@ -46,14 +47,15 @@ void Application::setup() { do { uint32_t new_app_state = STATUS_LED_WARNING; this->scheduler.call(); + this->feed_wdt(); for (uint32_t j = 0; j <= i; j++) { this->components_[j]->call(); new_app_state |= this->components_[j]->get_component_state(); this->app_state_ |= new_app_state; + this->feed_wdt(); } this->app_state_ = new_app_state; yield(); - this->feed_wdt(); } while (!component->can_proceed()); } @@ -65,6 +67,7 @@ void Application::loop() { uint32_t new_app_state = 0; this->scheduler.call(); + this->feed_wdt(); for (Component *component : this->looping_components_) { { WarnIfComponentBlockingGuard guard{component}; From 5404617d435a3e95c8e7e69a9d16039fa1d12094 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 7 Dec 2021 07:41:40 +1300 Subject: [PATCH 1808/1841] Bump esphome-dashboard to 20211207.0 (#2877) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 22cfeecd5d..e27ae0f625 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20211206.0 +esphome-dashboard==20211207.0 aioesphomeapi==10.6.0 zeroconf==0.36.13 From e5cc19de43a94bcfb413106b4f2d26f7777bf8a1 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Mon, 6 Dec 2021 23:26:06 +0100 Subject: [PATCH 1809/1841] Feed watchdog while setting up OTA (#2876) --- esphome/components/ota/ota_component.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 0cf5ea242c..92256eb1b6 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -372,6 +372,7 @@ bool OTAComponent::readall_(uint8_t *buf, size_t len) { ssize_t read = this->client_->read(buf + at, len - at); if (read == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { + App.feed_wdt(); delay(1); continue; } @@ -383,6 +384,7 @@ bool OTAComponent::readall_(uint8_t *buf, size_t len) { } else { at += read; } + App.feed_wdt(); delay(1); } @@ -401,6 +403,7 @@ bool OTAComponent::writeall_(const uint8_t *buf, size_t len) { ssize_t written = this->client_->write(buf + at, len - at); if (written == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { + App.feed_wdt(); delay(1); continue; } @@ -409,6 +412,7 @@ bool OTAComponent::writeall_(const uint8_t *buf, size_t len) { } else { at += written; } + App.feed_wdt(); delay(1); } return true; From 2253d4bc165ec8fa46795202c028a89f22d8c6d2 Mon Sep 17 00:00:00 2001 From: Yuval Brik Date: Tue, 7 Dec 2021 00:30:27 +0200 Subject: [PATCH 1810/1841] Support different run duration for non-timer wakeup (#2861) --- esphome/components/deep_sleep/__init__.py | 40 +++++++++++++- .../deep_sleep/deep_sleep_component.cpp | 35 ++++++++++++- .../deep_sleep/deep_sleep_component.h | 19 +++++++ .../deep_sleep/test_deep_sleep.py | 52 +++++++++++++++++++ .../deep_sleep/test_deep_sleep1.yaml | 9 ++++ .../deep_sleep/test_deep_sleep2.yaml | 11 ++++ tests/test2.yaml | 5 +- 7 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 tests/component_tests/deep_sleep/test_deep_sleep.py create mode 100644 tests/component_tests/deep_sleep/test_deep_sleep1.yaml create mode 100644 tests/component_tests/deep_sleep/test_deep_sleep2.yaml diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index f47888b8eb..ba4c2c0d7e 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -41,15 +41,30 @@ EXT1_WAKEUP_MODES = { "ALL_LOW": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ALL_LOW, "ANY_HIGH": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_HIGH, } +WakeupCauseToRunDuration = deep_sleep_ns.struct("WakeupCauseToRunDuration") CONF_WAKEUP_PIN_MODE = "wakeup_pin_mode" CONF_ESP32_EXT1_WAKEUP = "esp32_ext1_wakeup" CONF_TOUCH_WAKEUP = "touch_wakeup" +CONF_DEFAULT = "default" +CONF_GPIO_WAKEUP_REASON = "gpio_wakeup_reason" +CONF_TOUCH_WAKEUP_REASON = "touch_wakeup_reason" + +WAKEUP_CAUSES_SCHEMA = cv.Schema( + { + cv.Required(CONF_DEFAULT): cv.positive_time_period_milliseconds, + cv.Optional(CONF_TOUCH_WAKEUP_REASON): cv.positive_time_period_milliseconds, + cv.Optional(CONF_GPIO_WAKEUP_REASON): cv.positive_time_period_milliseconds, + } +) CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(DeepSleepComponent), - cv.Optional(CONF_RUN_DURATION): cv.positive_time_period_milliseconds, + cv.Optional(CONF_RUN_DURATION): cv.Any( + cv.All(cv.only_on_esp32, WAKEUP_CAUSES_SCHEMA), + cv.positive_time_period_milliseconds, + ), cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds, cv.Optional(CONF_WAKEUP_PIN): cv.All( cv.only_on_esp32, pins.internal_gpio_input_pin_schema, validate_pin_number @@ -85,7 +100,28 @@ async def to_code(config): if CONF_WAKEUP_PIN_MODE in config: cg.add(var.set_wakeup_pin_mode(config[CONF_WAKEUP_PIN_MODE])) if CONF_RUN_DURATION in config: - cg.add(var.set_run_duration(config[CONF_RUN_DURATION])) + run_duration_config = config[CONF_RUN_DURATION] + if not isinstance(run_duration_config, dict): + cg.add(var.set_run_duration(config[CONF_RUN_DURATION])) + else: + default_run_duration = run_duration_config[CONF_DEFAULT] + wakeup_cause_to_run_duration = cg.StructInitializer( + WakeupCauseToRunDuration, + ("default_cause", default_run_duration), + ( + "touch_cause", + run_duration_config.get( + CONF_TOUCH_WAKEUP_REASON, default_run_duration + ), + ), + ( + "gpio_cause", + run_duration_config.get( + CONF_GPIO_WAKEUP_REASON, default_run_duration + ), + ), + ) + cg.add(var.set_run_duration(wakeup_cause_to_run_duration)) if CONF_ESP32_EXT1_WAKEUP in config: conf = config[CONF_ESP32_EXT1_WAKEUP] diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index c854b6da6e..7774014d3d 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -13,12 +13,35 @@ static const char *const TAG = "deep_sleep"; bool global_has_deep_sleep = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +optional DeepSleepComponent::get_run_duration_() const { +#ifdef USE_ESP32 + if (this->wakeup_cause_to_run_duration_.has_value()) { + esp_sleep_wakeup_cause_t wakeup_cause = esp_sleep_get_wakeup_cause(); + switch (wakeup_cause) { + case ESP_SLEEP_WAKEUP_EXT0: + case ESP_SLEEP_WAKEUP_EXT1: + return this->wakeup_cause_to_run_duration_->gpio_cause; + case ESP_SLEEP_WAKEUP_TOUCHPAD: + return this->wakeup_cause_to_run_duration_->touch_cause; + default: + return this->wakeup_cause_to_run_duration_->default_cause; + } + } +#endif + return this->run_duration_; +} + void DeepSleepComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up Deep Sleep..."); global_has_deep_sleep = true; - if (this->run_duration_.has_value()) - this->set_timeout(*this->run_duration_, [this]() { this->begin_sleep(); }); + const optional run_duration = get_run_duration_(); + if (run_duration.has_value()) { + ESP_LOGI(TAG, "Scheduling Deep Sleep to start in %u ms", *run_duration); + this->set_timeout(*run_duration, [this]() { this->begin_sleep(); }); + } else { + ESP_LOGD(TAG, "Not scheduling Deep Sleep, as no run duration is configured."); + } } void DeepSleepComponent::dump_config() { ESP_LOGCONFIG(TAG, "Setting up Deep Sleep..."); @@ -33,6 +56,11 @@ void DeepSleepComponent::dump_config() { if (wakeup_pin_ != nullptr) { LOG_PIN(" Wakeup Pin: ", this->wakeup_pin_); } + if (this->wakeup_cause_to_run_duration_.has_value()) { + ESP_LOGCONFIG(TAG, " Default Wakeup Run Duration: %u ms", this->wakeup_cause_to_run_duration_->default_cause); + ESP_LOGCONFIG(TAG, " Touch Wakeup Run Duration: %u ms", this->wakeup_cause_to_run_duration_->touch_cause); + ESP_LOGCONFIG(TAG, " GPIO Wakeup Run Duration: %u ms", this->wakeup_cause_to_run_duration_->gpio_cause); + } #endif } void DeepSleepComponent::loop() { @@ -49,6 +77,9 @@ void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) { } void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; } void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; } +void DeepSleepComponent::set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration) { + wakeup_cause_to_run_duration_ = wakeup_cause_to_run_duration; +} #endif void DeepSleepComponent::set_run_duration(uint32_t time_ms) { this->run_duration_ = time_ms; } void DeepSleepComponent::begin_sleep(bool manual) { diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index d7969ba999..59df199a9f 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -32,6 +32,15 @@ struct Ext1Wakeup { esp_sleep_ext1_wakeup_mode_t wakeup_mode; }; +struct WakeupCauseToRunDuration { + // Run duration if woken up by timer or any other reason besides those below. + uint32_t default_cause; + // Run duration if woken up by touch pads. + uint32_t touch_cause; + // Run duration if woken up by GPIO pins. + uint32_t gpio_cause; +}; + #endif template class EnterDeepSleepAction; @@ -59,6 +68,11 @@ class DeepSleepComponent : public Component { void set_ext1_wakeup(Ext1Wakeup ext1_wakeup); void set_touch_wakeup(bool touch_wakeup); + + // Set the duration in ms for how long the code should run before entering + // deep sleep mode, according to the cause the ESP32 has woken. + void set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration); + #endif /// Set a duration in ms for how long the code should run before entering deep sleep mode. void set_run_duration(uint32_t time_ms); @@ -75,12 +89,17 @@ class DeepSleepComponent : public Component { void prevent_deep_sleep(); protected: + // Returns nullopt if no run duration is set. Otherwise, returns the run + // duration before entering deep sleep. + optional get_run_duration_() const; + optional sleep_duration_; #ifdef USE_ESP32 InternalGPIOPin *wakeup_pin_; WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE}; optional ext1_wakeup_; optional touch_wakeup_; + optional wakeup_cause_to_run_duration_; #endif optional run_duration_; bool next_enter_deep_sleep_{false}; diff --git a/tests/component_tests/deep_sleep/test_deep_sleep.py b/tests/component_tests/deep_sleep/test_deep_sleep.py new file mode 100644 index 0000000000..690d323a50 --- /dev/null +++ b/tests/component_tests/deep_sleep/test_deep_sleep.py @@ -0,0 +1,52 @@ +"""Tests for the deep sleep component.""" + + +def test_deep_sleep_setup(generate_main): + """ + When the deep sleep is set in the yaml file, it should be registered in main + """ + main_cpp = generate_main( + "tests/component_tests/deep_sleep/test_deep_sleep1.yaml" + ) + + assert "deepsleep = new deep_sleep::DeepSleepComponent();" in main_cpp + assert "App.register_component(deepsleep);" in main_cpp + + +def test_deep_sleep_sleep_duration(generate_main): + """ + When deep sleep is configured with sleep duration, it should be set. + """ + main_cpp = generate_main( + "tests/component_tests/deep_sleep/test_deep_sleep1.yaml" + ) + + assert "deepsleep->set_sleep_duration(60000);" in main_cpp + + +def test_deep_sleep_run_duration_simple(generate_main): + """ + When deep sleep is configured with run duration, it should be set. + """ + main_cpp = generate_main( + "tests/component_tests/deep_sleep/test_deep_sleep1.yaml" + ) + + assert "deepsleep->set_run_duration(10000);" in main_cpp + + +def test_deep_sleep_run_duration_dictionary(generate_main): + """ + When deep sleep is configured with dictionary run duration, it should be set. + """ + main_cpp = generate_main( + "tests/component_tests/deep_sleep/test_deep_sleep2.yaml" + ) + + assert ( + "deepsleep->set_run_duration(deep_sleep::WakeupCauseToRunDuration{\n" + " .default_cause = 10000,\n" + " .touch_cause = 10000,\n" + " .gpio_cause = 30000,\n" + "});" + ) in main_cpp diff --git a/tests/component_tests/deep_sleep/test_deep_sleep1.yaml b/tests/component_tests/deep_sleep/test_deep_sleep1.yaml new file mode 100644 index 0000000000..18a425df58 --- /dev/null +++ b/tests/component_tests/deep_sleep/test_deep_sleep1.yaml @@ -0,0 +1,9 @@ +esphome: + name: test + platform: ESP32 + board: nodemcu-32s + +deep_sleep: + id: deepsleep + sleep_duration: 1min + run_duration: 10s diff --git a/tests/component_tests/deep_sleep/test_deep_sleep2.yaml b/tests/component_tests/deep_sleep/test_deep_sleep2.yaml new file mode 100644 index 0000000000..49a7f510f2 --- /dev/null +++ b/tests/component_tests/deep_sleep/test_deep_sleep2.yaml @@ -0,0 +1,11 @@ +esphome: + name: test + platform: ESP32 + board: nodemcu-32s + +deep_sleep: + id: deepsleep + sleep_duration: 1min + run_duration: + default: 10s + gpio_wakeup_reason: 30s diff --git a/tests/test2.yaml b/tests/test2.yaml index 50743dc643..67b819a4d3 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -55,7 +55,10 @@ logger: level: DEBUG deep_sleep: - run_duration: 20s + run_duration: + default: 20s + gpio_wakeup_reason: 10s + touch_wakeup_reason: 15s sleep_duration: 50s wakeup_pin: GPIO39 wakeup_pin_mode: INVERT_WAKEUP From 6fe4ff7f85c625e90f28b31f43bdf241c303f2e8 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 7 Dec 2021 20:46:25 +0100 Subject: [PATCH 1811/1841] Drop len parameter from parse_number() (#2883) --- esphome/components/ezo/ezo.cpp | 2 +- esphome/core/helpers.h | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index 3c1b6e33e8..2ee5782ff6 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -79,7 +79,7 @@ void EZOSensor::loop() { if (buf[i] == ',') buf[i] = '\0'; - float val = parse_number((char *) &buf[1], sizeof(buf) - 2).value_or(0); + float val = parse_number((char *) &buf[1]).value_or(0); this->publish_state(val); } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 8b61e6aa38..208c555afd 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -370,9 +370,9 @@ std::string str_sanitize(const std::string &str); /// @name Parsing & formatting ///@{ -/// Parse an unsigned decimal number (requires null-terminated string). +/// Parse an unsigned decimal number from a null-terminated string. template::value && std::is_unsigned::value), int> = 0> -optional parse_number(const char *str, size_t len) { +optional parse_number(const char *str) { char *end = nullptr; unsigned long value = ::strtoul(str, &end, 10); // NOLINT(google-runtime-int) if (end == str || *end != '\0' || value > std::numeric_limits::max()) @@ -382,11 +382,11 @@ optional parse_number(const char *str, size_t len) { /// Parse an unsigned decimal number. template::value && std::is_unsigned::value), int> = 0> optional parse_number(const std::string &str) { - return parse_number(str.c_str(), str.length() + 1); + return parse_number(str.c_str()); } -/// Parse a signed decimal number (requires null-terminated string). +/// Parse a signed decimal number from a null-terminated string. template::value && std::is_signed::value), int> = 0> -optional parse_number(const char *str, size_t len) { +optional parse_number(const char *str) { char *end = nullptr; signed long value = ::strtol(str, &end, 10); // NOLINT(google-runtime-int) if (end == str || *end != '\0' || value < std::numeric_limits::min() || value > std::numeric_limits::max()) @@ -396,11 +396,10 @@ optional parse_number(const char *str, size_t len) { /// Parse a signed decimal number. template::value && std::is_signed::value), int> = 0> optional parse_number(const std::string &str) { - return parse_number(str.c_str(), str.length() + 1); + return parse_number(str.c_str()); } -/// Parse a decimal floating-point number (requires null-terminated string). -template::value), int> = 0> -optional parse_number(const char *str, size_t len) { +/// Parse a decimal floating-point number from a null-terminated string. +template::value), int> = 0> optional parse_number(const char *str) { char *end = nullptr; float value = ::strtof(str, &end); if (end == str || *end != '\0' || value == HUGE_VALF) @@ -410,7 +409,7 @@ optional parse_number(const char *str, size_t len) { /// Parse a decimal floating-point number. template::value), int> = 0> optional parse_number(const std::string &str) { - return parse_number(str.c_str(), str.length() + 1); + return parse_number(str.c_str()); } ///@} From 3d51ac8df0279dc714527304011ac8c405ae0b15 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:22:03 +1300 Subject: [PATCH 1812/1841] Use new platform component config blocks for wizard (#2885) --- esphome/wizard.py | 24 ++++++++++++++++++++++-- tests/unit_tests/test_wizard.py | 9 +++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/esphome/wizard.py b/esphome/wizard.py index 5f4f347ba7..f2632caf71 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -45,9 +45,9 @@ OTA_BIG = r""" ____ _______ BASE_CONFIG = """esphome: name: {name} - platform: {platform} - board: {board} +""" +LOGGER_API_CONFIG = """ # Enable logging logger: @@ -55,6 +55,18 @@ logger: api: """ +ESP8266_CONFIG = """ +esp8266: + board: {board} +""" + +ESP32_CONFIG = """ +esp32: + board: {board} + framework: + type: arduino +""" + def sanitize_double_quotes(value): return value.replace("\\", "\\\\").replace('"', '\\"') @@ -71,6 +83,14 @@ def wizard_file(**kwargs): config = BASE_CONFIG.format(**kwargs) + config += ( + ESP8266_CONFIG.format(**kwargs) + if kwargs["platform"] == "ESP8266" + else ESP32_CONFIG.format(**kwargs) + ) + + config += LOGGER_API_CONFIG + # Configure API if "password" in kwargs: config += f" password: \"{kwargs['password']}\"\n" diff --git a/tests/unit_tests/test_wizard.py b/tests/unit_tests/test_wizard.py index 18e040b0a6..59fcfbff60 100644 --- a/tests/unit_tests/test_wizard.py +++ b/tests/unit_tests/test_wizard.py @@ -11,7 +11,7 @@ def default_config(): return { "name": "test-name", "platform": "test_platform", - "board": "test_board", + "board": "esp01_1m", "ssid": "test_ssid", "psk": "test_psk", "password": "", @@ -105,6 +105,7 @@ def test_wizard_write_sets_platform(default_config, tmp_path, monkeypatch): If the platform is not explicitly set, use "ESP8266" if the board is one of the ESP8266 boards """ # Given + del default_config["platform"] monkeypatch.setattr(wz, "write_file", MagicMock()) # When @@ -112,7 +113,7 @@ def test_wizard_write_sets_platform(default_config, tmp_path, monkeypatch): # Then generated_config = wz.write_file.call_args.args[1] - assert f"platform: {default_config['platform']}" in generated_config + assert "esp8266:" in generated_config def test_wizard_write_defaults_platform_from_board_esp8266( @@ -132,7 +133,7 @@ def test_wizard_write_defaults_platform_from_board_esp8266( # Then generated_config = wz.write_file.call_args.args[1] - assert "platform: ESP8266" in generated_config + assert "esp8266:" in generated_config def test_wizard_write_defaults_platform_from_board_esp32( @@ -152,7 +153,7 @@ def test_wizard_write_defaults_platform_from_board_esp32( # Then generated_config = wz.write_file.call_args.args[1] - assert "platform: ESP32" in generated_config + assert "esp32:" in generated_config def test_safe_print_step_prints_step_number_and_description(monkeypatch): From 58fb7a02f6c0ad402cf6a2036668fd07a8452e67 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 8 Dec 2021 12:42:50 +1300 Subject: [PATCH 1813/1841] Bump esphome-dashboard to 20211208.0 (#2887) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e27ae0f625..f7b1a6a1fe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20211207.0 +esphome-dashboard==20211208.0 aioesphomeapi==10.6.0 zeroconf==0.36.13 From 6df1d5222d23e0af7bce2d30c248046717e8c043 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 8 Dec 2021 00:46:36 +0100 Subject: [PATCH 1814/1841] Drop unused xSemaphoreWait define (#2888) --- esphome/core/helpers.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 208c555afd..db507f824d 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -19,10 +19,6 @@ #define ALWAYS_INLINE __attribute__((always_inline)) #define PACKED __attribute__((packed)) -#define xSemaphoreWait(semaphore, wait_time) \ - xSemaphoreTake(semaphore, wait_time); \ - xSemaphoreGive(semaphore); - namespace esphome { /// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes). From 24ec5a6e9d6e379f0f571eaac724a76656fa2d82 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 10 Dec 2021 09:32:34 +1300 Subject: [PATCH 1815/1841] Fix published state for modbus number (#2894) --- .../modbus_controller/number/modbus_number.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index ba2ffdd09f..70be9eaa1f 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -27,6 +27,7 @@ void ModbusNumber::parse_and_publish(const std::vector &data) { void ModbusNumber::control(float value) { std::vector data; + float write_value = value; // Is there are lambda configured? if (this->write_transform_func_.has_value()) { // data is passed by reference @@ -35,23 +36,23 @@ void ModbusNumber::control(float value) { auto val = (*this->write_transform_func_)(this, value, data); if (val.has_value()) { ESP_LOGV(TAG, "Value overwritten by lambda"); - value = val.value(); + write_value = val.value(); } else { ESP_LOGV(TAG, "Communication handled by lambda - exiting control"); return; } } else { - value = multiply_by_ * value; + write_value = multiply_by_ * write_value; } // lambda didn't set payload if (data.empty()) { - data = float_to_payload(value, this->sensor_value_type); + data = float_to_payload(write_value, this->sensor_value_type); } ESP_LOGD(TAG, "Updating register: connected Sensor=%s start address=0x%X register count=%d new value=%.02f (val=%.02f)", - this->get_name().c_str(), this->start_address, this->register_count, value, value); + this->get_name().c_str(), this->start_address, this->register_count, write_value, write_value); // Create and send the write command auto write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, From c490388e8014ec895a6a7182ee98987c0665185a Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Thu, 9 Dec 2021 17:44:43 -0300 Subject: [PATCH 1816/1841] Modbus number/output use write single (#2896) Co-authored-by: Martin <25747549+martgras@users.noreply.github.com> --- esphome/components/modbus_controller/const.py | 1 + .../components/modbus_controller/number/__init__.py | 3 +++ .../modbus_controller/number/modbus_number.cpp | 10 +++++++--- .../modbus_controller/number/modbus_number.h | 2 ++ .../components/modbus_controller/output/__init__.py | 3 +++ .../modbus_controller/output/modbus_output.cpp | 10 ++++++++-- .../modbus_controller/output/modbus_output.h | 2 ++ .../components/modbus_controller/switch/__init__.py | 2 +- 8 files changed, 27 insertions(+), 6 deletions(-) diff --git a/esphome/components/modbus_controller/const.py b/esphome/components/modbus_controller/const.py index 8d1676dd38..baf72efb94 100644 --- a/esphome/components/modbus_controller/const.py +++ b/esphome/components/modbus_controller/const.py @@ -10,5 +10,6 @@ CONF_REGISTER_COUNT = "register_count" CONF_REGISTER_TYPE = "register_type" CONF_RESPONSE_SIZE = "response_size" CONF_SKIP_UPDATES = "skip_updates" +CONF_USE_WRITE_MULTIPLE = "use_write_multiple" CONF_VALUE_TYPE = "value_type" CONF_WRITE_LAMBDA = "write_lambda" diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py index 3c5db9b9c8..4ad6601fee 100644 --- a/esphome/components/modbus_controller/number/__init__.py +++ b/esphome/components/modbus_controller/number/__init__.py @@ -25,6 +25,7 @@ from ..const import ( CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, CONF_SKIP_UPDATES, + CONF_USE_WRITE_MULTIPLE, CONF_VALUE_TYPE, CONF_WRITE_LAMBDA, ) @@ -69,6 +70,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_MIN_VALUE, default=-16777215.0): cv.float_, cv.Optional(CONF_STEP, default=1): cv.positive_float, cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, + cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean, } ) .extend(cv.polling_component_schema("60s")), @@ -105,6 +107,7 @@ async def to_code(config): cg.add(var.set_parent(parent)) cg.add(parent.add_sensor_item(var)) await add_modbus_base_properties(var, config, ModbusNumber) + cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE])) if CONF_WRITE_LAMBDA in config: template_ = await cg.process_lambda( config[CONF_WRITE_LAMBDA], diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index 70be9eaa1f..e5afd0c611 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -55,9 +55,13 @@ void ModbusNumber::control(float value) { this->get_name().c_str(), this->start_address, this->register_count, write_value, write_value); // Create and send the write command - auto write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, - this->register_count, data); - + ModbusCommandItem write_cmd; + if (this->register_count == 1 && !this->use_write_multiple_) { + write_cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, data[0]); + } else { + write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, + this->register_count, data); + } // publish new value write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { diff --git a/esphome/components/modbus_controller/number/modbus_number.h b/esphome/components/modbus_controller/number/modbus_number.h index 271bbfac50..c678cd00cc 100644 --- a/esphome/components/modbus_controller/number/modbus_number.h +++ b/esphome/components/modbus_controller/number/modbus_number.h @@ -35,6 +35,7 @@ class ModbusNumber : public number::Number, public Component, public SensorItem using write_transform_func_t = std::function(ModbusNumber *, float, std::vector &)>; void set_template(transform_func_t &&f) { this->transform_func_ = f; } void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } + void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } protected: void control(float value) override; @@ -42,6 +43,7 @@ class ModbusNumber : public number::Number, public Component, public SensorItem optional write_transform_func_; ModbusController *parent_; float multiply_by_{1.0}; + bool use_write_multiple_{false}; }; } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/output/__init__.py b/esphome/components/modbus_controller/output/__init__.py index eacd96579f..a26d05a18b 100644 --- a/esphome/components/modbus_controller/output/__init__.py +++ b/esphome/components/modbus_controller/output/__init__.py @@ -18,6 +18,7 @@ from .. import ( from ..const import ( CONF_MODBUS_CONTROLLER_ID, + CONF_USE_WRITE_MULTIPLE, CONF_VALUE_TYPE, CONF_WRITE_LAMBDA, ) @@ -36,6 +37,7 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(): cv.declare_id(ModbusOutput), cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, + cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean, } ), validate_modbus_register, @@ -54,6 +56,7 @@ async def to_code(config): await output.register_output(var, config) cg.add(var.set_write_multiply(config[CONF_MULTIPLY])) parent = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) + cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE])) cg.add(var.set_parent(parent)) if CONF_WRITE_LAMBDA in config: template_ = await cg.process_lambda( diff --git a/esphome/components/modbus_controller/output/modbus_output.cpp b/esphome/components/modbus_controller/output/modbus_output.cpp index d2b5d02bda..4c2e5775b9 100644 --- a/esphome/components/modbus_controller/output/modbus_output.cpp +++ b/esphome/components/modbus_controller/output/modbus_output.cpp @@ -40,8 +40,14 @@ void ModbusOutput::write_state(float value) { this->start_address, this->register_count, value, original_value); // Create and send the write command - auto write_cmd = - ModbusCommandItem::create_write_multiple_command(parent_, this->start_address, this->register_count, data); + // Create and send the write command + ModbusCommandItem write_cmd; + if (this->register_count == 1 && !this->use_write_multiple_) { + write_cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, data[0]); + } else { + write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, + this->register_count, data); + } parent_->queue_command(write_cmd); } diff --git a/esphome/components/modbus_controller/output/modbus_output.h b/esphome/components/modbus_controller/output/modbus_output.h index 6e8521854b..78d3474ad6 100644 --- a/esphome/components/modbus_controller/output/modbus_output.h +++ b/esphome/components/modbus_controller/output/modbus_output.h @@ -33,6 +33,7 @@ class ModbusOutput : public output::FloatOutput, public Component, public Sensor using write_transform_func_t = std::function(ModbusOutput *, float, std::vector &)>; void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } + void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } protected: void write_state(float value) override; @@ -40,6 +41,7 @@ class ModbusOutput : public output::FloatOutput, public Component, public Sensor ModbusController *parent_; float multiply_by_{1.0}; + bool use_write_multiple_; }; } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/switch/__init__.py b/esphome/components/modbus_controller/switch/__init__.py index df11b268ac..9858d45617 100644 --- a/esphome/components/modbus_controller/switch/__init__.py +++ b/esphome/components/modbus_controller/switch/__init__.py @@ -18,10 +18,10 @@ from ..const import ( CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, CONF_REGISTER_TYPE, + CONF_USE_WRITE_MULTIPLE, CONF_WRITE_LAMBDA, ) -CONF_USE_WRITE_MULTIPLE = "use_write_multiple" DEPENDENCIES = ["modbus_controller"] CODEOWNERS = ["@martgras"] From cf5193d3e54e59525be78dded20fa142d5ff7d88 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sat, 11 Dec 2021 01:03:22 -0600 Subject: [PATCH 1817/1841] Fix for two points setting when fan_only_cooling is disabled (#2903) Co-authored-by: Paulus Schoutsen Co-authored-by: Keith Burzinski --- esphome/components/thermostat/climate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index 7b5ee7c624..20565e811c 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -431,7 +431,8 @@ async def to_code(config): heat_cool_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config two_points_available = CONF_HEAT_ACTION in config and ( - CONF_COOL_ACTION in config or CONF_FAN_ONLY_ACTION in config + CONF_COOL_ACTION in config + or (config[CONF_FAN_ONLY_COOLING] and CONF_FAN_ONLY_ACTION in config) ) sens = await cg.get_variable(config[CONF_SENSOR]) From 8375e1d64df7e7477f185b7021626517fed10b20 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 11 Dec 2021 21:03:41 +1300 Subject: [PATCH 1818/1841] Bump esphome-dashboard to 20211211.0 (#2904) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f7b1a6a1fe..c45797a71f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20211208.0 +esphome-dashboard==20211211.0 aioesphomeapi==10.6.0 zeroconf==0.36.13 From b2f05faee0bc41ac878f936fec19aab9b2fc87a5 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Sun, 12 Dec 2021 21:12:50 +0100 Subject: [PATCH 1819/1841] Move i2c scan to setup (#2869) Co-authored-by: Oxan van Leeuwen --- esphome/components/i2c/i2c_bus.h | 16 +++++++++++++ esphome/components/i2c/i2c_bus_arduino.cpp | 27 +++++++++++---------- esphome/components/i2c/i2c_bus_arduino.h | 1 - esphome/components/i2c/i2c_bus_esp_idf.cpp | 28 ++++++++++++---------- esphome/components/i2c/i2c_bus_esp_idf.h | 1 - 5 files changed, 46 insertions(+), 27 deletions(-) diff --git a/esphome/components/i2c/i2c_bus.h b/esphome/components/i2c/i2c_bus.h index cb00260f43..71f6b1d15b 100644 --- a/esphome/components/i2c/i2c_bus.h +++ b/esphome/components/i2c/i2c_bus.h @@ -1,6 +1,8 @@ #pragma once #include #include +#include +#include namespace esphome { namespace i2c { @@ -40,6 +42,20 @@ class I2CBus { return writev(address, &buf, 1); } virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) = 0; + + protected: + void i2c_scan_() { + for (uint8_t address = 8; address < 120; address++) { + auto err = writev(address, nullptr, 0); + if (err == ERROR_OK) { + scan_results_.emplace_back(address, true); + } else if (err == ERROR_UNKNOWN) { + scan_results_.emplace_back(address, false); + } + } + } + std::vector> scan_results_; + bool scan_{false}; }; } // namespace i2c diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index 4afabbfa53..eac2a47524 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -2,6 +2,7 @@ #include "i2c_bus_arduino.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" #include #include @@ -27,6 +28,10 @@ void ArduinoI2CBus::setup() { wire_->begin(sda_pin_, scl_pin_); wire_->setClock(frequency_); initialized_ = true; + if (this->scan_) { + ESP_LOGV(TAG, "Scanning i2c bus for active devices..."); + this->i2c_scan_(); + } } void ArduinoI2CBus::dump_config() { ESP_LOGCONFIG(TAG, "I2C Bus:"); @@ -45,22 +50,20 @@ void ArduinoI2CBus::dump_config() { break; } if (this->scan_) { - ESP_LOGI(TAG, "Scanning i2c bus for active devices..."); - uint8_t found = 0; - for (uint8_t address = 8; address < 120; address++) { - auto err = writev(address, nullptr, 0); - if (err == ERROR_OK) { - ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address); - found++; - } else if (err == ERROR_UNKNOWN) { - ESP_LOGI(TAG, "Unknown error at address 0x%02X", address); - } - } - if (found == 0) { + ESP_LOGI(TAG, "Results from i2c bus scan:"); + if (scan_results_.empty()) { ESP_LOGI(TAG, "Found no i2c devices!"); + } else { + for (const auto &s : scan_results_) { + if (s.second) + ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first); + else + ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first); + } } } } + ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { // logging is only enabled with vv level, if warnings are shown the caller // should log them diff --git a/esphome/components/i2c/i2c_bus_arduino.h b/esphome/components/i2c/i2c_bus_arduino.h index 82f043ef7d..f4151e4f37 100644 --- a/esphome/components/i2c/i2c_bus_arduino.h +++ b/esphome/components/i2c/i2c_bus_arduino.h @@ -34,7 +34,6 @@ class ArduinoI2CBus : public I2CBus, public Component { protected: TwoWire *wire_; - bool scan_; uint8_t sda_pin_; uint8_t scl_pin_; uint32_t frequency_; diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index f7ecfe5f7c..109c3f890d 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -3,6 +3,7 @@ #include "i2c_bus_esp_idf.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" #include namespace esphome { @@ -37,6 +38,10 @@ void IDFI2CBus::setup() { return; } initialized_ = true; + if (this->scan_) { + ESP_LOGV(TAG, "Scanning i2c bus for active devices..."); + this->i2c_scan_(); + } } void IDFI2CBus::dump_config() { ESP_LOGCONFIG(TAG, "I2C Bus:"); @@ -55,23 +60,20 @@ void IDFI2CBus::dump_config() { break; } if (this->scan_) { - ESP_LOGI(TAG, "Scanning i2c bus for active devices..."); - uint8_t found = 0; - for (uint8_t address = 8; address < 120; address++) { - auto err = writev(address, nullptr, 0); - - if (err == ERROR_OK) { - ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address); - found++; - } else if (err == ERROR_UNKNOWN) { - ESP_LOGI(TAG, "Unknown error at address 0x%02X", address); - } - } - if (found == 0) { + ESP_LOGI(TAG, "Results from i2c bus scan:"); + if (scan_results_.empty()) { ESP_LOGI(TAG, "Found no i2c devices!"); + } else { + for (const auto &s : scan_results_) { + if (s.second) + ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first); + else + ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first); + } } } } + ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { // logging is only enabled with vv level, if warnings are shown the caller // should log them diff --git a/esphome/components/i2c/i2c_bus_esp_idf.h b/esphome/components/i2c/i2c_bus_esp_idf.h index 13d996dbd8..d4b0626467 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.h +++ b/esphome/components/i2c/i2c_bus_esp_idf.h @@ -36,7 +36,6 @@ class IDFI2CBus : public I2CBus, public Component { protected: i2c_port_t port_; - bool scan_; uint8_t sda_pin_; bool sda_pullup_enabled_; uint8_t scl_pin_; From beeb0c7c5af85fa9d05a5338a36b76a3d95084cf Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 12 Dec 2021 21:15:23 +0100 Subject: [PATCH 1820/1841] Introduce hex parsing & formatting helper functions (#2882) --- esphome/components/api/api_frame_helper.cpp | 10 +- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 6 +- .../esp32_improv/esp32_improv_component.cpp | 2 +- esphome/components/md5/md5.cpp | 18 +--- esphome/components/md5/md5.h | 2 +- esphome/components/modbus/modbus.cpp | 4 +- .../switch/modbus_switch.cpp | 2 +- esphome/components/pn532_spi/pn532_spi.cpp | 8 +- .../components/remote_base/midea_protocol.h | 2 +- esphome/components/tuya/light/tuya_light.cpp | 12 +-- .../tuya/text_sensor/tuya_text_sensor.cpp | 2 +- esphome/components/tuya/tuya.cpp | 10 +- esphome/components/xiaomi_ble/xiaomi_ble.cpp | 16 +-- .../components/xiaomi_cgd1/xiaomi_cgd1.cpp | 2 +- .../components/xiaomi_cgdk2/xiaomi_cgdk2.cpp | 2 +- .../components/xiaomi_cgg1/xiaomi_cgg1.cpp | 2 +- .../xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp | 2 +- .../xiaomi_mhoc401/xiaomi_mhoc401.cpp | 2 +- esphome/core/helpers.cpp | 99 ++++++++++--------- esphome/core/helpers.h | 92 +++++++++++++++-- 20 files changed, 186 insertions(+), 109 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 23766ec1b1..151c2512de 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -252,7 +252,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { // uncomment for even more debugging #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); + ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str()); #endif frame->msg = std::move(rx_buf_); // consume msg @@ -546,7 +546,8 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { size_t total_write_len = 0; for (int i = 0; i < iovcnt; i++) { #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); + ESP_LOGVV(TAG, "Sending raw: %s", + format_hex_pretty(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); #endif total_write_len += iov[i].iov_len; } @@ -855,7 +856,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { // uncomment for even more debugging #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); + ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str()); #endif frame->msg = std::move(rx_buf_); // consume msg @@ -934,7 +935,8 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt size_t total_write_len = 0; for (int i = 0; i < iovcnt; i++) { #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); + ESP_LOGVV(TAG, "Sending raw: %s", + format_hex_pretty(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); #endif total_write_len += iov[i].iov_len; } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 303cb34aa7..e3de02e2d7 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -524,7 +524,7 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str()); } for (auto &data : this->manufacturer_datas_) { - ESP_LOGVV(TAG, " Manufacturer data: %s", hexencode(data.data).c_str()); + ESP_LOGVV(TAG, " Manufacturer data: %s", format_hex_pretty(data.data).c_str()); if (this->get_ibeacon().has_value()) { auto ibeacon = this->get_ibeacon().value(); ESP_LOGVV(TAG, " iBeacon data:"); @@ -537,10 +537,10 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e for (auto &data : this->service_datas_) { ESP_LOGVV(TAG, " Service data:"); ESP_LOGVV(TAG, " UUID: %s", data.uuid.to_string().c_str()); - ESP_LOGVV(TAG, " Data: %s", hexencode(data.data).c_str()); + ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str()); } - ESP_LOGVV(TAG, "Adv data: %s", hexencode(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str()); + ESP_LOGVV(TAG, "Adv data: %s", format_hex_pretty(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str()); #endif } void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 22bebdfe98..788e7a9460 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -219,7 +219,7 @@ void ESP32ImprovComponent::dump_config() { void ESP32ImprovComponent::process_incoming_data_() { uint8_t length = this->incoming_data_[1]; - ESP_LOGD(TAG, "Processing bytes - %s", hexencode(this->incoming_data_).c_str()); + ESP_LOGD(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str()); if (this->incoming_data_.size() - 3 == length) { this->set_error_(improv::ERROR_NONE); improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_); diff --git a/esphome/components/md5/md5.cpp b/esphome/components/md5/md5.cpp index c6ff783439..0528a87d0e 100644 --- a/esphome/components/md5/md5.cpp +++ b/esphome/components/md5/md5.cpp @@ -23,7 +23,7 @@ void MD5Digest::get_hex(char *output) { } } -bool MD5Digest::equals_bytes(const char *expected) { +bool MD5Digest::equals_bytes(const uint8_t *expected) { for (size_t i = 0; i < 16; i++) { if (expected[i] != this->digest_[i]) { return false; @@ -33,18 +33,10 @@ bool MD5Digest::equals_bytes(const char *expected) { } bool MD5Digest::equals_hex(const char *expected) { - for (size_t i = 0; i < 16; i++) { - auto high = parse_hex(expected[i * 2]); - auto low = parse_hex(expected[i * 2 + 1]); - if (!high.has_value() || !low.has_value()) { - return false; - } - auto value = (*high << 4) | *low; - if (value != this->digest_[i]) { - return false; - } - } - return true; + uint8_t parsed[16]; + if (!parse_hex(expected, parsed, 16)) + return false; + return equals_bytes(parsed); } } // namespace md5 diff --git a/esphome/components/md5/md5.h b/esphome/components/md5/md5.h index e40f419347..1c15c9e57d 100644 --- a/esphome/components/md5/md5.h +++ b/esphome/components/md5/md5.h @@ -44,7 +44,7 @@ class MD5Digest { void get_hex(char *output); /// Compare the digest against a provided byte-encoded digest (16 bytes). - bool equals_bytes(const char *expected); + bool equals_bytes(const uint8_t *expected); /// Compare the digest against a provided hex-encoded digest (32 bytes). bool equals_hex(const char *expected); diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 9524f9daf4..9ee3137be9 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -181,7 +181,7 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address this->flow_control_pin_->digital_write(false); waiting_for_response = address; last_send_ = millis(); - ESP_LOGV(TAG, "Modbus write: %s", hexencode(data).c_str()); + ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty(data).c_str()); } // Helper function for lambdas @@ -202,7 +202,7 @@ void Modbus::send_raw(const std::vector &payload) { if (this->flow_control_pin_ != nullptr) this->flow_control_pin_->digital_write(false); waiting_for_response = payload[0]; - ESP_LOGV(TAG, "Modbus write raw: %s", hexencode(payload).c_str()); + ESP_LOGV(TAG, "Modbus write raw: %s", format_hex_pretty(payload).c_str()); last_send_ = millis(); } diff --git a/esphome/components/modbus_controller/switch/modbus_switch.cpp b/esphome/components/modbus_controller/switch/modbus_switch.cpp index c7c3c419d4..ca8d0be720 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.cpp +++ b/esphome/components/modbus_controller/switch/modbus_switch.cpp @@ -61,7 +61,7 @@ void ModbusSwitch::write_state(bool state) { } } if (!data.empty()) { - ESP_LOGV(TAG, "Modbus Switch write raw: %s", hexencode(data).c_str()); + ESP_LOGV(TAG, "Modbus Switch write raw: %s", format_hex_pretty(data).c_str()); cmd = ModbusCommandItem::create_custom_command( this->parent_, data, [this, cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { diff --git a/esphome/components/pn532_spi/pn532_spi.cpp b/esphome/components/pn532_spi/pn532_spi.cpp index ec32e45b3d..be58f265b9 100644 --- a/esphome/components/pn532_spi/pn532_spi.cpp +++ b/esphome/components/pn532_spi/pn532_spi.cpp @@ -26,7 +26,7 @@ bool PN532Spi::write_data(const std::vector &data) { delay(2); // First byte, communication mode: Write data this->write_byte(0x01); - ESP_LOGV(TAG, "Writing data: %s", hexencode(data).c_str()); + ESP_LOGV(TAG, "Writing data: %s", format_hex_pretty(data).c_str()); this->write_array(data.data(), data.size()); this->disable(); @@ -65,7 +65,7 @@ bool PN532Spi::read_data(std::vector &data, uint8_t len) { this->read_array(data.data(), len); this->disable(); data.insert(data.begin(), 0x01); - ESP_LOGV(TAG, "Read data: %s", hexencode(data).c_str()); + ESP_LOGV(TAG, "Read data: %s", format_hex_pretty(data).c_str()); return true; } @@ -97,7 +97,7 @@ bool PN532Spi::read_response(uint8_t command, std::vector &data) { std::vector header(7); this->read_array(header.data(), 7); - ESP_LOGV(TAG, "Header data: %s", hexencode(header).c_str()); + ESP_LOGV(TAG, "Header data: %s", format_hex_pretty(header).c_str()); if (header[0] != 0x00 && header[1] != 0x00 && header[2] != 0xFF) { // invalid packet @@ -127,7 +127,7 @@ bool PN532Spi::read_response(uint8_t command, std::vector &data) { this->read_array(data.data(), len + 1); this->disable(); - ESP_LOGV(TAG, "Response data: %s", hexencode(data).c_str()); + ESP_LOGV(TAG, "Response data: %s", format_hex_pretty(data).c_str()); uint8_t checksum = header[5] + header[6]; // TFI + Command response code for (int i = 0; i < len - 1; i++) { diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h index 35ea23acfb..61e511601b 100644 --- a/esphome/components/remote_base/midea_protocol.h +++ b/esphome/components/remote_base/midea_protocol.h @@ -26,7 +26,7 @@ class MideaData { bool is_valid() const { return this->data_[OFFSET_CS] == this->calc_cs_(); } void finalize() { this->data_[OFFSET_CS] = this->calc_cs_(); } bool check_compliment(const MideaData &rhs) const; - std::string to_string() const { return hexencode(*this); } + std::string to_string() const { return format_hex_pretty(this->data_, sizeof(this->data_)); } // compare only 40-bits bool operator==(const MideaData &rhs) const { return !memcmp(this->data_, rhs.data_, OFFSET_CS); } enum MideaDataType : uint8_t { diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index 133ee1e557..ecd3802839 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -37,9 +37,9 @@ void TuyaLight::setup() { } if (rgb_id_.has_value()) { this->parent_->register_listener(*this->rgb_id_, [this](const TuyaDatapoint &datapoint) { - auto red = parse_hex(datapoint.value_string, 0, 2); - auto green = parse_hex(datapoint.value_string, 2, 2); - auto blue = parse_hex(datapoint.value_string, 4, 2); + auto red = parse_hex(datapoint.value_string.substr(0, 2)); + auto green = parse_hex(datapoint.value_string.substr(2, 2)); + auto blue = parse_hex(datapoint.value_string.substr(4, 2)); if (red.has_value() && green.has_value() && blue.has_value()) { auto call = this->state_->make_call(); call.set_rgb(float(*red) / 255, float(*green) / 255, float(*blue) / 255); @@ -48,9 +48,9 @@ void TuyaLight::setup() { }); } else if (hsv_id_.has_value()) { this->parent_->register_listener(*this->hsv_id_, [this](const TuyaDatapoint &datapoint) { - auto hue = parse_hex(datapoint.value_string, 0, 4); - auto saturation = parse_hex(datapoint.value_string, 4, 4); - auto value = parse_hex(datapoint.value_string, 8, 4); + auto hue = parse_hex(datapoint.value_string.substr(0, 4)); + auto saturation = parse_hex(datapoint.value_string.substr(4, 4)); + auto value = parse_hex(datapoint.value_string.substr(8, 4)); if (hue.has_value() && saturation.has_value() && value.has_value()) { float red, green, blue; hsv_to_rgb(*hue, float(*saturation) / 1000, float(*value) / 1000, red, green, blue); diff --git a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp index e939225453..0b51ba90c4 100644 --- a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp +++ b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp @@ -14,7 +14,7 @@ void TuyaTextSensor::setup() { this->publish_state(datapoint.value_string); break; case TuyaDatapointType::RAW: { - std::string data = hexencode(datapoint.value_raw); + std::string data = format_hex_pretty(datapoint.value_raw); ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, data.c_str()); this->publish_state(data); break; diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 404a70a80e..7ff8c66c44 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -34,7 +34,7 @@ void Tuya::dump_config() { } for (auto &info : this->datapoints_) { if (info.type == TuyaDatapointType::RAW) - ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, hexencode(info.value_raw).c_str()); + ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, format_hex_pretty(info.value_raw).c_str()); else if (info.type == TuyaDatapointType::BOOLEAN) ESP_LOGCONFIG(TAG, " Datapoint %u: switch (value: %s)", info.id, ONOFF(info.value_bool)); else if (info.type == TuyaDatapointType::INTEGER) @@ -104,7 +104,7 @@ bool Tuya::validate_message_() { // valid message const uint8_t *message_data = data + 6; ESP_LOGV(TAG, "Received Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version, - hexencode(message_data, length).c_str(), static_cast(this->init_state_)); + format_hex_pretty(message_data, length).c_str(), static_cast(this->init_state_)); this->handle_command_(command, version, message_data, length); // return false to reset rx buffer @@ -253,7 +253,7 @@ void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) { switch (datapoint.type) { case TuyaDatapointType::RAW: datapoint.value_raw = std::vector(data, data + data_len); - ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, hexencode(datapoint.value_raw).c_str()); + ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str()); break; case TuyaDatapointType::BOOLEAN: if (data_len != 1) { @@ -348,7 +348,7 @@ void Tuya::send_raw_command_(TuyaCommand command) { } ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast(command.cmd), - version, hexencode(command.payload).c_str(), static_cast(this->init_state_)); + version, format_hex_pretty(command.payload).c_str(), static_cast(this->init_state_)); this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo}); if (!command.payload.empty()) @@ -526,7 +526,7 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType } void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector &value, bool forced) { - ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, hexencode(value).c_str()); + ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty(value).c_str()); optional datapoint = this->get_datapoint_(datapoint_id); if (!datapoint.has_value()) { ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 7588198c70..583b68a77b 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -217,7 +217,7 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, const uint64_t &address) { if (!((raw.size() == 19) || ((raw.size() >= 22) && (raw.size() <= 24)))) { ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): data packet has wrong size (%d)!", raw.size()); - ESP_LOGVV(TAG, " Packet : %s", hexencode(raw.data(), raw.size()).c_str()); + ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str()); return false; } @@ -274,12 +274,12 @@ bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, c memcpy(mac_address + 4, mac_reverse + 1, 1); memcpy(mac_address + 5, mac_reverse, 1); ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption failed."); - ESP_LOGVV(TAG, " MAC address : %s", hexencode(mac_address, 6).c_str()); - ESP_LOGVV(TAG, " Packet : %s", hexencode(raw.data(), raw.size()).c_str()); - ESP_LOGVV(TAG, " Key : %s", hexencode(vector.key, vector.keysize).c_str()); - ESP_LOGVV(TAG, " Iv : %s", hexencode(vector.iv, vector.ivsize).c_str()); - ESP_LOGVV(TAG, " Cipher : %s", hexencode(vector.ciphertext, vector.datasize).c_str()); - ESP_LOGVV(TAG, " Tag : %s", hexencode(vector.tag, vector.tagsize).c_str()); + ESP_LOGVV(TAG, " MAC address : %s", format_hex_pretty(mac_address, 6).c_str()); + ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str()); + ESP_LOGVV(TAG, " Key : %s", format_hex_pretty(vector.key, vector.keysize).c_str()); + ESP_LOGVV(TAG, " Iv : %s", format_hex_pretty(vector.iv, vector.ivsize).c_str()); + ESP_LOGVV(TAG, " Cipher : %s", format_hex_pretty(vector.ciphertext, vector.datasize).c_str()); + ESP_LOGVV(TAG, " Tag : %s", format_hex_pretty(vector.tag, vector.tagsize).c_str()); mbedtls_ccm_free(&ctx); return false; } @@ -295,7 +295,7 @@ bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, c raw[0] &= ~0x08; ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption passed."); - ESP_LOGVV(TAG, " Plaintext : %s, Packet : %d", hexencode(raw.data() + cipher_pos, vector.datasize).c_str(), + ESP_LOGVV(TAG, " Plaintext : %s, Packet : %d", format_hex_pretty(raw.data() + cipher_pos, vector.datasize).c_str(), static_cast(raw[4])); mbedtls_ccm_free(&ctx); diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp index 97bbd6e6d6..baf9cb8075 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_cgd1"; void XiaomiCGD1::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi CGD1"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str()); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp index a97ca93206..c74794f4f4 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_cgdk2"; void XiaomiCGDK2::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi CGDK2"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str()); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp index e1f83e4ddd..c20c7578d0 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_cgg1"; void XiaomiCGG1::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi CGG1"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str()); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp index 547cc7c114..d0319c9474 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_lywsd03mmc"; void XiaomiLYWSD03MMC::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi LYWSD03MMC"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str()); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp index 0cad5c67b2..9ec2b10e12 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_mhoc401"; void XiaomiMHOC401::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi MHOC401"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str()); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 6678eddbff..b82a2666e7 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -244,38 +244,6 @@ std::string to_string(long double val) { return buf; } -optional parse_hex(const char chr) { - int out = chr; - if (out >= '0' && out <= '9') - return (out - '0'); - if (out >= 'A' && out <= 'F') - return (10 + (out - 'A')); - if (out >= 'a' && out <= 'f') - return (10 + (out - 'a')); - return {}; -} - -optional parse_hex(const std::string &str, size_t start, size_t length) { - if (str.length() < start) { - return {}; - } - size_t end = start + length; - if (str.length() < end) { - return {}; - } - int out = 0; - for (size_t i = start; i < end; i++) { - char chr = str[i]; - auto digit = parse_hex(chr); - if (!digit.has_value()) { - ESP_LOGW(TAG, "Can't convert '%s' to number, invalid character %c!", str.substr(start, length).c_str(), chr); - return {}; - } - out = (out << 4) | *digit; - } - return out; -} - uint32_t fnv1_hash(const std::string &str) { uint32_t hash = 2166136261UL; for (char c : str) { @@ -355,22 +323,6 @@ std::string str_sprintf(const char *fmt, ...) { return str; } -std::string hexencode(const uint8_t *data, uint32_t len) { - char buf[20]; - std::string res; - for (size_t i = 0; i < len; i++) { - if (i + 1 != len) { - sprintf(buf, "%02X.", data[i]); - } else { - sprintf(buf, "%02X ", data[i]); - } - res += buf; - } - sprintf(buf, "(%u)", len); - res += buf; - return res; -} - void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value) { float max_color_value = std::max(std::max(red, green), blue); float min_color_value = std::min(std::min(red, green), blue); @@ -445,6 +397,8 @@ IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } // --------------------------------------------------------------------------------------------------------------------- +// Strings + std::string str_truncate(const std::string &str, size_t length) { return str.length() > length ? str.substr(0, length) : str; } @@ -468,4 +422,53 @@ std::string str_sanitize(const std::string &str) { return out; } +// Parsing & formatting + +size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) { + uint8_t val; + size_t chars = std::min(length, 2 * count); + for (size_t i = 2 * count - chars; i < 2 * count; i++, str++) { + if (*str >= '0' && *str <= '9') + val = *str - '0'; + else if (*str >= 'A' && *str <= 'F') + val = 10 + (*str - 'A'); + else if (*str >= 'a' && *str <= 'f') + val = 10 + (*str - 'a'); + else + return 0; + data[i >> 1] = !(i & 1) ? val << 4 : data[i >> 1] | val; + } + return chars; +} + +static char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; } +std::string format_hex(const uint8_t *data, size_t length) { + std::string ret; + ret.resize(length * 2); + for (size_t i = 0; i < length; i++) { + ret[2 * i] = format_hex_char((data[i] & 0xF0) >> 4); + ret[2 * i + 1] = format_hex_char(data[i] & 0x0F); + } + return ret; +} +std::string format_hex(std::vector data) { return format_hex(data.data(), data.size()); } + +static char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; } +std::string format_hex_pretty(const uint8_t *data, size_t length) { + if (length == 0) + return ""; + std::string ret; + ret.resize(3 * length - 1); + for (size_t i = 0; i < length; i++) { + ret[3 * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4); + ret[3 * i + 1] = format_hex_pretty_char(data[i] & 0x0F); + if (i != length - 1) + ret[3 * i + 2] = '.'; + } + if (length > 4) + return ret + " (" + to_string(length) + ")"; + return ret; +} +std::string format_hex_pretty(std::vector data) { return format_hex_pretty(data.data(), data.size()); } + } // namespace esphome diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index db507f824d..90f35ee4ca 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -45,8 +46,6 @@ std::string to_string(unsigned long long val); // NOLINT std::string to_string(float val); std::string to_string(double val); std::string to_string(long double val); -optional parse_hex(const std::string &str, size_t start, size_t length); -optional parse_hex(char chr); /// Compare string a to string b (ignoring case) and return whether they are equal. bool str_equals_case_insensitive(const std::string &a, const std::string &b); @@ -186,10 +185,6 @@ enum ParseOnOffState { ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const char *off = nullptr); -// Encode raw data to a human-readable string (for debugging) -std::string hexencode(const uint8_t *data, uint32_t len); -template std::string hexencode(const T &data) { return hexencode(data.data(), data.size()); } - // https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer/7858971#7858971 template struct seq {}; // NOLINT template struct gens : gens {}; // NOLINT @@ -408,6 +403,77 @@ optional parse_number(const std::string &str) { return parse_number(str.c_str()); } +/** Parse bytes from a hex-encoded string into a byte array. + * + * When \p len is less than \p 2*count, the result is written to the back of \p data (i.e. this function treats \p str + * as if it were padded with zeros at the front). + * + * @param str String to read from. + * @param len Length of \p str (excluding optional null-terminator), is a limit on the number of characters parsed. + * @param data Byte array to write to. + * @param count Length of \p data. + * @return The number of characters parsed from \p str. + */ +size_t parse_hex(const char *str, size_t len, uint8_t *data, size_t count); +/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into array \p data. +inline bool parse_hex(const char *str, uint8_t *data, size_t count) { + return parse_hex(str, strlen(str), data, count) == 2 * count; +} +/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into array \p data. +inline bool parse_hex(const std::string &str, uint8_t *data, size_t count) { + return parse_hex(str.c_str(), str.length(), data, count) == 2 * count; +} +/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into vector \p data. +inline bool parse_hex(const char *str, std::vector &data, size_t count) { + data.resize(count); + return parse_hex(str, strlen(str), data.data(), count) == 2 * count; +} +/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into vector \p data. +inline bool parse_hex(const std::string &str, std::vector &data, size_t count) { + data.resize(count); + return parse_hex(str.c_str(), str.length(), data.data(), count) == 2 * count; +} +/** Parse a hex-encoded string into an unsigned integer. + * + * @param str String to read from, starting with the most significant byte. + * @param len Length of \p str (excluding optional null-terminator), is a limit on the number of characters parsed. + */ +template::value, int> = 0> +optional parse_hex(const char *str, size_t len) { + T val = 0; + if (len > 2 * sizeof(T) || parse_hex(str, len, reinterpret_cast(&val), sizeof(T)) == 0) + return {}; + return convert_big_endian(val); +} +/// Parse a hex-encoded null-terminated string (starting with the most significant byte) into an unsigned integer. +template::value, int> = 0> optional parse_hex(const char *str) { + return parse_hex(str, strlen(str)); +} +/// Parse a hex-encoded null-terminated string (starting with the most significant byte) into an unsigned integer. +template::value, int> = 0> optional parse_hex(const std::string &str) { + return parse_hex(str.c_str(), str.length()); +} + +/// Format the byte array \p data of length \p len in lowercased hex. +std::string format_hex(const uint8_t *data, size_t length); +/// Format the vector \p data in lowercased hex. +std::string format_hex(std::vector data); +/// Format an unsigned integer in lowercased hex, starting with the most significant byte. +template::value, int> = 0> std::string format_hex(T val) { + val = convert_big_endian(val); + return format_hex(reinterpret_cast(&val), sizeof(T)); +} + +/// Format the byte array \p data of length \p len in pretty-printed, human-readable hex. +std::string format_hex_pretty(const uint8_t *data, size_t length); +/// Format the vector \p data in pretty-printed, human-readable hex. +std::string format_hex_pretty(std::vector data); +/// Format an unsigned integer in pretty-printed, human-readable hex, starting with the most significant byte. +template::value, int> = 0> std::string format_hex_pretty(T val) { + val = convert_big_endian(val); + return format_hex_pretty(reinterpret_cast(&val), sizeof(T)); +} + ///@} /// @name Number manipulation @@ -420,4 +486,18 @@ template T remap(U value, U min, U max, T min_out, T max ///@} +/// @name Deprecated functions +///@{ + +ESPDEPRECATED("hexencode() is deprecated, use format_hex_pretty() instead.", "2022.1") +inline std::string hexencode(const uint8_t *data, uint32_t len) { return format_hex_pretty(data, len); } + +template +ESPDEPRECATED("hexencode() is deprecated, use format_hex_pretty() instead.", "2022.1") +std::string hexencode(const T &data) { + return hexencode(data.data(), data.size()); +} + +///@} + } // namespace esphome From 9c0506592b946c70670fdcfcf14f3729458ecec3 Mon Sep 17 00:00:00 2001 From: tony <42725386+tony-fav@users.noreply.github.com> Date: Sun, 12 Dec 2021 15:19:57 -0500 Subject: [PATCH 1821/1841] Add light.on_state trigger (#2868) --- esphome/components/light/__init__.py | 10 ++++++++++ esphome/components/light/automation.h | 7 +++++++ esphome/components/light/types.py | 1 + 3 files changed, 18 insertions(+) diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index 03224d4c10..fe8a90b8db 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -14,6 +14,7 @@ from esphome.const import ( CONF_RESTORE_MODE, CONF_ON_TURN_OFF, CONF_ON_TURN_ON, + CONF_ON_STATE, CONF_TRIGGER_ID, CONF_COLD_WHITE_COLOR_TEMPERATURE, CONF_WARM_WHITE_COLOR_TEMPERATURE, @@ -37,6 +38,7 @@ from .types import ( # noqa AddressableLight, LightTurnOnTrigger, LightTurnOffTrigger, + LightStateTrigger, ) CODEOWNERS = ["@esphome/core"] @@ -69,6 +71,11 @@ LIGHT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ex cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOffTrigger), } ), + cv.Optional(CONF_ON_STATE): auto.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightStateTrigger), + } + ), } ) @@ -151,6 +158,9 @@ async def setup_light_core_(light_var, output_var, config): for conf in config.get(CONF_ON_TURN_OFF, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], light_var) await auto.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_STATE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], light_var) + await auto.build_automation(trigger, [], conf) if CONF_COLOR_CORRECT in config: cg.add(output_var.set_correction(*config[CONF_COLOR_CORRECT])) diff --git a/esphome/components/light/automation.h b/esphome/components/light/automation.h index 5ec2cb626a..b63fc93dc5 100644 --- a/esphome/components/light/automation.h +++ b/esphome/components/light/automation.h @@ -141,6 +141,13 @@ class LightTurnOffTrigger : public Trigger<> { } }; +class LightStateTrigger : public Trigger<> { + public: + LightStateTrigger(LightState *a_light) { + a_light->add_new_remote_values_callback([this]() { this->trigger(); }); + } +}; + // This is slightly ugly, but we can't log in headers, and can't make this a static method on AddressableSet // due to the template. It's just a temporary warning anyway. void addressableset_warn_about_scale(const char *field); diff --git a/esphome/components/light/types.py b/esphome/components/light/types.py index bc20cd5555..a453debd94 100644 --- a/esphome/components/light/types.py +++ b/esphome/components/light/types.py @@ -41,6 +41,7 @@ LightTurnOnTrigger = light_ns.class_( LightTurnOffTrigger = light_ns.class_( "LightTurnOffTrigger", automation.Trigger.template() ) +LightStateTrigger = light_ns.class_("LightStateTrigger", automation.Trigger.template()) # Effects LightEffect = light_ns.class_("LightEffect") From 31a61b598be920abc1e35b430591e76fadededb6 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Sun, 12 Dec 2021 21:30:47 +0100 Subject: [PATCH 1822/1841] Reduce timing noise in duty_cycle (#2881) --- esphome/components/duty_cycle/duty_cycle_sensor.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.cpp b/esphome/components/duty_cycle/duty_cycle_sensor.cpp index aed22312a7..9a881c81f0 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.cpp +++ b/esphome/components/duty_cycle/duty_cycle_sensor.cpp @@ -23,22 +23,24 @@ void DutyCycleSensor::dump_config() { } void DutyCycleSensor::update() { const uint32_t now = micros(); + const uint32_t last_interrupt = this->store_.last_interrupt; // Read the measurement taken by the interrupt + uint32_t on_time = this->store_.on_time; + + this->store_.on_time = 0; // Start new measurement, exactly aligned with the micros() reading + this->store_.last_interrupt = now; + if (this->last_update_ != 0) { const bool level = this->store_.last_level; - const uint32_t last_interrupt = this->store_.last_interrupt; - uint32_t on_time = this->store_.on_time; if (level) on_time += now - last_interrupt; const float total_time = float(now - this->last_update_); - const float value = (on_time / total_time) * 100.0f; + const float value = (on_time * 100.0f) / total_time; ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value); this->publish_state(value); } - this->store_.on_time = 0; - this->store_.last_interrupt = now; this->last_update_ = now; } From da45923d05b3836be5d03fc290f17aed30baae49 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Sun, 12 Dec 2021 21:32:37 +0100 Subject: [PATCH 1823/1841] Turn verbose a debug statement in bme280 (#2906) --- esphome/components/bme280/bme280.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/bme280/bme280.cpp b/esphome/components/bme280/bme280.cpp index 627072443e..d3a228328b 100644 --- a/esphome/components/bme280/bme280.cpp +++ b/esphome/components/bme280/bme280.cpp @@ -201,7 +201,7 @@ void BME280Component::update() { float pressure = this->read_pressure_(data, t_fine); float humidity = this->read_humidity_(data, t_fine); - ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity); + ESP_LOGV(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity); if (this->temperature_sensor_ != nullptr) this->temperature_sensor_->publish_state(temperature); if (this->pressure_sensor_ != nullptr) From cec4a81e14322b970134a1ffff87cb4cdf42db85 Mon Sep 17 00:00:00 2001 From: Ben Owen Date: Sun, 12 Dec 2021 16:27:11 -0500 Subject: [PATCH 1824/1841] Add reset_duration option for waveshare epaper HAT rev 2.1 (#1481) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/waveshare_epaper/display.py | 9 ++++++++- esphome/components/waveshare_epaper/waveshare_epaper.h | 4 +++- esphome/const.py | 1 + tests/test4.yaml | 1 + 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 64f5597a65..1d1644dc25 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -1,6 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome import pins +from esphome import core, pins from esphome.components import display, spi from esphome.const import ( CONF_BUSY_PIN, @@ -10,6 +10,7 @@ from esphome.const import ( CONF_LAMBDA, CONF_MODEL, CONF_PAGES, + CONF_RESET_DURATION, CONF_RESET_PIN, ) @@ -95,6 +96,10 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema, cv.Optional(CONF_FULL_UPDATE_EVERY): cv.uint32_t, + cv.Optional(CONF_RESET_DURATION): cv.All( + cv.positive_time_period_milliseconds, + cv.Range(max=core.TimePeriod(milliseconds=500)), + ), } ) .extend(cv.polling_component_schema("1s")) @@ -135,3 +140,5 @@ async def to_code(config): cg.add(var.set_busy_pin(reset)) if CONF_FULL_UPDATE_EVERY in config: cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY])) + if CONF_RESET_DURATION in config: + cg.add(var.set_reset_duration(config[CONF_RESET_DURATION])) diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index a1e2f6037a..4de2ac7d97 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -16,6 +16,7 @@ class WaveshareEPaper : public PollingComponent, float get_setup_priority() const override; void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } void set_busy_pin(GPIOPin *busy) { this->busy_pin_ = busy; } + void set_reset_duration(uint32_t reset_duration) { this->reset_duration_ = reset_duration; } void command(uint8_t value); void data(uint8_t value); @@ -45,13 +46,14 @@ class WaveshareEPaper : public PollingComponent, void reset_() { if (this->reset_pin_ != nullptr) { this->reset_pin_->digital_write(false); - delay(200); // NOLINT + delay(reset_duration_); // NOLINT this->reset_pin_->digital_write(true); delay(200); // NOLINT } } uint32_t get_buffer_length_(); + uint32_t reset_duration_{200}; void start_command_(); void end_command_(); diff --git a/esphome/const.py b/esphome/const.py index df86216447..baaebbf90f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -539,6 +539,7 @@ CONF_REFERENCE_TEMPERATURE = "reference_temperature" CONF_REFRESH = "refresh" CONF_REPEAT = "repeat" CONF_REPOSITORY = "repository" +CONF_RESET_DURATION = "reset_duration" CONF_RESET_PIN = "reset_pin" CONF_RESIZE = "resize" CONF_RESOLUTION = "resolution" diff --git a/tests/test4.yaml b/tests/test4.yaml index b4708acf65..bb8f0f15c6 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -413,6 +413,7 @@ display: reset_pin: GPIO23 model: 2.90in full_update_every: 30 + reset_duration: 200ms lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: waveshare_epaper From 4e108813310c33a1584bbec7c8150e1fcde4b522 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 13 Dec 2021 10:28:19 +1300 Subject: [PATCH 1825/1841] Log the actual value in modbus number (#2901) --- esphome/components/modbus_controller/number/modbus_number.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index e5afd0c611..74dd615e61 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -52,7 +52,7 @@ void ModbusNumber::control(float value) { ESP_LOGD(TAG, "Updating register: connected Sensor=%s start address=0x%X register count=%d new value=%.02f (val=%.02f)", - this->get_name().c_str(), this->start_address, this->register_count, write_value, write_value); + this->get_name().c_str(), this->start_address, this->register_count, value, write_value); // Create and send the write command ModbusCommandItem write_cmd; From 4bb58b2de9eaf7e4c835b051ebf1918792637e5b Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Sun, 12 Dec 2021 23:03:08 +0100 Subject: [PATCH 1826/1841] Add gpio 12 to strapping pin list (#2902) --- esphome/components/esp32/gpio_esp32.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32/gpio_esp32.py b/esphome/components/esp32/gpio_esp32.py index 425d77b343..dbafb73dba 100644 --- a/esphome/components/esp32/gpio_esp32.py +++ b/esphome/components/esp32/gpio_esp32.py @@ -18,7 +18,7 @@ _ESP_SDIO_PINS = { 11: "Flash Command", } -_ESP32_STRAPPING_PINS = {0, 2, 4, 15} +_ESP32_STRAPPING_PINS = {0, 2, 4, 12, 15} _LOGGER = logging.getLogger(__name__) From a79c6aa9e0d8831349fbb22e7158a29191654631 Mon Sep 17 00:00:00 2001 From: myhomeiot <70070601+myhomeiot@users.noreply.github.com> Date: Mon, 13 Dec 2021 02:08:18 +0200 Subject: [PATCH 1827/1841] Added access to ble_scan_result_evt_param as get_scan_result (#2854) --- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp | 1 + esphome/components/esp32_ble_tracker/esp32_ble_tracker.h | 3 +++ 2 files changed, 4 insertions(+) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index e3de02e2d7..084dab4c84 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -483,6 +483,7 @@ optional ESPBLEiBeacon::from_manufacturer_data(const ServiceData } void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { + this->scan_result_ = param; for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++) this->address_[i] = param.bda[i]; this->address_type_ = param.ble_addr_type; diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 02e102f06c..9ff2a5a861 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -97,6 +97,8 @@ class ESPBTDevice { const std::vector &get_service_datas() const { return service_datas_; } + const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &get_scan_result() const { return scan_result_; } + optional get_ibeacon() const { for (auto &it : this->manufacturer_datas_) { auto res = ESPBLEiBeacon::from_manufacturer_data(it); @@ -121,6 +123,7 @@ class ESPBTDevice { std::vector service_uuids_; std::vector manufacturer_datas_{}; std::vector service_datas_{}; + esp_ble_gap_cb_param_t::ble_scan_result_evt_param scan_result_{}; }; class ESP32BLETracker; From b3fb35783e052fc6e151b29b4e47a030585de441 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Dec 2021 03:21:09 +0100 Subject: [PATCH 1828/1841] Set text sensor state property to filter output (#2893) --- esphome/components/text_sensor/text_sensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 0bcab90843..5d47e7465a 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -62,7 +62,7 @@ void TextSensor::add_on_raw_state_callback(std::function call std::string TextSensor::get_state() const { return this->state; } std::string TextSensor::get_raw_state() const { return this->raw_state; } void TextSensor::internal_send_state_to_frontend(const std::string &state) { - this->state = this->raw_state; + this->state = state; this->has_state_ = true; ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str()); this->callback_.call(state); From 16e7bd038833e2c1e149598697e566488b4006c0 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 13 Dec 2021 19:15:22 +0100 Subject: [PATCH 1829/1841] fix multi-line comment warning/error (#2891) --- esphome/__main__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/__main__.py b/esphome/__main__.py index 2f06a71b5f..6f57791480 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -145,6 +145,8 @@ def wrap_to_code(name, comp): if comp.config_schema is not None: conf_str = yaml_util.dump(conf) conf_str = conf_str.replace("//", "") + # remove tailing \ to avoid multi-line comment warning + conf_str = conf_str.replace("\\\n", "\n") cg.add(cg.LineComment(indent(conf_str))) await coro(conf) From 80e2bfada33a5a9848bf4c3a7492f006c47f0833 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Dec 2021 19:15:49 +0100 Subject: [PATCH 1830/1841] Bump black from 21.11b1 to 21.12b0 (#2879) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Oxan van Leeuwen --- esphome/wizard.py | 1 - requirements_test.txt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/esphome/wizard.py b/esphome/wizard.py index f2632caf71..c64ad3a583 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -160,7 +160,6 @@ if get_bool_env(ENV_QUICKWIZARD): def sleep(time): pass - else: from time import sleep diff --git a/requirements_test.txt b/requirements_test.txt index abfd07f022..4d5c40296f 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==2.12.2 flake8==4.0.1 -black==21.11b1 +black==21.12b0 pre-commit # Unit tests From 45e346cf1b9d18189af861dfeff354e60ea6825a Mon Sep 17 00:00:00 2001 From: wilberforce Date: Tue, 14 Dec 2021 15:08:01 +1300 Subject: [PATCH 1831/1841] Allow button POST on press from web server (#2913) --- esphome/components/web_server/web_server.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 29cb4827bd..1e7696edfb 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -390,8 +390,6 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM #ifdef USE_BUTTON void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (button::Button *obj : App.get_buttons()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; From a7b05db2a160e91c1e22be1f368bcb24b1224c7e Mon Sep 17 00:00:00 2001 From: jddonovan Date: Tue, 14 Dec 2021 20:39:50 +0200 Subject: [PATCH 1832/1841] Adding Pascal unit to constants (#2914) --- esphome/const.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/const.py b/esphome/const.py index baaebbf90f..36d2257c30 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -832,6 +832,7 @@ UNIT_MINUTE = "min" UNIT_OHM = "Ω" UNIT_PARTS_PER_BILLION = "ppb" UNIT_PARTS_PER_MILLION = "ppm" +UNIT_PASCAL = "Pa" UNIT_PERCENT = "%" UNIT_PULSES = "pulses" UNIT_PULSES_PER_MINUTE = "pulses/min" From 5d70ff702b40722e6a843926f1cf920445790dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Vran=C3=ADk?= Date: Tue, 14 Dec 2021 19:43:42 +0100 Subject: [PATCH 1833/1841] quantile filter support (#2900) Co-authored-by: Oxan van Leeuwen Co-authored-by: pvranik --- esphome/components/sensor/__init__.py | 26 +++++++++++++++++ esphome/components/sensor/filter.cpp | 40 +++++++++++++++++++++++++-- esphome/components/sensor/filter.h | 31 +++++++++++++++++++++ esphome/const.py | 1 + tests/test3.yaml | 5 ++++ 5 files changed, 101 insertions(+), 2 deletions(-) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index d9d226aab6..14a15da2f1 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -19,6 +19,7 @@ from esphome.const import ( CONF_ON_RAW_VALUE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, + CONF_QUANTILE, CONF_SEND_EVERY, CONF_SEND_FIRST_AT, CONF_STATE_CLASS, @@ -151,6 +152,7 @@ SensorPublishAction = sensor_ns.class_("SensorPublishAction", automation.Action) # Filters Filter = sensor_ns.class_("Filter") +QuantileFilter = sensor_ns.class_("QuantileFilter", Filter) MedianFilter = sensor_ns.class_("MedianFilter", Filter) MinFilter = sensor_ns.class_("MinFilter", Filter) MaxFilter = sensor_ns.class_("MaxFilter", Filter) @@ -285,6 +287,30 @@ async def filter_out_filter_to_code(config, filter_id): return cg.new_Pvariable(filter_id, config) +QUANTILE_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_WINDOW_SIZE, default=5): cv.positive_not_null_int, + cv.Optional(CONF_SEND_EVERY, default=5): cv.positive_not_null_int, + cv.Optional(CONF_SEND_FIRST_AT, default=1): cv.positive_not_null_int, + cv.Optional(CONF_QUANTILE, default=0.9): cv.zero_to_one_float, + } + ), + validate_send_first_at, +) + + +@FILTER_REGISTRY.register("quantile", QuantileFilter, QUANTILE_SCHEMA) +async def quantile_filter_to_code(config, filter_id): + return cg.new_Pvariable( + filter_id, + config[CONF_WINDOW_SIZE], + config[CONF_SEND_EVERY], + config[CONF_SEND_FIRST_AT], + config[CONF_QUANTILE], + ) + + MEDIAN_SCHEMA = cv.All( cv.Schema( { diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index 321e3a4a4f..7a8a557273 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -1,7 +1,8 @@ #include "filter.h" -#include "sensor.h" -#include "esphome/core/log.h" #include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "sensor.h" +#include namespace esphome { namespace sensor { @@ -66,6 +67,41 @@ optional MedianFilter::new_value(float value) { return {}; } +// QuantileFilter +QuantileFilter::QuantileFilter(size_t window_size, size_t send_every, size_t send_first_at, float quantile) + : send_every_(send_every), send_at_(send_every - send_first_at), window_size_(window_size), quantile_(quantile) {} +void QuantileFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; } +void QuantileFilter::set_window_size(size_t window_size) { this->window_size_ = window_size; } +void QuantileFilter::set_quantile(float quantile) { this->quantile_ = quantile; } +optional QuantileFilter::new_value(float value) { + if (!std::isnan(value)) { + while (this->queue_.size() >= this->window_size_) { + this->queue_.pop_front(); + } + this->queue_.push_back(value); + ESP_LOGVV(TAG, "QuantileFilter(%p)::new_value(%f), quantile:%f", this, value, this->quantile_); + } + + if (++this->send_at_ >= this->send_every_) { + this->send_at_ = 0; + + float result = 0.0f; + if (!this->queue_.empty()) { + std::deque quantile_queue = this->queue_; + sort(quantile_queue.begin(), quantile_queue.end()); + + size_t queue_size = quantile_queue.size(); + size_t position = ceilf(queue_size * this->quantile_) - 1; + ESP_LOGVV(TAG, "QuantileFilter(%p)::position: %d/%d", this, position, queue_size); + result = quantile_queue[position]; + } + + ESP_LOGVV(TAG, "QuantileFilter(%p)::new_value(%f) SENDING", this, result); + return result; + } + return {}; +} + // MinFilter MinFilter::MinFilter(size_t window_size, size_t send_every, size_t send_first_at) : send_every_(send_every), send_at_(send_every - send_first_at), window_size_(window_size) {} diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index d595e419a6..0ed7ce4801 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -42,6 +42,37 @@ class Filter { Sensor *parent_{nullptr}; }; +/** Simple quantile filter. + * + * Takes the quantile of the last values and pushes it out every . + */ +class QuantileFilter : public Filter { + public: + /** Construct a QuantileFilter. + * + * @param window_size The number of values that should be used in quantile calculation. + * @param send_every After how many sensor values should a new one be pushed out. + * @param send_first_at After how many values to forward the very first value. Defaults to the first value + * on startup being published on the first *raw* value, so with no filter applied. Must be less than or equal to + * send_every. + * @param quantile float 0..1 to pick the requested quantile. Defaults to 0.9. + */ + explicit QuantileFilter(size_t window_size, size_t send_every, size_t send_first_at, float quantile); + + optional new_value(float value) override; + + void set_send_every(size_t send_every); + void set_window_size(size_t window_size); + void set_quantile(float quantile); + + protected: + std::deque queue_; + size_t send_every_; + size_t send_at_; + size_t window_size_; + float quantile_; +}; + /** Simple median filter. * * Takes the median of the last values and pushes it out every . diff --git a/esphome/const.py b/esphome/const.py index 36d2257c30..28648412a5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -518,6 +518,7 @@ CONF_PULLDOWN = "pulldown" CONF_PULLUP = "pullup" CONF_PULSE_LENGTH = "pulse_length" CONF_QOS = "qos" +CONF_QUANTILE = "quantile" CONF_RADON = "radon" CONF_RADON_LONG_TERM = "radon_long_term" CONF_RANDOM = "random" diff --git a/tests/test3.yaml b/tests/test3.yaml index 50cd6d6cf6..61d68d824b 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -358,6 +358,11 @@ sensor: - filter_out: NAN - sliding_window_moving_average: - exponential_moving_average: + - quantile: + window_size: 5 + send_every: 5 + send_first_at: 3 + quantile: .8 - lambda: 'return 0;' - delta: 100 - throttle: 100ms From 192eb495898da6c630818580d797b9dc5d3c1335 Mon Sep 17 00:00:00 2001 From: sveip Date: Tue, 14 Dec 2021 19:46:43 +0100 Subject: [PATCH 1834/1841] ESP32 CAM add Automatic Exposure Control option (#2892) Co-authored-by: Peter Co-authored-by: Carlos Garcia Saura --- esphome/components/esp32_camera/__init__.py | 9 +++++++++ esphome/components/esp32_camera/esp32_camera.cpp | 12 +++++++++--- esphome/components/esp32_camera/esp32_camera.h | 6 ++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 2b1890267f..1135b31798 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -57,6 +57,9 @@ CONF_IDLE_FRAMERATE = "idle_framerate" CONF_JPEG_QUALITY = "jpeg_quality" CONF_VERTICAL_FLIP = "vertical_flip" CONF_HORIZONTAL_MIRROR = "horizontal_mirror" +CONF_AEC2 = "aec2" +CONF_AE_LEVEL = "ae_level" +CONF_AEC_VALUE = "aec_value" CONF_SATURATION = "saturation" CONF_TEST_PATTERN = "test_pattern" @@ -102,6 +105,9 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( cv.Optional(CONF_SATURATION, default=0): camera_range_param, cv.Optional(CONF_VERTICAL_FLIP, default=True): cv.boolean, cv.Optional(CONF_HORIZONTAL_MIRROR, default=True): cv.boolean, + cv.Optional(CONF_AEC2, default=False): cv.boolean, + cv.Optional(CONF_AE_LEVEL, default=0): camera_range_param, + cv.Optional(CONF_AEC_VALUE, default=300): cv.int_range(min=0, max=1200), cv.Optional(CONF_TEST_PATTERN, default=False): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA) @@ -116,6 +122,9 @@ SETTERS = { CONF_JPEG_QUALITY: "set_jpeg_quality", CONF_VERTICAL_FLIP: "set_vertical_flip", CONF_HORIZONTAL_MIRROR: "set_horizontal_mirror", + CONF_AEC2: "set_aec2", + CONF_AE_LEVEL: "set_ae_level", + CONF_AEC_VALUE: "set_aec_value", CONF_CONTRAST: "set_contrast", CONF_BRIGHTNESS: "set_brightness", CONF_SATURATION: "set_saturation", diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 6f93532f47..a6d4a30c27 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -26,6 +26,9 @@ void ESP32Camera::setup() { sensor_t *s = esp_camera_sensor_get(); s->set_vflip(s, this->vertical_flip_); s->set_hmirror(s, this->horizontal_mirror_); + s->set_aec2(s, this->aec2_); // 0 = disable , 1 = enable + s->set_ae_level(s, this->ae_level_); // -2 to 2 + s->set_aec_value(s, this->aec_value_); // 0 to 1200 s->set_contrast(s, this->contrast_); s->set_brightness(s, this->brightness_); s->set_saturation(s, this->saturation_); @@ -111,9 +114,9 @@ void ESP32Camera::dump_config() { // ESP_LOGCONFIG(TAG, " Auto White Balance: %u", st.awb); // ESP_LOGCONFIG(TAG, " Auto White Balance Gain: %u", st.awb_gain); // ESP_LOGCONFIG(TAG, " Auto Exposure Control: %u", st.aec); - // ESP_LOGCONFIG(TAG, " Auto Exposure Control 2: %u", st.aec2); - // ESP_LOGCONFIG(TAG, " Auto Exposure Level: %d", st.ae_level); - // ESP_LOGCONFIG(TAG, " Auto Exposure Value: %u", st.aec_value); + ESP_LOGCONFIG(TAG, " Auto Exposure Control 2: %u", st.aec2); + ESP_LOGCONFIG(TAG, " Auto Exposure Level: %d", st.ae_level); + ESP_LOGCONFIG(TAG, " Auto Exposure Value: %u", st.aec_value); // ESP_LOGCONFIG(TAG, " AGC: %u", st.agc); // ESP_LOGCONFIG(TAG, " AGC Gain: %u", st.agc_gain); // ESP_LOGCONFIG(TAG, " Gain Ceiling: %u", st.gainceiling); @@ -250,6 +253,9 @@ void ESP32Camera::add_image_callback(std::functionvertical_flip_ = vertical_flip; } void ESP32Camera::set_horizontal_mirror(bool horizontal_mirror) { this->horizontal_mirror_ = horizontal_mirror; } +void ESP32Camera::set_aec2(bool aec2) { this->aec2_ = aec2; } +void ESP32Camera::set_ae_level(int ae_level) { this->ae_level_ = ae_level; } +void ESP32Camera::set_aec_value(uint32_t aec_value) { this->aec_value_ = aec_value; } void ESP32Camera::set_contrast(int contrast) { this->contrast_ = contrast; } void ESP32Camera::set_brightness(int brightness) { this->brightness_ = brightness; } void ESP32Camera::set_saturation(int saturation) { this->saturation_ = saturation; } diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index 84f8d9cbea..b20485a0f7 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -67,6 +67,9 @@ class ESP32Camera : public Component, public EntityBase { void set_power_down_pin(uint8_t pin); void set_vertical_flip(bool vertical_flip); void set_horizontal_mirror(bool horizontal_mirror); + void set_aec2(bool aec2); + void set_ae_level(int ae_level); + void set_aec_value(uint32_t aec_value); void set_contrast(int contrast); void set_brightness(int brightness); void set_saturation(int saturation); @@ -91,6 +94,9 @@ class ESP32Camera : public Component, public EntityBase { camera_config_t config_{}; bool vertical_flip_{true}; bool horizontal_mirror_{true}; + bool aec2_{false}; + int ae_level_{0}; + uint32_t aec_value_{300}; int contrast_{0}; int brightness_{0}; int saturation_{0}; From 1fb0a7109d8f08e3e8bb7e2e8761085506193def Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Wed, 15 Dec 2021 10:38:23 +0100 Subject: [PATCH 1835/1841] Modbus: use multiply for publishing number (#2916) --- esphome/components/modbus_controller/number/modbus_number.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index 74dd615e61..5e977f5df4 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -8,7 +8,7 @@ namespace modbus_controller { static const char *const TAG = "modbus.number"; void ModbusNumber::parse_and_publish(const std::vector &data) { - float result = payload_to_float(data, *this); + float result = payload_to_float(data, *this) / multiply_by_; // Is there a lambda registered // call it with the pre converted value and the raw data array From 66e0ff839278bb80f9bdae14d16007105a0355aa Mon Sep 17 00:00:00 2001 From: Benny de Leeuw Date: Mon, 20 Dec 2021 02:30:23 +0100 Subject: [PATCH 1836/1841] Add growatt modbus sensor (#2922) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/growatt_solar/__init__.py | 0 .../growatt_solar/growatt_solar.cpp | 69 ++++++ .../components/growatt_solar/growatt_solar.h | 73 +++++++ esphome/components/growatt_solar/sensor.py | 201 ++++++++++++++++++ 5 files changed, 344 insertions(+) create mode 100644 esphome/components/growatt_solar/__init__.py create mode 100644 esphome/components/growatt_solar/growatt_solar.cpp create mode 100644 esphome/components/growatt_solar/growatt_solar.h create mode 100644 esphome/components/growatt_solar/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 6dbdef12ec..2f2260e0e4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -65,6 +65,7 @@ esphome/components/globals/* @esphome/core esphome/components/gpio/* @esphome/core esphome/components/gps/* @coogle esphome/components/graph/* @synco +esphome/components/growatt_solar/* @leeuwte esphome/components/havells_solar/* @sourabhjaiswal esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/light/* @DotNetDann diff --git a/esphome/components/growatt_solar/__init__.py b/esphome/components/growatt_solar/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/growatt_solar/growatt_solar.cpp b/esphome/components/growatt_solar/growatt_solar.cpp new file mode 100644 index 0000000000..ed7240ab6c --- /dev/null +++ b/esphome/components/growatt_solar/growatt_solar.cpp @@ -0,0 +1,69 @@ +#include "growatt_solar.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace growatt_solar { + +static const char *const TAG = "growatt_solar"; + +static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x04; +static const uint8_t MODBUS_REGISTER_COUNT = 33; + +void GrowattSolar::update() { this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT); } + +void GrowattSolar::on_modbus_data(const std::vector &data) { + auto publish_1_reg_sensor_state = [&](sensor::Sensor *sensor, size_t i, float unit) -> void { + if (sensor == nullptr) + return; + float value = encode_uint16(data[i * 2], data[i * 2 + 1]) * unit; + sensor->publish_state(value); + }; + + auto publish_2_reg_sensor_state = [&](sensor::Sensor *sensor, size_t reg1, size_t reg2, float unit) -> void { + float value = ((encode_uint16(data[reg1 * 2], data[reg1 * 2 + 1]) << 16) + + encode_uint16(data[reg2 * 2], data[reg2 * 2 + 1])) * + unit; + if (sensor != nullptr) + sensor->publish_state(value); + }; + + publish_1_reg_sensor_state(this->inverter_status_, 0, 1); + + publish_2_reg_sensor_state(this->pv_active_power_sensor_, 1, 2, ONE_DEC_UNIT); + + publish_1_reg_sensor_state(this->pvs_[0].voltage_sensor_, 3, ONE_DEC_UNIT); + publish_1_reg_sensor_state(this->pvs_[0].current_sensor_, 4, ONE_DEC_UNIT); + publish_2_reg_sensor_state(this->pvs_[0].active_power_sensor_, 5, 6, ONE_DEC_UNIT); + + publish_1_reg_sensor_state(this->pvs_[1].voltage_sensor_, 7, ONE_DEC_UNIT); + publish_1_reg_sensor_state(this->pvs_[1].current_sensor_, 8, ONE_DEC_UNIT); + publish_2_reg_sensor_state(this->pvs_[1].active_power_sensor_, 9, 10, ONE_DEC_UNIT); + + publish_2_reg_sensor_state(this->grid_active_power_sensor_, 11, 12, ONE_DEC_UNIT); + publish_1_reg_sensor_state(this->grid_frequency_sensor_, 13, TWO_DEC_UNIT); + + publish_1_reg_sensor_state(this->phases_[0].voltage_sensor_, 14, ONE_DEC_UNIT); + publish_1_reg_sensor_state(this->phases_[0].current_sensor_, 15, ONE_DEC_UNIT); + publish_2_reg_sensor_state(this->phases_[0].active_power_sensor_, 16, 17, ONE_DEC_UNIT); + + publish_1_reg_sensor_state(this->phases_[1].voltage_sensor_, 18, ONE_DEC_UNIT); + publish_1_reg_sensor_state(this->phases_[1].current_sensor_, 19, ONE_DEC_UNIT); + publish_2_reg_sensor_state(this->phases_[1].active_power_sensor_, 20, 21, ONE_DEC_UNIT); + + publish_1_reg_sensor_state(this->phases_[2].voltage_sensor_, 22, ONE_DEC_UNIT); + publish_1_reg_sensor_state(this->phases_[2].current_sensor_, 23, ONE_DEC_UNIT); + publish_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 24, 25, ONE_DEC_UNIT); + + publish_2_reg_sensor_state(this->today_production_, 26, 27, ONE_DEC_UNIT); + publish_2_reg_sensor_state(this->total_energy_production_, 28, 29, ONE_DEC_UNIT); + + publish_1_reg_sensor_state(this->inverter_module_temp_, 32, ONE_DEC_UNIT); +} + +void GrowattSolar::dump_config() { + ESP_LOGCONFIG(TAG, "GROWATT Solar:"); + ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); +} + +} // namespace growatt_solar +} // namespace esphome diff --git a/esphome/components/growatt_solar/growatt_solar.h b/esphome/components/growatt_solar/growatt_solar.h new file mode 100644 index 0000000000..5356ac907a --- /dev/null +++ b/esphome/components/growatt_solar/growatt_solar.h @@ -0,0 +1,73 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/modbus/modbus.h" + +namespace esphome { +namespace growatt_solar { + +static const float TWO_DEC_UNIT = 0.01; +static const float ONE_DEC_UNIT = 0.1; + +class GrowattSolar : public PollingComponent, public modbus::ModbusDevice { + public: + void update() override; + void on_modbus_data(const std::vector &data) override; + void dump_config() override; + + void set_inverter_status_sensor(sensor::Sensor *sensor) { this->inverter_status_ = sensor; } + + void set_grid_frequency_sensor(sensor::Sensor *sensor) { this->grid_frequency_sensor_ = sensor; } + void set_grid_active_power_sensor(sensor::Sensor *sensor) { this->grid_active_power_sensor_ = sensor; } + void set_pv_active_power_sensor(sensor::Sensor *sensor) { this->pv_active_power_sensor_ = sensor; } + + void set_today_production_sensor(sensor::Sensor *sensor) { this->today_production_ = sensor; } + void set_total_energy_production_sensor(sensor::Sensor *sensor) { this->total_energy_production_ = sensor; } + void set_inverter_module_temp_sensor(sensor::Sensor *sensor) { this->inverter_module_temp_ = sensor; } + + void set_voltage_sensor(uint8_t phase, sensor::Sensor *voltage_sensor) { + this->phases_[phase].voltage_sensor_ = voltage_sensor; + } + void set_current_sensor(uint8_t phase, sensor::Sensor *current_sensor) { + this->phases_[phase].current_sensor_ = current_sensor; + } + void set_active_power_sensor(uint8_t phase, sensor::Sensor *active_power_sensor) { + this->phases_[phase].active_power_sensor_ = active_power_sensor; + } + void set_voltage_sensor_pv(uint8_t pv, sensor::Sensor *voltage_sensor) { + this->pvs_[pv].voltage_sensor_ = voltage_sensor; + } + void set_current_sensor_pv(uint8_t pv, sensor::Sensor *current_sensor) { + this->pvs_[pv].current_sensor_ = current_sensor; + } + void set_active_power_sensor_pv(uint8_t pv, sensor::Sensor *active_power_sensor) { + this->pvs_[pv].active_power_sensor_ = active_power_sensor; + } + + protected: + struct GrowattPhase { + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *active_power_sensor_{nullptr}; + } phases_[3]; + struct GrowattPV { + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *active_power_sensor_{nullptr}; + } pvs_[2]; + + sensor::Sensor *inverter_status_{nullptr}; + + sensor::Sensor *grid_frequency_sensor_{nullptr}; + sensor::Sensor *grid_active_power_sensor_{nullptr}; + + sensor::Sensor *pv_active_power_sensor_{nullptr}; + + sensor::Sensor *today_production_{nullptr}; + sensor::Sensor *total_energy_production_{nullptr}; + sensor::Sensor *inverter_module_temp_{nullptr}; +}; + +} // namespace growatt_solar +} // namespace esphome diff --git a/esphome/components/growatt_solar/sensor.py b/esphome/components/growatt_solar/sensor.py new file mode 100644 index 0000000000..99936c33ee --- /dev/null +++ b/esphome/components/growatt_solar/sensor.py @@ -0,0 +1,201 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, modbus +from esphome.const import ( + CONF_ACTIVE_POWER, + CONF_CURRENT, + CONF_FREQUENCY, + CONF_ID, + CONF_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + ICON_CURRENT_AC, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, + UNIT_AMPERE, + UNIT_CELSIUS, + UNIT_HERTZ, + UNIT_VOLT, + UNIT_WATT, +) + +CONF_PHASE_A = "phase_a" +CONF_PHASE_B = "phase_b" +CONF_PHASE_C = "phase_c" + +CONF_ENERGY_PRODUCTION_DAY = "energy_production_day" +CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production" +CONF_TOTAL_GENERATION_TIME = "total_generation_time" +CONF_TODAY_GENERATION_TIME = "today_generation_time" +CONF_PV1 = "pv1" +CONF_PV2 = "pv2" +UNIT_KILOWATT_HOURS = "kWh" +UNIT_HOURS = "h" +UNIT_KOHM = "kΩ" +UNIT_MILLIAMPERE = "mA" + +CONF_INVERTER_STATUS = "inverter_status" +CONF_PV_ACTIVE_POWER = "pv_active_power" +CONF_INVERTER_MODULE_TEMP = "inverter_module_temp" + + +AUTO_LOAD = ["modbus"] +CODEOWNERS = ["@leeuwte"] + +growatt_solar_ns = cg.esphome_ns.namespace("growatt_solar") +GrowattSolar = growatt_solar_ns.class_( + "GrowattSolar", cg.PollingComponent, modbus.ModbusDevice +) + +PHASE_SENSORS = { + CONF_VOLTAGE: sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + ), + CONF_CURRENT: sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + CONF_ACTIVE_POWER: sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), +} +PV_SENSORS = { + CONF_VOLTAGE: sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + ), + CONF_CURRENT: sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + CONF_ACTIVE_POWER: sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), +} + +PHASE_SCHEMA = cv.Schema( + {cv.Optional(sensor): schema for sensor, schema in PHASE_SENSORS.items()} +) +PV_SCHEMA = cv.Schema( + {cv.Optional(sensor): schema for sensor, schema in PV_SENSORS.items()} +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(GrowattSolar), + cv.Optional(CONF_PHASE_A): PHASE_SCHEMA, + cv.Optional(CONF_PHASE_B): PHASE_SCHEMA, + cv.Optional(CONF_PHASE_C): PHASE_SCHEMA, + cv.Optional(CONF_PV1): PV_SCHEMA, + cv.Optional(CONF_PV2): PV_SCHEMA, + cv.Optional(CONF_INVERTER_STATUS): sensor.sensor_schema(), + cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( + unit_of_measurement=UNIT_HERTZ, + icon=ICON_CURRENT_AC, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ACTIVE_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PV_ACTIVE_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ENERGY_PRODUCTION_DAY): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional(CONF_INVERTER_MODULE_TEMP): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("10s")) + .extend(modbus.modbus_device_schema(0x01)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await modbus.register_modbus_device(var, config) + + if CONF_INVERTER_STATUS in config: + sens = await sensor.new_sensor(config[CONF_INVERTER_STATUS]) + cg.add(var.set_inverter_status_sensor(sens)) + + if CONF_FREQUENCY in config: + sens = await sensor.new_sensor(config[CONF_FREQUENCY]) + cg.add(var.set_grid_frequency_sensor(sens)) + + if CONF_ACTIVE_POWER in config: + sens = await sensor.new_sensor(config[CONF_ACTIVE_POWER]) + cg.add(var.set_grid_active_power_sensor(sens)) + + if CONF_PV_ACTIVE_POWER in config: + sens = await sensor.new_sensor(config[CONF_PV_ACTIVE_POWER]) + cg.add(var.set_pv_active_power_sensor(sens)) + + if CONF_ENERGY_PRODUCTION_DAY in config: + sens = await sensor.new_sensor(config[CONF_ENERGY_PRODUCTION_DAY]) + cg.add(var.set_today_production_sensor(sens)) + + if CONF_TOTAL_ENERGY_PRODUCTION in config: + sens = await sensor.new_sensor(config[CONF_TOTAL_ENERGY_PRODUCTION]) + cg.add(var.set_total_energy_production_sensor(sens)) + + if CONF_INVERTER_MODULE_TEMP in config: + sens = await sensor.new_sensor(config[CONF_INVERTER_MODULE_TEMP]) + cg.add(var.set_inverter_module_temp_sensor(sens)) + + for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]): + if phase not in config: + continue + + phase_config = config[phase] + for sensor_type in PHASE_SENSORS: + if sensor_type in phase_config: + sens = await sensor.new_sensor(phase_config[sensor_type]) + cg.add(getattr(var, f"set_{sensor_type}_sensor")(i, sens)) + + for i, pv in enumerate([CONF_PV1, CONF_PV2]): + if pv not in config: + continue + + pv_config = config[pv] + for sensor_type in pv_config: + if sensor_type in pv_config: + sens = await sensor.new_sensor(pv_config[sensor_type]) + cg.add(getattr(var, f"set_{sensor_type}_sensor_pv")(i, sens)) From 6ec9cfb0447a4e140e2071fe4947e5f43bf68f0a Mon Sep 17 00:00:00 2001 From: Frank Langtind Date: Mon, 20 Dec 2021 02:35:10 +0100 Subject: [PATCH 1837/1841] Add Tuya Number support (#2765) --- CODEOWNERS | 1 + esphome/components/tuya/number/__init__.py | 54 +++++++++++++++++++ .../components/tuya/number/tuya_number.cpp | 38 +++++++++++++ esphome/components/tuya/number/tuya_number.h | 27 ++++++++++ esphome/const.py | 1 + tests/test4.yaml | 8 ++- 6 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 esphome/components/tuya/number/__init__.py create mode 100644 esphome/components/tuya/number/tuya_number.cpp create mode 100644 esphome/components/tuya/number/tuya_number.h diff --git a/CODEOWNERS b/CODEOWNERS index 2f2260e0e4..fed6815a66 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -180,6 +180,7 @@ esphome/components/toshiba/* @kbx81 esphome/components/tsl2591/* @wjcarpenter esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/climate/* @jesserockz +esphome/components/tuya/number/* @frankiboy1 esphome/components/tuya/sensor/* @jesserockz esphome/components/tuya/switch/* @jesserockz esphome/components/tuya/text_sensor/* @dentra diff --git a/esphome/components/tuya/number/__init__.py b/esphome/components/tuya/number/__init__.py new file mode 100644 index 0000000000..12c0c0f6e5 --- /dev/null +++ b/esphome/components/tuya/number/__init__.py @@ -0,0 +1,54 @@ +from esphome.components import number +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import ( + CONF_ID, + CONF_NUMBER_DATAPOINT, + CONF_MAX_VALUE, + CONF_MIN_VALUE, + CONF_STEP, +) +from .. import tuya_ns, CONF_TUYA_ID, Tuya + +DEPENDENCIES = ["tuya"] +CODEOWNERS = ["@frankiboy1"] + +TuyaNumber = tuya_ns.class_("TuyaNumber", number.Number, cg.Component) + + +def validate_min_max(config): + if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]: + raise cv.Invalid("max_value must be greater than min_value") + return config + + +CONFIG_SCHEMA = cv.All( + number.NUMBER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TuyaNumber), + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Required(CONF_NUMBER_DATAPOINT): cv.uint8_t, + cv.Required(CONF_MAX_VALUE): cv.float_, + cv.Required(CONF_MIN_VALUE): cv.float_, + cv.Required(CONF_STEP): cv.positive_float, + } + ).extend(cv.COMPONENT_SCHEMA), + validate_min_max, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await number.register_number( + var, + config, + min_value=config[CONF_MIN_VALUE], + max_value=config[CONF_MAX_VALUE], + step=config[CONF_STEP], + ) + + paren = await cg.get_variable(config[CONF_TUYA_ID]) + cg.add(var.set_tuya_parent(paren)) + + cg.add(var.set_number_id(config[CONF_NUMBER_DATAPOINT])) diff --git a/esphome/components/tuya/number/tuya_number.cpp b/esphome/components/tuya/number/tuya_number.cpp new file mode 100644 index 0000000000..5c7cafbf7a --- /dev/null +++ b/esphome/components/tuya/number/tuya_number.cpp @@ -0,0 +1,38 @@ +#include "esphome/core/log.h" +#include "tuya_number.h" + +namespace esphome { +namespace tuya { + +static const char *const TAG = "tuya.number"; + +void TuyaNumber::setup() { + this->parent_->register_listener(this->number_id_, [this](const TuyaDatapoint &datapoint) { + if (datapoint.type == TuyaDatapointType::INTEGER) { + ESP_LOGV(TAG, "MCU reported number %u is: %d", datapoint.id, datapoint.value_int); + this->publish_state(datapoint.value_int); + } else if (datapoint.type == TuyaDatapointType::ENUM) { + ESP_LOGV(TAG, "MCU reported number %u is: %u", datapoint.id, datapoint.value_enum); + this->publish_state(datapoint.value_enum); + } + this->type_ = datapoint.type; + }); +} + +void TuyaNumber::control(float value) { + ESP_LOGV(TAG, "Setting number %u: %f", this->number_id_, value); + if (this->type_ == TuyaDatapointType::INTEGER) { + this->parent_->set_integer_datapoint_value(this->number_id_, value); + } else if (this->type_ == TuyaDatapointType::ENUM) { + this->parent_->set_enum_datapoint_value(this->number_id_, value); + } + this->publish_state(value); +} + +void TuyaNumber::dump_config() { + LOG_NUMBER("", "Tuya Number", this); + ESP_LOGCONFIG(TAG, " Number has datapoint ID %u", this->number_id_); +} + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/number/tuya_number.h b/esphome/components/tuya/number/tuya_number.h new file mode 100644 index 0000000000..7cca9fc646 --- /dev/null +++ b/esphome/components/tuya/number/tuya_number.h @@ -0,0 +1,27 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/tuya/tuya.h" +#include "esphome/components/number/number.h" + +namespace esphome { +namespace tuya { + +class TuyaNumber : public number::Number, public Component { + public: + void setup() override; + void dump_config() override; + void set_number_id(uint8_t number_id) { this->number_id_ = number_id; } + + void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } + + protected: + void control(float value) override; + + Tuya *parent_; + uint8_t number_id_{0}; + TuyaDatapointType type_{}; +}; + +} // namespace tuya +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 28648412a5..970b3c6578 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -402,6 +402,7 @@ CONF_NUM_CHIPS = "num_chips" CONF_NUM_LEDS = "num_leds" CONF_NUM_SCANS = "num_scans" CONF_NUMBER = "number" +CONF_NUMBER_DATAPOINT = "number_datapoint" CONF_OFF_MODE = "off_mode" CONF_OFFSET = "offset" CONF_ON = "on" diff --git a/tests/test4.yaml b/tests/test4.yaml index bb8f0f15c6..545806ba87 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -441,7 +441,13 @@ display: wakeup_pin: GPIO1 vcom_pin: GPIO1 - +number: + - platform: tuya + id: tuya_number + number_datapoint: 102 + min_value: 0 + max_value: 17 + step: 1 text_sensor: - platform: pipsolar From 542fb2175b999e1cb87aee097083a1754d8825ed Mon Sep 17 00:00:00 2001 From: Jonas De Kegel Date: Mon, 20 Dec 2021 09:30:35 +0100 Subject: [PATCH 1838/1841] Support inverted tm1637 display (#2878) Co-authored-by: Oxan van Leeuwen --- esphome/components/tm1637/display.py | 6 ++++ esphome/components/tm1637/tm1637.cpp | 43 +++++++++++++++++++++------- esphome/components/tm1637/tm1637.h | 4 +++ tests/test1.yaml | 2 ++ 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/esphome/components/tm1637/display.py b/esphome/components/tm1637/display.py index 7999029f5a..609c62fd10 100644 --- a/esphome/components/tm1637/display.py +++ b/esphome/components/tm1637/display.py @@ -8,6 +8,8 @@ from esphome.const import ( CONF_ID, CONF_LAMBDA, CONF_INTENSITY, + CONF_INVERTED, + CONF_LENGTH, ) CODEOWNERS = ["@glmnet"] @@ -22,6 +24,8 @@ CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend( cv.Optional(CONF_INTENSITY, default=7): cv.All( cv.uint8_t, cv.Range(min=0, max=7) ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + cv.Optional(CONF_LENGTH, default=6): cv.All(cv.uint8_t, cv.Range(min=1, max=6)), cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_DIO_PIN): pins.gpio_output_pin_schema, } @@ -39,6 +43,8 @@ async def to_code(config): cg.add(var.set_dio_pin(dio)) cg.add(var.set_intensity(config[CONF_INTENSITY])) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_length(config[CONF_LENGTH])) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( diff --git a/esphome/components/tm1637/tm1637.cpp b/esphome/components/tm1637/tm1637.cpp index 488f3b6727..ff8d5ea1bd 100644 --- a/esphome/components/tm1637/tm1637.cpp +++ b/esphome/components/tm1637/tm1637.cpp @@ -130,7 +130,9 @@ void TM1637Display::setup() { } void TM1637Display::dump_config() { ESP_LOGCONFIG(TAG, "TM1637:"); - ESP_LOGCONFIG(TAG, " INTENSITY: %d", this->intensity_); + ESP_LOGCONFIG(TAG, " Intensity: %d", this->intensity_); + ESP_LOGCONFIG(TAG, " Inverted: %d", this->inverted_); + ESP_LOGCONFIG(TAG, " Length: %d", this->length_); LOG_PIN(" CLK Pin: ", this->clk_pin_); LOG_PIN(" DIO Pin: ", this->dio_pin_); LOG_UPDATE_INTERVAL(this); @@ -173,8 +175,14 @@ void TM1637Display::display() { this->send_byte_(TM1637_I2C_COMM2); // Write the data bytes - for (auto b : this->buffer_) { - this->send_byte_(b); + if (this->inverted_) { + for (uint8_t i = this->length_ - 1; i >= 0; i--) { + this->send_byte_(this->buffer_[i]); + } + } else { + for (auto b : this->buffer_) { + this->send_byte_(b); + } } this->stop_(); @@ -241,14 +249,27 @@ uint8_t TM1637Display::print(uint8_t start_pos, const char *str) { } // Remap segments, for compatibility with MAX7219 segment definition which is // XABCDEFG, but TM1637 is // XGFEDCBA - data = ((data & 0x80) ? 0x80 : 0) | // no move X - ((data & 0x40) ? 0x1 : 0) | // A - ((data & 0x20) ? 0x2 : 0) | // B - ((data & 0x10) ? 0x4 : 0) | // C - ((data & 0x8) ? 0x8 : 0) | // D - ((data & 0x4) ? 0x10 : 0) | // E - ((data & 0x2) ? 0x20 : 0) | // F - ((data & 0x1) ? 0x40 : 0); // G + if (this->inverted_) { + // XABCDEFG > XGCBAFED + data = ((data & 0x80) ? 0x80 : 0) | // no move X + ((data & 0x40) ? 0x8 : 0) | // A + ((data & 0x20) ? 0x10 : 0) | // B + ((data & 0x10) ? 0x20 : 0) | // C + ((data & 0x8) ? 0x1 : 0) | // D + ((data & 0x4) ? 0x2 : 0) | // E + ((data & 0x2) ? 0x4 : 0) | // F + ((data & 0x1) ? 0x40 : 0); // G + } else { + // XABCDEFG > XGFEDCBA + data = ((data & 0x80) ? 0x80 : 0) | // no move X + ((data & 0x40) ? 0x1 : 0) | // A + ((data & 0x20) ? 0x2 : 0) | // B + ((data & 0x10) ? 0x4 : 0) | // C + ((data & 0x8) ? 0x8 : 0) | // D + ((data & 0x4) ? 0x10 : 0) | // E + ((data & 0x2) ? 0x20 : 0) | // F + ((data & 0x1) ? 0x40 : 0); // G + } if (*str == '.') { if (pos != start_pos) pos--; diff --git a/esphome/components/tm1637/tm1637.h b/esphome/components/tm1637/tm1637.h index 63b30ac13e..9b2f014ff9 100644 --- a/esphome/components/tm1637/tm1637.h +++ b/esphome/components/tm1637/tm1637.h @@ -41,6 +41,8 @@ class TM1637Display : public PollingComponent { uint8_t print(const char *str); void set_intensity(uint8_t intensity) { this->intensity_ = intensity; } + void set_inverted(bool inverted) { this->inverted_ = inverted; } + void set_length(uint8_t length) { this->length_ = length; } void display(); @@ -62,6 +64,8 @@ class TM1637Display : public PollingComponent { GPIOPin *dio_pin_; GPIOPin *clk_pin_; uint8_t intensity_; + uint8_t length_; + bool inverted_; optional writer_{}; uint8_t buffer_[6] = {0}; }; diff --git a/tests/test1.yaml b/tests/test1.yaml index 18c6610b08..7494146d1e 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2119,6 +2119,8 @@ display: mcp23xxx: mcp23017_hub number: 2 intensity: 3 + inverted: true + length: 4 lambda: |- it.print("1234"); - platform: pcd8544 From 1ccee86705fc65301bee41d2f17b27d88015dff3 Mon Sep 17 00:00:00 2001 From: Jonas De Kegel Date: Mon, 20 Dec 2021 18:06:04 +0100 Subject: [PATCH 1839/1841] Fix tm1637 bootloop (#2929) --- esphome/components/tm1637/tm1637.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tm1637/tm1637.cpp b/esphome/components/tm1637/tm1637.cpp index ff8d5ea1bd..a21d2d438d 100644 --- a/esphome/components/tm1637/tm1637.cpp +++ b/esphome/components/tm1637/tm1637.cpp @@ -176,7 +176,7 @@ void TM1637Display::display() { // Write the data bytes if (this->inverted_) { - for (uint8_t i = this->length_ - 1; i >= 0; i--) { + for (int8_t i = this->length_ - 1; i >= 0; i--) { this->send_byte_(this->buffer_[i]); } } else { From 4907e6f6d782f22d22e24918c161d15b6ef1447c Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 20 Dec 2021 20:19:20 +0100 Subject: [PATCH 1840/1841] Fix MQTT button press action (#2917) --- esphome/components/mqtt/mqtt_button.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index 25ff327cf9..5f3aaa1dd9 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -17,7 +17,7 @@ MQTTButtonComponent::MQTTButtonComponent(button::Button *button) : MQTTComponent void MQTTButtonComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { - if (payload == "press") { + if (payload == "PRESS") { this->button_->press(); } else { ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str()); @@ -31,6 +31,7 @@ void MQTTButtonComponent::dump_config() { } void MQTTButtonComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { + config.state_topic = false; if (!this->button_->get_device_class().empty()) root[MQTT_DEVICE_CLASS] = this->button_->get_device_class(); } From f431c7402f0cdcc8fa032302a5ebe77d8020648a Mon Sep 17 00:00:00 2001 From: jsuanet <75206491+jsuanet@users.noreply.github.com> Date: Mon, 20 Dec 2021 22:25:36 +0100 Subject: [PATCH 1841/1841] Add shutdown and safe_mode button (#2918) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Jos Suanet --- CODEOWNERS | 4 +-- esphome/components/safe_mode/__init__.py | 2 +- .../components/safe_mode/button/__init__.py | 36 +++++++++++++++++++ .../safe_mode/button/safe_mode_button.cpp | 25 +++++++++++++ .../safe_mode/button/safe_mode_button.h | 21 +++++++++++ esphome/components/shutdown/__init__.py | 2 +- .../components/shutdown/button/__init__.py | 23 ++++++++++++ .../shutdown/button/shutdown_button.cpp | 33 +++++++++++++++++ .../shutdown/button/shutdown_button.h | 18 ++++++++++ .../{switch.py => switch/__init__.py} | 0 .../shutdown/{ => switch}/shutdown_switch.cpp | 0 .../shutdown/{ => switch}/shutdown_switch.h | 0 tests/test4.yaml | 4 +++ 13 files changed, 164 insertions(+), 4 deletions(-) create mode 100644 esphome/components/safe_mode/button/__init__.py create mode 100644 esphome/components/safe_mode/button/safe_mode_button.cpp create mode 100644 esphome/components/safe_mode/button/safe_mode_button.h create mode 100644 esphome/components/shutdown/button/__init__.py create mode 100644 esphome/components/shutdown/button/shutdown_button.cpp create mode 100644 esphome/components/shutdown/button/shutdown_button.h rename esphome/components/shutdown/{switch.py => switch/__init__.py} (100%) rename esphome/components/shutdown/{ => switch}/shutdown_switch.cpp (100%) rename esphome/components/shutdown/{ => switch}/shutdown_switch.h (100%) diff --git a/CODEOWNERS b/CODEOWNERS index fed6815a66..452c560938 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -133,7 +133,7 @@ esphome/components/restart/* @esphome/core esphome/components/rf_bridge/* @jesserockz esphome/components/rgbct/* @jesserockz esphome/components/rtttl/* @glmnet -esphome/components/safe_mode/* @paulmonigatti +esphome/components/safe_mode/* @jsuanet @paulmonigatti esphome/components/scd4x/* @sjtrny esphome/components/script/* @esphome/core esphome/components/sdm_meter/* @jesserockz @polyfaces @@ -143,7 +143,7 @@ esphome/components/select/* @esphome/core esphome/components/sensor/* @esphome/core esphome/components/sgp40/* @SenexCrenshaw esphome/components/sht4x/* @sjtrny -esphome/components/shutdown/* @esphome/core +esphome/components/shutdown/* @esphome/core @jsuanet esphome/components/sim800l/* @glmnet esphome/components/sm2135/* @BoukeHaarsma23 esphome/components/socket/* @esphome/core diff --git a/esphome/components/safe_mode/__init__.py b/esphome/components/safe_mode/__init__.py index f150d6e086..ab884bfee4 100644 --- a/esphome/components/safe_mode/__init__.py +++ b/esphome/components/safe_mode/__init__.py @@ -1,5 +1,5 @@ import esphome.codegen as cg -CODEOWNERS = ["@paulmonigatti"] +CODEOWNERS = ["@paulmonigatti", "@jsuanet"] safe_mode_ns = cg.esphome_ns.namespace("safe_mode") diff --git a/esphome/components/safe_mode/button/__init__.py b/esphome/components/safe_mode/button/__init__.py new file mode 100644 index 0000000000..2cd8892afb --- /dev/null +++ b/esphome/components/safe_mode/button/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import button +from esphome.components.ota import OTAComponent +from esphome.const import ( + CONF_ID, + CONF_OTA, + DEVICE_CLASS_RESTART, + ENTITY_CATEGORY_CONFIG, + ICON_RESTART_ALERT, +) + +DEPENDENCIES = ["ota"] + +safe_mode_ns = cg.esphome_ns.namespace("safe_mode") +SafeModeButton = safe_mode_ns.class_("SafeModeButton", button.Button, cg.Component) + +CONFIG_SCHEMA = ( + button.button_schema( + device_class=DEVICE_CLASS_RESTART, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_RESTART_ALERT, + ) + .extend({cv.GenerateID(): cv.declare_id(SafeModeButton)}) + .extend({cv.GenerateID(CONF_OTA): cv.use_id(OTAComponent)}) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await button.register_button(var, config) + + ota = await cg.get_variable(config[CONF_OTA]) + cg.add(var.set_ota(ota)) diff --git a/esphome/components/safe_mode/button/safe_mode_button.cpp b/esphome/components/safe_mode/button/safe_mode_button.cpp new file mode 100644 index 0000000000..2b8654de46 --- /dev/null +++ b/esphome/components/safe_mode/button/safe_mode_button.cpp @@ -0,0 +1,25 @@ +#include "safe_mode_button.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace safe_mode { + +static const char *const TAG = "safe_mode.button"; + +void SafeModeButton::set_ota(ota::OTAComponent *ota) { this->ota_ = ota; } + +void SafeModeButton::press_action() { + ESP_LOGI(TAG, "Restarting device in safe mode..."); + this->ota_->set_safe_mode_pending(true); + + // Let MQTT settle a bit + delay(100); // NOLINT + App.safe_reboot(); +} + +void SafeModeButton::dump_config() { LOG_BUTTON("", "Safe Mode Button", this); } + +} // namespace safe_mode +} // namespace esphome diff --git a/esphome/components/safe_mode/button/safe_mode_button.h b/esphome/components/safe_mode/button/safe_mode_button.h new file mode 100644 index 0000000000..63e0d1755e --- /dev/null +++ b/esphome/components/safe_mode/button/safe_mode_button.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/ota/ota_component.h" +#include "esphome/components/button/button.h" + +namespace esphome { +namespace safe_mode { + +class SafeModeButton : public button::Button, public Component { + public: + void dump_config() override; + void set_ota(ota::OTAComponent *ota); + + protected: + ota::OTAComponent *ota_; + void press_action() override; +}; + +} // namespace safe_mode +} // namespace esphome diff --git a/esphome/components/shutdown/__init__.py b/esphome/components/shutdown/__init__.py index f70ffa9520..480a6f3e31 100644 --- a/esphome/components/shutdown/__init__.py +++ b/esphome/components/shutdown/__init__.py @@ -1 +1 @@ -CODEOWNERS = ["@esphome/core"] +CODEOWNERS = ["@esphome/core", "@jsuanet"] diff --git a/esphome/components/shutdown/button/__init__.py b/esphome/components/shutdown/button/__init__.py new file mode 100644 index 0000000000..51cd6d6da2 --- /dev/null +++ b/esphome/components/shutdown/button/__init__.py @@ -0,0 +1,23 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import button +from esphome.const import ( + CONF_ID, + ENTITY_CATEGORY_CONFIG, + ICON_POWER, +) + +shutdown_ns = cg.esphome_ns.namespace("shutdown") +ShutdownButton = shutdown_ns.class_("ShutdownButton", button.Button, cg.Component) + +CONFIG_SCHEMA = ( + button.button_schema(entity_category=ENTITY_CATEGORY_CONFIG, icon=ICON_POWER) + .extend({cv.GenerateID(): cv.declare_id(ShutdownButton)}) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await button.register_button(var, config) diff --git a/esphome/components/shutdown/button/shutdown_button.cpp b/esphome/components/shutdown/button/shutdown_button.cpp new file mode 100644 index 0000000000..be88a10d49 --- /dev/null +++ b/esphome/components/shutdown/button/shutdown_button.cpp @@ -0,0 +1,33 @@ +#include "shutdown_button.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +#ifdef USE_ESP32 +#include +#endif +#ifdef USE_ESP8266 +#include +#endif + +namespace esphome { +namespace shutdown { + +static const char *const TAG = "shutdown.button"; + +void ShutdownButton::dump_config() { LOG_BUTTON("", "Shutdown Button", this); } +void ShutdownButton::press_action() { + ESP_LOGI(TAG, "Shutting down..."); + // Let MQTT settle a bit + delay(100); // NOLINT + App.run_safe_shutdown_hooks(); +#ifdef USE_ESP8266 + ESP.deepSleep(0); // NOLINT(readability-static-accessed-through-instance) +#endif +#ifdef USE_ESP32 + esp_deep_sleep_start(); +#endif +} + +} // namespace shutdown +} // namespace esphome diff --git a/esphome/components/shutdown/button/shutdown_button.h b/esphome/components/shutdown/button/shutdown_button.h new file mode 100644 index 0000000000..d0094c899d --- /dev/null +++ b/esphome/components/shutdown/button/shutdown_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/button/button.h" + +namespace esphome { +namespace shutdown { + +class ShutdownButton : public button::Button, public Component { + public: + void dump_config() override; + + protected: + void press_action() override; +}; + +} // namespace shutdown +} // namespace esphome diff --git a/esphome/components/shutdown/switch.py b/esphome/components/shutdown/switch/__init__.py similarity index 100% rename from esphome/components/shutdown/switch.py rename to esphome/components/shutdown/switch/__init__.py diff --git a/esphome/components/shutdown/shutdown_switch.cpp b/esphome/components/shutdown/switch/shutdown_switch.cpp similarity index 100% rename from esphome/components/shutdown/shutdown_switch.cpp rename to esphome/components/shutdown/switch/shutdown_switch.cpp diff --git a/esphome/components/shutdown/shutdown_switch.h b/esphome/components/shutdown/switch/shutdown_switch.h similarity index 100% rename from esphome/components/shutdown/shutdown_switch.h rename to esphome/components/shutdown/switch/shutdown_switch.h diff --git a/tests/test4.yaml b/tests/test4.yaml index 545806ba87..7c1ddee6d8 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -531,3 +531,7 @@ xpt2046: button: - platform: restart name: Restart Button + - platform: safe_mode + name: Safe Mode Button + - platform: shutdown + name: Shutdown Button