mirror of
https://github.com/esphome/esphome.git
synced 2025-11-15 22:35:46 +00:00
Compare commits
61 Commits
2021.9.0b1
...
2021.9.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3ec4b514d | ||
|
|
fc5798fa71 | ||
|
|
95d7ad543f | ||
|
|
d9b2903d78 | ||
|
|
32a664eedc | ||
|
|
e7477890cf | ||
|
|
9bf72ff05f | ||
|
|
46b4c970d1 | ||
|
|
c83ecf764d | ||
|
|
a2485a18cb | ||
|
|
8ef2ad17b5 | ||
|
|
4579f78bf9 | ||
|
|
1853407645 | ||
|
|
cb5efc1c42 | ||
|
|
d26c2b1a44 | ||
|
|
8bda8e5393 | ||
|
|
954b8a0cff | ||
|
|
7c17e72db4 | ||
|
|
d180aee57f | ||
|
|
e3ffecefc0 | ||
|
|
4c61cf153c | ||
|
|
c78fb90e2f | ||
|
|
321504cf29 | ||
|
|
0f4a7bf1f5 | ||
|
|
711e74a12b | ||
|
|
aa8eb2c92a | ||
|
|
b422a63b2a | ||
|
|
ad5f2cd748 | ||
|
|
efae363739 | ||
|
|
2d79d21c50 | ||
|
|
3b9d126322 | ||
|
|
896654aaef | ||
|
|
5fad38f65f | ||
|
|
89f2ea5725 | ||
|
|
a32ad33b4e | ||
|
|
a328fff5a7 | ||
|
|
233783c76c | ||
|
|
39a18fb358 | ||
|
|
460a144ca8 | ||
|
|
23ead416d5 | ||
|
|
1b5f11bbee | ||
|
|
0da97289e6 | ||
|
|
91f12a50cf | ||
|
|
e92a9d1d9e | ||
|
|
4eb51ab4d6 | ||
|
|
f1a8d957f8 | ||
|
|
9821a3442b | ||
|
|
87842e097b | ||
|
|
7dd40e2014 | ||
|
|
5086cd716f | ||
|
|
4937af0cd9 | ||
|
|
877a5fda41 | ||
|
|
1fac91a659 | ||
|
|
0a4837c1f0 | ||
|
|
e7404183a0 | ||
|
|
44f8dcfb6e | ||
|
|
481e0e98f8 | ||
|
|
9de40c26eb | ||
|
|
ad953f02d1 | ||
|
|
3869e56521 | ||
|
|
63d87b17aa |
@@ -24,7 +24,7 @@ TYPE_LINT = 'lint'
|
||||
TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT]
|
||||
|
||||
|
||||
BASE_VERSION = "3.6.0"
|
||||
BASE_VERSION = "4.2.0"
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
@@ -256,7 +256,7 @@ def show_logs(config, args, port):
|
||||
run_miniterm(config, port)
|
||||
return 0
|
||||
if get_port_type(port) == "NETWORK" and "api" in config:
|
||||
from esphome.api.client import run_logs
|
||||
from esphome.components.api.client import run_logs
|
||||
|
||||
return run_logs(config, port)
|
||||
if get_port_type(port) == "MQTT" and "mqtt" in config:
|
||||
@@ -483,75 +483,9 @@ def parse_args(argv):
|
||||
metavar=("key", "value"),
|
||||
)
|
||||
|
||||
# Keep backward compatibility with the old command line format of
|
||||
# esphome <config> <command>.
|
||||
#
|
||||
# Unfortunately this can't be done by adding another configuration argument to the
|
||||
# main config parser, as argparse is greedy when parsing arguments, so in regular
|
||||
# usage it'll eat the command as the configuration argument and error out out
|
||||
# because it can't parse the configuration as a command.
|
||||
#
|
||||
# Instead, construct an ad-hoc parser for the old format that doesn't actually
|
||||
# process the arguments, but parses them enough to let us figure out if the old
|
||||
# format is used. In that case, swap the command and configuration in the arguments
|
||||
# and continue on with the normal parser (after raising a deprecation warning).
|
||||
#
|
||||
# Disable argparse's built-in help option and add it manually to prevent this
|
||||
# parser from printing the help messagefor the old format when invoked with -h.
|
||||
compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False)
|
||||
compat_parser.add_argument("-h", "--help")
|
||||
compat_parser.add_argument("configuration", nargs="*")
|
||||
compat_parser.add_argument(
|
||||
"command",
|
||||
choices=[
|
||||
"config",
|
||||
"compile",
|
||||
"upload",
|
||||
"logs",
|
||||
"run",
|
||||
"clean-mqtt",
|
||||
"wizard",
|
||||
"mqtt-fingerprint",
|
||||
"version",
|
||||
"clean",
|
||||
"dashboard",
|
||||
"vscode",
|
||||
"update-all",
|
||||
],
|
||||
)
|
||||
|
||||
# on Python 3.9+ we can simply set exit_on_error=False in the constructor
|
||||
def _raise(x):
|
||||
raise argparse.ArgumentError(None, x)
|
||||
|
||||
compat_parser.error = _raise
|
||||
|
||||
deprecated_argv_suggestion = None
|
||||
|
||||
if ["dashboard", "config"] == argv[1:3] or ["version"] == argv[1:2]:
|
||||
# this is most likely meant in new-style arg format. do not try compat parsing
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
result, unparsed = compat_parser.parse_known_args(argv[1:])
|
||||
last_option = len(argv) - len(unparsed) - 1 - len(result.configuration)
|
||||
unparsed = [
|
||||
"--device" if arg in ("--upload-port", "--serial-port") else arg
|
||||
for arg in unparsed
|
||||
]
|
||||
argv = (
|
||||
argv[0:last_option] + [result.command] + result.configuration + unparsed
|
||||
)
|
||||
deprecated_argv_suggestion = argv
|
||||
except argparse.ArgumentError:
|
||||
# This is not an old-style command line, so we don't have to do anything.
|
||||
pass
|
||||
|
||||
# And continue on with regular parsing
|
||||
parser = argparse.ArgumentParser(
|
||||
description=f"ESPHome v{const.__version__}", parents=[options_parser]
|
||||
)
|
||||
parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion)
|
||||
|
||||
mqtt_options = argparse.ArgumentParser(add_help=False)
|
||||
mqtt_options.add_argument("--topic", help="Manually set the MQTT topic.")
|
||||
@@ -701,7 +635,83 @@ def parse_args(argv):
|
||||
"configuration", help="Your YAML configuration file directories.", nargs="+"
|
||||
)
|
||||
|
||||
return parser.parse_args(argv[1:])
|
||||
# Keep backward compatibility with the old command line format of
|
||||
# esphome <config> <command>.
|
||||
#
|
||||
# Unfortunately this can't be done by adding another configuration argument to the
|
||||
# main config parser, as argparse is greedy when parsing arguments, so in regular
|
||||
# usage it'll eat the command as the configuration argument and error out out
|
||||
# because it can't parse the configuration as a command.
|
||||
#
|
||||
# Instead, if parsing using the current format fails, construct an ad-hoc parser
|
||||
# that doesn't actually process the arguments, but parses them enough to let us
|
||||
# figure out if the old format is used. In that case, swap the command and
|
||||
# configuration in the arguments and retry with the normal parser (and raise
|
||||
# a deprecation warning).
|
||||
arguments = argv[1:]
|
||||
|
||||
# On Python 3.9+ we can simply set exit_on_error=False in the constructor
|
||||
def _raise(x):
|
||||
raise argparse.ArgumentError(None, x)
|
||||
|
||||
# First, try new-style parsing, but don't exit in case of failure
|
||||
try:
|
||||
# duplicate parser so that we can use the original one to raise errors later on
|
||||
current_parser = argparse.ArgumentParser(add_help=False, parents=[parser])
|
||||
current_parser.set_defaults(deprecated_argv_suggestion=None)
|
||||
current_parser.error = _raise
|
||||
return current_parser.parse_args(arguments)
|
||||
except argparse.ArgumentError:
|
||||
pass
|
||||
|
||||
# Second, try compat parsing and rearrange the command-line if it succeeds
|
||||
# Disable argparse's built-in help option and add it manually to prevent this
|
||||
# parser from printing the help messagefor the old format when invoked with -h.
|
||||
compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False)
|
||||
compat_parser.add_argument("-h", "--help", action="store_true")
|
||||
compat_parser.add_argument("configuration", nargs="*")
|
||||
compat_parser.add_argument(
|
||||
"command",
|
||||
choices=[
|
||||
"config",
|
||||
"compile",
|
||||
"upload",
|
||||
"logs",
|
||||
"run",
|
||||
"clean-mqtt",
|
||||
"wizard",
|
||||
"mqtt-fingerprint",
|
||||
"version",
|
||||
"clean",
|
||||
"dashboard",
|
||||
"vscode",
|
||||
"update-all",
|
||||
],
|
||||
)
|
||||
|
||||
try:
|
||||
compat_parser.error = _raise
|
||||
result, unparsed = compat_parser.parse_known_args(argv[1:])
|
||||
last_option = len(arguments) - len(unparsed) - 1 - len(result.configuration)
|
||||
unparsed = [
|
||||
"--device" if arg in ("--upload-port", "--serial-port") else arg
|
||||
for arg in unparsed
|
||||
]
|
||||
arguments = (
|
||||
arguments[0:last_option]
|
||||
+ [result.command]
|
||||
+ result.configuration
|
||||
+ unparsed
|
||||
)
|
||||
deprecated_argv_suggestion = arguments
|
||||
except argparse.ArgumentError:
|
||||
# old-style parsing failed, don't suggest any argument
|
||||
deprecated_argv_suggestion = None
|
||||
|
||||
# Finally, run the new-style parser again with the possibly swapped arguments,
|
||||
# and let it error out if the command is unparsable.
|
||||
parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion)
|
||||
return parser.parse_args(arguments)
|
||||
|
||||
|
||||
def run_esphome(argv):
|
||||
@@ -715,7 +725,7 @@ def run_esphome(argv):
|
||||
"and will be removed in the future. "
|
||||
)
|
||||
_LOGGER.warning("Please instead use:")
|
||||
_LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion[1:]))
|
||||
_LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion))
|
||||
|
||||
if sys.version_info < (3, 7, 0):
|
||||
_LOGGER.error(
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,518 +0,0 @@
|
||||
from datetime import datetime
|
||||
import functools
|
||||
import logging
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from typing import Optional # noqa
|
||||
from google.protobuf import message # noqa
|
||||
|
||||
from esphome import const
|
||||
import esphome.api.api_pb2 as pb
|
||||
from esphome.const import CONF_PASSWORD, CONF_PORT
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.helpers import resolve_ip_address, indent
|
||||
from esphome.log import color, Fore
|
||||
from esphome.util import safe_print
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class APIConnectionError(EsphomeError):
|
||||
pass
|
||||
|
||||
|
||||
MESSAGE_TYPE_TO_PROTO = {
|
||||
1: pb.HelloRequest,
|
||||
2: pb.HelloResponse,
|
||||
3: pb.ConnectRequest,
|
||||
4: pb.ConnectResponse,
|
||||
5: pb.DisconnectRequest,
|
||||
6: pb.DisconnectResponse,
|
||||
7: pb.PingRequest,
|
||||
8: pb.PingResponse,
|
||||
9: pb.DeviceInfoRequest,
|
||||
10: pb.DeviceInfoResponse,
|
||||
11: pb.ListEntitiesRequest,
|
||||
12: pb.ListEntitiesBinarySensorResponse,
|
||||
13: pb.ListEntitiesCoverResponse,
|
||||
14: pb.ListEntitiesFanResponse,
|
||||
15: pb.ListEntitiesLightResponse,
|
||||
16: pb.ListEntitiesSensorResponse,
|
||||
17: pb.ListEntitiesSwitchResponse,
|
||||
18: pb.ListEntitiesTextSensorResponse,
|
||||
19: pb.ListEntitiesDoneResponse,
|
||||
20: pb.SubscribeStatesRequest,
|
||||
21: pb.BinarySensorStateResponse,
|
||||
22: pb.CoverStateResponse,
|
||||
23: pb.FanStateResponse,
|
||||
24: pb.LightStateResponse,
|
||||
25: pb.SensorStateResponse,
|
||||
26: pb.SwitchStateResponse,
|
||||
27: pb.TextSensorStateResponse,
|
||||
28: pb.SubscribeLogsRequest,
|
||||
29: pb.SubscribeLogsResponse,
|
||||
30: pb.CoverCommandRequest,
|
||||
31: pb.FanCommandRequest,
|
||||
32: pb.LightCommandRequest,
|
||||
33: pb.SwitchCommandRequest,
|
||||
34: pb.SubscribeServiceCallsRequest,
|
||||
35: pb.ServiceCallResponse,
|
||||
36: pb.GetTimeRequest,
|
||||
37: pb.GetTimeResponse,
|
||||
}
|
||||
|
||||
|
||||
def _varuint_to_bytes(value):
|
||||
if value <= 0x7F:
|
||||
return bytes([value])
|
||||
|
||||
ret = bytes()
|
||||
while value:
|
||||
temp = value & 0x7F
|
||||
value >>= 7
|
||||
if value:
|
||||
ret += bytes([temp | 0x80])
|
||||
else:
|
||||
ret += bytes([temp])
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _bytes_to_varuint(value):
|
||||
result = 0
|
||||
bitpos = 0
|
||||
for val in value:
|
||||
result |= (val & 0x7F) << bitpos
|
||||
bitpos += 7
|
||||
if (val & 0x80) == 0:
|
||||
return result
|
||||
return None
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes,not-callable
|
||||
class APIClient(threading.Thread):
|
||||
def __init__(self, address, port, password):
|
||||
threading.Thread.__init__(self)
|
||||
self._address = address # type: str
|
||||
self._port = port # type: int
|
||||
self._password = password # type: Optional[str]
|
||||
self._socket = None # type: Optional[socket.socket]
|
||||
self._socket_open_event = threading.Event()
|
||||
self._socket_write_lock = threading.Lock()
|
||||
self._connected = False
|
||||
self._authenticated = False
|
||||
self._message_handlers = []
|
||||
self._keepalive = 5
|
||||
self._ping_timer = None
|
||||
|
||||
self.on_disconnect = None
|
||||
self.on_connect = None
|
||||
self.on_login = None
|
||||
self.auto_reconnect = False
|
||||
self._running_event = threading.Event()
|
||||
self._stop_event = threading.Event()
|
||||
|
||||
@property
|
||||
def stopped(self):
|
||||
return self._stop_event.is_set()
|
||||
|
||||
def _refresh_ping(self):
|
||||
if self._ping_timer is not None:
|
||||
self._ping_timer.cancel()
|
||||
self._ping_timer = None
|
||||
|
||||
def func():
|
||||
self._ping_timer = None
|
||||
|
||||
if self._connected:
|
||||
try:
|
||||
self.ping()
|
||||
except APIConnectionError as err:
|
||||
self._fatal_error(err)
|
||||
else:
|
||||
self._refresh_ping()
|
||||
|
||||
self._ping_timer = threading.Timer(self._keepalive, func)
|
||||
self._ping_timer.start()
|
||||
|
||||
def _cancel_ping(self):
|
||||
if self._ping_timer is not None:
|
||||
self._ping_timer.cancel()
|
||||
self._ping_timer = None
|
||||
|
||||
def _close_socket(self):
|
||||
self._cancel_ping()
|
||||
if self._socket is not None:
|
||||
self._socket.close()
|
||||
self._socket = None
|
||||
self._socket_open_event.clear()
|
||||
self._connected = False
|
||||
self._authenticated = False
|
||||
self._message_handlers = []
|
||||
|
||||
def stop(self, force=False):
|
||||
if self.stopped:
|
||||
raise ValueError
|
||||
|
||||
if self._connected and not force:
|
||||
try:
|
||||
self.disconnect()
|
||||
except APIConnectionError:
|
||||
pass
|
||||
self._close_socket()
|
||||
|
||||
self._stop_event.set()
|
||||
if not force:
|
||||
self.join()
|
||||
|
||||
def connect(self):
|
||||
if not self._running_event.wait(0.1):
|
||||
raise APIConnectionError("You need to call start() first!")
|
||||
|
||||
if self._connected:
|
||||
self.disconnect(on_disconnect=False)
|
||||
|
||||
try:
|
||||
ip = resolve_ip_address(self._address)
|
||||
except EsphomeError as err:
|
||||
_LOGGER.warning(
|
||||
"Error resolving IP address of %s. Is it connected to WiFi?",
|
||||
self._address,
|
||||
)
|
||||
_LOGGER.warning(
|
||||
"(If this error persists, please set a static IP address: "
|
||||
"https://esphome.io/components/wifi.html#manual-ips)"
|
||||
)
|
||||
raise APIConnectionError(err) from err
|
||||
|
||||
_LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip)
|
||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self._socket.settimeout(10.0)
|
||||
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
try:
|
||||
self._socket.connect((ip, self._port))
|
||||
except OSError as err:
|
||||
err = APIConnectionError(f"Error connecting to {ip}: {err}")
|
||||
self._fatal_error(err)
|
||||
raise err
|
||||
self._socket.settimeout(0.1)
|
||||
|
||||
self._socket_open_event.set()
|
||||
|
||||
hello = pb.HelloRequest()
|
||||
hello.client_info = f"ESPHome v{const.__version__}"
|
||||
try:
|
||||
resp = self._send_message_await_response(hello, pb.HelloResponse)
|
||||
except APIConnectionError as err:
|
||||
self._fatal_error(err)
|
||||
raise err
|
||||
_LOGGER.debug(
|
||||
"Successfully connected to %s ('%s' API=%s.%s)",
|
||||
self._address,
|
||||
resp.server_info,
|
||||
resp.api_version_major,
|
||||
resp.api_version_minor,
|
||||
)
|
||||
self._connected = True
|
||||
self._refresh_ping()
|
||||
if self.on_connect is not None:
|
||||
self.on_connect()
|
||||
|
||||
def _check_connected(self):
|
||||
if not self._connected:
|
||||
err = APIConnectionError("Must be connected!")
|
||||
self._fatal_error(err)
|
||||
raise err
|
||||
|
||||
def login(self):
|
||||
self._check_connected()
|
||||
if self._authenticated:
|
||||
raise APIConnectionError("Already logged in!")
|
||||
|
||||
connect = pb.ConnectRequest()
|
||||
if self._password is not None:
|
||||
connect.password = self._password
|
||||
resp = self._send_message_await_response(connect, pb.ConnectResponse)
|
||||
if resp.invalid_password:
|
||||
raise APIConnectionError("Invalid password!")
|
||||
|
||||
self._authenticated = True
|
||||
if self.on_login is not None:
|
||||
self.on_login()
|
||||
|
||||
def _fatal_error(self, err):
|
||||
was_connected = self._connected
|
||||
|
||||
self._close_socket()
|
||||
|
||||
if was_connected and self.on_disconnect is not None:
|
||||
self.on_disconnect(err)
|
||||
|
||||
def _write(self, data): # type: (bytes) -> None
|
||||
if self._socket is None:
|
||||
raise APIConnectionError("Socket closed")
|
||||
|
||||
# _LOGGER.debug("Write: %s", format_bytes(data))
|
||||
with self._socket_write_lock:
|
||||
try:
|
||||
self._socket.sendall(data)
|
||||
except OSError as err:
|
||||
err = APIConnectionError(f"Error while writing data: {err}")
|
||||
self._fatal_error(err)
|
||||
raise err
|
||||
|
||||
def _send_message(self, msg):
|
||||
# type: (message.Message) -> None
|
||||
for message_type, klass in MESSAGE_TYPE_TO_PROTO.items():
|
||||
if isinstance(msg, klass):
|
||||
break
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
encoded = msg.SerializeToString()
|
||||
_LOGGER.debug("Sending %s:\n%s", type(msg), indent(str(msg)))
|
||||
req = bytes([0])
|
||||
req += _varuint_to_bytes(len(encoded))
|
||||
req += _varuint_to_bytes(message_type)
|
||||
req += encoded
|
||||
self._write(req)
|
||||
|
||||
def _send_message_await_response_complex(
|
||||
self, send_msg, do_append, do_stop, timeout=5
|
||||
):
|
||||
event = threading.Event()
|
||||
responses = []
|
||||
|
||||
def on_message(resp):
|
||||
if do_append(resp):
|
||||
responses.append(resp)
|
||||
if do_stop(resp):
|
||||
event.set()
|
||||
|
||||
self._message_handlers.append(on_message)
|
||||
self._send_message(send_msg)
|
||||
ret = event.wait(timeout)
|
||||
try:
|
||||
self._message_handlers.remove(on_message)
|
||||
except ValueError:
|
||||
pass
|
||||
if not ret:
|
||||
raise APIConnectionError("Timeout while waiting for message response!")
|
||||
return responses
|
||||
|
||||
def _send_message_await_response(self, send_msg, response_type, timeout=5):
|
||||
def is_response(msg):
|
||||
return isinstance(msg, response_type)
|
||||
|
||||
return self._send_message_await_response_complex(
|
||||
send_msg, is_response, is_response, timeout
|
||||
)[0]
|
||||
|
||||
def device_info(self):
|
||||
self._check_connected()
|
||||
return self._send_message_await_response(
|
||||
pb.DeviceInfoRequest(), pb.DeviceInfoResponse
|
||||
)
|
||||
|
||||
def ping(self):
|
||||
self._check_connected()
|
||||
return self._send_message_await_response(pb.PingRequest(), pb.PingResponse)
|
||||
|
||||
def disconnect(self, on_disconnect=True):
|
||||
self._check_connected()
|
||||
|
||||
try:
|
||||
self._send_message_await_response(
|
||||
pb.DisconnectRequest(), pb.DisconnectResponse
|
||||
)
|
||||
except APIConnectionError:
|
||||
pass
|
||||
self._close_socket()
|
||||
|
||||
if self.on_disconnect is not None and on_disconnect:
|
||||
self.on_disconnect(None)
|
||||
|
||||
def _check_authenticated(self):
|
||||
if not self._authenticated:
|
||||
raise APIConnectionError("Must login first!")
|
||||
|
||||
def subscribe_logs(self, on_log, log_level=7, dump_config=False):
|
||||
self._check_authenticated()
|
||||
|
||||
def on_msg(msg):
|
||||
if isinstance(msg, pb.SubscribeLogsResponse):
|
||||
on_log(msg)
|
||||
|
||||
self._message_handlers.append(on_msg)
|
||||
req = pb.SubscribeLogsRequest(dump_config=dump_config)
|
||||
req.level = log_level
|
||||
self._send_message(req)
|
||||
|
||||
def _recv(self, amount):
|
||||
ret = bytes()
|
||||
if amount == 0:
|
||||
return ret
|
||||
|
||||
while len(ret) < amount:
|
||||
if self.stopped:
|
||||
raise APIConnectionError("Stopped!")
|
||||
if not self._socket_open_event.is_set():
|
||||
raise APIConnectionError("No socket!")
|
||||
try:
|
||||
val = self._socket.recv(amount - len(ret))
|
||||
except AttributeError as err:
|
||||
raise APIConnectionError("Socket was closed") from err
|
||||
except socket.timeout:
|
||||
continue
|
||||
except OSError as err:
|
||||
raise APIConnectionError(f"Error while receiving data: {err}") from err
|
||||
ret += val
|
||||
return ret
|
||||
|
||||
def _recv_varint(self):
|
||||
raw = bytes()
|
||||
while not raw or raw[-1] & 0x80:
|
||||
raw += self._recv(1)
|
||||
return _bytes_to_varuint(raw)
|
||||
|
||||
def _run_once(self):
|
||||
if not self._socket_open_event.wait(0.1):
|
||||
return
|
||||
|
||||
# Preamble
|
||||
if self._recv(1)[0] != 0x00:
|
||||
raise APIConnectionError("Invalid preamble")
|
||||
|
||||
length = self._recv_varint()
|
||||
msg_type = self._recv_varint()
|
||||
|
||||
raw_msg = self._recv(length)
|
||||
if msg_type not in MESSAGE_TYPE_TO_PROTO:
|
||||
_LOGGER.debug("Skipping message type %s", msg_type)
|
||||
return
|
||||
|
||||
msg = MESSAGE_TYPE_TO_PROTO[msg_type]()
|
||||
msg.ParseFromString(raw_msg)
|
||||
_LOGGER.debug("Got message: %s:\n%s", type(msg), indent(str(msg)))
|
||||
for msg_handler in self._message_handlers[:]:
|
||||
msg_handler(msg)
|
||||
self._handle_internal_messages(msg)
|
||||
|
||||
def run(self):
|
||||
self._running_event.set()
|
||||
while not self.stopped:
|
||||
try:
|
||||
self._run_once()
|
||||
except APIConnectionError as err:
|
||||
if self.stopped:
|
||||
break
|
||||
if self._connected:
|
||||
_LOGGER.error("Error while reading incoming messages: %s", err)
|
||||
self._fatal_error(err)
|
||||
self._running_event.clear()
|
||||
|
||||
def _handle_internal_messages(self, msg):
|
||||
if isinstance(msg, pb.DisconnectRequest):
|
||||
self._send_message(pb.DisconnectResponse())
|
||||
if self._socket is not None:
|
||||
self._socket.close()
|
||||
self._socket = None
|
||||
self._connected = False
|
||||
if self.on_disconnect is not None:
|
||||
self.on_disconnect(None)
|
||||
elif isinstance(msg, pb.PingRequest):
|
||||
self._send_message(pb.PingResponse())
|
||||
elif isinstance(msg, pb.GetTimeRequest):
|
||||
resp = pb.GetTimeResponse()
|
||||
resp.epoch_seconds = int(time.time())
|
||||
self._send_message(resp)
|
||||
|
||||
|
||||
def run_logs(config, address):
|
||||
conf = config["api"]
|
||||
port = conf[CONF_PORT]
|
||||
password = conf[CONF_PASSWORD]
|
||||
_LOGGER.info("Starting log output from %s using esphome API", address)
|
||||
|
||||
cli = APIClient(address, port, password)
|
||||
stopping = False
|
||||
retry_timer = []
|
||||
|
||||
has_connects = []
|
||||
|
||||
def try_connect(err, tries=0):
|
||||
if stopping:
|
||||
return
|
||||
|
||||
if err:
|
||||
_LOGGER.warning("Disconnected from API: %s", err)
|
||||
|
||||
while retry_timer:
|
||||
retry_timer.pop(0).cancel()
|
||||
|
||||
error = None
|
||||
try:
|
||||
cli.connect()
|
||||
cli.login()
|
||||
except APIConnectionError as err2: # noqa
|
||||
error = err2
|
||||
|
||||
if error is None:
|
||||
_LOGGER.info("Successfully connected to %s", address)
|
||||
return
|
||||
|
||||
wait_time = int(min(1.5 ** min(tries, 100), 30))
|
||||
if not has_connects:
|
||||
_LOGGER.warning(
|
||||
"Initial connection failed. The ESP might not be connected "
|
||||
"to WiFi yet (%s). Re-Trying in %s seconds",
|
||||
error,
|
||||
wait_time,
|
||||
)
|
||||
else:
|
||||
_LOGGER.warning(
|
||||
"Couldn't connect to API (%s). Trying to reconnect in %s seconds",
|
||||
error,
|
||||
wait_time,
|
||||
)
|
||||
timer = threading.Timer(
|
||||
wait_time, functools.partial(try_connect, None, tries + 1)
|
||||
)
|
||||
timer.start()
|
||||
retry_timer.append(timer)
|
||||
|
||||
def on_log(msg):
|
||||
time_ = datetime.now().time().strftime("[%H:%M:%S]")
|
||||
text = msg.message
|
||||
if msg.send_failed:
|
||||
text = color(
|
||||
Fore.WHITE,
|
||||
"(Message skipped because it was too big to fit in "
|
||||
"TCP buffer - This is only cosmetic)",
|
||||
)
|
||||
safe_print(time_ + text)
|
||||
|
||||
def on_login():
|
||||
try:
|
||||
cli.subscribe_logs(on_log, dump_config=not has_connects)
|
||||
has_connects.append(True)
|
||||
except APIConnectionError:
|
||||
cli.disconnect()
|
||||
|
||||
cli.on_disconnect = try_connect
|
||||
cli.on_login = on_login
|
||||
cli.start()
|
||||
|
||||
try:
|
||||
try_connect(None)
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
stopping = True
|
||||
cli.stop(True)
|
||||
while retry_timer:
|
||||
retry_timer.pop(0).cancel()
|
||||
return 0
|
||||
@@ -121,7 +121,7 @@ async def to_code(config):
|
||||
decoded = base64.b64decode(conf[CONF_KEY])
|
||||
cg.add(var.set_noise_psk(list(decoded)))
|
||||
cg.add_define("USE_API_NOISE")
|
||||
cg.add_library("esphome/noise-c", "0.1.1")
|
||||
cg.add_library("esphome/noise-c", "0.1.3")
|
||||
else:
|
||||
cg.add_define("USE_API_PLAINTEXT")
|
||||
|
||||
|
||||
@@ -36,19 +36,14 @@ void APIConnection::start() {
|
||||
|
||||
APIError err = helper_->init();
|
||||
if (err != APIError::OK) {
|
||||
ESP_LOGW(TAG, "Helper init failed: %d errno=%d", (int) err, errno);
|
||||
remove_ = true;
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
|
||||
return;
|
||||
}
|
||||
client_info_ = helper_->getpeername();
|
||||
helper_->set_log_info(client_info_);
|
||||
}
|
||||
|
||||
void APIConnection::force_disconnect_client() {
|
||||
this->helper_->close();
|
||||
this->remove_ = true;
|
||||
}
|
||||
|
||||
void APIConnection::loop() {
|
||||
if (this->remove_)
|
||||
return;
|
||||
@@ -57,9 +52,11 @@ void APIConnection::loop() {
|
||||
// when network is disconnected force disconnect immediately
|
||||
// don't wait for timeout
|
||||
this->on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Network unavailable, disconnecting", client_info_.c_str());
|
||||
return;
|
||||
}
|
||||
if (this->next_close_) {
|
||||
// requested a disconnect
|
||||
this->helper_->close();
|
||||
this->remove_ = true;
|
||||
return;
|
||||
@@ -68,7 +65,7 @@ void APIConnection::loop() {
|
||||
APIError err = helper_->loop();
|
||||
if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Socket operation failed: %d", client_info_.c_str(), (int) err);
|
||||
ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
|
||||
return;
|
||||
}
|
||||
ReadPacketBuffer buffer;
|
||||
@@ -77,7 +74,11 @@ void APIConnection::loop() {
|
||||
// pass
|
||||
} else if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Reading failed: %d", client_info_.c_str(), (int) err);
|
||||
if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
|
||||
ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str());
|
||||
} else {
|
||||
ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
this->last_traffic_ = millis();
|
||||
@@ -95,8 +96,8 @@ void APIConnection::loop() {
|
||||
if (this->sent_ping_) {
|
||||
// Disconnect if not responded within 2.5*keepalive
|
||||
if (now - this->last_traffic_ > (keepalive * 5) / 2) {
|
||||
this->force_disconnect_client();
|
||||
ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
|
||||
}
|
||||
} else if (now - this->last_traffic_ > keepalive) {
|
||||
this->sent_ping_ = true;
|
||||
@@ -124,12 +125,40 @@ void APIConnection::loop() {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (state_subs_at_ != -1) {
|
||||
const auto &subs = this->parent_->get_state_subs();
|
||||
if (state_subs_at_ >= subs.size()) {
|
||||
state_subs_at_ = -1;
|
||||
} else {
|
||||
auto &it = subs[state_subs_at_];
|
||||
SubscribeHomeAssistantStateResponse resp;
|
||||
resp.entity_id = it.entity_id;
|
||||
resp.attribute = it.attribute.value();
|
||||
if (this->send_subscribe_home_assistant_state_response(resp)) {
|
||||
state_subs_at_++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) {
|
||||
return App.get_name() + component_type + nameable->get_object_id();
|
||||
}
|
||||
|
||||
DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) {
|
||||
// remote initiated disconnect_client
|
||||
// don't close yet, we still need to send the disconnect response
|
||||
// close will happen on next loop
|
||||
ESP_LOGD(TAG, "%s requested disconnected", client_info_.c_str());
|
||||
this->next_close_ = true;
|
||||
DisconnectResponse resp;
|
||||
return resp;
|
||||
}
|
||||
void APIConnection::on_disconnect_response(const DisconnectResponse &value) {
|
||||
// pass
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state) {
|
||||
if (!this->state_subscription_)
|
||||
@@ -214,6 +243,9 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) {
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
// Shut-up about usage of deprecated speed_level_to_enum/speed_enum_to_level functions for a bit.
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
bool APIConnection::send_fan_state(fan::FanState *fan) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
@@ -262,13 +294,13 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||
// Prefer level
|
||||
call.set_speed(msg.speed_level);
|
||||
} else if (msg.has_speed) {
|
||||
// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations)
|
||||
call.set_speed(fan::speed_enum_to_level(static_cast<fan::FanSpeed>(msg.speed), traits.supported_speed_count()));
|
||||
}
|
||||
if (msg.has_direction)
|
||||
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
|
||||
call.perform();
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
@@ -390,7 +422,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
|
||||
msg.accuracy_decimals = sensor->get_accuracy_decimals();
|
||||
msg.force_update = sensor->get_force_update();
|
||||
msg.device_class = sensor->get_device_class();
|
||||
msg.state_class = static_cast<enums::SensorStateClass>(sensor->state_class);
|
||||
msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
|
||||
msg.disabled_by_default = sensor->is_disabled_by_default();
|
||||
|
||||
return this->send_list_entities_sensor_response(msg);
|
||||
@@ -670,15 +702,7 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin
|
||||
// string message = 3;
|
||||
buffer.encode_string(3, line, strlen(line));
|
||||
// SubscribeLogsResponse - 29
|
||||
bool success = this->send_buffer(buffer, 29);
|
||||
if (!success) {
|
||||
buffer = this->create_buffer();
|
||||
// bool send_failed = 4;
|
||||
buffer.encode_bool(4, true);
|
||||
return this->send_buffer(buffer, 29);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return this->send_buffer(buffer, 29);
|
||||
}
|
||||
|
||||
HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
||||
@@ -700,7 +724,7 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
|
||||
// bool invalid_password = 1;
|
||||
resp.invalid_password = !correct;
|
||||
if (correct) {
|
||||
ESP_LOGD(TAG, "Client '%s' connected successfully!", this->client_info_.c_str());
|
||||
ESP_LOGD(TAG, "%s: Connected successfully", this->client_info_.c_str());
|
||||
this->connection_state_ = ConnectionState::AUTHENTICATED;
|
||||
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
@@ -746,28 +770,39 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
||||
}
|
||||
}
|
||||
void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {
|
||||
for (auto &it : this->parent_->get_state_subs()) {
|
||||
SubscribeHomeAssistantStateResponse resp;
|
||||
resp.entity_id = it.entity_id;
|
||||
resp.attribute = it.attribute.value();
|
||||
if (!this->send_subscribe_home_assistant_state_response(resp)) {
|
||||
this->on_fatal_error();
|
||||
return;
|
||||
}
|
||||
}
|
||||
state_subs_at_ = 0;
|
||||
}
|
||||
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) {
|
||||
if (this->remove_)
|
||||
return false;
|
||||
if (!this->helper_->can_write_without_blocking())
|
||||
return false;
|
||||
if (!this->helper_->can_write_without_blocking()) {
|
||||
delay(0);
|
||||
APIError err = helper_->loop();
|
||||
if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
|
||||
return false;
|
||||
}
|
||||
if (!this->helper_->can_write_without_blocking()) {
|
||||
// SubscribeLogsResponse
|
||||
if (message_type != 29) {
|
||||
ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
|
||||
}
|
||||
delay(0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
APIError err = this->helper_->write_packet(message_type, buffer.get_buffer()->data(), buffer.get_buffer()->size());
|
||||
if (err == APIError::WOULD_BLOCK)
|
||||
return false;
|
||||
if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Packet write failed %d errno=%d", client_info_.c_str(), (int) err, errno);
|
||||
if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) {
|
||||
ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str());
|
||||
} else {
|
||||
ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
this->last_traffic_ = millis();
|
||||
@@ -775,14 +810,13 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type)
|
||||
}
|
||||
void APIConnection::on_unauthenticated_access() {
|
||||
this->on_fatal_error();
|
||||
ESP_LOGD(TAG, "'%s' tried to access without authentication.", this->client_info_.c_str());
|
||||
ESP_LOGD(TAG, "%s: tried to access without authentication.", this->client_info_.c_str());
|
||||
}
|
||||
void APIConnection::on_no_setup_connection() {
|
||||
this->on_fatal_error();
|
||||
ESP_LOGD(TAG, "'%s' tried to access without full connection.", this->client_info_.c_str());
|
||||
ESP_LOGD(TAG, "%s: tried to access without full connection.", this->client_info_.c_str());
|
||||
}
|
||||
void APIConnection::on_fatal_error() {
|
||||
ESP_LOGV(TAG, "Error: Disconnecting %s", this->client_info_.c_str());
|
||||
this->helper_->close();
|
||||
this->remove_ = true;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ class APIConnection : public APIServerConnection {
|
||||
virtual ~APIConnection() = default;
|
||||
|
||||
void start();
|
||||
void force_disconnect_client();
|
||||
void loop();
|
||||
|
||||
bool send_list_info_done() {
|
||||
@@ -88,10 +87,7 @@ class APIConnection : public APIServerConnection {
|
||||
}
|
||||
#endif
|
||||
|
||||
void on_disconnect_response(const DisconnectResponse &value) override {
|
||||
this->helper_->close();
|
||||
this->remove_ = true;
|
||||
}
|
||||
void on_disconnect_response(const DisconnectResponse &value) override;
|
||||
void on_ping_response(const PingResponse &value) override {
|
||||
// we initiated ping
|
||||
this->sent_ping_ = false;
|
||||
@@ -102,14 +98,7 @@ class APIConnection : public APIServerConnection {
|
||||
#endif
|
||||
HelloResponse hello(const HelloRequest &msg) override;
|
||||
ConnectResponse connect(const ConnectRequest &msg) override;
|
||||
DisconnectResponse disconnect(const DisconnectRequest &msg) override {
|
||||
// remote initiated disconnect_client
|
||||
// don't close yet, we still need to send the disconnect response
|
||||
// close will happen on next loop
|
||||
this->next_close_ = true;
|
||||
DisconnectResponse resp;
|
||||
return resp;
|
||||
}
|
||||
DisconnectResponse disconnect(const DisconnectRequest &msg) override;
|
||||
PingResponse ping(const PingRequest &msg) override { return {}; }
|
||||
DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override;
|
||||
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); }
|
||||
@@ -177,6 +166,7 @@ class APIConnection : public APIServerConnection {
|
||||
APIServer *parent_;
|
||||
InitialStateIterator initial_state_iterator_;
|
||||
ListEntitiesIterator list_entities_iterator_;
|
||||
int state_subs_at_ = -1;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
||||
@@ -17,7 +17,59 @@ bool is_would_block(ssize_t ret) {
|
||||
return ret == 0;
|
||||
}
|
||||
|
||||
const char *api_error_to_str(APIError err) {
|
||||
// not using switch to ensure compiler doesn't try to build a big table out of it
|
||||
if (err == APIError::OK) {
|
||||
return "OK";
|
||||
} else if (err == APIError::WOULD_BLOCK) {
|
||||
return "WOULD_BLOCK";
|
||||
} else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
||||
return "BAD_HANDSHAKE_PACKET_LEN";
|
||||
} else if (err == APIError::BAD_INDICATOR) {
|
||||
return "BAD_INDICATOR";
|
||||
} else if (err == APIError::BAD_DATA_PACKET) {
|
||||
return "BAD_DATA_PACKET";
|
||||
} else if (err == APIError::TCP_NODELAY_FAILED) {
|
||||
return "TCP_NODELAY_FAILED";
|
||||
} else if (err == APIError::TCP_NONBLOCKING_FAILED) {
|
||||
return "TCP_NONBLOCKING_FAILED";
|
||||
} else if (err == APIError::CLOSE_FAILED) {
|
||||
return "CLOSE_FAILED";
|
||||
} else if (err == APIError::SHUTDOWN_FAILED) {
|
||||
return "SHUTDOWN_FAILED";
|
||||
} else if (err == APIError::BAD_STATE) {
|
||||
return "BAD_STATE";
|
||||
} else if (err == APIError::BAD_ARG) {
|
||||
return "BAD_ARG";
|
||||
} else if (err == APIError::SOCKET_READ_FAILED) {
|
||||
return "SOCKET_READ_FAILED";
|
||||
} else if (err == APIError::SOCKET_WRITE_FAILED) {
|
||||
return "SOCKET_WRITE_FAILED";
|
||||
} else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
|
||||
return "HANDSHAKESTATE_READ_FAILED";
|
||||
} else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
|
||||
return "HANDSHAKESTATE_WRITE_FAILED";
|
||||
} else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
|
||||
return "HANDSHAKESTATE_BAD_STATE";
|
||||
} else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
|
||||
return "CIPHERSTATE_DECRYPT_FAILED";
|
||||
} else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
|
||||
return "CIPHERSTATE_ENCRYPT_FAILED";
|
||||
} else if (err == APIError::OUT_OF_MEMORY) {
|
||||
return "OUT_OF_MEMORY";
|
||||
} else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
|
||||
return "HANDSHAKESTATE_SETUP_FAILED";
|
||||
} else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
|
||||
return "HANDSHAKESTATE_SPLIT_FAILED";
|
||||
} else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
|
||||
return "BAD_HANDSHAKE_ERROR_BYTE";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__)
|
||||
// uncomment to log raw packets
|
||||
//#define HELPER_LOG_PACKETS
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
|
||||
@@ -73,6 +125,7 @@ APIError APINoiseFrameHelper::init() {
|
||||
HELPER_LOG("Setting nonblocking failed with errno %d", errno);
|
||||
return APIError::TCP_NONBLOCKING_FAILED;
|
||||
}
|
||||
|
||||
int enable = 1;
|
||||
err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
|
||||
if (err != 0) {
|
||||
@@ -188,7 +241,9 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
}
|
||||
|
||||
// uncomment for even more debugging
|
||||
// ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
|
||||
#endif
|
||||
frame->msg = std::move(rx_buf_);
|
||||
// consume msg
|
||||
rx_buf_ = {};
|
||||
@@ -217,6 +272,14 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
// waiting for client hello
|
||||
ParsedFrame frame;
|
||||
aerr = try_read_frame_(&frame);
|
||||
if (aerr == APIError::BAD_INDICATOR) {
|
||||
send_explicit_handshake_reject_("Bad indicator byte");
|
||||
return aerr;
|
||||
}
|
||||
if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
||||
send_explicit_handshake_reject_("Bad handshake packet len");
|
||||
return aerr;
|
||||
}
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
// ignore contents, may be used in future for flags
|
||||
@@ -260,11 +323,11 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
|
||||
if (frame.msg.empty()) {
|
||||
send_explicit_handshake_reject_("Empty handshake message");
|
||||
return APIError::BAD_HANDSHAKE_PACKET_LEN;
|
||||
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||
} else if (frame.msg[0] != 0x00) {
|
||||
HELPER_LOG("Bad handshake error byte: %u", frame.msg[0]);
|
||||
send_explicit_handshake_reject_("Bad handshake error byte");
|
||||
return APIError::BAD_HANDSHAKE_PACKET_LEN;
|
||||
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||
}
|
||||
|
||||
NoiseBuffer mbuf;
|
||||
@@ -272,7 +335,6 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
noise_buffer_set_input(mbuf, frame.msg.data() + 1, frame.msg.size() - 1);
|
||||
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
|
||||
if (err != 0) {
|
||||
// TODO: explicit rejection
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_handshakestate_read_message failed: %s", noise_err_to_str(err).c_str());
|
||||
if (err == NOISE_ERROR_MAC_FAILURE) {
|
||||
@@ -320,12 +382,16 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
}
|
||||
void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) {
|
||||
std::vector<uint8_t> data;
|
||||
data.reserve(reason.size() + 1);
|
||||
data.resize(reason.length() + 1);
|
||||
data[0] = 0x01; // failure
|
||||
for (size_t i = 0; i < reason.size(); i++) {
|
||||
for (size_t i = 0; i < reason.length(); i++) {
|
||||
data[i + 1] = (uint8_t) reason[i];
|
||||
}
|
||||
// temporarily remove failed state
|
||||
auto orig_state = state_;
|
||||
state_ = State::EXPLICIT_REJECT;
|
||||
write_frame_(data.data(), data.size());
|
||||
state_ = orig_state;
|
||||
}
|
||||
|
||||
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
@@ -429,12 +495,13 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload
|
||||
size_t total_len = 3 + mbuf.size;
|
||||
tmpbuf[1] = (uint8_t)(mbuf.size >> 8);
|
||||
tmpbuf[2] = (uint8_t) mbuf.size;
|
||||
|
||||
struct iovec iov;
|
||||
iov.iov_base = &tmpbuf[0];
|
||||
iov.iov_len = total_len;
|
||||
|
||||
// write raw to not have two packets sent if NAGLE disabled
|
||||
aerr = write_raw_(&tmpbuf[0], total_len);
|
||||
if (aerr != APIError::OK) {
|
||||
return aerr;
|
||||
}
|
||||
return APIError::OK;
|
||||
return write_raw_(&iov, 1);
|
||||
}
|
||||
APIError APINoiseFrameHelper::try_send_tx_buf_() {
|
||||
// try send from tx_buf
|
||||
@@ -461,14 +528,19 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() {
|
||||
* @param data The data to write
|
||||
* @param len The length of data
|
||||
*/
|
||||
APIError APINoiseFrameHelper::write_raw_(const uint8_t *data, size_t len) {
|
||||
if (len == 0)
|
||||
APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
|
||||
if (iovcnt == 0)
|
||||
return APIError::OK;
|
||||
int err;
|
||||
APIError aerr;
|
||||
|
||||
// uncomment for even more debugging
|
||||
// ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str());
|
||||
size_t total_write_len = 0;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
|
||||
#endif
|
||||
total_write_len += iov[i].iov_len;
|
||||
}
|
||||
|
||||
if (!tx_buf_.empty()) {
|
||||
// try to empty tx_buf_ first
|
||||
@@ -479,41 +551,56 @@ APIError APINoiseFrameHelper::write_raw_(const uint8_t *data, size_t len) {
|
||||
|
||||
if (!tx_buf_.empty()) {
|
||||
// tx buf not empty, can't write now because then stream would be inconsistent
|
||||
tx_buf_.insert(tx_buf_.end(), data, data + len);
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
|
||||
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
ssize_t sent = socket_->write(data, len);
|
||||
ssize_t sent = socket_->writev(iov, iovcnt);
|
||||
if (is_would_block(sent)) {
|
||||
// operation would block, add buffer to tx_buf
|
||||
tx_buf_.insert(tx_buf_.end(), data, data + len);
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
|
||||
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
||||
}
|
||||
return APIError::OK;
|
||||
} else if (sent == -1) {
|
||||
// an error occured
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket write failed with errno %d", errno);
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
} else if (sent != len) {
|
||||
} else if (sent != total_write_len) {
|
||||
// partially sent, add end to tx_buf
|
||||
tx_buf_.insert(tx_buf_.end(), data + sent, data + len);
|
||||
size_t to_consume = sent;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
if (to_consume >= iov[i].iov_len) {
|
||||
to_consume -= iov[i].iov_len;
|
||||
} else {
|
||||
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
|
||||
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
||||
to_consume = 0;
|
||||
}
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
// fully sent
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
|
||||
APIError aerr;
|
||||
|
||||
uint8_t header[3];
|
||||
header[0] = 0x01; // indicator
|
||||
header[1] = (uint8_t)(len >> 8);
|
||||
header[2] = (uint8_t) len;
|
||||
|
||||
aerr = write_raw_(header, 3);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
aerr = write_raw_(data, len);
|
||||
return aerr;
|
||||
struct iovec iov[2];
|
||||
iov[0].iov_base = header;
|
||||
iov[0].iov_len = 3;
|
||||
iov[1].iov_base = const_cast<uint8_t *>(data);
|
||||
iov[1].iov_len = len;
|
||||
|
||||
return write_raw_(iov, 2);
|
||||
}
|
||||
|
||||
/** Initiate the data structures for the handshake.
|
||||
@@ -707,7 +794,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
}
|
||||
|
||||
size_t i = 1;
|
||||
size_t consumed = 0;
|
||||
uint32_t consumed = 0;
|
||||
auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed);
|
||||
if (!msg_size_varint.has_value()) {
|
||||
// not enough data there yet
|
||||
@@ -751,7 +838,9 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
}
|
||||
|
||||
// uncomment for even more debugging
|
||||
// ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
|
||||
#endif
|
||||
frame->msg = std::move(rx_buf_);
|
||||
// consume msg
|
||||
rx_buf_ = {};
|
||||
@@ -794,28 +883,24 @@ APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *pay
|
||||
ProtoVarInt(payload_len).encode(header);
|
||||
ProtoVarInt(type).encode(header);
|
||||
|
||||
aerr = write_raw_(&header[0], header.size());
|
||||
if (aerr != APIError::OK) {
|
||||
return aerr;
|
||||
}
|
||||
aerr = write_raw_(payload, payload_len);
|
||||
if (aerr != APIError::OK) {
|
||||
return aerr;
|
||||
}
|
||||
return APIError::OK;
|
||||
struct iovec iov[2];
|
||||
iov[0].iov_base = &header[0];
|
||||
iov[0].iov_len = header.size();
|
||||
iov[1].iov_base = const_cast<uint8_t *>(payload);
|
||||
iov[1].iov_len = payload_len;
|
||||
|
||||
return write_raw_(iov, 2);
|
||||
}
|
||||
APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
|
||||
// try send from tx_buf
|
||||
while (state_ != State::CLOSED && !tx_buf_.empty()) {
|
||||
ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size());
|
||||
if (sent == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN)
|
||||
break;
|
||||
if (is_would_block(sent)) {
|
||||
break;
|
||||
} else if (sent == -1) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket write failed with errno %d", errno);
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
} else if (sent == 0) {
|
||||
break;
|
||||
}
|
||||
// TODO: inefficient if multiple packets in txbuf
|
||||
// replace with deque of buffers
|
||||
@@ -829,14 +914,19 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
|
||||
* @param data The data to write
|
||||
* @param len The length of data
|
||||
*/
|
||||
APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) {
|
||||
if (len == 0)
|
||||
APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
|
||||
if (iovcnt == 0)
|
||||
return APIError::OK;
|
||||
int err;
|
||||
APIError aerr;
|
||||
|
||||
// uncomment for even more debugging
|
||||
// ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str());
|
||||
size_t total_write_len = 0;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
|
||||
#endif
|
||||
total_write_len += iov[i].iov_len;
|
||||
}
|
||||
|
||||
if (!tx_buf_.empty()) {
|
||||
// try to empty tx_buf_ first
|
||||
@@ -847,42 +937,43 @@ APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) {
|
||||
|
||||
if (!tx_buf_.empty()) {
|
||||
// tx buf not empty, can't write now because then stream would be inconsistent
|
||||
tx_buf_.insert(tx_buf_.end(), data, data + len);
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
|
||||
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
ssize_t sent = socket_->write(data, len);
|
||||
ssize_t sent = socket_->writev(iov, iovcnt);
|
||||
if (is_would_block(sent)) {
|
||||
// operation would block, add buffer to tx_buf
|
||||
tx_buf_.insert(tx_buf_.end(), data, data + len);
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
|
||||
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
||||
}
|
||||
return APIError::OK;
|
||||
} else if (sent == -1) {
|
||||
// an error occured
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket write failed with errno %d", errno);
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
} else if (sent != len) {
|
||||
} else if (sent != total_write_len) {
|
||||
// partially sent, add end to tx_buf
|
||||
tx_buf_.insert(tx_buf_.end(), data + sent, data + len);
|
||||
size_t to_consume = sent;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
if (to_consume >= iov[i].iov_len) {
|
||||
to_consume -= iov[i].iov_len;
|
||||
} else {
|
||||
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
|
||||
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
||||
to_consume = 0;
|
||||
}
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
// fully sent
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APIPlaintextFrameHelper::write_frame_(const uint8_t *data, size_t len) {
|
||||
APIError aerr;
|
||||
|
||||
uint8_t header[3];
|
||||
header[0] = 0x01; // indicator
|
||||
header[1] = (uint8_t)(len >> 8);
|
||||
header[2] = (uint8_t) len;
|
||||
|
||||
aerr = write_raw_(header, 3);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
aerr = write_raw_(data, len);
|
||||
return aerr;
|
||||
}
|
||||
|
||||
APIError APIPlaintextFrameHelper::close() {
|
||||
state_ = State::CLOSED;
|
||||
|
||||
@@ -51,10 +51,14 @@ enum class APIError : int {
|
||||
OUT_OF_MEMORY = 1018,
|
||||
HANDSHAKESTATE_SETUP_FAILED = 1019,
|
||||
HANDSHAKESTATE_SPLIT_FAILED = 1020,
|
||||
BAD_HANDSHAKE_ERROR_BYTE = 1021,
|
||||
};
|
||||
|
||||
const char *api_error_to_str(APIError err);
|
||||
|
||||
class APIFrameHelper {
|
||||
public:
|
||||
virtual ~APIFrameHelper() = default;
|
||||
virtual APIError init() = 0;
|
||||
virtual APIError loop() = 0;
|
||||
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
|
||||
@@ -93,7 +97,7 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
||||
APIError try_read_frame_(ParsedFrame *frame);
|
||||
APIError try_send_tx_buf_();
|
||||
APIError write_frame_(const uint8_t *data, size_t len);
|
||||
APIError write_raw_(const uint8_t *data, size_t len);
|
||||
APIError write_raw_(const struct iovec *iov, int iovcnt);
|
||||
APIError init_handshake_();
|
||||
APIError check_handshake_finished_();
|
||||
void send_explicit_handshake_reject_(const std::string &reason);
|
||||
@@ -123,6 +127,7 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
||||
DATA = 5,
|
||||
CLOSED = 6,
|
||||
FAILED = 7,
|
||||
EXPLICIT_REJECT = 8,
|
||||
} state_ = State::INITIALIZE;
|
||||
};
|
||||
#endif // USE_API_NOISE
|
||||
@@ -150,8 +155,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||
|
||||
APIError try_read_frame_(ParsedFrame *frame);
|
||||
APIError try_send_tx_buf_();
|
||||
APIError write_frame_(const uint8_t *data, size_t len);
|
||||
APIError write_raw_(const uint8_t *data, size_t len);
|
||||
APIError write_raw_(const struct iovec *iov, int iovcnt);
|
||||
|
||||
std::unique_ptr<socket::Socket> socket_;
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ void APIServer::loop() {
|
||||
std::partition(this->clients_.begin(), this->clients_.end(), [](APIConnection *conn) { return !conn->remove_; });
|
||||
// print disconnection messages
|
||||
for (auto it = new_end; it != this->clients_.end(); ++it) {
|
||||
ESP_LOGD(TAG, "Disconnecting %s", (*it)->client_info_.c_str());
|
||||
ESP_LOGV(TAG, "Removing connection to %s", (*it)->client_info_.c_str());
|
||||
}
|
||||
// only then delete the pointers, otherwise log routine
|
||||
// would access freed memory
|
||||
@@ -134,6 +134,11 @@ void APIServer::loop() {
|
||||
void APIServer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "API Server:");
|
||||
ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->port_);
|
||||
#ifdef USE_API_NOISE
|
||||
ESP_LOGCONFIG(TAG, " Using noise encryption: YES");
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, " Using noise encryption: NO");
|
||||
#endif
|
||||
}
|
||||
bool APIServer::uses_password() const { return !this->password_.empty(); }
|
||||
bool APIServer::check_password(const std::string &password) const {
|
||||
|
||||
73
esphome/components/api/client.py
Normal file
73
esphome/components/api/client.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from aioesphomeapi import APIClient, ReconnectLogic, APIConnectionError, LogLevel
|
||||
import zeroconf
|
||||
|
||||
from esphome.const import CONF_KEY, CONF_PORT, CONF_PASSWORD, __version__
|
||||
from esphome.util import safe_print
|
||||
from . import CONF_ENCRYPTION
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_run_logs(config, address):
|
||||
conf = config["api"]
|
||||
port: int = int(conf[CONF_PORT])
|
||||
password: str = conf[CONF_PASSWORD]
|
||||
noise_psk: Optional[str] = None
|
||||
if CONF_ENCRYPTION in conf:
|
||||
noise_psk = conf[CONF_ENCRYPTION][CONF_KEY]
|
||||
_LOGGER.info("Starting log output from %s using esphome API", address)
|
||||
zc = zeroconf.Zeroconf()
|
||||
cli = APIClient(
|
||||
asyncio.get_event_loop(),
|
||||
address,
|
||||
port,
|
||||
password,
|
||||
client_info=f"ESPHome Logs {__version__}",
|
||||
noise_psk=noise_psk,
|
||||
)
|
||||
first_connect = True
|
||||
|
||||
def on_log(msg):
|
||||
time_ = datetime.now().time().strftime("[%H:%M:%S]")
|
||||
text = msg.message.decode("utf8", "backslashreplace")
|
||||
safe_print(time_ + text)
|
||||
|
||||
async def on_connect():
|
||||
nonlocal first_connect
|
||||
try:
|
||||
await cli.subscribe_logs(
|
||||
on_log,
|
||||
log_level=LogLevel.LOG_LEVEL_VERY_VERBOSE,
|
||||
dump_config=first_connect,
|
||||
)
|
||||
first_connect = False
|
||||
except APIConnectionError:
|
||||
cli.disconnect()
|
||||
|
||||
async def on_disconnect():
|
||||
_LOGGER.warning("Disconnected from API")
|
||||
|
||||
zc = zeroconf.Zeroconf()
|
||||
reconnect = ReconnectLogic(
|
||||
client=cli,
|
||||
on_connect=on_connect,
|
||||
on_disconnect=on_disconnect,
|
||||
zeroconf_instance=zc,
|
||||
)
|
||||
await reconnect.start()
|
||||
|
||||
try:
|
||||
while True:
|
||||
await asyncio.sleep(60)
|
||||
except KeyboardInterrupt:
|
||||
await reconnect.stop()
|
||||
zc.close()
|
||||
|
||||
|
||||
def run_logs(config, address):
|
||||
asyncio.run(async_run_logs(config, address))
|
||||
@@ -11,7 +11,7 @@ template<typename... Ts> class OpenAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit OpenAction(Cover *cover) : cover_(cover) {}
|
||||
|
||||
void play(Ts... x) override { this->cover_->open(); }
|
||||
void play(Ts... x) override { this->cover_->make_call().set_command_open().perform(); }
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
@@ -21,7 +21,7 @@ template<typename... Ts> class CloseAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit CloseAction(Cover *cover) : cover_(cover) {}
|
||||
|
||||
void play(Ts... x) override { this->cover_->close(); }
|
||||
void play(Ts... x) override { this->cover_->make_call().set_command_close().perform(); }
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
@@ -31,7 +31,7 @@ template<typename... Ts> class StopAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit StopAction(Cover *cover) : cover_(cover) {}
|
||||
|
||||
void play(Ts... x) override { this->cover_->stop(); }
|
||||
void play(Ts... x) override { this->cover_->make_call().set_command_stop().perform(); }
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
|
||||
@@ -11,7 +11,7 @@ class DemoSensor : public sensor::Sensor, public PollingComponent {
|
||||
public:
|
||||
void update() override {
|
||||
float val = random_float();
|
||||
bool increasing = this->state_class == sensor::STATE_CLASS_TOTAL_INCREASING;
|
||||
bool increasing = this->get_state_class() == sensor::STATE_CLASS_TOTAL_INCREASING;
|
||||
if (increasing) {
|
||||
float base = isnan(this->state) ? 0.0f : this->state;
|
||||
this->publish_state(base + val * 10);
|
||||
|
||||
@@ -13,9 +13,13 @@ from esphome.const import (
|
||||
STATE_CLASS_NONE,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_AMPERE,
|
||||
UNIT_CUBIC_METER,
|
||||
UNIT_EMPTY,
|
||||
UNIT_KILOWATT,
|
||||
UNIT_KILOWATT_HOURS,
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE,
|
||||
UNIT_VOLT,
|
||||
UNIT_WATT,
|
||||
)
|
||||
from . import Dsmr, CONF_DSMR_ID
|
||||
|
||||
@@ -26,40 +30,80 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_DSMR_ID): cv.use_id(Dsmr),
|
||||
cv.Optional("energy_delivered_lux"): sensor.sensor_schema(
|
||||
"kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING
|
||||
UNIT_KILOWATT_HOURS,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional("energy_delivered_tariff1"): sensor.sensor_schema(
|
||||
"kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING
|
||||
UNIT_KILOWATT_HOURS,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional("energy_delivered_tariff2"): sensor.sensor_schema(
|
||||
"kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING
|
||||
UNIT_KILOWATT_HOURS,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional("energy_returned_lux"): sensor.sensor_schema(
|
||||
"kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING
|
||||
UNIT_KILOWATT_HOURS,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional("energy_returned_tariff1"): sensor.sensor_schema(
|
||||
"kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING
|
||||
UNIT_KILOWATT_HOURS,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional("energy_returned_tariff2"): sensor.sensor_schema(
|
||||
"kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING
|
||||
UNIT_KILOWATT_HOURS,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional("total_imported_energy"): sensor.sensor_schema(
|
||||
"kvarh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_NONE,
|
||||
),
|
||||
cv.Optional("total_exported_energy"): sensor.sensor_schema(
|
||||
"kvarh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_NONE,
|
||||
),
|
||||
cv.Optional("power_delivered"): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional("power_returned"): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional("reactive_power_delivered"): sensor.sensor_schema(
|
||||
"kvar", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_POWER,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional("reactive_power_returned"): sensor.sensor_schema(
|
||||
"kvar", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_POWER,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional("electricity_threshold"): sensor.sensor_schema(
|
||||
UNIT_EMPTY, ICON_EMPTY, 3, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE
|
||||
@@ -77,13 +121,13 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE
|
||||
),
|
||||
cv.Optional("electricity_sags_l2"): sensor.sensor_schema(
|
||||
UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT
|
||||
UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE
|
||||
),
|
||||
cv.Optional("electricity_sags_l3"): sensor.sensor_schema(
|
||||
UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE
|
||||
),
|
||||
cv.Optional("electricity_swells_l1"): sensor.sensor_schema(
|
||||
UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT
|
||||
UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE
|
||||
),
|
||||
cv.Optional("electricity_swells_l2"): sensor.sensor_schema(
|
||||
UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE
|
||||
@@ -101,40 +145,64 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional("power_delivered_l1"): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional("power_delivered_l2"): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional("power_delivered_l3"): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional("power_returned_l1"): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional("power_returned_l2"): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional("power_returned_l3"): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional("reactive_power_delivered_l1"): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_POWER,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional("reactive_power_delivered_l2"): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_POWER,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional("reactive_power_delivered_l3"): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_POWER,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional("reactive_power_returned_l1"): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_POWER,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional("reactive_power_returned_l2"): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_POWER,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional("reactive_power_returned_l3"): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_POWER,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional("voltage_l1"): sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE
|
||||
@@ -146,10 +214,18 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE
|
||||
),
|
||||
cv.Optional("gas_delivered"): sensor.sensor_schema(
|
||||
"m³", ICON_EMPTY, 3, DEVICE_CLASS_GAS, STATE_CLASS_TOTAL_INCREASING
|
||||
UNIT_CUBIC_METER,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_GAS,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional("gas_delivered_be"): sensor.sensor_schema(
|
||||
"m³", ICON_EMPTY, 3, DEVICE_CLASS_GAS, STATE_CLASS_TOTAL_INCREASING
|
||||
UNIT_CUBIC_METER,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_GAS,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
@@ -4,14 +4,15 @@
|
||||
namespace esphome {
|
||||
namespace fan {
|
||||
|
||||
// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations)
|
||||
// This whole file is deprecated, don't warn about usage of deprecated types in here.
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels) {
|
||||
const auto speed_ratio = static_cast<float>(speed_level) / (supported_speed_levels + 1);
|
||||
const auto legacy_level = clamp<int>(static_cast<int>(ceilf(speed_ratio * 3)), 1, 3);
|
||||
return static_cast<FanSpeed>(legacy_level - 1); // NOLINT(clang-diagnostic-deprecated-declarations)
|
||||
return static_cast<FanSpeed>(legacy_level - 1);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations)
|
||||
int speed_enum_to_level(FanSpeed speed, int supported_speed_levels) {
|
||||
const auto enum_level = static_cast<int>(speed) + 1;
|
||||
const auto speed_level = roundf(enum_level / 3.0f * supported_speed_levels);
|
||||
|
||||
@@ -4,8 +4,16 @@
|
||||
namespace esphome {
|
||||
namespace fan {
|
||||
|
||||
// Shut-up about usage of deprecated FanSpeed for a bit.
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
ESPDEPRECATED("FanSpeed and speed_level_to_enum() are deprecated.", "2021.9")
|
||||
FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels);
|
||||
ESPDEPRECATED("FanSpeed and speed_enum_to_level() are deprecated.", "2021.9")
|
||||
int speed_enum_to_level(FanSpeed speed, int supported_speed_levels);
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
} // namespace fan
|
||||
} // namespace esphome
|
||||
|
||||
@@ -67,6 +67,8 @@ void FanStateCall::perform() const {
|
||||
this->state_->state_callback_.call();
|
||||
}
|
||||
|
||||
// This whole method is deprecated, don't warn about usage of deprecated methods inside of it.
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
FanStateCall &FanStateCall::set_speed(const char *legacy_speed) {
|
||||
const auto supported_speed_count = this->state_->get_traits().supported_speed_count();
|
||||
if (strcasecmp(legacy_speed, "low") == 0) {
|
||||
|
||||
@@ -27,7 +27,7 @@ std::unique_ptr<LightTransformer> AddressableLight::create_default_transition()
|
||||
return make_unique<AddressableLightTransformer>(*this);
|
||||
}
|
||||
|
||||
Color esp_color_from_light_color_values(LightColorValues val) {
|
||||
Color color_from_light_color_values(LightColorValues val) {
|
||||
auto r = to_uint8_scale(val.get_color_brightness() * val.get_red());
|
||||
auto g = to_uint8_scale(val.get_color_brightness() * val.get_green());
|
||||
auto b = to_uint8_scale(val.get_color_brightness() * val.get_blue());
|
||||
@@ -44,7 +44,7 @@ void AddressableLight::update_state(LightState *state) {
|
||||
return;
|
||||
|
||||
// don't use LightState helper, gamma correction+brightness is handled by ESPColorView
|
||||
this->all() = esp_color_from_light_color_values(val);
|
||||
this->all() = color_from_light_color_values(val);
|
||||
this->schedule_show();
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ void AddressableLightTransformer::start() {
|
||||
return;
|
||||
|
||||
auto end_values = this->target_values_;
|
||||
this->target_color_ = esp_color_from_light_color_values(end_values);
|
||||
this->target_color_ = color_from_light_color_values(end_values);
|
||||
|
||||
// our transition will handle brightness, disable brightness in correction.
|
||||
this->light_.correction_.set_local_brightness(255);
|
||||
@@ -62,10 +62,13 @@ void AddressableLightTransformer::start() {
|
||||
}
|
||||
|
||||
optional<LightColorValues> AddressableLightTransformer::apply() {
|
||||
// Don't try to transition over running effects, instead immediately use the target values. write_state() and the
|
||||
// effects pick up the change from current_values.
|
||||
float smoothed_progress = LightTransitionTransformer::smoothed_progress(this->get_progress_());
|
||||
|
||||
// When running an output-buffer modifying effect, don't try to transition individual LEDs, but instead just fade the
|
||||
// LightColorValues. write_state() then picks up the change in brightness, and the color change is picked up by the
|
||||
// effects which respect it.
|
||||
if (this->light_.is_effect_active())
|
||||
return this->target_values_;
|
||||
return LightColorValues::lerp(this->get_start_values(), this->get_target_values(), smoothed_progress);
|
||||
|
||||
// Use a specialized transition for addressable lights: instead of using a unified transition for
|
||||
// all LEDs, we use the current state of each LED as the start.
|
||||
@@ -75,8 +78,6 @@ optional<LightColorValues> AddressableLightTransformer::apply() {
|
||||
// Instead, we "fake" the look of the LERP by using an exponential average over time and using
|
||||
// dynamically-calculated alpha values to match the look.
|
||||
|
||||
float smoothed_progress = LightTransitionTransformer::smoothed_progress(this->get_progress_());
|
||||
|
||||
float denom = (1.0f - smoothed_progress);
|
||||
float alpha = denom == 0.0f ? 0.0f : (smoothed_progress - this->last_transition_progress_) / denom;
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@ namespace light {
|
||||
|
||||
using ESPColor ESPDEPRECATED("esphome::light::ESPColor is deprecated, use esphome::Color instead.", "v1.21") = Color;
|
||||
|
||||
/// Convert the color information from a `LightColorValues` object to a `Color` object (does not apply brightness).
|
||||
Color color_from_light_color_values(LightColorValues val);
|
||||
|
||||
class AddressableLight : public LightOutput, public Component {
|
||||
public:
|
||||
virtual int32_t size() const = 0;
|
||||
|
||||
@@ -38,11 +38,8 @@ class AddressableLightEffect : public LightEffect {
|
||||
void stop() override { this->get_addressable_()->set_effect_active(false); }
|
||||
virtual void apply(AddressableLight &it, const Color ¤t_color) = 0;
|
||||
void apply() override {
|
||||
LightColorValues color = this->state_->remote_values;
|
||||
// not using any color correction etc. that will be handled by the addressable layer
|
||||
Color current_color =
|
||||
Color(static_cast<uint8_t>(color.get_red() * 255), static_cast<uint8_t>(color.get_green() * 255),
|
||||
static_cast<uint8_t>(color.get_blue() * 255), static_cast<uint8_t>(color.get_white() * 255));
|
||||
// not using any color correction etc. that will be handled by the addressable layer through ESPColorCorrection
|
||||
Color current_color = color_from_light_color_values(this->state_->remote_values);
|
||||
this->apply(*this->get_addressable_(), current_color);
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ class StrobeLightEffect : public LightEffect {
|
||||
|
||||
if (!color.is_on()) {
|
||||
// Don't turn the light off, otherwise the light effect will be stopped
|
||||
call.set_brightness_if_supported(0.0f);
|
||||
call.set_brightness(0.0f);
|
||||
call.set_state(true);
|
||||
}
|
||||
call.set_publish(false);
|
||||
|
||||
@@ -121,6 +121,9 @@ void LightState::loop() {
|
||||
}
|
||||
|
||||
if (this->transformer_->is_finished()) {
|
||||
// if the transition has written directly to the output, current_values is outdated, so update it
|
||||
this->current_values = this->transformer_->get_target_values();
|
||||
|
||||
this->transformer_->stop();
|
||||
this->transformer_ = nullptr;
|
||||
this->target_state_reached_callback_.call();
|
||||
|
||||
@@ -12,12 +12,18 @@ namespace light {
|
||||
class LightTransitionTransformer : public LightTransformer {
|
||||
public:
|
||||
void start() override {
|
||||
// When turning light on from off state, use colors from target state.
|
||||
// When turning light on from off state, use target state and only increase brightness from zero.
|
||||
if (!this->start_values_.is_on() && this->target_values_.is_on()) {
|
||||
this->start_values_ = LightColorValues(this->target_values_);
|
||||
this->start_values_.set_brightness(0.0f);
|
||||
}
|
||||
|
||||
// When turning light off from on state, use source state and only decrease brightness to zero.
|
||||
if (this->start_values_.is_on() && !this->target_values_.is_on()) {
|
||||
this->target_values_ = LightColorValues(this->start_values_);
|
||||
this->target_values_.set_brightness(0.0f);
|
||||
}
|
||||
|
||||
// When changing color mode, go through off state, as color modes are orthogonal and there can't be two active.
|
||||
if (this->start_values_.get_color_mode() != this->target_values_.get_color_mode()) {
|
||||
this->changing_color_mode_ = true;
|
||||
|
||||
@@ -281,4 +281,4 @@ async def to_code(config):
|
||||
if CONF_HUMIDITY_SETPOINT in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY_SETPOINT])
|
||||
cg.add(var.set_humidity_setpoint_sensor(sens))
|
||||
cg.add_library("dudanov/MideaUART", "1.1.5")
|
||||
cg.add_library("dudanov/MideaUART", "1.1.8")
|
||||
|
||||
@@ -100,6 +100,7 @@ bool MQTTFanComponent::publish_state() {
|
||||
auto traits = this->state_->get_traits();
|
||||
if (traits.supports_speed()) {
|
||||
const char *payload;
|
||||
// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations)
|
||||
switch (fan::speed_level_to_enum(this->state_->speed, traits.supported_speed_count())) {
|
||||
case FAN_SPEED_LOW: { // NOLINT(clang-diagnostic-deprecated-declarations)
|
||||
payload = "low";
|
||||
|
||||
@@ -31,16 +31,9 @@ void MQTTSensorComponent::dump_config() {
|
||||
std::string MQTTSensorComponent::component_type() const { return "sensor"; }
|
||||
|
||||
uint32_t MQTTSensorComponent::get_expire_after() const {
|
||||
if (this->expire_after_.has_value()) {
|
||||
if (this->expire_after_.has_value())
|
||||
return *this->expire_after_;
|
||||
} else {
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
if (deep_sleep::global_has_deep_sleep) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
return this->sensor_->calculate_expected_filter_update_interval() * 5;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire_after_ = expire_after; }
|
||||
void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; }
|
||||
@@ -61,8 +54,8 @@ void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo
|
||||
if (this->sensor_->get_force_update())
|
||||
root["force_update"] = true;
|
||||
|
||||
if (this->sensor_->state_class != STATE_CLASS_NONE)
|
||||
root["state_class"] = state_class_to_string(this->sensor_->state_class);
|
||||
if (this->sensor_->get_state_class() != STATE_CLASS_NONE)
|
||||
root["state_class"] = state_class_to_string(this->sensor_->get_state_class());
|
||||
|
||||
config.command_topic = false;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ void PN532::setup() {
|
||||
}
|
||||
|
||||
// Set up SAM (secure access module)
|
||||
uint8_t sam_timeout = std::min(255u, this->update_interval_ / 50);
|
||||
uint8_t sam_timeout = std::min<uint8_t>(255u, this->update_interval_ / 50);
|
||||
if (!this->write_command_({
|
||||
PN532_COMMAND_SAMCONFIGURATION,
|
||||
0x01, // normal mode
|
||||
|
||||
@@ -8,7 +8,6 @@ namespace sensor {
|
||||
static const char *const TAG = "sensor.filter";
|
||||
|
||||
// Filter
|
||||
uint32_t Filter::expected_interval(uint32_t input) { return input; }
|
||||
void Filter::input(float value) {
|
||||
ESP_LOGVV(TAG, "Filter(%p)::input(%f)", this, value);
|
||||
optional<float> out = this->new_value(value);
|
||||
@@ -29,15 +28,6 @@ void Filter::initialize(Sensor *parent, Filter *next) {
|
||||
this->parent_ = parent;
|
||||
this->next_ = next;
|
||||
}
|
||||
uint32_t Filter::calculate_remaining_interval(uint32_t input) {
|
||||
uint32_t this_interval = this->expected_interval(input);
|
||||
ESP_LOGVV(TAG, "Filter(%p)::calculate_remaining_interval(%u) -> %u", this, input, this_interval);
|
||||
if (this->next_ == nullptr) {
|
||||
return this_interval;
|
||||
} else {
|
||||
return this->next_->calculate_remaining_interval(this_interval);
|
||||
}
|
||||
}
|
||||
|
||||
// MedianFilter
|
||||
MedianFilter::MedianFilter(size_t window_size, size_t send_every, size_t send_first_at)
|
||||
@@ -75,8 +65,6 @@ optional<float> MedianFilter::new_value(float value) {
|
||||
return {};
|
||||
}
|
||||
|
||||
uint32_t MedianFilter::expected_interval(uint32_t input) { return input * this->send_every_; }
|
||||
|
||||
// MinFilter
|
||||
MinFilter::MinFilter(size_t window_size, size_t send_every, size_t send_first_at)
|
||||
: send_every_(send_every), send_at_(send_every - send_first_at), window_size_(window_size) {}
|
||||
@@ -106,8 +94,6 @@ optional<float> MinFilter::new_value(float value) {
|
||||
return {};
|
||||
}
|
||||
|
||||
uint32_t MinFilter::expected_interval(uint32_t input) { return input * this->send_every_; }
|
||||
|
||||
// MaxFilter
|
||||
MaxFilter::MaxFilter(size_t window_size, size_t send_every, size_t send_first_at)
|
||||
: send_every_(send_every), send_at_(send_every - send_first_at), window_size_(window_size) {}
|
||||
@@ -137,8 +123,6 @@ optional<float> MaxFilter::new_value(float value) {
|
||||
return {};
|
||||
}
|
||||
|
||||
uint32_t MaxFilter::expected_interval(uint32_t input) { return input * this->send_every_; }
|
||||
|
||||
// SlidingWindowMovingAverageFilter
|
||||
SlidingWindowMovingAverageFilter::SlidingWindowMovingAverageFilter(size_t window_size, size_t send_every,
|
||||
size_t send_first_at)
|
||||
@@ -177,8 +161,6 @@ optional<float> SlidingWindowMovingAverageFilter::new_value(float value) {
|
||||
return {};
|
||||
}
|
||||
|
||||
uint32_t SlidingWindowMovingAverageFilter::expected_interval(uint32_t input) { return input * this->send_every_; }
|
||||
|
||||
// ExponentialMovingAverageFilter
|
||||
ExponentialMovingAverageFilter::ExponentialMovingAverageFilter(float alpha, size_t send_every)
|
||||
: send_every_(send_every), send_at_(send_every - 1), alpha_(alpha) {}
|
||||
@@ -203,7 +185,6 @@ optional<float> ExponentialMovingAverageFilter::new_value(float value) {
|
||||
}
|
||||
void ExponentialMovingAverageFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; }
|
||||
void ExponentialMovingAverageFilter::set_alpha(float alpha) { this->alpha_ = alpha; }
|
||||
uint32_t ExponentialMovingAverageFilter::expected_interval(uint32_t input) { return input * this->send_every_; }
|
||||
|
||||
// LambdaFilter
|
||||
LambdaFilter::LambdaFilter(lambda_filter_t lambda_filter) : lambda_filter_(std::move(lambda_filter)) {}
|
||||
@@ -296,14 +277,6 @@ void OrFilter::initialize(Sensor *parent, Filter *next) {
|
||||
this->phi_.initialize(parent, nullptr);
|
||||
}
|
||||
|
||||
uint32_t OrFilter::expected_interval(uint32_t input) {
|
||||
uint32_t min_interval = UINT32_MAX;
|
||||
for (Filter *filter : this->filters_) {
|
||||
min_interval = std::min(min_interval, filter->calculate_remaining_interval(input));
|
||||
}
|
||||
|
||||
return min_interval;
|
||||
}
|
||||
// DebounceFilter
|
||||
optional<float> DebounceFilter::new_value(float value) {
|
||||
this->set_timeout("debounce", this->time_period_, [this, value]() { this->output(value); });
|
||||
@@ -324,7 +297,6 @@ optional<float> HeartbeatFilter::new_value(float value) {
|
||||
|
||||
return {};
|
||||
}
|
||||
uint32_t HeartbeatFilter::expected_interval(uint32_t input) { return this->time_period_; }
|
||||
void HeartbeatFilter::setup() {
|
||||
this->set_interval("heartbeat", this->time_period_, [this]() {
|
||||
ESP_LOGVV(TAG, "HeartbeatFilter(%p)::interval(has_value=%s, last_input=%f)", this, YESNO(this->has_value_),
|
||||
|
||||
@@ -33,11 +33,6 @@ class Filter {
|
||||
|
||||
void input(float value);
|
||||
|
||||
/// Return the amount of time that this filter is expected to take based on the input time interval.
|
||||
virtual uint32_t expected_interval(uint32_t input);
|
||||
|
||||
uint32_t calculate_remaining_interval(uint32_t input);
|
||||
|
||||
void output(float value);
|
||||
|
||||
protected:
|
||||
@@ -68,8 +63,6 @@ class MedianFilter : public Filter {
|
||||
void set_send_every(size_t send_every);
|
||||
void set_window_size(size_t window_size);
|
||||
|
||||
uint32_t expected_interval(uint32_t input) override;
|
||||
|
||||
protected:
|
||||
std::deque<float> queue_;
|
||||
size_t send_every_;
|
||||
@@ -98,8 +91,6 @@ class MinFilter : public Filter {
|
||||
void set_send_every(size_t send_every);
|
||||
void set_window_size(size_t window_size);
|
||||
|
||||
uint32_t expected_interval(uint32_t input) override;
|
||||
|
||||
protected:
|
||||
std::deque<float> queue_;
|
||||
size_t send_every_;
|
||||
@@ -128,8 +119,6 @@ class MaxFilter : public Filter {
|
||||
void set_send_every(size_t send_every);
|
||||
void set_window_size(size_t window_size);
|
||||
|
||||
uint32_t expected_interval(uint32_t input) override;
|
||||
|
||||
protected:
|
||||
std::deque<float> queue_;
|
||||
size_t send_every_;
|
||||
@@ -159,8 +148,6 @@ class SlidingWindowMovingAverageFilter : public Filter {
|
||||
void set_send_every(size_t send_every);
|
||||
void set_window_size(size_t window_size);
|
||||
|
||||
uint32_t expected_interval(uint32_t input) override;
|
||||
|
||||
protected:
|
||||
float sum_{0.0};
|
||||
std::deque<float> queue_;
|
||||
@@ -183,8 +170,6 @@ class ExponentialMovingAverageFilter : public Filter {
|
||||
void set_send_every(size_t send_every);
|
||||
void set_alpha(float alpha);
|
||||
|
||||
uint32_t expected_interval(uint32_t input) override;
|
||||
|
||||
protected:
|
||||
bool first_value_{true};
|
||||
float accumulator_{0.0f};
|
||||
@@ -279,8 +264,6 @@ class HeartbeatFilter : public Filter, public Component {
|
||||
|
||||
optional<float> new_value(float value) override;
|
||||
|
||||
uint32_t expected_interval(uint32_t input) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
@@ -306,8 +289,6 @@ class OrFilter : public Filter {
|
||||
|
||||
void initialize(Sensor *parent, Filter *next) override;
|
||||
|
||||
uint32_t expected_interval(uint32_t input) override;
|
||||
|
||||
optional<float> new_value(float value) override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace sensor {
|
||||
|
||||
static const char *const TAG = "sensor";
|
||||
|
||||
const char *state_class_to_string(StateClass state_class) {
|
||||
std::string state_class_to_string(StateClass state_class) {
|
||||
switch (state_class) {
|
||||
case STATE_CLASS_MEASUREMENT:
|
||||
return "measurement";
|
||||
@@ -18,6 +18,51 @@ const char *state_class_to_string(StateClass state_class) {
|
||||
}
|
||||
}
|
||||
|
||||
Sensor::Sensor(const std::string &name) : Nameable(name), state(NAN), raw_state(NAN) {}
|
||||
Sensor::Sensor() : Sensor("") {}
|
||||
|
||||
std::string Sensor::get_unit_of_measurement() {
|
||||
if (this->unit_of_measurement_.has_value())
|
||||
return *this->unit_of_measurement_;
|
||||
return this->unit_of_measurement();
|
||||
}
|
||||
void Sensor::set_unit_of_measurement(const std::string &unit_of_measurement) {
|
||||
this->unit_of_measurement_ = unit_of_measurement;
|
||||
}
|
||||
std::string Sensor::unit_of_measurement() { return ""; }
|
||||
|
||||
std::string Sensor::get_icon() {
|
||||
if (this->icon_.has_value())
|
||||
return *this->icon_;
|
||||
return this->icon();
|
||||
}
|
||||
void Sensor::set_icon(const std::string &icon) { this->icon_ = icon; }
|
||||
std::string Sensor::icon() { return ""; }
|
||||
|
||||
int8_t Sensor::get_accuracy_decimals() {
|
||||
if (this->accuracy_decimals_.has_value())
|
||||
return *this->accuracy_decimals_;
|
||||
return this->accuracy_decimals();
|
||||
}
|
||||
void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; }
|
||||
int8_t Sensor::accuracy_decimals() { return 0; }
|
||||
|
||||
std::string Sensor::get_device_class() {
|
||||
if (this->device_class_.has_value())
|
||||
return *this->device_class_;
|
||||
return this->device_class();
|
||||
}
|
||||
void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
|
||||
std::string Sensor::device_class() { return ""; }
|
||||
|
||||
void Sensor::set_state_class(StateClass state_class) { this->state_class_ = state_class; }
|
||||
StateClass Sensor::get_state_class() {
|
||||
if (this->state_class_.has_value())
|
||||
return *this->state_class_;
|
||||
return this->state_class();
|
||||
}
|
||||
StateClass Sensor::state_class() { return StateClass::STATE_CLASS_NONE; }
|
||||
|
||||
void Sensor::publish_state(float state) {
|
||||
this->raw_state = state;
|
||||
this->raw_callback_.call(state);
|
||||
@@ -30,54 +75,12 @@ void Sensor::publish_state(float state) {
|
||||
this->filter_list_->input(state);
|
||||
}
|
||||
}
|
||||
std::string Sensor::unit_of_measurement() { return ""; }
|
||||
std::string Sensor::icon() { return ""; }
|
||||
uint32_t Sensor::update_interval() { return 0; }
|
||||
int8_t Sensor::accuracy_decimals() { return 0; }
|
||||
Sensor::Sensor(const std::string &name) : Nameable(name), state(NAN), raw_state(NAN) {}
|
||||
Sensor::Sensor() : Sensor("") {}
|
||||
|
||||
void Sensor::set_unit_of_measurement(const std::string &unit_of_measurement) {
|
||||
this->unit_of_measurement_ = unit_of_measurement;
|
||||
}
|
||||
void Sensor::set_icon(const std::string &icon) { this->icon_ = icon; }
|
||||
void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; }
|
||||
void Sensor::add_on_state_callback(std::function<void(float)> &&callback) { this->callback_.add(std::move(callback)); }
|
||||
void Sensor::add_on_raw_state_callback(std::function<void(float)> &&callback) {
|
||||
this->raw_callback_.add(std::move(callback));
|
||||
}
|
||||
std::string Sensor::get_icon() {
|
||||
if (this->icon_.has_value())
|
||||
return *this->icon_;
|
||||
return this->icon();
|
||||
}
|
||||
void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
|
||||
std::string Sensor::get_device_class() {
|
||||
if (this->device_class_.has_value())
|
||||
return *this->device_class_;
|
||||
return this->device_class();
|
||||
}
|
||||
std::string Sensor::device_class() { return ""; }
|
||||
void Sensor::set_state_class(StateClass state_class) { this->state_class = state_class; }
|
||||
void Sensor::set_state_class(const std::string &state_class) {
|
||||
if (str_equals_case_insensitive(state_class, "measurement")) {
|
||||
this->state_class = STATE_CLASS_MEASUREMENT;
|
||||
} else if (str_equals_case_insensitive(state_class, "total_increasing")) {
|
||||
this->state_class = STATE_CLASS_TOTAL_INCREASING;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str());
|
||||
}
|
||||
}
|
||||
std::string Sensor::get_unit_of_measurement() {
|
||||
if (this->unit_of_measurement_.has_value())
|
||||
return *this->unit_of_measurement_;
|
||||
return this->unit_of_measurement();
|
||||
}
|
||||
int8_t Sensor::get_accuracy_decimals() {
|
||||
if (this->accuracy_decimals_.has_value())
|
||||
return *this->accuracy_decimals_;
|
||||
return this->accuracy_decimals();
|
||||
}
|
||||
|
||||
void Sensor::add_filter(Filter *filter) {
|
||||
// inefficient, but only happens once on every sensor setup and nobody's going to have massive amounts of
|
||||
// filters
|
||||
@@ -119,24 +122,7 @@ void Sensor::internal_send_state_to_frontend(float state) {
|
||||
this->callback_.call(state);
|
||||
}
|
||||
bool Sensor::has_state() const { return this->has_state_; }
|
||||
uint32_t Sensor::calculate_expected_filter_update_interval() {
|
||||
uint32_t interval = this->update_interval();
|
||||
if (interval == 4294967295UL)
|
||||
// update_interval: never
|
||||
return 0;
|
||||
|
||||
if (this->filter_list_ == nullptr) {
|
||||
return interval;
|
||||
}
|
||||
|
||||
return this->filter_list_->calculate_remaining_interval(interval);
|
||||
}
|
||||
uint32_t Sensor::hash_base() { return 2455723294UL; }
|
||||
|
||||
PollingSensorComponent::PollingSensorComponent(const std::string &name, uint32_t update_interval)
|
||||
: PollingComponent(update_interval), Sensor(name) {}
|
||||
|
||||
uint32_t PollingSensorComponent::update_interval() { return this->get_update_interval(); }
|
||||
|
||||
} // namespace sensor
|
||||
} // namespace esphome
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/sensor/filter.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/sensor/filter.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sensor {
|
||||
@@ -13,7 +13,7 @@ namespace sensor {
|
||||
if (!(obj)->get_device_class().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
||||
} \
|
||||
ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->state_class)); \
|
||||
ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->get_state_class()).c_str()); \
|
||||
ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \
|
||||
ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \
|
||||
if (!(obj)->get_icon().empty()) { \
|
||||
@@ -36,7 +36,7 @@ enum StateClass : uint8_t {
|
||||
STATE_CLASS_TOTAL_INCREASING = 2,
|
||||
};
|
||||
|
||||
const char *state_class_to_string(StateClass state_class);
|
||||
std::string state_class_to_string(StateClass state_class);
|
||||
|
||||
/** Base-class for all sensors.
|
||||
*
|
||||
@@ -47,26 +47,42 @@ class Sensor : public Nameable {
|
||||
explicit Sensor();
|
||||
explicit Sensor(const std::string &name);
|
||||
|
||||
/** Manually set the unit of measurement of this sensor. By default the sensor's default defined by
|
||||
* unit_of_measurement() is used.
|
||||
*
|
||||
* @param unit_of_measurement The unit of measurement, "" to disable.
|
||||
*/
|
||||
/// Get the unit of measurement, using the manual override if set.
|
||||
std::string get_unit_of_measurement();
|
||||
/// Manually set the unit of measurement.
|
||||
void set_unit_of_measurement(const std::string &unit_of_measurement);
|
||||
|
||||
/** Manually set the icon of this sensor. By default the sensor's default defined by icon() is used.
|
||||
*
|
||||
* @param icon The icon, for example "mdi:flash". "" to disable.
|
||||
*/
|
||||
/// Get the icon. Uses the manual override if specified or the default value instead.
|
||||
std::string get_icon();
|
||||
/// Manually set the icon, for example "mdi:flash".
|
||||
void set_icon(const std::string &icon);
|
||||
|
||||
/** Manually set the accuracy in decimals for this sensor. By default, the sensor's default defined by
|
||||
* accuracy_decimals() is used.
|
||||
*
|
||||
* @param accuracy_decimals The accuracy decimal that should be used.
|
||||
*/
|
||||
/// Get the accuracy in decimals, using the manual override if set.
|
||||
int8_t get_accuracy_decimals();
|
||||
/// Manually set the accuracy in decimals.
|
||||
void set_accuracy_decimals(int8_t accuracy_decimals);
|
||||
|
||||
/// Get the device class, using the manual override if set.
|
||||
std::string get_device_class();
|
||||
/// Manually set the device class.
|
||||
void set_device_class(const std::string &device_class);
|
||||
|
||||
/// Get the state class, using the manual override if set.
|
||||
StateClass get_state_class();
|
||||
/// Manually set the state class.
|
||||
void set_state_class(StateClass state_class);
|
||||
|
||||
/**
|
||||
* Get whether force update mode is enabled.
|
||||
*
|
||||
* If the sensor is in force_update mode, the frontend is required to save all
|
||||
* state changes to the database when they are published, even if the state is the
|
||||
* same as before.
|
||||
*/
|
||||
bool get_force_update() const { return force_update_; }
|
||||
/// Set force update mode.
|
||||
void set_force_update(bool force_update) { force_update_ = force_update; }
|
||||
|
||||
/// Add a filter to the filter chain. Will be appended to the back.
|
||||
void add_filter(Filter *filter);
|
||||
|
||||
@@ -93,15 +109,6 @@ class Sensor : public Nameable {
|
||||
/// Getter-syntax for .raw_state
|
||||
float get_raw_state() const;
|
||||
|
||||
/// Get the accuracy in decimals. Uses the manual override if specified or the default value instead.
|
||||
int8_t get_accuracy_decimals();
|
||||
|
||||
/// Get the unit of measurement. Uses the manual override if specified or the default value instead.
|
||||
std::string get_unit_of_measurement();
|
||||
|
||||
/// Get the Home Assistant Icon. Uses the manual override if specified or the default value instead.
|
||||
std::string get_icon();
|
||||
|
||||
/** Publish a new state to the front-end.
|
||||
*
|
||||
* First, the new state will be assigned to the raw_value. Then it's passed through all filters
|
||||
@@ -127,35 +134,15 @@ class Sensor : public Nameable {
|
||||
*/
|
||||
float state;
|
||||
|
||||
/// Manually set the Home Assistant device class (see sensor::device_class)
|
||||
void set_device_class(const std::string &device_class);
|
||||
|
||||
/// Get the device class for this sensor, using the manual override if specified.
|
||||
std::string get_device_class();
|
||||
|
||||
/** This member variable stores the current raw state of the sensor. Unlike .state,
|
||||
* this will be updated immediately when publish_state is called.
|
||||
/** This member variable stores the current raw state of the sensor, without any filters applied.
|
||||
*
|
||||
* Unlike .state,this will be updated immediately when publish_state is called.
|
||||
*/
|
||||
float raw_state;
|
||||
|
||||
/// Return whether this sensor has gotten a full state (that passed through all filters) yet.
|
||||
bool has_state() const;
|
||||
|
||||
// The state class of this sensor state
|
||||
StateClass state_class{STATE_CLASS_NONE};
|
||||
|
||||
/// Manually set the Home Assistant state class (see sensor::state_class)
|
||||
void set_state_class(StateClass state_class);
|
||||
void set_state_class(const std::string &state_class);
|
||||
|
||||
/** Override this to set the Home Assistant device class for this sensor.
|
||||
*
|
||||
* Return "" to disable this feature.
|
||||
*
|
||||
* @return The device class of this sensor, for example "temperature".
|
||||
*/
|
||||
virtual std::string device_class();
|
||||
|
||||
/** A unique ID for this sensor, empty for no unique id. See unique ID requirements:
|
||||
* https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements
|
||||
*
|
||||
@@ -163,65 +150,38 @@ class Sensor : public Nameable {
|
||||
*/
|
||||
virtual std::string unique_id();
|
||||
|
||||
/// Return with which interval the sensor is polled. Return 0 for non-polling mode.
|
||||
virtual uint32_t update_interval();
|
||||
|
||||
/// Calculate the expected update interval for values that pass through all filters.
|
||||
uint32_t calculate_expected_filter_update_interval();
|
||||
|
||||
void internal_send_state_to_frontend(float state);
|
||||
|
||||
bool get_force_update() const { return force_update_; }
|
||||
/** Set this sensor's force_update mode.
|
||||
*
|
||||
* If the sensor is in force_update mode, the frontend is required to save all
|
||||
* state changes to the database when they are published, even if the state is the
|
||||
* same as before.
|
||||
*/
|
||||
void set_force_update(bool force_update) { force_update_ = force_update; }
|
||||
|
||||
protected:
|
||||
/** Override this to set the Home Assistant unit of measurement for this sensor.
|
||||
*
|
||||
* Return "" to disable this feature.
|
||||
*
|
||||
* @return The icon of this sensor, for example "°C".
|
||||
*/
|
||||
/// Override this to set the default unit of measurement.
|
||||
virtual std::string unit_of_measurement(); // NOLINT
|
||||
|
||||
/** Override this to set the Home Assistant icon for this sensor.
|
||||
*
|
||||
* Return "" to disable this feature.
|
||||
*
|
||||
* @return The icon of this sensor, for example "mdi:battery".
|
||||
*/
|
||||
/// Override this to set the default icon.
|
||||
virtual std::string icon(); // NOLINT
|
||||
|
||||
/// Return the accuracy in decimals for this sensor.
|
||||
/// Override this to set the default accuracy in decimals.
|
||||
virtual int8_t accuracy_decimals(); // NOLINT
|
||||
|
||||
optional<std::string> device_class_{}; ///< Stores the override of the device class
|
||||
/// Override this to set the default device class.
|
||||
virtual std::string device_class(); // NOLINT
|
||||
|
||||
/// Override this to set the default state class.
|
||||
virtual StateClass state_class(); // NOLINT
|
||||
|
||||
uint32_t hash_base() override;
|
||||
|
||||
CallbackManager<void(float)> raw_callback_; ///< Storage for raw state callbacks.
|
||||
CallbackManager<void(float)> callback_; ///< Storage for filtered state callbacks.
|
||||
/// Override the unit of measurement
|
||||
optional<std::string> unit_of_measurement_;
|
||||
/// Override the icon advertised to Home Assistant, otherwise sensor's icon will be used.
|
||||
optional<std::string> icon_;
|
||||
/// Override the accuracy in decimals, otherwise the sensor's values will be used.
|
||||
optional<int8_t> accuracy_decimals_;
|
||||
Filter *filter_list_{nullptr}; ///< Store all active filters.
|
||||
|
||||
bool has_state_{false};
|
||||
bool force_update_{false};
|
||||
};
|
||||
Filter *filter_list_{nullptr}; ///< Store all active filters.
|
||||
|
||||
class PollingSensorComponent : public PollingComponent, public Sensor {
|
||||
public:
|
||||
explicit PollingSensorComponent(const std::string &name, uint32_t update_interval);
|
||||
|
||||
uint32_t update_interval() override;
|
||||
optional<std::string> unit_of_measurement_; ///< Unit of measurement override
|
||||
optional<std::string> icon_; ///< Icon override
|
||||
optional<int8_t> accuracy_decimals_; ///< Accuracy in decimals override
|
||||
optional<std::string> device_class_; ///< Device class override
|
||||
optional<StateClass> state_class_{STATE_CLASS_NONE}; ///< State class override
|
||||
bool force_update_{false}; ///< Force update mode
|
||||
};
|
||||
|
||||
} // namespace sensor
|
||||
|
||||
@@ -27,7 +27,8 @@ void SM300D2Sensor::update() {
|
||||
}
|
||||
|
||||
uint16_t calculated_checksum = this->sm300d2_checksum_(response);
|
||||
if (calculated_checksum != response[SM300D2_RESPONSE_LENGTH - 1]) {
|
||||
if ((calculated_checksum != response[SM300D2_RESPONSE_LENGTH - 1]) &&
|
||||
(calculated_checksum - 0x80 != response[SM300D2_RESPONSE_LENGTH - 1])) {
|
||||
ESP_LOGW(TAG, "SM300D2 Checksum doesn't match: 0x%02X!=0x%02X", response[SM300D2_RESPONSE_LENGTH - 1],
|
||||
calculated_checksum);
|
||||
this->status_set_warning();
|
||||
|
||||
@@ -16,7 +16,7 @@ CONFIG_SCHEMA = time_.TIME_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SNTPComponent),
|
||||
cv.Optional(CONF_SERVERS, default=DEFAULT_SERVERS): cv.All(
|
||||
cv.ensure_list(cv.domain), cv.Length(min=1, max=3)
|
||||
cv.ensure_list(cv.Any(cv.domain, cv.hostname)), cv.Length(min=1, max=3)
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <esp_idf_version.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace socket {
|
||||
|
||||
@@ -75,7 +79,57 @@ class BSDSocketImpl : public Socket {
|
||||
}
|
||||
int listen(int backlog) override { return ::listen(fd_, backlog); }
|
||||
ssize_t read(void *buf, size_t len) override { return ::read(fd_, buf, len); }
|
||||
ssize_t readv(const struct iovec *iov, int iovcnt) override {
|
||||
#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR < 4
|
||||
// esp-idf v3 doesn't have readv, emulate it
|
||||
ssize_t ret = 0;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
ssize_t err = this->read(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len);
|
||||
if (err == -1) {
|
||||
if (ret != 0)
|
||||
// if we already read some don't return an error
|
||||
break;
|
||||
return err;
|
||||
}
|
||||
ret += err;
|
||||
if (err != iov[i].iov_len)
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
// ESP-IDF v4 only has symbol lwip_readv
|
||||
return ::lwip_readv(fd_, iov, iovcnt);
|
||||
#else
|
||||
return ::readv(fd_, iov, iovcnt);
|
||||
#endif
|
||||
}
|
||||
ssize_t write(const void *buf, size_t len) override { return ::write(fd_, buf, len); }
|
||||
ssize_t send(void *buf, size_t len, int flags) { return ::send(fd_, buf, len, flags); }
|
||||
ssize_t writev(const struct iovec *iov, int iovcnt) override {
|
||||
#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR < 4
|
||||
// esp-idf v3 doesn't have writev, emulate it
|
||||
ssize_t ret = 0;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
ssize_t err =
|
||||
this->send(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len, i == iovcnt - 1 ? 0 : MSG_MORE);
|
||||
if (err == -1) {
|
||||
if (ret != 0)
|
||||
// if we already wrote some don't return an error
|
||||
break;
|
||||
return err;
|
||||
}
|
||||
ret += err;
|
||||
if (err != iov[i].iov_len)
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
// ESP-IDF v4 only has symbol lwip_writev
|
||||
return ::lwip_writev(fd_, iov, iovcnt);
|
||||
#else
|
||||
return ::writev(fd_, iov, iovcnt);
|
||||
#endif
|
||||
}
|
||||
int setblocking(bool blocking) override {
|
||||
int fl = ::fcntl(fd_, F_GETFL, 0);
|
||||
if (blocking) {
|
||||
|
||||
@@ -81,6 +81,11 @@ struct sockaddr_storage {
|
||||
};
|
||||
typedef uint32_t socklen_t;
|
||||
|
||||
struct iovec {
|
||||
void *iov_base;
|
||||
size_t iov_len;
|
||||
};
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
// arduino-esp8266 declares a global vars called INADDR_NONE/ANY which are invalid with the define
|
||||
#ifdef INADDR_ANY
|
||||
@@ -104,6 +109,7 @@ typedef uint32_t socklen_t;
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/uio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -109,14 +109,17 @@ class LWIPRawImpl : public Socket {
|
||||
LWIP_LOG("tcp_bind(%p ip=%u port=%u)", pcb_, ip.addr, port);
|
||||
err_t err = tcp_bind(pcb_, &ip, port);
|
||||
if (err == ERR_USE) {
|
||||
LWIP_LOG(" -> err ERR_USE");
|
||||
errno = EADDRINUSE;
|
||||
return -1;
|
||||
}
|
||||
if (err == ERR_VAL) {
|
||||
LWIP_LOG(" -> err ERR_VAL");
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (err != ERR_OK) {
|
||||
LWIP_LOG(" -> err %d", err);
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
@@ -124,12 +127,13 @@ class LWIPRawImpl : public Socket {
|
||||
}
|
||||
int close() override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
LWIP_LOG("tcp_close(%p)", pcb_);
|
||||
err_t err = tcp_close(pcb_);
|
||||
if (err != ERR_OK) {
|
||||
LWIP_LOG(" -> err %d", err);
|
||||
tcp_abort(pcb_);
|
||||
pcb_ = nullptr;
|
||||
errno = err == ERR_MEM ? ENOMEM : EIO;
|
||||
@@ -140,7 +144,7 @@ class LWIPRawImpl : public Socket {
|
||||
}
|
||||
int shutdown(int how) override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
bool shut_rx = false, shut_tx = false;
|
||||
@@ -157,6 +161,7 @@ class LWIPRawImpl : public Socket {
|
||||
LWIP_LOG("tcp_shutdown(%p shut_rx=%d shut_tx=%d)", pcb_, shut_rx ? 1 : 0, shut_tx ? 1 : 0);
|
||||
err_t err = tcp_shutdown(pcb_, shut_rx, shut_tx);
|
||||
if (err != ERR_OK) {
|
||||
LWIP_LOG(" -> err %d", err);
|
||||
errno = err == ERR_MEM ? ENOMEM : EIO;
|
||||
return -1;
|
||||
}
|
||||
@@ -165,7 +170,7 @@ class LWIPRawImpl : public Socket {
|
||||
|
||||
int getpeername(struct sockaddr *name, socklen_t *addrlen) override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
if (name == nullptr || addrlen == nullptr) {
|
||||
@@ -185,7 +190,7 @@ class LWIPRawImpl : public Socket {
|
||||
}
|
||||
std::string getpeername() override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
errno = ECONNRESET;
|
||||
return "";
|
||||
}
|
||||
char buffer[24];
|
||||
@@ -196,7 +201,7 @@ class LWIPRawImpl : public Socket {
|
||||
}
|
||||
int getsockname(struct sockaddr *name, socklen_t *addrlen) override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
if (name == nullptr || addrlen == nullptr) {
|
||||
@@ -216,7 +221,7 @@ class LWIPRawImpl : public Socket {
|
||||
}
|
||||
std::string getsockname() override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
errno = ECONNRESET;
|
||||
return "";
|
||||
}
|
||||
char buffer[24];
|
||||
@@ -227,7 +232,7 @@ class LWIPRawImpl : public Socket {
|
||||
}
|
||||
int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
if (optlen == nullptr || optval == nullptr) {
|
||||
@@ -251,7 +256,7 @@ class LWIPRawImpl : public Socket {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
*reinterpret_cast<int *>(optval) = tcp_nagle_disabled(pcb_);
|
||||
*reinterpret_cast<int *>(optval) = nodelay_;
|
||||
*optlen = 4;
|
||||
return 0;
|
||||
}
|
||||
@@ -261,7 +266,7 @@ class LWIPRawImpl : public Socket {
|
||||
}
|
||||
int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
if (level == SOL_SOCKET && optname == SO_REUSEADDR) {
|
||||
@@ -280,11 +285,7 @@ class LWIPRawImpl : public Socket {
|
||||
return -1;
|
||||
}
|
||||
int val = *reinterpret_cast<const int *>(optval);
|
||||
if (val != 0) {
|
||||
tcp_nagle_disable(pcb_);
|
||||
} else {
|
||||
tcp_nagle_enable(pcb_);
|
||||
}
|
||||
nodelay_ = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -314,7 +315,7 @@ class LWIPRawImpl : public Socket {
|
||||
}
|
||||
ssize_t read(void *buf, size_t len) override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
if (rx_closed_ && rx_buf_ == nullptr) {
|
||||
@@ -366,9 +367,25 @@ class LWIPRawImpl : public Socket {
|
||||
|
||||
return read;
|
||||
}
|
||||
ssize_t write(const void *buf, size_t len) override {
|
||||
ssize_t readv(const struct iovec *iov, int iovcnt) override {
|
||||
ssize_t ret = 0;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
ssize_t err = read(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len);
|
||||
if (err == -1) {
|
||||
if (ret != 0)
|
||||
// if we already read some don't return an error
|
||||
break;
|
||||
return err;
|
||||
}
|
||||
ret += err;
|
||||
if (err != iov[i].iov_len)
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
ssize_t internal_write(const void *buf, size_t len) {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
if (len == 0)
|
||||
@@ -386,24 +403,76 @@ class LWIPRawImpl : public Socket {
|
||||
LWIP_LOG("tcp_write(%p buf=%p %u)", pcb_, buf, to_send);
|
||||
err_t err = tcp_write(pcb_, buf, to_send, TCP_WRITE_FLAG_COPY);
|
||||
if (err == ERR_MEM) {
|
||||
LWIP_LOG(" -> err ERR_MEM");
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
if (err != ERR_OK) {
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
LWIP_LOG("tcp_output(%p)", pcb_);
|
||||
err = tcp_output(pcb_);
|
||||
if (err != ERR_OK) {
|
||||
errno = EIO;
|
||||
LWIP_LOG(" -> err %d", err);
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
return to_send;
|
||||
}
|
||||
int internal_output() {
|
||||
LWIP_LOG("tcp_output(%p)", pcb_);
|
||||
err_t err = tcp_output(pcb_);
|
||||
if (err == ERR_ABRT) {
|
||||
LWIP_LOG(" -> err ERR_ABRT");
|
||||
// sometimes lwip returns ERR_ABRT for no apparent reason
|
||||
// the connection works fine afterwards, and back with ESPAsyncTCP we
|
||||
// indirectly also ignored this error
|
||||
// FIXME: figure out where this is returned and what it means in this context
|
||||
return 0;
|
||||
}
|
||||
if (err != ERR_OK) {
|
||||
LWIP_LOG(" -> err %d", err);
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
ssize_t write(const void *buf, size_t len) override {
|
||||
ssize_t written = internal_write(buf, len);
|
||||
if (written == -1)
|
||||
return -1;
|
||||
if (written == 0)
|
||||
// no need to output if nothing written
|
||||
return 0;
|
||||
if (nodelay_) {
|
||||
int err = internal_output();
|
||||
if (err == -1)
|
||||
return -1;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
ssize_t writev(const struct iovec *iov, int iovcnt) override {
|
||||
ssize_t written = 0;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
ssize_t err = internal_write(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len);
|
||||
if (err == -1) {
|
||||
if (written != 0)
|
||||
// if we already read some don't return an error
|
||||
break;
|
||||
return err;
|
||||
}
|
||||
written += err;
|
||||
if (err != iov[i].iov_len)
|
||||
break;
|
||||
}
|
||||
if (written == 0)
|
||||
// no need to output if nothing written
|
||||
return 0;
|
||||
if (nodelay_) {
|
||||
int err = internal_output();
|
||||
if (err == -1)
|
||||
return -1;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
int setblocking(bool blocking) override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
if (blocking) {
|
||||
@@ -466,7 +535,7 @@ class LWIPRawImpl : public Socket {
|
||||
|
||||
static void s_err_fn(void *arg, err_t err) {
|
||||
LWIPRawImpl *arg_this = reinterpret_cast<LWIPRawImpl *>(arg);
|
||||
return arg_this->err_fn(err);
|
||||
arg_this->err_fn(err);
|
||||
}
|
||||
|
||||
static err_t s_recv_fn(void *arg, struct tcp_pcb *pcb, struct pbuf *pb, err_t err) {
|
||||
@@ -480,6 +549,9 @@ class LWIPRawImpl : public Socket {
|
||||
bool rx_closed_ = false;
|
||||
pbuf *rx_buf_ = nullptr;
|
||||
size_t rx_buf_offset_ = 0;
|
||||
// don't use lwip nodelay flag, it sometimes causes reconnect
|
||||
// instead use it for determining whether to call lwip_output
|
||||
bool nodelay_ = false;
|
||||
};
|
||||
|
||||
std::unique_ptr<Socket> socket(int domain, int type, int protocol) {
|
||||
|
||||
@@ -31,7 +31,9 @@ class Socket {
|
||||
virtual int setsockopt(int level, int optname, const void *optval, socklen_t optlen) = 0;
|
||||
virtual int listen(int backlog) = 0;
|
||||
virtual ssize_t read(void *buf, size_t len) = 0;
|
||||
virtual ssize_t readv(const struct iovec *iov, int iovcnt) = 0;
|
||||
virtual ssize_t write(const void *buf, size_t len) = 0;
|
||||
virtual ssize_t writev(const struct iovec *iov, int iovcnt) = 0;
|
||||
virtual int setblocking(bool blocking) = 0;
|
||||
virtual int loop() { return 0; };
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace t6615 {
|
||||
|
||||
static const char *const TAG = "t6615";
|
||||
|
||||
static const uint8_t T6615_RESPONSE_BUFFER_LENGTH = 32;
|
||||
static const uint32_t T6615_TIMEOUT = 1000;
|
||||
static const uint8_t T6615_MAGIC = 0xFF;
|
||||
static const uint8_t T6615_ADDR_HOST = 0xFA;
|
||||
static const uint8_t T6615_ADDR_SENSOR = 0xFE;
|
||||
@@ -19,31 +19,49 @@ static const uint8_t T6615_COMMAND_ENABLE_ABC[] = {0xB7, 0x01};
|
||||
static const uint8_t T6615_COMMAND_DISABLE_ABC[] = {0xB7, 0x02};
|
||||
static const uint8_t T6615_COMMAND_SET_ELEVATION[] = {0x03, 0x0F};
|
||||
|
||||
void T6615Component::loop() {
|
||||
if (!this->available())
|
||||
return;
|
||||
void T6615Component::send_ppm_command_() {
|
||||
this->command_time_ = millis();
|
||||
this->command_ = T6615Command::GET_PPM;
|
||||
this->write_byte(T6615_MAGIC);
|
||||
this->write_byte(T6615_ADDR_SENSOR);
|
||||
this->write_byte(sizeof(T6615_COMMAND_GET_PPM));
|
||||
this->write_array(T6615_COMMAND_GET_PPM, sizeof(T6615_COMMAND_GET_PPM));
|
||||
}
|
||||
|
||||
// Read header
|
||||
uint8_t header[3];
|
||||
this->read_array(header, 3);
|
||||
if (header[0] != T6615_MAGIC || header[1] != T6615_ADDR_HOST) {
|
||||
ESP_LOGW(TAG, "Reading data from T6615 failed!");
|
||||
while (this->available())
|
||||
this->read(); // Clear the incoming buffer
|
||||
this->status_set_warning();
|
||||
void T6615Component::loop() {
|
||||
if (this->available() < 5) {
|
||||
if (this->command_ == T6615Command::GET_PPM && millis() - this->command_time_ > T6615_TIMEOUT) {
|
||||
/* command got eaten, clear the buffer and fire another */
|
||||
while (this->available())
|
||||
this->read();
|
||||
this->send_ppm_command_();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Read body
|
||||
uint8_t length = header[2];
|
||||
uint8_t response[T6615_RESPONSE_BUFFER_LENGTH];
|
||||
this->read_array(response, length);
|
||||
uint8_t response_buffer[6];
|
||||
|
||||
/* by the time we get here, we know we have at least five bytes in the buffer */
|
||||
this->read_array(response_buffer, 5);
|
||||
|
||||
// Read header
|
||||
if (response_buffer[0] != T6615_MAGIC || response_buffer[1] != T6615_ADDR_HOST) {
|
||||
ESP_LOGW(TAG, "Got bad data from T6615! Magic was %02X and address was %02X", response_buffer[0],
|
||||
response_buffer[1]);
|
||||
/* make sure the buffer is empty */
|
||||
while (this->available())
|
||||
this->read();
|
||||
/* try again to read the sensor */
|
||||
this->send_ppm_command_();
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
|
||||
switch (this->command_) {
|
||||
case T6615Command::GET_PPM: {
|
||||
const uint16_t ppm = encode_uint16(response[0], response[1]);
|
||||
const uint16_t ppm = encode_uint16(response_buffer[3], response_buffer[4]);
|
||||
ESP_LOGD(TAG, "T6615 Received CO₂=%uppm", ppm);
|
||||
this->co2_sensor_->publish_state(ppm);
|
||||
break;
|
||||
@@ -51,23 +69,19 @@ void T6615Component::loop() {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this->command_time_ = 0;
|
||||
this->command_ = T6615Command::NONE;
|
||||
}
|
||||
|
||||
void T6615Component::update() { this->query_ppm_(); }
|
||||
|
||||
void T6615Component::query_ppm_() {
|
||||
if (this->co2_sensor_ == nullptr || this->command_ != T6615Command::NONE) {
|
||||
if (this->co2_sensor_ == nullptr ||
|
||||
(this->command_ != T6615Command::NONE && millis() - this->command_time_ < T6615_TIMEOUT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->command_ = T6615Command::GET_PPM;
|
||||
|
||||
this->write_byte(T6615_MAGIC);
|
||||
this->write_byte(T6615_ADDR_SENSOR);
|
||||
this->write_byte(sizeof(T6615_COMMAND_GET_PPM));
|
||||
this->write_array(T6615_COMMAND_GET_PPM, sizeof(T6615_COMMAND_GET_PPM));
|
||||
this->send_ppm_command_();
|
||||
}
|
||||
|
||||
float T6615Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
@@ -32,8 +32,10 @@ class T6615Component : public PollingComponent, public uart::UARTDevice {
|
||||
|
||||
protected:
|
||||
void query_ppm_();
|
||||
void send_ppm_command_();
|
||||
|
||||
T6615Command command_ = T6615Command::NONE;
|
||||
unsigned long command_time_ = 0;
|
||||
|
||||
sensor::Sensor *co2_sensor_{nullptr};
|
||||
};
|
||||
|
||||
@@ -73,7 +73,11 @@ void UARTComponent::setup() {
|
||||
// Use Arduino HardwareSerial UARTs if all used pins match the ones
|
||||
// preconfigured by the platform. For example if RX disabled but TX pin
|
||||
// is 1 we still want to use Serial.
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
||||
if (this->tx_pin_.value_or(21) == 21 && this->rx_pin_.value_or(20) == 20) {
|
||||
#else
|
||||
if (this->tx_pin_.value_or(1) == 1 && this->rx_pin_.value_or(3) == 3) {
|
||||
#endif
|
||||
this->hw_serial_ = &Serial;
|
||||
} else {
|
||||
this->hw_serial_ = new HardwareSerial(next_uart_num++);
|
||||
|
||||
@@ -34,8 +34,8 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_JS_INCLUDE): cv.file_,
|
||||
cv.Optional(CONF_AUTH): cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_USERNAME): cv.string_strict,
|
||||
cv.Required(CONF_PASSWORD): cv.string_strict,
|
||||
cv.Required(CONF_USERNAME): cv.All(cv.string_strict, cv.Length(min=1)),
|
||||
cv.Required(CONF_PASSWORD): cv.All(cv.string_strict, cv.Length(min=1)),
|
||||
}
|
||||
),
|
||||
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
|
||||
@@ -57,8 +57,8 @@ async def to_code(config):
|
||||
cg.add(var.set_css_url(config[CONF_CSS_URL]))
|
||||
cg.add(var.set_js_url(config[CONF_JS_URL]))
|
||||
if CONF_AUTH in config:
|
||||
cg.add(var.set_username(config[CONF_AUTH][CONF_USERNAME]))
|
||||
cg.add(var.set_password(config[CONF_AUTH][CONF_PASSWORD]))
|
||||
cg.add(paren.set_auth_username(config[CONF_AUTH][CONF_USERNAME]))
|
||||
cg.add(paren.set_auth_password(config[CONF_AUTH][CONF_PASSWORD]))
|
||||
if CONF_CSS_INCLUDE in config:
|
||||
cg.add_define("WEBSERVER_CSS_INCLUDE")
|
||||
path = CORE.relative_config_path(config[CONF_CSS_INCLUDE])
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "web_server.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/components/json/json_util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
|
||||
#include "StreamString.h"
|
||||
|
||||
@@ -151,9 +151,6 @@ void WebServer::setup() {
|
||||
void WebServer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Web Server:");
|
||||
ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->base_->get_port());
|
||||
if (this->using_auth()) {
|
||||
ESP_LOGCONFIG(TAG, " Basic authentication enabled");
|
||||
}
|
||||
}
|
||||
float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; }
|
||||
|
||||
@@ -397,6 +394,7 @@ std::string WebServer::fan_json(fan::FanState *obj) {
|
||||
const auto traits = obj->get_traits();
|
||||
if (traits.supports_speed()) {
|
||||
root["speed_level"] = obj->speed;
|
||||
// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations)
|
||||
switch (fan::speed_level_to_enum(obj->speed, traits.supported_speed_count())) {
|
||||
case fan::FAN_SPEED_LOW: // NOLINT(clang-diagnostic-deprecated-declarations)
|
||||
root["speed"] = "low";
|
||||
@@ -727,10 +725,6 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
|
||||
return false;
|
||||
}
|
||||
void WebServer::handleRequest(AsyncWebServerRequest *request) {
|
||||
if (this->using_auth() && !request->authenticate(this->username_, this->password_)) {
|
||||
return request->requestAuthentication();
|
||||
}
|
||||
|
||||
if (request->url() == "/") {
|
||||
this->handle_index_request(request);
|
||||
return;
|
||||
|
||||
@@ -30,10 +30,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
public:
|
||||
WebServer(web_server_base::WebServerBase *base) : base_(base) {}
|
||||
|
||||
void set_username(const char *username) { username_ = username; }
|
||||
|
||||
void set_password(const char *password) { password_ = password; }
|
||||
|
||||
/** Set the URL to the CSS <link> that's sent to each client. Defaults to
|
||||
* https://esphome.io/_static/webserver-v1.min.css
|
||||
*
|
||||
@@ -83,8 +79,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
void handle_js_request(AsyncWebServerRequest *request);
|
||||
#endif
|
||||
|
||||
bool using_auth() { return username_ != nullptr && password_ != nullptr; }
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void on_sensor_update(sensor::Sensor *obj, float state) override;
|
||||
/// Handle a sensor request under '/sensor/<id>'.
|
||||
@@ -182,8 +176,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
protected:
|
||||
web_server_base::WebServerBase *base_;
|
||||
AsyncEventSource events_{"/events"};
|
||||
const char *username_{nullptr};
|
||||
const char *password_{nullptr};
|
||||
const char *css_url_{nullptr};
|
||||
const char *css_include_{nullptr};
|
||||
const char *js_url_{nullptr};
|
||||
|
||||
@@ -15,6 +15,17 @@ namespace web_server_base {
|
||||
|
||||
static const char *const TAG = "web_server_base";
|
||||
|
||||
void WebServerBase::add_handler(AsyncWebHandler *handler) {
|
||||
// remove all handlers
|
||||
|
||||
if (!credentials_.username.empty()) {
|
||||
handler = new internal::AuthMiddlewareHandler(handler, &credentials_);
|
||||
}
|
||||
this->handlers_.push_back(handler);
|
||||
if (this->server_ != nullptr)
|
||||
this->server_->addHandler(handler);
|
||||
}
|
||||
|
||||
void report_ota_error() {
|
||||
StreamString ss;
|
||||
Update.printError(ss);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
@@ -7,6 +9,68 @@
|
||||
namespace esphome {
|
||||
namespace web_server_base {
|
||||
|
||||
namespace internal {
|
||||
|
||||
class MiddlewareHandler : public AsyncWebHandler {
|
||||
public:
|
||||
MiddlewareHandler(AsyncWebHandler *next) : next_(next) {}
|
||||
|
||||
bool canHandle(AsyncWebServerRequest *request) override { return next_->canHandle(request); }
|
||||
void handleRequest(AsyncWebServerRequest *request) override { next_->handleRequest(request); }
|
||||
void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len,
|
||||
bool final) override {
|
||||
next_->handleUpload(request, filename, index, data, len, final);
|
||||
}
|
||||
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override {
|
||||
next_->handleBody(request, data, len, index, total);
|
||||
}
|
||||
bool isRequestHandlerTrivial() override { return next_->isRequestHandlerTrivial(); }
|
||||
|
||||
protected:
|
||||
AsyncWebHandler *next_;
|
||||
};
|
||||
|
||||
struct Credentials {
|
||||
std::string username;
|
||||
std::string password;
|
||||
};
|
||||
|
||||
class AuthMiddlewareHandler : public MiddlewareHandler {
|
||||
public:
|
||||
AuthMiddlewareHandler(AsyncWebHandler *next, Credentials *credentials)
|
||||
: MiddlewareHandler(next), credentials_(credentials) {}
|
||||
|
||||
bool check_auth(AsyncWebServerRequest *request) {
|
||||
bool success = request->authenticate(credentials_->username.c_str(), credentials_->password.c_str());
|
||||
if (!success) {
|
||||
request->requestAuthentication();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void handleRequest(AsyncWebServerRequest *request) override {
|
||||
if (!check_auth(request))
|
||||
return;
|
||||
MiddlewareHandler::handleRequest(request);
|
||||
}
|
||||
void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len,
|
||||
bool final) override {
|
||||
if (!check_auth(request))
|
||||
return;
|
||||
MiddlewareHandler::handleUpload(request, filename, index, data, len, final);
|
||||
}
|
||||
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override {
|
||||
if (!check_auth(request))
|
||||
return;
|
||||
MiddlewareHandler::handleBody(request, data, len, index, total);
|
||||
}
|
||||
|
||||
protected:
|
||||
Credentials *credentials_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
class WebServerBase : public Component {
|
||||
public:
|
||||
void init() {
|
||||
@@ -32,13 +96,10 @@ class WebServerBase : public Component {
|
||||
AsyncWebServer *get_server() const { return server_; }
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void add_handler(AsyncWebHandler *handler) {
|
||||
// remove all handlers
|
||||
void set_auth_username(std::string auth_username) { credentials_.username = std::move(auth_username); }
|
||||
void set_auth_password(std::string auth_password) { credentials_.password = std::move(auth_password); }
|
||||
|
||||
this->handlers_.push_back(handler);
|
||||
if (this->server_ != nullptr)
|
||||
this->server_->addHandler(handler);
|
||||
}
|
||||
void add_handler(AsyncWebHandler *handler);
|
||||
|
||||
void add_ota_handler();
|
||||
|
||||
@@ -52,6 +113,7 @@ class WebServerBase : public Component {
|
||||
uint16_t port_{80};
|
||||
AsyncWebServer *server_{nullptr};
|
||||
std::vector<AsyncWebHandler *> handlers_;
|
||||
internal::Credentials credentials_;
|
||||
};
|
||||
|
||||
class OTARequestHandler : public AsyncWebHandler {
|
||||
|
||||
@@ -244,6 +244,8 @@ void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &pa
|
||||
sta.set_ssid(ssid);
|
||||
sta.set_password(password);
|
||||
this->set_sta(sta);
|
||||
|
||||
this->start_scanning();
|
||||
}
|
||||
|
||||
void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#endif
|
||||
#include "lwip/err.h"
|
||||
#include "lwip/dns.h"
|
||||
#include "lwip/apps/sntp.h"
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -92,6 +93,11 @@ bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
|
||||
tcpip_adapter_dhcp_status_t dhcp_status;
|
||||
tcpip_adapter_dhcpc_get_status(TCPIP_ADAPTER_IF_STA, &dhcp_status);
|
||||
if (!manual_ip.has_value()) {
|
||||
// lwIP starts the SNTP client if it gets an SNTP server from DHCP. We don't need the time, and more importantly,
|
||||
// the built-in SNTP client has a memory leak in certain situations. Disable this feature.
|
||||
// https://github.com/esphome/issues/issues/2299
|
||||
sntp_servermode_dhcp(false);
|
||||
|
||||
// Use DHCP client
|
||||
if (dhcp_status != TCPIP_ADAPTER_DHCP_STARTED) {
|
||||
esp_err_t err = tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA);
|
||||
|
||||
@@ -16,6 +16,7 @@ extern "C" {
|
||||
#include "lwip/dns.h"
|
||||
#include "lwip/dhcp.h"
|
||||
#include "lwip/init.h" // LWIP_VERSION_
|
||||
#include "lwip/apps/sntp.h"
|
||||
#if LWIP_IPV6
|
||||
#include "lwip/netif.h" // struct netif
|
||||
#endif
|
||||
@@ -112,6 +113,11 @@ bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
|
||||
|
||||
enum dhcp_status dhcp_status = wifi_station_dhcpc_status();
|
||||
if (!manual_ip.has_value()) {
|
||||
// lwIP starts the SNTP client if it gets an SNTP server from DHCP. We don't need the time, and more importantly,
|
||||
// the built-in SNTP client has a memory leak in certain situations. Disable this feature.
|
||||
// https://github.com/esphome/issues/issues/2299
|
||||
sntp_servermode_dhcp(false);
|
||||
|
||||
// Use DHCP client
|
||||
if (dhcp_status != DHCP_STARTED) {
|
||||
bool ret = wifi_station_dhcpc_start();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Constants used by esphome."""
|
||||
|
||||
__version__ = "2021.9.0b1"
|
||||
__version__ = "2021.9.3"
|
||||
|
||||
ESP_PLATFORM_ESP32 = "ESP32"
|
||||
ESP_PLATFORM_ESP8266 = "ESP8266"
|
||||
@@ -786,7 +786,9 @@ UNIT_KELVIN = "K"
|
||||
UNIT_KILOGRAM = "kg"
|
||||
UNIT_KILOMETER = "km"
|
||||
UNIT_KILOMETER_PER_HOUR = "km/h"
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE = "kVAr"
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kVArh"
|
||||
UNIT_KILOWATT = "kW"
|
||||
UNIT_KILOWATT_HOURS = "kWh"
|
||||
UNIT_LUX = "lx"
|
||||
UNIT_METER = "m"
|
||||
|
||||
@@ -4,9 +4,6 @@ import time
|
||||
from typing import Dict, Optional
|
||||
|
||||
from zeroconf import (
|
||||
_CLASS_IN,
|
||||
_FLAGS_QR_QUERY,
|
||||
_TYPE_A,
|
||||
DNSAddress,
|
||||
DNSOutgoing,
|
||||
DNSRecord,
|
||||
@@ -15,6 +12,10 @@ from zeroconf import (
|
||||
Zeroconf,
|
||||
)
|
||||
|
||||
_CLASS_IN = 1
|
||||
_FLAGS_QR_QUERY = 0x0000 # query
|
||||
_TYPE_A = 1
|
||||
|
||||
|
||||
class HostResolver(RecordUpdateListener):
|
||||
def __init__(self, name: str):
|
||||
@@ -48,7 +49,7 @@ class HostResolver(RecordUpdateListener):
|
||||
next_ = now + delay
|
||||
delay *= 2
|
||||
|
||||
zc.wait(min(next_, last) - now)
|
||||
time.sleep(min(next_, last) - now)
|
||||
now = time.time()
|
||||
finally:
|
||||
zc.remove_listener(self)
|
||||
|
||||
@@ -36,8 +36,8 @@ lib_deps =
|
||||
6306@1.0.3 ; HM3301
|
||||
glmnet/Dsmr@0.3 ; used by dsmr
|
||||
rweather/Crypto@0.2.0 ; used by dsmr
|
||||
esphome/noise-c@0.1.1 ; used by api
|
||||
dudanov/MideaUART@1.1.0 ; used by midea
|
||||
esphome/noise-c@0.1.3 ; used by api
|
||||
dudanov/MideaUART@1.1.8 ; used by midea
|
||||
|
||||
build_flags =
|
||||
-DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
|
||||
@@ -3,12 +3,11 @@ PyYAML==5.4.1
|
||||
paho-mqtt==1.5.1
|
||||
colorama==0.4.4
|
||||
tornado==6.1
|
||||
protobuf==3.17.3
|
||||
tzlocal==2.1
|
||||
pytz==2021.1
|
||||
pyserial==3.5
|
||||
ifaddr==0.1.7
|
||||
platformio==5.1.1
|
||||
platformio==5.2.0
|
||||
esptool==3.1
|
||||
click==7.1.2
|
||||
esphome-dashboard==20210908.0
|
||||
aioesphomeapi==9.1.4
|
||||
|
||||
Reference in New Issue
Block a user