Create Protobuf Plugin for automatically generating native API stubs (#633)

* Create Protobuf Plugin for automatically generating native API stubs

* Format

* Delete api.proto

* Cleanup, use no_delay conditionally

* Updates

* Update

* Lint

* Lint

* Fixes

* Camera

* CustomAPIDevice

* Fix negative VarInt, Add User-defined services arrays

* Home Assistant Event

* Fixes

* Update custom_api_device.h
This commit is contained in:
Otto Winter 2019-06-18 19:31:22 +02:00 committed by GitHub
parent fc465d6d93
commit 369d175694
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 7183 additions and 3110 deletions

View file

@ -11,7 +11,7 @@ from esphome import const, writer, yaml_util
import esphome.codegen as cg import esphome.codegen as cg
from esphome.config import iter_components, read_config, strip_default_ids from esphome.config import iter_components, read_config, strip_default_ids
from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \ from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
CONF_PASSWORD, CONF_PORT CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
from esphome.helpers import color, indent from esphome.helpers import color, indent
from esphome.py_compat import IS_PY2, safe_input from esphome.py_compat import IS_PY2, safe_input
@ -163,6 +163,7 @@ def compile_program(args, config):
def upload_using_esptool(config, port): def upload_using_esptool(config, port):
path = CORE.firmware_bin path = CORE.firmware_bin
cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset', cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset',
'--baud', config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get('upload_speed', 115200),
'--chip', 'esp8266', '--port', port, 'write_flash', '0x0', path] '--chip', 'esp8266', '--port', port, 'write_flash', '0x0', path]
if os.environ.get('ESPHOME_USE_SUBPROCESS') is None: if os.environ.get('ESPHOME_USE_SUBPROCESS') is None:

View file

@ -1,330 +0,0 @@
syntax = "proto3";
// The Home Assistant protocol is structured as a simple
// TCP socket with short binary messages encoded in the protocol buffers format
// First, a message in this protocol has a specific format:
// * VarInt denoting the size of the message object. (type is not part of this)
// * VarInt denoting the type of message.
// * The message object encoded as a ProtoBuf message
// The connection is established in 4 steps:
// * First, the client connects to the server and sends a "Hello Request" identifying itself
// * The server responds with a "Hello Response" and selects the protocol version
// * After receiving this message, the client attempts to authenticate itself using
// the password and a "Connect Request"
// * The server responds with a "Connect Response" and notifies of invalid password.
// If anything in this initial process fails, the connection must immediately closed
// by both sides and _no_ disconnection message is to be sent.
// Message sent at the beginning of each connection
// Can only be sent by the client and only at the beginning of the connection
message HelloRequest {
// Description of client (like User Agent)
// For example "Home Assistant"
// Not strictly necessary to send but nice for debugging
// purposes.
string client_info = 1;
}
// Confirmation of successful connection request.
// Can only be sent by the server and only at the beginning of the connection
message HelloResponse {
// The version of the API to use. The _client_ (for example Home Assistant) needs to check
// for compatibility and if necessary adopt to an older API.
// Major is for breaking changes in the base protocol - a mismatch will lead to immediate disconnect_client_
// Minor is for breaking changes in individual messages - a mismatch will lead to a warning message
uint32 api_version_major = 1;
uint32 api_version_minor = 2;
// A string identifying the server (ESP); like client info this may be empty
// and only exists for debugging/logging purposes.
// For example "ESPHome v1.10.0 on ESP8266"
string server_info = 3;
}
// Message sent at the beginning of each connection to authenticate the client
// Can only be sent by the client and only at the beginning of the connection
message ConnectRequest {
// The password to log in with
string password = 1;
}
// Confirmation of successful connection. After this the connection is available for all traffic.
// Can only be sent by the server and only at the beginning of the connection
message ConnectResponse {
bool invalid_password = 1;
}
// Request to close the connection.
// Can be sent by both the client and server
message DisconnectRequest {
// Do not close the connection before the acknowledgement arrives
}
message DisconnectResponse {
// Empty - Both parties are required to close the connection after this
// message has been received.
}
message PingRequest {
// Empty
}
message PingResponse {
// Empty
}
message DeviceInfoRequest {
// Empty
}
message DeviceInfoResponse {
bool uses_password = 1;
// The name of the node, given by "App.set_name()"
string name = 2;
// The mac address of the device. For example "AC:BC:32:89:0E:A9"
string mac_address = 3;
// A string describing the ESPHome version. For example "1.10.0"
string esphome_core_version = 4;
// A string describing the date of compilation, this is generated by the compiler
// and therefore may not be in the same format all the time.
// If the user isn't using esphome, this will also not be set.
string compilation_time = 5;
// The model of the board. For example NodeMCU
string model = 6;
bool has_deep_sleep = 7;
}
message ListEntitiesRequest {
// Empty
}
message ListEntitiesBinarySensorResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string device_class = 5;
bool is_status_binary_sensor = 6;
}
message ListEntitiesCoverResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
bool is_optimistic = 5;
}
message ListEntitiesFanResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
bool supports_oscillation = 5;
bool supports_speed = 6;
}
message ListEntitiesLightResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
bool supports_brightness = 5;
bool supports_rgb = 6;
bool supports_white_value = 7;
bool supports_color_temperature = 8;
float min_mireds = 9;
float max_mireds = 10;
repeated string effects = 11;
}
message ListEntitiesSensorResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
string unit_of_measurement = 6;
int32 accuracy_decimals = 7;
}
message ListEntitiesSwitchResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool optimistic = 6;
}
message ListEntitiesTextSensorResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
}
message ListEntitiesDoneResponse {
// Empty
}
message SubscribeStatesRequest {
// Empty
}
message BinarySensorStateResponse {
fixed32 key = 1;
bool state = 2;
}
message CoverStateResponse {
fixed32 key = 1;
enum CoverState {
OPEN = 0;
CLOSED = 1;
}
CoverState state = 2;
}
enum FanSpeed {
LOW = 0;
MEDIUM = 1;
HIGH = 2;
}
message FanStateResponse {
fixed32 key = 1;
bool state = 2;
bool oscillating = 3;
FanSpeed speed = 4;
}
message LightStateResponse {
fixed32 key = 1;
bool state = 2;
float brightness = 3;
float red = 4;
float green = 5;
float blue = 6;
float white = 7;
float color_temperature = 8;
string effect = 9;
}
message SensorStateResponse {
fixed32 key = 1;
float state = 2;
}
message SwitchStateResponse {
fixed32 key = 1;
bool state = 2;
}
message TextSensorStateResponse {
fixed32 key = 1;
string state = 2;
}
message CoverCommandRequest {
fixed32 key = 1;
enum CoverCommand {
OPEN = 0;
CLOSE = 1;
STOP = 2;
}
bool has_state = 2;
CoverCommand command = 3;
}
message FanCommandRequest {
fixed32 key = 1;
bool has_state = 2;
bool state = 3;
bool has_speed = 4;
FanSpeed speed = 5;
bool has_oscillating = 6;
bool oscillating = 7;
}
message LightCommandRequest {
fixed32 key = 1;
bool has_state = 2;
bool state = 3;
bool has_brightness = 4;
float brightness = 5;
bool has_rgb = 6;
float red = 7;
float green = 8;
float blue = 9;
bool has_white = 10;
float white = 11;
bool has_color_temperature = 12;
float color_temperature = 13;
bool has_transition_length = 14;
uint32 transition_length = 15;
bool has_flash_length = 16;
uint32 flash_length = 17;
bool has_effect = 18;
string effect = 19;
}
message SwitchCommandRequest {
fixed32 key = 1;
bool state = 2;
}
enum LogLevel {
NONE = 0;
ERROR = 1;
WARN = 2;
INFO = 3;
DEBUG = 4;
VERBOSE = 5;
VERY_VERBOSE = 6;
}
message SubscribeLogsRequest {
LogLevel level = 1;
bool dump_config = 2;
}
message SubscribeLogsResponse {
LogLevel level = 1;
string tag = 2;
string message = 3;
bool send_failed = 4;
}
message SubscribeServiceCallsRequest {
}
message ServiceCallResponse {
string service = 1;
map<string, string> data = 2;
map<string, string> data_template = 3;
map<string, string> variables = 4;
}
// 1. Client sends SubscribeHomeAssistantStatesRequest
// 2. Server responds with zero or more SubscribeHomeAssistantStateResponse (async)
// 3. Client sends HomeAssistantStateResponse for state changes.
message SubscribeHomeAssistantStatesRequest {
}
message SubscribeHomeAssistantStateResponse {
string entity_id = 1;
}
message HomeAssistantStateResponse {
string entity_id = 1;
string state = 2;
}
message GetTimeRequest {
}
message GetTimeResponse {
fixed32 epoch_seconds = 1;
}

View file

@ -14,7 +14,7 @@ import esphome.api.api_pb2 as pb
from esphome.const import CONF_PASSWORD, CONF_PORT from esphome.const import CONF_PASSWORD, CONF_PORT
from esphome.core import EsphomeError from esphome.core import EsphomeError
from esphome.helpers import resolve_ip_address, indent, color from esphome.helpers import resolve_ip_address, indent, color
from esphome.py_compat import text_type, IS_PY2, byte_to_bytes, char_to_byte, format_bytes from esphome.py_compat import text_type, IS_PY2, byte_to_bytes, char_to_byte
from esphome.util import safe_print from esphome.util import safe_print
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -247,7 +247,7 @@ class APIClient(threading.Thread):
if self._socket is None: if self._socket is None:
raise APIConnectionError("Socket closed") raise APIConnectionError("Socket closed")
_LOGGER.debug("Write: %s", format_bytes(data)) # _LOGGER.debug("Write: %s", format_bytes(data))
with self._socket_write_lock: with self._socket_write_lock:
try: try:
self._socket.sendall(data) self._socket.sendall(data)
@ -275,7 +275,7 @@ class APIClient(threading.Thread):
req += encoded req += encoded
self._write(req) self._write(req)
def _send_message_await_response_complex(self, send_msg, do_append, do_stop, timeout=1): def _send_message_await_response_complex(self, send_msg, do_append, do_stop, timeout=5):
event = threading.Event() event = threading.Event()
responses = [] responses = []
@ -296,7 +296,7 @@ class APIClient(threading.Thread):
raise APIConnectionError("Timeout while waiting for message response!") raise APIConnectionError("Timeout while waiting for message response!")
return responses return responses
def _send_message_await_response(self, send_msg, response_type, timeout=1): def _send_message_await_response(self, send_msg, response_type, timeout=5):
def is_response(msg): def is_response(msg):
return isinstance(msg, response_type) return isinstance(msg, response_type)
@ -327,7 +327,7 @@ class APIClient(threading.Thread):
if not self._authenticated: if not self._authenticated:
raise APIConnectionError("Must login first!") raise APIConnectionError("Must login first!")
def subscribe_logs(self, on_log, log_level=None, dump_config=False): def subscribe_logs(self, on_log, log_level=7, dump_config=False):
self._check_authenticated() self._check_authenticated()
def on_msg(msg): def on_msg(msg):
@ -336,7 +336,6 @@ class APIClient(threading.Thread):
self._message_handlers.append(on_msg) self._message_handlers.append(on_msg)
req = pb.SubscribeLogsRequest(dump_config=dump_config) req = pb.SubscribeLogsRequest(dump_config=dump_config)
if log_level is not None:
req.level = log_level req.level = log_level
self._send_message(req) self._send_message(req)

View file

@ -3,7 +3,7 @@ import esphome.config_validation as cv
from esphome import automation from esphome import automation
from esphome.automation import Condition from esphome.automation import Condition
from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \ from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \
CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID, CONF_EVENT
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
DEPENDENCIES = ['network'] DEPENDENCIES = ['network']
@ -11,24 +11,19 @@ DEPENDENCIES = ['network']
api_ns = cg.esphome_ns.namespace('api') api_ns = cg.esphome_ns.namespace('api')
APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller) APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)
HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', automation.Action) HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', automation.Action)
KeyValuePair = api_ns.class_('KeyValuePair')
TemplatableKeyValuePair = api_ns.class_('TemplatableKeyValuePair')
APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition) APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition)
UserService = api_ns.class_('UserService', automation.Trigger) UserServiceTrigger = api_ns.class_('UserServiceTrigger', automation.Trigger)
ServiceTypeArgument = api_ns.class_('ServiceTypeArgument') ListEntitiesServicesArgument = api_ns.class_('ListEntitiesServicesArgument')
ServiceArgType = api_ns.enum('ServiceArgType')
SERVICE_ARG_TYPES = {
'bool': ServiceArgType.SERVICE_ARG_TYPE_BOOL,
'int': ServiceArgType.SERVICE_ARG_TYPE_INT,
'float': ServiceArgType.SERVICE_ARG_TYPE_FLOAT,
'string': ServiceArgType.SERVICE_ARG_TYPE_STRING,
}
SERVICE_ARG_NATIVE_TYPES = { SERVICE_ARG_NATIVE_TYPES = {
'bool': bool, 'bool': bool,
'int': cg.int32, 'int': cg.int32,
'float': float, 'float': float,
'string': cg.std_string, 'string': cg.std_string,
'bool[]': cg.std_vector.template(bool),
'int[]': cg.std_vector.template(cg.int32),
'float[]': cg.std_vector.template(float),
'string[]': cg.std_vector.template(cg.std_string),
} }
CONFIG_SCHEMA = cv.Schema({ CONFIG_SCHEMA = cv.Schema({
@ -37,10 +32,10 @@ CONFIG_SCHEMA = cv.Schema({
cv.Optional(CONF_PASSWORD, default=''): cv.string_strict, cv.Optional(CONF_PASSWORD, default=''): cv.string_strict,
cv.Optional(CONF_REBOOT_TIMEOUT, default='5min'): cv.positive_time_period_milliseconds, cv.Optional(CONF_REBOOT_TIMEOUT, default='5min'): cv.positive_time_period_milliseconds,
cv.Optional(CONF_SERVICES): automation.validate_automation({ cv.Optional(CONF_SERVICES): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserService), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
cv.Required(CONF_SERVICE): cv.valid_name, cv.Required(CONF_SERVICE): cv.valid_name,
cv.Optional(CONF_VARIABLES, default={}): cv.Schema({ cv.Optional(CONF_VARIABLES, default={}): cv.Schema({
cv.validate_id_name: cv.one_of(*SERVICE_ARG_TYPES, lower=True), cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True),
}), }),
}), }),
}).extend(cv.COMPONENT_SCHEMA) }).extend(cv.COMPONENT_SCHEMA)
@ -58,37 +53,34 @@ def to_code(config):
for conf in config.get(CONF_SERVICES, []): for conf in config.get(CONF_SERVICES, []):
template_args = [] template_args = []
func_args = [] func_args = []
service_type_args = [] service_arg_names = []
for name, var_ in conf[CONF_VARIABLES].items(): for name, var_ in conf[CONF_VARIABLES].items():
native = SERVICE_ARG_NATIVE_TYPES[var_] native = SERVICE_ARG_NATIVE_TYPES[var_]
template_args.append(native) template_args.append(native)
func_args.append((native, name)) func_args.append((native, name))
service_type_args.append(ServiceTypeArgument(name, SERVICE_ARG_TYPES[var_])) service_arg_names.append(name)
templ = cg.TemplateArguments(*template_args) templ = cg.TemplateArguments(*template_args)
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], templ, trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], templ,
conf[CONF_SERVICE], service_type_args) conf[CONF_SERVICE], service_arg_names)
cg.add(var.register_user_service(trigger)) cg.add(var.register_user_service(trigger))
yield automation.build_automation(trigger, func_args, conf) yield automation.build_automation(trigger, func_args, conf)
cg.add_define('USE_API') cg.add_define('USE_API')
cg.add_global(api_ns.using)
if CORE.is_esp32: if CORE.is_esp32:
cg.add_library('AsyncTCP', '1.0.3') cg.add_library('AsyncTCP', '1.0.3')
elif CORE.is_esp8266: elif CORE.is_esp8266:
cg.add_library('ESPAsyncTCP', '1.2.0') cg.add_library('ESPAsyncTCP', '1.2.0')
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string)})
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({ HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({
cv.GenerateID(): cv.use_id(APIServer), cv.GenerateID(): cv.use_id(APIServer),
cv.Required(CONF_SERVICE): cv.string, cv.Required(CONF_SERVICE): cv.templatable(cv.string),
cv.Optional(CONF_DATA): cv.Schema({ cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
cv.string: cv.string, cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
}), cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA,
cv.Optional(CONF_DATA_TEMPLATE): cv.Schema({
cv.string: cv.string,
}),
cv.Optional(CONF_VARIABLES): cv.Schema({
cv.string: cv.returning_lambda,
}),
}) })
@ -96,20 +88,54 @@ HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({
HOMEASSISTANT_SERVICE_ACTION_SCHEMA) HOMEASSISTANT_SERVICE_ACTION_SCHEMA)
def homeassistant_service_to_code(config, action_id, template_arg, args): def homeassistant_service_to_code(config, action_id, template_arg, args):
serv = yield cg.get_variable(config[CONF_ID]) serv = yield cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv) var = cg.new_Pvariable(action_id, template_arg, serv, False)
cg.add(var.set_service(config[CONF_SERVICE])) templ = yield cg.templatable(config[CONF_SERVICE], args, None)
if CONF_DATA in config: cg.add(var.set_service(templ))
datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA].items()] for key, value in config[CONF_DATA].items():
cg.add(var.set_data(datas)) templ = yield cg.templatable(value, args, None)
if CONF_DATA_TEMPLATE in config: cg.add(var.add_data(key, templ))
datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA_TEMPLATE].items()] for key, value in config[CONF_DATA_TEMPLATE].items():
cg.add(var.set_data_template(datas)) templ = yield cg.templatable(value, args, None)
if CONF_VARIABLES in config: cg.add(var.add_data_template(key, templ))
datas = []
for key, value in config[CONF_VARIABLES].items(): for key, value in config[CONF_VARIABLES].items():
value_ = yield cg.process_lambda(value, []) templ = yield cg.templatable(value, args, None)
datas.append(TemplatableKeyValuePair(key, value_)) cg.add(var.add_variable(key, templ))
cg.add(var.set_variables(datas)) yield var
def validate_homeassistant_event(value):
value = cv.string(value)
if not value.startswith(u'esphome.'):
raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with "
"esphome. For example 'esphome.xyz'")
return value
HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema({
cv.GenerateID(): cv.use_id(APIServer),
cv.Required(CONF_EVENT): validate_homeassistant_event,
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA,
})
@automation.register_action('homeassistant.event', HomeAssistantServiceCallAction,
HOMEASSISTANT_EVENT_ACTION_SCHEMA)
def homeassistant_event_to_code(config, action_id, template_arg, args):
serv = yield cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, True)
templ = yield cg.templatable(config[CONF_EVENT], args, None)
cg.add(var.set_service(templ))
for key, value in config[CONF_DATA].items():
templ = yield cg.templatable(value, args, None)
cg.add(var.add_data(key, templ))
for key, value in config[CONF_DATA_TEMPLATE].items():
templ = yield cg.templatable(value, args, None)
cg.add(var.add_data_template(key, templ))
for key, value in config[CONF_VARIABLES].items():
templ = yield cg.templatable(value, args, None)
cg.add(var.add_variable(key, templ))
yield var yield var

View file

@ -1,5 +1,45 @@
syntax = "proto3"; syntax = "proto3";
import "api_options.proto";
service APIConnection {
rpc hello (HelloRequest) returns (HelloResponse) {
option (needs_setup_connection) = false;
option (needs_authentication) = false;
}
rpc connect (ConnectRequest) returns (ConnectResponse) {
option (needs_setup_connection) = false;
option (needs_authentication) = false;
}
rpc disconnect (DisconnectRequest) returns (DisconnectResponse) {
option (needs_setup_connection) = false;
option (needs_authentication) = false;
}
rpc ping (PingRequest) returns (PingResponse) {
option (needs_setup_connection) = false;
option (needs_authentication) = false;
}
rpc device_info (DeviceInfoRequest) returns (DeviceInfoResponse) {
option (needs_authentication) = false;
}
rpc list_entities (ListEntitiesRequest) returns (void) {}
rpc subscribe_states (SubscribeStatesRequest) returns (void) {}
rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {}
rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {}
rpc get_time (GetTimeRequest) returns (GetTimeResponse) {
option (needs_authentication) = false;
}
rpc execute_service (ExecuteServiceRequest) returns (void) {}
rpc cover_command (CoverCommandRequest) returns (void) {}
rpc fan_command (FanCommandRequest) returns (void) {}
rpc light_command (LightCommandRequest) returns (void) {}
rpc switch_command (SwitchCommandRequest) returns (void) {}
rpc camera_image (CameraImageRequest) returns (void) {}
rpc climate_command (ClimateCommandRequest) returns (void) {}
}
// ==================== BASE PACKETS ==================== // ==================== BASE PACKETS ====================
@ -21,8 +61,11 @@ syntax = "proto3";
// Message sent at the beginning of each connection // Message sent at the beginning of each connection
// Can only be sent by the client and only at the beginning of the connection // Can only be sent by the client and only at the beginning of the connection
// ID: 1
message HelloRequest { message HelloRequest {
option (id) = 1;
option (source) = SOURCE_CLIENT;
option (no_delay) = true;
// Description of client (like User Agent) // Description of client (like User Agent)
// For example "Home Assistant" // For example "Home Assistant"
// Not strictly necessary to send but nice for debugging // Not strictly necessary to send but nice for debugging
@ -32,8 +75,11 @@ message HelloRequest {
// Confirmation of successful connection request. // Confirmation of successful connection request.
// Can only be sent by the server and only at the beginning of the connection // Can only be sent by the server and only at the beginning of the connection
// ID: 2
message HelloResponse { message HelloResponse {
option (id) = 2;
option (source) = SOURCE_SERVER;
option (no_delay) = true;
// The version of the API to use. The _client_ (for example Home Assistant) needs to check // The version of the API to use. The _client_ (for example Home Assistant) needs to check
// for compatibility and if necessary adopt to an older API. // for compatibility and if necessary adopt to an older API.
// Major is for breaking changes in the base protocol - a mismatch will lead to immediate disconnect_client_ // Major is for breaking changes in the base protocol - a mismatch will lead to immediate disconnect_client_
@ -49,49 +95,66 @@ message HelloResponse {
// Message sent at the beginning of each connection to authenticate the client // Message sent at the beginning of each connection to authenticate the client
// Can only be sent by the client and only at the beginning of the connection // Can only be sent by the client and only at the beginning of the connection
// ID: 3
message ConnectRequest { message ConnectRequest {
option (id) = 3;
option (source) = SOURCE_CLIENT;
option (no_delay) = true;
// The password to log in with // The password to log in with
string password = 1; string password = 1;
} }
// Confirmation of successful connection. After this the connection is available for all traffic. // Confirmation of successful connection. After this the connection is available for all traffic.
// Can only be sent by the server and only at the beginning of the connection // Can only be sent by the server and only at the beginning of the connection
// ID: 4
message ConnectResponse { message ConnectResponse {
option (id) = 4;
option (source) = SOURCE_SERVER;
option (no_delay) = true;
bool invalid_password = 1; bool invalid_password = 1;
} }
// Request to close the connection. // Request to close the connection.
// Can be sent by both the client and server // Can be sent by both the client and server
// ID: 5
message DisconnectRequest { message DisconnectRequest {
option (id) = 5;
option (source) = SOURCE_BOTH;
option (no_delay) = true;
// Do not close the connection before the acknowledgement arrives // Do not close the connection before the acknowledgement arrives
} }
// ID: 6
message DisconnectResponse { message DisconnectResponse {
option (id) = 6;
option (source) = SOURCE_BOTH;
option (no_delay) = true;
// Empty - Both parties are required to close the connection after this // Empty - Both parties are required to close the connection after this
// message has been received. // message has been received.
} }
// ID: 7
message PingRequest { message PingRequest {
option (id) = 7;
option (source) = SOURCE_BOTH;
// Empty // Empty
} }
// ID: 8
message PingResponse { message PingResponse {
option (id) = 8;
option (source) = SOURCE_BOTH;
// Empty // Empty
} }
// ID: 9
message DeviceInfoRequest { message DeviceInfoRequest {
option (id) = 9;
option (source) = SOURCE_CLIENT;
// Empty // Empty
} }
// ID: 10
message DeviceInfoResponse { message DeviceInfoResponse {
option (id) = 10;
option (source) = SOURCE_SERVER;
bool uses_password = 1; bool uses_password = 1;
// The name of the node, given by "App.set_name()" // The name of the node, given by "App.set_name()"
@ -101,7 +164,7 @@ message DeviceInfoResponse {
string mac_address = 3; string mac_address = 3;
// A string describing the ESPHome version. For example "1.10.0" // A string describing the ESPHome version. For example "1.10.0"
string esphome_core_version = 4; string esphome_version = 4;
// A string describing the date of compilation, this is generated by the compiler // A string describing the date of compilation, this is generated by the compiler
// and therefore may not be in the same format all the time. // and therefore may not be in the same format all the time.
@ -114,22 +177,29 @@ message DeviceInfoResponse {
bool has_deep_sleep = 7; bool has_deep_sleep = 7;
} }
// ID: 11
message ListEntitiesRequest { message ListEntitiesRequest {
option (id) = 11;
option (source) = SOURCE_CLIENT;
// Empty // Empty
} }
// ID: 19
message ListEntitiesDoneResponse { message ListEntitiesDoneResponse {
option (id) = 19;
option (source) = SOURCE_SERVER;
option (no_delay) = true;
// Empty // Empty
} }
// ID: 20
message SubscribeStatesRequest { message SubscribeStatesRequest {
option (id) = 20;
option (source) = SOURCE_CLIENT;
// Empty // Empty
} }
// ==================== BINARY SENSOR ==================== // ==================== BINARY SENSOR ====================
// ID: 12
message ListEntitiesBinarySensorResponse { message ListEntitiesBinarySensorResponse {
option (id) = 12;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BINARY_SENSOR";
string object_id = 1; string object_id = 1;
fixed32 key = 2; fixed32 key = 2;
string name = 3; string name = 3;
@ -138,15 +208,22 @@ message ListEntitiesBinarySensorResponse {
string device_class = 5; string device_class = 5;
bool is_status_binary_sensor = 6; bool is_status_binary_sensor = 6;
} }
// ID: 21
message BinarySensorStateResponse { message BinarySensorStateResponse {
option (id) = 21;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BINARY_SENSOR";
option (no_delay) = true;
fixed32 key = 1; fixed32 key = 1;
bool state = 2; bool state = 2;
} }
// ==================== COVER ==================== // ==================== COVER ====================
// ID: 13
message ListEntitiesCoverResponse { message ListEntitiesCoverResponse {
option (id) = 13;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_COVER";
string object_id = 1; string object_id = 1;
fixed32 key = 2; fixed32 key = 2;
string name = 3; string name = 3;
@ -157,38 +234,47 @@ message ListEntitiesCoverResponse {
bool supports_tilt = 7; bool supports_tilt = 7;
string device_class = 8; string device_class = 8;
} }
// ID: 22
message CoverStateResponse {
fixed32 key = 1;
enum LegacyCoverState {
LEGACY_COVER_STATE_OPEN = 0;
LEGACY_COVER_STATE_CLOSED = 1;
}
enum CoverOperation {
COVER_OPERATION_IDLE = 0;
COVER_OPERATION_IS_OPENING = 1;
COVER_OPERATION_IS_CLOSING = 2;
}
message CoverStateResponse {
option (id) = 22;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_COVER";
option (no_delay) = true;
fixed32 key = 1;
// legacy: state has been removed in 1.13 // legacy: state has been removed in 1.13
// clients/servers must still send/accept it until the next protocol change // clients/servers must still send/accept it until the next protocol change
enum LegacyCoverState {
OPEN = 0;
CLOSED = 1;
}
LegacyCoverState legacy_state = 2; LegacyCoverState legacy_state = 2;
float position = 3; float position = 3;
float tilt = 4; float tilt = 4;
enum CoverOperation {
IDLE = 0;
IS_OPENING = 1;
IS_CLOSING = 2;
}
CoverOperation current_operation = 5; CoverOperation current_operation = 5;
} }
// ID: 30
enum LegacyCoverCommand {
LEGACY_COVER_COMMAND_OPEN = 0;
LEGACY_COVER_COMMAND_CLOSE = 1;
LEGACY_COVER_COMMAND_STOP = 2;
}
message CoverCommandRequest { message CoverCommandRequest {
option (id) = 30;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_COVER";
option (no_delay) = true;
fixed32 key = 1; fixed32 key = 1;
// legacy: command has been removed in 1.13 // legacy: command has been removed in 1.13
// clients/servers must still send/accept it until the next protocol change // clients/servers must still send/accept it until the next protocol change
enum LegacyCoverCommand {
OPEN = 0;
CLOSE = 1;
STOP = 2;
}
bool has_legacy_command = 2; bool has_legacy_command = 2;
LegacyCoverCommand legacy_command = 3; LegacyCoverCommand legacy_command = 3;
@ -200,8 +286,11 @@ message CoverCommandRequest {
} }
// ==================== FAN ==================== // ==================== FAN ====================
// ID: 14
message ListEntitiesFanResponse { message ListEntitiesFanResponse {
option (id) = 14;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_FAN";
string object_id = 1; string object_id = 1;
fixed32 key = 2; fixed32 key = 2;
string name = 3; string name = 3;
@ -211,19 +300,27 @@ message ListEntitiesFanResponse {
bool supports_speed = 6; bool supports_speed = 6;
} }
enum FanSpeed { enum FanSpeed {
LOW = 0; FAN_SPEED_LOW = 0;
MEDIUM = 1; FAN_SPEED_MEDIUM = 1;
HIGH = 2; FAN_SPEED_HIGH = 2;
} }
// ID: 23
message FanStateResponse { message FanStateResponse {
option (id) = 23;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_FAN";
option (no_delay) = true;
fixed32 key = 1; fixed32 key = 1;
bool state = 2; bool state = 2;
bool oscillating = 3; bool oscillating = 3;
FanSpeed speed = 4; FanSpeed speed = 4;
} }
// ID: 31
message FanCommandRequest { message FanCommandRequest {
option (id) = 31;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_FAN";
option (no_delay) = true;
fixed32 key = 1; fixed32 key = 1;
bool has_state = 2; bool has_state = 2;
bool state = 3; bool state = 3;
@ -234,8 +331,11 @@ message FanCommandRequest {
} }
// ==================== LIGHT ==================== // ==================== LIGHT ====================
// ID: 15
message ListEntitiesLightResponse { message ListEntitiesLightResponse {
option (id) = 15;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_LIGHT";
string object_id = 1; string object_id = 1;
fixed32 key = 2; fixed32 key = 2;
string name = 3; string name = 3;
@ -249,8 +349,12 @@ message ListEntitiesLightResponse {
float max_mireds = 10; float max_mireds = 10;
repeated string effects = 11; repeated string effects = 11;
} }
// ID: 24
message LightStateResponse { message LightStateResponse {
option (id) = 24;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_LIGHT";
option (no_delay) = true;
fixed32 key = 1; fixed32 key = 1;
bool state = 2; bool state = 2;
float brightness = 3; float brightness = 3;
@ -261,8 +365,12 @@ message LightStateResponse {
float color_temperature = 8; float color_temperature = 8;
string effect = 9; string effect = 9;
} }
// ID: 32
message LightCommandRequest { message LightCommandRequest {
option (id) = 32;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_LIGHT";
option (no_delay) = true;
fixed32 key = 1; fixed32 key = 1;
bool has_state = 2; bool has_state = 2;
bool state = 3; bool state = 3;
@ -285,8 +393,11 @@ message LightCommandRequest {
} }
// ==================== SENSOR ==================== // ==================== SENSOR ====================
// ID: 16
message ListEntitiesSensorResponse { message ListEntitiesSensorResponse {
option (id) = 16;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_SENSOR";
string object_id = 1; string object_id = 1;
fixed32 key = 2; fixed32 key = 2;
string name = 3; string name = 3;
@ -296,15 +407,22 @@ message ListEntitiesSensorResponse {
string unit_of_measurement = 6; string unit_of_measurement = 6;
int32 accuracy_decimals = 7; int32 accuracy_decimals = 7;
} }
// ID: 25
message SensorStateResponse { message SensorStateResponse {
option (id) = 25;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_SENSOR";
option (no_delay) = true;
fixed32 key = 1; fixed32 key = 1;
float state = 2; float state = 2;
} }
// ==================== SWITCH ==================== // ==================== SWITCH ====================
// ID: 17
message ListEntitiesSwitchResponse { message ListEntitiesSwitchResponse {
option (id) = 17;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_SWITCH";
string object_id = 1; string object_id = 1;
fixed32 key = 2; fixed32 key = 2;
string name = 3; string name = 3;
@ -313,20 +431,31 @@ message ListEntitiesSwitchResponse {
string icon = 5; string icon = 5;
bool assumed_state = 6; bool assumed_state = 6;
} }
// ID: 26
message SwitchStateResponse { message SwitchStateResponse {
option (id) = 26;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_SWITCH";
option (no_delay) = true;
fixed32 key = 1; fixed32 key = 1;
bool state = 2; bool state = 2;
} }
// ID: 33
message SwitchCommandRequest { message SwitchCommandRequest {
option (id) = 33;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_SWITCH";
option (no_delay) = true;
fixed32 key = 1; fixed32 key = 1;
bool state = 2; bool state = 2;
} }
// ==================== TEXT SENSOR ==================== // ==================== TEXT SENSOR ====================
// ID: 18
message ListEntitiesTextSensorResponse { message ListEntitiesTextSensorResponse {
option (id) = 18;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_TEXT_SENSOR";
string object_id = 1; string object_id = 1;
fixed32 key = 2; fixed32 key = 2;
string name = 3; string name = 3;
@ -334,29 +463,38 @@ message ListEntitiesTextSensorResponse {
string icon = 5; string icon = 5;
} }
// ID: 27
message TextSensorStateResponse { message TextSensorStateResponse {
option (id) = 27;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_TEXT_SENSOR";
option (no_delay) = true;
fixed32 key = 1; fixed32 key = 1;
string state = 2; string state = 2;
} }
// ==================== SUBSCRIBE LOGS ==================== // ==================== SUBSCRIBE LOGS ====================
enum LogLevel { enum LogLevel {
NONE = 0; LOG_LEVEL_NONE = 0;
ERROR = 1; LOG_LEVEL_ERROR = 1;
WARN = 2; LOG_LEVEL_WARN = 2;
INFO = 3; LOG_LEVEL_INFO = 3;
DEBUG = 4; LOG_LEVEL_DEBUG = 4;
VERBOSE = 5; LOG_LEVEL_VERBOSE = 5;
VERY_VERBOSE = 6; LOG_LEVEL_VERY_VERBOSE = 6;
} }
// ID: 28
message SubscribeLogsRequest { message SubscribeLogsRequest {
option (id) = 28;
option (source) = SOURCE_CLIENT;
LogLevel level = 1; LogLevel level = 1;
bool dump_config = 2; bool dump_config = 2;
} }
// ID: 29
message SubscribeLogsResponse { message SubscribeLogsResponse {
option (id) = 29;
option (source) = SOURCE_SERVER;
option (log) = false;
option (no_delay) = false;
LogLevel level = 1; LogLevel level = 1;
string tag = 2; string tag = 2;
string message = 3; string message = 3;
@ -364,109 +502,153 @@ message SubscribeLogsResponse {
} }
// ==================== HOMEASSISTANT.SERVICE ==================== // ==================== HOMEASSISTANT.SERVICE ====================
// ID: 34 message SubscribeHomeassistantServicesRequest {
message SubscribeServiceCallsRequest { option (id) = 34;
option (source) = SOURCE_CLIENT;
} }
// ID: 35 message HomeassistantServiceMap {
message ServiceCallResponse { string key = 1;
string value = 2;
}
message HomeassistantServiceResponse {
option (id) = 35;
option (source) = SOURCE_SERVER;
option (no_delay) = true;
string service = 1; string service = 1;
map<string, string> data = 2; repeated HomeassistantServiceMap data = 2;
map<string, string> data_template = 3; repeated HomeassistantServiceMap data_template = 3;
map<string, string> variables = 4; repeated HomeassistantServiceMap variables = 4;
bool is_event = 5;
} }
// ==================== IMPORT HOME ASSISTANT STATES ==================== // ==================== IMPORT HOME ASSISTANT STATES ====================
// 1. Client sends SubscribeHomeAssistantStatesRequest // 1. Client sends SubscribeHomeAssistantStatesRequest
// 2. Server responds with zero or more SubscribeHomeAssistantStateResponse (async) // 2. Server responds with zero or more SubscribeHomeAssistantStateResponse (async)
// 3. Client sends HomeAssistantStateResponse for state changes. // 3. Client sends HomeAssistantStateResponse for state changes.
// ID: 38
message SubscribeHomeAssistantStatesRequest { message SubscribeHomeAssistantStatesRequest {
option (id) = 38;
option (source) = SOURCE_CLIENT;
} }
// ID: 39
message SubscribeHomeAssistantStateResponse { message SubscribeHomeAssistantStateResponse {
option (id) = 39;
option (source) = SOURCE_SERVER;
string entity_id = 1; string entity_id = 1;
} }
// ID: 40
message HomeAssistantStateResponse { message HomeAssistantStateResponse {
option (id) = 40;
option (source) = SOURCE_CLIENT;
option (no_delay) = true;
string entity_id = 1; string entity_id = 1;
string state = 2; string state = 2;
} }
// ==================== IMPORT TIME ==================== // ==================== IMPORT TIME ====================
// ID: 36
message GetTimeRequest { message GetTimeRequest {
option (id) = 36;
option (source) = SOURCE_BOTH;
} }
// ID: 37
message GetTimeResponse { message GetTimeResponse {
option (id) = 37;
option (source) = SOURCE_BOTH;
option (no_delay) = true;
fixed32 epoch_seconds = 1; fixed32 epoch_seconds = 1;
} }
// ==================== USER-DEFINES SERVICES ==================== // ==================== USER-DEFINES SERVICES ====================
enum ServiceArgType {
SERVICE_ARG_TYPE_BOOL = 0;
SERVICE_ARG_TYPE_INT = 1;
SERVICE_ARG_TYPE_FLOAT = 2;
SERVICE_ARG_TYPE_STRING = 3;
SERVICE_ARG_TYPE_BOOL_ARRAY = 4;
SERVICE_ARG_TYPE_INT_ARRAY = 5;
SERVICE_ARG_TYPE_FLOAT_ARRAY = 6;
SERVICE_ARG_TYPE_STRING_ARRAY = 7;
}
message ListEntitiesServicesArgument { message ListEntitiesServicesArgument {
string name = 1; string name = 1;
enum Type { ServiceArgType type = 2;
BOOL = 0;
INT = 1;
FLOAT = 2;
STRING = 3;
}
Type type = 2;
} }
// ID: 41
message ListEntitiesServicesResponse { message ListEntitiesServicesResponse {
option (id) = 41;
option (source) = SOURCE_SERVER;
string name = 1; string name = 1;
fixed32 key = 2; fixed32 key = 2;
repeated ListEntitiesServicesArgument args = 3; repeated ListEntitiesServicesArgument args = 3;
} }
message ExecuteServiceArgument { message ExecuteServiceArgument {
bool bool_ = 1; bool bool_ = 1;
int32 int_ = 2; int32 legacy_int = 2;
float float_ = 3; float float_ = 3;
string string_ = 4; string string_ = 4;
// ESPHome 1.14 (api v1.3) make int a signed value
sint32 int_ = 5;
repeated bool bool_array = 6 [packed=false];
repeated sint32 int_array = 7 [packed=false];
repeated float float_array = 8 [packed=false];
repeated string string_array = 9;
} }
// ID: 42
message ExecuteServiceRequest { message ExecuteServiceRequest {
option (id) = 42;
option (source) = SOURCE_CLIENT;
option (no_delay) = true;
fixed32 key = 1; fixed32 key = 1;
repeated ExecuteServiceArgument args = 2; repeated ExecuteServiceArgument args = 2;
} }
// ==================== CAMERA ==================== // ==================== CAMERA ====================
// ID: 43
message ListEntitiesCameraResponse { message ListEntitiesCameraResponse {
option (id) = 43;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_ESP32_CAMERA";
string object_id = 1; string object_id = 1;
fixed32 key = 2; fixed32 key = 2;
string name = 3; string name = 3;
string unique_id = 4; string unique_id = 4;
} }
// ID: 44
message CameraImageResponse { message CameraImageResponse {
option (id) = 44;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_ESP32_CAMERA";
fixed32 key = 1; fixed32 key = 1;
bytes data = 2; bytes data = 2;
bool done = 3; bool done = 3;
} }
// ID: 45
message CameraImageRequest { message CameraImageRequest {
option (id) = 45;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_ESP32_CAMERA";
option (no_delay) = true;
bool single = 1; bool single = 1;
bool stream = 2; bool stream = 2;
} }
// ==================== CLIMATE ==================== // ==================== CLIMATE ====================
enum ClimateMode { enum ClimateMode {
OFF = 0; CLIMATE_MODE_OFF = 0;
AUTO = 1; CLIMATE_MODE_AUTO = 1;
COOL = 2; CLIMATE_MODE_COOL = 2;
HEAT = 3; CLIMATE_MODE_HEAT = 3;
} }
// ID: 46
message ListEntitiesClimateResponse { message ListEntitiesClimateResponse {
option (id) = 46;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_CLIMATE";
string object_id = 1; string object_id = 1;
fixed32 key = 2; fixed32 key = 2;
string name = 3; string name = 3;
@ -480,8 +662,12 @@ message ListEntitiesClimateResponse {
float visual_temperature_step = 10; float visual_temperature_step = 10;
bool supports_away = 11; bool supports_away = 11;
} }
// ID: 47
message ClimateStateResponse { message ClimateStateResponse {
option (id) = 47;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_CLIMATE";
option (no_delay) = true;
fixed32 key = 1; fixed32 key = 1;
ClimateMode mode = 2; ClimateMode mode = 2;
float current_temperature = 3; float current_temperature = 3;
@ -490,8 +676,12 @@ message ClimateStateResponse {
float target_temperature_high = 6; float target_temperature_high = 6;
bool away = 7; bool away = 7;
} }
// ID: 48
message ClimateCommandRequest { message ClimateCommandRequest {
option (id) = 48;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_CLIMATE";
option (no_delay) = true;
fixed32 key = 1; fixed32 key = 1;
bool has_mode = 2; bool has_mode = 2;
ClimateMode mode = 3; ClimateMode mode = 3;

View file

@ -0,0 +1,670 @@
#include "api_connection.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
#include "esphome/core/version.h"
#ifdef USE_DEEP_SLEEP
#include "esphome/components/deep_sleep/deep_sleep_component.h"
#endif
#ifdef USE_HOMEASSISTANT_TIME
#include "esphome/components/homeassistant/time/homeassistant_time.h"
#endif
namespace esphome {
namespace api {
static const char *TAG = "api.connection";
APIConnection::APIConnection(AsyncClient *client, APIServer *parent)
: client_(client), parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
this->client_->onError([](void *s, AsyncClient *c, int8_t error) { ((APIConnection *) s)->on_error_(error); }, this);
this->client_->onDisconnect([](void *s, AsyncClient *c) { ((APIConnection *) s)->on_disconnect_(); }, this);
this->client_->onTimeout([](void *s, AsyncClient *c, uint32_t time) { ((APIConnection *) s)->on_timeout_(time); },
this);
this->client_->onData([](void *s, AsyncClient *c, void *buf,
size_t len) { ((APIConnection *) s)->on_data_(reinterpret_cast<uint8_t *>(buf), len); },
this);
this->send_buffer_.reserve(64);
this->recv_buffer_.reserve(32);
this->client_info_ = this->client_->remoteIP().toString().c_str();
this->last_traffic_ = millis();
}
APIConnection::~APIConnection() { delete this->client_; }
void APIConnection::on_error_(int8_t error) { this->remove_ = true; }
void APIConnection::on_disconnect_() { this->remove_ = true; }
void APIConnection::on_timeout_(uint32_t time) { this->on_fatal_error(); }
void APIConnection::on_data_(uint8_t *buf, size_t len) {
if (len == 0 || buf == nullptr)
return;
this->recv_buffer_.insert(this->recv_buffer_.end(), buf, buf + len);
}
void APIConnection::parse_recv_buffer_() {
if (this->recv_buffer_.empty() || this->remove_)
return;
while (!this->recv_buffer_.empty()) {
if (this->recv_buffer_[0] != 0x00) {
ESP_LOGW(TAG, "Invalid preamble from %s", this->client_info_.c_str());
this->on_fatal_error();
return;
}
uint32_t i = 1;
const uint32_t size = this->recv_buffer_.size();
uint32_t consumed;
auto msg_size_varint = ProtoVarInt::parse(&this->recv_buffer_[i], size - i, &consumed);
if (!msg_size_varint.has_value())
// not enough data there yet
return;
i += consumed;
uint32_t msg_size = msg_size_varint->as_uint32();
auto msg_type_varint = ProtoVarInt::parse(&this->recv_buffer_[i], size - i, &consumed);
if (!msg_type_varint.has_value())
// not enough data there yet
return;
i += consumed;
uint32_t msg_type = msg_type_varint->as_uint32();
if (size - i < msg_size)
// message body not fully received
return;
uint8_t *msg = &this->recv_buffer_[i];
this->read_message(msg_size, msg_type, msg);
if (this->remove_)
return;
// pop front
uint32_t total = i + msg_size;
this->recv_buffer_.erase(this->recv_buffer_.begin(), this->recv_buffer_.begin() + total);
this->last_traffic_ = millis();
}
}
void APIConnection::disconnect_client() {
this->client_->close();
this->remove_ = true;
}
void APIConnection::loop() {
if (this->remove_)
return;
if (this->next_close_) {
this->disconnect_client();
return;
}
if (!network_is_connected()) {
// when network is disconnected force disconnect immediately
// don't wait for timeout
this->on_fatal_error();
return;
}
if (this->client_->disconnected()) {
// failsafe for disconnect logic
this->on_disconnect_();
return;
}
this->parse_recv_buffer_();
this->list_entities_iterator_.advance();
this->initial_state_iterator_.advance();
const uint32_t keepalive = 60000;
if (this->sent_ping_) {
// Disconnect if not responded within 2.5*keepalive
if (millis() - this->last_traffic_ > (keepalive * 5) / 2) {
ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
this->disconnect_client();
}
} else if (millis() - this->last_traffic_ > keepalive) {
this->sent_ping_ = true;
this->send_ping_request(PingRequest());
}
#ifdef USE_ESP32_CAMERA
if (this->image_reader_.available()) {
uint32_t space = this->client_->space();
// reserve 15 bytes for metadata, and at least 64 bytes of data
if (space >= 15 + 64) {
uint32_t to_send = std::min(space - 15, this->image_reader_.available());
auto buffer = this->create_buffer();
// fixed32 key = 1;
buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash());
// bytes data = 2;
buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send);
// bool done = 3;
bool done = this->image_reader_.available() == to_send;
buffer.encode_bool(3, done);
this->set_nodelay(false);
bool success = this->send_buffer(buffer, 44);
if (success) {
this->image_reader_.consume_data(to_send);
}
if (success && done) {
this->image_reader_.return_image();
}
}
}
#endif
}
std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) {
return App.get_name() + component_type + nameable->get_object_id();
}
#ifdef USE_BINARY_SENSOR
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state) {
if (!this->state_subscription_)
return false;
BinarySensorStateResponse resp;
resp.key = binary_sensor->get_object_id_hash();
resp.state = state;
return this->send_binary_sensor_state_response(resp);
}
bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor) {
ListEntitiesBinarySensorResponse msg;
msg.object_id = binary_sensor->get_object_id();
msg.key = binary_sensor->get_object_id_hash();
msg.name = binary_sensor->get_name();
msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor);
msg.device_class = binary_sensor->get_device_class();
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
return this->send_list_entities_binary_sensor_response(msg);
}
#endif
#ifdef USE_COVER
bool APIConnection::send_cover_state(cover::Cover *cover) {
if (!this->state_subscription_)
return false;
auto traits = cover->get_traits();
CoverStateResponse resp{};
resp.key = cover->get_object_id_hash();
resp.legacy_state = (cover->position == cover::COVER_OPEN) ? LEGACY_COVER_STATE_OPEN : LEGACY_COVER_STATE_CLOSED;
resp.position = cover->position;
if (traits.get_supports_tilt())
resp.tilt = cover->tilt;
resp.current_operation = static_cast<CoverOperation>(cover->current_operation);
return this->send_cover_state_response(resp);
}
bool APIConnection::send_cover_info(cover::Cover *cover) {
auto traits = cover->get_traits();
ListEntitiesCoverResponse msg;
msg.key = cover->get_object_id_hash();
msg.object_id = cover->get_object_id();
msg.name = cover->get_name();
msg.unique_id = get_default_unique_id("cover", cover);
msg.assumed_state = traits.get_is_assumed_state();
msg.supports_position = traits.get_supports_position();
msg.supports_tilt = traits.get_supports_tilt();
msg.device_class = cover->get_device_class();
return this->send_list_entities_cover_response(msg);
}
void APIConnection::cover_command(const CoverCommandRequest &msg) {
cover::Cover *cover = App.get_cover_by_key(msg.key);
if (cover == nullptr)
return;
auto call = cover->make_call();
if (msg.has_legacy_command) {
switch (msg.legacy_command) {
case LEGACY_COVER_COMMAND_OPEN:
call.set_command_open();
break;
case LEGACY_COVER_COMMAND_CLOSE:
call.set_command_close();
break;
case LEGACY_COVER_COMMAND_STOP:
call.set_command_stop();
break;
}
}
if (msg.has_position)
call.set_position(msg.position);
if (msg.has_tilt)
call.set_tilt(msg.tilt);
if (msg.stop)
call.set_command_stop();
call.perform();
}
#endif
#ifdef USE_FAN
bool APIConnection::send_fan_state(fan::FanState *fan) {
if (!this->state_subscription_)
return false;
auto traits = fan->get_traits();
FanStateResponse resp{};
resp.key = fan->get_object_id_hash();
resp.state = fan->state;
if (traits.supports_oscillation())
resp.oscillating = fan->oscillating;
if (traits.supports_speed())
resp.speed = static_cast<FanSpeed>(fan->speed);
return this->send_fan_state_response(resp);
}
bool APIConnection::send_fan_info(fan::FanState *fan) {
auto traits = fan->get_traits();
ListEntitiesFanResponse msg;
msg.key = fan->get_object_id_hash();
msg.object_id = fan->get_object_id();
msg.name = fan->get_name();
msg.unique_id = get_default_unique_id("fan", fan);
msg.supports_oscillation = traits.supports_oscillation();
msg.supports_speed = traits.supports_speed();
return this->send_list_entities_fan_response(msg);
}
void APIConnection::fan_command(const FanCommandRequest &msg) {
fan::FanState *fan = App.get_fan_by_key(msg.key);
if (fan == nullptr)
return;
auto call = fan->make_call();
if (msg.has_state)
call.set_state(msg.state);
if (msg.has_oscillating)
call.set_oscillating(msg.oscillating);
if (msg.has_speed)
call.set_speed(static_cast<fan::FanSpeed>(msg.speed));
call.perform();
}
#endif
#ifdef USE_LIGHT
bool APIConnection::send_light_state(light::LightState *light) {
if (!this->state_subscription_)
return false;
auto traits = light->get_traits();
auto values = light->remote_values;
LightStateResponse resp{};
resp.key = light->get_object_id_hash();
resp.state = values.is_on();
if (traits.get_supports_brightness())
resp.brightness = values.get_brightness();
if (traits.get_supports_rgb()) {
resp.red = values.get_red();
resp.green = values.get_green();
resp.blue = values.get_blue();
}
if (traits.get_supports_rgb_white_value())
resp.white = values.get_white();
if (traits.get_supports_color_temperature())
resp.color_temperature = values.get_color_temperature();
if (light->supports_effects())
resp.effect = light->get_effect_name();
return this->send_light_state_response(resp);
}
bool APIConnection::send_light_info(light::LightState *light) {
auto traits = light->get_traits();
ListEntitiesLightResponse msg;
msg.key = light->get_object_id_hash();
msg.object_id = light->get_object_id();
msg.name = light->get_name();
msg.unique_id = get_default_unique_id("light", light);
msg.supports_brightness = traits.get_supports_brightness();
msg.supports_rgb = traits.get_supports_rgb();
msg.supports_white_value = traits.get_supports_rgb_white_value();
msg.supports_color_temperature = traits.get_supports_color_temperature();
if (msg.supports_color_temperature) {
msg.min_mireds = traits.get_min_mireds();
msg.max_mireds = traits.get_max_mireds();
}
if (light->supports_effects()) {
for (auto *effect : light->get_effects())
msg.effects.push_back(effect->get_name());
}
return this->send_list_entities_light_response(msg);
}
void APIConnection::light_command(const LightCommandRequest &msg) {
light::LightState *light = App.get_light_by_key(msg.key);
if (light == nullptr)
return;
auto call = light->make_call();
if (msg.has_state)
call.set_state(msg.state);
if (msg.has_brightness)
call.set_brightness(msg.brightness);
if (msg.has_rgb) {
call.set_red(msg.red);
call.set_green(msg.green);
call.set_blue(msg.blue);
}
if (msg.has_white)
call.set_white(msg.white);
if (msg.has_color_temperature)
call.set_color_temperature(msg.color_temperature);
if (msg.has_transition_length)
call.set_transition_length(msg.transition_length);
if (msg.has_flash_length)
call.set_flash_length(msg.flash_length);
if (msg.has_effect)
call.set_effect(msg.effect);
call.perform();
}
#endif
#ifdef USE_SENSOR
bool APIConnection::send_sensor_state(sensor::Sensor *sensor, float state) {
if (!this->state_subscription_)
return false;
SensorStateResponse resp{};
resp.key = sensor->get_object_id_hash();
resp.state = state;
return this->send_sensor_state_response(resp);
}
bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
ListEntitiesSensorResponse msg;
msg.key = sensor->get_object_id_hash();
msg.object_id = sensor->get_object_id();
msg.name = sensor->get_name();
msg.unique_id = sensor->unique_id();
if (msg.unique_id.empty())
msg.unique_id = get_default_unique_id("sensor", sensor);
msg.icon = sensor->get_icon();
msg.unit_of_measurement = sensor->get_unit_of_measurement();
msg.accuracy_decimals = sensor->get_accuracy_decimals();
return this->send_list_entities_sensor_response(msg);
}
#endif
#ifdef USE_SWITCH
bool APIConnection::send_switch_state(switch_::Switch *a_switch, bool state) {
if (!this->state_subscription_)
return false;
SwitchStateResponse resp{};
resp.key = a_switch->get_object_id_hash();
resp.state = state;
return this->send_switch_state_response(resp);
}
bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
ListEntitiesSwitchResponse msg;
msg.key = a_switch->get_object_id_hash();
msg.object_id = a_switch->get_object_id();
msg.name = a_switch->get_name();
msg.unique_id = get_default_unique_id("switch", a_switch);
msg.icon = a_switch->get_icon();
msg.assumed_state = a_switch->assumed_state();
return this->send_list_entities_switch_response(msg);
}
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
switch_::Switch *a_switch = App.get_switch_by_key(msg.key);
if (a_switch == nullptr)
return;
if (msg.state)
a_switch->turn_on();
else
a_switch->turn_off();
}
#endif
#ifdef USE_TEXT_SENSOR
bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state) {
if (!this->state_subscription_)
return false;
TextSensorStateResponse resp{};
resp.key = text_sensor->get_object_id_hash();
resp.state = std::move(state);
return this->send_text_sensor_state_response(resp);
}
bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) {
ListEntitiesTextSensorResponse msg;
msg.key = text_sensor->get_object_id_hash();
msg.object_id = text_sensor->get_object_id();
msg.name = text_sensor->get_name();
msg.unique_id = text_sensor->unique_id();
if (msg.unique_id.empty())
msg.unique_id = get_default_unique_id("text_sensor", text_sensor);
msg.icon = text_sensor->get_icon();
return this->send_list_entities_text_sensor_response(msg);
}
#endif
#ifdef USE_CLIMATE
bool APIConnection::send_climate_state(climate::Climate *climate) {
if (!this->state_subscription_)
return false;
auto traits = climate->get_traits();
ClimateStateResponse resp{};
resp.key = climate->get_object_id_hash();
resp.mode = static_cast<ClimateMode>(climate->mode);
if (traits.get_supports_current_temperature())
resp.current_temperature = climate->current_temperature;
if (traits.get_supports_two_point_target_temperature()) {
resp.target_temperature_low = climate->target_temperature_low;
resp.target_temperature_high = climate->target_temperature_high;
} else {
resp.target_temperature = climate->target_temperature;
}
if (traits.get_supports_away())
resp.away = climate->away;
return this->send_climate_state_response(resp);
}
bool APIConnection::send_climate_info(climate::Climate *climate) {
auto traits = climate->get_traits();
ListEntitiesClimateResponse msg;
msg.key = climate->get_object_id_hash();
msg.object_id = climate->get_object_id();
msg.name = climate->get_name();
msg.unique_id = get_default_unique_id("climate", climate);
msg.supports_current_temperature = traits.get_supports_current_temperature();
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
for (auto mode : {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
climate::CLIMATE_MODE_HEAT}) {
if (traits.supports_mode(mode))
msg.supported_modes.push_back(static_cast<ClimateMode>(mode));
}
msg.visual_min_temperature = traits.get_visual_min_temperature();
msg.visual_max_temperature = traits.get_visual_max_temperature();
msg.visual_temperature_step = traits.get_visual_temperature_step();
msg.supports_away = traits.get_supports_away();
return this->send_list_entities_climate_response(msg);
}
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
climate::Climate *climate = App.get_climate_by_key(msg.key);
if (climate == nullptr)
return;
auto call = climate->make_call();
if (msg.has_mode)
call.set_mode(static_cast<climate::ClimateMode>(msg.mode));
if (msg.has_target_temperature)
call.set_target_temperature(msg.target_temperature);
if (msg.has_target_temperature_low)
call.set_target_temperature_low(msg.target_temperature_low);
if (msg.has_target_temperature_high)
call.set_target_temperature_high(msg.target_temperature_high);
if (msg.has_away)
call.set_away(msg.away);
call.perform();
}
#endif
#ifdef USE_ESP32_CAMERA
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
if (!this->state_subscription_)
return;
if (this->image_reader_.available())
return;
this->image_reader_.set_image(image);
}
bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
ListEntitiesCameraResponse msg;
msg.key = camera->get_object_id_hash();
msg.object_id = camera->get_object_id();
msg.name = camera->get_name();
msg.unique_id = get_default_unique_id("camera", camera);
return this->send_list_entities_camera_response(msg);
}
void APIConnection::camera_image(const CameraImageRequest &msg) {
if (esp32_camera::global_esp32_camera == nullptr)
return;
if (msg.single)
esp32_camera::global_esp32_camera->request_image();
if (msg.stream)
esp32_camera::global_esp32_camera->request_stream();
}
#endif
#ifdef USE_HOMEASSISTANT_TIME
void APIConnection::on_get_time_response(const GetTimeResponse &value) {
if (homeassistant::global_homeassistant_time != nullptr)
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
}
#endif
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
if (this->log_subscription_ < level)
return false;
this->set_nodelay(false);
// Send raw so that we don't copy too much
auto buffer = this->create_buffer();
// LogLevel level = 1;
buffer.encode_uint32(1, static_cast<uint32_t>(level));
// string tag = 2;
// buffer.encode_string(2, tag, strlen(tag));
// string message = 3;
buffer.encode_string(3, line, strlen(line));
// SubscribeLogsResponse - 29
bool success = this->send_buffer(buffer, 29);
if (!success) {
buffer = this->create_buffer();
// bool send_failed = 4;
buffer.encode_bool(4, true);
return this->send_buffer(buffer, 29);
} else {
return true;
}
}
HelloResponse APIConnection::hello(const HelloRequest &msg) {
this->client_info_ = msg.client_info + " (" + this->client_->remoteIP().toString().c_str();
this->client_info_ += ")";
ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str());
HelloResponse resp;
resp.api_version_major = 1;
resp.api_version_minor = 3;
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
this->connection_state_ = ConnectionState::CONNECTED;
return resp;
}
ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
bool correct = this->parent_->check_password(msg.password);
ConnectResponse resp;
// bool invalid_password = 1;
resp.invalid_password = !correct;
if (correct) {
ESP_LOGD(TAG, "Client '%s' connected successfully!", this->client_info_.c_str());
this->connection_state_ = ConnectionState::AUTHENTICATED;
#ifdef USE_HOMEASSISTANT_TIME
if (homeassistant::global_homeassistant_time != nullptr) {
this->send_time_request();
}
#endif
}
return resp;
}
DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
DeviceInfoResponse resp{};
resp.uses_password = this->parent_->uses_password();
resp.name = App.get_name();
resp.mac_address = get_mac_address_pretty();
resp.esphome_version = ESPHOME_VERSION;
resp.compilation_time = App.get_compilation_time();
#ifdef ARDUINO_BOARD
resp.model = ARDUINO_BOARD;
#endif
#ifdef USE_DEEP_SLEEP
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
#endif
return resp;
}
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
for (auto &it : this->parent_->get_state_subs())
if (it.entity_id == msg.entity_id)
it.callback(msg.state);
}
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
bool found = false;
for (auto *service : this->parent_->get_user_services()) {
if (service->execute_service(msg)) {
found = true;
}
}
if (!found) {
ESP_LOGV(TAG, "Could not find matching service!");
}
}
void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {
for (auto &it : this->parent_->get_state_subs()) {
SubscribeHomeAssistantStateResponse resp;
resp.entity_id = it.entity_id;
if (!this->send_subscribe_home_assistant_state_response(resp)) {
this->on_fatal_error();
return;
}
}
}
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) {
if (this->remove_)
return false;
std::vector<uint8_t> header;
header.push_back(0x00);
ProtoVarInt(buffer.get_buffer()->size()).encode(header);
ProtoVarInt(message_type).encode(header);
size_t needed_space = buffer.get_buffer()->size() + header.size();
if (needed_space > this->client_->space()) {
delay(0);
if (needed_space > this->client_->space()) {
// SubscribeLogsResponse
if (message_type != 29) {
ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
}
delay(0);
return false;
}
}
this->client_->add(reinterpret_cast<char *>(header.data()), header.size());
this->client_->add(reinterpret_cast<char *>(buffer.get_buffer()->data()), buffer.get_buffer()->size());
bool ret = this->client_->send();
return ret;
}
void APIConnection::on_unauthenticated_access() {
ESP_LOGD(TAG, "'%s' tried to access without authentication.", this->client_info_.c_str());
this->on_fatal_error();
}
void APIConnection::on_no_setup_connection() {
ESP_LOGD(TAG, "'%s' tried to access without full connection.", this->client_info_.c_str());
this->on_fatal_error();
}
void APIConnection::on_fatal_error() {
ESP_LOGV(TAG, "Error: Disconnecting %s", this->client_info_.c_str());
this->client_->close();
this->remove_ = true;
}
} // namespace api
} // namespace esphome

View file

@ -0,0 +1,178 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/application.h"
#include "api_pb2.h"
#include "api_pb2_service.h"
#include "api_server.h"
namespace esphome {
namespace api {
class APIConnection : public APIServerConnection {
public:
APIConnection(AsyncClient *client, APIServer *parent);
virtual ~APIConnection();
void disconnect_client();
void loop();
bool send_list_info_done() {
ListEntitiesDoneResponse resp;
return this->send_list_entities_done_response(resp);
}
#ifdef USE_BINARY_SENSOR
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state);
bool send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor);
#endif
#ifdef USE_COVER
bool send_cover_state(cover::Cover *cover);
bool send_cover_info(cover::Cover *cover);
void cover_command(const CoverCommandRequest &msg) override;
#endif
#ifdef USE_FAN
bool send_fan_state(fan::FanState *fan);
bool send_fan_info(fan::FanState *fan);
void fan_command(const FanCommandRequest &msg) override;
#endif
#ifdef USE_LIGHT
bool send_light_state(light::LightState *light);
bool send_light_info(light::LightState *light);
void light_command(const LightCommandRequest &msg) override;
#endif
#ifdef USE_SENSOR
bool send_sensor_state(sensor::Sensor *sensor, float state);
bool send_sensor_info(sensor::Sensor *sensor);
#endif
#ifdef USE_SWITCH
bool send_switch_state(switch_::Switch *a_switch, bool state);
bool send_switch_info(switch_::Switch *a_switch);
void switch_command(const SwitchCommandRequest &msg) override;
#endif
#ifdef USE_TEXT_SENSOR
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state);
bool send_text_sensor_info(text_sensor::TextSensor *text_sensor);
#endif
#ifdef USE_ESP32_CAMERA
void send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
bool send_camera_info(esp32_camera::ESP32Camera *camera);
void camera_image(const CameraImageRequest &msg) override;
#endif
#ifdef USE_CLIMATE
bool send_climate_state(climate::Climate *climate);
bool send_climate_info(climate::Climate *climate);
void climate_command(const ClimateCommandRequest &msg) override;
#endif
bool send_log_message(int level, const char *tag, const char *line);
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
if (!this->service_call_subscription_)
return;
this->send_homeassistant_service_response(call);
}
#ifdef USE_HOMEASSISTANT_TIME
void send_time_request() {
GetTimeRequest req;
this->send_get_time_request(req);
}
#endif
void on_disconnect_response(const DisconnectResponse &value) override {
// we initiated disconnect_client
this->next_close_ = true;
}
void on_ping_response(const PingResponse &value) override {
// we initiated ping
this->sent_ping_ = false;
}
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override;
#ifdef USE_HOMEASSISTANT_TIME
void on_get_time_response(const GetTimeResponse &value) override;
#endif
HelloResponse hello(const HelloRequest &msg) override;
ConnectResponse connect(const ConnectRequest &msg) override;
DisconnectResponse disconnect(const DisconnectRequest &msg) override {
// remote initiated disconnect_client
this->next_close_ = true;
DisconnectResponse resp;
return resp;
}
PingResponse ping(const PingRequest &msg) override { return {}; }
DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override;
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); }
void subscribe_states(const SubscribeStatesRequest &msg) override {
this->state_subscription_ = true;
this->initial_state_iterator_.begin();
}
void subscribe_logs(const SubscribeLogsRequest &msg) override {
this->log_subscription_ = msg.level;
if (msg.dump_config)
App.schedule_dump_config();
}
void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override {
this->service_call_subscription_ = true;
}
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
GetTimeResponse get_time(const GetTimeRequest &msg) override {
// TODO
return {};
}
void execute_service(const ExecuteServiceRequest &msg) override;
bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; }
bool is_connection_setup() override {
return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated();
}
void on_fatal_error() override;
void on_unauthenticated_access() override;
void on_no_setup_connection() override;
ProtoWriteBuffer create_buffer() override {
this->send_buffer_.clear();
return {&this->send_buffer_};
}
bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override;
protected:
friend APIServer;
void on_error_(int8_t error);
void on_disconnect_();
void on_timeout_(uint32_t time);
void on_data_(uint8_t *buf, size_t len);
void parse_recv_buffer_();
void set_nodelay(bool nodelay) override {
if (nodelay == this->current_nodelay_)
return;
this->client_->setNoDelay(nodelay);
this->current_nodelay_ = nodelay;
}
enum class ConnectionState {
WAITING_FOR_HELLO,
CONNECTED,
AUTHENTICATED,
} connection_state_{ConnectionState::WAITING_FOR_HELLO};
bool remove_{false};
std::vector<uint8_t> send_buffer_;
std::vector<uint8_t> recv_buffer_;
std::string client_info_;
#ifdef USE_ESP32_CAMERA
esp32_camera::CameraImageReader image_reader_;
#endif
bool state_subscription_{false};
int log_subscription_{ESPHOME_LOG_LEVEL_NONE};
uint32_t last_traffic_;
bool sent_ping_{false};
bool service_call_subscription_{false};
bool current_nodelay_{false};
bool next_close_{false};
AsyncClient *client_;
APIServer *parent_;
InitialStateIterator initial_state_iterator_;
ListEntitiesIterator list_entities_iterator_;
};
} // namespace api
} // namespace esphome

View file

@ -1,79 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "util.h"
namespace esphome {
namespace api {
enum class APIMessageType {
HELLO_REQUEST = 1,
HELLO_RESPONSE = 2,
CONNECT_REQUEST = 3,
CONNECT_RESPONSE = 4,
DISCONNECT_REQUEST = 5,
DISCONNECT_RESPONSE = 6,
PING_REQUEST = 7,
PING_RESPONSE = 8,
DEVICE_INFO_REQUEST = 9,
DEVICE_INFO_RESPONSE = 10,
LIST_ENTITIES_REQUEST = 11,
LIST_ENTITIES_BINARY_SENSOR_RESPONSE = 12,
LIST_ENTITIES_COVER_RESPONSE = 13,
LIST_ENTITIES_FAN_RESPONSE = 14,
LIST_ENTITIES_LIGHT_RESPONSE = 15,
LIST_ENTITIES_SENSOR_RESPONSE = 16,
LIST_ENTITIES_SWITCH_RESPONSE = 17,
LIST_ENTITIES_TEXT_SENSOR_RESPONSE = 18,
LIST_ENTITIES_SERVICE_RESPONSE = 41,
LIST_ENTITIES_CAMERA_RESPONSE = 43,
LIST_ENTITIES_CLIMATE_RESPONSE = 46,
LIST_ENTITIES_DONE_RESPONSE = 19,
SUBSCRIBE_STATES_REQUEST = 20,
BINARY_SENSOR_STATE_RESPONSE = 21,
COVER_STATE_RESPONSE = 22,
FAN_STATE_RESPONSE = 23,
LIGHT_STATE_RESPONSE = 24,
SENSOR_STATE_RESPONSE = 25,
SWITCH_STATE_RESPONSE = 26,
TEXT_SENSOR_STATE_RESPONSE = 27,
CAMERA_IMAGE_RESPONSE = 44,
CLIMATE_STATE_RESPONSE = 47,
SUBSCRIBE_LOGS_REQUEST = 28,
SUBSCRIBE_LOGS_RESPONSE = 29,
COVER_COMMAND_REQUEST = 30,
FAN_COMMAND_REQUEST = 31,
LIGHT_COMMAND_REQUEST = 32,
SWITCH_COMMAND_REQUEST = 33,
CAMERA_IMAGE_REQUEST = 45,
CLIMATE_COMMAND_REQUEST = 48,
SUBSCRIBE_SERVICE_CALLS_REQUEST = 34,
SERVICE_CALL_RESPONSE = 35,
GET_TIME_REQUEST = 36,
GET_TIME_RESPONSE = 37,
SUBSCRIBE_HOME_ASSISTANT_STATES_REQUEST = 38,
SUBSCRIBE_HOME_ASSISTANT_STATE_RESPONSE = 39,
HOME_ASSISTANT_STATE_RESPONSE = 40,
EXECUTE_SERVICE_REQUEST = 42,
};
class APIMessage {
public:
void decode(const uint8_t *buffer, size_t length);
virtual bool decode_varint(uint32_t field_id, uint32_t value);
virtual bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len);
virtual bool decode_32bit(uint32_t field_id, uint32_t value);
virtual APIMessageType message_type() const = 0;
virtual void encode(APIBuffer &buffer);
};
} // namespace api
} // namespace esphome

View file

@ -0,0 +1,24 @@
syntax = "proto2";
import "google/protobuf/descriptor.proto";
enum APISourceType {
SOURCE_BOTH = 0;
SOURCE_SERVER = 1;
SOURCE_CLIENT = 2;
}
message void {}
extend google.protobuf.MethodOptions {
optional bool needs_setup_connection = 1038 [default=true];
optional bool needs_authentication = 1039 [default=true];
}
extend google.protobuf.MessageOptions {
optional uint32 id = 1036 [default=0];
optional APISourceType source = 1037 [default=SOURCE_BOTH];
optional string ifdef = 1038;
optional bool log = 1039 [default=true];
optional bool no_delay = 1040 [default=false];
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,687 @@
#pragma once
#include "proto.h"
namespace esphome {
namespace api {
enum LegacyCoverState : uint32_t {
LEGACY_COVER_STATE_OPEN = 0,
LEGACY_COVER_STATE_CLOSED = 1,
};
enum CoverOperation : uint32_t {
COVER_OPERATION_IDLE = 0,
COVER_OPERATION_IS_OPENING = 1,
COVER_OPERATION_IS_CLOSING = 2,
};
enum LegacyCoverCommand : uint32_t {
LEGACY_COVER_COMMAND_OPEN = 0,
LEGACY_COVER_COMMAND_CLOSE = 1,
LEGACY_COVER_COMMAND_STOP = 2,
};
enum FanSpeed : uint32_t {
FAN_SPEED_LOW = 0,
FAN_SPEED_MEDIUM = 1,
FAN_SPEED_HIGH = 2,
};
enum LogLevel : uint32_t {
LOG_LEVEL_NONE = 0,
LOG_LEVEL_ERROR = 1,
LOG_LEVEL_WARN = 2,
LOG_LEVEL_INFO = 3,
LOG_LEVEL_DEBUG = 4,
LOG_LEVEL_VERBOSE = 5,
LOG_LEVEL_VERY_VERBOSE = 6,
};
enum ServiceArgType : uint32_t {
SERVICE_ARG_TYPE_BOOL = 0,
SERVICE_ARG_TYPE_INT = 1,
SERVICE_ARG_TYPE_FLOAT = 2,
SERVICE_ARG_TYPE_STRING = 3,
SERVICE_ARG_TYPE_BOOL_ARRAY = 4,
SERVICE_ARG_TYPE_INT_ARRAY = 5,
SERVICE_ARG_TYPE_FLOAT_ARRAY = 6,
SERVICE_ARG_TYPE_STRING_ARRAY = 7,
};
enum ClimateMode : uint32_t {
CLIMATE_MODE_OFF = 0,
CLIMATE_MODE_AUTO = 1,
CLIMATE_MODE_COOL = 2,
CLIMATE_MODE_HEAT = 3,
};
class HelloRequest : public ProtoMessage {
public:
std::string client_info{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class HelloResponse : public ProtoMessage {
public:
uint32_t api_version_major{0}; // NOLINT
uint32_t api_version_minor{0}; // NOLINT
std::string server_info{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ConnectRequest : public ProtoMessage {
public:
std::string password{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class ConnectResponse : public ProtoMessage {
public:
bool invalid_password{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class DisconnectRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
};
class DisconnectResponse : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
};
class PingRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
};
class PingResponse : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
};
class DeviceInfoRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
};
class DeviceInfoResponse : public ProtoMessage {
public:
bool uses_password{false}; // NOLINT
std::string name{}; // NOLINT
std::string mac_address{}; // NOLINT
std::string esphome_version{}; // NOLINT
std::string compilation_time{}; // NOLINT
std::string model{}; // NOLINT
bool has_deep_sleep{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
};
class ListEntitiesDoneResponse : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
};
class SubscribeStatesRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
};
class ListEntitiesBinarySensorResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
std::string device_class{}; // NOLINT
bool is_status_binary_sensor{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BinarySensorStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool state{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesCoverResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
bool assumed_state{false}; // NOLINT
bool supports_position{false}; // NOLINT
bool supports_tilt{false}; // NOLINT
std::string device_class{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class CoverStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
LegacyCoverState legacy_state{}; // NOLINT
float position{0.0f}; // NOLINT
float tilt{0.0f}; // NOLINT
CoverOperation current_operation{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class CoverCommandRequest : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool has_legacy_command{false}; // NOLINT
LegacyCoverCommand legacy_command{}; // NOLINT
bool has_position{false}; // NOLINT
float position{0.0f}; // NOLINT
bool has_tilt{false}; // NOLINT
float tilt{0.0f}; // NOLINT
bool stop{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesFanResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
bool supports_oscillation{false}; // NOLINT
bool supports_speed{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class FanStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool state{false}; // NOLINT
bool oscillating{false}; // NOLINT
FanSpeed speed{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class FanCommandRequest : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool has_state{false}; // NOLINT
bool state{false}; // NOLINT
bool has_speed{false}; // NOLINT
FanSpeed speed{}; // NOLINT
bool has_oscillating{false}; // NOLINT
bool oscillating{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesLightResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
bool supports_brightness{false}; // NOLINT
bool supports_rgb{false}; // NOLINT
bool supports_white_value{false}; // NOLINT
bool supports_color_temperature{false}; // NOLINT
float min_mireds{0.0f}; // NOLINT
float max_mireds{0.0f}; // NOLINT
std::vector<std::string> effects{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class LightStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool state{false}; // NOLINT
float brightness{0.0f}; // NOLINT
float red{0.0f}; // NOLINT
float green{0.0f}; // NOLINT
float blue{0.0f}; // NOLINT
float white{0.0f}; // NOLINT
float color_temperature{0.0f}; // NOLINT
std::string effect{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class LightCommandRequest : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool has_state{false}; // NOLINT
bool state{false}; // NOLINT
bool has_brightness{false}; // NOLINT
float brightness{0.0f}; // NOLINT
bool has_rgb{false}; // NOLINT
float red{0.0f}; // NOLINT
float green{0.0f}; // NOLINT
float blue{0.0f}; // NOLINT
bool has_white{false}; // NOLINT
float white{0.0f}; // NOLINT
bool has_color_temperature{false}; // NOLINT
float color_temperature{0.0f}; // NOLINT
bool has_transition_length{false}; // NOLINT
uint32_t transition_length{0}; // NOLINT
bool has_flash_length{false}; // NOLINT
uint32_t flash_length{0}; // NOLINT
bool has_effect{false}; // NOLINT
std::string effect{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesSensorResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
std::string icon{}; // NOLINT
std::string unit_of_measurement{}; // NOLINT
int32_t accuracy_decimals{0}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class SensorStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
float state{0.0f}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
};
class ListEntitiesSwitchResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
std::string icon{}; // NOLINT
bool assumed_state{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class SwitchStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool state{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class SwitchCommandRequest : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool state{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesTextSensorResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
std::string icon{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class TextSensorStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
std::string state{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class SubscribeLogsRequest : public ProtoMessage {
public:
LogLevel level{}; // NOLINT
bool dump_config{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class SubscribeLogsResponse : public ProtoMessage {
public:
LogLevel level{}; // NOLINT
std::string tag{}; // NOLINT
std::string message{}; // NOLINT
bool send_failed{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class SubscribeHomeassistantServicesRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
};
class HomeassistantServiceMap : public ProtoMessage {
public:
std::string key{}; // NOLINT
std::string value{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class HomeassistantServiceResponse : public ProtoMessage {
public:
std::string service{}; // NOLINT
std::vector<HomeassistantServiceMap> data{}; // NOLINT
std::vector<HomeassistantServiceMap> data_template{}; // NOLINT
std::vector<HomeassistantServiceMap> variables{}; // NOLINT
bool is_event{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class SubscribeHomeAssistantStatesRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
};
class SubscribeHomeAssistantStateResponse : public ProtoMessage {
public:
std::string entity_id{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class HomeAssistantStateResponse : public ProtoMessage {
public:
std::string entity_id{}; // NOLINT
std::string state{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class GetTimeRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
};
class GetTimeResponse : public ProtoMessage {
public:
uint32_t epoch_seconds{0}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
};
class ListEntitiesServicesArgument : public ProtoMessage {
public:
std::string name{}; // NOLINT
ServiceArgType type{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesServicesResponse : public ProtoMessage {
public:
std::string name{}; // NOLINT
uint32_t key{0}; // NOLINT
std::vector<ListEntitiesServicesArgument> args{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class ExecuteServiceArgument : public ProtoMessage {
public:
bool bool_{false}; // NOLINT
int32_t legacy_int{0}; // NOLINT
float float_{0.0f}; // NOLINT
std::string string_{}; // NOLINT
int32_t int_{0}; // NOLINT
std::vector<bool> bool_array{}; // NOLINT
std::vector<int32_t> int_array{}; // NOLINT
std::vector<float> float_array{}; // NOLINT
std::vector<std::string> string_array{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ExecuteServiceRequest : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
std::vector<ExecuteServiceArgument> args{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class ListEntitiesCameraResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class CameraImageResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
std::string data{}; // NOLINT
bool done{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class CameraImageRequest : public ProtoMessage {
public:
bool single{false}; // NOLINT
bool stream{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesClimateResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
bool supports_current_temperature{false}; // NOLINT
bool supports_two_point_target_temperature{false}; // NOLINT
std::vector<ClimateMode> supported_modes{}; // NOLINT
float visual_min_temperature{0.0f}; // NOLINT
float visual_max_temperature{0.0f}; // NOLINT
float visual_temperature_step{0.0f}; // NOLINT
bool supports_away{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ClimateStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
ClimateMode mode{}; // NOLINT
float current_temperature{0.0f}; // NOLINT
float target_temperature{0.0f}; // NOLINT
float target_temperature_low{0.0f}; // NOLINT
float target_temperature_high{0.0f}; // NOLINT
bool away{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ClimateCommandRequest : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool has_mode{false}; // NOLINT
ClimateMode mode{}; // NOLINT
bool has_target_temperature{false}; // NOLINT
float target_temperature{0.0f}; // NOLINT
bool has_target_temperature_low{false}; // NOLINT
float target_temperature_low{0.0f}; // NOLINT
bool has_target_temperature_high{false}; // NOLINT
float target_temperature_high{0.0f}; // NOLINT
bool has_away{false}; // NOLINT
bool away{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
} // namespace api
} // namespace esphome

View file

@ -0,0 +1,582 @@
#include "api_pb2_service.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
static const char *TAG = "api.service";
bool APIServerConnectionBase::send_hello_response(const HelloResponse &msg) {
ESP_LOGVV(TAG, "send_hello_response: %s", msg.dump().c_str());
this->set_nodelay(true);
return this->send_message_<HelloResponse>(msg, 2);
}
bool APIServerConnectionBase::send_connect_response(const ConnectResponse &msg) {
ESP_LOGVV(TAG, "send_connect_response: %s", msg.dump().c_str());
this->set_nodelay(true);
return this->send_message_<ConnectResponse>(msg, 4);
}
bool APIServerConnectionBase::send_disconnect_request(const DisconnectRequest &msg) {
ESP_LOGVV(TAG, "send_disconnect_request: %s", msg.dump().c_str());
this->set_nodelay(true);
return this->send_message_<DisconnectRequest>(msg, 5);
}
bool APIServerConnectionBase::send_disconnect_response(const DisconnectResponse &msg) {
ESP_LOGVV(TAG, "send_disconnect_response: %s", msg.dump().c_str());
this->set_nodelay(true);
return this->send_message_<DisconnectResponse>(msg, 6);
}
bool APIServerConnectionBase::send_ping_request(const PingRequest &msg) {
ESP_LOGVV(TAG, "send_ping_request: %s", msg.dump().c_str());
this->set_nodelay(false);
return this->send_message_<PingRequest>(msg, 7);
}
bool APIServerConnectionBase::send_ping_response(const PingResponse &msg) {
ESP_LOGVV(TAG, "send_ping_response: %s", msg.dump().c_str());
this->set_nodelay(false);
return this->send_message_<PingResponse>(msg, 8);
}
bool APIServerConnectionBase::send_device_info_response(const DeviceInfoResponse &msg) {
ESP_LOGVV(TAG, "send_device_info_response: %s", msg.dump().c_str());
this->set_nodelay(false);
return this->send_message_<DeviceInfoResponse>(msg, 10);
}
bool APIServerConnectionBase::send_list_entities_done_response(const ListEntitiesDoneResponse &msg) {
ESP_LOGVV(TAG, "send_list_entities_done_response: %s", msg.dump().c_str());
this->set_nodelay(true);
return this->send_message_<ListEntitiesDoneResponse>(msg, 19);
}
#ifdef USE_BINARY_SENSOR
bool APIServerConnectionBase::send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg) {
ESP_LOGVV(TAG, "send_list_entities_binary_sensor_response: %s", msg.dump().c_str());
this->set_nodelay(false);
return this->send_message_<ListEntitiesBinarySensorResponse>(msg, 12);
}
#endif
#ifdef USE_BINARY_SENSOR
bool APIServerConnectionBase::send_binary_sensor_state_response(const BinarySensorStateResponse &msg) {
ESP_LOGVV(TAG, "send_binary_sensor_state_response: %s", msg.dump().c_str());
this->set_nodelay(true);
return this->send_message_<BinarySensorStateResponse>(msg, 21);
}
#endif
#ifdef USE_COVER
bool APIServerConnectionBase::send_list_entities_cover_response(const ListEntitiesCoverResponse &msg) {
ESP_LOGVV(TAG, "send_list_entities_cover_response: %s", msg.dump().c_str());
this->set_nodelay(false);
return this->send_message_<ListEntitiesCoverResponse>(msg, 13);
}
#endif
#ifdef USE_COVER
bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse &msg) {
ESP_LOGVV(TAG, "send_cover_state_response: %s", msg.dump().c_str());
this->set_nodelay(true);
return this->send_message_<CoverStateResponse>(msg, 22);
}
#endif
#ifdef USE_COVER
#endif
#ifdef USE_FAN
bool APIServerConnectionBase::send_list_entities_fan_response(const ListEntitiesFanResponse &msg) {
ESP_LOGVV(TAG, "send_list_entities_fan_response: %s", msg.dump().c_str());
this->set_nodelay(false);
return this->send_message_<ListEntitiesFanResponse>(msg, 14);
}
#endif
#ifdef USE_FAN
bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &msg) {
ESP_LOGVV(TAG, "send_fan_state_response: %s", msg.dump().c_str());
this->set_nodelay(true);
return this->send_message_<FanStateResponse>(msg, 23);
}
#endif
#ifdef USE_FAN
#endif
#ifdef USE_LIGHT
bool APIServerConnectionBase::send_list_entities_light_response(const ListEntitiesLightResponse &msg) {
ESP_LOGVV(TAG, "send_list_entities_light_response: %s", msg.dump().c_str());
this->set_nodelay(false);
return this->send_message_<ListEntitiesLightResponse>(msg, 15);
}
#endif
#ifdef USE_LIGHT
bool APIServerConnectionBase::send_light_state_response(const LightStateResponse &msg) {
ESP_LOGVV(TAG, "send_light_state_response: %s", msg.dump().c_str());
this->set_nodelay(true);
return this->send_message_<LightStateResponse>(msg, 24);
}
#endif
#ifdef USE_LIGHT
#endif
#ifdef USE_SENSOR
bool APIServerConnectionBase::send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg) {
ESP_LOGVV(TAG, "send_list_entities_sensor_response: %s", msg.dump().c_str());
this->set_nodelay(false);
return this->send_message_<ListEntitiesSensorResponse>(msg, 16);
}
#endif
#ifdef USE_SENSOR
bool APIServerConnectionBase::send_sensor_state_response(const SensorStateResponse &msg) {
ESP_LOGVV(TAG, "send_sensor_state_response: %s", msg.dump().c_str());
this->set_nodelay(true);
return this->send_message_<SensorStateResponse>(msg, 25);
}
#endif
#ifdef USE_SWITCH
bool APIServerConnectionBase::send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg) {
ESP_LOGVV(TAG, "send_list_entities_switch_response: %s", msg.dump().c_str());
this->set_nodelay(false);
return this->send_message_<ListEntitiesSwitchResponse>(msg, 17);
}
#endif
#ifdef USE_SWITCH
bool APIServerConnectionBase::send_switch_state_response(const SwitchStateResponse &msg) {
ESP_LOGVV(TAG, "send_switch_state_response: %s", msg.dump().c_str());
this->set_nodelay(true);
return this->send_message_<SwitchStateResponse>(msg, 26);
}
#endif
#ifdef USE_SWITCH
#endif
#ifdef USE_TEXT_SENSOR
bool APIServerConnectionBase::send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg) {
ESP_LOGVV(TAG, "send_list_entities_text_sensor_response: %s", msg.dump().c_str());
this->set_nodelay(false);
return this->send_message_<ListEntitiesTextSensorResponse>(msg, 18);
}
#endif
#ifdef USE_TEXT_SENSOR
bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorStateResponse &msg) {
ESP_LOGVV(TAG, "send_text_sensor_state_response: %s", msg.dump().c_str());
this->set_nodelay(true);
return this->send_message_<TextSensorStateResponse>(msg, 27);
}
#endif
bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) {
this->set_nodelay(false);
return this->send_message_<SubscribeLogsResponse>(msg, 29);
}
bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) {
ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str());
this->set_nodelay(true);
return this->send_message_<HomeassistantServiceResponse>(msg, 35);
}
bool APIServerConnectionBase::send_subscribe_home_assistant_state_response(
const SubscribeHomeAssistantStateResponse &msg) {
ESP_LOGVV(TAG, "send_subscribe_home_assistant_state_response: %s", msg.dump().c_str());
this->set_nodelay(false);
return this->send_message_<SubscribeHomeAssistantStateResponse>(msg, 39);
}
bool APIServerConnectionBase::send_get_time_request(const GetTimeRequest &msg) {
ESP_LOGVV(TAG, "send_get_time_request: %s", msg.dump().c_str());
this->set_nodelay(false);
return this->send_message_<GetTimeRequest>(msg, 36);
}
bool APIServerConnectionBase::send_get_time_response(const GetTimeResponse &msg) {
ESP_LOGVV(TAG, "send_get_time_response: %s", msg.dump().c_str());
this->set_nodelay(true);
return this->send_message_<GetTimeResponse>(msg, 37);
}
bool APIServerConnectionBase::send_list_entities_services_response(const ListEntitiesServicesResponse &msg) {
ESP_LOGVV(TAG, "send_list_entities_services_response: %s", msg.dump().c_str());
this->set_nodelay(false);
return this->send_message_<ListEntitiesServicesResponse>(msg, 41);
}
#ifdef USE_ESP32_CAMERA
bool APIServerConnectionBase::send_list_entities_camera_response(const ListEntitiesCameraResponse &msg) {
ESP_LOGVV(TAG, "send_list_entities_camera_response: %s", msg.dump().c_str());
this->set_nodelay(false);
return this->send_message_<ListEntitiesCameraResponse>(msg, 43);
}
#endif
#ifdef USE_ESP32_CAMERA
bool APIServerConnectionBase::send_camera_image_response(const CameraImageResponse &msg) {
ESP_LOGVV(TAG, "send_camera_image_response: %s", msg.dump().c_str());
this->set_nodelay(false);
return this->send_message_<CameraImageResponse>(msg, 44);
}
#endif
#ifdef USE_ESP32_CAMERA
#endif
#ifdef USE_CLIMATE
bool APIServerConnectionBase::send_list_entities_climate_response(const ListEntitiesClimateResponse &msg) {
ESP_LOGVV(TAG, "send_list_entities_climate_response: %s", msg.dump().c_str());
this->set_nodelay(false);
return this->send_message_<ListEntitiesClimateResponse>(msg, 46);
}
#endif
#ifdef USE_CLIMATE
bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResponse &msg) {
ESP_LOGVV(TAG, "send_climate_state_response: %s", msg.dump().c_str());
this->set_nodelay(true);
return this->send_message_<ClimateStateResponse>(msg, 47);
}
#endif
#ifdef USE_CLIMATE
#endif
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
switch (msg_type) {
case 1: {
HelloRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_hello_request: %s", msg.dump().c_str());
this->on_hello_request(msg);
break;
}
case 3: {
ConnectRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_connect_request: %s", msg.dump().c_str());
this->on_connect_request(msg);
break;
}
case 5: {
DisconnectRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_disconnect_request: %s", msg.dump().c_str());
this->on_disconnect_request(msg);
break;
}
case 6: {
DisconnectResponse msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_disconnect_response: %s", msg.dump().c_str());
this->on_disconnect_response(msg);
break;
}
case 7: {
PingRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_ping_request: %s", msg.dump().c_str());
this->on_ping_request(msg);
break;
}
case 8: {
PingResponse msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_ping_response: %s", msg.dump().c_str());
this->on_ping_response(msg);
break;
}
case 9: {
DeviceInfoRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_device_info_request: %s", msg.dump().c_str());
this->on_device_info_request(msg);
break;
}
case 11: {
ListEntitiesRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_list_entities_request: %s", msg.dump().c_str());
this->on_list_entities_request(msg);
break;
}
case 20: {
SubscribeStatesRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_subscribe_states_request: %s", msg.dump().c_str());
this->on_subscribe_states_request(msg);
break;
}
case 28: {
SubscribeLogsRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_subscribe_logs_request: %s", msg.dump().c_str());
this->on_subscribe_logs_request(msg);
break;
}
case 30: {
#ifdef USE_COVER
CoverCommandRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_cover_command_request: %s", msg.dump().c_str());
this->on_cover_command_request(msg);
#endif
break;
}
case 31: {
#ifdef USE_FAN
FanCommandRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_fan_command_request: %s", msg.dump().c_str());
this->on_fan_command_request(msg);
#endif
break;
}
case 32: {
#ifdef USE_LIGHT
LightCommandRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_light_command_request: %s", msg.dump().c_str());
this->on_light_command_request(msg);
#endif
break;
}
case 33: {
#ifdef USE_SWITCH
SwitchCommandRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_switch_command_request: %s", msg.dump().c_str());
this->on_switch_command_request(msg);
#endif
break;
}
case 34: {
SubscribeHomeassistantServicesRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_subscribe_homeassistant_services_request: %s", msg.dump().c_str());
this->on_subscribe_homeassistant_services_request(msg);
break;
}
case 36: {
GetTimeRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_get_time_request: %s", msg.dump().c_str());
this->on_get_time_request(msg);
break;
}
case 37: {
GetTimeResponse msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_get_time_response: %s", msg.dump().c_str());
this->on_get_time_response(msg);
break;
}
case 38: {
SubscribeHomeAssistantStatesRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_subscribe_home_assistant_states_request: %s", msg.dump().c_str());
this->on_subscribe_home_assistant_states_request(msg);
break;
}
case 40: {
HomeAssistantStateResponse msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_home_assistant_state_response: %s", msg.dump().c_str());
this->on_home_assistant_state_response(msg);
break;
}
case 42: {
ExecuteServiceRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_execute_service_request: %s", msg.dump().c_str());
this->on_execute_service_request(msg);
break;
}
case 45: {
#ifdef USE_ESP32_CAMERA
CameraImageRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_camera_image_request: %s", msg.dump().c_str());
this->on_camera_image_request(msg);
#endif
break;
}
case 48: {
#ifdef USE_CLIMATE
ClimateCommandRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_climate_command_request: %s", msg.dump().c_str());
this->on_climate_command_request(msg);
#endif
break;
}
default:
return false;
}
return true;
}
void APIServerConnection::on_hello_request(const HelloRequest &msg) {
HelloResponse ret = this->hello(msg);
if (!this->send_hello_response(ret)) {
this->on_fatal_error();
}
}
void APIServerConnection::on_connect_request(const ConnectRequest &msg) {
ConnectResponse ret = this->connect(msg);
if (!this->send_connect_response(ret)) {
this->on_fatal_error();
}
}
void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) {
DisconnectResponse ret = this->disconnect(msg);
if (!this->send_disconnect_response(ret)) {
this->on_fatal_error();
}
}
void APIServerConnection::on_ping_request(const PingRequest &msg) {
PingResponse ret = this->ping(msg);
if (!this->send_ping_response(ret)) {
this->on_fatal_error();
}
}
void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
DeviceInfoResponse ret = this->device_info(msg);
if (!this->send_device_info_response(ret)) {
this->on_fatal_error();
}
}
void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->list_entities(msg);
}
void APIServerConnection::on_subscribe_states_request(const SubscribeStatesRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->subscribe_states(msg);
}
void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->subscribe_logs(msg);
}
void APIServerConnection::on_subscribe_homeassistant_services_request(
const SubscribeHomeassistantServicesRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->subscribe_homeassistant_services(msg);
}
void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->subscribe_home_assistant_states(msg);
}
void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
GetTimeResponse ret = this->get_time(msg);
if (!this->send_get_time_response(ret)) {
this->on_fatal_error();
}
}
void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->execute_service(msg);
}
#ifdef USE_COVER
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->cover_command(msg);
}
#endif
#ifdef USE_FAN
void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->fan_command(msg);
}
#endif
#ifdef USE_LIGHT
void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->light_command(msg);
}
#endif
#ifdef USE_SWITCH
void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->switch_command(msg);
}
#endif
#ifdef USE_ESP32_CAMERA
void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->camera_image(msg);
}
#endif
#ifdef USE_CLIMATE
void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->climate_command(msg);
}
#endif
} // namespace api
} // namespace esphome

View file

@ -0,0 +1,183 @@
#pragma once
#include "api_pb2.h"
#include "esphome/core/defines.h"
namespace esphome {
namespace api {
class APIServerConnectionBase : public ProtoService {
public:
virtual void on_hello_request(const HelloRequest &value){};
bool send_hello_response(const HelloResponse &msg);
virtual void on_connect_request(const ConnectRequest &value){};
bool send_connect_response(const ConnectResponse &msg);
bool send_disconnect_request(const DisconnectRequest &msg);
virtual void on_disconnect_request(const DisconnectRequest &value){};
bool send_disconnect_response(const DisconnectResponse &msg);
virtual void on_disconnect_response(const DisconnectResponse &value){};
bool send_ping_request(const PingRequest &msg);
virtual void on_ping_request(const PingRequest &value){};
bool send_ping_response(const PingResponse &msg);
virtual void on_ping_response(const PingResponse &value){};
virtual void on_device_info_request(const DeviceInfoRequest &value){};
bool send_device_info_response(const DeviceInfoResponse &msg);
virtual void on_list_entities_request(const ListEntitiesRequest &value){};
bool send_list_entities_done_response(const ListEntitiesDoneResponse &msg);
virtual void on_subscribe_states_request(const SubscribeStatesRequest &value){};
#ifdef USE_BINARY_SENSOR
bool send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg);
#endif
#ifdef USE_BINARY_SENSOR
bool send_binary_sensor_state_response(const BinarySensorStateResponse &msg);
#endif
#ifdef USE_COVER
bool send_list_entities_cover_response(const ListEntitiesCoverResponse &msg);
#endif
#ifdef USE_COVER
bool send_cover_state_response(const CoverStateResponse &msg);
#endif
#ifdef USE_COVER
virtual void on_cover_command_request(const CoverCommandRequest &value){};
#endif
#ifdef USE_FAN
bool send_list_entities_fan_response(const ListEntitiesFanResponse &msg);
#endif
#ifdef USE_FAN
bool send_fan_state_response(const FanStateResponse &msg);
#endif
#ifdef USE_FAN
virtual void on_fan_command_request(const FanCommandRequest &value){};
#endif
#ifdef USE_LIGHT
bool send_list_entities_light_response(const ListEntitiesLightResponse &msg);
#endif
#ifdef USE_LIGHT
bool send_light_state_response(const LightStateResponse &msg);
#endif
#ifdef USE_LIGHT
virtual void on_light_command_request(const LightCommandRequest &value){};
#endif
#ifdef USE_SENSOR
bool send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg);
#endif
#ifdef USE_SENSOR
bool send_sensor_state_response(const SensorStateResponse &msg);
#endif
#ifdef USE_SWITCH
bool send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg);
#endif
#ifdef USE_SWITCH
bool send_switch_state_response(const SwitchStateResponse &msg);
#endif
#ifdef USE_SWITCH
virtual void on_switch_command_request(const SwitchCommandRequest &value){};
#endif
#ifdef USE_TEXT_SENSOR
bool send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg);
#endif
#ifdef USE_TEXT_SENSOR
bool send_text_sensor_state_response(const TextSensorStateResponse &msg);
#endif
virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
bool send_subscribe_logs_response(const SubscribeLogsResponse &msg);
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
bool send_homeassistant_service_response(const HomeassistantServiceResponse &msg);
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
bool send_subscribe_home_assistant_state_response(const SubscribeHomeAssistantStateResponse &msg);
virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
bool send_get_time_request(const GetTimeRequest &msg);
virtual void on_get_time_request(const GetTimeRequest &value){};
bool send_get_time_response(const GetTimeResponse &msg);
virtual void on_get_time_response(const GetTimeResponse &value){};
bool send_list_entities_services_response(const ListEntitiesServicesResponse &msg);
virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
#ifdef USE_ESP32_CAMERA
bool send_list_entities_camera_response(const ListEntitiesCameraResponse &msg);
#endif
#ifdef USE_ESP32_CAMERA
bool send_camera_image_response(const CameraImageResponse &msg);
#endif
#ifdef USE_ESP32_CAMERA
virtual void on_camera_image_request(const CameraImageRequest &value){};
#endif
#ifdef USE_CLIMATE
bool send_list_entities_climate_response(const ListEntitiesClimateResponse &msg);
#endif
#ifdef USE_CLIMATE
bool send_climate_state_response(const ClimateStateResponse &msg);
#endif
#ifdef USE_CLIMATE
virtual void on_climate_command_request(const ClimateCommandRequest &value){};
#endif
protected:
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
};
class APIServerConnection : public APIServerConnectionBase {
public:
virtual HelloResponse hello(const HelloRequest &msg) = 0;
virtual ConnectResponse connect(const ConnectRequest &msg) = 0;
virtual DisconnectResponse disconnect(const DisconnectRequest &msg) = 0;
virtual PingResponse ping(const PingRequest &msg) = 0;
virtual DeviceInfoResponse device_info(const DeviceInfoRequest &msg) = 0;
virtual void list_entities(const ListEntitiesRequest &msg) = 0;
virtual void subscribe_states(const SubscribeStatesRequest &msg) = 0;
virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0;
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
#ifdef USE_COVER
virtual void cover_command(const CoverCommandRequest &msg) = 0;
#endif
#ifdef USE_FAN
virtual void fan_command(const FanCommandRequest &msg) = 0;
#endif
#ifdef USE_LIGHT
virtual void light_command(const LightCommandRequest &msg) = 0;
#endif
#ifdef USE_SWITCH
virtual void switch_command(const SwitchCommandRequest &msg) = 0;
#endif
#ifdef USE_ESP32_CAMERA
virtual void camera_image(const CameraImageRequest &msg) = 0;
#endif
#ifdef USE_CLIMATE
virtual void climate_command(const ClimateCommandRequest &msg) = 0;
#endif
protected:
void on_hello_request(const HelloRequest &msg) override;
void on_connect_request(const ConnectRequest &msg) override;
void on_disconnect_request(const DisconnectRequest &msg) override;
void on_ping_request(const PingRequest &msg) override;
void on_device_info_request(const DeviceInfoRequest &msg) override;
void on_list_entities_request(const ListEntitiesRequest &msg) override;
void on_subscribe_states_request(const SubscribeStatesRequest &msg) override;
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override;
void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
void on_get_time_request(const GetTimeRequest &msg) override;
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
#ifdef USE_COVER
void on_cover_command_request(const CoverCommandRequest &msg) override;
#endif
#ifdef USE_FAN
void on_fan_command_request(const FanCommandRequest &msg) override;
#endif
#ifdef USE_LIGHT
void on_light_command_request(const LightCommandRequest &msg) override;
#endif
#ifdef USE_SWITCH
void on_switch_command_request(const SwitchCommandRequest &msg) override;
#endif
#ifdef USE_ESP32_CAMERA
void on_camera_image_request(const CameraImageRequest &msg) override;
#endif
#ifdef USE_CLIMATE
void on_climate_command_request(const ClimateCommandRequest &msg) override;
#endif
};
} // namespace api
} // namespace esphome

View file

@ -1,19 +1,11 @@
#include <utility>
#include "api_server.h" #include "api_server.h"
#include "basic_messages.h" #include "api_connection.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/util.h" #include "esphome/core/util.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/version.h" #include "esphome/core/version.h"
#ifdef USE_DEEP_SLEEP
#include "esphome/components/deep_sleep/deep_sleep_component.h"
#endif
#ifdef USE_HOMEASSISTANT_TIME
#include "esphome/components/homeassistant/time/homeassistant_time.h"
#endif
#ifdef USE_LOGGER #ifdef USE_LOGGER
#include "esphome/components/logger/logger.h" #include "esphome/components/logger/logger.h"
#endif #endif
@ -210,9 +202,9 @@ void APIServer::set_port(uint16_t port) { this->port_ = port; }
APIServer *global_api_server = nullptr; APIServer *global_api_server = nullptr;
void APIServer::set_password(const std::string &password) { this->password_ = password; } void APIServer::set_password(const std::string &password) { this->password_ = password; }
void APIServer::send_service_call(ServiceCallResponse &call) { void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
for (auto *client : this->clients_) { for (auto *client : this->clients_) {
client->send_service_call(call); client->send_homeassistant_service_call(call);
} }
} }
APIServer::APIServer() { global_api_server = this; } APIServer::APIServer() { global_api_server = this; }
@ -238,965 +230,10 @@ void APIServer::request_time() {
bool APIServer::is_connected() const { return !this->clients_.empty(); } bool APIServer::is_connected() const { return !this->clients_.empty(); }
void APIServer::on_shutdown() { void APIServer::on_shutdown() {
for (auto *c : this->clients_) { for (auto *c : this->clients_) {
c->send_disconnect_request(); c->send_disconnect_request(DisconnectRequest());
} }
delay(10); delay(10);
} }
// APIConnection
APIConnection::APIConnection(AsyncClient *client, APIServer *parent)
: client_(client), parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
this->client_->onError([](void *s, AsyncClient *c, int8_t error) { ((APIConnection *) s)->on_error_(error); }, this);
this->client_->onDisconnect([](void *s, AsyncClient *c) { ((APIConnection *) s)->on_disconnect_(); }, this);
this->client_->onTimeout([](void *s, AsyncClient *c, uint32_t time) { ((APIConnection *) s)->on_timeout_(time); },
this);
this->client_->onData([](void *s, AsyncClient *c, void *buf,
size_t len) { ((APIConnection *) s)->on_data_(reinterpret_cast<uint8_t *>(buf), len); },
this);
this->send_buffer_.reserve(64);
this->recv_buffer_.reserve(32);
this->client_info_ = this->client_->remoteIP().toString().c_str();
this->last_traffic_ = millis();
}
APIConnection::~APIConnection() { delete this->client_; }
void APIConnection::on_error_(int8_t error) {
// disconnect will also be called, nothing to do here
this->remove_ = true;
}
void APIConnection::on_disconnect_() {
// delete self, generally unsafe but not in this case.
this->remove_ = true;
}
void APIConnection::on_timeout_(uint32_t time) { this->disconnect_client(); }
void APIConnection::on_data_(uint8_t *buf, size_t len) {
if (len == 0 || buf == nullptr)
return;
this->recv_buffer_.insert(this->recv_buffer_.end(), buf, buf + len);
// TODO: On ESP32, use queue to notify main thread of new data
}
void APIConnection::parse_recv_buffer_() {
if (this->recv_buffer_.empty() || this->remove_)
return;
while (!this->recv_buffer_.empty()) {
if (this->recv_buffer_[0] != 0x00) {
ESP_LOGW(TAG, "Invalid preamble from %s", this->client_info_.c_str());
this->fatal_error_();
return;
}
uint32_t i = 1;
const uint32_t size = this->recv_buffer_.size();
uint32_t msg_size = 0;
while (i < size) {
const uint8_t dat = this->recv_buffer_[i];
msg_size |= (dat & 0x7F);
// consume
i += 1;
if ((dat & 0x80) == 0x00) {
break;
} else {
msg_size <<= 7;
}
}
if (i == size)
// not enough data there yet
return;
uint32_t msg_type = 0;
bool msg_type_done = false;
while (i < size) {
const uint8_t dat = this->recv_buffer_[i];
msg_type |= (dat & 0x7F);
// consume
i += 1;
if ((dat & 0x80) == 0x00) {
msg_type_done = true;
break;
} else {
msg_type <<= 7;
}
}
if (!msg_type_done)
// not enough data there yet
return;
if (size - i < msg_size)
// message body not fully received
return;
// ESP_LOGVV(TAG, "RECV Message: Size=%u Type=%u", msg_size, msg_type);
if (!this->valid_rx_message_type_(msg_type)) {
ESP_LOGE(TAG, "Not a valid message type: %u", msg_type);
this->fatal_error_();
return;
}
uint8_t *msg = &this->recv_buffer_[i];
this->read_message_(msg_size, msg_type, msg);
if (this->remove_)
return;
// pop front
uint32_t total = i + msg_size;
this->recv_buffer_.erase(this->recv_buffer_.begin(), this->recv_buffer_.begin() + total);
}
}
void APIConnection::read_message_(uint32_t size, uint32_t type, uint8_t *msg) {
this->last_traffic_ = millis();
switch (static_cast<APIMessageType>(type)) {
case APIMessageType::HELLO_REQUEST: {
HelloRequest req;
req.decode(msg, size);
this->on_hello_request_(req);
break;
}
case APIMessageType::HELLO_RESPONSE: {
// Invalid
break;
}
case APIMessageType::CONNECT_REQUEST: {
ConnectRequest req;
req.decode(msg, size);
this->on_connect_request_(req);
break;
}
case APIMessageType::CONNECT_RESPONSE:
// Invalid
break;
case APIMessageType::DISCONNECT_REQUEST: {
DisconnectRequest req;
req.decode(msg, size);
this->on_disconnect_request_(req);
break;
}
case APIMessageType::DISCONNECT_RESPONSE: {
DisconnectResponse req;
req.decode(msg, size);
this->on_disconnect_response_(req);
break;
}
case APIMessageType::PING_REQUEST: {
PingRequest req;
req.decode(msg, size);
this->on_ping_request_(req);
break;
}
case APIMessageType::PING_RESPONSE: {
PingResponse req;
req.decode(msg, size);
this->on_ping_response_(req);
break;
}
case APIMessageType::DEVICE_INFO_REQUEST: {
DeviceInfoRequest req;
req.decode(msg, size);
this->on_device_info_request_(req);
break;
}
case APIMessageType::DEVICE_INFO_RESPONSE: {
// Invalid
break;
}
case APIMessageType::LIST_ENTITIES_REQUEST: {
ListEntitiesRequest req;
req.decode(msg, size);
this->on_list_entities_request_(req);
break;
}
case APIMessageType::LIST_ENTITIES_BINARY_SENSOR_RESPONSE:
case APIMessageType::LIST_ENTITIES_COVER_RESPONSE:
case APIMessageType::LIST_ENTITIES_FAN_RESPONSE:
case APIMessageType::LIST_ENTITIES_LIGHT_RESPONSE:
case APIMessageType::LIST_ENTITIES_SENSOR_RESPONSE:
case APIMessageType::LIST_ENTITIES_SWITCH_RESPONSE:
case APIMessageType::LIST_ENTITIES_TEXT_SENSOR_RESPONSE:
case APIMessageType::LIST_ENTITIES_SERVICE_RESPONSE:
case APIMessageType::LIST_ENTITIES_CAMERA_RESPONSE:
case APIMessageType::LIST_ENTITIES_CLIMATE_RESPONSE:
case APIMessageType::LIST_ENTITIES_DONE_RESPONSE:
// Invalid
break;
case APIMessageType::SUBSCRIBE_STATES_REQUEST: {
SubscribeStatesRequest req;
req.decode(msg, size);
this->on_subscribe_states_request_(req);
break;
}
case APIMessageType::BINARY_SENSOR_STATE_RESPONSE:
case APIMessageType::COVER_STATE_RESPONSE:
case APIMessageType::FAN_STATE_RESPONSE:
case APIMessageType::LIGHT_STATE_RESPONSE:
case APIMessageType::SENSOR_STATE_RESPONSE:
case APIMessageType::SWITCH_STATE_RESPONSE:
case APIMessageType::TEXT_SENSOR_STATE_RESPONSE:
case APIMessageType::CAMERA_IMAGE_RESPONSE:
case APIMessageType::CLIMATE_STATE_RESPONSE:
// Invalid
break;
case APIMessageType::SUBSCRIBE_LOGS_REQUEST: {
SubscribeLogsRequest req;
req.decode(msg, size);
this->on_subscribe_logs_request_(req);
break;
}
case APIMessageType ::SUBSCRIBE_LOGS_RESPONSE:
// Invalid
break;
case APIMessageType::COVER_COMMAND_REQUEST: {
#ifdef USE_COVER
CoverCommandRequest req;
req.decode(msg, size);
this->on_cover_command_request_(req);
#endif
break;
}
case APIMessageType::FAN_COMMAND_REQUEST: {
#ifdef USE_FAN
FanCommandRequest req;
req.decode(msg, size);
this->on_fan_command_request_(req);
#endif
break;
}
case APIMessageType::LIGHT_COMMAND_REQUEST: {
#ifdef USE_LIGHT
LightCommandRequest req;
req.decode(msg, size);
this->on_light_command_request_(req);
#endif
break;
}
case APIMessageType::SWITCH_COMMAND_REQUEST: {
#ifdef USE_SWITCH
SwitchCommandRequest req;
req.decode(msg, size);
this->on_switch_command_request_(req);
#endif
break;
}
case APIMessageType::CLIMATE_COMMAND_REQUEST: {
#ifdef USE_CLIMATE
ClimateCommandRequest req;
req.decode(msg, size);
this->on_climate_command_request_(req);
#endif
break;
}
case APIMessageType::SUBSCRIBE_SERVICE_CALLS_REQUEST: {
SubscribeServiceCallsRequest req;
req.decode(msg, size);
this->on_subscribe_service_calls_request_(req);
break;
}
case APIMessageType::SERVICE_CALL_RESPONSE:
// Invalid
break;
case APIMessageType::GET_TIME_REQUEST:
// Invalid
break;
case APIMessageType::GET_TIME_RESPONSE: {
#ifdef USE_HOMEASSISTANT_TIME
homeassistant::GetTimeResponse req;
req.decode(msg, size);
#endif
break;
}
case APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATES_REQUEST: {
SubscribeHomeAssistantStatesRequest req;
req.decode(msg, size);
this->on_subscribe_home_assistant_states_request_(req);
break;
}
case APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATE_RESPONSE:
// Invalid
break;
case APIMessageType::HOME_ASSISTANT_STATE_RESPONSE: {
HomeAssistantStateResponse req;
req.decode(msg, size);
this->on_home_assistant_state_response_(req);
break;
}
case APIMessageType::EXECUTE_SERVICE_REQUEST: {
ExecuteServiceRequest req;
req.decode(msg, size);
this->on_execute_service_(req);
break;
}
case APIMessageType::CAMERA_IMAGE_REQUEST: {
#ifdef USE_ESP32_CAMERA
CameraImageRequest req;
req.decode(msg, size);
this->on_camera_image_request_(req);
#endif
break;
}
}
}
void APIConnection::on_hello_request_(const HelloRequest &req) {
ESP_LOGVV(TAG, "on_hello_request_(client_info='%s')", req.get_client_info().c_str());
this->client_info_ = req.get_client_info() + " (" + this->client_->remoteIP().toString().c_str();
this->client_info_ += ")";
ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str());
auto buffer = this->get_buffer();
// uint32 api_version_major = 1; -> 1
buffer.encode_uint32(1, 1);
// uint32 api_version_minor = 2; -> 1
buffer.encode_uint32(2, 1);
// string server_info = 3;
buffer.encode_string(3, App.get_name() + " (esphome v" ESPHOME_VERSION ")");
bool success = this->send_buffer(APIMessageType::HELLO_RESPONSE);
if (!success) {
this->fatal_error_();
return;
}
this->connection_state_ = ConnectionState::WAITING_FOR_CONNECT;
}
void APIConnection::on_connect_request_(const ConnectRequest &req) {
ESP_LOGVV(TAG, "on_connect_request_(password='%s')", req.get_password().c_str());
bool correct = this->parent_->check_password(req.get_password());
auto buffer = this->get_buffer();
// bool invalid_password = 1;
buffer.encode_bool(1, !correct);
bool success = this->send_buffer(APIMessageType::CONNECT_RESPONSE);
if (!success) {
this->fatal_error_();
return;
}
if (correct) {
ESP_LOGD(TAG, "Client '%s' connected successfully!", this->client_info_.c_str());
this->connection_state_ = ConnectionState::CONNECTED;
#ifdef USE_HOMEASSISTANT_TIME
if (homeassistant::global_homeassistant_time != nullptr) {
this->send_time_request();
}
#endif
}
}
void APIConnection::on_disconnect_request_(const DisconnectRequest &req) {
ESP_LOGVV(TAG, "on_disconnect_request_");
// remote initiated disconnect_client
if (!this->send_empty_message(APIMessageType::DISCONNECT_RESPONSE)) {
this->fatal_error_();
return;
}
this->disconnect_client();
}
void APIConnection::on_disconnect_response_(const DisconnectResponse &req) {
ESP_LOGVV(TAG, "on_disconnect_response_");
// we initiated disconnect_client
this->disconnect_client();
}
void APIConnection::on_ping_request_(const PingRequest &req) {
ESP_LOGVV(TAG, "on_ping_request_");
PingResponse resp;
this->send_message(resp);
}
void APIConnection::on_ping_response_(const PingResponse &req) {
ESP_LOGVV(TAG, "on_ping_response_");
// we initiated ping
this->sent_ping_ = false;
}
void APIConnection::on_device_info_request_(const DeviceInfoRequest &req) {
ESP_LOGVV(TAG, "on_device_info_request_");
auto buffer = this->get_buffer();
// bool uses_password = 1;
buffer.encode_bool(1, this->parent_->uses_password());
// string name = 2;
buffer.encode_string(2, App.get_name());
// string mac_address = 3;
buffer.encode_string(3, get_mac_address_pretty());
// string esphome_version = 4;
buffer.encode_string(4, ESPHOME_VERSION);
// string compilation_time = 5;
buffer.encode_string(5, App.get_compilation_time());
#ifdef ARDUINO_BOARD
// string model = 6;
buffer.encode_string(6, ARDUINO_BOARD);
#endif
#ifdef USE_DEEP_SLEEP
// bool has_deep_sleep = 7;
buffer.encode_bool(7, deep_sleep::global_has_deep_sleep);
#endif
this->send_buffer(APIMessageType::DEVICE_INFO_RESPONSE);
}
void APIConnection::on_list_entities_request_(const ListEntitiesRequest &req) {
ESP_LOGVV(TAG, "on_list_entities_request_");
this->list_entities_iterator_.begin();
}
void APIConnection::on_subscribe_states_request_(const SubscribeStatesRequest &req) {
ESP_LOGVV(TAG, "on_subscribe_states_request_");
this->state_subscription_ = true;
this->initial_state_iterator_.begin();
}
void APIConnection::on_subscribe_logs_request_(const SubscribeLogsRequest &req) {
ESP_LOGVV(TAG, "on_subscribe_logs_request_");
this->log_subscription_ = req.get_level();
if (req.get_dump_config()) {
App.schedule_dump_config();
}
}
void APIConnection::fatal_error_() {
this->client_->close();
this->remove_ = true;
}
bool APIConnection::valid_rx_message_type_(uint32_t type) {
switch (static_cast<APIMessageType>(type)) {
case APIMessageType::HELLO_RESPONSE:
case APIMessageType::CONNECT_RESPONSE:
return false;
case APIMessageType::HELLO_REQUEST:
return this->connection_state_ == ConnectionState::WAITING_FOR_HELLO;
case APIMessageType::CONNECT_REQUEST:
return this->connection_state_ == ConnectionState::WAITING_FOR_CONNECT;
case APIMessageType::PING_REQUEST:
case APIMessageType::PING_RESPONSE:
case APIMessageType::DISCONNECT_REQUEST:
case APIMessageType::DISCONNECT_RESPONSE:
case APIMessageType::DEVICE_INFO_REQUEST:
if (this->connection_state_ == ConnectionState::WAITING_FOR_CONNECT)
return true;
default:
return this->connection_state_ == ConnectionState::CONNECTED;
}
}
bool APIConnection::send_message(APIMessage &msg) {
this->send_buffer_.clear();
APIBuffer buf(&this->send_buffer_);
msg.encode(buf);
return this->send_buffer(msg.message_type());
}
bool APIConnection::send_empty_message(APIMessageType type) {
this->send_buffer_.clear();
return this->send_buffer(type);
}
void APIConnection::disconnect_client() {
this->client_->close();
this->remove_ = true;
}
void encode_varint(uint8_t *dat, uint8_t *len, uint32_t value) {
if (value <= 0x7F) {
*dat = value;
(*len)++;
return;
}
while (value) {
uint8_t temp = value & 0x7F;
value >>= 7;
if (value) {
*dat = temp | 0x80;
} else {
*dat = temp;
}
dat++;
(*len)++;
}
}
bool APIConnection::send_buffer(APIMessageType type) {
uint8_t header[20];
header[0] = 0x00;
uint8_t header_len = 1;
encode_varint(header + header_len, &header_len, this->send_buffer_.size());
encode_varint(header + header_len, &header_len, static_cast<uint32_t>(type));
size_t needed_space = this->send_buffer_.size() + header_len;
if (needed_space > this->client_->space()) {
delay(0);
if (needed_space > this->client_->space()) {
if (type != APIMessageType::SUBSCRIBE_LOGS_RESPONSE) {
ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
}
delay(0);
return false;
}
}
// char buffer[512];
// uint32_t offset = 0;
// for (int j = 0; j < header_len; j++) {
// offset += snprintf(buffer + offset, 512 - offset, "0x%02X ", header[j]);
// }
// offset += snprintf(buffer + offset, 512 - offset, "| ");
// for (auto &it : this->send_buffer_) {
// int i = snprintf(buffer + offset, 512 - offset, "0x%02X ", it);
// if (i <= 0)
// break;
// offset += i;
// }
// ESP_LOGVV(TAG, "SEND %s", buffer);
this->client_->add(reinterpret_cast<char *>(header), header_len);
this->client_->add(reinterpret_cast<char *>(this->send_buffer_.data()), this->send_buffer_.size());
return this->client_->send();
}
void APIConnection::loop() {
if (!network_is_connected()) {
// when network is disconnected force disconnect immediately
// don't wait for timeout
this->fatal_error_();
return;
}
if (this->client_->disconnected()) {
// failsafe for disconnect logic
this->on_disconnect_();
return;
}
this->parse_recv_buffer_();
this->list_entities_iterator_.advance();
this->initial_state_iterator_.advance();
const uint32_t keepalive = 60000;
if (this->sent_ping_) {
if (millis() - this->last_traffic_ > (keepalive * 3) / 2) {
ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
this->disconnect_client();
}
} else if (millis() - this->last_traffic_ > keepalive) {
this->sent_ping_ = true;
this->send_ping_request();
}
#ifdef USE_ESP32_CAMERA
if (this->image_reader_.available()) {
uint32_t space = this->client_->space();
// reserve 15 bytes for metadata, and at least 64 bytes of data
if (space >= 15 + 64) {
uint32_t to_send = std::min(space - 15, this->image_reader_.available());
auto buffer = this->get_buffer();
// fixed32 key = 1;
buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash());
// bytes data = 2;
buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send);
// bool done = 3;
bool done = this->image_reader_.available() == to_send;
buffer.encode_bool(3, done);
bool success = this->send_buffer(APIMessageType::CAMERA_IMAGE_RESPONSE);
if (success) {
this->image_reader_.consume_data(to_send);
}
if (success && done) {
this->image_reader_.return_image();
}
}
}
#endif
}
#ifdef USE_BINARY_SENSOR
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state) {
if (!this->state_subscription_)
return false;
auto buffer = this->get_buffer();
// fixed32 key = 1;
buffer.encode_fixed32(1, binary_sensor->get_object_id_hash());
// bool state = 2;
buffer.encode_bool(2, state);
return this->send_buffer(APIMessageType::BINARY_SENSOR_STATE_RESPONSE);
}
#endif
#ifdef USE_COVER
bool APIConnection::send_cover_state(cover::Cover *cover) {
if (!this->state_subscription_)
return false;
auto buffer = this->get_buffer();
auto traits = cover->get_traits();
// fixed32 key = 1;
buffer.encode_fixed32(1, cover->get_object_id_hash());
// enum LegacyCoverState {
// OPEN = 0;
// CLOSED = 1;
// }
// LegacyCoverState legacy_state = 2;
uint32_t state = (cover->position == cover::COVER_OPEN) ? 0 : 1;
buffer.encode_uint32(2, state);
// float position = 3;
buffer.encode_float(3, cover->position);
if (traits.get_supports_tilt()) {
// float tilt = 4;
buffer.encode_float(4, cover->tilt);
}
// enum CoverCurrentOperation {
// IDLE = 0;
// IS_OPENING = 1;
// IS_CLOSING = 2;
// }
// CoverCurrentOperation current_operation = 5;
buffer.encode_uint32(5, cover->current_operation);
return this->send_buffer(APIMessageType::COVER_STATE_RESPONSE);
}
#endif
#ifdef USE_FAN
bool APIConnection::send_fan_state(fan::FanState *fan) {
if (!this->state_subscription_)
return false;
auto buffer = this->get_buffer();
// fixed32 key = 1;
buffer.encode_fixed32(1, fan->get_object_id_hash());
// bool state = 2;
buffer.encode_bool(2, fan->state);
// bool oscillating = 3;
if (fan->get_traits().supports_oscillation()) {
buffer.encode_bool(3, fan->oscillating);
}
// enum FanSpeed {
// LOW = 0;
// MEDIUM = 1;
// HIGH = 2;
// }
// FanSpeed speed = 4;
if (fan->get_traits().supports_speed()) {
buffer.encode_uint32(4, fan->speed);
}
return this->send_buffer(APIMessageType::FAN_STATE_RESPONSE);
}
#endif
#ifdef USE_LIGHT
bool APIConnection::send_light_state(light::LightState *light) {
if (!this->state_subscription_)
return false;
auto buffer = this->get_buffer();
auto traits = light->get_traits();
auto values = light->remote_values;
// fixed32 key = 1;
buffer.encode_fixed32(1, light->get_object_id_hash());
// bool state = 2;
buffer.encode_bool(2, values.get_state() != 0.0f);
// float brightness = 3;
if (traits.get_supports_brightness()) {
buffer.encode_float(3, values.get_brightness());
}
if (traits.get_supports_rgb()) {
// float red = 4;
buffer.encode_float(4, values.get_red());
// float green = 5;
buffer.encode_float(5, values.get_green());
// float blue = 6;
buffer.encode_float(6, values.get_blue());
}
// float white = 7;
if (traits.get_supports_rgb_white_value()) {
buffer.encode_float(7, values.get_white());
}
// float color_temperature = 8;
if (traits.get_supports_color_temperature()) {
buffer.encode_float(8, values.get_color_temperature());
}
// string effect = 9;
if (light->supports_effects()) {
buffer.encode_string(9, light->get_effect_name());
}
return this->send_buffer(APIMessageType::LIGHT_STATE_RESPONSE);
}
#endif
#ifdef USE_SENSOR
bool APIConnection::send_sensor_state(sensor::Sensor *sensor, float state) {
if (!this->state_subscription_)
return false;
auto buffer = this->get_buffer();
// fixed32 key = 1;
buffer.encode_fixed32(1, sensor->get_object_id_hash());
// float state = 2;
buffer.encode_float(2, state);
return this->send_buffer(APIMessageType::SENSOR_STATE_RESPONSE);
}
#endif
#ifdef USE_SWITCH
bool APIConnection::send_switch_state(switch_::Switch *a_switch, bool state) {
if (!this->state_subscription_)
return false;
auto buffer = this->get_buffer();
// fixed32 key = 1;
buffer.encode_fixed32(1, a_switch->get_object_id_hash());
// bool state = 2;
buffer.encode_bool(2, state);
return this->send_buffer(APIMessageType::SWITCH_STATE_RESPONSE);
}
#endif
#ifdef USE_TEXT_SENSOR
bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state) {
if (!this->state_subscription_)
return false;
auto buffer = this->get_buffer();
// fixed32 key = 1;
buffer.encode_fixed32(1, text_sensor->get_object_id_hash());
// string state = 2;
buffer.encode_string(2, state);
return this->send_buffer(APIMessageType::TEXT_SENSOR_STATE_RESPONSE);
}
#endif
#ifdef USE_CLIMATE
bool APIConnection::send_climate_state(climate::Climate *climate) {
if (!this->state_subscription_)
return false;
auto buffer = this->get_buffer();
auto traits = climate->get_traits();
// fixed32 key = 1;
buffer.encode_fixed32(1, climate->get_object_id_hash());
// ClimateMode mode = 2;
buffer.encode_uint32(2, static_cast<uint32_t>(climate->mode));
// float current_temperature = 3;
if (traits.get_supports_current_temperature()) {
buffer.encode_float(3, climate->current_temperature);
}
if (traits.get_supports_two_point_target_temperature()) {
// float target_temperature_low = 5;
buffer.encode_float(5, climate->target_temperature_low);
// float target_temperature_high = 6;
buffer.encode_float(6, climate->target_temperature_high);
} else {
// float target_temperature = 4;
buffer.encode_float(4, climate->target_temperature);
}
// bool away = 7;
if (traits.get_supports_away()) {
buffer.encode_bool(7, climate->away);
}
return this->send_buffer(APIMessageType::CLIMATE_STATE_RESPONSE);
}
#endif
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
if (this->log_subscription_ < level)
return false;
auto buffer = this->get_buffer();
// LogLevel level = 1;
buffer.encode_uint32(1, static_cast<uint32_t>(level));
// string tag = 2;
// buffer.encode_string(2, tag, strlen(tag));
// string message = 3;
buffer.encode_string(3, line, strlen(line));
bool success = this->send_buffer(APIMessageType::SUBSCRIBE_LOGS_RESPONSE);
if (!success) {
buffer = this->get_buffer();
// bool send_failed = 4;
buffer.encode_bool(4, true);
return this->send_buffer(APIMessageType::SUBSCRIBE_LOGS_RESPONSE);
} else {
return true;
}
}
bool APIConnection::send_disconnect_request() {
DisconnectRequest req;
return this->send_message(req);
}
bool APIConnection::send_ping_request() {
ESP_LOGVV(TAG, "Sending ping...");
PingRequest req;
return this->send_message(req);
}
#ifdef USE_COVER
void APIConnection::on_cover_command_request_(const CoverCommandRequest &req) {
ESP_LOGVV(TAG, "on_cover_command_request_");
cover::Cover *cover = App.get_cover_by_key(req.get_key());
if (cover == nullptr)
return;
auto call = cover->make_call();
if (req.get_legacy_command().has_value()) {
auto cmd = *req.get_legacy_command();
switch (cmd) {
case LEGACY_COVER_COMMAND_OPEN:
call.set_command_open();
break;
case LEGACY_COVER_COMMAND_CLOSE:
call.set_command_close();
break;
case LEGACY_COVER_COMMAND_STOP:
call.set_command_stop();
break;
}
}
if (req.get_position().has_value()) {
auto pos = *req.get_position();
call.set_position(pos);
}
if (req.get_tilt().has_value()) {
auto tilt = *req.get_tilt();
call.set_tilt(tilt);
}
if (req.get_stop()) {
call.set_command_stop();
}
call.perform();
}
#endif
#ifdef USE_FAN
void APIConnection::on_fan_command_request_(const FanCommandRequest &req) {
ESP_LOGVV(TAG, "on_fan_command_request_");
fan::FanState *fan = App.get_fan_by_key(req.get_key());
if (fan == nullptr)
return;
auto call = fan->make_call();
call.set_state(req.get_state());
call.set_oscillating(req.get_oscillating());
call.set_speed(req.get_speed());
call.perform();
}
#endif
#ifdef USE_LIGHT
void APIConnection::on_light_command_request_(const LightCommandRequest &req) {
ESP_LOGVV(TAG, "on_light_command_request_");
light::LightState *light = App.get_light_by_key(req.get_key());
if (light == nullptr)
return;
auto call = light->make_call();
call.set_state(req.get_state());
call.set_brightness(req.get_brightness());
call.set_red(req.get_red());
call.set_green(req.get_green());
call.set_blue(req.get_blue());
call.set_white(req.get_white());
call.set_color_temperature(req.get_color_temperature());
call.set_transition_length(req.get_transition_length());
call.set_flash_length(req.get_flash_length());
call.set_effect(req.get_effect());
call.perform();
}
#endif
#ifdef USE_SWITCH
void APIConnection::on_switch_command_request_(const SwitchCommandRequest &req) {
ESP_LOGVV(TAG, "on_switch_command_request_");
switch_::Switch *a_switch = App.get_switch_by_key(req.get_key());
if (a_switch == nullptr || a_switch->is_internal())
return;
if (req.get_state()) {
a_switch->turn_on();
} else {
a_switch->turn_off();
}
}
#endif
#ifdef USE_CLIMATE
void APIConnection::on_climate_command_request_(const ClimateCommandRequest &req) {
ESP_LOGVV(TAG, "on_climate_command_request_");
climate::Climate *climate = App.get_climate_by_key(req.get_key());
if (climate == nullptr)
return;
auto call = climate->make_call();
if (req.get_mode().has_value())
call.set_mode(*req.get_mode());
if (req.get_target_temperature().has_value())
call.set_target_temperature(*req.get_target_temperature());
if (req.get_target_temperature_low().has_value())
call.set_target_temperature_low(*req.get_target_temperature_low());
if (req.get_target_temperature_high().has_value())
call.set_target_temperature_high(*req.get_target_temperature_high());
if (req.get_away().has_value())
call.set_away(*req.get_away());
call.perform();
}
#endif
void APIConnection::on_subscribe_service_calls_request_(const SubscribeServiceCallsRequest &req) {
this->service_call_subscription_ = true;
}
void APIConnection::send_service_call(ServiceCallResponse &call) {
if (!this->service_call_subscription_)
return;
this->send_message(call);
}
void APIConnection::on_subscribe_home_assistant_states_request_(const SubscribeHomeAssistantStatesRequest &req) {
for (auto &it : this->parent_->get_state_subs()) {
auto buffer = this->get_buffer();
// string entity_id = 1;
buffer.encode_string(1, it.entity_id);
this->send_buffer(APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATE_RESPONSE);
}
}
void APIConnection::on_home_assistant_state_response_(const HomeAssistantStateResponse &req) {
for (auto &it : this->parent_->get_state_subs()) {
if (it.entity_id == req.get_entity_id()) {
it.callback(req.get_state());
}
}
}
void APIConnection::on_execute_service_(const ExecuteServiceRequest &req) {
ESP_LOGVV(TAG, "on_execute_service_");
bool found = false;
for (auto *service : this->parent_->get_user_services()) {
if (service->execute_service(req)) {
found = true;
}
}
if (!found) {
ESP_LOGV(TAG, "Could not find matching service!");
}
}
APIBuffer APIConnection::get_buffer() {
this->send_buffer_.clear();
return {&this->send_buffer_};
}
#ifdef USE_HOMEASSISTANT_TIME
void APIConnection::send_time_request() { this->send_empty_message(APIMessageType::GET_TIME_REQUEST); }
#endif
#ifdef USE_ESP32_CAMERA
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
if (!this->state_subscription_)
return;
if (this->image_reader_.available())
return;
this->image_reader_.set_image(image);
}
#endif
#ifdef USE_ESP32_CAMERA
void APIConnection::on_camera_image_request_(const CameraImageRequest &req) {
if (esp32_camera::global_esp32_camera == nullptr)
return;
ESP_LOGV(TAG, "on_camera_image_request_ stream=%s single=%s", YESNO(req.get_stream()), YESNO(req.get_single()));
if (req.get_single()) {
esp32_camera::global_esp32_camera->request_image();
}
if (req.get_stream()) {
esp32_camera::global_esp32_camera->request_stream();
}
}
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -4,14 +4,12 @@
#include "esphome/core/controller.h" #include "esphome/core/controller.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "api_pb2.h"
#include "api_pb2_service.h"
#include "util.h" #include "util.h"
#include "api_message.h"
#include "basic_messages.h"
#include "list_entities.h" #include "list_entities.h"
#include "subscribe_state.h" #include "subscribe_state.h"
#include "subscribe_logs.h" #include "homeassistant_service.h"
#include "command_messages.h"
#include "service_call_message.h"
#include "user_services.h" #include "user_services.h"
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
@ -24,130 +22,6 @@
namespace esphome { namespace esphome {
namespace api { namespace api {
class APIServer;
class APIConnection {
public:
APIConnection(AsyncClient *client, APIServer *parent);
~APIConnection();
void disconnect_client();
APIBuffer get_buffer();
bool send_buffer(APIMessageType type);
bool send_message(APIMessage &msg);
bool send_empty_message(APIMessageType type);
void loop();
#ifdef USE_BINARY_SENSOR
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state);
#endif
#ifdef USE_COVER
bool send_cover_state(cover::Cover *cover);
#endif
#ifdef USE_FAN
bool send_fan_state(fan::FanState *fan);
#endif
#ifdef USE_LIGHT
bool send_light_state(light::LightState *light);
#endif
#ifdef USE_SENSOR
bool send_sensor_state(sensor::Sensor *sensor, float state);
#endif
#ifdef USE_SWITCH
bool send_switch_state(switch_::Switch *a_switch, bool state);
#endif
#ifdef USE_TEXT_SENSOR
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state);
#endif
#ifdef USE_ESP32_CAMERA
void send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
#endif
#ifdef USE_CLIMATE
bool send_climate_state(climate::Climate *climate);
#endif
bool send_log_message(int level, const char *tag, const char *line);
bool send_disconnect_request();
bool send_ping_request();
void send_service_call(ServiceCallResponse &call);
#ifdef USE_HOMEASSISTANT_TIME
void send_time_request();
#endif
protected:
friend APIServer;
void on_error_(int8_t error);
void on_disconnect_();
void on_timeout_(uint32_t time);
void on_data_(uint8_t *buf, size_t len);
void fatal_error_();
bool valid_rx_message_type_(uint32_t msg_type);
void read_message_(uint32_t size, uint32_t type, uint8_t *msg);
void parse_recv_buffer_();
// request types
void on_hello_request_(const HelloRequest &req);
void on_connect_request_(const ConnectRequest &req);
void on_disconnect_request_(const DisconnectRequest &req);
void on_disconnect_response_(const DisconnectResponse &req);
void on_ping_request_(const PingRequest &req);
void on_ping_response_(const PingResponse &req);
void on_device_info_request_(const DeviceInfoRequest &req);
void on_list_entities_request_(const ListEntitiesRequest &req);
void on_subscribe_states_request_(const SubscribeStatesRequest &req);
void on_subscribe_logs_request_(const SubscribeLogsRequest &req);
#ifdef USE_COVER
void on_cover_command_request_(const CoverCommandRequest &req);
#endif
#ifdef USE_FAN
void on_fan_command_request_(const FanCommandRequest &req);
#endif
#ifdef USE_LIGHT
void on_light_command_request_(const LightCommandRequest &req);
#endif
#ifdef USE_SWITCH
void on_switch_command_request_(const SwitchCommandRequest &req);
#endif
#ifdef USE_CLIMATE
void on_climate_command_request_(const ClimateCommandRequest &req);
#endif
void on_subscribe_service_calls_request_(const SubscribeServiceCallsRequest &req);
void on_subscribe_home_assistant_states_request_(const SubscribeHomeAssistantStatesRequest &req);
void on_home_assistant_state_response_(const HomeAssistantStateResponse &req);
void on_execute_service_(const ExecuteServiceRequest &req);
#ifdef USE_ESP32_CAMERA
void on_camera_image_request_(const CameraImageRequest &req);
#endif
enum class ConnectionState {
WAITING_FOR_HELLO,
WAITING_FOR_CONNECT,
CONNECTED,
} connection_state_{ConnectionState::WAITING_FOR_HELLO};
bool remove_{false};
std::vector<uint8_t> send_buffer_;
std::vector<uint8_t> recv_buffer_;
std::string client_info_;
#ifdef USE_ESP32_CAMERA
esp32_camera::CameraImageReader image_reader_;
#endif
bool state_subscription_{false};
int log_subscription_{ESPHOME_LOG_LEVEL_NONE};
uint32_t last_traffic_;
bool sent_ping_{false};
bool service_call_subscription_{false};
AsyncClient *client_;
APIServer *parent_;
InitialStateIterator initial_state_iterator_;
ListEntitiesIterator list_entities_iterator_;
};
template<typename... Ts> class HomeAssistantServiceCallAction;
class APIServer : public Component, public Controller { class APIServer : public Component, public Controller {
public: public:
APIServer(); APIServer();
@ -187,7 +61,7 @@ class APIServer : public Component, public Controller {
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
void on_climate_update(climate::Climate *obj) override; void on_climate_update(climate::Climate *obj) override;
#endif #endif
void send_service_call(ServiceCallResponse &call); void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME
void request_time(); void request_time();
@ -217,22 +91,6 @@ class APIServer : public Component, public Controller {
extern APIServer *global_api_server; extern APIServer *global_api_server;
template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
public:
explicit HomeAssistantServiceCallAction(APIServer *parent) : parent_(parent) {}
void set_service(const std::string &service) { this->resp_.set_service(service); }
void set_data(const std::vector<KeyValuePair> &data) { this->resp_.set_data(data); }
void set_data_template(const std::vector<KeyValuePair> &data_template) {
this->resp_.set_data_template(data_template);
}
void set_variables(const std::vector<TemplatableKeyValuePair> &variables) { this->resp_.set_variables(variables); }
void play(Ts... x) override { this->parent_->send_service_call(this->resp_); }
protected:
APIServer *parent_;
ServiceCallResponse resp_;
};
template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> { template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
public: public:
bool check(Ts... x) override { return global_api_server->is_connected(); } bool check(Ts... x) override { return global_api_server->is_connected(); }

View file

@ -1,57 +0,0 @@
#include "basic_messages.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
// Hello
bool HelloRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
switch (field_id) {
case 1: // string client_info = 1;
this->client_info_ = as_string(value, len);
return true;
default:
return false;
}
}
const std::string &HelloRequest::get_client_info() const { return this->client_info_; }
void HelloRequest::set_client_info(const std::string &client_info) { this->client_info_ = client_info; }
APIMessageType HelloRequest::message_type() const { return APIMessageType::HELLO_REQUEST; }
// Connect
bool ConnectRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
switch (field_id) {
case 1: // string password = 1;
this->password_ = as_string(value, len);
return true;
default:
return false;
}
}
const std::string &ConnectRequest::get_password() const { return this->password_; }
void ConnectRequest::set_password(const std::string &password) { this->password_ = password; }
APIMessageType ConnectRequest::message_type() const { return APIMessageType::CONNECT_REQUEST; }
APIMessageType DeviceInfoRequest::message_type() const { return APIMessageType::DEVICE_INFO_REQUEST; }
APIMessageType DisconnectRequest::message_type() const { return APIMessageType::DISCONNECT_REQUEST; }
bool DisconnectRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
switch (field_id) {
case 1: // string reason = 1;
this->reason_ = as_string(value, len);
return true;
default:
return false;
}
}
const std::string &DisconnectRequest::get_reason() const { return this->reason_; }
void DisconnectRequest::set_reason(const std::string &reason) { this->reason_ = reason; }
void DisconnectRequest::encode(APIBuffer &buffer) {
// string reason = 1;
buffer.encode_string(1, this->reason_);
}
APIMessageType DisconnectResponse::message_type() const { return APIMessageType::DISCONNECT_RESPONSE; }
APIMessageType PingRequest::message_type() const { return APIMessageType::PING_REQUEST; }
APIMessageType PingResponse::message_type() const { return APIMessageType::PING_RESPONSE; }
} // namespace api
} // namespace esphome

View file

@ -1,63 +0,0 @@
#pragma once
#include "api_message.h"
namespace esphome {
namespace api {
class HelloRequest : public APIMessage {
public:
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
const std::string &get_client_info() const;
void set_client_info(const std::string &client_info);
APIMessageType message_type() const override;
protected:
std::string client_info_;
};
class ConnectRequest : public APIMessage {
public:
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
const std::string &get_password() const;
void set_password(const std::string &password);
APIMessageType message_type() const override;
protected:
std::string password_;
};
class DeviceInfoRequest : public APIMessage {
public:
APIMessageType message_type() const override;
};
class DisconnectRequest : public APIMessage {
public:
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
void encode(APIBuffer &buffer) override;
APIMessageType message_type() const override;
const std::string &get_reason() const;
void set_reason(const std::string &reason);
protected:
std::string reason_;
};
class DisconnectResponse : public APIMessage {
public:
APIMessageType message_type() const override;
};
class PingRequest : public APIMessage {
public:
APIMessageType message_type() const override;
};
class PingResponse : public APIMessage {
public:
APIMessageType message_type() const override;
};
} // namespace api
} // namespace esphome

View file

@ -1,417 +0,0 @@
#include "command_messages.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
#ifdef USE_COVER
bool CoverCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 2:
// bool has_legacy_command = 2;
this->has_legacy_command_ = value;
return true;
case 3:
// enum LegacyCoverCommand {
// OPEN = 0;
// CLOSE = 1;
// STOP = 2;
// }
// LegacyCoverCommand legacy_command_ = 3;
this->legacy_command_ = static_cast<LegacyCoverCommand>(value);
return true;
case 4:
// bool has_position = 4;
this->has_position_ = value;
return true;
case 6:
// bool has_tilt = 6;
this->has_tilt_ = value;
return true;
case 8:
// bool stop = 8;
this->stop_ = value;
default:
return false;
}
}
bool CoverCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 1:
// fixed32 key = 1;
this->key_ = value;
return true;
case 5:
// float position = 5;
this->position_ = as_float(value);
return true;
case 7:
// float tilt = 7;
this->tilt_ = as_float(value);
return true;
default:
return false;
}
}
APIMessageType CoverCommandRequest::message_type() const { return APIMessageType ::COVER_COMMAND_REQUEST; }
uint32_t CoverCommandRequest::get_key() const { return this->key_; }
optional<LegacyCoverCommand> CoverCommandRequest::get_legacy_command() const {
if (!this->has_legacy_command_)
return {};
return this->legacy_command_;
}
optional<float> CoverCommandRequest::get_position() const {
if (!this->has_position_)
return {};
return this->position_;
}
optional<float> CoverCommandRequest::get_tilt() const {
if (!this->has_tilt_)
return {};
return this->tilt_;
}
#endif
#ifdef USE_FAN
bool FanCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 2:
// bool has_state = 2;
this->has_state_ = value;
return true;
case 3:
// bool state = 3;
this->state_ = value;
return true;
case 4:
// bool has_speed = 4;
this->has_speed_ = value;
return true;
case 5:
// FanSpeed speed = 5;
this->speed_ = static_cast<fan::FanSpeed>(value);
return true;
case 6:
// bool has_oscillating = 6;
this->has_oscillating_ = value;
return true;
case 7:
// bool oscillating = 7;
this->oscillating_ = value;
return true;
default:
return false;
}
}
bool FanCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 1:
// fixed32 key = 1;
this->key_ = value;
return true;
default:
return false;
}
}
APIMessageType FanCommandRequest::message_type() const { return APIMessageType::FAN_COMMAND_REQUEST; }
uint32_t FanCommandRequest::get_key() const { return this->key_; }
optional<bool> FanCommandRequest::get_state() const {
if (!this->has_state_)
return {};
return this->state_;
}
optional<fan::FanSpeed> FanCommandRequest::get_speed() const {
if (!this->has_speed_)
return {};
return this->speed_;
}
optional<bool> FanCommandRequest::get_oscillating() const {
if (!this->has_oscillating_)
return {};
return this->oscillating_;
}
#endif
#ifdef USE_LIGHT
bool LightCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 2:
// bool has_state = 2;
this->has_state_ = value;
return true;
case 3:
// bool state = 3;
this->state_ = value;
return true;
case 4:
// bool has_brightness = 4;
this->has_brightness_ = value;
return true;
case 6:
// bool has_rgb = 6;
this->has_rgb_ = value;
return true;
case 10:
// bool has_white = 10;
this->has_white_ = value;
return true;
case 12:
// bool has_color_temperature = 12;
this->has_color_temperature_ = value;
return true;
case 14:
// bool has_transition_length = 14;
this->has_transition_length_ = value;
return true;
case 15:
// uint32 transition_length = 15;
this->transition_length_ = value;
return true;
case 16:
// bool has_flash_length = 16;
this->has_flash_length_ = value;
return true;
case 17:
// uint32 flash_length = 17;
this->flash_length_ = value;
return true;
case 18:
// bool has_effect = 18;
this->has_effect_ = value;
return true;
default:
return false;
}
}
bool LightCommandRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
switch (field_id) {
case 19:
// string effect = 19;
this->effect_ = as_string(value, len);
return true;
default:
return false;
}
}
bool LightCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 1:
// fixed32 key = 1;
this->key_ = value;
return true;
case 5:
// float brightness = 5;
this->brightness_ = as_float(value);
return true;
case 7:
// float red = 7;
this->red_ = as_float(value);
return true;
case 8:
// float green = 8;
this->green_ = as_float(value);
return true;
case 9:
// float blue = 9;
this->blue_ = as_float(value);
return true;
case 11:
// float white = 11;
this->white_ = as_float(value);
return true;
case 13:
// float color_temperature = 13;
this->color_temperature_ = as_float(value);
return true;
default:
return false;
}
}
APIMessageType LightCommandRequest::message_type() const { return APIMessageType::LIGHT_COMMAND_REQUEST; }
uint32_t LightCommandRequest::get_key() const { return this->key_; }
optional<bool> LightCommandRequest::get_state() const {
if (!this->has_state_)
return {};
return this->state_;
}
optional<float> LightCommandRequest::get_brightness() const {
if (!this->has_brightness_)
return {};
return this->brightness_;
}
optional<float> LightCommandRequest::get_red() const {
if (!this->has_rgb_)
return {};
return this->red_;
}
optional<float> LightCommandRequest::get_green() const {
if (!this->has_rgb_)
return {};
return this->green_;
}
optional<float> LightCommandRequest::get_blue() const {
if (!this->has_rgb_)
return {};
return this->blue_;
}
optional<float> LightCommandRequest::get_white() const {
if (!this->has_white_)
return {};
return this->white_;
}
optional<float> LightCommandRequest::get_color_temperature() const {
if (!this->has_color_temperature_)
return {};
return this->color_temperature_;
}
optional<uint32_t> LightCommandRequest::get_transition_length() const {
if (!this->has_transition_length_)
return {};
return this->transition_length_;
}
optional<uint32_t> LightCommandRequest::get_flash_length() const {
if (!this->has_flash_length_)
return {};
return this->flash_length_;
}
optional<std::string> LightCommandRequest::get_effect() const {
if (!this->has_effect_)
return {};
return this->effect_;
}
#endif
#ifdef USE_SWITCH
bool SwitchCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 2:
// bool state = 2;
this->state_ = value;
return true;
default:
return false;
}
}
bool SwitchCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 1:
// fixed32 key = 1;
this->key_ = value;
return true;
default:
return false;
}
}
APIMessageType SwitchCommandRequest::message_type() const { return APIMessageType::SWITCH_COMMAND_REQUEST; }
uint32_t SwitchCommandRequest::get_key() const { return this->key_; }
bool SwitchCommandRequest::get_state() const { return this->state_; }
#endif
#ifdef USE_ESP32_CAMERA
bool CameraImageRequest::get_single() const { return this->single_; }
bool CameraImageRequest::get_stream() const { return this->stream_; }
bool CameraImageRequest::decode_varint(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 1:
// bool single = 1;
this->single_ = value;
return true;
case 2:
// bool stream = 2;
this->stream_ = value;
return true;
default:
return false;
}
}
APIMessageType CameraImageRequest::message_type() const { return APIMessageType::CAMERA_IMAGE_REQUEST; }
#endif
#ifdef USE_CLIMATE
bool ClimateCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 2:
// bool has_mode = 2;
this->has_mode_ = value;
return true;
case 3:
// ClimateMode mode = 3;
this->mode_ = static_cast<climate::ClimateMode>(value);
return true;
case 4:
// bool has_target_temperature = 4;
this->has_target_temperature_ = value;
return true;
case 6:
// bool has_target_temperature_low = 6;
this->has_target_temperature_low_ = value;
return true;
case 8:
// bool has_target_temperature_high = 8;
this->has_target_temperature_high_ = value;
return true;
case 10:
// bool has_away = 10;
this->has_away_ = value;
return true;
case 11:
// bool away = 11;
this->away_ = value;
return true;
default:
return false;
}
}
bool ClimateCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 1:
// fixed32 key = 1;
this->key_ = value;
return true;
case 5:
// float target_temperature = 5;
this->target_temperature_ = as_float(value);
return true;
case 7:
// float target_temperature_low = 7;
this->target_temperature_low_ = as_float(value);
return true;
case 9:
// float target_temperature_high = 9;
this->target_temperature_high_ = as_float(value);
return true;
default:
return false;
}
}
APIMessageType ClimateCommandRequest::message_type() const { return APIMessageType::CLIMATE_COMMAND_REQUEST; }
uint32_t ClimateCommandRequest::get_key() const { return this->key_; }
optional<climate::ClimateMode> ClimateCommandRequest::get_mode() const {
if (!this->has_mode_)
return {};
return this->mode_;
}
optional<float> ClimateCommandRequest::get_target_temperature() const {
if (!this->has_target_temperature_)
return {};
return this->target_temperature_;
}
optional<float> ClimateCommandRequest::get_target_temperature_low() const {
if (!this->has_target_temperature_low_)
return {};
return this->target_temperature_low_;
}
optional<float> ClimateCommandRequest::get_target_temperature_high() const {
if (!this->has_target_temperature_high_)
return {};
return this->target_temperature_high_;
}
optional<bool> ClimateCommandRequest::get_away() const {
if (!this->has_away_)
return {};
return this->away_;
}
#endif
} // namespace api
} // namespace esphome

View file

@ -1,162 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "api_message.h"
namespace esphome {
namespace api {
#ifdef USE_COVER
enum LegacyCoverCommand {
LEGACY_COVER_COMMAND_OPEN = 0,
LEGACY_COVER_COMMAND_CLOSE = 1,
LEGACY_COVER_COMMAND_STOP = 2,
};
class CoverCommandRequest : public APIMessage {
public:
bool decode_varint(uint32_t field_id, uint32_t value) override;
bool decode_32bit(uint32_t field_id, uint32_t value) override;
APIMessageType message_type() const override;
uint32_t get_key() const;
optional<LegacyCoverCommand> get_legacy_command() const;
optional<float> get_position() const;
optional<float> get_tilt() const;
bool get_stop() const { return this->stop_; }
protected:
uint32_t key_{0};
bool has_legacy_command_{false};
LegacyCoverCommand legacy_command_{LEGACY_COVER_COMMAND_OPEN};
bool has_position_{false};
float position_{0.0f};
bool has_tilt_{false};
float tilt_{0.0f};
bool stop_{false};
};
#endif
#ifdef USE_FAN
class FanCommandRequest : public APIMessage {
public:
bool decode_varint(uint32_t field_id, uint32_t value) override;
bool decode_32bit(uint32_t field_id, uint32_t value) override;
APIMessageType message_type() const override;
uint32_t get_key() const;
optional<bool> get_state() const;
optional<fan::FanSpeed> get_speed() const;
optional<bool> get_oscillating() const;
protected:
uint32_t key_{0};
bool has_state_{false};
bool state_{false};
bool has_speed_{false};
fan::FanSpeed speed_{fan::FAN_SPEED_LOW};
bool has_oscillating_{false};
bool oscillating_{false};
};
#endif
#ifdef USE_LIGHT
class LightCommandRequest : public APIMessage {
public:
bool decode_varint(uint32_t field_id, uint32_t value) override;
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
bool decode_32bit(uint32_t field_id, uint32_t value) override;
APIMessageType message_type() const override;
uint32_t get_key() const;
optional<bool> get_state() const;
optional<float> get_brightness() const;
optional<float> get_red() const;
optional<float> get_green() const;
optional<float> get_blue() const;
optional<float> get_white() const;
optional<float> get_color_temperature() const;
optional<uint32_t> get_transition_length() const;
optional<uint32_t> get_flash_length() const;
optional<std::string> get_effect() const;
protected:
uint32_t key_{0};
bool has_state_{false};
bool state_{false};
bool has_brightness_{false};
float brightness_{0.0f};
bool has_rgb_{false};
float red_{0.0f};
float green_{0.0f};
float blue_{0.0f};
bool has_white_{false};
float white_{0.0f};
bool has_color_temperature_{false};
float color_temperature_{0.0f};
bool has_transition_length_{false};
uint32_t transition_length_{0};
bool has_flash_length_{false};
uint32_t flash_length_{0};
bool has_effect_{false};
std::string effect_{};
};
#endif
#ifdef USE_SWITCH
class SwitchCommandRequest : public APIMessage {
public:
bool decode_varint(uint32_t field_id, uint32_t value) override;
bool decode_32bit(uint32_t field_id, uint32_t value) override;
APIMessageType message_type() const override;
uint32_t get_key() const;
bool get_state() const;
protected:
uint32_t key_{0};
bool state_{false};
};
#endif
#ifdef USE_ESP32_CAMERA
class CameraImageRequest : public APIMessage {
public:
bool decode_varint(uint32_t field_id, uint32_t value) override;
bool get_single() const;
bool get_stream() const;
APIMessageType message_type() const override;
protected:
bool single_{false};
bool stream_{false};
};
#endif
#ifdef USE_CLIMATE
class ClimateCommandRequest : public APIMessage {
public:
bool decode_varint(uint32_t field_id, uint32_t value) override;
bool decode_32bit(uint32_t field_id, uint32_t value) override;
APIMessageType message_type() const override;
uint32_t get_key() const;
optional<climate::ClimateMode> get_mode() const;
optional<float> get_target_temperature() const;
optional<float> get_target_temperature_low() const;
optional<float> get_target_temperature_high() const;
optional<bool> get_away() const;
protected:
uint32_t key_{0};
bool has_mode_{false};
climate::ClimateMode mode_{climate::CLIMATE_MODE_OFF};
bool has_target_temperature_{false};
float target_temperature_{0.0f};
bool has_target_temperature_low_{false};
float target_temperature_low_{0.0f};
bool has_target_temperature_high_{false};
float target_temperature_high_{0.0f};
bool has_away_{false};
bool away_{false};
};
#endif
} // namespace api
} // namespace esphome

View file

@ -0,0 +1,214 @@
#pragma once
#include <map>
#include "user_services.h"
#include "api_server.h"
namespace esphome {
namespace api {
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
public:
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
void (T::*callback)(Ts...))
: UserServiceBase<Ts...>(name, arg_names), obj_(obj), callback_(callback) {}
protected:
void execute(Ts... x) override { (this->obj_->*this->callback_)(x...); } // NOLINT
T *obj_;
void (T::*callback_)(Ts...);
};
class CustomAPIDevice {
public:
/// Return if a client (such as Home Assistant) is connected to the native API.
bool is_connected() const { return global_api_server->is_connected(); }
/** Register a custom native API service that will show up in Home Assistant.
*
* Usage:
*
* ```cpp
* void setup() override {
* register_service(&CustomNativeAPI::on_start_washer_cycle, "start_washer_cycle",
* {"cycle_length"});
* }
*
* void on_start_washer_cycle(int cycle_length) {
* // Start washer cycle.
* }
* ```
*
* @tparam T The class type creating the service, automatically deduced from the function pointer.
* @tparam Ts The argument types for the service, automatically deduced from the function arguments.
* @param callback The member function to call when the service is triggered.
* @param name The name of the service to register.
* @param arg_names The name of the arguments for the service, must match the arguments of the function.
*/
template<typename T, typename... Ts>
void register_service(void (T::*callback)(Ts...), const std::string &name,
const std::array<std::string, sizeof...(Ts)> &arg_names) {
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback);
global_api_server->register_user_service(service);
}
/** Register a custom native API service that will show up in Home Assistant.
*
* Usage:
*
* ```cpp
* void setup() override {
* register_service(&CustomNativeAPI::on_hello_world, "hello_world");
* }
*
* void on_hello_world() {
* // Hello World service called.
* }
* ```
*
* @tparam T The class type creating the service, automatically deduced from the function pointer.
* @param callback The member function to call when the service is triggered.
* @param name The name of the arguments for the service, must match the arguments of the function.
*/
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback);
global_api_server->register_user_service(service);
}
/** Subscribe to the state of an entity from Home Assistant.
*
* Usage:
*
* ```cpp
* void setup() override {
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
* }
*
* void on_state_changed(std::string state) {
* // State of sensor.weather_forecast is `state`
* }
* ```
*
* @tparam T The class type creating the service, automatically deduced from the function pointer.
* @param callback The member function to call when the entity state changes.
* @param entity_id The entity_id to track.
*/
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id) {
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
global_api_server->subscribe_home_assistant_state(entity_id, f);
}
/** Subscribe to the state of an entity from Home Assistant.
*
* Usage:
*
* ```cpp
* void setup() override {
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
* }
*
* void on_state_changed(std::string entity_id, std::string state) {
* // State of `entity_id` is `state`
* }
* ```
*
* @tparam T The class type creating the service, automatically deduced from the function pointer.
* @param callback The member function to call when the entity state changes.
* @param entity_id The entity_id to track.
*/
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id) {
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
global_api_server->subscribe_home_assistant_state(entity_id, f);
}
/** Call a Home Assistant service from ESPHome.
*
* Usage:
*
* ```cpp
* call_homeassistant_service("homeassistant.restart");
* ```
*
* @param service_name The service to call.
*/
void call_homeassistant_service(const std::string &service_name) {
HomeassistantServiceResponse resp;
resp.service = service_name;
global_api_server->send_homeassistant_service_call(resp);
}
/** Call a Home Assistant service from ESPHome.
*
* Usage:
*
* ```cpp
* call_homeassistant_service("light.turn_on", {
* {"entity_id", "light.my_light"},
* {"brightness", "127"},
* });
* ```
*
* @param service_name The service to call.
* @param data The data for the service call, mapping from string to string.
*/
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
HomeassistantServiceResponse resp;
resp.service = service_name;
for (auto &it : data) {
HomeassistantServiceMap kv;
kv.key = it.first;
kv.value = it.second;
resp.data.push_back(kv);
}
global_api_server->send_homeassistant_service_call(resp);
}
/** Fire an ESPHome event in Home Assistant.
*
* Usage:
*
* ```cpp
* fire_homeassistant_event("esphome.something_happened");
* ```
*
* @param event_name The event to fire.
*/
void fire_homeassistant_event(const std::string &event_name) {
HomeassistantServiceResponse resp;
resp.service = event_name;
resp.is_event = true;
global_api_server->send_homeassistant_service_call(resp);
}
/** Fire an ESPHome event in Home Assistant.
*
* Usage:
*
* ```cpp
* fire_homeassistant_event("esphome.something_happened", {
* {"my_value", "500"},
* });
* ```
*
* @param event_name The event to fire.
* @param data The data for the event, mapping from string to string.
*/
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
HomeassistantServiceResponse resp;
resp.service = service_name;
resp.is_event = true;
for (auto &it : data) {
HomeassistantServiceMap kv;
kv.key = it.first;
kv.value = it.second;
resp.data.push_back(kv);
}
global_api_server->send_homeassistant_service_call(resp);
}
};
} // namespace api
} // namespace esphome

View file

@ -0,0 +1,66 @@
#pragma once
#include "esphome/core/helpers.h"
#include "esphome/core/automation.h"
#include "api_pb2.h"
#include "api_server.h"
namespace esphome {
namespace api {
template<typename... Ts> class TemplatableKeyValuePair {
public:
template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {}
std::string key;
TemplatableStringValue<Ts...> value;
};
template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
public:
explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {}
TEMPLATABLE_STRING_VALUE(service);
template<typename T> void add_data(std::string key, T value) {
this->data_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
}
template<typename T> void add_data_template(std::string key, T value) {
this->data_template_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
}
template<typename T> void add_variable(std::string key, T value) {
this->variables_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
}
void play(Ts... x) override {
HomeassistantServiceResponse resp;
resp.service = this->service_.value(x...);
resp.is_event = this->is_event_;
for (auto &it : this->data_) {
HomeassistantServiceMap kv;
kv.key = it.key;
kv.value = it.value.value(x...);
resp.data.push_back(kv);
}
for (auto &it : this->data_template_) {
HomeassistantServiceMap kv;
kv.key = it.key;
kv.value = it.value.value(x...);
resp.data_template.push_back(kv);
}
for (auto &it : this->variables_) {
HomeassistantServiceMap kv;
kv.key = it.key;
kv.value = it.value.value(x...);
resp.variables.push_back(kv);
}
this->parent_->send_homeassistant_service_call(resp);
}
protected:
APIServer *parent_;
bool is_event_;
std::vector<TemplatableKeyValuePair<Ts...>> data_;
std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
std::vector<TemplatableKeyValuePair<Ts...>> variables_;
};
} // namespace api
} // namespace esphome

View file

@ -2,189 +2,54 @@
#include "esphome/core/util.h" #include "esphome/core/util.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "api_connection.h"
namespace esphome { namespace esphome {
namespace api { namespace api {
std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) {
return App.get_name() + component_type + nameable->get_object_id();
}
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
auto buffer = this->client_->get_buffer(); return this->client_->send_binary_sensor_info(binary_sensor);
buffer.encode_nameable(binary_sensor);
// string unique_id = 4;
buffer.encode_string(4, get_default_unique_id("binary_sensor", binary_sensor));
// string device_class = 5;
buffer.encode_string(5, binary_sensor->get_device_class());
// bool is_status_binary_sensor = 6;
buffer.encode_bool(6, binary_sensor->is_status_binary_sensor());
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_BINARY_SENSOR_RESPONSE);
} }
#endif #endif
#ifdef USE_COVER #ifdef USE_COVER
bool ListEntitiesIterator::on_cover(cover::Cover *cover) { bool ListEntitiesIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_info(cover); }
auto buffer = this->client_->get_buffer();
buffer.encode_nameable(cover);
// string unique_id = 4;
buffer.encode_string(4, get_default_unique_id("cover", cover));
auto traits = cover->get_traits();
// bool assumed_state = 5;
buffer.encode_bool(5, traits.get_is_assumed_state());
// bool supports_position = 6;
buffer.encode_bool(6, traits.get_supports_position());
// bool supports_tilt = 7;
buffer.encode_bool(7, traits.get_supports_tilt());
// string device_class = 8;
buffer.encode_string(8, cover->get_device_class());
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_COVER_RESPONSE);
}
#endif #endif
#ifdef USE_FAN #ifdef USE_FAN
bool ListEntitiesIterator::on_fan(fan::FanState *fan) { bool ListEntitiesIterator::on_fan(fan::FanState *fan) { return this->client_->send_fan_info(fan); }
auto buffer = this->client_->get_buffer();
buffer.encode_nameable(fan);
// string unique_id = 4;
buffer.encode_string(4, get_default_unique_id("fan", fan));
// bool supports_oscillation = 5;
buffer.encode_bool(5, fan->get_traits().supports_oscillation());
// bool supports_speed = 6;
buffer.encode_bool(6, fan->get_traits().supports_speed());
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_FAN_RESPONSE);
}
#endif #endif
#ifdef USE_LIGHT #ifdef USE_LIGHT
bool ListEntitiesIterator::on_light(light::LightState *light) { bool ListEntitiesIterator::on_light(light::LightState *light) { return this->client_->send_light_info(light); }
auto buffer = this->client_->get_buffer();
buffer.encode_nameable(light);
// string unique_id = 4;
buffer.encode_string(4, get_default_unique_id("light", light));
// bool supports_brightness = 5;
auto traits = light->get_traits();
buffer.encode_bool(5, traits.get_supports_brightness());
// bool supports_rgb = 6;
buffer.encode_bool(6, traits.get_supports_rgb());
// bool supports_white_value = 7;
buffer.encode_bool(7, traits.get_supports_rgb_white_value());
// bool supports_color_temperature = 8;
buffer.encode_bool(8, traits.get_supports_color_temperature());
if (traits.get_supports_color_temperature()) {
// float min_mireds = 9;
buffer.encode_float(9, traits.get_min_mireds());
// float max_mireds = 10;
buffer.encode_float(10, traits.get_max_mireds());
}
// repeated string effects = 11;
if (light->supports_effects()) {
buffer.encode_string(11, "None");
for (auto *effect : light->get_effects()) {
buffer.encode_string(11, effect->get_name());
}
}
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_LIGHT_RESPONSE);
}
#endif #endif
#ifdef USE_SENSOR #ifdef USE_SENSOR
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->client_->send_sensor_info(sensor); }
auto buffer = this->client_->get_buffer();
buffer.encode_nameable(sensor);
// string unique_id = 4;
std::string unique_id = sensor->unique_id();
if (unique_id.empty())
unique_id = get_default_unique_id("sensor", sensor);
buffer.encode_string(4, unique_id);
// string icon = 5;
buffer.encode_string(5, sensor->get_icon());
// string unit_of_measurement = 6;
buffer.encode_string(6, sensor->get_unit_of_measurement());
// int32 accuracy_decimals = 7;
buffer.encode_int32(7, sensor->get_accuracy_decimals());
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SENSOR_RESPONSE);
}
#endif #endif
#ifdef USE_SWITCH #ifdef USE_SWITCH
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); }
auto buffer = this->client_->get_buffer();
buffer.encode_nameable(a_switch);
// string unique_id = 4;
buffer.encode_string(4, get_default_unique_id("switch", a_switch));
// string icon = 5;
buffer.encode_string(5, a_switch->get_icon());
// bool assumed_state = 6;
buffer.encode_bool(6, a_switch->assumed_state());
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SWITCH_RESPONSE);
}
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
auto buffer = this->client_->get_buffer(); return this->client_->send_text_sensor_info(text_sensor);
buffer.encode_nameable(text_sensor);
// string unique_id = 4;
std::string unique_id = text_sensor->unique_id();
if (unique_id.empty())
unique_id = get_default_unique_id("text_sensor", text_sensor);
buffer.encode_string(4, unique_id);
// string icon = 5;
buffer.encode_string(5, text_sensor->get_icon());
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_TEXT_SENSOR_RESPONSE);
} }
#endif #endif
bool ListEntitiesIterator::on_end() { bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
return this->client_->send_empty_message(APIMessageType::LIST_ENTITIES_DONE_RESPONSE);
}
ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client) ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client)
: ComponentIterator(server), client_(client) {} : ComponentIterator(server), client_(client) {}
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) { bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
auto buffer = this->client_->get_buffer(); auto resp = service->encode_list_service_response();
service->encode_list_service_response(buffer); return this->client_->send_list_entities_services_response(resp);
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SERVICE_RESPONSE);
} }
#ifdef USE_ESP32_CAMERA #ifdef USE_ESP32_CAMERA
bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) { bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) {
auto buffer = this->client_->get_buffer(); return this->client_->send_camera_info(camera);
buffer.encode_nameable(camera);
// string unique_id = 4;
buffer.encode_string(4, get_default_unique_id("camera", camera));
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_CAMERA_RESPONSE);
} }
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
bool ListEntitiesIterator::on_climate(climate::Climate *climate) { bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_info(climate); }
auto buffer = this->client_->get_buffer();
buffer.encode_nameable(climate);
// string unique_id = 4;
buffer.encode_string(4, get_default_unique_id("climate", climate));
auto traits = climate->get_traits();
// bool supports_current_temperature = 5;
buffer.encode_bool(5, traits.get_supports_current_temperature());
// bool supports_two_point_target_temperature = 6;
buffer.encode_bool(6, traits.get_supports_two_point_target_temperature());
// repeated ClimateMode supported_modes = 7;
for (auto mode : {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
climate::CLIMATE_MODE_HEAT}) {
if (traits.supports_mode(mode))
buffer.encode_uint32(7, mode, true);
}
// float visual_min_temperature = 8;
buffer.encode_float(8, traits.get_visual_min_temperature());
// float visual_max_temperature = 9;
buffer.encode_float(9, traits.get_visual_max_temperature());
// float visual_temperature_step = 10;
buffer.encode_float(10, traits.get_visual_temperature_step());
// bool supports_away = 11;
buffer.encode_bool(11, traits.get_supports_away());
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_CLIMATE_RESPONSE);
}
#endif #endif
APIMessageType ListEntitiesRequest::message_type() const { return APIMessageType::LIST_ENTITIES_REQUEST; }
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -2,16 +2,11 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "api_message.h" #include "util.h"
namespace esphome { namespace esphome {
namespace api { namespace api {
class ListEntitiesRequest : public APIMessage {
public:
APIMessageType message_type() const override;
};
class APIConnection; class APIConnection;
class ListEntitiesIterator : public ComponentIterator { class ListEntitiesIterator : public ComponentIterator {

View file

@ -1,61 +1,59 @@
#include "api_message.h" #include "proto.h"
#include "util.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
namespace esphome { namespace esphome {
namespace api { namespace api {
static const char *TAG = "api.message"; static const char *TAG = "api.proto";
bool APIMessage::decode_varint(uint32_t field_id, uint32_t value) { return false; } void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
bool APIMessage::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { return false; }
bool APIMessage::decode_32bit(uint32_t field_id, uint32_t value) { return false; }
void APIMessage::encode(APIBuffer &buffer) {}
void APIMessage::decode(const uint8_t *buffer, size_t length) {
uint32_t i = 0; uint32_t i = 0;
bool error = false; bool error = false;
while (i < length) { while (i < length) {
uint32_t consumed; uint32_t consumed;
auto res = proto_decode_varuint32(&buffer[i], length - i, &consumed); auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
if (!res.has_value()) { if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid field start at %u", i); ESP_LOGV(TAG, "Invalid field start at %u", i);
break; break;
} }
uint32_t field_type = (*res) & 0b111; uint32_t field_type = (res->as_uint32()) & 0b111;
uint32_t field_id = (*res) >> 3; uint32_t field_id = (res->as_uint32()) >> 3;
i += consumed; i += consumed;
switch (field_type) { switch (field_type) {
case 0: { // VarInt case 0: { // VarInt
res = proto_decode_varuint32(&buffer[i], length - i, &consumed); res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
if (!res.has_value()) { if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid VarInt at %u", i); ESP_LOGV(TAG, "Invalid VarInt at %u", i);
error = true; error = true;
break; break;
} }
if (!this->decode_varint(field_id, *res)) { if (!this->decode_varint(field_id, *res)) {
ESP_LOGV(TAG, "Cannot decode VarInt field %u with value %u!", field_id, *res); ESP_LOGV(TAG, "Cannot decode VarInt field %u with value %u!", field_id, res->as_uint32());
} }
i += consumed; i += consumed;
break; break;
} }
case 2: { // Length-delimited case 2: { // Length-delimited
res = proto_decode_varuint32(&buffer[i], length - i, &consumed); res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
if (!res.has_value()) { if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid Length Delimited at %u", i); ESP_LOGV(TAG, "Invalid Length Delimited at %u", i);
error = true; error = true;
break; break;
} }
uint32_t field_length = res->as_uint32();
i += consumed; i += consumed;
if (*res > length - i) { if (field_length > length - i) {
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %u", i); ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %u", i);
error = true; error = true;
break; break;
} }
if (!this->decode_length_delimited(field_id, &buffer[i], *res)) { if (!this->decode_length(field_id, ProtoLengthDelimited(&buffer[i], field_length))) {
ESP_LOGV(TAG, "Cannot decode Length Delimited field %u!", field_id); ESP_LOGV(TAG, "Cannot decode Length Delimited field %u!", field_id);
} }
i += *res; i += field_length;
break; break;
} }
case 5: { // 32-bit case 5: { // 32-bit
@ -66,7 +64,7 @@ void APIMessage::decode(const uint8_t *buffer, size_t length) {
} }
uint32_t val = (uint32_t(buffer[i]) << 0) | (uint32_t(buffer[i + 1]) << 8) | (uint32_t(buffer[i + 2]) << 16) | uint32_t val = (uint32_t(buffer[i]) << 0) | (uint32_t(buffer[i + 1]) << 8) | (uint32_t(buffer[i + 2]) << 16) |
(uint32_t(buffer[i + 3]) << 24); (uint32_t(buffer[i + 3]) << 24);
if (!this->decode_32bit(field_id, val)) { if (!this->decode_32bit(field_id, Proto32Bit(val))) {
ESP_LOGV(TAG, "Cannot decode 32-bit field %u with value %u!", field_id, val); ESP_LOGV(TAG, "Cannot decode 32-bit field %u with value %u!", field_id, val);
} }
i += 4; i += 4;
@ -83,5 +81,11 @@ void APIMessage::decode(const uint8_t *buffer, size_t length) {
} }
} }
std::string ProtoMessage::dump() const {
std::string out;
this->dump_to(out);
return out;
}
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -0,0 +1,279 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace api {
/// Representation of a VarInt - in ProtoBuf should be 64bit but we only use 32bit
class ProtoVarInt {
public:
ProtoVarInt() : value_(0) {}
explicit ProtoVarInt(uint64_t value) : value_(value) {}
static optional<ProtoVarInt> parse(const uint8_t *buffer, uint32_t len, uint32_t *consumed) {
if (consumed != nullptr)
*consumed = 0;
if (len == 0)
return {};
uint64_t result = 0;
uint8_t bitpos = 0;
for (uint32_t i = 0; i < len; i++) {
uint8_t val = buffer[i];
result |= uint64_t(val & 0x7F) << uint64_t(bitpos);
bitpos += 7;
if ((val & 0x80) == 0) {
if (consumed != nullptr)
*consumed = i + 1;
return ProtoVarInt(result);
}
}
return {};
}
uint32_t as_uint32() const { return this->value_; }
uint64_t as_uint64() const { return this->value_; }
bool as_bool() const { return this->value_; }
template<typename T> T as_enum() const { return static_cast<T>(this->as_uint32()); }
int32_t as_int32() const {
// Not ZigZag encoded
return static_cast<int32_t>(this->as_int64());
}
int64_t as_int64() const {
// Not ZigZag encoded
return static_cast<int64_t>(this->value_);
}
int32_t as_sint32() const {
// with ZigZag encoding
if (this->value_ & 1)
return static_cast<int32_t>(~(this->value_ >> 1));
else
return static_cast<int32_t>(this->value_ >> 1);
}
int64_t as_sint64() const {
// with ZigZag encoding
if (this->value_ & 1)
return static_cast<int64_t>(~(this->value_ >> 1));
else
return static_cast<int64_t>(this->value_ >> 1);
}
void encode(std::vector<uint8_t> &out) {
uint32_t val = this->value_;
if (val <= 0x7F) {
out.push_back(val);
return;
}
while (val) {
uint8_t temp = val & 0x7F;
val >>= 7;
if (val) {
out.push_back(temp | 0x80);
} else {
out.push_back(temp);
}
}
}
protected:
uint64_t value_;
};
class ProtoLengthDelimited {
public:
explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
template<class C> C as_message() const {
auto msg = C();
msg.decode(this->value_, this->length_);
return msg;
}
protected:
const uint8_t *const value_;
const size_t length_;
};
class Proto32Bit {
public:
explicit Proto32Bit(uint32_t value) : value_(value) {}
uint32_t as_fixed32() const { return this->value_; }
int32_t as_sfixed32() const { return static_cast<int32_t>(this->value_); }
float as_float() const {
union {
uint32_t raw;
float value;
} s{};
s.raw = this->value_;
return s.value;
}
protected:
const uint32_t value_;
};
class Proto64Bit {
public:
explicit Proto64Bit(uint64_t value) : value_(value) {}
uint64_t as_fixed64() const { return this->value_; }
int64_t as_sfixed64() const { return static_cast<int64_t>(this->value_); }
double as_double() const {
union {
uint64_t raw;
double value;
} s{};
s.raw = this->value_;
return s.value;
}
protected:
const uint64_t value_;
};
class ProtoWriteBuffer {
public:
ProtoWriteBuffer(std::vector<uint8_t> *buffer) : buffer_(buffer) {}
void write(uint8_t value) { this->buffer_->push_back(value); }
void encode_varint_raw(ProtoVarInt value) { value.encode(*this->buffer_); }
void encode_varint_raw(uint32_t value) { this->encode_varint_raw(ProtoVarInt(value)); }
void encode_field_raw(uint32_t field_id, uint32_t type) {
uint32_t val = (field_id << 3) | (type & 0b111);
this->encode_varint_raw(val);
}
void encode_string(uint32_t field_id, const char *string, size_t len, bool force = false) {
if (len == 0 && !force)
return;
this->encode_field_raw(field_id, 2);
this->encode_varint_raw(len);
auto *data = reinterpret_cast<const uint8_t *>(string);
for (size_t i = 0; i < len; i++)
this->write(data[i]);
}
void encode_string(uint32_t field_id, const std::string &value, bool force = false) {
this->encode_string(field_id, value.data(), value.size());
}
void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) {
this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force);
}
void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) {
if (value == 0 && !force)
return;
this->encode_field_raw(field_id, 0);
this->encode_varint_raw(value);
}
void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) {
if (value == 0 && !force)
return;
this->encode_field_raw(field_id, 0);
this->encode_varint_raw(ProtoVarInt(value));
}
void encode_bool(uint32_t field_id, bool value, bool force = false) {
if (!value && !force)
return;
this->encode_field_raw(field_id, 0);
this->write(0x01);
}
void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
if (value == 0 && !force)
return;
this->encode_field_raw(field_id, 5);
this->write((value >> 0) & 0xFF);
this->write((value >> 8) & 0xFF);
this->write((value >> 16) & 0xFF);
this->write((value >> 24) & 0xFF);
}
template<typename T> void encode_enum(uint32_t field_id, T value, bool force = false) {
this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
}
void encode_float(uint32_t field_id, float value, bool force = false) {
if (value == 0.0f && !force)
return;
union {
float value;
uint32_t raw;
} val{};
val.value = value;
this->encode_fixed32(field_id, val.raw);
}
void encode_int32(uint32_t field_id, int32_t value, bool force = false) {
if (value < 0) {
// negative int32 is always 10 byte long
this->encode_int64(field_id, value, force);
return;
}
this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
}
void encode_int64(uint32_t field_id, int64_t value, bool force = false) {
this->encode_uint64(field_id, static_cast<uint64_t>(value), force);
}
void encode_sint32(uint32_t field_id, int32_t value, bool force = false) {
uint32_t uvalue;
if (value < 0)
uvalue = ~(value << 1);
else
uvalue = value << 1;
this->encode_uint32(field_id, uvalue, force);
}
template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) {
this->encode_field_raw(field_id, 2);
size_t begin = this->buffer_->size();
value.encode(*this);
const uint32_t nested_length = this->buffer_->size() - begin;
// add size varint
std::vector<uint8_t> var;
ProtoVarInt(nested_length).encode(var);
this->buffer_->insert(this->buffer_->begin() + begin, var.begin(), var.end());
}
std::vector<uint8_t> *get_buffer() const { return buffer_; }
protected:
std::vector<uint8_t> *buffer_;
};
class ProtoMessage {
public:
virtual void encode(ProtoWriteBuffer buffer) const = 0;
void decode(const uint8_t *buffer, size_t length);
std::string dump() const;
virtual void dump_to(std::string &out) const = 0;
protected:
virtual bool decode_varint(uint32_t field_id, ProtoVarInt value) { return false; }
virtual bool decode_length(uint32_t field_id, ProtoLengthDelimited value) { return false; }
virtual bool decode_32bit(uint32_t field_id, Proto32Bit value) { return false; }
virtual bool decode_64bit(uint32_t field_id, Proto64Bit value) { return false; }
};
template<typename T> const char *proto_enum_to_string(T value);
class ProtoService {
public:
protected:
virtual bool is_authenticated() = 0;
virtual bool is_connection_setup() = 0;
virtual void on_fatal_error() = 0;
virtual void on_unauthenticated_access() = 0;
virtual void on_no_setup_connection() = 0;
virtual ProtoWriteBuffer create_buffer() = 0;
virtual bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) = 0;
virtual bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
virtual void set_nodelay(bool nodelay) = 0;
template<class C> bool send_message_(const C &msg, uint32_t message_type) {
auto buffer = this->create_buffer();
msg.encode(buffer);
return this->send_buffer(buffer, message_type);
}
};
} // namespace api
} // namespace esphome

View file

@ -1,49 +0,0 @@
#include "service_call_message.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
APIMessageType SubscribeServiceCallsRequest::message_type() const {
return APIMessageType::SUBSCRIBE_SERVICE_CALLS_REQUEST;
}
APIMessageType ServiceCallResponse::message_type() const { return APIMessageType::SERVICE_CALL_RESPONSE; }
void ServiceCallResponse::encode(APIBuffer &buffer) {
// string service = 1;
buffer.encode_string(1, this->service_);
// map<string, string> data = 2;
for (auto &it : this->data_) {
auto nested = buffer.begin_nested(2);
buffer.encode_string(1, it.key);
buffer.encode_string(2, it.value);
buffer.end_nested(nested);
}
// map<string, string> data_template = 3;
for (auto &it : this->data_template_) {
auto nested = buffer.begin_nested(3);
buffer.encode_string(1, it.key);
buffer.encode_string(2, it.value);
buffer.end_nested(nested);
}
// map<string, string> variables = 4;
for (auto &it : this->variables_) {
auto nested = buffer.begin_nested(4);
buffer.encode_string(1, it.key);
buffer.encode_string(2, it.value());
buffer.end_nested(nested);
}
}
void ServiceCallResponse::set_service(const std::string &service) { this->service_ = service; }
void ServiceCallResponse::set_data(const std::vector<KeyValuePair> &data) { this->data_ = data; }
void ServiceCallResponse::set_data_template(const std::vector<KeyValuePair> &data_template) {
this->data_template_ = data_template;
}
void ServiceCallResponse::set_variables(const std::vector<TemplatableKeyValuePair> &variables) {
this->variables_ = variables;
}
KeyValuePair::KeyValuePair(const std::string &key, const std::string &value) : key(key), value(value) {}
} // namespace api
} // namespace esphome

View file

@ -1,53 +0,0 @@
#pragma once
#include "esphome/core/helpers.h"
#include "esphome/core/automation.h"
#include "api_message.h"
namespace esphome {
namespace api {
class SubscribeServiceCallsRequest : public APIMessage {
public:
APIMessageType message_type() const override;
};
class KeyValuePair {
public:
KeyValuePair(const std::string &key, const std::string &value);
std::string key;
std::string value;
};
class TemplatableKeyValuePair {
public:
template<typename T> TemplatableKeyValuePair(std::string key, T func);
std::string key;
std::function<std::string()> value;
};
template<typename T> TemplatableKeyValuePair::TemplatableKeyValuePair(std::string key, T func) : key(key) {
this->value = [func]() -> std::string { return to_string(func()); };
}
class ServiceCallResponse : public APIMessage {
public:
APIMessageType message_type() const override;
void encode(APIBuffer &buffer) override;
void set_service(const std::string &service);
void set_data(const std::vector<KeyValuePair> &data);
void set_data_template(const std::vector<KeyValuePair> &data_template);
void set_variables(const std::vector<TemplatableKeyValuePair> &variables);
protected:
std::string service_;
std::vector<KeyValuePair> data_;
std::vector<KeyValuePair> data_template_;
std::vector<TemplatableKeyValuePair> variables_;
};
} // namespace api
} // namespace esphome

View file

@ -1,26 +0,0 @@
#include "subscribe_logs.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
APIMessageType SubscribeLogsRequest::message_type() const { return APIMessageType::SUBSCRIBE_LOGS_REQUEST; }
bool SubscribeLogsRequest::decode_varint(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 1: // LogLevel level = 1;
this->level_ = value;
return true;
case 2: // bool dump_config = 2;
this->dump_config_ = value;
return true;
default:
return false;
}
}
uint32_t SubscribeLogsRequest::get_level() const { return this->level_; }
void SubscribeLogsRequest::set_level(uint32_t level) { this->level_ = level; }
bool SubscribeLogsRequest::get_dump_config() const { return this->dump_config_; }
void SubscribeLogsRequest::set_dump_config(bool dump_config) { this->dump_config_ = dump_config; }
} // namespace api
} // namespace esphome

View file

@ -1,24 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "api_message.h"
namespace esphome {
namespace api {
class SubscribeLogsRequest : public APIMessage {
public:
bool decode_varint(uint32_t field_id, uint32_t value) override;
APIMessageType message_type() const override;
uint32_t get_level() const;
void set_level(uint32_t level);
bool get_dump_config() const;
void set_dump_config(bool dump_config);
protected:
uint32_t level_{6};
bool dump_config_{false};
};
} // namespace api
} // namespace esphome

View file

