Compare commits

..

No commits in common. "dev" and "2025.4.0" have entirely different histories.

88 changed files with 310 additions and 1926 deletions

View File

@ -220,7 +220,7 @@ jobs:
. venv/bin/activate
pytest -vv --cov-report=xml --tb=native tests
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5.4.2
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}

View File

@ -250,7 +250,6 @@ esphome/components/ltr501/* @latonita
esphome/components/ltr_als_ps/* @latonita
esphome/components/lvgl/* @clydebarrow
esphome/components/m5stack_8angle/* @rnauber
esphome/components/mapping/* @clydebarrow
esphome/components/matrix_keypad/* @ssieb
esphome/components/max17043/* @blacknell
esphome/components/max31865/* @DAVe3283
@ -325,7 +324,6 @@ esphome/components/pcf8563/* @KoenBreeman
esphome/components/pid/* @OttoWinter
esphome/components/pipsolar/* @andreashergert1984
esphome/components/pm1006/* @habbie
esphome/components/pm2005/* @andrewjswan
esphome/components/pmsa003i/* @sjtrny
esphome/components/pmwcs3/* @SeByDocKy
esphome/components/pn532/* @OttoWinter @jesserockz

View File

@ -14,8 +14,7 @@ void AnalogThresholdBinarySensor::setup() {
if (std::isnan(sensor_value)) {
this->publish_initial_state(false);
} else {
this->publish_initial_state(sensor_value >=
(this->lower_threshold_.value() + this->upper_threshold_.value()) / 2.0f);
this->publish_initial_state(sensor_value >= (this->lower_threshold_ + this->upper_threshold_) / 2.0f);
}
}
@ -25,8 +24,7 @@ void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) {
this->sensor_->add_on_state_callback([this](float sensor_value) {
// if there is an invalid sensor reading, ignore the change and keep the current state
if (!std::isnan(sensor_value)) {
this->publish_state(sensor_value >=
(this->state ? this->lower_threshold_.value() : this->upper_threshold_.value()));
this->publish_state(sensor_value >= (this->state ? this->lower_threshold_ : this->upper_threshold_));
}
});
}
@ -34,8 +32,8 @@ void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) {
void AnalogThresholdBinarySensor::dump_config() {
LOG_BINARY_SENSOR("", "Analog Threshold Binary Sensor", this);
LOG_SENSOR(" ", "Sensor", this->sensor_);
ESP_LOGCONFIG(TAG, " Upper threshold: %.11f", this->upper_threshold_.value());
ESP_LOGCONFIG(TAG, " Lower threshold: %.11f", this->lower_threshold_.value());
ESP_LOGCONFIG(TAG, " Upper threshold: %.11f", this->upper_threshold_);
ESP_LOGCONFIG(TAG, " Lower threshold: %.11f", this->lower_threshold_);
}
} // namespace analog_threshold

View File

@ -15,13 +15,14 @@ class AnalogThresholdBinarySensor : public Component, public binary_sensor::Bina
float get_setup_priority() const override { return setup_priority::DATA; }
void set_sensor(sensor::Sensor *analog_sensor);
template<typename T> void set_upper_threshold(T upper_threshold) { this->upper_threshold_ = upper_threshold; }
template<typename T> void set_lower_threshold(T lower_threshold) { this->lower_threshold_ = lower_threshold; }
void set_upper_threshold(float threshold) { this->upper_threshold_ = threshold; }
void set_lower_threshold(float threshold) { this->lower_threshold_ = threshold; }
protected:
sensor::Sensor *sensor_{nullptr};
TemplatableValue<float> upper_threshold_{};
TemplatableValue<float> lower_threshold_{};
float upper_threshold_;
float lower_threshold_;
};
} // namespace analog_threshold

View File

@ -18,11 +18,11 @@ CONFIG_SCHEMA = (
{
cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor),
cv.Required(CONF_THRESHOLD): cv.Any(
cv.templatable(cv.float_),
cv.float_,
cv.Schema(
{
cv.Required(CONF_UPPER): cv.templatable(cv.float_),
cv.Required(CONF_LOWER): cv.templatable(cv.float_),
cv.Required(CONF_UPPER): cv.float_,
cv.Required(CONF_LOWER): cv.float_,
}
),
),
@ -39,11 +39,9 @@ async def to_code(config):
sens = await cg.get_variable(config[CONF_SENSOR_ID])
cg.add(var.set_sensor(sens))
if isinstance(config[CONF_THRESHOLD], dict):
lower = await cg.templatable(config[CONF_THRESHOLD][CONF_LOWER], [], float)
upper = await cg.templatable(config[CONF_THRESHOLD][CONF_UPPER], [], float)
if isinstance(config[CONF_THRESHOLD], float):
cg.add(var.set_upper_threshold(config[CONF_THRESHOLD]))
cg.add(var.set_lower_threshold(config[CONF_THRESHOLD]))
else:
lower = await cg.templatable(config[CONF_THRESHOLD], [], float)
upper = lower
cg.add(var.set_upper_threshold(upper))
cg.add(var.set_lower_threshold(lower))
cg.add(var.set_upper_threshold(config[CONF_THRESHOLD][CONF_UPPER]))
cg.add(var.set_lower_threshold(config[CONF_THRESHOLD][CONF_LOWER]))

View File

@ -82,19 +82,6 @@ ACTIONS_SCHEMA = automation.validate_automation(
),
)
ENCRYPTION_SCHEMA = cv.Schema(
{
cv.Optional(CONF_KEY): validate_encryption_key,
}
)
def _encryption_schema(config):
if config is None:
config = {}
return ENCRYPTION_SCHEMA(config)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
@ -108,7 +95,11 @@ CONFIG_SCHEMA = cv.All(
CONF_SERVICES, group_of_exclusion=CONF_ACTIONS
): ACTIONS_SCHEMA,
cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA,
cv.Optional(CONF_ENCRYPTION): _encryption_schema,
cv.Optional(CONF_ENCRYPTION): cv.Schema(
{
cv.Required(CONF_KEY): validate_encryption_key,
}
),
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
single=True
),
@ -160,17 +151,9 @@ async def to_code(config):
config[CONF_ON_CLIENT_DISCONNECTED],
)
if (encryption_config := config.get(CONF_ENCRYPTION, None)) is not None:
if key := encryption_config.get(CONF_KEY):
decoded = base64.b64decode(key)
cg.add(var.set_noise_psk(list(decoded)))
else:
# No key provided, but encryption desired
# This will allow a plaintext client to provide a noise key,
# send it to the device, and then switch to noise.
# The key will be saved in flash and used for future connections
# and plaintext disabled. Only a factory reset can remove it.
cg.add_define("USE_API_PLAINTEXT")
if encryption_config := config.get(CONF_ENCRYPTION):
decoded = base64.b64decode(encryption_config[CONF_KEY])
cg.add(var.set_noise_psk(list(decoded)))
cg.add_define("USE_API_NOISE")
cg.add_library("esphome/noise-c", "0.1.6")
else:

View File

@ -31,7 +31,6 @@ service APIConnection {
option (needs_authentication) = false;
}
rpc execute_service (ExecuteServiceRequest) returns (void) {}
rpc noise_encryption_set_key (NoiseEncryptionSetKeyRequest) returns (NoiseEncryptionSetKeyResponse) {}
rpc cover_command (CoverCommandRequest) returns (void) {}
rpc fan_command (FanCommandRequest) returns (void) {}
@ -231,9 +230,6 @@ message DeviceInfoResponse {
// The Bluetooth mac address of the device. For example "AC:BC:32:89:0E:AA"
string bluetooth_mac_address = 18;
// Supports receiving and saving api encryption key
bool api_encryption_supported = 19;
}
message ListEntitiesRequest {
@ -658,23 +654,6 @@ message SubscribeLogsResponse {
bool send_failed = 4;
}
// ==================== NOISE ENCRYPTION ====================
message NoiseEncryptionSetKeyRequest {
option (id) = 124;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_API_NOISE";
bytes key = 1;
}
message NoiseEncryptionSetKeyResponse {
option (id) = 125;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_API_NOISE";
bool success = 1;
}
// ==================== HOMEASSISTANT.SERVICE ====================
message SubscribeHomeassistantServicesRequest {
option (id) = 34;

View File

@ -62,14 +62,7 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
: parent_(parent), deferred_message_queue_(this), initial_state_iterator_(this), list_entities_iterator_(this) {
this->proto_write_buffer_.reserve(64);
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
auto noise_ctx = parent->get_noise_ctx();
if (noise_ctx->has_psk()) {
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx)};
} else {
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
}
#elif defined(USE_API_PLAINTEXT)
#if defined(USE_API_PLAINTEXT)
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
#elif defined(USE_API_NOISE)
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
@ -1855,9 +1848,6 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
#ifdef USE_VOICE_ASSISTANT
resp.legacy_voice_assistant_version = voice_assistant::global_voice_assistant->get_legacy_version();
resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
#endif
#ifdef USE_API_NOISE
resp.api_encryption_supported = true;
#endif
return resp;
}
@ -1879,26 +1869,6 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
ESP_LOGV(TAG, "Could not find matching service!");
}
}
#ifdef USE_API_NOISE
NoiseEncryptionSetKeyResponse APIConnection::noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) {
psk_t psk{};
NoiseEncryptionSetKeyResponse resp;
if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
ESP_LOGW(TAG, "Invalid encryption key length");
resp.success = false;
return resp;
}
if (!this->parent_->save_noise_psk(psk, true)) {
ESP_LOGW(TAG, "Failed to save encryption key");
resp.success = false;
return resp;
}
resp.success = true;
return resp;
}
#endif
void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {
state_subs_at_ = 0;
}

View File

@ -300,9 +300,6 @@ class APIConnection : public APIServerConnection {
return {};
}
void execute_service(const ExecuteServiceRequest &msg) override;
#ifdef USE_API_NOISE
NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override;
#endif
bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; }
bool is_connection_setup() override {

View File

@ -1,6 +1,6 @@
#pragma once
#include <array>
#include <cstdint>
#include <array>
#include "esphome/core/defines.h"
namespace esphome {
@ -11,20 +11,11 @@ using psk_t = std::array<uint8_t, 32>;
class APINoiseContext {
public:
void set_psk(psk_t psk) {
this->psk_ = psk;
bool has_psk = false;
for (auto i : psk) {
has_psk |= i;
}
this->has_psk_ = has_psk;
}
const psk_t &get_psk() const { return this->psk_; }
bool has_psk() const { return this->has_psk_; }
void set_psk(psk_t psk) { psk_ = psk; }
const psk_t &get_psk() const { return psk_; }
protected:
psk_t psk_{};
bool has_psk_{false};
psk_t psk_;
};
#endif // USE_API_NOISE

View File

@ -792,10 +792,6 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->voice_assistant_feature_flags = value.as_uint32();
return true;
}
case 19: {
this->api_encryption_supported = value.as_bool();
return true;
}
default:
return false;
}
@ -869,7 +865,6 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(17, this->voice_assistant_feature_flags);
buffer.encode_string(16, this->suggested_area);
buffer.encode_string(18, this->bluetooth_mac_address);
buffer.encode_bool(19, this->api_encryption_supported);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoResponse::dump_to(std::string &out) const {
@ -951,10 +946,6 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
out.append(" bluetooth_mac_address: ");
out.append("'").append(this->bluetooth_mac_address).append("'");
out.append("\n");
out.append(" api_encryption_supported: ");
out.append(YESNO(this->api_encryption_supported));
out.append("\n");
out.append("}");
}
#endif
@ -3018,48 +3009,6 @@ void SubscribeLogsResponse::dump_to(std::string &out) const {
out.append("}");
}
#endif
bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->key = value.as_string();
return true;
}
default:
return false;
}
}
void NoiseEncryptionSetKeyRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->key); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("NoiseEncryptionSetKeyRequest {\n");
out.append(" key: ");
out.append("'").append(this->key).append("'");
out.append("\n");
out.append("}");
}
#endif
bool NoiseEncryptionSetKeyResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->success = value.as_bool();
return true;
}
default:
return false;
}
}
void NoiseEncryptionSetKeyResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->success); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("NoiseEncryptionSetKeyResponse {\n");
out.append(" success: ");
out.append(YESNO(this->success));
out.append("\n");
out.append("}");
}
#endif
void SubscribeHomeassistantServicesRequest::encode(ProtoWriteBuffer buffer) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const {

View File

@ -355,7 +355,6 @@ class DeviceInfoResponse : public ProtoMessage {
uint32_t voice_assistant_feature_flags{0};
std::string suggested_area{};
std::string bluetooth_mac_address{};
bool api_encryption_supported{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@ -792,28 +791,6 @@ class SubscribeLogsResponse : public ProtoMessage {
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class NoiseEncryptionSetKeyRequest : public ProtoMessage {
public:
std::string key{};
void encode(ProtoWriteBuffer buffer) const override;
#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;
};
class NoiseEncryptionSetKeyResponse : public ProtoMessage {
public:
bool success{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class SubscribeHomeassistantServicesRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;

View File

@ -179,16 +179,6 @@ bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorSt
bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) {
return this->send_message_<SubscribeLogsResponse>(msg, 29);
}
#ifdef USE_API_NOISE
#endif
#ifdef USE_API_NOISE
bool APIServerConnectionBase::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_noise_encryption_set_key_response: %s", msg.dump().c_str());
#endif
return this->send_message_<NoiseEncryptionSetKeyResponse>(msg, 125);
}
#endif
bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str());
@ -1201,17 +1191,6 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_voice_assistant_set_configuration: %s", msg.dump().c_str());
#endif
this->on_voice_assistant_set_configuration(msg);
#endif
break;
}
case 124: {
#ifdef USE_API_NOISE
NoiseEncryptionSetKeyRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_noise_encryption_set_key_request: %s", msg.dump().c_str());
#endif
this->on_noise_encryption_set_key_request(msg);
#endif
break;
}
@ -1332,22 +1311,6 @@ void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest
}
this->execute_service(msg);
}
#ifdef USE_API_NOISE
void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
NoiseEncryptionSetKeyResponse ret = this->noise_encryption_set_key(msg);
if (!this->send_noise_encryption_set_key_response(ret)) {
this->on_fatal_error();
}
}
#endif
#ifdef USE_COVER
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
if (!this->is_connection_setup()) {

View File

@ -83,12 +83,6 @@ class APIServerConnectionBase : public ProtoService {
#endif
virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
bool send_subscribe_logs_response(const SubscribeLogsResponse &msg);
#ifdef USE_API_NOISE
virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){};
#endif
#ifdef USE_API_NOISE
bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyResponse &msg);
#endif
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
bool send_homeassistant_service_response(const HomeassistantServiceResponse &msg);
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
@ -355,9 +349,6 @@ class APIServerConnection : public APIServerConnectionBase {
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
#ifdef USE_API_NOISE
virtual NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) = 0;
#endif
#ifdef USE_COVER
virtual void cover_command(const CoverCommandRequest &msg) = 0;
#endif
@ -466,9 +457,6 @@ class APIServerConnection : public APIServerConnectionBase {
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
void on_get_time_request(const GetTimeRequest &msg) override;
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
#ifdef USE_API_NOISE
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override;
#endif
#ifdef USE_COVER
void on_cover_command_request(const CoverCommandRequest &msg) override;
#endif

View File

@ -22,40 +22,22 @@ namespace api {
static const char *const TAG = "api";
// APIServer
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
APIServer::APIServer() { global_api_server = this; }
void APIServer::setup() {
ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
this->setup_controller();
#ifdef USE_API_NOISE
uint32_t hash = 88491486UL;
this->noise_pref_ = global_preferences->make_preference<SavedNoisePsk>(hash, true);
SavedNoisePsk noise_pref_saved{};
if (this->noise_pref_.load(&noise_pref_saved)) {
ESP_LOGD(TAG, "Loaded saved Noise PSK");
this->set_noise_psk(noise_pref_saved.psk);
}
#endif
this->socket_ = socket::socket_ip(SOCK_STREAM, 0);
if (this->socket_ == nullptr) {
ESP_LOGW(TAG, "Could not create socket");
socket_ = socket::socket_ip(SOCK_STREAM, 0);
if (socket_ == nullptr) {
ESP_LOGW(TAG, "Could not create socket.");
this->mark_failed();
return;
}
int enable = 1;
int err = this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
int err = socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
if (err != 0) {
ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
// we can still continue
}
err = this->socket_->setblocking(false);
err = socket_->setblocking(false);
if (err != 0) {
ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
this->mark_failed();
@ -71,14 +53,14 @@ void APIServer::setup() {
return;
}
err = this->socket_->bind((struct sockaddr *) &server, sl);
err = socket_->bind((struct sockaddr *) &server, sl);
if (err != 0) {
ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
this->mark_failed();
return;
}
err = this->socket_->listen(4);
err = socket_->listen(4);
if (err != 0) {
ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
this->mark_failed();
@ -110,19 +92,18 @@ void APIServer::setup() {
}
#endif
}
void APIServer::loop() {
// Accept new clients
while (true) {
struct sockaddr_storage source_addr;
socklen_t addr_len = sizeof(source_addr);
auto sock = this->socket_->accept((struct sockaddr *) &source_addr, &addr_len);
auto sock = socket_->accept((struct sockaddr *) &source_addr, &addr_len);
if (!sock)
break;
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
auto *conn = new APIConnection(std::move(sock), this);
this->clients_.emplace_back(conn);
clients_.emplace_back(conn);
conn->start();
}
@ -155,22 +136,16 @@ void APIServer::loop() {
}
}
}
void APIServer::dump_config() {
ESP_LOGCONFIG(TAG, "API Server:");
ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->port_);
#ifdef USE_API_NOISE
ESP_LOGCONFIG(TAG, " Using noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
if (!this->noise_ctx_->has_psk()) {
ESP_LOGCONFIG(TAG, " Supports noise encryption: YES");
}
ESP_LOGCONFIG(TAG, " Using noise encryption: YES");
#else
ESP_LOGCONFIG(TAG, " Using noise encryption: NO");
#endif
}
bool APIServer::uses_password() const { return !this->password_.empty(); }
bool APIServer::check_password(const std::string &password) const {
// depend only on input password length
const char *a = this->password_.c_str();
@ -199,9 +174,7 @@ bool APIServer::check_password(const std::string &password) const {
return result == 0;
}
void APIServer::handle_disconnect(APIConnection *conn) {}
#ifdef USE_BINARY_SENSOR
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
if (obj->is_internal())
@ -369,27 +342,18 @@ void APIServer::on_update(update::UpdateEntity *obj) {
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_alarm_control_panel_state(obj);
}
#endif
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
void APIServer::set_port(uint16_t port) { this->port_ = port; }
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void APIServer::set_password(const std::string &password) { this->password_ = password; }
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
for (auto &client : this->clients_) {
client->send_homeassistant_service_call(call);
}
}
APIServer::APIServer() { global_api_server = this; }
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
@ -399,7 +363,6 @@ void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<s
.once = false,
});
}
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
@ -409,47 +372,11 @@ void APIServer::get_home_assistant_state(std::string entity_id, optional<std::st
.once = true,
});
};
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
return this->state_subs_;
}
uint16_t APIServer::get_port() const { return this->port_; }
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
#ifdef USE_API_NOISE
bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
auto &old_psk = this->noise_ctx_->get_psk();
if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
ESP_LOGW(TAG, "New PSK matches old");
return true;
}
SavedNoisePsk new_saved_psk{psk};
if (!this->noise_pref_.save(&new_saved_psk)) {
ESP_LOGW(TAG, "Failed to save Noise PSK");
return false;
}
// ensure it's written immediately
if (!global_preferences->sync()) {
ESP_LOGW(TAG, "Failed to sync preferences");
return false;
}
ESP_LOGD(TAG, "Noise PSK saved");
if (make_active) {
this->set_timeout(100, [this, psk]() {
ESP_LOGW(TAG, "Disconnecting all clients to reset connections");
this->set_noise_psk(psk);
for (auto &c : this->clients_) {
c->send_disconnect_request(DisconnectRequest());
}
});
}
return true;
}
#endif
#ifdef USE_HOMEASSISTANT_TIME
void APIServer::request_time() {
for (auto &client : this->clients_) {
@ -458,9 +385,7 @@ void APIServer::request_time() {
}
}
#endif
bool APIServer::is_connected() const { return !this->clients_.empty(); }
void APIServer::on_shutdown() {
for (auto &c : this->clients_) {
c->send_disconnect_request(DisconnectRequest());
@ -468,6 +393,15 @@ void APIServer::on_shutdown() {
delay(10);
}
#ifdef USE_ALARM_CONTROL_PANEL
void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_alarm_control_panel_state(obj);
}
#endif
} // namespace api
} // namespace esphome
#endif

View File

@ -19,12 +19,6 @@
namespace esphome {
namespace api {
#ifdef USE_API_NOISE
struct SavedNoisePsk {
psk_t psk;
} PACKED; // NOLINT
#endif
class APIServer : public Component, public Controller {
public:
APIServer();
@ -41,7 +35,6 @@ class APIServer : public Component, public Controller {
void set_reboot_timeout(uint32_t reboot_timeout);
#ifdef USE_API_NOISE
bool save_noise_psk(psk_t psk, bool make_active = true);
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); }
std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
#endif // USE_API_NOISE
@ -149,7 +142,6 @@ class APIServer : public Component, public Controller {
#ifdef USE_API_NOISE
std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
ESPPreferenceObject noise_pref_;
#endif // USE_API_NOISE
};

View File

@ -3,8 +3,6 @@ from esphome.const import CONF_BLUE, CONF_GREEN, CONF_ID, CONF_RED, CONF_WHITE
ColorStruct = cg.esphome_ns.struct("Color")
INSTANCE_TYPE = ColorStruct
MULTI_CONF = True
CONF_RED_INT = "red_int"

View File

@ -251,7 +251,7 @@ ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0)
# The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 1, 6)
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 1, 5)
# The platformio/espressif32 version to use for esp-idf frameworks
# - https://github.com/platformio/platform-espressif32/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
@ -274,15 +274,12 @@ SUPPORTED_PLATFORMIO_ESP_IDF_5X = [
# pioarduino versions that don't require a release number
# List based on https://github.com/pioarduino/esp-idf/releases
SUPPORTED_PIOARDUINO_ESP_IDF_5X = [
cv.Version(5, 5, 0),
cv.Version(5, 4, 1),
cv.Version(5, 4, 0),
cv.Version(5, 3, 3),
cv.Version(5, 3, 2),
cv.Version(5, 3, 1),
cv.Version(5, 3, 0),
cv.Version(5, 1, 5),
cv.Version(5, 1, 6),
]
@ -324,8 +321,8 @@ def _arduino_check_versions(value):
def _esp_idf_check_versions(value):
value = value.copy()
lookups = {
"dev": (cv.Version(5, 1, 6), "https://github.com/espressif/esp-idf.git"),
"latest": (cv.Version(5, 1, 6), None),
"dev": (cv.Version(5, 1, 5), "https://github.com/espressif/esp-idf.git"),
"latest": (cv.Version(5, 1, 5), None),
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
}

View File

@ -17,7 +17,7 @@ static const char *const TAG = "esp32_can";
static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config) {
switch (bitrate) {
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3) || \
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2)
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H6)
case canbus::CAN_1KBPS:
*t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_1KBITS();
return true;

View File

@ -58,7 +58,7 @@ void ESP32RMTLEDStripLightOutput::setup() {
channel.flags.io_loop_back = 0;
channel.flags.io_od_mode = 0;
channel.flags.invert_out = 0;
channel.flags.with_dma = this->use_dma_;
channel.flags.with_dma = 0;
channel.intr_priority = 0;
if (rmt_new_tx_channel(&channel, &this->channel_) != ESP_OK) {
ESP_LOGE(TAG, "Channel creation failed");

View File

@ -51,7 +51,6 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; }
void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; }
void set_is_wrgb(bool is_wrgb) { this->is_wrgb_ = is_wrgb; }
void set_use_dma(bool use_dma) { this->use_dma_ = use_dma; }
void set_use_psram(bool use_psram) { this->use_psram_ = use_psram; }
/// Set a maximum refresh rate in µs as some lights do not like being updated too often.
@ -86,7 +85,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
rmt_encoder_handle_t encoder_{nullptr};
rmt_symbol_word_t *rmt_buf_{nullptr};
rmt_symbol_word_t bit0_, bit1_, reset_;
uint32_t rmt_symbols_{48};
uint32_t rmt_symbols_;
#else
rmt_item32_t *rmt_buf_{nullptr};
rmt_item32_t bit0_, bit1_, reset_;
@ -95,12 +94,11 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
uint8_t pin_;
uint16_t num_leds_;
bool is_rgbw_{false};
bool is_wrgb_{false};
bool use_dma_{false};
bool use_psram_{false};
bool is_rgbw_;
bool is_wrgb_;
bool use_psram_;
RGBOrder rgb_order_{ORDER_RGB};
RGBOrder rgb_order_;
uint32_t last_refresh_{0};
optional<uint32_t> max_refresh_rate_{};

View File

@ -3,7 +3,7 @@ import logging
from esphome import pins
import esphome.codegen as cg
from esphome.components import esp32, esp32_rmt, light
from esphome.components import esp32_rmt, light
import esphome.config_validation as cv
from esphome.const import (
CONF_CHIPSET,
@ -15,7 +15,6 @@ from esphome.const import (
CONF_RGB_ORDER,
CONF_RMT_CHANNEL,
CONF_RMT_SYMBOLS,
CONF_USE_DMA,
)
from esphome.core import CORE
@ -139,11 +138,6 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
cv.Optional(CONF_IS_WRGB, default=False): cv.boolean,
cv.Optional(CONF_USE_DMA): cv.All(
esp32.only_on_variant(supported=[esp32.const.VARIANT_ESP32S3]),
cv.only_with_esp_idf,
cv.boolean,
),
cv.Optional(CONF_USE_PSRAM, default=True): cv.boolean,
cv.Inclusive(
CONF_BIT0_HIGH,
@ -217,8 +211,6 @@ async def to_code(config):
if esp32_rmt.use_new_rmt_driver():
cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS]))
if CONF_USE_DMA in config:
cg.add(var.set_use_dma(config[CONF_USE_DMA]))
else:
rmt_channel_t = cg.global_ns.enum("rmt_channel_t")
cg.add(

View File

@ -291,8 +291,6 @@ SOURCE_WEB = "web"
Image_ = image_ns.class_("Image")
INSTANCE_TYPE = Image_
def compute_local_image_path(value) -> Path:
url = value[CONF_URL] if isinstance(value, dict) else value

View File

@ -9,7 +9,7 @@ uint8_t temprature_sens_read();
}
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \
defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || \
defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32P4)
defined(USE_ESP32_VARIANT_ESP32C2)
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
#include "driver/temp_sensor.h"
#else
@ -33,8 +33,7 @@ static const char *const TAG = "internal_temperature";
#ifdef USE_ESP32
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) && \
(defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2) || \
defined(USE_ESP32_VARIANT_ESP32P4))
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2))
static temperature_sensor_handle_t tsensNew = NULL;
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && USE_ESP32_VARIANT
#endif // USE_ESP32
@ -50,7 +49,7 @@ void InternalTemperatureSensor::update() {
success = (raw != 128);
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \
defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || \
defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32P4)
defined(USE_ESP32_VARIANT_ESP32C2)
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT();
temp_sensor_set_config(tsens);
@ -101,8 +100,7 @@ void InternalTemperatureSensor::setup() {
#ifdef USE_ESP32
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) && \
(defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2) || \
defined(USE_ESP32_VARIANT_ESP32P4))
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2))
ESP_LOGCONFIG(TAG, "Setting up temperature sensor...");
temperature_sensor_config_t tsens_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80);

View File

@ -433,11 +433,7 @@ void LvglComponent::setup() {
auto height = display->get_height();
size_t buffer_pixels = width * height / this->buffer_frac_;
auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8;
void *buffer = nullptr;
if (this->buffer_frac_ >= 4)
buffer = malloc(buf_bytes); // NOLINT
if (buffer == nullptr)
buffer = lv_custom_mem_alloc(buf_bytes); // NOLINT
auto *buffer = lv_custom_mem_alloc(buf_bytes); // NOLINT
if (buffer == nullptr) {
this->mark_failed();
this->status_set_error("Memory allocation failure");

View File

@ -1,134 +0,0 @@
import difflib
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_FROM, CONF_ID, CONF_TO
from esphome.core import CORE
from esphome.cpp_generator import MockObj, VariableDeclarationExpression, add_global
from esphome.loader import get_component
CODEOWNERS = ["@clydebarrow"]
MULTI_CONF = True
map_ = cg.std_ns.class_("map")
CONF_ENTRIES = "entries"
CONF_CLASS = "class"
class IndexType:
"""
Represents a type of index in a map.
"""
def __init__(self, validator, data_type, conversion):
self.validator = validator
self.data_type = data_type
self.conversion = conversion
INDEX_TYPES = {
"int": IndexType(cv.int_, cg.int_, int),
"string": IndexType(cv.string, cg.std_string, str),
}
def to_schema(value):
"""
Generate a schema for the 'to' field of a map. This can be either one of the index types or a class name.
:param value:
:return:
"""
return cv.Any(
cv.one_of(*INDEX_TYPES, lower=True),
cv.one_of(*CORE.id_classes.keys()),
)(value)
BASE_SCHEMA = cv.Schema(
{
cv.Required(CONF_ID): cv.declare_id(map_),
cv.Required(CONF_FROM): cv.one_of(*INDEX_TYPES, lower=True),
cv.Required(CONF_TO): cv.string,
},
extra=cv.ALLOW_EXTRA,
)
def get_object_type(to_):
"""
Get the object type from a string. Possible formats:
xxx The name of a component which defines INSTANCE_TYPE
esphome::xxx::yyy A C++ class name defined in a component
xxx::yyy A C++ class name defined in a component
yyy A C++ class name defined in the core
"""
if cls := CORE.id_classes.get(to_):
return cls
if cls := CORE.id_classes.get(to_.removeprefix("esphome::")):
return cls
# get_component will throw a wobbly if we don't check this first.
if "." in to_:
return None
if component := get_component(to_):
return component.instance_type
return None
def map_schema(config):
config = BASE_SCHEMA(config)
if CONF_ENTRIES not in config or not isinstance(config[CONF_ENTRIES], dict):
raise cv.Invalid("an entries list is required for a map")
entries = config[CONF_ENTRIES]
if len(entries) == 0:
raise cv.Invalid("Map must have at least one entry")
to_ = config[CONF_TO]
if to_ in INDEX_TYPES:
value_type = INDEX_TYPES[to_].validator
else:
value_type = get_object_type(to_)
if value_type is None:
matches = difflib.get_close_matches(to_, CORE.id_classes)
raise cv.Invalid(
f"No known mappable class name matches '{to_}'; did you mean one of {', '.join(matches)}?"
)
value_type = cv.use_id(value_type)
config[CONF_ENTRIES] = {k: value_type(v) for k, v in entries.items()}
return config
CONFIG_SCHEMA = map_schema
async def to_code(config):
entries = config[CONF_ENTRIES]
from_ = config[CONF_FROM]
to_ = config[CONF_TO]
index_conversion = INDEX_TYPES[from_].conversion
index_type = INDEX_TYPES[from_].data_type
if to_ in INDEX_TYPES:
value_conversion = INDEX_TYPES[to_].conversion
value_type = INDEX_TYPES[to_].data_type
entries = {
index_conversion(key): value_conversion(value)
for key, value in entries.items()
}
else:
entries = {
index_conversion(key): await cg.get_variable(value)
for key, value in entries.items()
}
value_type = get_object_type(to_)
if list(entries.values())[0].op != ".":
value_type = value_type.operator("ptr")
varid = config[CONF_ID]
varid.type = map_.template(index_type, value_type)
var = MockObj(varid, ".")
decl = VariableDeclarationExpression(varid.type, "", varid)
add_global(decl)
CORE.register_variable(varid, var)
for key, value in entries.items():
cg.add(var.insert((key, value)))
return var

View File

@ -1,9 +1,9 @@
#include "esphome/core/defines.h"
#ifdef USE_MDNS
#include "mdns_component.h"
#include "esphome/core/version.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "esphome/core/version.h"
#include "mdns_component.h"
#ifdef USE_API
#include "esphome/components/api/api_server.h"
@ -62,11 +62,7 @@ void MDNSComponent::compile_records_() {
#endif
#ifdef USE_API_NOISE
if (api::global_api_server->get_noise_ctx()->has_psk()) {
service.txt_records.push_back({"api_encryption", "Noise_NNpsk0_25519_ChaChaPoly_SHA256"});
} else {
service.txt_records.push_back({"api_encryption_supported", "Noise_NNpsk0_25519_ChaChaPoly_SHA256"});
}
service.txt_records.push_back({"api_encryption", "Noise_NNpsk0_25519_ChaChaPoly_SHA256"});
#endif
#ifdef ESPHOME_PROJECT_NAME

View File

@ -138,11 +138,7 @@ void MQTTClientComponent::send_device_info_() {
#endif
#ifdef USE_API_NOISE
if (api::global_api_server->get_noise_ctx()->has_psk()) {
root["api_encryption"] = "Noise_NNpsk0_25519_ChaChaPoly_SHA256";
} else {
root["api_encryption_supported"] = "Noise_NNpsk0_25519_ChaChaPoly_SHA256";
}
root["api_encryption"] = "Noise_NNpsk0_25519_ChaChaPoly_SHA256";
#endif
},
2, this->discovery_info_.retain);

View File

@ -26,7 +26,7 @@ CONFIG_SCHEMA = cv.Schema(
esp32_arduino=cv.Version(0, 0, 0),
esp8266_arduino=cv.Version(0, 0, 0),
rp2040_arduino=cv.Version(0, 0, 0),
bk72xx_arduino=cv.Version(1, 7, 0),
bk72xx_libretiny=cv.Version(1, 7, 0),
),
cv.boolean_false,
),

View File

@ -1 +0,0 @@
"""PM2005/2105 component for ESPHome."""

View File

@ -1,123 +0,0 @@
#include "esphome/core/log.h"
#include "pm2005.h"
namespace esphome {
namespace pm2005 {
static const char *const TAG = "pm2005";
// Converts a sensor situation to a human readable string
static const LogString *pm2005_get_situation_string(int status) {
switch (status) {
case 1:
return LOG_STR("Close");
case 2:
return LOG_STR("Malfunction");
case 3:
return LOG_STR("Under detecting");
case 0x80:
return LOG_STR("Detecting completed");
default:
return LOG_STR("Invalid");
}
}
// Converts a sensor measuring mode to a human readable string
static const LogString *pm2005_get_measuring_mode_string(int status) {
switch (status) {
case 2:
return LOG_STR("Single");
case 3:
return LOG_STR("Continuous");
case 5:
return LOG_STR("Dynamic");
default:
return LOG_STR("Timing");
}
}
static inline uint16_t get_sensor_value(const uint8_t *data, uint8_t i) { return data[i] * 0x100 + data[i + 1]; }
void PM2005Component::setup() {
if (this->sensor_type_ == PM2005) {
ESP_LOGCONFIG(TAG, "Setting up PM2005...");
this->situation_value_index_ = 3;
this->pm_1_0_value_index_ = 4;
this->pm_2_5_value_index_ = 6;
this->pm_10_0_value_index_ = 8;
this->measuring_value_index_ = 10;
} else {
ESP_LOGCONFIG(TAG, "Setting up PM2105...");
this->situation_value_index_ = 2;
this->pm_1_0_value_index_ = 3;
this->pm_2_5_value_index_ = 5;
this->pm_10_0_value_index_ = 7;
this->measuring_value_index_ = 9;
}
if (this->read(this->data_buffer_, 12) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Communication failed!");
this->mark_failed();
return;
}
}
void PM2005Component::update() {
if (this->read(this->data_buffer_, 12) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Read result failed.");
this->status_set_warning();
return;
}
if (this->sensor_situation_ == this->data_buffer_[this->situation_value_index_]) {
return;
}
this->sensor_situation_ = this->data_buffer_[this->situation_value_index_];
ESP_LOGD(TAG, "Sensor situation: %s.", LOG_STR_ARG(pm2005_get_situation_string(this->sensor_situation_)));
if (this->sensor_situation_ == 2) {
this->status_set_warning();
return;
}
if (this->sensor_situation_ != 0x80) {
return;
}
uint16_t pm1 = get_sensor_value(this->data_buffer_, this->pm_1_0_value_index_);
uint16_t pm25 = get_sensor_value(this->data_buffer_, this->pm_2_5_value_index_);
uint16_t pm10 = get_sensor_value(this->data_buffer_, this->pm_10_0_value_index_);
uint16_t sensor_measuring_mode = get_sensor_value(this->data_buffer_, this->measuring_value_index_);
ESP_LOGD(TAG, "PM1.0: %d, PM2.5: %d, PM10: %d, Measuring mode: %s.", pm1, pm25, pm10,
LOG_STR_ARG(pm2005_get_measuring_mode_string(sensor_measuring_mode)));
if (this->pm_1_0_sensor_ != nullptr) {
this->pm_1_0_sensor_->publish_state(pm1);
}
if (this->pm_2_5_sensor_ != nullptr) {
this->pm_2_5_sensor_->publish_state(pm25);
}
if (this->pm_10_0_sensor_ != nullptr) {
this->pm_10_0_sensor_->publish_state(pm10);
}
this->status_clear_warning();
}
void PM2005Component::dump_config() {
ESP_LOGCONFIG(TAG, "PM2005:");
ESP_LOGCONFIG(TAG, " Type: PM2%u05", this->sensor_type_ == PM2105);
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with PM2%u05 failed!", this->sensor_type_ == PM2105);
}
LOG_SENSOR(" ", "PM1.0", this->pm_1_0_sensor_);
LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_);
LOG_SENSOR(" ", "PM10 ", this->pm_10_0_sensor_);
}
} // namespace pm2005
} // namespace esphome

View File

@ -1,46 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace pm2005 {
enum SensorType {
PM2005,
PM2105,
};
class PM2005Component : public PollingComponent, public i2c::I2CDevice {
public:
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
void set_sensor_type(SensorType sensor_type) { this->sensor_type_ = sensor_type; }
void set_pm_1_0_sensor(sensor::Sensor *pm_1_0_sensor) { this->pm_1_0_sensor_ = pm_1_0_sensor; }
void set_pm_2_5_sensor(sensor::Sensor *pm_2_5_sensor) { this->pm_2_5_sensor_ = pm_2_5_sensor; }
void set_pm_10_0_sensor(sensor::Sensor *pm_10_0_sensor) { this->pm_10_0_sensor_ = pm_10_0_sensor; }
void setup() override;
void dump_config() override;
void update() override;
protected:
uint8_t sensor_situation_{0};
uint8_t data_buffer_[12];
SensorType sensor_type_{PM2005};
sensor::Sensor *pm_1_0_sensor_{nullptr};
sensor::Sensor *pm_2_5_sensor_{nullptr};
sensor::Sensor *pm_10_0_sensor_{nullptr};
uint8_t situation_value_index_{3};
uint8_t pm_1_0_value_index_{4};
uint8_t pm_2_5_value_index_{6};
uint8_t pm_10_0_value_index_{8};
uint8_t measuring_value_index_{10};
};
} // namespace pm2005
} // namespace esphome

View File

@ -1,86 +0,0 @@
"""PM2005/2105 Sensor component for ESPHome."""
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
CONF_PM_1_0,
CONF_PM_2_5,
CONF_PM_10_0,
CONF_TYPE,
DEVICE_CLASS_PM1,
DEVICE_CLASS_PM10,
DEVICE_CLASS_PM25,
ICON_CHEMICAL_WEAPON,
STATE_CLASS_MEASUREMENT,
UNIT_MICROGRAMS_PER_CUBIC_METER,
)
DEPENDENCIES = ["i2c"]
CODEOWNERS = ["@andrewjswan"]
pm2005_ns = cg.esphome_ns.namespace("pm2005")
PM2005Component = pm2005_ns.class_(
"PM2005Component", cg.PollingComponent, i2c.I2CDevice
)
SensorType = pm2005_ns.enum("SensorType")
SENSOR_TYPE = {
"PM2005": SensorType.PM2005,
"PM2105": SensorType.PM2105,
}
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(PM2005Component),
cv.Optional(CONF_TYPE, default="PM2005"): cv.enum(SENSOR_TYPE, upper=True),
cv.Optional(CONF_PM_1_0): sensor.sensor_schema(
unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER,
icon=ICON_CHEMICAL_WEAPON,
accuracy_decimals=0,
device_class=DEVICE_CLASS_PM1,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PM_2_5): sensor.sensor_schema(
unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER,
icon=ICON_CHEMICAL_WEAPON,
accuracy_decimals=0,
device_class=DEVICE_CLASS_PM25,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PM_10_0): sensor.sensor_schema(
unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER,
icon=ICON_CHEMICAL_WEAPON,
accuracy_decimals=0,
device_class=DEVICE_CLASS_PM10,
state_class=STATE_CLASS_MEASUREMENT,
),
},
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x28)),
)
async def to_code(config) -> None:
"""Code generation entry point."""
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_sensor_type(config[CONF_TYPE]))
if pm_1_0_config := config.get(CONF_PM_1_0):
sens = await sensor.new_sensor(pm_1_0_config)
cg.add(var.set_pm_1_0_sensor(sens))
if pm_2_5_config := config.get(CONF_PM_2_5):
sens = await sensor.new_sensor(pm_2_5_config)
cg.add(var.set_pm_2_5_sensor(sens))
if pm_10_0_config := config.get(CONF_PM_10_0):
sens = await sensor.new_sensor(pm_10_0_config)
cg.add(var.set_pm_10_0_sensor(sens))

View File

@ -89,12 +89,6 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) {
this->valve_row_(stream, obj, area, node, friendly_name);
#endif
#ifdef USE_CLIMATE
this->climate_type_(stream);
for (auto *obj : App.get_climates())
this->climate_row_(stream, obj, area, node, friendly_name);
#endif
req->send(stream);
}
@ -830,174 +824,6 @@ void PrometheusHandler::valve_row_(AsyncResponseStream *stream, valve::Valve *ob
}
#endif
#ifdef USE_CLIMATE
void PrometheusHandler::climate_type_(AsyncResponseStream *stream) {
stream->print(F("#TYPE esphome_climate_setting gauge\n"));
stream->print(F("#TYPE esphome_climate_value gauge\n"));
stream->print(F("#TYPE esphome_climate_failed gauge\n"));
}
void PrometheusHandler::climate_setting_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area,
std::string &node, std::string &friendly_name, std::string &setting,
const LogString *setting_value) {
stream->print(F("esphome_climate_setting{id=\""));
stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area);
add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\""));
stream->print(relabel_name_(obj).c_str());
stream->print(F("\",category=\""));
stream->print(setting.c_str());
stream->print(F("\",setting_value=\""));
stream->print(LOG_STR_ARG(setting_value));
stream->print(F("\"} "));
stream->print(F("1.0"));
stream->print(F("\n"));
}
void PrometheusHandler::climate_value_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area,
std::string &node, std::string &friendly_name, std::string &category,
std::string &climate_value) {
stream->print(F("esphome_climate_value{id=\""));
stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area);
add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\""));
stream->print(relabel_name_(obj).c_str());
stream->print(F("\",category=\""));
stream->print(category.c_str());
stream->print(F("\"} "));
stream->print(climate_value.c_str());
stream->print(F("\n"));
}
void PrometheusHandler::climate_failed_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area,
std::string &node, std::string &friendly_name, std::string &category,
bool is_failed_value) {
stream->print(F("esphome_climate_failed{id=\""));
stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area);
add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\""));
stream->print(relabel_name_(obj).c_str());
stream->print(F("\",category=\""));
stream->print(category.c_str());
stream->print(F("\"} "));
if (is_failed_value) {
stream->print(F("1.0"));
} else {
stream->print(F("0.0"));
}
stream->print(F("\n"));
}
void PrometheusHandler::climate_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area,
std::string &node, std::string &friendly_name) {
if (obj->is_internal() && !this->include_internal_)
return;
// Data itself
bool any_failures = false;
std::string climate_mode_category = "mode";
const auto *climate_mode_value = climate::climate_mode_to_string(obj->mode);
climate_setting_row_(stream, obj, area, node, friendly_name, climate_mode_category, climate_mode_value);
const auto traits = obj->get_traits();
// Now see if traits is supported
int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals();
// max temp
std::string max_temp = "maximum_temperature";
auto max_temp_value = value_accuracy_to_string(traits.get_visual_max_temperature(), target_accuracy);
climate_value_row_(stream, obj, area, node, friendly_name, max_temp, max_temp_value);
// max temp
std::string min_temp = "mininum_temperature";
auto min_temp_value = value_accuracy_to_string(traits.get_visual_min_temperature(), target_accuracy);
climate_value_row_(stream, obj, area, node, friendly_name, min_temp, min_temp_value);
// now check optional traits
if (traits.get_supports_current_temperature()) {
std::string current_temp = "current_temperature";
if (std::isnan(obj->current_temperature)) {
climate_failed_row_(stream, obj, area, node, friendly_name, current_temp, true);
any_failures = true;
} else {
auto current_temp_value = value_accuracy_to_string(obj->current_temperature, current_accuracy);
climate_value_row_(stream, obj, area, node, friendly_name, current_temp, current_temp_value);
climate_failed_row_(stream, obj, area, node, friendly_name, current_temp, false);
}
}
if (traits.get_supports_current_humidity()) {
std::string current_humidity = "current_humidity";
if (std::isnan(obj->current_humidity)) {
climate_failed_row_(stream, obj, area, node, friendly_name, current_humidity, true);
any_failures = true;
} else {
auto current_humidity_value = value_accuracy_to_string(obj->current_humidity, 0);
climate_value_row_(stream, obj, area, node, friendly_name, current_humidity, current_humidity_value);
climate_failed_row_(stream, obj, area, node, friendly_name, current_humidity, false);
}
}
if (traits.get_supports_target_humidity()) {
std::string target_humidity = "target_humidity";
if (std::isnan(obj->target_humidity)) {
climate_failed_row_(stream, obj, area, node, friendly_name, target_humidity, true);
any_failures = true;
} else {
auto target_humidity_value = value_accuracy_to_string(obj->target_humidity, 0);
climate_value_row_(stream, obj, area, node, friendly_name, target_humidity, target_humidity_value);
climate_failed_row_(stream, obj, area, node, friendly_name, target_humidity, false);
}
}
if (traits.get_supports_two_point_target_temperature()) {
std::string target_temp_low = "target_temperature_low";
auto target_temp_low_value = value_accuracy_to_string(obj->target_temperature_low, target_accuracy);
climate_value_row_(stream, obj, area, node, friendly_name, target_temp_low, target_temp_low_value);
std::string target_temp_high = "target_temperature_high";
auto target_temp_high_value = value_accuracy_to_string(obj->target_temperature_high, target_accuracy);
climate_value_row_(stream, obj, area, node, friendly_name, target_temp_high, target_temp_high_value);
} else {
std::string target_temp = "target_temperature";
auto target_temp_value = value_accuracy_to_string(obj->target_temperature, target_accuracy);
climate_value_row_(stream, obj, area, node, friendly_name, target_temp, target_temp_value);
}
if (traits.get_supports_action()) {
std::string climate_trait_category = "action";
const auto *climate_trait_value = climate::climate_action_to_string(obj->action);
climate_setting_row_(stream, obj, area, node, friendly_name, climate_trait_category, climate_trait_value);
}
if (traits.get_supports_fan_modes()) {
std::string climate_trait_category = "fan_mode";
if (obj->fan_mode.has_value()) {
const auto *climate_trait_value = climate::climate_fan_mode_to_string(obj->fan_mode.value());
climate_setting_row_(stream, obj, area, node, friendly_name, climate_trait_category, climate_trait_value);
climate_failed_row_(stream, obj, area, node, friendly_name, climate_trait_category, false);
} else {
climate_failed_row_(stream, obj, area, node, friendly_name, climate_trait_category, true);
any_failures = true;
}
}
if (traits.get_supports_presets()) {
std::string climate_trait_category = "preset";
if (obj->preset.has_value()) {
const auto *climate_trait_value = climate::climate_preset_to_string(obj->preset.value());
climate_setting_row_(stream, obj, area, node, friendly_name, climate_trait_category, climate_trait_value);
climate_failed_row_(stream, obj, area, node, friendly_name, climate_trait_category, false);
} else {
climate_failed_row_(stream, obj, area, node, friendly_name, climate_trait_category, true);
any_failures = true;
}
}
if (traits.get_supports_swing_modes()) {
std::string climate_trait_category = "swing_mode";
const auto *climate_trait_value = climate::climate_swing_mode_to_string(obj->swing_mode);
climate_setting_row_(stream, obj, area, node, friendly_name, climate_trait_category, climate_trait_value);
}
std::string all_climate_category = "all";
climate_failed_row_(stream, obj, area, node, friendly_name, all_climate_category, any_failures);
}
#endif
} // namespace prometheus
} // namespace esphome
#endif

View File

@ -8,9 +8,6 @@
#include "esphome/core/component.h"
#include "esphome/core/controller.h"
#include "esphome/core/entity_base.h"
#ifdef USE_CLIMATE
#include "esphome/core/log.h"
#endif
namespace esphome {
namespace prometheus {
@ -172,20 +169,6 @@ class PrometheusHandler : public AsyncWebHandler, public Component {
std::string &friendly_name);
#endif
#ifdef USE_CLIMATE
/// Return the type for prometheus
void climate_type_(AsyncResponseStream *stream);
/// Return the climate state as prometheus data point
void climate_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, std::string &node,
std::string &friendly_name);
void climate_failed_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, std::string &node,
std::string &friendly_name, std::string &category, bool is_failed_value);
void climate_setting_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, std::string &node,
std::string &friendly_name, std::string &setting, const LogString *setting_value);
void climate_value_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, std::string &node,
std::string &friendly_name, std::string &category, std::string &climate_value);
#endif
web_server_base::WebServerBase *base_;
bool include_internal_{false};
std::map<EntityBase *, std::string> relabel_map_id_;

View File

@ -16,8 +16,6 @@ from esphome.const import (
CONF_ID,
CONF_MODE,
CONF_SPEED,
KEY_CORE,
KEY_FRAMEWORK_VERSION,
PLATFORM_ESP32,
)
from esphome.core import CORE
@ -112,11 +110,11 @@ async def to_code(config):
add_idf_sdkconfig_option(f"{SPIRAM_MODES[config[CONF_MODE]]}", True)
add_idf_sdkconfig_option(f"{SPIRAM_SPEEDS[config[CONF_SPEED]]}", True)
if config[CONF_MODE] == TYPE_OCTAL and config[CONF_SPEED] == 120e6:
add_idf_sdkconfig_option("CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240", True)
if CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(5, 4, 0):
add_idf_sdkconfig_option(
"CONFIG_SPIRAM_TIMING_TUNING_POINT_VIA_TEMPERATURE_SENSOR", True
)
add_idf_sdkconfig_option("CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240", True)
# This works only on IDF 5.4.x but does no harm on earlier versions
add_idf_sdkconfig_option(
"CONFIG_SPIRAM_TIMING_TUNING_POINT_VIA_TEMPERATURE_SENSOR", True
)
if config[CONF_ENABLE_ECC]:
add_idf_sdkconfig_option("CONFIG_SPIRAM_ECC_ENABLE", True)

View File

@ -1,8 +1,7 @@
#ifdef USE_ESP32
#include "psram.h"
#include <esp_idf_version.h>
#if defined(USE_ESP_IDF) && ESP_IDF_VERSION_MAJOR >= 5
#ifdef USE_ESP_IDF
#include <esp_psram.h>
#endif // USE_ESP_IDF
@ -16,7 +15,7 @@ static const char *const TAG = "psram";
void PsramComponent::dump_config() {
ESP_LOGCONFIG(TAG, "PSRAM:");
#if defined(USE_ESP_IDF) && ESP_IDF_VERSION_MAJOR >= 5
#ifdef USE_ESP_IDF
bool available = esp_psram_is_initialized();
ESP_LOGCONFIG(TAG, " Available: %s", YESNO(available));

View File

@ -208,6 +208,7 @@ void RemoteReceiverComponent::loop() {
this->store_.buffer_read = next_read;
if (!this->temp_.empty()) {
this->temp_.push_back(-this->idle_us_);
this->call_listeners_dumpers_();
}
}
@ -218,9 +219,11 @@ void RemoteReceiverComponent::loop() {
this->decode_rmt_(item, len / sizeof(rmt_item32_t));
vRingbufferReturnItem(this->ringbuf_, item);
if (!this->temp_.empty()) {
this->call_listeners_dumpers_();
}
if (this->temp_.empty())
return;
this->temp_.push_back(-this->idle_us_);
this->call_listeners_dumpers_();
}
#endif
}
@ -231,7 +234,6 @@ void RemoteReceiverComponent::decode_rmt_(rmt_symbol_word_t *item, size_t item_c
void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t item_count) {
#endif
bool prev_level = false;
bool idle_level = false;
uint32_t prev_length = 0;
this->temp_.clear();
int32_t multiplier = this->pin_->is_inverted() ? -1 : 1;
@ -264,7 +266,7 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t item_count)
} else if ((bool(item[i].level0) == prev_level) || (item[i].duration0 < filter_ticks)) {
prev_length += item[i].duration0;
} else {
if (prev_length >= filter_ticks) {
if (prev_length > 0) {
if (prev_level) {
this->temp_.push_back(this->to_microseconds_(prev_length) * multiplier);
} else {
@ -274,7 +276,6 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t item_count)
prev_level = bool(item[i].level0);
prev_length = item[i].duration0;
}
idle_level = !bool(item[i].level0);
if (item[i].duration1 == 0u) {
// EOF, sometimes garbage follows, break early
@ -282,7 +283,7 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t item_count)
} else if ((bool(item[i].level1) == prev_level) || (item[i].duration1 < filter_ticks)) {
prev_length += item[i].duration1;
} else {
if (prev_length >= filter_ticks) {
if (prev_length > 0) {
if (prev_level) {
this->temp_.push_back(this->to_microseconds_(prev_length) * multiplier);
} else {
@ -292,22 +293,14 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t item_count)
prev_level = bool(item[i].level1);
prev_length = item[i].duration1;
}
idle_level = !bool(item[i].level1);
}
if (prev_length >= filter_ticks && prev_level != idle_level) {
if (prev_length > 0) {
if (prev_level) {
this->temp_.push_back(this->to_microseconds_(prev_length) * multiplier);
} else {
this->temp_.push_back(-int32_t(this->to_microseconds_(prev_length)) * multiplier);
}
}
if (!this->temp_.empty()) {
if (idle_level) {
this->temp_.push_back(this->idle_us_ * multiplier);
} else {
this->temp_.push_back(-int32_t(this->idle_us_) * multiplier);
}
}
}
} // namespace remote_receiver

View File

@ -18,8 +18,6 @@ from esphome.const import (
UNIT_CELSIUS,
UNIT_PARTS_PER_MILLION,
UNIT_PERCENT,
CONF_AUTOMATIC_SELF_CALIBRATION,
CONF_AMBIENT_PRESSURE_COMPENSATION,
)
DEPENDENCIES = ["i2c"]
@ -35,7 +33,10 @@ ForceRecalibrationWithReference = scd30_ns.class_(
"ForceRecalibrationWithReference", automation.Action
)
CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration"
CONF_ALTITUDE_COMPENSATION = "altitude_compensation"
CONF_AMBIENT_PRESSURE_COMPENSATION = "ambient_pressure_compensation"
CONFIG_SCHEMA = (
cv.Schema(

View File

@ -20,10 +20,6 @@ from esphome.const import (
UNIT_CELSIUS,
UNIT_PARTS_PER_MILLION,
UNIT_PERCENT,
CONF_AUTOMATIC_SELF_CALIBRATION,
CONF_AMBIENT_PRESSURE_COMPENSATION,
CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE,
CONF_MEASUREMENT_MODE,
)
CODEOWNERS = ["@sjtrny", "@martgras"]
@ -51,6 +47,11 @@ FactoryResetAction = scd4x_ns.class_("FactoryResetAction", automation.Action)
CONF_ALTITUDE_COMPENSATION = "altitude_compensation"
CONF_AMBIENT_PRESSURE_COMPENSATION = "ambient_pressure_compensation"
CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE = "ambient_pressure_compensation_source"
CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration"
CONF_MEASUREMENT_MODE = "measurement_mode"
CONFIG_SCHEMA = (
cv.Schema(

View File

@ -5,7 +5,6 @@ from esphome.const import (
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
UNIT_HECTOPASCAL,
CONF_MEASUREMENT_MODE,
)
DEPENDENCIES = ["i2c"]
@ -23,7 +22,7 @@ MEASUREMENT_MODE = {
"mass_flow": MeasurementMode.MASS_FLOW_AVG,
"differential_pressure": MeasurementMode.DP_AVG,
}
CONF_MEASUREMENT_MODE = "measurement_mode"
CONFIG_SCHEMA = (
sensor.sensor_schema(

View File

@ -1,59 +1,19 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_FORMAT,
CONF_HOURS,
CONF_ID,
CONF_MINUTES,
CONF_SECONDS,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_TIMER,
)
from esphome.const import ENTITY_CATEGORY_DIAGNOSTIC, ICON_TIMER
uptime_ns = cg.esphome_ns.namespace("uptime")
UptimeTextSensor = uptime_ns.class_(
"UptimeTextSensor", text_sensor.TextSensor, cg.PollingComponent
)
CONF_SEPARATOR = "separator"
CONF_DAYS = "days"
CONF_EXPAND = "expand"
CONFIG_SCHEMA = (
text_sensor.text_sensor_schema(
UptimeTextSensor,
icon=ICON_TIMER,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
)
.extend(
{
cv.Optional(CONF_FORMAT, default={}): cv.Schema(
{
cv.Optional(CONF_DAYS, default="d"): cv.string_strict,
cv.Optional(CONF_HOURS, default="h"): cv.string_strict,
cv.Optional(CONF_MINUTES, default="m"): cv.string_strict,
cv.Optional(CONF_SECONDS, default="s"): cv.string_strict,
cv.Optional(CONF_SEPARATOR, default=""): cv.string_strict,
cv.Optional(CONF_EXPAND, default=False): cv.boolean,
}
)
}
)
.extend(cv.polling_component_schema("30s"))
)
CONFIG_SCHEMA = text_sensor.text_sensor_schema(
UptimeTextSensor,
icon=ICON_TIMER,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
).extend(cv.polling_component_schema("30s"))
async def to_code(config):
format = config[CONF_FORMAT]
var = cg.new_Pvariable(
config[CONF_ID],
format[CONF_DAYS],
format[CONF_HOURS],
format[CONF_MINUTES],
format[CONF_SECONDS],
format[CONF_SEPARATOR],
format[CONF_EXPAND],
)
await text_sensor.register_text_sensor(var, config)
var = await text_sensor.new_text_sensor(config)
await cg.register_component(var, config)

View File

@ -16,11 +16,6 @@ void UptimeTextSensor::setup() {
this->update();
}
void UptimeTextSensor::insert_buffer_(std::string &buffer, const char *key, unsigned value) const {
buffer.insert(0, this->separator_);
buffer.insert(0, str_sprintf("%u%s", value, key));
}
void UptimeTextSensor::update() {
auto now = millis();
// get whole seconds since last update. Note that even if the millis count has overflowed between updates,
@ -37,25 +32,25 @@ void UptimeTextSensor::update() {
unsigned remainder = uptime % 60;
uptime /= 60;
if (interval < 30) {
this->insert_buffer_(buffer, this->seconds_text_, remainder);
if (!this->expand_ && uptime == 0)
buffer.insert(0, str_sprintf("%us", remainder));
if (uptime == 0)
break;
}
remainder = uptime % 60;
uptime /= 60;
if (interval < 1800) {
this->insert_buffer_(buffer, this->minutes_text_, remainder);
if (!this->expand_ && uptime == 0)
buffer.insert(0, str_sprintf("%um", remainder));
if (uptime == 0)
break;
}
remainder = uptime % 24;
uptime /= 24;
if (interval < 12 * 3600) {
this->insert_buffer_(buffer, this->hours_text_, remainder);
if (!this->expand_ && uptime == 0)
buffer.insert(0, str_sprintf("%uh", remainder));
if (uptime == 0)
break;
}
this->insert_buffer_(buffer, this->days_text_, (unsigned) uptime);
buffer.insert(0, str_sprintf("%ud", (unsigned) uptime));
break;
}
this->publish_state(buffer);

View File

@ -10,32 +10,13 @@ namespace uptime {
class UptimeTextSensor : public text_sensor::TextSensor, public PollingComponent {
public:
UptimeTextSensor(const char *days_text, const char *hours_text, const char *minutes_text, const char *seconds_text,
const char *separator, bool expand)
: days_text_(days_text),
hours_text_(hours_text),
minutes_text_(minutes_text),
seconds_text_(seconds_text),
separator_(separator),
expand_(expand) {}
void update() override;
void dump_config() override;
void setup() override;
float get_setup_priority() const override;
void set_days(const char *days_text) { this->days_text_ = days_text; }
void set_hours(const char *hours_text) { this->hours_text_ = hours_text; }
void set_minutes(const char *minutes_text) { this->minutes_text_ = minutes_text; }
void set_seconds(const char *seconds_text) { this->seconds_text_ = seconds_text; }
protected:
void insert_buffer_(std::string &buffer, const char *key, unsigned value) const;
const char *days_text_;
const char *hours_text_;
const char *minutes_text_;
const char *seconds_text_;
const char *separator_;
bool expand_{};
uint32_t uptime_{0}; // uptime in seconds, will overflow after 136 years
uint32_t last_ms_{0};
};

View File

@ -70,9 +70,6 @@ WaveshareEPaper4P2InBV2 = waveshare_epaper_ns.class_(
WaveshareEPaper4P2InBV2BWR = waveshare_epaper_ns.class_(
"WaveshareEPaper4P2InBV2BWR", WaveshareEPaperBWR
)
WaveshareEPaper5P65InF = waveshare_epaper_ns.class_(
"WaveshareEPaper5P65InF", WaveshareEPaper7C
)
WaveshareEPaper5P8In = waveshare_epaper_ns.class_(
"WaveshareEPaper5P8In", WaveshareEPaper
)
@ -153,7 +150,6 @@ MODELS = {
"4.20in": ("b", WaveshareEPaper4P2In),
"4.20in-bv2": ("b", WaveshareEPaper4P2InBV2),
"4.20in-bv2-bwr": ("b", WaveshareEPaper4P2InBV2BWR),
"5.65in-f": ("b", WaveshareEPaper5P65InF),
"5.83in": ("b", WaveshareEPaper5P8In),
"5.83inv2": ("b", WaveshareEPaper5P8InV2),
"7.30in-f": ("b", WaveshareEPaper7P3InF),

View File

@ -258,47 +258,6 @@ void WaveshareEPaper7C::fill(Color color) {
}
}
}
void WaveshareEPaper7C::send_buffers_() {
if (this->buffers_[0] == nullptr) {
ESP_LOGE(TAG, "Buffer unavailable!");
return;
}
uint32_t small_buffer_length = this->get_buffer_length_() / NUM_BUFFERS;
uint8_t byte_to_send;
for (auto &buffer : this->buffers_) {
for (uint32_t buffer_pos = 0; buffer_pos < small_buffer_length; buffer_pos += 3) {
std::bitset<24> triplet =
buffer[buffer_pos + 0] << 16 | buffer[buffer_pos + 1] << 8 | buffer[buffer_pos + 2] << 0;
// 8 bitset<3> are stored in 3 bytes
// |aaabbbaa|abbbaaab|bbaaabbb|
// | byte 1 | byte 2 | byte 3 |
byte_to_send = ((triplet >> 17).to_ulong() & 0b01110000) | ((triplet >> 18).to_ulong() & 0b00000111);
this->data(byte_to_send);
byte_to_send = ((triplet >> 11).to_ulong() & 0b01110000) | ((triplet >> 12).to_ulong() & 0b00000111);
this->data(byte_to_send);
byte_to_send = ((triplet >> 5).to_ulong() & 0b01110000) | ((triplet >> 6).to_ulong() & 0b00000111);
this->data(byte_to_send);
byte_to_send = ((triplet << 1).to_ulong() & 0b01110000) | ((triplet << 0).to_ulong() & 0b00000111);
this->data(byte_to_send);
}
App.feed_wdt();
}
}
void WaveshareEPaper7C::reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(true);
delay(20);
this->reset_pin_->digital_write(false);
delay(1);
this->reset_pin_->digital_write(true);
delay(20);
}
}
void HOT WaveshareEPaper::draw_absolute_pixel_internal(int x, int y, Color color) {
if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0)
return;
@ -3348,175 +3307,6 @@ void WaveshareEPaper7P5In::dump_config() {
LOG_PIN(" Busy Pin: ", this->busy_pin_);
LOG_UPDATE_INTERVAL(this);
}
// Waveshare 5.65F ========================================================
namespace cmddata_5P65InF {
// WaveshareEPaper5P65InF commands
// https://www.waveshare.com/wiki/5.65inch_e-Paper_Module_(F)
// R00H (PSR): Panel setting Register
// UD(1): scan up
// SHL(1) shift right
// SHD_N(1) DC-DC on
// RST_N(1) no reset
static const uint8_t R00_CMD_PSR[] = {0x00, 0xEF, 0x08};
// R01H (PWR): Power setting Register
// internal DC-DC power generation
static const uint8_t R01_CMD_PWR[] = {0x01, 0x07, 0x00, 0x00, 0x00};
// R02H (POF): Power OFF Command
static const uint8_t R02_CMD_POF[] = {0x02};
// R03H (PFS): Power off sequence setting Register
// T_VDS_OFF (00) = 1 frame
static const uint8_t R03_CMD_PFS[] = {0x03, 0x00};
// R04H (PON): Power ON Command
static const uint8_t R04_CMD_PON[] = {0x04};
// R06h (BTST): Booster Soft Start
static const uint8_t R06_CMD_BTST[] = {0x06, 0xC7, 0xC7, 0x1D};
// R07H (DSLP): Deep sleep#
// Note Documentation @ Waveshare shows cmd code as 0x10 in table, but
// 0x10 is DTM1.
static const uint8_t R07_CMD_DSLP[] = {0x07, 0xA5};
// R10H (DTM1): Data Start Transmission 1
static const uint8_t R10_CMD_DTM1[] = {0x10};
// R11H (DSP): Data Stop
static const uint8_t R11_CMD_DSP[] = {0x11};
// R12H (DRF): Display Refresh
static const uint8_t R12_CMD_DRF[] = {0x12};
// R13H (IPC): Image Process Command
static const uint8_t R13_CMD_IPC[] = {0x13, 0x00};
// R30H (PLL): PLL Control
// 0x3C = 50Hz
static const uint8_t R30_CMD_PLL[] = {0x30, 0x3C};
// R41H (TSE): Temperature Sensor Enable
// TSE(0) enable, TO(0000) +0 degree offset
static const uint8_t R41_CMD_TSE[] = {0x41, 0x00};
// R50H (CDI) VCOM and Data interval setting
// CDI(0111) 10
// DDX(1), VBD(001) Border output "White"
static const uint8_t R50_CMD_CDI[] = {0x50, 0x37};
// R60H (TCON) Gate and Source non overlap period command
// S2G(10) 12 units
// G2S(10) 12 units
static const uint8_t R60_CMD_TCON[] = {0x60, 0x22};
// R61H (TRES) Resolution Setting
// 0x258 = 600
// 0x1C0 = 448
static const uint8_t R61_CMD_TRES[] = {0x61, 0x02, 0x58, 0x01, 0xC0};
// RE3H (PWS) Power Savings
static const uint8_t RE3_CMD_PWS[] = {0xE3, 0xAA};
} // namespace cmddata_5P65InF
void WaveshareEPaper5P65InF::initialize() {
if (this->buffers_[0] == nullptr) {
ESP_LOGE(TAG, "Buffer unavailable!");
return;
}
this->reset_();
delay(20);
this->wait_until_(IDLE);
using namespace cmddata_5P65InF;
this->cmd_data(R00_CMD_PSR, sizeof(R00_CMD_PSR));
this->cmd_data(R01_CMD_PWR, sizeof(R01_CMD_PWR));
this->cmd_data(R03_CMD_PFS, sizeof(R03_CMD_PFS));
this->cmd_data(R06_CMD_BTST, sizeof(R06_CMD_BTST));
this->cmd_data(R30_CMD_PLL, sizeof(R30_CMD_PLL));
this->cmd_data(R41_CMD_TSE, sizeof(R41_CMD_TSE));
this->cmd_data(R50_CMD_CDI, sizeof(R50_CMD_CDI));
this->cmd_data(R60_CMD_TCON, sizeof(R60_CMD_TCON));
this->cmd_data(R61_CMD_TRES, sizeof(R61_CMD_TRES));
this->cmd_data(RE3_CMD_PWS, sizeof(RE3_CMD_PWS));
delay(100); // NOLINT
this->cmd_data(R50_CMD_CDI, sizeof(R50_CMD_CDI));
ESP_LOGI(TAG, "Display initialized successfully");
}
void HOT WaveshareEPaper5P65InF::display() {
// INITIALIZATION
ESP_LOGI(TAG, "Initialise the display");
this->initialize();
using namespace cmddata_5P65InF;
// COMMAND DATA START TRANSMISSION
ESP_LOGI(TAG, "Sending data to the display");
this->cmd_data(R61_CMD_TRES, sizeof(R61_CMD_TRES));
this->cmd_data(R10_CMD_DTM1, sizeof(R10_CMD_DTM1));
this->send_buffers_();
// COMMAND POWER ON
ESP_LOGI(TAG, "Power on the display");
this->cmd_data(R04_CMD_PON, sizeof(R04_CMD_PON));
this->wait_until_(IDLE);
// COMMAND REFRESH SCREEN
ESP_LOGI(TAG, "Refresh the display");
this->cmd_data(R12_CMD_DRF, sizeof(R12_CMD_DRF));
this->wait_until_(IDLE);
// COMMAND POWER OFF
ESP_LOGI(TAG, "Power off the display");
this->cmd_data(R02_CMD_POF, sizeof(R02_CMD_POF));
this->wait_until_(BUSY);
if (this->deep_sleep_between_updates_) {
ESP_LOGI(TAG, "Set the display to deep sleep");
this->cmd_data(R07_CMD_DSLP, sizeof(R07_CMD_DSLP));
}
}
int WaveshareEPaper5P65InF::get_width_internal() { return 600; }
int WaveshareEPaper5P65InF::get_height_internal() { return 448; }
uint32_t WaveshareEPaper5P65InF::idle_timeout_() { return 35000; }
void WaveshareEPaper5P65InF::dump_config() {
LOG_DISPLAY("", "Waveshare E-Paper", this);
ESP_LOGCONFIG(TAG, " Model: 5.65in-F");
LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Busy Pin: ", this->busy_pin_);
LOG_UPDATE_INTERVAL(this);
}
bool WaveshareEPaper5P65InF::wait_until_(WaitForState busy_state) {
if (this->busy_pin_ == nullptr) {
return true;
}
const uint32_t start = millis();
while (busy_state != this->busy_pin_->digital_read()) {
if (millis() - start > this->idle_timeout_()) {
ESP_LOGE(TAG, "Timeout while displaying image!");
return false;
}
App.feed_wdt();
delay(10);
}
return true;
}
void WaveshareEPaper7P3InF::initialize() {
if (this->buffers_[0] == nullptr) {
ESP_LOGE(TAG, "Buffer unavailable!");
@ -3621,6 +3411,11 @@ void WaveshareEPaper7P3InF::initialize() {
ESP_LOGI(TAG, "Display initialized successfully");
}
void HOT WaveshareEPaper7P3InF::display() {
if (this->buffers_[0] == nullptr) {
ESP_LOGE(TAG, "Buffer unavailable!");
return;
}
// INITIALIZATION
ESP_LOGI(TAG, "Initialise the display");
this->initialize();
@ -3628,7 +3423,29 @@ void HOT WaveshareEPaper7P3InF::display() {
// COMMAND DATA START TRANSMISSION
ESP_LOGI(TAG, "Sending data to the display");
this->command(0x10);
this->send_buffers_();
uint32_t small_buffer_length = this->get_buffer_length_() / NUM_BUFFERS;
uint8_t byte_to_send;
for (auto &buffer : this->buffers_) {
for (uint32_t buffer_pos = 0; buffer_pos < small_buffer_length; buffer_pos += 3) {
std::bitset<24> triplet =
buffer[buffer_pos + 0] << 16 | buffer[buffer_pos + 1] << 8 | buffer[buffer_pos + 2] << 0;
// 8 bitset<3> are stored in 3 bytes
// |aaabbbaa|abbbaaab|bbaaabbb|
// | byte 1 | byte 2 | byte 3 |
byte_to_send = ((triplet >> 17).to_ulong() & 0b01110000) | ((triplet >> 18).to_ulong() & 0b00000111);
this->data(byte_to_send);
byte_to_send = ((triplet >> 11).to_ulong() & 0b01110000) | ((triplet >> 12).to_ulong() & 0b00000111);
this->data(byte_to_send);
byte_to_send = ((triplet >> 5).to_ulong() & 0b01110000) | ((triplet >> 6).to_ulong() & 0b00000111);
this->data(byte_to_send);
byte_to_send = ((triplet << 1).to_ulong() & 0b01110000) | ((triplet << 0).to_ulong() & 0b00000111);
this->data(byte_to_send);
}
App.feed_wdt();
}
// COMMAND POWER ON
ESP_LOGI(TAG, "Power on the display");
@ -3647,11 +3464,9 @@ void HOT WaveshareEPaper7P3InF::display() {
this->data(0x00);
this->wait_until_idle_();
if (this->deep_sleep_between_updates_) {
ESP_LOGI(TAG, "Set the display to deep sleep");
this->command(0x07);
this->data(0xA5);
}
ESP_LOGI(TAG, "Set the display to deep sleep");
this->command(0x07);
this->data(0xA5);
}
int WaveshareEPaper7P3InF::get_width_internal() { return 800; }
int WaveshareEPaper7P3InF::get_height_internal() { return 480; }

View File

@ -94,10 +94,7 @@ class WaveshareEPaper7C : public WaveshareEPaperBase {
void draw_absolute_pixel_internal(int x, int y, Color color) override;
uint32_t get_buffer_length_() override;
void setup() override;
void init_internal_7c_(uint32_t buffer_length);
void send_buffers_();
void reset_();
static const int NUM_BUFFERS = 10;
uint8_t *buffers_[NUM_BUFFERS];
@ -686,29 +683,6 @@ class WaveshareEPaper5P8InV2 : public WaveshareEPaper {
int get_height_internal() override;
};
class WaveshareEPaper5P65InF : public WaveshareEPaper7C {
public:
void initialize() override;
void display() override;
void dump_config() override;
protected:
int get_width_internal() override;
int get_height_internal() override;
uint32_t idle_timeout_() override;
void deep_sleep() override { ; }
enum WaitForState { BUSY = true, IDLE = false };
bool wait_until_(WaitForState state);
bool deep_sleep_between_updates_{true};
};
class WaveshareEPaper7P3InF : public WaveshareEPaper7C {
public:
void initialize() override;
@ -729,6 +703,17 @@ class WaveshareEPaper7P3InF : public WaveshareEPaper7C {
bool wait_until_idle_();
bool deep_sleep_between_updates_{true};
void reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(true);
delay(20);
this->reset_pin_->digital_write(false);
delay(1);
this->reset_pin_->digital_write(true);
delay(20);
}
};
};
class WaveshareEPaper7P5In : public WaveshareEPaper {

View File

@ -56,6 +56,7 @@ from esphome.const import (
KEY_CORE,
KEY_FRAMEWORK_VERSION,
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2040,
@ -1941,28 +1942,70 @@ def platformio_version_constraint(value):
def require_framework_version(
*,
esp_idf=None,
esp32_arduino=None,
esp8266_arduino=None,
rp2040_arduino=None,
bk72xx_libretiny=None,
host=None,
max_version=False,
extra_message=None,
**kwargs,
):
def validator(value):
core_data = CORE.data[KEY_CORE]
framework = core_data[KEY_TARGET_FRAMEWORK]
if CORE.is_host and framework == "host":
key = "host"
elif framework == "esp-idf":
key = "esp_idf"
if framework == "esp-idf":
if esp_idf is None:
msg = "This feature is incompatible with esp-idf"
if extra_message:
msg += f". {extra_message}"
raise Invalid(msg)
required = esp_idf
elif CORE.is_bk72xx and framework == "arduino":
if bk72xx_libretiny is None:
msg = "This feature is incompatible with BK72XX"
if extra_message:
msg += f". {extra_message}"
raise Invalid(msg)
required = bk72xx_libretiny
elif CORE.is_esp32 and framework == "arduino":
if esp32_arduino is None:
msg = "This feature is incompatible with ESP32 using arduino framework"
if extra_message:
msg += f". {extra_message}"
raise Invalid(msg)
required = esp32_arduino
elif CORE.is_esp8266 and framework == "arduino":
if esp8266_arduino is None:
msg = "This feature is incompatible with ESP8266"
if extra_message:
msg += f". {extra_message}"
raise Invalid(msg)
required = esp8266_arduino
elif CORE.is_rp2040 and framework == "arduino":
if rp2040_arduino is None:
msg = "This feature is incompatible with RP2040"
if extra_message:
msg += f". {extra_message}"
raise Invalid(msg)
required = rp2040_arduino
elif CORE.is_host and framework == "host":
if host is None:
msg = "This feature is incompatible with host platform"
if extra_message:
msg += f". {extra_message}"
raise Invalid(msg)
required = host
else:
key = CORE.target_platform + "_" + framework
raise Invalid(
f"""
Internal Error: require_framework_version does not support this platform configuration
platform: {core_data[KEY_TARGET_PLATFORM]}
framework: {framework}
if key not in kwargs:
msg = f"This feature is incompatible with {CORE.target_platform.upper()} using {framework} framework"
if extra_message:
msg += f". {extra_message}"
raise Invalid(msg)
required = kwargs[key]
Please report this issue on GitHub -> https://github.com/esphome/issues/issues/new?template=bug_report.yml.
"""
)
if max_version:
if core_data[KEY_FRAMEWORK_VERSION] > required:

View File

@ -1,6 +1,6 @@
"""Constants used by esphome."""
__version__ = "2025.5.0-dev"
__version__ = "2025.4.0"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = (
@ -45,8 +45,6 @@ CONF_ALLOW_OTHER_USES = "allow_other_uses"
CONF_ALPHA = "alpha"
CONF_ALTITUDE = "altitude"
CONF_AMBIENT_LIGHT = "ambient_light"
CONF_AMBIENT_PRESSURE_COMPENSATION = "ambient_pressure_compensation"
CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE = "ambient_pressure_compensation_source"
CONF_AMMONIA = "ammonia"
CONF_ANALOG = "analog"
CONF_AND = "and"
@ -65,7 +63,6 @@ CONF_AUTH = "auth"
CONF_AUTO_CLEAR_ENABLED = "auto_clear_enabled"
CONF_AUTO_MODE = "auto_mode"
CONF_AUTOCONF = "autoconf"
CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration"
CONF_AUTOMATION_ID = "automation_id"
CONF_AVAILABILITY = "availability"
CONF_AWAY = "away"
@ -480,7 +477,6 @@ CONF_MAX_VALUE = "max_value"
CONF_MAX_VOLTAGE = "max_voltage"
CONF_MDNS = "mdns"
CONF_MEASUREMENT_DURATION = "measurement_duration"
CONF_MEASUREMENT_MODE = "measurement_mode"
CONF_MEASUREMENT_SEQUENCE_NUMBER = "measurement_sequence_number"
CONF_MEDIA_PLAYER = "media_player"
CONF_MEDIUM = "medium"

View File

@ -518,8 +518,6 @@ class EsphomeCore:
self.verbose = False
# Whether ESPHome was started in quiet mode
self.quiet = False
# A list of all known ID classes
self.id_classes = {}
def reset(self):
from esphome.pins import PIN_SCHEMA_REGISTRY

View File

@ -148,7 +148,7 @@
#endif
#ifdef USE_ESP_IDF
#define USE_ESP_IDF_VERSION_CODE VERSION_CODE(5, 1, 6)
#define USE_ESP_IDF_VERSION_CODE VERSION_CODE(5, 1, 5)
#endif
#if defined(USE_ESP32_VARIANT_ESP32S2)

View File

@ -789,17 +789,13 @@ class MockObj(Expression):
def class_(self, name: str, *parents: "MockObjClass") -> "MockObjClass":
op = "" if self.op == "" else "::"
result = MockObjClass(f"{self.base}{op}{name}", ".", parents=parents)
CORE.id_classes[str(result)] = result
return result
return MockObjClass(f"{self.base}{op}{name}", ".", parents=parents)
def struct(self, name: str) -> "MockObjClass":
return self.class_(name)
def enum(self, name: str, is_class: bool = False) -> "MockObj":
result = MockObjEnum(enum=name, is_class=is_class, base=self.base, op=self.op)
CORE.id_classes[str(result)] = result
return result
return MockObjEnum(enum=name, is_class=is_class, base=self.base, op=self.op)
def operator(self, name: str) -> "MockObj":
"""Various other operations.

View File

@ -38,7 +38,7 @@ import yaml
from yaml.nodes import Node
from esphome import const, platformio_api, yaml_util
from esphome.helpers import get_bool_env, mkdir_p, sort_ip_addresses
from esphome.helpers import get_bool_env, mkdir_p
from esphome.storage_json import (
StorageJSON,
archive_storage_path,
@ -336,7 +336,7 @@ class EsphomePortCommandWebSocket(EsphomeCommandWebSocket):
# Use the IP address if available but only
# if the API is loaded and the device is online
# since MQTT logging will not work otherwise
port = sort_ip_addresses(address_list)[0]
port = address_list[0]
elif (
entry.address
and (
@ -347,7 +347,7 @@ class EsphomePortCommandWebSocket(EsphomeCommandWebSocket):
and not isinstance(address_list, Exception)
):
# If mdns is not available, try to use the DNS cache
port = sort_ip_addresses(address_list)[0]
port = address_list[0]
return [
*DASHBOARD_COMMAND,

View File

@ -200,45 +200,6 @@ def resolve_ip_address(host, port):
return res
def sort_ip_addresses(address_list: list[str]) -> list[str]:
"""Takes a list of IP addresses in string form, e.g. from mDNS or MQTT,
and sorts them into the best order to actually try connecting to them.
This is roughly based on RFC6724 but a lot simpler: First we choose
IPv6 addresses, then Legacy IP addresses, and lowest priority is
link-local IPv6 addresses that don't have a link specified (which
are useless, but mDNS does provide them in that form). Addresses
which cannot be parsed are silently dropped.
"""
import socket
# First "resolve" all the IP addresses to getaddrinfo() tuples of the form
# (family, type, proto, canonname, sockaddr)
res: list[
tuple[
int,
int,
int,
Union[str, None],
Union[tuple[str, int], tuple[str, int, int, int]],
]
] = []
for addr in address_list:
# This should always work as these are supposed to be IP addresses
try:
res += socket.getaddrinfo(
addr, 0, proto=socket.IPPROTO_TCP, flags=socket.AI_NUMERICHOST
)
except OSError:
_LOGGER.info("Failed to parse IP address '%s'", addr)
# Now use that information to sort them.
res.sort(key=addr_preference_)
# Finally, turn the getaddrinfo() tuples back into plain hostnames.
return [socket.getnameinfo(r[4], socket.NI_NUMERICHOST)[0] for r in res]
def get_bool_env(var, default=False):
value = os.getenv(var, default)
if isinstance(value, str):

View File

@ -91,10 +91,6 @@ class ComponentManifest:
def codeowners(self) -> list[str]:
return getattr(self.module, "CODEOWNERS", [])
@property
def instance_type(self) -> list[str]:
return getattr(self.module, "INSTANCE_TYPE", None)
@property
def final_validate_schema(self) -> Optional[Callable[[ConfigType], None]]:
"""Components can declare a `FINAL_VALIDATE_SCHEMA` cv.Schema that gets called

View File

@ -142,7 +142,7 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script
extends = common:idf
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.06/platform-espressif32.zip
platform_packages =
pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v5.1.6/esp-idf-v5.1.6.zip
pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v5.1.5/esp-idf-v5.1.5.zip
framework = espidf
lib_deps =

View File

@ -13,7 +13,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
esptool==4.8.1
click==8.1.7
esphome-dashboard==20250415.0
aioesphomeapi==30.0.1
aioesphomeapi==29.10.0
zeroconf==0.146.5
puremagic==1.28
ruamel.yaml==0.18.10 # dashboard_import

View File

@ -1,12 +1,12 @@
pylint==3.3.6
flake8==7.2.0 # also change in .pre-commit-config.yaml when updating
ruff==0.11.6 # also change in .pre-commit-config.yaml when updating
ruff==0.11.2 # also change in .pre-commit-config.yaml when updating
pyupgrade==3.19.1 # also change in .pre-commit-config.yaml when updating
pre-commit
# Unit tests
pytest==8.3.5
pytest-cov==6.1.1
pytest-cov==6.0.0
pytest-mock==3.14.0
pytest-asyncio==0.26.0
asyncmock==0.4.2

View File

@ -1,20 +1,4 @@
#!/usr/bin/env python3
from __future__ import annotations
from abc import ABC, abstractmethod
import os
from pathlib import Path
import re
from subprocess import call
import sys
from textwrap import dedent
from typing import Any
# Generate with
# protoc --python_out=script/api_protobuf -I esphome/components/api/ api_options.proto
import aioesphomeapi.api_options_pb2 as pb
import google.protobuf.descriptor_pb2 as descriptor
"""Python 3 script to automatically generate C++ classes for ESPHome's native API.
It's pretty crappy spaghetti code, but it works.
@ -33,14 +17,25 @@ then run this script with python3 and the files
will be generated, they still need to be formatted
"""
from abc import ABC, abstractmethod
import os
from pathlib import Path
import re
from subprocess import call
import sys
from textwrap import dedent
# Generate with
# protoc --python_out=script/api_protobuf -I esphome/components/api/ api_options.proto
import aioesphomeapi.api_options_pb2 as pb
import google.protobuf.descriptor_pb2 as descriptor
FILE_HEADER = """// This file was automatically generated with a tool.
// See scripts/api_protobuf/api_protobuf.py
"""
def indent_list(text: str, padding: str = " ") -> list[str]:
"""Indent each line of the given text with the specified padding."""
def indent_list(text, padding=" "):
lines = []
for line in text.splitlines():
if line == "":
@ -53,62 +48,54 @@ def indent_list(text: str, padding: str = " ") -> list[str]:
return lines
def indent(text: str, padding: str = " ") -> str:
def indent(text, padding=" "):
return "\n".join(indent_list(text, padding))
def camel_to_snake(name: str) -> str:
def camel_to_snake(name):
# https://stackoverflow.com/a/1176023
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
class TypeInfo(ABC):
"""Base class for all type information."""
def __init__(self, field: descriptor.FieldDescriptorProto) -> None:
def __init__(self, field):
self._field = field
@property
def default_value(self) -> str:
"""Get the default value."""
def default_value(self):
return ""
@property
def name(self) -> str:
"""Get the name of the field."""
def name(self):
return self._field.name
@property
def arg_name(self) -> str:
"""Get the argument name."""
def arg_name(self):
return self.name
@property
def field_name(self) -> str:
"""Get the field name."""
def field_name(self):
return self.name
@property
def number(self) -> int:
"""Get the field number."""
def number(self):
return self._field.number
@property
def repeated(self) -> bool:
"""Check if the field is repeated."""
def repeated(self):
return self._field.label == 3
@property
def cpp_type(self) -> str:
def cpp_type(self):
raise NotImplementedError
@property
def reference_type(self) -> str:
def reference_type(self):
return f"{self.cpp_type} "
@property
def const_reference_type(self) -> str:
def const_reference_type(self):
return f"{self.cpp_type} "
@property
@ -184,31 +171,28 @@ class TypeInfo(ABC):
decode_64bit = None
@property
def encode_content(self) -> str:
def encode_content(self):
return f"buffer.{self.encode_func}({self.number}, this->{self.field_name});"
encode_func = None
@property
def dump_content(self) -> str:
def dump_content(self):
o = f'out.append(" {self.name}: ");\n'
o += self.dump(f"this->{self.field_name}") + "\n"
o += 'out.append("\\n");\n'
return o
@abstractmethod
def dump(self, name: str) -> str:
"""Dump the value to the output."""
def dump(self, name: str):
pass
TYPE_INFO: dict[int, TypeInfo] = {}
TYPE_INFO = {}
def register_type(name: int):
"""Decorator to register a type with a name and number."""
def func(value: TypeInfo) -> TypeInfo:
"""Register the type with the given name and number."""
def register_type(name):
def func(value):
TYPE_INFO[name] = value
return value
@ -222,7 +206,7 @@ class DoubleType(TypeInfo):
decode_64bit = "value.as_double()"
encode_func = "encode_double"
def dump(self, name: str) -> str:
def dump(self, name):
o = f'sprintf(buffer, "%g", {name});\n'
o += "out.append(buffer);"
return o
@ -235,7 +219,7 @@ class FloatType(TypeInfo):
decode_32bit = "value.as_float()"
encode_func = "encode_float"
def dump(self, name: str) -> str:
def dump(self, name):
o = f'sprintf(buffer, "%g", {name});\n'
o += "out.append(buffer);"
return o
@ -248,7 +232,7 @@ class Int64Type(TypeInfo):
decode_varint = "value.as_int64()"
encode_func = "encode_int64"
def dump(self, name: str) -> str:
def dump(self, name):
o = f'sprintf(buffer, "%lld", {name});\n'
o += "out.append(buffer);"
return o
@ -261,7 +245,7 @@ class UInt64Type(TypeInfo):
decode_varint = "value.as_uint64()"
encode_func = "encode_uint64"
def dump(self, name: str) -> str:
def dump(self, name):
o = f'sprintf(buffer, "%llu", {name});\n'
o += "out.append(buffer);"
return o
@ -274,7 +258,7 @@ class Int32Type(TypeInfo):
decode_varint = "value.as_int32()"
encode_func = "encode_int32"
def dump(self, name: str) -> str:
def dump(self, name):
o = f'sprintf(buffer, "%" PRId32, {name});\n'
o += "out.append(buffer);"
return o
@ -287,7 +271,7 @@ class Fixed64Type(TypeInfo):
decode_64bit = "value.as_fixed64()"
encode_func = "encode_fixed64"
def dump(self, name: str) -> str:
def dump(self, name):
o = f'sprintf(buffer, "%llu", {name});\n'
o += "out.append(buffer);"
return o
@ -300,7 +284,7 @@ class Fixed32Type(TypeInfo):
decode_32bit = "value.as_fixed32()"
encode_func = "encode_fixed32"
def dump(self, name: str) -> str:
def dump(self, name):
o = f'sprintf(buffer, "%" PRIu32, {name});\n'
o += "out.append(buffer);"
return o
@ -313,7 +297,7 @@ class BoolType(TypeInfo):
decode_varint = "value.as_bool()"
encode_func = "encode_bool"
def dump(self, name: str) -> str:
def dump(self, name):
o = f"out.append(YESNO({name}));"
return o
@ -335,28 +319,28 @@ class StringType(TypeInfo):
@register_type(11)
class MessageType(TypeInfo):
@property
def cpp_type(self) -> str:
def cpp_type(self):
return self._field.type_name[1:]
default_value = ""
@property
def reference_type(self) -> str:
def reference_type(self):
return f"{self.cpp_type} &"
@property
def const_reference_type(self) -> str:
def const_reference_type(self):
return f"const {self.cpp_type} &"
@property
def encode_func(self) -> str:
def encode_func(self):
return f"encode_message<{self.cpp_type}>"
@property
def decode_length(self) -> str:
def decode_length(self):
return f"value.as_message<{self.cpp_type}>()"
def dump(self, name: str) -> str:
def dump(self, name):
o = f"{name}.dump_to(out);"
return o
@ -370,7 +354,7 @@ class BytesType(TypeInfo):
decode_length = "value.as_string()"
encode_func = "encode_string"
def dump(self, name: str) -> str:
def dump(self, name):
o = f'out.append("\'").append({name}).append("\'");'
return o
@ -382,7 +366,7 @@ class UInt32Type(TypeInfo):
decode_varint = "value.as_uint32()"
encode_func = "encode_uint32"
def dump(self, name: str) -> str:
def dump(self, name):
o = f'sprintf(buffer, "%" PRIu32, {name});\n'
o += "out.append(buffer);"
return o
@ -391,20 +375,20 @@ class UInt32Type(TypeInfo):
@register_type(14)
class EnumType(TypeInfo):
@property
def cpp_type(self) -> str:
def cpp_type(self):
return f"enums::{self._field.type_name[1:]}"
@property
def decode_varint(self) -> str:
def decode_varint(self):
return f"value.as_enum<{self.cpp_type}>()"
default_value = ""
@property
def encode_func(self) -> str:
def encode_func(self):
return f"encode_enum<{self.cpp_type}>"
def dump(self, name: str) -> str:
def dump(self, name):
o = f"out.append(proto_enum_to_string<{self.cpp_type}>({name}));"
return o
@ -416,7 +400,7 @@ class SFixed32Type(TypeInfo):
decode_32bit = "value.as_sfixed32()"
encode_func = "encode_sfixed32"
def dump(self, name: str) -> str:
def dump(self, name):
o = f'sprintf(buffer, "%" PRId32, {name});\n'
o += "out.append(buffer);"
return o
@ -429,7 +413,7 @@ class SFixed64Type(TypeInfo):
decode_64bit = "value.as_sfixed64()"
encode_func = "encode_sfixed64"
def dump(self, name: str) -> str:
def dump(self, name):
o = f'sprintf(buffer, "%lld", {name});\n'
o += "out.append(buffer);"
return o
@ -442,7 +426,7 @@ class SInt32Type(TypeInfo):
decode_varint = "value.as_sint32()"
encode_func = "encode_sint32"
def dump(self, name: str) -> str:
def dump(self, name):
o = f'sprintf(buffer, "%" PRId32, {name});\n'
o += "out.append(buffer);"
return o
@ -455,27 +439,27 @@ class SInt64Type(TypeInfo):
decode_varint = "value.as_sint64()"
encode_func = "encode_sint64"
def dump(self, name: str) -> str:
def dump(self, name):
o = f'sprintf(buffer, "%lld", {name});\n'
o += "out.append(buffer);"
return o
class RepeatedTypeInfo(TypeInfo):
def __init__(self, field: descriptor.FieldDescriptorProto) -> None:
def __init__(self, field):
super().__init__(field)
self._ti: TypeInfo = TYPE_INFO[field.type](field)
self._ti = TYPE_INFO[field.type](field)
@property
def cpp_type(self) -> str:
def cpp_type(self):
return f"std::vector<{self._ti.cpp_type}>"
@property
def reference_type(self) -> str:
def reference_type(self):
return f"{self.cpp_type} &"
@property
def const_reference_type(self) -> str:
def const_reference_type(self):
return f"const {self.cpp_type} &"
@property
@ -531,19 +515,19 @@ class RepeatedTypeInfo(TypeInfo):
)
@property
def _ti_is_bool(self) -> bool:
def _ti_is_bool(self):
# std::vector is specialized for bool, reference does not work
return isinstance(self._ti, BoolType)
@property
def encode_content(self) -> str:
def encode_content(self):
o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n"
o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n"
o += "}"
return o
@property
def dump_content(self) -> str:
def dump_content(self):
o = f"for (const auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n"
o += f' out.append(" {self.name}: ");\n'
o += indent(self._ti.dump("it")) + "\n"
@ -555,8 +539,7 @@ class RepeatedTypeInfo(TypeInfo):
pass
def build_enum_type(desc) -> tuple[str, str]:
"""Builds the enum type."""
def build_enum_type(desc):
name = desc.name
out = f"enum {name} : uint32_t {{\n"
for v in desc.value:
@ -578,15 +561,15 @@ def build_enum_type(desc) -> tuple[str, str]:
return out, cpp
def build_message_type(desc: descriptor.DescriptorProto) -> tuple[str, str]:
public_content: list[str] = []
protected_content: list[str] = []
decode_varint: list[str] = []
decode_length: list[str] = []
decode_32bit: list[str] = []
decode_64bit: list[str] = []
encode: list[str] = []
dump: list[str] = []
def build_message_type(desc):
public_content = []
protected_content = []
decode_varint = []
decode_length = []
decode_32bit = []
decode_64bit = []
encode = []
dump = []
for field in desc.field:
if field.label == 3:
@ -704,35 +687,27 @@ SOURCE_BOTH = 0
SOURCE_SERVER = 1
SOURCE_CLIENT = 2
RECEIVE_CASES: dict[int, str] = {}
RECEIVE_CASES = {}
ifdefs: dict[str, str] = {}
ifdefs = {}
def get_opt(
desc: descriptor.DescriptorProto,
opt: descriptor.MessageOptions,
default: Any = None,
) -> Any:
"""Get the option from the descriptor."""
def get_opt(desc, opt, default=None):
if not desc.options.HasExtension(opt):
return default
return desc.options.Extensions[opt]
def build_service_message_type(
mt: descriptor.DescriptorProto,
) -> tuple[str, str] | None:
"""Builds the service message type."""
def build_service_message_type(mt):
snake = camel_to_snake(mt.name)
id_: int | None = get_opt(mt, pb.id)
id_ = get_opt(mt, pb.id)
if id_ is None:
return None
source: int = get_opt(mt, pb.source, 0)
source = get_opt(mt, pb.source, 0)
ifdef: str | None = get_opt(mt, pb.ifdef)
log: bool = get_opt(mt, pb.log, True)
ifdef = get_opt(mt, pb.ifdef)
log = get_opt(mt, pb.log, True)
hout = ""
cout = ""
@ -779,8 +754,7 @@ def build_service_message_type(
return hout, cout
def main() -> None:
"""Main function to generate the C++ classes."""
def main():
cwd = Path(__file__).resolve().parent
root = cwd.parent.parent / "esphome" / "components" / "api"
prot_file = root / "api.protoc"
@ -985,7 +959,7 @@ def main() -> None:
try:
import clang_format
def exec_clang_format(path: Path) -> None:
def exec_clang_format(path):
clang_format_path = os.path.join(
os.path.dirname(clang_format.__file__), "data", "bin", "clang-format"
)

View File

@ -21,8 +21,6 @@ pre-commit install
script/platformio_install_deps.py platformio.ini --libraries --tools --platforms
mkdir .temp
echo
echo
echo "Virtual environment created. Run 'source $location' to use it."

View File

@ -53,7 +53,7 @@ start_esphome() {
echo "> [$target_component] [$test_name] [$target_platform_with_version]"
set -x
# TODO: Validate escape of Command line substitution value
python3 -m esphome -s component_name $target_component -s component_dir ../../components/$target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file
python -m esphome -s component_name $target_component -s component_dir ../../components/$target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file
{ set +x; } 2>/dev/null
}

View File

@ -26,17 +26,3 @@ binary_sensor:
threshold: 100
filters:
- invert:
- platform: analog_threshold
name: Analog Threshold 3
sensor_id: template_sensor
threshold: !lambda return 100;
filters:
- invert:
- platform: analog_threshold
name: Analog Threshold 4
sensor_id: template_sensor
threshold:
upper: !lambda return 110;
lower: !lambda return 90;
filters:
- invert:

View File

@ -1,10 +0,0 @@
packages:
common: !include common.yaml
wifi:
ssid: MySSID
password: password1
api:
encryption:
key: !remove

View File

@ -1,71 +0,0 @@
image:
grayscale:
alpha_channel:
- file: ../../pnglogo.png
id: image_1
resize: 50x50
- file: ../../pnglogo.png
id: image_2
resize: 50x50
mapping:
- id: weather_map
from: string
to: "image::Image"
entries:
clear-night: image_1
sunny: image_2
- id: weather_map_1
from: string
to: esphome::image::Image
entries:
clear-night: image_1
sunny: image_2
- id: weather_map_2
from: string
to: image
entries:
clear-night: image_1
sunny: image_2
- id: int_map
from: int
to: string
entries:
1: "one"
2: "two"
3: "three"
77: "seventy-seven"
- id: string_map
from: string
to: int
entries:
one: 1
two: 2
three: 3
seventy-seven: 77
- id: color_map
from: string
to: color
entries:
red: red_id
blue: blue_id
green: green_id
color:
- id: red_id
red: 1.0
green: 0.0
blue: 0.0
- id: green_id
red: 0.0
green: 1.0
blue: 0.0
- id: blue_id
red: 0.0
green: 0.0
blue: 1.0
display:
lambda: |-
it.image(0, 0, id(weather_map)[0]);
it.image(0, 100, id(weather_map)[1]);

View File

@ -1,17 +0,0 @@
spi:
- id: spi_main_lcd
clk_pin: 16
mosi_pin: 17
miso_pin: 15
display:
- platform: ili9xxx
id: main_lcd
model: ili9342
cs_pin: 12
dc_pin: 13
reset_pin: 21
invert_colors: false
packages:
map: !include common.yaml

View File

@ -1,17 +0,0 @@
spi:
- id: spi_main_lcd
clk_pin: 6
mosi_pin: 7
miso_pin: 5
display:
- platform: ili9xxx
id: main_lcd
model: ili9342
cs_pin: 8
dc_pin: 9
reset_pin: 10
invert_colors: false
packages:
map: !include common.yaml

View File

@ -1,17 +0,0 @@
spi:
- id: spi_main_lcd
clk_pin: 6
mosi_pin: 7
miso_pin: 5
display:
- platform: ili9xxx
id: main_lcd
model: ili9342
cs_pin: 8
dc_pin: 9
reset_pin: 10
invert_colors: false
packages:
map: !include common.yaml

View File

@ -1,17 +0,0 @@
spi:
- id: spi_main_lcd
clk_pin: 16
mosi_pin: 17
miso_pin: 15
display:
- platform: ili9xxx
id: main_lcd
model: ili9342
cs_pin: 12
dc_pin: 13
reset_pin: 21
invert_colors: false
packages:
map: !include common.yaml

View File

@ -1,17 +0,0 @@
spi:
- id: spi_main_lcd
clk_pin: 14
mosi_pin: 13
miso_pin: 12
display:
- platform: ili9xxx
id: main_lcd
model: ili9342
cs_pin: 5
dc_pin: 15
reset_pin: 16
invert_colors: false
packages:
map: !include common.yaml

View File

@ -1,12 +0,0 @@
display:
- platform: sdl
id: sdl_display
update_interval: 1s
auto_clear_enabled: false
show_test_card: true
dimensions:
width: 450
height: 600
packages:
map: !include common.yaml

View File

@ -1,17 +0,0 @@
spi:
- id: spi_main_lcd
clk_pin: 2
mosi_pin: 3
miso_pin: 4
display:
- platform: ili9xxx
id: main_lcd
model: ili9342
cs_pin: 20
dc_pin: 21
reset_pin: 22
invert_colors: false
packages:
map: !include common.yaml

View File

@ -1,13 +0,0 @@
i2c:
- id: i2c_pm2005
scl: ${scl_pin}
sda: ${sda_pin}
sensor:
- platform: pm2005
pm_1_0:
name: PM1.0
pm_2_5:
name: PM2.5
pm_10_0:
name: PM10.0

View File

@ -1,5 +0,0 @@
substitutions:
scl_pin: GPIO16
sda_pin: GPIO17
<<: !include common.yaml

View File

@ -1,5 +0,0 @@
substitutions:
scl_pin: GPIO5
sda_pin: GPIO4
<<: !include common.yaml

View File

@ -1,5 +0,0 @@
substitutions:
scl_pin: GPIO5
sda_pin: GPIO4
<<: !include common.yaml

View File

@ -1,5 +0,0 @@
substitutions:
scl_pin: GPIO16
sda_pin: GPIO17
<<: !include common.yaml

View File

@ -1,5 +0,0 @@
substitutions:
scl_pin: GPIO5
sda_pin: GPIO4
<<: !include common.yaml

View File

@ -1,5 +0,0 @@
substitutions:
scl_pin: GPIO5
sda_pin: GPIO4
<<: !include common.yaml

View File

@ -1,3 +1,6 @@
substitutions:
verify_ssl: "false"
esphome:
name: livingroomdevice
friendly_name: Living Room Device
@ -126,14 +129,6 @@ valve:
optimistic: true
has_position: true
remote_transmitter:
pin: ${pin}
carrier_duty_percent: 50%
climate:
- platform: climate_ir_lg
name: LG Climate
prometheus:
include_internal: true
relabel:

View File

@ -1,7 +1,3 @@
substitutions:
verify_ssl: "false"
pin: GPIO5
<<: !include common.yaml
i2s_audio:

View File

@ -1,5 +1 @@
substitutions:
verify_ssl: "false"
pin: GPIO2
<<: !include common.yaml

View File

@ -1,5 +1 @@
substitutions:
verify_ssl: "false"
pin: GPIO2
<<: !include common.yaml

View File

@ -1,5 +1 @@
substitutions:
verify_ssl: "false"
pin: GPIO2
<<: !include common.yaml

View File

@ -1,5 +1 @@
substitutions:
verify_ssl: "false"
pin: GPIO5
<<: !include common.yaml

View File

@ -17,13 +17,3 @@ sensor:
text_sensor:
- platform: uptime
name: Uptime Text
- platform: uptime
name: Uptime Text With Separator
format:
separator: "-"
expand: true
days: "Days"
hours: "H"
minutes: "M"
seconds: "S"
update_interval: 10s

View File

@ -541,26 +541,6 @@ display:
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
# 5.65 inch displays
- platform: waveshare_epaper
id: epd_5_65
model: 5.65in-f
spi_id: spi_waveshare_epaper
cs_pin:
allow_other_uses: true
number: ${cs_pin}
dc_pin:
allow_other_uses: true
number: ${dc_pin}
busy_pin:
allow_other_uses: true
number: ${busy_pin}
reset_pin:
allow_other_uses: true
number: ${reset_pin}
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
# 5.83 inch displays
- platform: waveshare_epaper
id: epd_5_83

View File

@ -284,93 +284,3 @@ def test_split_default(framework, platform, variant, full, idf, arduino, simple)
assert schema({}).get("idf") == idf
assert schema({}).get("arduino") == arduino
assert schema({}).get("simple") == simple
@pytest.mark.parametrize(
"framework, platform, message",
[
("esp-idf", PLATFORM_ESP32, "ESP32 using esp-idf framework"),
("arduino", PLATFORM_ESP32, "ESP32 using arduino framework"),
("arduino", PLATFORM_ESP8266, "ESP8266 using arduino framework"),
("arduino", PLATFORM_RP2040, "RP2040 using arduino framework"),
("arduino", PLATFORM_BK72XX, "BK72XX using arduino framework"),
("host", PLATFORM_HOST, "HOST using host framework"),
],
)
def test_require_framework_version(framework, platform, message):
import voluptuous as vol
from esphome.const import (
KEY_CORE,
KEY_FRAMEWORK_VERSION,
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
)
CORE.data[KEY_CORE] = {}
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = platform
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = framework
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = config_validation.Version(1, 0, 0)
assert (
config_validation.require_framework_version(
esp_idf=config_validation.Version(0, 5, 0),
esp32_arduino=config_validation.Version(0, 5, 0),
esp8266_arduino=config_validation.Version(0, 5, 0),
rp2040_arduino=config_validation.Version(0, 5, 0),
bk72xx_arduino=config_validation.Version(0, 5, 0),
host=config_validation.Version(0, 5, 0),
extra_message="test 1",
)("test")
== "test"
)
with pytest.raises(
vol.error.Invalid,
match="This feature requires at least framework version 2.0.0. test 2",
):
config_validation.require_framework_version(
esp_idf=config_validation.Version(2, 0, 0),
esp32_arduino=config_validation.Version(2, 0, 0),
esp8266_arduino=config_validation.Version(2, 0, 0),
rp2040_arduino=config_validation.Version(2, 0, 0),
bk72xx_arduino=config_validation.Version(2, 0, 0),
host=config_validation.Version(2, 0, 0),
extra_message="test 2",
)("test")
assert (
config_validation.require_framework_version(
esp_idf=config_validation.Version(1, 5, 0),
esp32_arduino=config_validation.Version(1, 5, 0),
esp8266_arduino=config_validation.Version(1, 5, 0),
rp2040_arduino=config_validation.Version(1, 5, 0),
bk72xx_arduino=config_validation.Version(1, 5, 0),
host=config_validation.Version(1, 5, 0),
max_version=True,
extra_message="test 3",
)("test")
== "test"
)
with pytest.raises(
vol.error.Invalid,
match="This feature requires framework version 0.5.0 or lower. test 4",
):
config_validation.require_framework_version(
esp_idf=config_validation.Version(0, 5, 0),
esp32_arduino=config_validation.Version(0, 5, 0),
esp8266_arduino=config_validation.Version(0, 5, 0),
rp2040_arduino=config_validation.Version(0, 5, 0),
bk72xx_arduino=config_validation.Version(0, 5, 0),
host=config_validation.Version(0, 5, 0),
max_version=True,
extra_message="test 4",
)("test")
with pytest.raises(
vol.error.Invalid, match=f"This feature is incompatible with {message}. test 5"
):
config_validation.require_framework_version(
extra_message="test 5",
)("test")

View File

@ -267,13 +267,3 @@ def test_sanitize(text, expected):
actual = helpers.sanitize(text)
assert actual == expected
@pytest.mark.parametrize(
"text, expected",
((["127.0.0.1", "fe80::1", "2001::2"], ["2001::2", "127.0.0.1", "fe80::1"]),),
)
def test_sort_ip_addresses(text: list[str], expected: list[str]) -> None:
actual = helpers.sort_ip_addresses(text)
assert actual == expected