mirror of
https://github.com/esphome/esphome.git
synced 2025-07-28 14:16:40 +00:00
Merge branch 'dev' into esp-idf_516
This commit is contained in:
commit
85dc3b9636
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
|
||||
uses: codecov/codecov-action@v5.4.2
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
|
@ -250,6 +250,7 @@ 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
|
||||
@ -324,6 +325,7 @@ 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,10 +375,12 @@ def upload_program(config, args, host):
|
||||
password = ota_conf.get(CONF_PASSWORD, "")
|
||||
|
||||
if (
|
||||
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
|
||||
CONF_MQTT in config # pylint: disable=too-many-boolean-expressions
|
||||
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->mark_failed();
|
||||
this->status_set_warning();
|
||||
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->mark_failed();
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
if ((status & 0x80) == 0x80) {
|
||||
ESP_LOGE(TAG, "HW still busy!");
|
||||
this->mark_failed();
|
||||
this->status_set_warning();
|
||||
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->mark_failed();
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,8 @@ void AnalogThresholdBinarySensor::setup() {
|
||||
if (std::isnan(sensor_value)) {
|
||||
this->publish_initial_state(false);
|
||||
} else {
|
||||
this->publish_initial_state(sensor_value >= (this->lower_threshold_ + this->upper_threshold_) / 2.0f);
|
||||
this->publish_initial_state(sensor_value >=
|
||||
(this->lower_threshold_.value() + this->upper_threshold_.value()) / 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +25,8 @@ 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_ : this->upper_threshold_));
|
||||
this->publish_state(sensor_value >=
|
||||
(this->state ? this->lower_threshold_.value() : this->upper_threshold_.value()));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -32,8 +34,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_);
|
||||
ESP_LOGCONFIG(TAG, " Lower threshold: %.11f", this->lower_threshold_);
|
||||
ESP_LOGCONFIG(TAG, " Upper threshold: %.11f", this->upper_threshold_.value());
|
||||
ESP_LOGCONFIG(TAG, " Lower threshold: %.11f", this->lower_threshold_.value());
|
||||
}
|
||||
|
||||
} // namespace analog_threshold
|
||||
|
@ -15,14 +15,13 @@ 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);
|
||||
void set_upper_threshold(float threshold) { this->upper_threshold_ = threshold; }
|
||||
void set_lower_threshold(float threshold) { this->lower_threshold_ = threshold; }
|
||||
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; }
|
||||
|
||||
protected:
|
||||
sensor::Sensor *sensor_{nullptr};
|
||||
|
||||
float upper_threshold_;
|
||||
float lower_threshold_;
|
||||
TemplatableValue<float> upper_threshold_{};
|
||||
TemplatableValue<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.float_,
|
||||
cv.templatable(cv.float_),
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_UPPER): cv.float_,
|
||||
cv.Required(CONF_LOWER): cv.float_,
|
||||
cv.Required(CONF_UPPER): cv.templatable(cv.float_),
|
||||
cv.Required(CONF_LOWER): cv.templatable(cv.float_),
|
||||
}
|
||||
),
|
||||
),
|
||||
@ -39,9 +39,11 @@ 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], float):
|
||||
cg.add(var.set_upper_threshold(config[CONF_THRESHOLD]))
|
||||
cg.add(var.set_lower_threshold(config[CONF_THRESHOLD]))
|
||||
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)
|
||||
else:
|
||||
cg.add(var.set_upper_threshold(config[CONF_THRESHOLD][CONF_UPPER]))
|
||||
cg.add(var.set_lower_threshold(config[CONF_THRESHOLD][CONF_LOWER]))
|
||||
lower = await cg.templatable(config[CONF_THRESHOLD], [], float)
|
||||
upper = lower
|
||||
cg.add(var.set_upper_threshold(upper))
|
||||
cg.add(var.set_lower_threshold(lower))
|
||||
|
@ -82,6 +82,19 @@ 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(
|
||||
{
|
||||
@ -95,11 +108,7 @@ 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): cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_KEY): validate_encryption_key,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ENCRYPTION): _encryption_schema,
|
||||
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
@ -151,9 +160,17 @@ async def to_code(config):
|
||||
config[CONF_ON_CLIENT_DISCONNECTED],
|
||||
)
|
||||
|
||||
if encryption_config := config.get(CONF_ENCRYPTION):
|
||||
decoded = base64.b64decode(encryption_config[CONF_KEY])
|
||||
cg.add(var.set_noise_psk(list(decoded)))
|
||||
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")
|
||||
cg.add_define("USE_API_NOISE")
|
||||
cg.add_library("esphome/noise-c", "0.1.6")
|
||||
else:
|
||||
|
@ -31,6 +31,7 @@ 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) {}
|
||||
@ -230,6 +231,9 @@ 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 {
|
||||
@ -654,6 +658,23 @@ 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,7 +62,14 @@ 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)
|
||||
#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)
|
||||
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())};
|
||||
@ -1848,6 +1855,9 @@ 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;
|
||||
}
|
||||
@ -1869,6 +1879,26 @@ 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,6 +300,9 @@ 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 {
|
||||
|
@ -311,6 +311,10 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
const std::string &name = App.get_name();
|
||||
const uint8_t *name_ptr = reinterpret_cast<const uint8_t *>(name.c_str());
|
||||
msg.insert(msg.end(), name_ptr, name_ptr + name.size() + 1);
|
||||
// node mac, terminated by null byte
|
||||
const std::string &mac = get_mac_address();
|
||||
const uint8_t *mac_ptr = reinterpret_cast<const uint8_t *>(mac.c_str());
|
||||
msg.insert(msg.end(), mac_ptr, mac_ptr + mac.size() + 1);
|
||||
|
||||
aerr = write_frame_(msg.data(), msg.size());
|
||||
if (aerr != APIError::OK)
|
||||
|
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
namespace esphome {
|
||||
@ -11,11 +11,20 @@ using psk_t = std::array<uint8_t, 32>;
|
||||
|
||||
class APINoiseContext {
|
||||
public:
|
||||
void set_psk(psk_t psk) { psk_ = psk; }
|
||||
const psk_t &get_psk() const { return psk_; }
|
||||
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_; }
|
||||
|
||||
protected:
|
||||
psk_t psk_;
|
||||
psk_t psk_{};
|
||||
bool has_psk_{false};
|
||||
};
|
||||
#endif // USE_API_NOISE
|
||||
|
||||
|
@ -792,6 +792,10 @@ 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;
|
||||
}
|
||||
@ -865,6 +869,7 @@ 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 {
|
||||
@ -946,6 +951,10 @@ 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
|
||||
@ -3009,6 +3018,48 @@ 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,6 +355,7 @@ 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;
|
||||
@ -791,6 +792,28 @@ 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,6 +179,16 @@ 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());
|
||||
@ -1191,6 +1201,17 @@ 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;
|
||||
}
|
||||
@ -1311,6 +1332,22 @@ 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,6 +83,12 @@ 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){};
|
||||
@ -349,6 +355,9 @@ 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
|
||||
@ -457,6 +466,9 @@ 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,22 +22,40 @@ 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();
|
||||
socket_ = socket::socket_ip(SOCK_STREAM, 0);
|
||||
if (socket_ == nullptr) {
|
||||
ESP_LOGW(TAG, "Could not create socket.");
|
||||
|
||||
#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");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
int enable = 1;
|
||||
int err = socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
|
||||
int err = this->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 = socket_->setblocking(false);
|
||||
err = this->socket_->setblocking(false);
|
||||
if (err != 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
|
||||
this->mark_failed();
|
||||
@ -53,14 +71,14 @@ void APIServer::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
err = socket_->bind((struct sockaddr *) &server, sl);
|
||||
err = this->socket_->bind((struct sockaddr *) &server, sl);
|
||||
if (err != 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
err = socket_->listen(4);
|
||||
err = this->socket_->listen(4);
|
||||
if (err != 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
|
||||
this->mark_failed();
|
||||
@ -92,18 +110,19 @@ 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 = socket_->accept((struct sockaddr *) &source_addr, &addr_len);
|
||||
auto sock = this->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);
|
||||
clients_.emplace_back(conn);
|
||||
this->clients_.emplace_back(conn);
|
||||
conn->start();
|
||||
}
|
||||
|
||||
@ -136,16 +155,22 @@ 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: YES");
|
||||
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");
|
||||
}
|
||||
#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();
|
||||
@ -174,7 +199,9 @@ 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())
|
||||
@ -342,57 +369,6 @@ void APIServer::on_update(update::UpdateEntity *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{
|
||||
.entity_id = std::move(entity_id),
|
||||
.attribute = std::move(attribute),
|
||||
.callback = std::move(f),
|
||||
.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{
|
||||
.entity_id = std::move(entity_id),
|
||||
.attribute = std::move(attribute),
|
||||
.callback = std::move(f),
|
||||
.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_HOMEASSISTANT_TIME
|
||||
void APIServer::request_time() {
|
||||
for (auto &client : this->clients_) {
|
||||
if (!client->remove_ && client->is_authenticated())
|
||||
client->send_time_request();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
bool APIServer::is_connected() const { return !this->clients_.empty(); }
|
||||
void APIServer::on_shutdown() {
|
||||
for (auto &c : this->clients_) {
|
||||
c->send_disconnect_request(DisconnectRequest());
|
||||
}
|
||||
delay(10);
|
||||
}
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
|
||||
if (obj->is_internal())
|
||||
@ -402,6 +378,96 @@ void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlP
|
||||
}
|
||||
#endif
|
||||
|
||||
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
void APIServer::set_port(uint16_t port) { this->port_ = port; }
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
.entity_id = std::move(entity_id),
|
||||
.attribute = std::move(attribute),
|
||||
.callback = std::move(f),
|
||||
.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{
|
||||
.entity_id = std::move(entity_id),
|
||||
.attribute = std::move(attribute),
|
||||
.callback = std::move(f),
|
||||
.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_) {
|
||||
if (!client->remove_ && client->is_authenticated())
|
||||
client->send_time_request();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool APIServer::is_connected() const { return !this->clients_.empty(); }
|
||||
|
||||
void APIServer::on_shutdown() {
|
||||
for (auto &c : this->clients_) {
|
||||
c->send_disconnect_request(DisconnectRequest());
|
||||
}
|
||||
delay(10);
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
#endif
|
||||
|
@ -19,6 +19,12 @@
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
struct SavedNoisePsk {
|
||||
psk_t psk;
|
||||
} PACKED; // NOLINT
|
||||
#endif
|
||||
|
||||
class APIServer : public Component, public Controller {
|
||||
public:
|
||||
APIServer();
|
||||
@ -35,6 +41,7 @@ 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
|
||||
@ -142,6 +149,7 @@ 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
|
||||
};
|
||||
|
||||
|
@ -30,8 +30,12 @@ void AXS15231Touchscreen::setup() {
|
||||
this->interrupt_pin_->setup();
|
||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
}
|
||||
this->x_raw_max_ = this->display_->get_native_width();
|
||||
this->y_raw_max_ = this->display_->get_native_height();
|
||||
if (this->x_raw_max_ == 0) {
|
||||
this->x_raw_max_ = this->display_->get_native_width();
|
||||
}
|
||||
if (this->y_raw_max_ == 0) {
|
||||
this->y_raw_max_ = this->display_->get_native_height();
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "AXS15231 Touchscreen setup complete");
|
||||
}
|
||||
|
||||
@ -44,7 +48,7 @@ void AXS15231Touchscreen::update_touches() {
|
||||
err = this->read(data, sizeof(data));
|
||||
ERROR_CHECK(err);
|
||||
this->status_clear_warning();
|
||||
if (data[0] != 0) // no touches
|
||||
if (data[0] != 0 || data[1] == 0) // no touches
|
||||
return;
|
||||
uint16_t x = encode_uint16(data[2] & 0xF, data[3]);
|
||||
uint16_t y = encode_uint16(data[4] & 0xF, data[5]);
|
||||
|
@ -3,6 +3,8 @@ 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"
|
||||
|
@ -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_ESP32H6)
|
||||
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
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 = 0;
|
||||
channel.flags.with_dma = this->use_dma_;
|
||||
channel.intr_priority = 0;
|
||||
if (rmt_new_tx_channel(&channel, &this->channel_) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Channel creation failed");
|
||||
|
@ -51,6 +51,7 @@ 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.
|
||||
@ -85,7 +86,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_;
|
||||
uint32_t rmt_symbols_{48};
|
||||
#else
|
||||
rmt_item32_t *rmt_buf_{nullptr};
|
||||
rmt_item32_t bit0_, bit1_, reset_;
|
||||
@ -94,11 +95,12 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
||||
|
||||
uint8_t pin_;
|
||||
uint16_t num_leds_;
|
||||
bool is_rgbw_;
|
||||
bool is_wrgb_;
|
||||
bool use_psram_;
|
||||
bool is_rgbw_{false};
|
||||
bool is_wrgb_{false};
|
||||
bool use_dma_{false};
|
||||
bool use_psram_{false};
|
||||
|
||||
RGBOrder rgb_order_;
|
||||
RGBOrder rgb_order_{ORDER_RGB};
|
||||
|
||||
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_rmt, light
|
||||
from esphome.components import esp32, esp32_rmt, light
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_CHIPSET,
|
||||
@ -15,6 +15,7 @@ from esphome.const import (
|
||||
CONF_RGB_ORDER,
|
||||
CONF_RMT_CHANNEL,
|
||||
CONF_RMT_SYMBOLS,
|
||||
CONF_USE_DMA,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
@ -138,6 +139,11 @@ 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,
|
||||
@ -211,6 +217,8 @@ 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,6 +291,8 @@ 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_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32P4)
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
#include "driver/temp_sensor.h"
|
||||
#else
|
||||
@ -33,7 +33,8 @@ 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_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32P4))
|
||||
static temperature_sensor_handle_t tsensNew = NULL;
|
||||
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && USE_ESP32_VARIANT
|
||||
#endif // USE_ESP32
|
||||
@ -49,7 +50,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_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32P4)
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT();
|
||||
temp_sensor_set_config(tsens);
|
||||
@ -100,7 +101,8 @@ 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_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32P4))
|
||||
ESP_LOGCONFIG(TAG, "Setting up temperature sensor...");
|
||||
|
||||
temperature_sensor_config_t tsens_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80);
|
||||
|
@ -4,6 +4,7 @@ from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ACTION, CONF_GROUP, CONF_ID, CONF_TIMEOUT
|
||||
from esphome.core import Lambda
|
||||
from esphome.cpp_generator import TemplateArguments, get_variable
|
||||
from esphome.cpp_types import nullptr
|
||||
|
||||
@ -64,7 +65,14 @@ async def action_to_code(
|
||||
action_id,
|
||||
template_arg,
|
||||
args,
|
||||
config=None,
|
||||
):
|
||||
# Ensure all required ids have been processed, so our LambdaContext doesn't get context-switched.
|
||||
if config:
|
||||
for lamb in config.values():
|
||||
if isinstance(lamb, Lambda):
|
||||
for id_ in lamb.requires_ids:
|
||||
await get_variable(id_)
|
||||
await wait_for_widgets()
|
||||
async with LambdaContext(parameters=args, where=action_id) as context:
|
||||
for widget in widgets:
|
||||
@ -84,7 +92,9 @@ async def update_to_code(config, action_id, template_arg, args):
|
||||
lv.event_send(widget.obj, UPDATE_EVENT, nullptr)
|
||||
|
||||
widgets = await get_widgets(config[CONF_ID])
|
||||
return await action_to_code(widgets, do_update, action_id, template_arg, args)
|
||||
return await action_to_code(
|
||||
widgets, do_update, action_id, template_arg, args, config
|
||||
)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
@ -348,4 +358,6 @@ async def obj_update_to_code(config, action_id, template_arg, args):
|
||||
await set_obj_properties(widget, config)
|
||||
|
||||
widgets = await get_widgets(config[CONF_ID])
|
||||
return await action_to_code(widgets, do_update, action_id, template_arg, args)
|
||||
return await action_to_code(
|
||||
widgets, do_update, action_id, template_arg, args, config
|
||||
)
|
||||
|
@ -18,6 +18,7 @@ from .helpers import lvgl_components_required, requires_component
|
||||
from .lvcode import lv, lv_add, lv_assign, lv_expr, lv_Pvariable
|
||||
from .schemas import ENCODER_SCHEMA
|
||||
from .types import lv_group_t, lv_indev_type_t, lv_key_t
|
||||
from .widgets import get_widgets
|
||||
|
||||
ENCODERS_CONFIG = cv.ensure_list(
|
||||
ENCODER_SCHEMA.extend(
|
||||
@ -76,5 +77,5 @@ async def encoders_to_code(var, config, default_group):
|
||||
async def initial_focus_to_code(config):
|
||||
for enc_conf in config[CONF_ENCODERS]:
|
||||
if default_focus := enc_conf.get(CONF_INITIAL_FOCUS):
|
||||
obj = await cg.get_variable(default_focus)
|
||||
lv.group_focus_obj(obj)
|
||||
widget = await get_widgets(default_focus)
|
||||
lv.group_focus_obj(widget[0].obj)
|
||||
|
@ -173,7 +173,8 @@ class LambdaContext(CodeContext):
|
||||
|
||||
class LvContext(LambdaContext):
|
||||
"""
|
||||
Code generation into the LVGL initialisation code (called in `setup()`)
|
||||
Code generation into the LVGL initialisation code, called before setup() and loop()
|
||||
Basically just does cg.add, so now fairly redundant.
|
||||
"""
|
||||
|
||||
added_lambda_count = 0
|
||||
|
@ -433,7 +433,11 @@ 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;
|
||||
auto *buffer = lv_custom_mem_alloc(buf_bytes); // NOLINT
|
||||
void *buffer = nullptr;
|
||||
if (this->buffer_frac_ >= 4)
|
||||
buffer = malloc(buf_bytes); // NOLINT
|
||||
if (buffer == nullptr)
|
||||
buffer = lv_custom_mem_alloc(buf_bytes); // NOLINT
|
||||
if (buffer == nullptr) {
|
||||
this->mark_failed();
|
||||
this->status_set_error("Memory allocation failure");
|
||||
|
@ -1,6 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import number
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_RESTORE_VALUE
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
from ..defines import CONF_ANIMATED, CONF_UPDATE_ON_RELEASE, CONF_WIDGET
|
||||
@ -10,21 +11,21 @@ from ..lvcode import (
|
||||
EVENT_ARG,
|
||||
UPDATE_EVENT,
|
||||
LambdaContext,
|
||||
LvContext,
|
||||
ReturnStatement,
|
||||
lv,
|
||||
lv_add,
|
||||
lvgl_static,
|
||||
)
|
||||
from ..types import LV_EVENT, LvNumber, lvgl_ns
|
||||
from ..widgets import get_widgets, wait_for_widgets
|
||||
|
||||
LVGLNumber = lvgl_ns.class_("LVGLNumber", number.Number)
|
||||
LVGLNumber = lvgl_ns.class_("LVGLNumber", number.Number, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = number.number_schema(LVGLNumber).extend(
|
||||
{
|
||||
cv.Required(CONF_WIDGET): cv.use_id(LvNumber),
|
||||
cv.Optional(CONF_ANIMATED, default=True): animated,
|
||||
cv.Optional(CONF_UPDATE_ON_RELEASE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
@ -32,32 +33,34 @@ CONFIG_SCHEMA = number.number_schema(LVGLNumber).extend(
|
||||
async def to_code(config):
|
||||
widget = await get_widgets(config, CONF_WIDGET)
|
||||
widget = widget[0]
|
||||
var = await number.new_number(
|
||||
config,
|
||||
max_value=widget.get_max(),
|
||||
min_value=widget.get_min(),
|
||||
step=widget.get_step(),
|
||||
)
|
||||
|
||||
await wait_for_widgets()
|
||||
async with LambdaContext([], return_type=cg.float_) as value:
|
||||
value.add(ReturnStatement(widget.get_value()))
|
||||
async with LambdaContext([(cg.float_, "v")]) as control:
|
||||
await widget.set_property(
|
||||
"value", MockObj("v") * MockObj(widget.get_scale()), config[CONF_ANIMATED]
|
||||
)
|
||||
lv.event_send(widget.obj, API_EVENT, cg.nullptr)
|
||||
control.add(var.publish_state(widget.get_value()))
|
||||
async with LambdaContext(EVENT_ARG) as event:
|
||||
event.add(var.publish_state(widget.get_value()))
|
||||
event_code = (
|
||||
LV_EVENT.VALUE_CHANGED
|
||||
if not config[CONF_UPDATE_ON_RELEASE]
|
||||
else LV_EVENT.RELEASED
|
||||
)
|
||||
async with LvContext():
|
||||
lv_add(var.set_control_lambda(await control.get_lambda()))
|
||||
lv_add(
|
||||
lvgl_static.add_event_cb(
|
||||
widget.obj, await event.get_lambda(), UPDATE_EVENT, event_code
|
||||
)
|
||||
var = await number.new_number(
|
||||
config,
|
||||
await control.get_lambda(),
|
||||
await value.get_lambda(),
|
||||
event_code,
|
||||
config[CONF_RESTORE_VALUE],
|
||||
max_value=widget.get_max(),
|
||||
min_value=widget.get_min(),
|
||||
step=widget.get_step(),
|
||||
)
|
||||
async with LambdaContext(EVENT_ARG) as event:
|
||||
event.add(var.on_value())
|
||||
await cg.register_component(var, config)
|
||||
cg.add(
|
||||
lvgl_static.add_event_cb(
|
||||
widget.obj, await event.get_lambda(), UPDATE_EVENT, event_code
|
||||
)
|
||||
lv_add(var.publish_state(widget.get_value()))
|
||||
)
|
||||
|
@ -3,33 +3,46 @@
|
||||
#include <utility>
|
||||
|
||||
#include "esphome/components/number/number.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace lvgl {
|
||||
|
||||
class LVGLNumber : public number::Number {
|
||||
class LVGLNumber : public number::Number, public Component {
|
||||
public:
|
||||
void set_control_lambda(std::function<void(float)> control_lambda) {
|
||||
this->control_lambda_ = std::move(control_lambda);
|
||||
if (this->initial_state_.has_value()) {
|
||||
this->control_lambda_(this->initial_state_.value());
|
||||
this->initial_state_.reset();
|
||||
LVGLNumber(std::function<void(float)> control_lambda, std::function<float()> value_lambda, lv_event_code_t event,
|
||||
bool restore)
|
||||
: control_lambda_(std::move(control_lambda)),
|
||||
value_lambda_(std::move(value_lambda)),
|
||||
event_(event),
|
||||
restore_(restore) {}
|
||||
|
||||
void setup() override {
|
||||
float value = this->value_lambda_();
|
||||
if (this->restore_) {
|
||||
this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
|
||||
if (this->pref_.load(&value)) {
|
||||
this->control_lambda_(value);
|
||||
}
|
||||
}
|
||||
this->publish_state(value);
|
||||
}
|
||||
|
||||
void on_value() { this->publish_state(this->value_lambda_()); }
|
||||
|
||||
protected:
|
||||
void control(float value) override {
|
||||
if (this->control_lambda_ != nullptr) {
|
||||
this->control_lambda_(value);
|
||||
} else {
|
||||
this->initial_state_ = value;
|
||||
}
|
||||
this->control_lambda_(value);
|
||||
this->publish_state(value);
|
||||
if (this->restore_)
|
||||
this->pref_.save(&value);
|
||||
}
|
||||
std::function<void(float)> control_lambda_{};
|
||||
optional<float> initial_state_{};
|
||||
std::function<void(float)> control_lambda_;
|
||||
std::function<float()> value_lambda_;
|
||||
lv_event_code_t event_;
|
||||
bool restore_;
|
||||
ESPPreferenceObject pref_{};
|
||||
};
|
||||
|
||||
} // namespace lvgl
|
||||
|
@ -81,7 +81,9 @@ ENCODER_SCHEMA = cv.Schema(
|
||||
cv.declare_id(LVEncoderListener), requires_component("binary_sensor")
|
||||
),
|
||||
cv.Optional(CONF_GROUP): cv.declare_id(lv_group_t),
|
||||
cv.Optional(df.CONF_INITIAL_FOCUS): cv.use_id(lv_obj_t),
|
||||
cv.Optional(df.CONF_INITIAL_FOCUS): cv.All(
|
||||
LIST_ACTION_SCHEMA, cv.Length(min=1, max=1)
|
||||
),
|
||||
cv.Optional(df.CONF_LONG_PRESS_TIME, default="400ms"): PRESS_TIME,
|
||||
cv.Optional(df.CONF_LONG_PRESS_REPEAT_TIME, default="100ms"): PRESS_TIME,
|
||||
}
|
||||
|
@ -1,18 +1,19 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import select
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_OPTIONS
|
||||
from esphome.const import CONF_ID, CONF_OPTIONS, CONF_RESTORE_VALUE
|
||||
|
||||
from ..defines import CONF_ANIMATED, CONF_WIDGET, literal
|
||||
from ..lvcode import LvContext
|
||||
from ..types import LvSelect, lvgl_ns
|
||||
from ..widgets import get_widgets, wait_for_widgets
|
||||
from ..widgets import get_widgets
|
||||
|
||||
LVGLSelect = lvgl_ns.class_("LVGLSelect", select.Select)
|
||||
LVGLSelect = lvgl_ns.class_("LVGLSelect", select.Select, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = select.select_schema(LVGLSelect).extend(
|
||||
{
|
||||
cv.Required(CONF_WIDGET): cv.use_id(LvSelect),
|
||||
cv.Optional(CONF_ANIMATED, default=False): cv.boolean,
|
||||
cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
@ -21,12 +22,9 @@ async def to_code(config):
|
||||
widget = await get_widgets(config, CONF_WIDGET)
|
||||
widget = widget[0]
|
||||
options = widget.config.get(CONF_OPTIONS, [])
|
||||
selector = await select.new_select(config, options=options)
|
||||
await wait_for_widgets()
|
||||
async with LvContext() as ctx:
|
||||
ctx.add(
|
||||
selector.set_widget(
|
||||
widget.var,
|
||||
literal("LV_ANIM_ON" if config[CONF_ANIMATED] else "LV_ANIM_OFF"),
|
||||
)
|
||||
)
|
||||
animated = literal("LV_ANIM_ON" if config[CONF_ANIMATED] else "LV_ANIM_OFF")
|
||||
selector = cg.new_Pvariable(
|
||||
config[CONF_ID], widget.var, animated, config[CONF_RESTORE_VALUE]
|
||||
)
|
||||
await select.register_select(selector, config, options=options)
|
||||
await cg.register_component(selector, config)
|
||||
|
@ -11,12 +11,20 @@
|
||||
namespace esphome {
|
||||
namespace lvgl {
|
||||
|
||||
class LVGLSelect : public select::Select {
|
||||
class LVGLSelect : public select::Select, public Component {
|
||||
public:
|
||||
void set_widget(LvSelectable *widget, lv_anim_enable_t anim = LV_ANIM_OFF) {
|
||||
this->widget_ = widget;
|
||||
this->anim_ = anim;
|
||||
LVGLSelect(LvSelectable *widget, lv_anim_enable_t anim, bool restore)
|
||||
: widget_(widget), anim_(anim), restore_(restore) {}
|
||||
|
||||
void setup() override {
|
||||
this->set_options_();
|
||||
if (this->restore_) {
|
||||
size_t index;
|
||||
this->pref_ = global_preferences->make_preference<size_t>(this->get_object_id_hash());
|
||||
if (this->pref_.load(&index))
|
||||
this->widget_->set_selected_index(index, LV_ANIM_OFF);
|
||||
}
|
||||
this->publish();
|
||||
lv_obj_add_event_cb(
|
||||
this->widget_->obj,
|
||||
[](lv_event_t *e) {
|
||||
@ -24,11 +32,6 @@ class LVGLSelect : public select::Select {
|
||||
it->set_options_();
|
||||
},
|
||||
LV_EVENT_REFRESH, this);
|
||||
if (this->initial_state_.has_value()) {
|
||||
this->control(this->initial_state_.value());
|
||||
this->initial_state_.reset();
|
||||
}
|
||||
this->publish();
|
||||
auto lamb = [](lv_event_t *e) {
|
||||
auto *self = static_cast<LVGLSelect *>(e->user_data);
|
||||
self->publish();
|
||||
@ -37,21 +40,25 @@ class LVGLSelect : public select::Select {
|
||||
lv_obj_add_event_cb(this->widget_->obj, lamb, lv_update_event, this);
|
||||
}
|
||||
|
||||
void publish() { this->publish_state(this->widget_->get_selected_text()); }
|
||||
void publish() {
|
||||
this->publish_state(this->widget_->get_selected_text());
|
||||
if (this->restore_) {
|
||||
auto index = this->widget_->get_selected_index();
|
||||
this->pref_.save(&index);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
void control(const std::string &value) override {
|
||||
if (this->widget_ != nullptr) {
|
||||
this->widget_->set_selected_text(value, this->anim_);
|
||||
} else {
|
||||
this->initial_state_ = value;
|
||||
}
|
||||
this->widget_->set_selected_text(value, this->anim_);
|
||||
this->publish();
|
||||
}
|
||||
void set_options_() { this->traits.set_options(this->widget_->get_options()); }
|
||||
|
||||
LvSelectable *widget_{};
|
||||
optional<std::string> initial_state_{};
|
||||
lv_anim_enable_t anim_{LV_ANIM_OFF};
|
||||
LvSelectable *widget_;
|
||||
lv_anim_enable_t anim_;
|
||||
bool restore_;
|
||||
ESPPreferenceObject pref_{};
|
||||
};
|
||||
|
||||
} // namespace lvgl
|
||||
|
@ -250,7 +250,7 @@ async def button_update_to_code(config, action_id, template_arg, args):
|
||||
widgets = await get_widgets(config[CONF_ID])
|
||||
assert all(isinstance(w, MatrixButton) for w in widgets)
|
||||
|
||||
async def do_button_update(w: MatrixButton):
|
||||
async def do_button_update(w):
|
||||
if (width := config.get(CONF_WIDTH)) is not None:
|
||||
lv.btnmatrix_set_btn_width(w.obj, w.index, width)
|
||||
if config.get(CONF_SELECTED):
|
||||
@ -275,5 +275,5 @@ async def button_update_to_code(config, action_id, template_arg, args):
|
||||
)
|
||||
|
||||
return await action_to_code(
|
||||
widgets, do_button_update, action_id, template_arg, args
|
||||
widgets, do_button_update, action_id, template_arg, args, config
|
||||
)
|
||||
|
@ -97,7 +97,7 @@ async def canvas_fill(config, action_id, template_arg, args):
|
||||
async def do_fill(w: Widget):
|
||||
lv.canvas_fill_bg(w.obj, color, opa)
|
||||
|
||||
return await action_to_code(widget, do_fill, action_id, template_arg, args)
|
||||
return await action_to_code(widget, do_fill, action_id, template_arg, args, config)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
@ -145,7 +145,9 @@ async def canvas_set_pixel(config, action_id, template_arg, args):
|
||||
x, y = point
|
||||
lv.canvas_set_px_opa(w.obj, x, y, opa_var)
|
||||
|
||||
return await action_to_code(widget, do_set_pixels, action_id, template_arg, args)
|
||||
return await action_to_code(
|
||||
widget, do_set_pixels, action_id, template_arg, args, config
|
||||
)
|
||||
|
||||
|
||||
DRAW_SCHEMA = cv.Schema(
|
||||
@ -181,7 +183,9 @@ async def draw_to_code(config, dsc_type, props, do_draw, action_id, template_arg
|
||||
lv_assign(getattr(dsc, mapped_prop), value)
|
||||
await do_draw(w, x, y, dsc_addr)
|
||||
|
||||
return await action_to_code(widget, action_func, action_id, template_arg, args)
|
||||
return await action_to_code(
|
||||
widget, action_func, action_id, template_arg, args, config
|
||||
)
|
||||
|
||||
|
||||
RECT_PROPS = {
|
||||
|
@ -297,7 +297,9 @@ async def indicator_update_to_code(config, action_id, template_arg, args):
|
||||
async def set_value(w: Widget):
|
||||
await set_indicator_values(w.var, w.obj, config)
|
||||
|
||||
return await action_to_code(widget, set_value, action_id, template_arg, args)
|
||||
return await action_to_code(
|
||||
widget, set_value, action_id, template_arg, args, config
|
||||
)
|
||||
|
||||
|
||||
async def set_indicator_values(meter, indicator, config):
|
||||
|
134
esphome/components/mapping/__init__.py
Normal file
134
esphome/components/mapping/__init__.py
Normal file
@ -0,0 +1,134 @@
|
||||
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,7 +62,11 @@ void MDNSComponent::compile_records_() {
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
service.txt_records.push_back({"api_encryption", "Noise_NNpsk0_25519_ChaChaPoly_SHA256"});
|
||||
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"});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ESPHOME_PROJECT_NAME
|
||||
|
@ -138,7 +138,11 @@ void MQTTClientComponent::send_device_info_() {
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
root["api_encryption"] = "Noise_NNpsk0_25519_ChaChaPoly_SHA256";
|
||||
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";
|
||||
}
|
||||
#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_libretiny=cv.Version(1, 7, 0),
|
||||
bk72xx_arduino=cv.Version(1, 7, 0),
|
||||
),
|
||||
cv.boolean_false,
|
||||
),
|
||||
|
1
esphome/components/pm2005/__init__.py
Normal file
1
esphome/components/pm2005/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""PM2005/2105 component for ESPHome."""
|
123
esphome/components/pm2005/pm2005.cpp
Normal file
123
esphome/components/pm2005/pm2005.cpp
Normal file
@ -0,0 +1,123 @@
|
||||
#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
|
46
esphome/components/pm2005/pm2005.h
Normal file
46
esphome/components/pm2005/pm2005.h
Normal file
@ -0,0 +1,46 @@
|
||||
#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
|
86
esphome/components/pm2005/sensor.py
Normal file
86
esphome/components/pm2005/sensor.py
Normal file
@ -0,0 +1,86 @@
|
||||
"""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,6 +89,12 @@ 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);
|
||||
}
|
||||
|
||||
@ -824,6 +830,174 @@ 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,6 +8,9 @@
|
||||
#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 {
|
||||
@ -169,6 +172,20 @@ 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_;
|
||||
|
@ -1,7 +1,8 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include "psram.h"
|
||||
#ifdef USE_ESP_IDF
|
||||
#include <esp_idf_version.h>
|
||||
#if defined(USE_ESP_IDF) && ESP_IDF_VERSION_MAJOR >= 5
|
||||
#include <esp_psram.h>
|
||||
#endif // USE_ESP_IDF
|
||||
|
||||
@ -15,7 +16,7 @@ static const char *const TAG = "psram";
|
||||
|
||||
void PsramComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "PSRAM:");
|
||||
#ifdef USE_ESP_IDF
|
||||
#if defined(USE_ESP_IDF) && ESP_IDF_VERSION_MAJOR >= 5
|
||||
bool available = esp_psram_is_initialized();
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Available: %s", YESNO(available));
|
||||
|
@ -208,7 +208,6 @@ void RemoteReceiverComponent::loop() {
|
||||
this->store_.buffer_read = next_read;
|
||||
|
||||
if (!this->temp_.empty()) {
|
||||
this->temp_.push_back(-this->idle_us_);
|
||||
this->call_listeners_dumpers_();
|
||||
}
|
||||
}
|
||||
@ -219,11 +218,9 @@ void RemoteReceiverComponent::loop() {
|
||||
this->decode_rmt_(item, len / sizeof(rmt_item32_t));
|
||||
vRingbufferReturnItem(this->ringbuf_, item);
|
||||
|
||||
if (this->temp_.empty())
|
||||
return;
|
||||
|
||||
this->temp_.push_back(-this->idle_us_);
|
||||
this->call_listeners_dumpers_();
|
||||
if (!this->temp_.empty()) {
|
||||
this->call_listeners_dumpers_();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -234,6 +231,7 @@ 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;
|
||||
@ -266,7 +264,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 > 0) {
|
||||
if (prev_length >= filter_ticks) {
|
||||
if (prev_level) {
|
||||
this->temp_.push_back(this->to_microseconds_(prev_length) * multiplier);
|
||||
} else {
|
||||
@ -276,6 +274,7 @@ 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
|
||||
@ -283,7 +282,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 > 0) {
|
||||
if (prev_length >= filter_ticks) {
|
||||
if (prev_level) {
|
||||
this->temp_.push_back(this->to_microseconds_(prev_length) * multiplier);
|
||||
} else {
|
||||
@ -293,14 +292,22 @@ 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 > 0) {
|
||||
if (prev_length >= filter_ticks && prev_level != idle_level) {
|
||||
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,6 +18,8 @@ from esphome.const import (
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
UNIT_PERCENT,
|
||||
CONF_AUTOMATIC_SELF_CALIBRATION,
|
||||
CONF_AMBIENT_PRESSURE_COMPENSATION,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
@ -33,10 +35,7 @@ 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,6 +20,10 @@ 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"]
|
||||
@ -47,11 +51,6 @@ 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,6 +5,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_HECTOPASCAL,
|
||||
CONF_MEASUREMENT_MODE,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
@ -22,7 +23,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,9 +52,8 @@ void Sml::loop() {
|
||||
break;
|
||||
|
||||
// remove start/end sequence
|
||||
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_);
|
||||
this->process_sml_file_(
|
||||
BytesView(this->sml_data_).subview(START_SEQ.size(), this->sml_data_.size() - START_SEQ.size() - 8));
|
||||
}
|
||||
break;
|
||||
};
|
||||
@ -66,8 +65,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 bytes &sml_data) {
|
||||
SmlFile sml_file = SmlFile(sml_data);
|
||||
void Sml::process_sml_file_(const BytesView &sml_data) {
|
||||
SmlFile sml_file(sml_data);
|
||||
std::vector<ObisInfo> obis_info = sml_file.get_obis_info();
|
||||
this->publish_obis_info_(obis_info);
|
||||
|
||||
@ -75,6 +74,7 @@ void Sml::process_sml_file_(const bytes &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,6 +83,7 @@ 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) {
|
||||
@ -92,10 +93,11 @@ 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_info.code_repr() != sml_listener->obis_code)
|
||||
if (obis_code != 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 bytes &sml_data);
|
||||
void process_sml_file_(const BytesView &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(bytes buffer) : buffer_(std::move(buffer)) {
|
||||
SmlFile::SmlFile(const BytesView &buffer) : buffer_(buffer) {
|
||||
// extract messages
|
||||
this->pos_ = 0;
|
||||
while (this->pos_ < this->buffer_.size()) {
|
||||
if (this->buffer_[this->pos_] == 0x00)
|
||||
break; // EndOfSmlMsg
|
||||
|
||||
SmlNode message = SmlNode();
|
||||
SmlNode message;
|
||||
if (!this->setup_node(&message))
|
||||
break;
|
||||
this->messages.emplace_back(message);
|
||||
this->messages.emplace_back(std::move(message));
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,22 +62,20 @@ 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();
|
||||
SmlNode child_node;
|
||||
if (!this->setup_node(&child_node))
|
||||
return false;
|
||||
node->nodes.emplace_back(child_node);
|
||||
node->nodes.emplace_back(std::move(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 = bytes(this->buffer_.begin() + this->pos_, this->buffer_.begin() + this->pos_ + length);
|
||||
node->value_bytes = buffer_.subview(this->pos_, length);
|
||||
// Increment the pointer past all consumed bytes
|
||||
this->pos_ += length;
|
||||
}
|
||||
@ -87,14 +85,14 @@ bool SmlFile::setup_node(SmlNode *node) {
|
||||
std::vector<ObisInfo> SmlFile::get_obis_info() {
|
||||
std::vector<ObisInfo> obis_info;
|
||||
for (auto const &message : messages) {
|
||||
SmlNode message_body = message.nodes[3];
|
||||
const auto &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;
|
||||
|
||||
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];
|
||||
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];
|
||||
|
||||
for (auto const &val_list_entry : val_list.nodes) {
|
||||
obis_info.emplace_back(server_id, val_list_entry);
|
||||
@ -103,7 +101,7 @@ std::vector<ObisInfo> SmlFile::get_obis_info() {
|
||||
return obis_info;
|
||||
}
|
||||
|
||||
std::string bytes_repr(const bytes &buffer) {
|
||||
std::string bytes_repr(const BytesView &buffer) {
|
||||
std::string repr;
|
||||
for (auto const value : buffer) {
|
||||
repr += str_sprintf("%02x", value & 0xff);
|
||||
@ -111,7 +109,7 @@ std::string bytes_repr(const bytes &buffer) {
|
||||
return repr;
|
||||
}
|
||||
|
||||
uint64_t bytes_to_uint(const bytes &buffer) {
|
||||
uint64_t bytes_to_uint(const BytesView &buffer) {
|
||||
uint64_t val = 0;
|
||||
for (auto const value : buffer) {
|
||||
val = (val << 8) + value;
|
||||
@ -119,7 +117,7 @@ uint64_t bytes_to_uint(const bytes &buffer) {
|
||||
return val;
|
||||
}
|
||||
|
||||
int64_t bytes_to_int(const bytes &buffer) {
|
||||
int64_t bytes_to_int(const BytesView &buffer) {
|
||||
uint64_t tmp = bytes_to_uint(buffer);
|
||||
int64_t val;
|
||||
|
||||
@ -135,14 +133,14 @@ int64_t bytes_to_int(const bytes &buffer) {
|
||||
return val;
|
||||
}
|
||||
|
||||
std::string bytes_to_string(const bytes &buffer) { return std::string(buffer.begin(), buffer.end()); }
|
||||
std::string bytes_to_string(const BytesView &buffer) { return std::string(buffer.begin(), buffer.end()); }
|
||||
|
||||
ObisInfo::ObisInfo(bytes server_id, SmlNode val_list_entry) : server_id(std::move(server_id)) {
|
||||
ObisInfo::ObisInfo(const BytesView &server_id, const SmlNode &val_list_entry) : server_id(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);
|
||||
SmlNode value_node = val_list_entry.nodes[5];
|
||||
const auto &value_node = val_list_entry.nodes[5];
|
||||
this->value = value_node.value_bytes;
|
||||
this->value_type = value_node.type;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
@ -11,44 +12,73 @@ 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;
|
||||
bytes value_bytes;
|
||||
BytesView value_bytes;
|
||||
std::vector<SmlNode> nodes;
|
||||
};
|
||||
|
||||
class ObisInfo {
|
||||
public:
|
||||
ObisInfo(bytes server_id, SmlNode val_list_entry);
|
||||
bytes server_id;
|
||||
bytes code;
|
||||
bytes status;
|
||||
ObisInfo(const BytesView &server_id, const SmlNode &val_list_entry);
|
||||
BytesView server_id;
|
||||
BytesView code;
|
||||
BytesView status;
|
||||
char unit;
|
||||
char scaler;
|
||||
bytes value;
|
||||
BytesView value;
|
||||
uint16_t value_type;
|
||||
std::string code_repr() const;
|
||||
};
|
||||
|
||||
class SmlFile {
|
||||
public:
|
||||
SmlFile(bytes buffer);
|
||||
SmlFile(const BytesView &buffer);
|
||||
bool setup_node(SmlNode *node);
|
||||
std::vector<SmlNode> messages;
|
||||
std::vector<ObisInfo> get_obis_info();
|
||||
|
||||
protected:
|
||||
const bytes buffer_;
|
||||
const BytesView buffer_;
|
||||
size_t pos_;
|
||||
};
|
||||
|
||||
std::string bytes_repr(const bytes &buffer);
|
||||
std::string bytes_repr(const BytesView &buffer);
|
||||
|
||||
uint64_t bytes_to_uint(const bytes &buffer);
|
||||
uint64_t bytes_to_uint(const BytesView &buffer);
|
||||
|
||||
int64_t bytes_to_int(const bytes &buffer);
|
||||
int64_t bytes_to_int(const BytesView &buffer);
|
||||
|
||||
std::string bytes_to_string(const bytes &buffer);
|
||||
std::string bytes_to_string(const BytesView &buffer);
|
||||
} // namespace sml
|
||||
} // namespace esphome
|
||||
|
@ -441,9 +441,10 @@ void AudioPipeline::decode_task(void *params) {
|
||||
pdFALSE, // Wait for all the bits,
|
||||
portMAX_DELAY); // Block indefinitely until bit is set
|
||||
|
||||
xEventGroupClearBits(this_pipeline->event_group_,
|
||||
EventGroupBits::DECODER_MESSAGE_FINISHED | EventGroupBits::READER_MESSAGE_LOADED_MEDIA_TYPE);
|
||||
|
||||
if (!(event_bits & EventGroupBits::PIPELINE_COMMAND_STOP)) {
|
||||
xEventGroupClearBits(this_pipeline->event_group_,
|
||||
EventGroupBits::DECODER_MESSAGE_FINISHED | EventGroupBits::READER_MESSAGE_LOADED_MEDIA_TYPE);
|
||||
InfoErrorEvent event;
|
||||
event.source = InfoErrorSource::DECODER;
|
||||
|
||||
|
@ -1,19 +1,59 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import ENTITY_CATEGORY_DIAGNOSTIC, ICON_TIMER
|
||||
from esphome.const import (
|
||||
CONF_FORMAT,
|
||||
CONF_HOURS,
|
||||
CONF_ID,
|
||||
CONF_MINUTES,
|
||||
CONF_SECONDS,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_TIMER,
|
||||
)
|
||||
|
||||
uptime_ns = cg.esphome_ns.namespace("uptime")
|
||||
UptimeTextSensor = uptime_ns.class_(
|
||||
"UptimeTextSensor", text_sensor.TextSensor, cg.PollingComponent
|
||||
)
|
||||
CONFIG_SCHEMA = text_sensor.text_sensor_schema(
|
||||
UptimeTextSensor,
|
||||
icon=ICON_TIMER,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
).extend(cv.polling_component_schema("30s"))
|
||||
|
||||
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"))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await text_sensor.new_text_sensor(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)
|
||||
await cg.register_component(var, config)
|
||||
|
@ -16,6 +16,11 @@ 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,
|
||||
@ -32,25 +37,25 @@ void UptimeTextSensor::update() {
|
||||
unsigned remainder = uptime % 60;
|
||||
uptime /= 60;
|
||||
if (interval < 30) {
|
||||
buffer.insert(0, str_sprintf("%us", remainder));
|
||||
if (uptime == 0)
|
||||
this->insert_buffer_(buffer, this->seconds_text_, remainder);
|
||||
if (!this->expand_ && uptime == 0)
|
||||
break;
|
||||
}
|
||||
remainder = uptime % 60;
|
||||
uptime /= 60;
|
||||
if (interval < 1800) {
|
||||
buffer.insert(0, str_sprintf("%um", remainder));
|
||||
if (uptime == 0)
|
||||
this->insert_buffer_(buffer, this->minutes_text_, remainder);
|
||||
if (!this->expand_ && uptime == 0)
|
||||
break;
|
||||
}
|
||||
remainder = uptime % 24;
|
||||
uptime /= 24;
|
||||
if (interval < 12 * 3600) {
|
||||
buffer.insert(0, str_sprintf("%uh", remainder));
|
||||
if (uptime == 0)
|
||||
this->insert_buffer_(buffer, this->hours_text_, remainder);
|
||||
if (!this->expand_ && uptime == 0)
|
||||
break;
|
||||
}
|
||||
buffer.insert(0, str_sprintf("%ud", (unsigned) uptime));
|
||||
this->insert_buffer_(buffer, this->days_text_, (unsigned) uptime);
|
||||
break;
|
||||
}
|
||||
this->publish_state(buffer);
|
||||
|
@ -10,13 +10,32 @@ 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,6 +70,9 @@ 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
|
||||
)
|
||||
@ -150,6 +153,7 @@ 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,6 +258,47 @@ 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;
|
||||
@ -3307,6 +3348,175 @@ 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!");
|
||||
@ -3411,11 +3621,6 @@ 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();
|
||||
@ -3423,29 +3628,7 @@ void HOT WaveshareEPaper7P3InF::display() {
|
||||
// COMMAND DATA START TRANSMISSION
|
||||
ESP_LOGI(TAG, "Sending data to the display");
|
||||
this->command(0x10);
|
||||
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();
|
||||
}
|
||||
this->send_buffers_();
|
||||
|
||||
// COMMAND POWER ON
|
||||
ESP_LOGI(TAG, "Power on the display");
|
||||
@ -3464,9 +3647,11 @@ void HOT WaveshareEPaper7P3InF::display() {
|
||||
this->data(0x00);
|
||||
this->wait_until_idle_();
|
||||
|
||||
ESP_LOGI(TAG, "Set the display to deep sleep");
|
||||
this->command(0x07);
|
||||
this->data(0xA5);
|
||||
if (this->deep_sleep_between_updates_) {
|
||||
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,7 +94,10 @@ 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];
|
||||
@ -683,6 +686,29 @@ 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;
|
||||
@ -703,17 +729,6 @@ 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,7 +56,6 @@ from esphome.const import (
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
@ -1499,30 +1498,9 @@ 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)})."
|
||||
@ -1535,30 +1513,9 @@ 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)})."
|
||||
@ -1984,70 +1941,28 @@ 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 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:
|
||||
raise Invalid(
|
||||
f"""
|
||||
Internal Error: require_framework_version does not support this platform configuration
|
||||
platform: {core_data[KEY_TARGET_PLATFORM]}
|
||||
framework: {framework}
|
||||
|
||||
Please report this issue on GitHub -> https://github.com/esphome/issues/issues/new?template=bug_report.yml.
|
||||
"""
|
||||
)
|
||||
if CORE.is_host and framework == "host":
|
||||
key = "host"
|
||||
elif framework == "esp-idf":
|
||||
key = "esp_idf"
|
||||
else:
|
||||
key = CORE.target_platform + "_" + 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]
|
||||
|
||||
if max_version:
|
||||
if core_data[KEY_FRAMEWORK_VERSION] > required:
|
||||
|
@ -45,6 +45,8 @@ 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"
|
||||
@ -63,6 +65,7 @@ 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"
|
||||
@ -477,6 +480,7 @@ 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,7 +475,6 @@ 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
|
||||
@ -519,6 +518,8 @@ 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
|
||||
|
@ -789,13 +789,17 @@ class MockObj(Expression):
|
||||
|
||||
def class_(self, name: str, *parents: "MockObjClass") -> "MockObjClass":
|
||||
op = "" if self.op == "" else "::"
|
||||
return MockObjClass(f"{self.base}{op}{name}", ".", parents=parents)
|
||||
result = MockObjClass(f"{self.base}{op}{name}", ".", parents=parents)
|
||||
CORE.id_classes[str(result)] = result
|
||||
return result
|
||||
|
||||
def struct(self, name: str) -> "MockObjClass":
|
||||
return self.class_(name)
|
||||
|
||||
def enum(self, name: str, is_class: bool = False) -> "MockObj":
|
||||
return MockObjEnum(enum=name, is_class=is_class, base=self.base, op=self.op)
|
||||
result = MockObjEnum(enum=name, is_class=is_class, base=self.base, op=self.op)
|
||||
CORE.id_classes[str(result)] = result
|
||||
return result
|
||||
|
||||
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
|
||||
from esphome.helpers import get_bool_env, mkdir_p, sort_ip_addresses
|
||||
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 = address_list[0]
|
||||
port = sort_ip_addresses(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 = address_list[0]
|
||||
port = sort_ip_addresses(address_list)[0]
|
||||
|
||||
return [
|
||||
*DASHBOARD_COMMAND,
|
||||
|
@ -200,6 +200,45 @@ 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,6 +91,10 @@ 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,28 +78,47 @@ 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"
|
||||
assert data["type"] == "validate" or data["type"] == "exit"
|
||||
if data["type"] == "exit":
|
||||
return
|
||||
CORE.vscode = True
|
||||
CORE.ace = args.ace
|
||||
f = data["file"]
|
||||
if CORE.ace:
|
||||
CORE.config_path = os.path.join(args.configuration, f)
|
||||
if args.ace: # Running from ESPHome Compiler dashboard, not vscode
|
||||
CORE.config_path = os.path.join(args.configuration, data["file"])
|
||||
loader = _ace_loader
|
||||
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 = parse_yaml(file_name, StringIO(raw_yaml))
|
||||
config = loader(file_name)
|
||||
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 TextIOWrapper
|
||||
from io import BytesIO, TextIOBase, TextIOWrapper
|
||||
from ipaddress import _BaseAddress
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
from typing import Any
|
||||
from typing import Any, Callable
|
||||
import uuid
|
||||
|
||||
import yaml
|
||||
@ -69,7 +69,10 @@ class ESPForceValue:
|
||||
pass
|
||||
|
||||
|
||||
def make_data_base(value, from_database: ESPHomeDataBase = None):
|
||||
def make_data_base(
|
||||
value, from_database: ESPHomeDataBase = None
|
||||
) -> ESPHomeDataBase | Any:
|
||||
"""Wrap a value in a ESPHomeDataBase object."""
|
||||
try:
|
||||
value = add_class_to_obj(value, ESPHomeDataBase)
|
||||
if from_database is not None:
|
||||
@ -102,6 +105,11 @@ 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)
|
||||
@ -127,7 +135,7 @@ class ESPHomeLoaderMixin:
|
||||
return super().construct_yaml_seq(node)
|
||||
|
||||
@_add_data_ref
|
||||
def construct_yaml_map(self, node):
|
||||
def construct_yaml_map(self, node: yaml.MappingNode) -> OrderedDict[str, Any]:
|
||||
"""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
|
||||
@ -231,7 +239,7 @@ class ESPHomeLoaderMixin:
|
||||
return OrderedDict(pairs)
|
||||
|
||||
@_add_data_ref
|
||||
def construct_env_var(self, node):
|
||||
def construct_env_var(self, node: yaml.Node) -> str:
|
||||
args = node.value.split()
|
||||
# Check for a default value
|
||||
if len(args) > 1:
|
||||
@ -243,23 +251,23 @@ class ESPHomeLoaderMixin:
|
||||
)
|
||||
|
||||
@property
|
||||
def _directory(self):
|
||||
def _directory(self) -> str:
|
||||
return os.path.dirname(self.name)
|
||||
|
||||
def _rel_path(self, *args):
|
||||
def _rel_path(self, *args: str) -> str:
|
||||
return os.path.join(self._directory, *args)
|
||||
|
||||
@_add_data_ref
|
||||
def construct_secret(self, node):
|
||||
def construct_secret(self, node: yaml.Node) -> str:
|
||||
try:
|
||||
secrets = _load_yaml_internal(self._rel_path(SECRET_YAML))
|
||||
secrets = self.yaml_loader(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 = _load_yaml_internal(main_secret_yml)
|
||||
secrets = self.yaml_loader(main_secret_yml)
|
||||
except EsphomeError as er:
|
||||
raise EsphomeError(f"{e}\n{er}") from er
|
||||
|
||||
@ -272,7 +280,9 @@ class ESPHomeLoaderMixin:
|
||||
return val
|
||||
|
||||
@_add_data_ref
|
||||
def construct_include(self, node):
|
||||
def construct_include(
|
||||
self, node: yaml.Node
|
||||
) -> dict[str, Any] | OrderedDict[str, Any]:
|
||||
from esphome.const import CONF_VARS
|
||||
|
||||
def extract_file_vars(node):
|
||||
@ -290,71 +300,93 @@ class ESPHomeLoaderMixin:
|
||||
else:
|
||||
file, vars = node.value, None
|
||||
|
||||
result = _load_yaml_internal(self._rel_path(file))
|
||||
result = self.yaml_loader(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):
|
||||
def construct_include_dir_list(self, node: yaml.Node) -> list[dict[str, Any]]:
|
||||
files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml"))
|
||||
return [_load_yaml_internal(f) for f in files]
|
||||
return [self.yaml_loader(f) for f in files]
|
||||
|
||||
@_add_data_ref
|
||||
def construct_include_dir_merge_list(self, node):
|
||||
def construct_include_dir_merge_list(self, node: yaml.Node) -> list[dict[str, Any]]:
|
||||
files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml"))
|
||||
merged_list = []
|
||||
for fname in files:
|
||||
loaded_yaml = _load_yaml_internal(fname)
|
||||
loaded_yaml = self.yaml_loader(fname)
|
||||
if isinstance(loaded_yaml, list):
|
||||
merged_list.extend(loaded_yaml)
|
||||
return merged_list
|
||||
|
||||
@_add_data_ref
|
||||
def construct_include_dir_named(self, node):
|
||||
def construct_include_dir_named(
|
||||
self, node: yaml.Node
|
||||
) -> OrderedDict[str, dict[str, Any]]:
|
||||
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] = _load_yaml_internal(fname)
|
||||
mapping[filename] = self.yaml_loader(fname)
|
||||
return mapping
|
||||
|
||||
@_add_data_ref
|
||||
def construct_include_dir_merge_named(self, node):
|
||||
def construct_include_dir_merge_named(
|
||||
self, node: yaml.Node
|
||||
) -> OrderedDict[str, dict[str, Any]]:
|
||||
files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml"))
|
||||
mapping = OrderedDict()
|
||||
for fname in files:
|
||||
loaded_yaml = _load_yaml_internal(fname)
|
||||
loaded_yaml = self.yaml_loader(fname)
|
||||
if isinstance(loaded_yaml, dict):
|
||||
mapping.update(loaded_yaml)
|
||||
return mapping
|
||||
|
||||
@_add_data_ref
|
||||
def construct_lambda(self, node):
|
||||
def construct_lambda(self, node: yaml.Node) -> Lambda:
|
||||
return Lambda(str(node.value))
|
||||
|
||||
@_add_data_ref
|
||||
def construct_force(self, node):
|
||||
def construct_force(self, node: yaml.Node) -> ESPForceValue:
|
||||
obj = self.construct_scalar(node)
|
||||
return add_class_to_obj(obj, ESPForceValue)
|
||||
|
||||
@_add_data_ref
|
||||
def construct_extend(self, node):
|
||||
def construct_extend(self, node: yaml.Node) -> Extend:
|
||||
return Extend(str(node.value))
|
||||
|
||||
@_add_data_ref
|
||||
def construct_remove(self, node):
|
||||
def construct_remove(self, node: yaml.Node) -> Remove:
|
||||
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)
|
||||
@ -388,17 +420,30 @@ def load_yaml(fname: str, clear_secrets: bool = True) -> Any:
|
||||
return _load_yaml_internal(fname)
|
||||
|
||||
|
||||
def parse_yaml(file_name: str, file_handle: TextIOWrapper) -> Any:
|
||||
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:
|
||||
"""Parse a YAML file."""
|
||||
try:
|
||||
return _load_yaml_internal_with_type(ESPHomeLoader, file_name, file_handle)
|
||||
return _load_yaml_internal_with_type(
|
||||
ESPHomeLoader, file_name, file_handle, yaml_loader
|
||||
)
|
||||
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
|
||||
ESPHomePurePythonLoader, file_name, file_handle, yaml_loader
|
||||
)
|
||||
|
||||
|
||||
@ -435,23 +480,14 @@ 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)
|
||||
loader.name = fname
|
||||
loader = loader_type(content, fname, yaml_loader)
|
||||
try:
|
||||
return loader.get_single_data() or OrderedDict()
|
||||
except yaml.YAMLError as exc:
|
||||
@ -470,7 +506,7 @@ def dump(dict_, show_secrets=False):
|
||||
)
|
||||
|
||||
|
||||
def _is_file_valid(name):
|
||||
def _is_file_valid(name: str) -> bool:
|
||||
"""Decide if a file is valid."""
|
||||
return not name.startswith(".")
|
||||
|
||||
|
@ -12,9 +12,9 @@ pyserial==3.5
|
||||
platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
|
||||
esptool==4.8.1
|
||||
click==8.1.7
|
||||
esphome-dashboard==20250212.0
|
||||
aioesphomeapi==29.9.0
|
||||
zeroconf==0.146.3
|
||||
esphome-dashboard==20250415.0
|
||||
aioesphomeapi==30.0.1
|
||||
zeroconf==0.146.5
|
||||
puremagic==1.28
|
||||
ruamel.yaml==0.18.10 # dashboard_import
|
||||
esphome-glyphsets==0.2.0
|
||||
|
@ -1,6 +1,6 @@
|
||||
pylint==3.3.6
|
||||
flake8==7.2.0 # also change in .pre-commit-config.yaml when updating
|
||||
ruff==0.11.4 # also change in .pre-commit-config.yaml when updating
|
||||
ruff==0.11.5 # also change in .pre-commit-config.yaml when updating
|
||||
pyupgrade==3.19.1 # also change in .pre-commit-config.yaml when updating
|
||||
pre-commit
|
||||
|
||||
|
@ -1,4 +1,20 @@
|
||||
#!/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.
|
||||
@ -17,25 +33,14 @@ 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, padding=" "):
|
||||
def indent_list(text: str, padding: str = " ") -> list[str]:
|
||||
"""Indent each line of the given text with the specified padding."""
|
||||
lines = []
|
||||
for line in text.splitlines():
|
||||
if line == "":
|
||||
@ -48,54 +53,62 @@ def indent_list(text, padding=" "):
|
||||
return lines
|
||||
|
||||
|
||||
def indent(text, padding=" "):
|
||||
def indent(text: str, padding: str = " ") -> str:
|
||||
return "\n".join(indent_list(text, padding))
|
||||
|
||||
|
||||
def camel_to_snake(name):
|
||||
def camel_to_snake(name: str) -> str:
|
||||
# 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):
|
||||
def __init__(self, field):
|
||||
"""Base class for all type information."""
|
||||
|
||||
def __init__(self, field: descriptor.FieldDescriptorProto) -> None:
|
||||
self._field = field
|
||||
|
||||
@property
|
||||
def default_value(self):
|
||||
def default_value(self) -> str:
|
||||
"""Get the default value."""
|
||||
return ""
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def name(self) -> str:
|
||||
"""Get the name of the field."""
|
||||
return self._field.name
|
||||
|
||||
@property
|
||||
def arg_name(self):
|
||||
def arg_name(self) -> str:
|
||||
"""Get the argument name."""
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def field_name(self):
|
||||
def field_name(self) -> str:
|
||||
"""Get the field name."""
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def number(self):
|
||||
def number(self) -> int:
|
||||
"""Get the field number."""
|
||||
return self._field.number
|
||||
|
||||
@property
|
||||
def repeated(self):
|
||||
def repeated(self) -> bool:
|
||||
"""Check if the field is repeated."""
|
||||
return self._field.label == 3
|
||||
|
||||
@property
|
||||
def cpp_type(self):
|
||||
def cpp_type(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def reference_type(self):
|
||||
def reference_type(self) -> str:
|
||||
return f"{self.cpp_type} "
|
||||
|
||||
@property
|
||||
def const_reference_type(self):
|
||||
def const_reference_type(self) -> str:
|
||||
return f"{self.cpp_type} "
|
||||
|
||||
@property
|
||||
@ -171,28 +184,31 @@ class TypeInfo(ABC):
|
||||
decode_64bit = None
|
||||
|
||||
@property
|
||||
def encode_content(self):
|
||||
def encode_content(self) -> str:
|
||||
return f"buffer.{self.encode_func}({self.number}, this->{self.field_name});"
|
||||
|
||||
encode_func = None
|
||||
|
||||
@property
|
||||
def dump_content(self):
|
||||
def dump_content(self) -> str:
|
||||
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):
|
||||
pass
|
||||
def dump(self, name: str) -> str:
|
||||
"""Dump the value to the output."""
|
||||
|
||||
|
||||
TYPE_INFO = {}
|
||||
TYPE_INFO: dict[int, TypeInfo] = {}
|
||||
|
||||
|
||||
def register_type(name):
|
||||
def func(value):
|
||||
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."""
|
||||
TYPE_INFO[name] = value
|
||||
return value
|
||||
|
||||
@ -206,7 +222,7 @@ class DoubleType(TypeInfo):
|
||||
decode_64bit = "value.as_double()"
|
||||
encode_func = "encode_double"
|
||||
|
||||
def dump(self, name):
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'sprintf(buffer, "%g", {name});\n'
|
||||
o += "out.append(buffer);"
|
||||
return o
|
||||
@ -219,7 +235,7 @@ class FloatType(TypeInfo):
|
||||
decode_32bit = "value.as_float()"
|
||||
encode_func = "encode_float"
|
||||
|
||||
def dump(self, name):
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'sprintf(buffer, "%g", {name});\n'
|
||||
o += "out.append(buffer);"
|
||||
return o
|
||||
@ -232,7 +248,7 @@ class Int64Type(TypeInfo):
|
||||
decode_varint = "value.as_int64()"
|
||||
encode_func = "encode_int64"
|
||||
|
||||
def dump(self, name):
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'sprintf(buffer, "%lld", {name});\n'
|
||||
o += "out.append(buffer);"
|
||||
return o
|
||||
@ -245,7 +261,7 @@ class UInt64Type(TypeInfo):
|
||||
decode_varint = "value.as_uint64()"
|
||||
encode_func = "encode_uint64"
|
||||
|
||||
def dump(self, name):
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'sprintf(buffer, "%llu", {name});\n'
|
||||
o += "out.append(buffer);"
|
||||
return o
|
||||
@ -258,7 +274,7 @@ class Int32Type(TypeInfo):
|
||||
decode_varint = "value.as_int32()"
|
||||
encode_func = "encode_int32"
|
||||
|
||||
def dump(self, name):
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'sprintf(buffer, "%" PRId32, {name});\n'
|
||||
o += "out.append(buffer);"
|
||||
return o
|
||||
@ -271,7 +287,7 @@ class Fixed64Type(TypeInfo):
|
||||
decode_64bit = "value.as_fixed64()"
|
||||
encode_func = "encode_fixed64"
|
||||
|
||||
def dump(self, name):
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'sprintf(buffer, "%llu", {name});\n'
|
||||
o += "out.append(buffer);"
|
||||
return o
|
||||
@ -284,7 +300,7 @@ class Fixed32Type(TypeInfo):
|
||||
decode_32bit = "value.as_fixed32()"
|
||||
encode_func = "encode_fixed32"
|
||||
|
||||
def dump(self, name):
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'sprintf(buffer, "%" PRIu32, {name});\n'
|
||||
o += "out.append(buffer);"
|
||||
return o
|
||||
@ -297,7 +313,7 @@ class BoolType(TypeInfo):
|
||||
decode_varint = "value.as_bool()"
|
||||
encode_func = "encode_bool"
|
||||
|
||||
def dump(self, name):
|
||||
def dump(self, name: str) -> str:
|
||||
o = f"out.append(YESNO({name}));"
|
||||
return o
|
||||
|
||||
@ -319,28 +335,28 @@ class StringType(TypeInfo):
|
||||
@register_type(11)
|
||||
class MessageType(TypeInfo):
|
||||
@property
|
||||
def cpp_type(self):
|
||||
def cpp_type(self) -> str:
|
||||
return self._field.type_name[1:]
|
||||
|
||||
default_value = ""
|
||||
|
||||
@property
|
||||
def reference_type(self):
|
||||
def reference_type(self) -> str:
|
||||
return f"{self.cpp_type} &"
|
||||
|
||||
@property
|
||||
def const_reference_type(self):
|
||||
def const_reference_type(self) -> str:
|
||||
return f"const {self.cpp_type} &"
|
||||
|
||||
@property
|
||||
def encode_func(self):
|
||||
def encode_func(self) -> str:
|
||||
return f"encode_message<{self.cpp_type}>"
|
||||
|
||||
@property
|
||||
def decode_length(self):
|
||||
def decode_length(self) -> str:
|
||||
return f"value.as_message<{self.cpp_type}>()"
|
||||
|
||||
def dump(self, name):
|
||||
def dump(self, name: str) -> str:
|
||||
o = f"{name}.dump_to(out);"
|
||||
return o
|
||||
|
||||
@ -354,7 +370,7 @@ class BytesType(TypeInfo):
|
||||
decode_length = "value.as_string()"
|
||||
encode_func = "encode_string"
|
||||
|
||||
def dump(self, name):
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'out.append("\'").append({name}).append("\'");'
|
||||
return o
|
||||
|
||||
@ -366,7 +382,7 @@ class UInt32Type(TypeInfo):
|
||||
decode_varint = "value.as_uint32()"
|
||||
encode_func = "encode_uint32"
|
||||
|
||||
def dump(self, name):
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'sprintf(buffer, "%" PRIu32, {name});\n'
|
||||
o += "out.append(buffer);"
|
||||
return o
|
||||
@ -375,20 +391,20 @@ class UInt32Type(TypeInfo):
|
||||
@register_type(14)
|
||||
class EnumType(TypeInfo):
|
||||
@property
|
||||
def cpp_type(self):
|
||||
def cpp_type(self) -> str:
|
||||
return f"enums::{self._field.type_name[1:]}"
|
||||
|
||||
@property
|
||||
def decode_varint(self):
|
||||
def decode_varint(self) -> str:
|
||||
return f"value.as_enum<{self.cpp_type}>()"
|
||||
|
||||
default_value = ""
|
||||
|
||||
@property
|
||||
def encode_func(self):
|
||||
def encode_func(self) -> str:
|
||||
return f"encode_enum<{self.cpp_type}>"
|
||||
|
||||
def dump(self, name):
|
||||
def dump(self, name: str) -> str:
|
||||
o = f"out.append(proto_enum_to_string<{self.cpp_type}>({name}));"
|
||||
return o
|
||||
|
||||
@ -400,7 +416,7 @@ class SFixed32Type(TypeInfo):
|
||||
decode_32bit = "value.as_sfixed32()"
|
||||
encode_func = "encode_sfixed32"
|
||||
|
||||
def dump(self, name):
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'sprintf(buffer, "%" PRId32, {name});\n'
|
||||
o += "out.append(buffer);"
|
||||
return o
|
||||
@ -413,7 +429,7 @@ class SFixed64Type(TypeInfo):
|
||||
decode_64bit = "value.as_sfixed64()"
|
||||
encode_func = "encode_sfixed64"
|
||||
|
||||
def dump(self, name):
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'sprintf(buffer, "%lld", {name});\n'
|
||||
o += "out.append(buffer);"
|
||||
return o
|
||||
@ -426,7 +442,7 @@ class SInt32Type(TypeInfo):
|
||||
decode_varint = "value.as_sint32()"
|
||||
encode_func = "encode_sint32"
|
||||
|
||||
def dump(self, name):
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'sprintf(buffer, "%" PRId32, {name});\n'
|
||||
o += "out.append(buffer);"
|
||||
return o
|
||||
@ -439,27 +455,27 @@ class SInt64Type(TypeInfo):
|
||||
decode_varint = "value.as_sint64()"
|
||||
encode_func = "encode_sint64"
|
||||
|
||||
def dump(self, name):
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'sprintf(buffer, "%lld", {name});\n'
|
||||
o += "out.append(buffer);"
|
||||
return o
|
||||
|
||||
|
||||
class RepeatedTypeInfo(TypeInfo):
|
||||
def __init__(self, field):
|
||||
def __init__(self, field: descriptor.FieldDescriptorProto) -> None:
|
||||
super().__init__(field)
|
||||
self._ti = TYPE_INFO[field.type](field)
|
||||
self._ti: TypeInfo = TYPE_INFO[field.type](field)
|
||||
|
||||
@property
|
||||
def cpp_type(self):
|
||||
def cpp_type(self) -> str:
|
||||
return f"std::vector<{self._ti.cpp_type}>"
|
||||
|
||||
@property
|
||||
def reference_type(self):
|
||||
def reference_type(self) -> str:
|
||||
return f"{self.cpp_type} &"
|
||||
|
||||
@property
|
||||
def const_reference_type(self):
|
||||
def const_reference_type(self) -> str:
|
||||
return f"const {self.cpp_type} &"
|
||||
|
||||
@property
|
||||
@ -515,19 +531,19 @@ class RepeatedTypeInfo(TypeInfo):
|
||||
)
|
||||
|
||||
@property
|
||||
def _ti_is_bool(self):
|
||||
def _ti_is_bool(self) -> bool:
|
||||
# std::vector is specialized for bool, reference does not work
|
||||
return isinstance(self._ti, BoolType)
|
||||
|
||||
@property
|
||||
def encode_content(self):
|
||||
def encode_content(self) -> str:
|
||||
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):
|
||||
def dump_content(self) -> str:
|
||||
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"
|
||||
@ -539,7 +555,8 @@ class RepeatedTypeInfo(TypeInfo):
|
||||
pass
|
||||
|
||||
|
||||
def build_enum_type(desc):
|
||||
def build_enum_type(desc) -> tuple[str, str]:
|
||||
"""Builds the enum type."""
|
||||
name = desc.name
|
||||
out = f"enum {name} : uint32_t {{\n"
|
||||
for v in desc.value:
|
||||
@ -561,15 +578,15 @@ def build_enum_type(desc):
|
||||
return out, cpp
|
||||
|
||||
|
||||
def build_message_type(desc):
|
||||
public_content = []
|
||||
protected_content = []
|
||||
decode_varint = []
|
||||
decode_length = []
|
||||
decode_32bit = []
|
||||
decode_64bit = []
|
||||
encode = []
|
||||
dump = []
|
||||
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] = []
|
||||
|
||||
for field in desc.field:
|
||||
if field.label == 3:
|
||||
@ -687,27 +704,35 @@ SOURCE_BOTH = 0
|
||||
SOURCE_SERVER = 1
|
||||
SOURCE_CLIENT = 2
|
||||
|
||||
RECEIVE_CASES = {}
|
||||
RECEIVE_CASES: dict[int, str] = {}
|
||||
|
||||
ifdefs = {}
|
||||
ifdefs: dict[str, str] = {}
|
||||
|
||||
|
||||
def get_opt(desc, opt, default=None):
|
||||
def get_opt(
|
||||
desc: descriptor.DescriptorProto,
|
||||
opt: descriptor.MessageOptions,
|
||||
default: Any = None,
|
||||
) -> Any:
|
||||
"""Get the option from the descriptor."""
|
||||
if not desc.options.HasExtension(opt):
|
||||
return default
|
||||
return desc.options.Extensions[opt]
|
||||
|
||||
|
||||
def build_service_message_type(mt):
|
||||
def build_service_message_type(
|
||||
mt: descriptor.DescriptorProto,
|
||||
) -> tuple[str, str] | None:
|
||||
"""Builds the service message type."""
|
||||
snake = camel_to_snake(mt.name)
|
||||
id_ = get_opt(mt, pb.id)
|
||||
id_: int | None = get_opt(mt, pb.id)
|
||||
if id_ is None:
|
||||
return None
|
||||
|
||||
source = get_opt(mt, pb.source, 0)
|
||||
source: int = get_opt(mt, pb.source, 0)
|
||||
|
||||
ifdef = get_opt(mt, pb.ifdef)
|
||||
log = get_opt(mt, pb.log, True)
|
||||
ifdef: str | None = get_opt(mt, pb.ifdef)
|
||||
log: bool = get_opt(mt, pb.log, True)
|
||||
hout = ""
|
||||
cout = ""
|
||||
|
||||
@ -754,7 +779,8 @@ def build_service_message_type(mt):
|
||||
return hout, cout
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
"""Main function to generate the C++ classes."""
|
||||
cwd = Path(__file__).resolve().parent
|
||||
root = cwd.parent.parent / "esphome" / "components" / "api"
|
||||
prot_file = root / "api.protoc"
|
||||
@ -959,7 +985,7 @@ def main():
|
||||
try:
|
||||
import clang_format
|
||||
|
||||
def exec_clang_format(path):
|
||||
def exec_clang_format(path: Path) -> None:
|
||||
clang_format_path = os.path.join(
|
||||
os.path.dirname(clang_format.__file__), "data", "bin", "clang-format"
|
||||
)
|
||||
|
@ -21,6 +21,8 @@ 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
|
||||
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
|
||||
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
|
||||
{ set +x; } 2>/dev/null
|
||||
}
|
||||
|
||||
|
@ -26,3 +26,17 @@ 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:
|
||||
|
10
tests/components/api/test-dynamic-encryption.esp32-idf.yaml
Normal file
10
tests/components/api/test-dynamic-encryption.esp32-idf.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: !remove
|
@ -38,6 +38,7 @@ number:
|
||||
widget: slider_id
|
||||
name: LVGL Slider
|
||||
update_on_release: true
|
||||
restore_value: true
|
||||
- platform: lvgl
|
||||
widget: lv_arc
|
||||
id: lvgl_arc_number
|
||||
|
@ -990,3 +990,13 @@ color:
|
||||
green_int: 123
|
||||
blue_int: 64
|
||||
white_int: 255
|
||||
|
||||
select:
|
||||
- platform: lvgl
|
||||
id: lv_roller_select
|
||||
widget: lv_roller
|
||||
restore_value: true
|
||||
- platform: lvgl
|
||||
id: lv_dropdown_select
|
||||
widget: lv_dropdown
|
||||
restore_value: false
|
||||
|
@ -71,5 +71,6 @@ lvgl:
|
||||
sensor: encoder
|
||||
enter_button: pushbutton
|
||||
group: general
|
||||
initial_focus: lv_roller
|
||||
|
||||
<<: !include common.yaml
|
||||
|
71
tests/components/mapping/common.yaml
Normal file
71
tests/components/mapping/common.yaml
Normal file
@ -0,0 +1,71 @@
|
||||
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]);
|
17
tests/components/mapping/test.esp32-ard.yaml
Normal file
17
tests/components/mapping/test.esp32-ard.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
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
|
17
tests/components/mapping/test.esp32-c3-ard.yaml
Normal file
17
tests/components/mapping/test.esp32-c3-ard.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
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
|
17
tests/components/mapping/test.esp32-c3-idf.yaml
Normal file
17
tests/components/mapping/test.esp32-c3-idf.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
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
|
17
tests/components/mapping/test.esp32-idf.yaml
Normal file
17
tests/components/mapping/test.esp32-idf.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
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
|
17
tests/components/mapping/test.esp8266-ard.yaml
Normal file
17
tests/components/mapping/test.esp8266-ard.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
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
|
12
tests/components/mapping/test.host.yaml
Normal file
12
tests/components/mapping/test.host.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
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
|
17
tests/components/mapping/test.rp2040-ard.yaml
Normal file
17
tests/components/mapping/test.rp2040-ard.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
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
|
13
tests/components/pm2005/common.yaml
Normal file
13
tests/components/pm2005/common.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
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
|
5
tests/components/pm2005/test.esp32-ard.yaml
Normal file
5
tests/components/pm2005/test.esp32-ard.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/pm2005/test.esp32-c3-ard.yaml
Normal file
5
tests/components/pm2005/test.esp32-c3-ard.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/pm2005/test.esp32-c3-idf.yaml
Normal file
5
tests/components/pm2005/test.esp32-c3-idf.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/pm2005/test.esp32-idf.yaml
Normal file
5
tests/components/pm2005/test.esp32-idf.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/pm2005/test.esp8266-ard.yaml
Normal file
5
tests/components/pm2005/test.esp8266-ard.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/pm2005/test.rp2040-ard.yaml
Normal file
5
tests/components/pm2005/test.rp2040-ard.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
@ -1,6 +1,3 @@
|
||||
substitutions:
|
||||
verify_ssl: "false"
|
||||
|
||||
esphome:
|
||||
name: livingroomdevice
|
||||
friendly_name: Living Room Device
|
||||
@ -129,6 +126,14 @@ 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:
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user