mirror of
https://github.com/esphome/esphome.git
synced 2025-10-12 21:28:40 +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"
|
||||
- "platformio.ini"
|
||||
- "requirements_dev.txt"
|
||||
- "sdkconfig.defaults"
|
||||
- ".clang-tidy.hash"
|
||||
- "script/clang_tidy_hash.py"
|
||||
- ".github/workflows/ci-clang-tidy-hash.yml"
|
||||
|
@@ -9,7 +9,6 @@ import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ACTION,
|
||||
CONF_ACTIONS,
|
||||
CONF_CAPTURE_RESPONSE,
|
||||
CONF_DATA,
|
||||
CONF_DATA_TEMPLATE,
|
||||
CONF_EVENT,
|
||||
@@ -18,50 +17,30 @@ from esphome.const import (
|
||||
CONF_MAX_CONNECTIONS,
|
||||
CONF_ON_CLIENT_CONNECTED,
|
||||
CONF_ON_CLIENT_DISCONNECTED,
|
||||
CONF_ON_ERROR,
|
||||
CONF_ON_SUCCESS,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_REBOOT_TIMEOUT,
|
||||
CONF_RESPONSE_TEMPLATE,
|
||||
CONF_SERVICE,
|
||||
CONF_SERVICES,
|
||||
CONF_TAG,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_VARIABLES,
|
||||
)
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.cpp_generator import TemplateArgsType
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "api"
|
||||
DEPENDENCIES = ["network"]
|
||||
AUTO_LOAD = ["socket"]
|
||||
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")
|
||||
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
|
||||
HomeAssistantServiceCallAction = api_ns.class_(
|
||||
"HomeAssistantServiceCallAction", automation.Action
|
||||
)
|
||||
ActionResponse = api_ns.class_("ActionResponse")
|
||||
HomeAssistantActionResponseTrigger = api_ns.class_(
|
||||
"HomeAssistantActionResponseTrigger", automation.Trigger
|
||||
)
|
||||
APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition)
|
||||
|
||||
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)})
|
||||
|
||||
|
||||
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(
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -347,15 +303,10 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
|
||||
{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.rename_key(CONF_SERVICE, CONF_ACTION),
|
||||
_validate_response_config,
|
||||
)
|
||||
|
||||
|
||||
@@ -369,12 +320,7 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
||||
HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
|
||||
)
|
||||
async def homeassistant_service_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
):
|
||||
async def homeassistant_service_to_code(config, action_id, template_arg, args):
|
||||
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
|
||||
serv = await cg.get_variable(config[CONF_ID])
|
||||
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():
|
||||
templ = await cg.templatable(value, args, None)
|
||||
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
|
||||
|
||||
|
||||
|
@@ -780,22 +780,6 @@ message HomeassistantActionRequest {
|
||||
repeated HomeassistantServiceMap data_template = 3;
|
||||
repeated HomeassistantServiceMap variables = 4;
|
||||
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 ====================
|
||||
|
@@ -8,9 +8,9 @@
|
||||
#endif
|
||||
#include <cerrno>
|
||||
#include <cinttypes>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
@@ -1549,20 +1549,6 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
||||
}
|
||||
}
|
||||
#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
|
||||
bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) {
|
||||
NoiseEncryptionSetKeyResponse resp;
|
||||
|
@@ -129,10 +129,7 @@ class APIConnection final : public APIServerConnection {
|
||||
return;
|
||||
this->send_message(call, HomeassistantActionRequest::MESSAGE_TYPE);
|
||||
}
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
void on_homeassistant_action_response(const HomeassistantActionResponse &msg) override;
|
||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
#endif // USE_API_HOMEASSISTANT_SERVICES
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||
void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||
|
@@ -132,22 +132,24 @@ APIError APINoiseFrameHelper::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.
|
||||
* Caller is responsible for consuming rx_buf_ (e.g., via std::move).
|
||||
* @param frame: The struct to hold the frame information in.
|
||||
* 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 ENOMEM: Not enough memory for reading packet.
|
||||
* errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
||||
* errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
|
||||
*/
|
||||
APIError APINoiseFrameHelper::try_read_frame_() {
|
||||
// Clear buffer when starting a new frame (rx_buf_len_ == 0 means not resuming after WOULD_BLOCK)
|
||||
if (this->rx_buf_len_ == 0) {
|
||||
this->rx_buf_.clear();
|
||||
APIError APINoiseFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
||||
if (frame == nullptr) {
|
||||
HELPER_LOG("Bad argument for try_read_frame_");
|
||||
return APIError::BAD_ARG;
|
||||
}
|
||||
|
||||
// read header
|
||||
@@ -190,9 +192,9 @@ APIError APINoiseFrameHelper::try_read_frame_() {
|
||||
return APIError::BAD_DATA_PACKET;
|
||||
}
|
||||
|
||||
// Reserve space for body
|
||||
if (this->rx_buf_.size() != msg_size) {
|
||||
this->rx_buf_.resize(msg_size);
|
||||
// reserve space for body
|
||||
if (rx_buf_.size() != msg_size) {
|
||||
rx_buf_.resize(msg_size);
|
||||
}
|
||||
|
||||
if (rx_buf_len_ < msg_size) {
|
||||
@@ -210,12 +212,12 @@ APIError APINoiseFrameHelper::try_read_frame_() {
|
||||
}
|
||||
}
|
||||
|
||||
LOG_PACKET_RECEIVED(this->rx_buf_);
|
||||
|
||||
// Clear state for next frame (rx_buf_ still contains data for caller)
|
||||
this->rx_buf_len_ = 0;
|
||||
this->rx_header_buf_len_ = 0;
|
||||
|
||||
LOG_PACKET_RECEIVED(rx_buf_);
|
||||
*frame = std::move(rx_buf_);
|
||||
// consume msg
|
||||
rx_buf_ = {};
|
||||
rx_buf_len_ = 0;
|
||||
rx_header_buf_len_ = 0;
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
@@ -237,17 +239,18 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
}
|
||||
if (state_ == State::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) {
|
||||
return handle_handshake_frame_error_(aerr);
|
||||
}
|
||||
// ignore contents, may be used in future for flags
|
||||
// Resize for: existing prologue + 2 size bytes + frame data
|
||||
size_t old_size = this->prologue_.size();
|
||||
this->prologue_.resize(old_size + 2 + this->rx_buf_.size());
|
||||
this->prologue_[old_size] = (uint8_t) (this->rx_buf_.size() >> 8);
|
||||
this->prologue_[old_size + 1] = (uint8_t) this->rx_buf_.size();
|
||||
std::memcpy(this->prologue_.data() + old_size + 2, this->rx_buf_.data(), this->rx_buf_.size());
|
||||
size_t old_size = prologue_.size();
|
||||
prologue_.resize(old_size + 2 + frame.size());
|
||||
prologue_[old_size] = (uint8_t) (frame.size() >> 8);
|
||||
prologue_[old_size + 1] = (uint8_t) frame.size();
|
||||
std::memcpy(prologue_.data() + old_size + 2, frame.data(), frame.size());
|
||||
|
||||
state_ = State::SERVER_HELLO;
|
||||
}
|
||||
@@ -289,23 +292,24 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
int action = noise_handshakestate_get_action(handshake_);
|
||||
if (action == NOISE_ACTION_READ_MESSAGE) {
|
||||
// waiting for handshake msg
|
||||
aerr = this->try_read_frame_();
|
||||
std::vector<uint8_t> frame;
|
||||
aerr = try_read_frame_(&frame);
|
||||
if (aerr != APIError::OK) {
|
||||
return handle_handshake_frame_error_(aerr);
|
||||
}
|
||||
|
||||
if (this->rx_buf_.empty()) {
|
||||
if (frame.empty()) {
|
||||
send_explicit_handshake_reject_(LOG_STR("Empty handshake message"));
|
||||
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||
} else if (this->rx_buf_[0] != 0x00) {
|
||||
HELPER_LOG("Bad handshake error byte: %u", this->rx_buf_[0]);
|
||||
} else if (frame[0] != 0x00) {
|
||||
HELPER_LOG("Bad handshake error byte: %u", frame[0]);
|
||||
send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte"));
|
||||
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||
}
|
||||
|
||||
NoiseBuffer 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);
|
||||
if (err != 0) {
|
||||
// Special handling for MAC failure
|
||||
@@ -382,33 +386,35 @@ void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reaso
|
||||
state_ = orig_state;
|
||||
}
|
||||
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
APIError aerr = this->state_action_();
|
||||
int err;
|
||||
APIError aerr;
|
||||
aerr = state_action_();
|
||||
if (aerr != APIError::OK) {
|
||||
return aerr;
|
||||
}
|
||||
|
||||
if (this->state_ != State::DATA) {
|
||||
if (state_ != State::DATA) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
|
||||
aerr = this->try_read_frame_();
|
||||
std::vector<uint8_t> frame;
|
||||
aerr = try_read_frame_(&frame);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
NoiseBuffer mbuf;
|
||||
noise_buffer_init(mbuf);
|
||||
noise_buffer_set_inout(mbuf, this->rx_buf_.data(), this->rx_buf_.size(), this->rx_buf_.size());
|
||||
int err = noise_cipherstate_decrypt(this->recv_cipher_, &mbuf);
|
||||
noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size());
|
||||
err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
|
||||
APIError decrypt_err =
|
||||
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;
|
||||
}
|
||||
|
||||
uint16_t msg_size = mbuf.size;
|
||||
uint8_t *msg_data = this->rx_buf_.data();
|
||||
uint8_t *msg_data = frame.data();
|
||||
if (msg_size < 4) {
|
||||
this->state_ = State::FAILED;
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Bad data packet: size %d too short", msg_size);
|
||||
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 data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
|
||||
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);
|
||||
return APIError::BAD_DATA_PACKET;
|
||||
}
|
||||
|
||||
buffer->container = std::move(this->rx_buf_);
|
||||
buffer->container = std::move(frame);
|
||||
buffer->data_offset = 4;
|
||||
buffer->data_len = data_len;
|
||||
buffer->type = type;
|
||||
|
@@ -28,7 +28,7 @@ class APINoiseFrameHelper final : public APIFrameHelper {
|
||||
|
||||
protected:
|
||||
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 init_handshake_();
|
||||
APIError check_handshake_finished_();
|
||||
|
@@ -47,19 +47,19 @@ APIError APIPlaintextFrameHelper::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.
|
||||
* Caller is responsible for consuming rx_buf_ (e.g., via std::move).
|
||||
* @param frame: The struct to hold the frame information in.
|
||||
* msg: store the parsed frame in that struct
|
||||
*
|
||||
* @return See APIError
|
||||
*
|
||||
* error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
||||
*/
|
||||
APIError APIPlaintextFrameHelper::try_read_frame_() {
|
||||
// Clear buffer when starting a new frame (rx_buf_len_ == 0 means not resuming after WOULD_BLOCK)
|
||||
if (this->rx_buf_len_ == 0) {
|
||||
this->rx_buf_.clear();
|
||||
APIError APIPlaintextFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
||||
if (frame == nullptr) {
|
||||
HELPER_LOG("Bad argument for try_read_frame_");
|
||||
return APIError::BAD_ARG;
|
||||
}
|
||||
|
||||
// read header
|
||||
@@ -150,9 +150,9 @@ APIError APIPlaintextFrameHelper::try_read_frame_() {
|
||||
}
|
||||
// header reading done
|
||||
|
||||
// Reserve space for body
|
||||
if (this->rx_buf_.size() != this->rx_header_parsed_len_) {
|
||||
this->rx_buf_.resize(this->rx_header_parsed_len_);
|
||||
// reserve space for body
|
||||
if (rx_buf_.size() != rx_header_parsed_len_) {
|
||||
rx_buf_.resize(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_);
|
||||
|
||||
// Clear state for next frame (rx_buf_ still contains data for caller)
|
||||
this->rx_buf_len_ = 0;
|
||||
this->rx_header_buf_pos_ = 0;
|
||||
this->rx_header_parsed_ = false;
|
||||
|
||||
LOG_PACKET_RECEIVED(rx_buf_);
|
||||
*frame = std::move(rx_buf_);
|
||||
// consume msg
|
||||
rx_buf_ = {};
|
||||
rx_buf_len_ = 0;
|
||||
rx_header_buf_pos_ = 0;
|
||||
rx_header_parsed_ = false;
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
if (this->state_ != State::DATA) {
|
||||
APIError aerr;
|
||||
|
||||
if (state_ != State::DATA) {
|
||||
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::BAD_INDICATOR) {
|
||||
// Make sure to tell the remote that we don't
|
||||
@@ -218,10 +220,10 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
return aerr;
|
||||
}
|
||||
|
||||
buffer->container = std::move(this->rx_buf_);
|
||||
buffer->container = std::move(frame);
|
||||
buffer->data_offset = 0;
|
||||
buffer->data_len = this->rx_header_parsed_len_;
|
||||
buffer->type = this->rx_header_parsed_type_;
|
||||
buffer->data_len = rx_header_parsed_len_;
|
||||
buffer->type = rx_header_parsed_type_;
|
||||
return APIError::OK;
|
||||
}
|
||||
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;
|
||||
|
||||
protected:
|
||||
APIError try_read_frame_();
|
||||
APIError try_read_frame_(std::vector<uint8_t> *frame);
|
||||
|
||||
// Group 2-byte aligned types
|
||||
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_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 {
|
||||
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->variables);
|
||||
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
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
|
@@ -1104,7 +1104,7 @@ class HomeassistantServiceMap final : public ProtoMessage {
|
||||
class HomeassistantActionRequest final : public ProtoMessage {
|
||||
public:
|
||||
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
|
||||
const char *message_name() const override { return "homeassistant_action_request"; }
|
||||
#endif
|
||||
@@ -1114,15 +1114,6 @@ class HomeassistantActionRequest final : public ProtoMessage {
|
||||
std::vector<HomeassistantServiceMap> data_template{};
|
||||
std::vector<HomeassistantServiceMap> variables{};
|
||||
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 calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -1132,30 +1123,6 @@ class HomeassistantActionRequest final : public ProtoMessage {
|
||||
protected:
|
||||
};
|
||||
#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
|
||||
class SubscribeHomeAssistantStatesRequest final : public ProtoMessage {
|
||||
public:
|
||||
|
@@ -1122,28 +1122,6 @@ void HomeassistantActionRequest::dump_to(std::string &out) const {
|
||||
out.append("\n");
|
||||
}
|
||||
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
|
||||
#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);
|
||||
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
|
||||
default:
|
||||
break;
|
||||
|
@@ -66,9 +66,6 @@ class APIServerConnectionBase : public ProtoService {
|
||||
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
virtual void on_homeassistant_action_response(const HomeassistantActionResponse &value){};
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
|
||||
#endif
|
||||
|
@@ -9,16 +9,12 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/version.h"
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
#include "homeassistant_service.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
@@ -404,38 +400,7 @@ void APIServer::send_homeassistant_action(const HomeassistantActionRequest &call
|
||||
client->send_homeassistant_action(call);
|
||||
}
|
||||
}
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
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
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
|
@@ -16,7 +16,6 @@
|
||||
#include "user_services.h"
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::api {
|
||||
@@ -112,17 +111,7 @@ class APIServer : public Component, public Controller {
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
void send_homeassistant_action(const HomeassistantActionRequest &call);
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
// 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
|
||||
#endif
|
||||
#ifdef USE_API_SERVICES
|
||||
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||
#endif
|
||||
@@ -198,13 +187,6 @@ class APIServer : public Component, public Controller {
|
||||
#ifdef USE_API_SERVICES
|
||||
std::vector<UserServiceDescriptor *> user_services_;
|
||||
#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
|
||||
uint16_t port_{6053};
|
||||
|
@@ -3,13 +3,8 @@
|
||||
#include "api_server.h"
|
||||
#ifdef USE_API
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#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/helpers.h"
|
||||
|
||||
@@ -49,47 +44,9 @@ template<typename... Ts> class TemplatableKeyValuePair {
|
||||
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...> {
|
||||
public:
|
||||
explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent) {
|
||||
this->flags_.is_event = is_event;
|
||||
}
|
||||
explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#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 {
|
||||
HomeassistantActionRequest resp;
|
||||
std::string service_value = this->service_.value(x...);
|
||||
resp.set_service(StringRef(service_value));
|
||||
resp.is_event = this->flags_.is_event;
|
||||
resp.is_event = this->is_event_;
|
||||
for (auto &it : this->data_) {
|
||||
resp.data.emplace_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.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);
|
||||
}
|
||||
|
||||
protected:
|
||||
APIServer *parent_;
|
||||
bool is_event_;
|
||||
TemplatableStringValue<Ts...> service_{};
|
||||
std::vector<TemplatableKeyValuePair<Ts...>> data_;
|
||||
std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
|
||||
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
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@@ -35,7 +35,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
||||
msg.set_name(StringRef(this->name_));
|
||||
msg.key = this->key_;
|
||||
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();
|
||||
auto &arg = msg.args.back();
|
||||
arg.type = arg_types[i];
|
||||
|
@@ -131,16 +131,28 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const {
|
||||
if (this->uuid_.len == uuid.uuid_.len) {
|
||||
switch (this->uuid_.len) {
|
||||
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:
|
||||
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:
|
||||
return memcmp(this->uuid_.uuid.uuid128, uuid.uuid_.uuid.uuid128, ESP_UUID_LEN_128) == 0;
|
||||
default:
|
||||
return false;
|
||||
for (uint8_t i = 0; i < ESP_UUID_LEN_128; i++) {
|
||||
if (uuid.uuid_.uuid.uuid128[i] != this->uuid_.uuid.uuid128[i]) {
|
||||
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_; }
|
||||
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) {
|
||||
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) {
|
||||
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);
|
||||
};
|
||||
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->status_message_callback_.call((const char *) data, data_size);
|
||||
} 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_) {
|
||||
case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
|
||||
@@ -827,7 +827,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
||||
size_t expected_size =
|
||||
2 + this->status_message_header_size_ + this->real_control_packet_size_ + this->real_sensors_packet_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;
|
||||
}
|
||||
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_sensors_packet_bytes_{4};
|
||||
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};
|
||||
HonControlMethod control_method_;
|
||||
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
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_CAPTURE_RESPONSE,
|
||||
CONF_ESP8266_DISABLE_SSL_SUPPORT,
|
||||
CONF_ID,
|
||||
CONF_METHOD,
|
||||
@@ -58,6 +57,7 @@ CONF_HEADERS = "headers"
|
||||
CONF_COLLECT_HEADERS = "collect_headers"
|
||||
CONF_BODY = "body"
|
||||
CONF_JSON = "json"
|
||||
CONF_CAPTURE_RESPONSE = "capture_response"
|
||||
|
||||
|
||||
def validate_url(value):
|
||||
|
@@ -20,23 +20,6 @@ bool MCP2515::setup_internal() {
|
||||
return false;
|
||||
if (this->set_bitrate_(this->bit_rate_, this->mcp_clock_) != canbus::ERROR_OK)
|
||||
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)
|
||||
return false;
|
||||
uint8_t err_flags = this->get_error_flags_();
|
||||
|
@@ -26,16 +26,24 @@ void MDNSComponent::setup() {
|
||||
mdns_instance_name_set(this->hostname_.c_str());
|
||||
|
||||
for (const auto &service : this->services_) {
|
||||
std::vector<mdns_txt_item_t> txt_records(service.txt_records.size());
|
||||
for (size_t i = 0; i < service.txt_records.size(); i++) {
|
||||
// mdns_service_add copies the strings internally, no need to strdup
|
||||
txt_records[i].key = service.txt_records[i].key.c_str();
|
||||
txt_records[i].value = const_cast<TemplatableValue<std::string> &>(service.txt_records[i].value).value().c_str();
|
||||
std::vector<mdns_txt_item_t> txt_records;
|
||||
for (const auto &record : service.txt_records) {
|
||||
mdns_txt_item_t it{};
|
||||
// dup strings to ensure the pointer is valid even after the record loop
|
||||
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();
|
||||
err = mdns_service_add(nullptr, service.service_type.c_str(), service.proto.c_str(), port, txt_records.data(),
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
} 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) {
|
||||
this->write_array(ptr, w);
|
||||
} else if constexpr (BUS_TYPE == BUS_TYPE_QUAD) {
|
||||
@@ -372,8 +372,8 @@ class MipiSpi : public display::Display,
|
||||
uint8_t dbuffer[DISPLAYPIXEL * 48];
|
||||
uint8_t *dptr = dbuffer;
|
||||
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 x = 0; x != static_cast<size_t>(w); x++) {
|
||||
for (size_t y = 0; y != h; y++) {
|
||||
for (size_t x = 0; x != w; x++) {
|
||||
auto color_val = ptr[y * stride + x];
|
||||
if constexpr (DISPLAYPIXEL == PIXEL_MODE_18 && BUFFERPIXEL == PIXEL_MODE_16) {
|
||||
// 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
|
||||
from esphome.const import (
|
||||
CONF_BUFFER_SIZE,
|
||||
CONF_CARRIER_DUTY_PERCENT,
|
||||
CONF_CARRIER_FREQUENCY,
|
||||
CONF_CLOCK_RESOLUTION,
|
||||
CONF_DUMP,
|
||||
CONF_FILTER,
|
||||
@@ -151,14 +149,6 @@ CONFIG_SCHEMA = remote_base.validate_triggers(
|
||||
),
|
||||
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)
|
||||
@@ -178,8 +168,6 @@ async def to_code(config):
|
||||
cg.add(var.set_clock_resolution(config[CONF_CLOCK_RESOLUTION]))
|
||||
if CONF_FILTER_SYMBOLS in config:
|
||||
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:
|
||||
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_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_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
|
||||
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; }
|
||||
@@ -78,8 +76,6 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase,
|
||||
uint32_t filter_symbols_{0};
|
||||
uint32_t receive_symbols_{0};
|
||||
bool with_dma_{false};
|
||||
uint32_t carrier_frequency_{0};
|
||||
uint8_t carrier_duty_percent_{100};
|
||||
esp_err_t error_code_{ESP_OK};
|
||||
std::string error_string_{""};
|
||||
#endif
|
||||
|
@@ -72,21 +72,6 @@ void RemoteReceiverComponent::setup() {
|
||||
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;
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
callbacks.on_recv_done = rmt_callback;
|
||||
@@ -126,13 +111,11 @@ void RemoteReceiverComponent::dump_config() {
|
||||
" Filter symbols: %" PRIu32 "\n"
|
||||
" Receive symbols: %" PRIu32 "\n"
|
||||
" Tolerance: %" PRIu32 "%s\n"
|
||||
" Carrier frequency: %" PRIu32 " hz\n"
|
||||
" Carrier duty: %u%%\n"
|
||||
" Filter out pulses shorter than: %" PRIu32 " us\n"
|
||||
" Signal is done after %" PRIu32 " us of no changes",
|
||||
this->clock_resolution_, this->rmt_symbols_, this->filter_symbols_, this->receive_symbols_,
|
||||
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()) {
|
||||
ESP_LOGE(TAG, "Configuring RMT driver failed: %s (%s)", esp_err_to_name(this->error_code_),
|
||||
this->error_string_.c_str());
|
||||
|
@@ -215,7 +215,7 @@ void Rtttl::loop() {
|
||||
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;
|
||||
}
|
||||
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
|
||||
// status requests or commands, so a message needs to be sent first.
|
||||
optional<GateStatus> Tormatic::read_gate_status_() {
|
||||
if (this->available() < static_cast<int>(sizeof(MessageHeader))) {
|
||||
if (this->available() < sizeof(MessageHeader)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@@ -58,7 +58,7 @@ void UponorSmatrixClimate::control(const climate::ClimateCall &call) {
|
||||
}
|
||||
|
||||
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) {
|
||||
case UPONOR_ID_TARGET_TEMP_MIN:
|
||||
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) {
|
||||
for (size_t i = 0; i < data_len; i++) {
|
||||
for (int i = 0; i < data_len; i++) {
|
||||
switch (data[i].id) {
|
||||
case UPONOR_ID_ROOM_TEMP:
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
|
@@ -122,7 +122,7 @@ bool UponorSmatrixComponent::parse_byte_(uint8_t byte) {
|
||||
|
||||
// Decode packet payload data for easy access
|
||||
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].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.
|
||||
bool found_temperature = 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)
|
||||
found_temperature = true;
|
||||
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 >> 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].value >> 8);
|
||||
packet.push_back(data[i].value >> 0);
|
||||
|
@@ -1,7 +1,6 @@
|
||||
#include "veml7700.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <limits>
|
||||
|
||||
namespace esphome {
|
||||
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) {
|
||||
size_t i = 0;
|
||||
size_t idx = std::numeric_limits<size_t>::max();
|
||||
while (idx == std::numeric_limits<size_t>::max() && i < size) {
|
||||
size_t idx = -1;
|
||||
while (idx == -1 && i < size) {
|
||||
if (array[i] == val) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (idx == std::numeric_limits<size_t>::max() || i + 1 >= size)
|
||||
if (idx == -1 || i + 1 >= size)
|
||||
return val;
|
||||
return array[i + 1];
|
||||
}
|
||||
|
||||
template<typename T, size_t size> T get_prev(const T (&array)[size], const T val) {
|
||||
size_t i = size - 1;
|
||||
size_t idx = std::numeric_limits<size_t>::max();
|
||||
while (idx == std::numeric_limits<size_t>::max() && i > 0) {
|
||||
size_t idx = -1;
|
||||
while (idx == -1 && i > 0) {
|
||||
if (array[i] == val) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
if (idx == std::numeric_limits<size_t>::max() || i == 0)
|
||||
if (idx == -1 || i == 0)
|
||||
return val;
|
||||
return array[i - 1];
|
||||
}
|
||||
|
@@ -2274,11 +2274,11 @@ void GDEW0154M09::clear_() {
|
||||
uint32_t pixsize = this->get_buffer_length_();
|
||||
for (uint8_t j = 0; j < 2; j++) {
|
||||
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->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->command(CMD_DISPLAY_REFRESH);
|
||||
@@ -2291,11 +2291,11 @@ void HOT GDEW0154M09::display() {
|
||||
this->init_internal_();
|
||||
// "Mode 0 display" for now
|
||||
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->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->command(CMD_DISPLAY_REFRESH);
|
||||
|
@@ -67,31 +67,6 @@ ConfigPath = list[str | int]
|
||||
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(
|
||||
result: Config,
|
||||
component_name: str,
|
||||
@@ -116,7 +91,9 @@ def _process_platform_config(
|
||||
CORE.loaded_platforms.add(f"{component_name}/{platform_name}")
|
||||
|
||||
# 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
|
||||
p_domain = f"{component_name}.{platform_name}"
|
||||
@@ -413,7 +390,9 @@ class LoadValidationStep(ConfigValidationStep):
|
||||
result[self.domain] = self.conf = [self.conf]
|
||||
|
||||
# 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(
|
||||
MetadataValidationStep([self.domain], self.domain, self.conf, component)
|
||||
@@ -639,34 +618,6 @@ class MetadataValidationStep(ConfigValidationStep):
|
||||
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):
|
||||
"""Schema validation step.
|
||||
|
||||
|
@@ -174,7 +174,6 @@ CONF_CALIBRATE_LINEAR = "calibrate_linear"
|
||||
CONF_CALIBRATION = "calibration"
|
||||
CONF_CAPACITANCE = "capacitance"
|
||||
CONF_CAPACITY = "capacity"
|
||||
CONF_CAPTURE_RESPONSE = "capture_response"
|
||||
CONF_CARBON_MONOXIDE = "carbon_monoxide"
|
||||
CONF_CARRIER_DUTY_PERCENT = "carrier_duty_percent"
|
||||
CONF_CARRIER_FREQUENCY = "carrier_frequency"
|
||||
@@ -677,7 +676,6 @@ CONF_ON_RESPONSE = "on_response"
|
||||
CONF_ON_SHUTDOWN = "on_shutdown"
|
||||
CONF_ON_SPEED_SET = "on_speed_set"
|
||||
CONF_ON_STATE = "on_state"
|
||||
CONF_ON_SUCCESS = "on_success"
|
||||
CONF_ON_TAG = "on_tag"
|
||||
CONF_ON_TAG_REMOVED = "on_tag_removed"
|
||||
CONF_ON_TIME = "on_time"
|
||||
@@ -820,7 +818,6 @@ CONF_RESET_DURATION = "reset_duration"
|
||||
CONF_RESET_PIN = "reset_pin"
|
||||
CONF_RESIZE = "resize"
|
||||
CONF_RESOLUTION = "resolution"
|
||||
CONF_RESPONSE_TEMPLATE = "response_template"
|
||||
CONF_RESTART = "restart"
|
||||
CONF_RESTORE = "restore"
|
||||
CONF_RESTORE_MODE = "restore_mode"
|
||||
|
@@ -112,8 +112,6 @@
|
||||
#define USE_API
|
||||
#define USE_API_CLIENT_CONNECTED_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_STATES
|
||||
#define USE_API_NOISE
|
||||
|
@@ -77,7 +77,7 @@ bool ESPTime::strptime(const std::string &time_to_parse, ESPTime &esp_time) {
|
||||
&hour, // NOLINT
|
||||
&minute, // NOLINT
|
||||
&second, &num) == 6 && // NOLINT
|
||||
num == static_cast<int>(time_to_parse.size())) {
|
||||
num == time_to_parse.size()) {
|
||||
esp_time.year = year;
|
||||
esp_time.month = month;
|
||||
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
|
||||
&hour, // NOLINT
|
||||
&minute, &num) == 5 && // NOLINT
|
||||
num == static_cast<int>(time_to_parse.size())) {
|
||||
num == time_to_parse.size()) {
|
||||
esp_time.year = year;
|
||||
esp_time.month = month;
|
||||
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.second = 0;
|
||||
} 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.minute = minute;
|
||||
esp_time.second = second;
|
||||
} 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.minute = minute;
|
||||
esp_time.second = 0;
|
||||
} 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.month = month;
|
||||
esp_time.day_of_month = day;
|
||||
|
@@ -82,10 +82,11 @@ class ComponentManifest:
|
||||
return getattr(self.module, "CONFLICTS_WITH", [])
|
||||
|
||||
@property
|
||||
def auto_load(
|
||||
self,
|
||||
) -> list[str] | Callable[[], list[str]] | Callable[[ConfigType], list[str]]:
|
||||
return getattr(self.module, "AUTO_LOAD", [])
|
||||
def auto_load(self) -> list[str]:
|
||||
al = getattr(self.module, "AUTO_LOAD", [])
|
||||
if callable(al):
|
||||
return al()
|
||||
return al
|
||||
|
||||
@property
|
||||
def codeowners(self) -> list[str]:
|
||||
|
@@ -13,7 +13,7 @@ esptool==5.1.0
|
||||
click==8.1.7
|
||||
esphome-dashboard==20250904.0
|
||||
aioesphomeapi==41.11.0
|
||||
zeroconf==0.148.0
|
||||
zeroconf==0.147.2
|
||||
puremagic==1.30
|
||||
ruamel.yaml==0.18.15 # 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
|
||||
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
|
||||
|
@@ -48,10 +48,9 @@ def parse_requirement_line(line: str) -> tuple[str, str] | 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"""
|
||||
repo_root = _ensure_repo_root(repo_root)
|
||||
requirements_path = repo_root / "requirements_dev.txt"
|
||||
requirements_path = Path(__file__).parent.parent / "requirements_dev.txt"
|
||||
lines = read_file_lines(requirements_path)
|
||||
|
||||
for line in lines:
|
||||
@@ -69,49 +68,30 @@ def read_file_bytes(path: Path) -> bytes:
|
||||
return f.read()
|
||||
|
||||
|
||||
def get_repo_root() -> Path:
|
||||
"""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:
|
||||
def calculate_clang_tidy_hash() -> str:
|
||||
"""Calculate hash of clang-tidy configuration and version"""
|
||||
repo_root = _ensure_repo_root(repo_root)
|
||||
|
||||
hasher = hashlib.sha256()
|
||||
|
||||
# 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)
|
||||
hasher.update(content)
|
||||
|
||||
# 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())
|
||||
|
||||
# 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)
|
||||
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()
|
||||
|
||||
|
||||
def read_stored_hash(repo_root: Path | None = None) -> str | None:
|
||||
def read_stored_hash() -> str | None:
|
||||
"""Read the stored hash from file"""
|
||||
repo_root = _ensure_repo_root(repo_root)
|
||||
hash_file = repo_root / ".clang-tidy.hash"
|
||||
hash_file = Path(__file__).parent.parent / ".clang-tidy.hash"
|
||||
if hash_file.exists():
|
||||
lines = read_file_lines(hash_file)
|
||||
return lines[0].strip() if lines else None
|
||||
@@ -124,10 +104,9 @@ def write_file_content(path: Path, content: str) -> None:
|
||||
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"""
|
||||
repo_root = _ensure_repo_root(repo_root)
|
||||
hash_file = repo_root / ".clang-tidy.hash"
|
||||
hash_file = Path(__file__).parent.parent / ".clang-tidy.hash"
|
||||
# Strip any trailing newlines to ensure consistent formatting
|
||||
write_file_content(hash_file, hash_value.strip() + "\n")
|
||||
|
||||
@@ -155,28 +134,8 @@ def main() -> None:
|
||||
stored_hash = read_stored_hash()
|
||||
|
||||
if args.check:
|
||||
# Check if hash changed OR if .clang-tidy.hash was updated in this PR
|
||||
# This is used in CI to determine if a full clang-tidy scan is needed
|
||||
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")
|
||||
# Exit 0 if full scan needed (hash changed or no hash file)
|
||||
sys.exit(0 if current_hash != stored_hash else 1)
|
||||
|
||||
elif args.update:
|
||||
write_hash(current_hash)
|
||||
@@ -192,6 +151,15 @@ def main() -> None:
|
||||
print("Clang-tidy hash unchanged")
|
||||
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:
|
||||
print(f"Current hash: {current_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)
|
||||
|
||||
# Add auto_load components
|
||||
auto_load = 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)
|
||||
new_components.update(comp.auto_load)
|
||||
|
||||
# Check if we found any new components
|
||||
new_components -= all_components
|
||||
|
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
@@ -14,7 +13,7 @@ from esphome.const import (
|
||||
PLATFORM_ESP8266,
|
||||
)
|
||||
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):
|
||||
@@ -46,29 +45,6 @@ def add_item_to_components_graph(components_graph, parent, 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():
|
||||
# The root directory of the repo
|
||||
root = Path(__file__).parent.parent
|
||||
@@ -87,7 +63,7 @@ def create_components_graph():
|
||||
|
||||
components_graph = {}
|
||||
platforms = []
|
||||
components: list[tuple[ComponentManifest, str, Path]] = []
|
||||
components = []
|
||||
|
||||
for path in components_dir.iterdir():
|
||||
if not path.is_dir():
|
||||
@@ -116,8 +92,8 @@ def create_components_graph():
|
||||
|
||||
for target_config in TARGET_CONFIGURATIONS:
|
||||
CORE.data[KEY_CORE] = target_config
|
||||
for item in resolve_auto_load(comp.auto_load, config=None):
|
||||
add_item_to_components_graph(components_graph, item, name)
|
||||
for auto_load in comp.auto_load:
|
||||
add_item_to_components_graph(components_graph, auto_load, name)
|
||||
# restore config
|
||||
CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0]
|
||||
|
||||
@@ -138,8 +114,8 @@ def create_components_graph():
|
||||
|
||||
for target_config in TARGET_CONFIGURATIONS:
|
||||
CORE.data[KEY_CORE] = target_config
|
||||
for item in resolve_auto_load(platform.auto_load, config={}):
|
||||
add_item_to_components_graph(components_graph, item, name)
|
||||
for auto_load in platform.auto_load:
|
||||
add_item_to_components_graph(components_graph, auto_load, name)
|
||||
# restore config
|
||||
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_CHECK_IDLE_TASK_CPU0=n
|
||||
CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n
|
||||
CONFIG_AUTOSTART_ARDUINO=y
|
||||
|
||||
# esp32_ble
|
||||
CONFIG_BT_ENABLED=y
|
||||
|
@@ -10,39 +10,6 @@ esphome:
|
||||
data:
|
||||
message: Button was pressed
|
||||
- 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:
|
||||
port: 8000
|
||||
|
@@ -1,8 +1,6 @@
|
||||
substitutions:
|
||||
pin: GPIO2
|
||||
clock_resolution: "2000000"
|
||||
carrier_duty_percent: "25"
|
||||
carrier_frequency: "30000"
|
||||
filter_symbols: "2"
|
||||
receive_symbols: "4"
|
||||
rmt_symbols: "64"
|
||||
|
@@ -44,53 +44,37 @@ def test_get_clang_tidy_version_from_requirements(
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_calculate_clang_tidy_hash_with_sdkconfig(tmp_path: Path) -> None:
|
||||
"""Test calculating hash from all configuration sources including sdkconfig.defaults."""
|
||||
def test_calculate_clang_tidy_hash() -> None:
|
||||
"""Test calculating hash from all configuration sources."""
|
||||
clang_tidy_content = b"Checks: '-*,readability-*'\n"
|
||||
requirements_version = "clang-tidy==18.1.5"
|
||||
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_hasher = hashlib.sha256()
|
||||
expected_hasher.update(clang_tidy_content)
|
||||
expected_hasher.update(requirements_version.encode())
|
||||
expected_hasher.update(platformio_content)
|
||||
expected_hasher.update(sdkconfig_content)
|
||||
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
|
||||
|
||||
|
||||
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)
|
||||
mock_read_bytes.side_effect = read_file_mock
|
||||
result = clang_tidy_hash.calculate_clang_tidy_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.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
|
||||
|
||||
|
||||
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."""
|
||||
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
|
||||
|
||||
|
||||
def test_write_hash(tmp_path: Path) -> None:
|
||||
def test_write_hash() -> None:
|
||||
"""Test writing hash to file."""
|
||||
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()
|
||||
assert hash_file.read_text() == hash_value.strip() + "\n"
|
||||
# Verify write_file_content was called with correct parameters
|
||||
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(
|
||||
("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", "def456", False, 0), # Hashes differ, scan needed
|
||||
(["--check"], "abc123", None, False, 0), # No stored hash, scan needed
|
||||
(
|
||||
["--check"],
|
||||
"abc123",
|
||||
"abc123",
|
||||
True,
|
||||
0,
|
||||
), # Hash file updated in PR, scan needed
|
||||
(["--check"], "abc123", "abc123", 1), # Hashes match, no scan needed
|
||||
(["--check"], "abc123", "def456", 0), # Hashes differ, scan needed
|
||||
(["--check"], "abc123", None, 0), # No stored hash, scan needed
|
||||
],
|
||||
)
|
||||
def test_main_check_mode(
|
||||
args: list[str],
|
||||
current_hash: str,
|
||||
stored_hash: str | None,
|
||||
hash_file_in_changed: bool,
|
||||
expected_exit: int,
|
||||
args: list[str], current_hash: str, stored_hash: str | None, expected_exit: int
|
||||
) -> None:
|
||||
"""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 (
|
||||
patch("sys.argv", ["clang_tidy_hash.py"] + args),
|
||||
patch("clang_tidy_hash.calculate_clang_tidy_hash", return_value=current_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,
|
||||
):
|
||||
clang_tidy_hash.main()
|
||||
|
@@ -101,10 +101,3 @@ def mock_get_idedata() -> Generator[Mock, None, None]:
|
||||
"""Mock get_idedata for platformio_api."""
|
||||
with patch("esphome.platformio_api.get_idedata") as 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
|
||||
|
||||
|
||||
@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
|
||||
def mock_get_platform() -> Generator[Mock, None, None]:
|
||||
"""Fixture for mocking get_platform."""
|
||||
|
Reference in New Issue
Block a user