mirror of
https://github.com/esphome/esphome.git
synced 2025-10-13 05:38:42 +00:00
Compare commits
1 Commits
memory_api
...
ESPBTUUID_
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d24ba3727a |
@@ -1 +1 @@
|
|||||||
ab49c22900dd39c004623e450a1076b111d6741f31967a637ab6e0e3dd2e753e
|
499db61c1aa55b98b6629df603a56a1ba7aff5a9a7c781a5c1552a9dcd186c08
|
||||||
|
1
.github/workflows/ci-clang-tidy-hash.yml
vendored
1
.github/workflows/ci-clang-tidy-hash.yml
vendored
@@ -6,7 +6,6 @@ on:
|
|||||||
- ".clang-tidy"
|
- ".clang-tidy"
|
||||||
- "platformio.ini"
|
- "platformio.ini"
|
||||||
- "requirements_dev.txt"
|
- "requirements_dev.txt"
|
||||||
- "sdkconfig.defaults"
|
|
||||||
- ".clang-tidy.hash"
|
- ".clang-tidy.hash"
|
||||||
- "script/clang_tidy_hash.py"
|
- "script/clang_tidy_hash.py"
|
||||||
- ".github/workflows/ci-clang-tidy-hash.yml"
|
- ".github/workflows/ci-clang-tidy-hash.yml"
|
||||||
|
@@ -9,7 +9,6 @@ import esphome.config_validation as cv
|
|||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ACTION,
|
CONF_ACTION,
|
||||||
CONF_ACTIONS,
|
CONF_ACTIONS,
|
||||||
CONF_CAPTURE_RESPONSE,
|
|
||||||
CONF_DATA,
|
CONF_DATA,
|
||||||
CONF_DATA_TEMPLATE,
|
CONF_DATA_TEMPLATE,
|
||||||
CONF_EVENT,
|
CONF_EVENT,
|
||||||
@@ -18,50 +17,30 @@ from esphome.const import (
|
|||||||
CONF_MAX_CONNECTIONS,
|
CONF_MAX_CONNECTIONS,
|
||||||
CONF_ON_CLIENT_CONNECTED,
|
CONF_ON_CLIENT_CONNECTED,
|
||||||
CONF_ON_CLIENT_DISCONNECTED,
|
CONF_ON_CLIENT_DISCONNECTED,
|
||||||
CONF_ON_ERROR,
|
|
||||||
CONF_ON_SUCCESS,
|
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
CONF_PORT,
|
CONF_PORT,
|
||||||
CONF_REBOOT_TIMEOUT,
|
CONF_REBOOT_TIMEOUT,
|
||||||
CONF_RESPONSE_TEMPLATE,
|
|
||||||
CONF_SERVICE,
|
CONF_SERVICE,
|
||||||
CONF_SERVICES,
|
CONF_SERVICES,
|
||||||
CONF_TAG,
|
CONF_TAG,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_VARIABLES,
|
CONF_VARIABLES,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||||
from esphome.cpp_generator import TemplateArgsType
|
|
||||||
from esphome.types import ConfigType
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DOMAIN = "api"
|
DOMAIN = "api"
|
||||||
DEPENDENCIES = ["network"]
|
DEPENDENCIES = ["network"]
|
||||||
|
AUTO_LOAD = ["socket"]
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
|
||||||
|
|
||||||
def AUTO_LOAD(config: ConfigType) -> list[str]:
|
|
||||||
"""Conditionally auto-load json only when capture_response is used."""
|
|
||||||
base = ["socket"]
|
|
||||||
|
|
||||||
# Check if any homeassistant.action/homeassistant.service has capture_response: true
|
|
||||||
# This flag is set during config validation in _validate_response_config
|
|
||||||
if not config or CORE.data.get(DOMAIN, {}).get(CONF_CAPTURE_RESPONSE, False):
|
|
||||||
return base + ["json"]
|
|
||||||
|
|
||||||
return base
|
|
||||||
|
|
||||||
|
|
||||||
api_ns = cg.esphome_ns.namespace("api")
|
api_ns = cg.esphome_ns.namespace("api")
|
||||||
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
|
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
|
||||||
HomeAssistantServiceCallAction = api_ns.class_(
|
HomeAssistantServiceCallAction = api_ns.class_(
|
||||||
"HomeAssistantServiceCallAction", automation.Action
|
"HomeAssistantServiceCallAction", automation.Action
|
||||||
)
|
)
|
||||||
ActionResponse = api_ns.class_("ActionResponse")
|
|
||||||
HomeAssistantActionResponseTrigger = api_ns.class_(
|
|
||||||
"HomeAssistantActionResponseTrigger", automation.Trigger
|
|
||||||
)
|
|
||||||
APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition)
|
APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition)
|
||||||
|
|
||||||
UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger)
|
UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger)
|
||||||
@@ -309,29 +288,6 @@ async def to_code(config):
|
|||||||
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
|
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
|
||||||
|
|
||||||
|
|
||||||
def _validate_response_config(config: ConfigType) -> ConfigType:
|
|
||||||
# Validate dependencies:
|
|
||||||
# - response_template requires capture_response: true
|
|
||||||
# - capture_response: true requires on_success
|
|
||||||
if CONF_RESPONSE_TEMPLATE in config and not config[CONF_CAPTURE_RESPONSE]:
|
|
||||||
raise cv.Invalid(
|
|
||||||
f"`{CONF_RESPONSE_TEMPLATE}` requires `{CONF_CAPTURE_RESPONSE}: true` to be set.",
|
|
||||||
path=[CONF_RESPONSE_TEMPLATE],
|
|
||||||
)
|
|
||||||
|
|
||||||
if config[CONF_CAPTURE_RESPONSE] and CONF_ON_SUCCESS not in config:
|
|
||||||
raise cv.Invalid(
|
|
||||||
f"`{CONF_CAPTURE_RESPONSE}: true` requires `{CONF_ON_SUCCESS}` to be set.",
|
|
||||||
path=[CONF_CAPTURE_RESPONSE],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Track if any action uses capture_response for AUTO_LOAD
|
|
||||||
if config[CONF_CAPTURE_RESPONSE]:
|
|
||||||
CORE.data.setdefault(DOMAIN, {})[CONF_CAPTURE_RESPONSE] = True
|
|
||||||
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
@@ -347,15 +303,10 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
|
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
|
||||||
{cv.string: cv.returning_lambda}
|
{cv.string: cv.returning_lambda}
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_RESPONSE_TEMPLATE): cv.templatable(cv.string),
|
|
||||||
cv.Optional(CONF_CAPTURE_RESPONSE, default=False): cv.boolean,
|
|
||||||
cv.Optional(CONF_ON_SUCCESS): automation.validate_automation(single=True),
|
|
||||||
cv.Optional(CONF_ON_ERROR): automation.validate_automation(single=True),
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
|
cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
|
||||||
cv.rename_key(CONF_SERVICE, CONF_ACTION),
|
cv.rename_key(CONF_SERVICE, CONF_ACTION),
|
||||||
_validate_response_config,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -369,12 +320,7 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
|||||||
HomeAssistantServiceCallAction,
|
HomeAssistantServiceCallAction,
|
||||||
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
|
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
|
||||||
)
|
)
|
||||||
async def homeassistant_service_to_code(
|
async def homeassistant_service_to_code(config, action_id, template_arg, args):
|
||||||
config: ConfigType,
|
|
||||||
action_id: ID,
|
|
||||||
template_arg: cg.TemplateArguments,
|
|
||||||
args: TemplateArgsType,
|
|
||||||
):
|
|
||||||
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
|
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
|
||||||
serv = await cg.get_variable(config[CONF_ID])
|
serv = await cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, serv, False)
|
var = cg.new_Pvariable(action_id, template_arg, serv, False)
|
||||||
@@ -389,40 +335,6 @@ async def homeassistant_service_to_code(
|
|||||||
for key, value in config[CONF_VARIABLES].items():
|
for key, value in config[CONF_VARIABLES].items():
|
||||||
templ = await cg.templatable(value, args, None)
|
templ = await cg.templatable(value, args, None)
|
||||||
cg.add(var.add_variable(key, templ))
|
cg.add(var.add_variable(key, templ))
|
||||||
|
|
||||||
if on_error := config.get(CONF_ON_ERROR):
|
|
||||||
cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES")
|
|
||||||
cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES_ERRORS")
|
|
||||||
cg.add(var.set_wants_status())
|
|
||||||
await automation.build_automation(
|
|
||||||
var.get_error_trigger(),
|
|
||||||
[(cg.std_string, "error"), *args],
|
|
||||||
on_error,
|
|
||||||
)
|
|
||||||
|
|
||||||
if on_success := config.get(CONF_ON_SUCCESS):
|
|
||||||
cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES")
|
|
||||||
cg.add(var.set_wants_status())
|
|
||||||
if config[CONF_CAPTURE_RESPONSE]:
|
|
||||||
cg.add(var.set_wants_response())
|
|
||||||
cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON")
|
|
||||||
await automation.build_automation(
|
|
||||||
var.get_success_trigger_with_response(),
|
|
||||||
[(cg.JsonObjectConst, "response"), *args],
|
|
||||||
on_success,
|
|
||||||
)
|
|
||||||
|
|
||||||
if response_template := config.get(CONF_RESPONSE_TEMPLATE):
|
|
||||||
templ = await cg.templatable(response_template, args, cg.std_string)
|
|
||||||
cg.add(var.set_response_template(templ))
|
|
||||||
|
|
||||||
else:
|
|
||||||
await automation.build_automation(
|
|
||||||
var.get_success_trigger(),
|
|
||||||
args,
|
|
||||||
on_success,
|
|
||||||
)
|
|
||||||
|
|
||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
@@ -780,22 +780,6 @@ message HomeassistantActionRequest {
|
|||||||
repeated HomeassistantServiceMap data_template = 3;
|
repeated HomeassistantServiceMap data_template = 3;
|
||||||
repeated HomeassistantServiceMap variables = 4;
|
repeated HomeassistantServiceMap variables = 4;
|
||||||
bool is_event = 5;
|
bool is_event = 5;
|
||||||
uint32 call_id = 6 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES"];
|
|
||||||
bool wants_response = 7 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
|
|
||||||
string response_template = 8 [(no_zero_copy) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message sent by Home Assistant to ESPHome with service call response data
|
|
||||||
message HomeassistantActionResponse {
|
|
||||||
option (id) = 130;
|
|
||||||
option (source) = SOURCE_CLIENT;
|
|
||||||
option (no_delay) = true;
|
|
||||||
option (ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES";
|
|
||||||
|
|
||||||
uint32 call_id = 1; // Matches the call_id from HomeassistantActionRequest
|
|
||||||
bool success = 2; // Whether the service call succeeded
|
|
||||||
string error_message = 3; // Error message if success = false
|
|
||||||
bytes response_data = 4 [(pointer_to_buffer) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== IMPORT HOME ASSISTANT STATES ====================
|
// ==================== IMPORT HOME ASSISTANT STATES ====================
|
||||||
|
@@ -8,9 +8,9 @@
|
|||||||
#endif
|
#endif
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
#include <utility>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <utility>
|
|
||||||
#include "esphome/components/network/util.h"
|
#include "esphome/components/network/util.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/entity_base.h"
|
#include "esphome/core/entity_base.h"
|
||||||
@@ -1549,20 +1549,6 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
void APIConnection::on_homeassistant_action_response(const HomeassistantActionResponse &msg) {
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
if (msg.response_data_len > 0) {
|
|
||||||
this->parent_->handle_action_response(msg.call_id, msg.success, msg.error_message, msg.response_data,
|
|
||||||
msg.response_data_len);
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
this->parent_->handle_action_response(msg.call_id, msg.success, msg.error_message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) {
|
bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) {
|
||||||
NoiseEncryptionSetKeyResponse resp;
|
NoiseEncryptionSetKeyResponse resp;
|
||||||
|
@@ -129,10 +129,7 @@ class APIConnection final : public APIServerConnection {
|
|||||||
return;
|
return;
|
||||||
this->send_message(call, HomeassistantActionRequest::MESSAGE_TYPE);
|
this->send_message(call, HomeassistantActionRequest::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
#endif
|
||||||
void on_homeassistant_action_response(const HomeassistantActionResponse &msg) override;
|
|
||||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
#endif // USE_API_HOMEASSISTANT_SERVICES
|
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||||
void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||||
|
@@ -132,22 +132,24 @@ APIError APINoiseFrameHelper::loop() {
|
|||||||
return APIFrameHelper::loop();
|
return APIFrameHelper::loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Read a packet into the rx_buf_.
|
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
||||||
*
|
*
|
||||||
* On success, rx_buf_ contains the frame data and state variables are cleared for the next read.
|
* @param frame: The struct to hold the frame information in.
|
||||||
* Caller is responsible for consuming rx_buf_ (e.g., via std::move).
|
* msg_start: points to the start of the payload - this pointer is only valid until the next
|
||||||
|
* try_receive_raw_ call
|
||||||
*
|
*
|
||||||
* @return APIError::OK if a full packet is in rx_buf_
|
* @return 0 if a full packet is in rx_buf_
|
||||||
|
* @return -1 if error, check errno.
|
||||||
*
|
*
|
||||||
* errno EWOULDBLOCK: Packet could not be read without blocking. Try again later.
|
* errno EWOULDBLOCK: Packet could not be read without blocking. Try again later.
|
||||||
* errno ENOMEM: Not enough memory for reading packet.
|
* errno ENOMEM: Not enough memory for reading packet.
|
||||||
* errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
* errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
||||||
* errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
|
* errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
|
||||||
*/
|
*/
|
||||||
APIError APINoiseFrameHelper::try_read_frame_() {
|
APIError APINoiseFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
||||||
// Clear buffer when starting a new frame (rx_buf_len_ == 0 means not resuming after WOULD_BLOCK)
|
if (frame == nullptr) {
|
||||||
if (this->rx_buf_len_ == 0) {
|
HELPER_LOG("Bad argument for try_read_frame_");
|
||||||
this->rx_buf_.clear();
|
return APIError::BAD_ARG;
|
||||||
}
|
}
|
||||||
|
|
||||||
// read header
|
// read header
|
||||||
@@ -190,9 +192,9 @@ APIError APINoiseFrameHelper::try_read_frame_() {
|
|||||||
return APIError::BAD_DATA_PACKET;
|
return APIError::BAD_DATA_PACKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reserve space for body
|
// reserve space for body
|
||||||
if (this->rx_buf_.size() != msg_size) {
|
if (rx_buf_.size() != msg_size) {
|
||||||
this->rx_buf_.resize(msg_size);
|
rx_buf_.resize(msg_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rx_buf_len_ < msg_size) {
|
if (rx_buf_len_ < msg_size) {
|
||||||
@@ -210,12 +212,12 @@ APIError APINoiseFrameHelper::try_read_frame_() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_PACKET_RECEIVED(this->rx_buf_);
|
LOG_PACKET_RECEIVED(rx_buf_);
|
||||||
|
*frame = std::move(rx_buf_);
|
||||||
// Clear state for next frame (rx_buf_ still contains data for caller)
|
// consume msg
|
||||||
this->rx_buf_len_ = 0;
|
rx_buf_ = {};
|
||||||
this->rx_header_buf_len_ = 0;
|
rx_buf_len_ = 0;
|
||||||
|
rx_header_buf_len_ = 0;
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,17 +239,18 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
}
|
}
|
||||||
if (state_ == State::CLIENT_HELLO) {
|
if (state_ == State::CLIENT_HELLO) {
|
||||||
// waiting for client hello
|
// waiting for client hello
|
||||||
aerr = this->try_read_frame_();
|
std::vector<uint8_t> frame;
|
||||||
|
aerr = try_read_frame_(&frame);
|
||||||
if (aerr != APIError::OK) {
|
if (aerr != APIError::OK) {
|
||||||
return handle_handshake_frame_error_(aerr);
|
return handle_handshake_frame_error_(aerr);
|
||||||
}
|
}
|
||||||
// ignore contents, may be used in future for flags
|
// ignore contents, may be used in future for flags
|
||||||
// Resize for: existing prologue + 2 size bytes + frame data
|
// Resize for: existing prologue + 2 size bytes + frame data
|
||||||
size_t old_size = this->prologue_.size();
|
size_t old_size = prologue_.size();
|
||||||
this->prologue_.resize(old_size + 2 + this->rx_buf_.size());
|
prologue_.resize(old_size + 2 + frame.size());
|
||||||
this->prologue_[old_size] = (uint8_t) (this->rx_buf_.size() >> 8);
|
prologue_[old_size] = (uint8_t) (frame.size() >> 8);
|
||||||
this->prologue_[old_size + 1] = (uint8_t) this->rx_buf_.size();
|
prologue_[old_size + 1] = (uint8_t) frame.size();
|
||||||
std::memcpy(this->prologue_.data() + old_size + 2, this->rx_buf_.data(), this->rx_buf_.size());
|
std::memcpy(prologue_.data() + old_size + 2, frame.data(), frame.size());
|
||||||
|
|
||||||
state_ = State::SERVER_HELLO;
|
state_ = State::SERVER_HELLO;
|
||||||
}
|
}
|
||||||
@@ -289,23 +292,24 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
int action = noise_handshakestate_get_action(handshake_);
|
int action = noise_handshakestate_get_action(handshake_);
|
||||||
if (action == NOISE_ACTION_READ_MESSAGE) {
|
if (action == NOISE_ACTION_READ_MESSAGE) {
|
||||||
// waiting for handshake msg
|
// waiting for handshake msg
|
||||||
aerr = this->try_read_frame_();
|
std::vector<uint8_t> frame;
|
||||||
|
aerr = try_read_frame_(&frame);
|
||||||
if (aerr != APIError::OK) {
|
if (aerr != APIError::OK) {
|
||||||
return handle_handshake_frame_error_(aerr);
|
return handle_handshake_frame_error_(aerr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->rx_buf_.empty()) {
|
if (frame.empty()) {
|
||||||
send_explicit_handshake_reject_(LOG_STR("Empty handshake message"));
|
send_explicit_handshake_reject_(LOG_STR("Empty handshake message"));
|
||||||
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||||
} else if (this->rx_buf_[0] != 0x00) {
|
} else if (frame[0] != 0x00) {
|
||||||
HELPER_LOG("Bad handshake error byte: %u", this->rx_buf_[0]);
|
HELPER_LOG("Bad handshake error byte: %u", frame[0]);
|
||||||
send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte"));
|
send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte"));
|
||||||
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||||
}
|
}
|
||||||
|
|
||||||
NoiseBuffer mbuf;
|
NoiseBuffer mbuf;
|
||||||
noise_buffer_init(mbuf);
|
noise_buffer_init(mbuf);
|
||||||
noise_buffer_set_input(mbuf, this->rx_buf_.data() + 1, this->rx_buf_.size() - 1);
|
noise_buffer_set_input(mbuf, frame.data() + 1, frame.size() - 1);
|
||||||
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
|
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
// Special handling for MAC failure
|
// Special handling for MAC failure
|
||||||
@@ -382,33 +386,35 @@ void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reaso
|
|||||||
state_ = orig_state;
|
state_ = orig_state;
|
||||||
}
|
}
|
||||||
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||||
APIError aerr = this->state_action_();
|
int err;
|
||||||
|
APIError aerr;
|
||||||
|
aerr = state_action_();
|
||||||
if (aerr != APIError::OK) {
|
if (aerr != APIError::OK) {
|
||||||
return aerr;
|
return aerr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->state_ != State::DATA) {
|
if (state_ != State::DATA) {
|
||||||
return APIError::WOULD_BLOCK;
|
return APIError::WOULD_BLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
aerr = this->try_read_frame_();
|
std::vector<uint8_t> frame;
|
||||||
|
aerr = try_read_frame_(&frame);
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
|
|
||||||
NoiseBuffer mbuf;
|
NoiseBuffer mbuf;
|
||||||
noise_buffer_init(mbuf);
|
noise_buffer_init(mbuf);
|
||||||
noise_buffer_set_inout(mbuf, this->rx_buf_.data(), this->rx_buf_.size(), this->rx_buf_.size());
|
noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size());
|
||||||
int err = noise_cipherstate_decrypt(this->recv_cipher_, &mbuf);
|
err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
|
||||||
APIError decrypt_err =
|
APIError decrypt_err =
|
||||||
handle_noise_error_(err, LOG_STR("noise_cipherstate_decrypt"), APIError::CIPHERSTATE_DECRYPT_FAILED);
|
handle_noise_error_(err, LOG_STR("noise_cipherstate_decrypt"), APIError::CIPHERSTATE_DECRYPT_FAILED);
|
||||||
if (decrypt_err != APIError::OK) {
|
if (decrypt_err != APIError::OK)
|
||||||
return decrypt_err;
|
return decrypt_err;
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t msg_size = mbuf.size;
|
uint16_t msg_size = mbuf.size;
|
||||||
uint8_t *msg_data = this->rx_buf_.data();
|
uint8_t *msg_data = frame.data();
|
||||||
if (msg_size < 4) {
|
if (msg_size < 4) {
|
||||||
this->state_ = State::FAILED;
|
state_ = State::FAILED;
|
||||||
HELPER_LOG("Bad data packet: size %d too short", msg_size);
|
HELPER_LOG("Bad data packet: size %d too short", msg_size);
|
||||||
return APIError::BAD_DATA_PACKET;
|
return APIError::BAD_DATA_PACKET;
|
||||||
}
|
}
|
||||||
@@ -416,12 +422,12 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|||||||
uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
|
uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
|
||||||
uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
|
uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
|
||||||
if (data_len > msg_size - 4) {
|
if (data_len > msg_size - 4) {
|
||||||
this->state_ = State::FAILED;
|
state_ = State::FAILED;
|
||||||
HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size);
|
HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size);
|
||||||
return APIError::BAD_DATA_PACKET;
|
return APIError::BAD_DATA_PACKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer->container = std::move(this->rx_buf_);
|
buffer->container = std::move(frame);
|
||||||
buffer->data_offset = 4;
|
buffer->data_offset = 4;
|
||||||
buffer->data_len = data_len;
|
buffer->data_len = data_len;
|
||||||
buffer->type = type;
|
buffer->type = type;
|
||||||
|
@@ -28,7 +28,7 @@ class APINoiseFrameHelper final : public APIFrameHelper {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
APIError state_action_();
|
APIError state_action_();
|
||||||
APIError try_read_frame_();
|
APIError try_read_frame_(std::vector<uint8_t> *frame);
|
||||||
APIError write_frame_(const uint8_t *data, uint16_t len);
|
APIError write_frame_(const uint8_t *data, uint16_t len);
|
||||||
APIError init_handshake_();
|
APIError init_handshake_();
|
||||||
APIError check_handshake_finished_();
|
APIError check_handshake_finished_();
|
||||||
|
@@ -47,19 +47,19 @@ APIError APIPlaintextFrameHelper::loop() {
|
|||||||
return APIFrameHelper::loop();
|
return APIFrameHelper::loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Read a packet into the rx_buf_.
|
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
||||||
*
|
*
|
||||||
* On success, rx_buf_ contains the frame data and state variables are cleared for the next read.
|
* @param frame: The struct to hold the frame information in.
|
||||||
* Caller is responsible for consuming rx_buf_ (e.g., via std::move).
|
* msg: store the parsed frame in that struct
|
||||||
*
|
*
|
||||||
* @return See APIError
|
* @return See APIError
|
||||||
*
|
*
|
||||||
* error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
* error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
||||||
*/
|
*/
|
||||||
APIError APIPlaintextFrameHelper::try_read_frame_() {
|
APIError APIPlaintextFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
||||||
// Clear buffer when starting a new frame (rx_buf_len_ == 0 means not resuming after WOULD_BLOCK)
|
if (frame == nullptr) {
|
||||||
if (this->rx_buf_len_ == 0) {
|
HELPER_LOG("Bad argument for try_read_frame_");
|
||||||
this->rx_buf_.clear();
|
return APIError::BAD_ARG;
|
||||||
}
|
}
|
||||||
|
|
||||||
// read header
|
// read header
|
||||||
@@ -150,9 +150,9 @@ APIError APIPlaintextFrameHelper::try_read_frame_() {
|
|||||||
}
|
}
|
||||||
// header reading done
|
// header reading done
|
||||||
|
|
||||||
// Reserve space for body
|
// reserve space for body
|
||||||
if (this->rx_buf_.size() != this->rx_header_parsed_len_) {
|
if (rx_buf_.size() != rx_header_parsed_len_) {
|
||||||
this->rx_buf_.resize(this->rx_header_parsed_len_);
|
rx_buf_.resize(rx_header_parsed_len_);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rx_buf_len_ < rx_header_parsed_len_) {
|
if (rx_buf_len_ < rx_header_parsed_len_) {
|
||||||
@@ -170,22 +170,24 @@ APIError APIPlaintextFrameHelper::try_read_frame_() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_PACKET_RECEIVED(this->rx_buf_);
|
LOG_PACKET_RECEIVED(rx_buf_);
|
||||||
|
*frame = std::move(rx_buf_);
|
||||||
// Clear state for next frame (rx_buf_ still contains data for caller)
|
// consume msg
|
||||||
this->rx_buf_len_ = 0;
|
rx_buf_ = {};
|
||||||
this->rx_header_buf_pos_ = 0;
|
rx_buf_len_ = 0;
|
||||||
this->rx_header_parsed_ = false;
|
rx_header_buf_pos_ = 0;
|
||||||
|
rx_header_parsed_ = false;
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||||
if (this->state_ != State::DATA) {
|
APIError aerr;
|
||||||
|
|
||||||
|
if (state_ != State::DATA) {
|
||||||
return APIError::WOULD_BLOCK;
|
return APIError::WOULD_BLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
APIError aerr = this->try_read_frame_();
|
std::vector<uint8_t> frame;
|
||||||
|
aerr = try_read_frame_(&frame);
|
||||||
if (aerr != APIError::OK) {
|
if (aerr != APIError::OK) {
|
||||||
if (aerr == APIError::BAD_INDICATOR) {
|
if (aerr == APIError::BAD_INDICATOR) {
|
||||||
// Make sure to tell the remote that we don't
|
// Make sure to tell the remote that we don't
|
||||||
@@ -218,10 +220,10 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|||||||
return aerr;
|
return aerr;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer->container = std::move(this->rx_buf_);
|
buffer->container = std::move(frame);
|
||||||
buffer->data_offset = 0;
|
buffer->data_offset = 0;
|
||||||
buffer->data_len = this->rx_header_parsed_len_;
|
buffer->data_len = rx_header_parsed_len_;
|
||||||
buffer->type = this->rx_header_parsed_type_;
|
buffer->type = rx_header_parsed_type_;
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
||||||
|
@@ -24,7 +24,7 @@ class APIPlaintextFrameHelper final : public APIFrameHelper {
|
|||||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
APIError try_read_frame_();
|
APIError try_read_frame_(std::vector<uint8_t> *frame);
|
||||||
|
|
||||||
// Group 2-byte aligned types
|
// Group 2-byte aligned types
|
||||||
uint16_t rx_header_parsed_type_ = 0;
|
uint16_t rx_header_parsed_type_ = 0;
|
||||||
|
@@ -884,15 +884,6 @@ void HomeassistantActionRequest::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_message(4, it, true);
|
buffer.encode_message(4, it, true);
|
||||||
}
|
}
|
||||||
buffer.encode_bool(5, this->is_event);
|
buffer.encode_bool(5, this->is_event);
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
buffer.encode_uint32(6, this->call_id);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
buffer.encode_bool(7, this->wants_response);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
buffer.encode_string(8, this->response_template);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
void HomeassistantActionRequest::calculate_size(ProtoSize &size) const {
|
void HomeassistantActionRequest::calculate_size(ProtoSize &size) const {
|
||||||
size.add_length(1, this->service_ref_.size());
|
size.add_length(1, this->service_ref_.size());
|
||||||
@@ -900,48 +891,6 @@ void HomeassistantActionRequest::calculate_size(ProtoSize &size) const {
|
|||||||
size.add_repeated_message(1, this->data_template);
|
size.add_repeated_message(1, this->data_template);
|
||||||
size.add_repeated_message(1, this->variables);
|
size.add_repeated_message(1, this->variables);
|
||||||
size.add_bool(1, this->is_event);
|
size.add_bool(1, this->is_event);
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
size.add_uint32(1, this->call_id);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
size.add_bool(1, this->wants_response);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
size.add_length(1, this->response_template.size());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
bool HomeassistantActionResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 1:
|
|
||||||
this->call_id = value.as_uint32();
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
this->success = value.as_bool();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool HomeassistantActionResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
|
||||||
switch (field_id) {
|
|
||||||
case 3:
|
|
||||||
this->error_message = value.as_string();
|
|
||||||
break;
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
case 4: {
|
|
||||||
// Use raw data directly to avoid allocation
|
|
||||||
this->response_data = value.data();
|
|
||||||
this->response_data_len = value.size();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
|
@@ -1104,7 +1104,7 @@ class HomeassistantServiceMap final : public ProtoMessage {
|
|||||||
class HomeassistantActionRequest final : public ProtoMessage {
|
class HomeassistantActionRequest final : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 35;
|
static constexpr uint8_t MESSAGE_TYPE = 35;
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 128;
|
static constexpr uint8_t ESTIMATED_SIZE = 113;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "homeassistant_action_request"; }
|
const char *message_name() const override { return "homeassistant_action_request"; }
|
||||||
#endif
|
#endif
|
||||||
@@ -1114,15 +1114,6 @@ class HomeassistantActionRequest final : public ProtoMessage {
|
|||||||
std::vector<HomeassistantServiceMap> data_template{};
|
std::vector<HomeassistantServiceMap> data_template{};
|
||||||
std::vector<HomeassistantServiceMap> variables{};
|
std::vector<HomeassistantServiceMap> variables{};
|
||||||
bool is_event{false};
|
bool is_event{false};
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
uint32_t call_id{0};
|
|
||||||
#endif
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
bool wants_response{false};
|
|
||||||
#endif
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
std::string response_template{};
|
|
||||||
#endif
|
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(ProtoSize &size) const override;
|
void calculate_size(ProtoSize &size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@@ -1132,30 +1123,6 @@ class HomeassistantActionRequest final : public ProtoMessage {
|
|||||||
protected:
|
protected:
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
class HomeassistantActionResponse final : public ProtoDecodableMessage {
|
|
||||||
public:
|
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 130;
|
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 34;
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
const char *message_name() const override { return "homeassistant_action_response"; }
|
|
||||||
#endif
|
|
||||||
uint32_t call_id{0};
|
|
||||||
bool success{false};
|
|
||||||
std::string error_message{};
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
const uint8_t *response_data{nullptr};
|
|
||||||
uint16_t response_data_len{0};
|
|
||||||
#endif
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
void dump_to(std::string &out) const override;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
class SubscribeHomeAssistantStatesRequest final : public ProtoMessage {
|
class SubscribeHomeAssistantStatesRequest final : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
|
@@ -1122,28 +1122,6 @@ void HomeassistantActionRequest::dump_to(std::string &out) const {
|
|||||||
out.append("\n");
|
out.append("\n");
|
||||||
}
|
}
|
||||||
dump_field(out, "is_event", this->is_event);
|
dump_field(out, "is_event", this->is_event);
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
dump_field(out, "call_id", this->call_id);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
dump_field(out, "wants_response", this->wants_response);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
dump_field(out, "response_template", this->response_template);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
void HomeassistantActionResponse::dump_to(std::string &out) const {
|
|
||||||
MessageDumpHelper helper(out, "HomeassistantActionResponse");
|
|
||||||
dump_field(out, "call_id", this->call_id);
|
|
||||||
dump_field(out, "success", this->success);
|
|
||||||
dump_field(out, "error_message", this->error_message);
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
out.append(" response_data: ");
|
|
||||||
out.append(format_hex_pretty(this->response_data, this->response_data_len));
|
|
||||||
out.append("\n");
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
|
@@ -610,17 +610,6 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_z_wave_proxy_request(msg);
|
this->on_z_wave_proxy_request(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
case HomeassistantActionResponse::MESSAGE_TYPE: {
|
|
||||||
HomeassistantActionResponse msg;
|
|
||||||
msg.decode(msg_data, msg_size);
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
ESP_LOGVV(TAG, "on_homeassistant_action_response: %s", msg.dump().c_str());
|
|
||||||
#endif
|
|
||||||
this->on_homeassistant_action_response(msg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@@ -66,9 +66,6 @@ class APIServerConnectionBase : public ProtoService {
|
|||||||
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
|
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
virtual void on_homeassistant_action_response(const HomeassistantActionResponse &value){};
|
|
||||||
#endif
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
|
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
|
||||||
#endif
|
#endif
|
||||||
|
@@ -9,16 +9,12 @@
|
|||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/util.h"
|
#include "esphome/core/util.h"
|
||||||
#include "esphome/core/version.h"
|
#include "esphome/core/version.h"
|
||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
|
||||||
#include "homeassistant_service.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_LOGGER
|
#ifdef USE_LOGGER
|
||||||
#include "esphome/components/logger/logger.h"
|
#include "esphome/components/logger/logger.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
@@ -404,38 +400,7 @@ void APIServer::send_homeassistant_action(const HomeassistantActionRequest &call
|
|||||||
client->send_homeassistant_action(call);
|
client->send_homeassistant_action(call);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
#endif
|
||||||
void APIServer::register_action_response_callback(uint32_t call_id, ActionResponseCallback callback) {
|
|
||||||
this->action_response_callbacks_.push_back({call_id, std::move(callback)});
|
|
||||||
}
|
|
||||||
|
|
||||||
void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message) {
|
|
||||||
for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) {
|
|
||||||
if (it->call_id == call_id) {
|
|
||||||
auto callback = std::move(it->callback);
|
|
||||||
this->action_response_callbacks_.erase(it);
|
|
||||||
ActionResponse response(success, error_message);
|
|
||||||
callback(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message,
|
|
||||||
const uint8_t *response_data, size_t response_data_len) {
|
|
||||||
for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) {
|
|
||||||
if (it->call_id == call_id) {
|
|
||||||
auto callback = std::move(it->callback);
|
|
||||||
this->action_response_callbacks_.erase(it);
|
|
||||||
ActionResponse response(success, error_message, response_data, response_data_len);
|
|
||||||
callback(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
#endif // USE_API_HOMEASSISTANT_SERVICES
|
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||||
|
@@ -16,7 +16,6 @@
|
|||||||
#include "user_services.h"
|
#include "user_services.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
@@ -112,17 +111,7 @@ class APIServer : public Component, public Controller {
|
|||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
void send_homeassistant_action(const HomeassistantActionRequest &call);
|
void send_homeassistant_action(const HomeassistantActionRequest &call);
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
#endif
|
||||||
// Action response handling
|
|
||||||
using ActionResponseCallback = std::function<void(const class ActionResponse &)>;
|
|
||||||
void register_action_response_callback(uint32_t call_id, ActionResponseCallback callback);
|
|
||||||
void handle_action_response(uint32_t call_id, bool success, const std::string &error_message);
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
void handle_action_response(uint32_t call_id, bool success, const std::string &error_message,
|
|
||||||
const uint8_t *response_data, size_t response_data_len);
|
|
||||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
#endif // USE_API_HOMEASSISTANT_SERVICES
|
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||||
#endif
|
#endif
|
||||||
@@ -198,13 +187,6 @@ class APIServer : public Component, public Controller {
|
|||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
std::vector<UserServiceDescriptor *> user_services_;
|
std::vector<UserServiceDescriptor *> user_services_;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
struct PendingActionResponse {
|
|
||||||
uint32_t call_id;
|
|
||||||
ActionResponseCallback callback;
|
|
||||||
};
|
|
||||||
std::vector<PendingActionResponse> action_response_callbacks_;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Group smaller types together
|
// Group smaller types together
|
||||||
uint16_t port_{6053};
|
uint16_t port_{6053};
|
||||||
|
@@ -3,13 +3,8 @@
|
|||||||
#include "api_server.h"
|
#include "api_server.h"
|
||||||
#ifdef USE_API
|
#ifdef USE_API
|
||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
#include <functional>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "api_pb2.h"
|
#include "api_pb2.h"
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
#include "esphome/components/json/json_util.h"
|
|
||||||
#endif
|
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
@@ -49,47 +44,9 @@ template<typename... Ts> class TemplatableKeyValuePair {
|
|||||||
TemplatableStringValue<Ts...> value;
|
TemplatableStringValue<Ts...> value;
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
// Represents the response data from a Home Assistant action
|
|
||||||
class ActionResponse {
|
|
||||||
public:
|
|
||||||
ActionResponse(bool success, std::string error_message = "")
|
|
||||||
: success_(success), error_message_(std::move(error_message)) {}
|
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
ActionResponse(bool success, std::string error_message, const uint8_t *data, size_t data_len)
|
|
||||||
: success_(success), error_message_(std::move(error_message)) {
|
|
||||||
if (data == nullptr || data_len == 0)
|
|
||||||
return;
|
|
||||||
this->json_document_ = json::parse_json(data, data_len);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool is_success() const { return this->success_; }
|
|
||||||
const std::string &get_error_message() const { return this->error_message_; }
|
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
// Get data as parsed JSON object (const version returns read-only view)
|
|
||||||
JsonObjectConst get_json() const { return this->json_document_.as<JsonObjectConst>(); }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool success_;
|
|
||||||
std::string error_message_;
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
JsonDocument json_document_;
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
// Callback type for action responses
|
|
||||||
template<typename... Ts> using ActionResponseCallback = std::function<void(const ActionResponse &, Ts...)>;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
|
template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent) {
|
explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {}
|
||||||
this->flags_.is_event = is_event;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T> void set_service(T service) { this->service_ = service; }
|
template<typename T> void set_service(T service) { this->service_ = service; }
|
||||||
|
|
||||||
@@ -104,29 +61,11 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
|||||||
this->variables_.emplace_back(std::move(key), value);
|
this->variables_.emplace_back(std::move(key), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
template<typename T> void set_response_template(T response_template) {
|
|
||||||
this->response_template_ = response_template;
|
|
||||||
this->flags_.has_response_template = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_wants_status() { this->flags_.wants_status = true; }
|
|
||||||
void set_wants_response() { this->flags_.wants_response = true; }
|
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
Trigger<JsonObjectConst, Ts...> *get_success_trigger_with_response() const {
|
|
||||||
return this->success_trigger_with_response_;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
Trigger<Ts...> *get_success_trigger() const { return this->success_trigger_; }
|
|
||||||
Trigger<std::string, Ts...> *get_error_trigger() const { return this->error_trigger_; }
|
|
||||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
HomeassistantActionRequest resp;
|
HomeassistantActionRequest resp;
|
||||||
std::string service_value = this->service_.value(x...);
|
std::string service_value = this->service_.value(x...);
|
||||||
resp.set_service(StringRef(service_value));
|
resp.set_service(StringRef(service_value));
|
||||||
resp.is_event = this->flags_.is_event;
|
resp.is_event = this->is_event_;
|
||||||
for (auto &it : this->data_) {
|
for (auto &it : this->data_) {
|
||||||
resp.data.emplace_back();
|
resp.data.emplace_back();
|
||||||
auto &kv = resp.data.back();
|
auto &kv = resp.data.back();
|
||||||
@@ -145,74 +84,18 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
|||||||
kv.set_key(StringRef(it.key));
|
kv.set_key(StringRef(it.key));
|
||||||
kv.value = it.value.value(x...);
|
kv.value = it.value.value(x...);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
if (this->flags_.wants_status) {
|
|
||||||
// Generate a unique call ID for this service call
|
|
||||||
static uint32_t call_id_counter = 1;
|
|
||||||
uint32_t call_id = call_id_counter++;
|
|
||||||
resp.call_id = call_id;
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
if (this->flags_.wants_response) {
|
|
||||||
resp.wants_response = true;
|
|
||||||
// Set response template if provided
|
|
||||||
if (this->flags_.has_response_template) {
|
|
||||||
std::string response_template_value = this->response_template_.value(x...);
|
|
||||||
resp.response_template = response_template_value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
auto captured_args = std::make_tuple(x...);
|
|
||||||
this->parent_->register_action_response_callback(call_id, [this, captured_args](const ActionResponse &response) {
|
|
||||||
std::apply(
|
|
||||||
[this, &response](auto &&...args) {
|
|
||||||
if (response.is_success()) {
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
if (this->flags_.wants_response) {
|
|
||||||
this->success_trigger_with_response_->trigger(response.get_json(), args...);
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
this->success_trigger_->trigger(args...);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this->error_trigger_->trigger(response.get_error_message(), args...);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
captured_args);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
this->parent_->send_homeassistant_action(resp);
|
this->parent_->send_homeassistant_action(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
APIServer *parent_;
|
APIServer *parent_;
|
||||||
|
bool is_event_;
|
||||||
TemplatableStringValue<Ts...> service_{};
|
TemplatableStringValue<Ts...> service_{};
|
||||||
std::vector<TemplatableKeyValuePair<Ts...>> data_;
|
std::vector<TemplatableKeyValuePair<Ts...>> data_;
|
||||||
std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
|
std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
|
||||||
std::vector<TemplatableKeyValuePair<Ts...>> variables_;
|
std::vector<TemplatableKeyValuePair<Ts...>> variables_;
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
TemplatableStringValue<Ts...> response_template_{""};
|
|
||||||
Trigger<JsonObjectConst, Ts...> *success_trigger_with_response_ = new Trigger<JsonObjectConst, Ts...>();
|
|
||||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
Trigger<Ts...> *success_trigger_ = new Trigger<Ts...>();
|
|
||||||
Trigger<std::string, Ts...> *error_trigger_ = new Trigger<std::string, Ts...>();
|
|
||||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
|
|
||||||
struct Flags {
|
|
||||||
uint8_t is_event : 1;
|
|
||||||
uint8_t wants_status : 1;
|
|
||||||
uint8_t wants_response : 1;
|
|
||||||
uint8_t has_response_template : 1;
|
|
||||||
uint8_t reserved : 5;
|
|
||||||
} flags_{0};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::api
|
} // namespace esphome::api
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
@@ -35,7 +35,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
|||||||
msg.set_name(StringRef(this->name_));
|
msg.set_name(StringRef(this->name_));
|
||||||
msg.key = this->key_;
|
msg.key = this->key_;
|
||||||
std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
|
std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
|
||||||
for (size_t i = 0; i < sizeof...(Ts); i++) {
|
for (int i = 0; i < sizeof...(Ts); i++) {
|
||||||
msg.args.emplace_back();
|
msg.args.emplace_back();
|
||||||
auto &arg = msg.args.back();
|
auto &arg = msg.args.back();
|
||||||
arg.type = arg_types[i];
|
arg.type = arg_types[i];
|
||||||
|
@@ -131,16 +131,28 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const {
|
|||||||
if (this->uuid_.len == uuid.uuid_.len) {
|
if (this->uuid_.len == uuid.uuid_.len) {
|
||||||
switch (this->uuid_.len) {
|
switch (this->uuid_.len) {
|
||||||
case ESP_UUID_LEN_16:
|
case ESP_UUID_LEN_16:
|
||||||
return this->uuid_.uuid.uuid16 == uuid.uuid_.uuid.uuid16;
|
if (uuid.uuid_.uuid.uuid16 == this->uuid_.uuid.uuid16) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case ESP_UUID_LEN_32:
|
case ESP_UUID_LEN_32:
|
||||||
return this->uuid_.uuid.uuid32 == uuid.uuid_.uuid.uuid32;
|
if (uuid.uuid_.uuid.uuid32 == this->uuid_.uuid.uuid32) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case ESP_UUID_LEN_128:
|
case ESP_UUID_LEN_128:
|
||||||
return memcmp(this->uuid_.uuid.uuid128, uuid.uuid_.uuid.uuid128, ESP_UUID_LEN_128) == 0;
|
for (uint8_t i = 0; i < ESP_UUID_LEN_128; i++) {
|
||||||
default:
|
if (uuid.uuid_.uuid.uuid128[i] != this->uuid_.uuid.uuid128[i]) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return this->as_128bit() == uuid.as_128bit();
|
||||||
}
|
}
|
||||||
return this->as_128bit() == uuid.as_128bit();
|
return false;
|
||||||
}
|
}
|
||||||
esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; }
|
esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; }
|
||||||
std::string ESPBTUUID::to_string() const {
|
std::string ESPBTUUID::to_string() const {
|
||||||
|
@@ -179,7 +179,7 @@ void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color colo
|
|||||||
if (b) {
|
if (b) {
|
||||||
int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2 + y_offset;
|
int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2 + y_offset;
|
||||||
auto draw_pixel_at = [&buff, c, y_offset, this](int16_t x, int16_t y) {
|
auto draw_pixel_at = [&buff, c, y_offset, this](int16_t x, int16_t y) {
|
||||||
if (y >= y_offset && static_cast<uint32_t>(y) < y_offset + this->height_)
|
if (y >= y_offset && y < y_offset + this->height_)
|
||||||
buff->draw_pixel_at(x, y, c);
|
buff->draw_pixel_at(x, y, c);
|
||||||
};
|
};
|
||||||
if (!continuous || !has_prev || !prev_b || (abs(y - prev_y) <= thick)) {
|
if (!continuous || !has_prev || !prev_b || (abs(y - prev_y) <= thick)) {
|
||||||
|
@@ -213,7 +213,7 @@ haier_protocol::HandlerError HonClimate::status_handler_(haier_protocol::FrameTy
|
|||||||
this->real_control_packet_size_);
|
this->real_control_packet_size_);
|
||||||
this->status_message_callback_.call((const char *) data, data_size);
|
this->status_message_callback_.call((const char *) data, data_size);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "Status packet too small: %zu (should be >= %zu)", data_size, this->real_control_packet_size_);
|
ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, this->real_control_packet_size_);
|
||||||
}
|
}
|
||||||
switch (this->protocol_phase_) {
|
switch (this->protocol_phase_) {
|
||||||
case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
|
case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
|
||||||
@@ -827,7 +827,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
|||||||
size_t expected_size =
|
size_t expected_size =
|
||||||
2 + this->status_message_header_size_ + this->real_control_packet_size_ + this->real_sensors_packet_size_;
|
2 + this->status_message_header_size_ + this->real_control_packet_size_ + this->real_sensors_packet_size_;
|
||||||
if (size < expected_size) {
|
if (size < expected_size) {
|
||||||
ESP_LOGW(TAG, "Unexpected message size %u (expexted >= %zu)", size, expected_size);
|
ESP_LOGW(TAG, "Unexpected message size %d (expexted >= %d)", size, expected_size);
|
||||||
return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
|
return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
|
||||||
}
|
}
|
||||||
uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1];
|
uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1];
|
||||||
|
@@ -178,7 +178,7 @@ class HonClimate : public HaierClimateBase {
|
|||||||
int extra_control_packet_bytes_{0};
|
int extra_control_packet_bytes_{0};
|
||||||
int extra_sensors_packet_bytes_{4};
|
int extra_sensors_packet_bytes_{4};
|
||||||
int status_message_header_size_{0};
|
int status_message_header_size_{0};
|
||||||
size_t real_control_packet_size_{sizeof(hon_protocol::HaierPacketControl)};
|
int real_control_packet_size_{sizeof(hon_protocol::HaierPacketControl)};
|
||||||
int real_sensors_packet_size_{sizeof(hon_protocol::HaierPacketSensors) + 4};
|
int real_sensors_packet_size_{sizeof(hon_protocol::HaierPacketSensors) + 4};
|
||||||
HonControlMethod control_method_;
|
HonControlMethod control_method_;
|
||||||
std::queue<haier_protocol::HaierMessage> control_messages_queue_;
|
std::queue<haier_protocol::HaierMessage> control_messages_queue_;
|
||||||
|
@@ -5,7 +5,6 @@ from esphome.components.const import CONF_REQUEST_HEADERS
|
|||||||
from esphome.config_helpers import filter_source_files_from_platform
|
from esphome.config_helpers import filter_source_files_from_platform
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_CAPTURE_RESPONSE,
|
|
||||||
CONF_ESP8266_DISABLE_SSL_SUPPORT,
|
CONF_ESP8266_DISABLE_SSL_SUPPORT,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_METHOD,
|
CONF_METHOD,
|
||||||
@@ -58,6 +57,7 @@ CONF_HEADERS = "headers"
|
|||||||
CONF_COLLECT_HEADERS = "collect_headers"
|
CONF_COLLECT_HEADERS = "collect_headers"
|
||||||
CONF_BODY = "body"
|
CONF_BODY = "body"
|
||||||
CONF_JSON = "json"
|
CONF_JSON = "json"
|
||||||
|
CONF_CAPTURE_RESPONSE = "capture_response"
|
||||||
|
|
||||||
|
|
||||||
def validate_url(value):
|
def validate_url(value):
|
||||||
|
@@ -20,23 +20,6 @@ bool MCP2515::setup_internal() {
|
|||||||
return false;
|
return false;
|
||||||
if (this->set_bitrate_(this->bit_rate_, this->mcp_clock_) != canbus::ERROR_OK)
|
if (this->set_bitrate_(this->bit_rate_, this->mcp_clock_) != canbus::ERROR_OK)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// setup hardware filter RXF0 accepting all standard CAN IDs
|
|
||||||
if (this->set_filter_(RXF::RXF0, false, 0) != canbus::ERROR_OK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this->set_filter_mask_(MASK::MASK0, false, 0) != canbus::ERROR_OK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup hardware filter RXF1 accepting all extended CAN IDs
|
|
||||||
if (this->set_filter_(RXF::RXF1, true, 0) != canbus::ERROR_OK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this->set_filter_mask_(MASK::MASK1, true, 0) != canbus::ERROR_OK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->set_mode_(this->mcp_mode_) != canbus::ERROR_OK)
|
if (this->set_mode_(this->mcp_mode_) != canbus::ERROR_OK)
|
||||||
return false;
|
return false;
|
||||||
uint8_t err_flags = this->get_error_flags_();
|
uint8_t err_flags = this->get_error_flags_();
|
||||||
|
@@ -26,16 +26,24 @@ void MDNSComponent::setup() {
|
|||||||
mdns_instance_name_set(this->hostname_.c_str());
|
mdns_instance_name_set(this->hostname_.c_str());
|
||||||
|
|
||||||
for (const auto &service : this->services_) {
|
for (const auto &service : this->services_) {
|
||||||
std::vector<mdns_txt_item_t> txt_records(service.txt_records.size());
|
std::vector<mdns_txt_item_t> txt_records;
|
||||||
for (size_t i = 0; i < service.txt_records.size(); i++) {
|
for (const auto &record : service.txt_records) {
|
||||||
// mdns_service_add copies the strings internally, no need to strdup
|
mdns_txt_item_t it{};
|
||||||
txt_records[i].key = service.txt_records[i].key.c_str();
|
// dup strings to ensure the pointer is valid even after the record loop
|
||||||
txt_records[i].value = const_cast<TemplatableValue<std::string> &>(service.txt_records[i].value).value().c_str();
|
it.key = strdup(record.key.c_str());
|
||||||
|
it.value = strdup(const_cast<TemplatableValue<std::string> &>(record.value).value().c_str());
|
||||||
|
txt_records.push_back(it);
|
||||||
}
|
}
|
||||||
uint16_t port = const_cast<TemplatableValue<uint16_t> &>(service.port).value();
|
uint16_t port = const_cast<TemplatableValue<uint16_t> &>(service.port).value();
|
||||||
err = mdns_service_add(nullptr, service.service_type.c_str(), service.proto.c_str(), port, txt_records.data(),
|
err = mdns_service_add(nullptr, service.service_type.c_str(), service.proto.c_str(), port, txt_records.data(),
|
||||||
txt_records.size());
|
txt_records.size());
|
||||||
|
|
||||||
|
// free records
|
||||||
|
for (const auto &it : txt_records) {
|
||||||
|
delete it.key; // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
|
delete it.value; // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
|
}
|
||||||
|
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "Failed to register service %s: %s", service.service_type.c_str(), esp_err_to_name(err));
|
ESP_LOGW(TAG, "Failed to register service %s: %s", service.service_type.c_str(), esp_err_to_name(err));
|
||||||
}
|
}
|
||||||
|
@@ -340,7 +340,7 @@ class MipiSpi : public display::Display,
|
|||||||
this->write_cmd_addr_data(0, 0, 0, 0, ptr, w * h, 8);
|
this->write_cmd_addr_data(0, 0, 0, 0, ptr, w * h, 8);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (size_t y = 0; y != static_cast<size_t>(h); y++) {
|
for (size_t y = 0; y != h; y++) {
|
||||||
if constexpr (BUS_TYPE == BUS_TYPE_SINGLE || BUS_TYPE == BUS_TYPE_SINGLE_16) {
|
if constexpr (BUS_TYPE == BUS_TYPE_SINGLE || BUS_TYPE == BUS_TYPE_SINGLE_16) {
|
||||||
this->write_array(ptr, w);
|
this->write_array(ptr, w);
|
||||||
} else if constexpr (BUS_TYPE == BUS_TYPE_QUAD) {
|
} else if constexpr (BUS_TYPE == BUS_TYPE_QUAD) {
|
||||||
@@ -372,8 +372,8 @@ class MipiSpi : public display::Display,
|
|||||||
uint8_t dbuffer[DISPLAYPIXEL * 48];
|
uint8_t dbuffer[DISPLAYPIXEL * 48];
|
||||||
uint8_t *dptr = dbuffer;
|
uint8_t *dptr = dbuffer;
|
||||||
auto stride = x_offset + w + x_pad; // stride in pixels
|
auto stride = x_offset + w + x_pad; // stride in pixels
|
||||||
for (size_t y = 0; y != static_cast<size_t>(h); y++) {
|
for (size_t y = 0; y != h; y++) {
|
||||||
for (size_t x = 0; x != static_cast<size_t>(w); x++) {
|
for (size_t x = 0; x != w; x++) {
|
||||||
auto color_val = ptr[y * stride + x];
|
auto color_val = ptr[y * stride + x];
|
||||||
if constexpr (DISPLAYPIXEL == PIXEL_MODE_18 && BUFFERPIXEL == PIXEL_MODE_16) {
|
if constexpr (DISPLAYPIXEL == PIXEL_MODE_18 && BUFFERPIXEL == PIXEL_MODE_16) {
|
||||||
// 16 to 18 bit conversion
|
// 16 to 18 bit conversion
|
||||||
|
@@ -5,8 +5,6 @@ from esphome.config_helpers import filter_source_files_from_platform
|
|||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_BUFFER_SIZE,
|
CONF_BUFFER_SIZE,
|
||||||
CONF_CARRIER_DUTY_PERCENT,
|
|
||||||
CONF_CARRIER_FREQUENCY,
|
|
||||||
CONF_CLOCK_RESOLUTION,
|
CONF_CLOCK_RESOLUTION,
|
||||||
CONF_DUMP,
|
CONF_DUMP,
|
||||||
CONF_FILTER,
|
CONF_FILTER,
|
||||||
@@ -151,14 +149,6 @@ CONFIG_SCHEMA = remote_base.validate_triggers(
|
|||||||
),
|
),
|
||||||
cv.boolean,
|
cv.boolean,
|
||||||
),
|
),
|
||||||
cv.SplitDefault(CONF_CARRIER_DUTY_PERCENT, esp32=100): cv.All(
|
|
||||||
cv.only_on_esp32,
|
|
||||||
cv.percentage_int,
|
|
||||||
cv.Range(min=1, max=100),
|
|
||||||
),
|
|
||||||
cv.SplitDefault(CONF_CARRIER_FREQUENCY, esp32="0Hz"): cv.All(
|
|
||||||
cv.only_on_esp32, cv.frequency, cv.int_
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(cv.COMPONENT_SCHEMA)
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
@@ -178,8 +168,6 @@ async def to_code(config):
|
|||||||
cg.add(var.set_clock_resolution(config[CONF_CLOCK_RESOLUTION]))
|
cg.add(var.set_clock_resolution(config[CONF_CLOCK_RESOLUTION]))
|
||||||
if CONF_FILTER_SYMBOLS in config:
|
if CONF_FILTER_SYMBOLS in config:
|
||||||
cg.add(var.set_filter_symbols(config[CONF_FILTER_SYMBOLS]))
|
cg.add(var.set_filter_symbols(config[CONF_FILTER_SYMBOLS]))
|
||||||
cg.add(var.set_carrier_duty_percent(config[CONF_CARRIER_DUTY_PERCENT]))
|
|
||||||
cg.add(var.set_carrier_frequency(config[CONF_CARRIER_FREQUENCY]))
|
|
||||||
else:
|
else:
|
||||||
var = cg.new_Pvariable(config[CONF_ID], pin)
|
var = cg.new_Pvariable(config[CONF_ID], pin)
|
||||||
|
|
||||||
|
@@ -64,8 +64,6 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase,
|
|||||||
void set_filter_symbols(uint32_t filter_symbols) { this->filter_symbols_ = filter_symbols; }
|
void set_filter_symbols(uint32_t filter_symbols) { this->filter_symbols_ = filter_symbols; }
|
||||||
void set_receive_symbols(uint32_t receive_symbols) { this->receive_symbols_ = receive_symbols; }
|
void set_receive_symbols(uint32_t receive_symbols) { this->receive_symbols_ = receive_symbols; }
|
||||||
void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; }
|
void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; }
|
||||||
void set_carrier_duty_percent(uint8_t carrier_duty_percent) { this->carrier_duty_percent_ = carrier_duty_percent; }
|
|
||||||
void set_carrier_frequency(uint32_t carrier_frequency) { this->carrier_frequency_ = carrier_frequency; }
|
|
||||||
#endif
|
#endif
|
||||||
void set_buffer_size(uint32_t buffer_size) { this->buffer_size_ = buffer_size; }
|
void set_buffer_size(uint32_t buffer_size) { this->buffer_size_ = buffer_size; }
|
||||||
void set_filter_us(uint32_t filter_us) { this->filter_us_ = filter_us; }
|
void set_filter_us(uint32_t filter_us) { this->filter_us_ = filter_us; }
|
||||||
@@ -78,8 +76,6 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase,
|
|||||||
uint32_t filter_symbols_{0};
|
uint32_t filter_symbols_{0};
|
||||||
uint32_t receive_symbols_{0};
|
uint32_t receive_symbols_{0};
|
||||||
bool with_dma_{false};
|
bool with_dma_{false};
|
||||||
uint32_t carrier_frequency_{0};
|
|
||||||
uint8_t carrier_duty_percent_{100};
|
|
||||||
esp_err_t error_code_{ESP_OK};
|
esp_err_t error_code_{ESP_OK};
|
||||||
std::string error_string_{""};
|
std::string error_string_{""};
|
||||||
#endif
|
#endif
|
||||||
|
@@ -72,21 +72,6 @@ void RemoteReceiverComponent::setup() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->carrier_frequency_ > 0 && 0 < this->carrier_duty_percent_ && this->carrier_duty_percent_ < 100) {
|
|
||||||
rmt_carrier_config_t carrier;
|
|
||||||
memset(&carrier, 0, sizeof(carrier));
|
|
||||||
carrier.frequency_hz = this->carrier_frequency_;
|
|
||||||
carrier.duty_cycle = (float) this->carrier_duty_percent_ / 100.0f;
|
|
||||||
carrier.flags.polarity_active_low = this->pin_->is_inverted();
|
|
||||||
error = rmt_apply_carrier(this->channel_, &carrier);
|
|
||||||
if (error != ESP_OK) {
|
|
||||||
this->error_code_ = error;
|
|
||||||
this->error_string_ = "in rmt_apply_carrier";
|
|
||||||
this->mark_failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rmt_rx_event_callbacks_t callbacks;
|
rmt_rx_event_callbacks_t callbacks;
|
||||||
memset(&callbacks, 0, sizeof(callbacks));
|
memset(&callbacks, 0, sizeof(callbacks));
|
||||||
callbacks.on_recv_done = rmt_callback;
|
callbacks.on_recv_done = rmt_callback;
|
||||||
@@ -126,13 +111,11 @@ void RemoteReceiverComponent::dump_config() {
|
|||||||
" Filter symbols: %" PRIu32 "\n"
|
" Filter symbols: %" PRIu32 "\n"
|
||||||
" Receive symbols: %" PRIu32 "\n"
|
" Receive symbols: %" PRIu32 "\n"
|
||||||
" Tolerance: %" PRIu32 "%s\n"
|
" Tolerance: %" PRIu32 "%s\n"
|
||||||
" Carrier frequency: %" PRIu32 " hz\n"
|
|
||||||
" Carrier duty: %u%%\n"
|
|
||||||
" Filter out pulses shorter than: %" PRIu32 " us\n"
|
" Filter out pulses shorter than: %" PRIu32 " us\n"
|
||||||
" Signal is done after %" PRIu32 " us of no changes",
|
" Signal is done after %" PRIu32 " us of no changes",
|
||||||
this->clock_resolution_, this->rmt_symbols_, this->filter_symbols_, this->receive_symbols_,
|
this->clock_resolution_, this->rmt_symbols_, this->filter_symbols_, this->receive_symbols_,
|
||||||
this->tolerance_, (this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " us" : "%",
|
this->tolerance_, (this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " us" : "%",
|
||||||
this->carrier_frequency_, this->carrier_duty_percent_, this->filter_us_, this->idle_us_);
|
this->filter_us_, this->idle_us_);
|
||||||
if (this->is_failed()) {
|
if (this->is_failed()) {
|
||||||
ESP_LOGE(TAG, "Configuring RMT driver failed: %s (%s)", esp_err_to_name(this->error_code_),
|
ESP_LOGE(TAG, "Configuring RMT driver failed: %s (%s)", esp_err_to_name(this->error_code_),
|
||||||
this->error_string_.c_str());
|
this->error_string_.c_str());
|
||||||
|
@@ -215,7 +215,7 @@ void Rtttl::loop() {
|
|||||||
sample[x].right = 0;
|
sample[x].right = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (static_cast<size_t>(x) >= SAMPLE_BUFFER_SIZE || this->samples_sent_ >= this->samples_count_) {
|
if (x >= SAMPLE_BUFFER_SIZE || this->samples_sent_ >= this->samples_count_) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this->samples_sent_++;
|
this->samples_sent_++;
|
||||||
|
@@ -251,7 +251,7 @@ void Tormatic::stop_at_target_() {
|
|||||||
// Read a GateStatus from the unit. The unit only sends messages in response to
|
// Read a GateStatus from the unit. The unit only sends messages in response to
|
||||||
// status requests or commands, so a message needs to be sent first.
|
// status requests or commands, so a message needs to be sent first.
|
||||||
optional<GateStatus> Tormatic::read_gate_status_() {
|
optional<GateStatus> Tormatic::read_gate_status_() {
|
||||||
if (this->available() < static_cast<int>(sizeof(MessageHeader))) {
|
if (this->available() < sizeof(MessageHeader)) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -58,7 +58,7 @@ void UponorSmatrixClimate::control(const climate::ClimateCall &call) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void UponorSmatrixClimate::on_device_data(const UponorSmatrixData *data, size_t data_len) {
|
void UponorSmatrixClimate::on_device_data(const UponorSmatrixData *data, size_t data_len) {
|
||||||
for (size_t i = 0; i < data_len; i++) {
|
for (int i = 0; i < data_len; i++) {
|
||||||
switch (data[i].id) {
|
switch (data[i].id) {
|
||||||
case UPONOR_ID_TARGET_TEMP_MIN:
|
case UPONOR_ID_TARGET_TEMP_MIN:
|
||||||
this->min_temperature_ = raw_to_celsius(data[i].value);
|
this->min_temperature_ = raw_to_celsius(data[i].value);
|
||||||
|
@@ -18,7 +18,7 @@ void UponorSmatrixSensor::dump_config() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void UponorSmatrixSensor::on_device_data(const UponorSmatrixData *data, size_t data_len) {
|
void UponorSmatrixSensor::on_device_data(const UponorSmatrixData *data, size_t data_len) {
|
||||||
for (size_t i = 0; i < data_len; i++) {
|
for (int i = 0; i < data_len; i++) {
|
||||||
switch (data[i].id) {
|
switch (data[i].id) {
|
||||||
case UPONOR_ID_ROOM_TEMP:
|
case UPONOR_ID_ROOM_TEMP:
|
||||||
if (this->temperature_sensor_ != nullptr)
|
if (this->temperature_sensor_ != nullptr)
|
||||||
|
@@ -122,7 +122,7 @@ bool UponorSmatrixComponent::parse_byte_(uint8_t byte) {
|
|||||||
|
|
||||||
// Decode packet payload data for easy access
|
// Decode packet payload data for easy access
|
||||||
UponorSmatrixData data[data_len];
|
UponorSmatrixData data[data_len];
|
||||||
for (size_t i = 0; i < data_len; i++) {
|
for (int i = 0; i < data_len; i++) {
|
||||||
data[i].id = packet[(i * 3) + 4];
|
data[i].id = packet[(i * 3) + 4];
|
||||||
data[i].value = encode_uint16(packet[(i * 3) + 5], packet[(i * 3) + 6]);
|
data[i].value = encode_uint16(packet[(i * 3) + 5], packet[(i * 3) + 6]);
|
||||||
}
|
}
|
||||||
@@ -135,7 +135,7 @@ bool UponorSmatrixComponent::parse_byte_(uint8_t byte) {
|
|||||||
// thermostat sending both room temperature and time information.
|
// thermostat sending both room temperature and time information.
|
||||||
bool found_temperature = false;
|
bool found_temperature = false;
|
||||||
bool found_time = false;
|
bool found_time = false;
|
||||||
for (size_t i = 0; i < data_len; i++) {
|
for (int i = 0; i < data_len; i++) {
|
||||||
if (data[i].id == UPONOR_ID_ROOM_TEMP)
|
if (data[i].id == UPONOR_ID_ROOM_TEMP)
|
||||||
found_temperature = true;
|
found_temperature = true;
|
||||||
if (data[i].id == UPONOR_ID_DATETIME1)
|
if (data[i].id == UPONOR_ID_DATETIME1)
|
||||||
@@ -181,7 +181,7 @@ bool UponorSmatrixComponent::send(uint16_t device_address, const UponorSmatrixDa
|
|||||||
packet.push_back(device_address >> 8);
|
packet.push_back(device_address >> 8);
|
||||||
packet.push_back(device_address >> 0);
|
packet.push_back(device_address >> 0);
|
||||||
|
|
||||||
for (size_t i = 0; i < data_len; i++) {
|
for (int i = 0; i < data_len; i++) {
|
||||||
packet.push_back(data[i].id);
|
packet.push_back(data[i].id);
|
||||||
packet.push_back(data[i].value >> 8);
|
packet.push_back(data[i].value >> 8);
|
||||||
packet.push_back(data[i].value >> 0);
|
packet.push_back(data[i].value >> 0);
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
#include "veml7700.h"
|
#include "veml7700.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace veml7700 {
|
namespace veml7700 {
|
||||||
@@ -13,30 +12,30 @@ static float reduce_to_zero(float a, float b) { return (a > b) ? (a - b) : 0; }
|
|||||||
|
|
||||||
template<typename T, size_t size> T get_next(const T (&array)[size], const T val) {
|
template<typename T, size_t size> T get_next(const T (&array)[size], const T val) {
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
size_t idx = std::numeric_limits<size_t>::max();
|
size_t idx = -1;
|
||||||
while (idx == std::numeric_limits<size_t>::max() && i < size) {
|
while (idx == -1 && i < size) {
|
||||||
if (array[i] == val) {
|
if (array[i] == val) {
|
||||||
idx = i;
|
idx = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
if (idx == std::numeric_limits<size_t>::max() || i + 1 >= size)
|
if (idx == -1 || i + 1 >= size)
|
||||||
return val;
|
return val;
|
||||||
return array[i + 1];
|
return array[i + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, size_t size> T get_prev(const T (&array)[size], const T val) {
|
template<typename T, size_t size> T get_prev(const T (&array)[size], const T val) {
|
||||||
size_t i = size - 1;
|
size_t i = size - 1;
|
||||||
size_t idx = std::numeric_limits<size_t>::max();
|
size_t idx = -1;
|
||||||
while (idx == std::numeric_limits<size_t>::max() && i > 0) {
|
while (idx == -1 && i > 0) {
|
||||||
if (array[i] == val) {
|
if (array[i] == val) {
|
||||||
idx = i;
|
idx = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
i--;
|
i--;
|
||||||
}
|
}
|
||||||
if (idx == std::numeric_limits<size_t>::max() || i == 0)
|
if (idx == -1 || i == 0)
|
||||||
return val;
|
return val;
|
||||||
return array[i - 1];
|
return array[i - 1];
|
||||||
}
|
}
|
||||||
|
@@ -2274,11 +2274,11 @@ void GDEW0154M09::clear_() {
|
|||||||
uint32_t pixsize = this->get_buffer_length_();
|
uint32_t pixsize = this->get_buffer_length_();
|
||||||
for (uint8_t j = 0; j < 2; j++) {
|
for (uint8_t j = 0; j < 2; j++) {
|
||||||
this->command(CMD_DTM1_DATA_START_TRANS);
|
this->command(CMD_DTM1_DATA_START_TRANS);
|
||||||
for (uint32_t count = 0; count < pixsize; count++) {
|
for (int count = 0; count < pixsize; count++) {
|
||||||
this->data(0x00);
|
this->data(0x00);
|
||||||
}
|
}
|
||||||
this->command(CMD_DTM2_DATA_START_TRANS2);
|
this->command(CMD_DTM2_DATA_START_TRANS2);
|
||||||
for (uint32_t count = 0; count < pixsize; count++) {
|
for (int count = 0; count < pixsize; count++) {
|
||||||
this->data(0xff);
|
this->data(0xff);
|
||||||
}
|
}
|
||||||
this->command(CMD_DISPLAY_REFRESH);
|
this->command(CMD_DISPLAY_REFRESH);
|
||||||
@@ -2291,11 +2291,11 @@ void HOT GDEW0154M09::display() {
|
|||||||
this->init_internal_();
|
this->init_internal_();
|
||||||
// "Mode 0 display" for now
|
// "Mode 0 display" for now
|
||||||
this->command(CMD_DTM1_DATA_START_TRANS);
|
this->command(CMD_DTM1_DATA_START_TRANS);
|
||||||
for (uint32_t i = 0; i < this->get_buffer_length_(); i++) {
|
for (int i = 0; i < this->get_buffer_length_(); i++) {
|
||||||
this->data(0xff);
|
this->data(0xff);
|
||||||
}
|
}
|
||||||
this->command(CMD_DTM2_DATA_START_TRANS2); // write 'new' data to SRAM
|
this->command(CMD_DTM2_DATA_START_TRANS2); // write 'new' data to SRAM
|
||||||
for (uint32_t i = 0; i < this->get_buffer_length_(); i++) {
|
for (int i = 0; i < this->get_buffer_length_(); i++) {
|
||||||
this->data(this->buffer_[i]);
|
this->data(this->buffer_[i]);
|
||||||
}
|
}
|
||||||
this->command(CMD_DISPLAY_REFRESH);
|
this->command(CMD_DISPLAY_REFRESH);
|
||||||
|
@@ -67,31 +67,6 @@ ConfigPath = list[str | int]
|
|||||||
path_context = contextvars.ContextVar("Config path")
|
path_context = contextvars.ContextVar("Config path")
|
||||||
|
|
||||||
|
|
||||||
def _add_auto_load_steps(result: Config, loads: list[str]) -> None:
|
|
||||||
"""Add AutoLoadValidationStep for each component in loads that isn't already loaded."""
|
|
||||||
for load in loads:
|
|
||||||
if load not in result:
|
|
||||||
result.add_validation_step(AutoLoadValidationStep(load))
|
|
||||||
|
|
||||||
|
|
||||||
def _process_auto_load(
|
|
||||||
result: Config, platform: ComponentManifest, path: ConfigPath
|
|
||||||
) -> None:
|
|
||||||
# Process platform's AUTO_LOAD
|
|
||||||
auto_load = platform.auto_load
|
|
||||||
if isinstance(auto_load, list):
|
|
||||||
_add_auto_load_steps(result, auto_load)
|
|
||||||
elif callable(auto_load):
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
if inspect.signature(auto_load).parameters:
|
|
||||||
result.add_validation_step(
|
|
||||||
AddDynamicAutoLoadsValidationStep(path, platform)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
_add_auto_load_steps(result, auto_load())
|
|
||||||
|
|
||||||
|
|
||||||
def _process_platform_config(
|
def _process_platform_config(
|
||||||
result: Config,
|
result: Config,
|
||||||
component_name: str,
|
component_name: str,
|
||||||
@@ -116,7 +91,9 @@ def _process_platform_config(
|
|||||||
CORE.loaded_platforms.add(f"{component_name}/{platform_name}")
|
CORE.loaded_platforms.add(f"{component_name}/{platform_name}")
|
||||||
|
|
||||||
# Process platform's AUTO_LOAD
|
# Process platform's AUTO_LOAD
|
||||||
_process_auto_load(result, platform, path)
|
for load in platform.auto_load:
|
||||||
|
if load not in result:
|
||||||
|
result.add_validation_step(AutoLoadValidationStep(load))
|
||||||
|
|
||||||
# Add validation steps for the platform
|
# Add validation steps for the platform
|
||||||
p_domain = f"{component_name}.{platform_name}"
|
p_domain = f"{component_name}.{platform_name}"
|
||||||
@@ -413,7 +390,9 @@ class LoadValidationStep(ConfigValidationStep):
|
|||||||
result[self.domain] = self.conf = [self.conf]
|
result[self.domain] = self.conf = [self.conf]
|
||||||
|
|
||||||
# Process AUTO_LOAD
|
# Process AUTO_LOAD
|
||||||
_process_auto_load(result, component, path)
|
for load in component.auto_load:
|
||||||
|
if load not in result:
|
||||||
|
result.add_validation_step(AutoLoadValidationStep(load))
|
||||||
|
|
||||||
result.add_validation_step(
|
result.add_validation_step(
|
||||||
MetadataValidationStep([self.domain], self.domain, self.conf, component)
|
MetadataValidationStep([self.domain], self.domain, self.conf, component)
|
||||||
@@ -639,34 +618,6 @@ class MetadataValidationStep(ConfigValidationStep):
|
|||||||
result.add_validation_step(FinalValidateValidationStep(self.path, self.comp))
|
result.add_validation_step(FinalValidateValidationStep(self.path, self.comp))
|
||||||
|
|
||||||
|
|
||||||
class AddDynamicAutoLoadsValidationStep(ConfigValidationStep):
|
|
||||||
"""Add dynamic auto loads step.
|
|
||||||
|
|
||||||
This step is used to auto-load components where one component can alter its
|
|
||||||
AUTO_LOAD based on its configuration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Has to happen after normal schema is validated and before final schema validation
|
|
||||||
priority = -10.0
|
|
||||||
|
|
||||||
def __init__(self, path: ConfigPath, comp: ComponentManifest) -> None:
|
|
||||||
self.path = path
|
|
||||||
self.comp = comp
|
|
||||||
|
|
||||||
def run(self, result: Config) -> None:
|
|
||||||
if result.errors:
|
|
||||||
# If result already has errors, skip this step
|
|
||||||
return
|
|
||||||
|
|
||||||
conf = result.get_nested_item(self.path)
|
|
||||||
with result.catch_error(self.path):
|
|
||||||
auto_load = self.comp.auto_load
|
|
||||||
if not callable(auto_load):
|
|
||||||
return
|
|
||||||
loads = auto_load(conf)
|
|
||||||
_add_auto_load_steps(result, loads)
|
|
||||||
|
|
||||||
|
|
||||||
class SchemaValidationStep(ConfigValidationStep):
|
class SchemaValidationStep(ConfigValidationStep):
|
||||||
"""Schema validation step.
|
"""Schema validation step.
|
||||||
|
|
||||||
|
@@ -174,7 +174,6 @@ CONF_CALIBRATE_LINEAR = "calibrate_linear"
|
|||||||
CONF_CALIBRATION = "calibration"
|
CONF_CALIBRATION = "calibration"
|
||||||
CONF_CAPACITANCE = "capacitance"
|
CONF_CAPACITANCE = "capacitance"
|
||||||
CONF_CAPACITY = "capacity"
|
CONF_CAPACITY = "capacity"
|
||||||
CONF_CAPTURE_RESPONSE = "capture_response"
|
|
||||||
CONF_CARBON_MONOXIDE = "carbon_monoxide"
|
CONF_CARBON_MONOXIDE = "carbon_monoxide"
|
||||||
CONF_CARRIER_DUTY_PERCENT = "carrier_duty_percent"
|
CONF_CARRIER_DUTY_PERCENT = "carrier_duty_percent"
|
||||||
CONF_CARRIER_FREQUENCY = "carrier_frequency"
|
CONF_CARRIER_FREQUENCY = "carrier_frequency"
|
||||||
@@ -677,7 +676,6 @@ CONF_ON_RESPONSE = "on_response"
|
|||||||
CONF_ON_SHUTDOWN = "on_shutdown"
|
CONF_ON_SHUTDOWN = "on_shutdown"
|
||||||
CONF_ON_SPEED_SET = "on_speed_set"
|
CONF_ON_SPEED_SET = "on_speed_set"
|
||||||
CONF_ON_STATE = "on_state"
|
CONF_ON_STATE = "on_state"
|
||||||
CONF_ON_SUCCESS = "on_success"
|
|
||||||
CONF_ON_TAG = "on_tag"
|
CONF_ON_TAG = "on_tag"
|
||||||
CONF_ON_TAG_REMOVED = "on_tag_removed"
|
CONF_ON_TAG_REMOVED = "on_tag_removed"
|
||||||
CONF_ON_TIME = "on_time"
|
CONF_ON_TIME = "on_time"
|
||||||
@@ -820,7 +818,6 @@ CONF_RESET_DURATION = "reset_duration"
|
|||||||
CONF_RESET_PIN = "reset_pin"
|
CONF_RESET_PIN = "reset_pin"
|
||||||
CONF_RESIZE = "resize"
|
CONF_RESIZE = "resize"
|
||||||
CONF_RESOLUTION = "resolution"
|
CONF_RESOLUTION = "resolution"
|
||||||
CONF_RESPONSE_TEMPLATE = "response_template"
|
|
||||||
CONF_RESTART = "restart"
|
CONF_RESTART = "restart"
|
||||||
CONF_RESTORE = "restore"
|
CONF_RESTORE = "restore"
|
||||||
CONF_RESTORE_MODE = "restore_mode"
|
CONF_RESTORE_MODE = "restore_mode"
|
||||||
|
@@ -112,8 +112,6 @@
|
|||||||
#define USE_API
|
#define USE_API
|
||||||
#define USE_API_CLIENT_CONNECTED_TRIGGER
|
#define USE_API_CLIENT_CONNECTED_TRIGGER
|
||||||
#define USE_API_CLIENT_DISCONNECTED_TRIGGER
|
#define USE_API_CLIENT_DISCONNECTED_TRIGGER
|
||||||
#define USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
||||||
#define USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
||||||
#define USE_API_HOMEASSISTANT_SERVICES
|
#define USE_API_HOMEASSISTANT_SERVICES
|
||||||
#define USE_API_HOMEASSISTANT_STATES
|
#define USE_API_HOMEASSISTANT_STATES
|
||||||
#define USE_API_NOISE
|
#define USE_API_NOISE
|
||||||
|
@@ -77,7 +77,7 @@ bool ESPTime::strptime(const std::string &time_to_parse, ESPTime &esp_time) {
|
|||||||
&hour, // NOLINT
|
&hour, // NOLINT
|
||||||
&minute, // NOLINT
|
&minute, // NOLINT
|
||||||
&second, &num) == 6 && // NOLINT
|
&second, &num) == 6 && // NOLINT
|
||||||
num == static_cast<int>(time_to_parse.size())) {
|
num == time_to_parse.size()) {
|
||||||
esp_time.year = year;
|
esp_time.year = year;
|
||||||
esp_time.month = month;
|
esp_time.month = month;
|
||||||
esp_time.day_of_month = day;
|
esp_time.day_of_month = day;
|
||||||
@@ -87,7 +87,7 @@ bool ESPTime::strptime(const std::string &time_to_parse, ESPTime &esp_time) {
|
|||||||
} else if (sscanf(time_to_parse.c_str(), "%04hu-%02hhu-%02hhu %02hhu:%02hhu %n", &year, &month, &day, // NOLINT
|
} else if (sscanf(time_to_parse.c_str(), "%04hu-%02hhu-%02hhu %02hhu:%02hhu %n", &year, &month, &day, // NOLINT
|
||||||
&hour, // NOLINT
|
&hour, // NOLINT
|
||||||
&minute, &num) == 5 && // NOLINT
|
&minute, &num) == 5 && // NOLINT
|
||||||
num == static_cast<int>(time_to_parse.size())) {
|
num == time_to_parse.size()) {
|
||||||
esp_time.year = year;
|
esp_time.year = year;
|
||||||
esp_time.month = month;
|
esp_time.month = month;
|
||||||
esp_time.day_of_month = day;
|
esp_time.day_of_month = day;
|
||||||
@@ -95,17 +95,17 @@ bool ESPTime::strptime(const std::string &time_to_parse, ESPTime &esp_time) {
|
|||||||
esp_time.minute = minute;
|
esp_time.minute = minute;
|
||||||
esp_time.second = 0;
|
esp_time.second = 0;
|
||||||
} else if (sscanf(time_to_parse.c_str(), "%02hhu:%02hhu:%02hhu %n", &hour, &minute, &second, &num) == 3 && // NOLINT
|
} else if (sscanf(time_to_parse.c_str(), "%02hhu:%02hhu:%02hhu %n", &hour, &minute, &second, &num) == 3 && // NOLINT
|
||||||
num == static_cast<int>(time_to_parse.size())) {
|
num == time_to_parse.size()) {
|
||||||
esp_time.hour = hour;
|
esp_time.hour = hour;
|
||||||
esp_time.minute = minute;
|
esp_time.minute = minute;
|
||||||
esp_time.second = second;
|
esp_time.second = second;
|
||||||
} else if (sscanf(time_to_parse.c_str(), "%02hhu:%02hhu %n", &hour, &minute, &num) == 2 && // NOLINT
|
} else if (sscanf(time_to_parse.c_str(), "%02hhu:%02hhu %n", &hour, &minute, &num) == 2 && // NOLINT
|
||||||
num == static_cast<int>(time_to_parse.size())) {
|
num == time_to_parse.size()) {
|
||||||
esp_time.hour = hour;
|
esp_time.hour = hour;
|
||||||
esp_time.minute = minute;
|
esp_time.minute = minute;
|
||||||
esp_time.second = 0;
|
esp_time.second = 0;
|
||||||
} else if (sscanf(time_to_parse.c_str(), "%04hu-%02hhu-%02hhu %n", &year, &month, &day, &num) == 3 && // NOLINT
|
} else if (sscanf(time_to_parse.c_str(), "%04hu-%02hhu-%02hhu %n", &year, &month, &day, &num) == 3 && // NOLINT
|
||||||
num == static_cast<int>(time_to_parse.size())) {
|
num == time_to_parse.size()) {
|
||||||
esp_time.year = year;
|
esp_time.year = year;
|
||||||
esp_time.month = month;
|
esp_time.month = month;
|
||||||
esp_time.day_of_month = day;
|
esp_time.day_of_month = day;
|
||||||
|
@@ -82,10 +82,11 @@ class ComponentManifest:
|
|||||||
return getattr(self.module, "CONFLICTS_WITH", [])
|
return getattr(self.module, "CONFLICTS_WITH", [])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def auto_load(
|
def auto_load(self) -> list[str]:
|
||||||
self,
|
al = getattr(self.module, "AUTO_LOAD", [])
|
||||||
) -> list[str] | Callable[[], list[str]] | Callable[[ConfigType], list[str]]:
|
if callable(al):
|
||||||
return getattr(self.module, "AUTO_LOAD", [])
|
return al()
|
||||||
|
return al
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def codeowners(self) -> list[str]:
|
def codeowners(self) -> list[str]:
|
||||||
|
@@ -13,7 +13,7 @@ esptool==5.1.0
|
|||||||
click==8.1.7
|
click==8.1.7
|
||||||
esphome-dashboard==20250904.0
|
esphome-dashboard==20250904.0
|
||||||
aioesphomeapi==41.11.0
|
aioesphomeapi==41.11.0
|
||||||
zeroconf==0.148.0
|
zeroconf==0.147.2
|
||||||
puremagic==1.30
|
puremagic==1.30
|
||||||
ruamel.yaml==0.18.15 # dashboard_import
|
ruamel.yaml==0.18.15 # dashboard_import
|
||||||
ruamel.yaml.clib==0.2.12 # dashboard_import
|
ruamel.yaml.clib==0.2.12 # dashboard_import
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
pylint==3.3.9
|
pylint==3.3.8
|
||||||
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
|
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
|
||||||
ruff==0.13.3 # also change in .pre-commit-config.yaml when updating
|
ruff==0.13.3 # also change in .pre-commit-config.yaml when updating
|
||||||
pyupgrade==3.20.0 # also change in .pre-commit-config.yaml when updating
|
pyupgrade==3.20.0 # also change in .pre-commit-config.yaml when updating
|
||||||
|
@@ -48,10 +48,9 @@ def parse_requirement_line(line: str) -> tuple[str, str] | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_clang_tidy_version_from_requirements(repo_root: Path | None = None) -> str:
|
def get_clang_tidy_version_from_requirements() -> str:
|
||||||
"""Get clang-tidy version from requirements_dev.txt"""
|
"""Get clang-tidy version from requirements_dev.txt"""
|
||||||
repo_root = _ensure_repo_root(repo_root)
|
requirements_path = Path(__file__).parent.parent / "requirements_dev.txt"
|
||||||
requirements_path = repo_root / "requirements_dev.txt"
|
|
||||||
lines = read_file_lines(requirements_path)
|
lines = read_file_lines(requirements_path)
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
@@ -69,49 +68,30 @@ def read_file_bytes(path: Path) -> bytes:
|
|||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
def get_repo_root() -> Path:
|
def calculate_clang_tidy_hash() -> str:
|
||||||
"""Get the repository root directory."""
|
|
||||||
return Path(__file__).parent.parent
|
|
||||||
|
|
||||||
|
|
||||||
def _ensure_repo_root(repo_root: Path | None) -> Path:
|
|
||||||
"""Ensure repo_root is a Path, using default if None."""
|
|
||||||
return repo_root if repo_root is not None else get_repo_root()
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_clang_tidy_hash(repo_root: Path | None = None) -> str:
|
|
||||||
"""Calculate hash of clang-tidy configuration and version"""
|
"""Calculate hash of clang-tidy configuration and version"""
|
||||||
repo_root = _ensure_repo_root(repo_root)
|
|
||||||
|
|
||||||
hasher = hashlib.sha256()
|
hasher = hashlib.sha256()
|
||||||
|
|
||||||
# Hash .clang-tidy file
|
# Hash .clang-tidy file
|
||||||
clang_tidy_path = repo_root / ".clang-tidy"
|
clang_tidy_path = Path(__file__).parent.parent / ".clang-tidy"
|
||||||
content = read_file_bytes(clang_tidy_path)
|
content = read_file_bytes(clang_tidy_path)
|
||||||
hasher.update(content)
|
hasher.update(content)
|
||||||
|
|
||||||
# Hash clang-tidy version from requirements_dev.txt
|
# Hash clang-tidy version from requirements_dev.txt
|
||||||
version = get_clang_tidy_version_from_requirements(repo_root)
|
version = get_clang_tidy_version_from_requirements()
|
||||||
hasher.update(version.encode())
|
hasher.update(version.encode())
|
||||||
|
|
||||||
# Hash the entire platformio.ini file
|
# Hash the entire platformio.ini file
|
||||||
platformio_path = repo_root / "platformio.ini"
|
platformio_path = Path(__file__).parent.parent / "platformio.ini"
|
||||||
platformio_content = read_file_bytes(platformio_path)
|
platformio_content = read_file_bytes(platformio_path)
|
||||||
hasher.update(platformio_content)
|
hasher.update(platformio_content)
|
||||||
|
|
||||||
# Hash sdkconfig.defaults file
|
|
||||||
sdkconfig_path = repo_root / "sdkconfig.defaults"
|
|
||||||
if sdkconfig_path.exists():
|
|
||||||
sdkconfig_content = read_file_bytes(sdkconfig_path)
|
|
||||||
hasher.update(sdkconfig_content)
|
|
||||||
|
|
||||||
return hasher.hexdigest()
|
return hasher.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def read_stored_hash(repo_root: Path | None = None) -> str | None:
|
def read_stored_hash() -> str | None:
|
||||||
"""Read the stored hash from file"""
|
"""Read the stored hash from file"""
|
||||||
repo_root = _ensure_repo_root(repo_root)
|
hash_file = Path(__file__).parent.parent / ".clang-tidy.hash"
|
||||||
hash_file = repo_root / ".clang-tidy.hash"
|
|
||||||
if hash_file.exists():
|
if hash_file.exists():
|
||||||
lines = read_file_lines(hash_file)
|
lines = read_file_lines(hash_file)
|
||||||
return lines[0].strip() if lines else None
|
return lines[0].strip() if lines else None
|
||||||
@@ -124,10 +104,9 @@ def write_file_content(path: Path, content: str) -> None:
|
|||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
def write_hash(hash_value: str, repo_root: Path | None = None) -> None:
|
def write_hash(hash_value: str) -> None:
|
||||||
"""Write hash to file"""
|
"""Write hash to file"""
|
||||||
repo_root = _ensure_repo_root(repo_root)
|
hash_file = Path(__file__).parent.parent / ".clang-tidy.hash"
|
||||||
hash_file = repo_root / ".clang-tidy.hash"
|
|
||||||
# Strip any trailing newlines to ensure consistent formatting
|
# Strip any trailing newlines to ensure consistent formatting
|
||||||
write_file_content(hash_file, hash_value.strip() + "\n")
|
write_file_content(hash_file, hash_value.strip() + "\n")
|
||||||
|
|
||||||
@@ -155,28 +134,8 @@ def main() -> None:
|
|||||||
stored_hash = read_stored_hash()
|
stored_hash = read_stored_hash()
|
||||||
|
|
||||||
if args.check:
|
if args.check:
|
||||||
# Check if hash changed OR if .clang-tidy.hash was updated in this PR
|
# Exit 0 if full scan needed (hash changed or no hash file)
|
||||||
# This is used in CI to determine if a full clang-tidy scan is needed
|
sys.exit(0 if current_hash != stored_hash else 1)
|
||||||
hash_changed = current_hash != stored_hash
|
|
||||||
|
|
||||||
# Lazy import to avoid requiring dependencies that aren't needed for other modes
|
|
||||||
from helpers import changed_files # noqa: E402
|
|
||||||
|
|
||||||
hash_file_updated = ".clang-tidy.hash" in changed_files()
|
|
||||||
|
|
||||||
# Exit 0 if full scan needed
|
|
||||||
sys.exit(0 if (hash_changed or hash_file_updated) else 1)
|
|
||||||
|
|
||||||
elif args.verify:
|
|
||||||
# Verify that hash file is up to date with current configuration
|
|
||||||
# This is used in pre-commit and CI checks to ensure hash was updated
|
|
||||||
if current_hash != stored_hash:
|
|
||||||
print("ERROR: Clang-tidy configuration has changed but hash not updated!")
|
|
||||||
print(f"Expected: {current_hash}")
|
|
||||||
print(f"Found: {stored_hash}")
|
|
||||||
print("\nPlease run: script/clang_tidy_hash.py --update")
|
|
||||||
sys.exit(1)
|
|
||||||
print("Hash verification passed")
|
|
||||||
|
|
||||||
elif args.update:
|
elif args.update:
|
||||||
write_hash(current_hash)
|
write_hash(current_hash)
|
||||||
@@ -192,6 +151,15 @@ def main() -> None:
|
|||||||
print("Clang-tidy hash unchanged")
|
print("Clang-tidy hash unchanged")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
elif args.verify:
|
||||||
|
if current_hash != stored_hash:
|
||||||
|
print("ERROR: Clang-tidy configuration has changed but hash not updated!")
|
||||||
|
print(f"Expected: {current_hash}")
|
||||||
|
print(f"Found: {stored_hash}")
|
||||||
|
print("\nPlease run: script/clang_tidy_hash.py --update")
|
||||||
|
sys.exit(1)
|
||||||
|
print("Hash verification passed")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(f"Current hash: {current_hash}")
|
print(f"Current hash: {current_hash}")
|
||||||
print(f"Stored hash: {stored_hash}")
|
print(f"Stored hash: {stored_hash}")
|
||||||
|
@@ -529,16 +529,7 @@ def get_all_dependencies(component_names: set[str]) -> set[str]:
|
|||||||
new_components.update(dep.split(".")[0] for dep in comp.dependencies)
|
new_components.update(dep.split(".")[0] for dep in comp.dependencies)
|
||||||
|
|
||||||
# Add auto_load components
|
# Add auto_load components
|
||||||
auto_load = comp.auto_load
|
new_components.update(comp.auto_load)
|
||||||
if callable(auto_load):
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
if inspect.signature(auto_load).parameters:
|
|
||||||
auto_load = auto_load(None)
|
|
||||||
else:
|
|
||||||
auto_load = auto_load()
|
|
||||||
|
|
||||||
new_components.update(auto_load)
|
|
||||||
|
|
||||||
# Check if we found any new components
|
# Check if we found any new components
|
||||||
new_components -= all_components
|
new_components -= all_components
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import argparse
|
import argparse
|
||||||
from collections.abc import Callable
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -14,7 +13,7 @@ from esphome.const import (
|
|||||||
PLATFORM_ESP8266,
|
PLATFORM_ESP8266,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
from esphome.loader import ComponentManifest, get_component, get_platform
|
from esphome.loader import get_component, get_platform
|
||||||
|
|
||||||
|
|
||||||
def filter_component_files(str):
|
def filter_component_files(str):
|
||||||
@@ -46,29 +45,6 @@ def add_item_to_components_graph(components_graph, parent, child):
|
|||||||
components_graph[parent].append(child)
|
components_graph[parent].append(child)
|
||||||
|
|
||||||
|
|
||||||
def resolve_auto_load(
|
|
||||||
auto_load: list[str] | Callable[[], list[str]] | Callable[[dict | None], list[str]],
|
|
||||||
config: dict | None = None,
|
|
||||||
) -> list[str]:
|
|
||||||
"""Resolve AUTO_LOAD to a list, handling callables with or without config parameter.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
auto_load: The AUTO_LOAD value (list or callable)
|
|
||||||
config: Optional config to pass to callable AUTO_LOAD functions
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of component names to auto-load
|
|
||||||
"""
|
|
||||||
if not callable(auto_load):
|
|
||||||
return auto_load
|
|
||||||
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
if inspect.signature(auto_load).parameters:
|
|
||||||
return auto_load(config)
|
|
||||||
return auto_load()
|
|
||||||
|
|
||||||
|
|
||||||
def create_components_graph():
|
def create_components_graph():
|
||||||
# The root directory of the repo
|
# The root directory of the repo
|
||||||
root = Path(__file__).parent.parent
|
root = Path(__file__).parent.parent
|
||||||
@@ -87,7 +63,7 @@ def create_components_graph():
|
|||||||
|
|
||||||
components_graph = {}
|
components_graph = {}
|
||||||
platforms = []
|
platforms = []
|
||||||
components: list[tuple[ComponentManifest, str, Path]] = []
|
components = []
|
||||||
|
|
||||||
for path in components_dir.iterdir():
|
for path in components_dir.iterdir():
|
||||||
if not path.is_dir():
|
if not path.is_dir():
|
||||||
@@ -116,8 +92,8 @@ def create_components_graph():
|
|||||||
|
|
||||||
for target_config in TARGET_CONFIGURATIONS:
|
for target_config in TARGET_CONFIGURATIONS:
|
||||||
CORE.data[KEY_CORE] = target_config
|
CORE.data[KEY_CORE] = target_config
|
||||||
for item in resolve_auto_load(comp.auto_load, config=None):
|
for auto_load in comp.auto_load:
|
||||||
add_item_to_components_graph(components_graph, item, name)
|
add_item_to_components_graph(components_graph, auto_load, name)
|
||||||
# restore config
|
# restore config
|
||||||
CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0]
|
CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0]
|
||||||
|
|
||||||
@@ -138,8 +114,8 @@ def create_components_graph():
|
|||||||
|
|
||||||
for target_config in TARGET_CONFIGURATIONS:
|
for target_config in TARGET_CONFIGURATIONS:
|
||||||
CORE.data[KEY_CORE] = target_config
|
CORE.data[KEY_CORE] = target_config
|
||||||
for item in resolve_auto_load(platform.auto_load, config={}):
|
for auto_load in platform.auto_load:
|
||||||
add_item_to_components_graph(components_graph, item, name)
|
add_item_to_components_graph(components_graph, auto_load, name)
|
||||||
# restore config
|
# restore config
|
||||||
CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0]
|
CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0]
|
||||||
|
|
||||||
|
@@ -13,7 +13,6 @@ CONFIG_ESP_TASK_WDT=y
|
|||||||
CONFIG_ESP_TASK_WDT_PANIC=y
|
CONFIG_ESP_TASK_WDT_PANIC=y
|
||||||
CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n
|
CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n
|
||||||
CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n
|
CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n
|
||||||
CONFIG_AUTOSTART_ARDUINO=y
|
|
||||||
|
|
||||||
# esp32_ble
|
# esp32_ble
|
||||||
CONFIG_BT_ENABLED=y
|
CONFIG_BT_ENABLED=y
|
||||||
|
@@ -10,39 +10,6 @@ esphome:
|
|||||||
data:
|
data:
|
||||||
message: Button was pressed
|
message: Button was pressed
|
||||||
- homeassistant.tag_scanned: pulse
|
- homeassistant.tag_scanned: pulse
|
||||||
- homeassistant.action:
|
|
||||||
action: weather.get_forecasts
|
|
||||||
data:
|
|
||||||
entity_id: weather.forecast_home
|
|
||||||
type: hourly
|
|
||||||
capture_response: true
|
|
||||||
on_success:
|
|
||||||
- lambda: |-
|
|
||||||
JsonObject next_hour = response["response"]["weather.forecast_home"]["forecast"][0];
|
|
||||||
float next_temperature = next_hour["temperature"].as<float>();
|
|
||||||
ESP_LOGD("main", "Next hour temperature: %f", next_temperature);
|
|
||||||
on_error:
|
|
||||||
- lambda: |-
|
|
||||||
ESP_LOGE("main", "Action failed with error: %s", error.c_str());
|
|
||||||
- homeassistant.action:
|
|
||||||
action: weather.get_forecasts
|
|
||||||
data:
|
|
||||||
entity_id: weather.forecast_home
|
|
||||||
type: hourly
|
|
||||||
capture_response: true
|
|
||||||
response_template: "{{ response['weather.forecast_home']['forecast'][0]['temperature'] }}"
|
|
||||||
on_success:
|
|
||||||
- lambda: |-
|
|
||||||
float temperature = response["response"].as<float>();
|
|
||||||
ESP_LOGD("main", "Next hour temperature: %f", temperature);
|
|
||||||
- homeassistant.action:
|
|
||||||
action: light.toggle
|
|
||||||
data:
|
|
||||||
entity_id: light.demo_light
|
|
||||||
on_success:
|
|
||||||
- logger.log: "Toggled demo light"
|
|
||||||
on_error:
|
|
||||||
- logger.log: "Failed to toggle demo light"
|
|
||||||
|
|
||||||
api:
|
api:
|
||||||
port: 8000
|
port: 8000
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
substitutions:
|
substitutions:
|
||||||
pin: GPIO2
|
pin: GPIO2
|
||||||
clock_resolution: "2000000"
|
clock_resolution: "2000000"
|
||||||
carrier_duty_percent: "25"
|
|
||||||
carrier_frequency: "30000"
|
|
||||||
filter_symbols: "2"
|
filter_symbols: "2"
|
||||||
receive_symbols: "4"
|
receive_symbols: "4"
|
||||||
rmt_symbols: "64"
|
rmt_symbols: "64"
|
||||||
|
@@ -44,53 +44,37 @@ def test_get_clang_tidy_version_from_requirements(
|
|||||||
assert result == expected
|
assert result == expected
|
||||||
|
|
||||||
|
|
||||||
def test_calculate_clang_tidy_hash_with_sdkconfig(tmp_path: Path) -> None:
|
def test_calculate_clang_tidy_hash() -> None:
|
||||||
"""Test calculating hash from all configuration sources including sdkconfig.defaults."""
|
"""Test calculating hash from all configuration sources."""
|
||||||
clang_tidy_content = b"Checks: '-*,readability-*'\n"
|
clang_tidy_content = b"Checks: '-*,readability-*'\n"
|
||||||
requirements_version = "clang-tidy==18.1.5"
|
requirements_version = "clang-tidy==18.1.5"
|
||||||
platformio_content = b"[env:esp32]\nplatform = espressif32\n"
|
platformio_content = b"[env:esp32]\nplatform = espressif32\n"
|
||||||
sdkconfig_content = b"CONFIG_AUTOSTART_ARDUINO=y\n"
|
|
||||||
requirements_content = "clang-tidy==18.1.5\n"
|
|
||||||
|
|
||||||
# Create temporary files
|
|
||||||
(tmp_path / ".clang-tidy").write_bytes(clang_tidy_content)
|
|
||||||
(tmp_path / "platformio.ini").write_bytes(platformio_content)
|
|
||||||
(tmp_path / "sdkconfig.defaults").write_bytes(sdkconfig_content)
|
|
||||||
(tmp_path / "requirements_dev.txt").write_text(requirements_content)
|
|
||||||
|
|
||||||
# Expected hash calculation
|
# Expected hash calculation
|
||||||
expected_hasher = hashlib.sha256()
|
expected_hasher = hashlib.sha256()
|
||||||
expected_hasher.update(clang_tidy_content)
|
expected_hasher.update(clang_tidy_content)
|
||||||
expected_hasher.update(requirements_version.encode())
|
expected_hasher.update(requirements_version.encode())
|
||||||
expected_hasher.update(platformio_content)
|
expected_hasher.update(platformio_content)
|
||||||
expected_hasher.update(sdkconfig_content)
|
|
||||||
expected_hash = expected_hasher.hexdigest()
|
expected_hash = expected_hasher.hexdigest()
|
||||||
|
|
||||||
result = clang_tidy_hash.calculate_clang_tidy_hash(repo_root=tmp_path)
|
# Mock the dependencies
|
||||||
|
with (
|
||||||
|
patch("clang_tidy_hash.read_file_bytes") as mock_read_bytes,
|
||||||
|
patch(
|
||||||
|
"clang_tidy_hash.get_clang_tidy_version_from_requirements",
|
||||||
|
return_value=requirements_version,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
# Set up mock to return different content based on the file being read
|
||||||
|
def read_file_mock(path: Path) -> bytes:
|
||||||
|
if ".clang-tidy" in str(path):
|
||||||
|
return clang_tidy_content
|
||||||
|
if "platformio.ini" in str(path):
|
||||||
|
return platformio_content
|
||||||
|
return b""
|
||||||
|
|
||||||
assert result == expected_hash
|
mock_read_bytes.side_effect = read_file_mock
|
||||||
|
result = clang_tidy_hash.calculate_clang_tidy_hash()
|
||||||
|
|
||||||
def test_calculate_clang_tidy_hash_without_sdkconfig(tmp_path: Path) -> None:
|
|
||||||
"""Test calculating hash without sdkconfig.defaults file."""
|
|
||||||
clang_tidy_content = b"Checks: '-*,readability-*'\n"
|
|
||||||
requirements_version = "clang-tidy==18.1.5"
|
|
||||||
platformio_content = b"[env:esp32]\nplatform = espressif32\n"
|
|
||||||
requirements_content = "clang-tidy==18.1.5\n"
|
|
||||||
|
|
||||||
# Create temporary files (without sdkconfig.defaults)
|
|
||||||
(tmp_path / ".clang-tidy").write_bytes(clang_tidy_content)
|
|
||||||
(tmp_path / "platformio.ini").write_bytes(platformio_content)
|
|
||||||
(tmp_path / "requirements_dev.txt").write_text(requirements_content)
|
|
||||||
|
|
||||||
# Expected hash calculation (no sdkconfig)
|
|
||||||
expected_hasher = hashlib.sha256()
|
|
||||||
expected_hasher.update(clang_tidy_content)
|
|
||||||
expected_hasher.update(requirements_version.encode())
|
|
||||||
expected_hasher.update(platformio_content)
|
|
||||||
expected_hash = expected_hasher.hexdigest()
|
|
||||||
|
|
||||||
result = clang_tidy_hash.calculate_clang_tidy_hash(repo_root=tmp_path)
|
|
||||||
|
|
||||||
assert result == expected_hash
|
assert result == expected_hash
|
||||||
|
|
||||||
@@ -101,63 +85,67 @@ def test_read_stored_hash_exists(tmp_path: Path) -> None:
|
|||||||
hash_file = tmp_path / ".clang-tidy.hash"
|
hash_file = tmp_path / ".clang-tidy.hash"
|
||||||
hash_file.write_text(f"{stored_hash}\n")
|
hash_file.write_text(f"{stored_hash}\n")
|
||||||
|
|
||||||
result = clang_tidy_hash.read_stored_hash(repo_root=tmp_path)
|
with (
|
||||||
|
patch("clang_tidy_hash.Path") as mock_path_class,
|
||||||
|
patch("clang_tidy_hash.read_file_lines", return_value=[f"{stored_hash}\n"]),
|
||||||
|
):
|
||||||
|
# Mock the path calculation and exists check
|
||||||
|
mock_hash_file = Mock()
|
||||||
|
mock_hash_file.exists.return_value = True
|
||||||
|
mock_path_class.return_value.parent.parent.__truediv__.return_value = (
|
||||||
|
mock_hash_file
|
||||||
|
)
|
||||||
|
|
||||||
|
result = clang_tidy_hash.read_stored_hash()
|
||||||
|
|
||||||
assert result == stored_hash
|
assert result == stored_hash
|
||||||
|
|
||||||
|
|
||||||
def test_read_stored_hash_not_exists(tmp_path: Path) -> None:
|
def test_read_stored_hash_not_exists() -> None:
|
||||||
"""Test reading hash when file doesn't exist."""
|
"""Test reading hash when file doesn't exist."""
|
||||||
result = clang_tidy_hash.read_stored_hash(repo_root=tmp_path)
|
with patch("clang_tidy_hash.Path") as mock_path_class:
|
||||||
|
# Mock the path calculation and exists check
|
||||||
|
mock_hash_file = Mock()
|
||||||
|
mock_hash_file.exists.return_value = False
|
||||||
|
mock_path_class.return_value.parent.parent.__truediv__.return_value = (
|
||||||
|
mock_hash_file
|
||||||
|
)
|
||||||
|
|
||||||
|
result = clang_tidy_hash.read_stored_hash()
|
||||||
|
|
||||||
assert result is None
|
assert result is None
|
||||||
|
|
||||||
|
|
||||||
def test_write_hash(tmp_path: Path) -> None:
|
def test_write_hash() -> None:
|
||||||
"""Test writing hash to file."""
|
"""Test writing hash to file."""
|
||||||
hash_value = "abc123def456"
|
hash_value = "abc123def456"
|
||||||
hash_file = tmp_path / ".clang-tidy.hash"
|
|
||||||
|
|
||||||
clang_tidy_hash.write_hash(hash_value, repo_root=tmp_path)
|
with patch("clang_tidy_hash.write_file_content") as mock_write:
|
||||||
|
clang_tidy_hash.write_hash(hash_value)
|
||||||
|
|
||||||
assert hash_file.exists()
|
# Verify write_file_content was called with correct parameters
|
||||||
assert hash_file.read_text() == hash_value.strip() + "\n"
|
mock_write.assert_called_once()
|
||||||
|
args = mock_write.call_args[0]
|
||||||
|
assert str(args[0]).endswith(".clang-tidy.hash")
|
||||||
|
assert args[1] == hash_value.strip() + "\n"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("args", "current_hash", "stored_hash", "hash_file_in_changed", "expected_exit"),
|
("args", "current_hash", "stored_hash", "expected_exit"),
|
||||||
[
|
[
|
||||||
(["--check"], "abc123", "abc123", False, 1), # Hashes match, no scan needed
|
(["--check"], "abc123", "abc123", 1), # Hashes match, no scan needed
|
||||||
(["--check"], "abc123", "def456", False, 0), # Hashes differ, scan needed
|
(["--check"], "abc123", "def456", 0), # Hashes differ, scan needed
|
||||||
(["--check"], "abc123", None, False, 0), # No stored hash, scan needed
|
(["--check"], "abc123", None, 0), # No stored hash, scan needed
|
||||||
(
|
|
||||||
["--check"],
|
|
||||||
"abc123",
|
|
||||||
"abc123",
|
|
||||||
True,
|
|
||||||
0,
|
|
||||||
), # Hash file updated in PR, scan needed
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_main_check_mode(
|
def test_main_check_mode(
|
||||||
args: list[str],
|
args: list[str], current_hash: str, stored_hash: str | None, expected_exit: int
|
||||||
current_hash: str,
|
|
||||||
stored_hash: str | None,
|
|
||||||
hash_file_in_changed: bool,
|
|
||||||
expected_exit: int,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test main function in check mode."""
|
"""Test main function in check mode."""
|
||||||
changed = [".clang-tidy.hash"] if hash_file_in_changed else []
|
|
||||||
|
|
||||||
# Create a mock module that can be imported
|
|
||||||
mock_helpers = Mock()
|
|
||||||
mock_helpers.changed_files = Mock(return_value=changed)
|
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch("sys.argv", ["clang_tidy_hash.py"] + args),
|
patch("sys.argv", ["clang_tidy_hash.py"] + args),
|
||||||
patch("clang_tidy_hash.calculate_clang_tidy_hash", return_value=current_hash),
|
patch("clang_tidy_hash.calculate_clang_tidy_hash", return_value=current_hash),
|
||||||
patch("clang_tidy_hash.read_stored_hash", return_value=stored_hash),
|
patch("clang_tidy_hash.read_stored_hash", return_value=stored_hash),
|
||||||
patch.dict("sys.modules", {"helpers": mock_helpers}),
|
|
||||||
pytest.raises(SystemExit) as exc_info,
|
pytest.raises(SystemExit) as exc_info,
|
||||||
):
|
):
|
||||||
clang_tidy_hash.main()
|
clang_tidy_hash.main()
|
||||||
|
@@ -101,10 +101,3 @@ def mock_get_idedata() -> Generator[Mock, None, None]:
|
|||||||
"""Mock get_idedata for platformio_api."""
|
"""Mock get_idedata for platformio_api."""
|
||||||
with patch("esphome.platformio_api.get_idedata") as mock:
|
with patch("esphome.platformio_api.get_idedata") as mock:
|
||||||
yield mock
|
yield mock
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def mock_get_component() -> Generator[Mock, None, None]:
|
|
||||||
"""Mock get_component for config module."""
|
|
||||||
with patch("esphome.config.get_component") as mock:
|
|
||||||
yield mock
|
|
||||||
|
@@ -1,10 +0,0 @@
|
|||||||
esphome:
|
|
||||||
name: test-device
|
|
||||||
|
|
||||||
esp32:
|
|
||||||
board: esp32dev
|
|
||||||
|
|
||||||
# Test component with dynamic AUTO_LOAD
|
|
||||||
test_component:
|
|
||||||
enable_logger: true
|
|
||||||
enable_api: false
|
|
@@ -1,8 +0,0 @@
|
|||||||
esphome:
|
|
||||||
name: test-device
|
|
||||||
|
|
||||||
esp32:
|
|
||||||
board: esp32dev
|
|
||||||
|
|
||||||
# Test component with static AUTO_LOAD
|
|
||||||
test_component:
|
|
@@ -1,131 +0,0 @@
|
|||||||
"""Tests for AUTO_LOAD functionality including dynamic AUTO_LOAD."""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
|
||||||
from unittest.mock import Mock
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from esphome import config, config_validation as cv, yaml_util
|
|
||||||
from esphome.core import CORE
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def fixtures_dir() -> Path:
|
|
||||||
"""Get the fixtures directory."""
|
|
||||||
return Path(__file__).parent / "fixtures"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def default_component() -> Mock:
|
|
||||||
"""Create a default mock component for unmocked components."""
|
|
||||||
return Mock(
|
|
||||||
auto_load=[],
|
|
||||||
is_platform_component=False,
|
|
||||||
is_platform=False,
|
|
||||||
multi_conf=False,
|
|
||||||
multi_conf_no_default=False,
|
|
||||||
dependencies=[],
|
|
||||||
conflicts_with=[],
|
|
||||||
config_schema=cv.Schema({}, extra=cv.ALLOW_EXTRA),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def static_auto_load_component() -> Mock:
|
|
||||||
"""Create a mock component with static AUTO_LOAD."""
|
|
||||||
return Mock(
|
|
||||||
auto_load=["logger"],
|
|
||||||
is_platform_component=False,
|
|
||||||
is_platform=False,
|
|
||||||
multi_conf=False,
|
|
||||||
multi_conf_no_default=False,
|
|
||||||
dependencies=[],
|
|
||||||
conflicts_with=[],
|
|
||||||
config_schema=cv.Schema({}, extra=cv.ALLOW_EXTRA),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_static_auto_load_adds_components(
|
|
||||||
mock_get_component: Mock,
|
|
||||||
fixtures_dir: Path,
|
|
||||||
static_auto_load_component: Mock,
|
|
||||||
default_component: Mock,
|
|
||||||
) -> None:
|
|
||||||
"""Test that static AUTO_LOAD triggers loading of specified components."""
|
|
||||||
CORE.config_path = fixtures_dir / "auto_load_static.yaml"
|
|
||||||
|
|
||||||
config_file = fixtures_dir / "auto_load_static.yaml"
|
|
||||||
raw_config = yaml_util.load_yaml(config_file)
|
|
||||||
|
|
||||||
component_mocks = {"test_component": static_auto_load_component}
|
|
||||||
mock_get_component.side_effect = lambda name: component_mocks.get(
|
|
||||||
name, default_component
|
|
||||||
)
|
|
||||||
|
|
||||||
result = config.validate_config(raw_config, {})
|
|
||||||
|
|
||||||
# Check for validation errors
|
|
||||||
assert not result.errors, f"Validation errors: {result.errors}"
|
|
||||||
|
|
||||||
# Logger should have been auto-loaded by test_component
|
|
||||||
assert "logger" in result
|
|
||||||
assert "test_component" in result
|
|
||||||
|
|
||||||
|
|
||||||
def test_dynamic_auto_load_with_config_param(
|
|
||||||
mock_get_component: Mock,
|
|
||||||
fixtures_dir: Path,
|
|
||||||
default_component: Mock,
|
|
||||||
) -> None:
|
|
||||||
"""Test that dynamic AUTO_LOAD evaluates based on configuration."""
|
|
||||||
CORE.config_path = fixtures_dir / "auto_load_dynamic.yaml"
|
|
||||||
|
|
||||||
config_file = fixtures_dir / "auto_load_dynamic.yaml"
|
|
||||||
raw_config = yaml_util.load_yaml(config_file)
|
|
||||||
|
|
||||||
# Track if auto_load was called with config
|
|
||||||
auto_load_calls = []
|
|
||||||
|
|
||||||
def dynamic_auto_load(conf: dict[str, Any]) -> list[str]:
|
|
||||||
"""Dynamically load components based on config."""
|
|
||||||
auto_load_calls.append(conf)
|
|
||||||
component_map = {
|
|
||||||
"enable_logger": "logger",
|
|
||||||
"enable_api": "api",
|
|
||||||
}
|
|
||||||
return [comp for key, comp in component_map.items() if conf.get(key)]
|
|
||||||
|
|
||||||
dynamic_component = Mock(
|
|
||||||
auto_load=dynamic_auto_load,
|
|
||||||
is_platform_component=False,
|
|
||||||
is_platform=False,
|
|
||||||
multi_conf=False,
|
|
||||||
multi_conf_no_default=False,
|
|
||||||
dependencies=[],
|
|
||||||
conflicts_with=[],
|
|
||||||
config_schema=cv.Schema({}, extra=cv.ALLOW_EXTRA),
|
|
||||||
)
|
|
||||||
|
|
||||||
component_mocks = {"test_component": dynamic_component}
|
|
||||||
mock_get_component.side_effect = lambda name: component_mocks.get(
|
|
||||||
name, default_component
|
|
||||||
)
|
|
||||||
|
|
||||||
result = config.validate_config(raw_config, {})
|
|
||||||
|
|
||||||
# Check for validation errors
|
|
||||||
assert not result.errors, f"Validation errors: {result.errors}"
|
|
||||||
|
|
||||||
# Verify auto_load was called with the validated config
|
|
||||||
assert len(auto_load_calls) == 1, "auto_load should be called exactly once"
|
|
||||||
assert auto_load_calls[0].get("enable_logger") is True
|
|
||||||
assert auto_load_calls[0].get("enable_api") is False
|
|
||||||
|
|
||||||
# Only logger should be auto-loaded (enable_logger=true in YAML)
|
|
||||||
assert "logger" in result, (
|
|
||||||
f"Logger not found in result. Result keys: {list(result.keys())}"
|
|
||||||
)
|
|
||||||
# API should NOT be auto-loaded (enable_api=false in YAML)
|
|
||||||
assert "api" not in result
|
|
||||||
assert "test_component" in result
|
|
@@ -10,6 +10,13 @@ from esphome import config, yaml_util
|
|||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_get_component() -> Generator[Mock, None, None]:
|
||||||
|
"""Fixture for mocking get_component."""
|
||||||
|
with patch("esphome.config.get_component") as mock_get_component:
|
||||||
|
yield mock_get_component
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_get_platform() -> Generator[Mock, None, None]:
|
def mock_get_platform() -> Generator[Mock, None, None]:
|
||||||
"""Fixture for mocking get_platform."""
|
"""Fixture for mocking get_platform."""
|
||||||
|
Reference in New Issue
Block a user