mirror of
https://github.com/esphome/esphome.git
synced 2024-11-27 09:18:00 +01:00
Merge branch 'dev' into beta
This commit is contained in:
commit
bf6b11222a
380 changed files with 17501 additions and 5297 deletions
8
.github/FUNDING.yml
vendored
Normal file
8
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github:
|
||||||
|
patreon: ottowinter
|
||||||
|
open_collective:
|
||||||
|
ko_fi:
|
||||||
|
tidelift:
|
||||||
|
custom: https://esphome.io/guides/supporters.html
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -75,6 +75,7 @@ venv.bak/
|
||||||
|
|
||||||
.pioenvs
|
.pioenvs
|
||||||
.piolibdeps
|
.piolibdeps
|
||||||
|
.pio
|
||||||
.vscode
|
.vscode
|
||||||
CMakeListsPrivate.txt
|
CMakeListsPrivate.txt
|
||||||
CMakeLists.txt
|
CMakeLists.txt
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
variables:
|
variables:
|
||||||
DOCKER_DRIVER: overlay2
|
DOCKER_DRIVER: overlay2
|
||||||
DOCKER_HOST: tcp://docker:2375/
|
DOCKER_HOST: tcp://docker:2375/
|
||||||
BASE_VERSION: '1.5.1'
|
BASE_VERSION: '2.0.0'
|
||||||
TZ: UTC
|
TZ: UTC
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
|
@ -33,7 +33,7 @@ stages:
|
||||||
- docker info
|
- docker info
|
||||||
- docker login -u "$DOCKER_USER" -p "$DOCKER_PASSWORD"
|
- docker login -u "$DOCKER_USER" -p "$DOCKER_PASSWORD"
|
||||||
script:
|
script:
|
||||||
- docker run --rm --privileged hassioaddons/qemu-user-static:latest
|
- docker run --rm --privileged multiarch/qemu-user-static:4.1.0-1 --reset -p yes
|
||||||
- TAG="${CI_COMMIT_TAG#v}"
|
- TAG="${CI_COMMIT_TAG#v}"
|
||||||
- TAG="${TAG:-${CI_COMMIT_SHA:0:7}}"
|
- TAG="${TAG:-${CI_COMMIT_SHA:0:7}}"
|
||||||
- echo "Tag ${TAG}"
|
- echo "Tag ${TAG}"
|
||||||
|
@ -107,10 +107,6 @@ lint-tidy:
|
||||||
<<: *lint
|
<<: *lint
|
||||||
script:
|
script:
|
||||||
- pio init --ide atom
|
- pio init --ide atom
|
||||||
- |
|
|
||||||
if ! patch -R -p0 -s -f --dry-run <script/.neopixelbus.patch; then
|
|
||||||
patch -p0 < script/.neopixelbus.patch
|
|
||||||
fi
|
|
||||||
- script/clang-tidy --all-headers --fix
|
- script/clang-tidy --all-headers --fix
|
||||||
- script/ci-suggest-changes
|
- script/ci-suggest-changes
|
||||||
|
|
||||||
|
|
23
.travis.yml
23
.travis.yml
|
@ -1,29 +1,26 @@
|
||||||
sudo: false
|
sudo: false
|
||||||
language: python
|
language: python
|
||||||
|
python: '3.5'
|
||||||
install: script/setup
|
install: script/setup
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- "~/.platformio"
|
- "~/.platformio"
|
||||||
- "$TRAVIS_BUILD_DIR/.piolibdeps"
|
|
||||||
- "$TRAVIS_BUILD_DIR/tests/build/test1/.piolibdeps"
|
|
||||||
- "$TRAVIS_BUILD_DIR/tests/build/test2/.piolibdeps"
|
|
||||||
- "$TRAVIS_BUILD_DIR/tests/build/test3/.piolibdeps"
|
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
include:
|
include:
|
||||||
- python: "2.7"
|
- python: "3.7"
|
||||||
env: TARGET=Lint2.7
|
env: TARGET=Lint3.7
|
||||||
script:
|
script:
|
||||||
- script/ci-custom.py
|
- script/ci-custom.py
|
||||||
- flake8 esphome
|
- flake8 esphome
|
||||||
- pylint esphome
|
- pylint esphome
|
||||||
- python: "3.5.3"
|
- python: "3.5"
|
||||||
env: TARGET=Lint3.5
|
env: TARGET=Test3.5
|
||||||
script:
|
script:
|
||||||
- script/ci-custom.py
|
- esphome tests/test1.yaml compile
|
||||||
- flake8 esphome
|
- esphome tests/test2.yaml compile
|
||||||
- pylint esphome
|
- esphome tests/test3.yaml compile
|
||||||
- python: "2.7"
|
- python: "2.7"
|
||||||
env: TARGET=Test2.7
|
env: TARGET=Test2.7
|
||||||
script:
|
script:
|
||||||
|
@ -43,10 +40,6 @@ matrix:
|
||||||
- clang-format-7
|
- clang-format-7
|
||||||
before_script:
|
before_script:
|
||||||
- pio init --ide atom
|
- pio init --ide atom
|
||||||
- |
|
|
||||||
if ! patch -R -p0 -s -f --dry-run <script/.neopixelbus.patch; then
|
|
||||||
patch -p0 < script/.neopixelbus.patch
|
|
||||||
fi
|
|
||||||
- clang-tidy-7 -version
|
- clang-tidy-7 -version
|
||||||
- clang-format-7 -version
|
- clang-format-7 -version
|
||||||
- clang-apply-replacements-7 -version
|
- clang-apply-replacements-7 -version
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
include LICENSE
|
include LICENSE
|
||||||
include README.md
|
include README.md
|
||||||
include esphome/dashboard/templates/*.html
|
include esphome/dashboard/templates/*.html
|
||||||
recursive-include esphome/dashboard/static *.ico *.js *.css
|
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
|
||||||
recursive-include esphome *.cpp *.h *.tcc
|
recursive-include esphome *.cpp *.h *.tcc
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
ARG BUILD_FROM=esphome/esphome-base-amd64:1.5.1
|
ARG BUILD_FROM=esphome/esphome-base-amd64:2.0.0
|
||||||
FROM ${BUILD_FROM}
|
FROM ${BUILD_FROM}
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN pip2 install --no-cache-dir -e .
|
RUN pip3 install --no-cache-dir -e .
|
||||||
|
|
||||||
|
ENV USERNAME=""
|
||||||
|
ENV PASSWORD=""
|
||||||
|
|
||||||
WORKDIR /config
|
WORKDIR /config
|
||||||
ENTRYPOINT ["esphome"]
|
ENTRYPOINT ["esphome"]
|
||||||
|
|
|
@ -6,7 +6,7 @@ COPY docker/rootfs/ /
|
||||||
COPY setup.py setup.cfg MANIFEST.in /opt/esphome/
|
COPY setup.py setup.cfg MANIFEST.in /opt/esphome/
|
||||||
COPY esphome /opt/esphome/esphome
|
COPY esphome /opt/esphome/esphome
|
||||||
|
|
||||||
RUN pip2 install --no-cache-dir -e /opt/esphome
|
RUN pip3 install --no-cache-dir -e /opt/esphome
|
||||||
|
|
||||||
# Build arguments
|
# Build arguments
|
||||||
ARG BUILD_VERSION=dev
|
ARG BUILD_VERSION=dev
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM esphome/esphome-base-amd64:1.5.1
|
FROM esphome/esphome-base-amd64:2.0.0
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apt-get update \
|
apt-get update \
|
||||||
|
@ -12,7 +12,7 @@ RUN \
|
||||||
/var/lib/apt/lists/*
|
/var/lib/apt/lists/*
|
||||||
|
|
||||||
COPY requirements_test.txt /requirements_test.txt
|
COPY requirements_test.txt /requirements_test.txt
|
||||||
RUN pip2 install -r /requirements_test.txt
|
RUN pip3 install --no-cache-dir wheel && pip3 install --no-cache-dir -r /requirements_test.txt
|
||||||
|
|
||||||
VOLUME ["/esphome"]
|
VOLUME ["/esphome"]
|
||||||
WORKDIR /esphome
|
WORKDIR /esphome
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
FROM ubuntu:bionic
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
python \
|
|
||||||
python-pip \
|
|
||||||
python-setuptools \
|
|
||||||
python-pil \
|
|
||||||
git \
|
|
||||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/*rm -rf /var/lib/apt/lists/* /tmp/* && \
|
|
||||||
pip install --no-cache-dir platformio && \
|
|
||||||
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
|
|
||||||
|
|
||||||
COPY docker/platformio.ini /pio/platformio.ini
|
|
||||||
RUN platformio run -d /pio; rm -rf /pio
|
|
||||||
|
|
||||||
COPY requirements.txt /requirements.txt
|
|
||||||
|
|
||||||
RUN pip install --no-cache-dir -r /requirements.txt
|
|
|
@ -1,12 +0,0 @@
|
||||||
; This file allows the docker build file to install the required platformio
|
|
||||||
; platforms
|
|
||||||
|
|
||||||
[env:espressif8266]
|
|
||||||
platform = espressif8266@1.8.0
|
|
||||||
board = nodemcuv2
|
|
||||||
framework = arduino
|
|
||||||
|
|
||||||
[env:espressif32]
|
|
||||||
platform = espressif32@1.5.0
|
|
||||||
board = nodemcu-32s
|
|
||||||
framework = arduino
|
|
|
@ -10,6 +10,6 @@ if bashio::config.has_value 'esphome_version'; then
|
||||||
esphome_version=$(bashio::config 'esphome_version')
|
esphome_version=$(bashio::config 'esphome_version')
|
||||||
full_url="https://github.com/esphome/esphome/archive/${esphome_version}.zip"
|
full_url="https://github.com/esphome/esphome/archive/${esphome_version}.zip"
|
||||||
bashio::log.info "Installing esphome version '${esphome_version}' (${full_url})..."
|
bashio::log.info "Installing esphome version '${esphome_version}' (${full_url})..."
|
||||||
pip2 install -U --no-cache-dir "${full_url}" \
|
pip3 install -U --no-cache-dir "${full_url}" \
|
||||||
|| bashio::exit.nok "Failed installing esphome pinned version."
|
|| bashio::exit.nok "Failed installing esphome pinned version."
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -4,6 +4,11 @@ server {
|
||||||
include /etc/nginx/includes/server_params.conf;
|
include /etc/nginx/includes/server_params.conf;
|
||||||
include /etc/nginx/includes/proxy_params.conf;
|
include /etc/nginx/includes/proxy_params.conf;
|
||||||
include /etc/nginx/includes/ssl_params.conf;
|
include /etc/nginx/includes/ssl_params.conf;
|
||||||
|
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /ssl/%%certfile%%;
|
||||||
|
ssl_certificate_key /ssl/%%keyfile%%;
|
||||||
|
|
||||||
# Clear Hass.io Ingress header
|
# Clear Hass.io Ingress header
|
||||||
proxy_set_header X-Hassio-Ingress "";
|
proxy_set_header X-Hassio-Ingress "";
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,11 @@ from esphome import const, writer, yaml_util
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.config import iter_components, read_config, strip_default_ids
|
from esphome.config import iter_components, read_config, strip_default_ids
|
||||||
from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
|
from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
|
||||||
CONF_PASSWORD, CONF_PORT
|
CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS
|
||||||
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
|
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
|
||||||
from esphome.helpers import color, indent
|
from esphome.helpers import color, indent
|
||||||
from esphome.py_compat import IS_PY2, safe_input
|
from esphome.py_compat import IS_PY2, safe_input
|
||||||
from esphome.util import run_external_command, run_external_process, safe_print
|
from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ def get_serial_ports():
|
||||||
# from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py
|
# from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py
|
||||||
from serial.tools.list_ports import comports
|
from serial.tools.list_ports import comports
|
||||||
result = []
|
result = []
|
||||||
for port, desc, info in comports():
|
for port, desc, info in comports(include_links=True):
|
||||||
if not port:
|
if not port:
|
||||||
continue
|
continue
|
||||||
if "VID:PID" in info:
|
if "VID:PID" in info:
|
||||||
|
@ -35,7 +35,9 @@ def get_serial_ports():
|
||||||
|
|
||||||
def choose_prompt(options):
|
def choose_prompt(options):
|
||||||
if not options:
|
if not options:
|
||||||
raise ValueError
|
raise EsphomeError("Found no valid options for upload/logging, please make sure relevant "
|
||||||
|
"sections (ota, mqtt, ...) are in your configuration and/or the device "
|
||||||
|
"is plugged in.")
|
||||||
|
|
||||||
if len(options) == 1:
|
if len(options) == 1:
|
||||||
return options[0][1]
|
return options[0][1]
|
||||||
|
@ -130,6 +132,7 @@ def wrap_to_code(name, comp):
|
||||||
conf_str = yaml_util.dump(conf)
|
conf_str = yaml_util.dump(conf)
|
||||||
if IS_PY2:
|
if IS_PY2:
|
||||||
conf_str = conf_str.decode('utf-8')
|
conf_str = conf_str.decode('utf-8')
|
||||||
|
conf_str = conf_str.replace('//', '')
|
||||||
cg.add(cg.LineComment(indent(conf_str)))
|
cg.add(cg.LineComment(indent(conf_str)))
|
||||||
yield coro(conf)
|
yield coro(conf)
|
||||||
|
|
||||||
|
@ -157,12 +160,13 @@ def compile_program(args, config):
|
||||||
from esphome import platformio_api
|
from esphome import platformio_api
|
||||||
|
|
||||||
_LOGGER.info("Compiling app...")
|
_LOGGER.info("Compiling app...")
|
||||||
return platformio_api.run_compile(config, args.verbose)
|
return platformio_api.run_compile(config, CORE.verbose)
|
||||||
|
|
||||||
|
|
||||||
def upload_using_esptool(config, port):
|
def upload_using_esptool(config, port):
|
||||||
path = CORE.firmware_bin
|
path = CORE.firmware_bin
|
||||||
cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset',
|
cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset',
|
||||||
|
'--baud', str(config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get('upload_speed', 460800)),
|
||||||
'--chip', 'esp8266', '--port', port, 'write_flash', '0x0', path]
|
'--chip', 'esp8266', '--port', port, 'write_flash', '0x0', path]
|
||||||
|
|
||||||
if os.environ.get('ESPHOME_USE_SUBPROCESS') is None:
|
if os.environ.get('ESPHOME_USE_SUBPROCESS') is None:
|
||||||
|
@ -180,15 +184,14 @@ def upload_program(config, args, host):
|
||||||
|
|
||||||
if CORE.is_esp8266:
|
if CORE.is_esp8266:
|
||||||
return upload_using_esptool(config, host)
|
return upload_using_esptool(config, host)
|
||||||
return platformio_api.run_upload(config, args.verbose, host)
|
return platformio_api.run_upload(config, CORE.verbose, host)
|
||||||
|
|
||||||
from esphome import espota2
|
from esphome import espota2
|
||||||
|
|
||||||
ota_conf = config[CONF_OTA]
|
ota_conf = config[CONF_OTA]
|
||||||
remote_port = ota_conf[CONF_PORT]
|
remote_port = ota_conf[CONF_PORT]
|
||||||
password = ota_conf[CONF_PASSWORD]
|
password = ota_conf[CONF_PASSWORD]
|
||||||
res = espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def show_logs(config, args, port):
|
def show_logs(config, args, port):
|
||||||
|
@ -218,6 +221,7 @@ def clean_mqtt(config, args):
|
||||||
def setup_log(debug=False, quiet=False):
|
def setup_log(debug=False, quiet=False):
|
||||||
if debug:
|
if debug:
|
||||||
log_level = logging.DEBUG
|
log_level = logging.DEBUG
|
||||||
|
CORE.verbose = True
|
||||||
elif quiet:
|
elif quiet:
|
||||||
log_level = logging.CRITICAL
|
log_level = logging.CRITICAL
|
||||||
else:
|
else:
|
||||||
|
@ -250,12 +254,12 @@ def setup_log(debug=False, quiet=False):
|
||||||
def command_wizard(args):
|
def command_wizard(args):
|
||||||
from esphome import wizard
|
from esphome import wizard
|
||||||
|
|
||||||
return wizard.wizard(args.configuration)
|
return wizard.wizard(args.configuration[0])
|
||||||
|
|
||||||
|
|
||||||
def command_config(args, config):
|
def command_config(args, config):
|
||||||
_LOGGER.info("Configuration is valid!")
|
_LOGGER.info("Configuration is valid!")
|
||||||
if not args.verbose:
|
if not CORE.verbose:
|
||||||
config = strip_default_ids(config)
|
config = strip_default_ids(config)
|
||||||
safe_print(yaml_util.dump(config))
|
safe_print(yaml_util.dump(config))
|
||||||
return 0
|
return 0
|
||||||
|
@ -264,7 +268,7 @@ def command_config(args, config):
|
||||||
def command_vscode(args):
|
def command_vscode(args):
|
||||||
from esphome import vscode
|
from esphome import vscode
|
||||||
|
|
||||||
CORE.config_path = args.configuration
|
CORE.config_path = args.configuration[0]
|
||||||
vscode.read_config(args)
|
vscode.read_config(args)
|
||||||
|
|
||||||
|
|
||||||
|
@ -350,11 +354,52 @@ def command_dashboard(args):
|
||||||
return dashboard.start_web_server(args)
|
return dashboard.start_web_server(args)
|
||||||
|
|
||||||
|
|
||||||
|
def command_update_all(args):
|
||||||
|
import click
|
||||||
|
|
||||||
|
success = {}
|
||||||
|
files = list_yaml_files(args.configuration[0])
|
||||||
|
twidth = 60
|
||||||
|
|
||||||
|
def print_bar(middle_text):
|
||||||
|
middle_text = " {} ".format(middle_text)
|
||||||
|
width = len(click.unstyle(middle_text))
|
||||||
|
half_line = "=" * ((twidth - width) / 2)
|
||||||
|
click.echo("%s%s%s" % (half_line, middle_text, half_line))
|
||||||
|
|
||||||
|
for f in files:
|
||||||
|
print("Updating {}".format(color('cyan', f)))
|
||||||
|
print('-' * twidth)
|
||||||
|
print()
|
||||||
|
rc = run_external_process('esphome', '--dashboard', f, 'run', '--no-logs')
|
||||||
|
if rc == 0:
|
||||||
|
print_bar("[{}] {}".format(color('bold_green', 'SUCCESS'), f))
|
||||||
|
success[f] = True
|
||||||
|
else:
|
||||||
|
print_bar("[{}] {}".format(color('bold_red', 'ERROR'), f))
|
||||||
|
success[f] = False
|
||||||
|
|
||||||
|
print()
|
||||||
|
print()
|
||||||
|
print()
|
||||||
|
|
||||||
|
print_bar('[{}]'.format(color('bold_white', 'SUMMARY')))
|
||||||
|
failed = 0
|
||||||
|
for f in files:
|
||||||
|
if success[f]:
|
||||||
|
print(" - {}: {}".format(f, color('green', 'SUCCESS')))
|
||||||
|
else:
|
||||||
|
print(" - {}: {}".format(f, color('bold_red', 'FAILED')))
|
||||||
|
failed += 1
|
||||||
|
return failed
|
||||||
|
|
||||||
|
|
||||||
PRE_CONFIG_ACTIONS = {
|
PRE_CONFIG_ACTIONS = {
|
||||||
'wizard': command_wizard,
|
'wizard': command_wizard,
|
||||||
'version': command_version,
|
'version': command_version,
|
||||||
'dashboard': command_dashboard,
|
'dashboard': command_dashboard,
|
||||||
'vscode': command_vscode,
|
'vscode': command_vscode,
|
||||||
|
'update-all': command_update_all,
|
||||||
}
|
}
|
||||||
|
|
||||||
POST_CONFIG_ACTIONS = {
|
POST_CONFIG_ACTIONS = {
|
||||||
|
@ -370,13 +415,13 @@ POST_CONFIG_ACTIONS = {
|
||||||
|
|
||||||
|
|
||||||
def parse_args(argv):
|
def parse_args(argv):
|
||||||
parser = argparse.ArgumentParser(prog='esphome')
|
parser = argparse.ArgumentParser(description='ESPHome v{}'.format(const.__version__))
|
||||||
parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.",
|
parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.",
|
||||||
action='store_true')
|
action='store_true')
|
||||||
parser.add_argument('-q', '--quiet', help="Disable all esphome logs.",
|
parser.add_argument('-q', '--quiet', help="Disable all esphome logs.",
|
||||||
action='store_true')
|
action='store_true')
|
||||||
parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true')
|
parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true')
|
||||||
parser.add_argument('configuration', help='Your YAML configuration file.')
|
parser.add_argument('configuration', help='Your YAML configuration file.', nargs='*')
|
||||||
|
|
||||||
subparsers = parser.add_subparsers(help='Commands', dest='command')
|
subparsers = parser.add_subparsers(help='Commands', dest='command')
|
||||||
subparsers.required = True
|
subparsers.required = True
|
||||||
|
@ -433,7 +478,11 @@ def parse_args(argv):
|
||||||
help="Create a simple web server for a dashboard.")
|
help="Create a simple web server for a dashboard.")
|
||||||
dashboard.add_argument("--port", help="The HTTP port to open connections on. Defaults to 6052.",
|
dashboard.add_argument("--port", help="The HTTP port to open connections on. Defaults to 6052.",
|
||||||
type=int, default=6052)
|
type=int, default=6052)
|
||||||
dashboard.add_argument("--password", help="The optional password to require for all requests.",
|
dashboard.add_argument("--username", help="The optional username to require "
|
||||||
|
"for authentication.",
|
||||||
|
type=str, default='')
|
||||||
|
dashboard.add_argument("--password", help="The optional password to require "
|
||||||
|
"for authentication.",
|
||||||
type=str, default='')
|
type=str, default='')
|
||||||
dashboard.add_argument("--open-ui", help="Open the dashboard UI in a browser.",
|
dashboard.add_argument("--open-ui", help="Open the dashboard UI in a browser.",
|
||||||
action='store_true')
|
action='store_true')
|
||||||
|
@ -446,6 +495,8 @@ def parse_args(argv):
|
||||||
vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS)
|
vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS)
|
||||||
vscode.add_argument('--ace', action='store_true')
|
vscode.add_argument('--ace', action='store_true')
|
||||||
|
|
||||||
|
subparsers.add_parser('update-all', help=argparse.SUPPRESS)
|
||||||
|
|
||||||
return parser.parse_args(argv[1:])
|
return parser.parse_args(argv[1:])
|
||||||
|
|
||||||
|
|
||||||
|
@ -454,6 +505,10 @@ def run_esphome(argv):
|
||||||
CORE.dashboard = args.dashboard
|
CORE.dashboard = args.dashboard
|
||||||
|
|
||||||
setup_log(args.verbose, args.quiet)
|
setup_log(args.verbose, args.quiet)
|
||||||
|
if args.command != 'version' and not args.configuration:
|
||||||
|
_LOGGER.error("Missing configuration parameter, see esphome --help.")
|
||||||
|
return 1
|
||||||
|
|
||||||
if args.command in PRE_CONFIG_ACTIONS:
|
if args.command in PRE_CONFIG_ACTIONS:
|
||||||
try:
|
try:
|
||||||
return PRE_CONFIG_ACTIONS[args.command](args)
|
return PRE_CONFIG_ACTIONS[args.command](args)
|
||||||
|
@ -461,21 +516,28 @@ def run_esphome(argv):
|
||||||
_LOGGER.error(e)
|
_LOGGER.error(e)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
CORE.config_path = args.configuration
|
for conf_path in args.configuration:
|
||||||
|
CORE.config_path = conf_path
|
||||||
|
CORE.dashboard = args.dashboard
|
||||||
|
|
||||||
config = read_config(args.verbose)
|
config = read_config()
|
||||||
if config is None:
|
if config is None:
|
||||||
return 1
|
return 1
|
||||||
CORE.config = config
|
CORE.config = config
|
||||||
|
|
||||||
|
if args.command not in POST_CONFIG_ACTIONS:
|
||||||
|
safe_print(u"Unknown command {}".format(args.command))
|
||||||
|
|
||||||
if args.command in POST_CONFIG_ACTIONS:
|
|
||||||
try:
|
try:
|
||||||
return POST_CONFIG_ACTIONS[args.command](args, config)
|
rc = POST_CONFIG_ACTIONS[args.command](args, config)
|
||||||
except EsphomeError as e:
|
except EsphomeError as e:
|
||||||
_LOGGER.error(e)
|
_LOGGER.error(e)
|
||||||
return 1
|
return 1
|
||||||
safe_print(u"Unknown command {}".format(args.command))
|
if rc != 0:
|
||||||
return 1
|
return rc
|
||||||
|
|
||||||
|
CORE.reset()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
|
@ -1,330 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
// The Home Assistant protocol is structured as a simple
|
|
||||||
// TCP socket with short binary messages encoded in the protocol buffers format
|
|
||||||
// First, a message in this protocol has a specific format:
|
|
||||||
// * VarInt denoting the size of the message object. (type is not part of this)
|
|
||||||
// * VarInt denoting the type of message.
|
|
||||||
// * The message object encoded as a ProtoBuf message
|
|
||||||
|
|
||||||
// The connection is established in 4 steps:
|
|
||||||
// * First, the client connects to the server and sends a "Hello Request" identifying itself
|
|
||||||
// * The server responds with a "Hello Response" and selects the protocol version
|
|
||||||
// * After receiving this message, the client attempts to authenticate itself using
|
|
||||||
// the password and a "Connect Request"
|
|
||||||
// * The server responds with a "Connect Response" and notifies of invalid password.
|
|
||||||
// If anything in this initial process fails, the connection must immediately closed
|
|
||||||
// by both sides and _no_ disconnection message is to be sent.
|
|
||||||
|
|
||||||
// Message sent at the beginning of each connection
|
|
||||||
// Can only be sent by the client and only at the beginning of the connection
|
|
||||||
message HelloRequest {
|
|
||||||
// Description of client (like User Agent)
|
|
||||||
// For example "Home Assistant"
|
|
||||||
// Not strictly necessary to send but nice for debugging
|
|
||||||
// purposes.
|
|
||||||
string client_info = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirmation of successful connection request.
|
|
||||||
// Can only be sent by the server and only at the beginning of the connection
|
|
||||||
message HelloResponse {
|
|
||||||
// The version of the API to use. The _client_ (for example Home Assistant) needs to check
|
|
||||||
// for compatibility and if necessary adopt to an older API.
|
|
||||||
// Major is for breaking changes in the base protocol - a mismatch will lead to immediate disconnect_client_
|
|
||||||
// Minor is for breaking changes in individual messages - a mismatch will lead to a warning message
|
|
||||||
uint32 api_version_major = 1;
|
|
||||||
uint32 api_version_minor = 2;
|
|
||||||
|
|
||||||
// A string identifying the server (ESP); like client info this may be empty
|
|
||||||
// and only exists for debugging/logging purposes.
|
|
||||||
// For example "ESPHome v1.10.0 on ESP8266"
|
|
||||||
string server_info = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message sent at the beginning of each connection to authenticate the client
|
|
||||||
// Can only be sent by the client and only at the beginning of the connection
|
|
||||||
message ConnectRequest {
|
|
||||||
// The password to log in with
|
|
||||||
string password = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirmation of successful connection. After this the connection is available for all traffic.
|
|
||||||
// Can only be sent by the server and only at the beginning of the connection
|
|
||||||
message ConnectResponse {
|
|
||||||
bool invalid_password = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request to close the connection.
|
|
||||||
// Can be sent by both the client and server
|
|
||||||
message DisconnectRequest {
|
|
||||||
// Do not close the connection before the acknowledgement arrives
|
|
||||||
}
|
|
||||||
|
|
||||||
message DisconnectResponse {
|
|
||||||
// Empty - Both parties are required to close the connection after this
|
|
||||||
// message has been received.
|
|
||||||
}
|
|
||||||
|
|
||||||
message PingRequest {
|
|
||||||
// Empty
|
|
||||||
}
|
|
||||||
|
|
||||||
message PingResponse {
|
|
||||||
// Empty
|
|
||||||
}
|
|
||||||
|
|
||||||
message DeviceInfoRequest {
|
|
||||||
// Empty
|
|
||||||
}
|
|
||||||
|
|
||||||
message DeviceInfoResponse {
|
|
||||||
bool uses_password = 1;
|
|
||||||
|
|
||||||
// The name of the node, given by "App.set_name()"
|
|
||||||
string name = 2;
|
|
||||||
|
|
||||||
// The mac address of the device. For example "AC:BC:32:89:0E:A9"
|
|
||||||
string mac_address = 3;
|
|
||||||
|
|
||||||
// A string describing the ESPHome version. For example "1.10.0"
|
|
||||||
string esphome_core_version = 4;
|
|
||||||
|
|
||||||
// A string describing the date of compilation, this is generated by the compiler
|
|
||||||
// and therefore may not be in the same format all the time.
|
|
||||||
// If the user isn't using esphome, this will also not be set.
|
|
||||||
string compilation_time = 5;
|
|
||||||
|
|
||||||
// The model of the board. For example NodeMCU
|
|
||||||
string model = 6;
|
|
||||||
|
|
||||||
bool has_deep_sleep = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListEntitiesRequest {
|
|
||||||
// Empty
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListEntitiesBinarySensorResponse {
|
|
||||||
string object_id = 1;
|
|
||||||
fixed32 key = 2;
|
|
||||||
string name = 3;
|
|
||||||
string unique_id = 4;
|
|
||||||
|
|
||||||
string device_class = 5;
|
|
||||||
bool is_status_binary_sensor = 6;
|
|
||||||
}
|
|
||||||
message ListEntitiesCoverResponse {
|
|
||||||
string object_id = 1;
|
|
||||||
fixed32 key = 2;
|
|
||||||
string name = 3;
|
|
||||||
string unique_id = 4;
|
|
||||||
|
|
||||||
bool is_optimistic = 5;
|
|
||||||
}
|
|
||||||
message ListEntitiesFanResponse {
|
|
||||||
string object_id = 1;
|
|
||||||
fixed32 key = 2;
|
|
||||||
string name = 3;
|
|
||||||
string unique_id = 4;
|
|
||||||
|
|
||||||
bool supports_oscillation = 5;
|
|
||||||
bool supports_speed = 6;
|
|
||||||
}
|
|
||||||
message ListEntitiesLightResponse {
|
|
||||||
string object_id = 1;
|
|
||||||
fixed32 key = 2;
|
|
||||||
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;
|
|
||||||
float min_mireds = 9;
|
|
||||||
float max_mireds = 10;
|
|
||||||
repeated string effects = 11;
|
|
||||||
}
|
|
||||||
message ListEntitiesSensorResponse {
|
|
||||||
string object_id = 1;
|
|
||||||
fixed32 key = 2;
|
|
||||||
string name = 3;
|
|
||||||
string unique_id = 4;
|
|
||||||
|
|
||||||
string icon = 5;
|
|
||||||
string unit_of_measurement = 6;
|
|
||||||
int32 accuracy_decimals = 7;
|
|
||||||
}
|
|
||||||
message ListEntitiesSwitchResponse {
|
|
||||||
string object_id = 1;
|
|
||||||
fixed32 key = 2;
|
|
||||||
string name = 3;
|
|
||||||
string unique_id = 4;
|
|
||||||
|
|
||||||
string icon = 5;
|
|
||||||
bool optimistic = 6;
|
|
||||||
}
|
|
||||||
message ListEntitiesTextSensorResponse {
|
|
||||||
string object_id = 1;
|
|
||||||
fixed32 key = 2;
|
|
||||||
string name = 3;
|
|
||||||
string unique_id = 4;
|
|
||||||
|
|
||||||
string icon = 5;
|
|
||||||
}
|
|
||||||
message ListEntitiesDoneResponse {
|
|
||||||
// Empty
|
|
||||||
}
|
|
||||||
|
|
||||||
message SubscribeStatesRequest {
|
|
||||||
// Empty
|
|
||||||
}
|
|
||||||
message BinarySensorStateResponse {
|
|
||||||
fixed32 key = 1;
|
|
||||||
bool state = 2;
|
|
||||||
}
|
|
||||||
message CoverStateResponse {
|
|
||||||
fixed32 key = 1;
|
|
||||||
enum CoverState {
|
|
||||||
OPEN = 0;
|
|
||||||
CLOSED = 1;
|
|
||||||
}
|
|
||||||
CoverState state = 2;
|
|
||||||
}
|
|
||||||
enum FanSpeed {
|
|
||||||
LOW = 0;
|
|
||||||
MEDIUM = 1;
|
|
||||||
HIGH = 2;
|
|
||||||
}
|
|
||||||
message FanStateResponse {
|
|
||||||
fixed32 key = 1;
|
|
||||||
bool state = 2;
|
|
||||||
bool oscillating = 3;
|
|
||||||
FanSpeed speed = 4;
|
|
||||||
}
|
|
||||||
message LightStateResponse {
|
|
||||||
fixed32 key = 1;
|
|
||||||
bool state = 2;
|
|
||||||
float brightness = 3;
|
|
||||||
float red = 4;
|
|
||||||
float green = 5;
|
|
||||||
float blue = 6;
|
|
||||||
float white = 7;
|
|
||||||
float color_temperature = 8;
|
|
||||||
string effect = 9;
|
|
||||||
}
|
|
||||||
message SensorStateResponse {
|
|
||||||
fixed32 key = 1;
|
|
||||||
float state = 2;
|
|
||||||
}
|
|
||||||
message SwitchStateResponse {
|
|
||||||
fixed32 key = 1;
|
|
||||||
bool state = 2;
|
|
||||||
}
|
|
||||||
message TextSensorStateResponse {
|
|
||||||
fixed32 key = 1;
|
|
||||||
string state = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CoverCommandRequest {
|
|
||||||
fixed32 key = 1;
|
|
||||||
enum CoverCommand {
|
|
||||||
OPEN = 0;
|
|
||||||
CLOSE = 1;
|
|
||||||
STOP = 2;
|
|
||||||
}
|
|
||||||
bool has_state = 2;
|
|
||||||
CoverCommand command = 3;
|
|
||||||
}
|
|
||||||
message FanCommandRequest {
|
|
||||||
fixed32 key = 1;
|
|
||||||
bool has_state = 2;
|
|
||||||
bool state = 3;
|
|
||||||
bool has_speed = 4;
|
|
||||||
FanSpeed speed = 5;
|
|
||||||
bool has_oscillating = 6;
|
|
||||||
bool oscillating = 7;
|
|
||||||
}
|
|
||||||
message LightCommandRequest {
|
|
||||||
fixed32 key = 1;
|
|
||||||
bool has_state = 2;
|
|
||||||
bool state = 3;
|
|
||||||
bool has_brightness = 4;
|
|
||||||
float brightness = 5;
|
|
||||||
bool has_rgb = 6;
|
|
||||||
float red = 7;
|
|
||||||
float green = 8;
|
|
||||||
float blue = 9;
|
|
||||||
bool has_white = 10;
|
|
||||||
float white = 11;
|
|
||||||
bool has_color_temperature = 12;
|
|
||||||
float color_temperature = 13;
|
|
||||||
bool has_transition_length = 14;
|
|
||||||
uint32 transition_length = 15;
|
|
||||||
bool has_flash_length = 16;
|
|
||||||
uint32 flash_length = 17;
|
|
||||||
bool has_effect = 18;
|
|
||||||
string effect = 19;
|
|
||||||
}
|
|
||||||
message SwitchCommandRequest {
|
|
||||||
fixed32 key = 1;
|
|
||||||
bool state = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum LogLevel {
|
|
||||||
NONE = 0;
|
|
||||||
ERROR = 1;
|
|
||||||
WARN = 2;
|
|
||||||
INFO = 3;
|
|
||||||
DEBUG = 4;
|
|
||||||
VERBOSE = 5;
|
|
||||||
VERY_VERBOSE = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SubscribeLogsRequest {
|
|
||||||
LogLevel level = 1;
|
|
||||||
bool dump_config = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SubscribeLogsResponse {
|
|
||||||
LogLevel level = 1;
|
|
||||||
string tag = 2;
|
|
||||||
string message = 3;
|
|
||||||
bool send_failed = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SubscribeServiceCallsRequest {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
message ServiceCallResponse {
|
|
||||||
string service = 1;
|
|
||||||
map<string, string> data = 2;
|
|
||||||
map<string, string> data_template = 3;
|
|
||||||
map<string, string> variables = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Client sends SubscribeHomeAssistantStatesRequest
|
|
||||||
// 2. Server responds with zero or more SubscribeHomeAssistantStateResponse (async)
|
|
||||||
// 3. Client sends HomeAssistantStateResponse for state changes.
|
|
||||||
message SubscribeHomeAssistantStatesRequest {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
message SubscribeHomeAssistantStateResponse {
|
|
||||||
string entity_id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message HomeAssistantStateResponse {
|
|
||||||
string entity_id = 1;
|
|
||||||
string state = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetTimeRequest {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetTimeResponse {
|
|
||||||
fixed32 epoch_seconds = 1;
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import esphome.api.api_pb2 as pb
|
||||||
from esphome.const import CONF_PASSWORD, CONF_PORT
|
from esphome.const import CONF_PASSWORD, CONF_PORT
|
||||||
from esphome.core import EsphomeError
|
from esphome.core import EsphomeError
|
||||||
from esphome.helpers import resolve_ip_address, indent, color
|
from esphome.helpers import resolve_ip_address, indent, color
|
||||||
from esphome.py_compat import text_type, IS_PY2, byte_to_bytes, char_to_byte, format_bytes
|
from esphome.py_compat import text_type, IS_PY2, byte_to_bytes, char_to_byte
|
||||||
from esphome.util import safe_print
|
from esphome.util import safe_print
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -108,7 +108,6 @@ class APIClient(threading.Thread):
|
||||||
self._message_handlers = []
|
self._message_handlers = []
|
||||||
self._keepalive = 5
|
self._keepalive = 5
|
||||||
self._ping_timer = None
|
self._ping_timer = None
|
||||||
self._refresh_ping()
|
|
||||||
|
|
||||||
self.on_disconnect = None
|
self.on_disconnect = None
|
||||||
self.on_connect = None
|
self.on_connect = None
|
||||||
|
@ -132,8 +131,8 @@ class APIClient(threading.Thread):
|
||||||
if self._connected:
|
if self._connected:
|
||||||
try:
|
try:
|
||||||
self.ping()
|
self.ping()
|
||||||
except APIConnectionError:
|
except APIConnectionError as err:
|
||||||
self._fatal_error()
|
self._fatal_error(err)
|
||||||
else:
|
else:
|
||||||
self._refresh_ping()
|
self._refresh_ping()
|
||||||
|
|
||||||
|
@ -175,7 +174,7 @@ class APIClient(threading.Thread):
|
||||||
raise APIConnectionError("You need to call start() first!")
|
raise APIConnectionError("You need to call start() first!")
|
||||||
|
|
||||||
if self._connected:
|
if self._connected:
|
||||||
raise APIConnectionError("Already connected!")
|
self.disconnect(on_disconnect=False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ip = resolve_ip_address(self._address)
|
ip = resolve_ip_address(self._address)
|
||||||
|
@ -193,8 +192,9 @@ class APIClient(threading.Thread):
|
||||||
try:
|
try:
|
||||||
self._socket.connect((ip, self._port))
|
self._socket.connect((ip, self._port))
|
||||||
except socket.error as err:
|
except socket.error as err:
|
||||||
self._fatal_error()
|
err = APIConnectionError("Error connecting to {}: {}".format(ip, err))
|
||||||
raise APIConnectionError("Error connecting to {}: {}".format(ip, err))
|
self._fatal_error(err)
|
||||||
|
raise err
|
||||||
self._socket.settimeout(0.1)
|
self._socket.settimeout(0.1)
|
||||||
|
|
||||||
self._socket_open_event.set()
|
self._socket_open_event.set()
|
||||||
|
@ -204,18 +204,20 @@ class APIClient(threading.Thread):
|
||||||
try:
|
try:
|
||||||
resp = self._send_message_await_response(hello, pb.HelloResponse)
|
resp = self._send_message_await_response(hello, pb.HelloResponse)
|
||||||
except APIConnectionError as err:
|
except APIConnectionError as err:
|
||||||
self._fatal_error()
|
self._fatal_error(err)
|
||||||
raise err
|
raise err
|
||||||
_LOGGER.debug("Successfully connected to %s ('%s' API=%s.%s)", self._address,
|
_LOGGER.debug("Successfully connected to %s ('%s' API=%s.%s)", self._address,
|
||||||
resp.server_info, resp.api_version_major, resp.api_version_minor)
|
resp.server_info, resp.api_version_major, resp.api_version_minor)
|
||||||
self._connected = True
|
self._connected = True
|
||||||
|
self._refresh_ping()
|
||||||
if self.on_connect is not None:
|
if self.on_connect is not None:
|
||||||
self.on_connect()
|
self.on_connect()
|
||||||
|
|
||||||
def _check_connected(self):
|
def _check_connected(self):
|
||||||
if not self._connected:
|
if not self._connected:
|
||||||
self._fatal_error()
|
err = APIConnectionError("Must be connected!")
|
||||||
raise APIConnectionError("Must be connected!")
|
self._fatal_error(err)
|
||||||
|
raise err
|
||||||
|
|
||||||
def login(self):
|
def login(self):
|
||||||
self._check_connected()
|
self._check_connected()
|
||||||
|
@ -233,25 +235,26 @@ class APIClient(threading.Thread):
|
||||||
if self.on_login is not None:
|
if self.on_login is not None:
|
||||||
self.on_login()
|
self.on_login()
|
||||||
|
|
||||||
def _fatal_error(self):
|
def _fatal_error(self, err):
|
||||||
was_connected = self._connected
|
was_connected = self._connected
|
||||||
|
|
||||||
self._close_socket()
|
self._close_socket()
|
||||||
|
|
||||||
if was_connected and self.on_disconnect is not None:
|
if was_connected and self.on_disconnect is not None:
|
||||||
self.on_disconnect()
|
self.on_disconnect(err)
|
||||||
|
|
||||||
def _write(self, data): # type: (bytes) -> None
|
def _write(self, data): # type: (bytes) -> None
|
||||||
if self._socket is None:
|
if self._socket is None:
|
||||||
raise APIConnectionError("Socket closed")
|
raise APIConnectionError("Socket closed")
|
||||||
|
|
||||||
_LOGGER.debug("Write: %s", format_bytes(data))
|
# _LOGGER.debug("Write: %s", format_bytes(data))
|
||||||
with self._socket_write_lock:
|
with self._socket_write_lock:
|
||||||
try:
|
try:
|
||||||
self._socket.sendall(data)
|
self._socket.sendall(data)
|
||||||
except socket.error as err:
|
except socket.error as err:
|
||||||
self._fatal_error()
|
err = APIConnectionError("Error while writing data: {}".format(err))
|
||||||
raise APIConnectionError("Error while writing data: {}".format(err))
|
self._fatal_error(err)
|
||||||
|
raise err
|
||||||
|
|
||||||
def _send_message(self, msg):
|
def _send_message(self, msg):
|
||||||
# type: (message.Message) -> None
|
# type: (message.Message) -> None
|
||||||
|
@ -271,9 +274,8 @@ class APIClient(threading.Thread):
|
||||||
req += _varuint_to_bytes(message_type)
|
req += _varuint_to_bytes(message_type)
|
||||||
req += encoded
|
req += encoded
|
||||||
self._write(req)
|
self._write(req)
|
||||||
self._refresh_ping()
|
|
||||||
|
|
||||||
def _send_message_await_response_complex(self, send_msg, do_append, do_stop, timeout=1):
|
def _send_message_await_response_complex(self, send_msg, do_append, do_stop, timeout=5):
|
||||||
event = threading.Event()
|
event = threading.Event()
|
||||||
responses = []
|
responses = []
|
||||||
|
|
||||||
|
@ -294,7 +296,7 @@ class APIClient(threading.Thread):
|
||||||
raise APIConnectionError("Timeout while waiting for message response!")
|
raise APIConnectionError("Timeout while waiting for message response!")
|
||||||
return responses
|
return responses
|
||||||
|
|
||||||
def _send_message_await_response(self, send_msg, response_type, timeout=1):
|
def _send_message_await_response(self, send_msg, response_type, timeout=5):
|
||||||
def is_response(msg):
|
def is_response(msg):
|
||||||
return isinstance(msg, response_type)
|
return isinstance(msg, response_type)
|
||||||
|
|
||||||
|
@ -309,7 +311,7 @@ class APIClient(threading.Thread):
|
||||||
self._check_connected()
|
self._check_connected()
|
||||||
return self._send_message_await_response(pb.PingRequest(), pb.PingResponse)
|
return self._send_message_await_response(pb.PingRequest(), pb.PingResponse)
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self, on_disconnect=True):
|
||||||
self._check_connected()
|
self._check_connected()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -318,14 +320,14 @@ class APIClient(threading.Thread):
|
||||||
pass
|
pass
|
||||||
self._close_socket()
|
self._close_socket()
|
||||||
|
|
||||||
if self.on_disconnect is not None:
|
if self.on_disconnect is not None and on_disconnect:
|
||||||
self.on_disconnect()
|
self.on_disconnect(None)
|
||||||
|
|
||||||
def _check_authenticated(self):
|
def _check_authenticated(self):
|
||||||
if not self._authenticated:
|
if not self._authenticated:
|
||||||
raise APIConnectionError("Must login first!")
|
raise APIConnectionError("Must login first!")
|
||||||
|
|
||||||
def subscribe_logs(self, on_log, log_level=None, dump_config=False):
|
def subscribe_logs(self, on_log, log_level=7, dump_config=False):
|
||||||
self._check_authenticated()
|
self._check_authenticated()
|
||||||
|
|
||||||
def on_msg(msg):
|
def on_msg(msg):
|
||||||
|
@ -334,8 +336,7 @@ class APIClient(threading.Thread):
|
||||||
|
|
||||||
self._message_handlers.append(on_msg)
|
self._message_handlers.append(on_msg)
|
||||||
req = pb.SubscribeLogsRequest(dump_config=dump_config)
|
req = pb.SubscribeLogsRequest(dump_config=dump_config)
|
||||||
if log_level is not None:
|
req.level = log_level
|
||||||
req.level = log_level
|
|
||||||
self._send_message(req)
|
self._send_message(req)
|
||||||
|
|
||||||
def _recv(self, amount):
|
def _recv(self, amount):
|
||||||
|
@ -387,7 +388,6 @@ class APIClient(threading.Thread):
|
||||||
for msg_handler in self._message_handlers[:]:
|
for msg_handler in self._message_handlers[:]:
|
||||||
msg_handler(msg)
|
msg_handler(msg)
|
||||||
self._handle_internal_messages(msg)
|
self._handle_internal_messages(msg)
|
||||||
self._refresh_ping()
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self._running_event.set()
|
self._running_event.set()
|
||||||
|
@ -399,7 +399,7 @@ class APIClient(threading.Thread):
|
||||||
break
|
break
|
||||||
if self._connected:
|
if self._connected:
|
||||||
_LOGGER.error("Error while reading incoming messages: %s", err)
|
_LOGGER.error("Error while reading incoming messages: %s", err)
|
||||||
self._fatal_error()
|
self._fatal_error(err)
|
||||||
self._running_event.clear()
|
self._running_event.clear()
|
||||||
|
|
||||||
def _handle_internal_messages(self, msg):
|
def _handle_internal_messages(self, msg):
|
||||||
|
@ -410,7 +410,7 @@ class APIClient(threading.Thread):
|
||||||
self._socket = None
|
self._socket = None
|
||||||
self._connected = False
|
self._connected = False
|
||||||
if self.on_disconnect is not None:
|
if self.on_disconnect is not None:
|
||||||
self.on_disconnect()
|
self.on_disconnect(None)
|
||||||
elif isinstance(msg, pb.PingRequest):
|
elif isinstance(msg, pb.PingRequest):
|
||||||
self._send_message(pb.PingResponse())
|
self._send_message(pb.PingResponse())
|
||||||
elif isinstance(msg, pb.GetTimeRequest):
|
elif isinstance(msg, pb.GetTimeRequest):
|
||||||
|
@ -431,12 +431,12 @@ def run_logs(config, address):
|
||||||
|
|
||||||
has_connects = []
|
has_connects = []
|
||||||
|
|
||||||
def try_connect(tries=0, is_disconnect=True):
|
def try_connect(err, tries=0):
|
||||||
if stopping:
|
if stopping:
|
||||||
return
|
return
|
||||||
|
|
||||||
if is_disconnect:
|
if err:
|
||||||
_LOGGER.warning(u"Disconnected from API.")
|
_LOGGER.warning(u"Disconnected from API: %s", err)
|
||||||
|
|
||||||
while retry_timer:
|
while retry_timer:
|
||||||
retry_timer.pop(0).cancel()
|
retry_timer.pop(0).cancel()
|
||||||
|
@ -445,14 +445,14 @@ def run_logs(config, address):
|
||||||
try:
|
try:
|
||||||
cli.connect()
|
cli.connect()
|
||||||
cli.login()
|
cli.login()
|
||||||
except APIConnectionError as err: # noqa
|
except APIConnectionError as err2: # noqa
|
||||||
error = err
|
error = err2
|
||||||
|
|
||||||
if error is None:
|
if error is None:
|
||||||
_LOGGER.info("Successfully connected to %s", address)
|
_LOGGER.info("Successfully connected to %s", address)
|
||||||
return
|
return
|
||||||
|
|
||||||
wait_time = min(2**tries, 300)
|
wait_time = int(min(1.5**min(tries, 100), 30))
|
||||||
if not has_connects:
|
if not has_connects:
|
||||||
_LOGGER.warning(u"Initial connection failed. The ESP might not be connected "
|
_LOGGER.warning(u"Initial connection failed. The ESP might not be connected "
|
||||||
u"to WiFi yet (%s). Re-Trying in %s seconds",
|
u"to WiFi yet (%s). Re-Trying in %s seconds",
|
||||||
|
@ -460,7 +460,7 @@ def run_logs(config, address):
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning(u"Couldn't connect to API (%s). Trying to reconnect in %s seconds",
|
_LOGGER.warning(u"Couldn't connect to API (%s). Trying to reconnect in %s seconds",
|
||||||
error, wait_time)
|
error, wait_time)
|
||||||
timer = threading.Timer(wait_time, functools.partial(try_connect, tries + 1, is_disconnect))
|
timer = threading.Timer(wait_time, functools.partial(try_connect, None, tries + 1))
|
||||||
timer.start()
|
timer.start()
|
||||||
retry_timer.append(timer)
|
retry_timer.append(timer)
|
||||||
|
|
||||||
|
@ -484,7 +484,7 @@ def run_logs(config, address):
|
||||||
cli.start()
|
cli.start()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
try_connect(is_disconnect=False)
|
try_connect(None)
|
||||||
while True:
|
while True:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|
|
@ -55,7 +55,7 @@ UpdateComponentAction = cg.esphome_ns.class_('UpdateComponentAction', Action)
|
||||||
Automation = cg.esphome_ns.class_('Automation')
|
Automation = cg.esphome_ns.class_('Automation')
|
||||||
|
|
||||||
LambdaCondition = cg.esphome_ns.class_('LambdaCondition', Condition)
|
LambdaCondition = cg.esphome_ns.class_('LambdaCondition', Condition)
|
||||||
ForCondition = cg.esphome_ns.class_('ForCondition', Condition)
|
ForCondition = cg.esphome_ns.class_('ForCondition', Condition, cg.Component)
|
||||||
|
|
||||||
|
|
||||||
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||||
|
|
0
esphome/components/ade7953/__init__.py
Normal file
0
esphome/components/ade7953/__init__.py
Normal file
51
esphome/components/ade7953/ade7953.cpp
Normal file
51
esphome/components/ade7953/ade7953.cpp
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#include "ade7953.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ade7953 {
|
||||||
|
|
||||||
|
static const char *TAG = "ade7953";
|
||||||
|
|
||||||
|
void ADE7953::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "ADE7953:");
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
LOG_SENSOR(" ", "Voltage Sensor", this->voltage_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Current A Sensor", this->current_a_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Current B Sensor", this->current_b_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Active Power A Sensor", this->active_power_a_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Active Power B Sensor", this->active_power_b_sensor_);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ADE_PUBLISH_(name, factor) \
|
||||||
|
if (name) { \
|
||||||
|
float value = *name / factor; \
|
||||||
|
this->name##_sensor_->publish_state(value); \
|
||||||
|
}
|
||||||
|
#define ADE_PUBLISH(name, factor) ADE_PUBLISH_(name, factor)
|
||||||
|
|
||||||
|
void ADE7953::update() {
|
||||||
|
if (!this->is_setup_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto active_power_a = this->ade_read_<int32_t>(0x0312);
|
||||||
|
ADE_PUBLISH(active_power_a, 154.0f);
|
||||||
|
auto active_power_b = this->ade_read_<int32_t>(0x0313);
|
||||||
|
ADE_PUBLISH(active_power_b, 154.0f);
|
||||||
|
auto current_a = this->ade_read_<uint32_t>(0x031A);
|
||||||
|
ADE_PUBLISH(current_a, 100000.0f);
|
||||||
|
auto current_b = this->ade_read_<uint32_t>(0x031B);
|
||||||
|
ADE_PUBLISH(current_b, 100000.0f);
|
||||||
|
auto voltage = this->ade_read_<uint32_t>(0x031C);
|
||||||
|
ADE_PUBLISH(voltage, 26000.0f);
|
||||||
|
|
||||||
|
// auto apparent_power_a = this->ade_read_<int32_t>(0x0310);
|
||||||
|
// auto apparent_power_b = this->ade_read_<int32_t>(0x0311);
|
||||||
|
// auto reactive_power_a = this->ade_read_<int32_t>(0x0314);
|
||||||
|
// auto reactive_power_b = this->ade_read_<int32_t>(0x0315);
|
||||||
|
// auto power_factor_a = this->ade_read_<int16_t>(0x010A);
|
||||||
|
// auto power_factor_b = this->ade_read_<int16_t>(0x010B);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ade7953
|
||||||
|
} // namespace esphome
|
67
esphome/components/ade7953/ade7953.h
Normal file
67
esphome/components/ade7953/ade7953.h
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ade7953 {
|
||||||
|
|
||||||
|
class ADE7953 : public i2c::I2CDevice, public PollingComponent {
|
||||||
|
public:
|
||||||
|
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; }
|
||||||
|
void set_active_power_a_sensor(sensor::Sensor *active_power_a_sensor) {
|
||||||
|
active_power_a_sensor_ = active_power_a_sensor;
|
||||||
|
}
|
||||||
|
void set_active_power_b_sensor(sensor::Sensor *active_power_b_sensor) {
|
||||||
|
active_power_b_sensor_ = active_power_b_sensor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() override {
|
||||||
|
this->set_timeout(100, [this]() {
|
||||||
|
this->ade_write_<uint8_t>(0x0010, 0x04);
|
||||||
|
this->ade_write_<uint8_t>(0x00FE, 0xAD);
|
||||||
|
this->ade_write_<uint16_t>(0x0120, 0x0030);
|
||||||
|
this->is_setup_ = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template<typename T> bool ade_write_(uint16_t reg, T value) {
|
||||||
|
std::vector<uint8_t> 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);
|
||||||
|
}
|
||||||
|
template<typename T> optional<T> 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<sizeof(T)>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_setup_{false};
|
||||||
|
sensor::Sensor *voltage_sensor_{nullptr};
|
||||||
|
sensor::Sensor *current_a_sensor_{nullptr};
|
||||||
|
sensor::Sensor *current_b_sensor_{nullptr};
|
||||||
|
sensor::Sensor *active_power_a_sensor_{nullptr};
|
||||||
|
sensor::Sensor *active_power_b_sensor_{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ade7953
|
||||||
|
} // namespace esphome
|
39
esphome/components/ade7953/sensor.py
Normal file
39
esphome/components/ade7953/sensor.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor, i2c
|
||||||
|
from esphome.const import CONF_ID, CONF_VOLTAGE, \
|
||||||
|
UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT
|
||||||
|
|
||||||
|
DEPENDENCIES = ['i2c']
|
||||||
|
|
||||||
|
ace7953_ns = cg.esphome_ns.namespace('ade7953')
|
||||||
|
ADE7953 = ace7953_ns.class_('ADE7953', cg.PollingComponent, i2c.I2CDevice)
|
||||||
|
|
||||||
|
CONF_CURRENT_A = 'current_a'
|
||||||
|
CONF_CURRENT_B = 'current_b'
|
||||||
|
CONF_ACTIVE_POWER_A = 'active_power_a'
|
||||||
|
CONF_ACTIVE_POWER_B = 'active_power_b'
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Schema({
|
||||||
|
cv.GenerateID(): cv.declare_id(ADE7953),
|
||||||
|
|
||||||
|
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1),
|
||||||
|
cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
||||||
|
cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
||||||
|
cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1),
|
||||||
|
cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1),
|
||||||
|
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38))
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
yield i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
|
for key in [CONF_VOLTAGE, CONF_CURRENT_A, CONF_CURRENT_B, CONF_ACTIVE_POWER_A,
|
||||||
|
CONF_ACTIVE_POWER_B]:
|
||||||
|
if key not in config:
|
||||||
|
continue
|
||||||
|
conf = config[key]
|
||||||
|
sens = yield sensor.new_sensor(conf)
|
||||||
|
cg.add(getattr(var, 'set_{}_sensor'.format(key))(sens))
|
|
@ -10,8 +10,10 @@ MULTI_CONF = True
|
||||||
ads1115_ns = cg.esphome_ns.namespace('ads1115')
|
ads1115_ns = cg.esphome_ns.namespace('ads1115')
|
||||||
ADS1115Component = ads1115_ns.class_('ADS1115Component', cg.Component, i2c.I2CDevice)
|
ADS1115Component = ads1115_ns.class_('ADS1115Component', cg.Component, i2c.I2CDevice)
|
||||||
|
|
||||||
|
CONF_CONTINUOUS_MODE = 'continuous_mode'
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = cv.Schema({
|
||||||
cv.GenerateID(): cv.declare_id(ADS1115Component),
|
cv.GenerateID(): cv.declare_id(ADS1115Component),
|
||||||
|
cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean,
|
||||||
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(None))
|
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(None))
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,3 +21,5 @@ def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
yield cg.register_component(var, config)
|
||||||
yield i2c.register_i2c_device(var, config)
|
yield i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
|
cg.add(var.set_continuous_mode(config[CONF_CONTINUOUS_MODE]))
|
||||||
|
|
|
@ -29,9 +29,15 @@ void ADS1115Component::setup() {
|
||||||
// 0bxxxx000xxxxxxxxx
|
// 0bxxxx000xxxxxxxxx
|
||||||
config |= ADS1115_GAIN_6P144 << 9;
|
config |= ADS1115_GAIN_6P144 << 9;
|
||||||
|
|
||||||
// Set singleshot mode
|
if (this->continuous_mode_) {
|
||||||
// 0bxxxxxxx1xxxxxxxx
|
// Set continuous mode
|
||||||
config |= 0b0000000100000000;
|
// 0bxxxxxxx0xxxxxxxx
|
||||||
|
config |= 0b0000000000000000;
|
||||||
|
} else {
|
||||||
|
// Set singleshot mode
|
||||||
|
// 0bxxxxxxx1xxxxxxxx
|
||||||
|
config |= 0b0000000100000000;
|
||||||
|
}
|
||||||
|
|
||||||
// Set data rate - 860 samples per second (we're in singleshot mode)
|
// Set data rate - 860 samples per second (we're in singleshot mode)
|
||||||
// 0bxxxxxxxx100xxxxx
|
// 0bxxxxxxxx100xxxxx
|
||||||
|
@ -57,6 +63,8 @@ void ADS1115Component::setup() {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this->prev_config_ = config;
|
||||||
|
|
||||||
for (auto *sensor : this->sensors_) {
|
for (auto *sensor : this->sensors_) {
|
||||||
this->set_interval(sensor->get_name(), sensor->update_interval(),
|
this->set_interval(sensor->get_name(), sensor->update_interval(),
|
||||||
[this, sensor] { this->request_measurement(sensor); });
|
[this, sensor] { this->request_measurement(sensor); });
|
||||||
|
@ -75,13 +83,8 @@ void ADS1115Component::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, " Gain: %u", sensor->get_gain());
|
ESP_LOGCONFIG(TAG, " Gain: %u", sensor->get_gain());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
float ADS1115Component::get_setup_priority() const { return setup_priority::DATA; }
|
|
||||||
float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
|
float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
|
||||||
uint16_t config;
|
uint16_t config = this->prev_config_;
|
||||||
if (!this->read_byte_16(ADS1115_REGISTER_CONFIG, &config)) {
|
|
||||||
this->status_set_warning();
|
|
||||||
return NAN;
|
|
||||||
}
|
|
||||||
// Multiplexer
|
// Multiplexer
|
||||||
// 0bxBBBxxxxxxxxxxxx
|
// 0bxBBBxxxxxxxxxxxx
|
||||||
config &= 0b1000111111111111;
|
config &= 0b1000111111111111;
|
||||||
|
@ -91,25 +94,31 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
|
||||||
// 0bxxxxBBBxxxxxxxxx
|
// 0bxxxxBBBxxxxxxxxx
|
||||||
config &= 0b1111000111111111;
|
config &= 0b1111000111111111;
|
||||||
config |= (sensor->get_gain() & 0b111) << 9;
|
config |= (sensor->get_gain() & 0b111) << 9;
|
||||||
// Start conversion
|
|
||||||
config |= 0b1000000000000000;
|
|
||||||
|
|
||||||
if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) {
|
if (!this->continuous_mode_) {
|
||||||
this->status_set_warning();
|
// Start conversion
|
||||||
return NAN;
|
config |= 0b1000000000000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// about 1.6 ms with 860 samples per second
|
if (!this->continuous_mode_ || this->prev_config_ != config) {
|
||||||
delay(2);
|
if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) {
|
||||||
|
|
||||||
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();
|
this->status_set_warning();
|
||||||
return NAN;
|
return NAN;
|
||||||
}
|
}
|
||||||
yield();
|
this->prev_config_ = config;
|
||||||
|
|
||||||
|
// about 1.6 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;
|
||||||
|
}
|
||||||
|
yield();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t raw_conversion;
|
uint16_t raw_conversion;
|
||||||
|
@ -144,13 +153,9 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
|
||||||
}
|
}
|
||||||
|
|
||||||
this->status_clear_warning();
|
this->status_clear_warning();
|
||||||
return millivolts / 1e4f;
|
return millivolts / 1e3f;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t ADS1115Sensor::get_multiplexer() const { return this->multiplexer_; }
|
|
||||||
void ADS1115Sensor::set_multiplexer(ADS1115Multiplexer multiplexer) { this->multiplexer_ = multiplexer; }
|
|
||||||
uint8_t ADS1115Sensor::get_gain() const { return this->gain_; }
|
|
||||||
void ADS1115Sensor::set_gain(ADS1115Gain gain) { this->gain_ = gain; }
|
|
||||||
float ADS1115Sensor::sample() { return this->parent_->request_measurement(this); }
|
float ADS1115Sensor::sample() { return this->parent_->request_measurement(this); }
|
||||||
void ADS1115Sensor::update() {
|
void ADS1115Sensor::update() {
|
||||||
float v = this->parent_->request_measurement(this);
|
float v = this->parent_->request_measurement(this);
|
||||||
|
|
|
@ -37,13 +37,16 @@ class ADS1115Component : public Component, public i2c::I2CDevice {
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
/// HARDWARE_LATE setup priority
|
/// HARDWARE_LATE setup priority
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
void set_continuous_mode(bool continuous_mode) { continuous_mode_ = continuous_mode; }
|
||||||
|
|
||||||
/// Helper method to request a measurement from a sensor.
|
/// Helper method to request a measurement from a sensor.
|
||||||
float request_measurement(ADS1115Sensor *sensor);
|
float request_measurement(ADS1115Sensor *sensor);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::vector<ADS1115Sensor *> sensors_;
|
std::vector<ADS1115Sensor *> sensors_;
|
||||||
|
uint16_t prev_config_{0};
|
||||||
|
bool continuous_mode_;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Internal holder class that is in instance of Sensor so that the hub can create individual sensors.
|
/// Internal holder class that is in instance of Sensor so that the hub can create individual sensors.
|
||||||
|
@ -51,12 +54,12 @@ class ADS1115Sensor : public sensor::Sensor, public PollingComponent, public vol
|
||||||
public:
|
public:
|
||||||
ADS1115Sensor(ADS1115Component *parent) : parent_(parent) {}
|
ADS1115Sensor(ADS1115Component *parent) : parent_(parent) {}
|
||||||
void update() override;
|
void update() override;
|
||||||
void set_multiplexer(ADS1115Multiplexer multiplexer);
|
void set_multiplexer(ADS1115Multiplexer multiplexer) { multiplexer_ = multiplexer; }
|
||||||
void set_gain(ADS1115Gain gain);
|
void set_gain(ADS1115Gain gain) { gain_ = gain; }
|
||||||
|
|
||||||
float sample() override;
|
float sample() override;
|
||||||
uint8_t get_multiplexer() const;
|
uint8_t get_multiplexer() const { return multiplexer_; }
|
||||||
uint8_t get_gain() const;
|
uint8_t get_gain() const { return gain_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ADS1115Component *parent_;
|
ADS1115Component *parent_;
|
||||||
|
|
|
@ -3,44 +3,40 @@ import esphome.config_validation as cv
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.automation import Condition
|
from esphome.automation import Condition
|
||||||
from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \
|
from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \
|
||||||
CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID
|
CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID, CONF_EVENT
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
DEPENDENCIES = ['network']
|
DEPENDENCIES = ['network']
|
||||||
|
AUTO_LOAD = ['async_tcp']
|
||||||
|
|
||||||
api_ns = cg.esphome_ns.namespace('api')
|
api_ns = cg.esphome_ns.namespace('api')
|
||||||
APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)
|
APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)
|
||||||
HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', automation.Action)
|
HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', automation.Action)
|
||||||
KeyValuePair = api_ns.class_('KeyValuePair')
|
|
||||||
TemplatableKeyValuePair = api_ns.class_('TemplatableKeyValuePair')
|
|
||||||
APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition)
|
APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition)
|
||||||
|
|
||||||
UserService = api_ns.class_('UserService', automation.Trigger)
|
UserServiceTrigger = api_ns.class_('UserServiceTrigger', automation.Trigger)
|
||||||
ServiceTypeArgument = api_ns.class_('ServiceTypeArgument')
|
ListEntitiesServicesArgument = api_ns.class_('ListEntitiesServicesArgument')
|
||||||
ServiceArgType = api_ns.enum('ServiceArgType')
|
|
||||||
SERVICE_ARG_TYPES = {
|
|
||||||
'bool': ServiceArgType.SERVICE_ARG_TYPE_BOOL,
|
|
||||||
'int': ServiceArgType.SERVICE_ARG_TYPE_INT,
|
|
||||||
'float': ServiceArgType.SERVICE_ARG_TYPE_FLOAT,
|
|
||||||
'string': ServiceArgType.SERVICE_ARG_TYPE_STRING,
|
|
||||||
}
|
|
||||||
SERVICE_ARG_NATIVE_TYPES = {
|
SERVICE_ARG_NATIVE_TYPES = {
|
||||||
'bool': bool,
|
'bool': bool,
|
||||||
'int': cg.int32,
|
'int': cg.int32,
|
||||||
'float': float,
|
'float': float,
|
||||||
'string': cg.std_string,
|
'string': cg.std_string,
|
||||||
|
'bool[]': cg.std_vector.template(bool),
|
||||||
|
'int[]': cg.std_vector.template(cg.int32),
|
||||||
|
'float[]': cg.std_vector.template(float),
|
||||||
|
'string[]': cg.std_vector.template(cg.std_string),
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = cv.Schema({
|
||||||
cv.GenerateID(): cv.declare_id(APIServer),
|
cv.GenerateID(): cv.declare_id(APIServer),
|
||||||
cv.Optional(CONF_PORT, default=6053): cv.port,
|
cv.Optional(CONF_PORT, default=6053): cv.port,
|
||||||
cv.Optional(CONF_PASSWORD, default=''): cv.string_strict,
|
cv.Optional(CONF_PASSWORD, default=''): cv.string_strict,
|
||||||
cv.Optional(CONF_REBOOT_TIMEOUT, default='5min'): cv.positive_time_period_milliseconds,
|
cv.Optional(CONF_REBOOT_TIMEOUT, default='15min'): cv.positive_time_period_milliseconds,
|
||||||
cv.Optional(CONF_SERVICES): automation.validate_automation({
|
cv.Optional(CONF_SERVICES): automation.validate_automation({
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserService),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
|
||||||
cv.Required(CONF_SERVICE): cv.valid_name,
|
cv.Required(CONF_SERVICE): cv.valid_name,
|
||||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema({
|
cv.Optional(CONF_VARIABLES, default={}): cv.Schema({
|
||||||
cv.validate_id_name: cv.one_of(*SERVICE_ARG_TYPES, lower=True),
|
cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}).extend(cv.COMPONENT_SCHEMA)
|
}).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
@ -58,37 +54,30 @@ def to_code(config):
|
||||||
for conf in config.get(CONF_SERVICES, []):
|
for conf in config.get(CONF_SERVICES, []):
|
||||||
template_args = []
|
template_args = []
|
||||||
func_args = []
|
func_args = []
|
||||||
service_type_args = []
|
service_arg_names = []
|
||||||
for name, var_ in conf[CONF_VARIABLES].items():
|
for name, var_ in conf[CONF_VARIABLES].items():
|
||||||
native = SERVICE_ARG_NATIVE_TYPES[var_]
|
native = SERVICE_ARG_NATIVE_TYPES[var_]
|
||||||
template_args.append(native)
|
template_args.append(native)
|
||||||
func_args.append((native, name))
|
func_args.append((native, name))
|
||||||
service_type_args.append(ServiceTypeArgument(name, SERVICE_ARG_TYPES[var_]))
|
service_arg_names.append(name)
|
||||||
templ = cg.TemplateArguments(*template_args)
|
templ = cg.TemplateArguments(*template_args)
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], templ,
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], templ,
|
||||||
conf[CONF_SERVICE], service_type_args)
|
conf[CONF_SERVICE], service_arg_names)
|
||||||
cg.add(var.register_user_service(trigger))
|
cg.add(var.register_user_service(trigger))
|
||||||
yield automation.build_automation(trigger, func_args, conf)
|
yield automation.build_automation(trigger, func_args, conf)
|
||||||
|
|
||||||
cg.add_define('USE_API')
|
cg.add_define('USE_API')
|
||||||
if CORE.is_esp32:
|
cg.add_global(api_ns.using)
|
||||||
cg.add_library('AsyncTCP', '1.0.3')
|
|
||||||
elif CORE.is_esp8266:
|
|
||||||
cg.add_library('ESPAsyncTCP', '1.2.0')
|
|
||||||
|
|
||||||
|
|
||||||
|
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string)})
|
||||||
|
|
||||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({
|
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({
|
||||||
cv.GenerateID(): cv.use_id(APIServer),
|
cv.GenerateID(): cv.use_id(APIServer),
|
||||||
cv.Required(CONF_SERVICE): cv.string,
|
cv.Required(CONF_SERVICE): cv.templatable(cv.string),
|
||||||
cv.Optional(CONF_DATA): cv.Schema({
|
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
||||||
cv.string: cv.string,
|
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
||||||
}),
|
cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA,
|
||||||
cv.Optional(CONF_DATA_TEMPLATE): cv.Schema({
|
|
||||||
cv.string: cv.string,
|
|
||||||
}),
|
|
||||||
cv.Optional(CONF_VARIABLES): cv.Schema({
|
|
||||||
cv.string: cv.returning_lambda,
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -96,20 +85,54 @@ HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({
|
||||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA)
|
HOMEASSISTANT_SERVICE_ACTION_SCHEMA)
|
||||||
def homeassistant_service_to_code(config, action_id, template_arg, args):
|
def homeassistant_service_to_code(config, action_id, template_arg, args):
|
||||||
serv = yield cg.get_variable(config[CONF_ID])
|
serv = yield cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, serv)
|
var = cg.new_Pvariable(action_id, template_arg, serv, False)
|
||||||
cg.add(var.set_service(config[CONF_SERVICE]))
|
templ = yield cg.templatable(config[CONF_SERVICE], args, None)
|
||||||
if CONF_DATA in config:
|
cg.add(var.set_service(templ))
|
||||||
datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA].items()]
|
for key, value in config[CONF_DATA].items():
|
||||||
cg.add(var.set_data(datas))
|
templ = yield cg.templatable(value, args, None)
|
||||||
if CONF_DATA_TEMPLATE in config:
|
cg.add(var.add_data(key, templ))
|
||||||
datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA_TEMPLATE].items()]
|
for key, value in config[CONF_DATA_TEMPLATE].items():
|
||||||
cg.add(var.set_data_template(datas))
|
templ = yield cg.templatable(value, args, None)
|
||||||
if CONF_VARIABLES in config:
|
cg.add(var.add_data_template(key, templ))
|
||||||
datas = []
|
for key, value in config[CONF_VARIABLES].items():
|
||||||
for key, value in config[CONF_VARIABLES].items():
|
templ = yield cg.templatable(value, args, None)
|
||||||
value_ = yield cg.process_lambda(value, [])
|
cg.add(var.add_variable(key, templ))
|
||||||
datas.append(TemplatableKeyValuePair(key, value_))
|
yield var
|
||||||
cg.add(var.set_variables(datas))
|
|
||||||
|
|
||||||
|
def validate_homeassistant_event(value):
|
||||||
|
value = cv.string(value)
|
||||||
|
if not value.startswith(u'esphome.'):
|
||||||
|
raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with "
|
||||||
|
"esphome. For example 'esphome.xyz'")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema({
|
||||||
|
cv.GenerateID(): cv.use_id(APIServer),
|
||||||
|
cv.Required(CONF_EVENT): validate_homeassistant_event,
|
||||||
|
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
||||||
|
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
||||||
|
cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action('homeassistant.event', HomeAssistantServiceCallAction,
|
||||||
|
HOMEASSISTANT_EVENT_ACTION_SCHEMA)
|
||||||
|
def homeassistant_event_to_code(config, action_id, template_arg, args):
|
||||||
|
serv = yield cg.get_variable(config[CONF_ID])
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
||||||
|
templ = yield cg.templatable(config[CONF_EVENT], args, None)
|
||||||
|
cg.add(var.set_service(templ))
|
||||||
|
for key, value in config[CONF_DATA].items():
|
||||||
|
templ = yield cg.templatable(value, args, None)
|
||||||
|
cg.add(var.add_data(key, templ))
|
||||||
|
for key, value in config[CONF_DATA_TEMPLATE].items():
|
||||||
|
templ = yield cg.templatable(value, args, None)
|
||||||
|
cg.add(var.add_data_template(key, templ))
|
||||||
|
for key, value in config[CONF_VARIABLES].items():
|
||||||
|
templ = yield cg.templatable(value, args, None)
|
||||||
|
cg.add(var.add_variable(key, templ))
|
||||||
yield var
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,45 @@
|
||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
|
import "api_options.proto";
|
||||||
|
|
||||||
|
service APIConnection {
|
||||||
|
rpc hello (HelloRequest) returns (HelloResponse) {
|
||||||
|
option (needs_setup_connection) = false;
|
||||||
|
option (needs_authentication) = false;
|
||||||
|
}
|
||||||
|
rpc connect (ConnectRequest) returns (ConnectResponse) {
|
||||||
|
option (needs_setup_connection) = false;
|
||||||
|
option (needs_authentication) = false;
|
||||||
|
}
|
||||||
|
rpc disconnect (DisconnectRequest) returns (DisconnectResponse) {
|
||||||
|
option (needs_setup_connection) = false;
|
||||||
|
option (needs_authentication) = false;
|
||||||
|
}
|
||||||
|
rpc ping (PingRequest) returns (PingResponse) {
|
||||||
|
option (needs_setup_connection) = false;
|
||||||
|
option (needs_authentication) = false;
|
||||||
|
}
|
||||||
|
rpc device_info (DeviceInfoRequest) returns (DeviceInfoResponse) {
|
||||||
|
option (needs_authentication) = false;
|
||||||
|
}
|
||||||
|
rpc list_entities (ListEntitiesRequest) returns (void) {}
|
||||||
|
rpc subscribe_states (SubscribeStatesRequest) returns (void) {}
|
||||||
|
rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
|
||||||
|
rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {}
|
||||||
|
rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {}
|
||||||
|
rpc get_time (GetTimeRequest) returns (GetTimeResponse) {
|
||||||
|
option (needs_authentication) = false;
|
||||||
|
}
|
||||||
|
rpc execute_service (ExecuteServiceRequest) returns (void) {}
|
||||||
|
|
||||||
|
rpc cover_command (CoverCommandRequest) returns (void) {}
|
||||||
|
rpc fan_command (FanCommandRequest) returns (void) {}
|
||||||
|
rpc light_command (LightCommandRequest) returns (void) {}
|
||||||
|
rpc switch_command (SwitchCommandRequest) returns (void) {}
|
||||||
|
rpc camera_image (CameraImageRequest) returns (void) {}
|
||||||
|
rpc climate_command (ClimateCommandRequest) returns (void) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ==================== BASE PACKETS ====================
|
// ==================== BASE PACKETS ====================
|
||||||
|
|
||||||
|
@ -21,8 +61,11 @@ syntax = "proto3";
|
||||||
|
|
||||||
// Message sent at the beginning of each connection
|
// Message sent at the beginning of each connection
|
||||||
// Can only be sent by the client and only at the beginning of the connection
|
// Can only be sent by the client and only at the beginning of the connection
|
||||||
// ID: 1
|
|
||||||
message HelloRequest {
|
message HelloRequest {
|
||||||
|
option (id) = 1;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
// Description of client (like User Agent)
|
// Description of client (like User Agent)
|
||||||
// For example "Home Assistant"
|
// For example "Home Assistant"
|
||||||
// Not strictly necessary to send but nice for debugging
|
// Not strictly necessary to send but nice for debugging
|
||||||
|
@ -32,8 +75,11 @@ message HelloRequest {
|
||||||
|
|
||||||
// Confirmation of successful connection request.
|
// Confirmation of successful connection request.
|
||||||
// Can only be sent by the server and only at the beginning of the connection
|
// Can only be sent by the server and only at the beginning of the connection
|
||||||
// ID: 2
|
|
||||||
message HelloResponse {
|
message HelloResponse {
|
||||||
|
option (id) = 2;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
// The version of the API to use. The _client_ (for example Home Assistant) needs to check
|
// The version of the API to use. The _client_ (for example Home Assistant) needs to check
|
||||||
// for compatibility and if necessary adopt to an older API.
|
// for compatibility and if necessary adopt to an older API.
|
||||||
// Major is for breaking changes in the base protocol - a mismatch will lead to immediate disconnect_client_
|
// Major is for breaking changes in the base protocol - a mismatch will lead to immediate disconnect_client_
|
||||||
|
@ -49,49 +95,66 @@ message HelloResponse {
|
||||||
|
|
||||||
// Message sent at the beginning of each connection to authenticate the client
|
// Message sent at the beginning of each connection to authenticate the client
|
||||||
// Can only be sent by the client and only at the beginning of the connection
|
// Can only be sent by the client and only at the beginning of the connection
|
||||||
// ID: 3
|
|
||||||
message ConnectRequest {
|
message ConnectRequest {
|
||||||
|
option (id) = 3;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
// The password to log in with
|
// The password to log in with
|
||||||
string password = 1;
|
string password = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirmation of successful connection. After this the connection is available for all traffic.
|
// Confirmation of successful connection. After this the connection is available for all traffic.
|
||||||
// Can only be sent by the server and only at the beginning of the connection
|
// Can only be sent by the server and only at the beginning of the connection
|
||||||
// ID: 4
|
|
||||||
message ConnectResponse {
|
message ConnectResponse {
|
||||||
|
option (id) = 4;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
bool invalid_password = 1;
|
bool invalid_password = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request to close the connection.
|
// Request to close the connection.
|
||||||
// Can be sent by both the client and server
|
// Can be sent by both the client and server
|
||||||
// ID: 5
|
|
||||||
message DisconnectRequest {
|
message DisconnectRequest {
|
||||||
|
option (id) = 5;
|
||||||
|
option (source) = SOURCE_BOTH;
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
// Do not close the connection before the acknowledgement arrives
|
// Do not close the connection before the acknowledgement arrives
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID: 6
|
|
||||||
message DisconnectResponse {
|
message DisconnectResponse {
|
||||||
|
option (id) = 6;
|
||||||
|
option (source) = SOURCE_BOTH;
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
// Empty - Both parties are required to close the connection after this
|
// Empty - Both parties are required to close the connection after this
|
||||||
// message has been received.
|
// message has been received.
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID: 7
|
|
||||||
message PingRequest {
|
message PingRequest {
|
||||||
|
option (id) = 7;
|
||||||
|
option (source) = SOURCE_BOTH;
|
||||||
// Empty
|
// Empty
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID: 8
|
|
||||||
message PingResponse {
|
message PingResponse {
|
||||||
|
option (id) = 8;
|
||||||
|
option (source) = SOURCE_BOTH;
|
||||||
// Empty
|
// Empty
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID: 9
|
|
||||||
message DeviceInfoRequest {
|
message DeviceInfoRequest {
|
||||||
|
option (id) = 9;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
// Empty
|
// Empty
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID: 10
|
|
||||||
message DeviceInfoResponse {
|
message DeviceInfoResponse {
|
||||||
|
option (id) = 10;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
|
||||||
bool uses_password = 1;
|
bool uses_password = 1;
|
||||||
|
|
||||||
// The name of the node, given by "App.set_name()"
|
// The name of the node, given by "App.set_name()"
|
||||||
|
@ -101,7 +164,7 @@ message DeviceInfoResponse {
|
||||||
string mac_address = 3;
|
string mac_address = 3;
|
||||||
|
|
||||||
// A string describing the ESPHome version. For example "1.10.0"
|
// A string describing the ESPHome version. For example "1.10.0"
|
||||||
string esphome_core_version = 4;
|
string esphome_version = 4;
|
||||||
|
|
||||||
// A string describing the date of compilation, this is generated by the compiler
|
// A string describing the date of compilation, this is generated by the compiler
|
||||||
// and therefore may not be in the same format all the time.
|
// and therefore may not be in the same format all the time.
|
||||||
|
@ -114,22 +177,29 @@ message DeviceInfoResponse {
|
||||||
bool has_deep_sleep = 7;
|
bool has_deep_sleep = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID: 11
|
|
||||||
message ListEntitiesRequest {
|
message ListEntitiesRequest {
|
||||||
|
option (id) = 11;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
// Empty
|
// Empty
|
||||||
}
|
}
|
||||||
// ID: 19
|
|
||||||
message ListEntitiesDoneResponse {
|
message ListEntitiesDoneResponse {
|
||||||
|
option (id) = 19;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (no_delay) = true;
|
||||||
// Empty
|
// Empty
|
||||||
}
|
}
|
||||||
// ID: 20
|
|
||||||
message SubscribeStatesRequest {
|
message SubscribeStatesRequest {
|
||||||
|
option (id) = 20;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
// Empty
|
// Empty
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== BINARY SENSOR ====================
|
// ==================== BINARY SENSOR ====================
|
||||||
// ID: 12
|
|
||||||
message ListEntitiesBinarySensorResponse {
|
message ListEntitiesBinarySensorResponse {
|
||||||
|
option (id) = 12;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_BINARY_SENSOR";
|
||||||
|
|
||||||
string object_id = 1;
|
string object_id = 1;
|
||||||
fixed32 key = 2;
|
fixed32 key = 2;
|
||||||
string name = 3;
|
string name = 3;
|
||||||
|
@ -138,15 +208,22 @@ message ListEntitiesBinarySensorResponse {
|
||||||
string device_class = 5;
|
string device_class = 5;
|
||||||
bool is_status_binary_sensor = 6;
|
bool is_status_binary_sensor = 6;
|
||||||
}
|
}
|
||||||
// ID: 21
|
|
||||||
message BinarySensorStateResponse {
|
message BinarySensorStateResponse {
|
||||||
|
option (id) = 21;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_BINARY_SENSOR";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool state = 2;
|
bool state = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== COVER ====================
|
// ==================== COVER ====================
|
||||||
// ID: 13
|
|
||||||
message ListEntitiesCoverResponse {
|
message ListEntitiesCoverResponse {
|
||||||
|
option (id) = 13;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_COVER";
|
||||||
|
|
||||||
string object_id = 1;
|
string object_id = 1;
|
||||||
fixed32 key = 2;
|
fixed32 key = 2;
|
||||||
string name = 3;
|
string name = 3;
|
||||||
|
@ -157,38 +234,47 @@ message ListEntitiesCoverResponse {
|
||||||
bool supports_tilt = 7;
|
bool supports_tilt = 7;
|
||||||
string device_class = 8;
|
string device_class = 8;
|
||||||
}
|
}
|
||||||
// ID: 22
|
|
||||||
message CoverStateResponse {
|
|
||||||
fixed32 key = 1;
|
|
||||||
|
|
||||||
|
enum LegacyCoverState {
|
||||||
|
LEGACY_COVER_STATE_OPEN = 0;
|
||||||
|
LEGACY_COVER_STATE_CLOSED = 1;
|
||||||
|
}
|
||||||
|
enum CoverOperation {
|
||||||
|
COVER_OPERATION_IDLE = 0;
|
||||||
|
COVER_OPERATION_IS_OPENING = 1;
|
||||||
|
COVER_OPERATION_IS_CLOSING = 2;
|
||||||
|
}
|
||||||
|
message CoverStateResponse {
|
||||||
|
option (id) = 22;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_COVER";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
|
fixed32 key = 1;
|
||||||
// legacy: state has been removed in 1.13
|
// legacy: state has been removed in 1.13
|
||||||
// clients/servers must still send/accept it until the next protocol change
|
// clients/servers must still send/accept it until the next protocol change
|
||||||
enum LegacyCoverState {
|
|
||||||
OPEN = 0;
|
|
||||||
CLOSED = 1;
|
|
||||||
}
|
|
||||||
LegacyCoverState legacy_state = 2;
|
LegacyCoverState legacy_state = 2;
|
||||||
|
|
||||||
float position = 3;
|
float position = 3;
|
||||||
float tilt = 4;
|
float tilt = 4;
|
||||||
enum CoverOperation {
|
|
||||||
IDLE = 0;
|
|
||||||
IS_OPENING = 1;
|
|
||||||
IS_CLOSING = 2;
|
|
||||||
}
|
|
||||||
CoverOperation current_operation = 5;
|
CoverOperation current_operation = 5;
|
||||||
}
|
}
|
||||||
// ID: 30
|
|
||||||
|
enum LegacyCoverCommand {
|
||||||
|
LEGACY_COVER_COMMAND_OPEN = 0;
|
||||||
|
LEGACY_COVER_COMMAND_CLOSE = 1;
|
||||||
|
LEGACY_COVER_COMMAND_STOP = 2;
|
||||||
|
}
|
||||||
message CoverCommandRequest {
|
message CoverCommandRequest {
|
||||||
|
option (id) = 30;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (ifdef) = "USE_COVER";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
|
|
||||||
// legacy: command has been removed in 1.13
|
// legacy: command has been removed in 1.13
|
||||||
// clients/servers must still send/accept it until the next protocol change
|
// clients/servers must still send/accept it until the next protocol change
|
||||||
enum LegacyCoverCommand {
|
|
||||||
OPEN = 0;
|
|
||||||
CLOSE = 1;
|
|
||||||
STOP = 2;
|
|
||||||
}
|
|
||||||
bool has_legacy_command = 2;
|
bool has_legacy_command = 2;
|
||||||
LegacyCoverCommand legacy_command = 3;
|
LegacyCoverCommand legacy_command = 3;
|
||||||
|
|
||||||
|
@ -200,8 +286,11 @@ message CoverCommandRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== FAN ====================
|
// ==================== FAN ====================
|
||||||
// ID: 14
|
|
||||||
message ListEntitiesFanResponse {
|
message ListEntitiesFanResponse {
|
||||||
|
option (id) = 14;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_FAN";
|
||||||
|
|
||||||
string object_id = 1;
|
string object_id = 1;
|
||||||
fixed32 key = 2;
|
fixed32 key = 2;
|
||||||
string name = 3;
|
string name = 3;
|
||||||
|
@ -211,19 +300,27 @@ message ListEntitiesFanResponse {
|
||||||
bool supports_speed = 6;
|
bool supports_speed = 6;
|
||||||
}
|
}
|
||||||
enum FanSpeed {
|
enum FanSpeed {
|
||||||
LOW = 0;
|
FAN_SPEED_LOW = 0;
|
||||||
MEDIUM = 1;
|
FAN_SPEED_MEDIUM = 1;
|
||||||
HIGH = 2;
|
FAN_SPEED_HIGH = 2;
|
||||||
}
|
}
|
||||||
// ID: 23
|
|
||||||
message FanStateResponse {
|
message FanStateResponse {
|
||||||
|
option (id) = 23;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_FAN";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool state = 2;
|
bool state = 2;
|
||||||
bool oscillating = 3;
|
bool oscillating = 3;
|
||||||
FanSpeed speed = 4;
|
FanSpeed speed = 4;
|
||||||
}
|
}
|
||||||
// ID: 31
|
|
||||||
message FanCommandRequest {
|
message FanCommandRequest {
|
||||||
|
option (id) = 31;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (ifdef) = "USE_FAN";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool has_state = 2;
|
bool has_state = 2;
|
||||||
bool state = 3;
|
bool state = 3;
|
||||||
|
@ -234,8 +331,11 @@ message FanCommandRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== LIGHT ====================
|
// ==================== LIGHT ====================
|
||||||
// ID: 15
|
|
||||||
message ListEntitiesLightResponse {
|
message ListEntitiesLightResponse {
|
||||||
|
option (id) = 15;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_LIGHT";
|
||||||
|
|
||||||
string object_id = 1;
|
string object_id = 1;
|
||||||
fixed32 key = 2;
|
fixed32 key = 2;
|
||||||
string name = 3;
|
string name = 3;
|
||||||
|
@ -249,8 +349,12 @@ message ListEntitiesLightResponse {
|
||||||
float max_mireds = 10;
|
float max_mireds = 10;
|
||||||
repeated string effects = 11;
|
repeated string effects = 11;
|
||||||
}
|
}
|
||||||
// ID: 24
|
|
||||||
message LightStateResponse {
|
message LightStateResponse {
|
||||||
|
option (id) = 24;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_LIGHT";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool state = 2;
|
bool state = 2;
|
||||||
float brightness = 3;
|
float brightness = 3;
|
||||||
|
@ -261,8 +365,12 @@ message LightStateResponse {
|
||||||
float color_temperature = 8;
|
float color_temperature = 8;
|
||||||
string effect = 9;
|
string effect = 9;
|
||||||
}
|
}
|
||||||
// ID: 32
|
|
||||||
message LightCommandRequest {
|
message LightCommandRequest {
|
||||||
|
option (id) = 32;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (ifdef) = "USE_LIGHT";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool has_state = 2;
|
bool has_state = 2;
|
||||||
bool state = 3;
|
bool state = 3;
|
||||||
|
@ -285,8 +393,11 @@ message LightCommandRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== SENSOR ====================
|
// ==================== SENSOR ====================
|
||||||
// ID: 16
|
|
||||||
message ListEntitiesSensorResponse {
|
message ListEntitiesSensorResponse {
|
||||||
|
option (id) = 16;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_SENSOR";
|
||||||
|
|
||||||
string object_id = 1;
|
string object_id = 1;
|
||||||
fixed32 key = 2;
|
fixed32 key = 2;
|
||||||
string name = 3;
|
string name = 3;
|
||||||
|
@ -296,15 +407,22 @@ message ListEntitiesSensorResponse {
|
||||||
string unit_of_measurement = 6;
|
string unit_of_measurement = 6;
|
||||||
int32 accuracy_decimals = 7;
|
int32 accuracy_decimals = 7;
|
||||||
}
|
}
|
||||||
// ID: 25
|
|
||||||
message SensorStateResponse {
|
message SensorStateResponse {
|
||||||
|
option (id) = 25;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_SENSOR";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
float state = 2;
|
float state = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== SWITCH ====================
|
// ==================== SWITCH ====================
|
||||||
// ID: 17
|
|
||||||
message ListEntitiesSwitchResponse {
|
message ListEntitiesSwitchResponse {
|
||||||
|
option (id) = 17;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_SWITCH";
|
||||||
|
|
||||||
string object_id = 1;
|
string object_id = 1;
|
||||||
fixed32 key = 2;
|
fixed32 key = 2;
|
||||||
string name = 3;
|
string name = 3;
|
||||||
|
@ -313,20 +431,31 @@ message ListEntitiesSwitchResponse {
|
||||||
string icon = 5;
|
string icon = 5;
|
||||||
bool assumed_state = 6;
|
bool assumed_state = 6;
|
||||||
}
|
}
|
||||||
// ID: 26
|
|
||||||
message SwitchStateResponse {
|
message SwitchStateResponse {
|
||||||
|
option (id) = 26;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_SWITCH";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool state = 2;
|
bool state = 2;
|
||||||
}
|
}
|
||||||
// ID: 33
|
|
||||||
message SwitchCommandRequest {
|
message SwitchCommandRequest {
|
||||||
|
option (id) = 33;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (ifdef) = "USE_SWITCH";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool state = 2;
|
bool state = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== TEXT SENSOR ====================
|
// ==================== TEXT SENSOR ====================
|
||||||
// ID: 18
|
|
||||||
message ListEntitiesTextSensorResponse {
|
message ListEntitiesTextSensorResponse {
|
||||||
|
option (id) = 18;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_TEXT_SENSOR";
|
||||||
|
|
||||||
string object_id = 1;
|
string object_id = 1;
|
||||||
fixed32 key = 2;
|
fixed32 key = 2;
|
||||||
string name = 3;
|
string name = 3;
|
||||||
|
@ -334,29 +463,38 @@ message ListEntitiesTextSensorResponse {
|
||||||
|
|
||||||
string icon = 5;
|
string icon = 5;
|
||||||
}
|
}
|
||||||
// ID: 27
|
|
||||||
message TextSensorStateResponse {
|
message TextSensorStateResponse {
|
||||||
|
option (id) = 27;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_TEXT_SENSOR";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
string state = 2;
|
string state = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== SUBSCRIBE LOGS ====================
|
// ==================== SUBSCRIBE LOGS ====================
|
||||||
enum LogLevel {
|
enum LogLevel {
|
||||||
NONE = 0;
|
LOG_LEVEL_NONE = 0;
|
||||||
ERROR = 1;
|
LOG_LEVEL_ERROR = 1;
|
||||||
WARN = 2;
|
LOG_LEVEL_WARN = 2;
|
||||||
INFO = 3;
|
LOG_LEVEL_INFO = 3;
|
||||||
DEBUG = 4;
|
LOG_LEVEL_DEBUG = 4;
|
||||||
VERBOSE = 5;
|
LOG_LEVEL_VERBOSE = 5;
|
||||||
VERY_VERBOSE = 6;
|
LOG_LEVEL_VERY_VERBOSE = 6;
|
||||||
}
|
}
|
||||||
// ID: 28
|
|
||||||
message SubscribeLogsRequest {
|
message SubscribeLogsRequest {
|
||||||
|
option (id) = 28;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
LogLevel level = 1;
|
LogLevel level = 1;
|
||||||
bool dump_config = 2;
|
bool dump_config = 2;
|
||||||
}
|
}
|
||||||
// ID: 29
|
|
||||||
message SubscribeLogsResponse {
|
message SubscribeLogsResponse {
|
||||||
|
option (id) = 29;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (log) = false;
|
||||||
|
option (no_delay) = false;
|
||||||
|
|
||||||
LogLevel level = 1;
|
LogLevel level = 1;
|
||||||
string tag = 2;
|
string tag = 2;
|
||||||
string message = 3;
|
string message = 3;
|
||||||
|
@ -364,109 +502,159 @@ message SubscribeLogsResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== HOMEASSISTANT.SERVICE ====================
|
// ==================== HOMEASSISTANT.SERVICE ====================
|
||||||
// ID: 34
|
message SubscribeHomeassistantServicesRequest {
|
||||||
message SubscribeServiceCallsRequest {
|
option (id) = 34;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID: 35
|
message HomeassistantServiceMap {
|
||||||
message ServiceCallResponse {
|
string key = 1;
|
||||||
|
string value = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message HomeassistantServiceResponse {
|
||||||
|
option (id) = 35;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
string service = 1;
|
string service = 1;
|
||||||
map<string, string> data = 2;
|
repeated HomeassistantServiceMap data = 2;
|
||||||
map<string, string> data_template = 3;
|
repeated HomeassistantServiceMap data_template = 3;
|
||||||
map<string, string> variables = 4;
|
repeated HomeassistantServiceMap variables = 4;
|
||||||
|
bool is_event = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== IMPORT HOME ASSISTANT STATES ====================
|
// ==================== IMPORT HOME ASSISTANT STATES ====================
|
||||||
// 1. Client sends SubscribeHomeAssistantStatesRequest
|
// 1. Client sends SubscribeHomeAssistantStatesRequest
|
||||||
// 2. Server responds with zero or more SubscribeHomeAssistantStateResponse (async)
|
// 2. Server responds with zero or more SubscribeHomeAssistantStateResponse (async)
|
||||||
// 3. Client sends HomeAssistantStateResponse for state changes.
|
// 3. Client sends HomeAssistantStateResponse for state changes.
|
||||||
// ID: 38
|
|
||||||
message SubscribeHomeAssistantStatesRequest {
|
message SubscribeHomeAssistantStatesRequest {
|
||||||
|
option (id) = 38;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID: 39
|
|
||||||
message SubscribeHomeAssistantStateResponse {
|
message SubscribeHomeAssistantStateResponse {
|
||||||
|
option (id) = 39;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
string entity_id = 1;
|
string entity_id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID: 40
|
|
||||||
message HomeAssistantStateResponse {
|
message HomeAssistantStateResponse {
|
||||||
|
option (id) = 40;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
string entity_id = 1;
|
string entity_id = 1;
|
||||||
string state = 2;
|
string state = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== IMPORT TIME ====================
|
// ==================== IMPORT TIME ====================
|
||||||
// ID: 36
|
|
||||||
message GetTimeRequest {
|
message GetTimeRequest {
|
||||||
|
option (id) = 36;
|
||||||
|
option (source) = SOURCE_BOTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID: 37
|
|
||||||
message GetTimeResponse {
|
message GetTimeResponse {
|
||||||
|
option (id) = 37;
|
||||||
|
option (source) = SOURCE_BOTH;
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
fixed32 epoch_seconds = 1;
|
fixed32 epoch_seconds = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== USER-DEFINES SERVICES ====================
|
// ==================== USER-DEFINES SERVICES ====================
|
||||||
|
enum ServiceArgType {
|
||||||
|
SERVICE_ARG_TYPE_BOOL = 0;
|
||||||
|
SERVICE_ARG_TYPE_INT = 1;
|
||||||
|
SERVICE_ARG_TYPE_FLOAT = 2;
|
||||||
|
SERVICE_ARG_TYPE_STRING = 3;
|
||||||
|
SERVICE_ARG_TYPE_BOOL_ARRAY = 4;
|
||||||
|
SERVICE_ARG_TYPE_INT_ARRAY = 5;
|
||||||
|
SERVICE_ARG_TYPE_FLOAT_ARRAY = 6;
|
||||||
|
SERVICE_ARG_TYPE_STRING_ARRAY = 7;
|
||||||
|
}
|
||||||
message ListEntitiesServicesArgument {
|
message ListEntitiesServicesArgument {
|
||||||
string name = 1;
|
string name = 1;
|
||||||
enum Type {
|
ServiceArgType type = 2;
|
||||||
BOOL = 0;
|
|
||||||
INT = 1;
|
|
||||||
FLOAT = 2;
|
|
||||||
STRING = 3;
|
|
||||||
}
|
|
||||||
Type type = 2;
|
|
||||||
}
|
}
|
||||||
// ID: 41
|
|
||||||
message ListEntitiesServicesResponse {
|
message ListEntitiesServicesResponse {
|
||||||
|
option (id) = 41;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
|
||||||
string name = 1;
|
string name = 1;
|
||||||
fixed32 key = 2;
|
fixed32 key = 2;
|
||||||
repeated ListEntitiesServicesArgument args = 3;
|
repeated ListEntitiesServicesArgument args = 3;
|
||||||
}
|
}
|
||||||
message ExecuteServiceArgument {
|
message ExecuteServiceArgument {
|
||||||
bool bool_ = 1;
|
bool bool_ = 1;
|
||||||
int32 int_ = 2;
|
int32 legacy_int = 2;
|
||||||
float float_ = 3;
|
float float_ = 3;
|
||||||
string string_ = 4;
|
string string_ = 4;
|
||||||
|
// ESPHome 1.14 (api v1.3) make int a signed value
|
||||||
|
sint32 int_ = 5;
|
||||||
|
repeated bool bool_array = 6 [packed=false];
|
||||||
|
repeated sint32 int_array = 7 [packed=false];
|
||||||
|
repeated float float_array = 8 [packed=false];
|
||||||
|
repeated string string_array = 9;
|
||||||
}
|
}
|
||||||
// ID: 42
|
|
||||||
message ExecuteServiceRequest {
|
message ExecuteServiceRequest {
|
||||||
|
option (id) = 42;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
repeated ExecuteServiceArgument args = 2;
|
repeated ExecuteServiceArgument args = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== CAMERA ====================
|
// ==================== CAMERA ====================
|
||||||
// ID: 43
|
|
||||||
message ListEntitiesCameraResponse {
|
message ListEntitiesCameraResponse {
|
||||||
|
option (id) = 43;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_ESP32_CAMERA";
|
||||||
|
|
||||||
string object_id = 1;
|
string object_id = 1;
|
||||||
fixed32 key = 2;
|
fixed32 key = 2;
|
||||||
string name = 3;
|
string name = 3;
|
||||||
string unique_id = 4;
|
string unique_id = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID: 44
|
|
||||||
message CameraImageResponse {
|
message CameraImageResponse {
|
||||||
|
option (id) = 44;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_ESP32_CAMERA";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bytes data = 2;
|
bytes data = 2;
|
||||||
bool done = 3;
|
bool done = 3;
|
||||||
}
|
}
|
||||||
// ID: 45
|
|
||||||
message CameraImageRequest {
|
message CameraImageRequest {
|
||||||
|
option (id) = 45;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (ifdef) = "USE_ESP32_CAMERA";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
bool single = 1;
|
bool single = 1;
|
||||||
bool stream = 2;
|
bool stream = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== CLIMATE ====================
|
// ==================== CLIMATE ====================
|
||||||
enum ClimateMode {
|
enum ClimateMode {
|
||||||
OFF = 0;
|
CLIMATE_MODE_OFF = 0;
|
||||||
AUTO = 1;
|
CLIMATE_MODE_AUTO = 1;
|
||||||
COOL = 2;
|
CLIMATE_MODE_COOL = 2;
|
||||||
HEAT = 3;
|
CLIMATE_MODE_HEAT = 3;
|
||||||
|
}
|
||||||
|
enum ClimateAction {
|
||||||
|
CLIMATE_ACTION_OFF = 0;
|
||||||
|
// values same as mode for readability
|
||||||
|
CLIMATE_ACTION_COOLING = 2;
|
||||||
|
CLIMATE_ACTION_HEATING = 3;
|
||||||
}
|
}
|
||||||
// ID: 46
|
|
||||||
message ListEntitiesClimateResponse {
|
message ListEntitiesClimateResponse {
|
||||||
|
option (id) = 46;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_CLIMATE";
|
||||||
|
|
||||||
string object_id = 1;
|
string object_id = 1;
|
||||||
fixed32 key = 2;
|
fixed32 key = 2;
|
||||||
string name = 3;
|
string name = 3;
|
||||||
|
@ -479,9 +667,14 @@ message ListEntitiesClimateResponse {
|
||||||
float visual_max_temperature = 9;
|
float visual_max_temperature = 9;
|
||||||
float visual_temperature_step = 10;
|
float visual_temperature_step = 10;
|
||||||
bool supports_away = 11;
|
bool supports_away = 11;
|
||||||
|
bool supports_action = 12;
|
||||||
}
|
}
|
||||||
// ID: 47
|
|
||||||
message ClimateStateResponse {
|
message ClimateStateResponse {
|
||||||
|
option (id) = 47;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_CLIMATE";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
ClimateMode mode = 2;
|
ClimateMode mode = 2;
|
||||||
float current_temperature = 3;
|
float current_temperature = 3;
|
||||||
|
@ -489,9 +682,14 @@ message ClimateStateResponse {
|
||||||
float target_temperature_low = 5;
|
float target_temperature_low = 5;
|
||||||
float target_temperature_high = 6;
|
float target_temperature_high = 6;
|
||||||
bool away = 7;
|
bool away = 7;
|
||||||
|
ClimateAction action = 8;
|
||||||
}
|
}
|
||||||
// ID: 48
|
|
||||||
message ClimateCommandRequest {
|
message ClimateCommandRequest {
|
||||||
|
option (id) = 48;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (ifdef) = "USE_CLIMATE";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool has_mode = 2;
|
bool has_mode = 2;
|
||||||
ClimateMode mode = 3;
|
ClimateMode mode = 3;
|
||||||
|
|
673
esphome/components/api/api_connection.cpp
Normal file
673
esphome/components/api/api_connection.cpp
Normal file
|
@ -0,0 +1,673 @@
|
||||||
|
#include "api_connection.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/util.h"
|
||||||
|
#include "esphome/core/version.h"
|
||||||
|
|
||||||
|
#ifdef USE_DEEP_SLEEP
|
||||||
|
#include "esphome/components/deep_sleep/deep_sleep_component.h"
|
||||||
|
#endif
|
||||||
|
#ifdef USE_HOMEASSISTANT_TIME
|
||||||
|
#include "esphome/components/homeassistant/time/homeassistant_time.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace api {
|
||||||
|
|
||||||
|
static const char *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<uint8_t *>(buf), len); },
|
||||||
|
this);
|
||||||
|
|
||||||
|
this->send_buffer_.reserve(64);
|
||||||
|
this->recv_buffer_.reserve(32);
|
||||||
|
this->client_info_ = this->client_->remoteIP().toString().c_str();
|
||||||
|
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)
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void APIConnection::disconnect_client() {
|
||||||
|
this->client_->close();
|
||||||
|
this->remove_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->parse_recv_buffer_();
|
||||||
|
|
||||||
|
this->list_entities_iterator_.advance();
|
||||||
|
this->initial_state_iterator_.advance();
|
||||||
|
|
||||||
|
const uint32_t keepalive = 60000;
|
||||||
|
if (this->sent_ping_) {
|
||||||
|
// Disconnect if not responded within 2.5*keepalive
|
||||||
|
if (millis() - this->last_traffic_ > (keepalive * 5) / 2) {
|
||||||
|
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) {
|
||||||
|
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);
|
||||||
|
this->set_nodelay(false);
|
||||||
|
bool success = this->send_buffer(buffer, 44);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
this->image_reader_.consume_data(to_send);
|
||||||
|
}
|
||||||
|
if (success && done) {
|
||||||
|
this->image_reader_.return_image();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) {
|
||||||
|
return App.get_name() + component_type + nameable->get_object_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_BINARY_SENSOR
|
||||||
|
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state) {
|
||||||
|
if (!this->state_subscription_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
BinarySensorStateResponse resp;
|
||||||
|
resp.key = binary_sensor->get_object_id_hash();
|
||||||
|
resp.state = state;
|
||||||
|
return this->send_binary_sensor_state_response(resp);
|
||||||
|
}
|
||||||
|
bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor) {
|
||||||
|
ListEntitiesBinarySensorResponse msg;
|
||||||
|
msg.object_id = binary_sensor->get_object_id();
|
||||||
|
msg.key = binary_sensor->get_object_id_hash();
|
||||||
|
msg.name = binary_sensor->get_name();
|
||||||
|
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();
|
||||||
|
return this->send_list_entities_binary_sensor_response(msg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_COVER
|
||||||
|
bool APIConnection::send_cover_state(cover::Cover *cover) {
|
||||||
|
if (!this->state_subscription_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto traits = cover->get_traits();
|
||||||
|
CoverStateResponse resp{};
|
||||||
|
resp.key = cover->get_object_id_hash();
|
||||||
|
resp.legacy_state = (cover->position == cover::COVER_OPEN) ? LEGACY_COVER_STATE_OPEN : LEGACY_COVER_STATE_CLOSED;
|
||||||
|
resp.position = cover->position;
|
||||||
|
if (traits.get_supports_tilt())
|
||||||
|
resp.tilt = cover->tilt;
|
||||||
|
resp.current_operation = static_cast<EnumCoverOperation>(cover->current_operation);
|
||||||
|
return this->send_cover_state_response(resp);
|
||||||
|
}
|
||||||
|
bool APIConnection::send_cover_info(cover::Cover *cover) {
|
||||||
|
auto traits = cover->get_traits();
|
||||||
|
ListEntitiesCoverResponse msg;
|
||||||
|
msg.key = cover->get_object_id_hash();
|
||||||
|
msg.object_id = cover->get_object_id();
|
||||||
|
msg.name = cover->get_name();
|
||||||
|
msg.unique_id = get_default_unique_id("cover", cover);
|
||||||
|
msg.assumed_state = traits.get_is_assumed_state();
|
||||||
|
msg.supports_position = traits.get_supports_position();
|
||||||
|
msg.supports_tilt = traits.get_supports_tilt();
|
||||||
|
msg.device_class = cover->get_device_class();
|
||||||
|
return this->send_list_entities_cover_response(msg);
|
||||||
|
}
|
||||||
|
void APIConnection::cover_command(const CoverCommandRequest &msg) {
|
||||||
|
cover::Cover *cover = App.get_cover_by_key(msg.key);
|
||||||
|
if (cover == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto call = cover->make_call();
|
||||||
|
if (msg.has_legacy_command) {
|
||||||
|
switch (msg.legacy_command) {
|
||||||
|
case LEGACY_COVER_COMMAND_OPEN:
|
||||||
|
call.set_command_open();
|
||||||
|
break;
|
||||||
|
case LEGACY_COVER_COMMAND_CLOSE:
|
||||||
|
call.set_command_close();
|
||||||
|
break;
|
||||||
|
case LEGACY_COVER_COMMAND_STOP:
|
||||||
|
call.set_command_stop();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (msg.has_position)
|
||||||
|
call.set_position(msg.position);
|
||||||
|
if (msg.has_tilt)
|
||||||
|
call.set_tilt(msg.tilt);
|
||||||
|
if (msg.stop)
|
||||||
|
call.set_command_stop();
|
||||||
|
call.perform();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_FAN
|
||||||
|
bool APIConnection::send_fan_state(fan::FanState *fan) {
|
||||||
|
if (!this->state_subscription_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto traits = fan->get_traits();
|
||||||
|
FanStateResponse resp{};
|
||||||
|
resp.key = fan->get_object_id_hash();
|
||||||
|
resp.state = fan->state;
|
||||||
|
if (traits.supports_oscillation())
|
||||||
|
resp.oscillating = fan->oscillating;
|
||||||
|
if (traits.supports_speed())
|
||||||
|
resp.speed = static_cast<EnumFanSpeed>(fan->speed);
|
||||||
|
return this->send_fan_state_response(resp);
|
||||||
|
}
|
||||||
|
bool APIConnection::send_fan_info(fan::FanState *fan) {
|
||||||
|
auto traits = fan->get_traits();
|
||||||
|
ListEntitiesFanResponse msg;
|
||||||
|
msg.key = fan->get_object_id_hash();
|
||||||
|
msg.object_id = fan->get_object_id();
|
||||||
|
msg.name = fan->get_name();
|
||||||
|
msg.unique_id = get_default_unique_id("fan", fan);
|
||||||
|
msg.supports_oscillation = traits.supports_oscillation();
|
||||||
|
msg.supports_speed = traits.supports_speed();
|
||||||
|
return this->send_list_entities_fan_response(msg);
|
||||||
|
}
|
||||||
|
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||||
|
fan::FanState *fan = App.get_fan_by_key(msg.key);
|
||||||
|
if (fan == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto call = fan->make_call();
|
||||||
|
if (msg.has_state)
|
||||||
|
call.set_state(msg.state);
|
||||||
|
if (msg.has_oscillating)
|
||||||
|
call.set_oscillating(msg.oscillating);
|
||||||
|
if (msg.has_speed)
|
||||||
|
call.set_speed(static_cast<fan::FanSpeed>(msg.speed));
|
||||||
|
call.perform();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_LIGHT
|
||||||
|
bool APIConnection::send_light_state(light::LightState *light) {
|
||||||
|
if (!this->state_subscription_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto traits = light->get_traits();
|
||||||
|
auto values = light->remote_values;
|
||||||
|
LightStateResponse resp{};
|
||||||
|
|
||||||
|
resp.key = light->get_object_id_hash();
|
||||||
|
resp.state = values.is_on();
|
||||||
|
if (traits.get_supports_brightness())
|
||||||
|
resp.brightness = values.get_brightness();
|
||||||
|
if (traits.get_supports_rgb()) {
|
||||||
|
resp.red = values.get_red();
|
||||||
|
resp.green = values.get_green();
|
||||||
|
resp.blue = values.get_blue();
|
||||||
|
}
|
||||||
|
if (traits.get_supports_rgb_white_value())
|
||||||
|
resp.white = values.get_white();
|
||||||
|
if (traits.get_supports_color_temperature())
|
||||||
|
resp.color_temperature = values.get_color_temperature();
|
||||||
|
if (light->supports_effects())
|
||||||
|
resp.effect = light->get_effect_name();
|
||||||
|
return this->send_light_state_response(resp);
|
||||||
|
}
|
||||||
|
bool APIConnection::send_light_info(light::LightState *light) {
|
||||||
|
auto traits = light->get_traits();
|
||||||
|
ListEntitiesLightResponse msg;
|
||||||
|
msg.key = light->get_object_id_hash();
|
||||||
|
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) {
|
||||||
|
msg.min_mireds = traits.get_min_mireds();
|
||||||
|
msg.max_mireds = traits.get_max_mireds();
|
||||||
|
}
|
||||||
|
if (light->supports_effects()) {
|
||||||
|
msg.effects.emplace_back("None");
|
||||||
|
for (auto *effect : light->get_effects())
|
||||||
|
msg.effects.push_back(effect->get_name());
|
||||||
|
}
|
||||||
|
return this->send_list_entities_light_response(msg);
|
||||||
|
}
|
||||||
|
void APIConnection::light_command(const LightCommandRequest &msg) {
|
||||||
|
light::LightState *light = App.get_light_by_key(msg.key);
|
||||||
|
if (light == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto call = light->make_call();
|
||||||
|
if (msg.has_state)
|
||||||
|
call.set_state(msg.state);
|
||||||
|
if (msg.has_brightness)
|
||||||
|
call.set_brightness(msg.brightness);
|
||||||
|
if (msg.has_rgb) {
|
||||||
|
call.set_red(msg.red);
|
||||||
|
call.set_green(msg.green);
|
||||||
|
call.set_blue(msg.blue);
|
||||||
|
}
|
||||||
|
if (msg.has_white)
|
||||||
|
call.set_white(msg.white);
|
||||||
|
if (msg.has_color_temperature)
|
||||||
|
call.set_color_temperature(msg.color_temperature);
|
||||||
|
if (msg.has_transition_length)
|
||||||
|
call.set_transition_length(msg.transition_length);
|
||||||
|
if (msg.has_flash_length)
|
||||||
|
call.set_flash_length(msg.flash_length);
|
||||||
|
if (msg.has_effect)
|
||||||
|
call.set_effect(msg.effect);
|
||||||
|
call.perform();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_SENSOR
|
||||||
|
bool APIConnection::send_sensor_state(sensor::Sensor *sensor, float state) {
|
||||||
|
if (!this->state_subscription_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SensorStateResponse resp{};
|
||||||
|
resp.key = sensor->get_object_id_hash();
|
||||||
|
resp.state = state;
|
||||||
|
return this->send_sensor_state_response(resp);
|
||||||
|
}
|
||||||
|
bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
|
||||||
|
ListEntitiesSensorResponse msg;
|
||||||
|
msg.key = sensor->get_object_id_hash();
|
||||||
|
msg.object_id = sensor->get_object_id();
|
||||||
|
msg.name = sensor->get_name();
|
||||||
|
msg.unique_id = sensor->unique_id();
|
||||||
|
if (msg.unique_id.empty())
|
||||||
|
msg.unique_id = get_default_unique_id("sensor", sensor);
|
||||||
|
msg.icon = sensor->get_icon();
|
||||||
|
msg.unit_of_measurement = sensor->get_unit_of_measurement();
|
||||||
|
msg.accuracy_decimals = sensor->get_accuracy_decimals();
|
||||||
|
return this->send_list_entities_sensor_response(msg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
bool APIConnection::send_switch_state(switch_::Switch *a_switch, bool state) {
|
||||||
|
if (!this->state_subscription_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SwitchStateResponse resp{};
|
||||||
|
resp.key = a_switch->get_object_id_hash();
|
||||||
|
resp.state = state;
|
||||||
|
return this->send_switch_state_response(resp);
|
||||||
|
}
|
||||||
|
bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
|
||||||
|
ListEntitiesSwitchResponse msg;
|
||||||
|
msg.key = a_switch->get_object_id_hash();
|
||||||
|
msg.object_id = a_switch->get_object_id();
|
||||||
|
msg.name = a_switch->get_name();
|
||||||
|
msg.unique_id = get_default_unique_id("switch", a_switch);
|
||||||
|
msg.icon = a_switch->get_icon();
|
||||||
|
msg.assumed_state = a_switch->assumed_state();
|
||||||
|
return this->send_list_entities_switch_response(msg);
|
||||||
|
}
|
||||||
|
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
|
||||||
|
switch_::Switch *a_switch = App.get_switch_by_key(msg.key);
|
||||||
|
if (a_switch == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (msg.state)
|
||||||
|
a_switch->turn_on();
|
||||||
|
else
|
||||||
|
a_switch->turn_off();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_TEXT_SENSOR
|
||||||
|
bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state) {
|
||||||
|
if (!this->state_subscription_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
TextSensorStateResponse resp{};
|
||||||
|
resp.key = text_sensor->get_object_id_hash();
|
||||||
|
resp.state = std::move(state);
|
||||||
|
return this->send_text_sensor_state_response(resp);
|
||||||
|
}
|
||||||
|
bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) {
|
||||||
|
ListEntitiesTextSensorResponse msg;
|
||||||
|
msg.key = text_sensor->get_object_id_hash();
|
||||||
|
msg.object_id = text_sensor->get_object_id();
|
||||||
|
msg.name = text_sensor->get_name();
|
||||||
|
msg.unique_id = text_sensor->unique_id();
|
||||||
|
if (msg.unique_id.empty())
|
||||||
|
msg.unique_id = get_default_unique_id("text_sensor", text_sensor);
|
||||||
|
msg.icon = text_sensor->get_icon();
|
||||||
|
return this->send_list_entities_text_sensor_response(msg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_CLIMATE
|
||||||
|
bool APIConnection::send_climate_state(climate::Climate *climate) {
|
||||||
|
if (!this->state_subscription_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto traits = climate->get_traits();
|
||||||
|
ClimateStateResponse resp{};
|
||||||
|
resp.key = climate->get_object_id_hash();
|
||||||
|
resp.mode = static_cast<EnumClimateMode>(climate->mode);
|
||||||
|
resp.action = static_cast<EnumClimateAction>(climate->action);
|
||||||
|
if (traits.get_supports_current_temperature())
|
||||||
|
resp.current_temperature = climate->current_temperature;
|
||||||
|
if (traits.get_supports_two_point_target_temperature()) {
|
||||||
|
resp.target_temperature_low = climate->target_temperature_low;
|
||||||
|
resp.target_temperature_high = climate->target_temperature_high;
|
||||||
|
} else {
|
||||||
|
resp.target_temperature = climate->target_temperature;
|
||||||
|
}
|
||||||
|
if (traits.get_supports_away())
|
||||||
|
resp.away = climate->away;
|
||||||
|
return this->send_climate_state_response(resp);
|
||||||
|
}
|
||||||
|
bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||||
|
auto traits = climate->get_traits();
|
||||||
|
ListEntitiesClimateResponse msg;
|
||||||
|
msg.key = climate->get_object_id_hash();
|
||||||
|
msg.object_id = climate->get_object_id();
|
||||||
|
msg.name = climate->get_name();
|
||||||
|
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}) {
|
||||||
|
if (traits.supports_mode(mode))
|
||||||
|
msg.supported_modes.push_back(static_cast<EnumClimateMode>(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.supports_action = traits.get_supports_action();
|
||||||
|
return this->send_list_entities_climate_response(msg);
|
||||||
|
}
|
||||||
|
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
||||||
|
climate::Climate *climate = App.get_climate_by_key(msg.key);
|
||||||
|
if (climate == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto call = climate->make_call();
|
||||||
|
if (msg.has_mode)
|
||||||
|
call.set_mode(static_cast<climate::ClimateMode>(msg.mode));
|
||||||
|
if (msg.has_target_temperature)
|
||||||
|
call.set_target_temperature(msg.target_temperature);
|
||||||
|
if (msg.has_target_temperature_low)
|
||||||
|
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);
|
||||||
|
call.perform();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_ESP32_CAMERA
|
||||||
|
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
|
||||||
|
if (!this->state_subscription_)
|
||||||
|
return;
|
||||||
|
if (this->image_reader_.available())
|
||||||
|
return;
|
||||||
|
this->image_reader_.set_image(image);
|
||||||
|
}
|
||||||
|
bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
|
||||||
|
ListEntitiesCameraResponse msg;
|
||||||
|
msg.key = camera->get_object_id_hash();
|
||||||
|
msg.object_id = camera->get_object_id();
|
||||||
|
msg.name = camera->get_name();
|
||||||
|
msg.unique_id = get_default_unique_id("camera", camera);
|
||||||
|
return this->send_list_entities_camera_response(msg);
|
||||||
|
}
|
||||||
|
void APIConnection::camera_image(const CameraImageRequest &msg) {
|
||||||
|
if (esp32_camera::global_esp32_camera == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (msg.single)
|
||||||
|
esp32_camera::global_esp32_camera->request_image();
|
||||||
|
if (msg.stream)
|
||||||
|
esp32_camera::global_esp32_camera->request_stream();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_HOMEASSISTANT_TIME
|
||||||
|
void APIConnection::on_get_time_response(const GetTimeResponse &value) {
|
||||||
|
if (homeassistant::global_homeassistant_time != nullptr)
|
||||||
|
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
|
||||||
|
if (this->log_subscription_ < level)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this->set_nodelay(false);
|
||||||
|
|
||||||
|
// Send raw so that we don't copy too much
|
||||||
|
auto buffer = this->create_buffer();
|
||||||
|
// LogLevel level = 1;
|
||||||
|
buffer.encode_uint32(1, static_cast<uint32_t>(level));
|
||||||
|
// string tag = 2;
|
||||||
|
// buffer.encode_string(2, tag, strlen(tag));
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
||||||
|
this->client_info_ = msg.client_info + " (" + this->client_->remoteIP().toString().c_str();
|
||||||
|
this->client_info_ += ")";
|
||||||
|
ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str());
|
||||||
|
|
||||||
|
HelloResponse resp;
|
||||||
|
resp.api_version_major = 1;
|
||||||
|
resp.api_version_minor = 3;
|
||||||
|
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
||||||
|
this->connection_state_ = ConnectionState::CONNECTED;
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
|
||||||
|
bool correct = this->parent_->check_password(msg.password);
|
||||||
|
|
||||||
|
ConnectResponse resp;
|
||||||
|
// bool invalid_password = 1;
|
||||||
|
resp.invalid_password = !correct;
|
||||||
|
if (correct) {
|
||||||
|
ESP_LOGD(TAG, "Client '%s' connected successfully!", this->client_info_.c_str());
|
||||||
|
this->connection_state_ = ConnectionState::AUTHENTICATED;
|
||||||
|
|
||||||
|
#ifdef USE_HOMEASSISTANT_TIME
|
||||||
|
if (homeassistant::global_homeassistant_time != nullptr) {
|
||||||
|
this->send_time_request();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||||
|
DeviceInfoResponse resp{};
|
||||||
|
resp.uses_password = this->parent_->uses_password();
|
||||||
|
resp.name = App.get_name();
|
||||||
|
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
|
||||||
|
#ifdef USE_DEEP_SLEEP
|
||||||
|
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
|
||||||
|
#endif
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
|
||||||
|
for (auto &it : this->parent_->get_state_subs())
|
||||||
|
if (it.entity_id == msg.entity_id)
|
||||||
|
it.callback(msg.state);
|
||||||
|
}
|
||||||
|
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
||||||
|
bool found = false;
|
||||||
|
for (auto *service : this->parent_->get_user_services()) {
|
||||||
|
if (service->execute_service(msg)) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
ESP_LOGV(TAG, "Could not find matching service!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
if (!this->send_subscribe_home_assistant_state_response(resp)) {
|
||||||
|
this->on_fatal_error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) {
|
||||||
|
if (this->remove_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::vector<uint8_t> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->client_->add(reinterpret_cast<char *>(header.data()), header.size());
|
||||||
|
this->client_->add(reinterpret_cast<char *>(buffer.get_buffer()->data()), buffer.get_buffer()->size());
|
||||||
|
bool ret = this->client_->send();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
void APIConnection::on_unauthenticated_access() {
|
||||||
|
ESP_LOGD(TAG, "'%s' tried to access without authentication.", this->client_info_.c_str());
|
||||||
|
this->on_fatal_error();
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
void APIConnection::on_fatal_error() {
|
||||||
|
ESP_LOGV(TAG, "Error: Disconnecting %s", this->client_info_.c_str());
|
||||||
|
this->client_->close();
|
||||||
|
this->remove_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace api
|
||||||
|
} // namespace esphome
|
178
esphome/components/api/api_connection.h
Normal file
178
esphome/components/api/api_connection.h
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "api_pb2.h"
|
||||||
|
#include "api_pb2_service.h"
|
||||||
|
#include "api_server.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace api {
|
||||||
|
|
||||||
|
class APIConnection : public APIServerConnection {
|
||||||
|
public:
|
||||||
|
APIConnection(AsyncClient *client, APIServer *parent);
|
||||||
|
virtual ~APIConnection();
|
||||||
|
|
||||||
|
void disconnect_client();
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
bool send_list_info_done() {
|
||||||
|
ListEntitiesDoneResponse resp;
|
||||||
|
return this->send_list_entities_done_response(resp);
|
||||||
|
}
|
||||||
|
#ifdef USE_BINARY_SENSOR
|
||||||
|
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state);
|
||||||
|
bool send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_COVER
|
||||||
|
bool send_cover_state(cover::Cover *cover);
|
||||||
|
bool send_cover_info(cover::Cover *cover);
|
||||||
|
void cover_command(const CoverCommandRequest &msg) override;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_FAN
|
||||||
|
bool send_fan_state(fan::FanState *fan);
|
||||||
|
bool send_fan_info(fan::FanState *fan);
|
||||||
|
void fan_command(const FanCommandRequest &msg) override;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LIGHT
|
||||||
|
bool send_light_state(light::LightState *light);
|
||||||
|
bool send_light_info(light::LightState *light);
|
||||||
|
void light_command(const LightCommandRequest &msg) override;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SENSOR
|
||||||
|
bool send_sensor_state(sensor::Sensor *sensor, float state);
|
||||||
|
bool send_sensor_info(sensor::Sensor *sensor);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
bool send_switch_state(switch_::Switch *a_switch, bool state);
|
||||||
|
bool send_switch_info(switch_::Switch *a_switch);
|
||||||
|
void switch_command(const SwitchCommandRequest &msg) override;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_TEXT_SENSOR
|
||||||
|
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state);
|
||||||
|
bool send_text_sensor_info(text_sensor::TextSensor *text_sensor);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ESP32_CAMERA
|
||||||
|
void send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
|
||||||
|
bool send_camera_info(esp32_camera::ESP32Camera *camera);
|
||||||
|
void camera_image(const CameraImageRequest &msg) override;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_CLIMATE
|
||||||
|
bool send_climate_state(climate::Climate *climate);
|
||||||
|
bool send_climate_info(climate::Climate *climate);
|
||||||
|
void climate_command(const ClimateCommandRequest &msg) override;
|
||||||
|
#endif
|
||||||
|
bool send_log_message(int level, const char *tag, const char *line);
|
||||||
|
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||||
|
if (!this->service_call_subscription_)
|
||||||
|
return;
|
||||||
|
this->send_homeassistant_service_response(call);
|
||||||
|
}
|
||||||
|
#ifdef USE_HOMEASSISTANT_TIME
|
||||||
|
void send_time_request() {
|
||||||
|
GetTimeRequest req;
|
||||||
|
this->send_get_time_request(req);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void on_disconnect_response(const DisconnectResponse &value) override {
|
||||||
|
// we initiated disconnect_client
|
||||||
|
this->next_close_ = true;
|
||||||
|
}
|
||||||
|
void on_ping_response(const PingResponse &value) override {
|
||||||
|
// we initiated ping
|
||||||
|
this->sent_ping_ = false;
|
||||||
|
}
|
||||||
|
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override;
|
||||||
|
#ifdef USE_HOMEASSISTANT_TIME
|
||||||
|
void on_get_time_response(const GetTimeResponse &value) override;
|
||||||
|
#endif
|
||||||
|
HelloResponse hello(const HelloRequest &msg) override;
|
||||||
|
ConnectResponse connect(const ConnectRequest &msg) override;
|
||||||
|
DisconnectResponse disconnect(const DisconnectRequest &msg) override {
|
||||||
|
// remote initiated disconnect_client
|
||||||
|
this->next_close_ = true;
|
||||||
|
DisconnectResponse resp;
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
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(); }
|
||||||
|
void subscribe_states(const SubscribeStatesRequest &msg) override {
|
||||||
|
this->state_subscription_ = true;
|
||||||
|
this->initial_state_iterator_.begin();
|
||||||
|
}
|
||||||
|
void subscribe_logs(const SubscribeLogsRequest &msg) override {
|
||||||
|
this->log_subscription_ = msg.level;
|
||||||
|
if (msg.dump_config)
|
||||||
|
App.schedule_dump_config();
|
||||||
|
}
|
||||||
|
void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override {
|
||||||
|
this->service_call_subscription_ = true;
|
||||||
|
}
|
||||||
|
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
|
||||||
|
GetTimeResponse get_time(const GetTimeRequest &msg) override {
|
||||||
|
// TODO
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
void execute_service(const ExecuteServiceRequest &msg) override;
|
||||||
|
bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; }
|
||||||
|
bool is_connection_setup() override {
|
||||||
|
return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated();
|
||||||
|
}
|
||||||
|
void on_fatal_error() override;
|
||||||
|
void on_unauthenticated_access() override;
|
||||||
|
void on_no_setup_connection() override;
|
||||||
|
ProtoWriteBuffer create_buffer() override {
|
||||||
|
this->send_buffer_.clear();
|
||||||
|
return {&this->send_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_();
|
||||||
|
void set_nodelay(bool nodelay) override {
|
||||||
|
if (nodelay == this->current_nodelay_)
|
||||||
|
return;
|
||||||
|
this->client_->setNoDelay(nodelay);
|
||||||
|
this->current_nodelay_ = nodelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ConnectionState {
|
||||||
|
WAITING_FOR_HELLO,
|
||||||
|
CONNECTED,
|
||||||
|
AUTHENTICATED,
|
||||||
|
} connection_state_{ConnectionState::WAITING_FOR_HELLO};
|
||||||
|
|
||||||
|
bool remove_{false};
|
||||||
|
|
||||||
|
std::vector<uint8_t> send_buffer_;
|
||||||
|
std::vector<uint8_t> recv_buffer_;
|
||||||
|
|
||||||
|
std::string client_info_;
|
||||||
|
#ifdef USE_ESP32_CAMERA
|
||||||
|
esp32_camera::CameraImageReader image_reader_;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool state_subscription_{false};
|
||||||
|
int log_subscription_{ESPHOME_LOG_LEVEL_NONE};
|
||||||
|
uint32_t last_traffic_;
|
||||||
|
bool sent_ping_{false};
|
||||||
|
bool service_call_subscription_{false};
|
||||||
|
bool current_nodelay_{false};
|
||||||
|
bool next_close_{false};
|
||||||
|
AsyncClient *client_;
|
||||||
|
APIServer *parent_;
|
||||||
|
InitialStateIterator initial_state_iterator_;
|
||||||
|
ListEntitiesIterator list_entities_iterator_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace api
|
||||||
|
} // namespace esphome
|
|
@ -1,79 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace api {
|
|
||||||
|
|
||||||
enum class APIMessageType {
|
|
||||||
HELLO_REQUEST = 1,
|
|
||||||
HELLO_RESPONSE = 2,
|
|
||||||
CONNECT_REQUEST = 3,
|
|
||||||
CONNECT_RESPONSE = 4,
|
|
||||||
DISCONNECT_REQUEST = 5,
|
|
||||||
DISCONNECT_RESPONSE = 6,
|
|
||||||
PING_REQUEST = 7,
|
|
||||||
PING_RESPONSE = 8,
|
|
||||||
DEVICE_INFO_REQUEST = 9,
|
|
||||||
DEVICE_INFO_RESPONSE = 10,
|
|
||||||
|
|
||||||
LIST_ENTITIES_REQUEST = 11,
|
|
||||||
LIST_ENTITIES_BINARY_SENSOR_RESPONSE = 12,
|
|
||||||
LIST_ENTITIES_COVER_RESPONSE = 13,
|
|
||||||
LIST_ENTITIES_FAN_RESPONSE = 14,
|
|
||||||
LIST_ENTITIES_LIGHT_RESPONSE = 15,
|
|
||||||
LIST_ENTITIES_SENSOR_RESPONSE = 16,
|
|
||||||
LIST_ENTITIES_SWITCH_RESPONSE = 17,
|
|
||||||
LIST_ENTITIES_TEXT_SENSOR_RESPONSE = 18,
|
|
||||||
LIST_ENTITIES_SERVICE_RESPONSE = 41,
|
|
||||||
LIST_ENTITIES_CAMERA_RESPONSE = 43,
|
|
||||||
LIST_ENTITIES_CLIMATE_RESPONSE = 46,
|
|
||||||
LIST_ENTITIES_DONE_RESPONSE = 19,
|
|
||||||
|
|
||||||
SUBSCRIBE_STATES_REQUEST = 20,
|
|
||||||
BINARY_SENSOR_STATE_RESPONSE = 21,
|
|
||||||
COVER_STATE_RESPONSE = 22,
|
|
||||||
FAN_STATE_RESPONSE = 23,
|
|
||||||
LIGHT_STATE_RESPONSE = 24,
|
|
||||||
SENSOR_STATE_RESPONSE = 25,
|
|
||||||
SWITCH_STATE_RESPONSE = 26,
|
|
||||||
TEXT_SENSOR_STATE_RESPONSE = 27,
|
|
||||||
CAMERA_IMAGE_RESPONSE = 44,
|
|
||||||
CLIMATE_STATE_RESPONSE = 47,
|
|
||||||
|
|
||||||
SUBSCRIBE_LOGS_REQUEST = 28,
|
|
||||||
SUBSCRIBE_LOGS_RESPONSE = 29,
|
|
||||||
|
|
||||||
COVER_COMMAND_REQUEST = 30,
|
|
||||||
FAN_COMMAND_REQUEST = 31,
|
|
||||||
LIGHT_COMMAND_REQUEST = 32,
|
|
||||||
SWITCH_COMMAND_REQUEST = 33,
|
|
||||||
CAMERA_IMAGE_REQUEST = 45,
|
|
||||||
CLIMATE_COMMAND_REQUEST = 48,
|
|
||||||
|
|
||||||
SUBSCRIBE_SERVICE_CALLS_REQUEST = 34,
|
|
||||||
SERVICE_CALL_RESPONSE = 35,
|
|
||||||
GET_TIME_REQUEST = 36,
|
|
||||||
GET_TIME_RESPONSE = 37,
|
|
||||||
|
|
||||||
SUBSCRIBE_HOME_ASSISTANT_STATES_REQUEST = 38,
|
|
||||||
SUBSCRIBE_HOME_ASSISTANT_STATE_RESPONSE = 39,
|
|
||||||
HOME_ASSISTANT_STATE_RESPONSE = 40,
|
|
||||||
|
|
||||||
EXECUTE_SERVICE_REQUEST = 42,
|
|
||||||
};
|
|
||||||
|
|
||||||
class APIMessage {
|
|
||||||
public:
|
|
||||||
void decode(const uint8_t *buffer, size_t length);
|
|
||||||
virtual bool decode_varint(uint32_t field_id, uint32_t value);
|
|
||||||
virtual bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len);
|
|
||||||
virtual bool decode_32bit(uint32_t field_id, uint32_t value);
|
|
||||||
virtual APIMessageType message_type() const = 0;
|
|
||||||
|
|
||||||
virtual void encode(APIBuffer &buffer);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace api
|
|
||||||
} // namespace esphome
|
|
24
esphome/components/api/api_options.proto
Normal file
24
esphome/components/api/api_options.proto
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
import "google/protobuf/descriptor.proto";
|
||||||
|
|
||||||
|
|
||||||
|
enum APISourceType {
|
||||||
|
SOURCE_BOTH = 0;
|
||||||
|
SOURCE_SERVER = 1;
|
||||||
|
SOURCE_CLIENT = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message void {}
|
||||||
|
|
||||||
|
extend google.protobuf.MethodOptions {
|
||||||
|
optional bool needs_setup_connection = 1038 [default=true];
|
||||||
|
optional bool needs_authentication = 1039 [default=true];
|
||||||
|
}
|
||||||
|
|
||||||
|
extend google.protobuf.MessageOptions {
|
||||||
|
optional uint32 id = 1036 [default=0];
|
||||||
|
optional APISourceType source = 1037 [default=SOURCE_BOTH];
|
||||||
|
optional string ifdef = 1038;
|
||||||
|
optional bool log = 1039 [default=true];
|
||||||
|
optional bool no_delay = 1040 [default=false];
|
||||||
|
}
|
2749
esphome/components/api/api_pb2.cpp
Normal file
2749
esphome/components/api/api_pb2.cpp
Normal file
File diff suppressed because it is too large
Load diff
694
esphome/components/api/api_pb2.h
Normal file
694
esphome/components/api/api_pb2.h
Normal file
|
@ -0,0 +1,694 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "proto.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace api {
|
||||||
|
|
||||||
|
enum EnumLegacyCoverState : uint32_t {
|
||||||
|
LEGACY_COVER_STATE_OPEN = 0,
|
||||||
|
LEGACY_COVER_STATE_CLOSED = 1,
|
||||||
|
};
|
||||||
|
enum EnumCoverOperation : uint32_t {
|
||||||
|
COVER_OPERATION_IDLE = 0,
|
||||||
|
COVER_OPERATION_IS_OPENING = 1,
|
||||||
|
COVER_OPERATION_IS_CLOSING = 2,
|
||||||
|
};
|
||||||
|
enum EnumLegacyCoverCommand : uint32_t {
|
||||||
|
LEGACY_COVER_COMMAND_OPEN = 0,
|
||||||
|
LEGACY_COVER_COMMAND_CLOSE = 1,
|
||||||
|
LEGACY_COVER_COMMAND_STOP = 2,
|
||||||
|
};
|
||||||
|
enum EnumFanSpeed : uint32_t {
|
||||||
|
FAN_SPEED_LOW = 0,
|
||||||
|
FAN_SPEED_MEDIUM = 1,
|
||||||
|
FAN_SPEED_HIGH = 2,
|
||||||
|
};
|
||||||
|
enum EnumLogLevel : uint32_t {
|
||||||
|
LOG_LEVEL_NONE = 0,
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
enum EnumServiceArgType : uint32_t {
|
||||||
|
SERVICE_ARG_TYPE_BOOL = 0,
|
||||||
|
SERVICE_ARG_TYPE_INT = 1,
|
||||||
|
SERVICE_ARG_TYPE_FLOAT = 2,
|
||||||
|
SERVICE_ARG_TYPE_STRING = 3,
|
||||||
|
SERVICE_ARG_TYPE_BOOL_ARRAY = 4,
|
||||||
|
SERVICE_ARG_TYPE_INT_ARRAY = 5,
|
||||||
|
SERVICE_ARG_TYPE_FLOAT_ARRAY = 6,
|
||||||
|
SERVICE_ARG_TYPE_STRING_ARRAY = 7,
|
||||||
|
};
|
||||||
|
enum EnumClimateMode : uint32_t {
|
||||||
|
CLIMATE_MODE_OFF = 0,
|
||||||
|
CLIMATE_MODE_AUTO = 1,
|
||||||
|
CLIMATE_MODE_COOL = 2,
|
||||||
|
CLIMATE_MODE_HEAT = 3,
|
||||||
|
};
|
||||||
|
enum EnumClimateAction : uint32_t {
|
||||||
|
CLIMATE_ACTION_OFF = 0,
|
||||||
|
CLIMATE_ACTION_COOLING = 2,
|
||||||
|
CLIMATE_ACTION_HEATING = 3,
|
||||||
|
};
|
||||||
|
class HelloRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string client_info{}; // NOLINT
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
|
};
|
||||||
|
class HelloResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t api_version_major{0}; // NOLINT
|
||||||
|
uint32_t api_version_minor{0}; // NOLINT
|
||||||
|
std::string server_info{}; // NOLINT
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
|
};
|
||||||
|
class ConnectRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string password{}; // NOLINT
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
|
};
|
||||||
|
class ConnectResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
bool invalid_password{false}; // NOLINT
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
|
};
|
||||||
|
class DisconnectRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
};
|
||||||
|
class DisconnectResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
};
|
||||||
|
class PingRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
};
|
||||||
|
class PingResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
};
|
||||||
|
class DeviceInfoRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
};
|
||||||
|
class DeviceInfoResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
bool uses_password{false}; // NOLINT
|
||||||
|
std::string name{}; // NOLINT
|
||||||
|
std::string mac_address{}; // NOLINT
|
||||||
|
std::string esphome_version{}; // NOLINT
|
||||||
|
std::string compilation_time{}; // NOLINT
|
||||||
|
std::string model{}; // NOLINT
|
||||||
|
bool has_deep_sleep{false}; // NOLINT
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
|
};
|
||||||
|
class ListEntitiesRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
};
|
||||||
|
class ListEntitiesDoneResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
};
|
||||||
|
class SubscribeStatesRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
};
|
||||||
|
class ListEntitiesBinarySensorResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string object_id{}; // NOLINT
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
std::string name{}; // NOLINT
|
||||||
|
std::string unique_id{}; // NOLINT
|
||||||
|
std::string device_class{}; // NOLINT
|
||||||
|
bool is_status_binary_sensor{false}; // NOLINT
|
||||||
|
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 BinarySensorStateResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
bool state{false}; // NOLINT
|
||||||
|
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 ListEntitiesCoverResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string object_id{}; // NOLINT
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
std::string name{}; // NOLINT
|
||||||
|
std::string unique_id{}; // NOLINT
|
||||||
|
bool assumed_state{false}; // NOLINT
|
||||||
|
bool supports_position{false}; // NOLINT
|
||||||
|
bool supports_tilt{false}; // NOLINT
|
||||||
|
std::string device_class{}; // NOLINT
|
||||||
|
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 CoverStateResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
EnumLegacyCoverState legacy_state{}; // NOLINT
|
||||||
|
float position{0.0f}; // NOLINT
|
||||||
|
float tilt{0.0f}; // NOLINT
|
||||||
|
EnumCoverOperation current_operation{}; // NOLINT
|
||||||
|
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 CoverCommandRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
bool has_legacy_command{false}; // NOLINT
|
||||||
|
EnumLegacyCoverCommand legacy_command{}; // NOLINT
|
||||||
|
bool has_position{false}; // NOLINT
|
||||||
|
float position{0.0f}; // NOLINT
|
||||||
|
bool has_tilt{false}; // NOLINT
|
||||||
|
float tilt{0.0f}; // NOLINT
|
||||||
|
bool stop{false}; // NOLINT
|
||||||
|
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 ListEntitiesFanResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string object_id{}; // NOLINT
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
std::string name{}; // NOLINT
|
||||||
|
std::string unique_id{}; // NOLINT
|
||||||
|
bool supports_oscillation{false}; // NOLINT
|
||||||
|
bool supports_speed{false}; // NOLINT
|
||||||
|
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 FanStateResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
bool state{false}; // NOLINT
|
||||||
|
bool oscillating{false}; // NOLINT
|
||||||
|
EnumFanSpeed speed{}; // NOLINT
|
||||||
|
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 FanCommandRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
bool has_state{false}; // NOLINT
|
||||||
|
bool state{false}; // NOLINT
|
||||||
|
bool has_speed{false}; // NOLINT
|
||||||
|
EnumFanSpeed speed{}; // NOLINT
|
||||||
|
bool has_oscillating{false}; // NOLINT
|
||||||
|
bool oscillating{false}; // NOLINT
|
||||||
|
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 ListEntitiesLightResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string object_id{}; // NOLINT
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
std::string name{}; // NOLINT
|
||||||
|
std::string unique_id{}; // NOLINT
|
||||||
|
bool supports_brightness{false}; // NOLINT
|
||||||
|
bool supports_rgb{false}; // NOLINT
|
||||||
|
bool supports_white_value{false}; // NOLINT
|
||||||
|
bool supports_color_temperature{false}; // NOLINT
|
||||||
|
float min_mireds{0.0f}; // NOLINT
|
||||||
|
float max_mireds{0.0f}; // NOLINT
|
||||||
|
std::vector<std::string> effects{}; // NOLINT
|
||||||
|
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 LightStateResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
bool state{false}; // NOLINT
|
||||||
|
float brightness{0.0f}; // NOLINT
|
||||||
|
float red{0.0f}; // NOLINT
|
||||||
|
float green{0.0f}; // NOLINT
|
||||||
|
float blue{0.0f}; // NOLINT
|
||||||
|
float white{0.0f}; // NOLINT
|
||||||
|
float color_temperature{0.0f}; // NOLINT
|
||||||
|
std::string effect{}; // NOLINT
|
||||||
|
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 LightCommandRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
bool has_state{false}; // NOLINT
|
||||||
|
bool state{false}; // NOLINT
|
||||||
|
bool has_brightness{false}; // NOLINT
|
||||||
|
float brightness{0.0f}; // NOLINT
|
||||||
|
bool has_rgb{false}; // NOLINT
|
||||||
|
float red{0.0f}; // NOLINT
|
||||||
|
float green{0.0f}; // NOLINT
|
||||||
|
float blue{0.0f}; // NOLINT
|
||||||
|
bool has_white{false}; // NOLINT
|
||||||
|
float white{0.0f}; // NOLINT
|
||||||
|
bool has_color_temperature{false}; // NOLINT
|
||||||
|
float color_temperature{0.0f}; // NOLINT
|
||||||
|
bool has_transition_length{false}; // NOLINT
|
||||||
|
uint32_t transition_length{0}; // NOLINT
|
||||||
|
bool has_flash_length{false}; // NOLINT
|
||||||
|
uint32_t flash_length{0}; // NOLINT
|
||||||
|
bool has_effect{false}; // NOLINT
|
||||||
|
std::string effect{}; // NOLINT
|
||||||
|
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 ListEntitiesSensorResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string object_id{}; // NOLINT
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
std::string name{}; // NOLINT
|
||||||
|
std::string unique_id{}; // NOLINT
|
||||||
|
std::string icon{}; // NOLINT
|
||||||
|
std::string unit_of_measurement{}; // NOLINT
|
||||||
|
int32_t accuracy_decimals{0}; // NOLINT
|
||||||
|
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 SensorStateResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
float state{0.0f}; // NOLINT
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
class ListEntitiesSwitchResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string object_id{}; // NOLINT
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
std::string name{}; // NOLINT
|
||||||
|
std::string unique_id{}; // NOLINT
|
||||||
|
std::string icon{}; // NOLINT
|
||||||
|
bool assumed_state{false}; // NOLINT
|
||||||
|
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 SwitchStateResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
bool state{false}; // NOLINT
|
||||||
|
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 SwitchCommandRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
bool state{false}; // NOLINT
|
||||||
|
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 ListEntitiesTextSensorResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string object_id{}; // NOLINT
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
std::string name{}; // NOLINT
|
||||||
|
std::string unique_id{}; // NOLINT
|
||||||
|
std::string icon{}; // NOLINT
|
||||||
|
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 TextSensorStateResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
std::string state{}; // NOLINT
|
||||||
|
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 SubscribeLogsRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
EnumLogLevel level{}; // NOLINT
|
||||||
|
bool dump_config{false}; // NOLINT
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
|
};
|
||||||
|
class SubscribeLogsResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
EnumLogLevel level{}; // NOLINT
|
||||||
|
std::string tag{}; // NOLINT
|
||||||
|
std::string message{}; // NOLINT
|
||||||
|
bool send_failed{false}; // NOLINT
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
|
};
|
||||||
|
class SubscribeHomeassistantServicesRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
};
|
||||||
|
class HomeassistantServiceMap : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string key{}; // NOLINT
|
||||||
|
std::string value{}; // NOLINT
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
|
};
|
||||||
|
class HomeassistantServiceResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string service{}; // NOLINT
|
||||||
|
std::vector<HomeassistantServiceMap> data{}; // NOLINT
|
||||||
|
std::vector<HomeassistantServiceMap> data_template{}; // NOLINT
|
||||||
|
std::vector<HomeassistantServiceMap> variables{}; // NOLINT
|
||||||
|
bool is_event{false}; // NOLINT
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
|
};
|
||||||
|
class SubscribeHomeAssistantStatesRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
};
|
||||||
|
class SubscribeHomeAssistantStateResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string entity_id{}; // NOLINT
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
|
};
|
||||||
|
class HomeAssistantStateResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string entity_id{}; // NOLINT
|
||||||
|
std::string state{}; // NOLINT
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
|
};
|
||||||
|
class GetTimeRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
};
|
||||||
|
class GetTimeResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t epoch_seconds{0}; // NOLINT
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
class ListEntitiesServicesArgument : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string name{}; // NOLINT
|
||||||
|
EnumServiceArgType type{}; // NOLINT
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
|
};
|
||||||
|
class ListEntitiesServicesResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string name{}; // NOLINT
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
std::vector<ListEntitiesServicesArgument> args{}; // NOLINT
|
||||||
|
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 ExecuteServiceArgument : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
bool bool_{false}; // NOLINT
|
||||||
|
int32_t legacy_int{0}; // NOLINT
|
||||||
|
float float_{0.0f}; // NOLINT
|
||||||
|
std::string string_{}; // NOLINT
|
||||||
|
int32_t int_{0}; // NOLINT
|
||||||
|
std::vector<bool> bool_array{}; // NOLINT
|
||||||
|
std::vector<int32_t> int_array{}; // NOLINT
|
||||||
|
std::vector<float> float_array{}; // NOLINT
|
||||||
|
std::vector<std::string> string_array{}; // NOLINT
|
||||||
|
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 ExecuteServiceRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
std::vector<ExecuteServiceArgument> args{}; // NOLINT
|
||||||
|
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 ListEntitiesCameraResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string object_id{}; // NOLINT
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
std::string name{}; // NOLINT
|
||||||
|
std::string unique_id{}; // NOLINT
|
||||||
|
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 CameraImageResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
std::string data{}; // NOLINT
|
||||||
|
bool done{false}; // NOLINT
|
||||||
|
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 CameraImageRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
bool single{false}; // NOLINT
|
||||||
|
bool stream{false}; // NOLINT
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
|
};
|
||||||
|
class ListEntitiesClimateResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string object_id{}; // NOLINT
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
std::string name{}; // NOLINT
|
||||||
|
std::string unique_id{}; // NOLINT
|
||||||
|
bool supports_current_temperature{false}; // NOLINT
|
||||||
|
bool supports_two_point_target_temperature{false}; // NOLINT
|
||||||
|
std::vector<EnumClimateMode> supported_modes{}; // NOLINT
|
||||||
|
float visual_min_temperature{0.0f}; // NOLINT
|
||||||
|
float visual_max_temperature{0.0f}; // NOLINT
|
||||||
|
float visual_temperature_step{0.0f}; // NOLINT
|
||||||
|
bool supports_away{false}; // NOLINT
|
||||||
|
bool supports_action{false}; // NOLINT
|
||||||
|
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 ClimateStateResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
EnumClimateMode mode{}; // NOLINT
|
||||||
|
float current_temperature{0.0f}; // NOLINT
|
||||||
|
float target_temperature{0.0f}; // NOLINT
|
||||||
|
float target_temperature_low{0.0f}; // NOLINT
|
||||||
|
float target_temperature_high{0.0f}; // NOLINT
|
||||||
|
bool away{false}; // NOLINT
|
||||||
|
EnumClimateAction action{}; // NOLINT
|
||||||
|
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 ClimateCommandRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0}; // NOLINT
|
||||||
|
bool has_mode{false}; // NOLINT
|
||||||
|
EnumClimateMode mode{}; // NOLINT
|
||||||
|
bool has_target_temperature{false}; // NOLINT
|
||||||
|
float target_temperature{0.0f}; // NOLINT
|
||||||
|
bool has_target_temperature_low{false}; // NOLINT
|
||||||
|
float target_temperature_low{0.0f}; // NOLINT
|
||||||
|
bool has_target_temperature_high{false}; // NOLINT
|
||||||
|
float target_temperature_high{0.0f}; // NOLINT
|
||||||
|
bool has_away{false}; // NOLINT
|
||||||
|
bool away{false}; // NOLINT
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace api
|
||||||
|
} // namespace esphome
|
582
esphome/components/api/api_pb2_service.cpp
Normal file
582
esphome/components/api/api_pb2_service.cpp
Normal file
|
@ -0,0 +1,582 @@
|
||||||
|
#include "api_pb2_service.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace api {
|
||||||
|
|
||||||
|
static const char *TAG = "api.service";
|
||||||
|
|
||||||
|
bool APIServerConnectionBase::send_hello_response(const HelloResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_hello_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(true);
|
||||||
|
return this->send_message_<HelloResponse>(msg, 2);
|
||||||
|
}
|
||||||
|
bool APIServerConnectionBase::send_connect_response(const ConnectResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_connect_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(true);
|
||||||
|
return this->send_message_<ConnectResponse>(msg, 4);
|
||||||
|
}
|
||||||
|
bool APIServerConnectionBase::send_disconnect_request(const DisconnectRequest &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_disconnect_request: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(true);
|
||||||
|
return this->send_message_<DisconnectRequest>(msg, 5);
|
||||||
|
}
|
||||||
|
bool APIServerConnectionBase::send_disconnect_response(const DisconnectResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_disconnect_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(true);
|
||||||
|
return this->send_message_<DisconnectResponse>(msg, 6);
|
||||||
|
}
|
||||||
|
bool APIServerConnectionBase::send_ping_request(const PingRequest &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_ping_request: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(false);
|
||||||
|
return this->send_message_<PingRequest>(msg, 7);
|
||||||
|
}
|
||||||
|
bool APIServerConnectionBase::send_ping_response(const PingResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_ping_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(false);
|
||||||
|
return this->send_message_<PingResponse>(msg, 8);
|
||||||
|
}
|
||||||
|
bool APIServerConnectionBase::send_device_info_response(const DeviceInfoResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_device_info_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(false);
|
||||||
|
return this->send_message_<DeviceInfoResponse>(msg, 10);
|
||||||
|
}
|
||||||
|
bool APIServerConnectionBase::send_list_entities_done_response(const ListEntitiesDoneResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_list_entities_done_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(true);
|
||||||
|
return this->send_message_<ListEntitiesDoneResponse>(msg, 19);
|
||||||
|
}
|
||||||
|
#ifdef USE_BINARY_SENSOR
|
||||||
|
bool APIServerConnectionBase::send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_list_entities_binary_sensor_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(false);
|
||||||
|
return this->send_message_<ListEntitiesBinarySensorResponse>(msg, 12);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_BINARY_SENSOR
|
||||||
|
bool APIServerConnectionBase::send_binary_sensor_state_response(const BinarySensorStateResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_binary_sensor_state_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(true);
|
||||||
|
return this->send_message_<BinarySensorStateResponse>(msg, 21);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_COVER
|
||||||
|
bool APIServerConnectionBase::send_list_entities_cover_response(const ListEntitiesCoverResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_list_entities_cover_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(false);
|
||||||
|
return this->send_message_<ListEntitiesCoverResponse>(msg, 13);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_COVER
|
||||||
|
bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_cover_state_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(true);
|
||||||
|
return this->send_message_<CoverStateResponse>(msg, 22);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_COVER
|
||||||
|
#endif
|
||||||
|
#ifdef USE_FAN
|
||||||
|
bool APIServerConnectionBase::send_list_entities_fan_response(const ListEntitiesFanResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_list_entities_fan_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(false);
|
||||||
|
return this->send_message_<ListEntitiesFanResponse>(msg, 14);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_FAN
|
||||||
|
bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_fan_state_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(true);
|
||||||
|
return this->send_message_<FanStateResponse>(msg, 23);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_FAN
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LIGHT
|
||||||
|
bool APIServerConnectionBase::send_list_entities_light_response(const ListEntitiesLightResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_list_entities_light_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(false);
|
||||||
|
return this->send_message_<ListEntitiesLightResponse>(msg, 15);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LIGHT
|
||||||
|
bool APIServerConnectionBase::send_light_state_response(const LightStateResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_light_state_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(true);
|
||||||
|
return this->send_message_<LightStateResponse>(msg, 24);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LIGHT
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SENSOR
|
||||||
|
bool APIServerConnectionBase::send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_list_entities_sensor_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(false);
|
||||||
|
return this->send_message_<ListEntitiesSensorResponse>(msg, 16);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SENSOR
|
||||||
|
bool APIServerConnectionBase::send_sensor_state_response(const SensorStateResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_sensor_state_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(true);
|
||||||
|
return this->send_message_<SensorStateResponse>(msg, 25);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
bool APIServerConnectionBase::send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_list_entities_switch_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(false);
|
||||||
|
return this->send_message_<ListEntitiesSwitchResponse>(msg, 17);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
bool APIServerConnectionBase::send_switch_state_response(const SwitchStateResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_switch_state_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(true);
|
||||||
|
return this->send_message_<SwitchStateResponse>(msg, 26);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
#endif
|
||||||
|
#ifdef USE_TEXT_SENSOR
|
||||||
|
bool APIServerConnectionBase::send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_list_entities_text_sensor_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(false);
|
||||||
|
return this->send_message_<ListEntitiesTextSensorResponse>(msg, 18);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_TEXT_SENSOR
|
||||||
|
bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorStateResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_text_sensor_state_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(true);
|
||||||
|
return this->send_message_<TextSensorStateResponse>(msg, 27);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) {
|
||||||
|
this->set_nodelay(false);
|
||||||
|
return this->send_message_<SubscribeLogsResponse>(msg, 29);
|
||||||
|
}
|
||||||
|
bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(true);
|
||||||
|
return this->send_message_<HomeassistantServiceResponse>(msg, 35);
|
||||||
|
}
|
||||||
|
bool APIServerConnectionBase::send_subscribe_home_assistant_state_response(
|
||||||
|
const SubscribeHomeAssistantStateResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_subscribe_home_assistant_state_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(false);
|
||||||
|
return this->send_message_<SubscribeHomeAssistantStateResponse>(msg, 39);
|
||||||
|
}
|
||||||
|
bool APIServerConnectionBase::send_get_time_request(const GetTimeRequest &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_get_time_request: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(false);
|
||||||
|
return this->send_message_<GetTimeRequest>(msg, 36);
|
||||||
|
}
|
||||||
|
bool APIServerConnectionBase::send_get_time_response(const GetTimeResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_get_time_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(true);
|
||||||
|
return this->send_message_<GetTimeResponse>(msg, 37);
|
||||||
|
}
|
||||||
|
bool APIServerConnectionBase::send_list_entities_services_response(const ListEntitiesServicesResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_list_entities_services_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(false);
|
||||||
|
return this->send_message_<ListEntitiesServicesResponse>(msg, 41);
|
||||||
|
}
|
||||||
|
#ifdef USE_ESP32_CAMERA
|
||||||
|
bool APIServerConnectionBase::send_list_entities_camera_response(const ListEntitiesCameraResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_list_entities_camera_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(false);
|
||||||
|
return this->send_message_<ListEntitiesCameraResponse>(msg, 43);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ESP32_CAMERA
|
||||||
|
bool APIServerConnectionBase::send_camera_image_response(const CameraImageResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_camera_image_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(false);
|
||||||
|
return this->send_message_<CameraImageResponse>(msg, 44);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ESP32_CAMERA
|
||||||
|
#endif
|
||||||
|
#ifdef USE_CLIMATE
|
||||||
|
bool APIServerConnectionBase::send_list_entities_climate_response(const ListEntitiesClimateResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_list_entities_climate_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(false);
|
||||||
|
return this->send_message_<ListEntitiesClimateResponse>(msg, 46);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_CLIMATE
|
||||||
|
bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResponse &msg) {
|
||||||
|
ESP_LOGVV(TAG, "send_climate_state_response: %s", msg.dump().c_str());
|
||||||
|
this->set_nodelay(true);
|
||||||
|
return this->send_message_<ClimateStateResponse>(msg, 47);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_CLIMATE
|
||||||
|
#endif
|
||||||
|
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
||||||
|
switch (msg_type) {
|
||||||
|
case 1: {
|
||||||
|
HelloRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_hello_request: %s", msg.dump().c_str());
|
||||||
|
this->on_hello_request(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
ConnectRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_connect_request: %s", msg.dump().c_str());
|
||||||
|
this->on_connect_request(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 5: {
|
||||||
|
DisconnectRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_disconnect_request: %s", msg.dump().c_str());
|
||||||
|
this->on_disconnect_request(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 6: {
|
||||||
|
DisconnectResponse msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_disconnect_response: %s", msg.dump().c_str());
|
||||||
|
this->on_disconnect_response(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 7: {
|
||||||
|
PingRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_ping_request: %s", msg.dump().c_str());
|
||||||
|
this->on_ping_request(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 8: {
|
||||||
|
PingResponse msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_ping_response: %s", msg.dump().c_str());
|
||||||
|
this->on_ping_response(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 9: {
|
||||||
|
DeviceInfoRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_device_info_request: %s", msg.dump().c_str());
|
||||||
|
this->on_device_info_request(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 11: {
|
||||||
|
ListEntitiesRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_list_entities_request: %s", msg.dump().c_str());
|
||||||
|
this->on_list_entities_request(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 20: {
|
||||||
|
SubscribeStatesRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_subscribe_states_request: %s", msg.dump().c_str());
|
||||||
|
this->on_subscribe_states_request(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 28: {
|
||||||
|
SubscribeLogsRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_subscribe_logs_request: %s", msg.dump().c_str());
|
||||||
|
this->on_subscribe_logs_request(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 30: {
|
||||||
|
#ifdef USE_COVER
|
||||||
|
CoverCommandRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_cover_command_request: %s", msg.dump().c_str());
|
||||||
|
this->on_cover_command_request(msg);
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 31: {
|
||||||
|
#ifdef USE_FAN
|
||||||
|
FanCommandRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_fan_command_request: %s", msg.dump().c_str());
|
||||||
|
this->on_fan_command_request(msg);
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 32: {
|
||||||
|
#ifdef USE_LIGHT
|
||||||
|
LightCommandRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_light_command_request: %s", msg.dump().c_str());
|
||||||
|
this->on_light_command_request(msg);
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 33: {
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
SwitchCommandRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_switch_command_request: %s", msg.dump().c_str());
|
||||||
|
this->on_switch_command_request(msg);
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 34: {
|
||||||
|
SubscribeHomeassistantServicesRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_subscribe_homeassistant_services_request: %s", msg.dump().c_str());
|
||||||
|
this->on_subscribe_homeassistant_services_request(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 36: {
|
||||||
|
GetTimeRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_get_time_request: %s", msg.dump().c_str());
|
||||||
|
this->on_get_time_request(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 37: {
|
||||||
|
GetTimeResponse msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_get_time_response: %s", msg.dump().c_str());
|
||||||
|
this->on_get_time_response(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 38: {
|
||||||
|
SubscribeHomeAssistantStatesRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_subscribe_home_assistant_states_request: %s", msg.dump().c_str());
|
||||||
|
this->on_subscribe_home_assistant_states_request(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 40: {
|
||||||
|
HomeAssistantStateResponse msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_home_assistant_state_response: %s", msg.dump().c_str());
|
||||||
|
this->on_home_assistant_state_response(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 42: {
|
||||||
|
ExecuteServiceRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_execute_service_request: %s", msg.dump().c_str());
|
||||||
|
this->on_execute_service_request(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 45: {
|
||||||
|
#ifdef USE_ESP32_CAMERA
|
||||||
|
CameraImageRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
ESP_LOGVV(TAG, "on_camera_image_request: %s", msg.dump().c_str());
|
||||||
|
this->on_camera_image_request(msg);
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 48: {
|
||||||
|
#ifdef USE_CLIMATE
|
||||||
|
ClimateCommandRequest msg;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void APIServerConnection::on_hello_request(const HelloRequest &msg) {
|
||||||
|
HelloResponse ret = this->hello(msg);
|
||||||
|
if (!this->send_hello_response(ret)) {
|
||||||
|
this->on_fatal_error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void APIServerConnection::on_connect_request(const ConnectRequest &msg) {
|
||||||
|
ConnectResponse ret = this->connect(msg);
|
||||||
|
if (!this->send_connect_response(ret)) {
|
||||||
|
this->on_fatal_error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) {
|
||||||
|
DisconnectResponse ret = this->disconnect(msg);
|
||||||
|
if (!this->send_disconnect_response(ret)) {
|
||||||
|
this->on_fatal_error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void APIServerConnection::on_ping_request(const PingRequest &msg) {
|
||||||
|
PingResponse ret = this->ping(msg);
|
||||||
|
if (!this->send_ping_response(ret)) {
|
||||||
|
this->on_fatal_error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) {
|
||||||
|
if (!this->is_connection_setup()) {
|
||||||
|
this->on_no_setup_connection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DeviceInfoResponse ret = this->device_info(msg);
|
||||||
|
if (!this->send_device_info_response(ret)) {
|
||||||
|
this->on_fatal_error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) {
|
||||||
|
if (!this->is_connection_setup()) {
|
||||||
|
this->on_no_setup_connection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->is_authenticated()) {
|
||||||
|
this->on_unauthenticated_access();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->list_entities(msg);
|
||||||
|
}
|
||||||
|
void APIServerConnection::on_subscribe_states_request(const SubscribeStatesRequest &msg) {
|
||||||
|
if (!this->is_connection_setup()) {
|
||||||
|
this->on_no_setup_connection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->is_authenticated()) {
|
||||||
|
this->on_unauthenticated_access();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->subscribe_states(msg);
|
||||||
|
}
|
||||||
|
void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) {
|
||||||
|
if (!this->is_connection_setup()) {
|
||||||
|
this->on_no_setup_connection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->is_authenticated()) {
|
||||||
|
this->on_unauthenticated_access();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->subscribe_logs(msg);
|
||||||
|
}
|
||||||
|
void APIServerConnection::on_subscribe_homeassistant_services_request(
|
||||||
|
const SubscribeHomeassistantServicesRequest &msg) {
|
||||||
|
if (!this->is_connection_setup()) {
|
||||||
|
this->on_no_setup_connection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->is_authenticated()) {
|
||||||
|
this->on_unauthenticated_access();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->subscribe_homeassistant_services(msg);
|
||||||
|
}
|
||||||
|
void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) {
|
||||||
|
if (!this->is_connection_setup()) {
|
||||||
|
this->on_no_setup_connection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->is_authenticated()) {
|
||||||
|
this->on_unauthenticated_access();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->subscribe_home_assistant_states(msg);
|
||||||
|
}
|
||||||
|
void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
|
||||||
|
if (!this->is_connection_setup()) {
|
||||||
|
this->on_no_setup_connection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GetTimeResponse ret = this->get_time(msg);
|
||||||
|
if (!this->send_get_time_response(ret)) {
|
||||||
|
this->on_fatal_error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) {
|
||||||
|
if (!this->is_connection_setup()) {
|
||||||
|
this->on_no_setup_connection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->is_authenticated()) {
|
||||||
|
this->on_unauthenticated_access();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->execute_service(msg);
|
||||||
|
}
|
||||||
|
#ifdef USE_COVER
|
||||||
|
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
|
||||||
|
if (!this->is_connection_setup()) {
|
||||||
|
this->on_no_setup_connection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->is_authenticated()) {
|
||||||
|
this->on_unauthenticated_access();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->cover_command(msg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_FAN
|
||||||
|
void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) {
|
||||||
|
if (!this->is_connection_setup()) {
|
||||||
|
this->on_no_setup_connection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->is_authenticated()) {
|
||||||
|
this->on_unauthenticated_access();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->fan_command(msg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LIGHT
|
||||||
|
void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) {
|
||||||
|
if (!this->is_connection_setup()) {
|
||||||
|
this->on_no_setup_connection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->is_authenticated()) {
|
||||||
|
this->on_unauthenticated_access();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->light_command(msg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) {
|
||||||
|
if (!this->is_connection_setup()) {
|
||||||
|
this->on_no_setup_connection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->is_authenticated()) {
|
||||||
|
this->on_unauthenticated_access();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->switch_command(msg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ESP32_CAMERA
|
||||||
|
void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) {
|
||||||
|
if (!this->is_connection_setup()) {
|
||||||
|
this->on_no_setup_connection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->is_authenticated()) {
|
||||||
|
this->on_unauthenticated_access();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->camera_image(msg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_CLIMATE
|
||||||
|
void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) {
|
||||||
|
if (!this->is_connection_setup()) {
|
||||||
|
this->on_no_setup_connection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->is_authenticated()) {
|
||||||
|
this->on_unauthenticated_access();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->climate_command(msg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace api
|
||||||
|
} // namespace esphome
|
183
esphome/components/api/api_pb2_service.h
Normal file
183
esphome/components/api/api_pb2_service.h
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "api_pb2.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace api {
|
||||||
|
|
||||||
|
class APIServerConnectionBase : public ProtoService {
|
||||||
|
public:
|
||||||
|
virtual void on_hello_request(const HelloRequest &value){};
|
||||||
|
bool send_hello_response(const HelloResponse &msg);
|
||||||
|
virtual void on_connect_request(const ConnectRequest &value){};
|
||||||
|
bool send_connect_response(const ConnectResponse &msg);
|
||||||
|
bool send_disconnect_request(const DisconnectRequest &msg);
|
||||||
|
virtual void on_disconnect_request(const DisconnectRequest &value){};
|
||||||
|
bool send_disconnect_response(const DisconnectResponse &msg);
|
||||||
|
virtual void on_disconnect_response(const DisconnectResponse &value){};
|
||||||
|
bool send_ping_request(const PingRequest &msg);
|
||||||
|
virtual void on_ping_request(const PingRequest &value){};
|
||||||
|
bool send_ping_response(const PingResponse &msg);
|
||||||
|
virtual void on_ping_response(const PingResponse &value){};
|
||||||
|
virtual void on_device_info_request(const DeviceInfoRequest &value){};
|
||||||
|
bool send_device_info_response(const DeviceInfoResponse &msg);
|
||||||
|
virtual void on_list_entities_request(const ListEntitiesRequest &value){};
|
||||||
|
bool send_list_entities_done_response(const ListEntitiesDoneResponse &msg);
|
||||||
|
virtual void on_subscribe_states_request(const SubscribeStatesRequest &value){};
|
||||||
|
#ifdef USE_BINARY_SENSOR
|
||||||
|
bool send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_BINARY_SENSOR
|
||||||
|
bool send_binary_sensor_state_response(const BinarySensorStateResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_COVER
|
||||||
|
bool send_list_entities_cover_response(const ListEntitiesCoverResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_COVER
|
||||||
|
bool send_cover_state_response(const CoverStateResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_COVER
|
||||||
|
virtual void on_cover_command_request(const CoverCommandRequest &value){};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_FAN
|
||||||
|
bool send_list_entities_fan_response(const ListEntitiesFanResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_FAN
|
||||||
|
bool send_fan_state_response(const FanStateResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_FAN
|
||||||
|
virtual void on_fan_command_request(const FanCommandRequest &value){};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LIGHT
|
||||||
|
bool send_list_entities_light_response(const ListEntitiesLightResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LIGHT
|
||||||
|
bool send_light_state_response(const LightStateResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LIGHT
|
||||||
|
virtual void on_light_command_request(const LightCommandRequest &value){};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SENSOR
|
||||||
|
bool send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SENSOR
|
||||||
|
bool send_sensor_state_response(const SensorStateResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
bool send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
bool send_switch_state_response(const SwitchStateResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
virtual void on_switch_command_request(const SwitchCommandRequest &value){};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_TEXT_SENSOR
|
||||||
|
bool send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_TEXT_SENSOR
|
||||||
|
bool send_text_sensor_state_response(const TextSensorStateResponse &msg);
|
||||||
|
#endif
|
||||||
|
virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
|
||||||
|
bool send_subscribe_logs_response(const SubscribeLogsResponse &msg);
|
||||||
|
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
|
||||||
|
bool send_homeassistant_service_response(const HomeassistantServiceResponse &msg);
|
||||||
|
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
|
||||||
|
bool send_subscribe_home_assistant_state_response(const SubscribeHomeAssistantStateResponse &msg);
|
||||||
|
virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
|
||||||
|
bool send_get_time_request(const GetTimeRequest &msg);
|
||||||
|
virtual void on_get_time_request(const GetTimeRequest &value){};
|
||||||
|
bool send_get_time_response(const GetTimeResponse &msg);
|
||||||
|
virtual void on_get_time_response(const GetTimeResponse &value){};
|
||||||
|
bool send_list_entities_services_response(const ListEntitiesServicesResponse &msg);
|
||||||
|
virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
|
||||||
|
#ifdef USE_ESP32_CAMERA
|
||||||
|
bool send_list_entities_camera_response(const ListEntitiesCameraResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ESP32_CAMERA
|
||||||
|
bool send_camera_image_response(const CameraImageResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ESP32_CAMERA
|
||||||
|
virtual void on_camera_image_request(const CameraImageRequest &value){};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_CLIMATE
|
||||||
|
bool send_list_entities_climate_response(const ListEntitiesClimateResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_CLIMATE
|
||||||
|
bool send_climate_state_response(const ClimateStateResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_CLIMATE
|
||||||
|
virtual void on_climate_command_request(const ClimateCommandRequest &value){};
|
||||||
|
#endif
|
||||||
|
protected:
|
||||||
|
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class APIServerConnection : public APIServerConnectionBase {
|
||||||
|
public:
|
||||||
|
virtual HelloResponse hello(const HelloRequest &msg) = 0;
|
||||||
|
virtual ConnectResponse connect(const ConnectRequest &msg) = 0;
|
||||||
|
virtual DisconnectResponse disconnect(const DisconnectRequest &msg) = 0;
|
||||||
|
virtual PingResponse ping(const PingRequest &msg) = 0;
|
||||||
|
virtual DeviceInfoResponse device_info(const DeviceInfoRequest &msg) = 0;
|
||||||
|
virtual void list_entities(const ListEntitiesRequest &msg) = 0;
|
||||||
|
virtual void subscribe_states(const SubscribeStatesRequest &msg) = 0;
|
||||||
|
virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0;
|
||||||
|
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
|
||||||
|
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
|
||||||
|
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
|
||||||
|
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
|
||||||
|
#ifdef USE_COVER
|
||||||
|
virtual void cover_command(const CoverCommandRequest &msg) = 0;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_FAN
|
||||||
|
virtual void fan_command(const FanCommandRequest &msg) = 0;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LIGHT
|
||||||
|
virtual void light_command(const LightCommandRequest &msg) = 0;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
virtual void switch_command(const SwitchCommandRequest &msg) = 0;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ESP32_CAMERA
|
||||||
|
virtual void camera_image(const CameraImageRequest &msg) = 0;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_CLIMATE
|
||||||
|
virtual void climate_command(const ClimateCommandRequest &msg) = 0;
|
||||||
|
#endif
|
||||||
|
protected:
|
||||||
|
void on_hello_request(const HelloRequest &msg) override;
|
||||||
|
void on_connect_request(const ConnectRequest &msg) override;
|
||||||
|
void on_disconnect_request(const DisconnectRequest &msg) override;
|
||||||
|
void on_ping_request(const PingRequest &msg) override;
|
||||||
|
void on_device_info_request(const DeviceInfoRequest &msg) override;
|
||||||
|
void on_list_entities_request(const ListEntitiesRequest &msg) override;
|
||||||
|
void on_subscribe_states_request(const SubscribeStatesRequest &msg) override;
|
||||||
|
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override;
|
||||||
|
void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
|
||||||
|
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
|
||||||
|
void on_get_time_request(const GetTimeRequest &msg) override;
|
||||||
|
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
|
||||||
|
#ifdef USE_COVER
|
||||||
|
void on_cover_command_request(const CoverCommandRequest &msg) override;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_FAN
|
||||||
|
void on_fan_command_request(const FanCommandRequest &msg) override;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LIGHT
|
||||||
|
void on_light_command_request(const LightCommandRequest &msg) override;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
void on_switch_command_request(const SwitchCommandRequest &msg) override;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ESP32_CAMERA
|
||||||
|
void on_camera_image_request(const CameraImageRequest &msg) override;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_CLIMATE
|
||||||
|
void on_climate_command_request(const ClimateCommandRequest &msg) override;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace api
|
||||||
|
} // namespace esphome
|
|
@ -1,18 +1,11 @@
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include "api_server.h"
|
#include "api_server.h"
|
||||||
#include "basic_messages.h"
|
#include "api_connection.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/util.h"
|
#include "esphome/core/util.h"
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/version.h"
|
||||||
|
|
||||||
#ifdef USE_DEEP_SLEEP
|
|
||||||
#include "esphome/components/deep_sleep/deep_sleep_component.h"
|
|
||||||
#endif
|
|
||||||
#ifdef USE_HOMEASSISTANT_TIME
|
|
||||||
#include "esphome/components/homeassistant/time/homeassistant_time.h"
|
|
||||||
#endif
|
|
||||||
#ifdef USE_LOGGER
|
#ifdef USE_LOGGER
|
||||||
#include "esphome/components/logger/logger.h"
|
#include "esphome/components/logger/logger.h"
|
||||||
#endif
|
#endif
|
||||||
|
@ -209,9 +202,9 @@ void APIServer::set_port(uint16_t port) { this->port_ = port; }
|
||||||
APIServer *global_api_server = nullptr;
|
APIServer *global_api_server = nullptr;
|
||||||
|
|
||||||
void APIServer::set_password(const std::string &password) { this->password_ = password; }
|
void APIServer::set_password(const std::string &password) { this->password_ = password; }
|
||||||
void APIServer::send_service_call(ServiceCallResponse &call) {
|
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||||
for (auto *client : this->clients_) {
|
for (auto *client : this->clients_) {
|
||||||
client->send_service_call(call);
|
client->send_homeassistant_service_call(call);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
APIServer::APIServer() { global_api_server = this; }
|
APIServer::APIServer() { global_api_server = this; }
|
||||||
|
@ -237,965 +230,10 @@ void APIServer::request_time() {
|
||||||
bool APIServer::is_connected() const { return !this->clients_.empty(); }
|
bool APIServer::is_connected() const { return !this->clients_.empty(); }
|
||||||
void APIServer::on_shutdown() {
|
void APIServer::on_shutdown() {
|
||||||
for (auto *c : this->clients_) {
|
for (auto *c : this->clients_) {
|
||||||
c->send_disconnect_request();
|
c->send_disconnect_request(DisconnectRequest());
|
||||||
}
|
}
|
||||||
delay(10);
|
delay(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// APIConnection
|
|
||||||
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<uint8_t *>(buf), len); },
|
|
||||||
this);
|
|
||||||
|
|
||||||
this->send_buffer_.reserve(64);
|
|
||||||
this->recv_buffer_.reserve(32);
|
|
||||||
this->client_info_ = this->client_->remoteIP().toString().c_str();
|
|
||||||
this->last_traffic_ = millis();
|
|
||||||
}
|
|
||||||
APIConnection::~APIConnection() { delete this->client_; }
|
|
||||||
void APIConnection::on_error_(int8_t error) {
|
|
||||||
// disconnect will also be called, nothing to do here
|
|
||||||
this->remove_ = true;
|
|
||||||
}
|
|
||||||
void APIConnection::on_disconnect_() {
|
|
||||||
// delete self, generally unsafe but not in this case.
|
|
||||||
this->remove_ = true;
|
|
||||||
}
|
|
||||||
void APIConnection::on_timeout_(uint32_t time) { this->disconnect_client(); }
|
|
||||||
void APIConnection::on_data_(uint8_t *buf, size_t len) {
|
|
||||||
if (len == 0 || buf == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this->recv_buffer_.insert(this->recv_buffer_.end(), buf, buf + len);
|
|
||||||
// TODO: On ESP32, use queue to notify main thread of new data
|
|
||||||
}
|
|
||||||
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->fatal_error_();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint32_t i = 1;
|
|
||||||
const uint32_t size = this->recv_buffer_.size();
|
|
||||||
uint32_t msg_size = 0;
|
|
||||||
while (i < size) {
|
|
||||||
const uint8_t dat = this->recv_buffer_[i];
|
|
||||||
msg_size |= (dat & 0x7F);
|
|
||||||
// consume
|
|
||||||
i += 1;
|
|
||||||
if ((dat & 0x80) == 0x00) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
msg_size <<= 7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (i == size)
|
|
||||||
// not enough data there yet
|
|
||||||
return;
|
|
||||||
|
|
||||||
uint32_t msg_type = 0;
|
|
||||||
bool msg_type_done = false;
|
|
||||||
while (i < size) {
|
|
||||||
const uint8_t dat = this->recv_buffer_[i];
|
|
||||||
msg_type |= (dat & 0x7F);
|
|
||||||
// consume
|
|
||||||
i += 1;
|
|
||||||
if ((dat & 0x80) == 0x00) {
|
|
||||||
msg_type_done = true;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
msg_type <<= 7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!msg_type_done)
|
|
||||||
// not enough data there yet
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (size - i < msg_size)
|
|
||||||
// message body not fully received
|
|
||||||
return;
|
|
||||||
|
|
||||||
// ESP_LOGVV(TAG, "RECV Message: Size=%u Type=%u", msg_size, msg_type);
|
|
||||||
|
|
||||||
if (!this->valid_rx_message_type_(msg_type)) {
|
|
||||||
ESP_LOGE(TAG, "Not a valid message type: %u", msg_type);
|
|
||||||
this->fatal_error_();
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void APIConnection::read_message_(uint32_t size, uint32_t type, uint8_t *msg) {
|
|
||||||
this->last_traffic_ = millis();
|
|
||||||
|
|
||||||
switch (static_cast<APIMessageType>(type)) {
|
|
||||||
case APIMessageType::HELLO_REQUEST: {
|
|
||||||
HelloRequest req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_hello_request_(req);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::HELLO_RESPONSE: {
|
|
||||||
// Invalid
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::CONNECT_REQUEST: {
|
|
||||||
ConnectRequest req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_connect_request_(req);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::CONNECT_RESPONSE:
|
|
||||||
// Invalid
|
|
||||||
break;
|
|
||||||
case APIMessageType::DISCONNECT_REQUEST: {
|
|
||||||
DisconnectRequest req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_disconnect_request_(req);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::DISCONNECT_RESPONSE: {
|
|
||||||
DisconnectResponse req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_disconnect_response_(req);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::PING_REQUEST: {
|
|
||||||
PingRequest req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_ping_request_(req);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::PING_RESPONSE: {
|
|
||||||
PingResponse req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_ping_response_(req);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::DEVICE_INFO_REQUEST: {
|
|
||||||
DeviceInfoRequest req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_device_info_request_(req);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::DEVICE_INFO_RESPONSE: {
|
|
||||||
// Invalid
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::LIST_ENTITIES_REQUEST: {
|
|
||||||
ListEntitiesRequest req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_list_entities_request_(req);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::LIST_ENTITIES_BINARY_SENSOR_RESPONSE:
|
|
||||||
case APIMessageType::LIST_ENTITIES_COVER_RESPONSE:
|
|
||||||
case APIMessageType::LIST_ENTITIES_FAN_RESPONSE:
|
|
||||||
case APIMessageType::LIST_ENTITIES_LIGHT_RESPONSE:
|
|
||||||
case APIMessageType::LIST_ENTITIES_SENSOR_RESPONSE:
|
|
||||||
case APIMessageType::LIST_ENTITIES_SWITCH_RESPONSE:
|
|
||||||
case APIMessageType::LIST_ENTITIES_TEXT_SENSOR_RESPONSE:
|
|
||||||
case APIMessageType::LIST_ENTITIES_SERVICE_RESPONSE:
|
|
||||||
case APIMessageType::LIST_ENTITIES_CAMERA_RESPONSE:
|
|
||||||
case APIMessageType::LIST_ENTITIES_CLIMATE_RESPONSE:
|
|
||||||
case APIMessageType::LIST_ENTITIES_DONE_RESPONSE:
|
|
||||||
// Invalid
|
|
||||||
break;
|
|
||||||
case APIMessageType::SUBSCRIBE_STATES_REQUEST: {
|
|
||||||
SubscribeStatesRequest req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_subscribe_states_request_(req);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::BINARY_SENSOR_STATE_RESPONSE:
|
|
||||||
case APIMessageType::COVER_STATE_RESPONSE:
|
|
||||||
case APIMessageType::FAN_STATE_RESPONSE:
|
|
||||||
case APIMessageType::LIGHT_STATE_RESPONSE:
|
|
||||||
case APIMessageType::SENSOR_STATE_RESPONSE:
|
|
||||||
case APIMessageType::SWITCH_STATE_RESPONSE:
|
|
||||||
case APIMessageType::TEXT_SENSOR_STATE_RESPONSE:
|
|
||||||
case APIMessageType::CAMERA_IMAGE_RESPONSE:
|
|
||||||
case APIMessageType::CLIMATE_STATE_RESPONSE:
|
|
||||||
// Invalid
|
|
||||||
break;
|
|
||||||
case APIMessageType::SUBSCRIBE_LOGS_REQUEST: {
|
|
||||||
SubscribeLogsRequest req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_subscribe_logs_request_(req);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType ::SUBSCRIBE_LOGS_RESPONSE:
|
|
||||||
// Invalid
|
|
||||||
break;
|
|
||||||
case APIMessageType::COVER_COMMAND_REQUEST: {
|
|
||||||
#ifdef USE_COVER
|
|
||||||
CoverCommandRequest req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_cover_command_request_(req);
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::FAN_COMMAND_REQUEST: {
|
|
||||||
#ifdef USE_FAN
|
|
||||||
FanCommandRequest req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_fan_command_request_(req);
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::LIGHT_COMMAND_REQUEST: {
|
|
||||||
#ifdef USE_LIGHT
|
|
||||||
LightCommandRequest req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_light_command_request_(req);
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::SWITCH_COMMAND_REQUEST: {
|
|
||||||
#ifdef USE_SWITCH
|
|
||||||
SwitchCommandRequest req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_switch_command_request_(req);
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::CLIMATE_COMMAND_REQUEST: {
|
|
||||||
#ifdef USE_CLIMATE
|
|
||||||
ClimateCommandRequest req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_climate_command_request_(req);
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::SUBSCRIBE_SERVICE_CALLS_REQUEST: {
|
|
||||||
SubscribeServiceCallsRequest req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_subscribe_service_calls_request_(req);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::SERVICE_CALL_RESPONSE:
|
|
||||||
// Invalid
|
|
||||||
break;
|
|
||||||
case APIMessageType::GET_TIME_REQUEST:
|
|
||||||
// Invalid
|
|
||||||
break;
|
|
||||||
case APIMessageType::GET_TIME_RESPONSE: {
|
|
||||||
#ifdef USE_HOMEASSISTANT_TIME
|
|
||||||
homeassistant::GetTimeResponse req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATES_REQUEST: {
|
|
||||||
SubscribeHomeAssistantStatesRequest req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_subscribe_home_assistant_states_request_(req);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATE_RESPONSE:
|
|
||||||
// Invalid
|
|
||||||
break;
|
|
||||||
case APIMessageType::HOME_ASSISTANT_STATE_RESPONSE: {
|
|
||||||
HomeAssistantStateResponse req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_home_assistant_state_response_(req);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::EXECUTE_SERVICE_REQUEST: {
|
|
||||||
ExecuteServiceRequest req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_execute_service_(req);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APIMessageType::CAMERA_IMAGE_REQUEST: {
|
|
||||||
#ifdef USE_ESP32_CAMERA
|
|
||||||
CameraImageRequest req;
|
|
||||||
req.decode(msg, size);
|
|
||||||
this->on_camera_image_request_(req);
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void APIConnection::on_hello_request_(const HelloRequest &req) {
|
|
||||||
ESP_LOGVV(TAG, "on_hello_request_(client_info='%s')", req.get_client_info().c_str());
|
|
||||||
this->client_info_ = req.get_client_info() + " (" + this->client_->remoteIP().toString().c_str();
|
|
||||||
this->client_info_ += ")";
|
|
||||||
ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str());
|
|
||||||
|
|
||||||
auto buffer = this->get_buffer();
|
|
||||||
// uint32 api_version_major = 1; -> 1
|
|
||||||
buffer.encode_uint32(1, 1);
|
|
||||||
// uint32 api_version_minor = 2; -> 1
|
|
||||||
buffer.encode_uint32(2, 1);
|
|
||||||
|
|
||||||
// string server_info = 3;
|
|
||||||
buffer.encode_string(3, App.get_name() + " (esphome v" ESPHOME_VERSION ")");
|
|
||||||
bool success = this->send_buffer(APIMessageType::HELLO_RESPONSE);
|
|
||||||
if (!success) {
|
|
||||||
this->fatal_error_();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->connection_state_ = ConnectionState::WAITING_FOR_CONNECT;
|
|
||||||
}
|
|
||||||
void APIConnection::on_connect_request_(const ConnectRequest &req) {
|
|
||||||
ESP_LOGVV(TAG, "on_connect_request_(password='%s')", req.get_password().c_str());
|
|
||||||
bool correct = this->parent_->check_password(req.get_password());
|
|
||||||
auto buffer = this->get_buffer();
|
|
||||||
// bool invalid_password = 1;
|
|
||||||
buffer.encode_bool(1, !correct);
|
|
||||||
bool success = this->send_buffer(APIMessageType::CONNECT_RESPONSE);
|
|
||||||
if (!success) {
|
|
||||||
this->fatal_error_();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (correct) {
|
|
||||||
ESP_LOGD(TAG, "Client '%s' connected successfully!", this->client_info_.c_str());
|
|
||||||
this->connection_state_ = ConnectionState::CONNECTED;
|
|
||||||
|
|
||||||
#ifdef USE_HOMEASSISTANT_TIME
|
|
||||||
if (homeassistant::global_homeassistant_time != nullptr) {
|
|
||||||
this->send_time_request();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void APIConnection::on_disconnect_request_(const DisconnectRequest &req) {
|
|
||||||
ESP_LOGVV(TAG, "on_disconnect_request_");
|
|
||||||
// remote initiated disconnect_client
|
|
||||||
if (!this->send_empty_message(APIMessageType::DISCONNECT_RESPONSE)) {
|
|
||||||
this->fatal_error_();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->disconnect_client();
|
|
||||||
}
|
|
||||||
void APIConnection::on_disconnect_response_(const DisconnectResponse &req) {
|
|
||||||
ESP_LOGVV(TAG, "on_disconnect_response_");
|
|
||||||
// we initiated disconnect_client
|
|
||||||
this->disconnect_client();
|
|
||||||
}
|
|
||||||
void APIConnection::on_ping_request_(const PingRequest &req) {
|
|
||||||
ESP_LOGVV(TAG, "on_ping_request_");
|
|
||||||
PingResponse resp;
|
|
||||||
this->send_message(resp);
|
|
||||||
}
|
|
||||||
void APIConnection::on_ping_response_(const PingResponse &req) {
|
|
||||||
ESP_LOGVV(TAG, "on_ping_response_");
|
|
||||||
// we initiated ping
|
|
||||||
this->sent_ping_ = false;
|
|
||||||
}
|
|
||||||
void APIConnection::on_device_info_request_(const DeviceInfoRequest &req) {
|
|
||||||
ESP_LOGVV(TAG, "on_device_info_request_");
|
|
||||||
auto buffer = this->get_buffer();
|
|
||||||
// bool uses_password = 1;
|
|
||||||
buffer.encode_bool(1, this->parent_->uses_password());
|
|
||||||
// string name = 2;
|
|
||||||
buffer.encode_string(2, App.get_name());
|
|
||||||
// string mac_address = 3;
|
|
||||||
buffer.encode_string(3, get_mac_address_pretty());
|
|
||||||
// string esphome_version = 4;
|
|
||||||
buffer.encode_string(4, ESPHOME_VERSION);
|
|
||||||
// string compilation_time = 5;
|
|
||||||
buffer.encode_string(5, App.get_compilation_time());
|
|
||||||
#ifdef ARDUINO_BOARD
|
|
||||||
// string model = 6;
|
|
||||||
buffer.encode_string(6, ARDUINO_BOARD);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_DEEP_SLEEP
|
|
||||||
// bool has_deep_sleep = 7;
|
|
||||||
buffer.encode_bool(7, deep_sleep::global_has_deep_sleep);
|
|
||||||
#endif
|
|
||||||
this->send_buffer(APIMessageType::DEVICE_INFO_RESPONSE);
|
|
||||||
}
|
|
||||||
void APIConnection::on_list_entities_request_(const ListEntitiesRequest &req) {
|
|
||||||
ESP_LOGVV(TAG, "on_list_entities_request_");
|
|
||||||
this->list_entities_iterator_.begin();
|
|
||||||
}
|
|
||||||
void APIConnection::on_subscribe_states_request_(const SubscribeStatesRequest &req) {
|
|
||||||
ESP_LOGVV(TAG, "on_subscribe_states_request_");
|
|
||||||
this->state_subscription_ = true;
|
|
||||||
this->initial_state_iterator_.begin();
|
|
||||||
}
|
|
||||||
void APIConnection::on_subscribe_logs_request_(const SubscribeLogsRequest &req) {
|
|
||||||
ESP_LOGVV(TAG, "on_subscribe_logs_request_");
|
|
||||||
this->log_subscription_ = req.get_level();
|
|
||||||
if (req.get_dump_config()) {
|
|
||||||
App.schedule_dump_config();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void APIConnection::fatal_error_() {
|
|
||||||
this->client_->close();
|
|
||||||
this->remove_ = true;
|
|
||||||
}
|
|
||||||
bool APIConnection::valid_rx_message_type_(uint32_t type) {
|
|
||||||
switch (static_cast<APIMessageType>(type)) {
|
|
||||||
case APIMessageType::HELLO_RESPONSE:
|
|
||||||
case APIMessageType::CONNECT_RESPONSE:
|
|
||||||
return false;
|
|
||||||
case APIMessageType::HELLO_REQUEST:
|
|
||||||
return this->connection_state_ == ConnectionState::WAITING_FOR_HELLO;
|
|
||||||
case APIMessageType::CONNECT_REQUEST:
|
|
||||||
return this->connection_state_ == ConnectionState::WAITING_FOR_CONNECT;
|
|
||||||
case APIMessageType::PING_REQUEST:
|
|
||||||
case APIMessageType::PING_RESPONSE:
|
|
||||||
case APIMessageType::DISCONNECT_REQUEST:
|
|
||||||
case APIMessageType::DISCONNECT_RESPONSE:
|
|
||||||
case APIMessageType::DEVICE_INFO_REQUEST:
|
|
||||||
if (this->connection_state_ == ConnectionState::WAITING_FOR_CONNECT)
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return this->connection_state_ == ConnectionState::CONNECTED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool APIConnection::send_message(APIMessage &msg) {
|
|
||||||
this->send_buffer_.clear();
|
|
||||||
APIBuffer buf(&this->send_buffer_);
|
|
||||||
msg.encode(buf);
|
|
||||||
return this->send_buffer(msg.message_type());
|
|
||||||
}
|
|
||||||
bool APIConnection::send_empty_message(APIMessageType type) {
|
|
||||||
this->send_buffer_.clear();
|
|
||||||
return this->send_buffer(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
void APIConnection::disconnect_client() {
|
|
||||||
this->client_->close();
|
|
||||||
this->remove_ = true;
|
|
||||||
}
|
|
||||||
void encode_varint(uint8_t *dat, uint8_t *len, uint32_t value) {
|
|
||||||
if (value <= 0x7F) {
|
|
||||||
*dat = value;
|
|
||||||
(*len)++;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (value) {
|
|
||||||
uint8_t temp = value & 0x7F;
|
|
||||||
value >>= 7;
|
|
||||||
if (value) {
|
|
||||||
*dat = temp | 0x80;
|
|
||||||
} else {
|
|
||||||
*dat = temp;
|
|
||||||
}
|
|
||||||
dat++;
|
|
||||||
(*len)++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool APIConnection::send_buffer(APIMessageType type) {
|
|
||||||
uint8_t header[20];
|
|
||||||
header[0] = 0x00;
|
|
||||||
uint8_t header_len = 1;
|
|
||||||
encode_varint(header + header_len, &header_len, this->send_buffer_.size());
|
|
||||||
encode_varint(header + header_len, &header_len, static_cast<uint32_t>(type));
|
|
||||||
|
|
||||||
size_t needed_space = this->send_buffer_.size() + header_len;
|
|
||||||
|
|
||||||
if (needed_space > this->client_->space()) {
|
|
||||||
delay(5);
|
|
||||||
if (needed_space > this->client_->space()) {
|
|
||||||
if (type != APIMessageType::SUBSCRIBE_LOGS_RESPONSE) {
|
|
||||||
ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
|
|
||||||
}
|
|
||||||
delay(5);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// char buffer[512];
|
|
||||||
// uint32_t offset = 0;
|
|
||||||
// for (int j = 0; j < header_len; j++) {
|
|
||||||
// offset += snprintf(buffer + offset, 512 - offset, "0x%02X ", header[j]);
|
|
||||||
// }
|
|
||||||
// offset += snprintf(buffer + offset, 512 - offset, "| ");
|
|
||||||
// for (auto &it : this->send_buffer_) {
|
|
||||||
// int i = snprintf(buffer + offset, 512 - offset, "0x%02X ", it);
|
|
||||||
// if (i <= 0)
|
|
||||||
// break;
|
|
||||||
// offset += i;
|
|
||||||
// }
|
|
||||||
// ESP_LOGVV(TAG, "SEND %s", buffer);
|
|
||||||
|
|
||||||
this->client_->add(reinterpret_cast<char *>(header), header_len);
|
|
||||||
this->client_->add(reinterpret_cast<char *>(this->send_buffer_.data()), this->send_buffer_.size());
|
|
||||||
return this->client_->send();
|
|
||||||
}
|
|
||||||
|
|
||||||
void APIConnection::loop() {
|
|
||||||
if (!network_is_connected()) {
|
|
||||||
// when network is disconnected force disconnect immediately
|
|
||||||
// don't wait for timeout
|
|
||||||
this->fatal_error_();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this->client_->disconnected()) {
|
|
||||||
// failsafe for disconnect logic
|
|
||||||
this->on_disconnect_();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->parse_recv_buffer_();
|
|
||||||
|
|
||||||
this->list_entities_iterator_.advance();
|
|
||||||
this->initial_state_iterator_.advance();
|
|
||||||
|
|
||||||
const uint32_t keepalive = 60000;
|
|
||||||
if (this->sent_ping_) {
|
|
||||||
if (millis() - this->last_traffic_ > (keepalive * 3) / 2) {
|
|
||||||
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) {
|
|
||||||
this->sent_ping_ = true;
|
|
||||||
this->send_ping_request();
|
|
||||||
}
|
|
||||||
|
|
||||||
#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->get_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(APIMessageType::CAMERA_IMAGE_RESPONSE);
|
|
||||||
if (success) {
|
|
||||||
this->image_reader_.consume_data(to_send);
|
|
||||||
}
|
|
||||||
if (success && done) {
|
|
||||||
this->image_reader_.return_image();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef USE_BINARY_SENSOR
|
|
||||||
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state) {
|
|
||||||
if (!this->state_subscription_)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto buffer = this->get_buffer();
|
|
||||||
// fixed32 key = 1;
|
|
||||||
buffer.encode_fixed32(1, binary_sensor->get_object_id_hash());
|
|
||||||
// bool state = 2;
|
|
||||||
buffer.encode_bool(2, state);
|
|
||||||
return this->send_buffer(APIMessageType::BINARY_SENSOR_STATE_RESPONSE);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_COVER
|
|
||||||
bool APIConnection::send_cover_state(cover::Cover *cover) {
|
|
||||||
if (!this->state_subscription_)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto buffer = this->get_buffer();
|
|
||||||
auto traits = cover->get_traits();
|
|
||||||
// fixed32 key = 1;
|
|
||||||
buffer.encode_fixed32(1, cover->get_object_id_hash());
|
|
||||||
// enum LegacyCoverState {
|
|
||||||
// OPEN = 0;
|
|
||||||
// CLOSED = 1;
|
|
||||||
// }
|
|
||||||
// LegacyCoverState legacy_state = 2;
|
|
||||||
uint32_t state = (cover->position == cover::COVER_OPEN) ? 0 : 1;
|
|
||||||
buffer.encode_uint32(2, state);
|
|
||||||
// float position = 3;
|
|
||||||
buffer.encode_float(3, cover->position);
|
|
||||||
if (traits.get_supports_tilt()) {
|
|
||||||
// float tilt = 4;
|
|
||||||
buffer.encode_float(4, cover->tilt);
|
|
||||||
}
|
|
||||||
// enum CoverCurrentOperation {
|
|
||||||
// IDLE = 0;
|
|
||||||
// IS_OPENING = 1;
|
|
||||||
// IS_CLOSING = 2;
|
|
||||||
// }
|
|
||||||
// CoverCurrentOperation current_operation = 5;
|
|
||||||
buffer.encode_uint32(5, cover->current_operation);
|
|
||||||
return this->send_buffer(APIMessageType::COVER_STATE_RESPONSE);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_FAN
|
|
||||||
bool APIConnection::send_fan_state(fan::FanState *fan) {
|
|
||||||
if (!this->state_subscription_)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto buffer = this->get_buffer();
|
|
||||||
// fixed32 key = 1;
|
|
||||||
buffer.encode_fixed32(1, fan->get_object_id_hash());
|
|
||||||
// bool state = 2;
|
|
||||||
buffer.encode_bool(2, fan->state);
|
|
||||||
// bool oscillating = 3;
|
|
||||||
if (fan->get_traits().supports_oscillation()) {
|
|
||||||
buffer.encode_bool(3, fan->oscillating);
|
|
||||||
}
|
|
||||||
// enum FanSpeed {
|
|
||||||
// LOW = 0;
|
|
||||||
// MEDIUM = 1;
|
|
||||||
// HIGH = 2;
|
|
||||||
// }
|
|
||||||
// FanSpeed speed = 4;
|
|
||||||
if (fan->get_traits().supports_speed()) {
|
|
||||||
buffer.encode_uint32(4, fan->speed);
|
|
||||||
}
|
|
||||||
return this->send_buffer(APIMessageType::FAN_STATE_RESPONSE);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_LIGHT
|
|
||||||
bool APIConnection::send_light_state(light::LightState *light) {
|
|
||||||
if (!this->state_subscription_)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto buffer = this->get_buffer();
|
|
||||||
auto traits = light->get_traits();
|
|
||||||
auto values = light->remote_values;
|
|
||||||
|
|
||||||
// fixed32 key = 1;
|
|
||||||
buffer.encode_fixed32(1, light->get_object_id_hash());
|
|
||||||
// bool state = 2;
|
|
||||||
buffer.encode_bool(2, values.get_state() != 0.0f);
|
|
||||||
// float brightness = 3;
|
|
||||||
if (traits.get_supports_brightness()) {
|
|
||||||
buffer.encode_float(3, values.get_brightness());
|
|
||||||
}
|
|
||||||
if (traits.get_supports_rgb()) {
|
|
||||||
// float red = 4;
|
|
||||||
buffer.encode_float(4, values.get_red());
|
|
||||||
// float green = 5;
|
|
||||||
buffer.encode_float(5, values.get_green());
|
|
||||||
// float blue = 6;
|
|
||||||
buffer.encode_float(6, values.get_blue());
|
|
||||||
}
|
|
||||||
// float white = 7;
|
|
||||||
if (traits.get_supports_rgb_white_value()) {
|
|
||||||
buffer.encode_float(7, values.get_white());
|
|
||||||
}
|
|
||||||
// float color_temperature = 8;
|
|
||||||
if (traits.get_supports_color_temperature()) {
|
|
||||||
buffer.encode_float(8, values.get_color_temperature());
|
|
||||||
}
|
|
||||||
// string effect = 9;
|
|
||||||
if (light->supports_effects()) {
|
|
||||||
buffer.encode_string(9, light->get_effect_name());
|
|
||||||
}
|
|
||||||
return this->send_buffer(APIMessageType::LIGHT_STATE_RESPONSE);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_SENSOR
|
|
||||||
bool APIConnection::send_sensor_state(sensor::Sensor *sensor, float state) {
|
|
||||||
if (!this->state_subscription_)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto buffer = this->get_buffer();
|
|
||||||
// fixed32 key = 1;
|
|
||||||
buffer.encode_fixed32(1, sensor->get_object_id_hash());
|
|
||||||
// float state = 2;
|
|
||||||
buffer.encode_float(2, state);
|
|
||||||
return this->send_buffer(APIMessageType::SENSOR_STATE_RESPONSE);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_SWITCH
|
|
||||||
bool APIConnection::send_switch_state(switch_::Switch *a_switch, bool state) {
|
|
||||||
if (!this->state_subscription_)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto buffer = this->get_buffer();
|
|
||||||
// fixed32 key = 1;
|
|
||||||
buffer.encode_fixed32(1, a_switch->get_object_id_hash());
|
|
||||||
// bool state = 2;
|
|
||||||
buffer.encode_bool(2, state);
|
|
||||||
return this->send_buffer(APIMessageType::SWITCH_STATE_RESPONSE);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_TEXT_SENSOR
|
|
||||||
bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state) {
|
|
||||||
if (!this->state_subscription_)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto buffer = this->get_buffer();
|
|
||||||
// fixed32 key = 1;
|
|
||||||
buffer.encode_fixed32(1, text_sensor->get_object_id_hash());
|
|
||||||
// string state = 2;
|
|
||||||
buffer.encode_string(2, state);
|
|
||||||
return this->send_buffer(APIMessageType::TEXT_SENSOR_STATE_RESPONSE);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_CLIMATE
|
|
||||||
bool APIConnection::send_climate_state(climate::Climate *climate) {
|
|
||||||
if (!this->state_subscription_)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto buffer = this->get_buffer();
|
|
||||||
auto traits = climate->get_traits();
|
|
||||||
// fixed32 key = 1;
|
|
||||||
buffer.encode_fixed32(1, climate->get_object_id_hash());
|
|
||||||
// ClimateMode mode = 2;
|
|
||||||
buffer.encode_uint32(2, static_cast<uint32_t>(climate->mode));
|
|
||||||
// float current_temperature = 3;
|
|
||||||
if (traits.get_supports_current_temperature()) {
|
|
||||||
buffer.encode_float(3, climate->current_temperature);
|
|
||||||
}
|
|
||||||
if (traits.get_supports_two_point_target_temperature()) {
|
|
||||||
// float target_temperature_low = 5;
|
|
||||||
buffer.encode_float(5, climate->target_temperature_low);
|
|
||||||
// float target_temperature_high = 6;
|
|
||||||
buffer.encode_float(6, climate->target_temperature_high);
|
|
||||||
} else {
|
|
||||||
// float target_temperature = 4;
|
|
||||||
buffer.encode_float(4, climate->target_temperature);
|
|
||||||
}
|
|
||||||
// bool away = 7;
|
|
||||||
if (traits.get_supports_away()) {
|
|
||||||
buffer.encode_bool(7, climate->away);
|
|
||||||
}
|
|
||||||
return this->send_buffer(APIMessageType::CLIMATE_STATE_RESPONSE);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
|
|
||||||
if (this->log_subscription_ < level)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto buffer = this->get_buffer();
|
|
||||||
// LogLevel level = 1;
|
|
||||||
buffer.encode_uint32(1, static_cast<uint32_t>(level));
|
|
||||||
// string tag = 2;
|
|
||||||
// buffer.encode_string(2, tag, strlen(tag));
|
|
||||||
// string message = 3;
|
|
||||||
buffer.encode_string(3, line, strlen(line));
|
|
||||||
bool success = this->send_buffer(APIMessageType::SUBSCRIBE_LOGS_RESPONSE);
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
buffer = this->get_buffer();
|
|
||||||
// bool send_failed = 4;
|
|
||||||
buffer.encode_bool(4, true);
|
|
||||||
return this->send_buffer(APIMessageType::SUBSCRIBE_LOGS_RESPONSE);
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool APIConnection::send_disconnect_request() {
|
|
||||||
DisconnectRequest req;
|
|
||||||
return this->send_message(req);
|
|
||||||
}
|
|
||||||
bool APIConnection::send_ping_request() {
|
|
||||||
ESP_LOGVV(TAG, "Sending ping...");
|
|
||||||
PingRequest req;
|
|
||||||
return this->send_message(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef USE_COVER
|
|
||||||
void APIConnection::on_cover_command_request_(const CoverCommandRequest &req) {
|
|
||||||
ESP_LOGVV(TAG, "on_cover_command_request_");
|
|
||||||
cover::Cover *cover = App.get_cover_by_key(req.get_key());
|
|
||||||
if (cover == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto call = cover->make_call();
|
|
||||||
if (req.get_legacy_command().has_value()) {
|
|
||||||
auto cmd = *req.get_legacy_command();
|
|
||||||
switch (cmd) {
|
|
||||||
case LEGACY_COVER_COMMAND_OPEN:
|
|
||||||
call.set_command_open();
|
|
||||||
break;
|
|
||||||
case LEGACY_COVER_COMMAND_CLOSE:
|
|
||||||
call.set_command_close();
|
|
||||||
break;
|
|
||||||
case LEGACY_COVER_COMMAND_STOP:
|
|
||||||
call.set_command_stop();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (req.get_position().has_value()) {
|
|
||||||
auto pos = *req.get_position();
|
|
||||||
call.set_position(pos);
|
|
||||||
}
|
|
||||||
if (req.get_tilt().has_value()) {
|
|
||||||
auto tilt = *req.get_tilt();
|
|
||||||
call.set_tilt(tilt);
|
|
||||||
}
|
|
||||||
if (req.get_stop()) {
|
|
||||||
call.set_command_stop();
|
|
||||||
}
|
|
||||||
call.perform();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_FAN
|
|
||||||
void APIConnection::on_fan_command_request_(const FanCommandRequest &req) {
|
|
||||||
ESP_LOGVV(TAG, "on_fan_command_request_");
|
|
||||||
fan::FanState *fan = App.get_fan_by_key(req.get_key());
|
|
||||||
if (fan == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto call = fan->make_call();
|
|
||||||
call.set_state(req.get_state());
|
|
||||||
call.set_oscillating(req.get_oscillating());
|
|
||||||
call.set_speed(req.get_speed());
|
|
||||||
call.perform();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_LIGHT
|
|
||||||
void APIConnection::on_light_command_request_(const LightCommandRequest &req) {
|
|
||||||
ESP_LOGVV(TAG, "on_light_command_request_");
|
|
||||||
light::LightState *light = App.get_light_by_key(req.get_key());
|
|
||||||
if (light == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto call = light->make_call();
|
|
||||||
call.set_state(req.get_state());
|
|
||||||
call.set_brightness(req.get_brightness());
|
|
||||||
call.set_red(req.get_red());
|
|
||||||
call.set_green(req.get_green());
|
|
||||||
call.set_blue(req.get_blue());
|
|
||||||
call.set_white(req.get_white());
|
|
||||||
call.set_color_temperature(req.get_color_temperature());
|
|
||||||
call.set_transition_length(req.get_transition_length());
|
|
||||||
call.set_flash_length(req.get_flash_length());
|
|
||||||
call.set_effect(req.get_effect());
|
|
||||||
call.perform();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_SWITCH
|
|
||||||
void APIConnection::on_switch_command_request_(const SwitchCommandRequest &req) {
|
|
||||||
ESP_LOGVV(TAG, "on_switch_command_request_");
|
|
||||||
switch_::Switch *a_switch = App.get_switch_by_key(req.get_key());
|
|
||||||
if (a_switch == nullptr || a_switch->is_internal())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (req.get_state()) {
|
|
||||||
a_switch->turn_on();
|
|
||||||
} else {
|
|
||||||
a_switch->turn_off();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_CLIMATE
|
|
||||||
void APIConnection::on_climate_command_request_(const ClimateCommandRequest &req) {
|
|
||||||
ESP_LOGVV(TAG, "on_climate_command_request_");
|
|
||||||
climate::Climate *climate = App.get_climate_by_key(req.get_key());
|
|
||||||
if (climate == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto call = climate->make_call();
|
|
||||||
if (req.get_mode().has_value())
|
|
||||||
call.set_mode(*req.get_mode());
|
|
||||||
if (req.get_target_temperature().has_value())
|
|
||||||
call.set_target_temperature(*req.get_target_temperature());
|
|
||||||
if (req.get_target_temperature_low().has_value())
|
|
||||||
call.set_target_temperature_low(*req.get_target_temperature_low());
|
|
||||||
if (req.get_target_temperature_high().has_value())
|
|
||||||
call.set_target_temperature_high(*req.get_target_temperature_high());
|
|
||||||
if (req.get_away().has_value())
|
|
||||||
call.set_away(*req.get_away());
|
|
||||||
call.perform();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void APIConnection::on_subscribe_service_calls_request_(const SubscribeServiceCallsRequest &req) {
|
|
||||||
this->service_call_subscription_ = true;
|
|
||||||
}
|
|
||||||
void APIConnection::send_service_call(ServiceCallResponse &call) {
|
|
||||||
if (!this->service_call_subscription_)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this->send_message(call);
|
|
||||||
}
|
|
||||||
void APIConnection::on_subscribe_home_assistant_states_request_(const SubscribeHomeAssistantStatesRequest &req) {
|
|
||||||
for (auto &it : this->parent_->get_state_subs()) {
|
|
||||||
auto buffer = this->get_buffer();
|
|
||||||
// string entity_id = 1;
|
|
||||||
buffer.encode_string(1, it.entity_id);
|
|
||||||
this->send_buffer(APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATE_RESPONSE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void APIConnection::on_home_assistant_state_response_(const HomeAssistantStateResponse &req) {
|
|
||||||
for (auto &it : this->parent_->get_state_subs()) {
|
|
||||||
if (it.entity_id == req.get_entity_id()) {
|
|
||||||
it.callback(req.get_state());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void APIConnection::on_execute_service_(const ExecuteServiceRequest &req) {
|
|
||||||
ESP_LOGVV(TAG, "on_execute_service_");
|
|
||||||
bool found = false;
|
|
||||||
for (auto *service : this->parent_->get_user_services()) {
|
|
||||||
if (service->execute_service(req)) {
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) {
|
|
||||||
ESP_LOGV(TAG, "Could not find matching service!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
APIBuffer APIConnection::get_buffer() {
|
|
||||||
this->send_buffer_.clear();
|
|
||||||
return {&this->send_buffer_};
|
|
||||||
}
|
|
||||||
#ifdef USE_HOMEASSISTANT_TIME
|
|
||||||
void APIConnection::send_time_request() { this->send_empty_message(APIMessageType::GET_TIME_REQUEST); }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_ESP32_CAMERA
|
|
||||||
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
|
|
||||||
if (!this->state_subscription_)
|
|
||||||
return;
|
|
||||||
if (this->image_reader_.available())
|
|
||||||
return;
|
|
||||||
this->image_reader_.set_image(image);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_ESP32_CAMERA
|
|
||||||
void APIConnection::on_camera_image_request_(const CameraImageRequest &req) {
|
|
||||||
if (esp32_camera::global_esp32_camera == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ESP_LOGV(TAG, "on_camera_image_request_ stream=%s single=%s", YESNO(req.get_stream()), YESNO(req.get_single()));
|
|
||||||
if (req.get_single()) {
|
|
||||||
esp32_camera::global_esp32_camera->request_image();
|
|
||||||
}
|
|
||||||
if (req.get_stream()) {
|
|
||||||
esp32_camera::global_esp32_camera->request_stream();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -4,14 +4,12 @@
|
||||||
#include "esphome/core/controller.h"
|
#include "esphome/core/controller.h"
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "api_pb2.h"
|
||||||
|
#include "api_pb2_service.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "api_message.h"
|
|
||||||
#include "basic_messages.h"
|
|
||||||
#include "list_entities.h"
|
#include "list_entities.h"
|
||||||
#include "subscribe_state.h"
|
#include "subscribe_state.h"
|
||||||
#include "subscribe_logs.h"
|
#include "homeassistant_service.h"
|
||||||
#include "command_messages.h"
|
|
||||||
#include "service_call_message.h"
|
|
||||||
#include "user_services.h"
|
#include "user_services.h"
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
@ -24,130 +22,6 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
class APIServer;
|
|
||||||
|
|
||||||
class APIConnection {
|
|
||||||
public:
|
|
||||||
APIConnection(AsyncClient *client, APIServer *parent);
|
|
||||||
~APIConnection();
|
|
||||||
|
|
||||||
void disconnect_client();
|
|
||||||
APIBuffer get_buffer();
|
|
||||||
bool send_buffer(APIMessageType type);
|
|
||||||
bool send_message(APIMessage &msg);
|
|
||||||
bool send_empty_message(APIMessageType type);
|
|
||||||
void loop();
|
|
||||||
|
|
||||||
#ifdef USE_BINARY_SENSOR
|
|
||||||
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_COVER
|
|
||||||
bool send_cover_state(cover::Cover *cover);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_FAN
|
|
||||||
bool send_fan_state(fan::FanState *fan);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_LIGHT
|
|
||||||
bool send_light_state(light::LightState *light);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_SENSOR
|
|
||||||
bool send_sensor_state(sensor::Sensor *sensor, float state);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_SWITCH
|
|
||||||
bool send_switch_state(switch_::Switch *a_switch, bool state);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_TEXT_SENSOR
|
|
||||||
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_ESP32_CAMERA
|
|
||||||
void send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_CLIMATE
|
|
||||||
bool send_climate_state(climate::Climate *climate);
|
|
||||||
#endif
|
|
||||||
bool send_log_message(int level, const char *tag, const char *line);
|
|
||||||
bool send_disconnect_request();
|
|
||||||
bool send_ping_request();
|
|
||||||
void send_service_call(ServiceCallResponse &call);
|
|
||||||
#ifdef USE_HOMEASSISTANT_TIME
|
|
||||||
void send_time_request();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
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 fatal_error_();
|
|
||||||
bool valid_rx_message_type_(uint32_t msg_type);
|
|
||||||
void read_message_(uint32_t size, uint32_t type, uint8_t *msg);
|
|
||||||
void parse_recv_buffer_();
|
|
||||||
|
|
||||||
// request types
|
|
||||||
void on_hello_request_(const HelloRequest &req);
|
|
||||||
void on_connect_request_(const ConnectRequest &req);
|
|
||||||
void on_disconnect_request_(const DisconnectRequest &req);
|
|
||||||
void on_disconnect_response_(const DisconnectResponse &req);
|
|
||||||
void on_ping_request_(const PingRequest &req);
|
|
||||||
void on_ping_response_(const PingResponse &req);
|
|
||||||
void on_device_info_request_(const DeviceInfoRequest &req);
|
|
||||||
void on_list_entities_request_(const ListEntitiesRequest &req);
|
|
||||||
void on_subscribe_states_request_(const SubscribeStatesRequest &req);
|
|
||||||
void on_subscribe_logs_request_(const SubscribeLogsRequest &req);
|
|
||||||
#ifdef USE_COVER
|
|
||||||
void on_cover_command_request_(const CoverCommandRequest &req);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_FAN
|
|
||||||
void on_fan_command_request_(const FanCommandRequest &req);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_LIGHT
|
|
||||||
void on_light_command_request_(const LightCommandRequest &req);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_SWITCH
|
|
||||||
void on_switch_command_request_(const SwitchCommandRequest &req);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_CLIMATE
|
|
||||||
void on_climate_command_request_(const ClimateCommandRequest &req);
|
|
||||||
#endif
|
|
||||||
void on_subscribe_service_calls_request_(const SubscribeServiceCallsRequest &req);
|
|
||||||
void on_subscribe_home_assistant_states_request_(const SubscribeHomeAssistantStatesRequest &req);
|
|
||||||
void on_home_assistant_state_response_(const HomeAssistantStateResponse &req);
|
|
||||||
void on_execute_service_(const ExecuteServiceRequest &req);
|
|
||||||
#ifdef USE_ESP32_CAMERA
|
|
||||||
void on_camera_image_request_(const CameraImageRequest &req);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
enum class ConnectionState {
|
|
||||||
WAITING_FOR_HELLO,
|
|
||||||
WAITING_FOR_CONNECT,
|
|
||||||
CONNECTED,
|
|
||||||
} connection_state_{ConnectionState::WAITING_FOR_HELLO};
|
|
||||||
|
|
||||||
bool remove_{false};
|
|
||||||
|
|
||||||
std::vector<uint8_t> send_buffer_;
|
|
||||||
std::vector<uint8_t> recv_buffer_;
|
|
||||||
|
|
||||||
std::string client_info_;
|
|
||||||
#ifdef USE_ESP32_CAMERA
|
|
||||||
esp32_camera::CameraImageReader image_reader_;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool state_subscription_{false};
|
|
||||||
int log_subscription_{ESPHOME_LOG_LEVEL_NONE};
|
|
||||||
uint32_t last_traffic_;
|
|
||||||
bool sent_ping_{false};
|
|
||||||
bool service_call_subscription_{false};
|
|
||||||
AsyncClient *client_;
|
|
||||||
APIServer *parent_;
|
|
||||||
InitialStateIterator initial_state_iterator_;
|
|
||||||
ListEntitiesIterator list_entities_iterator_;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename... Ts> class HomeAssistantServiceCallAction;
|
|
||||||
|
|
||||||
class APIServer : public Component, public Controller {
|
class APIServer : public Component, public Controller {
|
||||||
public:
|
public:
|
||||||
APIServer();
|
APIServer();
|
||||||
|
@ -187,7 +61,7 @@ class APIServer : public Component, public Controller {
|
||||||
#ifdef USE_CLIMATE
|
#ifdef USE_CLIMATE
|
||||||
void on_climate_update(climate::Climate *obj) override;
|
void on_climate_update(climate::Climate *obj) override;
|
||||||
#endif
|
#endif
|
||||||
void send_service_call(ServiceCallResponse &call);
|
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
|
||||||
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||||
#ifdef USE_HOMEASSISTANT_TIME
|
#ifdef USE_HOMEASSISTANT_TIME
|
||||||
void request_time();
|
void request_time();
|
||||||
|
@ -217,22 +91,6 @@ class APIServer : public Component, public Controller {
|
||||||
|
|
||||||
extern APIServer *global_api_server;
|
extern APIServer *global_api_server;
|
||||||
|
|
||||||
template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
|
|
||||||
public:
|
|
||||||
explicit HomeAssistantServiceCallAction(APIServer *parent) : parent_(parent) {}
|
|
||||||
void set_service(const std::string &service) { this->resp_.set_service(service); }
|
|
||||||
void set_data(const std::vector<KeyValuePair> &data) { this->resp_.set_data(data); }
|
|
||||||
void set_data_template(const std::vector<KeyValuePair> &data_template) {
|
|
||||||
this->resp_.set_data_template(data_template);
|
|
||||||
}
|
|
||||||
void set_variables(const std::vector<TemplatableKeyValuePair> &variables) { this->resp_.set_variables(variables); }
|
|
||||||
void play(Ts... x) override { this->parent_->send_service_call(this->resp_); }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
APIServer *parent_;
|
|
||||||
ServiceCallResponse resp_;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
|
template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
|
||||||
public:
|
public:
|
||||||
bool check(Ts... x) override { return global_api_server->is_connected(); }
|
bool check(Ts... x) override { return global_api_server->is_connected(); }
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
#include "basic_messages.h"
|
|
||||||
#include "esphome/core/log.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace api {
|
|
||||||
|
|
||||||
// Hello
|
|
||||||
bool HelloRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 1: // string client_info = 1;
|
|
||||||
this->client_info_ = as_string(value, len);
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const std::string &HelloRequest::get_client_info() const { return this->client_info_; }
|
|
||||||
void HelloRequest::set_client_info(const std::string &client_info) { this->client_info_ = client_info; }
|
|
||||||
APIMessageType HelloRequest::message_type() const { return APIMessageType::HELLO_REQUEST; }
|
|
||||||
|
|
||||||
// Connect
|
|
||||||
bool ConnectRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 1: // string password = 1;
|
|
||||||
this->password_ = as_string(value, len);
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const std::string &ConnectRequest::get_password() const { return this->password_; }
|
|
||||||
void ConnectRequest::set_password(const std::string &password) { this->password_ = password; }
|
|
||||||
APIMessageType ConnectRequest::message_type() const { return APIMessageType::CONNECT_REQUEST; }
|
|
||||||
|
|
||||||
APIMessageType DeviceInfoRequest::message_type() const { return APIMessageType::DEVICE_INFO_REQUEST; }
|
|
||||||
APIMessageType DisconnectRequest::message_type() const { return APIMessageType::DISCONNECT_REQUEST; }
|
|
||||||
bool DisconnectRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 1: // string reason = 1;
|
|
||||||
this->reason_ = as_string(value, len);
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const std::string &DisconnectRequest::get_reason() const { return this->reason_; }
|
|
||||||
void DisconnectRequest::set_reason(const std::string &reason) { this->reason_ = reason; }
|
|
||||||
void DisconnectRequest::encode(APIBuffer &buffer) {
|
|
||||||
// string reason = 1;
|
|
||||||
buffer.encode_string(1, this->reason_);
|
|
||||||
}
|
|
||||||
APIMessageType DisconnectResponse::message_type() const { return APIMessageType::DISCONNECT_RESPONSE; }
|
|
||||||
APIMessageType PingRequest::message_type() const { return APIMessageType::PING_REQUEST; }
|
|
||||||
APIMessageType PingResponse::message_type() const { return APIMessageType::PING_RESPONSE; }
|
|
||||||
|
|
||||||
} // namespace api
|
|
||||||
} // namespace esphome
|
|
|
@ -1,63 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "api_message.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace api {
|
|
||||||
|
|
||||||
class HelloRequest : public APIMessage {
|
|
||||||
public:
|
|
||||||
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
|
|
||||||
const std::string &get_client_info() const;
|
|
||||||
void set_client_info(const std::string &client_info);
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::string client_info_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ConnectRequest : public APIMessage {
|
|
||||||
public:
|
|
||||||
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
|
|
||||||
const std::string &get_password() const;
|
|
||||||
void set_password(const std::string &password);
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::string password_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DeviceInfoRequest : public APIMessage {
|
|
||||||
public:
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DisconnectRequest : public APIMessage {
|
|
||||||
public:
|
|
||||||
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
|
|
||||||
void encode(APIBuffer &buffer) override;
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
const std::string &get_reason() const;
|
|
||||||
void set_reason(const std::string &reason);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::string reason_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DisconnectResponse : public APIMessage {
|
|
||||||
public:
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class PingRequest : public APIMessage {
|
|
||||||
public:
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class PingResponse : public APIMessage {
|
|
||||||
public:
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace api
|
|
||||||
} // namespace esphome
|
|
|
@ -1,417 +0,0 @@
|
||||||
#include "command_messages.h"
|
|
||||||
#include "esphome/core/log.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace api {
|
|
||||||
|
|
||||||
#ifdef USE_COVER
|
|
||||||
bool CoverCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 2:
|
|
||||||
// bool has_legacy_command = 2;
|
|
||||||
this->has_legacy_command_ = value;
|
|
||||||
return true;
|
|
||||||
case 3:
|
|
||||||
// enum LegacyCoverCommand {
|
|
||||||
// OPEN = 0;
|
|
||||||
// CLOSE = 1;
|
|
||||||
// STOP = 2;
|
|
||||||
// }
|
|
||||||
// LegacyCoverCommand legacy_command_ = 3;
|
|
||||||
this->legacy_command_ = static_cast<LegacyCoverCommand>(value);
|
|
||||||
return true;
|
|
||||||
case 4:
|
|
||||||
// bool has_position = 4;
|
|
||||||
this->has_position_ = value;
|
|
||||||
return true;
|
|
||||||
case 6:
|
|
||||||
// bool has_tilt = 6;
|
|
||||||
this->has_tilt_ = value;
|
|
||||||
return true;
|
|
||||||
case 8:
|
|
||||||
// bool stop = 8;
|
|
||||||
this->stop_ = value;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool CoverCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 1:
|
|
||||||
// fixed32 key = 1;
|
|
||||||
this->key_ = value;
|
|
||||||
return true;
|
|
||||||
case 5:
|
|
||||||
// float position = 5;
|
|
||||||
this->position_ = as_float(value);
|
|
||||||
return true;
|
|
||||||
case 7:
|
|
||||||
// float tilt = 7;
|
|
||||||
this->tilt_ = as_float(value);
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
APIMessageType CoverCommandRequest::message_type() const { return APIMessageType ::COVER_COMMAND_REQUEST; }
|
|
||||||
uint32_t CoverCommandRequest::get_key() const { return this->key_; }
|
|
||||||
optional<LegacyCoverCommand> CoverCommandRequest::get_legacy_command() const {
|
|
||||||
if (!this->has_legacy_command_)
|
|
||||||
return {};
|
|
||||||
return this->legacy_command_;
|
|
||||||
}
|
|
||||||
optional<float> CoverCommandRequest::get_position() const {
|
|
||||||
if (!this->has_position_)
|
|
||||||
return {};
|
|
||||||
return this->position_;
|
|
||||||
}
|
|
||||||
optional<float> CoverCommandRequest::get_tilt() const {
|
|
||||||
if (!this->has_tilt_)
|
|
||||||
return {};
|
|
||||||
return this->tilt_;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_FAN
|
|
||||||
bool FanCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 2:
|
|
||||||
// bool has_state = 2;
|
|
||||||
this->has_state_ = value;
|
|
||||||
return true;
|
|
||||||
case 3:
|
|
||||||
// bool state = 3;
|
|
||||||
this->state_ = value;
|
|
||||||
return true;
|
|
||||||
case 4:
|
|
||||||
// bool has_speed = 4;
|
|
||||||
this->has_speed_ = value;
|
|
||||||
return true;
|
|
||||||
case 5:
|
|
||||||
// FanSpeed speed = 5;
|
|
||||||
this->speed_ = static_cast<fan::FanSpeed>(value);
|
|
||||||
return true;
|
|
||||||
case 6:
|
|
||||||
// bool has_oscillating = 6;
|
|
||||||
this->has_oscillating_ = value;
|
|
||||||
return true;
|
|
||||||
case 7:
|
|
||||||
// bool oscillating = 7;
|
|
||||||
this->oscillating_ = value;
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool FanCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 1:
|
|
||||||
// fixed32 key = 1;
|
|
||||||
this->key_ = value;
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
APIMessageType FanCommandRequest::message_type() const { return APIMessageType::FAN_COMMAND_REQUEST; }
|
|
||||||
uint32_t FanCommandRequest::get_key() const { return this->key_; }
|
|
||||||
optional<bool> FanCommandRequest::get_state() const {
|
|
||||||
if (!this->has_state_)
|
|
||||||
return {};
|
|
||||||
return this->state_;
|
|
||||||
}
|
|
||||||
optional<fan::FanSpeed> FanCommandRequest::get_speed() const {
|
|
||||||
if (!this->has_speed_)
|
|
||||||
return {};
|
|
||||||
return this->speed_;
|
|
||||||
}
|
|
||||||
optional<bool> FanCommandRequest::get_oscillating() const {
|
|
||||||
if (!this->has_oscillating_)
|
|
||||||
return {};
|
|
||||||
return this->oscillating_;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_LIGHT
|
|
||||||
bool LightCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 2:
|
|
||||||
// bool has_state = 2;
|
|
||||||
this->has_state_ = value;
|
|
||||||
return true;
|
|
||||||
case 3:
|
|
||||||
// bool state = 3;
|
|
||||||
this->state_ = value;
|
|
||||||
return true;
|
|
||||||
case 4:
|
|
||||||
// bool has_brightness = 4;
|
|
||||||
this->has_brightness_ = value;
|
|
||||||
return true;
|
|
||||||
case 6:
|
|
||||||
// bool has_rgb = 6;
|
|
||||||
this->has_rgb_ = value;
|
|
||||||
return true;
|
|
||||||
case 10:
|
|
||||||
// bool has_white = 10;
|
|
||||||
this->has_white_ = value;
|
|
||||||
return true;
|
|
||||||
case 12:
|
|
||||||
// bool has_color_temperature = 12;
|
|
||||||
this->has_color_temperature_ = value;
|
|
||||||
return true;
|
|
||||||
case 14:
|
|
||||||
// bool has_transition_length = 14;
|
|
||||||
this->has_transition_length_ = value;
|
|
||||||
return true;
|
|
||||||
case 15:
|
|
||||||
// uint32 transition_length = 15;
|
|
||||||
this->transition_length_ = value;
|
|
||||||
return true;
|
|
||||||
case 16:
|
|
||||||
// bool has_flash_length = 16;
|
|
||||||
this->has_flash_length_ = value;
|
|
||||||
return true;
|
|
||||||
case 17:
|
|
||||||
// uint32 flash_length = 17;
|
|
||||||
this->flash_length_ = value;
|
|
||||||
return true;
|
|
||||||
case 18:
|
|
||||||
// bool has_effect = 18;
|
|
||||||
this->has_effect_ = value;
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool LightCommandRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 19:
|
|
||||||
// string effect = 19;
|
|
||||||
this->effect_ = as_string(value, len);
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool LightCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 1:
|
|
||||||
// fixed32 key = 1;
|
|
||||||
this->key_ = value;
|
|
||||||
return true;
|
|
||||||
case 5:
|
|
||||||
// float brightness = 5;
|
|
||||||
this->brightness_ = as_float(value);
|
|
||||||
return true;
|
|
||||||
case 7:
|
|
||||||
// float red = 7;
|
|
||||||
this->red_ = as_float(value);
|
|
||||||
return true;
|
|
||||||
case 8:
|
|
||||||
// float green = 8;
|
|
||||||
this->green_ = as_float(value);
|
|
||||||
return true;
|
|
||||||
case 9:
|
|
||||||
// float blue = 9;
|
|
||||||
this->blue_ = as_float(value);
|
|
||||||
return true;
|
|
||||||
case 11:
|
|
||||||
// float white = 11;
|
|
||||||
this->white_ = as_float(value);
|
|
||||||
return true;
|
|
||||||
case 13:
|
|
||||||
// float color_temperature = 13;
|
|
||||||
this->color_temperature_ = as_float(value);
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
APIMessageType LightCommandRequest::message_type() const { return APIMessageType::LIGHT_COMMAND_REQUEST; }
|
|
||||||
uint32_t LightCommandRequest::get_key() const { return this->key_; }
|
|
||||||
optional<bool> LightCommandRequest::get_state() const {
|
|
||||||
if (!this->has_state_)
|
|
||||||
return {};
|
|
||||||
return this->state_;
|
|
||||||
}
|
|
||||||
optional<float> LightCommandRequest::get_brightness() const {
|
|
||||||
if (!this->has_brightness_)
|
|
||||||
return {};
|
|
||||||
return this->brightness_;
|
|
||||||
}
|
|
||||||
optional<float> LightCommandRequest::get_red() const {
|
|
||||||
if (!this->has_rgb_)
|
|
||||||
return {};
|
|
||||||
return this->red_;
|
|
||||||
}
|
|
||||||
optional<float> LightCommandRequest::get_green() const {
|
|
||||||
if (!this->has_rgb_)
|
|
||||||
return {};
|
|
||||||
return this->green_;
|
|
||||||
}
|
|
||||||
optional<float> LightCommandRequest::get_blue() const {
|
|
||||||
if (!this->has_rgb_)
|
|
||||||
return {};
|
|
||||||
return this->blue_;
|
|
||||||
}
|
|
||||||
optional<float> LightCommandRequest::get_white() const {
|
|
||||||
if (!this->has_white_)
|
|
||||||
return {};
|
|
||||||
return this->white_;
|
|
||||||
}
|
|
||||||
optional<float> LightCommandRequest::get_color_temperature() const {
|
|
||||||
if (!this->has_color_temperature_)
|
|
||||||
return {};
|
|
||||||
return this->color_temperature_;
|
|
||||||
}
|
|
||||||
optional<uint32_t> LightCommandRequest::get_transition_length() const {
|
|
||||||
if (!this->has_transition_length_)
|
|
||||||
return {};
|
|
||||||
return this->transition_length_;
|
|
||||||
}
|
|
||||||
optional<uint32_t> LightCommandRequest::get_flash_length() const {
|
|
||||||
if (!this->has_flash_length_)
|
|
||||||
return {};
|
|
||||||
return this->flash_length_;
|
|
||||||
}
|
|
||||||
optional<std::string> LightCommandRequest::get_effect() const {
|
|
||||||
if (!this->has_effect_)
|
|
||||||
return {};
|
|
||||||
return this->effect_;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_SWITCH
|
|
||||||
bool SwitchCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 2:
|
|
||||||
// bool state = 2;
|
|
||||||
this->state_ = value;
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool SwitchCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 1:
|
|
||||||
// fixed32 key = 1;
|
|
||||||
this->key_ = value;
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
APIMessageType SwitchCommandRequest::message_type() const { return APIMessageType::SWITCH_COMMAND_REQUEST; }
|
|
||||||
uint32_t SwitchCommandRequest::get_key() const { return this->key_; }
|
|
||||||
bool SwitchCommandRequest::get_state() const { return this->state_; }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_ESP32_CAMERA
|
|
||||||
bool CameraImageRequest::get_single() const { return this->single_; }
|
|
||||||
bool CameraImageRequest::get_stream() const { return this->stream_; }
|
|
||||||
bool CameraImageRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 1:
|
|
||||||
// bool single = 1;
|
|
||||||
this->single_ = value;
|
|
||||||
return true;
|
|
||||||
case 2:
|
|
||||||
// bool stream = 2;
|
|
||||||
this->stream_ = value;
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
APIMessageType CameraImageRequest::message_type() const { return APIMessageType::CAMERA_IMAGE_REQUEST; }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_CLIMATE
|
|
||||||
bool ClimateCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 2:
|
|
||||||
// bool has_mode = 2;
|
|
||||||
this->has_mode_ = value;
|
|
||||||
return true;
|
|
||||||
case 3:
|
|
||||||
// ClimateMode mode = 3;
|
|
||||||
this->mode_ = static_cast<climate::ClimateMode>(value);
|
|
||||||
return true;
|
|
||||||
case 4:
|
|
||||||
// bool has_target_temperature = 4;
|
|
||||||
this->has_target_temperature_ = value;
|
|
||||||
return true;
|
|
||||||
case 6:
|
|
||||||
// bool has_target_temperature_low = 6;
|
|
||||||
this->has_target_temperature_low_ = value;
|
|
||||||
return true;
|
|
||||||
case 8:
|
|
||||||
// bool has_target_temperature_high = 8;
|
|
||||||
this->has_target_temperature_high_ = value;
|
|
||||||
return true;
|
|
||||||
case 10:
|
|
||||||
// bool has_away = 10;
|
|
||||||
this->has_away_ = value;
|
|
||||||
return true;
|
|
||||||
case 11:
|
|
||||||
// bool away = 11;
|
|
||||||
this->away_ = value;
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool ClimateCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 1:
|
|
||||||
// fixed32 key = 1;
|
|
||||||
this->key_ = value;
|
|
||||||
return true;
|
|
||||||
case 5:
|
|
||||||
// float target_temperature = 5;
|
|
||||||
this->target_temperature_ = as_float(value);
|
|
||||||
return true;
|
|
||||||
case 7:
|
|
||||||
// float target_temperature_low = 7;
|
|
||||||
this->target_temperature_low_ = as_float(value);
|
|
||||||
return true;
|
|
||||||
case 9:
|
|
||||||
// float target_temperature_high = 9;
|
|
||||||
this->target_temperature_high_ = as_float(value);
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
APIMessageType ClimateCommandRequest::message_type() const { return APIMessageType::CLIMATE_COMMAND_REQUEST; }
|
|
||||||
uint32_t ClimateCommandRequest::get_key() const { return this->key_; }
|
|
||||||
optional<climate::ClimateMode> ClimateCommandRequest::get_mode() const {
|
|
||||||
if (!this->has_mode_)
|
|
||||||
return {};
|
|
||||||
return this->mode_;
|
|
||||||
}
|
|
||||||
optional<float> ClimateCommandRequest::get_target_temperature() const {
|
|
||||||
if (!this->has_target_temperature_)
|
|
||||||
return {};
|
|
||||||
return this->target_temperature_;
|
|
||||||
}
|
|
||||||
optional<float> ClimateCommandRequest::get_target_temperature_low() const {
|
|
||||||
if (!this->has_target_temperature_low_)
|
|
||||||
return {};
|
|
||||||
return this->target_temperature_low_;
|
|
||||||
}
|
|
||||||
optional<float> ClimateCommandRequest::get_target_temperature_high() const {
|
|
||||||
if (!this->has_target_temperature_high_)
|
|
||||||
return {};
|
|
||||||
return this->target_temperature_high_;
|
|
||||||
}
|
|
||||||
optional<bool> ClimateCommandRequest::get_away() const {
|
|
||||||
if (!this->has_away_)
|
|
||||||
return {};
|
|
||||||
return this->away_;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace api
|
|
||||||
} // namespace esphome
|
|
|
@ -1,162 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
#include "api_message.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace api {
|
|
||||||
|
|
||||||
#ifdef USE_COVER
|
|
||||||
enum LegacyCoverCommand {
|
|
||||||
LEGACY_COVER_COMMAND_OPEN = 0,
|
|
||||||
LEGACY_COVER_COMMAND_CLOSE = 1,
|
|
||||||
LEGACY_COVER_COMMAND_STOP = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
class CoverCommandRequest : public APIMessage {
|
|
||||||
public:
|
|
||||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
|
||||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
uint32_t get_key() const;
|
|
||||||
optional<LegacyCoverCommand> get_legacy_command() const;
|
|
||||||
optional<float> get_position() const;
|
|
||||||
optional<float> get_tilt() const;
|
|
||||||
bool get_stop() const { return this->stop_; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
uint32_t key_{0};
|
|
||||||
bool has_legacy_command_{false};
|
|
||||||
LegacyCoverCommand legacy_command_{LEGACY_COVER_COMMAND_OPEN};
|
|
||||||
bool has_position_{false};
|
|
||||||
float position_{0.0f};
|
|
||||||
bool has_tilt_{false};
|
|
||||||
float tilt_{0.0f};
|
|
||||||
bool stop_{false};
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_FAN
|
|
||||||
class FanCommandRequest : public APIMessage {
|
|
||||||
public:
|
|
||||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
|
||||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
uint32_t get_key() const;
|
|
||||||
optional<bool> get_state() const;
|
|
||||||
optional<fan::FanSpeed> get_speed() const;
|
|
||||||
optional<bool> get_oscillating() const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
uint32_t key_{0};
|
|
||||||
bool has_state_{false};
|
|
||||||
bool state_{false};
|
|
||||||
bool has_speed_{false};
|
|
||||||
fan::FanSpeed speed_{fan::FAN_SPEED_LOW};
|
|
||||||
bool has_oscillating_{false};
|
|
||||||
bool oscillating_{false};
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_LIGHT
|
|
||||||
class LightCommandRequest : public APIMessage {
|
|
||||||
public:
|
|
||||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
|
||||||
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
|
|
||||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
uint32_t get_key() const;
|
|
||||||
optional<bool> get_state() const;
|
|
||||||
optional<float> get_brightness() const;
|
|
||||||
optional<float> get_red() const;
|
|
||||||
optional<float> get_green() const;
|
|
||||||
optional<float> get_blue() const;
|
|
||||||
optional<float> get_white() const;
|
|
||||||
optional<float> get_color_temperature() const;
|
|
||||||
optional<uint32_t> get_transition_length() const;
|
|
||||||
optional<uint32_t> get_flash_length() const;
|
|
||||||
optional<std::string> get_effect() const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
uint32_t key_{0};
|
|
||||||
bool has_state_{false};
|
|
||||||
bool state_{false};
|
|
||||||
bool has_brightness_{false};
|
|
||||||
float brightness_{0.0f};
|
|
||||||
bool has_rgb_{false};
|
|
||||||
float red_{0.0f};
|
|
||||||
float green_{0.0f};
|
|
||||||
float blue_{0.0f};
|
|
||||||
bool has_white_{false};
|
|
||||||
float white_{0.0f};
|
|
||||||
bool has_color_temperature_{false};
|
|
||||||
float color_temperature_{0.0f};
|
|
||||||
bool has_transition_length_{false};
|
|
||||||
uint32_t transition_length_{0};
|
|
||||||
bool has_flash_length_{false};
|
|
||||||
uint32_t flash_length_{0};
|
|
||||||
bool has_effect_{false};
|
|
||||||
std::string effect_{};
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_SWITCH
|
|
||||||
class SwitchCommandRequest : public APIMessage {
|
|
||||||
public:
|
|
||||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
|
||||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
uint32_t get_key() const;
|
|
||||||
bool get_state() const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
uint32_t key_{0};
|
|
||||||
bool state_{false};
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_ESP32_CAMERA
|
|
||||||
class CameraImageRequest : public APIMessage {
|
|
||||||
public:
|
|
||||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
|
||||||
bool get_single() const;
|
|
||||||
bool get_stream() const;
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool single_{false};
|
|
||||||
bool stream_{false};
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_CLIMATE
|
|
||||||
class ClimateCommandRequest : public APIMessage {
|
|
||||||
public:
|
|
||||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
|
||||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
uint32_t get_key() const;
|
|
||||||
optional<climate::ClimateMode> get_mode() const;
|
|
||||||
optional<float> get_target_temperature() const;
|
|
||||||
optional<float> get_target_temperature_low() const;
|
|
||||||
optional<float> get_target_temperature_high() const;
|
|
||||||
optional<bool> get_away() const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
uint32_t key_{0};
|
|
||||||
bool has_mode_{false};
|
|
||||||
climate::ClimateMode mode_{climate::CLIMATE_MODE_OFF};
|
|
||||||
bool has_target_temperature_{false};
|
|
||||||
float target_temperature_{0.0f};
|
|
||||||
bool has_target_temperature_low_{false};
|
|
||||||
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};
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace api
|
|
||||||
} // namespace esphome
|
|
214
esphome/components/api/custom_api_device.h
Normal file
214
esphome/components/api/custom_api_device.h
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include "user_services.h"
|
||||||
|
#include "api_server.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace api {
|
||||||
|
|
||||||
|
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
|
||||||
|
public:
|
||||||
|
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
|
||||||
|
void (T::*callback)(Ts...))
|
||||||
|
: UserServiceBase<Ts...>(name, arg_names), obj_(obj), callback_(callback) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void execute(Ts... x) override { (this->obj_->*this->callback_)(x...); } // NOLINT
|
||||||
|
|
||||||
|
T *obj_;
|
||||||
|
void (T::*callback_)(Ts...);
|
||||||
|
};
|
||||||
|
|
||||||
|
class CustomAPIDevice {
|
||||||
|
public:
|
||||||
|
/// Return if a client (such as Home Assistant) is connected to the native API.
|
||||||
|
bool is_connected() const { return global_api_server->is_connected(); }
|
||||||
|
|
||||||
|
/** Register a custom native API service that will show up in Home Assistant.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* ```cpp
|
||||||
|
* void setup() override {
|
||||||
|
* register_service(&CustomNativeAPI::on_start_washer_cycle, "start_washer_cycle",
|
||||||
|
* {"cycle_length"});
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* void on_start_washer_cycle(int cycle_length) {
|
||||||
|
* // Start washer cycle.
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||||
|
* @tparam Ts The argument types for the service, automatically deduced from the function arguments.
|
||||||
|
* @param callback The member function to call when the service is triggered.
|
||||||
|
* @param name The name of the service to register.
|
||||||
|
* @param arg_names The name of the arguments for the service, must match the arguments of the function.
|
||||||
|
*/
|
||||||
|
template<typename T, typename... Ts>
|
||||||
|
void register_service(void (T::*callback)(Ts...), const std::string &name,
|
||||||
|
const std::array<std::string, sizeof...(Ts)> &arg_names) {
|
||||||
|
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback);
|
||||||
|
global_api_server->register_user_service(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Register a custom native API service that will show up in Home Assistant.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* ```cpp
|
||||||
|
* void setup() override {
|
||||||
|
* register_service(&CustomNativeAPI::on_hello_world, "hello_world");
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* void on_hello_world() {
|
||||||
|
* // Hello World service called.
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||||
|
* @param callback The member function to call when the service is triggered.
|
||||||
|
* @param name The name of the arguments for the service, must match the arguments of the function.
|
||||||
|
*/
|
||||||
|
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
|
||||||
|
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback);
|
||||||
|
global_api_server->register_user_service(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Subscribe to the state of an entity from Home Assistant.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* ```cpp
|
||||||
|
* void setup() override {
|
||||||
|
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* void on_state_changed(std::string state) {
|
||||||
|
* // State of sensor.weather_forecast is `state`
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||||
|
* @param callback The member function to call when the entity state changes.
|
||||||
|
* @param entity_id The entity_id to track.
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id) {
|
||||||
|
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
|
||||||
|
global_api_server->subscribe_home_assistant_state(entity_id, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Subscribe to the state of an entity from Home Assistant.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* ```cpp
|
||||||
|
* void setup() override {
|
||||||
|
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* void on_state_changed(std::string entity_id, std::string state) {
|
||||||
|
* // State of `entity_id` is `state`
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||||
|
* @param callback The member function to call when the entity state changes.
|
||||||
|
* @param entity_id The entity_id to track.
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id) {
|
||||||
|
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
||||||
|
global_api_server->subscribe_home_assistant_state(entity_id, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Call a Home Assistant service from ESPHome.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* ```cpp
|
||||||
|
* call_homeassistant_service("homeassistant.restart");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param service_name The service to call.
|
||||||
|
*/
|
||||||
|
void call_homeassistant_service(const std::string &service_name) {
|
||||||
|
HomeassistantServiceResponse resp;
|
||||||
|
resp.service = service_name;
|
||||||
|
global_api_server->send_homeassistant_service_call(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Call a Home Assistant service from ESPHome.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* ```cpp
|
||||||
|
* call_homeassistant_service("light.turn_on", {
|
||||||
|
* {"entity_id", "light.my_light"},
|
||||||
|
* {"brightness", "127"},
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param service_name The service to call.
|
||||||
|
* @param data The data for the service call, mapping from string to string.
|
||||||
|
*/
|
||||||
|
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||||
|
HomeassistantServiceResponse resp;
|
||||||
|
resp.service = service_name;
|
||||||
|
for (auto &it : data) {
|
||||||
|
HomeassistantServiceMap kv;
|
||||||
|
kv.key = it.first;
|
||||||
|
kv.value = it.second;
|
||||||
|
resp.data.push_back(kv);
|
||||||
|
}
|
||||||
|
global_api_server->send_homeassistant_service_call(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fire an ESPHome event in Home Assistant.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* ```cpp
|
||||||
|
* fire_homeassistant_event("esphome.something_happened");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param event_name The event to fire.
|
||||||
|
*/
|
||||||
|
void fire_homeassistant_event(const std::string &event_name) {
|
||||||
|
HomeassistantServiceResponse resp;
|
||||||
|
resp.service = event_name;
|
||||||
|
resp.is_event = true;
|
||||||
|
global_api_server->send_homeassistant_service_call(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fire an ESPHome event in Home Assistant.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* ```cpp
|
||||||
|
* fire_homeassistant_event("esphome.something_happened", {
|
||||||
|
* {"my_value", "500"},
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param event_name The event to fire.
|
||||||
|
* @param data The data for the event, mapping from string to string.
|
||||||
|
*/
|
||||||
|
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||||
|
HomeassistantServiceResponse resp;
|
||||||
|
resp.service = service_name;
|
||||||
|
resp.is_event = true;
|
||||||
|
for (auto &it : data) {
|
||||||
|
HomeassistantServiceMap kv;
|
||||||
|
kv.key = it.first;
|
||||||
|
kv.value = it.second;
|
||||||
|
resp.data.push_back(kv);
|
||||||
|
}
|
||||||
|
global_api_server->send_homeassistant_service_call(resp);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace api
|
||||||
|
} // namespace esphome
|
66
esphome/components/api/homeassistant_service.h
Normal file
66
esphome/components/api/homeassistant_service.h
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "api_pb2.h"
|
||||||
|
#include "api_server.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace api {
|
||||||
|
|
||||||
|
template<typename... Ts> class TemplatableKeyValuePair {
|
||||||
|
public:
|
||||||
|
template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {}
|
||||||
|
std::string key;
|
||||||
|
TemplatableStringValue<Ts...> value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {}
|
||||||
|
|
||||||
|
TEMPLATABLE_STRING_VALUE(service);
|
||||||
|
template<typename T> void add_data(std::string key, T value) {
|
||||||
|
this->data_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
||||||
|
}
|
||||||
|
template<typename T> void add_data_template(std::string key, T value) {
|
||||||
|
this->data_template_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
||||||
|
}
|
||||||
|
template<typename T> void add_variable(std::string key, T value) {
|
||||||
|
this->variables_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
||||||
|
}
|
||||||
|
void play(Ts... x) override {
|
||||||
|
HomeassistantServiceResponse resp;
|
||||||
|
resp.service = this->service_.value(x...);
|
||||||
|
resp.is_event = this->is_event_;
|
||||||
|
for (auto &it : this->data_) {
|
||||||
|
HomeassistantServiceMap kv;
|
||||||
|
kv.key = it.key;
|
||||||
|
kv.value = it.value.value(x...);
|
||||||
|
resp.data.push_back(kv);
|
||||||
|
}
|
||||||
|
for (auto &it : this->data_template_) {
|
||||||
|
HomeassistantServiceMap kv;
|
||||||
|
kv.key = it.key;
|
||||||
|
kv.value = it.value.value(x...);
|
||||||
|
resp.data_template.push_back(kv);
|
||||||
|
}
|
||||||
|
for (auto &it : this->variables_) {
|
||||||
|
HomeassistantServiceMap kv;
|
||||||
|
kv.key = it.key;
|
||||||
|
kv.value = it.value.value(x...);
|
||||||
|
resp.variables.push_back(kv);
|
||||||
|
}
|
||||||
|
this->parent_->send_homeassistant_service_call(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
APIServer *parent_;
|
||||||
|
bool is_event_;
|
||||||
|
std::vector<TemplatableKeyValuePair<Ts...>> data_;
|
||||||
|
std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
|
||||||
|
std::vector<TemplatableKeyValuePair<Ts...>> variables_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace api
|
||||||
|
} // namespace esphome
|
|
@ -2,189 +2,54 @@
|
||||||
#include "esphome/core/util.h"
|
#include "esphome/core/util.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
|
#include "api_connection.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) {
|
|
||||||
return App.get_name() + component_type + nameable->get_object_id();
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
|
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
|
||||||
auto buffer = this->client_->get_buffer();
|
return this->client_->send_binary_sensor_info(binary_sensor);
|
||||||
buffer.encode_nameable(binary_sensor);
|
|
||||||
// string unique_id = 4;
|
|
||||||
buffer.encode_string(4, get_default_unique_id("binary_sensor", binary_sensor));
|
|
||||||
// string device_class = 5;
|
|
||||||
buffer.encode_string(5, binary_sensor->get_device_class());
|
|
||||||
// bool is_status_binary_sensor = 6;
|
|
||||||
buffer.encode_bool(6, binary_sensor->is_status_binary_sensor());
|
|
||||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_BINARY_SENSOR_RESPONSE);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_COVER
|
#ifdef USE_COVER
|
||||||
bool ListEntitiesIterator::on_cover(cover::Cover *cover) {
|
bool ListEntitiesIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_info(cover); }
|
||||||
auto buffer = this->client_->get_buffer();
|
|
||||||
buffer.encode_nameable(cover);
|
|
||||||
// string unique_id = 4;
|
|
||||||
buffer.encode_string(4, get_default_unique_id("cover", cover));
|
|
||||||
auto traits = cover->get_traits();
|
|
||||||
|
|
||||||
// bool assumed_state = 5;
|
|
||||||
buffer.encode_bool(5, traits.get_is_assumed_state());
|
|
||||||
// bool supports_position = 6;
|
|
||||||
buffer.encode_bool(6, traits.get_supports_position());
|
|
||||||
// bool supports_tilt = 7;
|
|
||||||
buffer.encode_bool(7, traits.get_supports_tilt());
|
|
||||||
// string device_class = 8;
|
|
||||||
buffer.encode_string(8, cover->get_device_class());
|
|
||||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_COVER_RESPONSE);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_FAN
|
#ifdef USE_FAN
|
||||||
bool ListEntitiesIterator::on_fan(fan::FanState *fan) {
|
bool ListEntitiesIterator::on_fan(fan::FanState *fan) { return this->client_->send_fan_info(fan); }
|
||||||
auto buffer = this->client_->get_buffer();
|
|
||||||
buffer.encode_nameable(fan);
|
|
||||||
// string unique_id = 4;
|
|
||||||
buffer.encode_string(4, get_default_unique_id("fan", fan));
|
|
||||||
// bool supports_oscillation = 5;
|
|
||||||
buffer.encode_bool(5, fan->get_traits().supports_oscillation());
|
|
||||||
// bool supports_speed = 6;
|
|
||||||
buffer.encode_bool(6, fan->get_traits().supports_speed());
|
|
||||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_FAN_RESPONSE);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_LIGHT
|
#ifdef USE_LIGHT
|
||||||
bool ListEntitiesIterator::on_light(light::LightState *light) {
|
bool ListEntitiesIterator::on_light(light::LightState *light) { return this->client_->send_light_info(light); }
|
||||||
auto buffer = this->client_->get_buffer();
|
|
||||||
buffer.encode_nameable(light);
|
|
||||||
// string unique_id = 4;
|
|
||||||
buffer.encode_string(4, get_default_unique_id("light", light));
|
|
||||||
// bool supports_brightness = 5;
|
|
||||||
auto traits = light->get_traits();
|
|
||||||
buffer.encode_bool(5, traits.get_supports_brightness());
|
|
||||||
// bool supports_rgb = 6;
|
|
||||||
buffer.encode_bool(6, traits.get_supports_rgb());
|
|
||||||
// bool supports_white_value = 7;
|
|
||||||
buffer.encode_bool(7, traits.get_supports_rgb_white_value());
|
|
||||||
// bool supports_color_temperature = 8;
|
|
||||||
buffer.encode_bool(8, traits.get_supports_color_temperature());
|
|
||||||
if (traits.get_supports_color_temperature()) {
|
|
||||||
// float min_mireds = 9;
|
|
||||||
buffer.encode_float(9, traits.get_min_mireds());
|
|
||||||
// float max_mireds = 10;
|
|
||||||
buffer.encode_float(10, traits.get_max_mireds());
|
|
||||||
}
|
|
||||||
// repeated string effects = 11;
|
|
||||||
if (light->supports_effects()) {
|
|
||||||
buffer.encode_string(11, "None");
|
|
||||||
for (auto *effect : light->get_effects()) {
|
|
||||||
buffer.encode_string(11, effect->get_name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_LIGHT_RESPONSE);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) {
|
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->client_->send_sensor_info(sensor); }
|
||||||
auto buffer = this->client_->get_buffer();
|
|
||||||
buffer.encode_nameable(sensor);
|
|
||||||
// string unique_id = 4;
|
|
||||||
std::string unique_id = sensor->unique_id();
|
|
||||||
if (unique_id.empty())
|
|
||||||
unique_id = get_default_unique_id("sensor", sensor);
|
|
||||||
buffer.encode_string(4, unique_id);
|
|
||||||
// string icon = 5;
|
|
||||||
buffer.encode_string(5, sensor->get_icon());
|
|
||||||
// string unit_of_measurement = 6;
|
|
||||||
buffer.encode_string(6, sensor->get_unit_of_measurement());
|
|
||||||
// int32 accuracy_decimals = 7;
|
|
||||||
buffer.encode_int32(7, sensor->get_accuracy_decimals());
|
|
||||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SENSOR_RESPONSE);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) {
|
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); }
|
||||||
auto buffer = this->client_->get_buffer();
|
|
||||||
buffer.encode_nameable(a_switch);
|
|
||||||
// string unique_id = 4;
|
|
||||||
buffer.encode_string(4, get_default_unique_id("switch", a_switch));
|
|
||||||
// string icon = 5;
|
|
||||||
buffer.encode_string(5, a_switch->get_icon());
|
|
||||||
// bool assumed_state = 6;
|
|
||||||
buffer.encode_bool(6, a_switch->assumed_state());
|
|
||||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SWITCH_RESPONSE);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
|
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
|
||||||
auto buffer = this->client_->get_buffer();
|
return this->client_->send_text_sensor_info(text_sensor);
|
||||||
buffer.encode_nameable(text_sensor);
|
|
||||||
// string unique_id = 4;
|
|
||||||
std::string unique_id = text_sensor->unique_id();
|
|
||||||
if (unique_id.empty())
|
|
||||||
unique_id = get_default_unique_id("text_sensor", text_sensor);
|
|
||||||
buffer.encode_string(4, unique_id);
|
|
||||||
// string icon = 5;
|
|
||||||
buffer.encode_string(5, text_sensor->get_icon());
|
|
||||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_TEXT_SENSOR_RESPONSE);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool ListEntitiesIterator::on_end() {
|
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
|
||||||
return this->client_->send_empty_message(APIMessageType::LIST_ENTITIES_DONE_RESPONSE);
|
|
||||||
}
|
|
||||||
ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client)
|
ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client)
|
||||||
: ComponentIterator(server), client_(client) {}
|
: ComponentIterator(server), client_(client) {}
|
||||||
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
|
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
|
||||||
auto buffer = this->client_->get_buffer();
|
auto resp = service->encode_list_service_response();
|
||||||
service->encode_list_service_response(buffer);
|
return this->client_->send_list_entities_services_response(resp);
|
||||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SERVICE_RESPONSE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_ESP32_CAMERA
|
#ifdef USE_ESP32_CAMERA
|
||||||
bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) {
|
bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) {
|
||||||
auto buffer = this->client_->get_buffer();
|
return this->client_->send_camera_info(camera);
|
||||||
buffer.encode_nameable(camera);
|
|
||||||
// string unique_id = 4;
|
|
||||||
buffer.encode_string(4, get_default_unique_id("camera", camera));
|
|
||||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_CAMERA_RESPONSE);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_CLIMATE
|
#ifdef USE_CLIMATE
|
||||||
bool ListEntitiesIterator::on_climate(climate::Climate *climate) {
|
bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_info(climate); }
|
||||||
auto buffer = this->client_->get_buffer();
|
|
||||||
buffer.encode_nameable(climate);
|
|
||||||
// string unique_id = 4;
|
|
||||||
buffer.encode_string(4, get_default_unique_id("climate", climate));
|
|
||||||
|
|
||||||
auto traits = climate->get_traits();
|
|
||||||
// bool supports_current_temperature = 5;
|
|
||||||
buffer.encode_bool(5, traits.get_supports_current_temperature());
|
|
||||||
// bool supports_two_point_target_temperature = 6;
|
|
||||||
buffer.encode_bool(6, traits.get_supports_two_point_target_temperature());
|
|
||||||
// repeated ClimateMode supported_modes = 7;
|
|
||||||
for (auto mode : {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
|
|
||||||
climate::CLIMATE_MODE_HEAT}) {
|
|
||||||
if (traits.supports_mode(mode))
|
|
||||||
buffer.encode_uint32(7, mode, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// float visual_min_temperature = 8;
|
|
||||||
buffer.encode_float(8, traits.get_visual_min_temperature());
|
|
||||||
// float visual_max_temperature = 9;
|
|
||||||
buffer.encode_float(9, traits.get_visual_max_temperature());
|
|
||||||
// float visual_temperature_step = 10;
|
|
||||||
buffer.encode_float(10, traits.get_visual_temperature_step());
|
|
||||||
// bool supports_away = 11;
|
|
||||||
buffer.encode_bool(11, traits.get_supports_away());
|
|
||||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_CLIMATE_RESPONSE);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
APIMessageType ListEntitiesRequest::message_type() const { return APIMessageType::LIST_ENTITIES_REQUEST; }
|
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -2,16 +2,11 @@
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#include "api_message.h"
|
#include "util.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
class ListEntitiesRequest : public APIMessage {
|
|
||||||
public:
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class APIConnection;
|
class APIConnection;
|
||||||
|
|
||||||
class ListEntitiesIterator : public ComponentIterator {
|
class ListEntitiesIterator : public ComponentIterator {
|
||||||
|
|
|
@ -1,61 +1,59 @@
|
||||||
#include "api_message.h"
|
#include "proto.h"
|
||||||
|
#include "util.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
static const char *TAG = "api.message";
|
static const char *TAG = "api.proto";
|
||||||
|
|
||||||
bool APIMessage::decode_varint(uint32_t field_id, uint32_t value) { return false; }
|
void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
|
||||||
bool APIMessage::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { return false; }
|
|
||||||
bool APIMessage::decode_32bit(uint32_t field_id, uint32_t value) { return false; }
|
|
||||||
void APIMessage::encode(APIBuffer &buffer) {}
|
|
||||||
void APIMessage::decode(const uint8_t *buffer, size_t length) {
|
|
||||||
uint32_t i = 0;
|
uint32_t i = 0;
|
||||||
bool error = false;
|
bool error = false;
|
||||||
while (i < length) {
|
while (i < length) {
|
||||||
uint32_t consumed;
|
uint32_t consumed;
|
||||||
auto res = proto_decode_varuint32(&buffer[i], length - i, &consumed);
|
auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||||
if (!res.has_value()) {
|
if (!res.has_value()) {
|
||||||
ESP_LOGV(TAG, "Invalid field start at %u", i);
|
ESP_LOGV(TAG, "Invalid field start at %u", i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t field_type = (*res) & 0b111;
|
uint32_t field_type = (res->as_uint32()) & 0b111;
|
||||||
uint32_t field_id = (*res) >> 3;
|
uint32_t field_id = (res->as_uint32()) >> 3;
|
||||||
i += consumed;
|
i += consumed;
|
||||||
|
|
||||||
switch (field_type) {
|
switch (field_type) {
|
||||||
case 0: { // VarInt
|
case 0: { // VarInt
|
||||||
res = proto_decode_varuint32(&buffer[i], length - i, &consumed);
|
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||||
if (!res.has_value()) {
|
if (!res.has_value()) {
|
||||||
ESP_LOGV(TAG, "Invalid VarInt at %u", i);
|
ESP_LOGV(TAG, "Invalid VarInt at %u", i);
|
||||||
error = true;
|
error = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!this->decode_varint(field_id, *res)) {
|
if (!this->decode_varint(field_id, *res)) {
|
||||||
ESP_LOGV(TAG, "Cannot decode VarInt field %u with value %u!", field_id, *res);
|
ESP_LOGV(TAG, "Cannot decode VarInt field %u with value %u!", field_id, res->as_uint32());
|
||||||
}
|
}
|
||||||
i += consumed;
|
i += consumed;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 2: { // Length-delimited
|
case 2: { // Length-delimited
|
||||||
res = proto_decode_varuint32(&buffer[i], length - i, &consumed);
|
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||||
if (!res.has_value()) {
|
if (!res.has_value()) {
|
||||||
ESP_LOGV(TAG, "Invalid Length Delimited at %u", i);
|
ESP_LOGV(TAG, "Invalid Length Delimited at %u", i);
|
||||||
error = true;
|
error = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
uint32_t field_length = res->as_uint32();
|
||||||
i += consumed;
|
i += consumed;
|
||||||
if (*res > length - i) {
|
if (field_length > length - i) {
|
||||||
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %u", i);
|
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %u", i);
|
||||||
error = true;
|
error = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!this->decode_length_delimited(field_id, &buffer[i], *res)) {
|
if (!this->decode_length(field_id, ProtoLengthDelimited(&buffer[i], field_length))) {
|
||||||
ESP_LOGV(TAG, "Cannot decode Length Delimited field %u!", field_id);
|
ESP_LOGV(TAG, "Cannot decode Length Delimited field %u!", field_id);
|
||||||
}
|
}
|
||||||
i += *res;
|
i += field_length;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 5: { // 32-bit
|
case 5: { // 32-bit
|
||||||
|
@ -66,7 +64,7 @@ void APIMessage::decode(const uint8_t *buffer, size_t length) {
|
||||||
}
|
}
|
||||||
uint32_t val = (uint32_t(buffer[i]) << 0) | (uint32_t(buffer[i + 1]) << 8) | (uint32_t(buffer[i + 2]) << 16) |
|
uint32_t val = (uint32_t(buffer[i]) << 0) | (uint32_t(buffer[i + 1]) << 8) | (uint32_t(buffer[i + 2]) << 16) |
|
||||||
(uint32_t(buffer[i + 3]) << 24);
|
(uint32_t(buffer[i + 3]) << 24);
|
||||||
if (!this->decode_32bit(field_id, val)) {
|
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
|
||||||
ESP_LOGV(TAG, "Cannot decode 32-bit field %u with value %u!", field_id, val);
|
ESP_LOGV(TAG, "Cannot decode 32-bit field %u with value %u!", field_id, val);
|
||||||
}
|
}
|
||||||
i += 4;
|
i += 4;
|
||||||
|
@ -83,5 +81,11 @@ void APIMessage::decode(const uint8_t *buffer, size_t length) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string ProtoMessage::dump() const {
|
||||||
|
std::string out;
|
||||||
|
this->dump_to(out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
279
esphome/components/api/proto.h
Normal file
279
esphome/components/api/proto.h
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace api {
|
||||||
|
|
||||||
|
/// Representation of a VarInt - in ProtoBuf should be 64bit but we only use 32bit
|
||||||
|
class ProtoVarInt {
|
||||||
|
public:
|
||||||
|
ProtoVarInt() : value_(0) {}
|
||||||
|
explicit ProtoVarInt(uint64_t value) : value_(value) {}
|
||||||
|
|
||||||
|
static optional<ProtoVarInt> parse(const uint8_t *buffer, uint32_t len, uint32_t *consumed) {
|
||||||
|
if (consumed != nullptr)
|
||||||
|
*consumed = 0;
|
||||||
|
|
||||||
|
if (len == 0)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
uint64_t result = 0;
|
||||||
|
uint8_t bitpos = 0;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < len; i++) {
|
||||||
|
uint8_t val = buffer[i];
|
||||||
|
result |= uint64_t(val & 0x7F) << uint64_t(bitpos);
|
||||||
|
bitpos += 7;
|
||||||
|
if ((val & 0x80) == 0) {
|
||||||
|
if (consumed != nullptr)
|
||||||
|
*consumed = i + 1;
|
||||||
|
return ProtoVarInt(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t as_uint32() const { return this->value_; }
|
||||||
|
uint64_t as_uint64() const { return this->value_; }
|
||||||
|
bool as_bool() const { return this->value_; }
|
||||||
|
template<typename T> T as_enum() const { return static_cast<T>(this->as_uint32()); }
|
||||||
|
int32_t as_int32() const {
|
||||||
|
// Not ZigZag encoded
|
||||||
|
return static_cast<int32_t>(this->as_int64());
|
||||||
|
}
|
||||||
|
int64_t as_int64() const {
|
||||||
|
// Not ZigZag encoded
|
||||||
|
return static_cast<int64_t>(this->value_);
|
||||||
|
}
|
||||||
|
int32_t as_sint32() const {
|
||||||
|
// with ZigZag encoding
|
||||||
|
if (this->value_ & 1)
|
||||||
|
return static_cast<int32_t>(~(this->value_ >> 1));
|
||||||
|
else
|
||||||
|
return static_cast<int32_t>(this->value_ >> 1);
|
||||||
|
}
|
||||||
|
int64_t as_sint64() const {
|
||||||
|
// with ZigZag encoding
|
||||||
|
if (this->value_ & 1)
|
||||||
|
return static_cast<int64_t>(~(this->value_ >> 1));
|
||||||
|
else
|
||||||
|
return static_cast<int64_t>(this->value_ >> 1);
|
||||||
|
}
|
||||||
|
void encode(std::vector<uint8_t> &out) {
|
||||||
|
uint32_t val = this->value_;
|
||||||
|
if (val <= 0x7F) {
|
||||||
|
out.push_back(val);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (val) {
|
||||||
|
uint8_t temp = val & 0x7F;
|
||||||
|
val >>= 7;
|
||||||
|
if (val) {
|
||||||
|
out.push_back(temp | 0x80);
|
||||||
|
} else {
|
||||||
|
out.push_back(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint64_t value_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ProtoLengthDelimited {
|
||||||
|
public:
|
||||||
|
explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
|
||||||
|
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
|
||||||
|
template<class C> C as_message() const {
|
||||||
|
auto msg = C();
|
||||||
|
msg.decode(this->value_, this->length_);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const uint8_t *const value_;
|
||||||
|
const size_t length_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Proto32Bit {
|
||||||
|
public:
|
||||||
|
explicit Proto32Bit(uint32_t value) : value_(value) {}
|
||||||
|
uint32_t as_fixed32() const { return this->value_; }
|
||||||
|
int32_t as_sfixed32() const { return static_cast<int32_t>(this->value_); }
|
||||||
|
float as_float() const {
|
||||||
|
union {
|
||||||
|
uint32_t raw;
|
||||||
|
float value;
|
||||||
|
} s{};
|
||||||
|
s.raw = this->value_;
|
||||||
|
return s.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const uint32_t value_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Proto64Bit {
|
||||||
|
public:
|
||||||
|
explicit Proto64Bit(uint64_t value) : value_(value) {}
|
||||||
|
uint64_t as_fixed64() const { return this->value_; }
|
||||||
|
int64_t as_sfixed64() const { return static_cast<int64_t>(this->value_); }
|
||||||
|
double as_double() const {
|
||||||
|
union {
|
||||||
|
uint64_t raw;
|
||||||
|
double value;
|
||||||
|
} s{};
|
||||||
|
s.raw = this->value_;
|
||||||
|
return s.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const uint64_t value_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ProtoWriteBuffer {
|
||||||
|
public:
|
||||||
|
ProtoWriteBuffer(std::vector<uint8_t> *buffer) : buffer_(buffer) {}
|
||||||
|
void write(uint8_t value) { this->buffer_->push_back(value); }
|
||||||
|
void encode_varint_raw(ProtoVarInt value) { value.encode(*this->buffer_); }
|
||||||
|
void encode_varint_raw(uint32_t value) { this->encode_varint_raw(ProtoVarInt(value)); }
|
||||||
|
void encode_field_raw(uint32_t field_id, uint32_t type) {
|
||||||
|
uint32_t val = (field_id << 3) | (type & 0b111);
|
||||||
|
this->encode_varint_raw(val);
|
||||||
|
}
|
||||||
|
void encode_string(uint32_t field_id, const char *string, size_t len, bool force = false) {
|
||||||
|
if (len == 0 && !force)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->encode_field_raw(field_id, 2);
|
||||||
|
this->encode_varint_raw(len);
|
||||||
|
auto *data = reinterpret_cast<const uint8_t *>(string);
|
||||||
|
for (size_t i = 0; i < len; i++)
|
||||||
|
this->write(data[i]);
|
||||||
|
}
|
||||||
|
void encode_string(uint32_t field_id, const std::string &value, bool force = false) {
|
||||||
|
this->encode_string(field_id, value.data(), value.size());
|
||||||
|
}
|
||||||
|
void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) {
|
||||||
|
this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force);
|
||||||
|
}
|
||||||
|
void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||||
|
if (value == 0 && !force)
|
||||||
|
return;
|
||||||
|
this->encode_field_raw(field_id, 0);
|
||||||
|
this->encode_varint_raw(value);
|
||||||
|
}
|
||||||
|
void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) {
|
||||||
|
if (value == 0 && !force)
|
||||||
|
return;
|
||||||
|
this->encode_field_raw(field_id, 0);
|
||||||
|
this->encode_varint_raw(ProtoVarInt(value));
|
||||||
|
}
|
||||||
|
void encode_bool(uint32_t field_id, bool value, bool force = false) {
|
||||||
|
if (!value && !force)
|
||||||
|
return;
|
||||||
|
this->encode_field_raw(field_id, 0);
|
||||||
|
this->write(0x01);
|
||||||
|
}
|
||||||
|
void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||||
|
if (value == 0 && !force)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->encode_field_raw(field_id, 5);
|
||||||
|
this->write((value >> 0) & 0xFF);
|
||||||
|
this->write((value >> 8) & 0xFF);
|
||||||
|
this->write((value >> 16) & 0xFF);
|
||||||
|
this->write((value >> 24) & 0xFF);
|
||||||
|
}
|
||||||
|
template<typename T> void encode_enum(uint32_t field_id, T value, bool force = false) {
|
||||||
|
this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
|
||||||
|
}
|
||||||
|
void encode_float(uint32_t field_id, float value, bool force = false) {
|
||||||
|
if (value == 0.0f && !force)
|
||||||
|
return;
|
||||||
|
|
||||||
|
union {
|
||||||
|
float value;
|
||||||
|
uint32_t raw;
|
||||||
|
} val{};
|
||||||
|
val.value = value;
|
||||||
|
this->encode_fixed32(field_id, val.raw);
|
||||||
|
}
|
||||||
|
void encode_int32(uint32_t field_id, int32_t value, bool force = false) {
|
||||||
|
if (value < 0) {
|
||||||
|
// negative int32 is always 10 byte long
|
||||||
|
this->encode_int64(field_id, value, force);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
|
||||||
|
}
|
||||||
|
void encode_int64(uint32_t field_id, int64_t value, bool force = false) {
|
||||||
|
this->encode_uint64(field_id, static_cast<uint64_t>(value), force);
|
||||||
|
}
|
||||||
|
void encode_sint32(uint32_t field_id, int32_t value, bool force = false) {
|
||||||
|
uint32_t uvalue;
|
||||||
|
if (value < 0)
|
||||||
|
uvalue = ~(value << 1);
|
||||||
|
else
|
||||||
|
uvalue = value << 1;
|
||||||
|
this->encode_uint32(field_id, uvalue, force);
|
||||||
|
}
|
||||||
|
template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) {
|
||||||
|
this->encode_field_raw(field_id, 2);
|
||||||
|
size_t begin = this->buffer_->size();
|
||||||
|
|
||||||
|
value.encode(*this);
|
||||||
|
|
||||||
|
const uint32_t nested_length = this->buffer_->size() - begin;
|
||||||
|
// add size varint
|
||||||
|
std::vector<uint8_t> var;
|
||||||
|
ProtoVarInt(nested_length).encode(var);
|
||||||
|
this->buffer_->insert(this->buffer_->begin() + begin, var.begin(), var.end());
|
||||||
|
}
|
||||||
|
std::vector<uint8_t> *get_buffer() const { return buffer_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<uint8_t> *buffer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ProtoMessage {
|
||||||
|
public:
|
||||||
|
virtual void encode(ProtoWriteBuffer buffer) const = 0;
|
||||||
|
void decode(const uint8_t *buffer, size_t length);
|
||||||
|
std::string dump() const;
|
||||||
|
virtual void dump_to(std::string &out) const = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual bool decode_varint(uint32_t field_id, ProtoVarInt value) { return false; }
|
||||||
|
virtual bool decode_length(uint32_t field_id, ProtoLengthDelimited value) { return false; }
|
||||||
|
virtual bool decode_32bit(uint32_t field_id, Proto32Bit value) { return false; }
|
||||||
|
virtual bool decode_64bit(uint32_t field_id, Proto64Bit value) { return false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T> const char *proto_enum_to_string(T value);
|
||||||
|
|
||||||
|
class ProtoService {
|
||||||
|
public:
|
||||||
|
protected:
|
||||||
|
virtual bool is_authenticated() = 0;
|
||||||
|
virtual bool is_connection_setup() = 0;
|
||||||
|
virtual void on_fatal_error() = 0;
|
||||||
|
virtual void on_unauthenticated_access() = 0;
|
||||||
|
virtual void on_no_setup_connection() = 0;
|
||||||
|
virtual ProtoWriteBuffer create_buffer() = 0;
|
||||||
|
virtual bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) = 0;
|
||||||
|
virtual bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
|
||||||
|
virtual void set_nodelay(bool nodelay) = 0;
|
||||||
|
|
||||||
|
template<class C> bool send_message_(const C &msg, uint32_t message_type) {
|
||||||
|
auto buffer = this->create_buffer();
|
||||||
|
msg.encode(buffer);
|
||||||
|
return this->send_buffer(buffer, message_type);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace api
|
||||||
|
} // namespace esphome
|
|
@ -1,49 +0,0 @@
|
||||||
#include "service_call_message.h"
|
|
||||||
#include "esphome/core/log.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace api {
|
|
||||||
|
|
||||||
APIMessageType SubscribeServiceCallsRequest::message_type() const {
|
|
||||||
return APIMessageType::SUBSCRIBE_SERVICE_CALLS_REQUEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
APIMessageType ServiceCallResponse::message_type() const { return APIMessageType::SERVICE_CALL_RESPONSE; }
|
|
||||||
void ServiceCallResponse::encode(APIBuffer &buffer) {
|
|
||||||
// string service = 1;
|
|
||||||
buffer.encode_string(1, this->service_);
|
|
||||||
// map<string, string> data = 2;
|
|
||||||
for (auto &it : this->data_) {
|
|
||||||
auto nested = buffer.begin_nested(2);
|
|
||||||
buffer.encode_string(1, it.key);
|
|
||||||
buffer.encode_string(2, it.value);
|
|
||||||
buffer.end_nested(nested);
|
|
||||||
}
|
|
||||||
// map<string, string> data_template = 3;
|
|
||||||
for (auto &it : this->data_template_) {
|
|
||||||
auto nested = buffer.begin_nested(3);
|
|
||||||
buffer.encode_string(1, it.key);
|
|
||||||
buffer.encode_string(2, it.value);
|
|
||||||
buffer.end_nested(nested);
|
|
||||||
}
|
|
||||||
// map<string, string> variables = 4;
|
|
||||||
for (auto &it : this->variables_) {
|
|
||||||
auto nested = buffer.begin_nested(4);
|
|
||||||
buffer.encode_string(1, it.key);
|
|
||||||
buffer.encode_string(2, it.value());
|
|
||||||
buffer.end_nested(nested);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void ServiceCallResponse::set_service(const std::string &service) { this->service_ = service; }
|
|
||||||
void ServiceCallResponse::set_data(const std::vector<KeyValuePair> &data) { this->data_ = data; }
|
|
||||||
void ServiceCallResponse::set_data_template(const std::vector<KeyValuePair> &data_template) {
|
|
||||||
this->data_template_ = data_template;
|
|
||||||
}
|
|
||||||
void ServiceCallResponse::set_variables(const std::vector<TemplatableKeyValuePair> &variables) {
|
|
||||||
this->variables_ = variables;
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyValuePair::KeyValuePair(const std::string &key, const std::string &value) : key(key), value(value) {}
|
|
||||||
|
|
||||||
} // namespace api
|
|
||||||
} // namespace esphome
|
|
|
@ -1,53 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "esphome/core/helpers.h"
|
|
||||||
#include "esphome/core/automation.h"
|
|
||||||
#include "api_message.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace api {
|
|
||||||
|
|
||||||
class SubscribeServiceCallsRequest : public APIMessage {
|
|
||||||
public:
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class KeyValuePair {
|
|
||||||
public:
|
|
||||||
KeyValuePair(const std::string &key, const std::string &value);
|
|
||||||
|
|
||||||
std::string key;
|
|
||||||
std::string value;
|
|
||||||
};
|
|
||||||
|
|
||||||
class TemplatableKeyValuePair {
|
|
||||||
public:
|
|
||||||
template<typename T> TemplatableKeyValuePair(std::string key, T func);
|
|
||||||
|
|
||||||
std::string key;
|
|
||||||
std::function<std::string()> value;
|
|
||||||
};
|
|
||||||
template<typename T> TemplatableKeyValuePair::TemplatableKeyValuePair(std::string key, T func) : key(key) {
|
|
||||||
this->value = [func]() -> std::string { return to_string(func()); };
|
|
||||||
}
|
|
||||||
|
|
||||||
class ServiceCallResponse : public APIMessage {
|
|
||||||
public:
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
|
|
||||||
void encode(APIBuffer &buffer) override;
|
|
||||||
|
|
||||||
void set_service(const std::string &service);
|
|
||||||
void set_data(const std::vector<KeyValuePair> &data);
|
|
||||||
void set_data_template(const std::vector<KeyValuePair> &data_template);
|
|
||||||
void set_variables(const std::vector<TemplatableKeyValuePair> &variables);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::string service_;
|
|
||||||
std::vector<KeyValuePair> data_;
|
|
||||||
std::vector<KeyValuePair> data_template_;
|
|
||||||
std::vector<TemplatableKeyValuePair> variables_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace api
|
|
||||||
} // namespace esphome
|
|
|
@ -1,26 +0,0 @@
|
||||||
#include "subscribe_logs.h"
|
|
||||||
#include "esphome/core/log.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace api {
|
|
||||||
|
|
||||||
APIMessageType SubscribeLogsRequest::message_type() const { return APIMessageType::SUBSCRIBE_LOGS_REQUEST; }
|
|
||||||
bool SubscribeLogsRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 1: // LogLevel level = 1;
|
|
||||||
this->level_ = value;
|
|
||||||
return true;
|
|
||||||
case 2: // bool dump_config = 2;
|
|
||||||
this->dump_config_ = value;
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uint32_t SubscribeLogsRequest::get_level() const { return this->level_; }
|
|
||||||
void SubscribeLogsRequest::set_level(uint32_t level) { this->level_ = level; }
|
|
||||||
bool SubscribeLogsRequest::get_dump_config() const { return this->dump_config_; }
|
|
||||||
void SubscribeLogsRequest::set_dump_config(bool dump_config) { this->dump_config_ = dump_config; }
|
|
||||||
|
|
||||||
} // namespace api
|
|
||||||
} // namespace esphome
|
|
|
@ -1,24 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
|
||||||
#include "api_message.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace api {
|
|
||||||
|
|
||||||
class SubscribeLogsRequest : public APIMessage {
|
|
||||||
public:
|
|
||||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
uint32_t get_level() const;
|
|
||||||
void set_level(uint32_t level);
|
|
||||||
bool get_dump_config() const;
|
|
||||||
void set_dump_config(bool dump_config);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
uint32_t level_{6};
|
|
||||||
bool dump_config_{false};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace api
|
|
||||||
} // namespace esphome
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "subscribe_state.h"
|
#include "subscribe_state.h"
|
||||||
|
#include "api_connection.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -48,30 +49,5 @@ bool InitialStateIterator::on_climate(climate::Climate *climate) { return this->
|
||||||
InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client)
|
InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client)
|
||||||
: ComponentIterator(server), client_(client) {}
|
: ComponentIterator(server), client_(client) {}
|
||||||
|
|
||||||
APIMessageType SubscribeStatesRequest::message_type() const { return APIMessageType::SUBSCRIBE_STATES_REQUEST; }
|
|
||||||
|
|
||||||
bool HomeAssistantStateResponse::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 1:
|
|
||||||
// string entity_id = 1;
|
|
||||||
this->entity_id_ = as_string(value, len);
|
|
||||||
return true;
|
|
||||||
case 2:
|
|
||||||
// string state = 2;
|
|
||||||
this->state_ = as_string(value, len);
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
APIMessageType HomeAssistantStateResponse::message_type() const {
|
|
||||||
return APIMessageType::HOME_ASSISTANT_STATE_RESPONSE;
|
|
||||||
}
|
|
||||||
const std::string &HomeAssistantStateResponse::get_entity_id() const { return this->entity_id_; }
|
|
||||||
const std::string &HomeAssistantStateResponse::get_state() const { return this->state_; }
|
|
||||||
APIMessageType SubscribeHomeAssistantStatesRequest::message_type() const {
|
|
||||||
return APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATES_REQUEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -4,16 +4,10 @@
|
||||||
#include "esphome/core/controller.h"
|
#include "esphome/core/controller.h"
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "api_message.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
class SubscribeStatesRequest : public APIMessage {
|
|
||||||
public:
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class APIConnection;
|
class APIConnection;
|
||||||
|
|
||||||
class InitialStateIterator : public ComponentIterator {
|
class InitialStateIterator : public ComponentIterator {
|
||||||
|
@ -47,23 +41,6 @@ class InitialStateIterator : public ComponentIterator {
|
||||||
APIConnection *client_;
|
APIConnection *client_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SubscribeHomeAssistantStatesRequest : public APIMessage {
|
|
||||||
public:
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class HomeAssistantStateResponse : public APIMessage {
|
|
||||||
public:
|
|
||||||
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
const std::string &get_entity_id() const;
|
|
||||||
const std::string &get_state() const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::string entity_id_;
|
|
||||||
std::string state_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
|
|
@ -4,71 +4,35 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
template<> bool ExecuteServiceArgument::get_value<bool>() { return this->value_bool_; }
|
template<> bool get_execute_arg_value<bool>(const ExecuteServiceArgument &arg) { return arg.bool_; }
|
||||||
template<> int ExecuteServiceArgument::get_value<int>() { return this->value_int_; }
|
template<> int get_execute_arg_value<int>(const ExecuteServiceArgument &arg) {
|
||||||
template<> float ExecuteServiceArgument::get_value<float>() { return this->value_float_; }
|
if (arg.legacy_int != 0)
|
||||||
template<> std::string ExecuteServiceArgument::get_value<std::string>() { return this->value_string_; }
|
return arg.legacy_int;
|
||||||
|
return arg.int_;
|
||||||
APIMessageType ExecuteServiceArgument::message_type() const { return APIMessageType::EXECUTE_SERVICE_REQUEST; }
|
|
||||||
bool ExecuteServiceArgument::decode_varint(uint32_t field_id, uint32_t value) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 1: // bool bool_ = 1;
|
|
||||||
this->value_bool_ = value;
|
|
||||||
return true;
|
|
||||||
case 2: // int32 int_ = 2;
|
|
||||||
this->value_int_ = value;
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
bool ExecuteServiceArgument::decode_32bit(uint32_t field_id, uint32_t value) {
|
template<> float get_execute_arg_value<float>(const ExecuteServiceArgument &arg) { return arg.float_; }
|
||||||
switch (field_id) {
|
template<> std::string get_execute_arg_value<std::string>(const ExecuteServiceArgument &arg) { return arg.string_; }
|
||||||
case 3: // float float_ = 3;
|
template<> std::vector<bool> get_execute_arg_value<std::vector<bool>>(const ExecuteServiceArgument &arg) {
|
||||||
this->value_float_ = as_float(value);
|
return arg.bool_array;
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
bool ExecuteServiceArgument::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
|
template<> std::vector<int> get_execute_arg_value<std::vector<int>>(const ExecuteServiceArgument &arg) {
|
||||||
switch (field_id) {
|
return arg.int_array;
|
||||||
case 4: // string string_ = 4;
|
}
|
||||||
this->value_string_ = as_string(value, len);
|
template<> std::vector<float> get_execute_arg_value<std::vector<float>>(const ExecuteServiceArgument &arg) {
|
||||||
return true;
|
return arg.float_array;
|
||||||
default:
|
}
|
||||||
return false;
|
template<> std::vector<std::string> get_execute_arg_value<std::vector<std::string>>(const ExecuteServiceArgument &arg) {
|
||||||
}
|
return arg.string_array;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExecuteServiceRequest::decode_32bit(uint32_t field_id, uint32_t value) {
|
template<> EnumServiceArgType to_service_arg_type<bool>() { return SERVICE_ARG_TYPE_BOOL; }
|
||||||
switch (field_id) {
|
template<> EnumServiceArgType to_service_arg_type<int>() { return SERVICE_ARG_TYPE_INT; }
|
||||||
case 1: // fixed32 key = 1;
|
template<> EnumServiceArgType to_service_arg_type<float>() { return SERVICE_ARG_TYPE_FLOAT; }
|
||||||
this->key_ = value;
|
template<> EnumServiceArgType to_service_arg_type<std::string>() { return SERVICE_ARG_TYPE_STRING; }
|
||||||
return true;
|
template<> EnumServiceArgType to_service_arg_type<std::vector<bool>>() { return SERVICE_ARG_TYPE_BOOL_ARRAY; }
|
||||||
default:
|
template<> EnumServiceArgType to_service_arg_type<std::vector<int>>() { return SERVICE_ARG_TYPE_INT_ARRAY; }
|
||||||
return false;
|
template<> EnumServiceArgType to_service_arg_type<std::vector<float>>() { return SERVICE_ARG_TYPE_FLOAT_ARRAY; }
|
||||||
}
|
template<> EnumServiceArgType to_service_arg_type<std::vector<std::string>>() { return SERVICE_ARG_TYPE_STRING_ARRAY; }
|
||||||
}
|
|
||||||
bool ExecuteServiceRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 2: { // repeated ExecuteServiceArgument args = 2;
|
|
||||||
ExecuteServiceArgument arg;
|
|
||||||
arg.decode(value, len);
|
|
||||||
this->args_.push_back(arg);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
APIMessageType ExecuteServiceRequest::message_type() const { return APIMessageType::EXECUTE_SERVICE_REQUEST; }
|
|
||||||
const std::vector<ExecuteServiceArgument> &ExecuteServiceRequest::get_args() const { return this->args_; }
|
|
||||||
uint32_t ExecuteServiceRequest::get_key() const { return this->key_; }
|
|
||||||
|
|
||||||
ServiceTypeArgument::ServiceTypeArgument(const std::string &name, ServiceArgType type) : name_(name), type_(type) {}
|
|
||||||
const std::string &ServiceTypeArgument::get_name() const { return this->name_; }
|
|
||||||
ServiceArgType ServiceTypeArgument::get_type() const { return this->type_; }
|
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -2,124 +2,71 @@
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "api_message.h"
|
#include "api_pb2.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
enum ServiceArgType {
|
|
||||||
SERVICE_ARG_TYPE_BOOL = 0,
|
|
||||||
SERVICE_ARG_TYPE_INT = 1,
|
|
||||||
SERVICE_ARG_TYPE_FLOAT = 2,
|
|
||||||
SERVICE_ARG_TYPE_STRING = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
class ServiceTypeArgument {
|
|
||||||
public:
|
|
||||||
ServiceTypeArgument(const std::string &name, ServiceArgType type);
|
|
||||||
const std::string &get_name() const;
|
|
||||||
ServiceArgType get_type() const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::string name_;
|
|
||||||
ServiceArgType type_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ExecuteServiceArgument : public APIMessage {
|
|
||||||
public:
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
template<typename T> T get_value();
|
|
||||||
|
|
||||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
|
||||||
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
|
|
||||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool value_bool_{false};
|
|
||||||
int value_int_{0};
|
|
||||||
float value_float_{0.0f};
|
|
||||||
std::string value_string_{};
|
|
||||||
};
|
|
||||||
|
|
||||||
class ExecuteServiceRequest : public APIMessage {
|
|
||||||
public:
|
|
||||||
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
|
|
||||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
|
||||||
APIMessageType message_type() const override;
|
|
||||||
|
|
||||||
uint32_t get_key() const;
|
|
||||||
const std::vector<ExecuteServiceArgument> &get_args() const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
uint32_t key_;
|
|
||||||
std::vector<ExecuteServiceArgument> args_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class UserServiceDescriptor {
|
class UserServiceDescriptor {
|
||||||
public:
|
public:
|
||||||
virtual void encode_list_service_response(APIBuffer &buffer) = 0;
|
virtual ListEntitiesServicesResponse encode_list_service_response() = 0;
|
||||||
|
|
||||||
virtual bool execute_service(const ExecuteServiceRequest &req) = 0;
|
virtual bool execute_service(const ExecuteServiceRequest &req) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class UserService : public UserServiceDescriptor, public Trigger<Ts...> {
|
template<typename T> T get_execute_arg_value(const ExecuteServiceArgument &arg);
|
||||||
|
|
||||||
|
template<typename T> EnumServiceArgType to_service_arg_type();
|
||||||
|
|
||||||
|
template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
||||||
public:
|
public:
|
||||||
UserService(const std::string &name, const std::array<ServiceTypeArgument, sizeof...(Ts)> &args);
|
UserServiceBase(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
|
||||||
|
: name_(name), arg_names_(arg_names) {
|
||||||
|
this->key_ = fnv1_hash(this->name_);
|
||||||
|
}
|
||||||
|
|
||||||
void encode_list_service_response(APIBuffer &buffer) override;
|
ListEntitiesServicesResponse encode_list_service_response() override {
|
||||||
|
ListEntitiesServicesResponse msg;
|
||||||
|
msg.name = this->name_;
|
||||||
|
msg.key = this->key_;
|
||||||
|
std::array<EnumServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
|
||||||
|
for (int i = 0; i < sizeof...(Ts); i++) {
|
||||||
|
ListEntitiesServicesArgument arg;
|
||||||
|
arg.type = arg_types[i];
|
||||||
|
arg.name = this->arg_names_[i];
|
||||||
|
msg.args.push_back(arg);
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
bool execute_service(const ExecuteServiceRequest &req) override;
|
bool execute_service(const ExecuteServiceRequest &req) override {
|
||||||
|
if (req.key != this->key_)
|
||||||
|
return false;
|
||||||
|
if (req.args.size() != this->arg_names_.size())
|
||||||
|
return false;
|
||||||
|
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
template<int... S> void execute_(std::vector<ExecuteServiceArgument> args, seq<S...>);
|
virtual void execute(Ts... x) = 0;
|
||||||
|
template<int... S> void execute_(std::vector<ExecuteServiceArgument> args, seq<S...>) {
|
||||||
|
this->execute((get_execute_arg_value<Ts>(args[S]))...);
|
||||||
|
}
|
||||||
|
|
||||||
std::string name_;
|
std::string name_;
|
||||||
uint32_t key_{0};
|
uint32_t key_{0};
|
||||||
std::array<ServiceTypeArgument, sizeof...(Ts)> args_;
|
std::array<std::string, sizeof...(Ts)> arg_names_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts>
|
template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...>, public Trigger<Ts...> {
|
||||||
template<int... S>
|
public:
|
||||||
void UserService<Ts...>::execute_(std::vector<ExecuteServiceArgument> args, seq<S...>) {
|
UserServiceTrigger(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
|
||||||
this->trigger((args[S].get_value<Ts>())...);
|
: UserServiceBase<Ts...>(name, arg_names) {}
|
||||||
}
|
|
||||||
template<typename... Ts> void UserService<Ts...>::encode_list_service_response(APIBuffer &buffer) {
|
|
||||||
// string name = 1;
|
|
||||||
buffer.encode_string(1, this->name_);
|
|
||||||
// fixed32 key = 2;
|
|
||||||
buffer.encode_fixed32(2, this->key_);
|
|
||||||
|
|
||||||
// repeated ListServicesArgument args = 3;
|
protected:
|
||||||
for (auto &arg : this->args_) {
|
void execute(Ts... x) override { this->trigger(x...); } // NOLINT
|
||||||
auto nested = buffer.begin_nested(3);
|
};
|
||||||
// string name = 1;
|
|
||||||
buffer.encode_string(1, arg.get_name());
|
|
||||||
// Type type = 2;
|
|
||||||
buffer.encode_int32(2, arg.get_type());
|
|
||||||
buffer.end_nested(nested);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
template<typename... Ts> bool UserService<Ts...>::execute_service(const ExecuteServiceRequest &req) {
|
|
||||||
if (req.get_key() != this->key_)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (req.get_args().size() != this->args_.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->execute_(req.get_args(), typename gens<sizeof...(Ts)>::type());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
template<typename... Ts>
|
|
||||||
UserService<Ts...>::UserService(const std::string &name, const std::array<ServiceTypeArgument, sizeof...(Ts)> &args)
|
|
||||||
: name_(name), args_(args) {
|
|
||||||
this->key_ = fnv1_hash(this->name_);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<> bool ExecuteServiceArgument::get_value<bool>();
|
|
||||||
template<> int ExecuteServiceArgument::get_value<int>();
|
|
||||||
template<> float ExecuteServiceArgument::get_value<float>();
|
|
||||||
template<> std::string ExecuteServiceArgument::get_value<std::string>();
|
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -7,166 +7,6 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
APIBuffer::APIBuffer(std::vector<uint8_t> *buffer) : buffer_(buffer) {}
|
|
||||||
size_t APIBuffer::get_length() const { return this->buffer_->size(); }
|
|
||||||
void APIBuffer::write(uint8_t value) { this->buffer_->push_back(value); }
|
|
||||||
void APIBuffer::encode_uint32(uint32_t field, uint32_t value, bool force) {
|
|
||||||
if (value == 0 && !force)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this->encode_field_raw(field, 0);
|
|
||||||
this->encode_varint_raw(value);
|
|
||||||
}
|
|
||||||
void APIBuffer::encode_int32(uint32_t field, int32_t value, bool force) {
|
|
||||||
this->encode_uint32(field, static_cast<uint32_t>(value), force);
|
|
||||||
}
|
|
||||||
void APIBuffer::encode_bool(uint32_t field, bool value, bool force) {
|
|
||||||
if (!value && !force)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this->encode_field_raw(field, 0);
|
|
||||||
this->write(0x01);
|
|
||||||
}
|
|
||||||
void APIBuffer::encode_string(uint32_t field, const std::string &value) {
|
|
||||||
this->encode_string(field, value.data(), value.size());
|
|
||||||
}
|
|
||||||
void APIBuffer::encode_bytes(uint32_t field, const uint8_t *data, size_t len) {
|
|
||||||
this->encode_string(field, reinterpret_cast<const char *>(data), len);
|
|
||||||
}
|
|
||||||
void APIBuffer::encode_string(uint32_t field, const char *string, size_t len) {
|
|
||||||
if (len == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this->encode_field_raw(field, 2);
|
|
||||||
this->encode_varint_raw(len);
|
|
||||||
const uint8_t *data = reinterpret_cast<const uint8_t *>(string);
|
|
||||||
for (size_t i = 0; i < len; i++) {
|
|
||||||
this->write(data[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void APIBuffer::encode_fixed32(uint32_t field, uint32_t value, bool force) {
|
|
||||||
if (value == 0 && !force)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this->encode_field_raw(field, 5);
|
|
||||||
this->write((value >> 0) & 0xFF);
|
|
||||||
this->write((value >> 8) & 0xFF);
|
|
||||||
this->write((value >> 16) & 0xFF);
|
|
||||||
this->write((value >> 24) & 0xFF);
|
|
||||||
}
|
|
||||||
void APIBuffer::encode_float(uint32_t field, float value, bool force) {
|
|
||||||
if (value == 0.0f && !force)
|
|
||||||
return;
|
|
||||||
|
|
||||||
union {
|
|
||||||
float value_f;
|
|
||||||
uint32_t value_raw;
|
|
||||||
} val;
|
|
||||||
val.value_f = value;
|
|
||||||
this->encode_fixed32(field, val.value_raw);
|
|
||||||
}
|
|
||||||
void APIBuffer::encode_field_raw(uint32_t field, uint32_t type) {
|
|
||||||
uint32_t val = (field << 3) | (type & 0b111);
|
|
||||||
this->encode_varint_raw(val);
|
|
||||||
}
|
|
||||||
void APIBuffer::encode_varint_raw(uint32_t value) {
|
|
||||||
if (value <= 0x7F) {
|
|
||||||
this->write(value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (value) {
|
|
||||||
uint8_t temp = value & 0x7F;
|
|
||||||
value >>= 7;
|
|
||||||
if (value) {
|
|
||||||
this->write(temp | 0x80);
|
|
||||||
} else {
|
|
||||||
this->write(temp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void APIBuffer::encode_sint32(uint32_t field, int32_t value, bool force) {
|
|
||||||
if (value < 0)
|
|
||||||
this->encode_uint32(field, ~(uint32_t(value) << 1), force);
|
|
||||||
else
|
|
||||||
this->encode_uint32(field, uint32_t(value) << 1, force);
|
|
||||||
}
|
|
||||||
void APIBuffer::encode_nameable(Nameable *nameable) {
|
|
||||||
// string object_id = 1;
|
|
||||||
this->encode_string(1, nameable->get_object_id());
|
|
||||||
// fixed32 key = 2;
|
|
||||||
this->encode_fixed32(2, nameable->get_object_id_hash());
|
|
||||||
// string name = 3;
|
|
||||||
this->encode_string(3, nameable->get_name());
|
|
||||||
}
|
|
||||||
size_t APIBuffer::begin_nested(uint32_t field) {
|
|
||||||
this->encode_field_raw(field, 2);
|
|
||||||
return this->buffer_->size();
|
|
||||||
}
|
|
||||||
void APIBuffer::end_nested(size_t begin_index) {
|
|
||||||
const uint32_t nested_length = this->buffer_->size() - begin_index;
|
|
||||||
// add varint
|
|
||||||
std::vector<uint8_t> var;
|
|
||||||
uint32_t val = nested_length;
|
|
||||||
if (val <= 0x7F) {
|
|
||||||
var.push_back(val);
|
|
||||||
} else {
|
|
||||||
while (val) {
|
|
||||||
uint8_t temp = val & 0x7F;
|
|
||||||
val >>= 7;
|
|
||||||
if (val) {
|
|
||||||
var.push_back(temp | 0x80);
|
|
||||||
} else {
|
|
||||||
var.push_back(temp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this->buffer_->insert(this->buffer_->begin() + begin_index, var.begin(), var.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
optional<uint32_t> proto_decode_varuint32(const uint8_t *buf, size_t len, uint32_t *consumed) {
|
|
||||||
if (len == 0)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
uint32_t result = 0;
|
|
||||||
uint8_t bitpos = 0;
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < len; i++) {
|
|
||||||
uint8_t val = buf[i];
|
|
||||||
result |= uint32_t(val & 0x7F) << bitpos;
|
|
||||||
bitpos += 7;
|
|
||||||
if ((val & 0x80) == 0) {
|
|
||||||
if (consumed != nullptr) {
|
|
||||||
*consumed = i + 1;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string as_string(const uint8_t *value, size_t len) {
|
|
||||||
return std::string(reinterpret_cast<const char *>(value), len);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t as_sint32(uint32_t val) {
|
|
||||||
if (val & 1)
|
|
||||||
return uint32_t(~(val >> 1));
|
|
||||||
else
|
|
||||||
return uint32_t(val >> 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
float as_float(uint32_t val) {
|
|
||||||
static_assert(sizeof(uint32_t) == sizeof(float), "float must be 32bit long");
|
|
||||||
union {
|
|
||||||
uint32_t raw;
|
|
||||||
float value;
|
|
||||||
} x;
|
|
||||||
x.raw = val;
|
|
||||||
return x.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
ComponentIterator::ComponentIterator(APIServer *server) : server_(server) {}
|
ComponentIterator::ComponentIterator(APIServer *server) : server_(server) {}
|
||||||
void ComponentIterator::begin() {
|
void ComponentIterator::begin() {
|
||||||
this->state_ = IteratorState::BEGIN;
|
this->state_ = IteratorState::BEGIN;
|
||||||
|
|
|
@ -10,40 +10,6 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
class APIBuffer {
|
|
||||||
public:
|
|
||||||
APIBuffer(std::vector<uint8_t> *buffer);
|
|
||||||
|
|
||||||
size_t get_length() const;
|
|
||||||
void write(uint8_t value);
|
|
||||||
|
|
||||||
void encode_int32(uint32_t field, int32_t value, bool force = false);
|
|
||||||
void encode_uint32(uint32_t field, uint32_t value, bool force = false);
|
|
||||||
void encode_sint32(uint32_t field, int32_t value, bool force = false);
|
|
||||||
void encode_bool(uint32_t field, bool value, bool force = false);
|
|
||||||
void encode_string(uint32_t field, const std::string &value);
|
|
||||||
void encode_string(uint32_t field, const char *string, size_t len);
|
|
||||||
void encode_bytes(uint32_t field, const uint8_t *data, size_t len);
|
|
||||||
void encode_fixed32(uint32_t field, uint32_t value, bool force = false);
|
|
||||||
void encode_float(uint32_t field, float value, bool force = false);
|
|
||||||
void encode_nameable(Nameable *nameable);
|
|
||||||
|
|
||||||
size_t begin_nested(uint32_t field);
|
|
||||||
void end_nested(size_t begin_index);
|
|
||||||
|
|
||||||
void encode_field_raw(uint32_t field, uint32_t type);
|
|
||||||
void encode_varint_raw(uint32_t value);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::vector<uint8_t> *buffer_;
|
|
||||||
};
|
|
||||||
|
|
||||||
optional<uint32_t> proto_decode_varuint32(const uint8_t *buf, size_t len, uint32_t *consumed = nullptr);
|
|
||||||
|
|
||||||
std::string as_string(const uint8_t *value, size_t len);
|
|
||||||
int32_t as_sint32(uint32_t val);
|
|
||||||
float as_float(uint32_t val);
|
|
||||||
|
|
||||||
class APIServer;
|
class APIServer;
|
||||||
class UserServiceDescriptor;
|
class UserServiceDescriptor;
|
||||||
|
|
||||||
|
|
47
esphome/components/as3935/__init__.py
Normal file
47
esphome/components/as3935/__init__.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome import pins
|
||||||
|
from esphome.const import CONF_PIN, CONF_INDOOR, CONF_WATCHDOG_THRESHOLD, \
|
||||||
|
CONF_NOISE_LEVEL, CONF_SPIKE_REJECTION, CONF_LIGHTNING_THRESHOLD, \
|
||||||
|
CONF_MASK_DISTURBER, CONF_DIV_RATIO, CONF_CAPACITANCE
|
||||||
|
from esphome.core import coroutine
|
||||||
|
|
||||||
|
|
||||||
|
AUTO_LOAD = ['sensor', 'binary_sensor']
|
||||||
|
MULTI_CONF = True
|
||||||
|
|
||||||
|
CONF_AS3935_ID = 'as3935_id'
|
||||||
|
|
||||||
|
as3935_ns = cg.esphome_ns.namespace('as3935')
|
||||||
|
AS3935 = as3935_ns.class_('AS3935Component', cg.Component)
|
||||||
|
|
||||||
|
AS3935_SCHEMA = cv.Schema({
|
||||||
|
cv.GenerateID(): cv.declare_id(AS3935),
|
||||||
|
cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema,
|
||||||
|
pins.validate_has_interrupt),
|
||||||
|
|
||||||
|
cv.Optional(CONF_INDOOR, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_NOISE_LEVEL, default=2): cv.int_range(min=1, max=7),
|
||||||
|
cv.Optional(CONF_WATCHDOG_THRESHOLD, default=2): cv.int_range(min=1, max=10),
|
||||||
|
cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11),
|
||||||
|
cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(1, 5, 9, 16, int=True),
|
||||||
|
cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 22, 64, 128, int=True),
|
||||||
|
cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine
|
||||||
|
def setup_as3935(var, config):
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
|
||||||
|
pin = yield cg.gpio_pin_expression(config[CONF_PIN])
|
||||||
|
cg.add(var.set_pin(pin))
|
||||||
|
cg.add(var.set_indoor(config[CONF_INDOOR]))
|
||||||
|
cg.add(var.set_noise_level(config[CONF_NOISE_LEVEL]))
|
||||||
|
cg.add(var.set_watchdog_threshold(config[CONF_WATCHDOG_THRESHOLD]))
|
||||||
|
cg.add(var.set_spike_rejection(config[CONF_SPIKE_REJECTION]))
|
||||||
|
cg.add(var.set_lightning_threshold(config[CONF_LIGHTNING_THRESHOLD]))
|
||||||
|
cg.add(var.set_mask_disturber(config[CONF_MASK_DISTURBER]))
|
||||||
|
cg.add(var.set_div_ratio(config[CONF_DIV_RATIO]))
|
||||||
|
cg.add(var.set_capacitance(config[CONF_CAPACITANCE]))
|
228
esphome/components/as3935/as3935.cpp
Normal file
228
esphome/components/as3935/as3935.cpp
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
#include "as3935.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace as3935 {
|
||||||
|
|
||||||
|
static const char *TAG = "as3935";
|
||||||
|
|
||||||
|
void AS3935Component::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up AS3935...");
|
||||||
|
|
||||||
|
this->pin_->setup();
|
||||||
|
this->store_.pin = this->pin_->to_isr();
|
||||||
|
LOG_PIN(" Interrupt Pin: ", this->pin_);
|
||||||
|
this->pin_->attach_interrupt(AS3935ComponentStore::gpio_intr, &this->store_, RISING);
|
||||||
|
|
||||||
|
// Write properties to sensor
|
||||||
|
this->write_indoor(this->indoor_);
|
||||||
|
this->write_noise_level(this->noise_level_);
|
||||||
|
this->write_watchdog_threshold(this->watchdog_threshold_);
|
||||||
|
this->write_spike_rejection(this->spike_rejection_);
|
||||||
|
this->write_lightning_threshold(this->lightning_threshold_);
|
||||||
|
this->write_mask_disturber(this->mask_disturber_);
|
||||||
|
this->write_div_ratio(this->div_ratio_);
|
||||||
|
this->write_capacitance(this->capacitance_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AS3935Component::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "AS3935:");
|
||||||
|
LOG_PIN(" Interrupt Pin: ", this->pin_);
|
||||||
|
}
|
||||||
|
|
||||||
|
float AS3935Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
void AS3935Component::loop() {
|
||||||
|
if (!this->store_.interrupt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint8_t int_value = this->read_interrupt_register_();
|
||||||
|
if (int_value == NOISE_INT) {
|
||||||
|
ESP_LOGI(TAG, "Noise was detected - try increasing the noise level value!");
|
||||||
|
} else if (int_value == DISTURBER_INT) {
|
||||||
|
ESP_LOGI(TAG, "Disturber was detected - try increasing the spike rejection value!");
|
||||||
|
} else if (int_value == LIGHTNING_INT) {
|
||||||
|
ESP_LOGI(TAG, "Lightning has been detected!");
|
||||||
|
if (this->thunder_alert_binary_sensor_ != nullptr)
|
||||||
|
this->thunder_alert_binary_sensor_->publish_state(true);
|
||||||
|
uint8_t distance = this->get_distance_to_storm_();
|
||||||
|
if (this->distance_sensor_ != nullptr)
|
||||||
|
this->distance_sensor_->publish_state(distance);
|
||||||
|
uint32_t energy = this->get_lightning_energy_();
|
||||||
|
if (this->energy_sensor_ != nullptr)
|
||||||
|
this->energy_sensor_->publish_state(energy);
|
||||||
|
}
|
||||||
|
this->thunder_alert_binary_sensor_->publish_state(false);
|
||||||
|
this->store_.interrupt = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AS3935Component::write_indoor(bool indoor) {
|
||||||
|
ESP_LOGV(TAG, "Setting indoor to %d", indoor);
|
||||||
|
if (indoor)
|
||||||
|
this->write_register(AFE_GAIN, GAIN_MASK, INDOOR, 1);
|
||||||
|
else
|
||||||
|
this->write_register(AFE_GAIN, GAIN_MASK, OUTDOOR, 1);
|
||||||
|
}
|
||||||
|
// REG0x01, bits[3:0], manufacturer default: 0010 (2).
|
||||||
|
// This setting determines the threshold for events that trigger the
|
||||||
|
// IRQ Pin.
|
||||||
|
void AS3935Component::write_watchdog_threshold(uint8_t watchdog_threshold) {
|
||||||
|
ESP_LOGV(TAG, "Setting watchdog sensitivity to %d", watchdog_threshold);
|
||||||
|
if ((watchdog_threshold < 1) || (watchdog_threshold > 10)) // 10 is the max sensitivity setting
|
||||||
|
return;
|
||||||
|
this->write_register(THRESHOLD, THRESH_MASK, watchdog_threshold, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// REG0x01, bits [6:4], manufacturer default: 010 (2).
|
||||||
|
// The noise floor level is compared to a known reference voltage. If this
|
||||||
|
// level is exceeded the chip will issue an interrupt to the IRQ pin,
|
||||||
|
// broadcasting that it can not operate properly due to noise (INT_NH).
|
||||||
|
// Check datasheet for specific noise level tolerances when setting this register.
|
||||||
|
void AS3935Component::write_noise_level(uint8_t noise_level) {
|
||||||
|
ESP_LOGV(TAG, "Setting noise level to %d", noise_level);
|
||||||
|
if ((noise_level < 1) || (noise_level > 7))
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->write_register(THRESHOLD, NOISE_FLOOR_MASK, noise_level, 4);
|
||||||
|
}
|
||||||
|
// REG0x02, bits [3:0], manufacturer default: 0010 (2).
|
||||||
|
// This setting, like the watchdog threshold, can help determine between false
|
||||||
|
// events and actual lightning. The shape of the spike is analyzed during the
|
||||||
|
// chip's signal validation routine. Increasing this value increases robustness
|
||||||
|
// at the cost of sensitivity to distant events.
|
||||||
|
void AS3935Component::write_spike_rejection(uint8_t spike_rejection) {
|
||||||
|
ESP_LOGV(TAG, "Setting spike rejection to %d", spike_rejection);
|
||||||
|
if ((spike_rejection < 1) || (spike_rejection > 11))
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->write_register(LIGHTNING_REG, SPIKE_MASK, spike_rejection, 0);
|
||||||
|
}
|
||||||
|
// REG0x02, bits [5:4], manufacturer default: 0 (single lightning strike).
|
||||||
|
// The number of lightning events before IRQ is set high. 15 minutes is The
|
||||||
|
// window of time before the number of detected lightning events is reset.
|
||||||
|
// The number of lightning strikes can be set to 1,5,9, or 16.
|
||||||
|
void AS3935Component::write_lightning_threshold(uint8_t lightning_threshold) {
|
||||||
|
ESP_LOGV(TAG, "Setting lightning threshold to %d", lightning_threshold);
|
||||||
|
switch (lightning_threshold) {
|
||||||
|
case 1:
|
||||||
|
this->write_register(LIGHTNING_REG, ((1 << 5) | (1 << 4)), 0, 4); // Demonstrative
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
this->write_register(LIGHTNING_REG, ((1 << 5) | (1 << 4)), 1, 4);
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
this->write_register(LIGHTNING_REG, ((1 << 5) | (1 << 4)), 1, 5);
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
this->write_register(LIGHTNING_REG, ((1 << 5) | (1 << 4)), 3, 4);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// REG0x03, bit [5], manufacturer default: 0.
|
||||||
|
// This setting will return whether or not disturbers trigger the IRQ Pin.
|
||||||
|
void AS3935Component::write_mask_disturber(bool enabled) {
|
||||||
|
ESP_LOGV(TAG, "Setting mask disturber to %d", enabled);
|
||||||
|
if (enabled) {
|
||||||
|
this->write_register(INT_MASK_ANT, (1 << 5), 1, 5);
|
||||||
|
} else {
|
||||||
|
this->write_register(INT_MASK_ANT, (1 << 5), 0, 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// REG0x03, bit [7:6], manufacturer default: 0 (16 division ratio).
|
||||||
|
// The antenna is designed to resonate at 500kHz and so can be tuned with the
|
||||||
|
// following setting. The accuracy of the antenna must be within 3.5 percent of
|
||||||
|
// that value for proper signal validation and distance estimation.
|
||||||
|
void AS3935Component::write_div_ratio(uint8_t div_ratio) {
|
||||||
|
ESP_LOGV(TAG, "Setting div ratio to %d", div_ratio);
|
||||||
|
switch (div_ratio) {
|
||||||
|
case 16:
|
||||||
|
this->write_register(INT_MASK_ANT, ((1 << 7) | (1 << 6)), 0, 6);
|
||||||
|
break;
|
||||||
|
case 22:
|
||||||
|
this->write_register(INT_MASK_ANT, ((1 << 7) | (1 << 6)), 1, 6);
|
||||||
|
break;
|
||||||
|
case 64:
|
||||||
|
this->write_register(INT_MASK_ANT, ((1 << 7) | (1 << 6)), 1, 7);
|
||||||
|
break;
|
||||||
|
case 128:
|
||||||
|
this->write_register(INT_MASK_ANT, ((1 << 7) | (1 << 6)), 3, 6);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// REG0x08, bits [3:0], manufacturer default: 0.
|
||||||
|
// This setting will add capacitance to the series RLC antenna on the product
|
||||||
|
// to help tune its resonance. The datasheet specifies being within 3.5 percent
|
||||||
|
// of 500kHz to get optimal lightning detection and distance sensing.
|
||||||
|
// It's possible to add up to 120pF in steps of 8pF to the antenna.
|
||||||
|
void AS3935Component::write_capacitance(uint8_t capacitance) {
|
||||||
|
ESP_LOGV(TAG, "Setting tune cap to %d pF", capacitance * 8);
|
||||||
|
this->write_register(FREQ_DISP_IRQ, CAP_MASK, capacitance, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// REG0x03, bits [3:0], manufacturer default: 0.
|
||||||
|
// When there is an event that exceeds the watchdog threshold, the register is written
|
||||||
|
// with the type of event. This consists of two messages: INT_D (disturber detected) and
|
||||||
|
// INT_L (Lightning detected). A third interrupt INT_NH (noise level too HIGH)
|
||||||
|
// indicates that the noise level has been exceeded and will persist until the
|
||||||
|
// noise has ended. Events are active HIGH. There is a one second window of time to
|
||||||
|
// read the interrupt register after lightning is detected, and 1.5 after
|
||||||
|
// disturber.
|
||||||
|
uint8_t AS3935Component::read_interrupt_register_() {
|
||||||
|
// A 2ms delay is added to allow for the memory register to be populated
|
||||||
|
// after the interrupt pin goes HIGH. See "Interrupt Management" in
|
||||||
|
// datasheet.
|
||||||
|
ESP_LOGV(TAG, "Calling read_interrupt_register_");
|
||||||
|
delay(2);
|
||||||
|
return this->read_register_(INT_MASK_ANT, INT_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// REG0x02, bit [6], manufacturer default: 1.
|
||||||
|
// This register clears the number of lightning strikes that has been read in
|
||||||
|
// the last 15 minute block.
|
||||||
|
void AS3935Component::clear_statistics_() {
|
||||||
|
// Write high, then low, then high to clear.
|
||||||
|
ESP_LOGV(TAG, "Calling clear_statistics_");
|
||||||
|
this->write_register(LIGHTNING_REG, (1 << 6), 1, 6);
|
||||||
|
this->write_register(LIGHTNING_REG, (1 << 6), 0, 6);
|
||||||
|
this->write_register(LIGHTNING_REG, (1 << 6), 1, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
// REG0x07, bit [5:0], manufacturer default: 0.
|
||||||
|
// This register holds the distance to the front of the storm and not the
|
||||||
|
// distance to a lightning strike.
|
||||||
|
uint8_t AS3935Component::get_distance_to_storm_() {
|
||||||
|
ESP_LOGV(TAG, "Calling get_distance_to_storm_");
|
||||||
|
return this->read_register_(DISTANCE, DISTANCE_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t AS3935Component::get_lightning_energy_() {
|
||||||
|
ESP_LOGV(TAG, "Calling get_lightning_energy_");
|
||||||
|
uint32_t pure_light = 0; // Variable for lightning energy which is just a pure number.
|
||||||
|
uint32_t temp = 0;
|
||||||
|
// Temp variable for lightning energy.
|
||||||
|
temp = this->read_register_(ENERGY_LIGHT_MMSB, ENERGY_MASK);
|
||||||
|
// Temporary Value is large enough to handle a shift of 16 bits.
|
||||||
|
pure_light = temp << 16;
|
||||||
|
temp = this->read_register(ENERGY_LIGHT_MSB);
|
||||||
|
// Temporary value is large enough to handle a shift of 8 bits.
|
||||||
|
pure_light |= temp << 8;
|
||||||
|
// No shift here, directly OR'ed into pure_light variable.
|
||||||
|
temp = this->read_register(ENERGY_LIGHT_LSB);
|
||||||
|
pure_light |= temp;
|
||||||
|
return pure_light;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t AS3935Component::read_register_(uint8_t reg, uint8_t mask) {
|
||||||
|
uint8_t value = this->read_register(reg);
|
||||||
|
value &= (~mask);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ICACHE_RAM_ATTR AS3935ComponentStore::gpio_intr(AS3935ComponentStore *arg) { arg->interrupt = true; }
|
||||||
|
|
||||||
|
} // namespace as3935
|
||||||
|
} // namespace esphome
|
119
esphome/components/as3935/as3935.h
Normal file
119
esphome/components/as3935/as3935.h
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace as3935 {
|
||||||
|
|
||||||
|
enum AS3935RegisterNames {
|
||||||
|
AFE_GAIN = 0x00,
|
||||||
|
THRESHOLD,
|
||||||
|
LIGHTNING_REG,
|
||||||
|
INT_MASK_ANT,
|
||||||
|
ENERGY_LIGHT_LSB,
|
||||||
|
ENERGY_LIGHT_MSB,
|
||||||
|
ENERGY_LIGHT_MMSB,
|
||||||
|
DISTANCE,
|
||||||
|
FREQ_DISP_IRQ,
|
||||||
|
CALIB_TRCO = 0x3A,
|
||||||
|
CALIB_SRCO = 0x3B,
|
||||||
|
DEFAULT_RESET = 0x3C,
|
||||||
|
CALIB_RCO = 0x3D
|
||||||
|
};
|
||||||
|
|
||||||
|
enum AS3935RegisterMasks {
|
||||||
|
GAIN_MASK = 0x3E,
|
||||||
|
SPIKE_MASK = 0xF,
|
||||||
|
IO_MASK = 0xC1,
|
||||||
|
DISTANCE_MASK = 0xC0,
|
||||||
|
INT_MASK = 0xF0,
|
||||||
|
THRESH_MASK = 0x0F,
|
||||||
|
R_SPIKE_MASK = 0xF0,
|
||||||
|
ENERGY_MASK = 0xF0,
|
||||||
|
CAP_MASK = 0xF0,
|
||||||
|
LIGHT_MASK = 0xCF,
|
||||||
|
DISTURB_MASK = 0xDF,
|
||||||
|
NOISE_FLOOR_MASK = 0x70,
|
||||||
|
OSC_MASK = 0xE0,
|
||||||
|
CALIB_MASK = 0x7F,
|
||||||
|
DIV_MASK = 0x3F
|
||||||
|
};
|
||||||
|
|
||||||
|
enum AS3935Values {
|
||||||
|
AS3935_ADDR = 0x03,
|
||||||
|
INDOOR = 0x12,
|
||||||
|
OUTDOOR = 0xE,
|
||||||
|
LIGHTNING_INT = 0x08,
|
||||||
|
DISTURBER_INT = 0x04,
|
||||||
|
NOISE_INT = 0x01
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Store data in a class that doesn't use multiple-inheritance (vtables in flash)
|
||||||
|
struct AS3935ComponentStore {
|
||||||
|
volatile bool interrupt;
|
||||||
|
|
||||||
|
ISRInternalGPIOPin *pin;
|
||||||
|
static void gpio_intr(AS3935ComponentStore *arg);
|
||||||
|
};
|
||||||
|
|
||||||
|
class AS3935Component : public Component {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
void loop() override;
|
||||||
|
|
||||||
|
void set_pin(GPIOPin *pin) { pin_ = pin; }
|
||||||
|
void set_distance_sensor(sensor::Sensor *distance_sensor) { distance_sensor_ = distance_sensor; }
|
||||||
|
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
|
||||||
|
void set_thunder_alert_binary_sensor(binary_sensor::BinarySensor *thunder_alert_binary_sensor) {
|
||||||
|
thunder_alert_binary_sensor_ = thunder_alert_binary_sensor;
|
||||||
|
}
|
||||||
|
void set_indoor(bool indoor) { indoor_ = indoor; }
|
||||||
|
void write_indoor(bool indoor);
|
||||||
|
void set_noise_level(uint8_t noise_level) { noise_level_ = noise_level; }
|
||||||
|
void write_noise_level(uint8_t noise_level);
|
||||||
|
void set_watchdog_threshold(uint8_t watchdog_threshold) { watchdog_threshold_ = watchdog_threshold; }
|
||||||
|
void write_watchdog_threshold(uint8_t watchdog_threshold);
|
||||||
|
void set_spike_rejection(uint8_t spike_rejection) { spike_rejection_ = spike_rejection; }
|
||||||
|
void write_spike_rejection(uint8_t write_spike_rejection);
|
||||||
|
void set_lightning_threshold(uint8_t lightning_threshold) { lightning_threshold_ = lightning_threshold; }
|
||||||
|
void write_lightning_threshold(uint8_t lightning_threshold);
|
||||||
|
void set_mask_disturber(bool mask_disturber) { mask_disturber_ = mask_disturber; }
|
||||||
|
void write_mask_disturber(bool enabled);
|
||||||
|
void set_div_ratio(uint8_t div_ratio) { div_ratio_ = div_ratio; }
|
||||||
|
void write_div_ratio(uint8_t div_ratio);
|
||||||
|
void set_capacitance(uint8_t capacitance) { capacitance_ = capacitance; }
|
||||||
|
void write_capacitance(uint8_t capacitance);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint8_t read_interrupt_register_();
|
||||||
|
void clear_statistics_();
|
||||||
|
uint8_t get_distance_to_storm_();
|
||||||
|
uint32_t get_lightning_energy_();
|
||||||
|
|
||||||
|
virtual uint8_t read_register(uint8_t reg) = 0;
|
||||||
|
uint8_t read_register_(uint8_t reg, uint8_t mask);
|
||||||
|
|
||||||
|
virtual void write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_position) = 0;
|
||||||
|
|
||||||
|
sensor::Sensor *distance_sensor_;
|
||||||
|
sensor::Sensor *energy_sensor_;
|
||||||
|
binary_sensor::BinarySensor *thunder_alert_binary_sensor_;
|
||||||
|
GPIOPin *pin_;
|
||||||
|
AS3935ComponentStore store_;
|
||||||
|
|
||||||
|
bool indoor_;
|
||||||
|
uint8_t noise_level_;
|
||||||
|
uint8_t watchdog_threshold_;
|
||||||
|
uint8_t spike_rejection_;
|
||||||
|
uint8_t lightning_threshold_;
|
||||||
|
bool mask_disturber_;
|
||||||
|
uint8_t div_ratio_;
|
||||||
|
uint8_t capacitance_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace as3935
|
||||||
|
} // namespace esphome
|
16
esphome/components/as3935/binary_sensor.py
Normal file
16
esphome/components/as3935/binary_sensor.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import binary_sensor
|
||||||
|
from . import AS3935, CONF_AS3935_ID
|
||||||
|
|
||||||
|
DEPENDENCIES = ['as3935']
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
||||||
|
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
hub = yield cg.get_variable(config[CONF_AS3935_ID])
|
||||||
|
var = yield binary_sensor.new_binary_sensor(config)
|
||||||
|
cg.add(hub.set_thunder_alert_binary_sensor(var))
|
30
esphome/components/as3935/sensor.py
Normal file
30
esphome/components/as3935/sensor.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor
|
||||||
|
from esphome.const import CONF_DISTANCE, CONF_LIGHTNING_ENERGY, \
|
||||||
|
UNIT_KILOMETER, UNIT_EMPTY, ICON_SIGNAL_DISTANCE_VARIANT, ICON_FLASH
|
||||||
|
from . import AS3935, CONF_AS3935_ID
|
||||||
|
|
||||||
|
DEPENDENCIES = ['as3935']
|
||||||
|
|
||||||
|
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),
|
||||||
|
cv.Optional(CONF_LIGHTNING_ENERGY):
|
||||||
|
sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 1),
|
||||||
|
}).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
hub = yield cg.get_variable(config[CONF_AS3935_ID])
|
||||||
|
|
||||||
|
if CONF_DISTANCE in config:
|
||||||
|
conf = config[CONF_DISTANCE]
|
||||||
|
distance_sensor = yield sensor.new_sensor(conf)
|
||||||
|
cg.add(hub.set_distance_sensor(distance_sensor))
|
||||||
|
|
||||||
|
if CONF_LIGHTNING_ENERGY in config:
|
||||||
|
conf = config[CONF_LIGHTNING_ENERGY]
|
||||||
|
lightning_energy_sensor = yield sensor.new_sensor(conf)
|
||||||
|
cg.add(hub.set_distance_sensor(lightning_energy_sensor))
|
20
esphome/components/as3935_i2c/__init__.py
Normal file
20
esphome/components/as3935_i2c/__init__.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import as3935, i2c
|
||||||
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
|
AUTO_LOAD = ['as3935']
|
||||||
|
DEPENDENCIES = ['i2c']
|
||||||
|
|
||||||
|
as3935_i2c_ns = cg.esphome_ns.namespace('as3935_i2c')
|
||||||
|
I2CAS3935 = as3935_i2c_ns.class_('I2CAS3935Component', as3935.AS3935, i2c.I2CDevice)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({
|
||||||
|
cv.GenerateID(): cv.declare_id(I2CAS3935),
|
||||||
|
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x03)))
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
yield as3935.setup_as3935(var, config)
|
||||||
|
yield i2c.register_i2c_device(var, config)
|
36
esphome/components/as3935_i2c/as3935_i2c.cpp
Normal file
36
esphome/components/as3935_i2c/as3935_i2c.cpp
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#include "as3935_i2c.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace as3935_i2c {
|
||||||
|
|
||||||
|
static const char *TAG = "as3935_i2c";
|
||||||
|
|
||||||
|
void I2CAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_pos) {
|
||||||
|
uint8_t write_reg;
|
||||||
|
if (!this->read_byte(reg, &write_reg)) {
|
||||||
|
this->mark_failed();
|
||||||
|
ESP_LOGW(TAG, "read_byte failed - increase log level for more details!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_reg &= (~mask);
|
||||||
|
write_reg |= (bits << start_pos);
|
||||||
|
|
||||||
|
if (!this->write_byte(reg, write_reg)) {
|
||||||
|
ESP_LOGW(TAG, "write_byte failed - increase log level for more details!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t I2CAS3935Component::read_register(uint8_t reg) {
|
||||||
|
uint8_t value;
|
||||||
|
if (!this->read_byte(reg, &value, 2)) {
|
||||||
|
ESP_LOGW(TAG, "Read failed!");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace as3935_i2c
|
||||||
|
} // namespace esphome
|
19
esphome/components/as3935_i2c/as3935_i2c.h
Normal file
19
esphome/components/as3935_i2c/as3935_i2c.h
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/as3935/as3935.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace as3935_i2c {
|
||||||
|
|
||||||
|
class I2CAS3935Component : public as3935::AS3935Component, public i2c::I2CDevice {
|
||||||
|
protected:
|
||||||
|
void write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_position) override;
|
||||||
|
uint8_t read_register(uint8_t reg) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace as3935_i2c
|
||||||
|
} // namespace esphome
|
20
esphome/components/as3935_spi/__init__.py
Normal file
20
esphome/components/as3935_spi/__init__.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import as3935, spi
|
||||||
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
|
AUTO_LOAD = ['as3935']
|
||||||
|
DEPENDENCIES = ['spi']
|
||||||
|
|
||||||
|
as3935_spi_ns = cg.esphome_ns.namespace('as3935_spi')
|
||||||
|
SPIAS3935 = as3935_spi_ns.class_('SPIAS3935Component', as3935.AS3935, spi.SPIDevice)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({
|
||||||
|
cv.GenerateID(): cv.declare_id(SPIAS3935)
|
||||||
|
}).extend(cv.COMPONENT_SCHEMA).extend(spi.SPI_DEVICE_SCHEMA))
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
yield as3935.setup_as3935(var, config)
|
||||||
|
yield spi.register_spi_device(var, config)
|
48
esphome/components/as3935_spi/as3935_spi.cpp
Normal file
48
esphome/components/as3935_spi/as3935_spi.cpp
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#include "as3935_spi.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace as3935_spi {
|
||||||
|
|
||||||
|
static const char *TAG = "as3935_spi";
|
||||||
|
|
||||||
|
void SPIAS3935Component::setup() {
|
||||||
|
ESP_LOGI(TAG, "SPIAS3935Component setup started!");
|
||||||
|
this->spi_setup();
|
||||||
|
ESP_LOGI(TAG, "SPI setup finished!");
|
||||||
|
AS3935Component::setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SPIAS3935Component::dump_config() {
|
||||||
|
AS3935Component::dump_config();
|
||||||
|
LOG_PIN(" CS Pin: ", this->cs_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SPIAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_pos) {
|
||||||
|
uint8_t write_reg = this->read_register(reg);
|
||||||
|
|
||||||
|
write_reg &= (~mask);
|
||||||
|
write_reg |= (bits << start_pos);
|
||||||
|
|
||||||
|
this->enable();
|
||||||
|
this->write_byte(reg);
|
||||||
|
this->write_byte(write_reg);
|
||||||
|
this->disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t SPIAS3935Component::read_register(uint8_t reg) {
|
||||||
|
uint8_t value = 0;
|
||||||
|
this->enable();
|
||||||
|
this->write_byte(reg |= SPI_READ_M);
|
||||||
|
value = this->read_byte();
|
||||||
|
// According to datsheet, the chip select must be written HIGH, LOW, HIGH
|
||||||
|
// to correctly end the READ command.
|
||||||
|
this->cs_->digital_write(true);
|
||||||
|
this->cs_->digital_write(false);
|
||||||
|
this->disable();
|
||||||
|
ESP_LOGV(TAG, "read_register_: %d", value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace as3935_spi
|
||||||
|
} // namespace esphome
|
27
esphome/components/as3935_spi/as3935_spi.h
Normal file
27
esphome/components/as3935_spi/as3935_spi.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/as3935/as3935.h"
|
||||||
|
#include "esphome/components/spi/spi.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace as3935_spi {
|
||||||
|
|
||||||
|
enum AS3935RegisterMasks { SPI_READ_M = 0x40 };
|
||||||
|
|
||||||
|
class SPIAS3935Component : public as3935::AS3935Component,
|
||||||
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||||
|
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_2MHZ> {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_position) override;
|
||||||
|
uint8_t read_register(uint8_t reg) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace as3935_spi
|
||||||
|
} // namespace esphome
|
23
esphome/components/async_tcp/__init__.py
Normal file
23
esphome/components/async_tcp/__init__.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Dummy integration to allow relying on AsyncTCP
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.const import ARDUINO_VERSION_ESP32_1_0_0, ARDUINO_VERSION_ESP32_1_0_1, \
|
||||||
|
ARDUINO_VERSION_ESP32_1_0_2
|
||||||
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(200.0)
|
||||||
|
def to_code(config):
|
||||||
|
if CORE.is_esp32:
|
||||||
|
# https://github.com/me-no-dev/AsyncTCP/blob/master/library.json
|
||||||
|
versions_requiring_older_asynctcp = [
|
||||||
|
ARDUINO_VERSION_ESP32_1_0_0,
|
||||||
|
ARDUINO_VERSION_ESP32_1_0_1,
|
||||||
|
ARDUINO_VERSION_ESP32_1_0_2,
|
||||||
|
]
|
||||||
|
if CORE.arduino_version in versions_requiring_older_asynctcp:
|
||||||
|
cg.add_library('AsyncTCP', '1.0.3')
|
||||||
|
else:
|
||||||
|
cg.add_library('AsyncTCP', '1.1.1')
|
||||||
|
elif CORE.is_esp8266:
|
||||||
|
# https://github.com/OttoWinter/ESPAsyncTCP
|
||||||
|
cg.add_library('ESPAsyncTCP-esphome', '1.2.2')
|
0
esphome/components/atm90e32/__init__.py
Normal file
0
esphome/components/atm90e32/__init__.py
Normal file
188
esphome/components/atm90e32/atm90e32.cpp
Normal file
188
esphome/components/atm90e32/atm90e32.cpp
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
#include "atm90e32.h"
|
||||||
|
#include "atm90e32_reg.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace atm90e32 {
|
||||||
|
|
||||||
|
static const char *TAG = "atm90e32";
|
||||||
|
|
||||||
|
void ATM90E32Component::update() {
|
||||||
|
if (this->read16_(ATM90E32_REGISTER_METEREN) != 1) {
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->phase_[0].voltage_sensor_ != nullptr) {
|
||||||
|
this->phase_[0].voltage_sensor_->publish_state(this->get_line_voltage_a_());
|
||||||
|
}
|
||||||
|
if (this->phase_[1].voltage_sensor_ != nullptr) {
|
||||||
|
this->phase_[1].voltage_sensor_->publish_state(this->get_line_voltage_b_());
|
||||||
|
}
|
||||||
|
if (this->phase_[2].voltage_sensor_ != nullptr) {
|
||||||
|
this->phase_[2].voltage_sensor_->publish_state(this->get_line_voltage_c_());
|
||||||
|
}
|
||||||
|
if (this->phase_[0].current_sensor_ != nullptr) {
|
||||||
|
this->phase_[0].current_sensor_->publish_state(this->get_line_current_a_());
|
||||||
|
}
|
||||||
|
if (this->phase_[1].current_sensor_ != nullptr) {
|
||||||
|
this->phase_[1].current_sensor_->publish_state(this->get_line_current_b_());
|
||||||
|
}
|
||||||
|
if (this->phase_[2].current_sensor_ != nullptr) {
|
||||||
|
this->phase_[2].current_sensor_->publish_state(this->get_line_current_c_());
|
||||||
|
}
|
||||||
|
if (this->phase_[0].power_sensor_ != nullptr) {
|
||||||
|
this->phase_[0].power_sensor_->publish_state(this->get_active_power_a_());
|
||||||
|
}
|
||||||
|
if (this->phase_[1].power_sensor_ != nullptr) {
|
||||||
|
this->phase_[1].power_sensor_->publish_state(this->get_active_power_b_());
|
||||||
|
}
|
||||||
|
if (this->phase_[2].power_sensor_ != nullptr) {
|
||||||
|
this->phase_[2].power_sensor_->publish_state(this->get_active_power_c_());
|
||||||
|
}
|
||||||
|
if (this->freq_sensor_ != nullptr) {
|
||||||
|
this->freq_sensor_->publish_state(this->get_frequency_());
|
||||||
|
}
|
||||||
|
this->status_clear_warning();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ATM90E32Component::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up ATM90E32Component...");
|
||||||
|
this->spi_setup();
|
||||||
|
|
||||||
|
uint16_t mmode0 = 0x185;
|
||||||
|
if (line_freq_ == 60) {
|
||||||
|
mmode0 |= 1 << 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A); // Perform soft reset
|
||||||
|
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA); // enable register config access
|
||||||
|
this->write16_(ATM90E32_REGISTER_METEREN, 0x0001); // Enable Metering
|
||||||
|
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != 0x0001) {
|
||||||
|
ESP_LOGW(TAG, "Could not initialize ATM90E32 IC, check SPI settings");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0x0A55); // ZX2, ZX1, ZX0 pin config
|
||||||
|
this->write16_(ATM90E32_REGISTER_MMODE0, mmode0); // Mode Config (frequency set in main program)
|
||||||
|
this->write16_(ATM90E32_REGISTER_MMODE1, pga_gain_); // PGA Gain Configuration for Current Channels
|
||||||
|
this->write16_(ATM90E32_REGISTER_PSTARTTH, 0x0AFC); // Active Startup Power Threshold = 50%
|
||||||
|
this->write16_(ATM90E32_REGISTER_QSTARTTH, 0x0AEC); // Reactive Startup Power Threshold = 50%
|
||||||
|
this->write16_(ATM90E32_REGISTER_PPHASETH, 0x00BC); // Active Phase Threshold = 10%
|
||||||
|
this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[0].volt_gain_); // A Voltage rms gain
|
||||||
|
this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[0].ct_gain_); // A line current gain
|
||||||
|
this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[1].volt_gain_); // B Voltage rms gain
|
||||||
|
this->write16_(ATM90E32_REGISTER_IGAINB, this->phase_[1].ct_gain_); // B line current gain
|
||||||
|
this->write16_(ATM90E32_REGISTER_UGAINC, this->phase_[2].volt_gain_); // C Voltage rms gain
|
||||||
|
this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[2].ct_gain_); // C line current gain
|
||||||
|
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000); // end configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
void ATM90E32Component::dump_config() {
|
||||||
|
ESP_LOGCONFIG("", "ATM90E32:");
|
||||||
|
LOG_PIN(" CS Pin: ", this->cs_);
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGE(TAG, "Communication with ATM90E32 failed!");
|
||||||
|
}
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
LOG_SENSOR(" ", "Voltage A", this->phase_[0].voltage_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Current A", this->phase_[0].current_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Power A", this->phase_[0].power_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Voltage B", this->phase_[1].voltage_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Current B", this->phase_[1].current_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Power B", this->phase_[1].power_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Voltage C", this->phase_[2].voltage_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Current C", this->phase_[2].current_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Power C", this->phase_[2].power_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Frequency", this->freq_sensor_)
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
uint16_t ATM90E32Component::read16_(uint16_t a_register) {
|
||||||
|
uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
|
||||||
|
uint8_t addrl = (a_register & 0xFF);
|
||||||
|
uint8_t data[2];
|
||||||
|
uint16_t output;
|
||||||
|
|
||||||
|
this->enable();
|
||||||
|
delayMicroseconds(10);
|
||||||
|
this->write_byte(addrh);
|
||||||
|
this->write_byte(addrl);
|
||||||
|
delayMicroseconds(4);
|
||||||
|
this->read_array(data, 2);
|
||||||
|
this->disable();
|
||||||
|
|
||||||
|
output = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF);
|
||||||
|
ESP_LOGVV(TAG, "read16_ 0x%04X output 0x%04X", a_register, output);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
|
||||||
|
uint16_t val_h = this->read16_(addr_h);
|
||||||
|
uint16_t val_l = this->read16_(addr_l);
|
||||||
|
int32_t val = (val_h << 16) | val_l;
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "read32_ addr_h 0x%04X val_h 0x%04X addr_l 0x%04X val_l 0x%04X = %d", addr_h, val_h, addr_l, val_l,
|
||||||
|
val);
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ATM90E32Component::write16_(uint16_t a_register, uint16_t val) {
|
||||||
|
uint8_t addrh = (a_register >> 8) & 0x03;
|
||||||
|
uint8_t addrl = (a_register & 0xFF);
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "write16_ 0x%04X val 0x%04X", a_register, val);
|
||||||
|
this->enable();
|
||||||
|
delayMicroseconds(10);
|
||||||
|
this->write_byte(addrh);
|
||||||
|
this->write_byte(addrl);
|
||||||
|
delayMicroseconds(4);
|
||||||
|
this->write_byte((val >> 8) & 0xff);
|
||||||
|
this->write_byte(val & 0xFF);
|
||||||
|
this->disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
float ATM90E32Component::get_line_voltage_a_() {
|
||||||
|
uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMSA);
|
||||||
|
return (float) voltage / 100;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_line_voltage_b_() {
|
||||||
|
uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMSB);
|
||||||
|
return (float) voltage / 100;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_line_voltage_c_() {
|
||||||
|
uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMSC);
|
||||||
|
return (float) voltage / 100;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_line_current_a_() {
|
||||||
|
uint16_t current = this->read16_(ATM90E32_REGISTER_IRMSA);
|
||||||
|
return (float) current / 1000;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_line_current_b_() {
|
||||||
|
uint16_t current = this->read16_(ATM90E32_REGISTER_IRMSB);
|
||||||
|
return (float) current / 1000;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_line_current_c_() {
|
||||||
|
uint16_t current = this->read16_(ATM90E32_REGISTER_IRMSC);
|
||||||
|
return (float) current / 1000;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_active_power_a_() {
|
||||||
|
int val = this->read32_(ATM90E32_REGISTER_PMEANA, ATM90E32_REGISTER_PMEANALSB);
|
||||||
|
return val * 0.00032f;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_active_power_b_() {
|
||||||
|
int val = this->read32_(ATM90E32_REGISTER_PMEANB, ATM90E32_REGISTER_PMEANBLSB);
|
||||||
|
return val * 0.00032f;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_active_power_c_() {
|
||||||
|
int val = this->read32_(ATM90E32_REGISTER_PMEANC, ATM90E32_REGISTER_PMEANCLSB);
|
||||||
|
return val * 0.00032f;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_frequency_() {
|
||||||
|
uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
|
||||||
|
return (float) freq / 100;
|
||||||
|
}
|
||||||
|
} // namespace atm90e32
|
||||||
|
} // namespace esphome
|
58
esphome/components/atm90e32/atm90e32.h
Normal file
58
esphome/components/atm90e32/atm90e32.h
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/spi/spi.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace atm90e32 {
|
||||||
|
|
||||||
|
class ATM90E32Component : public PollingComponent,
|
||||||
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
|
||||||
|
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_200KHZ> {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
void set_voltage_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].voltage_sensor_ = obj; }
|
||||||
|
void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; }
|
||||||
|
void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; }
|
||||||
|
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].volt_gain_ = gain; }
|
||||||
|
void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
|
||||||
|
|
||||||
|
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
|
||||||
|
void set_line_freq(int freq) { line_freq_ = freq; }
|
||||||
|
void set_pga_gain(uint16_t gain) { pga_gain_ = gain; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint16_t read16_(uint16_t a_register);
|
||||||
|
int read32_(uint16_t addr_h, uint16_t addr_l);
|
||||||
|
void write16_(uint16_t a_register, uint16_t val);
|
||||||
|
|
||||||
|
float get_line_voltage_a_();
|
||||||
|
float get_line_voltage_b_();
|
||||||
|
float get_line_voltage_c_();
|
||||||
|
float get_line_current_a_();
|
||||||
|
float get_line_current_b_();
|
||||||
|
float get_line_current_c_();
|
||||||
|
float get_active_power_a_();
|
||||||
|
float get_active_power_b_();
|
||||||
|
float get_active_power_c_();
|
||||||
|
float get_frequency_();
|
||||||
|
|
||||||
|
struct ATM90E32Phase {
|
||||||
|
uint16_t volt_gain_{41820};
|
||||||
|
uint16_t ct_gain_{25498};
|
||||||
|
sensor::Sensor *voltage_sensor_{nullptr};
|
||||||
|
sensor::Sensor *current_sensor_{nullptr};
|
||||||
|
sensor::Sensor *power_sensor_{nullptr};
|
||||||
|
} phase_[3];
|
||||||
|
sensor::Sensor *freq_sensor_{nullptr};
|
||||||
|
uint16_t pga_gain_{0x15};
|
||||||
|
int line_freq_{60};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace atm90e32
|
||||||
|
} // namespace esphome
|
253
esphome/components/atm90e32/atm90e32_reg.h
Normal file
253
esphome/components/atm90e32/atm90e32_reg.h
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace atm90e32 {
|
||||||
|
|
||||||
|
/* STATUS REGISTERS */
|
||||||
|
static const uint16_t ATM90E32_REGISTER_METEREN = 0x00; // Metering Enable
|
||||||
|
static const uint16_t ATM90E32_REGISTER_CHANNELMAPI = 0x01; // Current Channel Mapping Configuration
|
||||||
|
static const uint16_t ATM90E32_REGISTER_CHANNELMAPU = 0x02; // Voltage Channel Mapping Configuration
|
||||||
|
static const uint16_t ATM90E32_REGISTER_SAGPEAKDETCFG = 0x05; // Sag and Peak Detector Period Configuration
|
||||||
|
static const uint16_t ATM90E32_REGISTER_OVTH = 0x06; // Over Voltage Threshold
|
||||||
|
static const uint16_t ATM90E32_REGISTER_ZXCONFIG = 0x07; // Zero-Crossing Config
|
||||||
|
static const uint16_t ATM90E32_REGISTER_SAGTH = 0x08; // Voltage Sag Th
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PHASELOSSTH = 0x09; // Voltage Phase Losing Th
|
||||||
|
static const uint16_t ATM90E32_REGISTER_INWARNTH = 0x0A; // Neutral Current (Calculated) Warning Threshold
|
||||||
|
static const uint16_t ATM90E32_REGISTER_OITH = 0x0B; // Over Current Threshold
|
||||||
|
static const uint16_t ATM90E32_REGISTER_FREQLOTH = 0x0C; // Low Threshold for Frequency Detection
|
||||||
|
static const uint16_t ATM90E32_REGISTER_FREQHITH = 0x0D; // High Threshold for Frequency Detection
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMPWRCTRL = 0x0E; // Partial Measurement Mode Power Control
|
||||||
|
static const uint16_t ATM90E32_REGISTER_IRQ0MERGECFG = 0x0F; // IRQ0 Merge Configuration
|
||||||
|
|
||||||
|
/* EMM STATUS REGISTERS */
|
||||||
|
static const uint16_t ATM90E32_REGISTER_SOFTRESET = 0x70; // Software Reset
|
||||||
|
static const uint16_t ATM90E32_REGISTER_EMMSTATE0 = 0x71; // EMM State 0
|
||||||
|
static const uint16_t ATM90E32_REGISTER_EMMSTATE1 = 0x72; // EMM State 1
|
||||||
|
static const uint16_t ATM90E32_REGISTER_EMMINTSTATE0 = 0x73; // EMM Interrupt Status 0
|
||||||
|
static const uint16_t ATM90E32_REGISTER_EMMINTSTATE1 = 0x74; // EMM Interrupt Status 1
|
||||||
|
static const uint16_t ATM90E32_REGISTER_EMMINTEN0 = 0x75; // EMM Interrupt Enable 0
|
||||||
|
static const uint16_t ATM90E32_REGISTER_EMMINTEN1 = 0x76; // EMM Interrupt Enable 1
|
||||||
|
static const uint16_t ATM90E32_REGISTER_LASTSPIDATA = 0x78; // Last Read/Write SPI Value
|
||||||
|
static const uint16_t ATM90E32_REGISTER_CRCERRSTATUS = 0x79; // CRC Error Status
|
||||||
|
static const uint16_t ATM90E32_REGISTER_CRCDIGEST = 0x7A; // CRC Digest
|
||||||
|
static const uint16_t ATM90E32_REGISTER_CFGREGACCEN = 0x7F; // Configure Register Access Enable
|
||||||
|
static const uint16_t ATM90E32_STATUS_S0_OIPHASEAST = 1 << 15; // Over current on phase A
|
||||||
|
static const uint16_t ATM90E32_STATUS_S0_OIPHASEBST = 1 << 14; // Over current on phase B
|
||||||
|
static const uint16_t ATM90E32_STATUS_S0_OIPHASECST = 1 << 13; // Over current on phase C
|
||||||
|
static const uint16_t ATM90E32_STATUS_S0_OVPHASEAST = 1 << 12; // Over voltage on phase A
|
||||||
|
static const uint16_t ATM90E32_STATUS_S0_OVPHASEBST = 1 << 11; // Over voltage on phase B
|
||||||
|
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_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
|
||||||
|
static const uint16_t ATM90E32_STATUS_S0_CF1REVST = 1 << 3; // Energy for CF1 Forward/Reverse status
|
||||||
|
static const uint16_t ATM90E32_STATUS_S0_CF2REVST = 1 << 2; // Energy for CF2 Forward/Reverse status
|
||||||
|
static const uint16_t ATM90E32_STATUS_S0_CF3REVST = 1 << 1; // Energy for CF3 Forward/Reverse status
|
||||||
|
static const uint16_t ATM90E32_STATUS_S0_CF4REVST = 1 << 0; // Energy for CF4 Forward/Reverse status
|
||||||
|
static const uint16_t ATM90E32_STATUS_S1_FREQHIST = 1 << 15; // Frequency is greater than the high threshold
|
||||||
|
static const uint16_t ATM90E32_STATUS_S1_SAGPHASEAST = 1 << 14; // Voltage sag on phase A
|
||||||
|
static const uint16_t ATM90E32_STATUS_S1_SAGPHASEBST = 1 << 13; // Voltage sag on phase B
|
||||||
|
static const uint16_t ATM90E32_STATUS_S1_SAGPHASECST = 1 << 12; // Voltage sag on phase C
|
||||||
|
static const uint16_t ATM90E32_STATUS_S1_FREQLOST = 1 << 11; // Frequency is lesser than the low threshold
|
||||||
|
static const uint16_t ATM90E32_STATUS_S1_PHASELOSSAST = 1 << 10; // Phase loss in Phase A
|
||||||
|
static const uint16_t ATM90E32_STATUS_S1_PHASELOSSBST = 1 << 9; // Phase loss in Phase B
|
||||||
|
static const uint16_t ATM90E32_STATUS_S1_PHASELOSSCST = 1 << 8; // Phase loss in Phase C
|
||||||
|
static const uint16_t ATM90E32_STATUS_S1_QEREGTPST = 1 << 7; // ReActive Energy register of sum (T) Positive Status
|
||||||
|
static const uint16_t ATM90E32_STATUS_S1_QEREGAPST = 1 << 6; // ReActive Energy register of Channel A Positive Status
|
||||||
|
static const uint16_t ATM90E32_STATUS_S1_QEREGBPST = 1 << 5; // ReActive Energy register of Channel B Positive Status
|
||||||
|
static const uint16_t ATM90E32_STATUS_S1_QEREGCPST = 1 << 4; // ReActive Energy register of Channel C Positive Status
|
||||||
|
static const uint16_t ATM90E32_STATUS_S1_PEREGTPST = 1 << 3; // Active Energy register of sum (T) Positive Status
|
||||||
|
static const uint16_t ATM90E32_STATUS_S1_PEREGAPST = 1 << 2; // Active Energy register of Channel A Positive Status
|
||||||
|
static const uint16_t ATM90E32_STATUS_S1_PEREGBPST = 1 << 1; // Active Energy register of Channel B Positive Status
|
||||||
|
static const uint16_t ATM90E32_STATUS_S1_PEREGCPST = 1 << 0; // Active Energy register of Channel C Positive Status
|
||||||
|
|
||||||
|
/* LOW POWER MODE REGISTERS - NOT USED */
|
||||||
|
static const uint16_t ATM90E32_REGISTER_DETECTCTRL = 0x10;
|
||||||
|
static const uint16_t ATM90E32_REGISTER_DETECTTH1 = 0x11;
|
||||||
|
static const uint16_t ATM90E32_REGISTER_DETECTTH2 = 0x12;
|
||||||
|
static const uint16_t ATM90E32_REGISTER_DETECTTH3 = 0x13;
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMOFFSETA = 0x14;
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMOFFSETB = 0x15;
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMOFFSETC = 0x16;
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMPGA = 0x17;
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMIRMSA = 0x18;
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMIRMSB = 0x19;
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMIRMSC = 0x1A;
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMCONFIG = 0x10B;
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMAVGSAMPLES = 0x1C;
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMIRMSLSB = 0x1D;
|
||||||
|
|
||||||
|
/* CONFIGURATION REGISTERS */
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PLCONSTH = 0x31; // High Word of PL_Constant
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PLCONSTL = 0x32; // Low Word of PL_Constant
|
||||||
|
static const uint16_t ATM90E32_REGISTER_MMODE0 = 0x33; // Metering Mode Config
|
||||||
|
static const uint16_t ATM90E32_REGISTER_MMODE1 = 0x34; // PGA Gain Configuration for Current Channels
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PSTARTTH = 0x35; // Startup Power Th (P)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_QSTARTTH = 0x36; // Startup Power Th (Q)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_SSTARTTH = 0x37; // Startup Power Th (S)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PPHASETH = 0x38; // Startup Power Accum Th (P)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_QPHASETH = 0x39; // Startup Power Accum Th (Q)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_SPHASETH = 0x3A; // Startup Power Accum Th (S)
|
||||||
|
|
||||||
|
/* CALIBRATION REGISTERS */
|
||||||
|
static const uint16_t ATM90E32_REGISTER_POFFSETA = 0x41; // A Line Power Offset (P)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_QOFFSETA = 0x42; // A Line Power Offset (Q)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_POFFSETB = 0x43; // B Line Power Offset (P)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_QOFFSETB = 0x44; // B Line Power Offset (Q)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_POFFSETC = 0x45; // C Line Power Offset (P)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_QOFFSETC = 0x46; // C Line Power Offset (Q)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PQGAINA = 0x47; // A Line Calibration Gain
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PHIA = 0x48; // A Line Calibration Angle
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PQGAINB = 0x49; // B Line Calibration Gain
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PHIB = 0x4A; // B Line Calibration Angle
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PQGAINC = 0x4B; // C Line Calibration Gain
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PHIC = 0x4C; // C Line Calibration Angle
|
||||||
|
|
||||||
|
/* FUNDAMENTAL/HARMONIC ENERGY CALIBRATION REGISTERS */
|
||||||
|
static const uint16_t ATM90E32_REGISTER_POFFSETAF = 0x51; // A Fund Power Offset (P)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_POFFSETBF = 0x52; // B Fund Power Offset (P)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_POFFSETCF = 0x53; // C Fund Power Offset (P)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PGAINAF = 0x54; // A Fund Power Gain (P)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PGAINBF = 0x55; // B Fund Power Gain (P)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PGAINCF = 0x56; // C Fund Power Gain (P)
|
||||||
|
|
||||||
|
/* MEASUREMENT CALIBRATION REGISTERS */
|
||||||
|
static const uint16_t ATM90E32_REGISTER_UGAINA = 0x61; // A Voltage RMS Gain
|
||||||
|
static const uint16_t ATM90E32_REGISTER_IGAINA = 0x62; // A Current RMS Gain
|
||||||
|
static const uint16_t ATM90E32_REGISTER_UOFFSETA = 0x63; // A Voltage Offset
|
||||||
|
static const uint16_t ATM90E32_REGISTER_IOFFSETA = 0x64; // A Current Offset
|
||||||
|
static const uint16_t ATM90E32_REGISTER_UGAINB = 0x65; // B Voltage RMS Gain
|
||||||
|
static const uint16_t ATM90E32_REGISTER_IGAINB = 0x66; // B Current RMS Gain
|
||||||
|
static const uint16_t ATM90E32_REGISTER_UOFFSETB = 0x67; // B Voltage Offset
|
||||||
|
static const uint16_t ATM90E32_REGISTER_IOFFSETB = 0x68; // B Current Offset
|
||||||
|
static const uint16_t ATM90E32_REGISTER_UGAINC = 0x69; // C Voltage RMS Gain
|
||||||
|
static const uint16_t ATM90E32_REGISTER_IGAINC = 0x6A; // C Current RMS Gain
|
||||||
|
static const uint16_t ATM90E32_REGISTER_UOFFSETC = 0x6B; // C Voltage Offset
|
||||||
|
static const uint16_t ATM90E32_REGISTER_IOFFSETC = 0x6C; // C Current Offset
|
||||||
|
static const uint16_t ATM90E32_REGISTER_IOFFSETN = 0x6E; // N Current Offset
|
||||||
|
|
||||||
|
/* ENERGY REGISTERS */
|
||||||
|
static const uint16_t ATM90E32_REGISTER_APENERGYT = 0x80; // Total Forward Active
|
||||||
|
static const uint16_t ATM90E32_REGISTER_APENERGYA = 0x81; // A Forward Active
|
||||||
|
static const uint16_t ATM90E32_REGISTER_APENERGYB = 0x82; // B Forward Active
|
||||||
|
static const uint16_t ATM90E32_REGISTER_APENERGYC = 0x83; // C Forward Active
|
||||||
|
static const uint16_t ATM90E32_REGISTER_ANENERGYT = 0x84; // Total Reverse Active
|
||||||
|
static const uint16_t ATM90E32_REGISTER_ANENERGYA = 0x85; // A Reverse Active
|
||||||
|
static const uint16_t ATM90E32_REGISTER_ANENERGYB = 0x86; // B Reverse Active
|
||||||
|
static const uint16_t ATM90E32_REGISTER_ANENERGYC = 0x87; // C Reverse Active
|
||||||
|
static const uint16_t ATM90E32_REGISTER_RPENERGYT = 0x88; // Total Forward Reactive
|
||||||
|
static const uint16_t ATM90E32_REGISTER_RPENERGYA = 0x89; // A Forward Reactive
|
||||||
|
static const uint16_t ATM90E32_REGISTER_RPENERGYB = 0x8A; // B Forward Reactive
|
||||||
|
static const uint16_t ATM90E32_REGISTER_RPENERGYC = 0x8B; // C Forward Reactive
|
||||||
|
static const uint16_t ATM90E32_REGISTER_RNENERGYT = 0x8C; // Total Reverse Reactive
|
||||||
|
static const uint16_t ATM90E32_REGISTER_RNENERGYA = 0x8D; // A Reverse Reactive
|
||||||
|
static const uint16_t ATM90E32_REGISTER_RNENERGYB = 0x8E; // B Reverse Reactive
|
||||||
|
static const uint16_t ATM90E32_REGISTER_RNENERGYC = 0x8F; // C Reverse Reactive
|
||||||
|
|
||||||
|
static const uint16_t ATM90E32_REGISTER_SAENERGYT = 0x90; // Total Apparent Energy
|
||||||
|
static const uint16_t ATM90E32_REGISTER_SENERGYA = 0x91; // A Apparent Energy
|
||||||
|
static const uint16_t ATM90E32_REGISTER_SENERGYB = 0x92; // B Apparent Energy
|
||||||
|
static const uint16_t ATM90E32_REGISTER_SENERGYC = 0x93; // C Apparent Energy
|
||||||
|
|
||||||
|
/* FUNDAMENTAL / HARMONIC ENERGY REGISTERS */
|
||||||
|
static const uint16_t ATM90E32_REGISTER_APENERGYTF = 0xA0; // Total Forward Fund. Energy
|
||||||
|
static const uint16_t ATM90E32_REGISTER_APENERGYAF = 0xA1; // A Forward Fund. Energy
|
||||||
|
static const uint16_t ATM90E32_REGISTER_APENERGYBF = 0xA2; // B Forward Fund. Energy
|
||||||
|
static const uint16_t ATM90E32_REGISTER_APENERGYCF = 0xA3; // C Forward Fund. Energy
|
||||||
|
static const uint16_t ATM90E32_REGISTER_ANENERGYTF = 0xA4; // Total Reverse Fund Energy
|
||||||
|
static const uint16_t ATM90E32_REGISTER_ANENERGYAF = 0xA5; // A Reverse Fund. Energy
|
||||||
|
static const uint16_t ATM90E32_REGISTER_ANENERGYBF = 0xA6; // B Reverse Fund. Energy
|
||||||
|
static const uint16_t ATM90E32_REGISTER_ANENERGYCF = 0xA7; // C Reverse Fund. Energy
|
||||||
|
static const uint16_t ATM90E32_REGISTER_APENERGYTH = 0xA8; // Total Forward Harm. Energy
|
||||||
|
static const uint16_t ATM90E32_REGISTER_APENERGYAH = 0xA9; // A Forward Harm. Energy
|
||||||
|
static const uint16_t ATM90E32_REGISTER_APENERGYBH = 0xAA; // B Forward Harm. Energy
|
||||||
|
static const uint16_t ATM90E32_REGISTER_APENERGYCH = 0xAB; // C Forward Harm. Energy
|
||||||
|
static const uint16_t ATM90E32_REGISTER_ANENERGYTH = 0xAC; // Total Reverse Harm. Energy
|
||||||
|
static const uint16_t ATM90E32_REGISTER_ANENERGYAH = 0xAD; // A Reverse Harm. Energy
|
||||||
|
static const uint16_t ATM90E32_REGISTER_ANENERGYBH = 0xAE; // B Reverse Harm. Energy
|
||||||
|
static const uint16_t ATM90E32_REGISTER_ANENERGYCH = 0xAF; // C Reverse Harm. Energy
|
||||||
|
|
||||||
|
/* POWER & P.F. REGISTERS */
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANT = 0xB0; // Total Mean Power (P)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANA = 0xB1; // A Mean Power (P)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANB = 0xB2; // B Mean Power (P)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANC = 0xB3; // C Mean Power (P)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_QMEANT = 0xB4; // Total Mean Power (Q)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_QMEANA = 0xB5; // A Mean Power (Q)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_QMEANB = 0xB6; // B Mean Power (Q)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_QMEANC = 0xB7; // C Mean Power (Q)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_SMEANT = 0xB8; // Total Mean Power (S)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_SMEANA = 0xB9; // A Mean Power (S)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_SMEANB = 0xBA; // B Mean Power (S)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_SMEANC = 0xBB; // C Mean Power (S)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PFMEANT = 0xBC; // Mean Power Factor
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PFMEANA = 0xBD; // A Power Factor
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PFMEANB = 0xBE; // B Power Factor
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PFMEANC = 0xBF; // C Power Factor
|
||||||
|
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANTLSB = 0xC0; // Lower Word (Tot. Act. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANALSB = 0xC1; // Lower Word (A Act. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANBLSB = 0xC2; // Lower Word (B Act. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANCLSB = 0xC3; // Lower Word (C Act. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_QMEANTLSB = 0xC4; // Lower Word (Tot. React. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_QMEANALSB = 0xC5; // Lower Word (A React. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_QMEANBLSB = 0xC6; // Lower Word (B React. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_QMEANCLSB = 0xC7; // Lower Word (C React. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_SAMEANTLSB = 0xC8; // Lower Word (Tot. App. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_SMEANALSB = 0xC9; // Lower Word (A App. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_SMEANBLSB = 0xCA; // Lower Word (B App. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_SMEANCLSB = 0xCB; // Lower Word (C App. Power)
|
||||||
|
|
||||||
|
/* FUND/HARM POWER & V/I RMS REGISTERS */
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANTF = 0xD0; // Total Active Fund. Power
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANAF = 0xD1; // A Active Fund. Power
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANBF = 0xD2; // B Active Fund. Power
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANCF = 0xD3; // C Active Fund. Power
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANTH = 0xD4; // Total Active Harm. Power
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANAH = 0xD5; // A Active Harm. Power
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANBH = 0xD6; // B Active Harm. Power
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANCH = 0xD7; // C Active Harm. Power
|
||||||
|
static const uint16_t ATM90E32_REGISTER_URMSA = 0xD9; // A RMS Voltage
|
||||||
|
static const uint16_t ATM90E32_REGISTER_URMSB = 0xDA; // B RMS Voltage
|
||||||
|
static const uint16_t ATM90E32_REGISTER_URMSC = 0xDB; // C RMS Voltage
|
||||||
|
static const uint16_t ATM90E32_REGISTER_IRMSA = 0xDD; // A RMS Current
|
||||||
|
static const uint16_t ATM90E32_REGISTER_IRMSB = 0xDE; // B RMS Current
|
||||||
|
static const uint16_t ATM90E32_REGISTER_IRMSC = 0xDF; // C RMS Current
|
||||||
|
static const uint16_t ATM90E32_REGISTER_IRMSN = 0xD8; // Calculated N RMS Current
|
||||||
|
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANTFLSB = 0xE0; // Lower Word (Tot. Act. Fund. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANAFLSB = 0xE1; // Lower Word (A Act. Fund. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANBFLSB = 0xE2; // Lower Word (B Act. Fund. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANCFLSB = 0xE3; // Lower Word (C Act. Fund. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANTHLSB = 0xE4; // Lower Word (Tot. Act. Harm. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANAHLSB = 0xE5; // Lower Word (A Act. Harm. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANBHLSB = 0xE6; // Lower Word (B Act. Harm. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PMEANCHLSB = 0xE7; // Lower Word (C Act. Harm. Power)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_URMSALSB = 0xE9; // Lower Word (A RMS Voltage)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_URMSBLSB = 0xEA; // Lower Word (B RMS Voltage)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_URMSCLSB = 0xEB; // Lower Word (C RMS Voltage)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_IRMSALSB = 0xED; // Lower Word (A RMS Current)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_IRMSBLSB = 0xEE; // Lower Word (B RMS Current)
|
||||||
|
static const uint16_t ATM90E32_REGISTER_IRMSCLSB = 0xEF; // Lower Word (C RMS Current)
|
||||||
|
|
||||||
|
/* THD, FREQUENCY, ANGLE & TEMPTEMP REGISTERS*/
|
||||||
|
static const uint16_t ATM90E32_REGISTER_THDNUA = 0xF1; // A Voltage THD+N
|
||||||
|
static const uint16_t ATM90E32_REGISTER_THDNUB = 0xF2; // B Voltage THD+N
|
||||||
|
static const uint16_t ATM90E32_REGISTER_THDNUC = 0xF3; // C Voltage THD+N
|
||||||
|
static const uint16_t ATM90E32_REGISTER_THDNIA = 0xF5; // A Current THD+N
|
||||||
|
static const uint16_t ATM90E32_REGISTER_THDNIB = 0xF6; // B Current THD+N
|
||||||
|
static const uint16_t ATM90E32_REGISTER_THDNIC = 0xF7; // C Current THD+N
|
||||||
|
static const uint16_t ATM90E32_REGISTER_FREQ = 0xF8; // Frequency
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PANGLEA = 0xF9; // A Mean Phase Angle
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PANGLEB = 0xFA; // B Mean Phase Angle
|
||||||
|
static const uint16_t ATM90E32_REGISTER_PANGLEC = 0xFB; // C Mean Phase Angle
|
||||||
|
static const uint16_t ATM90E32_REGISTER_TEMP = 0xFC; // Measured Temperature
|
||||||
|
static const uint16_t ATM90E32_REGISTER_UANGLEA = 0xFD; // A Voltage Phase Angle
|
||||||
|
static const uint16_t ATM90E32_REGISTER_UANGLEB = 0xFE; // B Voltage Phase Angle
|
||||||
|
static const uint16_t ATM90E32_REGISTER_UANGLEC = 0xFF; // C Voltage Phase Angle
|
||||||
|
|
||||||
|
} // namespace atm90e32
|
||||||
|
} // namespace esphome
|
72
esphome/components/atm90e32/sensor.py
Normal file
72
esphome/components/atm90e32/sensor.py
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor, spi
|
||||||
|
from esphome.const import \
|
||||||
|
CONF_ID, CONF_VOLTAGE, CONF_CURRENT, CONF_POWER, CONF_FREQUENCY, \
|
||||||
|
ICON_FLASH, UNIT_HZ, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT
|
||||||
|
|
||||||
|
CONF_PHASE_A = 'phase_a'
|
||||||
|
CONF_PHASE_B = 'phase_b'
|
||||||
|
CONF_PHASE_C = 'phase_c'
|
||||||
|
|
||||||
|
CONF_LINE_FREQUENCY = 'line_frequency'
|
||||||
|
CONF_GAIN_PGA = 'gain_pga'
|
||||||
|
CONF_GAIN_VOLTAGE = 'gain_voltage'
|
||||||
|
CONF_GAIN_CT = 'gain_ct'
|
||||||
|
LINE_FREQS = {
|
||||||
|
'50HZ': 50,
|
||||||
|
'60HZ': 60,
|
||||||
|
}
|
||||||
|
PGA_GAINS = {
|
||||||
|
'1X': 0x0,
|
||||||
|
'2X': 0x15,
|
||||||
|
'4X': 0x2A,
|
||||||
|
}
|
||||||
|
|
||||||
|
atm90e32_ns = cg.esphome_ns.namespace('atm90e32')
|
||||||
|
ATM90E32Component = atm90e32_ns.class_('ATM90E32Component', cg.PollingComponent, spi.SPIDevice)
|
||||||
|
|
||||||
|
ATM90E32_PHASE_SCHEMA = cv.Schema({
|
||||||
|
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2),
|
||||||
|
cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
||||||
|
cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 2),
|
||||||
|
cv.Optional(CONF_GAIN_VOLTAGE, default=41820): cv.uint16_t,
|
||||||
|
cv.Optional(CONF_GAIN_CT, default=25498): cv.uint16_t,
|
||||||
|
})
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Schema({
|
||||||
|
cv.GenerateID(): cv.declare_id(ATM90E32Component),
|
||||||
|
cv.Optional(CONF_PHASE_A): ATM90E32_PHASE_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_HZ, ICON_FLASH, 1),
|
||||||
|
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
|
||||||
|
cv.Optional(CONF_GAIN_PGA, default='2X'): cv.enum(PGA_GAINS, upper=True),
|
||||||
|
}).extend(cv.polling_component_schema('60s')).extend(spi.SPI_DEVICE_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
yield spi.register_spi_device(var, config)
|
||||||
|
|
||||||
|
for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]):
|
||||||
|
if phase not in config:
|
||||||
|
continue
|
||||||
|
conf = config[phase]
|
||||||
|
cg.add(var.set_volt_gain(i, conf[CONF_GAIN_VOLTAGE]))
|
||||||
|
cg.add(var.set_ct_gain(i, conf[CONF_GAIN_CT]))
|
||||||
|
if CONF_VOLTAGE in conf:
|
||||||
|
sens = yield sensor.new_sensor(conf[CONF_VOLTAGE])
|
||||||
|
cg.add(var.set_voltage_sensor(i, sens))
|
||||||
|
if CONF_CURRENT in conf:
|
||||||
|
sens = yield sensor.new_sensor(conf[CONF_CURRENT])
|
||||||
|
cg.add(var.set_current_sensor(i, sens))
|
||||||
|
if CONF_POWER in conf:
|
||||||
|
sens = yield sensor.new_sensor(conf[CONF_POWER])
|
||||||
|
cg.add(var.set_power_sensor(i, sens))
|
||||||
|
if CONF_FREQUENCY in config:
|
||||||
|
sens = yield sensor.new_sensor(config[CONF_FREQUENCY])
|
||||||
|
cg.add(var.set_freq_sensor(sens))
|
||||||
|
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
|
||||||
|
cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))
|
|
@ -46,52 +46,51 @@ climate::ClimateTraits BangBangClimate::traits() {
|
||||||
traits.set_supports_heat_mode(this->supports_heat_);
|
traits.set_supports_heat_mode(this->supports_heat_);
|
||||||
traits.set_supports_two_point_target_temperature(true);
|
traits.set_supports_two_point_target_temperature(true);
|
||||||
traits.set_supports_away(this->supports_away_);
|
traits.set_supports_away(this->supports_away_);
|
||||||
|
traits.set_supports_action(true);
|
||||||
return traits;
|
return traits;
|
||||||
}
|
}
|
||||||
void BangBangClimate::compute_state_() {
|
void BangBangClimate::compute_state_() {
|
||||||
if (this->mode != climate::CLIMATE_MODE_AUTO) {
|
if (this->mode != climate::CLIMATE_MODE_AUTO) {
|
||||||
// in non-auto mode
|
// in non-auto mode
|
||||||
this->switch_to_mode_(this->mode);
|
this->switch_to_action_(static_cast<climate::ClimateAction>(this->mode));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// auto mode, compute target mode
|
|
||||||
if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || isnan(this->target_temperature_high)) {
|
if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || isnan(this->target_temperature_high)) {
|
||||||
// if any control values are nan, go to OFF (idle) mode
|
// if any control values are nan, go to OFF (idle) mode
|
||||||
this->switch_to_mode_(climate::CLIMATE_MODE_OFF);
|
this->switch_to_action_(climate::CLIMATE_ACTION_OFF);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const bool too_cold = this->current_temperature < this->target_temperature_low;
|
const bool too_cold = this->current_temperature < this->target_temperature_low;
|
||||||
const bool too_hot = this->current_temperature > this->target_temperature_high;
|
const bool too_hot = this->current_temperature > this->target_temperature_high;
|
||||||
|
|
||||||
climate::ClimateMode target_mode;
|
climate::ClimateAction target_action;
|
||||||
if (too_cold) {
|
if (too_cold) {
|
||||||
// too cold -> enable heating if possible, else idle
|
// too cold -> enable heating if possible, else idle
|
||||||
if (this->supports_heat_)
|
if (this->supports_heat_)
|
||||||
target_mode = climate::CLIMATE_MODE_HEAT;
|
target_action = climate::CLIMATE_ACTION_HEATING;
|
||||||
else
|
else
|
||||||
target_mode = climate::CLIMATE_MODE_OFF;
|
target_action = climate::CLIMATE_ACTION_OFF;
|
||||||
} else if (too_hot) {
|
} else if (too_hot) {
|
||||||
// too hot -> enable cooling if possible, else idle
|
// too hot -> enable cooling if possible, else idle
|
||||||
if (this->supports_cool_)
|
if (this->supports_cool_)
|
||||||
target_mode = climate::CLIMATE_MODE_COOL;
|
target_action = climate::CLIMATE_ACTION_COOLING;
|
||||||
else
|
else
|
||||||
target_mode = climate::CLIMATE_MODE_OFF;
|
target_action = climate::CLIMATE_ACTION_OFF;
|
||||||
} else {
|
} else {
|
||||||
// neither too hot nor too cold -> in range
|
// neither too hot nor too cold -> in range
|
||||||
if (this->supports_cool_ && this->supports_heat_) {
|
if (this->supports_cool_ && this->supports_heat_) {
|
||||||
// if supports both ends, go to idle mode
|
// if supports both ends, go to idle mode
|
||||||
target_mode = climate::CLIMATE_MODE_OFF;
|
target_action = climate::CLIMATE_ACTION_OFF;
|
||||||
} else {
|
} else {
|
||||||
// else use current mode and don't change (hysteresis)
|
// else use current mode and don't change (hysteresis)
|
||||||
target_mode = this->internal_mode_;
|
target_action = this->action;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->switch_to_mode_(target_mode);
|
this->switch_to_action_(target_action);
|
||||||
}
|
}
|
||||||
void BangBangClimate::switch_to_mode_(climate::ClimateMode mode) {
|
void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
|
||||||
if (mode == this->internal_mode_)
|
if (action == this->action)
|
||||||
// already in target mode
|
// already in target mode
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -100,14 +99,14 @@ void BangBangClimate::switch_to_mode_(climate::ClimateMode mode) {
|
||||||
this->prev_trigger_ = nullptr;
|
this->prev_trigger_ = nullptr;
|
||||||
}
|
}
|
||||||
Trigger<> *trig;
|
Trigger<> *trig;
|
||||||
switch (mode) {
|
switch (action) {
|
||||||
case climate::CLIMATE_MODE_OFF:
|
case climate::CLIMATE_ACTION_OFF:
|
||||||
trig = this->idle_trigger_;
|
trig = this->idle_trigger_;
|
||||||
break;
|
break;
|
||||||
case climate::CLIMATE_MODE_COOL:
|
case climate::CLIMATE_ACTION_COOLING:
|
||||||
trig = this->cool_trigger_;
|
trig = this->cool_trigger_;
|
||||||
break;
|
break;
|
||||||
case climate::CLIMATE_MODE_HEAT:
|
case climate::CLIMATE_ACTION_HEATING:
|
||||||
trig = this->heat_trigger_;
|
trig = this->heat_trigger_;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -116,7 +115,7 @@ void BangBangClimate::switch_to_mode_(climate::ClimateMode mode) {
|
||||||
if (trig != nullptr) {
|
if (trig != nullptr) {
|
||||||
// trig should never be null, but still check so that we don't crash
|
// trig should never be null, but still check so that we don't crash
|
||||||
trig->trigger();
|
trig->trigger();
|
||||||
this->internal_mode_ = mode;
|
this->action = action;
|
||||||
this->prev_trigger_ = trig;
|
this->prev_trigger_ = trig;
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ class BangBangClimate : public climate::Climate, public Component {
|
||||||
void compute_state_();
|
void compute_state_();
|
||||||
|
|
||||||
/// Switch the climate device to the given climate mode.
|
/// Switch the climate device to the given climate mode.
|
||||||
void switch_to_mode_(climate::ClimateMode mode);
|
void switch_to_action_(climate::ClimateAction action);
|
||||||
|
|
||||||
/// The sensor used for getting the current temperature
|
/// The sensor used for getting the current temperature
|
||||||
sensor::Sensor *sensor_{nullptr};
|
sensor::Sensor *sensor_{nullptr};
|
||||||
|
@ -74,11 +74,6 @@ class BangBangClimate : public climate::Climate, public Component {
|
||||||
* This is so that the previous trigger can be stopped before enabling a new one.
|
* This is so that the previous trigger can be stopped before enabling a new one.
|
||||||
*/
|
*/
|
||||||
Trigger<> *prev_trigger_{nullptr};
|
Trigger<> *prev_trigger_{nullptr};
|
||||||
/** The climate mode that is currently active - for a `.mode = AUTO` this will
|
|
||||||
* contain the actual mode the device
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
climate::ClimateMode internal_mode_{climate::CLIMATE_MODE_OFF};
|
|
||||||
|
|
||||||
BangBangClimateTargetTempConfig normal_config_{};
|
BangBangClimateTargetTempConfig normal_config_{};
|
||||||
bool supports_away_{false};
|
bool supports_away_{false};
|
||||||
|
|
|
@ -41,6 +41,7 @@ BinarySensorCondition = binary_sensor_ns.class_('BinarySensorCondition', Conditi
|
||||||
|
|
||||||
# Filters
|
# Filters
|
||||||
Filter = binary_sensor_ns.class_('Filter')
|
Filter = binary_sensor_ns.class_('Filter')
|
||||||
|
DelayedOnOffFilter = binary_sensor_ns.class_('DelayedOnOffFilter', Filter, cg.Component)
|
||||||
DelayedOnFilter = binary_sensor_ns.class_('DelayedOnFilter', Filter, cg.Component)
|
DelayedOnFilter = binary_sensor_ns.class_('DelayedOnFilter', Filter, cg.Component)
|
||||||
DelayedOffFilter = binary_sensor_ns.class_('DelayedOffFilter', Filter, cg.Component)
|
DelayedOffFilter = binary_sensor_ns.class_('DelayedOffFilter', Filter, cg.Component)
|
||||||
InvertFilter = binary_sensor_ns.class_('InvertFilter', Filter)
|
InvertFilter = binary_sensor_ns.class_('InvertFilter', Filter)
|
||||||
|
@ -55,6 +56,14 @@ def invert_filter_to_code(config, filter_id):
|
||||||
yield cg.new_Pvariable(filter_id)
|
yield cg.new_Pvariable(filter_id)
|
||||||
|
|
||||||
|
|
||||||
|
@FILTER_REGISTRY.register('delayed_on_off', DelayedOnOffFilter,
|
||||||
|
cv.positive_time_period_milliseconds)
|
||||||
|
def delayed_on_off_filter_to_code(config, filter_id):
|
||||||
|
var = cg.new_Pvariable(filter_id, config)
|
||||||
|
yield cg.register_component(var, {})
|
||||||
|
yield var
|
||||||
|
|
||||||
|
|
||||||
@FILTER_REGISTRY.register('delayed_on', DelayedOnFilter,
|
@FILTER_REGISTRY.register('delayed_on', DelayedOnFilter,
|
||||||
cv.positive_time_period_milliseconds)
|
cv.positive_time_period_milliseconds)
|
||||||
def delayed_on_filter_to_code(config, filter_id):
|
def delayed_on_filter_to_code(config, filter_id):
|
||||||
|
|
|
@ -30,7 +30,11 @@ void BinarySensor::publish_initial_state(bool state) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void BinarySensor::send_state_internal(bool state, bool is_initial) {
|
void BinarySensor::send_state_internal(bool state, bool is_initial) {
|
||||||
ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), state ? "ON" : "OFF");
|
if (is_initial) {
|
||||||
|
ESP_LOGD(TAG, "'%s': Sending initial state %s", this->get_name().c_str(), ONOFF(state));
|
||||||
|
} else {
|
||||||
|
ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), ONOFF(state));
|
||||||
|
}
|
||||||
this->has_state_ = true;
|
this->has_state_ = true;
|
||||||
this->state = state;
|
this->state = state;
|
||||||
if (!is_initial) {
|
if (!is_initial) {
|
||||||
|
|
|
@ -23,6 +23,19 @@ void Filter::input(bool value, bool is_initial) {
|
||||||
this->output(*b, is_initial);
|
this->output(*b, is_initial);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DelayedOnOffFilter::DelayedOnOffFilter(uint32_t delay) : delay_(delay) {}
|
||||||
|
optional<bool> DelayedOnOffFilter::new_value(bool value, bool is_initial) {
|
||||||
|
if (value) {
|
||||||
|
this->set_timeout("ON_OFF", this->delay_, [this, is_initial]() { this->output(true, is_initial); });
|
||||||
|
} else {
|
||||||
|
this->set_timeout("ON_OFF", this->delay_, [this, is_initial]() { this->output(false, is_initial); });
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
DelayedOnFilter::DelayedOnFilter(uint32_t delay) : delay_(delay) {}
|
DelayedOnFilter::DelayedOnFilter(uint32_t delay) : delay_(delay) {}
|
||||||
optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
|
optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
|
||||||
if (value) {
|
if (value) {
|
||||||
|
@ -46,6 +59,7 @@ optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
optional<bool> InvertFilter::new_value(bool value, bool is_initial) { return !value; }
|
optional<bool> InvertFilter::new_value(bool value, bool is_initial) { return !value; }
|
||||||
|
|
|
@ -25,6 +25,18 @@ class Filter {
|
||||||
Deduplicator<bool> dedup_;
|
Deduplicator<bool> dedup_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class DelayedOnOffFilter : public Filter, public Component {
|
||||||
|
public:
|
||||||
|
explicit DelayedOnOffFilter(uint32_t delay);
|
||||||
|
|
||||||
|
optional<bool> new_value(bool value, bool is_initial) override;
|
||||||
|
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint32_t delay_;
|
||||||
|
};
|
||||||
|
|
||||||
class DelayedOnFilter : public Filter, public Component {
|
class DelayedOnFilter : public Filter, public Component {
|
||||||
public:
|
public:
|
||||||
explicit DelayedOnFilter(uint32_t delay);
|
explicit DelayedOnFilter(uint32_t delay);
|
||||||
|
|
|
@ -178,7 +178,7 @@ void BME280Component::update() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float meas_time = 1;
|
float meas_time = 1.5;
|
||||||
meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_);
|
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->pressure_oversampling_) + 0.575f;
|
||||||
meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f;
|
meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f;
|
||||||
|
|
26
esphome/components/captive_portal/__init__.py
Normal file
26
esphome/components/captive_portal/__init__.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
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
|
||||||
|
|
||||||
|
AUTO_LOAD = ['web_server_base']
|
||||||
|
DEPENDENCIES = ['wifi']
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(64.0)
|
||||||
|
def to_code(config):
|
||||||
|
paren = yield cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
|
||||||
|
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID], paren)
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
cg.add_define('USE_CAPTIVE_PORTAL')
|
173
esphome/components/captive_portal/captive_portal.cpp
Normal file
173
esphome/components/captive_portal/captive_portal.cpp
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
#include "captive_portal.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/components/wifi/wifi_component.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace captive_portal {
|
||||||
|
|
||||||
|
static const char *TAG = "captive_portal";
|
||||||
|
|
||||||
|
void CaptivePortal::handle_index(AsyncWebServerRequest *request) {
|
||||||
|
AsyncResponseStream *stream = request->beginResponseStream("text/html");
|
||||||
|
stream->print(F("<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" "
|
||||||
|
"content=\"width=device-width,initial-scale=1,user-scalable=no\"/><title>"));
|
||||||
|
stream->print(App.get_name().c_str());
|
||||||
|
stream->print(F("</title><link rel=\"stylesheet\" href=\"/stylesheet.css\">"));
|
||||||
|
stream->print(F("<script>function c(l){document.getElementById('ssid').value=l.innerText||l.textContent; "
|
||||||
|
"document.getElementById('psk').focus();}</script>"));
|
||||||
|
stream->print(F("</head>"));
|
||||||
|
stream->print(F("<body><div class=\"main\"><h1>WiFi Networks</h1>"));
|
||||||
|
|
||||||
|
if (request->hasArg("save")) {
|
||||||
|
stream->print(F("<div class=\"info\">The ESP will now try to connect to the network...<br/>Please give it some "
|
||||||
|
"time to connect.<br/>Note: Copy the changed network to your YAML file - the next OTA update will "
|
||||||
|
"overwrite these settings.</div>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &scan : wifi::global_wifi_component->get_scan_result()) {
|
||||||
|
if (scan.get_is_hidden())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
stream->print(F("<div class=\"network\" onclick=\"c(this)\"><a href=\"#\" class=\"network-left\">"));
|
||||||
|
|
||||||
|
if (scan.get_rssi() >= -50) {
|
||||||
|
stream->print(F("<img src=\"/wifi-strength-4.svg\">"));
|
||||||
|
} else if (scan.get_rssi() >= -65) {
|
||||||
|
stream->print(F("<img src=\"/wifi-strength-3.svg\">"));
|
||||||
|
} else if (scan.get_rssi() >= -85) {
|
||||||
|
stream->print(F("<img src=\"/wifi-strength-2.svg\">"));
|
||||||
|
} else {
|
||||||
|
stream->print(F("<img src=\"/wifi-strength-1.svg\">"));
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->print(F("<span class=\"network-ssid\">"));
|
||||||
|
stream->print(scan.get_ssid().c_str());
|
||||||
|
stream->print(F("</span></a>"));
|
||||||
|
if (scan.get_with_auth()) {
|
||||||
|
stream->print(F("<img src=\"/lock.svg\">"));
|
||||||
|
}
|
||||||
|
stream->print(F("</div>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->print(F("<h3>WiFi Settings</h3><form method=\"GET\" action=\"/wifisave\"><input id=\"ssid\" name=\"ssid\" "
|
||||||
|
"length=32 placeholder=\"SSID\"><br/><input id=\"psk\" name=\"psk\" length=64 type=\"password\" "
|
||||||
|
"placeholder=\"Password\"><br/><br/><button type=\"submit\">Save</button></form><br><hr><br>"));
|
||||||
|
stream->print(F("<h1>OTA Update</h1><form method=\"POST\" action=\"/update\" enctype=\"multipart/form-data\"><input "
|
||||||
|
"type=\"file\" name=\"update\"><button type=\"submit\">Update</button></form>"));
|
||||||
|
stream->print(F("</div></body></html>"));
|
||||||
|
request->send(stream);
|
||||||
|
}
|
||||||
|
void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
|
||||||
|
std::string ssid = request->arg("ssid").c_str();
|
||||||
|
std::string psk = request->arg("psk").c_str();
|
||||||
|
ESP_LOGI(TAG, "Captive Portal Requested WiFi Settings Change:");
|
||||||
|
ESP_LOGI(TAG, " SSID='%s'", ssid.c_str());
|
||||||
|
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
|
||||||
|
this->override_sta_(ssid, psk);
|
||||||
|
request->redirect("/?save=true");
|
||||||
|
}
|
||||||
|
void CaptivePortal::override_sta_(const std::string &ssid, const std::string &password) {
|
||||||
|
CaptivePortalSettings save{};
|
||||||
|
strcpy(save.ssid, ssid.c_str());
|
||||||
|
strcpy(save.password, password.c_str());
|
||||||
|
this->pref_.save(&save);
|
||||||
|
|
||||||
|
wifi::WiFiAP sta{};
|
||||||
|
sta.set_ssid(ssid);
|
||||||
|
sta.set_password(password);
|
||||||
|
wifi::global_wifi_component->set_sta(sta);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptivePortal::setup() {
|
||||||
|
// Hash with compilation time
|
||||||
|
// This ensures the AP override is not applied for OTA
|
||||||
|
uint32_t hash = fnv1_hash(App.get_compilation_time());
|
||||||
|
this->pref_ = global_preferences.make_preference<CaptivePortalSettings>(hash, true);
|
||||||
|
|
||||||
|
CaptivePortalSettings save{};
|
||||||
|
if (this->pref_.load(&save)) {
|
||||||
|
this->override_sta_(save.ssid, save.password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void CaptivePortal::start() {
|
||||||
|
this->base_->init();
|
||||||
|
if (!this->initialized_) {
|
||||||
|
this->base_->add_handler(this);
|
||||||
|
this->base_->add_ota_handler();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->dns_server_ = new DNSServer();
|
||||||
|
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
|
||||||
|
IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
|
||||||
|
this->dns_server_->start(53, "*", 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()) {
|
||||||
|
not_found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (not_found) {
|
||||||
|
req->send(404, "text/html", "File not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto url = "http://" + wifi::global_wifi_component->wifi_soft_ap_ip().toString();
|
||||||
|
req->redirect(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
this->initialized_ = true;
|
||||||
|
this->active_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char STYLESHEET_CSS[] PROGMEM =
|
||||||
|
R"(*{box-sizing:inherit}div,input{padding:5px;font-size:1em}input{width:95%}body{text-align:center;font-family:sans-serif}button{border:0;border-radius:.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;padding:0}.main{text-align:left;display:inline-block;min-width:260px}.network{display:flex;justify-content:space-between;align-items:center}.network-left{display:flex;align-items:center}.network-ssid{margin-bottom:-7px;margin-left:10px}.info{border:1px solid;margin:10px 0;padding:15px 10px;color:#4f8a10;background-color:#dff2bf})";
|
||||||
|
const char LOCK_SVG[] PROGMEM =
|
||||||
|
R"(<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path d="M12 17a2 2 0 0 0 2-2 2 2 0 0 0-2-2 2 2 0 0 0-2 2 2 2 0 0 0 2 2m6-9a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2h1V6a5 5 0 0 1 5-5 5 5 0 0 1 5 5v2h1m-6-5a3 3 0 0 0-3 3v2h6V6a3 3 0 0 0-3-3z"/></svg>)";
|
||||||
|
|
||||||
|
void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
|
||||||
|
if (req->url() == "/") {
|
||||||
|
this->handle_index(req);
|
||||||
|
return;
|
||||||
|
} else if (req->url() == "/wifisave") {
|
||||||
|
this->handle_wifisave(req);
|
||||||
|
return;
|
||||||
|
} else if (req->url() == "/stylesheet.css") {
|
||||||
|
req->send_P(200, "text/css", STYLESHEET_CSS);
|
||||||
|
return;
|
||||||
|
} else if (req->url() == "/lock.svg") {
|
||||||
|
req->send_P(200, "image/svg+xml", LOCK_SVG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncResponseStream *stream = req->beginResponseStream("image/svg+xml");
|
||||||
|
stream->print(F("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\"><path d=\"M12 3A18.9 18.9 0 0 "
|
||||||
|
"0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 "));
|
||||||
|
if (req->url() == "/wifi-strength-4.svg") {
|
||||||
|
stream->print(F("3z"));
|
||||||
|
} else {
|
||||||
|
if (req->url() == "/wifi-strength-1.svg") {
|
||||||
|
stream->print(F("3m0 2c3.07 0 6.09.86 8.71 2.45l-5.1 6.36a8.43 8.43 0 0 0-7.22-.01L3.27 7.4"));
|
||||||
|
} else if (req->url() == "/wifi-strength-2.svg") {
|
||||||
|
stream->print(F("3m0 2c3.07 0 6.09.86 8.71 2.45l-3.21 3.98a11.32 11.32 0 0 0-11 0L3.27 7.4"));
|
||||||
|
} else if (req->url() == "/wifi-strength-3.svg") {
|
||||||
|
stream->print(F("3m0 2c3.07 0 6.09.86 8.71 2.45l-1.94 2.43A13.6 13.6 0 0 0 12 8C9 8 6.68 9 5.21 9.84l-1.94-2."));
|
||||||
|
}
|
||||||
|
stream->print(F("4A16.94 16.94 0 0 1 12 5z"));
|
||||||
|
}
|
||||||
|
stream->print(F("\"/></svg>"));
|
||||||
|
req->send(stream);
|
||||||
|
}
|
||||||
|
CaptivePortal::CaptivePortal(web_server_base::WebServerBase *base) : base_(base) { global_captive_portal = this; }
|
||||||
|
float CaptivePortal::get_setup_priority() const {
|
||||||
|
// Before WiFi
|
||||||
|
return setup_priority::WIFI + 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
CaptivePortal *global_captive_portal = nullptr;
|
||||||
|
|
||||||
|
} // namespace captive_portal
|
||||||
|
} // namespace esphome
|
80
esphome/components/captive_portal/captive_portal.h
Normal file
80
esphome/components/captive_portal/captive_portal.h
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <DNSServer.h>
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/preferences.h"
|
||||||
|
#include "esphome/components/web_server_base/web_server_base.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
|
||||||
|
namespace captive_portal {
|
||||||
|
|
||||||
|
struct CaptivePortalSettings {
|
||||||
|
char ssid[33];
|
||||||
|
char password[65];
|
||||||
|
} PACKED; // NOLINT
|
||||||
|
|
||||||
|
class CaptivePortal : public AsyncWebHandler, public Component {
|
||||||
|
public:
|
||||||
|
CaptivePortal(web_server_base::WebServerBase *base);
|
||||||
|
void setup() override;
|
||||||
|
void loop() override {
|
||||||
|
if (this->dns_server_ != nullptr)
|
||||||
|
this->dns_server_->processNextRequest();
|
||||||
|
}
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
void start();
|
||||||
|
bool is_active() const { return this->active_; }
|
||||||
|
void end() {
|
||||||
|
this->active_ = false;
|
||||||
|
this->base_->deinit();
|
||||||
|
this->dns_server_->stop();
|
||||||
|
delete this->dns_server_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool canHandle(AsyncWebServerRequest *request) override {
|
||||||
|
if (!this->active_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (request->method() == HTTP_GET) {
|
||||||
|
if (request->url() == "/")
|
||||||
|
return true;
|
||||||
|
if (request->url() == "/stylesheet.css")
|
||||||
|
return true;
|
||||||
|
if (request->url() == "/wifi-strength-1.svg")
|
||||||
|
return true;
|
||||||
|
if (request->url() == "/wifi-strength-2.svg")
|
||||||
|
return true;
|
||||||
|
if (request->url() == "/wifi-strength-3.svg")
|
||||||
|
return true;
|
||||||
|
if (request->url() == "/wifi-strength-4.svg")
|
||||||
|
return true;
|
||||||
|
if (request->url() == "/lock.svg")
|
||||||
|
return true;
|
||||||
|
if (request->url() == "/wifisave")
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_index(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
|
void handle_wifisave(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
|
void handleRequest(AsyncWebServerRequest *req) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void override_sta_(const std::string &ssid, const std::string &password);
|
||||||
|
|
||||||
|
web_server_base::WebServerBase *base_;
|
||||||
|
bool initialized_{false};
|
||||||
|
bool active_{false};
|
||||||
|
ESPPreferenceObject pref_;
|
||||||
|
DNSServer *dns_server_{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
extern CaptivePortal *global_captive_portal;
|
||||||
|
|
||||||
|
} // namespace captive_portal
|
||||||
|
} // namespace esphome
|
55
esphome/components/captive_portal/index.html
Normal file
55
esphome/components/captive_portal/index.html
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<!-- HTTP_HEAD -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
|
||||||
|
<title>{{ App.get_name() }}</title>
|
||||||
|
<link rel="stylesheet" href="./stylesheet.css">
|
||||||
|
<script>
|
||||||
|
function c(l) {
|
||||||
|
document.getElementById('ssid').value = l.innerText || l.textContent;
|
||||||
|
document.getElementById('psk').focus();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="main">
|
||||||
|
<h1>WiFi Networks</h1>
|
||||||
|
<div class="info">
|
||||||
|
The ESP will now try to connect to the network...<br/>
|
||||||
|
Please give it some time to connect.<br/>
|
||||||
|
Note: Copy the changed network to your YAML file - the next OTA update will overwrite these settings.
|
||||||
|
</div>
|
||||||
|
<div class="network" onclick="c(this)">
|
||||||
|
<a href="#" class="network-left">
|
||||||
|
<img src="./wifi-strength-4.svg">
|
||||||
|
<span class="network-ssid">AP1</span>
|
||||||
|
</a>
|
||||||
|
<img src="./lock.svg">
|
||||||
|
</div>
|
||||||
|
<div class="network" onclick="c(this)">
|
||||||
|
<a href="#" class="network-left">
|
||||||
|
<img src="./wifi-strength-2.svg">
|
||||||
|
<span class="network-ssid">AP2</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>WiFi Settings</h3>
|
||||||
|
<form method="GET" action="/wifisave">
|
||||||
|
<input id="ssid" name="ssid" length=32 placeholder="SSID"><br/>
|
||||||
|
<input id="psk" name="psk" length=64 type="password" placeholder="Password"><br/>
|
||||||
|
<br/>
|
||||||
|
<button type="submit">Save</button>
|
||||||
|
</form>
|
||||||
|
<br><hr>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<h1>OTA Update</h1>
|
||||||
|
<form method="POST" action="/update" enctype="multipart/form-data">
|
||||||
|
<input type="file" name="update">
|
||||||
|
<button type="submit">Update</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
1
esphome/components/captive_portal/lock.svg
Normal file
1
esphome/components/captive_portal/lock.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path d="M12 17a2 2 0 0 0 2-2 2 2 0 0 0-2-2 2 2 0 0 0-2 2 2 2 0 0 0 2 2m6-9a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2h1V6a5 5 0 0 1 5-5 5 5 0 0 1 5 5v2h1m-6-5a3 3 0 0 0-3 3v2h6V6a3 3 0 0 0-3-3z"/></svg>
|
After Width: | Height: | Size: 307 B |
58
esphome/components/captive_portal/stylesheet.css
Normal file
58
esphome/components/captive_portal/stylesheet.css
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
* {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
div, input {
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
text-align: center;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
background-color: #1fa3ec;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 2.4rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
text-align: left;
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-ssid {
|
||||||
|
margin-bottom: -7px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
border: 1px solid;
|
||||||
|
margin: 10px 0px;
|
||||||
|
padding: 15px 10px;
|
||||||
|
color: #4f8a10;
|
||||||
|
background-color: #dff2bf;
|
||||||
|
}
|
1
esphome/components/captive_portal/wifi-strength-1.svg
Normal file
1
esphome/components/captive_portal/wifi-strength-1.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M12 3A18.9 18.9 0 0 0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 3m0 2c3.07 0 6.09.86 8.71 2.45l-5.1 6.36a8.43 8.43 0 0 0-7.22-.01L3.27 7.44A16.94 16.94 0 0 1 12 5z"/></svg>
|
After Width: | Height: | Size: 268 B |
1
esphome/components/captive_portal/wifi-strength-2.svg
Normal file
1
esphome/components/captive_portal/wifi-strength-2.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M12 3A18.9 18.9 0 0 0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 3m0 2c3.07 0 6.09.86 8.71 2.45l-3.21 3.98a11.32 11.32 0 0 0-11 0L3.27 7.44A16.94 16.94 0 0 1 12 5z"/></svg>
|
After Width: | Height: | Size: 267 B |
1
esphome/components/captive_portal/wifi-strength-3.svg
Normal file
1
esphome/components/captive_portal/wifi-strength-3.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M12 3A18.9 18.9 0 0 0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 3m0 2c3.07 0 6.09.86 8.71 2.45l-1.94 2.43A13.6 13.6 0 0 0 12 8C9 8 6.68 9 5.21 9.84l-1.94-2.4A16.94 16.94 0 0 1 12 5z"/></svg>
|
After Width: | Height: | Size: 286 B |
1
esphome/components/captive_portal/wifi-strength-4.svg
Normal file
1
esphome/components/captive_portal/wifi-strength-4.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M12 3A18.9 18.9 0 0 0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 3z"/></svg>
|
After Width: | Height: | Size: 171 B |
|
@ -173,6 +173,9 @@ void Climate::publish_state() {
|
||||||
auto traits = this->get_traits();
|
auto traits = this->get_traits();
|
||||||
|
|
||||||
ESP_LOGD(TAG, " Mode: %s", climate_mode_to_string(this->mode));
|
ESP_LOGD(TAG, " Mode: %s", climate_mode_to_string(this->mode));
|
||||||
|
if (traits.get_supports_action()) {
|
||||||
|
ESP_LOGD(TAG, " Action: %s", climate_action_to_string(this->action));
|
||||||
|
}
|
||||||
if (traits.get_supports_current_temperature()) {
|
if (traits.get_supports_current_temperature()) {
|
||||||
ESP_LOGD(TAG, " Current Temperature: %.2f°C", this->current_temperature);
|
ESP_LOGD(TAG, " Current Temperature: %.2f°C", this->current_temperature);
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,6 +121,8 @@ class Climate : public Nameable {
|
||||||
|
|
||||||
/// The active mode of the climate device.
|
/// The active mode of the climate device.
|
||||||
ClimateMode mode{CLIMATE_MODE_OFF};
|
ClimateMode mode{CLIMATE_MODE_OFF};
|
||||||
|
/// The active state of the climate device.
|
||||||
|
ClimateAction action{CLIMATE_ACTION_OFF};
|
||||||
/// The current temperature of the climate device, as reported from the integration.
|
/// The current temperature of the climate device, as reported from the integration.
|
||||||
float current_temperature{NAN};
|
float current_temperature{NAN};
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,18 @@ const char *climate_mode_to_string(ClimateMode mode) {
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const char *climate_action_to_string(ClimateAction action) {
|
||||||
|
switch (action) {
|
||||||
|
case CLIMATE_ACTION_OFF:
|
||||||
|
return "OFF";
|
||||||
|
case CLIMATE_ACTION_COOLING:
|
||||||
|
return "COOLING";
|
||||||
|
case CLIMATE_ACTION_HEATING:
|
||||||
|
return "HEATING";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace climate
|
} // namespace climate
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -17,8 +17,19 @@ enum ClimateMode : uint8_t {
|
||||||
CLIMATE_MODE_HEAT = 3,
|
CLIMATE_MODE_HEAT = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Enum for the current action of the climate device. Values match those of ClimateMode.
|
||||||
|
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)
|
||||||
|
CLIMATE_ACTION_COOLING = 2,
|
||||||
|
/// The climate device is actively heating (usually in heat or auto mode)
|
||||||
|
CLIMATE_ACTION_HEATING = 3,
|
||||||
|
};
|
||||||
|
|
||||||
/// Convert the given ClimateMode to a human-readable string.
|
/// Convert the given ClimateMode to a human-readable string.
|
||||||
const char *climate_mode_to_string(ClimateMode mode);
|
const char *climate_mode_to_string(ClimateMode mode);
|
||||||
|
const char *climate_action_to_string(ClimateAction action);
|
||||||
|
|
||||||
} // namespace climate
|
} // namespace climate
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -30,6 +30,7 @@ void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_a
|
||||||
void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_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_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; }
|
||||||
void ClimateTraits::set_supports_away(bool supports_away) { supports_away_ = supports_away; }
|
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_; }
|
float ClimateTraits::get_visual_min_temperature() const { return visual_min_temperature_; }
|
||||||
void ClimateTraits::set_visual_min_temperature(float visual_min_temperature) {
|
void ClimateTraits::set_visual_min_temperature(float visual_min_temperature) {
|
||||||
visual_min_temperature_ = visual_min_temperature;
|
visual_min_temperature_ = visual_min_temperature;
|
||||||
|
@ -52,6 +53,7 @@ int8_t ClimateTraits::get_temperature_accuracy_decimals() const {
|
||||||
}
|
}
|
||||||
void ClimateTraits::set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; }
|
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_away() const { return supports_away_; }
|
||||||
|
bool ClimateTraits::get_supports_action() const { return supports_action_; }
|
||||||
|
|
||||||
} // namespace climate
|
} // namespace climate
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -23,6 +23,8 @@ namespace climate {
|
||||||
* - heat mode (increases current temperature)
|
* - heat mode (increases current temperature)
|
||||||
* - supports away - away mode means that the climate device supports two different
|
* - 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.
|
* 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.
|
||||||
*
|
*
|
||||||
* This class also contains static data for the climate device display:
|
* This class also contains static data for the climate device display:
|
||||||
* - visual min/max temperature - tells the frontend what range of temperatures the climate device
|
* - visual min/max temperature - tells the frontend what range of temperatures the climate device
|
||||||
|
@ -41,6 +43,8 @@ class ClimateTraits {
|
||||||
void set_supports_heat_mode(bool supports_heat_mode);
|
void set_supports_heat_mode(bool supports_heat_mode);
|
||||||
void set_supports_away(bool supports_away);
|
void set_supports_away(bool supports_away);
|
||||||
bool get_supports_away() const;
|
bool get_supports_away() const;
|
||||||
|
void set_supports_action(bool supports_action);
|
||||||
|
bool get_supports_action() const;
|
||||||
bool supports_mode(ClimateMode mode) const;
|
bool supports_mode(ClimateMode mode) const;
|
||||||
|
|
||||||
float get_visual_min_temperature() const;
|
float get_visual_min_temperature() const;
|
||||||
|
@ -58,6 +62,7 @@ class ClimateTraits {
|
||||||
bool supports_cool_mode_{false};
|
bool supports_cool_mode_{false};
|
||||||
bool supports_heat_mode_{false};
|
bool supports_heat_mode_{false};
|
||||||
bool supports_away_{false};
|
bool supports_away_{false};
|
||||||
|
bool supports_action_{false};
|
||||||
|
|
||||||
float visual_min_temperature_{10};
|
float visual_min_temperature_{10};
|
||||||
float visual_max_temperature_{30};
|
float visual_max_temperature_{30};
|
||||||
|
|
0
esphome/components/climate_ir/__init__.py
Normal file
0
esphome/components/climate_ir/__init__.py
Normal file
57
esphome/components/climate_ir/climate_ir.cpp
Normal file
57
esphome/components/climate_ir/climate_ir.cpp
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
#include "climate_ir.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace climate {
|
||||||
|
|
||||||
|
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_cool_mode(this->supports_cool_);
|
||||||
|
traits.set_supports_heat_mode(this->supports_heat_);
|
||||||
|
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_);
|
||||||
|
return traits;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClimateIR::setup() {
|
||||||
|
if (this->sensor_) {
|
||||||
|
this->sensor_->add_on_state_callback([this](float state) {
|
||||||
|
this->current_temperature = state;
|
||||||
|
// 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_));
|
||||||
|
}
|
||||||
|
// Never send nan to HA
|
||||||
|
if (isnan(this->target_temperature))
|
||||||
|
this->target_temperature = 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClimateIR::control(const climate::ClimateCall &call) {
|
||||||
|
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();
|
||||||
|
|
||||||
|
this->transmit_state();
|
||||||
|
this->publish_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace climate
|
||||||
|
} // namespace esphome
|
53
esphome/components/climate_ir/climate_ir.h
Normal file
53
esphome/components/climate_ir/climate_ir.h
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/climate/climate.h"
|
||||||
|
#include "esphome/components/remote_base/remote_base.h"
|
||||||
|
#include "esphome/components/remote_transmitter/remote_transmitter.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace climate {
|
||||||
|
|
||||||
|
/* A base for climate which works by sending (and receiving) IR codes
|
||||||
|
|
||||||
|
To send IR codes implement
|
||||||
|
void ClimateIR::transmit_state_()
|
||||||
|
|
||||||
|
Likewise to decode a IR into the AC state, implement
|
||||||
|
bool RemoteReceiverListener::on_receive(remote_base::RemoteReceiveData data) and return true
|
||||||
|
*/
|
||||||
|
class ClimateIR : public climate::Climate, public Component, public remote_base::RemoteReceiverListener {
|
||||||
|
public:
|
||||||
|
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f) {
|
||||||
|
this->minimum_temperature_ = minimum_temperature;
|
||||||
|
this->maximum_temperature_ = maximum_temperature;
|
||||||
|
this->temperature_step_ = temperature_step;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) {
|
||||||
|
this->transmitter_ = transmitter;
|
||||||
|
}
|
||||||
|
void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
|
||||||
|
void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
|
||||||
|
void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
float minimum_temperature_, maximum_temperature_, temperature_step_;
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
|
||||||
|
/// Transmit via IR the state of this climate controller.
|
||||||
|
virtual void transmit_state() {}
|
||||||
|
|
||||||
|
bool supports_cool_{true};
|
||||||
|
bool supports_heat_{true};
|
||||||
|
|
||||||
|
remote_transmitter::RemoteTransmitterComponent *transmitter_;
|
||||||
|
sensor::Sensor *sensor_{nullptr};
|
||||||
|
};
|
||||||
|
} // namespace climate
|
||||||
|
} // namespace esphome
|
|
@ -1,20 +1,22 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import climate, remote_transmitter, sensor
|
from esphome.components import climate, remote_transmitter, remote_receiver, sensor
|
||||||
from esphome.const import CONF_ID, CONF_SENSOR
|
from esphome.const import CONF_ID, CONF_SENSOR
|
||||||
|
|
||||||
AUTO_LOAD = ['sensor']
|
AUTO_LOAD = ['sensor', 'climate_ir']
|
||||||
|
|
||||||
coolix_ns = cg.esphome_ns.namespace('coolix')
|
coolix_ns = cg.esphome_ns.namespace('coolix')
|
||||||
CoolixClimate = coolix_ns.class_('CoolixClimate', climate.Climate, cg.Component)
|
CoolixClimate = coolix_ns.class_('CoolixClimate', climate.Climate, cg.Component)
|
||||||
|
|
||||||
CONF_TRANSMITTER_ID = 'transmitter_id'
|
CONF_TRANSMITTER_ID = 'transmitter_id'
|
||||||
|
CONF_RECEIVER_ID = 'receiver_id'
|
||||||
CONF_SUPPORTS_HEAT = 'supports_heat'
|
CONF_SUPPORTS_HEAT = 'supports_heat'
|
||||||
CONF_SUPPORTS_COOL = 'supports_cool'
|
CONF_SUPPORTS_COOL = 'supports_cool'
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({
|
CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({
|
||||||
cv.GenerateID(): cv.declare_id(CoolixClimate),
|
cv.GenerateID(): cv.declare_id(CoolixClimate),
|
||||||
cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(remote_transmitter.RemoteTransmitterComponent),
|
cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(remote_transmitter.RemoteTransmitterComponent),
|
||||||
|
cv.Optional(CONF_RECEIVER_ID): cv.use_id(remote_receiver.RemoteReceiverComponent),
|
||||||
cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean,
|
cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean,
|
||||||
cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean,
|
cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean,
|
||||||
cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
||||||
|
@ -31,6 +33,9 @@ def to_code(config):
|
||||||
if CONF_SENSOR in config:
|
if CONF_SENSOR in config:
|
||||||
sens = yield cg.get_variable(config[CONF_SENSOR])
|
sens = yield cg.get_variable(config[CONF_SENSOR])
|
||||||
cg.add(var.set_sensor(sens))
|
cg.add(var.set_sensor(sens))
|
||||||
|
if CONF_RECEIVER_ID in config:
|
||||||
|
receiver = yield cg.get_variable(config[CONF_RECEIVER_ID])
|
||||||
|
cg.add(receiver.register_listener(var))
|
||||||
|
|
||||||
transmitter = yield cg.get_variable(config[CONF_TRANSMITTER_ID])
|
transmitter = yield cg.get_variable(config[CONF_TRANSMITTER_ID])
|
||||||
cg.add(var.set_transmitter(transmitter))
|
cg.add(var.set_transmitter(transmitter))
|
||||||
|
|
|
@ -7,19 +7,26 @@ namespace coolix {
|
||||||
static const char *TAG = "coolix.climate";
|
static const char *TAG = "coolix.climate";
|
||||||
|
|
||||||
const uint32_t COOLIX_OFF = 0xB27BE0;
|
const uint32_t COOLIX_OFF = 0xB27BE0;
|
||||||
|
const uint32_t COOLIX_SWING = 0xB26BE0;
|
||||||
|
const uint32_t COOLIX_LED = 0xB5F5A5;
|
||||||
|
const uint32_t COOLIX_SILENCE_FP = 0xB5F5B6;
|
||||||
|
|
||||||
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
|
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
|
||||||
const uint32_t COOLIX_DEFAULT_STATE = 0xB2BFC8;
|
const uint32_t COOLIX_DEFAULT_STATE = 0xB2BFC8;
|
||||||
const uint32_t COOLIX_DEFAULT_STATE_AUTO_24_FAN = 0xB21F48;
|
const uint32_t COOLIX_DEFAULT_STATE_AUTO_24_FAN = 0xB21F48;
|
||||||
const uint8_t COOLIX_COOL = 0b00;
|
const uint8_t COOLIX_COOL = 0b0000;
|
||||||
const uint8_t COOLIX_DRY = 0b01;
|
const uint8_t COOLIX_DRY_FAN = 0b0100;
|
||||||
const uint8_t COOLIX_AUTO = 0b10;
|
const uint8_t COOLIX_AUTO = 0b1000;
|
||||||
const uint8_t COOLIX_HEAT = 0b11;
|
const uint8_t COOLIX_HEAT = 0b1100;
|
||||||
const uint8_t COOLIX_FAN = 4; // Synthetic.
|
const uint32_t COOLIX_MODE_MASK = 0b1100;
|
||||||
const uint32_t COOLIX_MODE_MASK = 0b000000000000000000001100; // 0xC
|
const uint32_t COOLIX_FAN_MASK = 0xF000;
|
||||||
|
const uint32_t COOLIX_FAN_DRY = 0x1000;
|
||||||
|
const uint32_t COOLIX_FAN_AUTO = 0xB000;
|
||||||
|
const uint32_t COOLIX_FAN_MIN = 0x9000;
|
||||||
|
const uint32_t COOLIX_FAN_MED = 0x5000;
|
||||||
|
const uint32_t COOLIX_FAN_MAX = 0x3000;
|
||||||
|
|
||||||
// Temperature
|
// Temperature
|
||||||
const uint8_t COOLIX_TEMP_MIN = 17; // Celsius
|
|
||||||
const uint8_t COOLIX_TEMP_MAX = 30; // Celsius
|
|
||||||
const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1;
|
const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1;
|
||||||
const uint8_t COOLIX_FAN_TEMP_CODE = 0b1110; // Part of Fan Mode.
|
const uint8_t COOLIX_FAN_TEMP_CODE = 0b1110; // Part of Fan Mode.
|
||||||
const uint32_t COOLIX_TEMP_MASK = 0b11110000;
|
const uint32_t COOLIX_TEMP_MASK = 0b11110000;
|
||||||
|
@ -41,80 +48,25 @@ const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
// Pulse parms are *50-100 for the Mark and *50+100 for the space
|
static const uint32_t BIT_MARK_US = 660;
|
||||||
// First MARK is the one after the long gap
|
static const uint32_t HEADER_MARK_US = 560 * 8;
|
||||||
// pulse parameters in usec
|
static const uint32_t HEADER_SPACE_US = 560 * 8;
|
||||||
const uint16_t COOLIX_TICK = 560; // Approximately 21 cycles at 38kHz
|
static const uint32_t BIT_ONE_SPACE_US = 1500;
|
||||||
const uint16_t COOLIX_BIT_MARK_TICKS = 1;
|
static const uint32_t BIT_ZERO_SPACE_US = 450;
|
||||||
const uint16_t COOLIX_BIT_MARK = COOLIX_BIT_MARK_TICKS * COOLIX_TICK;
|
static const uint32_t FOOTER_MARK_US = BIT_MARK_US;
|
||||||
const uint16_t COOLIX_ONE_SPACE_TICKS = 3;
|
static const uint32_t FOOTER_SPACE_US = HEADER_SPACE_US;
|
||||||
const uint16_t COOLIX_ONE_SPACE = COOLIX_ONE_SPACE_TICKS * COOLIX_TICK;
|
|
||||||
const uint16_t COOLIX_ZERO_SPACE_TICKS = 1;
|
|
||||||
const uint16_t COOLIX_ZERO_SPACE = COOLIX_ZERO_SPACE_TICKS * COOLIX_TICK;
|
|
||||||
const uint16_t COOLIX_HEADER_MARK_TICKS = 8;
|
|
||||||
const uint16_t COOLIX_HEADER_MARK = COOLIX_HEADER_MARK_TICKS * COOLIX_TICK;
|
|
||||||
const uint16_t COOLIX_HEADER_SPACE_TICKS = 8;
|
|
||||||
const uint16_t COOLIX_HEADER_SPACE = COOLIX_HEADER_SPACE_TICKS * COOLIX_TICK;
|
|
||||||
const uint16_t COOLIX_MIN_GAP_TICKS = COOLIX_HEADER_MARK_TICKS + COOLIX_ZERO_SPACE_TICKS;
|
|
||||||
const uint16_t COOLIX_MIN_GAP = COOLIX_MIN_GAP_TICKS * COOLIX_TICK;
|
|
||||||
|
|
||||||
const uint16_t COOLIX_BITS = 24;
|
const uint16_t COOLIX_BITS = 24;
|
||||||
|
|
||||||
climate::ClimateTraits CoolixClimate::traits() {
|
void CoolixClimate::transmit_state() {
|
||||||
auto traits = climate::ClimateTraits();
|
|
||||||
traits.set_supports_current_temperature(this->sensor_ != nullptr);
|
|
||||||
traits.set_supports_auto_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);
|
|
||||||
traits.set_supports_away(false);
|
|
||||||
traits.set_visual_min_temperature(17);
|
|
||||||
traits.set_visual_max_temperature(30);
|
|
||||||
traits.set_visual_temperature_step(1);
|
|
||||||
return traits;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CoolixClimate::setup() {
|
|
||||||
if (this->sensor_) {
|
|
||||||
this->sensor_->add_on_state_callback([this](float state) {
|
|
||||||
this->current_temperature = state;
|
|
||||||
// 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_AUTO;
|
|
||||||
// initialize target temperature to some value so that it's not NAN
|
|
||||||
this->target_temperature = roundf(this->current_temperature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CoolixClimate::control(const climate::ClimateCall &call) {
|
|
||||||
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();
|
|
||||||
|
|
||||||
this->transmit_state_();
|
|
||||||
this->publish_state();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CoolixClimate::transmit_state_() {
|
|
||||||
uint32_t remote_state;
|
uint32_t remote_state;
|
||||||
|
|
||||||
switch (this->mode) {
|
switch (this->mode) {
|
||||||
case climate::CLIMATE_MODE_COOL:
|
case climate::CLIMATE_MODE_COOL:
|
||||||
remote_state = (COOLIX_DEFAULT_STATE & ~COOLIX_MODE_MASK) | (COOLIX_COOL << 2);
|
remote_state = (COOLIX_DEFAULT_STATE & ~COOLIX_MODE_MASK) | COOLIX_COOL;
|
||||||
break;
|
break;
|
||||||
case climate::CLIMATE_MODE_HEAT:
|
case climate::CLIMATE_MODE_HEAT:
|
||||||
remote_state = (COOLIX_DEFAULT_STATE & ~COOLIX_MODE_MASK) | (COOLIX_HEAT << 2);
|
remote_state = (COOLIX_DEFAULT_STATE & ~COOLIX_MODE_MASK) | COOLIX_HEAT;
|
||||||
break;
|
break;
|
||||||
case climate::CLIMATE_MODE_AUTO:
|
case climate::CLIMATE_MODE_AUTO:
|
||||||
remote_state = COOLIX_DEFAULT_STATE_AUTO_24_FAN;
|
remote_state = COOLIX_DEFAULT_STATE_AUTO_24_FAN;
|
||||||
|
@ -127,10 +79,10 @@ void CoolixClimate::transmit_state_() {
|
||||||
if (this->mode != climate::CLIMATE_MODE_OFF) {
|
if (this->mode != climate::CLIMATE_MODE_OFF) {
|
||||||
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_MASK; // Clear the old temp.
|
remote_state &= ~COOLIX_TEMP_MASK; // Clear the old temp.
|
||||||
remote_state |= (COOLIX_TEMP_MAP[temp - COOLIX_TEMP_MIN] << 4);
|
remote_state |= COOLIX_TEMP_MAP[temp - COOLIX_TEMP_MIN] << 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Sending coolix code: %u", remote_state);
|
ESP_LOGV(TAG, "Sending coolix code: 0x%02X", remote_state);
|
||||||
|
|
||||||
auto transmit = this->transmitter_->transmit();
|
auto transmit = this->transmitter_->transmit();
|
||||||
auto data = transmit.get_data();
|
auto data = transmit.get_data();
|
||||||
|
@ -139,32 +91,113 @@ void CoolixClimate::transmit_state_() {
|
||||||
uint16_t repeat = 1;
|
uint16_t repeat = 1;
|
||||||
for (uint16_t r = 0; r <= repeat; r++) {
|
for (uint16_t r = 0; r <= repeat; r++) {
|
||||||
// Header
|
// Header
|
||||||
data->mark(COOLIX_HEADER_MARK);
|
data->mark(HEADER_MARK_US);
|
||||||
data->space(COOLIX_HEADER_SPACE);
|
data->space(HEADER_SPACE_US);
|
||||||
// Data
|
// Data
|
||||||
// Break data into byte segments, starting at the Most Significant
|
// Break data into bytes, starting at the Most Significant
|
||||||
// Byte. Each byte then being sent normal, then followed inverted.
|
// Byte. Each byte then being sent normal, then followed inverted.
|
||||||
for (uint16_t i = 8; i <= COOLIX_BITS; i += 8) {
|
for (uint16_t i = 8; i <= COOLIX_BITS; i += 8) {
|
||||||
// Grab a bytes worth of data.
|
// Grab a bytes worth of data.
|
||||||
uint8_t segment = (remote_state >> (COOLIX_BITS - i)) & 0xFF;
|
uint8_t byte = (remote_state >> (COOLIX_BITS - i)) & 0xFF;
|
||||||
// Normal
|
// Normal
|
||||||
for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) {
|
for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) {
|
||||||
data->mark(COOLIX_BIT_MARK);
|
data->mark(BIT_MARK_US);
|
||||||
data->space((segment & mask) ? COOLIX_ONE_SPACE : COOLIX_ZERO_SPACE);
|
data->space((byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US);
|
||||||
}
|
}
|
||||||
// Inverted
|
// Inverted
|
||||||
for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) {
|
for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) {
|
||||||
data->mark(COOLIX_BIT_MARK);
|
data->mark(BIT_MARK_US);
|
||||||
data->space(!(segment & mask) ? COOLIX_ONE_SPACE : COOLIX_ZERO_SPACE);
|
data->space(!(byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Footer
|
// Footer
|
||||||
data->mark(COOLIX_BIT_MARK);
|
data->mark(BIT_MARK_US);
|
||||||
data->space(COOLIX_MIN_GAP); // Pause before repeating
|
data->space(FOOTER_SPACE_US); // Pause before repeating
|
||||||
}
|
}
|
||||||
|
|
||||||
transmit.perform();
|
transmit.perform();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||||
|
// Decoded remote state y 3 bytes long code.
|
||||||
|
uint32_t remote_state = 0;
|
||||||
|
// The protocol sends the data twice, read here
|
||||||
|
uint32_t loop_read;
|
||||||
|
for (uint16_t loop = 1; loop <= 2; loop++) {
|
||||||
|
if (!data.expect_item(HEADER_MARK_US, HEADER_SPACE_US))
|
||||||
|
return false;
|
||||||
|
loop_read = 0;
|
||||||
|
for (uint8_t a_byte = 0; a_byte < 3; a_byte++) {
|
||||||
|
uint8_t byte = 0;
|
||||||
|
for (int8_t a_bit = 7; a_bit >= 0; a_bit--) {
|
||||||
|
if (data.expect_item(BIT_MARK_US, BIT_ONE_SPACE_US))
|
||||||
|
byte |= 1 << a_bit;
|
||||||
|
else if (!data.expect_item(BIT_MARK_US, BIT_ZERO_SPACE_US))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Need to see this segment inverted
|
||||||
|
for (int8_t a_bit = 7; a_bit >= 0; a_bit--) {
|
||||||
|
bool bit = byte & (1 << a_bit);
|
||||||
|
if (!data.expect_item(BIT_MARK_US, bit ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Receiving MSB first: reorder bytes
|
||||||
|
loop_read |= byte << ((2 - a_byte) * 8);
|
||||||
|
}
|
||||||
|
// Footer Mark
|
||||||
|
if (!data.expect_mark(BIT_MARK_US))
|
||||||
|
return false;
|
||||||
|
if (loop == 1) {
|
||||||
|
// Back up state on first loop
|
||||||
|
remote_state = loop_read;
|
||||||
|
if (!data.expect_space(FOOTER_SPACE_US))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "Decoded 0x%02X", remote_state);
|
||||||
|
if (remote_state != loop_read || (remote_state & 0xFF0000) != 0xB20000)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (remote_state == COOLIX_OFF) {
|
||||||
|
this->mode = climate::CLIMATE_MODE_OFF;
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
|
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) {
|
||||||
|
// climate::CLIMATE_MODE_DRY;
|
||||||
|
if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_DRY)
|
||||||
|
ESP_LOGV(TAG, "Not supported DRY mode. Reporting AUTO");
|
||||||
|
else
|
||||||
|
ESP_LOGV(TAG, "Not supported FAN Auto mode. Reporting AUTO");
|
||||||
|
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||||
|
} else
|
||||||
|
this->mode = climate::CLIMATE_MODE_COOL;
|
||||||
|
|
||||||
|
// Fan Speed
|
||||||
|
// When climate::CLIMATE_MODE_DRY is implemented replace following line with this:
|
||||||
|
// if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_DRY)
|
||||||
|
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO)
|
||||||
|
ESP_LOGV(TAG, "Not supported FAN speed AUTO");
|
||||||
|
else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN)
|
||||||
|
ESP_LOGV(TAG, "Not supported FAN speed MIN");
|
||||||
|
else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED)
|
||||||
|
ESP_LOGV(TAG, "Not supported FAN speed MED");
|
||||||
|
else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX)
|
||||||
|
ESP_LOGV(TAG, "Not supported FAN speed MAX");
|
||||||
|
|
||||||
|
// Temperature
|
||||||
|
uint8_t temperature_code = (remote_state & COOLIX_TEMP_MASK) >> 4;
|
||||||
|
for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++)
|
||||||
|
if (COOLIX_TEMP_MAP[i] == temperature_code)
|
||||||
|
this->target_temperature = i + COOLIX_TEMP_MIN;
|
||||||
|
}
|
||||||
|
this->publish_state();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace coolix
|
} // namespace coolix
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue