mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	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:
		| @@ -11,7 +11,7 @@ from esphome import const, writer, yaml_util | ||||
| import esphome.codegen as cg | ||||
| from esphome.config import iter_components, read_config, strip_default_ids | ||||
| 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.helpers import color, indent | ||||
| from esphome.py_compat import IS_PY2, safe_input | ||||
| @@ -163,6 +163,7 @@ def compile_program(args, config): | ||||
| def upload_using_esptool(config, port): | ||||
|     path = CORE.firmware_bin | ||||
|     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] | ||||
|  | ||||
|     if os.environ.get('ESPHOME_USE_SUBPROCESS') is None: | ||||
|   | ||||
| @@ -1,330 +0,0 @@ | ||||
| syntax = "proto3"; | ||||
|  | ||||
| // The Home Assistant protocol is structured as a simple | ||||
| // TCP socket with short binary messages encoded in the protocol buffers format | ||||
| // First, a message in this protocol has a specific format: | ||||
| //  * VarInt denoting the size of the message object. (type is not part of this) | ||||
| //  * VarInt denoting the type of message. | ||||
| //  * The message object encoded as a ProtoBuf message | ||||
|  | ||||
| // The connection is established in 4 steps: | ||||
| //  * First, the client connects to the server and sends a "Hello Request" identifying itself | ||||
| //  * The server responds with a "Hello Response" and selects the protocol version | ||||
| //  * After receiving this message, the client attempts to authenticate itself using | ||||
| //    the password and a "Connect Request" | ||||
| //  * The server responds with a "Connect Response" and notifies of invalid password. | ||||
| // If anything in this initial process fails, the connection must immediately closed | ||||
| // by both sides and _no_ disconnection message is to be sent. | ||||
|  | ||||
| // Message sent at the beginning of each connection | ||||
| // Can only be sent by the client and only at the beginning of the connection | ||||
| message HelloRequest { | ||||
|   // Description of client (like User Agent) | ||||
|   // For example "Home Assistant" | ||||
|   // Not strictly necessary to send but nice for debugging | ||||
|   // purposes. | ||||
|   string client_info = 1; | ||||
| } | ||||
|  | ||||
| // Confirmation of successful connection request. | ||||
| // Can only be sent by the server and only at the beginning of the connection | ||||
| message HelloResponse { | ||||
|   // The version of the API to use. The _client_ (for example Home Assistant) needs to check | ||||
|   // for compatibility and if necessary adopt to an older API. | ||||
|   // Major is for breaking changes in the base protocol - a mismatch will lead to immediate disconnect_client_ | ||||
|   // Minor is for breaking changes in individual messages - a mismatch will lead to a warning message | ||||
|   uint32 api_version_major = 1; | ||||
|   uint32 api_version_minor = 2; | ||||
|  | ||||
|   // A string identifying the server (ESP); like client info this may be empty | ||||
|   // and only exists for debugging/logging purposes. | ||||
|   // For example "ESPHome v1.10.0 on ESP8266" | ||||
|   string server_info = 3; | ||||
| } | ||||
|  | ||||
| // Message sent at the beginning of each connection to authenticate the client | ||||
| // Can only be sent by the client and only at the beginning of the connection | ||||
| message ConnectRequest { | ||||
|   // The password to log in with | ||||
|   string password = 1; | ||||
| } | ||||
|  | ||||
| // Confirmation of successful connection. After this the connection is available for all traffic. | ||||
| // Can only be sent by the server and only at the beginning of the connection | ||||
| message ConnectResponse { | ||||
|   bool invalid_password = 1; | ||||
| } | ||||
|  | ||||
| // Request to close the connection. | ||||
| // Can be sent by both the client and server | ||||
| message DisconnectRequest { | ||||
|   // Do not close the connection before the acknowledgement arrives | ||||
| } | ||||
|  | ||||
| message DisconnectResponse { | ||||
|   // Empty - Both parties are required to close the connection after this | ||||
|   // message has been received. | ||||
| } | ||||
|  | ||||
| message PingRequest { | ||||
|   // Empty | ||||
| } | ||||
|  | ||||
| message PingResponse { | ||||
|   // Empty | ||||
| } | ||||
|  | ||||
| message DeviceInfoRequest { | ||||
|   // Empty | ||||
| } | ||||
|  | ||||
| message DeviceInfoResponse { | ||||
|   bool uses_password = 1; | ||||
|  | ||||
|   // The name of the node, given by "App.set_name()" | ||||
|   string name = 2; | ||||
|  | ||||
|   // The mac address of the device. For example "AC:BC:32:89:0E:A9" | ||||
|   string mac_address = 3; | ||||
|  | ||||
|   // A string describing the ESPHome version. For example "1.10.0" | ||||
|   string esphome_core_version = 4; | ||||
|  | ||||
|   // A string describing the date of compilation, this is generated by the compiler | ||||
|   // and therefore may not be in the same format all the time. | ||||
|   // If the user isn't using esphome, this will also not be set. | ||||
|   string compilation_time = 5; | ||||
|  | ||||
|   // The model of the board. For example NodeMCU | ||||
|   string model = 6; | ||||
|  | ||||
|   bool has_deep_sleep = 7; | ||||
| } | ||||
|  | ||||
| message ListEntitiesRequest { | ||||
|   // Empty | ||||
| } | ||||
|  | ||||
| message ListEntitiesBinarySensorResponse { | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   string device_class = 5; | ||||
|   bool is_status_binary_sensor = 6; | ||||
| } | ||||
| message ListEntitiesCoverResponse { | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   bool is_optimistic = 5; | ||||
| } | ||||
| message ListEntitiesFanResponse { | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   bool supports_oscillation = 5; | ||||
|   bool supports_speed = 6; | ||||
| } | ||||
| message ListEntitiesLightResponse { | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   bool supports_brightness = 5; | ||||
|   bool supports_rgb = 6; | ||||
|   bool supports_white_value = 7; | ||||
|   bool supports_color_temperature = 8; | ||||
|   float min_mireds = 9; | ||||
|   float max_mireds = 10; | ||||
|   repeated string effects = 11; | ||||
| } | ||||
| message ListEntitiesSensorResponse { | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   string icon = 5; | ||||
|   string unit_of_measurement = 6; | ||||
|   int32 accuracy_decimals = 7; | ||||
| } | ||||
| message ListEntitiesSwitchResponse { | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   string icon = 5; | ||||
|   bool optimistic = 6; | ||||
| } | ||||
| message ListEntitiesTextSensorResponse { | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   string icon = 5; | ||||
| } | ||||
| message ListEntitiesDoneResponse { | ||||
|   // Empty | ||||
| } | ||||
|  | ||||
| message SubscribeStatesRequest { | ||||
|   // Empty | ||||
| } | ||||
| message BinarySensorStateResponse { | ||||
|   fixed32 key = 1; | ||||
|   bool state = 2; | ||||
| } | ||||
| message CoverStateResponse { | ||||
|   fixed32 key = 1; | ||||
|   enum CoverState { | ||||
|     OPEN = 0; | ||||
|     CLOSED = 1; | ||||
|   } | ||||
|   CoverState state = 2; | ||||
| } | ||||
| enum FanSpeed { | ||||
|   LOW = 0; | ||||
|   MEDIUM = 1; | ||||
|   HIGH = 2; | ||||
| } | ||||
| message FanStateResponse { | ||||
|   fixed32 key = 1; | ||||
|   bool state = 2; | ||||
|   bool oscillating = 3; | ||||
|   FanSpeed speed = 4; | ||||
| } | ||||
| message LightStateResponse { | ||||
|   fixed32 key = 1; | ||||
|   bool state = 2; | ||||
|   float brightness = 3; | ||||
|   float red = 4; | ||||
|   float green = 5; | ||||
|   float blue = 6; | ||||
|   float white = 7; | ||||
|   float color_temperature = 8; | ||||
|   string effect = 9; | ||||
| } | ||||
| message SensorStateResponse { | ||||
|   fixed32 key = 1; | ||||
|   float state = 2; | ||||
| } | ||||
| message SwitchStateResponse { | ||||
|   fixed32 key = 1; | ||||
|   bool state = 2; | ||||
| } | ||||
| message TextSensorStateResponse { | ||||
|   fixed32 key = 1; | ||||
|   string state = 2; | ||||
| } | ||||
|  | ||||
| message CoverCommandRequest { | ||||
|   fixed32 key = 1; | ||||
|   enum CoverCommand { | ||||
|     OPEN = 0; | ||||
|     CLOSE = 1; | ||||
|     STOP = 2; | ||||
|   } | ||||
|   bool has_state = 2; | ||||
|   CoverCommand command = 3; | ||||
| } | ||||
| message FanCommandRequest { | ||||
|   fixed32 key = 1; | ||||
|   bool has_state = 2; | ||||
|   bool state = 3; | ||||
|   bool has_speed = 4; | ||||
|   FanSpeed speed = 5; | ||||
|   bool has_oscillating = 6; | ||||
|   bool oscillating = 7; | ||||
| } | ||||
| message LightCommandRequest { | ||||
|   fixed32 key = 1; | ||||
|   bool has_state = 2; | ||||
|   bool state = 3; | ||||
|   bool has_brightness = 4; | ||||
|   float brightness = 5; | ||||
|   bool has_rgb = 6; | ||||
|   float red = 7; | ||||
|   float green = 8; | ||||
|   float blue = 9; | ||||
|   bool has_white = 10; | ||||
|   float white = 11; | ||||
|   bool has_color_temperature = 12; | ||||
|   float color_temperature = 13; | ||||
|   bool has_transition_length = 14; | ||||
|   uint32 transition_length = 15; | ||||
|   bool has_flash_length = 16; | ||||
|   uint32 flash_length = 17; | ||||
|   bool has_effect = 18; | ||||
|   string effect = 19; | ||||
| } | ||||
| message SwitchCommandRequest { | ||||
|   fixed32 key = 1; | ||||
|   bool state = 2; | ||||
| } | ||||
|  | ||||
| enum LogLevel { | ||||
|   NONE = 0; | ||||
|   ERROR = 1; | ||||
|   WARN = 2; | ||||
|   INFO = 3; | ||||
|   DEBUG = 4; | ||||
|   VERBOSE = 5; | ||||
|   VERY_VERBOSE = 6; | ||||
| } | ||||
|  | ||||
| message SubscribeLogsRequest { | ||||
|   LogLevel level = 1; | ||||
|   bool dump_config = 2; | ||||
| } | ||||
|  | ||||
| message SubscribeLogsResponse { | ||||
|   LogLevel level = 1; | ||||
|   string tag = 2; | ||||
|   string message = 3; | ||||
|   bool send_failed = 4; | ||||
| } | ||||
|  | ||||
| message SubscribeServiceCallsRequest { | ||||
|  | ||||
| } | ||||
|  | ||||
| message ServiceCallResponse { | ||||
|   string service = 1; | ||||
|   map<string, string> data = 2; | ||||
|   map<string, string> data_template = 3; | ||||
|   map<string, string> variables = 4; | ||||
| } | ||||
|  | ||||
| // 1. Client sends SubscribeHomeAssistantStatesRequest | ||||
| // 2. Server responds with zero or more SubscribeHomeAssistantStateResponse (async) | ||||
| // 3. Client sends HomeAssistantStateResponse for state changes. | ||||
| message SubscribeHomeAssistantStatesRequest { | ||||
|  | ||||
| } | ||||
|  | ||||
| message SubscribeHomeAssistantStateResponse { | ||||
|   string entity_id = 1; | ||||
| } | ||||
|  | ||||
| message HomeAssistantStateResponse { | ||||
|   string entity_id = 1; | ||||
|   string state = 2; | ||||
| } | ||||
|  | ||||
| message GetTimeRequest { | ||||
|  | ||||
| } | ||||
|  | ||||
| message GetTimeResponse { | ||||
|   fixed32 epoch_seconds = 1; | ||||
| } | ||||
|  | ||||
| @@ -14,7 +14,7 @@ import esphome.api.api_pb2 as pb | ||||
| from esphome.const import CONF_PASSWORD, CONF_PORT | ||||
| from esphome.core import EsphomeError | ||||
| 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 | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
| @@ -247,7 +247,7 @@ class APIClient(threading.Thread): | ||||
|         if self._socket is None: | ||||
|             raise APIConnectionError("Socket closed") | ||||
|  | ||||
|         _LOGGER.debug("Write: %s", format_bytes(data)) | ||||
|         # _LOGGER.debug("Write: %s", format_bytes(data)) | ||||
|         with self._socket_write_lock: | ||||
|             try: | ||||
|                 self._socket.sendall(data) | ||||
| @@ -275,7 +275,7 @@ class APIClient(threading.Thread): | ||||
|         req += encoded | ||||
|         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() | ||||
|         responses = [] | ||||
|  | ||||
| @@ -296,7 +296,7 @@ class APIClient(threading.Thread): | ||||
|             raise APIConnectionError("Timeout while waiting for message response!") | ||||
|         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): | ||||
|             return isinstance(msg, response_type) | ||||
|  | ||||
| @@ -327,7 +327,7 @@ class APIClient(threading.Thread): | ||||
|         if not self._authenticated: | ||||
|             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() | ||||
|  | ||||
|         def on_msg(msg): | ||||
| @@ -336,8 +336,7 @@ class APIClient(threading.Thread): | ||||
|  | ||||
|         self._message_handlers.append(on_msg) | ||||
|         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) | ||||
|  | ||||
|     def _recv(self, amount): | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import esphome.config_validation as cv | ||||
| from esphome import automation | ||||
| from esphome.automation import Condition | ||||
| 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 | ||||
|  | ||||
| DEPENDENCIES = ['network'] | ||||
| @@ -11,24 +11,19 @@ DEPENDENCIES = ['network'] | ||||
| api_ns = cg.esphome_ns.namespace('api') | ||||
| APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller) | ||||
| HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', automation.Action) | ||||
| KeyValuePair = api_ns.class_('KeyValuePair') | ||||
| TemplatableKeyValuePair = api_ns.class_('TemplatableKeyValuePair') | ||||
| APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition) | ||||
|  | ||||
| UserService = api_ns.class_('UserService', automation.Trigger) | ||||
| ServiceTypeArgument = api_ns.class_('ServiceTypeArgument') | ||||
| 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, | ||||
| } | ||||
| UserServiceTrigger = api_ns.class_('UserServiceTrigger', automation.Trigger) | ||||
| ListEntitiesServicesArgument = api_ns.class_('ListEntitiesServicesArgument') | ||||
| SERVICE_ARG_NATIVE_TYPES = { | ||||
|     'bool': bool, | ||||
|     'int': cg.int32, | ||||
|     'float': float, | ||||
|     '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({ | ||||
| @@ -37,10 +32,10 @@ CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.Optional(CONF_PASSWORD, default=''): cv.string_strict, | ||||
|     cv.Optional(CONF_REBOOT_TIMEOUT, default='5min'): cv.positive_time_period_milliseconds, | ||||
|     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.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) | ||||
| @@ -58,37 +53,34 @@ def to_code(config): | ||||
|     for conf in config.get(CONF_SERVICES, []): | ||||
|         template_args = [] | ||||
|         func_args = [] | ||||
|         service_type_args = [] | ||||
|         service_arg_names = [] | ||||
|         for name, var_ in conf[CONF_VARIABLES].items(): | ||||
|             native = SERVICE_ARG_NATIVE_TYPES[var_] | ||||
|             template_args.append(native) | ||||
|             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) | ||||
|         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)) | ||||
|         yield automation.build_automation(trigger, func_args, conf) | ||||
|  | ||||
|     cg.add_define('USE_API') | ||||
|     cg.add_global(api_ns.using) | ||||
|     if CORE.is_esp32: | ||||
|         cg.add_library('AsyncTCP', '1.0.3') | ||||
|     elif CORE.is_esp8266: | ||||
|         cg.add_library('ESPAsyncTCP', '1.2.0') | ||||
|  | ||||
|  | ||||
| KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string)}) | ||||
|  | ||||
| HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.use_id(APIServer), | ||||
|     cv.Required(CONF_SERVICE): cv.string, | ||||
|     cv.Optional(CONF_DATA): cv.Schema({ | ||||
|         cv.string: cv.string, | ||||
|     }), | ||||
|     cv.Optional(CONF_DATA_TEMPLATE): cv.Schema({ | ||||
|         cv.string: cv.string, | ||||
|     }), | ||||
|     cv.Optional(CONF_VARIABLES): cv.Schema({ | ||||
|         cv.string: cv.returning_lambda, | ||||
|     }), | ||||
|     cv.Required(CONF_SERVICE): cv.templatable(cv.string), | ||||
|     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, | ||||
| }) | ||||
|  | ||||
|  | ||||
| @@ -96,20 +88,54 @@ HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({ | ||||
|                             HOMEASSISTANT_SERVICE_ACTION_SCHEMA) | ||||
| def homeassistant_service_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) | ||||
|     cg.add(var.set_service(config[CONF_SERVICE])) | ||||
|     if CONF_DATA in config: | ||||
|         datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA].items()] | ||||
|         cg.add(var.set_data(datas)) | ||||
|     if CONF_DATA_TEMPLATE in config: | ||||
|         datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA_TEMPLATE].items()] | ||||
|         cg.add(var.set_data_template(datas)) | ||||
|     if CONF_VARIABLES in config: | ||||
|         datas = [] | ||||
|         for key, value in config[CONF_VARIABLES].items(): | ||||
|             value_ = yield cg.process_lambda(value, []) | ||||
|             datas.append(TemplatableKeyValuePair(key, value_)) | ||||
|         cg.add(var.set_variables(datas)) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, serv, False) | ||||
|     templ = yield cg.templatable(config[CONF_SERVICE], 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 | ||||
|  | ||||
|  | ||||
| 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 | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,45 @@ | ||||
| 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 ==================== | ||||
|  | ||||
| @@ -21,8 +61,11 @@ syntax = "proto3"; | ||||
|  | ||||
| // Message sent at the beginning of each connection | ||||
| // Can only be sent by the client and only at the beginning of the connection | ||||
| // ID: 1 | ||||
| message HelloRequest { | ||||
|   option (id) = 1; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   // Description of client (like User Agent) | ||||
|   // For example "Home Assistant" | ||||
|   // Not strictly necessary to send but nice for debugging | ||||
| @@ -32,8 +75,11 @@ message HelloRequest { | ||||
|  | ||||
| // Confirmation of successful connection request. | ||||
| // Can only be sent by the server and only at the beginning of the connection | ||||
| // ID: 2 | ||||
| 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 | ||||
|   // 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_ | ||||
| @@ -49,49 +95,66 @@ message HelloResponse { | ||||
|  | ||||
| // 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 | ||||
| // ID: 3 | ||||
| message ConnectRequest { | ||||
|   option (id) = 3; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   // 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 | ||||
| // ID: 4 | ||||
| message ConnectResponse { | ||||
|   option (id) = 4; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   bool invalid_password = 1; | ||||
| } | ||||
|  | ||||
| // Request to close the connection. | ||||
| // Can be sent by both the client and server | ||||
| // ID: 5 | ||||
| message DisconnectRequest { | ||||
|   option (id) = 5; | ||||
|   option (source) = SOURCE_BOTH; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   // Do not close the connection before the acknowledgement arrives | ||||
| } | ||||
|  | ||||
| // ID: 6 | ||||
| message DisconnectResponse { | ||||
|   option (id) = 6; | ||||
|   option (source) = SOURCE_BOTH; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   // Empty - Both parties are required to close the connection after this | ||||
|   // message has been received. | ||||
| } | ||||
|  | ||||
| // ID: 7 | ||||
| message PingRequest { | ||||
|   option (id) = 7; | ||||
|   option (source) = SOURCE_BOTH; | ||||
|   // Empty | ||||
| } | ||||
|  | ||||
| // ID: 8 | ||||
| message PingResponse { | ||||
|   option (id) = 8; | ||||
|   option (source) = SOURCE_BOTH; | ||||
|   // Empty | ||||
| } | ||||
|  | ||||
| // ID: 9 | ||||
| message DeviceInfoRequest { | ||||
|   option (id) = 9; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   // Empty | ||||
| } | ||||
|  | ||||
| // ID: 10 | ||||
| message DeviceInfoResponse { | ||||
|   option (id) = 10; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|  | ||||
|   bool uses_password = 1; | ||||
|  | ||||
|   // The name of the node, given by "App.set_name()" | ||||
| @@ -101,7 +164,7 @@ message DeviceInfoResponse { | ||||
|   string mac_address = 3; | ||||
|  | ||||
|   // 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 | ||||
|   // and therefore may not be in the same format all the time. | ||||
| @@ -114,22 +177,29 @@ message DeviceInfoResponse { | ||||
|   bool has_deep_sleep = 7; | ||||
| } | ||||
|  | ||||
| // ID: 11 | ||||
| message ListEntitiesRequest { | ||||
|   option (id) = 11; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   // Empty | ||||
| } | ||||
| // ID: 19 | ||||
| message ListEntitiesDoneResponse { | ||||
|   option (id) = 19; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (no_delay) = true; | ||||
|   // Empty | ||||
| } | ||||
| // ID: 20 | ||||
| message SubscribeStatesRequest { | ||||
|   option (id) = 20; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   // Empty | ||||
| } | ||||
|  | ||||
| // ==================== BINARY SENSOR ==================== | ||||
| // ID: 12 | ||||
| message ListEntitiesBinarySensorResponse { | ||||
|   option (id) = 12; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_BINARY_SENSOR"; | ||||
|  | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
| @@ -138,15 +208,22 @@ message ListEntitiesBinarySensorResponse { | ||||
|   string device_class = 5; | ||||
|   bool is_status_binary_sensor = 6; | ||||
| } | ||||
| // ID: 21 | ||||
| message BinarySensorStateResponse { | ||||
|   option (id) = 21; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_BINARY_SENSOR"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   bool state = 2; | ||||
| } | ||||
|  | ||||
| // ==================== COVER ==================== | ||||
| // ID: 13 | ||||
| message ListEntitiesCoverResponse { | ||||
|   option (id) = 13; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_COVER"; | ||||
|  | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
| @@ -157,38 +234,47 @@ message ListEntitiesCoverResponse { | ||||
|   bool supports_tilt = 7; | ||||
|   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 | ||||
|   // clients/servers must still send/accept it until the next protocol change | ||||
|   enum LegacyCoverState { | ||||
|     OPEN = 0; | ||||
|     CLOSED = 1; | ||||
|   } | ||||
|   LegacyCoverState legacy_state = 2; | ||||
|  | ||||
|   float position = 3; | ||||
|   float tilt = 4; | ||||
|   enum CoverOperation { | ||||
|     IDLE = 0; | ||||
|     IS_OPENING = 1; | ||||
|     IS_CLOSING = 2; | ||||
|   } | ||||
|   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 { | ||||
|   option (id) = 30; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_COVER"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|  | ||||
|   // legacy: command has been removed in 1.13 | ||||
|   // 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; | ||||
|   LegacyCoverCommand legacy_command = 3; | ||||
|  | ||||
| @@ -200,8 +286,11 @@ message CoverCommandRequest { | ||||
| } | ||||
|  | ||||
| // ==================== FAN ==================== | ||||
| // ID: 14 | ||||
| message ListEntitiesFanResponse { | ||||
|   option (id) = 14; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_FAN"; | ||||
|  | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
| @@ -211,19 +300,27 @@ message ListEntitiesFanResponse { | ||||
|   bool supports_speed = 6; | ||||
| } | ||||
| enum FanSpeed { | ||||
|   LOW = 0; | ||||
|   MEDIUM = 1; | ||||
|   HIGH = 2; | ||||
|   FAN_SPEED_LOW = 0; | ||||
|   FAN_SPEED_MEDIUM = 1; | ||||
|   FAN_SPEED_HIGH = 2; | ||||
| } | ||||
| // ID: 23 | ||||
| message FanStateResponse { | ||||
|   option (id) = 23; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_FAN"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   bool state = 2; | ||||
|   bool oscillating = 3; | ||||
|   FanSpeed speed = 4; | ||||
| } | ||||
| // ID: 31 | ||||
| message FanCommandRequest { | ||||
|   option (id) = 31; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_FAN"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   bool has_state = 2; | ||||
|   bool state = 3; | ||||
| @@ -234,8 +331,11 @@ message FanCommandRequest { | ||||
| } | ||||
|  | ||||
| // ==================== LIGHT ==================== | ||||
| // ID: 15 | ||||
| message ListEntitiesLightResponse { | ||||
|   option (id) = 15; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_LIGHT"; | ||||
|  | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
| @@ -249,8 +349,12 @@ message ListEntitiesLightResponse { | ||||
|   float max_mireds = 10; | ||||
|   repeated string effects = 11; | ||||
| } | ||||
| // ID: 24 | ||||
| message LightStateResponse { | ||||
|   option (id) = 24; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_LIGHT"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   bool state = 2; | ||||
|   float brightness = 3; | ||||
| @@ -261,8 +365,12 @@ message LightStateResponse { | ||||
|   float color_temperature = 8; | ||||
|   string effect = 9; | ||||
| } | ||||
| // ID: 32 | ||||
| message LightCommandRequest { | ||||
|   option (id) = 32; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_LIGHT"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   bool has_state = 2; | ||||
|   bool state = 3; | ||||
| @@ -285,8 +393,11 @@ message LightCommandRequest { | ||||
| } | ||||
|  | ||||
| // ==================== SENSOR ==================== | ||||
| // ID: 16 | ||||
| message ListEntitiesSensorResponse { | ||||
|   option (id) = 16; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_SENSOR"; | ||||
|  | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
| @@ -296,15 +407,22 @@ message ListEntitiesSensorResponse { | ||||
|   string unit_of_measurement = 6; | ||||
|   int32 accuracy_decimals = 7; | ||||
| } | ||||
| // ID: 25 | ||||
| message SensorStateResponse { | ||||
|   option (id) = 25; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_SENSOR"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   float state = 2; | ||||
| } | ||||
|  | ||||
| // ==================== SWITCH ==================== | ||||
| // ID: 17 | ||||
| message ListEntitiesSwitchResponse { | ||||
|   option (id) = 17; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_SWITCH"; | ||||
|  | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
| @@ -313,20 +431,31 @@ message ListEntitiesSwitchResponse { | ||||
|   string icon = 5; | ||||
|   bool assumed_state = 6; | ||||
| } | ||||
| // ID: 26 | ||||
| message SwitchStateResponse { | ||||
|   option (id) = 26; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_SWITCH"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   bool state = 2; | ||||
| } | ||||
| // ID: 33 | ||||
| message SwitchCommandRequest { | ||||
|   option (id) = 33; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_SWITCH"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   bool state = 2; | ||||
| } | ||||
|  | ||||
| // ==================== TEXT SENSOR ==================== | ||||
| // ID: 18 | ||||
| message ListEntitiesTextSensorResponse { | ||||
|   option (id) = 18; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_TEXT_SENSOR"; | ||||
|  | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
| @@ -334,29 +463,38 @@ message ListEntitiesTextSensorResponse { | ||||
|  | ||||
|   string icon = 5; | ||||
| } | ||||
| // ID: 27 | ||||
| message TextSensorStateResponse { | ||||
|   option (id) = 27; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_TEXT_SENSOR"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   string state = 2; | ||||
| } | ||||
|  | ||||
| // ==================== SUBSCRIBE LOGS ==================== | ||||
| enum LogLevel { | ||||
|   NONE = 0; | ||||
|   ERROR = 1; | ||||
|   WARN = 2; | ||||
|   INFO = 3; | ||||
|   DEBUG = 4; | ||||
|   VERBOSE = 5; | ||||
|   VERY_VERBOSE = 6; | ||||
|   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; | ||||
| } | ||||
| // ID: 28 | ||||
| message SubscribeLogsRequest { | ||||
|   option (id) = 28; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   LogLevel level = 1; | ||||
|   bool dump_config = 2; | ||||
| } | ||||
| // ID: 29 | ||||
| message SubscribeLogsResponse { | ||||
|   option (id) = 29; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (log) = false; | ||||
|   option (no_delay) = false; | ||||
|  | ||||
|   LogLevel level = 1; | ||||
|   string tag = 2; | ||||
|   string message = 3; | ||||
| @@ -364,109 +502,153 @@ message SubscribeLogsResponse { | ||||
| } | ||||
|  | ||||
| // ==================== HOMEASSISTANT.SERVICE ==================== | ||||
| // ID: 34 | ||||
| message SubscribeServiceCallsRequest { | ||||
|  | ||||
| message SubscribeHomeassistantServicesRequest { | ||||
|   option (id) = 34; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
| } | ||||
|  | ||||
| // ID: 35 | ||||
| message ServiceCallResponse { | ||||
| message HomeassistantServiceMap { | ||||
|   string key = 1; | ||||
|   string value = 2; | ||||
| } | ||||
|  | ||||
| message HomeassistantServiceResponse { | ||||
|   option (id) = 35; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   string service = 1; | ||||
|   map<string, string> data = 2; | ||||
|   map<string, string> data_template = 3; | ||||
|   map<string, string> variables = 4; | ||||
|   repeated HomeassistantServiceMap data = 2; | ||||
|   repeated HomeassistantServiceMap data_template = 3; | ||||
|   repeated HomeassistantServiceMap variables = 4; | ||||
|   bool is_event = 5; | ||||
| } | ||||
|  | ||||
| // ==================== IMPORT HOME ASSISTANT STATES ==================== | ||||
| // 1. Client sends SubscribeHomeAssistantStatesRequest | ||||
| // 2. Server responds with zero or more SubscribeHomeAssistantStateResponse (async) | ||||
| // 3. Client sends HomeAssistantStateResponse for state changes. | ||||
| // ID: 38 | ||||
| message SubscribeHomeAssistantStatesRequest { | ||||
|  | ||||
|   option (id) = 38; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
| } | ||||
|  | ||||
| // ID: 39 | ||||
| message SubscribeHomeAssistantStateResponse { | ||||
|   option (id) = 39; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   string entity_id = 1; | ||||
| } | ||||
|  | ||||
| // ID: 40 | ||||
| message HomeAssistantStateResponse { | ||||
|   option (id) = 40; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   string entity_id = 1; | ||||
|   string state = 2; | ||||
| } | ||||
|  | ||||
| // ==================== IMPORT TIME ==================== | ||||
| // ID: 36 | ||||
| message GetTimeRequest { | ||||
|  | ||||
|   option (id) = 36; | ||||
|   option (source) = SOURCE_BOTH; | ||||
| } | ||||
|  | ||||
| // ID: 37 | ||||
| message GetTimeResponse { | ||||
|   option (id) = 37; | ||||
|   option (source) = SOURCE_BOTH; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 epoch_seconds = 1; | ||||
| } | ||||
|  | ||||
| // ==================== 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 { | ||||
|   string name = 1; | ||||
|   enum Type { | ||||
|     BOOL = 0; | ||||
|     INT = 1; | ||||
|     FLOAT = 2; | ||||
|     STRING = 3; | ||||
|   } | ||||
|   Type type = 2; | ||||
|   ServiceArgType type = 2; | ||||
| } | ||||
| // ID: 41 | ||||
| message ListEntitiesServicesResponse { | ||||
|   option (id) = 41; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|  | ||||
|   string name = 1; | ||||
|   fixed32 key = 2; | ||||
|   repeated ListEntitiesServicesArgument args = 3; | ||||
| } | ||||
| message ExecuteServiceArgument { | ||||
|   bool bool_ = 1; | ||||
|   int32 int_ = 2; | ||||
|   int32 legacy_int = 2; | ||||
|   float float_ = 3; | ||||
|   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 { | ||||
|   option (id) = 42; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   repeated ExecuteServiceArgument args = 2; | ||||
| } | ||||
|  | ||||
| // ==================== CAMERA ==================== | ||||
| // ID: 43 | ||||
| message ListEntitiesCameraResponse { | ||||
|   option (id) = 43; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_ESP32_CAMERA"; | ||||
|  | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
| } | ||||
|  | ||||
| // ID: 44 | ||||
| message CameraImageResponse { | ||||
|   option (id) = 44; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_ESP32_CAMERA"; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   bytes data = 2; | ||||
|   bool done = 3; | ||||
| } | ||||
| // ID: 45 | ||||
| message CameraImageRequest { | ||||
|   option (id) = 45; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_ESP32_CAMERA"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   bool single = 1; | ||||
|   bool stream = 2; | ||||
| } | ||||
|  | ||||
| // ==================== CLIMATE ==================== | ||||
| enum ClimateMode { | ||||
|   OFF = 0; | ||||
|   AUTO = 1; | ||||
|   COOL = 2; | ||||
|   HEAT = 3; | ||||
|   CLIMATE_MODE_OFF = 0; | ||||
|   CLIMATE_MODE_AUTO = 1; | ||||
|   CLIMATE_MODE_COOL = 2; | ||||
|   CLIMATE_MODE_HEAT = 3; | ||||
| } | ||||
| // ID: 46 | ||||
| message ListEntitiesClimateResponse { | ||||
|   option (id) = 46; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_CLIMATE"; | ||||
|  | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
| @@ -480,8 +662,12 @@ message ListEntitiesClimateResponse { | ||||
|   float visual_temperature_step = 10; | ||||
|   bool supports_away = 11; | ||||
| } | ||||
| // ID: 47 | ||||
| message ClimateStateResponse { | ||||
|   option (id) = 47; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_CLIMATE"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   ClimateMode mode = 2; | ||||
|   float current_temperature = 3; | ||||
| @@ -490,8 +676,12 @@ message ClimateStateResponse { | ||||
|   float target_temperature_high = 6; | ||||
|   bool away = 7; | ||||
| } | ||||
| // ID: 48 | ||||
| message ClimateCommandRequest { | ||||
|   option (id) = 48; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_CLIMATE"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   fixed32 key = 1; | ||||
|   bool has_mode = 2; | ||||
|   ClimateMode mode = 3; | ||||
|   | ||||
							
								
								
									
										670
									
								
								esphome/components/api/api_connection.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										670
									
								
								esphome/components/api/api_connection.cpp
									
									
									
									
									
										Normal 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 | ||||
							
								
								
									
										178
									
								
								esphome/components/api/api_connection.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								esphome/components/api/api_connection.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "api_pb2.h" | ||||
| #include "api_pb2_service.h" | ||||
| #include "api_server.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| class APIConnection : public APIServerConnection { | ||||
|  public: | ||||
|   APIConnection(AsyncClient *client, APIServer *parent); | ||||
|   virtual ~APIConnection(); | ||||
|  | ||||
|   void disconnect_client(); | ||||
|   void loop(); | ||||
|  | ||||
|   bool send_list_info_done() { | ||||
|     ListEntitiesDoneResponse resp; | ||||
|     return this->send_list_entities_done_response(resp); | ||||
|   } | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state); | ||||
|   bool send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor); | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|   bool send_cover_state(cover::Cover *cover); | ||||
|   bool send_cover_info(cover::Cover *cover); | ||||
|   void cover_command(const CoverCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   bool send_fan_state(fan::FanState *fan); | ||||
|   bool send_fan_info(fan::FanState *fan); | ||||
|   void fan_command(const FanCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   bool send_light_state(light::LightState *light); | ||||
|   bool send_light_info(light::LightState *light); | ||||
|   void light_command(const LightCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   bool send_sensor_state(sensor::Sensor *sensor, float state); | ||||
|   bool send_sensor_info(sensor::Sensor *sensor); | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   bool send_switch_state(switch_::Switch *a_switch, bool state); | ||||
|   bool send_switch_info(switch_::Switch *a_switch); | ||||
|   void switch_command(const SwitchCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state); | ||||
|   bool send_text_sensor_info(text_sensor::TextSensor *text_sensor); | ||||
| #endif | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   void send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image); | ||||
|   bool send_camera_info(esp32_camera::ESP32Camera *camera); | ||||
|   void camera_image(const CameraImageRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   bool send_climate_state(climate::Climate *climate); | ||||
|   bool send_climate_info(climate::Climate *climate); | ||||
|   void climate_command(const ClimateCommandRequest &msg) override; | ||||
| #endif | ||||
|   bool send_log_message(int level, const char *tag, const char *line); | ||||
|   void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { | ||||
|     if (!this->service_call_subscription_) | ||||
|       return; | ||||
|     this->send_homeassistant_service_response(call); | ||||
|   } | ||||
| #ifdef USE_HOMEASSISTANT_TIME | ||||
|   void send_time_request() { | ||||
|     GetTimeRequest req; | ||||
|     this->send_get_time_request(req); | ||||
|   } | ||||
| #endif | ||||
|  | ||||
|   void on_disconnect_response(const DisconnectResponse &value) override { | ||||
|     // we initiated disconnect_client | ||||
|     this->next_close_ = true; | ||||
|   } | ||||
|   void on_ping_response(const PingResponse &value) override { | ||||
|     // we initiated ping | ||||
|     this->sent_ping_ = false; | ||||
|   } | ||||
|   void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override; | ||||
| #ifdef USE_HOMEASSISTANT_TIME | ||||
|   void on_get_time_response(const GetTimeResponse &value) override; | ||||
| #endif | ||||
|   HelloResponse hello(const HelloRequest &msg) override; | ||||
|   ConnectResponse connect(const ConnectRequest &msg) override; | ||||
|   DisconnectResponse disconnect(const DisconnectRequest &msg) override { | ||||
|     // remote initiated disconnect_client | ||||
|     this->next_close_ = true; | ||||
|     DisconnectResponse resp; | ||||
|     return resp; | ||||
|   } | ||||
|   PingResponse ping(const PingRequest &msg) override { return {}; } | ||||
|   DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override; | ||||
|   void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); } | ||||
|   void subscribe_states(const SubscribeStatesRequest &msg) override { | ||||
|     this->state_subscription_ = true; | ||||
|     this->initial_state_iterator_.begin(); | ||||
|   } | ||||
|   void subscribe_logs(const SubscribeLogsRequest &msg) override { | ||||
|     this->log_subscription_ = msg.level; | ||||
|     if (msg.dump_config) | ||||
|       App.schedule_dump_config(); | ||||
|   } | ||||
|   void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override { | ||||
|     this->service_call_subscription_ = true; | ||||
|   } | ||||
|   void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override; | ||||
|   GetTimeResponse get_time(const GetTimeRequest &msg) override { | ||||
|     // TODO | ||||
|     return {}; | ||||
|   } | ||||
|   void execute_service(const ExecuteServiceRequest &msg) override; | ||||
|   bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; } | ||||
|   bool is_connection_setup() override { | ||||
|     return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated(); | ||||
|   } | ||||
|   void on_fatal_error() override; | ||||
|   void on_unauthenticated_access() override; | ||||
|   void on_no_setup_connection() override; | ||||
|   ProtoWriteBuffer create_buffer() override { | ||||
|     this->send_buffer_.clear(); | ||||
|     return {&this->send_buffer_}; | ||||
|   } | ||||
|   bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override; | ||||
|  | ||||
|  protected: | ||||
|   friend APIServer; | ||||
|  | ||||
|   void on_error_(int8_t error); | ||||
|   void on_disconnect_(); | ||||
|   void on_timeout_(uint32_t time); | ||||
|   void on_data_(uint8_t *buf, size_t len); | ||||
|   void parse_recv_buffer_(); | ||||
|   void set_nodelay(bool nodelay) override { | ||||
|     if (nodelay == this->current_nodelay_) | ||||
|       return; | ||||
|     this->client_->setNoDelay(nodelay); | ||||
|     this->current_nodelay_ = nodelay; | ||||
|   } | ||||
|  | ||||
|   enum class ConnectionState { | ||||
|     WAITING_FOR_HELLO, | ||||
|     CONNECTED, | ||||
|     AUTHENTICATED, | ||||
|   } connection_state_{ConnectionState::WAITING_FOR_HELLO}; | ||||
|  | ||||
|   bool remove_{false}; | ||||
|  | ||||
|   std::vector<uint8_t> send_buffer_; | ||||
|   std::vector<uint8_t> recv_buffer_; | ||||
|  | ||||
|   std::string client_info_; | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   esp32_camera::CameraImageReader image_reader_; | ||||
| #endif | ||||
|  | ||||
|   bool state_subscription_{false}; | ||||
|   int log_subscription_{ESPHOME_LOG_LEVEL_NONE}; | ||||
|   uint32_t last_traffic_; | ||||
|   bool sent_ping_{false}; | ||||
|   bool service_call_subscription_{false}; | ||||
|   bool current_nodelay_{false}; | ||||
|   bool next_close_{false}; | ||||
|   AsyncClient *client_; | ||||
|   APIServer *parent_; | ||||
|   InitialStateIterator initial_state_iterator_; | ||||
|   ListEntitiesIterator list_entities_iterator_; | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| @@ -1,79 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "util.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| enum class APIMessageType { | ||||
|   HELLO_REQUEST = 1, | ||||
|   HELLO_RESPONSE = 2, | ||||
|   CONNECT_REQUEST = 3, | ||||
|   CONNECT_RESPONSE = 4, | ||||
|   DISCONNECT_REQUEST = 5, | ||||
|   DISCONNECT_RESPONSE = 6, | ||||
|   PING_REQUEST = 7, | ||||
|   PING_RESPONSE = 8, | ||||
|   DEVICE_INFO_REQUEST = 9, | ||||
|   DEVICE_INFO_RESPONSE = 10, | ||||
|  | ||||
|   LIST_ENTITIES_REQUEST = 11, | ||||
|   LIST_ENTITIES_BINARY_SENSOR_RESPONSE = 12, | ||||
|   LIST_ENTITIES_COVER_RESPONSE = 13, | ||||
|   LIST_ENTITIES_FAN_RESPONSE = 14, | ||||
|   LIST_ENTITIES_LIGHT_RESPONSE = 15, | ||||
|   LIST_ENTITIES_SENSOR_RESPONSE = 16, | ||||
|   LIST_ENTITIES_SWITCH_RESPONSE = 17, | ||||
|   LIST_ENTITIES_TEXT_SENSOR_RESPONSE = 18, | ||||
|   LIST_ENTITIES_SERVICE_RESPONSE = 41, | ||||
|   LIST_ENTITIES_CAMERA_RESPONSE = 43, | ||||
|   LIST_ENTITIES_CLIMATE_RESPONSE = 46, | ||||
|   LIST_ENTITIES_DONE_RESPONSE = 19, | ||||
|  | ||||
|   SUBSCRIBE_STATES_REQUEST = 20, | ||||
|   BINARY_SENSOR_STATE_RESPONSE = 21, | ||||
|   COVER_STATE_RESPONSE = 22, | ||||
|   FAN_STATE_RESPONSE = 23, | ||||
|   LIGHT_STATE_RESPONSE = 24, | ||||
|   SENSOR_STATE_RESPONSE = 25, | ||||
|   SWITCH_STATE_RESPONSE = 26, | ||||
|   TEXT_SENSOR_STATE_RESPONSE = 27, | ||||
|   CAMERA_IMAGE_RESPONSE = 44, | ||||
|   CLIMATE_STATE_RESPONSE = 47, | ||||
|  | ||||
|   SUBSCRIBE_LOGS_REQUEST = 28, | ||||
|   SUBSCRIBE_LOGS_RESPONSE = 29, | ||||
|  | ||||
|   COVER_COMMAND_REQUEST = 30, | ||||
|   FAN_COMMAND_REQUEST = 31, | ||||
|   LIGHT_COMMAND_REQUEST = 32, | ||||
|   SWITCH_COMMAND_REQUEST = 33, | ||||
|   CAMERA_IMAGE_REQUEST = 45, | ||||
|   CLIMATE_COMMAND_REQUEST = 48, | ||||
|  | ||||
|   SUBSCRIBE_SERVICE_CALLS_REQUEST = 34, | ||||
|   SERVICE_CALL_RESPONSE = 35, | ||||
|   GET_TIME_REQUEST = 36, | ||||
|   GET_TIME_RESPONSE = 37, | ||||
|  | ||||
|   SUBSCRIBE_HOME_ASSISTANT_STATES_REQUEST = 38, | ||||
|   SUBSCRIBE_HOME_ASSISTANT_STATE_RESPONSE = 39, | ||||
|   HOME_ASSISTANT_STATE_RESPONSE = 40, | ||||
|  | ||||
|   EXECUTE_SERVICE_REQUEST = 42, | ||||
| }; | ||||
|  | ||||
| class APIMessage { | ||||
|  public: | ||||
|   void decode(const uint8_t *buffer, size_t length); | ||||
|   virtual bool decode_varint(uint32_t field_id, uint32_t value); | ||||
|   virtual bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len); | ||||
|   virtual bool decode_32bit(uint32_t field_id, uint32_t value); | ||||
|   virtual APIMessageType message_type() const = 0; | ||||
|  | ||||
|   virtual void encode(APIBuffer &buffer); | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										24
									
								
								esphome/components/api/api_options.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								esphome/components/api/api_options.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| syntax = "proto2"; | ||||
| import "google/protobuf/descriptor.proto"; | ||||
|  | ||||
|  | ||||
| enum APISourceType { | ||||
|     SOURCE_BOTH = 0; | ||||
|     SOURCE_SERVER = 1; | ||||
|     SOURCE_CLIENT = 2; | ||||
| } | ||||
|  | ||||
| message void {} | ||||
|  | ||||
| extend google.protobuf.MethodOptions { | ||||
|     optional bool needs_setup_connection = 1038 [default=true]; | ||||
|     optional bool needs_authentication = 1039 [default=true]; | ||||
| } | ||||
|  | ||||
| extend google.protobuf.MessageOptions { | ||||
|     optional uint32 id = 1036 [default=0]; | ||||
|     optional APISourceType source = 1037 [default=SOURCE_BOTH]; | ||||
|     optional string ifdef = 1038; | ||||
|     optional bool log = 1039 [default=true]; | ||||
|     optional bool no_delay = 1040 [default=false]; | ||||
| } | ||||
							
								
								
									
										2719
									
								
								esphome/components/api/api_pb2.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2719
									
								
								esphome/components/api/api_pb2.cpp
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										687
									
								
								esphome/components/api/api_pb2.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										687
									
								
								esphome/components/api/api_pb2.h
									
									
									
									
									
										Normal 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 | ||||
							
								
								
									
										582
									
								
								esphome/components/api/api_pb2_service.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										582
									
								
								esphome/components/api/api_pb2_service.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,582 @@ | ||||
| #include "api_pb2_service.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| static const char *TAG = "api.service"; | ||||
|  | ||||
| bool APIServerConnectionBase::send_hello_response(const HelloResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_hello_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(true); | ||||
|   return this->send_message_<HelloResponse>(msg, 2); | ||||
| } | ||||
| bool APIServerConnectionBase::send_connect_response(const ConnectResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_connect_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(true); | ||||
|   return this->send_message_<ConnectResponse>(msg, 4); | ||||
| } | ||||
| bool APIServerConnectionBase::send_disconnect_request(const DisconnectRequest &msg) { | ||||
|   ESP_LOGVV(TAG, "send_disconnect_request: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(true); | ||||
|   return this->send_message_<DisconnectRequest>(msg, 5); | ||||
| } | ||||
| bool APIServerConnectionBase::send_disconnect_response(const DisconnectResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_disconnect_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(true); | ||||
|   return this->send_message_<DisconnectResponse>(msg, 6); | ||||
| } | ||||
| bool APIServerConnectionBase::send_ping_request(const PingRequest &msg) { | ||||
|   ESP_LOGVV(TAG, "send_ping_request: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(false); | ||||
|   return this->send_message_<PingRequest>(msg, 7); | ||||
| } | ||||
| bool APIServerConnectionBase::send_ping_response(const PingResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_ping_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(false); | ||||
|   return this->send_message_<PingResponse>(msg, 8); | ||||
| } | ||||
| bool APIServerConnectionBase::send_device_info_response(const DeviceInfoResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_device_info_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(false); | ||||
|   return this->send_message_<DeviceInfoResponse>(msg, 10); | ||||
| } | ||||
| bool APIServerConnectionBase::send_list_entities_done_response(const ListEntitiesDoneResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_list_entities_done_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(true); | ||||
|   return this->send_message_<ListEntitiesDoneResponse>(msg, 19); | ||||
| } | ||||
| #ifdef USE_BINARY_SENSOR | ||||
| bool APIServerConnectionBase::send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_list_entities_binary_sensor_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(false); | ||||
|   return this->send_message_<ListEntitiesBinarySensorResponse>(msg, 12); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_BINARY_SENSOR | ||||
| bool APIServerConnectionBase::send_binary_sensor_state_response(const BinarySensorStateResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_binary_sensor_state_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(true); | ||||
|   return this->send_message_<BinarySensorStateResponse>(msg, 21); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
| bool APIServerConnectionBase::send_list_entities_cover_response(const ListEntitiesCoverResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_list_entities_cover_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(false); | ||||
|   return this->send_message_<ListEntitiesCoverResponse>(msg, 13); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
| bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_cover_state_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(true); | ||||
|   return this->send_message_<CoverStateResponse>(msg, 22); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
| bool APIServerConnectionBase::send_list_entities_fan_response(const ListEntitiesFanResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_list_entities_fan_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(false); | ||||
|   return this->send_message_<ListEntitiesFanResponse>(msg, 14); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
| bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_fan_state_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(true); | ||||
|   return this->send_message_<FanStateResponse>(msg, 23); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
| bool APIServerConnectionBase::send_list_entities_light_response(const ListEntitiesLightResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_list_entities_light_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(false); | ||||
|   return this->send_message_<ListEntitiesLightResponse>(msg, 15); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
| bool APIServerConnectionBase::send_light_state_response(const LightStateResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_light_state_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(true); | ||||
|   return this->send_message_<LightStateResponse>(msg, 24); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
| bool APIServerConnectionBase::send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_list_entities_sensor_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(false); | ||||
|   return this->send_message_<ListEntitiesSensorResponse>(msg, 16); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
| bool APIServerConnectionBase::send_sensor_state_response(const SensorStateResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_sensor_state_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(true); | ||||
|   return this->send_message_<SensorStateResponse>(msg, 25); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
| bool APIServerConnectionBase::send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_list_entities_switch_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(false); | ||||
|   return this->send_message_<ListEntitiesSwitchResponse>(msg, 17); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
| bool APIServerConnectionBase::send_switch_state_response(const SwitchStateResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_switch_state_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(true); | ||||
|   return this->send_message_<SwitchStateResponse>(msg, 26); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
| bool APIServerConnectionBase::send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_list_entities_text_sensor_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(false); | ||||
|   return this->send_message_<ListEntitiesTextSensorResponse>(msg, 18); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
| bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorStateResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_text_sensor_state_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(true); | ||||
|   return this->send_message_<TextSensorStateResponse>(msg, 27); | ||||
| } | ||||
| #endif | ||||
| bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) { | ||||
|   this->set_nodelay(false); | ||||
|   return this->send_message_<SubscribeLogsResponse>(msg, 29); | ||||
| } | ||||
| bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(true); | ||||
|   return this->send_message_<HomeassistantServiceResponse>(msg, 35); | ||||
| } | ||||
| bool APIServerConnectionBase::send_subscribe_home_assistant_state_response( | ||||
|     const SubscribeHomeAssistantStateResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_subscribe_home_assistant_state_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(false); | ||||
|   return this->send_message_<SubscribeHomeAssistantStateResponse>(msg, 39); | ||||
| } | ||||
| bool APIServerConnectionBase::send_get_time_request(const GetTimeRequest &msg) { | ||||
|   ESP_LOGVV(TAG, "send_get_time_request: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(false); | ||||
|   return this->send_message_<GetTimeRequest>(msg, 36); | ||||
| } | ||||
| bool APIServerConnectionBase::send_get_time_response(const GetTimeResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_get_time_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(true); | ||||
|   return this->send_message_<GetTimeResponse>(msg, 37); | ||||
| } | ||||
| bool APIServerConnectionBase::send_list_entities_services_response(const ListEntitiesServicesResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_list_entities_services_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(false); | ||||
|   return this->send_message_<ListEntitiesServicesResponse>(msg, 41); | ||||
| } | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| bool APIServerConnectionBase::send_list_entities_camera_response(const ListEntitiesCameraResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_list_entities_camera_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(false); | ||||
|   return this->send_message_<ListEntitiesCameraResponse>(msg, 43); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| bool APIServerConnectionBase::send_camera_image_response(const CameraImageResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_camera_image_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(false); | ||||
|   return this->send_message_<CameraImageResponse>(msg, 44); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
| bool APIServerConnectionBase::send_list_entities_climate_response(const ListEntitiesClimateResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_list_entities_climate_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(false); | ||||
|   return this->send_message_<ListEntitiesClimateResponse>(msg, 46); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
| bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResponse &msg) { | ||||
|   ESP_LOGVV(TAG, "send_climate_state_response: %s", msg.dump().c_str()); | ||||
|   this->set_nodelay(true); | ||||
|   return this->send_message_<ClimateStateResponse>(msg, 47); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
| #endif | ||||
| bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { | ||||
|   switch (msg_type) { | ||||
|     case 1: { | ||||
|       HelloRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_hello_request: %s", msg.dump().c_str()); | ||||
|       this->on_hello_request(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 3: { | ||||
|       ConnectRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_connect_request: %s", msg.dump().c_str()); | ||||
|       this->on_connect_request(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 5: { | ||||
|       DisconnectRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_disconnect_request: %s", msg.dump().c_str()); | ||||
|       this->on_disconnect_request(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 6: { | ||||
|       DisconnectResponse msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_disconnect_response: %s", msg.dump().c_str()); | ||||
|       this->on_disconnect_response(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 7: { | ||||
|       PingRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_ping_request: %s", msg.dump().c_str()); | ||||
|       this->on_ping_request(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 8: { | ||||
|       PingResponse msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_ping_response: %s", msg.dump().c_str()); | ||||
|       this->on_ping_response(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 9: { | ||||
|       DeviceInfoRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_device_info_request: %s", msg.dump().c_str()); | ||||
|       this->on_device_info_request(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 11: { | ||||
|       ListEntitiesRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_list_entities_request: %s", msg.dump().c_str()); | ||||
|       this->on_list_entities_request(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 20: { | ||||
|       SubscribeStatesRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_subscribe_states_request: %s", msg.dump().c_str()); | ||||
|       this->on_subscribe_states_request(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 28: { | ||||
|       SubscribeLogsRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_subscribe_logs_request: %s", msg.dump().c_str()); | ||||
|       this->on_subscribe_logs_request(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 30: { | ||||
| #ifdef USE_COVER | ||||
|       CoverCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_cover_command_request: %s", msg.dump().c_str()); | ||||
|       this->on_cover_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 31: { | ||||
| #ifdef USE_FAN | ||||
|       FanCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_fan_command_request: %s", msg.dump().c_str()); | ||||
|       this->on_fan_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 32: { | ||||
| #ifdef USE_LIGHT | ||||
|       LightCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_light_command_request: %s", msg.dump().c_str()); | ||||
|       this->on_light_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 33: { | ||||
| #ifdef USE_SWITCH | ||||
|       SwitchCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_switch_command_request: %s", msg.dump().c_str()); | ||||
|       this->on_switch_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 34: { | ||||
|       SubscribeHomeassistantServicesRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_subscribe_homeassistant_services_request: %s", msg.dump().c_str()); | ||||
|       this->on_subscribe_homeassistant_services_request(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 36: { | ||||
|       GetTimeRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_get_time_request: %s", msg.dump().c_str()); | ||||
|       this->on_get_time_request(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 37: { | ||||
|       GetTimeResponse msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_get_time_response: %s", msg.dump().c_str()); | ||||
|       this->on_get_time_response(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 38: { | ||||
|       SubscribeHomeAssistantStatesRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_subscribe_home_assistant_states_request: %s", msg.dump().c_str()); | ||||
|       this->on_subscribe_home_assistant_states_request(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 40: { | ||||
|       HomeAssistantStateResponse msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_home_assistant_state_response: %s", msg.dump().c_str()); | ||||
|       this->on_home_assistant_state_response(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 42: { | ||||
|       ExecuteServiceRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_execute_service_request: %s", msg.dump().c_str()); | ||||
|       this->on_execute_service_request(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 45: { | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|       CameraImageRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_camera_image_request: %s", msg.dump().c_str()); | ||||
|       this->on_camera_image_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 48: { | ||||
| #ifdef USE_CLIMATE | ||||
|       ClimateCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
|       ESP_LOGVV(TAG, "on_climate_command_request: %s", msg.dump().c_str()); | ||||
|       this->on_climate_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void APIServerConnection::on_hello_request(const HelloRequest &msg) { | ||||
|   HelloResponse ret = this->hello(msg); | ||||
|   if (!this->send_hello_response(ret)) { | ||||
|     this->on_fatal_error(); | ||||
|   } | ||||
| } | ||||
| void APIServerConnection::on_connect_request(const ConnectRequest &msg) { | ||||
|   ConnectResponse ret = this->connect(msg); | ||||
|   if (!this->send_connect_response(ret)) { | ||||
|     this->on_fatal_error(); | ||||
|   } | ||||
| } | ||||
| void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) { | ||||
|   DisconnectResponse ret = this->disconnect(msg); | ||||
|   if (!this->send_disconnect_response(ret)) { | ||||
|     this->on_fatal_error(); | ||||
|   } | ||||
| } | ||||
| void APIServerConnection::on_ping_request(const PingRequest &msg) { | ||||
|   PingResponse ret = this->ping(msg); | ||||
|   if (!this->send_ping_response(ret)) { | ||||
|     this->on_fatal_error(); | ||||
|   } | ||||
| } | ||||
| void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   DeviceInfoResponse ret = this->device_info(msg); | ||||
|   if (!this->send_device_info_response(ret)) { | ||||
|     this->on_fatal_error(); | ||||
|   } | ||||
| } | ||||
| void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->list_entities(msg); | ||||
| } | ||||
| void APIServerConnection::on_subscribe_states_request(const SubscribeStatesRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->subscribe_states(msg); | ||||
| } | ||||
| void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->subscribe_logs(msg); | ||||
| } | ||||
| void APIServerConnection::on_subscribe_homeassistant_services_request( | ||||
|     const SubscribeHomeassistantServicesRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->subscribe_homeassistant_services(msg); | ||||
| } | ||||
| void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->subscribe_home_assistant_states(msg); | ||||
| } | ||||
| void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   GetTimeResponse ret = this->get_time(msg); | ||||
|   if (!this->send_get_time_response(ret)) { | ||||
|     this->on_fatal_error(); | ||||
|   } | ||||
| } | ||||
| void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->execute_service(msg); | ||||
| } | ||||
| #ifdef USE_COVER | ||||
| void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->cover_command(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
| void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->fan_command(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
| void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->light_command(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
| void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->switch_command(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->camera_image(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
| void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->climate_command(msg); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										183
									
								
								esphome/components/api/api_pb2_service.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								esphome/components/api/api_pb2_service.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,183 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "api_pb2.h" | ||||
| #include "esphome/core/defines.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| class APIServerConnectionBase : public ProtoService { | ||||
|  public: | ||||
|   virtual void on_hello_request(const HelloRequest &value){}; | ||||
|   bool send_hello_response(const HelloResponse &msg); | ||||
|   virtual void on_connect_request(const ConnectRequest &value){}; | ||||
|   bool send_connect_response(const ConnectResponse &msg); | ||||
|   bool send_disconnect_request(const DisconnectRequest &msg); | ||||
|   virtual void on_disconnect_request(const DisconnectRequest &value){}; | ||||
|   bool send_disconnect_response(const DisconnectResponse &msg); | ||||
|   virtual void on_disconnect_response(const DisconnectResponse &value){}; | ||||
|   bool send_ping_request(const PingRequest &msg); | ||||
|   virtual void on_ping_request(const PingRequest &value){}; | ||||
|   bool send_ping_response(const PingResponse &msg); | ||||
|   virtual void on_ping_response(const PingResponse &value){}; | ||||
|   virtual void on_device_info_request(const DeviceInfoRequest &value){}; | ||||
|   bool send_device_info_response(const DeviceInfoResponse &msg); | ||||
|   virtual void on_list_entities_request(const ListEntitiesRequest &value){}; | ||||
|   bool send_list_entities_done_response(const ListEntitiesDoneResponse &msg); | ||||
|   virtual void on_subscribe_states_request(const SubscribeStatesRequest &value){}; | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   bool send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   bool send_binary_sensor_state_response(const BinarySensorStateResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|   bool send_list_entities_cover_response(const ListEntitiesCoverResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|   bool send_cover_state_response(const CoverStateResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|   virtual void on_cover_command_request(const CoverCommandRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   bool send_list_entities_fan_response(const ListEntitiesFanResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   bool send_fan_state_response(const FanStateResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   virtual void on_fan_command_request(const FanCommandRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   bool send_list_entities_light_response(const ListEntitiesLightResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   bool send_light_state_response(const LightStateResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   virtual void on_light_command_request(const LightCommandRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   bool send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   bool send_sensor_state_response(const SensorStateResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   bool send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   bool send_switch_state_response(const SwitchStateResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   virtual void on_switch_command_request(const SwitchCommandRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   bool send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   bool send_text_sensor_state_response(const TextSensorStateResponse &msg); | ||||
| #endif | ||||
|   virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){}; | ||||
|   bool send_subscribe_logs_response(const SubscribeLogsResponse &msg); | ||||
|   virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){}; | ||||
|   bool send_homeassistant_service_response(const HomeassistantServiceResponse &msg); | ||||
|   virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){}; | ||||
|   bool send_subscribe_home_assistant_state_response(const SubscribeHomeAssistantStateResponse &msg); | ||||
|   virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){}; | ||||
|   bool send_get_time_request(const GetTimeRequest &msg); | ||||
|   virtual void on_get_time_request(const GetTimeRequest &value){}; | ||||
|   bool send_get_time_response(const GetTimeResponse &msg); | ||||
|   virtual void on_get_time_response(const GetTimeResponse &value){}; | ||||
|   bool send_list_entities_services_response(const ListEntitiesServicesResponse &msg); | ||||
|   virtual void on_execute_service_request(const ExecuteServiceRequest &value){}; | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   bool send_list_entities_camera_response(const ListEntitiesCameraResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   bool send_camera_image_response(const CameraImageResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   virtual void on_camera_image_request(const CameraImageRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   bool send_list_entities_climate_response(const ListEntitiesClimateResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   bool send_climate_state_response(const ClimateStateResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   virtual void on_climate_command_request(const ClimateCommandRequest &value){}; | ||||
| #endif | ||||
|  protected: | ||||
|   bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; | ||||
| }; | ||||
|  | ||||
| class APIServerConnection : public APIServerConnectionBase { | ||||
|  public: | ||||
|   virtual HelloResponse hello(const HelloRequest &msg) = 0; | ||||
|   virtual ConnectResponse connect(const ConnectRequest &msg) = 0; | ||||
|   virtual DisconnectResponse disconnect(const DisconnectRequest &msg) = 0; | ||||
|   virtual PingResponse ping(const PingRequest &msg) = 0; | ||||
|   virtual DeviceInfoResponse device_info(const DeviceInfoRequest &msg) = 0; | ||||
|   virtual void list_entities(const ListEntitiesRequest &msg) = 0; | ||||
|   virtual void subscribe_states(const SubscribeStatesRequest &msg) = 0; | ||||
|   virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0; | ||||
|   virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0; | ||||
|   virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0; | ||||
|   virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0; | ||||
|   virtual void execute_service(const ExecuteServiceRequest &msg) = 0; | ||||
| #ifdef USE_COVER | ||||
|   virtual void cover_command(const CoverCommandRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   virtual void fan_command(const FanCommandRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   virtual void light_command(const LightCommandRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   virtual void switch_command(const SwitchCommandRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   virtual void camera_image(const CameraImageRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   virtual void climate_command(const ClimateCommandRequest &msg) = 0; | ||||
| #endif | ||||
|  protected: | ||||
|   void on_hello_request(const HelloRequest &msg) override; | ||||
|   void on_connect_request(const ConnectRequest &msg) override; | ||||
|   void on_disconnect_request(const DisconnectRequest &msg) override; | ||||
|   void on_ping_request(const PingRequest &msg) override; | ||||
|   void on_device_info_request(const DeviceInfoRequest &msg) override; | ||||
|   void on_list_entities_request(const ListEntitiesRequest &msg) override; | ||||
|   void on_subscribe_states_request(const SubscribeStatesRequest &msg) override; | ||||
|   void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override; | ||||
|   void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override; | ||||
|   void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override; | ||||
|   void on_get_time_request(const GetTimeRequest &msg) override; | ||||
|   void on_execute_service_request(const ExecuteServiceRequest &msg) override; | ||||
| #ifdef USE_COVER | ||||
|   void on_cover_command_request(const CoverCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   void on_fan_command_request(const FanCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   void on_light_command_request(const LightCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   void on_switch_command_request(const SwitchCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   void on_camera_image_request(const CameraImageRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   void on_climate_command_request(const ClimateCommandRequest &msg) override; | ||||
| #endif | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| @@ -1,19 +1,11 @@ | ||||
| #include <utility> | ||||
|  | ||||
| #include "api_server.h" | ||||
| #include "basic_messages.h" | ||||
| #include "api_connection.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/util.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/core/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 | ||||
| #include "esphome/components/logger/logger.h" | ||||
| #endif | ||||
| @@ -210,9 +202,9 @@ void APIServer::set_port(uint16_t port) { this->port_ = port; } | ||||
| APIServer *global_api_server = nullptr; | ||||
|  | ||||
| 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_) { | ||||
|     client->send_service_call(call); | ||||
|     client->send_homeassistant_service_call(call); | ||||
|   } | ||||
| } | ||||
| APIServer::APIServer() { global_api_server = this; } | ||||
| @@ -238,965 +230,10 @@ void APIServer::request_time() { | ||||
| bool APIServer::is_connected() const { return !this->clients_.empty(); } | ||||
| void APIServer::on_shutdown() { | ||||
|   for (auto *c : this->clients_) { | ||||
|     c->send_disconnect_request(); | ||||
|     c->send_disconnect_request(DisconnectRequest()); | ||||
|   } | ||||
|   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 esphome | ||||
|   | ||||
| @@ -4,14 +4,12 @@ | ||||
| #include "esphome/core/controller.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "api_pb2.h" | ||||
| #include "api_pb2_service.h" | ||||
| #include "util.h" | ||||
| #include "api_message.h" | ||||
| #include "basic_messages.h" | ||||
| #include "list_entities.h" | ||||
| #include "subscribe_state.h" | ||||
| #include "subscribe_logs.h" | ||||
| #include "command_messages.h" | ||||
| #include "service_call_message.h" | ||||
| #include "homeassistant_service.h" | ||||
| #include "user_services.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
| @@ -24,130 +22,6 @@ | ||||
| namespace esphome { | ||||
| 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 { | ||||
|  public: | ||||
|   APIServer(); | ||||
| @@ -187,7 +61,7 @@ class APIServer : public Component, public Controller { | ||||
| #ifdef USE_CLIMATE | ||||
|   void on_climate_update(climate::Climate *obj) override; | ||||
| #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); } | ||||
| #ifdef USE_HOMEASSISTANT_TIME | ||||
|   void request_time(); | ||||
| @@ -217,22 +91,6 @@ class APIServer : public Component, public Controller { | ||||
|  | ||||
| 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...> { | ||||
|  public: | ||||
|   bool check(Ts... x) override { return global_api_server->is_connected(); } | ||||
|   | ||||
| @@ -1,57 +0,0 @@ | ||||
| #include "basic_messages.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| // Hello | ||||
| bool HelloRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { | ||||
|   switch (field_id) { | ||||
|     case 1:  // string client_info = 1; | ||||
|       this->client_info_ = as_string(value, len); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| const std::string &HelloRequest::get_client_info() const { return this->client_info_; } | ||||
| void HelloRequest::set_client_info(const std::string &client_info) { this->client_info_ = client_info; } | ||||
| APIMessageType HelloRequest::message_type() const { return APIMessageType::HELLO_REQUEST; } | ||||
|  | ||||
| // Connect | ||||
| bool ConnectRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { | ||||
|   switch (field_id) { | ||||
|     case 1:  // string password = 1; | ||||
|       this->password_ = as_string(value, len); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| const std::string &ConnectRequest::get_password() const { return this->password_; } | ||||
| void ConnectRequest::set_password(const std::string &password) { this->password_ = password; } | ||||
| APIMessageType ConnectRequest::message_type() const { return APIMessageType::CONNECT_REQUEST; } | ||||
|  | ||||
| APIMessageType DeviceInfoRequest::message_type() const { return APIMessageType::DEVICE_INFO_REQUEST; } | ||||
| APIMessageType DisconnectRequest::message_type() const { return APIMessageType::DISCONNECT_REQUEST; } | ||||
| bool DisconnectRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { | ||||
|   switch (field_id) { | ||||
|     case 1:  // string reason = 1; | ||||
|       this->reason_ = as_string(value, len); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| const std::string &DisconnectRequest::get_reason() const { return this->reason_; } | ||||
| void DisconnectRequest::set_reason(const std::string &reason) { this->reason_ = reason; } | ||||
| void DisconnectRequest::encode(APIBuffer &buffer) { | ||||
|   // string reason = 1; | ||||
|   buffer.encode_string(1, this->reason_); | ||||
| } | ||||
| APIMessageType DisconnectResponse::message_type() const { return APIMessageType::DISCONNECT_RESPONSE; } | ||||
| APIMessageType PingRequest::message_type() const { return APIMessageType::PING_REQUEST; } | ||||
| APIMessageType PingResponse::message_type() const { return APIMessageType::PING_RESPONSE; } | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| @@ -1,63 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "api_message.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| class HelloRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override; | ||||
|   const std::string &get_client_info() const; | ||||
|   void set_client_info(const std::string &client_info); | ||||
|   APIMessageType message_type() const override; | ||||
|  | ||||
|  protected: | ||||
|   std::string client_info_; | ||||
| }; | ||||
|  | ||||
| class ConnectRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override; | ||||
|   const std::string &get_password() const; | ||||
|   void set_password(const std::string &password); | ||||
|   APIMessageType message_type() const override; | ||||
|  | ||||
|  protected: | ||||
|   std::string password_; | ||||
| }; | ||||
|  | ||||
| class DeviceInfoRequest : public APIMessage { | ||||
|  public: | ||||
|   APIMessageType message_type() const override; | ||||
| }; | ||||
|  | ||||
| class DisconnectRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override; | ||||
|   void encode(APIBuffer &buffer) override; | ||||
|   APIMessageType message_type() const override; | ||||
|   const std::string &get_reason() const; | ||||
|   void set_reason(const std::string &reason); | ||||
|  | ||||
|  protected: | ||||
|   std::string reason_; | ||||
| }; | ||||
|  | ||||
| class DisconnectResponse : public APIMessage { | ||||
|  public: | ||||
|   APIMessageType message_type() const override; | ||||
| }; | ||||
|  | ||||
| class PingRequest : public APIMessage { | ||||
|  public: | ||||
|   APIMessageType message_type() const override; | ||||
| }; | ||||
|  | ||||
| class PingResponse : public APIMessage { | ||||
|  public: | ||||
|   APIMessageType message_type() const override; | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| @@ -1,417 +0,0 @@ | ||||
| #include "command_messages.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| #ifdef USE_COVER | ||||
| bool CoverCommandRequest::decode_varint(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 2: | ||||
|       // bool has_legacy_command = 2; | ||||
|       this->has_legacy_command_ = value; | ||||
|       return true; | ||||
|     case 3: | ||||
|       // enum LegacyCoverCommand { | ||||
|       //   OPEN = 0; | ||||
|       //   CLOSE = 1; | ||||
|       //   STOP = 2; | ||||
|       // } | ||||
|       // LegacyCoverCommand legacy_command_ = 3; | ||||
|       this->legacy_command_ = static_cast<LegacyCoverCommand>(value); | ||||
|       return true; | ||||
|     case 4: | ||||
|       // bool has_position = 4; | ||||
|       this->has_position_ = value; | ||||
|       return true; | ||||
|     case 6: | ||||
|       // bool has_tilt = 6; | ||||
|       this->has_tilt_ = value; | ||||
|       return true; | ||||
|     case 8: | ||||
|       // bool stop = 8; | ||||
|       this->stop_ = value; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool CoverCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 1: | ||||
|       // fixed32 key = 1; | ||||
|       this->key_ = value; | ||||
|       return true; | ||||
|     case 5: | ||||
|       // float position = 5; | ||||
|       this->position_ = as_float(value); | ||||
|       return true; | ||||
|     case 7: | ||||
|       // float tilt = 7; | ||||
|       this->tilt_ = as_float(value); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| APIMessageType CoverCommandRequest::message_type() const { return APIMessageType ::COVER_COMMAND_REQUEST; } | ||||
| uint32_t CoverCommandRequest::get_key() const { return this->key_; } | ||||
| optional<LegacyCoverCommand> CoverCommandRequest::get_legacy_command() const { | ||||
|   if (!this->has_legacy_command_) | ||||
|     return {}; | ||||
|   return this->legacy_command_; | ||||
| } | ||||
| optional<float> CoverCommandRequest::get_position() const { | ||||
|   if (!this->has_position_) | ||||
|     return {}; | ||||
|   return this->position_; | ||||
| } | ||||
| optional<float> CoverCommandRequest::get_tilt() const { | ||||
|   if (!this->has_tilt_) | ||||
|     return {}; | ||||
|   return this->tilt_; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_FAN | ||||
| bool FanCommandRequest::decode_varint(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 2: | ||||
|       // bool has_state = 2; | ||||
|       this->has_state_ = value; | ||||
|       return true; | ||||
|     case 3: | ||||
|       // bool state = 3; | ||||
|       this->state_ = value; | ||||
|       return true; | ||||
|     case 4: | ||||
|       // bool has_speed = 4; | ||||
|       this->has_speed_ = value; | ||||
|       return true; | ||||
|     case 5: | ||||
|       // FanSpeed speed = 5; | ||||
|       this->speed_ = static_cast<fan::FanSpeed>(value); | ||||
|       return true; | ||||
|     case 6: | ||||
|       // bool has_oscillating = 6; | ||||
|       this->has_oscillating_ = value; | ||||
|       return true; | ||||
|     case 7: | ||||
|       // bool oscillating = 7; | ||||
|       this->oscillating_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool FanCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 1: | ||||
|       // fixed32 key = 1; | ||||
|       this->key_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| APIMessageType FanCommandRequest::message_type() const { return APIMessageType::FAN_COMMAND_REQUEST; } | ||||
| uint32_t FanCommandRequest::get_key() const { return this->key_; } | ||||
| optional<bool> FanCommandRequest::get_state() const { | ||||
|   if (!this->has_state_) | ||||
|     return {}; | ||||
|   return this->state_; | ||||
| } | ||||
| optional<fan::FanSpeed> FanCommandRequest::get_speed() const { | ||||
|   if (!this->has_speed_) | ||||
|     return {}; | ||||
|   return this->speed_; | ||||
| } | ||||
| optional<bool> FanCommandRequest::get_oscillating() const { | ||||
|   if (!this->has_oscillating_) | ||||
|     return {}; | ||||
|   return this->oscillating_; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_LIGHT | ||||
| bool LightCommandRequest::decode_varint(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 2: | ||||
|       // bool has_state = 2; | ||||
|       this->has_state_ = value; | ||||
|       return true; | ||||
|     case 3: | ||||
|       // bool state = 3; | ||||
|       this->state_ = value; | ||||
|       return true; | ||||
|     case 4: | ||||
|       // bool has_brightness = 4; | ||||
|       this->has_brightness_ = value; | ||||
|       return true; | ||||
|     case 6: | ||||
|       // bool has_rgb = 6; | ||||
|       this->has_rgb_ = value; | ||||
|       return true; | ||||
|     case 10: | ||||
|       // bool has_white = 10; | ||||
|       this->has_white_ = value; | ||||
|       return true; | ||||
|     case 12: | ||||
|       // bool has_color_temperature = 12; | ||||
|       this->has_color_temperature_ = value; | ||||
|       return true; | ||||
|     case 14: | ||||
|       // bool has_transition_length = 14; | ||||
|       this->has_transition_length_ = value; | ||||
|       return true; | ||||
|     case 15: | ||||
|       // uint32 transition_length = 15; | ||||
|       this->transition_length_ = value; | ||||
|       return true; | ||||
|     case 16: | ||||
|       // bool has_flash_length = 16; | ||||
|       this->has_flash_length_ = value; | ||||
|       return true; | ||||
|     case 17: | ||||
|       // uint32 flash_length = 17; | ||||
|       this->flash_length_ = value; | ||||
|       return true; | ||||
|     case 18: | ||||
|       // bool has_effect = 18; | ||||
|       this->has_effect_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool LightCommandRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { | ||||
|   switch (field_id) { | ||||
|     case 19: | ||||
|       // string effect = 19; | ||||
|       this->effect_ = as_string(value, len); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool LightCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 1: | ||||
|       // fixed32 key = 1; | ||||
|       this->key_ = value; | ||||
|       return true; | ||||
|     case 5: | ||||
|       // float brightness = 5; | ||||
|       this->brightness_ = as_float(value); | ||||
|       return true; | ||||
|     case 7: | ||||
|       // float red = 7; | ||||
|       this->red_ = as_float(value); | ||||
|       return true; | ||||
|     case 8: | ||||
|       // float green = 8; | ||||
|       this->green_ = as_float(value); | ||||
|       return true; | ||||
|     case 9: | ||||
|       // float blue = 9; | ||||
|       this->blue_ = as_float(value); | ||||
|       return true; | ||||
|     case 11: | ||||
|       // float white = 11; | ||||
|       this->white_ = as_float(value); | ||||
|       return true; | ||||
|     case 13: | ||||
|       // float color_temperature = 13; | ||||
|       this->color_temperature_ = as_float(value); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| APIMessageType LightCommandRequest::message_type() const { return APIMessageType::LIGHT_COMMAND_REQUEST; } | ||||
| uint32_t LightCommandRequest::get_key() const { return this->key_; } | ||||
| optional<bool> LightCommandRequest::get_state() const { | ||||
|   if (!this->has_state_) | ||||
|     return {}; | ||||
|   return this->state_; | ||||
| } | ||||
| optional<float> LightCommandRequest::get_brightness() const { | ||||
|   if (!this->has_brightness_) | ||||
|     return {}; | ||||
|   return this->brightness_; | ||||
| } | ||||
| optional<float> LightCommandRequest::get_red() const { | ||||
|   if (!this->has_rgb_) | ||||
|     return {}; | ||||
|   return this->red_; | ||||
| } | ||||
| optional<float> LightCommandRequest::get_green() const { | ||||
|   if (!this->has_rgb_) | ||||
|     return {}; | ||||
|   return this->green_; | ||||
| } | ||||
| optional<float> LightCommandRequest::get_blue() const { | ||||
|   if (!this->has_rgb_) | ||||
|     return {}; | ||||
|   return this->blue_; | ||||
| } | ||||
| optional<float> LightCommandRequest::get_white() const { | ||||
|   if (!this->has_white_) | ||||
|     return {}; | ||||
|   return this->white_; | ||||
| } | ||||
| optional<float> LightCommandRequest::get_color_temperature() const { | ||||
|   if (!this->has_color_temperature_) | ||||
|     return {}; | ||||
|   return this->color_temperature_; | ||||
| } | ||||
| optional<uint32_t> LightCommandRequest::get_transition_length() const { | ||||
|   if (!this->has_transition_length_) | ||||
|     return {}; | ||||
|   return this->transition_length_; | ||||
| } | ||||
| optional<uint32_t> LightCommandRequest::get_flash_length() const { | ||||
|   if (!this->has_flash_length_) | ||||
|     return {}; | ||||
|   return this->flash_length_; | ||||
| } | ||||
| optional<std::string> LightCommandRequest::get_effect() const { | ||||
|   if (!this->has_effect_) | ||||
|     return {}; | ||||
|   return this->effect_; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_SWITCH | ||||
| bool SwitchCommandRequest::decode_varint(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 2: | ||||
|       // bool state = 2; | ||||
|       this->state_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool SwitchCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 1: | ||||
|       // fixed32 key = 1; | ||||
|       this->key_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| APIMessageType SwitchCommandRequest::message_type() const { return APIMessageType::SWITCH_COMMAND_REQUEST; } | ||||
| uint32_t SwitchCommandRequest::get_key() const { return this->key_; } | ||||
| bool SwitchCommandRequest::get_state() const { return this->state_; } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| bool CameraImageRequest::get_single() const { return this->single_; } | ||||
| bool CameraImageRequest::get_stream() const { return this->stream_; } | ||||
| bool CameraImageRequest::decode_varint(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 1: | ||||
|       // bool single = 1; | ||||
|       this->single_ = value; | ||||
|       return true; | ||||
|     case 2: | ||||
|       // bool stream = 2; | ||||
|       this->stream_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| APIMessageType CameraImageRequest::message_type() const { return APIMessageType::CAMERA_IMAGE_REQUEST; } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_CLIMATE | ||||
| bool ClimateCommandRequest::decode_varint(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 2: | ||||
|       // bool has_mode = 2; | ||||
|       this->has_mode_ = value; | ||||
|       return true; | ||||
|     case 3: | ||||
|       // ClimateMode mode = 3; | ||||
|       this->mode_ = static_cast<climate::ClimateMode>(value); | ||||
|       return true; | ||||
|     case 4: | ||||
|       // bool has_target_temperature = 4; | ||||
|       this->has_target_temperature_ = value; | ||||
|       return true; | ||||
|     case 6: | ||||
|       // bool has_target_temperature_low = 6; | ||||
|       this->has_target_temperature_low_ = value; | ||||
|       return true; | ||||
|     case 8: | ||||
|       // bool has_target_temperature_high = 8; | ||||
|       this->has_target_temperature_high_ = value; | ||||
|       return true; | ||||
|     case 10: | ||||
|       // bool has_away = 10; | ||||
|       this->has_away_ = value; | ||||
|       return true; | ||||
|     case 11: | ||||
|       // bool away = 11; | ||||
|       this->away_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool ClimateCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 1: | ||||
|       // fixed32 key = 1; | ||||
|       this->key_ = value; | ||||
|       return true; | ||||
|     case 5: | ||||
|       // float target_temperature = 5; | ||||
|       this->target_temperature_ = as_float(value); | ||||
|       return true; | ||||
|     case 7: | ||||
|       // float target_temperature_low = 7; | ||||
|       this->target_temperature_low_ = as_float(value); | ||||
|       return true; | ||||
|     case 9: | ||||
|       // float target_temperature_high = 9; | ||||
|       this->target_temperature_high_ = as_float(value); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| APIMessageType ClimateCommandRequest::message_type() const { return APIMessageType::CLIMATE_COMMAND_REQUEST; } | ||||
| uint32_t ClimateCommandRequest::get_key() const { return this->key_; } | ||||
| optional<climate::ClimateMode> ClimateCommandRequest::get_mode() const { | ||||
|   if (!this->has_mode_) | ||||
|     return {}; | ||||
|   return this->mode_; | ||||
| } | ||||
| optional<float> ClimateCommandRequest::get_target_temperature() const { | ||||
|   if (!this->has_target_temperature_) | ||||
|     return {}; | ||||
|   return this->target_temperature_; | ||||
| } | ||||
| optional<float> ClimateCommandRequest::get_target_temperature_low() const { | ||||
|   if (!this->has_target_temperature_low_) | ||||
|     return {}; | ||||
|   return this->target_temperature_low_; | ||||
| } | ||||
| optional<float> ClimateCommandRequest::get_target_temperature_high() const { | ||||
|   if (!this->has_target_temperature_high_) | ||||
|     return {}; | ||||
|   return this->target_temperature_high_; | ||||
| } | ||||
| optional<bool> ClimateCommandRequest::get_away() const { | ||||
|   if (!this->has_away_) | ||||
|     return {}; | ||||
|   return this->away_; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| @@ -1,162 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "api_message.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| #ifdef USE_COVER | ||||
| enum LegacyCoverCommand { | ||||
|   LEGACY_COVER_COMMAND_OPEN = 0, | ||||
|   LEGACY_COVER_COMMAND_CLOSE = 1, | ||||
|   LEGACY_COVER_COMMAND_STOP = 2, | ||||
| }; | ||||
|  | ||||
| class CoverCommandRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_varint(uint32_t field_id, uint32_t value) override; | ||||
|   bool decode_32bit(uint32_t field_id, uint32_t value) override; | ||||
|   APIMessageType message_type() const override; | ||||
|   uint32_t get_key() const; | ||||
|   optional<LegacyCoverCommand> get_legacy_command() const; | ||||
|   optional<float> get_position() const; | ||||
|   optional<float> get_tilt() const; | ||||
|   bool get_stop() const { return this->stop_; } | ||||
|  | ||||
|  protected: | ||||
|   uint32_t key_{0}; | ||||
|   bool has_legacy_command_{false}; | ||||
|   LegacyCoverCommand legacy_command_{LEGACY_COVER_COMMAND_OPEN}; | ||||
|   bool has_position_{false}; | ||||
|   float position_{0.0f}; | ||||
|   bool has_tilt_{false}; | ||||
|   float tilt_{0.0f}; | ||||
|   bool stop_{false}; | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_FAN | ||||
| class FanCommandRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_varint(uint32_t field_id, uint32_t value) override; | ||||
|   bool decode_32bit(uint32_t field_id, uint32_t value) override; | ||||
|   APIMessageType message_type() const override; | ||||
|   uint32_t get_key() const; | ||||
|   optional<bool> get_state() const; | ||||
|   optional<fan::FanSpeed> get_speed() const; | ||||
|   optional<bool> get_oscillating() const; | ||||
|  | ||||
|  protected: | ||||
|   uint32_t key_{0}; | ||||
|   bool has_state_{false}; | ||||
|   bool state_{false}; | ||||
|   bool has_speed_{false}; | ||||
|   fan::FanSpeed speed_{fan::FAN_SPEED_LOW}; | ||||
|   bool has_oscillating_{false}; | ||||
|   bool oscillating_{false}; | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_LIGHT | ||||
| class LightCommandRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_varint(uint32_t field_id, uint32_t value) override; | ||||
|   bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override; | ||||
|   bool decode_32bit(uint32_t field_id, uint32_t value) override; | ||||
|   APIMessageType message_type() const override; | ||||
|   uint32_t get_key() const; | ||||
|   optional<bool> get_state() const; | ||||
|   optional<float> get_brightness() const; | ||||
|   optional<float> get_red() const; | ||||
|   optional<float> get_green() const; | ||||
|   optional<float> get_blue() const; | ||||
|   optional<float> get_white() const; | ||||
|   optional<float> get_color_temperature() const; | ||||
|   optional<uint32_t> get_transition_length() const; | ||||
|   optional<uint32_t> get_flash_length() const; | ||||
|   optional<std::string> get_effect() const; | ||||
|  | ||||
|  protected: | ||||
|   uint32_t key_{0}; | ||||
|   bool has_state_{false}; | ||||
|   bool state_{false}; | ||||
|   bool has_brightness_{false}; | ||||
|   float brightness_{0.0f}; | ||||
|   bool has_rgb_{false}; | ||||
|   float red_{0.0f}; | ||||
|   float green_{0.0f}; | ||||
|   float blue_{0.0f}; | ||||
|   bool has_white_{false}; | ||||
|   float white_{0.0f}; | ||||
|   bool has_color_temperature_{false}; | ||||
|   float color_temperature_{0.0f}; | ||||
|   bool has_transition_length_{false}; | ||||
|   uint32_t transition_length_{0}; | ||||
|   bool has_flash_length_{false}; | ||||
|   uint32_t flash_length_{0}; | ||||
|   bool has_effect_{false}; | ||||
|   std::string effect_{}; | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_SWITCH | ||||
| class SwitchCommandRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_varint(uint32_t field_id, uint32_t value) override; | ||||
|   bool decode_32bit(uint32_t field_id, uint32_t value) override; | ||||
|   APIMessageType message_type() const override; | ||||
|   uint32_t get_key() const; | ||||
|   bool get_state() const; | ||||
|  | ||||
|  protected: | ||||
|   uint32_t key_{0}; | ||||
|   bool state_{false}; | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| class CameraImageRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_varint(uint32_t field_id, uint32_t value) override; | ||||
|   bool get_single() const; | ||||
|   bool get_stream() const; | ||||
|   APIMessageType message_type() const override; | ||||
|  | ||||
|  protected: | ||||
|   bool single_{false}; | ||||
|   bool stream_{false}; | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_CLIMATE | ||||
| class ClimateCommandRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_varint(uint32_t field_id, uint32_t value) override; | ||||
|   bool decode_32bit(uint32_t field_id, uint32_t value) override; | ||||
|   APIMessageType message_type() const override; | ||||
|   uint32_t get_key() const; | ||||
|   optional<climate::ClimateMode> get_mode() const; | ||||
|   optional<float> get_target_temperature() const; | ||||
|   optional<float> get_target_temperature_low() const; | ||||
|   optional<float> get_target_temperature_high() const; | ||||
|   optional<bool> get_away() const; | ||||
|  | ||||
|  protected: | ||||
|   uint32_t key_{0}; | ||||
|   bool has_mode_{false}; | ||||
|   climate::ClimateMode mode_{climate::CLIMATE_MODE_OFF}; | ||||
|   bool has_target_temperature_{false}; | ||||
|   float target_temperature_{0.0f}; | ||||
|   bool has_target_temperature_low_{false}; | ||||
|   float target_temperature_low_{0.0f}; | ||||
|   bool has_target_temperature_high_{false}; | ||||
|   float target_temperature_high_{0.0f}; | ||||
|   bool has_away_{false}; | ||||
|   bool away_{false}; | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										214
									
								
								esphome/components/api/custom_api_device.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								esphome/components/api/custom_api_device.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,214 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <map> | ||||
| #include "user_services.h" | ||||
| #include "api_server.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> { | ||||
|  public: | ||||
|   CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj, | ||||
|                          void (T::*callback)(Ts...)) | ||||
|       : UserServiceBase<Ts...>(name, arg_names), obj_(obj), callback_(callback) {} | ||||
|  | ||||
|  protected: | ||||
|   void execute(Ts... x) override { (this->obj_->*this->callback_)(x...); }  // NOLINT | ||||
|  | ||||
|   T *obj_; | ||||
|   void (T::*callback_)(Ts...); | ||||
| }; | ||||
|  | ||||
| class CustomAPIDevice { | ||||
|  public: | ||||
|   /// Return if a client (such as Home Assistant) is connected to the native API. | ||||
|   bool is_connected() const { return global_api_server->is_connected(); } | ||||
|  | ||||
|   /** Register a custom native API service that will show up in Home Assistant. | ||||
|    * | ||||
|    * Usage: | ||||
|    * | ||||
|    * ```cpp | ||||
|    * void setup() override { | ||||
|    *   register_service(&CustomNativeAPI::on_start_washer_cycle, "start_washer_cycle", | ||||
|    *                    {"cycle_length"}); | ||||
|    * } | ||||
|    * | ||||
|    * void on_start_washer_cycle(int cycle_length) { | ||||
|    *   // Start washer cycle. | ||||
|    * } | ||||
|    * ``` | ||||
|    * | ||||
|    * @tparam T The class type creating the service, automatically deduced from the function pointer. | ||||
|    * @tparam Ts The argument types for the service, automatically deduced from the function arguments. | ||||
|    * @param callback The member function to call when the service is triggered. | ||||
|    * @param name The name of the service to register. | ||||
|    * @param arg_names The name of the arguments for the service, must match the arguments of the function. | ||||
|    */ | ||||
|   template<typename T, typename... Ts> | ||||
|   void register_service(void (T::*callback)(Ts...), const std::string &name, | ||||
|                         const std::array<std::string, sizeof...(Ts)> &arg_names) { | ||||
|     auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); | ||||
|     global_api_server->register_user_service(service); | ||||
|   } | ||||
|  | ||||
|   /** Register a custom native API service that will show up in Home Assistant. | ||||
|    * | ||||
|    * Usage: | ||||
|    * | ||||
|    * ```cpp | ||||
|    * void setup() override { | ||||
|    *   register_service(&CustomNativeAPI::on_hello_world, "hello_world"); | ||||
|    * } | ||||
|    * | ||||
|    * void on_hello_world() { | ||||
|    *   // Hello World service called. | ||||
|    * } | ||||
|    * ``` | ||||
|    * | ||||
|    * @tparam T The class type creating the service, automatically deduced from the function pointer. | ||||
|    * @param callback The member function to call when the service is triggered. | ||||
|    * @param name The name of the arguments for the service, must match the arguments of the function. | ||||
|    */ | ||||
|   template<typename T> void register_service(void (T::*callback)(), const std::string &name) { | ||||
|     auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); | ||||
|     global_api_server->register_user_service(service); | ||||
|   } | ||||
|  | ||||
|   /** Subscribe to the state of an entity from Home Assistant. | ||||
|    * | ||||
|    * Usage: | ||||
|    * | ||||
|    * ```cpp | ||||
|    * void setup() override { | ||||
|    *   subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast"); | ||||
|    * } | ||||
|    * | ||||
|    * void on_state_changed(std::string state) { | ||||
|    *   // State of sensor.weather_forecast is `state` | ||||
|    * } | ||||
|    * ``` | ||||
|    * | ||||
|    * @tparam T The class type creating the service, automatically deduced from the function pointer. | ||||
|    * @param callback The member function to call when the entity state changes. | ||||
|    * @param entity_id The entity_id to track. | ||||
|    */ | ||||
|   template<typename T> | ||||
|   void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id) { | ||||
|     auto f = std::bind(callback, (T *) this, std::placeholders::_1); | ||||
|     global_api_server->subscribe_home_assistant_state(entity_id, f); | ||||
|   } | ||||
|  | ||||
|   /** Subscribe to the state of an entity from Home Assistant. | ||||
|    * | ||||
|    * Usage: | ||||
|    * | ||||
|    * ```cpp | ||||
|    * void setup() override { | ||||
|    *   subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast"); | ||||
|    * } | ||||
|    * | ||||
|    * void on_state_changed(std::string entity_id, std::string state) { | ||||
|    *   // State of `entity_id` is `state` | ||||
|    * } | ||||
|    * ``` | ||||
|    * | ||||
|    * @tparam T The class type creating the service, automatically deduced from the function pointer. | ||||
|    * @param callback The member function to call when the entity state changes. | ||||
|    * @param entity_id The entity_id to track. | ||||
|    */ | ||||
|   template<typename T> | ||||
|   void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id) { | ||||
|     auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1); | ||||
|     global_api_server->subscribe_home_assistant_state(entity_id, f); | ||||
|   } | ||||
|  | ||||
|   /** Call a Home Assistant service from ESPHome. | ||||
|    * | ||||
|    * Usage: | ||||
|    * | ||||
|    * ```cpp | ||||
|    * call_homeassistant_service("homeassistant.restart"); | ||||
|    * ``` | ||||
|    * | ||||
|    * @param service_name The service to call. | ||||
|    */ | ||||
|   void call_homeassistant_service(const std::string &service_name) { | ||||
|     HomeassistantServiceResponse resp; | ||||
|     resp.service = service_name; | ||||
|     global_api_server->send_homeassistant_service_call(resp); | ||||
|   } | ||||
|  | ||||
|   /** Call a Home Assistant service from ESPHome. | ||||
|    * | ||||
|    * Usage: | ||||
|    * | ||||
|    * ```cpp | ||||
|    * call_homeassistant_service("light.turn_on", { | ||||
|    *   {"entity_id", "light.my_light"}, | ||||
|    *   {"brightness", "127"}, | ||||
|    * }); | ||||
|    * ``` | ||||
|    * | ||||
|    * @param service_name The service to call. | ||||
|    * @param data The data for the service call, mapping from string to string. | ||||
|    */ | ||||
|   void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) { | ||||
|     HomeassistantServiceResponse resp; | ||||
|     resp.service = service_name; | ||||
|     for (auto &it : data) { | ||||
|       HomeassistantServiceMap kv; | ||||
|       kv.key = it.first; | ||||
|       kv.value = it.second; | ||||
|       resp.data.push_back(kv); | ||||
|     } | ||||
|     global_api_server->send_homeassistant_service_call(resp); | ||||
|   } | ||||
|  | ||||
|   /** Fire an ESPHome event in Home Assistant. | ||||
|    * | ||||
|    * Usage: | ||||
|    * | ||||
|    * ```cpp | ||||
|    * fire_homeassistant_event("esphome.something_happened"); | ||||
|    * ``` | ||||
|    * | ||||
|    * @param event_name The event to fire. | ||||
|    */ | ||||
|   void fire_homeassistant_event(const std::string &event_name) { | ||||
|     HomeassistantServiceResponse resp; | ||||
|     resp.service = event_name; | ||||
|     resp.is_event = true; | ||||
|     global_api_server->send_homeassistant_service_call(resp); | ||||
|   } | ||||
|  | ||||
|   /** Fire an ESPHome event in Home Assistant. | ||||
|    * | ||||
|    * Usage: | ||||
|    * | ||||
|    * ```cpp | ||||
|    * fire_homeassistant_event("esphome.something_happened", { | ||||
|    *   {"my_value", "500"}, | ||||
|    * }); | ||||
|    * ``` | ||||
|    * | ||||
|    * @param event_name The event to fire. | ||||
|    * @param data The data for the event, mapping from string to string. | ||||
|    */ | ||||
|   void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) { | ||||
|     HomeassistantServiceResponse resp; | ||||
|     resp.service = service_name; | ||||
|     resp.is_event = true; | ||||
|     for (auto &it : data) { | ||||
|       HomeassistantServiceMap kv; | ||||
|       kv.key = it.first; | ||||
|       kv.value = it.second; | ||||
|       resp.data.push_back(kv); | ||||
|     } | ||||
|     global_api_server->send_homeassistant_service_call(resp); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										66
									
								
								esphome/components/api/homeassistant_service.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								esphome/components/api/homeassistant_service.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/automation.h" | ||||
| #include "api_pb2.h" | ||||
| #include "api_server.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| template<typename... Ts> class TemplatableKeyValuePair { | ||||
|  public: | ||||
|   template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {} | ||||
|   std::string key; | ||||
|   TemplatableStringValue<Ts...> value; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> { | ||||
|  public: | ||||
|   explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {} | ||||
|  | ||||
|   TEMPLATABLE_STRING_VALUE(service); | ||||
|   template<typename T> void add_data(std::string key, T value) { | ||||
|     this->data_.push_back(TemplatableKeyValuePair<Ts...>(key, value)); | ||||
|   } | ||||
|   template<typename T> void add_data_template(std::string key, T value) { | ||||
|     this->data_template_.push_back(TemplatableKeyValuePair<Ts...>(key, value)); | ||||
|   } | ||||
|   template<typename T> void add_variable(std::string key, T value) { | ||||
|     this->variables_.push_back(TemplatableKeyValuePair<Ts...>(key, value)); | ||||
|   } | ||||
|   void play(Ts... x) override { | ||||
|     HomeassistantServiceResponse resp; | ||||
|     resp.service = this->service_.value(x...); | ||||
|     resp.is_event = this->is_event_; | ||||
|     for (auto &it : this->data_) { | ||||
|       HomeassistantServiceMap kv; | ||||
|       kv.key = it.key; | ||||
|       kv.value = it.value.value(x...); | ||||
|       resp.data.push_back(kv); | ||||
|     } | ||||
|     for (auto &it : this->data_template_) { | ||||
|       HomeassistantServiceMap kv; | ||||
|       kv.key = it.key; | ||||
|       kv.value = it.value.value(x...); | ||||
|       resp.data_template.push_back(kv); | ||||
|     } | ||||
|     for (auto &it : this->variables_) { | ||||
|       HomeassistantServiceMap kv; | ||||
|       kv.key = it.key; | ||||
|       kv.value = it.value.value(x...); | ||||
|       resp.variables.push_back(kv); | ||||
|     } | ||||
|     this->parent_->send_homeassistant_service_call(resp); | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   APIServer *parent_; | ||||
|   bool is_event_; | ||||
|   std::vector<TemplatableKeyValuePair<Ts...>> data_; | ||||
|   std::vector<TemplatableKeyValuePair<Ts...>> data_template_; | ||||
|   std::vector<TemplatableKeyValuePair<Ts...>> variables_; | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| @@ -2,189 +2,54 @@ | ||||
| #include "esphome/core/util.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "api_connection.h" | ||||
|  | ||||
| namespace esphome { | ||||
| 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 | ||||
| bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { | ||||
|   auto buffer = this->client_->get_buffer(); | ||||
|   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); | ||||
|   return this->client_->send_binary_sensor_info(binary_sensor); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
| bool ListEntitiesIterator::on_cover(cover::Cover *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); | ||||
| } | ||||
| bool ListEntitiesIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_info(cover); } | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
| bool ListEntitiesIterator::on_fan(fan::FanState *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); | ||||
| } | ||||
| bool ListEntitiesIterator::on_fan(fan::FanState *fan) { return this->client_->send_fan_info(fan); } | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
| bool ListEntitiesIterator::on_light(light::LightState *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); | ||||
| } | ||||
| bool ListEntitiesIterator::on_light(light::LightState *light) { return this->client_->send_light_info(light); } | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
| bool ListEntitiesIterator::on_sensor(sensor::Sensor *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); | ||||
| } | ||||
| bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->client_->send_sensor_info(sensor); } | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
| bool ListEntitiesIterator::on_switch(switch_::Switch *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); | ||||
| } | ||||
| bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); } | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
| bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { | ||||
|   auto buffer = this->client_->get_buffer(); | ||||
|   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); | ||||
|   return this->client_->send_text_sensor_info(text_sensor); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| bool ListEntitiesIterator::on_end() { | ||||
|   return this->client_->send_empty_message(APIMessageType::LIST_ENTITIES_DONE_RESPONSE); | ||||
| } | ||||
| bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); } | ||||
| ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client) | ||||
|     : ComponentIterator(server), client_(client) {} | ||||
| bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) { | ||||
|   auto buffer = this->client_->get_buffer(); | ||||
|   service->encode_list_service_response(buffer); | ||||
|   return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SERVICE_RESPONSE); | ||||
|   auto resp = service->encode_list_service_response(); | ||||
|   return this->client_->send_list_entities_services_response(resp); | ||||
| } | ||||
|  | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) { | ||||
|   auto buffer = this->client_->get_buffer(); | ||||
|   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); | ||||
|   return this->client_->send_camera_info(camera); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_CLIMATE | ||||
| bool ListEntitiesIterator::on_climate(climate::Climate *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); | ||||
| } | ||||
| bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_info(climate); } | ||||
| #endif | ||||
|  | ||||
| APIMessageType ListEntitiesRequest::message_type() const { return APIMessageType::LIST_ENTITIES_REQUEST; } | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -2,16 +2,11 @@ | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "api_message.h" | ||||
| #include "util.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| class ListEntitiesRequest : public APIMessage { | ||||
|  public: | ||||
|   APIMessageType message_type() const override; | ||||
| }; | ||||
|  | ||||
| class APIConnection; | ||||
|  | ||||
| class ListEntitiesIterator : public ComponentIterator { | ||||
|   | ||||
| @@ -1,61 +1,59 @@ | ||||
| #include "api_message.h" | ||||
| #include "proto.h" | ||||
| #include "util.h" | ||||
| #include "esphome/core/log.h" | ||||
| 
 | ||||
| namespace esphome { | ||||
| 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; } | ||||
| 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) { | ||||
| void ProtoMessage::decode(const uint8_t *buffer, size_t length) { | ||||
|   uint32_t i = 0; | ||||
|   bool error = false; | ||||
|   while (i < length) { | ||||
|     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()) { | ||||
|       ESP_LOGV(TAG, "Invalid field start at %u", i); | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     uint32_t field_type = (*res) & 0b111; | ||||
|     uint32_t field_id = (*res) >> 3; | ||||
|     uint32_t field_type = (res->as_uint32()) & 0b111; | ||||
|     uint32_t field_id = (res->as_uint32()) >> 3; | ||||
|     i += consumed; | ||||
| 
 | ||||
|     switch (field_type) { | ||||
|       case 0: {  // VarInt
 | ||||
|         res = proto_decode_varuint32(&buffer[i], length - i, &consumed); | ||||
|         res = ProtoVarInt::parse(&buffer[i], length - i, &consumed); | ||||
|         if (!res.has_value()) { | ||||
|           ESP_LOGV(TAG, "Invalid VarInt at %u", i); | ||||
|           error = true; | ||||
|           break; | ||||
|         } | ||||
|         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; | ||||
|         break; | ||||
|       } | ||||
|       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()) { | ||||
|           ESP_LOGV(TAG, "Invalid Length Delimited at %u", i); | ||||
|           error = true; | ||||
|           break; | ||||
|         } | ||||
|         uint32_t field_length = res->as_uint32(); | ||||
|         i += consumed; | ||||
|         if (*res > length - i) { | ||||
|         if (field_length > length - i) { | ||||
|           ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %u", i); | ||||
|           error = true; | ||||
|           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); | ||||
|         } | ||||
|         i += *res; | ||||
|         i += field_length; | ||||
|         break; | ||||
|       } | ||||
|       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(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); | ||||
|         } | ||||
|         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 esphome
 | ||||
							
								
								
									
										279
									
								
								esphome/components/api/proto.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										279
									
								
								esphome/components/api/proto.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,279 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/helpers.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| /// Representation of a VarInt - in ProtoBuf should be 64bit but we only use 32bit | ||||
| class ProtoVarInt { | ||||
|  public: | ||||
|   ProtoVarInt() : value_(0) {} | ||||
|   explicit ProtoVarInt(uint64_t value) : value_(value) {} | ||||
|  | ||||
|   static optional<ProtoVarInt> parse(const uint8_t *buffer, uint32_t len, uint32_t *consumed) { | ||||
|     if (consumed != nullptr) | ||||
|       *consumed = 0; | ||||
|  | ||||
|     if (len == 0) | ||||
|       return {}; | ||||
|  | ||||
|     uint64_t result = 0; | ||||
|     uint8_t bitpos = 0; | ||||
|  | ||||
|     for (uint32_t i = 0; i < len; i++) { | ||||
|       uint8_t val = buffer[i]; | ||||
|       result |= uint64_t(val & 0x7F) << uint64_t(bitpos); | ||||
|       bitpos += 7; | ||||
|       if ((val & 0x80) == 0) { | ||||
|         if (consumed != nullptr) | ||||
|           *consumed = i + 1; | ||||
|         return ProtoVarInt(result); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return {}; | ||||
|   } | ||||
|  | ||||
|   uint32_t as_uint32() const { return this->value_; } | ||||
|   uint64_t as_uint64() const { return this->value_; } | ||||
|   bool as_bool() const { return this->value_; } | ||||
|   template<typename T> T as_enum() const { return static_cast<T>(this->as_uint32()); } | ||||
|   int32_t as_int32() const { | ||||
|     // Not ZigZag encoded | ||||
|     return static_cast<int32_t>(this->as_int64()); | ||||
|   } | ||||
|   int64_t as_int64() const { | ||||
|     // Not ZigZag encoded | ||||
|     return static_cast<int64_t>(this->value_); | ||||
|   } | ||||
|   int32_t as_sint32() const { | ||||
|     // with ZigZag encoding | ||||
|     if (this->value_ & 1) | ||||
|       return static_cast<int32_t>(~(this->value_ >> 1)); | ||||
|     else | ||||
|       return static_cast<int32_t>(this->value_ >> 1); | ||||
|   } | ||||
|   int64_t as_sint64() const { | ||||
|     // with ZigZag encoding | ||||
|     if (this->value_ & 1) | ||||
|       return static_cast<int64_t>(~(this->value_ >> 1)); | ||||
|     else | ||||
|       return static_cast<int64_t>(this->value_ >> 1); | ||||
|   } | ||||
|   void encode(std::vector<uint8_t> &out) { | ||||
|     uint32_t val = this->value_; | ||||
|     if (val <= 0x7F) { | ||||
|       out.push_back(val); | ||||
|       return; | ||||
|     } | ||||
|     while (val) { | ||||
|       uint8_t temp = val & 0x7F; | ||||
|       val >>= 7; | ||||
|       if (val) { | ||||
|         out.push_back(temp | 0x80); | ||||
|       } else { | ||||
|         out.push_back(temp); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   uint64_t value_; | ||||
| }; | ||||
|  | ||||
| class ProtoLengthDelimited { | ||||
|  public: | ||||
|   explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {} | ||||
|   std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); } | ||||
|   template<class C> C as_message() const { | ||||
|     auto msg = C(); | ||||
|     msg.decode(this->value_, this->length_); | ||||
|     return msg; | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   const uint8_t *const value_; | ||||
|   const size_t length_; | ||||
| }; | ||||
|  | ||||
| class Proto32Bit { | ||||
|  public: | ||||
|   explicit Proto32Bit(uint32_t value) : value_(value) {} | ||||
|   uint32_t as_fixed32() const { return this->value_; } | ||||
|   int32_t as_sfixed32() const { return static_cast<int32_t>(this->value_); } | ||||
|   float as_float() const { | ||||
|     union { | ||||
|       uint32_t raw; | ||||
|       float value; | ||||
|     } s{}; | ||||
|     s.raw = this->value_; | ||||
|     return s.value; | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   const uint32_t value_; | ||||
| }; | ||||
|  | ||||
| class Proto64Bit { | ||||
|  public: | ||||
|   explicit Proto64Bit(uint64_t value) : value_(value) {} | ||||
|   uint64_t as_fixed64() const { return this->value_; } | ||||
|   int64_t as_sfixed64() const { return static_cast<int64_t>(this->value_); } | ||||
|   double as_double() const { | ||||
|     union { | ||||
|       uint64_t raw; | ||||
|       double value; | ||||
|     } s{}; | ||||
|     s.raw = this->value_; | ||||
|     return s.value; | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   const uint64_t value_; | ||||
| }; | ||||
|  | ||||
| class ProtoWriteBuffer { | ||||
|  public: | ||||
|   ProtoWriteBuffer(std::vector<uint8_t> *buffer) : buffer_(buffer) {} | ||||
|   void write(uint8_t value) { this->buffer_->push_back(value); } | ||||
|   void encode_varint_raw(ProtoVarInt value) { value.encode(*this->buffer_); } | ||||
|   void encode_varint_raw(uint32_t value) { this->encode_varint_raw(ProtoVarInt(value)); } | ||||
|   void encode_field_raw(uint32_t field_id, uint32_t type) { | ||||
|     uint32_t val = (field_id << 3) | (type & 0b111); | ||||
|     this->encode_varint_raw(val); | ||||
|   } | ||||
|   void encode_string(uint32_t field_id, const char *string, size_t len, bool force = false) { | ||||
|     if (len == 0 && !force) | ||||
|       return; | ||||
|  | ||||
|     this->encode_field_raw(field_id, 2); | ||||
|     this->encode_varint_raw(len); | ||||
|     auto *data = reinterpret_cast<const uint8_t *>(string); | ||||
|     for (size_t i = 0; i < len; i++) | ||||
|       this->write(data[i]); | ||||
|   } | ||||
|   void encode_string(uint32_t field_id, const std::string &value, bool force = false) { | ||||
|     this->encode_string(field_id, value.data(), value.size()); | ||||
|   } | ||||
|   void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) { | ||||
|     this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force); | ||||
|   } | ||||
|   void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) { | ||||
|     if (value == 0 && !force) | ||||
|       return; | ||||
|     this->encode_field_raw(field_id, 0); | ||||
|     this->encode_varint_raw(value); | ||||
|   } | ||||
|   void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) { | ||||
|     if (value == 0 && !force) | ||||
|       return; | ||||
|     this->encode_field_raw(field_id, 0); | ||||
|     this->encode_varint_raw(ProtoVarInt(value)); | ||||
|   } | ||||
|   void encode_bool(uint32_t field_id, bool value, bool force = false) { | ||||
|     if (!value && !force) | ||||
|       return; | ||||
|     this->encode_field_raw(field_id, 0); | ||||
|     this->write(0x01); | ||||
|   } | ||||
|   void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) { | ||||
|     if (value == 0 && !force) | ||||
|       return; | ||||
|  | ||||
|     this->encode_field_raw(field_id, 5); | ||||
|     this->write((value >> 0) & 0xFF); | ||||
|     this->write((value >> 8) & 0xFF); | ||||
|     this->write((value >> 16) & 0xFF); | ||||
|     this->write((value >> 24) & 0xFF); | ||||
|   } | ||||
|   template<typename T> void encode_enum(uint32_t field_id, T value, bool force = false) { | ||||
|     this->encode_uint32(field_id, static_cast<uint32_t>(value), force); | ||||
|   } | ||||
|   void encode_float(uint32_t field_id, float value, bool force = false) { | ||||
|     if (value == 0.0f && !force) | ||||
|       return; | ||||
|  | ||||
|     union { | ||||
|       float value; | ||||
|       uint32_t raw; | ||||
|     } val{}; | ||||
|     val.value = value; | ||||
|     this->encode_fixed32(field_id, val.raw); | ||||
|   } | ||||
|   void encode_int32(uint32_t field_id, int32_t value, bool force = false) { | ||||
|     if (value < 0) { | ||||
|       // negative int32 is always 10 byte long | ||||
|       this->encode_int64(field_id, value, force); | ||||
|       return; | ||||
|     } | ||||
|     this->encode_uint32(field_id, static_cast<uint32_t>(value), force); | ||||
|   } | ||||
|   void encode_int64(uint32_t field_id, int64_t value, bool force = false) { | ||||
|     this->encode_uint64(field_id, static_cast<uint64_t>(value), force); | ||||
|   } | ||||
|   void encode_sint32(uint32_t field_id, int32_t value, bool force = false) { | ||||
|     uint32_t uvalue; | ||||
|     if (value < 0) | ||||
|       uvalue = ~(value << 1); | ||||
|     else | ||||
|       uvalue = value << 1; | ||||
|     this->encode_uint32(field_id, uvalue, force); | ||||
|   } | ||||
|   template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) { | ||||
|     this->encode_field_raw(field_id, 2); | ||||
|     size_t begin = this->buffer_->size(); | ||||
|  | ||||
|     value.encode(*this); | ||||
|  | ||||
|     const uint32_t nested_length = this->buffer_->size() - begin; | ||||
|     // add size varint | ||||
|     std::vector<uint8_t> var; | ||||
|     ProtoVarInt(nested_length).encode(var); | ||||
|     this->buffer_->insert(this->buffer_->begin() + begin, var.begin(), var.end()); | ||||
|   } | ||||
|   std::vector<uint8_t> *get_buffer() const { return buffer_; } | ||||
|  | ||||
|  protected: | ||||
|   std::vector<uint8_t> *buffer_; | ||||
| }; | ||||
|  | ||||
| class ProtoMessage { | ||||
|  public: | ||||
|   virtual void encode(ProtoWriteBuffer buffer) const = 0; | ||||
|   void decode(const uint8_t *buffer, size_t length); | ||||
|   std::string dump() const; | ||||
|   virtual void dump_to(std::string &out) const = 0; | ||||
|  | ||||
|  protected: | ||||
|   virtual bool decode_varint(uint32_t field_id, ProtoVarInt value) { return false; } | ||||
|   virtual bool decode_length(uint32_t field_id, ProtoLengthDelimited value) { return false; } | ||||
|   virtual bool decode_32bit(uint32_t field_id, Proto32Bit value) { return false; } | ||||
|   virtual bool decode_64bit(uint32_t field_id, Proto64Bit value) { return false; } | ||||
| }; | ||||
|  | ||||
| template<typename T> const char *proto_enum_to_string(T value); | ||||
|  | ||||
| class ProtoService { | ||||
|  public: | ||||
|  protected: | ||||
|   virtual bool is_authenticated() = 0; | ||||
|   virtual bool is_connection_setup() = 0; | ||||
|   virtual void on_fatal_error() = 0; | ||||
|   virtual void on_unauthenticated_access() = 0; | ||||
|   virtual void on_no_setup_connection() = 0; | ||||
|   virtual ProtoWriteBuffer create_buffer() = 0; | ||||
|   virtual bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) = 0; | ||||
|   virtual bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0; | ||||
|   virtual void set_nodelay(bool nodelay) = 0; | ||||
|  | ||||
|   template<class C> bool send_message_(const C &msg, uint32_t message_type) { | ||||
|     auto buffer = this->create_buffer(); | ||||
|     msg.encode(buffer); | ||||
|     return this->send_buffer(buffer, message_type); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| @@ -1,49 +0,0 @@ | ||||
| #include "service_call_message.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| APIMessageType SubscribeServiceCallsRequest::message_type() const { | ||||
|   return APIMessageType::SUBSCRIBE_SERVICE_CALLS_REQUEST; | ||||
| } | ||||
|  | ||||
| APIMessageType ServiceCallResponse::message_type() const { return APIMessageType::SERVICE_CALL_RESPONSE; } | ||||
| void ServiceCallResponse::encode(APIBuffer &buffer) { | ||||
|   // string service = 1; | ||||
|   buffer.encode_string(1, this->service_); | ||||
|   // map<string, string> data = 2; | ||||
|   for (auto &it : this->data_) { | ||||
|     auto nested = buffer.begin_nested(2); | ||||
|     buffer.encode_string(1, it.key); | ||||
|     buffer.encode_string(2, it.value); | ||||
|     buffer.end_nested(nested); | ||||
|   } | ||||
|   // map<string, string> data_template = 3; | ||||
|   for (auto &it : this->data_template_) { | ||||
|     auto nested = buffer.begin_nested(3); | ||||
|     buffer.encode_string(1, it.key); | ||||
|     buffer.encode_string(2, it.value); | ||||
|     buffer.end_nested(nested); | ||||
|   } | ||||
|   // map<string, string> variables = 4; | ||||
|   for (auto &it : this->variables_) { | ||||
|     auto nested = buffer.begin_nested(4); | ||||
|     buffer.encode_string(1, it.key); | ||||
|     buffer.encode_string(2, it.value()); | ||||
|     buffer.end_nested(nested); | ||||
|   } | ||||
| } | ||||
| void ServiceCallResponse::set_service(const std::string &service) { this->service_ = service; } | ||||
| void ServiceCallResponse::set_data(const std::vector<KeyValuePair> &data) { this->data_ = data; } | ||||
| void ServiceCallResponse::set_data_template(const std::vector<KeyValuePair> &data_template) { | ||||
|   this->data_template_ = data_template; | ||||
| } | ||||
| void ServiceCallResponse::set_variables(const std::vector<TemplatableKeyValuePair> &variables) { | ||||
|   this->variables_ = variables; | ||||
| } | ||||
|  | ||||
| KeyValuePair::KeyValuePair(const std::string &key, const std::string &value) : key(key), value(value) {} | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| @@ -1,53 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/automation.h" | ||||
| #include "api_message.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| class SubscribeServiceCallsRequest : public APIMessage { | ||||
|  public: | ||||
|   APIMessageType message_type() const override; | ||||
| }; | ||||
|  | ||||
| class KeyValuePair { | ||||
|  public: | ||||
|   KeyValuePair(const std::string &key, const std::string &value); | ||||
|  | ||||
|   std::string key; | ||||
|   std::string value; | ||||
| }; | ||||
|  | ||||
| class TemplatableKeyValuePair { | ||||
|  public: | ||||
|   template<typename T> TemplatableKeyValuePair(std::string key, T func); | ||||
|  | ||||
|   std::string key; | ||||
|   std::function<std::string()> value; | ||||
| }; | ||||
| template<typename T> TemplatableKeyValuePair::TemplatableKeyValuePair(std::string key, T func) : key(key) { | ||||
|   this->value = [func]() -> std::string { return to_string(func()); }; | ||||
| } | ||||
|  | ||||
| class ServiceCallResponse : public APIMessage { | ||||
|  public: | ||||
|   APIMessageType message_type() const override; | ||||
|  | ||||
|   void encode(APIBuffer &buffer) override; | ||||
|  | ||||
|   void set_service(const std::string &service); | ||||
|   void set_data(const std::vector<KeyValuePair> &data); | ||||
|   void set_data_template(const std::vector<KeyValuePair> &data_template); | ||||
|   void set_variables(const std::vector<TemplatableKeyValuePair> &variables); | ||||
|  | ||||
|  protected: | ||||
|   std::string service_; | ||||
|   std::vector<KeyValuePair> data_; | ||||
|   std::vector<KeyValuePair> data_template_; | ||||
|   std::vector<TemplatableKeyValuePair> variables_; | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| @@ -1,26 +0,0 @@ | ||||
| #include "subscribe_logs.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| APIMessageType SubscribeLogsRequest::message_type() const { return APIMessageType::SUBSCRIBE_LOGS_REQUEST; } | ||||
| bool SubscribeLogsRequest::decode_varint(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 1:  // LogLevel level = 1; | ||||
|       this->level_ = value; | ||||
|       return true; | ||||
|     case 2:  // bool dump_config = 2; | ||||
|       this->dump_config_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| uint32_t SubscribeLogsRequest::get_level() const { return this->level_; } | ||||
| void SubscribeLogsRequest::set_level(uint32_t level) { this->level_ = level; } | ||||
| bool SubscribeLogsRequest::get_dump_config() const { return this->dump_config_; } | ||||
| void SubscribeLogsRequest::set_dump_config(bool dump_config) { this->dump_config_ = dump_config; } | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| @@ -1,24 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "api_message.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| class SubscribeLogsRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_varint(uint32_t field_id, uint32_t value) override; | ||||
|   APIMessageType message_type() const override; | ||||
|   uint32_t get_level() const; | ||||
|   void set_level(uint32_t level); | ||||
|   bool get_dump_config() const; | ||||
|   void set_dump_config(bool dump_config); | ||||
|  | ||||
|  protected: | ||||
|   uint32_t level_{6}; | ||||
|   bool dump_config_{false}; | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| @@ -1,4 +1,5 @@ | ||||
| #include "subscribe_state.h" | ||||
| #include "api_connection.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| @@ -48,30 +49,5 @@ bool InitialStateIterator::on_climate(climate::Climate *climate) { return this-> | ||||
| InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *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 esphome | ||||
|   | ||||
| @@ -4,16 +4,10 @@ | ||||
| #include "esphome/core/controller.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "util.h" | ||||
| #include "api_message.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| class SubscribeStatesRequest : public APIMessage { | ||||
|  public: | ||||
|   APIMessageType message_type() const override; | ||||
| }; | ||||
|  | ||||
| class APIConnection; | ||||
|  | ||||
| class InitialStateIterator : public ComponentIterator { | ||||
| @@ -47,23 +41,6 @@ class InitialStateIterator : public ComponentIterator { | ||||
|   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 esphome | ||||
|  | ||||
|   | ||||
| @@ -4,71 +4,35 @@ | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| template<> bool ExecuteServiceArgument::get_value<bool>() { return this->value_bool_; } | ||||
| template<> int ExecuteServiceArgument::get_value<int>() { return this->value_int_; } | ||||
| template<> float ExecuteServiceArgument::get_value<float>() { return this->value_float_; } | ||||
| template<> std::string ExecuteServiceArgument::get_value<std::string>() { return this->value_string_; } | ||||
|  | ||||
| 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; | ||||
|   } | ||||
| template<> bool get_execute_arg_value<bool>(const ExecuteServiceArgument &arg) { return arg.bool_; } | ||||
| template<> int get_execute_arg_value<int>(const ExecuteServiceArgument &arg) { | ||||
|   if (arg.legacy_int != 0) | ||||
|     return arg.legacy_int; | ||||
|   return arg.int_; | ||||
| } | ||||
| bool ExecuteServiceArgument::decode_32bit(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 3:  // float float_ = 3; | ||||
|       this->value_float_ = as_float(value); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| template<> float get_execute_arg_value<float>(const ExecuteServiceArgument &arg) { return arg.float_; } | ||||
| template<> std::string get_execute_arg_value<std::string>(const ExecuteServiceArgument &arg) { return arg.string_; } | ||||
| template<> std::vector<bool> get_execute_arg_value<std::vector<bool>>(const ExecuteServiceArgument &arg) { | ||||
|   return arg.bool_array; | ||||
| } | ||||
| bool ExecuteServiceArgument::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { | ||||
|   switch (field_id) { | ||||
|     case 4:  // string string_ = 4; | ||||
|       this->value_string_ = as_string(value, len); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| template<> std::vector<int> get_execute_arg_value<std::vector<int>>(const ExecuteServiceArgument &arg) { | ||||
|   return arg.int_array; | ||||
| } | ||||
| template<> std::vector<float> get_execute_arg_value<std::vector<float>>(const ExecuteServiceArgument &arg) { | ||||
|   return arg.float_array; | ||||
| } | ||||
| 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) { | ||||
|   switch (field_id) { | ||||
|     case 1:  // fixed32 key = 1; | ||||
|       this->key_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| 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_; } | ||||
| template<> ServiceArgType to_service_arg_type<bool>() { return SERVICE_ARG_TYPE_BOOL; } | ||||
| template<> ServiceArgType to_service_arg_type<int>() { return SERVICE_ARG_TYPE_INT; } | ||||
| template<> ServiceArgType to_service_arg_type<float>() { return SERVICE_ARG_TYPE_FLOAT; } | ||||
| template<> ServiceArgType to_service_arg_type<std::string>() { return SERVICE_ARG_TYPE_STRING; } | ||||
| template<> ServiceArgType to_service_arg_type<std::vector<bool>>() { return SERVICE_ARG_TYPE_BOOL_ARRAY; } | ||||
| template<> ServiceArgType to_service_arg_type<std::vector<int>>() { return SERVICE_ARG_TYPE_INT_ARRAY; } | ||||
| 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; } | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -2,124 +2,71 @@ | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/automation.h" | ||||
| #include "api_message.h" | ||||
| #include "api_pb2.h" | ||||
|  | ||||
| namespace esphome { | ||||
| 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 { | ||||
|  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; | ||||
| }; | ||||
|  | ||||
| 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: | ||||
|   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: | ||||
|   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_; | ||||
|   uint32_t key_{0}; | ||||
|   std::array<ServiceTypeArgument, sizeof...(Ts)> args_; | ||||
|   std::array<std::string, sizeof...(Ts)> arg_names_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> | ||||
| template<int... S> | ||||
| void UserService<Ts...>::execute_(std::vector<ExecuteServiceArgument> args, seq<S...>) { | ||||
|   this->trigger((args[S].get_value<Ts>())...); | ||||
| } | ||||
| 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_); | ||||
| template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...>, public Trigger<Ts...> { | ||||
|  public: | ||||
|   UserServiceTrigger(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names) | ||||
|       : UserServiceBase<Ts...>(name, arg_names) {} | ||||
|  | ||||
|   // repeated ListServicesArgument args = 3; | ||||
|   for (auto &arg : this->args_) { | ||||
|     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>(); | ||||
|  protected: | ||||
|   void execute(Ts... x) override { this->trigger(x...); }  // NOLINT | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -7,166 +7,6 @@ | ||||
| namespace esphome { | ||||
| 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) {} | ||||
| void ComponentIterator::begin() { | ||||
|   this->state_ = IteratorState::BEGIN; | ||||
|   | ||||
| @@ -10,40 +10,6 @@ | ||||
| namespace esphome { | ||||
| 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 UserServiceDescriptor; | ||||
|  | ||||
|   | ||||
| @@ -22,19 +22,5 @@ void HomeassistantTime::setup() { | ||||
|  | ||||
| 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 esphome | ||||
|   | ||||
| @@ -17,11 +17,5 @@ class HomeassistantTime : public time::RealTimeClock { | ||||
|  | ||||
| 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 esphome | ||||
|   | ||||
| @@ -38,6 +38,7 @@ CONFIG_SCHEMA = cv.All(cv.Schema({ | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     cg.add_global(uart_ns.using) | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|  | ||||
|   | ||||
| @@ -136,6 +136,7 @@ CONF_ENTITY_ID = 'entity_id' | ||||
| CONF_ESP8266_RESTORE_FROM_FLASH = 'esp8266_restore_from_flash' | ||||
| CONF_ESPHOME = 'esphome' | ||||
| CONF_ESPHOME_CORE_VERSION = 'esphome_core_version' | ||||
| CONF_EVENT = 'event' | ||||
| CONF_EXPIRE_AFTER = 'expire_after' | ||||
| CONF_EXTERNAL_VCC = 'external_vcc' | ||||
| CONF_FALLING_EDGE = 'falling_edge' | ||||
|   | ||||
| @@ -17,6 +17,15 @@ namespace esphome { | ||||
|  | ||||
| #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. | ||||
|  * | ||||
|  * @tparam Ts The template parameters to pass when executing. | ||||
|   | ||||
| @@ -243,6 +243,18 @@ template<typename T, typename... X> class TemplatableValue { | ||||
|   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); | ||||
|  | ||||
| template<typename T> class Deduplicator { | ||||
|   | ||||
| @@ -54,9 +54,9 @@ wifi: | ||||
|   ssid: "{ssid}" | ||||
|   password: "{psk}" | ||||
|  | ||||
|   # Enable fallback network (captive portal) | ||||
|   # Enable fallback hotspot (captive portal) in case wifi connection fails | ||||
|   ap: | ||||
|     ssid: "{name} Fallback AP" | ||||
|     ssid: "{fallback_name}" | ||||
|     password: "{fallback_psk}" | ||||
|  | ||||
| captive_portal: | ||||
| @@ -75,6 +75,7 @@ def sanitize_double_quotes(value): | ||||
|  | ||||
| def wizard_file(**kwargs): | ||||
|     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)) | ||||
|  | ||||
|     config = BASE_CONFIG.format(**kwargs) | ||||
|   | ||||
							
								
								
									
										168
									
								
								script/api_protobuf/api_options_pb2.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								script/api_protobuf/api_options_pb2.py
									
									
									
									
									
										Normal 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) | ||||
							
								
								
									
										866
									
								
								script/api_protobuf/api_protobuf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										866
									
								
								script/api_protobuf/api_protobuf.py
									
									
									
									
									
										Normal 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() | ||||
| @@ -28,6 +28,7 @@ EXECUTABLE_BIT = { | ||||
|     s[3].strip(): int(s[0]) for s in lines | ||||
| } | ||||
| files = [s[3].strip() for s in lines] | ||||
| files = list(filter(os.path.exists, files)) | ||||
| files.sort() | ||||
|  | ||||
| file_types = ('.h', '.c', '.cpp', '.tcc', '.yaml', '.yml', '.ini', '.txt', '.ico', '.svg', | ||||
|   | ||||
| @@ -41,3 +41,32 @@ class CustomFloatOutput : public FloatOutput, public Component { | ||||
|  protected: | ||||
|   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()); } | ||||
| }; | ||||
|   | ||||
| @@ -40,6 +40,24 @@ api: | ||||
|         - stepper.set_target: | ||||
|             id: my_stepper2 | ||||
|             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: | ||||
|   ssid: 'MySSID' | ||||
|   | ||||
		Reference in New Issue
	
	Block a user