mirror of
https://github.com/esphome/esphome.git
synced 2025-04-19 21:27:19 +00:00
Compare commits
No commits in common. "dev" and "2025.4.0b2" have entirely different histories.
dev
...
2025.4.0b2
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -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 }}
|
||||
|
||||
|
@ -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
|
||||
|
@ -375,12 +375,10 @@ def upload_program(config, args, host):
|
||||
password = ota_conf.get(CONF_PASSWORD, "")
|
||||
|
||||
if (
|
||||
CONF_MQTT in config # pylint: disable=too-many-boolean-expressions
|
||||
not is_ip_address(CORE.address) # pylint: disable=too-many-boolean-expressions
|
||||
and (get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED])
|
||||
and CONF_MQTT in config
|
||||
and (not args.device or args.device in ("MQTT", "OTA"))
|
||||
and (
|
||||
((config[CONF_MDNS][CONF_DISABLED]) and not is_ip_address(CORE.address))
|
||||
or get_port_type(host) == "MQTT"
|
||||
)
|
||||
):
|
||||
from esphome import mqtt
|
||||
|
||||
|
@ -128,7 +128,7 @@ void AM2315C::update() {
|
||||
data[2] = 0x00;
|
||||
if (this->write(data, 3) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Write failed!");
|
||||
this->status_set_warning();
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -138,12 +138,12 @@ void AM2315C::update() {
|
||||
uint8_t status = 0;
|
||||
if (this->read(&status, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Read failed!");
|
||||
this->status_set_warning();
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if ((status & 0x80) == 0x80) {
|
||||
ESP_LOGE(TAG, "HW still busy!");
|
||||
this->status_set_warning();
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -151,7 +151,7 @@ void AM2315C::update() {
|
||||
uint8_t data[7];
|
||||
if (this->read(data, 7) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Read failed!");
|
||||
this->status_set_warning();
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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]))
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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()) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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),
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
|
@ -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_{};
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -1 +0,0 @@
|
||||
"""PM2005/2105 component for ESPHome."""
|
@ -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
|
@ -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
|
@ -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))
|
@ -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
|
||||
|
@ -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_;
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -52,8 +52,9 @@ void Sml::loop() {
|
||||
break;
|
||||
|
||||
// remove start/end sequence
|
||||
this->process_sml_file_(
|
||||
BytesView(this->sml_data_).subview(START_SEQ.size(), this->sml_data_.size() - START_SEQ.size() - 8));
|
||||
this->sml_data_.erase(this->sml_data_.begin(), this->sml_data_.begin() + START_SEQ.size());
|
||||
this->sml_data_.resize(this->sml_data_.size() - 8);
|
||||
this->process_sml_file_(this->sml_data_);
|
||||
}
|
||||
break;
|
||||
};
|
||||
@ -65,8 +66,8 @@ void Sml::add_on_data_callback(std::function<void(std::vector<uint8_t>, bool)> &
|
||||
this->data_callbacks_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void Sml::process_sml_file_(const BytesView &sml_data) {
|
||||
SmlFile sml_file(sml_data);
|
||||
void Sml::process_sml_file_(const bytes &sml_data) {
|
||||
SmlFile sml_file = SmlFile(sml_data);
|
||||
std::vector<ObisInfo> obis_info = sml_file.get_obis_info();
|
||||
this->publish_obis_info_(obis_info);
|
||||
|
||||
@ -74,7 +75,6 @@ void Sml::process_sml_file_(const BytesView &sml_data) {
|
||||
}
|
||||
|
||||
void Sml::log_obis_info_(const std::vector<ObisInfo> &obis_info_vec) {
|
||||
#ifdef ESPHOME_LOG_HAS_DEBUG
|
||||
ESP_LOGD(TAG, "OBIS info:");
|
||||
for (auto const &obis_info : obis_info_vec) {
|
||||
std::string info;
|
||||
@ -83,7 +83,6 @@ void Sml::log_obis_info_(const std::vector<ObisInfo> &obis_info_vec) {
|
||||
info += " [0x" + bytes_repr(obis_info.value) + "]";
|
||||
ESP_LOGD(TAG, "%s", info.c_str());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Sml::publish_obis_info_(const std::vector<ObisInfo> &obis_info_vec) {
|
||||
@ -93,11 +92,10 @@ void Sml::publish_obis_info_(const std::vector<ObisInfo> &obis_info_vec) {
|
||||
}
|
||||
|
||||
void Sml::publish_value_(const ObisInfo &obis_info) {
|
||||
const auto obis_code = obis_info.code_repr();
|
||||
for (auto const &sml_listener : sml_listeners_) {
|
||||
if ((!sml_listener->server_id.empty()) && (bytes_repr(obis_info.server_id) != sml_listener->server_id))
|
||||
continue;
|
||||
if (obis_code != sml_listener->obis_code)
|
||||
if (obis_info.code_repr() != sml_listener->obis_code)
|
||||
continue;
|
||||
sml_listener->publish_val(obis_info);
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ class Sml : public Component, public uart::UARTDevice {
|
||||
void add_on_data_callback(std::function<void(std::vector<uint8_t>, bool)> &&callback);
|
||||
|
||||
protected:
|
||||
void process_sml_file_(const BytesView &sml_data);
|
||||
void process_sml_file_(const bytes &sml_data);
|
||||
void log_obis_info_(const std::vector<ObisInfo> &obis_info_vec);
|
||||
void publish_obis_info_(const std::vector<ObisInfo> &obis_info_vec);
|
||||
char check_start_end_bytes_(uint8_t byte);
|
||||
|
@ -5,17 +5,17 @@
|
||||
namespace esphome {
|
||||
namespace sml {
|
||||
|
||||
SmlFile::SmlFile(const BytesView &buffer) : buffer_(buffer) {
|
||||
SmlFile::SmlFile(bytes buffer) : buffer_(std::move(buffer)) {
|
||||
// extract messages
|
||||
this->pos_ = 0;
|
||||
while (this->pos_ < this->buffer_.size()) {
|
||||
if (this->buffer_[this->pos_] == 0x00)
|
||||
break; // EndOfSmlMsg
|
||||
|
||||
SmlNode message;
|
||||
SmlNode message = SmlNode();
|
||||
if (!this->setup_node(&message))
|
||||
break;
|
||||
this->messages.emplace_back(std::move(message));
|
||||
this->messages.emplace_back(message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,20 +62,22 @@ bool SmlFile::setup_node(SmlNode *node) {
|
||||
return false;
|
||||
|
||||
node->type = type;
|
||||
node->nodes.clear();
|
||||
node->value_bytes.clear();
|
||||
|
||||
if (type == SML_LIST) {
|
||||
node->nodes.reserve(length);
|
||||
for (size_t i = 0; i != length; i++) {
|
||||
SmlNode child_node;
|
||||
SmlNode child_node = SmlNode();
|
||||
if (!this->setup_node(&child_node))
|
||||
return false;
|
||||
node->nodes.emplace_back(std::move(child_node));
|
||||
node->nodes.emplace_back(child_node);
|
||||
}
|
||||
} else {
|
||||
// Value starts at the current position
|
||||
// Value ends "length" bytes later,
|
||||
// (since the TL field is counted but already subtracted from length)
|
||||
node->value_bytes = buffer_.subview(this->pos_, length);
|
||||
node->value_bytes = bytes(this->buffer_.begin() + this->pos_, this->buffer_.begin() + this->pos_ + length);
|
||||
// Increment the pointer past all consumed bytes
|
||||
this->pos_ += length;
|
||||
}
|
||||
@ -85,14 +87,14 @@ bool SmlFile::setup_node(SmlNode *node) {
|
||||
std::vector<ObisInfo> SmlFile::get_obis_info() {
|
||||
std::vector<ObisInfo> obis_info;
|
||||
for (auto const &message : messages) {
|
||||
const auto &message_body = message.nodes[3];
|
||||
SmlNode message_body = message.nodes[3];
|
||||
uint16_t message_type = bytes_to_uint(message_body.nodes[0].value_bytes);
|
||||
if (message_type != SML_GET_LIST_RES)
|
||||
continue;
|
||||
|
||||
const auto &get_list_response = message_body.nodes[1];
|
||||
const auto &server_id = get_list_response.nodes[1].value_bytes;
|
||||
const auto &val_list = get_list_response.nodes[4];
|
||||
SmlNode get_list_response = message_body.nodes[1];
|
||||
bytes server_id = get_list_response.nodes[1].value_bytes;
|
||||
SmlNode val_list = get_list_response.nodes[4];
|
||||
|
||||
for (auto const &val_list_entry : val_list.nodes) {
|
||||
obis_info.emplace_back(server_id, val_list_entry);
|
||||
@ -101,7 +103,7 @@ std::vector<ObisInfo> SmlFile::get_obis_info() {
|
||||
return obis_info;
|
||||
}
|
||||
|
||||
std::string bytes_repr(const BytesView &buffer) {
|
||||
std::string bytes_repr(const bytes &buffer) {
|
||||
std::string repr;
|
||||
for (auto const value : buffer) {
|
||||
repr += str_sprintf("%02x", value & 0xff);
|
||||
@ -109,7 +111,7 @@ std::string bytes_repr(const BytesView &buffer) {
|
||||
return repr;
|
||||
}
|
||||
|
||||
uint64_t bytes_to_uint(const BytesView &buffer) {
|
||||
uint64_t bytes_to_uint(const bytes &buffer) {
|
||||
uint64_t val = 0;
|
||||
for (auto const value : buffer) {
|
||||
val = (val << 8) + value;
|
||||
@ -117,7 +119,7 @@ uint64_t bytes_to_uint(const BytesView &buffer) {
|
||||
return val;
|
||||
}
|
||||
|
||||
int64_t bytes_to_int(const BytesView &buffer) {
|
||||
int64_t bytes_to_int(const bytes &buffer) {
|
||||
uint64_t tmp = bytes_to_uint(buffer);
|
||||
int64_t val;
|
||||
|
||||
@ -133,14 +135,14 @@ int64_t bytes_to_int(const BytesView &buffer) {
|
||||
return val;
|
||||
}
|
||||
|
||||
std::string bytes_to_string(const BytesView &buffer) { return std::string(buffer.begin(), buffer.end()); }
|
||||
std::string bytes_to_string(const bytes &buffer) { return std::string(buffer.begin(), buffer.end()); }
|
||||
|
||||
ObisInfo::ObisInfo(const BytesView &server_id, const SmlNode &val_list_entry) : server_id(server_id) {
|
||||
ObisInfo::ObisInfo(bytes server_id, SmlNode val_list_entry) : server_id(std::move(server_id)) {
|
||||
this->code = val_list_entry.nodes[0].value_bytes;
|
||||
this->status = val_list_entry.nodes[1].value_bytes;
|
||||
this->unit = bytes_to_uint(val_list_entry.nodes[3].value_bytes);
|
||||
this->scaler = bytes_to_int(val_list_entry.nodes[4].value_bytes);
|
||||
const auto &value_node = val_list_entry.nodes[5];
|
||||
SmlNode value_node = val_list_entry.nodes[5];
|
||||
this->value = value_node.value_bytes;
|
||||
this->value_type = value_node.type;
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
@ -12,73 +11,44 @@ namespace sml {
|
||||
|
||||
using bytes = std::vector<uint8_t>;
|
||||
|
||||
class BytesView {
|
||||
public:
|
||||
BytesView() noexcept = default;
|
||||
|
||||
explicit BytesView(const uint8_t *first, size_t count) noexcept : data_{first}, count_{count} {}
|
||||
|
||||
explicit BytesView(const bytes &bytes) noexcept : data_{bytes.data()}, count_{bytes.size()} {}
|
||||
|
||||
size_t size() const noexcept { return count_; }
|
||||
|
||||
uint8_t operator[](size_t index) const noexcept {
|
||||
assert(index < count_);
|
||||
return data_[index];
|
||||
}
|
||||
|
||||
BytesView subview(size_t offset, size_t count) const noexcept {
|
||||
assert(offset + count <= count_);
|
||||
return BytesView{data_ + offset, count};
|
||||
}
|
||||
|
||||
const uint8_t *begin() const noexcept { return data_; }
|
||||
|
||||
const uint8_t *end() const noexcept { return data_ + count_; }
|
||||
|
||||
private:
|
||||
const uint8_t *data_ = nullptr;
|
||||
size_t count_ = 0;
|
||||
};
|
||||
|
||||
class SmlNode {
|
||||
public:
|
||||
uint8_t type;
|
||||
BytesView value_bytes;
|
||||
bytes value_bytes;
|
||||
std::vector<SmlNode> nodes;
|
||||
};
|
||||
|
||||
class ObisInfo {
|
||||
public:
|
||||
ObisInfo(const BytesView &server_id, const SmlNode &val_list_entry);
|
||||
BytesView server_id;
|
||||
BytesView code;
|
||||
BytesView status;
|
||||
ObisInfo(bytes server_id, SmlNode val_list_entry);
|
||||
bytes server_id;
|
||||
bytes code;
|
||||
bytes status;
|
||||
char unit;
|
||||
char scaler;
|
||||
BytesView value;
|
||||
bytes value;
|
||||
uint16_t value_type;
|
||||
std::string code_repr() const;
|
||||
};
|
||||
|
||||
class SmlFile {
|
||||
public:
|
||||
SmlFile(const BytesView &buffer);
|
||||
SmlFile(bytes buffer);
|
||||
bool setup_node(SmlNode *node);
|
||||
std::vector<SmlNode> messages;
|
||||
std::vector<ObisInfo> get_obis_info();
|
||||
|
||||
protected:
|
||||
const BytesView buffer_;
|
||||
const bytes buffer_;
|
||||
size_t pos_;
|
||||
};
|
||||
|
||||
std::string bytes_repr(const BytesView &buffer);
|
||||
std::string bytes_repr(const bytes &buffer);
|
||||
|
||||
uint64_t bytes_to_uint(const BytesView &buffer);
|
||||
uint64_t bytes_to_uint(const bytes &buffer);
|
||||
|
||||
int64_t bytes_to_int(const BytesView &buffer);
|
||||
int64_t bytes_to_int(const bytes &buffer);
|
||||
|
||||
std::string bytes_to_string(const BytesView &buffer);
|
||||
std::string bytes_to_string(const bytes &buffer);
|
||||
} // namespace sml
|
||||
} // namespace esphome
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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};
|
||||
};
|
||||
|
@ -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),
|
||||
|
@ -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; }
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
@ -1498,9 +1499,30 @@ def dimensions(value):
|
||||
|
||||
|
||||
def directory(value):
|
||||
import json
|
||||
|
||||
value = string(value)
|
||||
path = CORE.relative_config_path(value)
|
||||
|
||||
if CORE.vscode and (
|
||||
not CORE.ace or os.path.abspath(path) == os.path.abspath(CORE.config_path)
|
||||
):
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"type": "check_directory_exists",
|
||||
"path": path,
|
||||
}
|
||||
)
|
||||
)
|
||||
data = json.loads(input())
|
||||
assert data["type"] == "directory_exists_response"
|
||||
if data["content"]:
|
||||
return value
|
||||
raise Invalid(
|
||||
f"Could not find directory '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})."
|
||||
)
|
||||
|
||||
if not os.path.exists(path):
|
||||
raise Invalid(
|
||||
f"Could not find directory '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})."
|
||||
@ -1513,9 +1535,30 @@ def directory(value):
|
||||
|
||||
|
||||
def file_(value):
|
||||
import json
|
||||
|
||||
value = string(value)
|
||||
path = CORE.relative_config_path(value)
|
||||
|
||||
if CORE.vscode and (
|
||||
not CORE.ace or os.path.abspath(path) == os.path.abspath(CORE.config_path)
|
||||
):
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"type": "check_file_exists",
|
||||
"path": path,
|
||||
}
|
||||
)
|
||||
)
|
||||
data = json.loads(input())
|
||||
assert data["type"] == "file_exists_response"
|
||||
if data["content"]:
|
||||
return value
|
||||
raise Invalid(
|
||||
f"Could not find file '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})."
|
||||
)
|
||||
|
||||
if not os.path.exists(path):
|
||||
raise Invalid(
|
||||
f"Could not find file '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})."
|
||||
@ -1941,28 +1984,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:
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""Constants used by esphome."""
|
||||
|
||||
__version__ = "2025.5.0-dev"
|
||||
__version__ = "2025.4.0b2"
|
||||
|
||||
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"
|
||||
|
@ -475,6 +475,7 @@ class EsphomeCore:
|
||||
self.dashboard = False
|
||||
# True if command is run from vscode api
|
||||
self.vscode = False
|
||||
self.ace = False
|
||||
# The name of the node
|
||||
self.name: Optional[str] = None
|
||||
# The friendly name of the node
|
||||
@ -518,8 +519,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
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -78,47 +78,28 @@ def _print_file_read_event(path: str) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _request_and_get_stream_on_stdin(fname: str) -> StringIO:
|
||||
_print_file_read_event(fname)
|
||||
raw_yaml_stream = StringIO(_read_file_content_from_json_on_stdin())
|
||||
return raw_yaml_stream
|
||||
|
||||
|
||||
def _vscode_loader(fname: str) -> dict[str, Any]:
|
||||
raw_yaml_stream = _request_and_get_stream_on_stdin(fname)
|
||||
# it is required to set the name on StringIO so document on start_mark
|
||||
# is set properly. Otherwise it is initialized with "<file>"
|
||||
raw_yaml_stream.name = fname
|
||||
return parse_yaml(fname, raw_yaml_stream, _vscode_loader)
|
||||
|
||||
|
||||
def _ace_loader(fname: str) -> dict[str, Any]:
|
||||
raw_yaml_stream = _request_and_get_stream_on_stdin(fname)
|
||||
return parse_yaml(fname, raw_yaml_stream)
|
||||
|
||||
|
||||
def read_config(args):
|
||||
while True:
|
||||
CORE.reset()
|
||||
data = json.loads(input())
|
||||
assert data["type"] == "validate" or data["type"] == "exit"
|
||||
if data["type"] == "exit":
|
||||
return
|
||||
assert data["type"] == "validate"
|
||||
CORE.vscode = True
|
||||
if args.ace: # Running from ESPHome Compiler dashboard, not vscode
|
||||
CORE.config_path = os.path.join(args.configuration, data["file"])
|
||||
loader = _ace_loader
|
||||
CORE.ace = args.ace
|
||||
f = data["file"]
|
||||
if CORE.ace:
|
||||
CORE.config_path = os.path.join(args.configuration, f)
|
||||
else:
|
||||
CORE.config_path = data["file"]
|
||||
loader = _vscode_loader
|
||||
|
||||
file_name = CORE.config_path
|
||||
_print_file_read_event(file_name)
|
||||
raw_yaml = _read_file_content_from_json_on_stdin()
|
||||
command_line_substitutions: dict[str, Any] = (
|
||||
dict(args.substitution) if args.substitution else {}
|
||||
)
|
||||
vs = VSCodeResult()
|
||||
try:
|
||||
config = loader(file_name)
|
||||
config = parse_yaml(file_name, StringIO(raw_yaml))
|
||||
res = validate_config(config, command_line_substitutions)
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
vs.add_yaml_error(str(err))
|
||||
|
@ -3,12 +3,12 @@ from __future__ import annotations
|
||||
import fnmatch
|
||||
import functools
|
||||
import inspect
|
||||
from io import BytesIO, TextIOBase, TextIOWrapper
|
||||
from io import TextIOWrapper
|
||||
from ipaddress import _BaseAddress
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
from typing import Any, Callable
|
||||
from typing import Any
|
||||
import uuid
|
||||
|
||||
import yaml
|
||||
@ -69,10 +69,7 @@ class ESPForceValue:
|
||||
pass
|
||||
|
||||
|
||||
def make_data_base(
|
||||
value, from_database: ESPHomeDataBase = None
|
||||
) -> ESPHomeDataBase | Any:
|
||||
"""Wrap a value in a ESPHomeDataBase object."""
|
||||
def make_data_base(value, from_database: ESPHomeDataBase = None):
|
||||
try:
|
||||
value = add_class_to_obj(value, ESPHomeDataBase)
|
||||
if from_database is not None:
|
||||
@ -105,11 +102,6 @@ def _add_data_ref(fn):
|
||||
class ESPHomeLoaderMixin:
|
||||
"""Loader class that keeps track of line numbers."""
|
||||
|
||||
def __init__(self, name: str, yaml_loader: Callable[[str], dict[str, Any]]) -> None:
|
||||
"""Initialize the loader."""
|
||||
self.name = name
|
||||
self.yaml_loader = yaml_loader
|
||||
|
||||
@_add_data_ref
|
||||
def construct_yaml_int(self, node):
|
||||
return super().construct_yaml_int(node)
|
||||
@ -135,7 +127,7 @@ class ESPHomeLoaderMixin:
|
||||
return super().construct_yaml_seq(node)
|
||||
|
||||
@_add_data_ref
|
||||
def construct_yaml_map(self, node: yaml.MappingNode) -> OrderedDict[str, Any]:
|
||||
def construct_yaml_map(self, node):
|
||||
"""Traverses the given mapping node and returns a list of constructed key-value pairs."""
|
||||
assert isinstance(node, yaml.MappingNode)
|
||||
# A list of key-value pairs we find in the current mapping
|
||||
@ -239,7 +231,7 @@ class ESPHomeLoaderMixin:
|
||||
return OrderedDict(pairs)
|
||||
|
||||
@_add_data_ref
|
||||
def construct_env_var(self, node: yaml.Node) -> str:
|
||||
def construct_env_var(self, node):
|
||||
args = node.value.split()
|
||||
# Check for a default value
|
||||
if len(args) > 1:
|
||||
@ -251,23 +243,23 @@ class ESPHomeLoaderMixin:
|
||||
)
|
||||
|
||||
@property
|
||||
def _directory(self) -> str:
|
||||
def _directory(self):
|
||||
return os.path.dirname(self.name)
|
||||
|
||||
def _rel_path(self, *args: str) -> str:
|
||||
def _rel_path(self, *args):
|
||||
return os.path.join(self._directory, *args)
|
||||
|
||||
@_add_data_ref
|
||||
def construct_secret(self, node: yaml.Node) -> str:
|
||||
def construct_secret(self, node):
|
||||
try:
|
||||
secrets = self.yaml_loader(self._rel_path(SECRET_YAML))
|
||||
secrets = _load_yaml_internal(self._rel_path(SECRET_YAML))
|
||||
except EsphomeError as e:
|
||||
if self.name == CORE.config_path:
|
||||
raise e
|
||||
try:
|
||||
main_config_dir = os.path.dirname(CORE.config_path)
|
||||
main_secret_yml = os.path.join(main_config_dir, SECRET_YAML)
|
||||
secrets = self.yaml_loader(main_secret_yml)
|
||||
secrets = _load_yaml_internal(main_secret_yml)
|
||||
except EsphomeError as er:
|
||||
raise EsphomeError(f"{e}\n{er}") from er
|
||||
|
||||
@ -280,9 +272,7 @@ class ESPHomeLoaderMixin:
|
||||
return val
|
||||
|
||||
@_add_data_ref
|
||||
def construct_include(
|
||||
self, node: yaml.Node
|
||||
) -> dict[str, Any] | OrderedDict[str, Any]:
|
||||
def construct_include(self, node):
|
||||
from esphome.const import CONF_VARS
|
||||
|
||||
def extract_file_vars(node):
|
||||
@ -300,93 +290,71 @@ class ESPHomeLoaderMixin:
|
||||
else:
|
||||
file, vars = node.value, None
|
||||
|
||||
result = self.yaml_loader(self._rel_path(file))
|
||||
result = _load_yaml_internal(self._rel_path(file))
|
||||
if not vars:
|
||||
vars = {}
|
||||
result = substitute_vars(result, vars)
|
||||
return result
|
||||
|
||||
@_add_data_ref
|
||||
def construct_include_dir_list(self, node: yaml.Node) -> list[dict[str, Any]]:
|
||||
def construct_include_dir_list(self, node):
|
||||
files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml"))
|
||||
return [self.yaml_loader(f) for f in files]
|
||||
return [_load_yaml_internal(f) for f in files]
|
||||
|
||||
@_add_data_ref
|
||||
def construct_include_dir_merge_list(self, node: yaml.Node) -> list[dict[str, Any]]:
|
||||
def construct_include_dir_merge_list(self, node):
|
||||
files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml"))
|
||||
merged_list = []
|
||||
for fname in files:
|
||||
loaded_yaml = self.yaml_loader(fname)
|
||||
loaded_yaml = _load_yaml_internal(fname)
|
||||
if isinstance(loaded_yaml, list):
|
||||
merged_list.extend(loaded_yaml)
|
||||
return merged_list
|
||||
|
||||
@_add_data_ref
|
||||
def construct_include_dir_named(
|
||||
self, node: yaml.Node
|
||||
) -> OrderedDict[str, dict[str, Any]]:
|
||||
def construct_include_dir_named(self, node):
|
||||
files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml"))
|
||||
mapping = OrderedDict()
|
||||
for fname in files:
|
||||
filename = os.path.splitext(os.path.basename(fname))[0]
|
||||
mapping[filename] = self.yaml_loader(fname)
|
||||
mapping[filename] = _load_yaml_internal(fname)
|
||||
return mapping
|
||||
|
||||
@_add_data_ref
|
||||
def construct_include_dir_merge_named(
|
||||
self, node: yaml.Node
|
||||
) -> OrderedDict[str, dict[str, Any]]:
|
||||
def construct_include_dir_merge_named(self, node):
|
||||
files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml"))
|
||||
mapping = OrderedDict()
|
||||
for fname in files:
|
||||
loaded_yaml = self.yaml_loader(fname)
|
||||
loaded_yaml = _load_yaml_internal(fname)
|
||||
if isinstance(loaded_yaml, dict):
|
||||
mapping.update(loaded_yaml)
|
||||
return mapping
|
||||
|
||||
@_add_data_ref
|
||||
def construct_lambda(self, node: yaml.Node) -> Lambda:
|
||||
def construct_lambda(self, node):
|
||||
return Lambda(str(node.value))
|
||||
|
||||
@_add_data_ref
|
||||
def construct_force(self, node: yaml.Node) -> ESPForceValue:
|
||||
def construct_force(self, node):
|
||||
obj = self.construct_scalar(node)
|
||||
return add_class_to_obj(obj, ESPForceValue)
|
||||
|
||||
@_add_data_ref
|
||||
def construct_extend(self, node: yaml.Node) -> Extend:
|
||||
def construct_extend(self, node):
|
||||
return Extend(str(node.value))
|
||||
|
||||
@_add_data_ref
|
||||
def construct_remove(self, node: yaml.Node) -> Remove:
|
||||
def construct_remove(self, node):
|
||||
return Remove(str(node.value))
|
||||
|
||||
|
||||
class ESPHomeLoader(ESPHomeLoaderMixin, FastestAvailableSafeLoader):
|
||||
"""Loader class that keeps track of line numbers."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
stream: TextIOBase | BytesIO,
|
||||
name: str,
|
||||
yaml_loader: Callable[[str], dict[str, Any]],
|
||||
) -> None:
|
||||
FastestAvailableSafeLoader.__init__(self, stream)
|
||||
ESPHomeLoaderMixin.__init__(self, name, yaml_loader)
|
||||
|
||||
|
||||
class ESPHomePurePythonLoader(ESPHomeLoaderMixin, PurePythonLoader):
|
||||
"""Loader class that keeps track of line numbers."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
stream: TextIOBase | BytesIO,
|
||||
name: str,
|
||||
yaml_loader: Callable[[str], dict[str, Any]],
|
||||
) -> None:
|
||||
PurePythonLoader.__init__(self, stream)
|
||||
ESPHomeLoaderMixin.__init__(self, name, yaml_loader)
|
||||
|
||||
|
||||
for _loader in (ESPHomeLoader, ESPHomePurePythonLoader):
|
||||
_loader.add_constructor("tag:yaml.org,2002:int", _loader.construct_yaml_int)
|
||||
@ -420,30 +388,17 @@ def load_yaml(fname: str, clear_secrets: bool = True) -> Any:
|
||||
return _load_yaml_internal(fname)
|
||||
|
||||
|
||||
def _load_yaml_internal(fname: str) -> Any:
|
||||
"""Load a YAML file."""
|
||||
try:
|
||||
with open(fname, encoding="utf-8") as f_handle:
|
||||
return parse_yaml(fname, f_handle)
|
||||
except (UnicodeDecodeError, OSError) as err:
|
||||
raise EsphomeError(f"Error reading file {fname}: {err}") from err
|
||||
|
||||
|
||||
def parse_yaml(
|
||||
file_name: str, file_handle: TextIOWrapper, yaml_loader=_load_yaml_internal
|
||||
) -> Any:
|
||||
def parse_yaml(file_name: str, file_handle: TextIOWrapper) -> Any:
|
||||
"""Parse a YAML file."""
|
||||
try:
|
||||
return _load_yaml_internal_with_type(
|
||||
ESPHomeLoader, file_name, file_handle, yaml_loader
|
||||
)
|
||||
return _load_yaml_internal_with_type(ESPHomeLoader, file_name, file_handle)
|
||||
except EsphomeError:
|
||||
# Loading failed, so we now load with the Python loader which has more
|
||||
# readable exceptions
|
||||
# Rewind the stream so we can try again
|
||||
file_handle.seek(0, 0)
|
||||
return _load_yaml_internal_with_type(
|
||||
ESPHomePurePythonLoader, file_name, file_handle, yaml_loader
|
||||
ESPHomePurePythonLoader, file_name, file_handle
|
||||
)
|
||||
|
||||
|
||||
@ -480,14 +435,23 @@ def substitute_vars(config, vars):
|
||||
return result
|
||||
|
||||
|
||||
def _load_yaml_internal(fname: str) -> Any:
|
||||
"""Load a YAML file."""
|
||||
try:
|
||||
with open(fname, encoding="utf-8") as f_handle:
|
||||
return parse_yaml(fname, f_handle)
|
||||
except (UnicodeDecodeError, OSError) as err:
|
||||
raise EsphomeError(f"Error reading file {fname}: {err}") from err
|
||||
|
||||
|
||||
def _load_yaml_internal_with_type(
|
||||
loader_type: type[ESPHomeLoader] | type[ESPHomePurePythonLoader],
|
||||
fname: str,
|
||||
content: TextIOWrapper,
|
||||
yaml_loader: Any,
|
||||
) -> Any:
|
||||
"""Load a YAML file."""
|
||||
loader = loader_type(content, fname, yaml_loader)
|
||||
loader = loader_type(content)
|
||||
loader.name = fname
|
||||
try:
|
||||
return loader.get_single_data() or OrderedDict()
|
||||
except yaml.YAMLError as exc:
|
||||
@ -506,7 +470,7 @@ def dump(dict_, show_secrets=False):
|
||||
)
|
||||
|
||||
|
||||
def _is_file_valid(name: str) -> bool:
|
||||
def _is_file_valid(name):
|
||||
"""Decide if a file is valid."""
|
||||
return not name.startswith(".")
|
||||
|
||||
|
@ -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 =
|
||||
|
@ -13,8 +13,8 @@ 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
|
||||
zeroconf==0.146.5
|
||||
aioesphomeapi==29.10.0
|
||||
zeroconf==0.146.4
|
||||
puremagic==1.28
|
||||
ruamel.yaml==0.18.10 # dashboard_import
|
||||
esphome-glyphsets==0.2.0
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
)
|
||||
|
@ -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."
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -1,10 +0,0 @@
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: !remove
|
@ -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]);
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
|
||||
<<: !include common.yaml
|
@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
|
||||
<<: !include common.yaml
|
@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
@ -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:
|
||||
|
@ -1,7 +1,3 @@
|
||||
substitutions:
|
||||
verify_ssl: "false"
|
||||
pin: GPIO5
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
i2s_audio:
|
||||
|
@ -1,5 +1 @@
|
||||
substitutions:
|
||||
verify_ssl: "false"
|
||||
pin: GPIO2
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -1,5 +1 @@
|
||||
substitutions:
|
||||
verify_ssl: "false"
|
||||
pin: GPIO2
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -1,5 +1 @@
|
||||
substitutions:
|
||||
verify_ssl: "false"
|
||||
pin: GPIO2
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -1,5 +1 @@
|
||||
substitutions:
|
||||
verify_ssl: "false"
|
||||
pin: GPIO5
|
||||
|
||||
<<: !include common.yaml
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -1,125 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from esphome import vscode
|
||||
|
||||
|
||||
def _run_repl_test(input_data):
|
||||
"""Reusable test function for different input scenarios."""
|
||||
input_data.append(_exit())
|
||||
with (
|
||||
patch("builtins.input", side_effect=input_data),
|
||||
patch("sys.stdout") as mock_stdout,
|
||||
):
|
||||
args = Mock([])
|
||||
args.ace = False
|
||||
args.substitution = None
|
||||
vscode.read_config(args)
|
||||
|
||||
# Capture printed output
|
||||
full_output = "".join(call[0][0] for call in mock_stdout.write.call_args_list)
|
||||
return full_output.strip().split("\n")
|
||||
|
||||
|
||||
def _validate(file_path: str):
|
||||
return json.dumps({"type": "validate", "file": file_path})
|
||||
|
||||
|
||||
def _file_response(data: str):
|
||||
return json.dumps({"type": "file_response", "content": data})
|
||||
|
||||
|
||||
def _read_file(file_path: str):
|
||||
return json.dumps({"type": "read_file", "path": file_path})
|
||||
|
||||
|
||||
def _exit():
|
||||
return json.dumps({"type": "exit"})
|
||||
|
||||
|
||||
RESULT_NO_ERROR = '{"type": "result", "yaml_errors": [], "validation_errors": []}'
|
||||
|
||||
|
||||
def test_multi_file():
|
||||
source_path = os.path.join("dir_path", "x.yaml")
|
||||
output_lines = _run_repl_test(
|
||||
[
|
||||
_validate(source_path),
|
||||
# read_file x.yaml
|
||||
_file_response("""esphome:
|
||||
name: test1
|
||||
esp8266:
|
||||
board: !secret my_secret_board
|
||||
"""),
|
||||
# read_file secrets.yaml
|
||||
_file_response("""my_secret_board: esp1f"""),
|
||||
]
|
||||
)
|
||||
|
||||
expected_lines = [
|
||||
_read_file(source_path),
|
||||
_read_file(os.path.join("dir_path", "secrets.yaml")),
|
||||
RESULT_NO_ERROR,
|
||||
]
|
||||
|
||||
assert output_lines == expected_lines
|
||||
|
||||
|
||||
def test_shows_correct_range_error():
|
||||
source_path = os.path.join("dir_path", "x.yaml")
|
||||
output_lines = _run_repl_test(
|
||||
[
|
||||
_validate(source_path),
|
||||
# read_file x.yaml
|
||||
_file_response("""esphome:
|
||||
name: test1
|
||||
esp8266:
|
||||
broad: !secret my_secret_board # typo here
|
||||
"""),
|
||||
# read_file secrets.yaml
|
||||
_file_response("""my_secret_board: esp1f"""),
|
||||
]
|
||||
)
|
||||
|
||||
assert len(output_lines) == 3
|
||||
error = json.loads(output_lines[2])
|
||||
validation_error = error["validation_errors"][0]
|
||||
assert validation_error["message"].startswith("[broad] is an invalid option for")
|
||||
range = validation_error["range"]
|
||||
assert range["document"] == source_path
|
||||
assert range["start_line"] == 3
|
||||
assert range["start_col"] == 2
|
||||
assert range["end_line"] == 3
|
||||
assert range["end_col"] == 7
|
||||
|
||||
|
||||
def test_shows_correct_loaded_file_error():
|
||||
source_path = os.path.join("dir_path", "x.yaml")
|
||||
output_lines = _run_repl_test(
|
||||
[
|
||||
_validate(source_path),
|
||||
# read_file x.yaml
|
||||
_file_response("""esphome:
|
||||
name: test1
|
||||
|
||||
packages:
|
||||
board: !include .pkg.esp8266.yaml
|
||||
"""),
|
||||
# read_file .pkg.esp8266.yaml
|
||||
_file_response("""esp8266:
|
||||
broad: esp1f # typo here
|
||||
"""),
|
||||
]
|
||||
)
|
||||
|
||||
assert len(output_lines) == 3
|
||||
error = json.loads(output_lines[2])
|
||||
validation_error = error["validation_errors"][0]
|
||||
assert validation_error["message"].startswith("[broad] is an invalid option for")
|
||||
range = validation_error["range"]
|
||||
assert range["document"] == os.path.join("dir_path", ".pkg.esp8266.yaml")
|
||||
assert range["start_line"] == 1
|
||||
assert range["start_col"] == 2
|
||||
assert range["end_line"] == 1
|
||||
assert range["end_col"] == 7
|
@ -42,23 +42,3 @@ def test_loading_a_missing_file(fixture_path):
|
||||
yaml_util.load_yaml(yaml_file)
|
||||
except EsphomeError as err:
|
||||
assert "missing.yaml" in str(err)
|
||||
|
||||
|
||||
def test_parsing_with_custom_loader(fixture_path):
|
||||
"""Test custom loader used for vscode connection
|
||||
Default loader is tested in test_include_with_vars
|
||||
"""
|
||||
yaml_file = fixture_path / "yaml_util" / "includetest.yaml"
|
||||
|
||||
loader_calls = []
|
||||
|
||||
def custom_loader(fname):
|
||||
loader_calls.append(fname)
|
||||
|
||||
with open(yaml_file, encoding="utf-8") as f_handle:
|
||||
yaml_util.parse_yaml(yaml_file, f_handle, custom_loader)
|
||||
|
||||
assert len(loader_calls) == 3
|
||||
assert loader_calls[0].endswith("includes/included.yaml")
|
||||
assert loader_calls[1].endswith("includes/list.yaml")
|
||||
assert loader_calls[2].endswith("includes/scalar.yaml")
|
||||
|
Loading…
x
Reference in New Issue
Block a user