mirror of
https://github.com/esphome/esphome.git
synced 2025-07-28 22:26:36 +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
|
. venv/bin/activate
|
||||||
pytest -vv --cov-report=xml --tb=native tests
|
pytest -vv --cov-report=xml --tb=native tests
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v5
|
uses: codecov/codecov-action@v5.4.2
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
|
@ -250,6 +250,7 @@ esphome/components/ltr501/* @latonita
|
|||||||
esphome/components/ltr_als_ps/* @latonita
|
esphome/components/ltr_als_ps/* @latonita
|
||||||
esphome/components/lvgl/* @clydebarrow
|
esphome/components/lvgl/* @clydebarrow
|
||||||
esphome/components/m5stack_8angle/* @rnauber
|
esphome/components/m5stack_8angle/* @rnauber
|
||||||
|
esphome/components/mapping/* @clydebarrow
|
||||||
esphome/components/matrix_keypad/* @ssieb
|
esphome/components/matrix_keypad/* @ssieb
|
||||||
esphome/components/max17043/* @blacknell
|
esphome/components/max17043/* @blacknell
|
||||||
esphome/components/max31865/* @DAVe3283
|
esphome/components/max31865/* @DAVe3283
|
||||||
@ -324,6 +325,7 @@ esphome/components/pcf8563/* @KoenBreeman
|
|||||||
esphome/components/pid/* @OttoWinter
|
esphome/components/pid/* @OttoWinter
|
||||||
esphome/components/pipsolar/* @andreashergert1984
|
esphome/components/pipsolar/* @andreashergert1984
|
||||||
esphome/components/pm1006/* @habbie
|
esphome/components/pm1006/* @habbie
|
||||||
|
esphome/components/pm2005/* @andrewjswan
|
||||||
esphome/components/pmsa003i/* @sjtrny
|
esphome/components/pmsa003i/* @sjtrny
|
||||||
esphome/components/pmwcs3/* @SeByDocKy
|
esphome/components/pmwcs3/* @SeByDocKy
|
||||||
esphome/components/pn532/* @OttoWinter @jesserockz
|
esphome/components/pn532/* @OttoWinter @jesserockz
|
||||||
|
@ -375,10 +375,12 @@ def upload_program(config, args, host):
|
|||||||
password = ota_conf.get(CONF_PASSWORD, "")
|
password = ota_conf.get(CONF_PASSWORD, "")
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not is_ip_address(CORE.address) # pylint: disable=too-many-boolean-expressions
|
CONF_MQTT in config # pylint: disable=too-many-boolean-expressions
|
||||||
and (get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED])
|
|
||||||
and CONF_MQTT in config
|
|
||||||
and (not args.device or args.device in ("MQTT", "OTA"))
|
and (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
|
from esphome import mqtt
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ void AM2315C::update() {
|
|||||||
data[2] = 0x00;
|
data[2] = 0x00;
|
||||||
if (this->write(data, 3) != i2c::ERROR_OK) {
|
if (this->write(data, 3) != i2c::ERROR_OK) {
|
||||||
ESP_LOGE(TAG, "Write failed!");
|
ESP_LOGE(TAG, "Write failed!");
|
||||||
this->mark_failed();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,12 +138,12 @@ void AM2315C::update() {
|
|||||||
uint8_t status = 0;
|
uint8_t status = 0;
|
||||||
if (this->read(&status, 1) != i2c::ERROR_OK) {
|
if (this->read(&status, 1) != i2c::ERROR_OK) {
|
||||||
ESP_LOGE(TAG, "Read failed!");
|
ESP_LOGE(TAG, "Read failed!");
|
||||||
this->mark_failed();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((status & 0x80) == 0x80) {
|
if ((status & 0x80) == 0x80) {
|
||||||
ESP_LOGE(TAG, "HW still busy!");
|
ESP_LOGE(TAG, "HW still busy!");
|
||||||
this->mark_failed();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +151,7 @@ void AM2315C::update() {
|
|||||||
uint8_t data[7];
|
uint8_t data[7];
|
||||||
if (this->read(data, 7) != i2c::ERROR_OK) {
|
if (this->read(data, 7) != i2c::ERROR_OK) {
|
||||||
ESP_LOGE(TAG, "Read failed!");
|
ESP_LOGE(TAG, "Read failed!");
|
||||||
this->mark_failed();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,8 @@ void AnalogThresholdBinarySensor::setup() {
|
|||||||
if (std::isnan(sensor_value)) {
|
if (std::isnan(sensor_value)) {
|
||||||
this->publish_initial_state(false);
|
this->publish_initial_state(false);
|
||||||
} else {
|
} 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) {
|
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 there is an invalid sensor reading, ignore the change and keep the current state
|
||||||
if (!std::isnan(sensor_value)) {
|
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() {
|
void AnalogThresholdBinarySensor::dump_config() {
|
||||||
LOG_BINARY_SENSOR("", "Analog Threshold Binary Sensor", this);
|
LOG_BINARY_SENSOR("", "Analog Threshold Binary Sensor", this);
|
||||||
LOG_SENSOR(" ", "Sensor", this->sensor_);
|
LOG_SENSOR(" ", "Sensor", this->sensor_);
|
||||||
ESP_LOGCONFIG(TAG, " Upper threshold: %.11f", this->upper_threshold_);
|
ESP_LOGCONFIG(TAG, " Upper threshold: %.11f", this->upper_threshold_.value());
|
||||||
ESP_LOGCONFIG(TAG, " Lower threshold: %.11f", this->lower_threshold_);
|
ESP_LOGCONFIG(TAG, " Lower threshold: %.11f", this->lower_threshold_.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace analog_threshold
|
} // 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; }
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
|
||||||
void set_sensor(sensor::Sensor *analog_sensor);
|
void set_sensor(sensor::Sensor *analog_sensor);
|
||||||
void set_upper_threshold(float threshold) { this->upper_threshold_ = threshold; }
|
template<typename T> void set_upper_threshold(T upper_threshold) { this->upper_threshold_ = upper_threshold; }
|
||||||
void set_lower_threshold(float threshold) { this->lower_threshold_ = threshold; }
|
template<typename T> void set_lower_threshold(T lower_threshold) { this->lower_threshold_ = lower_threshold; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
sensor::Sensor *sensor_{nullptr};
|
sensor::Sensor *sensor_{nullptr};
|
||||||
|
TemplatableValue<float> upper_threshold_{};
|
||||||
float upper_threshold_;
|
TemplatableValue<float> lower_threshold_{};
|
||||||
float lower_threshold_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace analog_threshold
|
} // namespace analog_threshold
|
||||||
|
@ -18,11 +18,11 @@ CONFIG_SCHEMA = (
|
|||||||
{
|
{
|
||||||
cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor),
|
cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor),
|
||||||
cv.Required(CONF_THRESHOLD): cv.Any(
|
cv.Required(CONF_THRESHOLD): cv.Any(
|
||||||
cv.float_,
|
cv.templatable(cv.float_),
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_UPPER): cv.float_,
|
cv.Required(CONF_UPPER): cv.templatable(cv.float_),
|
||||||
cv.Required(CONF_LOWER): 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])
|
sens = await cg.get_variable(config[CONF_SENSOR_ID])
|
||||||
cg.add(var.set_sensor(sens))
|
cg.add(var.set_sensor(sens))
|
||||||
|
|
||||||
if isinstance(config[CONF_THRESHOLD], float):
|
if isinstance(config[CONF_THRESHOLD], dict):
|
||||||
cg.add(var.set_upper_threshold(config[CONF_THRESHOLD]))
|
lower = await cg.templatable(config[CONF_THRESHOLD][CONF_LOWER], [], float)
|
||||||
cg.add(var.set_lower_threshold(config[CONF_THRESHOLD]))
|
upper = await cg.templatable(config[CONF_THRESHOLD][CONF_UPPER], [], float)
|
||||||
else:
|
else:
|
||||||
cg.add(var.set_upper_threshold(config[CONF_THRESHOLD][CONF_UPPER]))
|
lower = await cg.templatable(config[CONF_THRESHOLD], [], float)
|
||||||
cg.add(var.set_lower_threshold(config[CONF_THRESHOLD][CONF_LOWER]))
|
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(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
@ -95,11 +108,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
CONF_SERVICES, group_of_exclusion=CONF_ACTIONS
|
CONF_SERVICES, group_of_exclusion=CONF_ACTIONS
|
||||||
): ACTIONS_SCHEMA,
|
): ACTIONS_SCHEMA,
|
||||||
cv.Exclusive(CONF_ACTIONS, 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.Optional(CONF_ENCRYPTION): _encryption_schema,
|
||||||
{
|
|
||||||
cv.Required(CONF_KEY): validate_encryption_key,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
|
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
|
||||||
single=True
|
single=True
|
||||||
),
|
),
|
||||||
@ -151,9 +160,17 @@ async def to_code(config):
|
|||||||
config[CONF_ON_CLIENT_DISCONNECTED],
|
config[CONF_ON_CLIENT_DISCONNECTED],
|
||||||
)
|
)
|
||||||
|
|
||||||
if encryption_config := config.get(CONF_ENCRYPTION):
|
if (encryption_config := config.get(CONF_ENCRYPTION, None)) is not None:
|
||||||
decoded = base64.b64decode(encryption_config[CONF_KEY])
|
if key := encryption_config.get(CONF_KEY):
|
||||||
cg.add(var.set_noise_psk(list(decoded)))
|
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_define("USE_API_NOISE")
|
||||||
cg.add_library("esphome/noise-c", "0.1.6")
|
cg.add_library("esphome/noise-c", "0.1.6")
|
||||||
else:
|
else:
|
||||||
|
@ -31,6 +31,7 @@ service APIConnection {
|
|||||||
option (needs_authentication) = false;
|
option (needs_authentication) = false;
|
||||||
}
|
}
|
||||||
rpc execute_service (ExecuteServiceRequest) returns (void) {}
|
rpc execute_service (ExecuteServiceRequest) returns (void) {}
|
||||||
|
rpc noise_encryption_set_key (NoiseEncryptionSetKeyRequest) returns (NoiseEncryptionSetKeyResponse) {}
|
||||||
|
|
||||||
rpc cover_command (CoverCommandRequest) returns (void) {}
|
rpc cover_command (CoverCommandRequest) returns (void) {}
|
||||||
rpc fan_command (FanCommandRequest) 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"
|
// The Bluetooth mac address of the device. For example "AC:BC:32:89:0E:AA"
|
||||||
string bluetooth_mac_address = 18;
|
string bluetooth_mac_address = 18;
|
||||||
|
|
||||||
|
// Supports receiving and saving api encryption key
|
||||||
|
bool api_encryption_supported = 19;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ListEntitiesRequest {
|
message ListEntitiesRequest {
|
||||||
@ -654,6 +658,23 @@ message SubscribeLogsResponse {
|
|||||||
bool send_failed = 4;
|
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 ====================
|
// ==================== HOMEASSISTANT.SERVICE ====================
|
||||||
message SubscribeHomeassistantServicesRequest {
|
message SubscribeHomeassistantServicesRequest {
|
||||||
option (id) = 34;
|
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) {
|
: parent_(parent), deferred_message_queue_(this), initial_state_iterator_(this), list_entities_iterator_(this) {
|
||||||
this->proto_write_buffer_.reserve(64);
|
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))};
|
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
|
||||||
#elif defined(USE_API_NOISE)
|
#elif defined(USE_API_NOISE)
|
||||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
|
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
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
resp.legacy_voice_assistant_version = voice_assistant::global_voice_assistant->get_legacy_version();
|
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();
|
resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
|
||||||
|
#endif
|
||||||
|
#ifdef USE_API_NOISE
|
||||||
|
resp.api_encryption_supported = true;
|
||||||
#endif
|
#endif
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
@ -1869,6 +1879,26 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
|||||||
ESP_LOGV(TAG, "Could not find matching service!");
|
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) {
|
void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {
|
||||||
state_subs_at_ = 0;
|
state_subs_at_ = 0;
|
||||||
}
|
}
|
||||||
|
@ -300,6 +300,9 @@ class APIConnection : public APIServerConnection {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
void execute_service(const ExecuteServiceRequest &msg) override;
|
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_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; }
|
||||||
bool is_connection_setup() override {
|
bool is_connection_setup() override {
|
||||||
|
@ -311,6 +311,10 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
const std::string &name = App.get_name();
|
const std::string &name = App.get_name();
|
||||||
const uint8_t *name_ptr = reinterpret_cast<const uint8_t *>(name.c_str());
|
const uint8_t *name_ptr = reinterpret_cast<const uint8_t *>(name.c_str());
|
||||||
msg.insert(msg.end(), name_ptr, name_ptr + name.size() + 1);
|
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());
|
aerr = write_frame_(msg.data(), msg.size());
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdint>
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@ -11,11 +11,20 @@ using psk_t = std::array<uint8_t, 32>;
|
|||||||
|
|
||||||
class APINoiseContext {
|
class APINoiseContext {
|
||||||
public:
|
public:
|
||||||
void set_psk(psk_t psk) { psk_ = psk; }
|
void set_psk(psk_t psk) {
|
||||||
const psk_t &get_psk() const { return 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:
|
protected:
|
||||||
psk_t psk_;
|
psk_t psk_{};
|
||||||
|
bool has_psk_{false};
|
||||||
};
|
};
|
||||||
#endif // USE_API_NOISE
|
#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();
|
this->voice_assistant_feature_flags = value.as_uint32();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 19: {
|
||||||
|
this->api_encryption_supported = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -865,6 +869,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_uint32(17, this->voice_assistant_feature_flags);
|
buffer.encode_uint32(17, this->voice_assistant_feature_flags);
|
||||||
buffer.encode_string(16, this->suggested_area);
|
buffer.encode_string(16, this->suggested_area);
|
||||||
buffer.encode_string(18, this->bluetooth_mac_address);
|
buffer.encode_string(18, this->bluetooth_mac_address);
|
||||||
|
buffer.encode_bool(19, this->api_encryption_supported);
|
||||||
}
|
}
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
void DeviceInfoResponse::dump_to(std::string &out) const {
|
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(" bluetooth_mac_address: ");
|
||||||
out.append("'").append(this->bluetooth_mac_address).append("'");
|
out.append("'").append(this->bluetooth_mac_address).append("'");
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" api_encryption_supported: ");
|
||||||
|
out.append(YESNO(this->api_encryption_supported));
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -3009,6 +3018,48 @@ void SubscribeLogsResponse::dump_to(std::string &out) const {
|
|||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#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 {}
|
void SubscribeHomeassistantServicesRequest::encode(ProtoWriteBuffer buffer) const {}
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const {
|
void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const {
|
||||||
|
@ -355,6 +355,7 @@ class DeviceInfoResponse : public ProtoMessage {
|
|||||||
uint32_t voice_assistant_feature_flags{0};
|
uint32_t voice_assistant_feature_flags{0};
|
||||||
std::string suggested_area{};
|
std::string suggested_area{};
|
||||||
std::string bluetooth_mac_address{};
|
std::string bluetooth_mac_address{};
|
||||||
|
bool api_encryption_supported{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
void dump_to(std::string &out) const override;
|
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_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt 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 {
|
class SubscribeHomeassistantServicesRequest : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
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) {
|
bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) {
|
||||||
return this->send_message_<SubscribeLogsResponse>(msg, 29);
|
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) {
|
bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) {
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str());
|
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());
|
ESP_LOGVV(TAG, "on_voice_assistant_set_configuration: %s", msg.dump().c_str());
|
||||||
#endif
|
#endif
|
||||||
this->on_voice_assistant_set_configuration(msg);
|
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
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1311,6 +1332,22 @@ void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest
|
|||||||
}
|
}
|
||||||
this->execute_service(msg);
|
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
|
#ifdef USE_COVER
|
||||||
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
|
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
|
||||||
if (!this->is_connection_setup()) {
|
if (!this->is_connection_setup()) {
|
||||||
|
@ -83,6 +83,12 @@ class APIServerConnectionBase : public ProtoService {
|
|||||||
#endif
|
#endif
|
||||||
virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
|
virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
|
||||||
bool send_subscribe_logs_response(const SubscribeLogsResponse &msg);
|
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){};
|
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
|
||||||
bool send_homeassistant_service_response(const HomeassistantServiceResponse &msg);
|
bool send_homeassistant_service_response(const HomeassistantServiceResponse &msg);
|
||||||
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
|
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 void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
|
||||||
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
|
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
|
||||||
virtual void execute_service(const ExecuteServiceRequest &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
|
#ifdef USE_COVER
|
||||||
virtual void cover_command(const CoverCommandRequest &msg) = 0;
|
virtual void cover_command(const CoverCommandRequest &msg) = 0;
|
||||||
#endif
|
#endif
|
||||||
@ -457,6 +466,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||||||
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
|
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
|
||||||
void on_get_time_request(const GetTimeRequest &msg) override;
|
void on_get_time_request(const GetTimeRequest &msg) override;
|
||||||
void on_execute_service_request(const ExecuteServiceRequest &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
|
#ifdef USE_COVER
|
||||||
void on_cover_command_request(const CoverCommandRequest &msg) override;
|
void on_cover_command_request(const CoverCommandRequest &msg) override;
|
||||||
#endif
|
#endif
|
||||||
|
@ -22,22 +22,40 @@ namespace api {
|
|||||||
static const char *const TAG = "api";
|
static const char *const TAG = "api";
|
||||||
|
|
||||||
// APIServer
|
// APIServer
|
||||||
|
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|
||||||
|
APIServer::APIServer() { global_api_server = this; }
|
||||||
|
|
||||||
void APIServer::setup() {
|
void APIServer::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
|
ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
|
||||||
this->setup_controller();
|
this->setup_controller();
|
||||||
socket_ = socket::socket_ip(SOCK_STREAM, 0);
|
|
||||||
if (socket_ == nullptr) {
|
#ifdef USE_API_NOISE
|
||||||
ESP_LOGW(TAG, "Could not create socket.");
|
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();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int enable = 1;
|
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) {
|
if (err != 0) {
|
||||||
ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
|
ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
|
||||||
// we can still continue
|
// we can still continue
|
||||||
}
|
}
|
||||||
err = socket_->setblocking(false);
|
err = this->socket_->setblocking(false);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
|
ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
@ -53,14 +71,14 @@ void APIServer::setup() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = socket_->bind((struct sockaddr *) &server, sl);
|
err = this->socket_->bind((struct sockaddr *) &server, sl);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
|
ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = socket_->listen(4);
|
err = this->socket_->listen(4);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
|
ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
@ -92,18 +110,19 @@ void APIServer::setup() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void APIServer::loop() {
|
void APIServer::loop() {
|
||||||
// Accept new clients
|
// Accept new clients
|
||||||
while (true) {
|
while (true) {
|
||||||
struct sockaddr_storage source_addr;
|
struct sockaddr_storage source_addr;
|
||||||
socklen_t addr_len = sizeof(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)
|
if (!sock)
|
||||||
break;
|
break;
|
||||||
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
|
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
|
||||||
|
|
||||||
auto *conn = new APIConnection(std::move(sock), this);
|
auto *conn = new APIConnection(std::move(sock), this);
|
||||||
clients_.emplace_back(conn);
|
this->clients_.emplace_back(conn);
|
||||||
conn->start();
|
conn->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,16 +155,22 @@ void APIServer::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void APIServer::dump_config() {
|
void APIServer::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "API Server:");
|
ESP_LOGCONFIG(TAG, "API Server:");
|
||||||
ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->port_);
|
ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->port_);
|
||||||
#ifdef USE_API_NOISE
|
#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
|
#else
|
||||||
ESP_LOGCONFIG(TAG, " Using noise encryption: NO");
|
ESP_LOGCONFIG(TAG, " Using noise encryption: NO");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool APIServer::uses_password() const { return !this->password_.empty(); }
|
bool APIServer::uses_password() const { return !this->password_.empty(); }
|
||||||
|
|
||||||
bool APIServer::check_password(const std::string &password) const {
|
bool APIServer::check_password(const std::string &password) const {
|
||||||
// depend only on input password length
|
// depend only on input password length
|
||||||
const char *a = this->password_.c_str();
|
const char *a = this->password_.c_str();
|
||||||
@ -174,7 +199,9 @@ bool APIServer::check_password(const std::string &password) const {
|
|||||||
|
|
||||||
return result == 0;
|
return result == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void APIServer::handle_disconnect(APIConnection *conn) {}
|
void APIServer::handle_disconnect(APIConnection *conn) {}
|
||||||
|
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
|
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
|
||||||
if (obj->is_internal())
|
if (obj->is_internal())
|
||||||
@ -342,57 +369,6 @@ void APIServer::on_update(update::UpdateEntity *obj) {
|
|||||||
}
|
}
|
||||||
#endif
|
#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
|
#ifdef USE_ALARM_CONTROL_PANEL
|
||||||
void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
|
void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
|
||||||
if (obj->is_internal())
|
if (obj->is_internal())
|
||||||
@ -402,6 +378,96 @@ void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlP
|
|||||||
}
|
}
|
||||||
#endif
|
#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 api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
#endif
|
#endif
|
||||||
|
@ -19,6 +19,12 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
|
#ifdef USE_API_NOISE
|
||||||
|
struct SavedNoisePsk {
|
||||||
|
psk_t psk;
|
||||||
|
} PACKED; // NOLINT
|
||||||
|
#endif
|
||||||
|
|
||||||
class APIServer : public Component, public Controller {
|
class APIServer : public Component, public Controller {
|
||||||
public:
|
public:
|
||||||
APIServer();
|
APIServer();
|
||||||
@ -35,6 +41,7 @@ class APIServer : public Component, public Controller {
|
|||||||
void set_reboot_timeout(uint32_t reboot_timeout);
|
void set_reboot_timeout(uint32_t reboot_timeout);
|
||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#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); }
|
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); }
|
||||||
std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
|
std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
|
||||||
#endif // USE_API_NOISE
|
#endif // USE_API_NOISE
|
||||||
@ -142,6 +149,7 @@ class APIServer : public Component, public Controller {
|
|||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
|
std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
|
||||||
|
ESPPreferenceObject noise_pref_;
|
||||||
#endif // USE_API_NOISE
|
#endif // USE_API_NOISE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,8 +30,12 @@ void AXS15231Touchscreen::setup() {
|
|||||||
this->interrupt_pin_->setup();
|
this->interrupt_pin_->setup();
|
||||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||||
}
|
}
|
||||||
this->x_raw_max_ = this->display_->get_native_width();
|
if (this->x_raw_max_ == 0) {
|
||||||
this->y_raw_max_ = this->display_->get_native_height();
|
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");
|
ESP_LOGCONFIG(TAG, "AXS15231 Touchscreen setup complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +48,7 @@ void AXS15231Touchscreen::update_touches() {
|
|||||||
err = this->read(data, sizeof(data));
|
err = this->read(data, sizeof(data));
|
||||||
ERROR_CHECK(err);
|
ERROR_CHECK(err);
|
||||||
this->status_clear_warning();
|
this->status_clear_warning();
|
||||||
if (data[0] != 0) // no touches
|
if (data[0] != 0 || data[1] == 0) // no touches
|
||||||
return;
|
return;
|
||||||
uint16_t x = encode_uint16(data[2] & 0xF, data[3]);
|
uint16_t x = encode_uint16(data[2] & 0xF, data[3]);
|
||||||
uint16_t y = encode_uint16(data[4] & 0xF, data[5]);
|
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")
|
ColorStruct = cg.esphome_ns.struct("Color")
|
||||||
|
|
||||||
|
INSTANCE_TYPE = ColorStruct
|
||||||
|
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
|
|
||||||
CONF_RED_INT = "red_int"
|
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) {
|
static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config) {
|
||||||
switch (bitrate) {
|
switch (bitrate) {
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3) || \
|
#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:
|
case canbus::CAN_1KBPS:
|
||||||
*t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_1KBITS();
|
*t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_1KBITS();
|
||||||
return true;
|
return true;
|
||||||
|
@ -58,7 +58,7 @@ void ESP32RMTLEDStripLightOutput::setup() {
|
|||||||
channel.flags.io_loop_back = 0;
|
channel.flags.io_loop_back = 0;
|
||||||
channel.flags.io_od_mode = 0;
|
channel.flags.io_od_mode = 0;
|
||||||
channel.flags.invert_out = 0;
|
channel.flags.invert_out = 0;
|
||||||
channel.flags.with_dma = 0;
|
channel.flags.with_dma = this->use_dma_;
|
||||||
channel.intr_priority = 0;
|
channel.intr_priority = 0;
|
||||||
if (rmt_new_tx_channel(&channel, &this->channel_) != ESP_OK) {
|
if (rmt_new_tx_channel(&channel, &this->channel_) != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "Channel creation failed");
|
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_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_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; }
|
||||||
void set_is_wrgb(bool is_wrgb) { this->is_wrgb_ = is_wrgb; }
|
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; }
|
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.
|
/// 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_encoder_handle_t encoder_{nullptr};
|
||||||
rmt_symbol_word_t *rmt_buf_{nullptr};
|
rmt_symbol_word_t *rmt_buf_{nullptr};
|
||||||
rmt_symbol_word_t bit0_, bit1_, reset_;
|
rmt_symbol_word_t bit0_, bit1_, reset_;
|
||||||
uint32_t rmt_symbols_;
|
uint32_t rmt_symbols_{48};
|
||||||
#else
|
#else
|
||||||
rmt_item32_t *rmt_buf_{nullptr};
|
rmt_item32_t *rmt_buf_{nullptr};
|
||||||
rmt_item32_t bit0_, bit1_, reset_;
|
rmt_item32_t bit0_, bit1_, reset_;
|
||||||
@ -94,11 +95,12 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
|||||||
|
|
||||||
uint8_t pin_;
|
uint8_t pin_;
|
||||||
uint16_t num_leds_;
|
uint16_t num_leds_;
|
||||||
bool is_rgbw_;
|
bool is_rgbw_{false};
|
||||||
bool is_wrgb_;
|
bool is_wrgb_{false};
|
||||||
bool use_psram_;
|
bool use_dma_{false};
|
||||||
|
bool use_psram_{false};
|
||||||
|
|
||||||
RGBOrder rgb_order_;
|
RGBOrder rgb_order_{ORDER_RGB};
|
||||||
|
|
||||||
uint32_t last_refresh_{0};
|
uint32_t last_refresh_{0};
|
||||||
optional<uint32_t> max_refresh_rate_{};
|
optional<uint32_t> max_refresh_rate_{};
|
||||||
|
@ -3,7 +3,7 @@ import logging
|
|||||||
|
|
||||||
from esphome import pins
|
from esphome import pins
|
||||||
import esphome.codegen as cg
|
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
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_CHIPSET,
|
CONF_CHIPSET,
|
||||||
@ -15,6 +15,7 @@ from esphome.const import (
|
|||||||
CONF_RGB_ORDER,
|
CONF_RGB_ORDER,
|
||||||
CONF_RMT_CHANNEL,
|
CONF_RMT_CHANNEL,
|
||||||
CONF_RMT_SYMBOLS,
|
CONF_RMT_SYMBOLS,
|
||||||
|
CONF_USE_DMA,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE
|
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_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
|
||||||
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
|
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_IS_WRGB, 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.Optional(CONF_USE_PSRAM, default=True): cv.boolean,
|
||||||
cv.Inclusive(
|
cv.Inclusive(
|
||||||
CONF_BIT0_HIGH,
|
CONF_BIT0_HIGH,
|
||||||
@ -211,6 +217,8 @@ async def to_code(config):
|
|||||||
|
|
||||||
if esp32_rmt.use_new_rmt_driver():
|
if esp32_rmt.use_new_rmt_driver():
|
||||||
cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS]))
|
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:
|
else:
|
||||||
rmt_channel_t = cg.global_ns.enum("rmt_channel_t")
|
rmt_channel_t = cg.global_ns.enum("rmt_channel_t")
|
||||||
cg.add(
|
cg.add(
|
||||||
|
@ -291,6 +291,8 @@ SOURCE_WEB = "web"
|
|||||||
|
|
||||||
Image_ = image_ns.class_("Image")
|
Image_ = image_ns.class_("Image")
|
||||||
|
|
||||||
|
INSTANCE_TYPE = Image_
|
||||||
|
|
||||||
|
|
||||||
def compute_local_image_path(value) -> Path:
|
def compute_local_image_path(value) -> Path:
|
||||||
url = value[CONF_URL] if isinstance(value, dict) else value
|
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) || \
|
#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_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)
|
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||||
#include "driver/temp_sensor.h"
|
#include "driver/temp_sensor.h"
|
||||||
#else
|
#else
|
||||||
@ -33,7 +33,8 @@ static const char *const TAG = "internal_temperature";
|
|||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) && \
|
#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_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;
|
static temperature_sensor_handle_t tsensNew = NULL;
|
||||||
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && USE_ESP32_VARIANT
|
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && USE_ESP32_VARIANT
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
@ -49,7 +50,7 @@ void InternalTemperatureSensor::update() {
|
|||||||
success = (raw != 128);
|
success = (raw != 128);
|
||||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \
|
#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_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)
|
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||||
temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT();
|
temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT();
|
||||||
temp_sensor_set_config(tsens);
|
temp_sensor_set_config(tsens);
|
||||||
@ -100,7 +101,8 @@ void InternalTemperatureSensor::setup() {
|
|||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) && \
|
#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_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...");
|
ESP_LOGCONFIG(TAG, "Setting up temperature sensor...");
|
||||||
|
|
||||||
temperature_sensor_config_t tsens_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80);
|
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.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ACTION, CONF_GROUP, CONF_ID, CONF_TIMEOUT
|
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_generator import TemplateArguments, get_variable
|
||||||
from esphome.cpp_types import nullptr
|
from esphome.cpp_types import nullptr
|
||||||
|
|
||||||
@ -64,7 +65,14 @@ async def action_to_code(
|
|||||||
action_id,
|
action_id,
|
||||||
template_arg,
|
template_arg,
|
||||||
args,
|
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()
|
await wait_for_widgets()
|
||||||
async with LambdaContext(parameters=args, where=action_id) as context:
|
async with LambdaContext(parameters=args, where=action_id) as context:
|
||||||
for widget in widgets:
|
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)
|
lv.event_send(widget.obj, UPDATE_EVENT, nullptr)
|
||||||
|
|
||||||
widgets = await get_widgets(config[CONF_ID])
|
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(
|
@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)
|
await set_obj_properties(widget, config)
|
||||||
|
|
||||||
widgets = await get_widgets(config[CONF_ID])
|
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 .lvcode import lv, lv_add, lv_assign, lv_expr, lv_Pvariable
|
||||||
from .schemas import ENCODER_SCHEMA
|
from .schemas import ENCODER_SCHEMA
|
||||||
from .types import lv_group_t, lv_indev_type_t, lv_key_t
|
from .types import lv_group_t, lv_indev_type_t, lv_key_t
|
||||||
|
from .widgets import get_widgets
|
||||||
|
|
||||||
ENCODERS_CONFIG = cv.ensure_list(
|
ENCODERS_CONFIG = cv.ensure_list(
|
||||||
ENCODER_SCHEMA.extend(
|
ENCODER_SCHEMA.extend(
|
||||||
@ -76,5 +77,5 @@ async def encoders_to_code(var, config, default_group):
|
|||||||
async def initial_focus_to_code(config):
|
async def initial_focus_to_code(config):
|
||||||
for enc_conf in config[CONF_ENCODERS]:
|
for enc_conf in config[CONF_ENCODERS]:
|
||||||
if default_focus := enc_conf.get(CONF_INITIAL_FOCUS):
|
if default_focus := enc_conf.get(CONF_INITIAL_FOCUS):
|
||||||
obj = await cg.get_variable(default_focus)
|
widget = await get_widgets(default_focus)
|
||||||
lv.group_focus_obj(obj)
|
lv.group_focus_obj(widget[0].obj)
|
||||||
|
@ -173,7 +173,8 @@ class LambdaContext(CodeContext):
|
|||||||
|
|
||||||
class LvContext(LambdaContext):
|
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
|
added_lambda_count = 0
|
||||||
|
@ -433,7 +433,11 @@ void LvglComponent::setup() {
|
|||||||
auto height = display->get_height();
|
auto height = display->get_height();
|
||||||
size_t buffer_pixels = width * height / this->buffer_frac_;
|
size_t buffer_pixels = width * height / this->buffer_frac_;
|
||||||
auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8;
|
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) {
|
if (buffer == nullptr) {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
this->status_set_error("Memory allocation failure");
|
this->status_set_error("Memory allocation failure");
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import number
|
from esphome.components import number
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import CONF_RESTORE_VALUE
|
||||||
from esphome.cpp_generator import MockObj
|
from esphome.cpp_generator import MockObj
|
||||||
|
|
||||||
from ..defines import CONF_ANIMATED, CONF_UPDATE_ON_RELEASE, CONF_WIDGET
|
from ..defines import CONF_ANIMATED, CONF_UPDATE_ON_RELEASE, CONF_WIDGET
|
||||||
@ -10,21 +11,21 @@ from ..lvcode import (
|
|||||||
EVENT_ARG,
|
EVENT_ARG,
|
||||||
UPDATE_EVENT,
|
UPDATE_EVENT,
|
||||||
LambdaContext,
|
LambdaContext,
|
||||||
LvContext,
|
ReturnStatement,
|
||||||
lv,
|
lv,
|
||||||
lv_add,
|
|
||||||
lvgl_static,
|
lvgl_static,
|
||||||
)
|
)
|
||||||
from ..types import LV_EVENT, LvNumber, lvgl_ns
|
from ..types import LV_EVENT, LvNumber, lvgl_ns
|
||||||
from ..widgets import get_widgets, wait_for_widgets
|
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(
|
CONFIG_SCHEMA = number.number_schema(LVGLNumber).extend(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_WIDGET): cv.use_id(LvNumber),
|
cv.Required(CONF_WIDGET): cv.use_id(LvNumber),
|
||||||
cv.Optional(CONF_ANIMATED, default=True): animated,
|
cv.Optional(CONF_ANIMATED, default=True): animated,
|
||||||
cv.Optional(CONF_UPDATE_ON_RELEASE, default=False): cv.boolean,
|
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):
|
async def to_code(config):
|
||||||
widget = await get_widgets(config, CONF_WIDGET)
|
widget = await get_widgets(config, CONF_WIDGET)
|
||||||
widget = widget[0]
|
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()
|
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:
|
async with LambdaContext([(cg.float_, "v")]) as control:
|
||||||
await widget.set_property(
|
await widget.set_property(
|
||||||
"value", MockObj("v") * MockObj(widget.get_scale()), config[CONF_ANIMATED]
|
"value", MockObj("v") * MockObj(widget.get_scale()), config[CONF_ANIMATED]
|
||||||
)
|
)
|
||||||
lv.event_send(widget.obj, API_EVENT, cg.nullptr)
|
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 = (
|
event_code = (
|
||||||
LV_EVENT.VALUE_CHANGED
|
LV_EVENT.VALUE_CHANGED
|
||||||
if not config[CONF_UPDATE_ON_RELEASE]
|
if not config[CONF_UPDATE_ON_RELEASE]
|
||||||
else LV_EVENT.RELEASED
|
else LV_EVENT.RELEASED
|
||||||
)
|
)
|
||||||
async with LvContext():
|
var = await number.new_number(
|
||||||
lv_add(var.set_control_lambda(await control.get_lambda()))
|
config,
|
||||||
lv_add(
|
await control.get_lambda(),
|
||||||
lvgl_static.add_event_cb(
|
await value.get_lambda(),
|
||||||
widget.obj, await event.get_lambda(), UPDATE_EVENT, event_code
|
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 <utility>
|
||||||
|
|
||||||
#include "esphome/components/number/number.h"
|
#include "esphome/components/number/number.h"
|
||||||
#include "esphome/core/automation.h"
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/preferences.h"
|
#include "esphome/core/preferences.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace lvgl {
|
namespace lvgl {
|
||||||
|
|
||||||
class LVGLNumber : public number::Number {
|
class LVGLNumber : public number::Number, public Component {
|
||||||
public:
|
public:
|
||||||
void set_control_lambda(std::function<void(float)> control_lambda) {
|
LVGLNumber(std::function<void(float)> control_lambda, std::function<float()> value_lambda, lv_event_code_t event,
|
||||||
this->control_lambda_ = std::move(control_lambda);
|
bool restore)
|
||||||
if (this->initial_state_.has_value()) {
|
: control_lambda_(std::move(control_lambda)),
|
||||||
this->control_lambda_(this->initial_state_.value());
|
value_lambda_(std::move(value_lambda)),
|
||||||
this->initial_state_.reset();
|
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:
|
protected:
|
||||||
void control(float value) override {
|
void control(float value) override {
|
||||||
if (this->control_lambda_ != nullptr) {
|
this->control_lambda_(value);
|
||||||
this->control_lambda_(value);
|
this->publish_state(value);
|
||||||
} else {
|
if (this->restore_)
|
||||||
this->initial_state_ = value;
|
this->pref_.save(&value);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
std::function<void(float)> control_lambda_{};
|
std::function<void(float)> control_lambda_;
|
||||||
optional<float> initial_state_{};
|
std::function<float()> value_lambda_;
|
||||||
|
lv_event_code_t event_;
|
||||||
|
bool restore_;
|
||||||
|
ESPPreferenceObject pref_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace lvgl
|
} // namespace lvgl
|
||||||
|
@ -81,7 +81,9 @@ ENCODER_SCHEMA = cv.Schema(
|
|||||||
cv.declare_id(LVEncoderListener), requires_component("binary_sensor")
|
cv.declare_id(LVEncoderListener), requires_component("binary_sensor")
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_GROUP): cv.declare_id(lv_group_t),
|
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_TIME, default="400ms"): PRESS_TIME,
|
||||||
cv.Optional(df.CONF_LONG_PRESS_REPEAT_TIME, default="100ms"): 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
|
from esphome.components import select
|
||||||
import esphome.config_validation as cv
|
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 ..defines import CONF_ANIMATED, CONF_WIDGET, literal
|
||||||
from ..lvcode import LvContext
|
|
||||||
from ..types import LvSelect, lvgl_ns
|
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(
|
CONFIG_SCHEMA = select.select_schema(LVGLSelect).extend(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_WIDGET): cv.use_id(LvSelect),
|
cv.Required(CONF_WIDGET): cv.use_id(LvSelect),
|
||||||
cv.Optional(CONF_ANIMATED, default=False): cv.boolean,
|
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 = await get_widgets(config, CONF_WIDGET)
|
||||||
widget = widget[0]
|
widget = widget[0]
|
||||||
options = widget.config.get(CONF_OPTIONS, [])
|
options = widget.config.get(CONF_OPTIONS, [])
|
||||||
selector = await select.new_select(config, options=options)
|
animated = literal("LV_ANIM_ON" if config[CONF_ANIMATED] else "LV_ANIM_OFF")
|
||||||
await wait_for_widgets()
|
selector = cg.new_Pvariable(
|
||||||
async with LvContext() as ctx:
|
config[CONF_ID], widget.var, animated, config[CONF_RESTORE_VALUE]
|
||||||
ctx.add(
|
)
|
||||||
selector.set_widget(
|
await select.register_select(selector, config, options=options)
|
||||||
widget.var,
|
await cg.register_component(selector, config)
|
||||||
literal("LV_ANIM_ON" if config[CONF_ANIMATED] else "LV_ANIM_OFF"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
@ -11,12 +11,20 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace lvgl {
|
namespace lvgl {
|
||||||
|
|
||||||
class LVGLSelect : public select::Select {
|
class LVGLSelect : public select::Select, public Component {
|
||||||
public:
|
public:
|
||||||
void set_widget(LvSelectable *widget, lv_anim_enable_t anim = LV_ANIM_OFF) {
|
LVGLSelect(LvSelectable *widget, lv_anim_enable_t anim, bool restore)
|
||||||
this->widget_ = widget;
|
: widget_(widget), anim_(anim), restore_(restore) {}
|
||||||
this->anim_ = anim;
|
|
||||||
|
void setup() override {
|
||||||
this->set_options_();
|
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(
|
lv_obj_add_event_cb(
|
||||||
this->widget_->obj,
|
this->widget_->obj,
|
||||||
[](lv_event_t *e) {
|
[](lv_event_t *e) {
|
||||||
@ -24,11 +32,6 @@ class LVGLSelect : public select::Select {
|
|||||||
it->set_options_();
|
it->set_options_();
|
||||||
},
|
},
|
||||||
LV_EVENT_REFRESH, this);
|
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 lamb = [](lv_event_t *e) {
|
||||||
auto *self = static_cast<LVGLSelect *>(e->user_data);
|
auto *self = static_cast<LVGLSelect *>(e->user_data);
|
||||||
self->publish();
|
self->publish();
|
||||||
@ -37,21 +40,25 @@ class LVGLSelect : public select::Select {
|
|||||||
lv_obj_add_event_cb(this->widget_->obj, lamb, lv_update_event, this);
|
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:
|
protected:
|
||||||
void control(const std::string &value) override {
|
void control(const std::string &value) override {
|
||||||
if (this->widget_ != nullptr) {
|
this->widget_->set_selected_text(value, this->anim_);
|
||||||
this->widget_->set_selected_text(value, this->anim_);
|
this->publish();
|
||||||
} else {
|
|
||||||
this->initial_state_ = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
void set_options_() { this->traits.set_options(this->widget_->get_options()); }
|
void set_options_() { this->traits.set_options(this->widget_->get_options()); }
|
||||||
|
|
||||||
LvSelectable *widget_{};
|
LvSelectable *widget_;
|
||||||
optional<std::string> initial_state_{};
|
lv_anim_enable_t anim_;
|
||||||
lv_anim_enable_t anim_{LV_ANIM_OFF};
|
bool restore_;
|
||||||
|
ESPPreferenceObject pref_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace lvgl
|
} // 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])
|
widgets = await get_widgets(config[CONF_ID])
|
||||||
assert all(isinstance(w, MatrixButton) for w in widgets)
|
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:
|
if (width := config.get(CONF_WIDTH)) is not None:
|
||||||
lv.btnmatrix_set_btn_width(w.obj, w.index, width)
|
lv.btnmatrix_set_btn_width(w.obj, w.index, width)
|
||||||
if config.get(CONF_SELECTED):
|
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(
|
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):
|
async def do_fill(w: Widget):
|
||||||
lv.canvas_fill_bg(w.obj, color, opa)
|
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(
|
@automation.register_action(
|
||||||
@ -145,7 +145,9 @@ async def canvas_set_pixel(config, action_id, template_arg, args):
|
|||||||
x, y = point
|
x, y = point
|
||||||
lv.canvas_set_px_opa(w.obj, x, y, opa_var)
|
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(
|
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)
|
lv_assign(getattr(dsc, mapped_prop), value)
|
||||||
await do_draw(w, x, y, dsc_addr)
|
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 = {
|
RECT_PROPS = {
|
||||||
|
@ -297,7 +297,9 @@ async def indicator_update_to_code(config, action_id, template_arg, args):
|
|||||||
async def set_value(w: Widget):
|
async def set_value(w: Widget):
|
||||||
await set_indicator_values(w.var, w.obj, config)
|
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):
|
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"
|
#include "esphome/core/defines.h"
|
||||||
#ifdef USE_MDNS
|
#ifdef USE_MDNS
|
||||||
#include "mdns_component.h"
|
|
||||||
#include "esphome/core/version.h"
|
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/version.h"
|
||||||
|
#include "mdns_component.h"
|
||||||
|
|
||||||
#ifdef USE_API
|
#ifdef USE_API
|
||||||
#include "esphome/components/api/api_server.h"
|
#include "esphome/components/api/api_server.h"
|
||||||
@ -62,7 +62,11 @@ void MDNSComponent::compile_records_() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#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
|
#endif
|
||||||
|
|
||||||
#ifdef ESPHOME_PROJECT_NAME
|
#ifdef ESPHOME_PROJECT_NAME
|
||||||
|
@ -138,7 +138,11 @@ void MQTTClientComponent::send_device_info_() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#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
|
#endif
|
||||||
},
|
},
|
||||||
2, this->discovery_info_.retain);
|
2, this->discovery_info_.retain);
|
||||||
|
@ -26,7 +26,7 @@ CONFIG_SCHEMA = cv.Schema(
|
|||||||
esp32_arduino=cv.Version(0, 0, 0),
|
esp32_arduino=cv.Version(0, 0, 0),
|
||||||
esp8266_arduino=cv.Version(0, 0, 0),
|
esp8266_arduino=cv.Version(0, 0, 0),
|
||||||
rp2040_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,
|
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);
|
this->valve_row_(stream, obj, area, node, friendly_name);
|
||||||
#endif
|
#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);
|
req->send(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -824,6 +830,174 @@ void PrometheusHandler::valve_row_(AsyncResponseStream *stream, valve::Valve *ob
|
|||||||
}
|
}
|
||||||
#endif
|
#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 prometheus
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
#endif
|
#endif
|
||||||
|
@ -8,6 +8,9 @@
|
|||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/controller.h"
|
#include "esphome/core/controller.h"
|
||||||
#include "esphome/core/entity_base.h"
|
#include "esphome/core/entity_base.h"
|
||||||
|
#ifdef USE_CLIMATE
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace prometheus {
|
namespace prometheus {
|
||||||
@ -169,6 +172,20 @@ class PrometheusHandler : public AsyncWebHandler, public Component {
|
|||||||
std::string &friendly_name);
|
std::string &friendly_name);
|
||||||
#endif
|
#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_;
|
web_server_base::WebServerBase *base_;
|
||||||
bool include_internal_{false};
|
bool include_internal_{false};
|
||||||
std::map<EntityBase *, std::string> relabel_map_id_;
|
std::map<EntityBase *, std::string> relabel_map_id_;
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#include "psram.h"
|
#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>
|
#include <esp_psram.h>
|
||||||
#endif // USE_ESP_IDF
|
#endif // USE_ESP_IDF
|
||||||
|
|
||||||
@ -15,7 +16,7 @@ static const char *const TAG = "psram";
|
|||||||
|
|
||||||
void PsramComponent::dump_config() {
|
void PsramComponent::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "PSRAM:");
|
ESP_LOGCONFIG(TAG, "PSRAM:");
|
||||||
#ifdef USE_ESP_IDF
|
#if defined(USE_ESP_IDF) && ESP_IDF_VERSION_MAJOR >= 5
|
||||||
bool available = esp_psram_is_initialized();
|
bool available = esp_psram_is_initialized();
|
||||||
|
|
||||||
ESP_LOGCONFIG(TAG, " Available: %s", YESNO(available));
|
ESP_LOGCONFIG(TAG, " Available: %s", YESNO(available));
|
||||||
|
@ -208,7 +208,6 @@ void RemoteReceiverComponent::loop() {
|
|||||||
this->store_.buffer_read = next_read;
|
this->store_.buffer_read = next_read;
|
||||||
|
|
||||||
if (!this->temp_.empty()) {
|
if (!this->temp_.empty()) {
|
||||||
this->temp_.push_back(-this->idle_us_);
|
|
||||||
this->call_listeners_dumpers_();
|
this->call_listeners_dumpers_();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -219,11 +218,9 @@ void RemoteReceiverComponent::loop() {
|
|||||||
this->decode_rmt_(item, len / sizeof(rmt_item32_t));
|
this->decode_rmt_(item, len / sizeof(rmt_item32_t));
|
||||||
vRingbufferReturnItem(this->ringbuf_, item);
|
vRingbufferReturnItem(this->ringbuf_, item);
|
||||||
|
|
||||||
if (this->temp_.empty())
|
if (!this->temp_.empty()) {
|
||||||
return;
|
this->call_listeners_dumpers_();
|
||||||
|
}
|
||||||
this->temp_.push_back(-this->idle_us_);
|
|
||||||
this->call_listeners_dumpers_();
|
|
||||||
}
|
}
|
||||||
#endif
|
#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) {
|
void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t item_count) {
|
||||||
#endif
|
#endif
|
||||||
bool prev_level = false;
|
bool prev_level = false;
|
||||||
|
bool idle_level = false;
|
||||||
uint32_t prev_length = 0;
|
uint32_t prev_length = 0;
|
||||||
this->temp_.clear();
|
this->temp_.clear();
|
||||||
int32_t multiplier = this->pin_->is_inverted() ? -1 : 1;
|
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)) {
|
} else if ((bool(item[i].level0) == prev_level) || (item[i].duration0 < filter_ticks)) {
|
||||||
prev_length += item[i].duration0;
|
prev_length += item[i].duration0;
|
||||||
} else {
|
} else {
|
||||||
if (prev_length > 0) {
|
if (prev_length >= filter_ticks) {
|
||||||
if (prev_level) {
|
if (prev_level) {
|
||||||
this->temp_.push_back(this->to_microseconds_(prev_length) * multiplier);
|
this->temp_.push_back(this->to_microseconds_(prev_length) * multiplier);
|
||||||
} else {
|
} else {
|
||||||
@ -276,6 +274,7 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t item_count)
|
|||||||
prev_level = bool(item[i].level0);
|
prev_level = bool(item[i].level0);
|
||||||
prev_length = item[i].duration0;
|
prev_length = item[i].duration0;
|
||||||
}
|
}
|
||||||
|
idle_level = !bool(item[i].level0);
|
||||||
|
|
||||||
if (item[i].duration1 == 0u) {
|
if (item[i].duration1 == 0u) {
|
||||||
// EOF, sometimes garbage follows, break early
|
// 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)) {
|
} else if ((bool(item[i].level1) == prev_level) || (item[i].duration1 < filter_ticks)) {
|
||||||
prev_length += item[i].duration1;
|
prev_length += item[i].duration1;
|
||||||
} else {
|
} else {
|
||||||
if (prev_length > 0) {
|
if (prev_length >= filter_ticks) {
|
||||||
if (prev_level) {
|
if (prev_level) {
|
||||||
this->temp_.push_back(this->to_microseconds_(prev_length) * multiplier);
|
this->temp_.push_back(this->to_microseconds_(prev_length) * multiplier);
|
||||||
} else {
|
} else {
|
||||||
@ -293,14 +292,22 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t item_count)
|
|||||||
prev_level = bool(item[i].level1);
|
prev_level = bool(item[i].level1);
|
||||||
prev_length = item[i].duration1;
|
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) {
|
if (prev_level) {
|
||||||
this->temp_.push_back(this->to_microseconds_(prev_length) * multiplier);
|
this->temp_.push_back(this->to_microseconds_(prev_length) * multiplier);
|
||||||
} else {
|
} else {
|
||||||
this->temp_.push_back(-int32_t(this->to_microseconds_(prev_length)) * multiplier);
|
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
|
} // namespace remote_receiver
|
||||||
|
@ -18,6 +18,8 @@ from esphome.const import (
|
|||||||
UNIT_CELSIUS,
|
UNIT_CELSIUS,
|
||||||
UNIT_PARTS_PER_MILLION,
|
UNIT_PARTS_PER_MILLION,
|
||||||
UNIT_PERCENT,
|
UNIT_PERCENT,
|
||||||
|
CONF_AUTOMATIC_SELF_CALIBRATION,
|
||||||
|
CONF_AMBIENT_PRESSURE_COMPENSATION,
|
||||||
)
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
@ -33,10 +35,7 @@ ForceRecalibrationWithReference = scd30_ns.class_(
|
|||||||
"ForceRecalibrationWithReference", automation.Action
|
"ForceRecalibrationWithReference", automation.Action
|
||||||
)
|
)
|
||||||
|
|
||||||
CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration"
|
|
||||||
CONF_ALTITUDE_COMPENSATION = "altitude_compensation"
|
CONF_ALTITUDE_COMPENSATION = "altitude_compensation"
|
||||||
CONF_AMBIENT_PRESSURE_COMPENSATION = "ambient_pressure_compensation"
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
|
@ -20,6 +20,10 @@ from esphome.const import (
|
|||||||
UNIT_CELSIUS,
|
UNIT_CELSIUS,
|
||||||
UNIT_PARTS_PER_MILLION,
|
UNIT_PARTS_PER_MILLION,
|
||||||
UNIT_PERCENT,
|
UNIT_PERCENT,
|
||||||
|
CONF_AUTOMATIC_SELF_CALIBRATION,
|
||||||
|
CONF_AMBIENT_PRESSURE_COMPENSATION,
|
||||||
|
CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE,
|
||||||
|
CONF_MEASUREMENT_MODE,
|
||||||
)
|
)
|
||||||
|
|
||||||
CODEOWNERS = ["@sjtrny", "@martgras"]
|
CODEOWNERS = ["@sjtrny", "@martgras"]
|
||||||
@ -47,11 +51,6 @@ FactoryResetAction = scd4x_ns.class_("FactoryResetAction", automation.Action)
|
|||||||
|
|
||||||
|
|
||||||
CONF_ALTITUDE_COMPENSATION = "altitude_compensation"
|
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 = (
|
CONFIG_SCHEMA = (
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
|
@ -5,6 +5,7 @@ from esphome.const import (
|
|||||||
DEVICE_CLASS_PRESSURE,
|
DEVICE_CLASS_PRESSURE,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
UNIT_HECTOPASCAL,
|
UNIT_HECTOPASCAL,
|
||||||
|
CONF_MEASUREMENT_MODE,
|
||||||
)
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
@ -22,7 +23,7 @@ MEASUREMENT_MODE = {
|
|||||||
"mass_flow": MeasurementMode.MASS_FLOW_AVG,
|
"mass_flow": MeasurementMode.MASS_FLOW_AVG,
|
||||||
"differential_pressure": MeasurementMode.DP_AVG,
|
"differential_pressure": MeasurementMode.DP_AVG,
|
||||||
}
|
}
|
||||||
CONF_MEASUREMENT_MODE = "measurement_mode"
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
sensor.sensor_schema(
|
sensor.sensor_schema(
|
||||||
|
@ -52,9 +52,8 @@ void Sml::loop() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
// remove start/end sequence
|
// remove start/end sequence
|
||||||
this->sml_data_.erase(this->sml_data_.begin(), this->sml_data_.begin() + START_SEQ.size());
|
this->process_sml_file_(
|
||||||
this->sml_data_.resize(this->sml_data_.size() - 8);
|
BytesView(this->sml_data_).subview(START_SEQ.size(), this->sml_data_.size() - START_SEQ.size() - 8));
|
||||||
this->process_sml_file_(this->sml_data_);
|
|
||||||
}
|
}
|
||||||
break;
|
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));
|
this->data_callbacks_.add(std::move(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sml::process_sml_file_(const bytes &sml_data) {
|
void Sml::process_sml_file_(const BytesView &sml_data) {
|
||||||
SmlFile sml_file = SmlFile(sml_data);
|
SmlFile sml_file(sml_data);
|
||||||
std::vector<ObisInfo> obis_info = sml_file.get_obis_info();
|
std::vector<ObisInfo> obis_info = sml_file.get_obis_info();
|
||||||
this->publish_obis_info_(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) {
|
void Sml::log_obis_info_(const std::vector<ObisInfo> &obis_info_vec) {
|
||||||
|
#ifdef ESPHOME_LOG_HAS_DEBUG
|
||||||
ESP_LOGD(TAG, "OBIS info:");
|
ESP_LOGD(TAG, "OBIS info:");
|
||||||
for (auto const &obis_info : obis_info_vec) {
|
for (auto const &obis_info : obis_info_vec) {
|
||||||
std::string info;
|
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) + "]";
|
info += " [0x" + bytes_repr(obis_info.value) + "]";
|
||||||
ESP_LOGD(TAG, "%s", info.c_str());
|
ESP_LOGD(TAG, "%s", info.c_str());
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sml::publish_obis_info_(const std::vector<ObisInfo> &obis_info_vec) {
|
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) {
|
void Sml::publish_value_(const ObisInfo &obis_info) {
|
||||||
|
const auto obis_code = obis_info.code_repr();
|
||||||
for (auto const &sml_listener : sml_listeners_) {
|
for (auto const &sml_listener : sml_listeners_) {
|
||||||
if ((!sml_listener->server_id.empty()) && (bytes_repr(obis_info.server_id) != sml_listener->server_id))
|
if ((!sml_listener->server_id.empty()) && (bytes_repr(obis_info.server_id) != sml_listener->server_id))
|
||||||
continue;
|
continue;
|
||||||
if (obis_info.code_repr() != sml_listener->obis_code)
|
if (obis_code != sml_listener->obis_code)
|
||||||
continue;
|
continue;
|
||||||
sml_listener->publish_val(obis_info);
|
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);
|
void add_on_data_callback(std::function<void(std::vector<uint8_t>, bool)> &&callback);
|
||||||
|
|
||||||
protected:
|
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 log_obis_info_(const std::vector<ObisInfo> &obis_info_vec);
|
||||||
void publish_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);
|
char check_start_end_bytes_(uint8_t byte);
|
||||||
|
@ -5,17 +5,17 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace sml {
|
namespace sml {
|
||||||
|
|
||||||
SmlFile::SmlFile(bytes buffer) : buffer_(std::move(buffer)) {
|
SmlFile::SmlFile(const BytesView &buffer) : buffer_(buffer) {
|
||||||
// extract messages
|
// extract messages
|
||||||
this->pos_ = 0;
|
this->pos_ = 0;
|
||||||
while (this->pos_ < this->buffer_.size()) {
|
while (this->pos_ < this->buffer_.size()) {
|
||||||
if (this->buffer_[this->pos_] == 0x00)
|
if (this->buffer_[this->pos_] == 0x00)
|
||||||
break; // EndOfSmlMsg
|
break; // EndOfSmlMsg
|
||||||
|
|
||||||
SmlNode message = SmlNode();
|
SmlNode message;
|
||||||
if (!this->setup_node(&message))
|
if (!this->setup_node(&message))
|
||||||
break;
|
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;
|
return false;
|
||||||
|
|
||||||
node->type = type;
|
node->type = type;
|
||||||
node->nodes.clear();
|
|
||||||
node->value_bytes.clear();
|
|
||||||
|
|
||||||
if (type == SML_LIST) {
|
if (type == SML_LIST) {
|
||||||
node->nodes.reserve(length);
|
node->nodes.reserve(length);
|
||||||
for (size_t i = 0; i != length; i++) {
|
for (size_t i = 0; i != length; i++) {
|
||||||
SmlNode child_node = SmlNode();
|
SmlNode child_node;
|
||||||
if (!this->setup_node(&child_node))
|
if (!this->setup_node(&child_node))
|
||||||
return false;
|
return false;
|
||||||
node->nodes.emplace_back(child_node);
|
node->nodes.emplace_back(std::move(child_node));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Value starts at the current position
|
// Value starts at the current position
|
||||||
// Value ends "length" bytes later,
|
// Value ends "length" bytes later,
|
||||||
// (since the TL field is counted but already subtracted from length)
|
// (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
|
// Increment the pointer past all consumed bytes
|
||||||
this->pos_ += length;
|
this->pos_ += length;
|
||||||
}
|
}
|
||||||
@ -87,14 +85,14 @@ bool SmlFile::setup_node(SmlNode *node) {
|
|||||||
std::vector<ObisInfo> SmlFile::get_obis_info() {
|
std::vector<ObisInfo> SmlFile::get_obis_info() {
|
||||||
std::vector<ObisInfo> obis_info;
|
std::vector<ObisInfo> obis_info;
|
||||||
for (auto const &message : messages) {
|
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);
|
uint16_t message_type = bytes_to_uint(message_body.nodes[0].value_bytes);
|
||||||
if (message_type != SML_GET_LIST_RES)
|
if (message_type != SML_GET_LIST_RES)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
SmlNode get_list_response = message_body.nodes[1];
|
const auto &get_list_response = message_body.nodes[1];
|
||||||
bytes server_id = get_list_response.nodes[1].value_bytes;
|
const auto &server_id = get_list_response.nodes[1].value_bytes;
|
||||||
SmlNode val_list = get_list_response.nodes[4];
|
const auto &val_list = get_list_response.nodes[4];
|
||||||
|
|
||||||
for (auto const &val_list_entry : val_list.nodes) {
|
for (auto const &val_list_entry : val_list.nodes) {
|
||||||
obis_info.emplace_back(server_id, val_list_entry);
|
obis_info.emplace_back(server_id, val_list_entry);
|
||||||
@ -103,7 +101,7 @@ std::vector<ObisInfo> SmlFile::get_obis_info() {
|
|||||||
return obis_info;
|
return obis_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string bytes_repr(const bytes &buffer) {
|
std::string bytes_repr(const BytesView &buffer) {
|
||||||
std::string repr;
|
std::string repr;
|
||||||
for (auto const value : buffer) {
|
for (auto const value : buffer) {
|
||||||
repr += str_sprintf("%02x", value & 0xff);
|
repr += str_sprintf("%02x", value & 0xff);
|
||||||
@ -111,7 +109,7 @@ std::string bytes_repr(const bytes &buffer) {
|
|||||||
return repr;
|
return repr;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t bytes_to_uint(const bytes &buffer) {
|
uint64_t bytes_to_uint(const BytesView &buffer) {
|
||||||
uint64_t val = 0;
|
uint64_t val = 0;
|
||||||
for (auto const value : buffer) {
|
for (auto const value : buffer) {
|
||||||
val = (val << 8) + value;
|
val = (val << 8) + value;
|
||||||
@ -119,7 +117,7 @@ uint64_t bytes_to_uint(const bytes &buffer) {
|
|||||||
return val;
|
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);
|
uint64_t tmp = bytes_to_uint(buffer);
|
||||||
int64_t val;
|
int64_t val;
|
||||||
|
|
||||||
@ -135,14 +133,14 @@ int64_t bytes_to_int(const bytes &buffer) {
|
|||||||
return val;
|
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->code = val_list_entry.nodes[0].value_bytes;
|
||||||
this->status = val_list_entry.nodes[1].value_bytes;
|
this->status = val_list_entry.nodes[1].value_bytes;
|
||||||
this->unit = bytes_to_uint(val_list_entry.nodes[3].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);
|
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 = value_node.value_bytes;
|
||||||
this->value_type = value_node.type;
|
this->value_type = value_node.type;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -11,44 +12,73 @@ namespace sml {
|
|||||||
|
|
||||||
using bytes = std::vector<uint8_t>;
|
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 {
|
class SmlNode {
|
||||||
public:
|
public:
|
||||||
uint8_t type;
|
uint8_t type;
|
||||||
bytes value_bytes;
|
BytesView value_bytes;
|
||||||
std::vector<SmlNode> nodes;
|
std::vector<SmlNode> nodes;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ObisInfo {
|
class ObisInfo {
|
||||||
public:
|
public:
|
||||||
ObisInfo(bytes server_id, SmlNode val_list_entry);
|
ObisInfo(const BytesView &server_id, const SmlNode &val_list_entry);
|
||||||
bytes server_id;
|
BytesView server_id;
|
||||||
bytes code;
|
BytesView code;
|
||||||
bytes status;
|
BytesView status;
|
||||||
char unit;
|
char unit;
|
||||||
char scaler;
|
char scaler;
|
||||||
bytes value;
|
BytesView value;
|
||||||
uint16_t value_type;
|
uint16_t value_type;
|
||||||
std::string code_repr() const;
|
std::string code_repr() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SmlFile {
|
class SmlFile {
|
||||||
public:
|
public:
|
||||||
SmlFile(bytes buffer);
|
SmlFile(const BytesView &buffer);
|
||||||
bool setup_node(SmlNode *node);
|
bool setup_node(SmlNode *node);
|
||||||
std::vector<SmlNode> messages;
|
std::vector<SmlNode> messages;
|
||||||
std::vector<ObisInfo> get_obis_info();
|
std::vector<ObisInfo> get_obis_info();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
const bytes buffer_;
|
const BytesView buffer_;
|
||||||
size_t pos_;
|
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 sml
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
@ -441,9 +441,10 @@ void AudioPipeline::decode_task(void *params) {
|
|||||||
pdFALSE, // Wait for all the bits,
|
pdFALSE, // Wait for all the bits,
|
||||||
portMAX_DELAY); // Block indefinitely until bit is set
|
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)) {
|
if (!(event_bits & EventGroupBits::PIPELINE_COMMAND_STOP)) {
|
||||||
xEventGroupClearBits(this_pipeline->event_group_,
|
|
||||||
EventGroupBits::DECODER_MESSAGE_FINISHED | EventGroupBits::READER_MESSAGE_LOADED_MEDIA_TYPE);
|
|
||||||
InfoErrorEvent event;
|
InfoErrorEvent event;
|
||||||
event.source = InfoErrorSource::DECODER;
|
event.source = InfoErrorSource::DECODER;
|
||||||
|
|
||||||
|
@ -1,19 +1,59 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import text_sensor
|
from esphome.components import text_sensor
|
||||||
import esphome.config_validation as cv
|
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")
|
uptime_ns = cg.esphome_ns.namespace("uptime")
|
||||||
UptimeTextSensor = uptime_ns.class_(
|
UptimeTextSensor = uptime_ns.class_(
|
||||||
"UptimeTextSensor", text_sensor.TextSensor, cg.PollingComponent
|
"UptimeTextSensor", text_sensor.TextSensor, cg.PollingComponent
|
||||||
)
|
)
|
||||||
CONFIG_SCHEMA = text_sensor.text_sensor_schema(
|
|
||||||
UptimeTextSensor,
|
CONF_SEPARATOR = "separator"
|
||||||
icon=ICON_TIMER,
|
CONF_DAYS = "days"
|
||||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
CONF_EXPAND = "expand"
|
||||||
).extend(cv.polling_component_schema("30s"))
|
|
||||||
|
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):
|
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)
|
await cg.register_component(var, config)
|
||||||
|
@ -16,6 +16,11 @@ void UptimeTextSensor::setup() {
|
|||||||
this->update();
|
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() {
|
void UptimeTextSensor::update() {
|
||||||
auto now = millis();
|
auto now = millis();
|
||||||
// get whole seconds since last update. Note that even if the millis count has overflowed between updates,
|
// 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;
|
unsigned remainder = uptime % 60;
|
||||||
uptime /= 60;
|
uptime /= 60;
|
||||||
if (interval < 30) {
|
if (interval < 30) {
|
||||||
buffer.insert(0, str_sprintf("%us", remainder));
|
this->insert_buffer_(buffer, this->seconds_text_, remainder);
|
||||||
if (uptime == 0)
|
if (!this->expand_ && uptime == 0)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
remainder = uptime % 60;
|
remainder = uptime % 60;
|
||||||
uptime /= 60;
|
uptime /= 60;
|
||||||
if (interval < 1800) {
|
if (interval < 1800) {
|
||||||
buffer.insert(0, str_sprintf("%um", remainder));
|
this->insert_buffer_(buffer, this->minutes_text_, remainder);
|
||||||
if (uptime == 0)
|
if (!this->expand_ && uptime == 0)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
remainder = uptime % 24;
|
remainder = uptime % 24;
|
||||||
uptime /= 24;
|
uptime /= 24;
|
||||||
if (interval < 12 * 3600) {
|
if (interval < 12 * 3600) {
|
||||||
buffer.insert(0, str_sprintf("%uh", remainder));
|
this->insert_buffer_(buffer, this->hours_text_, remainder);
|
||||||
if (uptime == 0)
|
if (!this->expand_ && uptime == 0)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
buffer.insert(0, str_sprintf("%ud", (unsigned) uptime));
|
this->insert_buffer_(buffer, this->days_text_, (unsigned) uptime);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this->publish_state(buffer);
|
this->publish_state(buffer);
|
||||||
|
@ -10,13 +10,32 @@ namespace uptime {
|
|||||||
|
|
||||||
class UptimeTextSensor : public text_sensor::TextSensor, public PollingComponent {
|
class UptimeTextSensor : public text_sensor::TextSensor, public PollingComponent {
|
||||||
public:
|
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 update() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void setup() override;
|
void setup() override;
|
||||||
|
|
||||||
float get_setup_priority() const 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:
|
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 uptime_{0}; // uptime in seconds, will overflow after 136 years
|
||||||
uint32_t last_ms_{0};
|
uint32_t last_ms_{0};
|
||||||
};
|
};
|
||||||
|
@ -70,6 +70,9 @@ WaveshareEPaper4P2InBV2 = waveshare_epaper_ns.class_(
|
|||||||
WaveshareEPaper4P2InBV2BWR = waveshare_epaper_ns.class_(
|
WaveshareEPaper4P2InBV2BWR = waveshare_epaper_ns.class_(
|
||||||
"WaveshareEPaper4P2InBV2BWR", WaveshareEPaperBWR
|
"WaveshareEPaper4P2InBV2BWR", WaveshareEPaperBWR
|
||||||
)
|
)
|
||||||
|
WaveshareEPaper5P65InF = waveshare_epaper_ns.class_(
|
||||||
|
"WaveshareEPaper5P65InF", WaveshareEPaper7C
|
||||||
|
)
|
||||||
WaveshareEPaper5P8In = waveshare_epaper_ns.class_(
|
WaveshareEPaper5P8In = waveshare_epaper_ns.class_(
|
||||||
"WaveshareEPaper5P8In", WaveshareEPaper
|
"WaveshareEPaper5P8In", WaveshareEPaper
|
||||||
)
|
)
|
||||||
@ -150,6 +153,7 @@ MODELS = {
|
|||||||
"4.20in": ("b", WaveshareEPaper4P2In),
|
"4.20in": ("b", WaveshareEPaper4P2In),
|
||||||
"4.20in-bv2": ("b", WaveshareEPaper4P2InBV2),
|
"4.20in-bv2": ("b", WaveshareEPaper4P2InBV2),
|
||||||
"4.20in-bv2-bwr": ("b", WaveshareEPaper4P2InBV2BWR),
|
"4.20in-bv2-bwr": ("b", WaveshareEPaper4P2InBV2BWR),
|
||||||
|
"5.65in-f": ("b", WaveshareEPaper5P65InF),
|
||||||
"5.83in": ("b", WaveshareEPaper5P8In),
|
"5.83in": ("b", WaveshareEPaper5P8In),
|
||||||
"5.83inv2": ("b", WaveshareEPaper5P8InV2),
|
"5.83inv2": ("b", WaveshareEPaper5P8InV2),
|
||||||
"7.30in-f": ("b", WaveshareEPaper7P3InF),
|
"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) {
|
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)
|
if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0)
|
||||||
return;
|
return;
|
||||||
@ -3307,6 +3348,175 @@ void WaveshareEPaper7P5In::dump_config() {
|
|||||||
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||||
LOG_UPDATE_INTERVAL(this);
|
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() {
|
void WaveshareEPaper7P3InF::initialize() {
|
||||||
if (this->buffers_[0] == nullptr) {
|
if (this->buffers_[0] == nullptr) {
|
||||||
ESP_LOGE(TAG, "Buffer unavailable!");
|
ESP_LOGE(TAG, "Buffer unavailable!");
|
||||||
@ -3411,11 +3621,6 @@ void WaveshareEPaper7P3InF::initialize() {
|
|||||||
ESP_LOGI(TAG, "Display initialized successfully");
|
ESP_LOGI(TAG, "Display initialized successfully");
|
||||||
}
|
}
|
||||||
void HOT WaveshareEPaper7P3InF::display() {
|
void HOT WaveshareEPaper7P3InF::display() {
|
||||||
if (this->buffers_[0] == nullptr) {
|
|
||||||
ESP_LOGE(TAG, "Buffer unavailable!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// INITIALIZATION
|
// INITIALIZATION
|
||||||
ESP_LOGI(TAG, "Initialise the display");
|
ESP_LOGI(TAG, "Initialise the display");
|
||||||
this->initialize();
|
this->initialize();
|
||||||
@ -3423,29 +3628,7 @@ void HOT WaveshareEPaper7P3InF::display() {
|
|||||||
// COMMAND DATA START TRANSMISSION
|
// COMMAND DATA START TRANSMISSION
|
||||||
ESP_LOGI(TAG, "Sending data to the display");
|
ESP_LOGI(TAG, "Sending data to the display");
|
||||||
this->command(0x10);
|
this->command(0x10);
|
||||||
uint32_t small_buffer_length = this->get_buffer_length_() / NUM_BUFFERS;
|
this->send_buffers_();
|
||||||
uint8_t byte_to_send;
|
|
||||||
for (auto &buffer : this->buffers_) {
|
|
||||||
for (uint32_t buffer_pos = 0; buffer_pos < small_buffer_length; buffer_pos += 3) {
|
|
||||||
std::bitset<24> triplet =
|
|
||||||
buffer[buffer_pos + 0] << 16 | buffer[buffer_pos + 1] << 8 | buffer[buffer_pos + 2] << 0;
|
|
||||||
// 8 bitset<3> are stored in 3 bytes
|
|
||||||
// |aaabbbaa|abbbaaab|bbaaabbb|
|
|
||||||
// | byte 1 | byte 2 | byte 3 |
|
|
||||||
byte_to_send = ((triplet >> 17).to_ulong() & 0b01110000) | ((triplet >> 18).to_ulong() & 0b00000111);
|
|
||||||
this->data(byte_to_send);
|
|
||||||
|
|
||||||
byte_to_send = ((triplet >> 11).to_ulong() & 0b01110000) | ((triplet >> 12).to_ulong() & 0b00000111);
|
|
||||||
this->data(byte_to_send);
|
|
||||||
|
|
||||||
byte_to_send = ((triplet >> 5).to_ulong() & 0b01110000) | ((triplet >> 6).to_ulong() & 0b00000111);
|
|
||||||
this->data(byte_to_send);
|
|
||||||
|
|
||||||
byte_to_send = ((triplet << 1).to_ulong() & 0b01110000) | ((triplet << 0).to_ulong() & 0b00000111);
|
|
||||||
this->data(byte_to_send);
|
|
||||||
}
|
|
||||||
App.feed_wdt();
|
|
||||||
}
|
|
||||||
|
|
||||||
// COMMAND POWER ON
|
// COMMAND POWER ON
|
||||||
ESP_LOGI(TAG, "Power on the display");
|
ESP_LOGI(TAG, "Power on the display");
|
||||||
@ -3464,9 +3647,11 @@ void HOT WaveshareEPaper7P3InF::display() {
|
|||||||
this->data(0x00);
|
this->data(0x00);
|
||||||
this->wait_until_idle_();
|
this->wait_until_idle_();
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Set the display to deep sleep");
|
if (this->deep_sleep_between_updates_) {
|
||||||
this->command(0x07);
|
ESP_LOGI(TAG, "Set the display to deep sleep");
|
||||||
this->data(0xA5);
|
this->command(0x07);
|
||||||
|
this->data(0xA5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int WaveshareEPaper7P3InF::get_width_internal() { return 800; }
|
int WaveshareEPaper7P3InF::get_width_internal() { return 800; }
|
||||||
int WaveshareEPaper7P3InF::get_height_internal() { return 480; }
|
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;
|
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||||
uint32_t get_buffer_length_() override;
|
uint32_t get_buffer_length_() override;
|
||||||
void setup() override;
|
void setup() override;
|
||||||
|
|
||||||
void init_internal_7c_(uint32_t buffer_length);
|
void init_internal_7c_(uint32_t buffer_length);
|
||||||
|
void send_buffers_();
|
||||||
|
void reset_();
|
||||||
|
|
||||||
static const int NUM_BUFFERS = 10;
|
static const int NUM_BUFFERS = 10;
|
||||||
uint8_t *buffers_[NUM_BUFFERS];
|
uint8_t *buffers_[NUM_BUFFERS];
|
||||||
@ -683,6 +686,29 @@ class WaveshareEPaper5P8InV2 : public WaveshareEPaper {
|
|||||||
int get_height_internal() override;
|
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 {
|
class WaveshareEPaper7P3InF : public WaveshareEPaper7C {
|
||||||
public:
|
public:
|
||||||
void initialize() override;
|
void initialize() override;
|
||||||
@ -703,17 +729,6 @@ class WaveshareEPaper7P3InF : public WaveshareEPaper7C {
|
|||||||
bool wait_until_idle_();
|
bool wait_until_idle_();
|
||||||
|
|
||||||
bool deep_sleep_between_updates_{true};
|
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 {
|
class WaveshareEPaper7P5In : public WaveshareEPaper {
|
||||||
|
@ -56,7 +56,6 @@ from esphome.const import (
|
|||||||
KEY_CORE,
|
KEY_CORE,
|
||||||
KEY_FRAMEWORK_VERSION,
|
KEY_FRAMEWORK_VERSION,
|
||||||
KEY_TARGET_FRAMEWORK,
|
KEY_TARGET_FRAMEWORK,
|
||||||
KEY_TARGET_PLATFORM,
|
|
||||||
PLATFORM_ESP32,
|
PLATFORM_ESP32,
|
||||||
PLATFORM_ESP8266,
|
PLATFORM_ESP8266,
|
||||||
PLATFORM_RP2040,
|
PLATFORM_RP2040,
|
||||||
@ -1499,30 +1498,9 @@ def dimensions(value):
|
|||||||
|
|
||||||
|
|
||||||
def directory(value):
|
def directory(value):
|
||||||
import json
|
|
||||||
|
|
||||||
value = string(value)
|
value = string(value)
|
||||||
path = CORE.relative_config_path(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):
|
if not os.path.exists(path):
|
||||||
raise Invalid(
|
raise Invalid(
|
||||||
f"Could not find directory '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})."
|
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):
|
def file_(value):
|
||||||
import json
|
|
||||||
|
|
||||||
value = string(value)
|
value = string(value)
|
||||||
path = CORE.relative_config_path(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):
|
if not os.path.exists(path):
|
||||||
raise Invalid(
|
raise Invalid(
|
||||||
f"Could not find file '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})."
|
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(
|
def require_framework_version(
|
||||||
*,
|
*,
|
||||||
esp_idf=None,
|
|
||||||
esp32_arduino=None,
|
|
||||||
esp8266_arduino=None,
|
|
||||||
rp2040_arduino=None,
|
|
||||||
bk72xx_libretiny=None,
|
|
||||||
host=None,
|
|
||||||
max_version=False,
|
max_version=False,
|
||||||
extra_message=None,
|
extra_message=None,
|
||||||
|
**kwargs,
|
||||||
):
|
):
|
||||||
def validator(value):
|
def validator(value):
|
||||||
core_data = CORE.data[KEY_CORE]
|
core_data = CORE.data[KEY_CORE]
|
||||||
framework = core_data[KEY_TARGET_FRAMEWORK]
|
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 max_version:
|
||||||
if core_data[KEY_FRAMEWORK_VERSION] > required:
|
if core_data[KEY_FRAMEWORK_VERSION] > required:
|
||||||
|
@ -45,6 +45,8 @@ CONF_ALLOW_OTHER_USES = "allow_other_uses"
|
|||||||
CONF_ALPHA = "alpha"
|
CONF_ALPHA = "alpha"
|
||||||
CONF_ALTITUDE = "altitude"
|
CONF_ALTITUDE = "altitude"
|
||||||
CONF_AMBIENT_LIGHT = "ambient_light"
|
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_AMMONIA = "ammonia"
|
||||||
CONF_ANALOG = "analog"
|
CONF_ANALOG = "analog"
|
||||||
CONF_AND = "and"
|
CONF_AND = "and"
|
||||||
@ -63,6 +65,7 @@ CONF_AUTH = "auth"
|
|||||||
CONF_AUTO_CLEAR_ENABLED = "auto_clear_enabled"
|
CONF_AUTO_CLEAR_ENABLED = "auto_clear_enabled"
|
||||||
CONF_AUTO_MODE = "auto_mode"
|
CONF_AUTO_MODE = "auto_mode"
|
||||||
CONF_AUTOCONF = "autoconf"
|
CONF_AUTOCONF = "autoconf"
|
||||||
|
CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration"
|
||||||
CONF_AUTOMATION_ID = "automation_id"
|
CONF_AUTOMATION_ID = "automation_id"
|
||||||
CONF_AVAILABILITY = "availability"
|
CONF_AVAILABILITY = "availability"
|
||||||
CONF_AWAY = "away"
|
CONF_AWAY = "away"
|
||||||
@ -477,6 +480,7 @@ CONF_MAX_VALUE = "max_value"
|
|||||||
CONF_MAX_VOLTAGE = "max_voltage"
|
CONF_MAX_VOLTAGE = "max_voltage"
|
||||||
CONF_MDNS = "mdns"
|
CONF_MDNS = "mdns"
|
||||||
CONF_MEASUREMENT_DURATION = "measurement_duration"
|
CONF_MEASUREMENT_DURATION = "measurement_duration"
|
||||||
|
CONF_MEASUREMENT_MODE = "measurement_mode"
|
||||||
CONF_MEASUREMENT_SEQUENCE_NUMBER = "measurement_sequence_number"
|
CONF_MEASUREMENT_SEQUENCE_NUMBER = "measurement_sequence_number"
|
||||||
CONF_MEDIA_PLAYER = "media_player"
|
CONF_MEDIA_PLAYER = "media_player"
|
||||||
CONF_MEDIUM = "medium"
|
CONF_MEDIUM = "medium"
|
||||||
|
@ -475,7 +475,6 @@ class EsphomeCore:
|
|||||||
self.dashboard = False
|
self.dashboard = False
|
||||||
# True if command is run from vscode api
|
# True if command is run from vscode api
|
||||||
self.vscode = False
|
self.vscode = False
|
||||||
self.ace = False
|
|
||||||
# The name of the node
|
# The name of the node
|
||||||
self.name: Optional[str] = None
|
self.name: Optional[str] = None
|
||||||
# The friendly name of the node
|
# The friendly name of the node
|
||||||
@ -519,6 +518,8 @@ class EsphomeCore:
|
|||||||
self.verbose = False
|
self.verbose = False
|
||||||
# Whether ESPHome was started in quiet mode
|
# Whether ESPHome was started in quiet mode
|
||||||
self.quiet = False
|
self.quiet = False
|
||||||
|
# A list of all known ID classes
|
||||||
|
self.id_classes = {}
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
from esphome.pins import PIN_SCHEMA_REGISTRY
|
from esphome.pins import PIN_SCHEMA_REGISTRY
|
||||||
|
@ -789,13 +789,17 @@ class MockObj(Expression):
|
|||||||
|
|
||||||
def class_(self, name: str, *parents: "MockObjClass") -> "MockObjClass":
|
def class_(self, name: str, *parents: "MockObjClass") -> "MockObjClass":
|
||||||
op = "" if self.op == "" else "::"
|
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":
|
def struct(self, name: str) -> "MockObjClass":
|
||||||
return self.class_(name)
|
return self.class_(name)
|
||||||
|
|
||||||
def enum(self, name: str, is_class: bool = False) -> "MockObj":
|
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":
|
def operator(self, name: str) -> "MockObj":
|
||||||
"""Various other operations.
|
"""Various other operations.
|
||||||
|
@ -38,7 +38,7 @@ import yaml
|
|||||||
from yaml.nodes import Node
|
from yaml.nodes import Node
|
||||||
|
|
||||||
from esphome import const, platformio_api, yaml_util
|
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 (
|
from esphome.storage_json import (
|
||||||
StorageJSON,
|
StorageJSON,
|
||||||
archive_storage_path,
|
archive_storage_path,
|
||||||
@ -336,7 +336,7 @@ class EsphomePortCommandWebSocket(EsphomeCommandWebSocket):
|
|||||||
# Use the IP address if available but only
|
# Use the IP address if available but only
|
||||||
# if the API is loaded and the device is online
|
# if the API is loaded and the device is online
|
||||||
# since MQTT logging will not work otherwise
|
# since MQTT logging will not work otherwise
|
||||||
port = address_list[0]
|
port = sort_ip_addresses(address_list)[0]
|
||||||
elif (
|
elif (
|
||||||
entry.address
|
entry.address
|
||||||
and (
|
and (
|
||||||
@ -347,7 +347,7 @@ class EsphomePortCommandWebSocket(EsphomeCommandWebSocket):
|
|||||||
and not isinstance(address_list, Exception)
|
and not isinstance(address_list, Exception)
|
||||||
):
|
):
|
||||||
# If mdns is not available, try to use the DNS cache
|
# If mdns is not available, try to use the DNS cache
|
||||||
port = address_list[0]
|
port = sort_ip_addresses(address_list)[0]
|
||||||
|
|
||||||
return [
|
return [
|
||||||
*DASHBOARD_COMMAND,
|
*DASHBOARD_COMMAND,
|
||||||
|
@ -200,6 +200,45 @@ def resolve_ip_address(host, port):
|
|||||||
return res
|
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):
|
def get_bool_env(var, default=False):
|
||||||
value = os.getenv(var, default)
|
value = os.getenv(var, default)
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
|
@ -91,6 +91,10 @@ class ComponentManifest:
|
|||||||
def codeowners(self) -> list[str]:
|
def codeowners(self) -> list[str]:
|
||||||
return getattr(self.module, "CODEOWNERS", [])
|
return getattr(self.module, "CODEOWNERS", [])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def instance_type(self) -> list[str]:
|
||||||
|
return getattr(self.module, "INSTANCE_TYPE", None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def final_validate_schema(self) -> Optional[Callable[[ConfigType], None]]:
|
def final_validate_schema(self) -> Optional[Callable[[ConfigType], None]]:
|
||||||
"""Components can declare a `FINAL_VALIDATE_SCHEMA` cv.Schema that gets called
|
"""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):
|
def read_config(args):
|
||||||
while True:
|
while True:
|
||||||
CORE.reset()
|
CORE.reset()
|
||||||
data = json.loads(input())
|
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.vscode = True
|
||||||
CORE.ace = args.ace
|
if args.ace: # Running from ESPHome Compiler dashboard, not vscode
|
||||||
f = data["file"]
|
CORE.config_path = os.path.join(args.configuration, data["file"])
|
||||||
if CORE.ace:
|
loader = _ace_loader
|
||||||
CORE.config_path = os.path.join(args.configuration, f)
|
|
||||||
else:
|
else:
|
||||||
CORE.config_path = data["file"]
|
CORE.config_path = data["file"]
|
||||||
|
loader = _vscode_loader
|
||||||
|
|
||||||
file_name = CORE.config_path
|
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] = (
|
command_line_substitutions: dict[str, Any] = (
|
||||||
dict(args.substitution) if args.substitution else {}
|
dict(args.substitution) if args.substitution else {}
|
||||||
)
|
)
|
||||||
vs = VSCodeResult()
|
vs = VSCodeResult()
|
||||||
try:
|
try:
|
||||||
config = parse_yaml(file_name, StringIO(raw_yaml))
|
config = loader(file_name)
|
||||||
res = validate_config(config, command_line_substitutions)
|
res = validate_config(config, command_line_substitutions)
|
||||||
except Exception as err: # pylint: disable=broad-except
|
except Exception as err: # pylint: disable=broad-except
|
||||||
vs.add_yaml_error(str(err))
|
vs.add_yaml_error(str(err))
|
||||||
|
@ -3,12 +3,12 @@ from __future__ import annotations
|
|||||||
import fnmatch
|
import fnmatch
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
from io import TextIOWrapper
|
from io import BytesIO, TextIOBase, TextIOWrapper
|
||||||
from ipaddress import _BaseAddress
|
from ipaddress import _BaseAddress
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
from typing import Any
|
from typing import Any, Callable
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
@ -69,7 +69,10 @@ class ESPForceValue:
|
|||||||
pass
|
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:
|
try:
|
||||||
value = add_class_to_obj(value, ESPHomeDataBase)
|
value = add_class_to_obj(value, ESPHomeDataBase)
|
||||||
if from_database is not None:
|
if from_database is not None:
|
||||||
@ -102,6 +105,11 @@ def _add_data_ref(fn):
|
|||||||
class ESPHomeLoaderMixin:
|
class ESPHomeLoaderMixin:
|
||||||
"""Loader class that keeps track of line numbers."""
|
"""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
|
@_add_data_ref
|
||||||
def construct_yaml_int(self, node):
|
def construct_yaml_int(self, node):
|
||||||
return super().construct_yaml_int(node)
|
return super().construct_yaml_int(node)
|
||||||
@ -127,7 +135,7 @@ class ESPHomeLoaderMixin:
|
|||||||
return super().construct_yaml_seq(node)
|
return super().construct_yaml_seq(node)
|
||||||
|
|
||||||
@_add_data_ref
|
@_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."""
|
"""Traverses the given mapping node and returns a list of constructed key-value pairs."""
|
||||||
assert isinstance(node, yaml.MappingNode)
|
assert isinstance(node, yaml.MappingNode)
|
||||||
# A list of key-value pairs we find in the current mapping
|
# A list of key-value pairs we find in the current mapping
|
||||||
@ -231,7 +239,7 @@ class ESPHomeLoaderMixin:
|
|||||||
return OrderedDict(pairs)
|
return OrderedDict(pairs)
|
||||||
|
|
||||||
@_add_data_ref
|
@_add_data_ref
|
||||||
def construct_env_var(self, node):
|
def construct_env_var(self, node: yaml.Node) -> str:
|
||||||
args = node.value.split()
|
args = node.value.split()
|
||||||
# Check for a default value
|
# Check for a default value
|
||||||
if len(args) > 1:
|
if len(args) > 1:
|
||||||
@ -243,23 +251,23 @@ class ESPHomeLoaderMixin:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _directory(self):
|
def _directory(self) -> str:
|
||||||
return os.path.dirname(self.name)
|
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)
|
return os.path.join(self._directory, *args)
|
||||||
|
|
||||||
@_add_data_ref
|
@_add_data_ref
|
||||||
def construct_secret(self, node):
|
def construct_secret(self, node: yaml.Node) -> str:
|
||||||
try:
|
try:
|
||||||
secrets = _load_yaml_internal(self._rel_path(SECRET_YAML))
|
secrets = self.yaml_loader(self._rel_path(SECRET_YAML))
|
||||||
except EsphomeError as e:
|
except EsphomeError as e:
|
||||||
if self.name == CORE.config_path:
|
if self.name == CORE.config_path:
|
||||||
raise e
|
raise e
|
||||||
try:
|
try:
|
||||||
main_config_dir = os.path.dirname(CORE.config_path)
|
main_config_dir = os.path.dirname(CORE.config_path)
|
||||||
main_secret_yml = os.path.join(main_config_dir, SECRET_YAML)
|
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:
|
except EsphomeError as er:
|
||||||
raise EsphomeError(f"{e}\n{er}") from er
|
raise EsphomeError(f"{e}\n{er}") from er
|
||||||
|
|
||||||
@ -272,7 +280,9 @@ class ESPHomeLoaderMixin:
|
|||||||
return val
|
return val
|
||||||
|
|
||||||
@_add_data_ref
|
@_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
|
from esphome.const import CONF_VARS
|
||||||
|
|
||||||
def extract_file_vars(node):
|
def extract_file_vars(node):
|
||||||
@ -290,71 +300,93 @@ class ESPHomeLoaderMixin:
|
|||||||
else:
|
else:
|
||||||
file, vars = node.value, None
|
file, vars = node.value, None
|
||||||
|
|
||||||
result = _load_yaml_internal(self._rel_path(file))
|
result = self.yaml_loader(self._rel_path(file))
|
||||||
if not vars:
|
if not vars:
|
||||||
vars = {}
|
vars = {}
|
||||||
result = substitute_vars(result, vars)
|
result = substitute_vars(result, vars)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@_add_data_ref
|
@_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"))
|
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
|
@_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"))
|
files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml"))
|
||||||
merged_list = []
|
merged_list = []
|
||||||
for fname in files:
|
for fname in files:
|
||||||
loaded_yaml = _load_yaml_internal(fname)
|
loaded_yaml = self.yaml_loader(fname)
|
||||||
if isinstance(loaded_yaml, list):
|
if isinstance(loaded_yaml, list):
|
||||||
merged_list.extend(loaded_yaml)
|
merged_list.extend(loaded_yaml)
|
||||||
return merged_list
|
return merged_list
|
||||||
|
|
||||||
@_add_data_ref
|
@_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"))
|
files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml"))
|
||||||
mapping = OrderedDict()
|
mapping = OrderedDict()
|
||||||
for fname in files:
|
for fname in files:
|
||||||
filename = os.path.splitext(os.path.basename(fname))[0]
|
filename = os.path.splitext(os.path.basename(fname))[0]
|
||||||
mapping[filename] = _load_yaml_internal(fname)
|
mapping[filename] = self.yaml_loader(fname)
|
||||||
return mapping
|
return mapping
|
||||||
|
|
||||||
@_add_data_ref
|
@_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"))
|
files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml"))
|
||||||
mapping = OrderedDict()
|
mapping = OrderedDict()
|
||||||
for fname in files:
|
for fname in files:
|
||||||
loaded_yaml = _load_yaml_internal(fname)
|
loaded_yaml = self.yaml_loader(fname)
|
||||||
if isinstance(loaded_yaml, dict):
|
if isinstance(loaded_yaml, dict):
|
||||||
mapping.update(loaded_yaml)
|
mapping.update(loaded_yaml)
|
||||||
return mapping
|
return mapping
|
||||||
|
|
||||||
@_add_data_ref
|
@_add_data_ref
|
||||||
def construct_lambda(self, node):
|
def construct_lambda(self, node: yaml.Node) -> Lambda:
|
||||||
return Lambda(str(node.value))
|
return Lambda(str(node.value))
|
||||||
|
|
||||||
@_add_data_ref
|
@_add_data_ref
|
||||||
def construct_force(self, node):
|
def construct_force(self, node: yaml.Node) -> ESPForceValue:
|
||||||
obj = self.construct_scalar(node)
|
obj = self.construct_scalar(node)
|
||||||
return add_class_to_obj(obj, ESPForceValue)
|
return add_class_to_obj(obj, ESPForceValue)
|
||||||
|
|
||||||
@_add_data_ref
|
@_add_data_ref
|
||||||
def construct_extend(self, node):
|
def construct_extend(self, node: yaml.Node) -> Extend:
|
||||||
return Extend(str(node.value))
|
return Extend(str(node.value))
|
||||||
|
|
||||||
@_add_data_ref
|
@_add_data_ref
|
||||||
def construct_remove(self, node):
|
def construct_remove(self, node: yaml.Node) -> Remove:
|
||||||
return Remove(str(node.value))
|
return Remove(str(node.value))
|
||||||
|
|
||||||
|
|
||||||
class ESPHomeLoader(ESPHomeLoaderMixin, FastestAvailableSafeLoader):
|
class ESPHomeLoader(ESPHomeLoaderMixin, FastestAvailableSafeLoader):
|
||||||
"""Loader class that keeps track of line numbers."""
|
"""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):
|
class ESPHomePurePythonLoader(ESPHomeLoaderMixin, PurePythonLoader):
|
||||||
"""Loader class that keeps track of line numbers."""
|
"""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):
|
for _loader in (ESPHomeLoader, ESPHomePurePythonLoader):
|
||||||
_loader.add_constructor("tag:yaml.org,2002:int", _loader.construct_yaml_int)
|
_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)
|
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."""
|
"""Parse a YAML file."""
|
||||||
try:
|
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:
|
except EsphomeError:
|
||||||
# Loading failed, so we now load with the Python loader which has more
|
# Loading failed, so we now load with the Python loader which has more
|
||||||
# readable exceptions
|
# readable exceptions
|
||||||
# Rewind the stream so we can try again
|
# Rewind the stream so we can try again
|
||||||
file_handle.seek(0, 0)
|
file_handle.seek(0, 0)
|
||||||
return _load_yaml_internal_with_type(
|
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
|
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(
|
def _load_yaml_internal_with_type(
|
||||||
loader_type: type[ESPHomeLoader] | type[ESPHomePurePythonLoader],
|
loader_type: type[ESPHomeLoader] | type[ESPHomePurePythonLoader],
|
||||||
fname: str,
|
fname: str,
|
||||||
content: TextIOWrapper,
|
content: TextIOWrapper,
|
||||||
|
yaml_loader: Any,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""Load a YAML file."""
|
"""Load a YAML file."""
|
||||||
loader = loader_type(content)
|
loader = loader_type(content, fname, yaml_loader)
|
||||||
loader.name = fname
|
|
||||||
try:
|
try:
|
||||||
return loader.get_single_data() or OrderedDict()
|
return loader.get_single_data() or OrderedDict()
|
||||||
except yaml.YAMLError as exc:
|
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."""
|
"""Decide if a file is valid."""
|
||||||
return not name.startswith(".")
|
return not name.startswith(".")
|
||||||
|
|
||||||
|
@ -12,9 +12,9 @@ pyserial==3.5
|
|||||||
platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
|
platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
|
||||||
esptool==4.8.1
|
esptool==4.8.1
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
esphome-dashboard==20250212.0
|
esphome-dashboard==20250415.0
|
||||||
aioesphomeapi==29.9.0
|
aioesphomeapi==30.0.1
|
||||||
zeroconf==0.146.3
|
zeroconf==0.146.5
|
||||||
puremagic==1.28
|
puremagic==1.28
|
||||||
ruamel.yaml==0.18.10 # dashboard_import
|
ruamel.yaml==0.18.10 # dashboard_import
|
||||||
esphome-glyphsets==0.2.0
|
esphome-glyphsets==0.2.0
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
pylint==3.3.6
|
pylint==3.3.6
|
||||||
flake8==7.2.0 # also change in .pre-commit-config.yaml when updating
|
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
|
pyupgrade==3.19.1 # also change in .pre-commit-config.yaml when updating
|
||||||
pre-commit
|
pre-commit
|
||||||
|
|
||||||
|
@ -1,4 +1,20 @@
|
|||||||
#!/usr/bin/env python3
|
#!/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.
|
"""Python 3 script to automatically generate C++ classes for ESPHome's native API.
|
||||||
|
|
||||||
It's pretty crappy spaghetti code, but it works.
|
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
|
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.
|
FILE_HEADER = """// This file was automatically generated with a tool.
|
||||||
// See scripts/api_protobuf/api_protobuf.py
|
// 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 = []
|
lines = []
|
||||||
for line in text.splitlines():
|
for line in text.splitlines():
|
||||||
if line == "":
|
if line == "":
|
||||||
@ -48,54 +53,62 @@ def indent_list(text, padding=" "):
|
|||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def indent(text, padding=" "):
|
def indent(text: str, padding: str = " ") -> str:
|
||||||
return "\n".join(indent_list(text, padding))
|
return "\n".join(indent_list(text, padding))
|
||||||
|
|
||||||
|
|
||||||
def camel_to_snake(name):
|
def camel_to_snake(name: str) -> str:
|
||||||
# https://stackoverflow.com/a/1176023
|
# https://stackoverflow.com/a/1176023
|
||||||
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
|
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
|
||||||
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
|
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
|
||||||
|
|
||||||
|
|
||||||
class TypeInfo(ABC):
|
class TypeInfo(ABC):
|
||||||
def __init__(self, field):
|
"""Base class for all type information."""
|
||||||
|
|
||||||
|
def __init__(self, field: descriptor.FieldDescriptorProto) -> None:
|
||||||
self._field = field
|
self._field = field
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def default_value(self):
|
def default_value(self) -> str:
|
||||||
|
"""Get the default value."""
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self) -> str:
|
||||||
|
"""Get the name of the field."""
|
||||||
return self._field.name
|
return self._field.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def arg_name(self):
|
def arg_name(self) -> str:
|
||||||
|
"""Get the argument name."""
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def field_name(self):
|
def field_name(self) -> str:
|
||||||
|
"""Get the field name."""
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def number(self):
|
def number(self) -> int:
|
||||||
|
"""Get the field number."""
|
||||||
return self._field.number
|
return self._field.number
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def repeated(self):
|
def repeated(self) -> bool:
|
||||||
|
"""Check if the field is repeated."""
|
||||||
return self._field.label == 3
|
return self._field.label == 3
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cpp_type(self):
|
def cpp_type(self) -> str:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def reference_type(self):
|
def reference_type(self) -> str:
|
||||||
return f"{self.cpp_type} "
|
return f"{self.cpp_type} "
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def const_reference_type(self):
|
def const_reference_type(self) -> str:
|
||||||
return f"{self.cpp_type} "
|
return f"{self.cpp_type} "
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -171,28 +184,31 @@ class TypeInfo(ABC):
|
|||||||
decode_64bit = None
|
decode_64bit = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def encode_content(self):
|
def encode_content(self) -> str:
|
||||||
return f"buffer.{self.encode_func}({self.number}, this->{self.field_name});"
|
return f"buffer.{self.encode_func}({self.number}, this->{self.field_name});"
|
||||||
|
|
||||||
encode_func = None
|
encode_func = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dump_content(self):
|
def dump_content(self) -> str:
|
||||||
o = f'out.append(" {self.name}: ");\n'
|
o = f'out.append(" {self.name}: ");\n'
|
||||||
o += self.dump(f"this->{self.field_name}") + "\n"
|
o += self.dump(f"this->{self.field_name}") + "\n"
|
||||||
o += 'out.append("\\n");\n'
|
o += 'out.append("\\n");\n'
|
||||||
return o
|
return o
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def dump(self, name: str):
|
def dump(self, name: str) -> str:
|
||||||
pass
|
"""Dump the value to the output."""
|
||||||
|
|
||||||
|
|
||||||
TYPE_INFO = {}
|
TYPE_INFO: dict[int, TypeInfo] = {}
|
||||||
|
|
||||||
|
|
||||||
def register_type(name):
|
def register_type(name: int):
|
||||||
def func(value):
|
"""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
|
TYPE_INFO[name] = value
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@ -206,7 +222,7 @@ class DoubleType(TypeInfo):
|
|||||||
decode_64bit = "value.as_double()"
|
decode_64bit = "value.as_double()"
|
||||||
encode_func = "encode_double"
|
encode_func = "encode_double"
|
||||||
|
|
||||||
def dump(self, name):
|
def dump(self, name: str) -> str:
|
||||||
o = f'sprintf(buffer, "%g", {name});\n'
|
o = f'sprintf(buffer, "%g", {name});\n'
|
||||||
o += "out.append(buffer);"
|
o += "out.append(buffer);"
|
||||||
return o
|
return o
|
||||||
@ -219,7 +235,7 @@ class FloatType(TypeInfo):
|
|||||||
decode_32bit = "value.as_float()"
|
decode_32bit = "value.as_float()"
|
||||||
encode_func = "encode_float"
|
encode_func = "encode_float"
|
||||||
|
|
||||||
def dump(self, name):
|
def dump(self, name: str) -> str:
|
||||||
o = f'sprintf(buffer, "%g", {name});\n'
|
o = f'sprintf(buffer, "%g", {name});\n'
|
||||||
o += "out.append(buffer);"
|
o += "out.append(buffer);"
|
||||||
return o
|
return o
|
||||||
@ -232,7 +248,7 @@ class Int64Type(TypeInfo):
|
|||||||
decode_varint = "value.as_int64()"
|
decode_varint = "value.as_int64()"
|
||||||
encode_func = "encode_int64"
|
encode_func = "encode_int64"
|
||||||
|
|
||||||
def dump(self, name):
|
def dump(self, name: str) -> str:
|
||||||
o = f'sprintf(buffer, "%lld", {name});\n'
|
o = f'sprintf(buffer, "%lld", {name});\n'
|
||||||
o += "out.append(buffer);"
|
o += "out.append(buffer);"
|
||||||
return o
|
return o
|
||||||
@ -245,7 +261,7 @@ class UInt64Type(TypeInfo):
|
|||||||
decode_varint = "value.as_uint64()"
|
decode_varint = "value.as_uint64()"
|
||||||
encode_func = "encode_uint64"
|
encode_func = "encode_uint64"
|
||||||
|
|
||||||
def dump(self, name):
|
def dump(self, name: str) -> str:
|
||||||
o = f'sprintf(buffer, "%llu", {name});\n'
|
o = f'sprintf(buffer, "%llu", {name});\n'
|
||||||
o += "out.append(buffer);"
|
o += "out.append(buffer);"
|
||||||
return o
|
return o
|
||||||
@ -258,7 +274,7 @@ class Int32Type(TypeInfo):
|
|||||||
decode_varint = "value.as_int32()"
|
decode_varint = "value.as_int32()"
|
||||||
encode_func = "encode_int32"
|
encode_func = "encode_int32"
|
||||||
|
|
||||||
def dump(self, name):
|
def dump(self, name: str) -> str:
|
||||||
o = f'sprintf(buffer, "%" PRId32, {name});\n'
|
o = f'sprintf(buffer, "%" PRId32, {name});\n'
|
||||||
o += "out.append(buffer);"
|
o += "out.append(buffer);"
|
||||||
return o
|
return o
|
||||||
@ -271,7 +287,7 @@ class Fixed64Type(TypeInfo):
|
|||||||
decode_64bit = "value.as_fixed64()"
|
decode_64bit = "value.as_fixed64()"
|
||||||
encode_func = "encode_fixed64"
|
encode_func = "encode_fixed64"
|
||||||
|
|
||||||
def dump(self, name):
|
def dump(self, name: str) -> str:
|
||||||
o = f'sprintf(buffer, "%llu", {name});\n'
|
o = f'sprintf(buffer, "%llu", {name});\n'
|
||||||
o += "out.append(buffer);"
|
o += "out.append(buffer);"
|
||||||
return o
|
return o
|
||||||
@ -284,7 +300,7 @@ class Fixed32Type(TypeInfo):
|
|||||||
decode_32bit = "value.as_fixed32()"
|
decode_32bit = "value.as_fixed32()"
|
||||||
encode_func = "encode_fixed32"
|
encode_func = "encode_fixed32"
|
||||||
|
|
||||||
def dump(self, name):
|
def dump(self, name: str) -> str:
|
||||||
o = f'sprintf(buffer, "%" PRIu32, {name});\n'
|
o = f'sprintf(buffer, "%" PRIu32, {name});\n'
|
||||||
o += "out.append(buffer);"
|
o += "out.append(buffer);"
|
||||||
return o
|
return o
|
||||||
@ -297,7 +313,7 @@ class BoolType(TypeInfo):
|
|||||||
decode_varint = "value.as_bool()"
|
decode_varint = "value.as_bool()"
|
||||||
encode_func = "encode_bool"
|
encode_func = "encode_bool"
|
||||||
|
|
||||||
def dump(self, name):
|
def dump(self, name: str) -> str:
|
||||||
o = f"out.append(YESNO({name}));"
|
o = f"out.append(YESNO({name}));"
|
||||||
return o
|
return o
|
||||||
|
|
||||||
@ -319,28 +335,28 @@ class StringType(TypeInfo):
|
|||||||
@register_type(11)
|
@register_type(11)
|
||||||
class MessageType(TypeInfo):
|
class MessageType(TypeInfo):
|
||||||
@property
|
@property
|
||||||
def cpp_type(self):
|
def cpp_type(self) -> str:
|
||||||
return self._field.type_name[1:]
|
return self._field.type_name[1:]
|
||||||
|
|
||||||
default_value = ""
|
default_value = ""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def reference_type(self):
|
def reference_type(self) -> str:
|
||||||
return f"{self.cpp_type} &"
|
return f"{self.cpp_type} &"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def const_reference_type(self):
|
def const_reference_type(self) -> str:
|
||||||
return f"const {self.cpp_type} &"
|
return f"const {self.cpp_type} &"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def encode_func(self):
|
def encode_func(self) -> str:
|
||||||
return f"encode_message<{self.cpp_type}>"
|
return f"encode_message<{self.cpp_type}>"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def decode_length(self):
|
def decode_length(self) -> str:
|
||||||
return f"value.as_message<{self.cpp_type}>()"
|
return f"value.as_message<{self.cpp_type}>()"
|
||||||
|
|
||||||
def dump(self, name):
|
def dump(self, name: str) -> str:
|
||||||
o = f"{name}.dump_to(out);"
|
o = f"{name}.dump_to(out);"
|
||||||
return o
|
return o
|
||||||
|
|
||||||
@ -354,7 +370,7 @@ class BytesType(TypeInfo):
|
|||||||
decode_length = "value.as_string()"
|
decode_length = "value.as_string()"
|
||||||
encode_func = "encode_string"
|
encode_func = "encode_string"
|
||||||
|
|
||||||
def dump(self, name):
|
def dump(self, name: str) -> str:
|
||||||
o = f'out.append("\'").append({name}).append("\'");'
|
o = f'out.append("\'").append({name}).append("\'");'
|
||||||
return o
|
return o
|
||||||
|
|
||||||
@ -366,7 +382,7 @@ class UInt32Type(TypeInfo):
|
|||||||
decode_varint = "value.as_uint32()"
|
decode_varint = "value.as_uint32()"
|
||||||
encode_func = "encode_uint32"
|
encode_func = "encode_uint32"
|
||||||
|
|
||||||
def dump(self, name):
|
def dump(self, name: str) -> str:
|
||||||
o = f'sprintf(buffer, "%" PRIu32, {name});\n'
|
o = f'sprintf(buffer, "%" PRIu32, {name});\n'
|
||||||
o += "out.append(buffer);"
|
o += "out.append(buffer);"
|
||||||
return o
|
return o
|
||||||
@ -375,20 +391,20 @@ class UInt32Type(TypeInfo):
|
|||||||
@register_type(14)
|
@register_type(14)
|
||||||
class EnumType(TypeInfo):
|
class EnumType(TypeInfo):
|
||||||
@property
|
@property
|
||||||
def cpp_type(self):
|
def cpp_type(self) -> str:
|
||||||
return f"enums::{self._field.type_name[1:]}"
|
return f"enums::{self._field.type_name[1:]}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def decode_varint(self):
|
def decode_varint(self) -> str:
|
||||||
return f"value.as_enum<{self.cpp_type}>()"
|
return f"value.as_enum<{self.cpp_type}>()"
|
||||||
|
|
||||||
default_value = ""
|
default_value = ""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def encode_func(self):
|
def encode_func(self) -> str:
|
||||||
return f"encode_enum<{self.cpp_type}>"
|
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}));"
|
o = f"out.append(proto_enum_to_string<{self.cpp_type}>({name}));"
|
||||||
return o
|
return o
|
||||||
|
|
||||||
@ -400,7 +416,7 @@ class SFixed32Type(TypeInfo):
|
|||||||
decode_32bit = "value.as_sfixed32()"
|
decode_32bit = "value.as_sfixed32()"
|
||||||
encode_func = "encode_sfixed32"
|
encode_func = "encode_sfixed32"
|
||||||
|
|
||||||
def dump(self, name):
|
def dump(self, name: str) -> str:
|
||||||
o = f'sprintf(buffer, "%" PRId32, {name});\n'
|
o = f'sprintf(buffer, "%" PRId32, {name});\n'
|
||||||
o += "out.append(buffer);"
|
o += "out.append(buffer);"
|
||||||
return o
|
return o
|
||||||
@ -413,7 +429,7 @@ class SFixed64Type(TypeInfo):
|
|||||||
decode_64bit = "value.as_sfixed64()"
|
decode_64bit = "value.as_sfixed64()"
|
||||||
encode_func = "encode_sfixed64"
|
encode_func = "encode_sfixed64"
|
||||||
|
|
||||||
def dump(self, name):
|
def dump(self, name: str) -> str:
|
||||||
o = f'sprintf(buffer, "%lld", {name});\n'
|
o = f'sprintf(buffer, "%lld", {name});\n'
|
||||||
o += "out.append(buffer);"
|
o += "out.append(buffer);"
|
||||||
return o
|
return o
|
||||||
@ -426,7 +442,7 @@ class SInt32Type(TypeInfo):
|
|||||||
decode_varint = "value.as_sint32()"
|
decode_varint = "value.as_sint32()"
|
||||||
encode_func = "encode_sint32"
|
encode_func = "encode_sint32"
|
||||||
|
|
||||||
def dump(self, name):
|
def dump(self, name: str) -> str:
|
||||||
o = f'sprintf(buffer, "%" PRId32, {name});\n'
|
o = f'sprintf(buffer, "%" PRId32, {name});\n'
|
||||||
o += "out.append(buffer);"
|
o += "out.append(buffer);"
|
||||||
return o
|
return o
|
||||||
@ -439,27 +455,27 @@ class SInt64Type(TypeInfo):
|
|||||||
decode_varint = "value.as_sint64()"
|
decode_varint = "value.as_sint64()"
|
||||||
encode_func = "encode_sint64"
|
encode_func = "encode_sint64"
|
||||||
|
|
||||||
def dump(self, name):
|
def dump(self, name: str) -> str:
|
||||||
o = f'sprintf(buffer, "%lld", {name});\n'
|
o = f'sprintf(buffer, "%lld", {name});\n'
|
||||||
o += "out.append(buffer);"
|
o += "out.append(buffer);"
|
||||||
return o
|
return o
|
||||||
|
|
||||||
|
|
||||||
class RepeatedTypeInfo(TypeInfo):
|
class RepeatedTypeInfo(TypeInfo):
|
||||||
def __init__(self, field):
|
def __init__(self, field: descriptor.FieldDescriptorProto) -> None:
|
||||||
super().__init__(field)
|
super().__init__(field)
|
||||||
self._ti = TYPE_INFO[field.type](field)
|
self._ti: TypeInfo = TYPE_INFO[field.type](field)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cpp_type(self):
|
def cpp_type(self) -> str:
|
||||||
return f"std::vector<{self._ti.cpp_type}>"
|
return f"std::vector<{self._ti.cpp_type}>"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def reference_type(self):
|
def reference_type(self) -> str:
|
||||||
return f"{self.cpp_type} &"
|
return f"{self.cpp_type} &"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def const_reference_type(self):
|
def const_reference_type(self) -> str:
|
||||||
return f"const {self.cpp_type} &"
|
return f"const {self.cpp_type} &"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -515,19 +531,19 @@ class RepeatedTypeInfo(TypeInfo):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _ti_is_bool(self):
|
def _ti_is_bool(self) -> bool:
|
||||||
# std::vector is specialized for bool, reference does not work
|
# std::vector is specialized for bool, reference does not work
|
||||||
return isinstance(self._ti, BoolType)
|
return isinstance(self._ti, BoolType)
|
||||||
|
|
||||||
@property
|
@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"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 += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n"
|
||||||
o += "}"
|
o += "}"
|
||||||
return o
|
return o
|
||||||
|
|
||||||
@property
|
@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"for (const auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n"
|
||||||
o += f' out.append(" {self.name}: ");\n'
|
o += f' out.append(" {self.name}: ");\n'
|
||||||
o += indent(self._ti.dump("it")) + "\n"
|
o += indent(self._ti.dump("it")) + "\n"
|
||||||
@ -539,7 +555,8 @@ class RepeatedTypeInfo(TypeInfo):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def build_enum_type(desc):
|
def build_enum_type(desc) -> tuple[str, str]:
|
||||||
|
"""Builds the enum type."""
|
||||||
name = desc.name
|
name = desc.name
|
||||||
out = f"enum {name} : uint32_t {{\n"
|
out = f"enum {name} : uint32_t {{\n"
|
||||||
for v in desc.value:
|
for v in desc.value:
|
||||||
@ -561,15 +578,15 @@ def build_enum_type(desc):
|
|||||||
return out, cpp
|
return out, cpp
|
||||||
|
|
||||||
|
|
||||||
def build_message_type(desc):
|
def build_message_type(desc: descriptor.DescriptorProto) -> tuple[str, str]:
|
||||||
public_content = []
|
public_content: list[str] = []
|
||||||
protected_content = []
|
protected_content: list[str] = []
|
||||||
decode_varint = []
|
decode_varint: list[str] = []
|
||||||
decode_length = []
|
decode_length: list[str] = []
|
||||||
decode_32bit = []
|
decode_32bit: list[str] = []
|
||||||
decode_64bit = []
|
decode_64bit: list[str] = []
|
||||||
encode = []
|
encode: list[str] = []
|
||||||
dump = []
|
dump: list[str] = []
|
||||||
|
|
||||||
for field in desc.field:
|
for field in desc.field:
|
||||||
if field.label == 3:
|
if field.label == 3:
|
||||||
@ -687,27 +704,35 @@ SOURCE_BOTH = 0
|
|||||||
SOURCE_SERVER = 1
|
SOURCE_SERVER = 1
|
||||||
SOURCE_CLIENT = 2
|
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):
|
if not desc.options.HasExtension(opt):
|
||||||
return default
|
return default
|
||||||
return desc.options.Extensions[opt]
|
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)
|
snake = camel_to_snake(mt.name)
|
||||||
id_ = get_opt(mt, pb.id)
|
id_: int | None = get_opt(mt, pb.id)
|
||||||
if id_ is None:
|
if id_ is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
source = get_opt(mt, pb.source, 0)
|
source: int = get_opt(mt, pb.source, 0)
|
||||||
|
|
||||||
ifdef = get_opt(mt, pb.ifdef)
|
ifdef: str | None = get_opt(mt, pb.ifdef)
|
||||||
log = get_opt(mt, pb.log, True)
|
log: bool = get_opt(mt, pb.log, True)
|
||||||
hout = ""
|
hout = ""
|
||||||
cout = ""
|
cout = ""
|
||||||
|
|
||||||
@ -754,7 +779,8 @@ def build_service_message_type(mt):
|
|||||||
return hout, cout
|
return hout, cout
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
|
"""Main function to generate the C++ classes."""
|
||||||
cwd = Path(__file__).resolve().parent
|
cwd = Path(__file__).resolve().parent
|
||||||
root = cwd.parent.parent / "esphome" / "components" / "api"
|
root = cwd.parent.parent / "esphome" / "components" / "api"
|
||||||
prot_file = root / "api.protoc"
|
prot_file = root / "api.protoc"
|
||||||
@ -959,7 +985,7 @@ def main():
|
|||||||
try:
|
try:
|
||||||
import clang_format
|
import clang_format
|
||||||
|
|
||||||
def exec_clang_format(path):
|
def exec_clang_format(path: Path) -> None:
|
||||||
clang_format_path = os.path.join(
|
clang_format_path = os.path.join(
|
||||||
os.path.dirname(clang_format.__file__), "data", "bin", "clang-format"
|
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
|
script/platformio_install_deps.py platformio.ini --libraries --tools --platforms
|
||||||
|
|
||||||
|
mkdir .temp
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo
|
echo
|
||||||
echo "Virtual environment created. Run 'source $location' to use it."
|
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]"
|
echo "> [$target_component] [$test_name] [$target_platform_with_version]"
|
||||||
set -x
|
set -x
|
||||||
# TODO: Validate escape of Command line substitution value
|
# 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
|
{ set +x; } 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,3 +26,17 @@ binary_sensor:
|
|||||||
threshold: 100
|
threshold: 100
|
||||||
filters:
|
filters:
|
||||||
- invert:
|
- 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
|
widget: slider_id
|
||||||
name: LVGL Slider
|
name: LVGL Slider
|
||||||
update_on_release: true
|
update_on_release: true
|
||||||
|
restore_value: true
|
||||||
- platform: lvgl
|
- platform: lvgl
|
||||||
widget: lv_arc
|
widget: lv_arc
|
||||||
id: lvgl_arc_number
|
id: lvgl_arc_number
|
||||||
|
@ -990,3 +990,13 @@ color:
|
|||||||
green_int: 123
|
green_int: 123
|
||||||
blue_int: 64
|
blue_int: 64
|
||||||
white_int: 255
|
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
|
sensor: encoder
|
||||||
enter_button: pushbutton
|
enter_button: pushbutton
|
||||||
group: general
|
group: general
|
||||||
|
initial_focus: lv_roller
|
||||||
|
|
||||||
<<: !include common.yaml
|
<<: !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:
|
esphome:
|
||||||
name: livingroomdevice
|
name: livingroomdevice
|
||||||
friendly_name: Living Room Device
|
friendly_name: Living Room Device
|
||||||
@ -129,6 +126,14 @@ valve:
|
|||||||
optimistic: true
|
optimistic: true
|
||||||
has_position: true
|
has_position: true
|
||||||
|
|
||||||
|
remote_transmitter:
|
||||||
|
pin: ${pin}
|
||||||
|
carrier_duty_percent: 50%
|
||||||
|
|
||||||
|
climate:
|
||||||
|
- platform: climate_ir_lg
|
||||||
|
name: LG Climate
|
||||||
|
|
||||||
prometheus:
|
prometheus:
|
||||||
include_internal: true
|
include_internal: true
|
||||||
relabel:
|
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