@ -1,4 +1,5 @@
#include "subscribe_state.h" #include "subscribe_state.h"
#include "api_connection.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
namespace esphome { namespace esphome {
@ -48,30 +49,5 @@ bool InitialStateIterator::on_climate(climate::Climate *climate) { return this->
InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client) InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client)
: ComponentIterator(server), client_(client) {} : ComponentIterator(server), client_(client) {}
APIMessageType SubscribeStatesRequest::message_type() const { return APIMessageType::SUBSCRIBE_STATES_REQUEST; }
bool HomeAssistantStateResponse::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
switch (field_id) {
case 1:
// string entity_id = 1;
this->entity_id_ = as_string(value, len);
return true;
case 2:
// string state = 2;
this->state_ = as_string(value, len);
return true;
default:
return false;
}
}
APIMessageType HomeAssistantStateResponse::message_type() const {
return APIMessageType::HOME_ASSISTANT_STATE_RESPONSE;
}
const std::string &HomeAssistantStateResponse::get_entity_id() const { return this->entity_id_; }
const std::string &HomeAssistantStateResponse::get_state() const { return this->state_; }
APIMessageType SubscribeHomeAssistantStatesRequest::message_type() const {
return APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATES_REQUEST;
}
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -4,16 +4,10 @@
#include "esphome/core/controller.h" #include "esphome/core/controller.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "util.h" #include "util.h"
#include "api_message.h"
namespace esphome { namespace esphome {
namespace api { namespace api {
class SubscribeStatesRequest : public APIMessage {
public:
APIMessageType message_type() const override;
};
class APIConnection; class APIConnection;
class InitialStateIterator : public ComponentIterator { class InitialStateIterator : public ComponentIterator {
@ -47,23 +41,6 @@ class InitialStateIterator : public ComponentIterator {
APIConnection *client_; APIConnection *client_;
}; };
class SubscribeHomeAssistantStatesRequest : public APIMessage {
public:
APIMessageType message_type() const override;
};
class HomeAssistantStateResponse : public APIMessage {
public:
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
APIMessageType message_type() const override;
const std::string &get_entity_id() const;
const std::string &get_state() const;
protected:
std::string entity_id_;
std::string state_;
};
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -4,71 +4,35 @@
namespace esphome { namespace esphome {
namespace api { namespace api {
template<> bool ExecuteServiceArgument::get_value<bool>() { return this->value_bool_; } template<> bool get_execute_arg_value<bool>(const ExecuteServiceArgument &arg) { return arg.bool_; }
template<> int ExecuteServiceArgument::get_value<int>() { return this->value_int_; } template<> int get_execute_arg_value<int>(const ExecuteServiceArgument &arg) {
template<> float ExecuteServiceArgument::get_value<float>() { return this->value_float_; } if (arg.legacy_int != 0)
template<> std::string ExecuteServiceArgument::get_value<std::string>() { return this->value_string_; } return arg.legacy_int;
return arg.int_;
APIMessageType ExecuteServiceArgument::message_type() const { return APIMessageType::EXECUTE_SERVICE_REQUEST; }
bool ExecuteServiceArgument::decode_varint(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 1: // bool bool_ = 1;
this->value_bool_ = value;
return true;
case 2: // int32 int_ = 2;
this->value_int_ = value;
return true;
default:
return false;
}
} }
bool ExecuteServiceArgument::decode_32bit(uint32_t field_id, uint32_t value) { template<> float get_execute_arg_value<float>(const ExecuteServiceArgument &arg) { return arg.float_; }
switch (field_id) { template<> std::string get_execute_arg_value<std::string>(const ExecuteServiceArgument &arg) { return arg.string_; }
case 3: // float float_ = 3; template<> std::vector<bool> get_execute_arg_value<std::vector<bool>>(const ExecuteServiceArgument &arg) {
this->value_float_ = as_float(value); return arg.bool_array;
return true;
default:
return false;
}
} }
bool ExecuteServiceArgument::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { template<> std::vector<int> get_execute_arg_value<std::vector<int>>(const ExecuteServiceArgument &arg) {
switch (field_id) { return arg.int_array;
case 4: // string string_ = 4; }
this->value_string_ = as_string(value, len); template<> std::vector<float> get_execute_arg_value<std::vector<float>>(const ExecuteServiceArgument &arg) {
return true; return arg.float_array;
default: }
return false; template<> std::vector<std::string> get_execute_arg_value<std::vector<std::string>>(const ExecuteServiceArgument &arg) {
} return arg.string_array;
} }
bool ExecuteServiceRequest::decode_32bit(uint32_t field_id, uint32_t value) { template<> ServiceArgType to_service_arg_type<bool>() { return SERVICE_ARG_TYPE_BOOL; }
switch (field_id) { template<> ServiceArgType to_service_arg_type<int>() { return SERVICE_ARG_TYPE_INT; }
case 1: // fixed32 key = 1; template<> ServiceArgType to_service_arg_type<float>() { return SERVICE_ARG_TYPE_FLOAT; }
this->key_ = value; template<> ServiceArgType to_service_arg_type<std::string>() { return SERVICE_ARG_TYPE_STRING; }
return true; template<> ServiceArgType to_service_arg_type<std::vector<bool>>() { return SERVICE_ARG_TYPE_BOOL_ARRAY; }
default: template<> ServiceArgType to_service_arg_type<std::vector<int>>() { return SERVICE_ARG_TYPE_INT_ARRAY; }
return false; template<> ServiceArgType to_service_arg_type<std::vector<float>>() { return SERVICE_ARG_TYPE_FLOAT_ARRAY; }
} template<> ServiceArgType to_service_arg_type<std::vector<std::string>>() { return SERVICE_ARG_TYPE_STRING_ARRAY; }
}
bool ExecuteServiceRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
switch (field_id) {
case 2: { // repeated ExecuteServiceArgument args = 2;
ExecuteServiceArgument arg;
arg.decode(value, len);
this->args_.push_back(arg);
return true;
}
default:
return false;
}
}
APIMessageType ExecuteServiceRequest::message_type() const { return APIMessageType::EXECUTE_SERVICE_REQUEST; }
const std::vector<ExecuteServiceArgument> &ExecuteServiceRequest::get_args() const { return this->args_; }
uint32_t ExecuteServiceRequest::get_key() const { return this->key_; }
ServiceTypeArgument::ServiceTypeArgument(const std::string &name, ServiceArgType type) : name_(name), type_(type) {}
const std::string &ServiceTypeArgument::get_name() const { return this->name_; }
ServiceArgType ServiceTypeArgument::get_type() const { return this->type_; }
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -2,124 +2,71 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "api_message.h" #include "api_pb2.h"
namespace esphome { namespace esphome {
namespace api { namespace api {
enum ServiceArgType {
SERVICE_ARG_TYPE_BOOL = 0,
SERVICE_ARG_TYPE_INT = 1,
SERVICE_ARG_TYPE_FLOAT = 2,
SERVICE_ARG_TYPE_STRING = 3,
};
class ServiceTypeArgument {
public:
ServiceTypeArgument(const std::string &name, ServiceArgType type);
const std::string &get_name() const;
ServiceArgType get_type() const;
protected:
std::string name_;
ServiceArgType type_;
};
class ExecuteServiceArgument : public APIMessage {
public:
APIMessageType message_type() const override;
template<typename T> T get_value();
bool decode_varint(uint32_t field_id, uint32_t value) override;
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
bool decode_32bit(uint32_t field_id, uint32_t value) override;
protected:
bool value_bool_{false};
int value_int_{0};
float value_float_{0.0f};
std::string value_string_{};
};
class ExecuteServiceRequest : public APIMessage {
public:
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
bool decode_32bit(uint32_t field_id, uint32_t value) override;
APIMessageType message_type() const override;
uint32_t get_key() const;
const std::vector<ExecuteServiceArgument> &get_args() const;
protected:
uint32_t key_;
std::vector<ExecuteServiceArgument> args_;
};
class UserServiceDescriptor { class UserServiceDescriptor {
public: public:
virtual void encode_list_service_response(APIBuffer &buffer) = 0; virtual ListEntitiesServicesResponse encode_list_service_response() = 0;
virtual bool execute_service(const ExecuteServiceRequest &req) = 0; virtual bool execute_service(const ExecuteServiceRequest &req) = 0;
}; };
template<typename... Ts> class UserService : public UserServiceDescriptor, public Trigger<Ts...> { template<typename T> T get_execute_arg_value(const ExecuteServiceArgument &arg);
template<typename T> ServiceArgType to_service_arg_type();
template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
public: public:
UserService(const std::string &name, const std::array<ServiceTypeArgument, sizeof...(Ts)> &args); UserServiceBase(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
: name_(name), arg_names_(arg_names) {
this->key_ = fnv1_hash(this->name_);
}
void encode_list_service_response(APIBuffer &buffer) override; ListEntitiesServicesResponse encode_list_service_response() override {
ListEntitiesServicesResponse msg;
msg.name = this->name_;
msg.key = this->key_;
std::array<ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
for (int i = 0; i < sizeof...(Ts); i++) {
ListEntitiesServicesArgument arg;
arg.type = arg_types[i];
arg.name = this->arg_names_[i];
msg.args.push_back(arg);
}
return msg;
}
bool execute_service(const ExecuteServiceRequest &req) override; bool execute_service(const ExecuteServiceRequest &req) override {
if (req.key != this->key_)
return false;
if (req.args.size() != this->arg_names_.size())
return false;
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
return true;
}
protected: protected:
template<int... S> void execute_(std::vector<ExecuteServiceArgument> args, seq<S...>); virtual void execute(Ts... x) = 0;
template<int... S> void execute_(std::vector<ExecuteServiceArgument> args, seq<S...>) {
this->execute((get_execute_arg_value<Ts>(args[S]))...);
}
std::string name_; std::string name_;
uint32_t key_{0}; uint32_t key_{0};
std::array<ServiceTypeArgument, sizeof...(Ts)> args_; std::array<std::string, sizeof...(Ts)> arg_names_;
}; };
template<typename... Ts> template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...>, public Trigger<Ts...> {
template<int... S> public:
void UserService<Ts...>::execute_(std::vector<ExecuteServiceArgument> args, seq<S...>) { UserServiceTrigger(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
this->trigger((args[S].get_value<Ts>())...); : UserServiceBase<Ts...>(name, arg_names) {}
}
template<typename... Ts> void UserService<Ts...>::encode_list_service_response(APIBuffer &buffer) {
// string name = 1;
buffer.encode_string(1, this->name_);
// fixed32 key = 2;
buffer.encode_fixed32(2, this->key_);
// repeated ListServicesArgument args = 3; protected:
for (auto &arg : this->args_) { void execute(Ts... x) override { this->trigger(x...); } // NOLINT
auto nested = buffer.begin_nested(3); };
// string name = 1;
buffer.encode_string(1, arg.get_name());
// Type type = 2;
buffer.encode_int32(2, arg.get_type());
buffer.end_nested(nested);
}
}
template<typename... Ts> bool UserService<Ts...>::execute_service(const ExecuteServiceRequest &req) {
if (req.get_key() != this->key_)
return false;
if (req.get_args().size() != this->args_.size()) {
return false;
}
this->execute_(req.get_args(), typename gens<sizeof...(Ts)>::type());
return true;
}
template<typename... Ts>
UserService<Ts...>::UserService(const std::string &name, const std::array<ServiceTypeArgument, sizeof...(Ts)> &args)
: name_(name), args_(args) {
this->key_ = fnv1_hash(this->name_);
}
template<> bool ExecuteServiceArgument::get_value<bool>();
template<> int ExecuteServiceArgument::get_value<int>();
template<> float ExecuteServiceArgument::get_value<float>();
template<> std::string ExecuteServiceArgument::get_value<std::string>();
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -7,166 +7,6 @@
namespace esphome { namespace esphome {
namespace api { namespace api {
APIBuffer::APIBuffer(std::vector<uint8_t> *buffer) : buffer_(buffer) {}
size_t APIBuffer::get_length() const { return this->buffer_->size(); }
void APIBuffer::write(uint8_t value) { this->buffer_->push_back(value); }
void APIBuffer::encode_uint32(uint32_t field, uint32_t value, bool force) {
if (value == 0 && !force)
return;
this->encode_field_raw(field, 0);
this->encode_varint_raw(value);
}
void APIBuffer::encode_int32(uint32_t field, int32_t value, bool force) {
this->encode_uint32(field, static_cast<uint32_t>(value), force);
}
void APIBuffer::encode_bool(uint32_t field, bool value, bool force) {
if (!value && !force)
return;
this->encode_field_raw(field, 0);
this->write(0x01);
}
void APIBuffer::encode_string(uint32_t field, const std::string &value) {
this->encode_string(field, value.data(), value.size());
}
void APIBuffer::encode_bytes(uint32_t field, const uint8_t *data, size_t len) {
this->encode_string(field, reinterpret_cast<const char *>(data), len);
}
void APIBuffer::encode_string(uint32_t field, const char *string, size_t len) {
if (len == 0)
return;
this->encode_field_raw(field, 2);
this->encode_varint_raw(len);
const uint8_t *data = reinterpret_cast<const uint8_t *>(string);
for (size_t i = 0; i < len; i++) {
this->write(data[i]);
}
}
void APIBuffer::encode_fixed32(uint32_t field, uint32_t value, bool force) {
if (value == 0 && !force)
return;
this->encode_field_raw(field, 5);
this->write((value >> 0) & 0xFF);
this->write((value >> 8) & 0xFF);
this->write((value >> 16) & 0xFF);
this->write((value >> 24) & 0xFF);
}
void APIBuffer::encode_float(uint32_t field, float value, bool force) {
if (value == 0.0f && !force)
return;
union {
float value_f;
uint32_t value_raw;
} val;
val.value_f = value;
this->encode_fixed32(field, val.value_raw);
}
void APIBuffer::encode_field_raw(uint32_t field, uint32_t type) {
uint32_t val = (field << 3) | (type & 0b111);
this->encode_varint_raw(val);
}
void APIBuffer::encode_varint_raw(uint32_t value) {
if (value <= 0x7F) {
this->write(value);
return;
}
while (value) {
uint8_t temp = value & 0x7F;
value >>= 7;
if (value) {
this->write(temp | 0x80);
} else {
this->write(temp);
}
}
}
void APIBuffer::encode_sint32(uint32_t field, int32_t value, bool force) {
if (value < 0)
this->encode_uint32(field, ~(uint32_t(value) << 1), force);
else
this->encode_uint32(field, uint32_t(value) << 1, force);
}
void APIBuffer::encode_nameable(Nameable *nameable) {
// string object_id = 1;
this->encode_string(1, nameable->get_object_id());
// fixed32 key = 2;
this->encode_fixed32(2, nameable->get_object_id_hash());
// string name = 3;
this->encode_string(3, nameable->get_name());
}
size_t APIBuffer::begin_nested(uint32_t field) {
this->encode_field_raw(field, 2);
return this->buffer_->size();
}
void APIBuffer::end_nested(size_t begin_index) {
const uint32_t nested_length = this->buffer_->size() - begin_index;
// add varint
std::vector<uint8_t> var;
uint32_t val = nested_length;
if (val <= 0x7F) {
var.push_back(val);
} else {
while (val) {
uint8_t temp = val & 0x7F;
val >>= 7;
if (val) {
var.push_back(temp | 0x80);
} else {
var.push_back(temp);
}
}
}
this->buffer_->insert(this->buffer_->begin() + begin_index, var.begin(), var.end());
}
optional<uint32_t> proto_decode_varuint32(const uint8_t *buf, size_t len, uint32_t *consumed) {
if (len == 0)
return {};
uint32_t result = 0;
uint8_t bitpos = 0;
for (uint32_t i = 0; i < len; i++) {
uint8_t val = buf[i];
result |= uint32_t(val & 0x7F) << bitpos;
bitpos += 7;
if ((val & 0x80) == 0) {
if (consumed != nullptr) {
*consumed = i + 1;
}
return result;
}
}
return {};
}
std::string as_string(const uint8_t *value, size_t len) {
return std::string(reinterpret_cast<const char *>(value), len);
}
int32_t as_sint32(uint32_t val) {
if (val & 1)
return uint32_t(~(val >> 1));
else
return uint32_t(val >> 1);
}
float as_float(uint32_t val) {
static_assert(sizeof(uint32_t) == sizeof(float), "float must be 32bit long");
union {
uint32_t raw;
float value;
} x;
x.raw = val;
return x.value;
}
ComponentIterator::ComponentIterator(APIServer *server) : server_(server) {} ComponentIterator::ComponentIterator(APIServer *server) : server_(server) {}
void ComponentIterator::begin() { void ComponentIterator::begin() {
this->state_ = IteratorState::BEGIN; this->state_ = IteratorState::BEGIN;

View file

@ -10,40 +10,6 @@
namespace esphome { namespace esphome {
namespace api { namespace api {
class APIBuffer {
public:
APIBuffer(std::vector<uint8_t> *buffer);
size_t get_length() const;
void write(uint8_t value);
void encode_int32(uint32_t field, int32_t value, bool force = false);
void encode_uint32(uint32_t field, uint32_t value, bool force = false);
void encode_sint32(uint32_t field, int32_t value, bool force = false);
void encode_bool(uint32_t field, bool value, bool force = false);
void encode_string(uint32_t field, const std::string &value);
void encode_string(uint32_t field, const char *string, size_t len);
void encode_bytes(uint32_t field, const uint8_t *data, size_t len);
void encode_fixed32(uint32_t field, uint32_t value, bool force = false);
void encode_float(uint32_t field, float value, bool force = false);
void encode_nameable(Nameable *nameable);
size_t begin_nested(uint32_t field);
void end_nested(size_t begin_index);
void encode_field_raw(uint32_t field, uint32_t type);
void encode_varint_raw(uint32_t value);
protected:
std::vector<uint8_t> *buffer_;
};
optional<uint32_t> proto_decode_varuint32(const uint8_t *buf, size_t len, uint32_t *consumed = nullptr);
std::string as_string(const uint8_t *value, size_t len);
int32_t as_sint32(uint32_t val);
float as_float(uint32_t val);
class APIServer; class APIServer;
class UserServiceDescriptor; class UserServiceDescriptor;

View file

@ -22,19 +22,5 @@ void HomeassistantTime::setup() {
HomeassistantTime *global_homeassistant_time = nullptr; HomeassistantTime *global_homeassistant_time = nullptr;
bool GetTimeResponse::decode_32bit(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 1:
// fixed32 epoch_seconds = 1;
if (global_homeassistant_time != nullptr) {
global_homeassistant_time->set_epoch_time(value);
}
return true;
default:
return false;
}
}
api::APIMessageType GetTimeResponse::message_type() const { return api::APIMessageType::GET_TIME_RESPONSE; }
} // namespace homeassistant } // namespace homeassistant
} // namespace esphome } // namespace esphome

View file

@ -17,11 +17,5 @@ class HomeassistantTime : public time::RealTimeClock {
extern HomeassistantTime *global_homeassistant_time; extern HomeassistantTime *global_homeassistant_time;
class GetTimeResponse : public api::APIMessage {
public:
bool decode_32bit(uint32_t field_id, uint32_t value) override;
api::APIMessageType message_type() const override;
};
} // namespace homeassistant } // namespace homeassistant
} // namespace esphome } // namespace esphome

View file

@ -38,6 +38,7 @@ CONFIG_SCHEMA = cv.All(cv.Schema({
def to_code(config): def to_code(config):
cg.add_global(uart_ns.using)
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config) yield cg.register_component(var, config)

View file

@ -136,6 +136,7 @@ CONF_ENTITY_ID = 'entity_id'
CONF_ESP8266_RESTORE_FROM_FLASH = 'esp8266_restore_from_flash' CONF_ESP8266_RESTORE_FROM_FLASH = 'esp8266_restore_from_flash'
CONF_ESPHOME = 'esphome' CONF_ESPHOME = 'esphome'
CONF_ESPHOME_CORE_VERSION = 'esphome_core_version' CONF_ESPHOME_CORE_VERSION = 'esphome_core_version'
CONF_EVENT = 'event'
CONF_EXPIRE_AFTER = 'expire_after' CONF_EXPIRE_AFTER = 'expire_after'
CONF_EXTERNAL_VCC = 'external_vcc' CONF_EXTERNAL_VCC = 'external_vcc'
CONF_FALLING_EDGE = 'falling_edge' CONF_FALLING_EDGE = 'falling_edge'

View file

@ -17,6 +17,15 @@ namespace esphome {
#define TEMPLATABLE_VALUE(type, name) TEMPLATABLE_VALUE_(type, name) #define TEMPLATABLE_VALUE(type, name) TEMPLATABLE_VALUE_(type, name)
#define TEMPLATABLE_STRING_VALUE_(name) \
protected: \
TemplatableStringValue<Ts...> name##_{}; \
\
public: \
template<typename V> void set_##name(V name) { this->name##_ = name; }
#define TEMPLATABLE_STRING_VALUE(name) TEMPLATABLE_STRING_VALUE_(name)
/** Base class for all automation conditions. /** Base class for all automation conditions.
* *
* @tparam Ts The template parameters to pass when executing. * @tparam Ts The template parameters to pass when executing.

View file

@ -243,6 +243,18 @@ template<typename T, typename... X> class TemplatableValue {
std::function<T(X...)> f_; std::function<T(X...)> f_;
}; };
template<typename... X> class TemplatableStringValue : public TemplatableValue<std::string, X...> {
public:
TemplatableStringValue() : TemplatableValue<std::string, X...>() {}
template<typename F, enable_if_t<!is_callable<F, X...>::value, int> = 0>
TemplatableStringValue(F value) : TemplatableValue<std::string, X...>(value) {}
template<typename F, enable_if_t<is_callable<F, X...>::value, int> = 0>
TemplatableStringValue(F f)
: TemplatableValue<std::string, X...>([f](X... x) -> std::string { return to_string(f(x...)); }) {}
};
void delay_microseconds_accurate(uint32_t usec); void delay_microseconds_accurate(uint32_t usec);
template<typename T> class Deduplicator { template<typename T> class Deduplicator {

View file

@ -54,9 +54,9 @@ wifi:
ssid: "{ssid}" ssid: "{ssid}"
password: "{psk}" password: "{psk}"
# Enable fallback network (captive portal) # Enable fallback hotspot (captive portal) in case wifi connection fails
ap: ap:
ssid: "{name} Fallback AP" ssid: "{fallback_name}"
password: "{fallback_psk}" password: "{fallback_psk}"
captive_portal: captive_portal:
@ -75,6 +75,7 @@ def sanitize_double_quotes(value):
def wizard_file(**kwargs): def wizard_file(**kwargs):
letters = string.ascii_letters + string.digits letters = string.ascii_letters + string.digits
kwargs['fallback_name'] = "{} Fallback Hotspot".format(kwargs['name'].replace('_', ' ').title())
kwargs['fallback_psk'] = ''.join(random.choice(letters) for _ in range(12)) kwargs['fallback_psk'] = ''.join(random.choice(letters) for _ in range(12))
config = BASE_CONFIG.format(**kwargs) config = BASE_CONFIG.format(**kwargs)

View file

@ -0,0 +1,168 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: api_options.proto
import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf.internal import enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2
DESCRIPTOR = _descriptor.FileDescriptor(
name='api_options.proto',
package='',
syntax='proto2',
serialized_options=None,
serialized_pb=_b('\n\x11\x61pi_options.proto\x1a google/protobuf/descriptor.proto\"\x06\n\x04void*F\n\rAPISourceType\x12\x0f\n\x0bSOURCE_BOTH\x10\x00\x12\x11\n\rSOURCE_SERVER\x10\x01\x12\x11\n\rSOURCE_CLIENT\x10\x02:E\n\x16needs_setup_connection\x12\x1e.google.protobuf.MethodOptions\x18\x8e\x08 \x01(\x08:\x04true:C\n\x14needs_authentication\x12\x1e.google.protobuf.MethodOptions\x18\x8f\x08 \x01(\x08:\x04true:/\n\x02id\x12\x1f.google.protobuf.MessageOptions\x18\x8c\x08 \x01(\r:\x01\x30:M\n\x06source\x12\x1f.google.protobuf.MessageOptions\x18\x8d\x08 \x01(\x0e\x32\x0e.APISourceType:\x0bSOURCE_BOTH:/\n\x05ifdef\x12\x1f.google.protobuf.MessageOptions\x18\x8e\x08 \x01(\t:3\n\x03log\x12\x1f.google.protobuf.MessageOptions\x18\x8f\x08 \x01(\x08:\x04true:9\n\x08no_delay\x12\x1f.google.protobuf.MessageOptions\x18\x90\x08 \x01(\x08:\x05\x66\x61lse')
,
dependencies=[google_dot_protobuf_dot_descriptor__pb2.DESCRIPTOR,])
_APISOURCETYPE = _descriptor.EnumDescriptor(
name='APISourceType',
full_name='APISourceType',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='SOURCE_BOTH', index=0, number=0,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='SOURCE_SERVER', index=1, number=1,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='SOURCE_CLIENT', index=2, number=2,
serialized_options=None,
type=None),
],
containing_type=None,
serialized_options=None,
serialized_start=63,
serialized_end=133,
)
_sym_db.RegisterEnumDescriptor(_APISOURCETYPE)
APISourceType = enum_type_wrapper.EnumTypeWrapper(_APISOURCETYPE)
SOURCE_BOTH = 0
SOURCE_SERVER = 1
SOURCE_CLIENT = 2
NEEDS_SETUP_CONNECTION_FIELD_NUMBER = 1038
needs_setup_connection = _descriptor.FieldDescriptor(
name='needs_setup_connection', full_name='needs_setup_connection', index=0,
number=1038, type=8, cpp_type=7, label=1,
has_default_value=True, default_value=True,
message_type=None, enum_type=None, containing_type=None,
is_extension=True, extension_scope=None,
serialized_options=None, file=DESCRIPTOR)
NEEDS_AUTHENTICATION_FIELD_NUMBER = 1039
needs_authentication = _descriptor.FieldDescriptor(
name='needs_authentication', full_name='needs_authentication', index=1,
number=1039, type=8, cpp_type=7, label=1,
has_default_value=True, default_value=True,
message_type=None, enum_type=None, containing_type=None,
is_extension=True, extension_scope=None,
serialized_options=None, file=DESCRIPTOR)
ID_FIELD_NUMBER = 1036
id = _descriptor.FieldDescriptor(
name='id', full_name='id', index=2,
number=1036, type=13, cpp_type=3, label=1,
has_default_value=True, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=True, extension_scope=None,
serialized_options=None, file=DESCRIPTOR)
SOURCE_FIELD_NUMBER = 1037
source = _descriptor.FieldDescriptor(
name='source', full_name='source', index=3,
number=1037, type=14, cpp_type=8, label=1,
has_default_value=True, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=True, extension_scope=None,
serialized_options=None, file=DESCRIPTOR)
IFDEF_FIELD_NUMBER = 1038
ifdef = _descriptor.FieldDescriptor(
name='ifdef', full_name='ifdef', index=4,
number=1038, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=True, extension_scope=None,
serialized_options=None, file=DESCRIPTOR)
LOG_FIELD_NUMBER = 1039
log = _descriptor.FieldDescriptor(
name='log', full_name='log', index=5,
number=1039, type=8, cpp_type=7, label=1,
has_default_value=True, default_value=True,
message_type=None, enum_type=None, containing_type=None,
is_extension=True, extension_scope=None,
serialized_options=None, file=DESCRIPTOR)
NO_DELAY_FIELD_NUMBER = 1040
no_delay = _descriptor.FieldDescriptor(
name='no_delay', full_name='no_delay', index=6,
number=1040, type=8, cpp_type=7, label=1,
has_default_value=True, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=True, extension_scope=None,
serialized_options=None, file=DESCRIPTOR)
_VOID = _descriptor.Descriptor(
name='void',
full_name='void',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[
],
serialized_start=55,
serialized_end=61,
)
DESCRIPTOR.message_types_by_name['void'] = _VOID
DESCRIPTOR.enum_types_by_name['APISourceType'] = _APISOURCETYPE
DESCRIPTOR.extensions_by_name['needs_setup_connection'] = needs_setup_connection
DESCRIPTOR.extensions_by_name['needs_authentication'] = needs_authentication
DESCRIPTOR.extensions_by_name['id'] = id
DESCRIPTOR.extensions_by_name['source'] = source
DESCRIPTOR.extensions_by_name['ifdef'] = ifdef
DESCRIPTOR.extensions_by_name['log'] = log
DESCRIPTOR.extensions_by_name['no_delay'] = no_delay
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
void = _reflection.GeneratedProtocolMessageType('void', (_message.Message,), dict(
DESCRIPTOR = _VOID,
__module__ = 'api_options_pb2'
# @@protoc_insertion_point(class_scope:void)
))
_sym_db.RegisterMessage(void)
google_dot_protobuf_dot_descriptor__pb2.MethodOptions.RegisterExtension(needs_setup_connection)
google_dot_protobuf_dot_descriptor__pb2.MethodOptions.RegisterExtension(needs_authentication)
google_dot_protobuf_dot_descriptor__pb2.MessageOptions.RegisterExtension(id)
source.enum_type = _APISOURCETYPE
google_dot_protobuf_dot_descriptor__pb2.MessageOptions.RegisterExtension(source)
google_dot_protobuf_dot_descriptor__pb2.MessageOptions.RegisterExtension(ifdef)
google_dot_protobuf_dot_descriptor__pb2.MessageOptions.RegisterExtension(log)
google_dot_protobuf_dot_descriptor__pb2.MessageOptions.RegisterExtension(no_delay)
# @@protoc_insertion_point(module_scope)

View file

@ -0,0 +1,866 @@
"""Python 3 script to automatically generate C++ classes for ESPHome's native API.
It's pretty crappy spaghetti code, but it works.
"""
import re
from pathlib import Path
from textwrap import dedent
from subprocess import call
# Generate with
# protoc --python_out=script/api_protobuf -I esphome/components/api/ api_options.proto
import api_options_pb2 as pb
import google.protobuf.descriptor_pb2 as descriptor
cwd = Path(__file__).parent
root = cwd.parent.parent / 'esphome' / 'components' / 'api'
prot = cwd / 'api.protoc'
call(['protoc', '-o', prot, '-I', root, 'api.proto'])
content = prot.read_bytes()
d = descriptor.FileDescriptorSet.FromString(content)
def indent_list(text, padding=u' '):
return [padding + line for line in text.splitlines()]
def indent(text, padding=u' '):
return u'\n'.join(indent_list(text, padding))
def camel_to_snake(name):
# https://stackoverflow.com/a/1176023
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
class TypeInfo():
def __init__(self, field):
self._field = field
@property
def default_value(self):
return ''
@property
def name(self):
return self._field.name
@property
def arg_name(self):
return self.name
@property
def field_name(self):
return self.name
@property
def number(self):
return self._field.number
@property
def repeated(self):
return self._field.label == 3
@property
def cpp_type(self):
raise NotImplementedError
@property
def reference_type(self):
return f'{self.cpp_type} '
@property
def const_reference_type(self):
return f'{self.cpp_type} '
@property
def public_content(self) -> str:
return [self.class_member]
@property
def protected_content(self) -> str:
return []
@property
def class_member(self) -> str:
return f'{self.cpp_type} {self.field_name}{{{self.default_value}}}; // NOLINT'
@property
def decode_varint_content(self) -> str:
content = self.decode_varint
if content is None:
return None
return dedent(f'''\
case {self.number}: {{
this->{self.field_name} = {content};
return true;
}}''')
decode_varint = None
@property
def decode_length_content(self) -> str:
content = self.decode_length
if content is None:
return None
return dedent(f'''\
case {self.number}: {{
this->{self.field_name} = {content};
return true;
}}''')
decode_length = None
@property
def decode_32bit_content(self) -> str:
content = self.decode_32bit
if content is None:
return None
return dedent(f'''\
case {self.number}: {{
this->{self.field_name} = {content};
return true;
}}''')
decode_32bit = None
@property
def decode_64bit_content(self) -> str:
content = self.decode_64bit
if content is None:
return None
return dedent(f'''\
case {self.number}: {{
this->{self.field_name} = {content};
return true;
}}''')
decode_64bit = None
@property
def encode_content(self):
return f'buffer.{self.encode_func}({self.number}, this->{self.field_name});'
encode_func = None
@property
def dump_content(self):
o = f'out.append(" {self.name}: ");\n'
o += self.dump(f'this->{self.field_name}') + '\n'
o += f'out.append("\\n");\n'
return o
dump = None
TYPE_INFO = {}
def register_type(name):
def func(value):
TYPE_INFO[name] = value
return value
return func
@register_type(1)
class DoubleType(TypeInfo):
cpp_type = 'double'
default_value = '0.0'
decode_64bit = 'value.as_double()'
encode_func = 'encode_double'
def dump(self, name):
o = f'sprintf(buffer, "%g", {name});\n'
o += f'out.append(buffer);'
return o
@register_type(2)
class FloatType(TypeInfo):
cpp_type = 'float'
default_value = '0.0f'
decode_32bit = 'value.as_float()'
encode_func = 'encode_float'
def dump(self, name):
o = f'sprintf(buffer, "%g", {name});\n'
o += f'out.append(buffer);'
return o
@register_type(3)
class Int64Type(TypeInfo):
cpp_type = 'int64_t'
default_value = '0'
decode_varint = 'value.as_int64()'
encode_func = 'encode_int64'
def dump(self, name):
o = f'sprintf(buffer, "%ll", {name});\n'
o += f'out.append(buffer);'
return o
@register_type(4)
class UInt64Type(TypeInfo):
cpp_type = 'uint64_t'
default_value = '0'
decode_varint = 'value.as_uint64()'
encode_func = 'encode_uint64'
def dump(self, name):
o = f'sprintf(buffer, "%ull", {name});\n'
o += f'out.append(buffer);'
return o
@register_type(5)
class Int32Type(TypeInfo):
cpp_type = 'int32_t'
default_value = '0'
decode_varint = 'value.as_int32()'
encode_func = 'encode_int32'
def dump(self, name):
o = f'sprintf(buffer, "%d", {name});\n'
o += f'out.append(buffer);'
return o
@register_type(6)
class Fixed64Type(TypeInfo):
cpp_type = 'uint64_t'
default_value = '0'
decode_64bit = 'value.as_fixed64()'
encode_func = 'encode_fixed64'
def dump(self, name):
o = f'sprintf(buffer, "%ull", {name});\n'
o += f'out.append(buffer);'
return o
@register_type(7)
class Fixed32Type(TypeInfo):
cpp_type = 'uint32_t'
default_value = '0'
decode_32bit = 'value.as_fixed32()'
encode_func = 'encode_fixed32'
def dump(self, name):
o = f'sprintf(buffer, "%u", {name});\n'
o += f'out.append(buffer);'
return o
@register_type(8)
class BoolType(TypeInfo):
cpp_type = 'bool'
default_value = 'false'
decode_varint = 'value.as_bool()'
encode_func = 'encode_bool'
def dump(self, name):
o = f'out.append(YESNO({name}));'
return o
@register_type(9)
class StringType(TypeInfo):
cpp_type = 'std::string'
default_value = ''
reference_type = 'std::string &'
const_reference_type = 'const std::string &'
decode_length = 'value.as_string()'
encode_func = 'encode_string'
def dump(self, name):
o = f'out.append("\'").append({name}).append("\'");'
return o
@register_type(11)
class MessageType(TypeInfo):
@property
def cpp_type(self):
return self._field.type_name[1:]
default_value = ''
@property
def reference_type(self):
return f'{self.cpp_type} &'
@property
def const_reference_type(self):
return f'const {self.cpp_type} &'
@property
def encode_func(self):
return f'encode_message<{self.cpp_type}>'
@property
def decode_length(self):
return f'value.as_message<{self.cpp_type}>()'
def dump(self, name):
o = f'{name}.dump_to(out);'
return o
@register_type(12)
class BytesType(TypeInfo):
cpp_type = 'std::string'
default_value = ''
reference_type = 'std::string &'
const_reference_type = 'const std::string &'
decode_length = 'value.as_string()'
encode_func = 'encode_string'
def dump(self, name):
o = f'out.append("\'").append({name}).append("\'");'
return o
@register_type(13)
class UInt32Type(TypeInfo):
cpp_type = 'uint32_t'
default_value = '0'
decode_varint = 'value.as_uint32()'
encode_func = 'encode_uint32'
def dump(self, name):
o = f'sprintf(buffer, "%u", {name});\n'
o += f'out.append(buffer);'
return o
@register_type(14)
class EnumType(TypeInfo):
@property
def cpp_type(self):
return self._field.type_name[1:]
@property
def decode_varint(self):
return f'value.as_enum<{self.cpp_type}>()'
default_value = ''
@property
def encode_func(self):
return f'encode_enum<{self.cpp_type}>'
def dump(self, name):
o = f'out.append(proto_enum_to_string<{self.cpp_type}>({name}));'
return o
@register_type(15)
class SFixed32Type(TypeInfo):
cpp_type = 'int32_t'
default_value = '0'
decode_32bit = 'value.as_sfixed32()'
encode_func = 'encode_sfixed32'
def dump(self, name):
o = f'sprintf(buffer, "%d", {name});\n'
o += f'out.append(buffer);'
return o
@register_type(16)
class SFixed64Type(TypeInfo):
cpp_type = 'int64_t'
default_value = '0'
decode_64bit = 'value.as_sfixed64()'
encode_func = 'encode_sfixed64'
def dump(self, name):
o = f'sprintf(buffer, "%ll", {name});\n'
o += f'out.append(buffer);'
return o
@register_type(17)
class SInt32Type(TypeInfo):
cpp_type = 'int32_t'
default_value = '0'
decode_varint = 'value.as_sint32()'
encode_func = 'encode_sint32'
def dump(self, name):
o = f'sprintf(buffer, "%d", {name});\n'
o += f'out.append(buffer);'
return o
@register_type(18)
class SInt64Type(TypeInfo):
cpp_type = 'int64_t'
default_value = '0'
decode_varint = 'value.as_sint64()'
encode_func = 'encode_sin64'
def dump(self):
o = f'sprintf(buffer, "%ll", {name});\n'
o += f'out.append(buffer);'
return o
class RepeatedTypeInfo(TypeInfo):
def __init__(self, field):
super(RepeatedTypeInfo, self).__init__(field)
self._ti = TYPE_INFO[field.type](field)
@property
def cpp_type(self):
return f'std::vector<{self._ti.cpp_type}>'
@property
def reference_type(self):
return f'{self.cpp_type} &'
@property
def const_reference_type(self):
return f'const {self.cpp_type} &'
@property
def decode_varint_content(self) -> str:
content = self._ti.decode_varint
if content is None:
return None
return dedent(f'''\
case {self.number}: {{
this->{self.field_name}.push_back({content});
return true;
}}''')
@property
def decode_length_content(self) -> str:
content = self._ti.decode_length
if content is None:
return None
return dedent(f'''\
case {self.number}: {{
this->{self.field_name}.push_back({content});
return true;
}}''')
@property
def decode_32bit_content(self) -> str:
content = self._ti.decode_32bit
if content is None:
return None
return dedent(f'''\
case {self.number}: {{
this->{self.field_name}.push_back({content});
return true;
}}''')
@property
def decode_64bit_content(self) -> str:
content = self._ti.decode_64bit
if content is None:
return None
return dedent(f'''\
case {self.number}: {{
this->{self.field_name}.push_back({content});
return true;
}}''')
@property
def _ti_is_bool(self):
# std::vector is specialized for bool, reference does not work
return isinstance(self._ti, BoolType)
@property
def encode_content(self):
return f"""\
for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{
buffer.{self._ti.encode_func}({self.number}, it, true);
}}"""
@property
def dump_content(self):
o = f'for (const auto {"" if self._ti_is_bool else "&"}it : this->{self.field_name}) {{\n'
o += f' out.append(" {self.name}: ");\n'
o += indent(self._ti.dump('it')) + '\n'
o += f' out.append("\\n");\n'
o += f'}}\n'
return o
def build_enum_type(desc):
out = f"enum {desc.name} : uint32_t {{\n"
for v in desc.value:
out += f' {v.name} = {v.number},\n'
out += '};\n'
cpp = f"template<>\n"
cpp += f"const char *proto_enum_to_string<{desc.name}>({desc.name} value) {{\n"
cpp += f" switch (value) {{\n"
for v in desc.value:
cpp += f' case {v.name}: return "{v.name}";\n'
cpp += f' default: return "UNKNOWN";\n'
cpp += f' }}\n'
cpp += f'}}\n'
return out, cpp
def build_message_type(desc):
public_content = []
protected_content = []
decode_varint = []
decode_length = []
decode_32bit = []
decode_64bit = []
encode = []
dump = []
for field in desc.field:
if field.label == 3:
ti = RepeatedTypeInfo(field)
else:
ti = TYPE_INFO[field.type](field)
protected_content.extend(ti.protected_content)
public_content.extend(ti.public_content)
encode.append(ti.encode_content)
if ti.decode_varint_content:
decode_varint.append(ti.decode_varint_content)
if ti.decode_length_content:
decode_length.append(ti.decode_length_content)
if ti.decode_32bit_content:
decode_32bit.append(ti.decode_32bit_content)
if ti.decode_64bit_content:
decode_64bit.append(ti.decode_64bit_content)
if ti.dump_content:
dump.append(ti.dump_content)
cpp = ''
if decode_varint:
decode_varint.append('default:\n return false;')
o = f'bool {desc.name}::decode_varint(uint32_t field_id, ProtoVarInt value) {{\n'
o += ' switch (field_id) {\n'
o += indent("\n".join(decode_varint), ' ') + '\n'
o += ' }\n'
o += '}\n'
cpp += o
prot = 'bool decode_varint(uint32_t field_id, ProtoVarInt value) override;'
protected_content.insert(0, prot)
if decode_length:
decode_length.append('default:\n return false;')
o = f'bool {desc.name}::decode_length(uint32_t field_id, ProtoLengthDelimited value) {{\n'
o += ' switch (field_id) {\n'
o += indent("\n".join(decode_length), ' ') + '\n'
o += ' }\n'
o += '}\n'
cpp += o
prot = 'bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;'
protected_content.insert(0, prot)
if decode_32bit:
decode_32bit.append('default:\n return false;')
o = f'bool {desc.name}::decode_32bit(uint32_t field_id, Proto32Bit value) {{\n'
o += ' switch (field_id) {\n'
o += indent("\n".join(decode_32bit), ' ') + '\n'
o += ' }\n'
o += '}\n'
cpp += o
prot = 'bool decode_32bit(uint32_t field_id, Proto32Bit value) override;'
protected_content.insert(0, prot)
if decode_64bit:
decode_64bit.append('default:\n return false;')
o = f'bool {desc.name}::decode_64bit(uint32_t field_id, Proto64bit value) {{\n'
o += ' switch (field_id) {\n'
o += indent("\n".join(decode_64bit), ' ') + '\n'
o += ' }\n'
o += '}\n'
cpp += o
prot = 'bool decode_64bit(uint32_t field_id, Proto64bit value) override;'
protected_content.insert(0, prot)
o = f"void {desc.name}::encode(ProtoWriteBuffer buffer) const {{\n"
o += indent('\n'.join(encode)) + '\n'
o += '}\n'
cpp += o
prot = 'void encode(ProtoWriteBuffer buffer) const override;'
public_content.append(prot)
o = f"void {desc.name}::dump_to(std::string &out) const {{\n"
if dump:
o += f" char buffer[64];\n"
o += f' out.append("{desc.name} {{\\n");\n'
o += indent('\n'.join(dump)) + '\n'
o += f' out.append("}}");\n'
else:
o += f' out.append("{desc.name} {{}}");\n'
o += '}\n'
cpp += o
prot = 'void dump_to(std::string &out) const override;'
public_content.append(prot)
out = f"class {desc.name} : public ProtoMessage {{\n"
out += ' public:\n'
out += indent('\n'.join(public_content)) + '\n'
out += ' protected:\n'
out += indent('\n'.join(protected_content)) + '\n'
out += "};\n"
return out, cpp
file = d.file[0]
content = '''\
#pragma once
#include "proto.h"
namespace esphome {
namespace api {
'''
cpp = '''\
#include "api_pb2.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
'''
for enum in file.enum_type:
s, c = build_enum_type(enum)
content += s
cpp += c
mt = file.message_type
for m in mt:
s, c = build_message_type(m)
content += s
cpp += c
content += '''\
} // namespace api
} // namespace esphome
'''
cpp += '''\
} // namespace api
} // namespace esphome
'''
with open(root / 'api_pb2.h', 'w') as f:
f.write(content)
with open(root / 'api_pb2.cpp', 'w') as f:
f.write(cpp)
SOURCE_BOTH = 0
SOURCE_SERVER = 1
SOURCE_CLIENT = 2
RECEIVE_CASES = {}
class_name = 'APIServerConnectionBase'
ifdefs = {}
def get_opt(desc, opt, default=None):
if not desc.options.HasExtension(opt):
return default
return desc.options.Extensions[opt]
def build_service_message_type(mt):
snake = camel_to_snake(mt.name)
id_ = get_opt(mt, pb.id)
if id_ is None:
return None
source = get_opt(mt, pb.source, 0)
ifdef = get_opt(mt, pb.ifdef)
log = get_opt(mt, pb.log, True)
nodelay = get_opt(mt, pb.no_delay, False)
hout = ''
cout = ''
if ifdef is not None:
ifdefs[str(mt.name)] = ifdef
hout += f'#ifdef {ifdef}\n'
cout += f'#ifdef {ifdef}\n'
if source in (SOURCE_BOTH, SOURCE_SERVER):
# Generate send
func = f'send_{snake}'
hout += f'bool {func}(const {mt.name} &msg);\n'
cout += f'bool {class_name}::{func}(const {mt.name} &msg) {{\n'
if log:
cout += f' ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n'
cout += f' this->set_nodelay({str(nodelay).lower()});\n'
cout += f' return this->send_message_<{mt.name}>(msg, {id_});\n'
cout += f'}}\n'
if source in (SOURCE_BOTH, SOURCE_CLIENT):
# Generate receive
func = f'on_{snake}'
hout += f'virtual void {func}(const {mt.name} &value){{}};\n'
case = ''
if ifdef is not None:
case += f'#ifdef {ifdef}\n'
case += f'{mt.name} msg;\n'
case += f'msg.decode(msg_data, msg_size);\n'
if log:
case += f'ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n'
case += f'this->{func}(msg);\n'
if ifdef is not None:
case += f'#endif\n'
case += 'break;'
RECEIVE_CASES[id_] = case
if ifdef is not None:
hout += f'#endif\n'
cout += f'#endif\n'
return hout, cout
hpp = '''\
#pragma once
#include "api_pb2.h"
#include "esphome/core/defines.h"
namespace esphome {
namespace api {
'''
cpp = '''\
#include "api_pb2_service.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
static const char *TAG = "api.service";
'''
hpp += f'class {class_name} : public ProtoService {{\n'
hpp += ' public:\n'
for mt in file.message_type:
obj = build_service_message_type(mt)
if obj is None:
continue
hout, cout = obj
hpp += indent(hout) + '\n'
cpp += cout
cases = list(RECEIVE_CASES.items())
cases.sort()
hpp += ' protected:\n'
hpp += f' bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;\n'
out = f'bool {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {{\n'
out += f' switch(msg_type) {{\n'
for i, case in cases:
c = f'case {i}: {{\n'
c += indent(case) + '\n'
c += f'}}'
out += indent(c, ' ') + '\n'
out += ' default: \n'
out += ' return false;\n'
out += ' }\n'
out += ' return true;\n'
out += '}\n'
cpp += out
hpp += '};\n'
serv = file.service[0]
class_name = 'APIServerConnection'
hpp += '\n'
hpp += f'class {class_name} : public {class_name}Base {{\n'
hpp += ' public:\n'
hpp_protected = ''
cpp += '\n'
m = serv.method[0]
for m in serv.method:
func = m.name
inp = m.input_type[1:]
ret = m.output_type[1:]
is_void = ret == 'void'
snake = camel_to_snake(inp)
on_func = f'on_{snake}'
needs_conn = get_opt(m, pb.needs_setup_connection, True)
needs_auth = get_opt(m, pb.needs_authentication, True)
ifdef = ifdefs.get(inp, None)
if ifdef is not None:
hpp += f'#ifdef {ifdef}\n'
hpp_protected += f'#ifdef {ifdef}\n'
cpp += f'#ifdef {ifdef}\n'
hpp_protected += f' void {on_func}(const {inp} &msg) override;\n'
hpp += f' virtual {ret} {func}(const {inp} &msg) = 0;\n'
cpp += f'void {class_name}::{on_func}(const {inp} &msg) {{\n'
body = ''
if needs_conn:
body += 'if (!this->is_connection_setup()) {\n'
body += ' this->on_no_setup_connection();\n'
body += ' return;\n'
body += '}\n'
if needs_auth:
body += 'if (!this->is_authenticated()) {\n'
body += ' this->on_unauthenticated_access();\n'
body += ' return;\n'
body += '}\n'
if is_void:
body += f'this->{func}(msg);\n'
else:
body += f'{ret} ret = this->{func}(msg);\n'
ret_snake = camel_to_snake(ret)
body += f'if (!this->send_{ret_snake}(ret)) {{\n'
body += f' this->on_fatal_error();\n'
body += '}\n'
cpp += indent(body) + '\n' + '}\n'
if ifdef is not None:
hpp += f'#endif\n'
hpp_protected += f'#endif\n'
cpp += f'#endif\n'
hpp += ' protected:\n'
hpp += hpp_protected
hpp += '};\n'
hpp += '''\
} // namespace api
} // namespace esphome
'''
cpp += '''\
} // namespace api
} // namespace esphome
'''
with open(root / 'api_pb2_service.h', 'w') as f:
f.write(hpp)
with open(root / 'api_pb2_service.cpp', 'w') as f:
f.write(cpp)
prot.unlink()

View file

@ -28,6 +28,7 @@ EXECUTABLE_BIT = {
s[3].strip(): int(s[0]) for s in lines s[3].strip(): int(s[0]) for s in lines
} }
files = [s[3].strip() for s in lines] files = [s[3].strip() for s in lines]
files = list(filter(os.path.exists, files))
files.sort() files.sort()
file_types = ('.h', '.c', '.cpp', '.tcc', '.yaml', '.yml', '.ini', '.txt', '.ico', '.svg', file_types = ('.h', '.c', '.cpp', '.tcc', '.yaml', '.yml', '.ini', '.txt', '.ico', '.svg',

View file

@ -41,3 +41,32 @@ class CustomFloatOutput : public FloatOutput, public Component {
protected: protected:
void write_state(float state) override { ESP_LOGD("custom_output", "Setting %f", state); } void write_state(float state) override { ESP_LOGD("custom_output", "Setting %f", state); }
}; };
class CustomNativeAPI : public Component, public CustomAPIDevice {
public:
void setup() override {
register_service(&CustomNativeAPI::on_hello_world, "hello_world");
register_service(&CustomNativeAPI::on_start_dryer, "start_dryer", {"value"});
register_service(&CustomNativeAPI::on_many_values, "many_values", {"bool", "int", "float", "str1", "str2"});
subscribe_homeassistant_state(&CustomNativeAPI::on_light_state, "light.my_light");
}
void on_hello_world() { ESP_LOGD("custom_api", "Hello World from native API service!"); }
void on_start_dryer(int value) { ESP_LOGD("custom_api", "Value is %d", value); }
void on_many_values(bool a_bool, int a_int, float a_float, std::string str1, std::string str2) {
ESP_LOGD("custom_api", "Bool: %s", ONOFF(a_bool));
ESP_LOGD("custom_api", "Int: %d", a_int);
ESP_LOGD("custom_api", "Float: %f", a_float);
ESP_LOGD("custom_api", "String1: %s", str1.c_str());
ESP_LOGD("custom_api", "String2: %s", str2.c_str());
ESP_LOGD("custom_api", "Connected: %s", YESNO(is_connected()));
call_homeassistant_service("light.turn_on", {
{"entity_id", "light.my_light"},
{"brightness", "127"},
});
// no args
call_homeassistant_service("homeassistant.restart");
}
void on_light_state(std::string state) { ESP_LOGD("custom_api", "Got state %s", state.c_str()); }
};

View file

@ -40,6 +40,24 @@ api:
- stepper.set_target: - stepper.set_target:
id: my_stepper2 id: my_stepper2
target: !lambda 'return int_;' target: !lambda 'return int_;'
- service: array_types
variables:
bool_arr: bool[]
int_arr: int[]
float_arr: float[]
string_arr: string[]
then:
- logger.log:
format: 'Bool: %s (%u), Int: %d (%u), Float: %f (%u), String: %s (%u)'
args:
- YESNO(bool_arr[0])
- bool_arr.size()
- int_arr[0]
- int_arr.size()
- float_arr[0]
- float_arr.size()
- string_arr[0].c_str()
- string_arr.size()
wifi: wifi:
ssid: 'MySSID' ssid: 'MySSID'